From dd5603ed82db4b4d127281fd7cfef94c142eaee9 Mon Sep 17 00:00:00 2001
From: 42Geese <0E5EB1FB2C894978ADAD6308@gmail.com>
Date: Mon, 3 Apr 2023 21:23:08 -0400
Subject: [PATCH 1/4] Audio Id3v2 add support for the Podcast tags
---
.../TaggingFormats/Id3V2Test.cs | 134 ++++++++-
src/TaglibSharp/Id3v2/FrameFactory.cs | 4 +
src/TaglibSharp/Id3v2/FrameTypes.cs | 6 +
.../Id3v2/Frames/PodcastFlagFrame.cs | 267 ++++++++++++++++++
src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs | 8 +
src/TaglibSharp/Id3v2/Tag.cs | 102 ++++++-
6 files changed, 517 insertions(+), 4 deletions(-)
create mode 100644 src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
diff --git a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
index 8b2ea676c..5ea8277dc 100644
--- a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
+++ b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
@@ -576,6 +576,126 @@ public void TestPictures ()
}
}
+ [Test]
+ public void TestPocastFlag ()
+ {
+ Tag tag = new Tag ();
+
+ // This property isn't supported in version 2.
+ for (byte version = 3; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsFalse (t.PodcastFlag, "Initial (False): " + m);
+ }, 3);
+
+ tag.PodcastFlag = true;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.IsTrue (t.PodcastFlag, "Value Set (!False): " + m);
+ }, 3);
+
+ tag.PodcastFlag = false;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsFalse (t.PodcastFlag, "Value Cleared (False): " + m);
+ }, 3);
+ }
+ }
+
+ [Test]
+ public void TestPodcastIdentifier ()
+ {
+ var tag = new Tag ();
+
+ // This property isn't supported in version 2.
+ for (byte version = 3; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastIdentifier, "Initial (Null): " + m);
+ }, 3);
+
+ tag.PodcastIdentifier = val_sing;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.AreEqual (val_sing, t.PodcastIdentifier, "Value Set (!Null): " + m);
+ }, 3);
+
+ tag.PodcastIdentifier = string.Empty;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastIdentifier, "Value Cleared (Null): " + m);
+ }, 3);
+ }
+ }
+
+ [Test]
+ public void TestPodcastFeed ()
+ {
+ var tag = new Tag ();
+
+ // This property isn't supported in version 2.
+ for (byte version = 3; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastFeed, "Initial (Null): " + m);
+ }, 3);
+
+ tag.PodcastFeed = val_sing;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.AreEqual (val_sing, t.PodcastFeed, "Value Set (!Null): " + m);
+ }, 3);
+
+ tag.PodcastFeed = string.Empty;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastFeed, "Value Cleared (Null): " + m);
+ }, 3);
+ }
+ }
+
+ [Test]
+ public void TestPodcastDescription ()
+ {
+ var tag = new Tag ();
+
+ // This property isn't supported in version 2.
+ for (byte version = 3; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastDescription, "Initial (Null): " + m);
+ }, 3);
+
+ tag.PodcastDescription = val_sing;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.AreEqual (val_sing, t.PodcastDescription, "Value Set (!Null): " + m);
+ }, 3);
+
+ tag.PodcastDescription = string.Empty;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsNull (t.PodcastDescription, "Value Cleared (Null): " + m);
+ }, 3);
+ }
+ }
+
[Test]
public void TestIsCompilation ()
{
@@ -1078,7 +1198,11 @@ public void TestClear ()
Publisher = "L",
ISRC = "M",
Length = "L",
- RemixedBy = "N"
+ RemixedBy = "N",
+ PodcastFlag = true,
+ PodcastDescription = "description here",
+ PodcastFeed = "https://example.org/feed.rss",
+ PodcastIdentifier = "unique id"
};
@@ -1109,6 +1233,10 @@ public void TestClear ()
Assert.IsNull (tag.ISRC, "ISRC");
Assert.IsNull (tag.Length, "Length");
Assert.IsNull (tag.RemixedBy, "RemixedBy");
+ Assert.IsFalse (tag.PodcastFlag, "PodcastFlag");
+ Assert.IsNull (tag.PodcastDescription, "PodcastDescription");
+ Assert.IsNull (tag.PodcastFeed, "PodcastFeed");
+ Assert.IsNull (tag.PodcastIdentifier, "PodcastIdentifier");
}
[Test]
@@ -1634,10 +1762,10 @@ public void TestInvolvedPersonsFrame ()
delegate void TagTestFunc (Tag tag, string msg);
- void TagTestWithSave (ref Tag tag, TagTestFunc testFunc)
+ void TagTestWithSave (ref Tag tag, TagTestFunc testFunc, byte minVersion = 2)
{
testFunc (tag, "Before Save");
- for (byte version = 2; version <= 4; version++) {
+ for (byte version = minVersion; version <= 4; version++) {
tag.Version = version;
tag = new Tag (tag.Render ());
testFunc (tag, "After Save, Version: " + version);
diff --git a/src/TaglibSharp/Id3v2/FrameFactory.cs b/src/TaglibSharp/Id3v2/FrameFactory.cs
index 9a1b9c5c6..309f3c2c9 100644
--- a/src/TaglibSharp/Id3v2/FrameFactory.cs
+++ b/src/TaglibSharp/Id3v2/FrameFactory.cs
@@ -289,6 +289,10 @@ public static Frame CreateFrame (ByteVector data, File file, ref int offset, byt
// Table of Contents (ID3v2 Chapter Frame Addendum)
if (header.FrameId == FrameType.CTOC)
return new TableOfContentsFrame (data, position, header, version);
+
+ // Podcast Flag Frame
+ if (header.FrameId == FrameType.PCST)
+ return new PodcastFlagFrame(data, position, header, version);
return new UnknownFrame (data, position, header, version);
}
diff --git a/src/TaglibSharp/Id3v2/FrameTypes.cs b/src/TaglibSharp/Id3v2/FrameTypes.cs
index 2fda85de8..84dbaf6c8 100644
--- a/src/TaglibSharp/Id3v2/FrameTypes.cs
+++ b/src/TaglibSharp/Id3v2/FrameTypes.cs
@@ -101,5 +101,11 @@ static class FrameType
public static readonly ReadOnlyByteVector WPUB = "WPUB";
public static readonly ReadOnlyByteVector WXXX = "WXXX";
public static readonly ReadOnlyByteVector ETCO = "ETCO";
+ public static readonly ReadOnlyByteVector PCST = "PCST"; // Podcast Flag Frame.
+ public static readonly ReadOnlyByteVector TDES = "TDES"; // Podcast Description Frame.
+ public static readonly ReadOnlyByteVector TGID = "TGID"; // Podcast Identifier Frame.
+ public static readonly ReadOnlyByteVector WFED = "WFED"; // Podcast Feed Url Frame.
+ public static readonly ReadOnlyByteVector TCAT = "TCAT"; // Podcast Category Frame.
+ public static readonly ReadOnlyByteVector TKWD = "TKWD"; // Podcast Keywords Frame.
}
}
diff --git a/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs b/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
new file mode 100644
index 000000000..ec7d1898a
--- /dev/null
+++ b/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
@@ -0,0 +1,267 @@
+//
+// PodcastFlagFrame.cs:
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+namespace TagLib.Id3v2
+{
+ ///
+ /// This class extends , implementing support for
+ /// Podcast Flag (PCST) Frames.
+ ///
+ ///
+ /// Getting and setting the podcast flag of a file.
+ ///
+ /// using TagLib;
+ /// using TagLib.Id3v2;
+ ///
+ /// public static class TrackUtil
+ /// {
+ /// public static bool GetPodcastFlag (string filename)
+ /// {
+ /// File file = File.Create (filename, ReadStyle.None);
+ /// Id3v2.Tag tag = file.GetTag (TagTypes.Id3v2, false) as Id3v2.Tag;
+ /// if (tag == null)
+ /// return false;
+ ///
+ /// PodcastFlagFrame frame = PodcastFlagFrame.Get (tag, false);
+ /// if (frame == null)
+ /// return false;
+ ///
+ /// return true;
+ /// }
+ ///
+ /// public static void SetPodcastFlag (string filename)
+ /// {
+ /// File file = File.Create (filename, ReadStyle.None);
+ /// Id3v2.Tag tag = file.GetTag (TagTypes.Id3v2, true) as Id3v2.Tag;
+ /// if (tag == null)
+ /// return;
+ ///
+ /// PodcastFlagFrame.Get (tag, true);
+ /// file.Save ();
+ /// }
+ /// }
+ ///
+ ///
+ /// #using <System.dll>
+ /// #using <taglib-sharp.dll>
+ ///
+ /// using System;
+ /// using TagLib;
+ /// using TagLib::Id3v2;
+ ///
+ /// public ref class TrackUtil abstract sealed
+ /// {
+ /// public:
+ /// static bool GetPodcastFlag (String^ filename)
+ /// {
+ /// File^ file = File.Create (filename, ReadStyle.None);
+ /// Id3v2::Tag^ tag = dynamic_cast<Id3v2::Tag^> (file.GetTag (TagTypes::Id3v2, false));
+ /// if (tag == null)
+ /// return false;
+ ///
+ /// PodcastFlagFrame^ frame = PodcastFlagFrame::Get (tag, false);
+ /// if (frame == null)
+ /// return false;
+ ///
+ /// return true;
+ /// }
+ ///
+ /// static void SetPodcastFlag (String^ filename)
+ /// {
+ /// File^ file = File::Create (filename, ReadStyle::None);
+ /// Id3v2.Tag^ tag = dynamic_cast<Id3v2::Tag^> (file.GetTag (TagTypes::Id3v2, true));
+ /// if (tag == null)
+ /// return;
+ ///
+ /// PodcastFlagFrame::Get (tag, true);
+ /// file->Save ();
+ /// }
+ /// }
+ ///
+ ///
+ public class PodcastFlagFrame : Frame
+ {
+ #region Constants
+
+ ///
+ /// The PodcastFlagFrame data is simply an array of 4 null bytes.
+ ///
+ private readonly static ReadOnlyByteVector ExpectedData = new ReadOnlyByteVector(new byte[] { 0x0, 0x0, 0x0, 0x0 });
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Constructs and initializes a new instance of .
+ ///
+ ///
+ /// When a frame is created, it is not automatically added to
+ /// the tag. Consider using for more
+ /// integrated frame creation.
+ ///
+ public PodcastFlagFrame () : base (FrameType.PCST, 4)
+ {
+ }
+
+ ///
+ /// Constructs and initializes a new instance of by reading its raw data in a
+ /// specified ID3v2 version.
+ ///
+ ///
+ /// A object starting with the raw
+ /// representation of the new frame.
+ ///
+ ///
+ /// A indicating the ID3v2 version the
+ /// raw frame is encoded in.
+ ///
+ public PodcastFlagFrame (ByteVector data, byte version)
+ : base (data, version)
+ {
+ SetData (data, 0, version, true);
+ }
+
+ ///
+ /// Constructs and initializes a new instance of by reading its raw data in a
+ /// specified ID3v2 version.
+ ///
+ ///
+ /// A object containing the raw
+ /// representation of the new frame.
+ ///
+ ///
+ /// A indicating at what offset in
+ /// the frame actually begins.
+ ///
+ ///
+ /// A containing the header of the
+ /// frame found at in the data.
+ ///
+ ///
+ /// A indicating the ID3v2 version the
+ /// raw frame is encoded in.
+ ///
+ protected internal PodcastFlagFrame (ByteVector data, int offset, FrameHeader header, byte version)
+ : base (header)
+ {
+ SetData (data, offset, version, false);
+ }
+
+ #endregion
+
+ #region Public Static Methods
+
+ ///
+ /// Gets a podcast flag frame from a specified tag, optionally
+ /// creating it if it does not exist.
+ ///
+ ///
+ /// A object to search in.
+ ///
+ ///
+ /// A specifying whether or not to create
+ /// and add a new frame to the tag if a match is not found.
+ ///
+ ///
+ /// A object containing the
+ /// matching frame, or if a match
+ /// wasn't found and is .
+ ///
+ public static PodcastFlagFrame Get (Tag tag, bool create)
+ {
+ PodcastFlagFrame pcst;
+ foreach (Frame frame in tag) {
+ pcst = frame as PodcastFlagFrame;
+
+ if (pcst != null)
+ return pcst;
+ }
+
+ if (!create)
+ return null;
+
+ pcst = new PodcastFlagFrame ();
+ tag.AddFrame (pcst);
+ return pcst;
+ }
+
+ #endregion
+
+
+
+ #region Protected Methods
+
+ ///
+ /// Populates the values in the current instance by parsing
+ /// its field data in a specified version.
+ ///
+ ///
+ /// A object containing the
+ /// extracted field data.
+ ///
+ ///
+ /// A indicating the ID3v2 version the
+ /// field data is encoded in.
+ ///
+ protected override void ParseFields (ByteVector data, byte version)
+ {
+ if (data.CompareTo(ExpectedData) != 0)
+ throw new CorruptFileException ("Podcast flag value is incorrect.");
+ }
+
+ ///
+ /// Renders the values in the current instance into field
+ /// data for a specified version.
+ ///
+ ///
+ /// A indicating the ID3v2 version the
+ /// field data is to be encoded in.
+ ///
+ ///
+ /// A object containing the
+ /// rendered field data.
+ ///
+ protected override ByteVector RenderFields (byte version)
+ {
+ ByteVector data = new ByteVector(ExpectedData);
+
+ return data;
+ }
+
+ #endregion
+
+
+
+ #region ICloneable
+
+ ///
+ /// Creates a deep copy of the current instance.
+ ///
+ ///
+ /// A new object identical to the
+ /// current instance.
+ ///
+ public override Frame Clone ()
+ {
+ Frame frame = new PodcastFlagFrame();
+
+ return frame;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs b/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs
index 3a7bee876..86722d0e7 100644
--- a/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs
+++ b/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs
@@ -386,6 +386,11 @@ protected void ParseRawData ()
ByteVector delim = ByteVector.TextDelimiter (encoding);
+ // The WFED has a leading null byte so we remove it during parsing.
+ if (FrameId == FrameType.WFED && data.Count > 1 && data[0] == (byte) 0) {
+ data.RemoveAt(0);
+ }
+
if (FrameId != FrameType.WXXX) {
field_list.AddRange (data.ToStrings (StringType.Latin1, 0));
} else if (data.Count > 1 && !data.Mid (0,
@@ -439,6 +444,9 @@ protected override ByteVector RenderFields (byte version)
if (wxxx)
v = new ByteVector ((byte)encoding);
+ // The WFED has a leading null byte so we add it during render.
+ else if (FrameId == FrameType.WFED)
+ v = new ByteVector ((byte)0);
else
v = new ByteVector ();
string[] text = text_fields;
diff --git a/src/TaglibSharp/Id3v2/Tag.cs b/src/TaglibSharp/Id3v2/Tag.cs
index 1c3676ff7..9dd285443 100644
--- a/src/TaglibSharp/Id3v2/Tag.cs
+++ b/src/TaglibSharp/Id3v2/Tag.cs
@@ -31,6 +31,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Text;
namespace TagLib.Id3v2
@@ -2183,7 +2184,6 @@ public override double ReplayGainAlbumGain {
}
set {
if (double.IsNaN (value)) {
- SetUserTextAsString ("REPLAYGAIN_ALBUM_GAIN", null, false);
} else {
string text = value.ToString ("0.00 dB", CultureInfo.InvariantCulture);
SetUserTextAsString ("REPLAYGAIN_ALBUM_GAIN", text, false);
@@ -2331,6 +2331,106 @@ public override IPicture[] Pictures {
}
}
+ ///
+ /// Gets and sets the podcast flag of the media represented by the
+ /// current instance.
+ ///
+ ///
+ /// A object containing the podcast flag of the song.
+ ///
+ ///
+ /// This property is implemented using the "PCST" field.
+ /// This property is supported in version 3 forward.
+ ///
+ public bool PodcastFlag
+ {
+ get
+ {
+ IEnumerable items = this.GetFrames(FrameType.PCST);
+
+ if (items == null || items.Count() <= 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ set
+ {
+ if (Version < 3)
+ throw new InvalidOperationException("Version must be at least 3.");
+
+ if (PodcastFlag == value) {
+ // No change
+ } else if (value) {
+ PodcastFlagFrame frame = new PodcastFlagFrame();
+ AddFrame(frame);
+ } else {
+ RemoveFrames(FrameType.PCST);
+ }
+ }
+ }
+
+ ///
+ /// Gets and sets the podcast identifier of the song.
+ ///
+ ///
+ /// A object containing the podcast identifier of the song.
+ ///
+ ///
+ /// This property is implemented using the "TGID" field.
+ /// This property is supported in version 3 forward.
+ ///
+ public string PodcastIdentifier {
+ get { return GetTextAsString (FrameType.TGID); }
+ set
+ {
+ if (Version < 3)
+ throw new InvalidOperationException("Version must be at least 3.");
+
+ SetTextFrame (FrameType.TGID, value);
+ }
+ }
+
+ ///
+ /// Gets and sets the podcast feed of the song.
+ ///
+ ///
+ /// A object containing the podcast feed of the song.
+ ///
+ ///
+ /// This property is implemented using the "WFED" field.
+ /// This property is supported in version 3 forward.
+ ///
+ public string PodcastFeed {
+ get { return GetTextAsString (FrameType.WFED); }
+ set {
+ if (Version < 3)
+ throw new InvalidOperationException("Version must be at least 3.");
+
+ SetTextFrame (FrameType.WFED, value);
+ }
+ }
+
+ ///
+ /// Gets and sets the podcast description of the song.
+ ///
+ ///
+ /// A object containing the podcast description of the song.
+ ///
+ ///
+ /// This property is implemented using the "TDES" field.
+ /// This property is supported in version 3 forward.
+ ///
+ public string PodcastDescription {
+ get { return GetTextAsString (FrameType.TDES); }
+ set {
+ if (Version < 3)
+ throw new InvalidOperationException("Version must be at least 3.");
+
+ SetTextFrame (FrameType.TDES, value);
+ }
+ }
+
///
/// Gets whether or not the current instance is empty.
///
From f1b2a86fd2544ff6ae80586888fa7d24c25036ce Mon Sep 17 00:00:00 2001
From: 42Geese <0E5EB1FB2C894978ADAD6308@gmail.com>
Date: Mon, 3 Apr 2023 21:25:36 -0400
Subject: [PATCH 2/4] Fixing indent and spacing on new class.
---
src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs b/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
index ec7d1898a..ea41a3030 100644
--- a/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
+++ b/src/TaglibSharp/Id3v2/Frames/PodcastFlagFrame.cs
@@ -201,8 +201,6 @@ public static PodcastFlagFrame Get (Tag tag, bool create)
#endregion
-
-
#region Protected Methods
///
@@ -244,8 +242,6 @@ protected override ByteVector RenderFields (byte version)
#endregion
-
-
#region ICloneable
///
From f01d080d50737200df9cb5ae5c248bf0f835c461 Mon Sep 17 00:00:00 2001
From: 42Geese <129562715+42Geese@users.noreply.github.com>
Date: Fri, 9 Jun 2023 21:20:50 -0400
Subject: [PATCH 3/4] Audio Id3v2 Add Support For EncodedBy (#4)
---
.../TaggingFormats/Id3V2Test.cs | 29 +++++++++++++++++++
src/TaglibSharp/Id3v2/FrameTypes.cs | 1 +
src/TaglibSharp/Id3v2/Tag.cs | 14 +++++++++
3 files changed, 44 insertions(+)
diff --git a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
index 8b2ea676c..d3394761a 100644
--- a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
+++ b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
@@ -968,6 +968,35 @@ public void TestPublisher ()
}
}
+
+ [Test]
+ public void TestEncodedBy ()
+ {
+ Tag tag = new Tag ();
+ for (byte version = 2; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsNull (t.EncodedBy, "Initial (Null): " + m);
+ });
+
+ tag.EncodedBy = val_sing;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.AreEqual (val_sing, t.EncodedBy, "Value Set (!Null): " + m);
+ });
+
+ tag.EncodedBy = string.Empty;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsNull (t.EncodedBy, "Value Cleared (Null): " + m);
+ });
+ }
+ }
+
[Test]
public void TestISRC ()
{
diff --git a/src/TaglibSharp/Id3v2/FrameTypes.cs b/src/TaglibSharp/Id3v2/FrameTypes.cs
index 2fda85de8..36b86c078 100644
--- a/src/TaglibSharp/Id3v2/FrameTypes.cs
+++ b/src/TaglibSharp/Id3v2/FrameTypes.cs
@@ -101,5 +101,6 @@ static class FrameType
public static readonly ReadOnlyByteVector WPUB = "WPUB";
public static readonly ReadOnlyByteVector WXXX = "WXXX";
public static readonly ReadOnlyByteVector ETCO = "ETCO";
+ public static readonly ReadOnlyByteVector TENC = "TENC"; // Encoded By Frame.
}
}
diff --git a/src/TaglibSharp/Id3v2/Tag.cs b/src/TaglibSharp/Id3v2/Tag.cs
index 1c3676ff7..d38a9ae5c 100644
--- a/src/TaglibSharp/Id3v2/Tag.cs
+++ b/src/TaglibSharp/Id3v2/Tag.cs
@@ -2265,6 +2265,20 @@ public override string Publisher {
set { SetTextFrame (FrameType.TPUB, value); }
}
+ ///
+ /// Gets and sets the TENC (Encoded by) of the song.
+ ///
+ ///
+ /// A object containing the TENC of the song.
+ ///
+ ///
+ /// This property is implemented using the "TENC" field.
+ ///
+ public string EncodedBy {
+ get { return GetTextAsString (FrameType.TENC); }
+ set { SetTextFrame (FrameType.TENC, value); }
+ }
+
///
/// Gets and sets the ISRC (International Standard Recording Code) of the song.
///
From d31280cc16dbdf2d65aa7ca5fba6b7730a314143 Mon Sep 17 00:00:00 2001
From: 42Geese <129562715+42Geese@users.noreply.github.com>
Date: Fri, 9 Jun 2023 21:21:44 -0400
Subject: [PATCH 4/4] Audio Id3v2 Add Support For The Release Date (#3)
---
.../TaggingFormats/Id3V2Test.cs | 34 ++++++++++++++-
src/TaglibSharp/Id3v2/FrameTypes.cs | 1 +
src/TaglibSharp/Id3v2/Tag.cs | 42 +++++++++++++++++++
3 files changed, 75 insertions(+), 2 deletions(-)
diff --git a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
index d3394761a..1f6fc3a97 100644
--- a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
+++ b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs
@@ -18,6 +18,8 @@ public class Id3V2Test
static readonly string[] val_gnre = {"Rap",
"Jazz", "Non-Genre", "Blues"};
+ static readonly System.DateTime val_date = new System.DateTime (2022, 10, 20, 16, 45, 23, 0, 0);
+
[Test]
public void TestTitle ()
{
@@ -1025,6 +1027,34 @@ public void TestISRC ()
}
}
+ [Test]
+ public void TestReleaseDate ()
+ {
+ Tag tag = new Tag ();
+ for (byte version = 4; version <= 4; version++) {
+ tag.Version = version;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Initial (IsEmpty): " + m);
+ Assert.IsNull (t.ReleaseDate, "Initial (Null): " + m);
+ }, 4);
+
+ tag.ReleaseDate = val_date;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsFalse (t.IsEmpty, "Value Set (!IsEmpty): " + m);
+ Assert.AreEqual (val_date, t.ReleaseDate.Value, "Value Set (!Null): " + m);
+ }, 4);
+
+ tag.ReleaseDate = null;
+
+ TagTestWithSave (ref tag, delegate (Tag t, string m) {
+ Assert.IsTrue (t.IsEmpty, "Value Cleared (IsEmpty): " + m);
+ Assert.IsNull (t.ReleaseDate, "Value Cleared (Null): " + m);
+ }, 4);
+ }
+ }
+
[Test]
public void TestLength ()
{
@@ -1663,10 +1693,10 @@ public void TestInvolvedPersonsFrame ()
delegate void TagTestFunc (Tag tag, string msg);
- void TagTestWithSave (ref Tag tag, TagTestFunc testFunc)
+ void TagTestWithSave (ref Tag tag, TagTestFunc testFunc, byte minVersion = 2)
{
testFunc (tag, "Before Save");
- for (byte version = 2; version <= 4; version++) {
+ for (byte version = minVersion; version <= 4; version++) {
tag.Version = version;
tag = new Tag (tag.Render ());
testFunc (tag, "After Save, Version: " + version);
diff --git a/src/TaglibSharp/Id3v2/FrameTypes.cs b/src/TaglibSharp/Id3v2/FrameTypes.cs
index 36b86c078..7cad5ec6f 100644
--- a/src/TaglibSharp/Id3v2/FrameTypes.cs
+++ b/src/TaglibSharp/Id3v2/FrameTypes.cs
@@ -101,6 +101,7 @@ static class FrameType
public static readonly ReadOnlyByteVector WPUB = "WPUB";
public static readonly ReadOnlyByteVector WXXX = "WXXX";
public static readonly ReadOnlyByteVector ETCO = "ETCO";
+ public static readonly ReadOnlyByteVector TDRL = "TDRL"; // Release Time Frame
public static readonly ReadOnlyByteVector TENC = "TENC"; // Encoded By Frame.
}
}
diff --git a/src/TaglibSharp/Id3v2/Tag.cs b/src/TaglibSharp/Id3v2/Tag.cs
index d38a9ae5c..da236b4c7 100644
--- a/src/TaglibSharp/Id3v2/Tag.cs
+++ b/src/TaglibSharp/Id3v2/Tag.cs
@@ -2293,6 +2293,48 @@ public override string ISRC {
set { SetTextFrame (FrameType.TSRC, value); }
}
+ ///
+ /// Gets and sets the date at which the song has been released.
+ ///
+ ///
+ /// A nullable object containing the
+ /// date at which the song has been released, or if no value present.
+ ///
+ ///
+ /// This property is implemented using the "TDRL" field.
+ /// This is a ID3v2.4 type tag.
+ ///
+ public DateTime? ReleaseDate {
+ get {
+ string value = GetTextAsString (FrameType.TDRL);
+
+ if (String.IsNullOrWhiteSpace(value)) {
+ return null;
+ } else if (DateTime.TryParseExact (value.Replace ('T', ' '), "yyyy-MM-dd HH:mm:ss", null, DateTimeStyles.None, out DateTime exactDate)) {
+ return exactDate;
+ } else if (DateTime.TryParse(value, out DateTime parsedDate)) {
+ return parsedDate;
+ }
+
+ return null;
+ }
+ set {
+ string date = null;
+
+ if (value != null) {
+ date = $"{value:yyyy-MM-dd HH:mm:ss}";
+ date = date.Replace (' ', 'T');
+ }
+
+ if (date == null) {
+ RemoveFrames(FrameType.TDRL);
+ } else {
+ SetTextFrame(FrameType.TDRL, date);
+ }
+ }
+ }
+
///
/// Gets and sets the length of the media represented
/// by the current instance.