diff --git a/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs b/src/TaglibSharp.Tests/TaggingFormats/Id3V2Test.cs index 6c6b2381..aa74277a 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 string val_url = "https://example.com/data"; + [Test] public void TestTitle () { @@ -1555,6 +1557,43 @@ public void TestUserTextInformationFrame () }); } + [Test] + public void TestUrlLinkFrame () + { + var frame = new UrlLinkFrame ("WPUB") { + Url = val_url + }; + + FrameTest (frame, 3, + delegate (Frame f, StringType e) { }, + (d, v) => new UrlLinkFrame (d, v), + + delegate (Frame f, string m) { + var g = (f as UrlLinkFrame); + Assert.AreEqual ("WPUB", g.FrameId, m); + Assert.AreEqual (val_url, g.Url, m); + }); + } + + [Test] + public void TestUserUrlLinkFrame () + { + var frame = new UserUrlLinkFrame(val_sing) { + Url = val_url + }; + + FrameTest (frame, 3, + delegate (Frame f, StringType e) { }, + (d, v) => new UserUrlLinkFrame (d, v), + + delegate (Frame f, string m) { + var g = (f as UserUrlLinkFrame); + Assert.AreEqual ("WXXX", g.FrameId, m); + Assert.AreEqual (val_sing, g.Description, m); + Assert.AreEqual (val_url, g.Url, m); + }); + } + [Test] public void TestMovementNameFrame () { @@ -1777,7 +1816,7 @@ void FrameTest (Frame frame, byte minVersion, var tmp = frame.Render (version); //Extras.DumpHex (tmp.Data); frame = createFunc (tmp, version); - testFunc (frame, "Render: Version " + version + "; Encoding " + (StringType)encoding); + testFunc (frame, "Render: Version " + version + "; Encoding " + (StringType)encoding); frame = frame.Clone (); testFunc (frame, "Clone: Version " + version + "; Encoding " + (StringType)encoding); } diff --git a/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs b/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs index 3a7bee87..332966c5 100644 --- a/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs +++ b/src/TaglibSharp/Id3v2/Frames/UrlLinkFrame.cs @@ -28,8 +28,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; using System.Text; + namespace TagLib.Id3v2 { /// @@ -110,15 +114,7 @@ public class UrlLinkFrame : Frame { #region Private Fields - /// - /// Contains the encoding to use for the text. - /// - StringType encoding = StringType.Latin1; - - /// - /// Contains the text fields. - /// - string[] text_fields = new string[0]; + string url; /// /// Contains the raw data from the frame, or @@ -129,7 +125,7 @@ public class UrlLinkFrame : Frame /// it is parsed on demand, reducing the ammount of /// unnecessary conversion. /// - ByteVector raw_data; + protected ByteVector raw_data; /// /// Contains the ID3v2 version of . @@ -198,6 +194,9 @@ public UrlLinkFrame (ByteVector data, byte version) protected internal UrlLinkFrame (ByteVector data, int offset, FrameHeader header, byte version) : base (header) { + if (data[offset] != (byte)'W') { + throw new ArgumentException("Invalid header data. Expecting a WNNN frame"); + } SetData (data, offset, version, false); } @@ -229,40 +228,36 @@ protected internal UrlLinkFrame (ByteVector data, int offset, FrameHeader header /// /* Replacing the value completely: */ /// frame.Text = new string [] {"http://www.somewhere.com"}; /// + [Obsolete("Use property Url instead")] public virtual string[] Text { get { - ParseRawData (); - return (string[])text_fields.Clone (); + return new string[] { Url }; } set { - raw_data = null; - text_fields = value != null ? - (string[])value.Clone () : - new string[0]; + if (value?.Length > 0){ + raw_data = null; + Url = value[0]; + return; + } + + throw new ArgumentException ("Text must be a one-element array"); } } /// - /// Gets and sets the text encoding to use when rendering - /// the current instance. + /// Gets or sets the url of the frame. /// - /// - /// A value specifying the encoding - /// to use when rendering the current instance. - /// - /// - /// This value will be overwritten if - /// is - /// . - /// - public StringType TextEncoding { + public string Url { get { - ParseRawData (); - return encoding; + ParseRawData(); + return url; + } + set { + url = value; } - set { encoding = value; } } + #endregion #region Public Methods @@ -276,7 +271,7 @@ public StringType TextEncoding { public override string ToString () { ParseRawData (); - return string.Join ("; ", Text); + return Url; } #endregion @@ -365,6 +360,17 @@ protected override void ParseFields (ByteVector data, byte version) /// /// Performs the actual parsing of the raw data. + /// + /// + /// With these frames dynamic data such as webpages with touring information, price information or plain ordinary news can be added to the tag. + /// There may only be one URL link frame of its kind in an tag, except when stated otherwise in the frame description. + /// If the textstring is followed by a termination ($00 (00)) all the following information should be ignored and not be displayed. + /// All URL link frame identifiers begins with "W". + /// Only URL link frame identifiers begins with "W". All URL link frames have the following format: + /// + /// <Header for 'URL link frame', ID: "W000" - "WZZZ", excluding "WXXX" described in 4.3.2.> + /// URL<text string> + /// /// /// /// Because of the high parsing cost and relatively low usage @@ -374,44 +380,14 @@ protected override void ParseFields (ByteVector data, byte version) /// this method is called, and only on the first call does it /// actually parse the data. /// - protected void ParseRawData () + protected virtual void ParseRawData () { if (raw_data == null) return; ByteVector data = raw_data; raw_data = null; - - var field_list = new List (); - - ByteVector delim = ByteVector.TextDelimiter (encoding); - - if (FrameId != FrameType.WXXX) { - field_list.AddRange (data.ToStrings (StringType.Latin1, 0)); - } else if (data.Count > 1 && !data.Mid (0, - delim.Count).Equals (delim)) { - string value = data.ToString (StringType.Latin1, 1, - data.Count - 1); - - // Do a fast removal of end bytes. - if (value.Length > 1 && - value[value.Length - 1] == 0) - for (int i = value.Length - 1; i >= 0; i--) - if (value[i] != 0) { - value = value.Substring (0, i + 1); - break; - } - - field_list.Add (value); - } - - // Bad tags may have one or more nul characters at the - // end of a string, resulting in empty strings at the - // end of the FieldList. Strip them off. - while (field_list.Count != 0 && string.IsNullOrEmpty (field_list[field_list.Count - 1])) - field_list.RemoveAt (field_list.Count - 1); - - text_fields = field_list.ToArray (); + Url = data.ToString (); } /// @@ -431,33 +407,7 @@ protected override ByteVector RenderFields (byte version) if (raw_data != null && raw_version == version) return raw_data; - StringType encoding = CorrectEncoding (TextEncoding, version); - - bool wxxx = FrameId == FrameType.WXXX; - - ByteVector v; - - if (wxxx) - v = new ByteVector ((byte)encoding); - else - v = new ByteVector (); - string[] text = text_fields; - - if (version > 3 || wxxx) { - if (wxxx) { - if (text.Length == 0) - text = new string[] { null, null }; - else if (text.Length == 1) - text = new[] {text [0], - null}; - } - - v.Add (ByteVector.FromString (string.Join ("/", text), StringType.Latin1)); - } else { - v.Add (ByteVector.FromString (string.Join ("/", text), StringType.Latin1)); - } - - return v; + return ByteVector.FromString (Url, StringType.Latin1); } @@ -476,15 +426,7 @@ protected override ByteVector RenderFields (byte version) /// public override Frame Clone () { - UrlLinkFrame frame = (this is UserUrlLinkFrame) ? - new UserUrlLinkFrame (null, encoding) : new UrlLinkFrame (FrameId); - - frame.text_fields = (string[])text_fields.Clone (); - if (raw_data != null) - frame.raw_data = new ByteVector (raw_data); - - frame.raw_version = raw_version; - return frame; + return new UrlLinkFrame (header.FrameId) { Url = Url, raw_version = raw_version }; } #endregion @@ -496,8 +438,11 @@ public override Frame Clone () /// This class extends to provide /// support for ID3v2 User Url Link (WXXX) Frames. /// - public class UserUrlLinkFrame : UrlLinkFrame + public sealed class UserUrlLinkFrame : UrlLinkFrame { + string description; + private StringType descriptionEncoding; + #region Constructors /// @@ -522,7 +467,11 @@ public class UserUrlLinkFrame : UrlLinkFrame public UserUrlLinkFrame (string description, StringType encoding) : base (FrameType.WXXX) { - base.Text = new[] { description }; + if (string.IsNullOrEmpty (description)) { + throw new ArgumentException ("A description must not be null or empty."); + } + Description = description; + DescriptionEncoding = encoding; } /// @@ -541,9 +490,8 @@ public UserUrlLinkFrame (string description, StringType encoding) /// creation. /// public UserUrlLinkFrame (string description) - : base (FrameType.WXXX) + : this(description, StringType.Latin1) { - base.Text = new[] { description }; } /// @@ -585,7 +533,7 @@ public UserUrlLinkFrame (ByteVector data, byte version) /// A indicating the ID3v2 version the /// raw frame is encoded in. /// - protected internal UserUrlLinkFrame (ByteVector data, int offset, FrameHeader header, byte version) + internal UserUrlLinkFrame (ByteVector data, int offset, FrameHeader header, byte version) : base (data, offset, header, version) { } @@ -610,19 +558,10 @@ protected internal UserUrlLinkFrame (ByteVector data, int offset, FrameHeader he /// public string Description { get { - string[] text = base.Text; - return text.Length > 0 ? text[0] : null; - } - - set { - string[] text = base.Text; - if (text.Length > 0) - text[0] = value; - else - text = new[] { value }; - - base.Text = text; + ParseRawData(); + return description; } + set => description = value; } /// @@ -638,29 +577,26 @@ public string Description { /// not modify the contents of the current instance. The /// value must be reassigned for the value to change. /// + [Obsolete("Use property Url instead.")] public override string[] Text { get { - string[] text = base.Text; - if (text.Length < 2) - return new string[0]; - - string[] new_text = new string[text.Length - 1]; - for (int i = 0; i < new_text.Length; i++) - new_text[i] = text[i + 1]; - - return new_text; + return new string[] { Url }; } set { - string[] new_value = new string[ - value?.Length + 1 ?? 1]; - - new_value[0] = Description; - - for (int i = 1; i < new_value.Length; i++) - new_value[i] = value[i - 1]; + + Url = value[0]; + } + } - base.Text = new_value; + /// + /// The encoding of the description. Defaults to . + /// + public StringType DescriptionEncoding { + get { + ParseRawData(); + return descriptionEncoding; } + set => descriptionEncoding = value; } #endregion @@ -680,12 +616,72 @@ public override string ToString () return new StringBuilder ().Append ("[") .Append (Description) .Append ("] ") - .Append (base.ToString ()).ToString (); + .Append (Url).ToString (); } #endregion + /// + /// Performs the actual parsing of the raw data. + /// + /// + /// This frame is intended for URL links concerning the audiofile in a similar way to the other "W"-frames. + /// The frame body consists of a description of the string, represented as a terminated string, followed by the actual URL. + /// The URL is always encoded with ISO-8859-1. + /// There may be more than one "WXXX" frame in each tag, but only one with the same description. + /// + /// + /// + /// Text encoding $xx + /// Description $00 (00) + /// URL + /// ]]> + /// + /// + protected override void ParseRawData () + { + if (raw_data == null) { + return; + } + + ByteVector data = raw_data; + raw_data = null; + + var descUrlSplit = data.Find (ByteVector.TextDelimiter (StringType.Latin1), 1); + var urlStartIndex = descUrlSplit + 1; + var urlEndOffset = data.Find (ByteVector.TextDelimiter (StringType.Latin1), urlStartIndex); + if (urlEndOffset == -1) { + urlEndOffset = data.Count; + } + + var descriptionText = data.ToString (StringType.Latin1, 1, descUrlSplit - 1); + var url = data.ToString (StringType.Latin1, urlStartIndex, urlEndOffset - urlStartIndex); + + Description = descriptionText; + Url = url; + } + + /// + public override Frame Clone () + { + return new UserUrlLinkFrame (Description, DescriptionEncoding) { Url = Url }; + } + + /// + protected override ByteVector RenderFields (byte version) + { + // render the frame header + var result = new ByteVector (); + result.Add((byte)DescriptionEncoding); + result.Add (ByteVector.FromString(Description, DescriptionEncoding)); + result.Add (0); + result.Add (ByteVector.FromString(Url, StringType.Latin1)); + result.Add (0); + + return result; + } #region Public Static Methods diff --git a/src/TaglibSharp/Id3v2/Tag.cs b/src/TaglibSharp/Id3v2/Tag.cs index cd669dc0..981a20c1 100644 --- a/src/TaglibSharp/Id3v2/Tag.cs +++ b/src/TaglibSharp/Id3v2/Tag.cs @@ -442,12 +442,17 @@ public void SetTextFrame (ByteVector ident, params string[] text) // Handle URL Link frames differently if (ident[0] == 'W') { - var urlFrame = UrlLinkFrame.Get (this, ident, true); - - urlFrame.Text = text; - urlFrame.TextEncoding = DefaultEncoding; - return; - } + var urlLinkFrame = UrlLinkFrame.Get (this, ident, true); + var isUserUrlLink = ident[1] == 'X'; + if (isUserUrlLink) { + var userUrlLinkFrame = (UserUrlLinkFrame)urlLinkFrame; + userUrlLinkFrame.DescriptionEncoding = DefaultEncoding; + userUrlLinkFrame.Description = text[0]; + userUrlLinkFrame.Url = text[1]; + } else { + urlLinkFrame.Url = text[0]; + } + } var frame = TextInformationFrame.Get (this, ident, true);