From 68c5f659b3bfb1381eb0902326e71c5f84bd65ba Mon Sep 17 00:00:00 2001 From: Zeugma440 Date: Wed, 17 Apr 2024 14:09:33 +0200 Subject: [PATCH] General : Prevent setting a standard field twice using AdditionalFields [#260] --- ATL.benchmark/Program.cs | 2 +- ATL.unit-test/IO/MetaData/APE.cs | 1 + ATL.unit-test/IO/MetaData/ID3v2.cs | 1 + ATL.unit-test/IO/MetaData/MP4.cs | 1 + ATL.unit-test/IO/MetaData/MetaIOTest.cs | 29 +++++++++++++++++ ATL.unit-test/IO/MetaData/PSF.cs | 29 ++++++++++++++++- ATL.unit-test/IO/MetaData/VQF.cs | 27 ++++++++++++++++ ATL.unit-test/IO/MetaData/Vorbis_FLAC.cs | 28 ++++++++++++++++ ATL.unit-test/IO/MetaData/WAV.cs | 3 +- ATL.unit-test/IO/MetaData/WMA.cs | 27 ++++++++++++++++ ATL/AudioData/IO/APEtag.cs | 8 ++++- ATL/AudioData/IO/ID3v2.cs | 14 ++++---- ATL/AudioData/IO/MP4.cs | 4 +++ ATL/AudioData/IO/PSF.cs | 9 +++++- ATL/AudioData/IO/TwinVQ.cs | 9 +++++- ATL/AudioData/IO/VorbisTag.cs | 11 +++++-- ATL/AudioData/IO/WMA.cs | 41 ++++++++++++++++++++---- 17 files changed, 223 insertions(+), 21 deletions(-) diff --git a/ATL.benchmark/Program.cs b/ATL.benchmark/Program.cs index f6ed4613..0da8e4b0 100644 --- a/ATL.benchmark/Program.cs +++ b/ATL.benchmark/Program.cs @@ -27,7 +27,7 @@ static void Main(string[] args) //browseFor(@"E:\Music\", "*.mp3"); - //writeAt(@"D:\temp\wav\broadcastwave_bext_info.wav"); + writeAt(@"D:\temp\m4a-mp4\261\321668598-26d5e109-ad7a-42ac-82fa-18c7134891f3.mp4"); //info(@"D:\temp\wav\74\empty_tagged_audacity.wav"); diff --git a/ATL.unit-test/IO/MetaData/APE.cs b/ATL.unit-test/IO/MetaData/APE.cs index 51c57a2a..85e2acf2 100644 --- a/ATL.unit-test/IO/MetaData/APE.cs +++ b/ATL.unit-test/IO/MetaData/APE.cs @@ -14,6 +14,7 @@ public APE() emptyFile = "MP3/empty.mp3"; notEmptyFile = "MP3/APE.mp3"; tagType = MetaDataIOFactory.TagType.APE; + titleFieldCode = "TITLE"; // Initialize specific test data (Publisher and Description fields not supported in APE tag) testData.Publisher = null; diff --git a/ATL.unit-test/IO/MetaData/ID3v2.cs b/ATL.unit-test/IO/MetaData/ID3v2.cs index dee9f741..61534147 100644 --- a/ATL.unit-test/IO/MetaData/ID3v2.cs +++ b/ATL.unit-test/IO/MetaData/ID3v2.cs @@ -35,6 +35,7 @@ public ID3v2() testData.OriginalReleaseDate = new DateTime(2003, 03, 23); tagType = MetaDataIOFactory.TagType.ID3V2; + titleFieldCode = "TIT2"; var pics = testData.EmbeddedPictures; foreach (PictureInfo pic in pics) pic.TagType = tagType; testData.EmbeddedPictures = pics; diff --git a/ATL.unit-test/IO/MetaData/MP4.cs b/ATL.unit-test/IO/MetaData/MP4.cs index 6789e626..6d526c07 100644 --- a/ATL.unit-test/IO/MetaData/MP4.cs +++ b/ATL.unit-test/IO/MetaData/MP4.cs @@ -63,6 +63,7 @@ public MP4() emptyFile = "MP4/empty.m4a"; // Has empty udta/meta tags notEmptyFile = "MP4/mp4.m4a"; tagType = MetaDataIOFactory.TagType.NATIVE; + titleFieldCode = "©nam"; // MP4 does not support leading zeroes testData.TrackNumber = 1; diff --git a/ATL.unit-test/IO/MetaData/MetaIOTest.cs b/ATL.unit-test/IO/MetaData/MetaIOTest.cs index f2659e55..39156805 100644 --- a/ATL.unit-test/IO/MetaData/MetaIOTest.cs +++ b/ATL.unit-test/IO/MetaData/MetaIOTest.cs @@ -50,6 +50,7 @@ public abstract class MetaIOTest protected bool supportsExtraEmbeddedPictures = true; protected bool supportsInternationalChars = true; protected bool canMetaNotExist = true; + protected string titleFieldCode = ""; public delegate void StreamDelegate(FileStream fs); @@ -567,6 +568,34 @@ public void test_RW_Empty( Assert.AreEqual(testData.AdditionalFields.Count - 1, meta.AdditionalFields.Count); } + // Setting a standard field using additional fields shouldn't be possible + if (testData.AdditionalFields != null && testData.AdditionalFields.Count > 0 && titleFieldCode.Length > 0) + { + theTag.Title = "THE TITLE"; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + theTag.AdditionalFields = new Dictionary { { titleFieldCode, "THAT TITLE" } }; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + } + // Remove the tag and check that it has been indeed removed Assert.IsTrue(theFile.RemoveTagFromFileAsync(tagType).GetAwaiter().GetResult()); diff --git a/ATL.unit-test/IO/MetaData/PSF.cs b/ATL.unit-test/IO/MetaData/PSF.cs index 259b2944..8cafadd9 100644 --- a/ATL.unit-test/IO/MetaData/PSF.cs +++ b/ATL.unit-test/IO/MetaData/PSF.cs @@ -40,6 +40,8 @@ public PSF() { emptyFile = "PSF/empty.psf"; notEmptyFile = "PSF/psf.psf"; + titleFieldCode = "title"; + tagType = MetaDataIOFactory.TagType.NATIVE; } [TestMethod] @@ -97,8 +99,33 @@ public void TagIO_RW_PSF_Empty() Assert.AreEqual("FPS", theFile.NativeTag.Genre); + // Setting a standard field using additional fields shouldn't be possible + theTag.Title = "THE TITLE"; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + Assert.IsTrue(theFile.ReadFromFile()); + + var meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + theTag.AdditionalFields = new Dictionary { { titleFieldCode, "THAT TITLE" } }; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + // Remove the tag and check that it has been indeed removed - Assert.IsTrue(theFile.RemoveTagFromFile(MetaDataIOFactory.TagType.NATIVE)); + Assert.IsTrue(theFile.RemoveTagFromFile(tagType)); Assert.IsTrue(theFile.ReadFromFile()); diff --git a/ATL.unit-test/IO/MetaData/VQF.cs b/ATL.unit-test/IO/MetaData/VQF.cs index ec2f9bb2..4ca407fd 100644 --- a/ATL.unit-test/IO/MetaData/VQF.cs +++ b/ATL.unit-test/IO/MetaData/VQF.cs @@ -40,6 +40,8 @@ public VQF() { emptyFile = "VQF/empty.vqf"; notEmptyFile = "VQF/vqf.vqf"; + titleFieldCode = "NAME"; + tagType = MetaDataIOFactory.TagType.NATIVE; } [TestMethod] @@ -100,6 +102,31 @@ public void TagIO_RW_VQF_Empty() Assert.AreEqual(22, theFile.NativeTag.TrackNumber); + // Setting a standard field using additional fields shouldn't be possible + theTag.Title = "THE TITLE"; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + Assert.IsTrue(theFile.ReadFromFile()); + + var meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + theTag.AdditionalFields = new Dictionary { { titleFieldCode, "THAT TITLE" } }; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + // Remove the tag and check that it has been indeed removed Assert.IsTrue(theFile.RemoveTagFromFile(MetaDataIOFactory.TagType.NATIVE)); diff --git a/ATL.unit-test/IO/MetaData/Vorbis_FLAC.cs b/ATL.unit-test/IO/MetaData/Vorbis_FLAC.cs index 902ad173..5a3f0b91 100644 --- a/ATL.unit-test/IO/MetaData/Vorbis_FLAC.cs +++ b/ATL.unit-test/IO/MetaData/Vorbis_FLAC.cs @@ -58,6 +58,8 @@ public Vorbis_FLAC() emptyFile = "FLAC/empty.flac"; notEmptyFile = "FLAC/flac.flac"; tagType = MetaDataIOFactory.TagType.NATIVE; + titleFieldCode = "TITLE"; + var pics = testData.EmbeddedPictures; foreach (PictureInfo pic in pics) pic.TagType = tagType; testData.EmbeddedPictures = pics; @@ -211,6 +213,32 @@ public void TagIO_RW_VorbisFLAC_Empty() Assert.AreEqual(550, theFile.NativeTag.BPM); + // Setting a standard field using additional fields shouldn't be possible + theTag.Title = "THE TITLE"; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + var meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + theTag.AdditionalFields = new Dictionary { { titleFieldCode, "THAT TITLE" } }; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + // Remove the tag and check that it has been indeed removed Assert.IsTrue(theFile.RemoveTagFromFile(MetaDataIOFactory.TagType.NATIVE)); diff --git a/ATL.unit-test/IO/MetaData/WAV.cs b/ATL.unit-test/IO/MetaData/WAV.cs index 5699bba8..c0ba1f09 100644 --- a/ATL.unit-test/IO/MetaData/WAV.cs +++ b/ATL.unit-test/IO/MetaData/WAV.cs @@ -22,6 +22,7 @@ public WAV() tagType = MetaDataIOFactory.TagType.NATIVE; supportsInternationalChars = false; supportsExtraEmbeddedPictures = false; + titleFieldCode = "info.INAM"; testData.TrackTotal = 0; } @@ -420,7 +421,7 @@ public void TagIO_RW_WAV_BEXT_Empty() } [TestMethod] - public void TagIO_RW_WAV_INFO_Empty() + public void TagIO_RW_WAV_LIST_INFO_Empty() { initListInfoTestData(); test_RW_Empty(emptyFile, true, true, true, true); diff --git a/ATL.unit-test/IO/MetaData/WMA.cs b/ATL.unit-test/IO/MetaData/WMA.cs index 922a161b..06cafcf6 100644 --- a/ATL.unit-test/IO/MetaData/WMA.cs +++ b/ATL.unit-test/IO/MetaData/WMA.cs @@ -57,6 +57,7 @@ public WMA() emptyFile = "WMA/empty_full.wma"; notEmptyFile = "WMA/wma.wma"; tagType = MetaDataIOFactory.TagType.NATIVE; + titleFieldCode = "WM/TITLE"; testData.Conductor = null; testData.Date = DateTime.MinValue; @@ -138,6 +139,32 @@ public void TagIO_RW_WMA_Empty() Assert.AreEqual("https://anywhe.re", theFile.NativeTag.AudioSourceUrl); + // Setting a standard field using additional fields shouldn't be possible + theTag.Title = "THE TITLE"; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + var meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + theTag.AdditionalFields = new Dictionary { { titleFieldCode, "THAT TITLE" } }; + + Assert.IsTrue(theFile.UpdateTagInFileAsync(theTag.tagData, tagType).GetAwaiter().GetResult()); + + Assert.IsTrue(theFile.ReadFromFile(false, true)); + + meta = theFile.getMeta(tagType); + Assert.IsNotNull(meta); + Assert.IsTrue(meta.Exists); + + Assert.AreEqual("THE TITLE", meta.Title); + + // Remove the tag and check that it has been indeed removed Assert.IsTrue(theFile.RemoveTagFromFile(MetaDataIOFactory.TagType.NATIVE)); diff --git a/ATL/AudioData/IO/APEtag.cs b/ATL/AudioData/IO/APEtag.cs index 3e1fca50..3417d192 100644 --- a/ATL/AudioData/IO/APEtag.cs +++ b/ATL/AudioData/IO/APEtag.cs @@ -415,6 +415,8 @@ private int write(TagData tag, BinaryWriter w) private int writeFrames(TagData tag, BinaryWriter w) { int nbFrames = 0; + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); // Picture fields (first before textual fields, since APE tag is located on the footer) foreach (PictureInfo picInfo in tag.Pictures) @@ -445,6 +447,7 @@ private int writeFrames(TagData tag, BinaryWriter w) { string value = formatBeforeWriting(frameType, tag, map); writeTextFrame(w, s, value); + writtenFieldCodes.Add(s.ToUpper()); nbFrames++; } break; @@ -455,7 +458,10 @@ private int writeFrames(TagData tag, BinaryWriter w) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion) + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) + ) { writeTextFrame(w, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); nbFrames++; diff --git a/ATL/AudioData/IO/ID3v2.cs b/ATL/AudioData/IO/ID3v2.cs index b4a5d80a..dd9ea7c1 100644 --- a/ATL/AudioData/IO/ID3v2.cs +++ b/ATL/AudioData/IO/ID3v2.cs @@ -1209,12 +1209,6 @@ public bool Read(Stream source, long offset, ReadTagParams readTagParams) return result; } - // Writes tag info using ID3v2.4 conventions - internal int writeInternal(TagData tag, BinaryWriter w) - { - return write(tag, w); - } - /// /// Writes the given tag into the given Writer using ID3v2.4 conventions /// @@ -1437,6 +1431,8 @@ private int writeFrames(TagData tag, BinaryWriter w, Encoding tagEncoding) IDictionary mapping = frameMapping_v24; if (3 == Settings.ID3v2_tagSubVersion) mapping = frameMapping_v23; + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); foreach (Field frameType in map.Keys) { @@ -1448,6 +1444,7 @@ private int writeFrames(TagData tag, BinaryWriter w, Encoding tagEncoding) string value = formatBeforeWriting(frameType, tag, map); writeTextFrame(w, s, value, tagEncoding); + writtenFieldCodes.Add(s.ToUpper()); nbFrames++; break; } @@ -1468,7 +1465,10 @@ private int writeFrames(TagData tag, BinaryWriter w, Encoding tagEncoding) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion) + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) + ) { var fieldCode = fieldInfo.NativeFieldCode; if (fieldCode.Equals(VorbisTag.VENDOR_METADATA_ID)) continue; // Specific mandatory field exclusive to VorbisComment diff --git a/ATL/AudioData/IO/MP4.cs b/ATL/AudioData/IO/MP4.cs index fd1340bc..58897467 100644 --- a/ATL/AudioData/IO/MP4.cs +++ b/ATL/AudioData/IO/MP4.cs @@ -1828,6 +1828,8 @@ private int writeMeta(TagData tag, BinaryWriter w) private int writeFrames(TagData tag, BinaryWriter w) { int counter = 0; + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); IDictionary map = tag.ToMap(); @@ -1843,6 +1845,7 @@ private int writeFrames(TagData tag, BinaryWriter w) string value = formatBeforeWriting(frameType, tag, map); writeTextFrame(w, s, value); + writtenFieldCodes.Add(s.ToUpper()); counter++; } break; @@ -1858,6 +1861,7 @@ private int writeFrames(TagData tag, BinaryWriter w) && !fieldInfo.MarkedForDeletion && !fieldInfo.NativeFieldCode.StartsWith("uuid.") && !fieldInfo.NativeFieldCode.StartsWith("xmp.") + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) ) { writeTextFrame(w, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); diff --git a/ATL/AudioData/IO/PSF.cs b/ATL/AudioData/IO/PSF.cs index 25dd58a5..cf7be5ea 100644 --- a/ATL/AudioData/IO/PSF.cs +++ b/ATL/AudioData/IO/PSF.cs @@ -404,6 +404,8 @@ protected override int write(TagData tag, Stream s, string zone) private int write(TagData tag, BinaryWriter w) { int result = 0; + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); w.Write(Utils.Latin1Encoding.GetBytes(TAG_HEADER)); @@ -423,6 +425,7 @@ private int write(TagData tag, BinaryWriter w) if (map[frameType].Length > 0) // No frame with empty value { writeTextFrame(w, s, map[frameType]); + writtenFieldCodes.Add(s.ToUpper()); result++; } break; @@ -433,7 +436,11 @@ private int write(TagData tag, BinaryWriter w) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion && !fieldInfo.NativeFieldCode.Equals("utf8")) // utf8 already written + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion + && !fieldInfo.NativeFieldCode.Equals("utf8") // utf8 already written + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) + ) { writeTextFrame(w, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); result++; diff --git a/ATL/AudioData/IO/TwinVQ.cs b/ATL/AudioData/IO/TwinVQ.cs index 8e96d02d..606bd24c 100644 --- a/ATL/AudioData/IO/TwinVQ.cs +++ b/ATL/AudioData/IO/TwinVQ.cs @@ -297,6 +297,9 @@ protected override bool read(Stream source, ReadTagParams readTagParams) protected override int write(TagData tag, Stream s, string zone) { int result = 0; + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); + string recordingYear = ""; IDictionary map = tag.ToMap(); @@ -326,6 +329,7 @@ protected override int write(TagData tag, Stream s, string zone) { string value = formatBeforeWriting(frameType, tag, map); writeTextFrame(s, str, value); + writtenFieldCodes.Add(str.ToUpper()); result++; } break; @@ -336,7 +340,10 @@ protected override int write(TagData tag, Stream s, string zone) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion && fieldInfo.NativeFieldCode.Length > 0) + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion && fieldInfo.NativeFieldCode.Length > 0 + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) + ) { writeTextFrame(s, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); result++; diff --git a/ATL/AudioData/IO/VorbisTag.cs b/ATL/AudioData/IO/VorbisTag.cs index b2e17543..2c899dbe 100644 --- a/ATL/AudioData/IO/VorbisTag.cs +++ b/ATL/AudioData/IO/VorbisTag.cs @@ -488,6 +488,8 @@ private uint writeFrames(TagData tag, BinaryWriter w) uint nbFrames = 0; IDictionary map = tag.ToMap(); + // Keep these in memory to prevent setting them twice using AdditionalFields + var writtenFieldCodes = new HashSet(); // Supported textual fields foreach (Field frameType in map.Keys) @@ -496,7 +498,7 @@ private uint writeFrames(TagData tag, BinaryWriter w) { if (frameType == frameMapping[s]) { - if (map[frameType].Length > 0) // No frame with empty value + if (map[frameType].Length > 0) // Don't write frames with empty values { string value = formatBeforeWriting(frameType, tag, map); if (frameType == Field.ARTIST && value.Contains(Settings.DisplayValueSeparator + "")) @@ -509,6 +511,7 @@ private uint writeFrames(TagData tag, BinaryWriter w) { writeTextFrame(w, s, value); } + writtenFieldCodes.Add(s.ToUpper()); nbFrames++; } break; @@ -522,7 +525,11 @@ private uint writeFrames(TagData tag, BinaryWriter w) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion && !fieldInfo.NativeFieldCode.Equals(VENDOR_METADATA_ID)) + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion + && !fieldInfo.NativeFieldCode.Equals(VENDOR_METADATA_ID) + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToUpper()) + ) { writeTextFrame(w, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); nbFrames++; diff --git a/ATL/AudioData/IO/WMA.cs b/ATL/AudioData/IO/WMA.cs index 0d7046b2..1b6a288d 100644 --- a/ATL/AudioData/IO/WMA.cs +++ b/ATL/AudioData/IO/WMA.cs @@ -8,6 +8,7 @@ using System.Collections.Concurrent; using static ATL.TagData; using System.Threading.Tasks; +using System.Reflection; namespace ATL.AudioData.IO { @@ -121,6 +122,8 @@ private void Reset() public static readonly IDictionary frameMappingLower = new Dictionary(); + public static readonly IDictionary invertedFrameMapping = new Dictionary(); + // Field that are embedded in standard ASF description, and do not need to be written in any other frame private static readonly IList embeddedFields = new List { @@ -143,6 +146,9 @@ private void Reset() private AudioDataManager.SizeInfo m_sizeInfo; + // Keep these in memory to prevent setting them twice using AdditionalFields + private readonly ISet m_writtenFieldCodes = new HashSet(); + // ---------- INFORMATIVE INTERFACE IMPLEMENTATIONS & MANDATORY OVERRIDES @@ -678,6 +684,12 @@ protected override bool read(Stream source, ReadTagParams readTagParams) return result; } + protected override void preprocessWrite(TagData dataToWrite) + { + base.preprocessWrite(dataToWrite); + m_writtenFieldCodes.Clear(); + } + /// protected override int write(TagData tag, Stream s, string zone) { @@ -691,15 +703,15 @@ private int write(TagData tag, BinaryWriter w, string zone) return zone switch { - ZONE_CONTENT_DESCRIPTION => writeContentDescription(tag, w), + ZONE_CONTENT_DESCRIPTION => writeContentDescription(tag, m_writtenFieldCodes, w), ZONE_EXTENDED_HEADER_METADATA => writeExtendedHeaderMeta(tag, w), ZONE_EXTENDED_HEADER_METADATA_LIBRARY => writeExtendedHeaderMetaLibrary(tag, w), - ZONE_EXTENDED_CONTENT_DESCRIPTION => writeExtendedContentDescription(tag, w), + ZONE_EXTENDED_CONTENT_DESCRIPTION => writeExtendedContentDescription(tag, m_writtenFieldCodes, w), _ => 0 }; } - private static int writeContentDescription(TagData tag, BinaryWriter w) + private static int writeContentDescription(TagData tag, ISet writtenFieldCodes, BinaryWriter w) { var beginPos = w.BaseStream.Position; w.Write(WMA_CONTENT_DESCRIPTION_ID); @@ -713,6 +725,13 @@ private static int writeContentDescription(TagData tag, BinaryWriter w) string rating = ""; IDictionary map = tag.ToMap(); + if (0 == invertedFrameMapping.Count) + { + foreach (var kvp in frameMapping) + { + invertedFrameMapping[kvp.Value] = kvp.Key.ToLower(); + } + } // Supported textual fields foreach (Field frameType in map.Keys) @@ -723,18 +742,23 @@ private static int writeContentDescription(TagData tag, BinaryWriter w) { case Field.TITLE: title = map[frameType]; + writtenFieldCodes.Add(invertedFrameMapping[frameType]); break; case Field.ARTIST: author = map[frameType]; + writtenFieldCodes.Add(invertedFrameMapping[frameType]); break; case Field.COPYRIGHT: copyright = map[frameType]; + writtenFieldCodes.Add(invertedFrameMapping[frameType]); break; case Field.COMMENT: comment = map[frameType]; + writtenFieldCodes.Add(invertedFrameMapping[frameType]); break; case Field.RATING: rating = map[frameType]; + writtenFieldCodes.Add(invertedFrameMapping[frameType]); break; } } @@ -761,7 +785,7 @@ private static int writeContentDescription(TagData tag, BinaryWriter w) return (title.Length > 0 ? 1 : 0) + (author.Length > 0 ? 1 : 0) + (copyright.Length > 0 ? 1 : 0) + (comment.Length > 0 ? 1 : 0) + (rating.Length > 0 ? 1 : 0); } - private int writeExtendedContentDescription(TagData tag, BinaryWriter w) + private int writeExtendedContentDescription(TagData tag, ISet writtenFieldCodes, BinaryWriter w) { ushort counter = 0; @@ -772,7 +796,7 @@ private int writeExtendedContentDescription(TagData tag, BinaryWriter w) var counterPos = w.BaseStream.Position; w.Write((ushort)0); // Counter placeholder to be rewritten at the end of the method - IDictionary map = tag.ToMap(); + IDictionary map = tag.ToMap(); // Supported textual fields foreach (Field frameType in map.Keys) @@ -786,6 +810,7 @@ private int writeExtendedContentDescription(TagData tag, BinaryWriter w) string value = formatBeforeWriting(frameType, tag, map); writeTextFrame(w, s, value); + writtenFieldCodes.Add(s.ToLower()); counter++; } break; @@ -796,7 +821,11 @@ private int writeExtendedContentDescription(TagData tag, BinaryWriter w) // Other textual fields foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { - if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) && !fieldInfo.MarkedForDeletion && (ZONE_EXTENDED_CONTENT_DESCRIPTION.Equals(fieldInfo.Zone) || "".Equals(fieldInfo.Zone))) + if ((fieldInfo.TagType.Equals(MetaDataIOFactory.TagType.ANY) || fieldInfo.TagType.Equals(getImplementedTagType())) + && !fieldInfo.MarkedForDeletion + && (ZONE_EXTENDED_CONTENT_DESCRIPTION.Equals(fieldInfo.Zone) || "".Equals(fieldInfo.Zone)) + && !writtenFieldCodes.Contains(fieldInfo.NativeFieldCode.ToLower()) + ) { writeTextFrame(w, fieldInfo.NativeFieldCode, FormatBeforeWriting(fieldInfo.Value)); counter++;