Skip to content

Commit

Permalink
Support for the "Encoded By" metadata [#245]
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeugma440 committed Jan 8, 2024
1 parent aac363f commit 4cc6341
Show file tree
Hide file tree
Showing 17 changed files with 94 additions and 44 deletions.
6 changes: 3 additions & 3 deletions ATL.unit-test/IO/HighLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,14 +653,14 @@ public void TagIO_RW_UpdateTagAdditionalField()
string testFileLocation = TestUtils.CopyAsTempTestFile("MP3/01 - Title Screen.mp3");
Track theTrack = new Track(testFileLocation);

theTrack.AdditionalFields["TENC"] = "update test";
theTrack.AdditionalFields["TOFN"] = "update test";
Assert.IsTrue(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"]);
Assert.IsTrue(theTrack.AdditionalFields.ContainsKey("TOFN"));
Assert.AreEqual("update test", theTrack.AdditionalFields["TOFN"]);

// Get rid of the working copy
if (Settings.DeleteAfterSuccess) File.Delete(testFileLocation);
Expand Down
1 change: 1 addition & 0 deletions ATL.unit-test/IO/MetaData/APE.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public APE()
testData.GeneralDescription = null;
testData.Date = DateTime.Parse("01/01/1997");
testData.Publisher = "abc publishing";
testData.EncodedBy = "enKoder";

// Initialize specific test data (Picture native codes are strings)
IList<PictureInfo> testPictureInfos = new List<PictureInfo>();
Expand Down
3 changes: 3 additions & 0 deletions ATL.unit-test/IO/MetaData/MetaIOTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ public void test_RW_Empty(
if (testData.SeriesPart != "") theTag.SeriesPart = "2";
if (testData.LongDescription != "") theTag.LongDescription = "LongDescription";
if (testData.BPM != 0) theTag.BPM = 550;
if (testData.EncodedBy != "") theTag.EncodedBy = "reaKtor";

if (testData.AdditionalFields != null && testData.AdditionalFields.Count > 0)
{
Expand Down Expand Up @@ -518,6 +519,7 @@ public void test_RW_Empty(
if (testData.SeriesPart != "") Assert.AreEqual("2", meta.SeriesPart);
if (testData.LongDescription != "") Assert.AreEqual("LongDescription", meta.LongDescription);
if (testData.BPM != 0) Assert.AreEqual(550, meta.BPM);
if (testData.EncodedBy != "") Assert.AreEqual("reaKtor", meta.EncodedBy);

if (testData.AdditionalFields != null && testData.AdditionalFields.Count > 0)
{
Expand Down Expand Up @@ -813,6 +815,7 @@ protected void readExistingTagsOnFile(AudioDataManager theFile, int nbPictures =
if (testData.GeneralDescription != "") Assert.AreEqual(testData.GeneralDescription, meta.GeneralDescription);
if (testData.ProductId != "") Assert.AreEqual(testData.ProductId, meta.ProductId);
if (testData.BPM != 0) Assert.AreEqual(testData.BPM, meta.BPM);
if (testData.EncodedBy != "") Assert.AreEqual(testData.EncodedBy, meta.EncodedBy);

// Additional fields
if (testData.AdditionalFields != null && testData.AdditionalFields.Count > 0)
Expand Down
2 changes: 1 addition & 1 deletion ATL.unit-test/IO/MetaData/WAV.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ private void initListInfoTestData()
testData.Date = System.DateTime.Parse("2018-01-09T01:23:45");
testData.TrackNumber = 5;
testData.Popularity = 0.2f;
testData.EncodedBy = "info.ITCH";

IDictionary<string, string> tags = new Dictionary<string, string>();
tags.Add(new KeyValuePair<string, string>("info.IARL", "info.IARL"));
Expand All @@ -77,7 +78,6 @@ private void initListInfoTestData()
tags.Add(new KeyValuePair<string, string>("info.ISFT", "info.ISFT"));
tags.Add(new KeyValuePair<string, string>("info.ISRC", "info.ISRC"));
tags.Add(new KeyValuePair<string, string>("info.ISRF", "info.ISRF"));
tags.Add(new KeyValuePair<string, string>("info.ITCH", "info.ITCH"));
testData.AdditionalFields = tags;
}

Expand Down
Binary file modified ATL.unit-test/Resources/MP3/APE.mp3
Binary file not shown.
16 changes: 16 additions & 0 deletions ATL/AudioData/CrossMetadataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,22 @@ public int? BPM
}
}

/// <inheritdoc/>
public string EncodedBy
{
get
{
string result = "";
foreach (IMetaDataIO reader in metaReaders)
{
result = reader.EncodedBy;
if (result != "") break;
}

return result;
}
}

/// <inheritdoc/>
public float? Popularity
{
Expand Down
3 changes: 2 additions & 1 deletion ATL/AudioData/IO/APEtag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public class APEtag : MetaDataIO
{ "CONDUCTOR", Field.CONDUCTOR },
{ "LYRICS", Field.LYRICS_UNSYNCH },
{ "PUBLISHER", Field.PUBLISHER },
{ "BPM", Field.BPM }
{ "BPM", Field.BPM },
{ "ENCODEDBY", Field.ENCODED_BY }
};


Expand Down
33 changes: 17 additions & 16 deletions ATL/AudioData/IO/Helpers/ListTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,22 @@ public static string FromStream(Stream source, MetaDataIO meta, ReadTagParams re
private static void readInfoPurpose(Stream source, MetaDataIO meta, ReadTagParams readTagParams, long chunkSize, long maxPos)
{
byte[] data = new byte[chunkSize];
string key, value;
int size;

while (source.Position < maxPos)
{
// Key
source.Read(data, 0, 4);
key = Utils.Latin1Encoding.GetString(data, 0, 4);
var key = Utils.Latin1Encoding.GetString(data, 0, 4);
// Size
source.Read(data, 0, 4);
size = StreamUtils.DecodeInt32(data);
var size = StreamUtils.DecodeInt32(data);
// Do _NOT_ use StreamUtils.ReadNullTerminatedString because non-textual fields may be found here (e.g. NITR)
if (size > 0)
{
source.Read(data, 0, size);
// Manage parasite zeroes at the end of data
if (source.Position < maxPos && source.ReadByte() != 0) source.Seek(-1, SeekOrigin.Current);
value = Encoding.UTF8.GetString(data, 0, size);
var value = Encoding.UTF8.GetString(data, 0, size);
meta.SetMetaField("info." + key, Utils.StripEndingZeroChars(value), readTagParams.ReadAllMetaFrames);

WavHelper.skipEndPadding(source, maxPos);
Expand All @@ -83,16 +81,15 @@ private static void readDataListPurpose(Stream source, MetaDataIO meta, ReadTagP
// Size
source.Read(data, 0, 4);
size = StreamUtils.DecodeInt32(data);
if (size > 0)
{
meta.SetMetaField("adtl.Labels[" + position + "].Type", id, readTagParams.ReadAllMetaFrames);
if (id.Equals(CHUNK_LABEL, StringComparison.OrdinalIgnoreCase)) readLabelSubChunk(source, meta, position, size, readTagParams);
else if (id.Equals(CHUNK_NOTE, StringComparison.OrdinalIgnoreCase)) readLabelSubChunk(source, meta, position, size, readTagParams);
else if (id.Equals(CHUNK_LABELED_TEXT, StringComparison.OrdinalIgnoreCase)) readLabeledTextSubChunk(source, meta, position, size, readTagParams);
if (size <= 0) continue;

WavHelper.skipEndPadding(source, maxPos);
position++;
}
meta.SetMetaField("adtl.Labels[" + position + "].Type", id, readTagParams.ReadAllMetaFrames);
if (id.Equals(CHUNK_LABEL, StringComparison.OrdinalIgnoreCase)) readLabelSubChunk(source, meta, position, size, readTagParams);
else if (id.Equals(CHUNK_NOTE, StringComparison.OrdinalIgnoreCase)) readLabelSubChunk(source, meta, position, size, readTagParams);
else if (id.Equals(CHUNK_LABELED_TEXT, StringComparison.OrdinalIgnoreCase)) readLabeledTextSubChunk(source, meta, position, size, readTagParams);

WavHelper.skipEndPadding(source, maxPos);
position++;
}
}

Expand Down Expand Up @@ -137,6 +134,7 @@ public static bool IsDataEligible(MetaDataIO meta)
if (meta.Copyright.Length > 0) return true;
if (meta.TrackNumber > 0) return true;
if (meta.Popularity > 0) return true;
if (meta.EncodedBy.Length > 0) return true;

return WavHelper.IsDataEligible(meta, "info.") || WavHelper.IsDataEligible(meta, "adtl.");
}
Expand Down Expand Up @@ -220,11 +218,14 @@ private static void writeInfoPurpose(BinaryWriter w, MetaDataIO meta)
if (value.Length > 0) writeSizeAndNullTerminatedString("IPRT", value, w, writtenFields);
if (value.Length > 0) writeSizeAndNullTerminatedString("ITRK", value, w, writtenFields);
}
// Encoded by
value = Utils.ProtectValue(meta.EncodedBy);
if (0 == value.Length && additionalFields.TryGetValue("info.ITCH", out var field5)) value = field5;
if (value.Length > 0) writeSizeAndNullTerminatedString("ITCH", value, w, writtenFields);

string shortKey;
foreach (var key in additionalFields.Keys.Where(key => key.StartsWith("info.")))
{
shortKey = key.Substring(5, key.Length - 5).ToUpper();
var shortKey = key.Substring(5, key.Length - 5).ToUpper();
if (!writtenFields.ContainsKey(key) && additionalFields[key].Length > 0)
writeSizeAndNullTerminatedString(shortKey, meta.FormatBeforeWriting(additionalFields[key]), w, writtenFields);
}
Expand Down
9 changes: 6 additions & 3 deletions ATL/AudioData/IO/ID3v2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ public class ID3v2 : MetaDataIO
{ "TCO", Field.GENRE },
{ "TCR", Field.COPYRIGHT },
{ "TPB", Field.PUBLISHER },
{ "TBP", Field.BPM }
{ "TBP", Field.BPM },
{ "TEN", Field.ENCODED_BY }
};

// Mapping between standard fields and ID3v2.3 identifiers
Expand Down Expand Up @@ -186,7 +187,8 @@ public class ID3v2 : MetaDataIO
{ "MVIN", Field.SERIES_PART}, // Not part of ID3v2.3 standard
{ "MVNM", Field.SERIES_TITLE }, // Not part of ID3v2.3 standard
{ "TDES", Field.LONG_DESCRIPTION }, // Not part of ID3v2.3 standard
{ "TBPM", Field.BPM }
{ "TBPM", Field.BPM },
{ "TENC", Field.ENCODED_BY }
};

// Mapping between standard fields and ID3v2.4 identifiers
Expand Down Expand Up @@ -218,7 +220,8 @@ public class ID3v2 : MetaDataIO
{ "MVIN", Field.SERIES_PART}, // Not part of ID3v2.4 standard
{ "MVNM", Field.SERIES_TITLE }, // Not part of ID3v2.4 standard
{ "TDES", Field.LONG_DESCRIPTION }, // Not part of ID3v2.4 standard
{ "TBPM", Field.BPM }
{ "TBPM", Field.BPM },
{ "TENC", Field.ENCODED_BY }
};

// Mapping between ID3v2.2/3 fields and ID3v2.4 fields not included in frameMapping_v2x, and that have changed between versions
Expand Down
11 changes: 4 additions & 7 deletions ATL/AudioData/IO/MP4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class MP4 : MetaDataIO, IAudioDataIO
{ "©mvi", Field.SERIES_PART},
{ "©mvn", Field.SERIES_TITLE },
{ "ldes", Field.LONG_DESCRIPTION },
{ "tmpo", Field.BPM }
{ "tmpo", Field.BPM },
{ "©enc", Field.ENCODED_BY }
};

// Mapping between MP4 frame codes and frame classes that aren't class 1 (UTF-8 text)
Expand Down Expand Up @@ -311,10 +312,6 @@ private void readQTChapters(BinaryReader source, IList<MP4Sample> chapterTextTra
/// <param name="readTagParams">Reading parameters</param>
private bool readMP4(BinaryReader source, ReadTagParams readTagParams)
{
long moovPosition;

uint atomSize;

byte[] data32 = new byte[4];

IList<long> audioTrackOffsets = new List<long>(); // Offset of all detected audio/video tracks (tracks with a media type of 'soun' or 'vide')
Expand All @@ -329,7 +326,7 @@ private bool readMP4(BinaryReader source, ReadTagParams readTagParams)

// FTYP atom
source.Read(data32, 0, 4);
atomSize = StreamUtils.DecodeBEUInt32(data32);
var atomSize = StreamUtils.DecodeBEUInt32(data32);
source.BaseStream.Seek(atomSize - 4, SeekOrigin.Current);

// MOOV atom
Expand All @@ -340,7 +337,7 @@ private bool readMP4(BinaryReader source, ReadTagParams readTagParams)
return false;
}

moovPosition = source.BaseStream.Position;
var moovPosition = source.BaseStream.Position;
if (readTagParams.PrepareForWriting)
{
structureHelper.AddSize(moovPosition - 8, moovSize, ZONE_MP4_NOUDTA);
Expand Down
3 changes: 2 additions & 1 deletion ATL/AudioData/IO/VorbisTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public class VorbisMetaDataBlockPicture
{ "ORIGINALDATE", Field.PUBLISHING_DATE },
{ "PRODUCTNUMBER", Field.PRODUCT_ID },
{ "LYRICS", Field.LYRICS_UNSYNCH },
{ "BPM", Field.BPM }
{ "BPM", Field.BPM },
{ "ENCODED-BY", Field.ENCODED_BY }
};

// Tweak to prevent/allow pictures to be written within the rest of metadata (OGG vs. FLAC behaviour)
Expand Down
23 changes: 12 additions & 11 deletions ATL/AudioData/IO/WAV.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class WAV : MetaDataIO, IAudioDataIO, IMetaDataEmbedder
{ "info.IRTD", Field.RATING },
{ "info.TRCK", Field.TRACK_NUMBER },
{ "info.IPRT", Field.TRACK_NUMBER },
{ "info.ITRK", Field.TRACK_NUMBER }
{ "info.ITRK", Field.TRACK_NUMBER },
{ "info.ITCH", Field.ENCODED_BY },
};


Expand Down Expand Up @@ -555,18 +556,18 @@ private int write(BinaryWriter w, string zone)
result += CueTag.ToStream(w, isLittleEndian, this);
break;
default:
{
if (zone.StartsWith(CHUNK_LIST) && List.IsDataEligible(this))
{
string[] zoneParts = zone.Split('.');
if (zoneParts.Length > 1) result += List.ToStream(w, isLittleEndian, zoneParts[1], this);
}
else if (zone.Equals(CHUNK_DISP + ".0") && DispTag.IsDataEligible(this)) result += DispTag.ToStream(w, isLittleEndian, this); // Process the 1st position as a whole
else if (zone.Equals(CHUNK_BEXT) && BextTag.IsDataEligible(this)) result += BextTag.ToStream(w, isLittleEndian, this);
else if (zone.Equals(CHUNK_IXML) && IXmlTag.IsDataEligible(this)) result += IXmlTag.ToStream(w, isLittleEndian, this);
if (zone.StartsWith(CHUNK_LIST) && List.IsDataEligible(this))
{
string[] zoneParts = zone.Split('.');
if (zoneParts.Length > 1) result += List.ToStream(w, isLittleEndian, zoneParts[1], this);
}
else if (zone.Equals(CHUNK_DISP + ".0") && DispTag.IsDataEligible(this)) result += DispTag.ToStream(w, isLittleEndian, this); // Process the 1st position as a whole
else if (zone.Equals(CHUNK_BEXT) && BextTag.IsDataEligible(this)) result += BextTag.ToStream(w, isLittleEndian, this);
else if (zone.Equals(CHUNK_IXML) && IXmlTag.IsDataEligible(this)) result += IXmlTag.ToStream(w, isLittleEndian, this);

break;
}
break;
}
}

return result;
Expand Down
1 change: 1 addition & 0 deletions ATL/AudioData/IO/WMA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ private void Reset()
{ "WM/TitleSortOrder", Field.SORT_TITLE },
{ "WM/ContentGroupDescription", Field.GROUP },
{ "WM/BeatsPerMinute", Field.BPM },
{ "WM/EncodedBy", Field.ENCODED_BY },
{ "WM/OriginalReleaseTime", Field.PUBLISHING_DATE } // De facto standard as specs don't define any dedicated field to store a full date
};

Expand Down
7 changes: 7 additions & 0 deletions ATL/AudioData/Interfaces/IMetaData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ string LongDescription
get;
}
/// <summary>
/// Person or organization that encoded the file
/// </summary>
string EncodedBy
{
get;
}
/// <summary>
/// Contains any other metadata field that is not represented by a getter in the above interface
/// NB1 : Use MetaDataHolder.DATETIME_PREFIX + DateTime.ToFileTime() to set dates. ATL will format them properly.
/// NB2 : when querying multi-stream files (e.g. MP4, ASF), this attribute will only return stream-independent properties of the whole file, in the first language available
Expand Down
7 changes: 7 additions & 0 deletions ATL/Entities/MetadataHolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@ public int? BPM
set => tagData.IntegrateValue(Field.BPM, (null == value) ? null : value.ToString());
}

/// <inheritdoc/>
public string EncodedBy
{
get => Utils.ProtectValue(tagData[Field.ENCODED_BY]);
set => tagData.IntegrateValue(Field.ENCODED_BY, value);
}

/// <summary>
/// Collection of fields that are not supported by ATL (i.e. not implemented by a getter/setter of MetaDataIO class; e.g. custom fields such as "MOOD")
/// NB : when querying multi-stream files (e.g. MP4, ASF), this attribute will only return stream-independent properties of the whole file, in the first language available
Expand Down
6 changes: 5 additions & 1 deletion ATL/Entities/TagData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,11 @@ public enum Field
/// <summary>
/// Beats per minute
/// </summary>
BPM = 37
BPM = 37,
/// <summary>
/// Person or organization that encoded the file
/// </summary>
ENCODED_BY = 38
}

private static readonly ICollection<Field> numericFields = new HashSet<Field>
Expand Down
7 changes: 7 additions & 0 deletions ATL/Entities/Track.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ public Track(Stream stream, string mimeType = "")
/// Beats per minute
/// </summary>
public int? BPM { get; set; }
/// <summary>
/// Person or organization that encoded the file
/// </summary>
public string EncodedBy{ get; set; }

/// <summary>
/// Recording Date (set to DateTime.MinValue to remove)
Expand Down Expand Up @@ -409,6 +413,7 @@ protected void Update(bool onlyReadEmbeddedPictures = false)
DiscTotal = update(metadata.DiscTotal);
Popularity = metadata.Popularity;
BPM = metadata.BPM;
EncodedBy = metadata.EncodedBy;

Chapters = metadata.Chapters;
ChaptersTableDescription = Utils.ProtectValue(metadata.ChaptersTableDescription);
Expand Down Expand Up @@ -469,6 +474,7 @@ internal TagData toTagData()
result.IntegrateValue(Field.SERIES_PART, SeriesPart);
result.IntegrateValue(Field.LONG_DESCRIPTION, LongDescription);
result.IntegrateValue(Field.BPM, toTagValue(BPM));
result.IntegrateValue(Field.ENCODED_BY, EncodedBy);
if (isYearExplicit)
{
result.IntegrateValue(Field.RECORDING_YEAR, toTagValue(Year));
Expand Down Expand Up @@ -709,6 +715,7 @@ public void CopyMetadataTo(Track t)
t.DiscTotal = DiscTotal;
t.Popularity = Popularity;
t.BPM = BPM;
t.EncodedBy = EncodedBy;

t.Chapters ??= new List<ChapterInfo>();
t.Chapters.Clear();
Expand Down

0 comments on commit 4cc6341

Please sign in to comment.