Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix unicode string decoding #60

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Id3.Net/Frames/String/CustomTextFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public CustomTextFrame(string value) : base(value)
{
}

public string Description { get; set; }

public static implicit operator CustomTextFrame(string value) => new CustomTextFrame(value);
}

Expand Down
199 changes: 140 additions & 59 deletions src/Id3.Net/Id3/v2/Id3v23Handler.Frames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ namespace Id3.v2
{
internal sealed partial class Id3V23Handler
{
// from: https://id3.org/id3v2.3.0#Text_information_frames
// <Header for 'Text information frame', ID: "T000" - "TZZZ", excluding "TXXX" described in 4.2.2.>
// Text encoding $xx
// Information <text string according to encoding>

private static TFrame DecodeText<TFrame>(byte[] data)
where TFrame : TextFrameBase, new()
{
Expand All @@ -36,16 +41,13 @@ private static TFrame DecodeText<TFrame>(byte[] data)
if (encodingByte == 0 || encodingByte == 1)
{
frame.EncodingType = (Id3TextEncoding) encodingByte;
Encoding encoding = TextEncodingHelper.GetEncoding(frame.EncodingType);
value = encoding.GetString(data, 1, data.Length - 1);
if (value.Length > 0 && frame.EncodingType == Id3TextEncoding.Unicode &&
(value[0] == '\xFFFE' || value[0] == '\xFEFF'))
value = value.Remove(0, 1);
int currentPos = 1;
value = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
} else
{
frame.EncodingType = Id3TextEncoding.Iso8859_1;
Encoding encoding = TextEncodingHelper.GetEncoding(frame.EncodingType);
value = encoding.GetString(data, 0, data.Length);
int currentPos = 0;
value = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
}

frame.TextValue = value;
Expand All @@ -67,10 +69,54 @@ private static byte[] EncodeText<TFrame>(Id3Frame id3Frame)
return data;
}


// from: https://id3.org/id3v2.3.0#User_defined_text_information_frame
// <Header for 'User defined text information frame', ID: "TXXX">
// Text encoding $xx
// Description <text string according to encoding> $00 (00)
// Value <text string according to encoding>

private static CustomTextFrame DecodeCustomText(byte[] data)
{
var frame = new CustomTextFrame { EncodingType = (Id3TextEncoding)data[0] };

int currentPos = 1;
frame.Description = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
frame.TextValue = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);

return frame;
}

private static byte[] EncodeCustomText(Id3Frame id3Frame)
{
var frame = (CustomTextFrame)id3Frame;

var bytes = new List<byte> {
(byte) frame.EncodingType
};

Encoding encoding = TextEncodingHelper.GetEncoding(frame.EncodingType);
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Description))
bytes.AddRange(encoding.GetBytes(frame.Description));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(frame.EncodingType));
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.TextValue))
bytes.AddRange(encoding.GetBytes(frame.TextValue));

return bytes.ToArray();
}


// from: https://id3.org/id3v2.3.0#URL_link_frames
// <Header for 'URL link frame', ID: "W000" - "WZZZ", excluding "WXXX" described in 4.3.2.>
// URL <text string>

private static TFrame DecodeUrlLink<TFrame>(byte[] data)
where TFrame : UrlLinkFrame, new()
{
var frame = new TFrame {Url = TextEncodingHelper.GetDefaultString(data, 0, data.Length)};
int currentPos = 0;
var frame = new TFrame {Url = TextEncodingHelper.DecodeString(data, ref currentPos, Id3TextEncoding.Iso8859_1)};
return frame;
}

Expand All @@ -81,6 +127,14 @@ private static byte[] EncodeUrlLink<TFrame>(Id3Frame id3Frame)
return frame.Url != null ? TextEncodingHelper.GetDefaultEncoding().GetBytes(frame.Url) : new byte[0];
}


// from: https://id3.org/id3v2.3.0#Comments
// <Header for 'Comment', ID: "COMM">
// Text encoding $xx
// Language $xx xx xx
// Short content descrip. <text string according to encoding> $00 (00)
// The actual text <full text string according to encoding>

private static Id3Frame DecodeComment(byte[] data)
{
var frame = new CommentFrame {EncodingType = (Id3TextEncoding) data[0]};
Expand All @@ -91,13 +145,9 @@ private static Id3Frame DecodeComment(byte[] data)
else
frame.Language = (Id3Language) Enum.Parse(typeof(Id3Language), language, true);

string[] splitStrings = TextEncodingHelper.GetSplitStrings(data, 4, data.Length - 4, frame.EncodingType);
if (splitStrings.Length > 1)
{
frame.Description = splitStrings[0];
frame.Comment = splitStrings[1];
} else if (splitStrings.Length == 1)
frame.Comment = splitStrings[0];
int currentPos = 4;
frame.Description = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
frame.Comment = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);

return frame;
}
Expand All @@ -116,30 +166,28 @@ private static byte[] EncodeComment(Id3Frame id3Frame)
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Description))
bytes.AddRange(encoding.GetBytes(frame.Description));
bytes.AddRange(TextEncodingHelper.GetSplitterBytes(frame.EncodingType));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(frame.EncodingType));
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Comment))
bytes.AddRange(encoding.GetBytes(frame.Comment));

return bytes.ToArray();
}


// from: https://id3.org/id3v2.3.0#User_defined_URL_link_frame
// <Header for 'User defined URL link frame', ID: "WXXX">
// Text encoding $xx
// Description <text string according to encoding> $00 (00)
// URL <text string>

private static Id3Frame DecodeCustomUrlLink(byte[] data)
{
var frame = new CustomUrlLinkFrame {EncodingType = (Id3TextEncoding) data[0]};

byte[][] splitBytes = ByteArrayHelper.SplitBySequence(data, 1, data.Length - 1,
TextEncodingHelper.GetSplitterBytes(frame.EncodingType));
string url = null;
if (splitBytes.Length > 1)
{
frame.Description =
TextEncodingHelper.GetString(splitBytes[0], 0, splitBytes[0].Length, frame.EncodingType);
url = TextEncodingHelper.GetDefaultString(splitBytes[1], 0, splitBytes[1].Length);
} else if (splitBytes.Length == 1)
url = TextEncodingHelper.GetDefaultString(splitBytes[0], 0, splitBytes[0].Length);

frame.Url = url;
int currentPos = 1;
frame.Description = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
frame.Url = TextEncodingHelper.DecodeString(data, ref currentPos, Id3TextEncoding.Iso8859_1);

return frame;
}
Expand All @@ -156,13 +204,21 @@ private static byte[] EncodeCustomUrlLink(Id3Frame id3Frame)
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Description))
bytes.AddRange(encoding.GetBytes(frame.Description));
bytes.AddRange(TextEncodingHelper.GetSplitterBytes(frame.EncodingType));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(frame.EncodingType));
if (frame.Url != null)
bytes.AddRange(TextEncodingHelper.GetDefaultEncoding().GetBytes(frame.Url));

return bytes.ToArray();
}


// from: https://id3.org/id3v2.3.0#Unsychronised_lyrics.2Ftext_transcription
// <Header for 'Unsynchronised lyrics/text transcription', ID: "USLT">
// Text encoding $xx
// Language $xx xx xx
// Content descriptor <text string according to encoding> $00 (00)
// Lyrics/text <full text string according to encoding>

private static Id3Frame DecodeLyrics(byte[] data)
{
var frame = new LyricsFrame {EncodingType = (Id3TextEncoding) data[0]};
Expand All @@ -173,48 +229,66 @@ private static Id3Frame DecodeLyrics(byte[] data)
else
frame.Language = (Id3Language) Enum.Parse(typeof(Id3Language), language, true);

string[] splitStrings = TextEncodingHelper.GetSplitStrings(data, 4, data.Length - 4, frame.EncodingType);
if (splitStrings.Length > 1)
{
frame.Description = splitStrings[0];
frame.Lyrics = splitStrings[1];
} else if (splitStrings.Length == 1)
frame.Lyrics = splitStrings[0];
int currentPos = 4;
frame.Description = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);
frame.Lyrics = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);

return frame;
}

private static byte[] EncodeLyrics(Id3Frame id3Frame)
{
throw new NotImplementedException();
var frame = (LyricsFrame)id3Frame;

var bytes = new List<byte> {
(byte) frame.EncodingType
};

bytes.AddRange(TextEncodingHelper.GetDefaultEncoding().GetBytes(frame.Language.ToString()));

Encoding encoding = TextEncodingHelper.GetEncoding(frame.EncodingType);
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Description))
bytes.AddRange(encoding.GetBytes(frame.Description));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(frame.EncodingType));
bytes.AddRange(encoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Lyrics))
bytes.AddRange(encoding.GetBytes(frame.Lyrics));

return bytes.ToArray();
}


// from: https://id3.org/id3v2.3.0#Attached_picture
// <Header for 'Attached picture', ID: "APIC">
// Text encoding $xx
// MIME type <text string> $00
// Picture type $xx
// Description <text string according to encoding> $00 (00)
// Picture data <binary data>

private static Id3Frame DecodePicture(byte[] data)
{
var frame = new PictureFrame {EncodingType = (Id3TextEncoding) data[0]};

byte[] mimeType = ByteArrayHelper.GetBytesUptoSequence(data, 1, new byte[] {0x00});
if (mimeType == null)
int currentPos = 1;
frame.MimeType = TextEncodingHelper.DecodeString(data, ref currentPos, Id3TextEncoding.Iso8859_1);
if (frame.MimeType == null)
{
frame.MimeType = "image/";
return frame;
}

frame.MimeType = TextEncodingHelper.GetDefaultString(mimeType, 0, mimeType.Length);

int currentPos = mimeType.Length + 2;
frame.PictureType = (PictureType) data[currentPos];

currentPos++;
byte[] description = ByteArrayHelper.GetBytesUptoSequence(data, currentPos,
TextEncodingHelper.GetSplitterBytes(frame.EncodingType));
if (description == null)
return frame;
frame.Description = TextEncodingHelper.GetString(description, 0, description.Length, frame.EncodingType);

currentPos += description.Length + TextEncodingHelper.GetSplitterBytes(frame.EncodingType).Length;
frame.PictureData = new byte[data.Length - currentPos];
Array.Copy(data, currentPos, frame.PictureData, 0, frame.PictureData.Length);
frame.Description = TextEncodingHelper.DecodeString(data, ref currentPos, frame.EncodingType);

if (currentPos < data.Length)
{
frame.PictureData = new byte[data.Length - currentPos];
Array.Copy(data, currentPos, frame.PictureData, 0, frame.PictureData.Length);
}

return frame;
}
Expand All @@ -239,23 +313,30 @@ private static byte[] EncodePicture(Id3Frame id3Frame)
bytes.AddRange(descriptionEncoding.GetPreamble());
if (!string.IsNullOrEmpty(frame.Description))
bytes.AddRange(descriptionEncoding.GetBytes(frame.Description));
bytes.AddRange(TextEncodingHelper.GetSplitterBytes(frame.EncodingType));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(frame.EncodingType));

if (frame.PictureData != null && frame.PictureData.Length > 0)
bytes.AddRange(frame.PictureData);

return bytes.ToArray();
}


// from: https://id3.org/id3v2.3.0#Private_frame
// <Header for 'Private frame', ID: "PRIV">
// Owner identifier <text string> $00
// The private data <binary data>

private static Id3Frame DecodePrivate(byte[] data)
{
var frame = new PrivateFrame();
byte[] splitterSequence = TextEncodingHelper.GetSplitterBytes(Id3TextEncoding.Iso8859_1);
byte[] ownerIdBytes = ByteArrayHelper.GetBytesUptoSequence(data, 0, splitterSequence);
frame.OwnerId =
TextEncodingHelper.GetString(ownerIdBytes, 0, ownerIdBytes.Length, Id3TextEncoding.Iso8859_1);
frame.Data = new byte[data.Length - ownerIdBytes.Length - splitterSequence.Length];
Array.Copy(data, ownerIdBytes.Length + splitterSequence.Length, frame.Data, 0, frame.Data.Length);

int currentPos = 0;
frame.OwnerId = TextEncodingHelper.DecodeString(data, ref currentPos, Id3TextEncoding.Iso8859_1);

frame.Data = new byte[data.Length - currentPos];
Array.Copy(data, currentPos, frame.Data, 0, frame.Data.Length);

return frame;
}

Expand All @@ -265,7 +346,7 @@ private static byte[] EncodePrivate(Id3Frame id3Frame)

var bytes = new List<byte>();
bytes.AddRange(TextEncodingHelper.GetEncoding(Id3TextEncoding.Iso8859_1).GetBytes(frame.OwnerId));
bytes.AddRange(TextEncodingHelper.GetSplitterBytes(Id3TextEncoding.Iso8859_1));
bytes.AddRange(TextEncodingHelper.GetTerminationBytes(Id3TextEncoding.Iso8859_1));
bytes.AddRange(frame.Data ?? new byte[0]);
return bytes.ToArray();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Id3.Net/Id3/v2/Id3v23Handler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ protected override void BuildFrameHandlers(FrameHandlers mappings)
mappings.Add<ContentGroupDescriptionFrame>("TIT1", EncodeText<ContentGroupDescriptionFrame>, DecodeText<ContentGroupDescriptionFrame>);
mappings.Add<CopyrightFrame>("TCOP", EncodeText<CopyrightFrame>, DecodeText<CopyrightFrame>);
mappings.Add<CopyrightUrlFrame>("WCOP", EncodeUrlLink<CopyrightUrlFrame>, DecodeUrlLink<CopyrightUrlFrame>);
mappings.Add<CustomTextFrame>("TXXX", EncodeText<CustomTextFrame>, DecodeText<CustomTextFrame>);
mappings.Add<CustomTextFrame>("TXXX", EncodeCustomText, DecodeCustomText);
mappings.Add<CustomUrlLinkFrame>("WXXX", EncodeCustomUrlLink, DecodeCustomUrlLink);
mappings.Add<EncoderFrame>("TENC", EncodeText<EncoderFrame>, DecodeText<EncoderFrame>);
mappings.Add<EncodingSettingsFrame>("TSSE", EncodeText<EncodingSettingsFrame>, DecodeText<EncodingSettingsFrame>);
Expand Down
Loading