コード例 #1
0
ファイル: CoverArtAdapter.cs プロジェクト: djkurtb/AudioWorks
        internal static ICoverArt FromBase64(ReadOnlySpan <byte> value)
        {
            // Use heap allocations for cover art > 256kB
            var byteCount    = Base64.GetMaxDecodedFromUtf8Length(value.Length);
            var decodedValue = byteCount < 0x40000 ? stackalloc byte[byteCount] : new byte[byteCount];

            Base64.DecodeFromUtf8(value, decodedValue, out _, out _);

            // If the image isn't a "Front Cover" or "Other", return null
            var imageType = BinaryPrimitives.ReadUInt32BigEndian(decodedValue);

            if (imageType != 3 && imageType != 0)
            {
                return(null);
            }

            var offset = 4;

            // Seek past the mime type and description
            offset += (int)BinaryPrimitives.ReadUInt32BigEndian(decodedValue.Slice(offset)) + 4;
            offset += (int)BinaryPrimitives.ReadUInt32BigEndian(decodedValue.Slice(offset)) + 4;

            // Seek past the width, height, color depth and type
            offset += 16;

            return(CoverArtFactory.GetOrCreate(
                       decodedValue.Slice(offset + 4, (int)BinaryPrimitives.ReadUInt32BigEndian(decodedValue.Slice(offset)))));
        }
コード例 #2
0
 public void CreatePathInvalidThrowsException([NotNull] string fileName)
 {
     Assert.Throws <ImageInvalidException>(() =>
                                           CoverArtFactory.GetOrCreate(Path.Combine(
                                                                           new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.Parent?.Parent?.FullName,
                                                                           "TestFiles",
                                                                           "Invalid",
                                                                           fileName)));
 }
コード例 #3
0
ファイル: CoverArtTests.cs プロジェクト: djkurtb/AudioWorks
 public void GetDataReturnsExpectedValue([NotNull] string fileName, [NotNull] string expectedHash)
 {
     Assert.Equal(expectedHash, HashUtility.CalculateHash(CoverArtFactory.GetOrCreate(
                                                              Path.Combine(
                                                                  new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.Parent?.Parent?.FullName,
                                                                  "TestFiles",
                                                                  "Valid",
                                                                  fileName))
                                                          .Data.ToArray()));
 }
コード例 #4
0
ファイル: CoverArtTests.cs プロジェクト: djkurtb/AudioWorks
 public void HasExpectedMimeType([NotNull] string fileName, [NotNull] string mimeType)
 {
     Assert.Equal(mimeType, CoverArtFactory.GetOrCreate(
                      Path.Combine(
                          new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.Parent?.Parent?.FullName,
                          "TestFiles",
                          "Valid",
                          fileName))
                  .MimeType);
 }
コード例 #5
0
ファイル: CoverArtTests.cs プロジェクト: djkurtb/AudioWorks
 public void HasExpectedLossless([NotNull] string fileName, bool expectedLossless)
 {
     Assert.Equal(expectedLossless, CoverArtFactory.GetOrCreate(
                      Path.Combine(
                          new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.Parent?.Parent?.FullName,
                          "TestFiles",
                          "Valid",
                          fileName))
                  .Lossless);
 }
コード例 #6
0
ファイル: CoverArtTests.cs プロジェクト: djkurtb/AudioWorks
 public void HasExpectedColorDepth([NotNull] string fileName, int expectedColorDepth)
 {
     Assert.Equal(expectedColorDepth, CoverArtFactory.GetOrCreate(
                      Path.Combine(
                          new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.Parent?.Parent?.FullName,
                          "TestFiles",
                          "Valid",
                          fileName))
                  .ColorDepth);
 }
コード例 #7
0
        internal MetadataToIlstAtomAdapter([NotNull] AudioMetadata metadata, bool compressCoverArt)
        {
            if (!string.IsNullOrEmpty(metadata.Title))
            {
                _atoms.Add(new TextAtom("©nam", metadata.Title));
            }
            if (!string.IsNullOrEmpty(metadata.Artist))
            {
                _atoms.Add(new TextAtom("©ART", metadata.Artist));
            }
            if (!string.IsNullOrEmpty(metadata.Album))
            {
                _atoms.Add(new TextAtom("©alb", metadata.Album));
            }
            if (!string.IsNullOrEmpty(metadata.AlbumArtist))
            {
                _atoms.Add(new TextAtom("aART", metadata.AlbumArtist));
            }
            if (!string.IsNullOrEmpty(metadata.Composer))
            {
                _atoms.Add(new TextAtom("©wrt", metadata.Composer));
            }
            if (!string.IsNullOrEmpty(metadata.Genre))
            {
                _atoms.Add(new TextAtom("©gen", metadata.Genre));
            }
            if (!string.IsNullOrEmpty(metadata.Comment))
            {
                _atoms.Add(new TextAtom("©cmt", metadata.Comment));
            }

            if (!string.IsNullOrEmpty(metadata.Day) &&
                !string.IsNullOrEmpty(metadata.Month) &&
                !string.IsNullOrEmpty(metadata.Year))
            {
                _atoms.Add(new TextAtom("©day",
                                        $"{metadata.Year}-{metadata.Month}-{metadata.Day}"));
            }
            else if (!string.IsNullOrEmpty(metadata.Year))
            {
                _atoms.Add(new TextAtom("©day", metadata.Year));
            }

            if (!string.IsNullOrEmpty(metadata.TrackNumber))
            {
                _atoms.Add(new TrackNumberAtom(metadata.TrackNumber, metadata.TrackCount));
            }

            if (metadata.CoverArt != null)
            {
                _atoms.Add(new CoverAtom(compressCoverArt
                    ? CoverArtFactory.ConvertToLossy(metadata.CoverArt)
                    : metadata.CoverArt));
            }
        }
コード例 #8
0
        public void CreatesExpectedOutput(
            int index,
            string fileName,
            TestAudioMetadata metadata,
            string imageFileName,
            SettingDictionary settings,
            string[] validHashes)
        {
            var sourceDirectory = Path.Combine(PathUtility.GetTestFileRoot(), "Valid");
            var path            = Path.Combine("Output", "Save-AudioMetadata", "Valid", $"{index:000} - {fileName}");

            Directory.CreateDirectory(Path.GetDirectoryName(path) !);
            File.Copy(Path.Combine(sourceDirectory, fileName), path, true);
            var audioFile = new TaggedAudioFile(path);

            _mapper.Map(metadata, audioFile.Metadata);
            if (!string.IsNullOrEmpty(imageFileName))
            {
                audioFile.Metadata.CoverArt = CoverArtFactory.GetOrCreate(Path.Combine(sourceDirectory, imageFileName));
            }
            using (var ps = PowerShell.Create())
            {
                ps.Runspace = _moduleFixture.Runspace;
                ps.AddCommand("Save-AudioMetadata")
                .AddArgument(audioFile);
                foreach (var item in settings)
                {
                    if (item.Value is bool boolValue)
                    {
                        if (boolValue)
                        {
                            ps.AddParameter(item.Key);
                        }
                    }
                    else
                    {
                        ps.AddParameter(item.Key, item.Value);
                    }
                }

                ps.Invoke();
            }

            Assert.Contains(HashUtility.CalculateHash(audioFile.Path), validHashes);
        }
コード例 #9
0
        protected override unsafe void MetadataCallback(IntPtr handle, IntPtr metadataBlock, IntPtr userData)
        {
            // ReSharper disable once SwitchStatementMissingSomeCases
            switch ((MetadataType)Marshal.ReadInt32(metadataBlock))
            {
            case MetadataType.VorbisComment:
                var vorbisComment = Marshal.PtrToStructure <VorbisCommentMetadataBlock>(metadataBlock).VorbisComment;
                for (var commentIndex = 0; commentIndex < vorbisComment.Count; commentIndex++)
                {
                    var entry = Marshal.PtrToStructure <VorbisCommentEntry>(IntPtr.Add(vorbisComment.Comments,
                                                                                       commentIndex * Marshal.SizeOf <VorbisCommentEntry>()));

                    var commentBytes = new Span <byte>(entry.Entry.ToPointer(), (int)entry.Length);
                    var delimiter    = commentBytes.IndexOf((byte)0x3D);   // '='
#if NETSTANDARD2_0
                    var keyBytes   = commentBytes.Slice(0, delimiter);
                    var valueBytes = commentBytes.Slice(delimiter + 1);
                    AudioMetadata.Set(
                        Encoding.ASCII.GetString(
                            (byte *)Unsafe.AsPointer(ref MemoryMarshal.GetReference(keyBytes)),
                            keyBytes.Length),
                        Encoding.UTF8.GetString(
                            (byte *)Unsafe.AsPointer(ref MemoryMarshal.GetReference(valueBytes)),
                            valueBytes.Length));
#else
                    AudioMetadata.Set(
                        Encoding.ASCII.GetString(commentBytes.Slice(0, delimiter)),
                        Encoding.UTF8.GetString(commentBytes.Slice(delimiter + 1)));
#endif
                }

                break;

            case MetadataType.Picture:
                var picture = Marshal.PtrToStructure <PictureMetadataBlock>(metadataBlock).Picture;
                if (picture.Type == PictureType.CoverFront || picture.Type == PictureType.Other)
                {
                    AudioMetadata.CoverArt = CoverArtFactory.GetOrCreate(
                        new Span <byte>(picture.Data.ToPointer(), (int)picture.DataLength));
                }
                break;
            }
        }
コード例 #10
0
        internal MetadataToTagModelAdapter([NotNull] AudioMetadata metadata, [NotNull] string encoding)
        {
            AddTextFrame("TIT2", metadata.Title, encoding);
            AddTextFrame("TPE1", metadata.Artist, encoding);
            AddTextFrame("TALB", metadata.Album, encoding);
            AddTextFrame("TPE2", metadata.AlbumArtist, encoding);
            AddTextFrame("TCOM", metadata.Composer, encoding);
            AddTextFrame("TCON", metadata.Genre, encoding);
            AddFullTextFrame("COMM", metadata.Comment, "eng", encoding);
            AddTextFrame("TDAT", GetDateText(metadata), encoding);
            AddTextFrame("TYER", metadata.Year, encoding);
            AddTextFrame("TRCK", GetTrackText(metadata), encoding);

            // ReplayGain fields are always in Latin-1, encoding as per specification
            AddUserDefinedFrame("REPLAYGAIN_TRACK_PEAK", metadata.TrackPeak, "Latin1", true);
            AddUserDefinedFrame("REPLAYGAIN_ALBUM_PEAK", metadata.AlbumPeak, "Latin1", true);
            if (!string.IsNullOrEmpty(metadata.TrackGain))
            {
                AddUserDefinedFrame("REPLAYGAIN_TRACK_GAIN", $"{metadata.TrackGain} dB", "Latin1", true);
            }
            if (!string.IsNullOrEmpty(metadata.AlbumGain))
            {
                AddUserDefinedFrame("REPLAYGAIN_ALBUM_GAIN", $"{metadata.AlbumGain} dB", "Latin1", true);
            }

            if (metadata.CoverArt == null)
            {
                return;
            }

            // Always store images in JPEG format since MP3 is also lossy
            var lossyCoverArt = CoverArtFactory.ConvertToLossy(metadata.CoverArt);

            Add(new FramePicture("APIC")
            {
                PictureType = PictureTypeCode.CoverFront,
                Mime        = lossyCoverArt.MimeType,
                PictureData = lossyCoverArt.Data.ToArray()
            });
        }
コード例 #11
0
        void ProcessPath(string path)
        {
            try
            {
                ICoverArt result;

                try
                {
                    result = CoverArtFactory.GetOrCreate(path);
                }
                finally
                {
                    ProcessLogMessages();
                }

                WriteObject(result);
            }
            catch (AudioException e)
            {
                WriteError(new(e, e.GetType().Name, ErrorCategory.InvalidData, Path));
            }
        }
コード例 #12
0
        internal unsafe MetadataToOpusCommentAdapter([NotNull] AudioMetadata metadata)
        {
            Handle = SafeNativeMethods.OpusEncoderCommentsCreate();

            if (!string.IsNullOrEmpty(metadata.Title))
            {
                AddTag("TITLE", metadata.Title);
            }
            if (!string.IsNullOrEmpty(metadata.Artist))
            {
                AddTag("ARTIST", metadata.Artist);
            }
            if (!string.IsNullOrEmpty(metadata.Album))
            {
                AddTag("ALBUM", metadata.Album);
            }
            if (!string.IsNullOrEmpty(metadata.AlbumArtist))
            {
                AddTag("ALBUMARTIST", metadata.AlbumArtist);
            }
            if (!string.IsNullOrEmpty(metadata.Composer))
            {
                AddTag("COMPOSER", metadata.Composer);
            }
            if (!string.IsNullOrEmpty(metadata.Genre))
            {
                AddTag("GENRE", metadata.Genre);
            }
            if (!string.IsNullOrEmpty(metadata.Comment))
            {
                AddTag("DESCRIPTION", metadata.Comment);
            }

            if (!string.IsNullOrEmpty(metadata.Day) &&
                !string.IsNullOrEmpty(metadata.Month) &&
                !string.IsNullOrEmpty(metadata.Year))
            {
                AddTag("DATE", $"{metadata.Year}-{metadata.Month}-{metadata.Day}");
            }
            else if (!string.IsNullOrEmpty(metadata.Year))
            {
                AddTag("YEAR", metadata.Year);
            }

            if (!string.IsNullOrEmpty(metadata.TrackNumber))
            {
                AddTag("TRACKNUMBER", !string.IsNullOrEmpty(metadata.TrackCount)
                    ? $"{metadata.TrackNumber}/{metadata.TrackCount}"
                    : metadata.TrackNumber);
            }

            if (!string.IsNullOrEmpty(metadata.TrackGain))
            {
                AddTag("R128_TRACK_GAIN", ConvertGain(metadata.TrackGain));
            }
            if (!string.IsNullOrEmpty(metadata.AlbumGain))
            {
                AddTag("R128_ALBUM_GAIN", ConvertGain(metadata.AlbumGain));
            }

            if (metadata.CoverArt == null)
            {
                return;
            }

            // Always store images in JPEG format since Vorbis is also lossy
            var coverArt = CoverArtFactory.ConvertToLossy(metadata.CoverArt);

            fixed(byte *coverArtAddress = coverArt.Data)
            {
                var error = SafeNativeMethods.OpusEncoderCommentsAddPictureFromMemory(
                    Handle,
                    coverArtAddress,
                    new IntPtr(coverArt.Data.Length),
                    -1,
                    IntPtr.Zero);

                if (error != 0)
                {
                    throw new AudioEncodingException($"Opus encountered error {error} writing the cover art.");
                }
            }
        }
コード例 #13
0
        internal TagModelToMetadataAdapter([NotNull] TagModel tagModel)
        {
            foreach (var frame in tagModel)
            {
                switch (frame)
                {
                case FrameText frameText:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameText.FrameId)
                    {
                    case "TIT2":
                        Title = frameText.Text;
                        break;

                    case "TPE1":
                        Artist = frameText.Text;
                        break;

                    case "TALB":
                        Album = frameText.Text;
                        break;

                    case "TPE2":
                        AlbumArtist = frameText.Text;
                        break;

                    case "TCOM":
                        Composer = frameText.Text;
                        break;

                    case "TCON":
                        Genre = frameText.Text;
                        break;

                    // The TDAT frame contains the day and the month:
                    case "TDAT":
                        Day   = frameText.Text.Substring(0, 2);
                        Month = frameText.Text.Substring(2);
                        break;

                    case "TYER":
                        Year = frameText.Text;
                        break;

                    // The TRCK frame contains the track number and (optionally) the track count:
                    case "TRCK":
                        var segments = frameText.Text.Split('/');
                        TrackNumber = segments[0];
                        if (segments.Length > 1)
                        {
                            TrackCount = segments[1];
                        }
                        break;
                    }
                    break;

                case FrameFullText frameFullText:
                    if (frameFullText.FrameId.Equals("COMM", StringComparison.Ordinal) &&
                        string.IsNullOrEmpty(frameFullText.Description))
                    {
                        Comment = frameFullText.Text;
                    }
                    break;

                case FrameTextUserDef frameTextUserDef:
                    // ReSharper disable once SwitchStatementMissingSomeCases
                    switch (frameTextUserDef.Description)
                    {
                    case "REPLAYGAIN_TRACK_PEAK":
                        TrackPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_ALBUM_PEAK":
                        AlbumPeak = frameTextUserDef.Text;
                        break;

                    case "REPLAYGAIN_TRACK_GAIN":
#if NETSTANDARD2_0
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        TrackGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;

                    case "REPLAYGAIN_ALBUM_GAIN":
#if NETSTANDARD2_0
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty);
#else
                        AlbumGain = frameTextUserDef.Text.Replace(" dB", string.Empty,
                                                                  StringComparison.OrdinalIgnoreCase);
#endif
                        break;
                    }
                    break;

                case FramePicture framePicture:
                    CoverArt = CoverArtFactory.GetOrCreate(framePicture.PictureData);
                    break;
                }
            }
        }
コード例 #14
0
        internal MetadataToTagModelAdapter(AudioMetadata metadata, int version, string encoding)
        {
            Header.Version = (byte)version;

            var textType = encoding switch
            {
                "UTF16" => TextType.Utf16,
                "UTF8" => TextType.Utf8,
                _ => TextType.Ascii
            };

            AddTextFrame("TIT2", metadata.Title, textType);
            AddTextFrame("TPE1", metadata.Artist, textType);
            AddTextFrame("TALB", metadata.Album, textType);
            AddTextFrame("TPE2", metadata.AlbumArtist, textType);
            AddTextFrame("TCOM", metadata.Composer, textType);
            AddTextFrame("TCON", metadata.Genre, textType);
            AddTextFrame("COMM", metadata.Comment, textType);

            if (version == 3)
            {
                AddTextFrame("TDAT", GetDateText(metadata), textType);
                AddTextFrame("TYER", metadata.Year, textType);
            }
            else
            {
                AddTextFrame("TDRC", GetTimeStamp(metadata), textType);
            }

            AddTextFrame("TRCK", GetTrackText(metadata), textType);

            AddReplayGainFrame(metadata.TrackPeak, "REPLAYGAIN_TRACK_PEAK");
            AddReplayGainFrame(metadata.AlbumPeak, "REPLAYGAIN_ALBUM_PEAK");
            AddReplayGainFormattedFrame(metadata.TrackGain, "REPLAYGAIN_TRACK_GAIN");
            AddReplayGainFormattedFrame(metadata.AlbumGain, "REPLAYGAIN_ALBUM_GAIN");

            if (metadata.CoverArt == null)
            {
                return;
            }

            // Always store images in JPEG format, since MP3 is also lossy
            var lossyCoverArt = CoverArtFactory.ConvertToLossy(metadata.CoverArt);

            Frames.Add(new FramePicture
            {
                PictureType = PictureType.CoverFront,
                Mime        = lossyCoverArt.MimeType,
                PictureData = lossyCoverArt.Data.ToArray()
            });
        }

        void AddReplayGainFormattedFrame(string value, string description)
        {
            if (!string.IsNullOrEmpty(value))
            {
                AddReplayGainFrame($"{value} dB", description);
            }
        }

        void AddReplayGainFrame(string value, string description) => AddTextFrame("TXXX", value, TextType.Ascii, description, true);

        void AddTextFrame(string frameId, string value, TextType textType = TextType.Ascii, string?description = null, bool fileAlter = false)
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            var frame = (FrameText)FrameFactory.Build(frameId);

            frame.Text      = value;
            frame.TextType  = textType;
            frame.FileAlter = fileAlter;

            if (description != null)
            {
                if (frame is IFrameDescription frameDescription)
                {
                    frameDescription.Description = description;
                }
            }

            Frames.Add(frame);
        }
コード例 #15
0
 public void CreateDataNullThrowsException()
 {
     Assert.Throws <ArgumentNullException>(() => CoverArtFactory.GetOrCreate((byte[])null));
 }
コード例 #16
0
 public void CreatePathNullThrowsException()
 {
     Assert.Throws <ArgumentNullException>(() => CoverArtFactory.GetOrCreate((string)null));
 }
コード例 #17
0
        internal MetadataToVorbisCommentAdapter([NotNull] AudioMetadata metadata)
        {
            SafeNativeMethods.VorbisCommentInit(out _comment);

            if (!string.IsNullOrEmpty(metadata.Title))
            {
                AddTag("TITLE", metadata.Title);
            }
            if (!string.IsNullOrEmpty(metadata.Artist))
            {
                AddTag("ARTIST", metadata.Artist);
            }
            if (!string.IsNullOrEmpty(metadata.Album))
            {
                AddTag("ALBUM", metadata.Album);
            }
            if (!string.IsNullOrEmpty(metadata.AlbumArtist))
            {
                AddTag("ALBUMARTIST", metadata.AlbumArtist);
            }
            if (!string.IsNullOrEmpty(metadata.Composer))
            {
                AddTag("COMPOSER", metadata.Composer);
            }
            if (!string.IsNullOrEmpty(metadata.Genre))
            {
                AddTag("GENRE", metadata.Genre);
            }
            if (!string.IsNullOrEmpty(metadata.Comment))
            {
                AddTag("DESCRIPTION", metadata.Comment);
            }

            if (!string.IsNullOrEmpty(metadata.Day) &&
                !string.IsNullOrEmpty(metadata.Month) &&
                !string.IsNullOrEmpty(metadata.Year))
            {
                AddTag("DATE", $"{metadata.Year}-{metadata.Month}-{metadata.Day}");
            }
            else if (!string.IsNullOrEmpty(metadata.Year))
            {
                AddTag("YEAR", metadata.Year);
            }

            if (!string.IsNullOrEmpty(metadata.TrackNumber))
            {
                AddTag("TRACKNUMBER", !string.IsNullOrEmpty(metadata.TrackCount)
                    ? $"{metadata.TrackNumber}/{metadata.TrackCount}"
                    : metadata.TrackNumber);
            }

            if (!string.IsNullOrEmpty(metadata.TrackPeak))
            {
                AddTag("REPLAYGAIN_TRACK_PEAK", metadata.TrackPeak);
            }
            if (!string.IsNullOrEmpty(metadata.AlbumPeak))
            {
                AddTag("REPLAYGAIN_ALBUM_PEAK", metadata.AlbumPeak);
            }
            if (!string.IsNullOrEmpty(metadata.TrackGain))
            {
                AddTag("REPLAYGAIN_TRACK_GAIN", $"{metadata.TrackGain} dB");
            }
            if (!string.IsNullOrEmpty(metadata.AlbumGain))
            {
                AddTag("REPLAYGAIN_ALBUM_GAIN", $"{metadata.AlbumGain} dB");
            }

            // Always store images in JPEG format since Vorbis is also lossy
            if (metadata.CoverArt != null)
            {
                AddTag("METADATA_BLOCK_PICTURE", CoverArtAdapter.ToBase64(
                           CoverArtFactory.ConvertToLossy(metadata.CoverArt)));
            }
        }
コード例 #18
0
 public void CreatePathNotFoundThrowsException()
 {
     Assert.Throws <FileNotFoundException>(() => CoverArtFactory.GetOrCreate("Foo"));
 }
コード例 #19
0
ファイル: CoverAtom.cs プロジェクト: djkurtb/AudioWorks
 public CoverAtom(ReadOnlySpan <byte> data)
 {
     // There could be more than one data atom. Ignore all but the first.
     Value = CoverArtFactory.GetOrCreate(data.Slice(24,
                                                    (int)BinaryPrimitives.ReadUInt32BigEndian(data.Slice(8, 4)) - 16));
 }