From 45b575d6741693b0c72c0f56c81105969059524d Mon Sep 17 00:00:00 2001 From: Z440 Date: Sun, 22 Oct 2017 17:07:16 +0200 Subject: [PATCH] One can now add, remove and update embedded pictures using the Track class --- ATL.test/IO/HighLevel.cs | 97 +++++++++++++++++++++++++++++++++++++++- ATL/Entities/TagData.cs | 17 +++++++ ATL/Entities/Track.cs | 83 +++++++++++++++++++++++++++++----- 3 files changed, 186 insertions(+), 11 deletions(-) diff --git a/ATL.test/IO/HighLevel.cs b/ATL.test/IO/HighLevel.cs index fdd215bd..522d344c 100644 --- a/ATL.test/IO/HighLevel.cs +++ b/ATL.test/IO/HighLevel.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using ATL.AudioData; using System.IO; +using System.Drawing; namespace ATL.test.IO { @@ -109,7 +110,7 @@ public void TagIO_RW_UpdateTagBaseField() } [TestMethod] - public void TagIO_RW_UpdateTagAdditionalField() + public void TagIO_RW_AddRemoveTagAdditionalField() { string testFileLocation = TestUtils.GetTempTestFile("MP3/01 - Title Screen.mp3"); Track theTrack = new Track(testFileLocation); @@ -128,6 +129,100 @@ public void TagIO_RW_UpdateTagAdditionalField() File.Delete(testFileLocation); } + [TestMethod] + public void TagIO_RW_UpdateTagAdditionalField() + { + string testFileLocation = TestUtils.GetTempTestFile("MP3/01 - Title Screen.mp3"); + Track theTrack = new Track(testFileLocation); + + theTrack.AdditionalFields["TENC"] = "update test"; + theTrack.Save(); + + theTrack = new Track(testFileLocation); + + Assert.AreEqual(1, theTrack.AdditionalFields.Count); + Assert.IsTrue(theTrack.AdditionalFields.ContainsKey("TENC")); + Assert.AreEqual("update test", theTrack.AdditionalFields["TENC"]); + + // Get rid of the working copy + File.Delete(testFileLocation); + } + + [TestMethod] + public void TagIO_RW_AddRemoveTagPictures() + { + string testFileLocation = TestUtils.GetTempTestFile("MP3/id3v2.4_UTF8.mp3"); + Track theTrack = new Track(testFileLocation); + + theTrack.EmbeddedPictures.RemoveAt(1); // Remove Conductor; Front Cover remains + + // Add CD + TagData.PictureInfo newPicture = new TagData.PictureInfo(Commons.ImageFormat.Gif, TagData.PIC_TYPE.CD); + newPicture.PictureData = File.ReadAllBytes(TestUtils.GetResourceLocationRoot() + "_Images/pic1.gif"); + theTrack.EmbeddedPictures.Add(newPicture); + + theTrack.Save(); + + theTrack = new Track(testFileLocation); + + Assert.AreEqual(2, theTrack.EmbeddedPictures.Count); // Front Cover, CD + + bool foundFront = false; + bool foundCD = false; + + foreach (TagData.PictureInfo pic in theTrack.EmbeddedPictures) + { + if (pic.PicType.Equals(TagData.PIC_TYPE.Front)) foundFront = true; + if (pic.PicType.Equals(TagData.PIC_TYPE.CD)) foundCD = true; + } + + Assert.IsTrue(foundFront); + Assert.IsTrue(foundCD); + + // Get rid of the working copy + File.Delete(testFileLocation); + } + + [TestMethod] + public void TagIO_RW_UpdateTagPictures() + { + string testFileLocation = TestUtils.GetTempTestFile("MP3/id3v2.4_UTF8.mp3"); + Track theTrack = new Track(testFileLocation); + + // Update Front picture + TagData.PictureInfo newPicture = new TagData.PictureInfo(Commons.ImageFormat.Jpeg, TagData.PIC_TYPE.Front); + newPicture.PictureData = File.ReadAllBytes(TestUtils.GetResourceLocationRoot() + "_Images/pic2.jpg"); + theTrack.EmbeddedPictures.Add(newPicture); + + theTrack.Save(); + + theTrack = new Track(testFileLocation); + + Assert.AreEqual(2, theTrack.EmbeddedPictures.Count); // Front Cover, Conductor + + bool foundFront = false; + bool foundConductor = false; + + foreach (TagData.PictureInfo pic in theTrack.EmbeddedPictures) + { + if (pic.PicType.Equals(TagData.PIC_TYPE.Front)) + { + foundFront = true; + Image picture = Image.FromStream(new MemoryStream(pic.PictureData)); + Assert.AreEqual(picture.RawFormat, System.Drawing.Imaging.ImageFormat.Jpeg); + Assert.AreEqual(picture.Width, 900); + Assert.AreEqual(picture.Height, 290); + } + if (pic.PicType.Equals(TagData.PIC_TYPE.Unsupported)) foundConductor = true; + } + + Assert.IsTrue(foundFront); + Assert.IsTrue(foundConductor); + + // Get rid of the working copy + File.Delete(testFileLocation); + } + [TestMethod] public void TagIO_RW_UpdateKeepDataIntegrity() { diff --git a/ATL/Entities/TagData.cs b/ATL/Entities/TagData.cs index 4755e8ed..d7e14e82 100644 --- a/ATL/Entities/TagData.cs +++ b/ATL/Entities/TagData.cs @@ -33,11 +33,28 @@ public class PictureInfo // TODO - add a description field public byte[] PictureData; // Binary picture data + public uint PictureHash; // Hash of binary picture data public bool MarkedForDeletion = false; // True if the field has to be deleted in the next IMetaDataIO.Write operation + public int Flag; // Freeform value to be used by other parts of the library // ---------------- CONSTRUCTORS + public PictureInfo(PictureInfo picInfo) + { + this.PicType = picInfo.PicType; + this.NativeFormat = picInfo.NativeFormat; + this.Position = picInfo.Position; + this.TagType = picInfo.TagType; + this.NativePicCode = picInfo.NativePicCode; + this.NativePicCodeStr = picInfo.NativePicCodeStr; + if (picInfo.PictureData != null) + { + this.PictureData = new byte[picInfo.PictureData.Length]; + picInfo.PictureData.CopyTo(this.PictureData, 0); + } + this.MarkedForDeletion = picInfo.MarkedForDeletion; + } public PictureInfo(ImageFormat nativeFormat, PIC_TYPE picType, int tagType, object nativePicCode, int position = 1) { PicType = picType; NativeFormat = nativeFormat; TagType = tagType; Position = position; diff --git a/ATL/Entities/Track.cs b/ATL/Entities/Track.cs index f1d0495b..98391270 100644 --- a/ATL/Entities/Track.cs +++ b/ATL/Entities/Track.cs @@ -42,11 +42,14 @@ public Track(string iPath) public int TrackNumber; public int DiscNumber; public int Rating; - public IDictionary AdditionalFields; - private ICollection InitialAdditionalFields; // Initial fields, to track removed ones public IList PictureTokens = null; + public IDictionary AdditionalFields; + private ICollection initialAdditionalFields; // Initial fields, used to identify removed ones + private IList embeddedPictures = null; + private ICollection initialEmbeddedPictures; // Initial fields, used to identify removed ones + private AudioFileIO fileIO; @@ -65,6 +68,7 @@ public IList EmbeddedPictures if (null == embeddedPictures) { embeddedPictures = new List(); + initialEmbeddedPictures = new List(); Update(new TagData.PictureStreamHandlerDelegate(readBinaryImageData)); } @@ -77,7 +81,12 @@ protected void readBinaryImageData(ref MemoryStream s, TagData.PIC_TYPE picType, TagData.PictureInfo picInfo = new TagData.PictureInfo(imgFormat, picType, originalTag, picCode, position); picInfo.PictureData = s.ToArray(); + // Initial pic info, without picture data + TagData.PictureInfo initialPicInfo = new TagData.PictureInfo(imgFormat, picType, originalTag, picCode, position); + initialPicInfo.PictureHash = HashDepot.Fnv1a.Hash32(picInfo.PictureData); + embeddedPictures.Add(picInfo); + initialEmbeddedPictures.Add(initialPicInfo); } protected void Update(TagData.PictureStreamHandlerDelegate pictureStreamHandler = null) @@ -101,8 +110,6 @@ protected void Update(TagData.PictureStreamHandlerDelegate pictureStreamHandler Publisher = Utils.ProtectValue(fileIO.Publisher); AlbumArtist = Utils.ProtectValue(fileIO.AlbumArtist); Conductor = Utils.ProtectValue(fileIO.Conductor); - AdditionalFields = fileIO.AdditionalFields; // ??? - InitialAdditionalFields = fileIO.AdditionalFields.Keys; Year = fileIO.IntYear; Album = fileIO.Album; TrackNumber = fileIO.Track; @@ -113,12 +120,18 @@ protected void Update(TagData.PictureStreamHandlerDelegate pictureStreamHandler Rating = fileIO.Rating; IsVBR = fileIO.IsVBR; SampleRate = fileIO.SampleRate; + + AdditionalFields = fileIO.AdditionalFields; + initialAdditionalFields = fileIO.AdditionalFields.Keys; + PictureTokens = new List(fileIO.PictureTokens); if (null == pictureStreamHandler && embeddedPictures != null) { embeddedPictures.Clear(); + initialEmbeddedPictures.Clear(); embeddedPictures = null; + initialEmbeddedPictures = null; } } @@ -143,21 +156,71 @@ private TagData toTagData() result.TrackNumber = TrackNumber.ToString(); result.DiscNumber = DiscNumber.ToString(); result.Rating = Rating.ToString(); - result.Pictures = embeddedPictures; foreach (string s in AdditionalFields.Keys) { result.AdditionalFields.Add(new TagData.MetaFieldInfo(MetaDataIOFactory.TAG_ANY, s, AdditionalFields[s])); } - // Detect and tag deleted Additional fields - foreach (string s in InitialAdditionalFields) + // Detect and tag deleted Additional fields (=those which were in initialAdditionalFields and do not appear in AdditionalFields anymore) + foreach (string s in initialAdditionalFields) { if (!AdditionalFields.ContainsKey(s)) { - TagData.MetaFieldInfo metaField = new TagData.MetaFieldInfo(MetaDataIOFactory.TAG_ANY, s, ""); - metaField.MarkedForDeletion = true; - result.AdditionalFields.Add(metaField); + TagData.MetaFieldInfo metaFieldToDelete = new TagData.MetaFieldInfo(MetaDataIOFactory.TAG_ANY, s, ""); + metaFieldToDelete.MarkedForDeletion = true; + result.AdditionalFields.Add(metaFieldToDelete); + } + } + + result.Pictures = new List(); + if (embeddedPictures != null) foreach (TagData.PictureInfo targetPic in embeddedPictures) targetPic.Flag = 0; + + if (initialEmbeddedPictures != null) + { + foreach (TagData.PictureInfo picInfo in initialEmbeddedPictures) + { + // Detect and tag deleted pictures (=those which were in initialEmbeddedPictures and do not appear in embeddedPictures anymore) + if (!embeddedPictures.Contains(picInfo)) + { + TagData.PictureInfo picToDelete = new TagData.PictureInfo(picInfo); + picToDelete.MarkedForDeletion = true; + result.Pictures.Add(picToDelete); + } + else // Only add new additions (pictures identical to initial list will be kept, and do not have to make it to the list, or else a duplicate will be created) + { + foreach (TagData.PictureInfo targetPic in embeddedPictures) + { + if (targetPic.Equals(picInfo)) + { + // Compare picture contents + uint newPictureHash = HashDepot.Fnv1a.Hash32(targetPic.PictureData); + + if (newPictureHash != picInfo.PictureHash) + { + // A new picture content has been defined for an existing location + result.Pictures.Add(targetPic); + + TagData.PictureInfo picToDelete = new TagData.PictureInfo(picInfo); + picToDelete.MarkedForDeletion = true; + result.Pictures.Add(picToDelete); + } + + targetPic.Flag = 1; + } + } + } + } + + if (embeddedPictures != null) + { + foreach (TagData.PictureInfo targetPic in embeddedPictures) + { + if (0 == targetPic.Flag) // Entirely new pictures without equivalent in initialEmbeddedPictures + { + result.Pictures.Add(targetPic); + } + } } }