/// <summary> /// Retrieve a tag with an arbitrary name. /// Returns an empty tag if the value isn't found or if no Xiph tags are present. /// </summary> /// <param name="TagName"> /// A <see cref="System.String"/> containing the name of the tag to find /// </param> /// <returns> /// An <see cref="OggTag"/> containing the returned tag /// </returns> public OggTag GetTag(string TagName) { if (TagName.Length <= 0) { return(OggUtilities.GetEmptyTag()); } // Save some processing time and just exit if we haven't been given a tag name // Based on tasty examples @ "Accessing Hidden Gems": http://developer.novell.com/wiki/index.php/TagLib_Sharp:_Examples XiphComment XC = (XiphComment)m_TagLibFile.GetTag(TagTypes.Xiph); if (XC != null) { string[] TagValue = XC.GetField(TagName); if (TagValue.Length == 0) { // Tag doesn't exist, return empty return(OggUtilities.GetEmptyTag()); } else { OggTag tmpTag; tmpTag.Name = TagName; tmpTag.IsArray = (TagValue.Length > 1); tmpTag.IsEmpty = false; tmpTag.Values = TagValue; tmpTag.Value = TagValue[0]; return(tmpTag); } } else { // No valid Xiph tags found return(OggUtilities.GetEmptyTag()); } }
protected override void SetPicture(TagLib.File file, Tag tag) { TagLib.Ogg.File oggFile = (TagLib.Ogg.File)file; GroupedComment groupedCommentTag = (GroupedComment)tag; if (string.IsNullOrWhiteSpace(CoverFilePath)) { return; } // Is there a way to get Ogg file header using TagLib#? PropertyInfo headerProp = oggFile.GetType().GetProperty("LastPageHeader", BindingFlags.Instance | BindingFlags.NonPublic); PageHeader header = (PageHeader)headerProp.GetValue(oggFile); // Add cover art to Vorbis Comment approved METADATA_BLOCK_PICTURE field. TagLib.Flac.Picture pic = new TagLib.Flac.Picture(new Picture(CoverFilePath)) { Description = "" }; ByteVector picData = pic.Render(); XiphComment xiphComment = groupedCommentTag.GetComment(header.StreamSerialNumber); xiphComment.SetField("METADATA_BLOCK_PICTURE", Convert.ToBase64String(picData.Data)); }
/// <summary> /// Replaces the comment packet in a collection of packets /// with the rendered version of a Xiph comment or inserts a /// comment packet if the stream lacks one. /// </summary> /// <param name="packets"> /// A <see cref="ByteVectorCollection" /> object containing /// a collection of packets. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store the rendered /// version of in <paramref name="packets" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="packets" /> or <paramref name="comment" /// /> is <see langword="null" />. /// </exception> public override void SetCommentPacket(ByteVectorCollection packets, XiphComment comment) { if (packets == null) { throw new ArgumentNullException("packets"); } if (comment == null) { throw new ArgumentNullException("comment"); } ByteVector data = new ByteVector((byte)0x03); data.Add(id); data.Add(comment.Render(true)); if (packets.Count > 1 && PacketType(packets [1]) == 0x03) { if (data.Count < packets [1].Count) { data.Add(new ByteVector(packets [1].Count - data.Count, 0)); } packets [1] = data; } else { packets.Insert(1, data); } }
/// <summary> /// Remove a tag from the file. /// This command writes to disk. /// </summary> /// <param name="TagName"> /// A <see cref="System.String"/> indicating which tag to remove /// </param> /// <returns> /// An <see cref="OggTagWriteCommandReturn"/> indicating the result of the operation /// </returns> public OggTagWriteCommandReturn RemoveTag(string TagName) { // Check that the tag name contains at least one character if (TagName.Length < 1) { return(OggTagWriteCommandReturn.UnknownTag); } XiphComment XC = (XiphComment)m_TagLibFile.GetTag(TagTypes.Xiph, false); if (XC != null) { // Remove the tag XC.RemoveField(TagName); // Copy the XC instance into our file (might need to clear the Xiph block first, but we'll see) XC.CopyTo(m_TagLibFile.Tag, true); // Save m_TagLibFile.Save(); return(OggTagWriteCommandReturn.Success); } else { // Either there isn't a Xiph comment block or something went wrong return(OggTagWriteCommandReturn.Error); } }
/// <summary> /// Replaces the comment packet in a collection of packets /// with the rendered version of a Xiph comment or inserts a /// comment packet if the stream lacks one. /// </summary> /// <param name="packets"> /// A <see cref="ByteVectorCollection" /> object containing /// a collection of packets. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store the rendered /// version of in <paramref name="packets" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="packets" /> or <paramref name="comment" /// /> is <see langword="null" />. /// </exception> public override void SetCommentPacket(ByteVectorCollection packets, XiphComment comment) { if (packets == null) { throw new ArgumentNullException("packets"); } if (comment == null) { throw new ArgumentNullException("comment"); } ByteVector data = new ByteVector(); data.Add(magic_signature_comment); data.Add(comment.Render(true)); if (packets.Count > 1 && MagicSignature(packets [1]) == magic_signature_comment) { packets [1] = data; } else { packets.Insert(1, data); } }
public void TestWriteFlacUid() { var flacFile = string.Format("{0}\\{1:N}.flac", Path.GetTempPath(), Guid.NewGuid()); System.IO.File.Copy(@"TestData\hei.flac", flacFile, true); var file = File.Create(flacFile); Metadata metadata = (Metadata)file.GetTag(TagTypes.FlacMetadata, true); XiphComment xiphComment = (XiphComment)metadata.Tags.First(); xiphComment.SetField("SW-AlbumUid", Guid.NewGuid().ToString("N")); file.Save(); //metadata.SetTextFrame((ReadOnlyByteVector) "UFID", "http://www.id3.org/dummy/ufid.html", Guid.NewGuid().ToString("N")); //file.Save(); //File actualFile = File.Create(mp3File); //metadata = (Tag)actualFile.GetTag(TagTypes.Id3v2, true); //// Get the private frame, create if necessary. //var frame = metadata.GetFrames().FirstOrDefault(f => f.FrameId == "UFID"); //frame.Should().NotBeNull(); System.IO.File.Delete(flacFile); }
/// <summary> /// Write a single Xiph tag to the file /// This will overwrite the tag if it already exists, or create it if it doesn't /// It will also create the Xiph tag block within the file if it doesn't already have one /// This function writes to disk. If setting multiple tags consider using SetTags(OggTag[] Tags) to reduce the number of write operations /// If setting an array, Tag.Values must contain at least one item. Tag.Value is ignored in this case /// If setting a single value, Tag.Value must contain at least one character. Tag.Values is ignored in this case /// </summary> /// <param name="Tag"> /// The <see cref="OggTag"/> to write /// </param> /// <returns> /// An <see cref="OggTagWriteCommandReturn"/> indicating the result of the operation /// </returns> public OggTagWriteCommandReturn SetTag(OggTag Tag) { // Validate Tag if (Tag.IsEmpty) { return(OggTagWriteCommandReturn.InvalidValue); } if (Tag.Name.Length <= 0) { return(OggTagWriteCommandReturn.UnknownTag); } if (Tag.IsArray) { if (Tag.Values.Length <= 0) { return(OggTagWriteCommandReturn.InvalidValue); } } else { if (Tag.Value.Length <= 0) { return(OggTagWriteCommandReturn.InvalidValue); } } // Tag valid, try and write it XiphComment XC = (XiphComment)m_TagLibFile.GetTag(TagTypes.Xiph, true); if (XC != null) { string[] tmpStrArray; if (Tag.IsArray) { tmpStrArray = Tag.Values; } else { tmpStrArray = new string[1]; tmpStrArray[0] = Tag.Value; } // Set field XC.SetField(Tag.Name, tmpStrArray); // Copy the XC instance into our file (not sure if this is needed) XC.CopyTo(m_TagLibFile.Tag, true); // Commit m_TagLibFile.Save(); return(OggTagWriteCommandReturn.Success); } else { // If we're null something went wrong (we tried to create the XiphComment block and it failed probably) return(OggTagWriteCommandReturn.Error); } }
public override void SetCommentPacket (ByteVectorCollection packets, XiphComment comment) { if (packets == null) throw new ArgumentNullException ("packets"); if (comment == null) throw new ArgumentNullException ("comment"); ByteVector data = new ByteVector ((byte) 0x03); data.Add (id); data.Add (comment.Render (true)); if (packets.Count > 1 && PacketType (packets [1]) == 0x03) packets [1] = data; else packets.Insert (1, data); }
/// <summary> /// Retrieve an array of all tag values /// Returns a zero-length array if no Xiph tags are found /// </summary> /// <returns> /// An <see cref="OggTag[]"/> containing the returned values /// </returns> public OggTag[] GetTags() { XiphComment XC = (XiphComment)m_TagLibFile.GetTag(TagTypes.Xiph); if (XC != null) { if (XC.FieldCount > 0) { OggTag[] tmpOggTag = new OggTag[XC.FieldCount]; int Index = 0; foreach (string FieldName in XC) { string[] TagValue = XC.GetField(FieldName); if (TagValue.Length == 0) { tmpOggTag[Index] = OggUtilities.GetEmptyTag(); // This should never happen, but I bet if I don't check it it will! } else { // Populate this tag tmpOggTag[Index].Name = FieldName; tmpOggTag[Index].IsArray = (TagValue.Length > 1); tmpOggTag[Index].IsEmpty = false; tmpOggTag[Index].Values = TagValue; tmpOggTag[Index].Value = TagValue[0]; } ++Index; // Increment the index so we know which OggTag we're molesting } // Done! Return the heap of tags return(tmpOggTag); } else { // Xiph tags contain no items return(new OggTag[0]); } } else { // No valid Xiph tags found return(new OggTag[0]); } }
public XiphComment GetComment(bool create, Tag copy) { foreach (Tag tag in this.Tags) { if (tag is XiphComment) { return (tag as XiphComment); } } if (!create) { return null; } XiphComment target = new XiphComment(); if (copy != null) { copy.CopyTo(target, true); } base.AddTag(target); return target; }
public override void SetCommentPacket(ByteVectorCollection packets, XiphComment comment) { if (packets == null) { throw new ArgumentNullException("packets"); } if (comment == null) { throw new ArgumentNullException("comment"); } byte[] data = new byte[] { 0x81 }; ByteVector item = new ByteVector(data) { id, comment.Render(1) }; if ((packets.Count > 1) && (PacketType(packets[1]) == 0x81)) { packets[1] = item; } else { packets.Insert(1, item); } }
/// <summary> /// Replaces the comment packet in a collection of packets /// with the rendered version of a Xiph comment or inserts a /// comment packet if the stream lacks one. /// </summary> /// <param name="packets"> /// A <see cref="ByteVectorCollection" /> object containing /// a collection of packets. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store the rendered /// version of in <paramref name="packets" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="packets" /> or <paramref name="comment" /// /> is <see langword="null" />. /// </exception> public override void SetCommentPacket (ByteVectorCollection packets, XiphComment comment) { if (packets == null) throw new ArgumentNullException ("packets"); if (comment == null) throw new ArgumentNullException ("comment"); ByteVector data = new ByteVector (); data.Add (magic_signature_comment); data.Add (comment.Render (true)); if (packets.Count > 1 && MagicSignature (packets [1]) == magic_signature_comment) packets [1] = data; else packets.Insert (1, data); }
/// <summary> /// Save the Modified file /// </summary> /// <param name="track"></param> /// <param name="errorMessage"></param> /// <returns></returns> public static bool SaveFile(TrackData track, ref string errorMessage) { errorMessage = ""; if (!track.Changed) { return(true); } if (track.Readonly && !Options.MainSettings.ChangeReadOnlyAttributte && (Options.ReadOnlyFileHandling == 0 || Options.ReadOnlyFileHandling == 2)) { Form dlg = new ReadOnlyDialog(track.FullFileName); DialogResult dlgResult = dlg.ShowDialog(); switch (dlgResult) { case DialogResult.Yes: Options.ReadOnlyFileHandling = 0; // Yes break; case DialogResult.OK: Options.ReadOnlyFileHandling = 1; // Yes to All break; case DialogResult.No: Options.ReadOnlyFileHandling = 2; // No break; case DialogResult.Cancel: Options.ReadOnlyFileHandling = 3; // No to All break; } } if (track.Readonly) { if (!Options.MainSettings.ChangeReadOnlyAttributte && Options.ReadOnlyFileHandling > 1) { errorMessage = "File is readonly"; return(false); } try { System.IO.File.SetAttributes(track.FullFileName, System.IO.File.GetAttributes(track.FullFileName) & ~FileAttributes.ReadOnly); track.Readonly = false; } catch (Exception ex) { log.Error("File Save: Can't reset Readonly attribute: {0} {1}", track.FullFileName, ex.Message); errorMessage = ServiceScope.Get <ILocalisation>().ToString("message", "ErrorResetAttr"); return(false); } } TagLib.File file = null; bool error = false; try { TagLib.ByteVector.UseBrokenLatin1Behavior = true; file = TagLib.File.Create(track.FullFileName); } catch (CorruptFileException) { log.Warn("File Read: Ignoring track {0} - Corrupt File!", track.FullFileName); errorMessage = ServiceScope.Get <ILocalisation>().ToString("message", "CorruptFile"); error = true; } catch (UnsupportedFormatException) { log.Warn("File Read: Ignoring track {0} - Unsupported format!", track.FullFileName); errorMessage = ServiceScope.Get <ILocalisation>().ToString("message", "UnsupportedFormat"); error = true; } catch (FileNotFoundException) { log.Warn("File Read: Ignoring track {0} - Physical file no longer existing!", track.FullFileName); errorMessage = ServiceScope.Get <ILocalisation>().ToString("message", "NonExistingFile"); error = true; } catch (Exception ex) { log.Error("File Read: Error processing file: {0} {1}", track.FullFileName, ex.Message); errorMessage = string.Format(ServiceScope.Get <ILocalisation>().ToString("message", "ErrorReadingFile"), ex.Message); error = true; } if (file == null || error) { log.Error("File Read: Error processing file.: {0}", track.FullFileName); return(false); } try { // Get the ID3 Frame for ID3 specifc frame handling TagLib.Id3v1.Tag id3v1tag = null; TagLib.Id3v2.Tag id3v2tag = null; if (track.IsMp3) { id3v1tag = file.GetTag(TagTypes.Id3v1, true) as TagLib.Id3v1.Tag; id3v2tag = file.GetTag(TagTypes.Id3v2, true) as TagLib.Id3v2.Tag; } // Remove Tags, if they have been removed in TagEdit Panel foreach (TagLib.TagTypes tagType in track.TagsRemoved) { file.RemoveTags(tagType); } #region Main Tags string[] splitValues = track.Artist.Split(new[] { ';', '|' }); file.Tag.Performers = splitValues; splitValues = track.AlbumArtist.Split(new[] { ';', '|' }); file.Tag.AlbumArtists = splitValues; file.Tag.Album = track.Album.Trim(); file.Tag.BeatsPerMinute = (uint)track.BPM; if (track.Comment != "") { file.Tag.Comment = ""; if (track.IsMp3) { id3v1tag.Comment = track.Comment; foreach (Comment comment in track.ID3Comments) { CommentsFrame commentsframe = CommentsFrame.Get(id3v2tag, comment.Description, comment.Language, true); commentsframe.Text = comment.Text; commentsframe.Description = comment.Description; commentsframe.Language = comment.Language; } } else { file.Tag.Comment = track.Comment; } } else { file.Tag.Comment = ""; } if (track.IsMp3) { id3v2tag.IsCompilation = track.Compilation; } file.Tag.Disc = track.DiscNumber; file.Tag.DiscCount = track.DiscCount; splitValues = track.Genre.Split(new[] { ';', '|' }); file.Tag.Genres = splitValues; file.Tag.Title = track.Title; file.Tag.Track = track.TrackNumber; file.Tag.TrackCount = track.TrackCount; file.Tag.Year = (uint)track.Year; file.Tag.ReplayGainTrack = track.ReplayGainTrack; file.Tag.ReplayGainTrackPeak = track.ReplayGainTrackPeak; file.Tag.ReplayGainAlbum = track.ReplayGainAlbum; file.Tag.ReplayGainAlbumPeak = track.ReplayGainAlbumPeak; #endregion #region Detailed Information splitValues = track.Composer.Split(new[] { ';', '|' }); file.Tag.Composers = splitValues; file.Tag.Conductor = track.Conductor; file.Tag.Copyright = track.Copyright; file.Tag.Grouping = track.Grouping; splitValues = track.ArtistSortName.Split(new[] { ';', '|' }); file.Tag.PerformersSort = splitValues; splitValues = track.AlbumArtistSortName.Split(new[] { ';', '|' }); file.Tag.AlbumArtistsSort = splitValues; file.Tag.AlbumSort = track.AlbumSortName; file.Tag.TitleSort = track.TitleSortName; #endregion #region Picture List <TagLib.Picture> pics = new List <TagLib.Picture>(); foreach (Picture pic in track.Pictures) { TagLib.Picture tagPic = new TagLib.Picture(); try { byte[] byteArray = pic.Data; ByteVector data = new ByteVector(byteArray); tagPic.Data = data; tagPic.Description = pic.Description; tagPic.MimeType = "image/jpg"; tagPic.Type = pic.Type; pics.Add(tagPic); } catch (Exception ex) { log.Error("Error saving Picture: {0}", ex.Message); } file.Tag.Pictures = pics.ToArray(); } // Clear the picture if (track.Pictures.Count == 0) { file.Tag.Pictures = pics.ToArray(); } #endregion #region Lyrics if (track.Lyrics != null && track.Lyrics != "") { file.Tag.Lyrics = track.Lyrics; if (track.IsMp3) { foreach (Lyric lyric in track.LyricsFrames) { UnsynchronisedLyricsFrame lyricframe = UnsynchronisedLyricsFrame.Get(id3v2tag, lyric.Description, lyric.Language, true); lyricframe.Text = lyric.Text; lyricframe.Description = lyric.Description; lyricframe.Language = lyric.Language; } } else { file.Tag.Lyrics = track.Lyrics; } } else { file.Tag.Lyrics = ""; } #endregion #region Ratings if (track.IsMp3) { if (track.Ratings.Count > 0) { foreach (PopmFrame rating in track.Ratings) { PopularimeterFrame popmFrame = PopularimeterFrame.Get(id3v2tag, rating.User, true); popmFrame.Rating = Convert.ToByte(rating.Rating); popmFrame.PlayCount = Convert.ToUInt32(rating.PlayCount); } } else { id3v2tag.RemoveFrames("POPM"); } } else if (track.TagType == "ogg" || track.TagType == "flac") { if (track.Ratings.Count > 0) { XiphComment xiph = file.GetTag(TagLib.TagTypes.Xiph, true) as XiphComment; xiph.SetField("RATING", track.Rating.ToString()); } } #endregion #region Non- Standard Taglib and User Defined Frames if (Options.MainSettings.ClearUserFrames) { foreach (Frame frame in track.UserFrames) { ByteVector frameId = new ByteVector(frame.Id); if (frame.Id == "TXXX") { id3v2tag.SetUserTextAsString(frame.Description, ""); } else { id3v2tag.SetTextFrame(frameId, ""); } } } List <Frame> allFrames = new List <Frame>(); allFrames.AddRange(track.Frames); // The only way to avoid duplicates of User Frames is to delete them by assigning blank values to them if (track.SavedUserFrames != null && !Options.MainSettings.ClearUserFrames) { // Clean the previously saved Userframes, to avoid duplicates foreach (Frame frame in track.SavedUserFrames) { ByteVector frameId = new ByteVector(frame.Id); if (frame.Id == "TXXX") { id3v2tag.SetUserTextAsString(frame.Description, ""); } else { id3v2tag.SetTextFrame(frameId, ""); } } allFrames.AddRange(track.UserFrames); } foreach (Frame frame in allFrames) { ByteVector frameId = new ByteVector(frame.Id); // The only way to avoid duplicates of User Frames is to delete them by assigning blank values to them if (frame.Id == "TXXX") { if (frame.Description != "") { id3v2tag.SetUserTextAsString(frame.Description, ""); id3v2tag.SetUserTextAsString(frame.Description, frame.Value); } } else { id3v2tag.SetTextFrame(frameId, ""); id3v2tag.SetTextFrame(frameId, frame.Value); } } #endregion // Now, depending on which frames the user wants to save, we will remove the other Frames file = Util.FormatID3Tag(file); // Set the encoding for ID3 Tags if (track.IsMp3) { TagLib.Id3v2.Tag.ForceDefaultEncoding = true; switch (Options.MainSettings.CharacterEncoding) { case 0: TagLib.Id3v2.Tag.DefaultEncoding = StringType.Latin1; break; case 1: TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16; break; case 2: TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16BE; break; case 3: TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF8; break; case 4: TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16LE; break; } } // Save the file file.Save(); } catch (Exception ex) { log.Error("File Save: Error processing file: {0} {1}", track.FullFileName, ex.Message); errorMessage = ServiceScope.Get <ILocalisation>().ToString("message", "ErrorSave"); error = true; } if (error) { return(false); } return(true); }
/// <summary> /// Read the Tags from the File /// </summary> /// <param name="fileName"></param> /// <returns></returns> public static TrackData Create(string fileName) { TrackData track = new TrackData(); TagLib.File file = null; bool error = false; try { TagLib.ByteVector.UseBrokenLatin1Behavior = true; file = TagLib.File.Create(fileName); } catch (CorruptFileException) { log.Warn("File Read: Ignoring track {0} - Corrupt File!", fileName); error = true; } catch (UnsupportedFormatException) { log.Warn("File Read: Ignoring track {0} - Unsupported format!", fileName); error = true; } catch (FileNotFoundException) { log.Warn("File Read: Ignoring track {0} - Physical file no longer existing!", fileName); error = true; } catch (Exception ex) { log.Error("File Read: Error processing file: {0} {1}", fileName, ex.Message); error = true; } if (error) { return(null); } TagLib.Id3v2.Tag id3v2tag = null; try { if (file.MimeType.Substring(file.MimeType.IndexOf("/") + 1) == "mp3") { id3v2tag = file.GetTag(TagTypes.Id3v2, false) as TagLib.Id3v2.Tag; } } catch (Exception ex) { log.Error("File Read: Error retrieving id3tag: {0} {1}", fileName, ex.Message); return(null); } #region Set Common Values FileInfo fi = new FileInfo(fileName); try { track.Id = Guid.NewGuid(); track.FullFileName = fileName; track.FileName = Path.GetFileName(fileName); track.Readonly = fi.IsReadOnly; track.TagType = file.MimeType.Substring(file.MimeType.IndexOf("/") + 1); } catch (Exception ex) { log.Error("File Read: Error setting Common tags: {0} {1}", fileName, ex.Message); return(null); } #endregion #region Set Tags try { // Artist track.Artist = String.Join(";", file.Tag.Performers); if (track.Artist.Contains("AC;DC")) { track.Artist = track.Artist.Replace("AC;DC", "AC/DC"); } track.ArtistSortName = String.Join(";", file.Tag.PerformersSort); if (track.ArtistSortName.Contains("AC;DC")) { track.ArtistSortName = track.ArtistSortName.Replace("AC;DC", "AC/DC"); } track.AlbumArtist = String.Join(";", file.Tag.AlbumArtists); if (track.AlbumArtist.Contains("AC;DC")) { track.AlbumArtist = track.AlbumArtist.Replace("AC;DC", "AC/DC"); } track.AlbumArtistSortName = String.Join(";", file.Tag.AlbumArtistsSort); if (track.AlbumArtistSortName.Contains("AC;DC")) { track.AlbumArtistSortName = track.AlbumArtistSortName.Replace("AC;DC", "AC/DC"); } track.Album = file.Tag.Album ?? ""; track.AlbumSortName = file.Tag.AlbumSort ?? ""; track.BPM = (int)file.Tag.BeatsPerMinute; track.Compilation = id3v2tag == null ? false : id3v2tag.IsCompilation; track.Composer = string.Join(";", file.Tag.Composers); track.Conductor = file.Tag.Conductor ?? ""; track.Copyright = file.Tag.Copyright ?? ""; track.DiscNumber = file.Tag.Disc; track.DiscCount = file.Tag.DiscCount; track.Genre = string.Join(";", file.Tag.Genres); track.Grouping = file.Tag.Grouping ?? ""; track.Title = file.Tag.Title ?? ""; track.TitleSortName = file.Tag.TitleSort ?? ""; track.ReplayGainTrack = file.Tag.ReplayGainTrack ?? ""; track.ReplayGainTrackPeak = file.Tag.ReplayGainTrackPeak ?? ""; track.ReplayGainAlbum = file.Tag.ReplayGainAlbum ?? ""; track.ReplayGainAlbumPeak = file.Tag.ReplayGainAlbumPeak ?? ""; track.TrackNumber = file.Tag.Track; track.TrackCount = file.Tag.TrackCount; track.Year = (int)file.Tag.Year; // Pictures foreach (IPicture picture in file.Tag.Pictures) { MPTagThat.Core.Common.Picture pic = new MPTagThat.Core.Common.Picture { Type = picture.Type, MimeType = picture.MimeType, Description = picture.Description }; pic.Data = picture.Data.Data; track.Pictures.Add(pic); } // Comments if (track.IsMp3 && id3v2tag != null) { foreach (CommentsFrame commentsframe in id3v2tag.GetFrames <CommentsFrame>()) { track.ID3Comments.Add(new Comment(commentsframe.Description, commentsframe.Language, commentsframe.Text)); } } else { track.Comment = file.Tag.Comment; } // Lyrics track.Lyrics = file.Tag.Lyrics; if (track.IsMp3 && id3v2tag != null) { foreach (UnsynchronisedLyricsFrame lyricsframe in id3v2tag.GetFrames <UnsynchronisedLyricsFrame>()) { // Only add non-empty Frames if (lyricsframe.Text != "") { track.LyricsFrames.Add(new Lyric(lyricsframe.Description, lyricsframe.Language, lyricsframe.Text)); } } } // Rating if (track.IsMp3) { TagLib.Id3v2.PopularimeterFrame popmFrame = null; // First read in all POPM Frames found if (id3v2tag != null) { foreach (PopularimeterFrame popmframe in id3v2tag.GetFrames <PopularimeterFrame>()) { // Only add valid POPM Frames if (popmframe.User != "" && popmframe.Rating > 0) { track.Ratings.Add(new PopmFrame(popmframe.User, (int)popmframe.Rating, (int)popmframe.PlayCount)); } } popmFrame = TagLib.Id3v2.PopularimeterFrame.Get(id3v2tag, "MPTagThat", false); if (popmFrame != null) { track.Rating = popmFrame.Rating; } } if (popmFrame == null) { // Now check for Ape Rating TagLib.Ape.Tag apetag = file.GetTag(TagTypes.Ape, true) as TagLib.Ape.Tag; TagLib.Ape.Item apeItem = apetag.GetItem("RATING"); if (apeItem != null) { string rating = apeItem.ToString(); try { track.Rating = Convert.ToInt32(rating); } catch (Exception) { } } } } else if (track.TagType == "ape") { TagLib.Ape.Tag apetag = file.GetTag(TagTypes.Ape, true) as TagLib.Ape.Tag; TagLib.Ape.Item apeItem = apetag.GetItem("RATING"); if (apeItem != null) { string rating = apeItem.ToString(); try { track.Rating = Convert.ToInt32(rating); } catch (Exception) { } } } else if (track.TagType == "ogg" || track.TagType == "flac") { XiphComment xiph = file.GetTag(TagLib.TagTypes.Xiph, false) as XiphComment; string[] rating = xiph.GetField("RATING"); if (rating.Length > 0) { try { track.Rating = Convert.ToInt32(rating[0]); } catch (Exception) { } } } } catch (Exception ex) { log.Error("Exception setting Tags for file: {0}. {1}", fileName, ex.Message); } #endregion #region Set Properties try { track.DurationTimespan = file.Properties.Duration; int fileLength = (int)(fi.Length / 1024); track.FileSize = fileLength.ToString(); track.BitRate = file.Properties.AudioBitrate.ToString(); track.SampleRate = file.Properties.AudioSampleRate.ToString(); track.Channels = file.Properties.AudioChannels.ToString(); track.Version = file.Properties.Description; track.CreationTime = string.Format("{0:yyyy-MM-dd HH:mm:ss}", fi.CreationTime); track.LastWriteTime = string.Format("{0:yyyy-MM-dd HH:mm:ss}", fi.LastWriteTime); } catch (Exception ex) { log.Error("Exception setting Properties for file: {0}. {1}", fileName, ex.Message); } #endregion // Now copy all Text frames of an ID3 V2 try { if (track.IsMp3 && id3v2tag != null) { foreach (TagLib.Id3v2.Frame frame in id3v2tag.GetFrames()) { string id = frame.FrameId.ToString(); if (!Util.StandardFrames.Contains(id) && Util.ExtendedFrames.Contains(id)) { track.Frames.Add(new Frame(id, "", frame.ToString())); } else if (!Util.StandardFrames.Contains(id) && !Util.ExtendedFrames.Contains(id)) { if ((Type)frame.GetType() == typeof(UserTextInformationFrame)) { // Don't add Replaygain frames, as they are handled in taglib tags if (!Util.IsReplayGain((frame as UserTextInformationFrame).Description)) { track.UserFrames.Add(new Frame(id, (frame as UserTextInformationFrame).Description ?? "", (frame as UserTextInformationFrame).Text.Length == 0 ? "" : (frame as UserTextInformationFrame).Text[0])); } } else if ((Type)frame.GetType() == typeof(PrivateFrame)) { track.UserFrames.Add(new Frame(id, (frame as PrivateFrame).Owner ?? "", (frame as PrivateFrame).PrivateData == null ? "" : (frame as PrivateFrame).PrivateData.ToString())); } else if ((Type)frame.GetType() == typeof(UniqueFileIdentifierFrame)) { track.UserFrames.Add(new Frame(id, (frame as UniqueFileIdentifierFrame).Owner ?? "", (frame as UniqueFileIdentifierFrame).Identifier == null ? "" : (frame as UniqueFileIdentifierFrame).Identifier.ToString())); } else if ((Type)frame.GetType() == typeof(UnknownFrame)) { track.UserFrames.Add(new Frame(id, "", (frame as UnknownFrame).Data == null ? "" : (frame as UnknownFrame).Data.ToString())); } else { track.UserFrames.Add(new Frame(id, "", frame.ToString())); } } } track.ID3Version = id3v2tag.Version; } } catch (Exception ex) { log.Error("Exception getting User Defined frames for file: {0}. {1}", fileName, ex.Message); } return(track); }
/// <summary> /// Stores a Xiph comment in the codec-specific comment /// packet. /// </summary> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store in the /// comment packet. /// </param> public void SetComment(XiphComment comment) { codec.SetCommentPacket(packets, comment); }
/// <summary> /// Replaces the comment packet in a collection of packets /// with the rendered version of a Xiph comment or inserts a /// comment packet if the stream lacks one. /// </summary> /// <param name="packets"> /// A <see cref="ByteVectorCollection" /> object containing /// a collection of packets. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store the rendered /// version of in <paramref name="packets" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="packets" /> or <paramref name="comment" /// /> is <see langword="null" />. /// </exception> public abstract void SetCommentPacket (ByteVectorCollection packets, XiphComment comment);
/// <summary> /// Adds a Xiph comment to the current instance. /// </summary> /// <param name="streamSerialNumber"> /// A <see cref="uint" /> value containing the serial number /// of the stream containing the comment. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to add to the current /// instance. /// </param> public void AddComment(uint streamSerialNumber, XiphComment comment) { comment_hash.Add (streamSerialNumber, comment); tags.Add (comment); }
/// <summary> /// Replaces the comment packet in a collection of packets /// with the rendered version of a Xiph comment or inserts a /// comment packet if the stream lacks one. /// </summary> /// <param name="packets"> /// A <see cref="ByteVectorCollection" /> object containing /// a collection of packets. /// </param> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store the rendered /// version of in <paramref name="packets" />. /// </param> /// <exception cref="ArgumentNullException"> /// <paramref name="packets" /> or <paramref name="comment" /// /> is <see langword="null" />. /// </exception> public abstract void SetCommentPacket(ByteVectorCollection packets, XiphComment comment);
/// <summary> /// Write multiple Xiph tags to the file /// This will overwrite any existing tags, and create them if they don't exist /// It will also create the Xiph tag block within the file if it doesn't already have one /// This function writes to disk. If setting only a single tag, consider using SetTag(OggTag Tag) to reduce the array handling overheads /// If setting an array value Tags[i].Values must contain at least one item. Tags[i].Value is ignored in this case /// If setting a single value, Tags[i].Value must contain at least one character. Tags[i].Values is ignored in this case /// If AbortOnError is true, this function will abort (and not write) if any item in the Tags array is invalid. /// If AbortOnError is false, this function will continue (and write) if items in the Tags array are invalid. It will still abort (and not write) if there are other errors. /// </summary> /// <param name="Tags"> /// An <see cref="OggTag[]"/> containing the tags to be written /// </param> /// <param name="AbortOnError"> /// A <see cref="System.bool"/> indicating whether to invalid items in the Tags array. /// <returns> /// An <see cref="OggTagWriteCommandReturn"/> indicating the result of the operation /// </returns> public OggTagWriteCommandReturn SetTags(OggTag[] Tags, bool AbortOnError) { // Check that the Tags array has at least one item in it if (Tags.Length < 1) { return(OggTagWriteCommandReturn.UnknownTag); } XiphComment XC = (XiphComment)m_TagLibFile.GetTag(TagTypes.Xiph, true); if (XC != null) { // Write the tags to the 'virtual' file foreach (OggTag Tag in Tags) { // Validate tag if (Tag.IsEmpty) { if (AbortOnError) { return(OggTagWriteCommandReturn.InvalidValue); } else { continue; } } if (Tag.Name.Length <= 0) { if (AbortOnError) { return(OggTagWriteCommandReturn.UnknownTag); } else { continue; } } if (Tag.IsArray) { if (Tag.Values.Length <= 0) { if (AbortOnError) { return(OggTagWriteCommandReturn.InvalidValue); } else { continue; } } } else { if (Tag.Value.Length <= 0) { if (AbortOnError) { return(OggTagWriteCommandReturn.InvalidValue); } else { continue; } } } string[] tmpStrArray; if (Tag.IsArray) { tmpStrArray = Tag.Values; } else { tmpStrArray = new string[1]; tmpStrArray[0] = Tag.Value; } // Write tag XC.SetField(Tag.Name, tmpStrArray); } // Copy the XC instance into our file (not sure if this is needed) XC.CopyTo(m_TagLibFile.Tag, true); // Save to disk m_TagLibFile.Save(); return(OggTagWriteCommandReturn.Success); } else { // If we're null something went wrong (we tried to create the XiphComment block and it failed probably) return(OggTagWriteCommandReturn.Error); } }
/// <summary> /// Save the Modified file /// </summary> /// <param name="song"></param> /// <param name="errorMessage"></param> /// <returns></returns> public static bool SaveFile(SongData song, ref string errorMessage) { errorMessage = ""; if (!song.Changed) { return(true); } var options = (ServiceLocator.Current.GetInstance(typeof(ISettingsManager)) as ISettingsManager)?.GetOptions; /* * if (song.Readonly && !options.MainSettings.ChangeReadOnlyAttributte && * (options.ReadOnlyFileHandling == 0 || options.ReadOnlyFileHandling == 2)) * { * Form dlg = new ReadOnlyDialog(song.FullFileName); * DialogResult dlgResult = dlg.ShowDialog(); * * switch (dlgResult) * { * case DialogResult.Yes: * options.ReadOnlyFileHandling = 0; // Yes * break; * * case DialogResult.OK: * options.ReadOnlyFileHandling = 1; // Yes to All * break; * * case DialogResult.No: * options.ReadOnlyFileHandling = 2; // No * break; * * case DialogResult.Cancel: * options.ReadOnlyFileHandling = 3; // No to All * break; * } * } */ if (song.Readonly) { if (!options.MainSettings.ChangeReadOnlyAttribute && options.ReadOnlyFileHandling > 1) { errorMessage = "File is readonly"; return(false); } try { System.IO.File.SetAttributes(song.FullFileName, System.IO.File.GetAttributes(song.FullFileName) & ~FileAttributes.ReadOnly); song.Readonly = false; } catch (Exception ex) { log.Error($"File Save: Can't reset Readonly attribute: {song.FullFileName} {ex.Message}"); errorMessage = LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_ErrorResetAttr", LocalizeDictionary.Instance.Culture).ToString(); return(false); } } TagLib.File file = null; bool error = false; try { TagLib.ByteVector.UseBrokenLatin1Behavior = true; file = TagLib.File.Create(song.FullFileName); } catch (CorruptFileException) { log.Warn($"File Read: Ignoring song {song.FullFileName} - Corrupt File!"); errorMessage = LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_CorruptFile", LocalizeDictionary.Instance.Culture).ToString(); error = true; } catch (UnsupportedFormatException) { log.Warn($"File Read: Ignoring song {song.FullFileName} - Unsupported format!"); errorMessage = LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_UnsupportedFormat", LocalizeDictionary.Instance.Culture).ToString(); error = true; } catch (FileNotFoundException) { log.Warn($"File Read: Ignoring song {song.FullFileName} - Physical file no longer existing!"); errorMessage = LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_NonExistingFile", LocalizeDictionary.Instance.Culture).ToString(); error = true; } catch (Exception ex) { log.Error($"File Read: Error processing file: {song.FullFileName} {ex.Message}"); errorMessage = string.Format(LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_ErrorReadingFile", LocalizeDictionary.Instance.Culture).ToString(), song.FullFileName); error = true; } if (file == null || error) { log.Error("File Read: Error processing file.: {0}", song.FullFileName); return(false); } try { // Get the ID3 Frame for ID3 specifc frame handling TagLib.Id3v1.Tag id3v1tag = null; TagLib.Id3v2.Tag id3v2tag = null; if (song.IsMp3) { id3v1tag = file.GetTag(TagTypes.Id3v1, true) as TagLib.Id3v1.Tag; id3v2tag = file.GetTag(TagTypes.Id3v2, true) as TagLib.Id3v2.Tag; } // Remove Tags, if they have been removed in TagEdit Panel foreach (TagLib.TagTypes tagType in song.TagsRemoved) { file.RemoveTags(tagType); } if (file.Tag != null) { #region Main Tags string[] splitValues = song.Artist.Split(new[] { ';', '|' }); file.Tag.Performers = splitValues; splitValues = song.AlbumArtist.Split(new[] { ';', '|' }); file.Tag.AlbumArtists = splitValues; file.Tag.Album = song.Album.Trim(); file.Tag.BeatsPerMinute = (uint)song.BPM; if (song.Comment != "") { file.Tag.Comment = ""; if (song.IsMp3) { id3v1tag.Comment = song.Comment; foreach (Comment comment in song.ID3Comments) { CommentsFrame commentsframe = CommentsFrame.Get(id3v2tag, comment.Description, comment.Language, true); commentsframe.Text = comment.Text; commentsframe.Description = comment.Description; commentsframe.Language = comment.Language; } } else { file.Tag.Comment = song.Comment; } } else { if (song.IsMp3 && id3v2tag != null) { id3v2tag.RemoveFrames("COMM"); } else { file.Tag.Comment = ""; } } if (song.IsMp3) { id3v2tag.IsCompilation = song.Compilation; } file.Tag.Disc = song.DiscNumber; file.Tag.DiscCount = song.DiscCount; splitValues = song.Genre.Split(new[] { ';', '|' }); file.Tag.Genres = splitValues; file.Tag.Title = song.Title; file.Tag.Track = song.TrackNumber; file.Tag.TrackCount = song.TrackCount; file.Tag.Year = (uint)song.Year; double gain; var replayGainTrack = string.IsNullOrEmpty(song.ReplayGainTrack) ? "" : song.ReplayGainTrack.Substring(0, song.ReplayGainTrack.IndexOf(" ", StringComparison.Ordinal)); if (double.TryParse(replayGainTrack, NumberStyles.Any, CultureInfo.InvariantCulture, out gain)) { file.Tag.ReplayGainTrackGain = gain; } if (Double.TryParse(song.ReplayGainTrackPeak, NumberStyles.Any, CultureInfo.InvariantCulture, out gain)) { file.Tag.ReplayGainTrackPeak = gain; } var replayGainAlbum = string.IsNullOrEmpty(song.ReplayGainAlbum) ? "" : song.ReplayGainAlbum.Substring(0, song.ReplayGainAlbum.IndexOf(" ", StringComparison.Ordinal)); if (Double.TryParse(replayGainAlbum, NumberStyles.Any, CultureInfo.InvariantCulture, out gain)) { file.Tag.ReplayGainAlbumGain = gain; } if (Double.TryParse(song.ReplayGainAlbumPeak, NumberStyles.Any, CultureInfo.InvariantCulture, out gain)) { file.Tag.ReplayGainAlbumPeak = gain; } #endregion #region MusicBrainz file.Tag.MusicBrainzArtistId = song.MusicBrainzArtistId; file.Tag.MusicBrainzDiscId = song.MusicBrainzDiscId; file.Tag.MusicBrainzReleaseArtistId = song.MusicBrainzReleaseArtistId; file.Tag.MusicBrainzReleaseCountry = song.MusicBrainzReleaseCountry; file.Tag.MusicBrainzReleaseGroupId = song.MusicBrainzReleaseGroupId; file.Tag.MusicBrainzReleaseId = song.MusicBrainzReleaseId; file.Tag.MusicBrainzReleaseStatus = song.MusicBrainzReleaseStatus; file.Tag.MusicBrainzTrackId = song.MusicBrainzTrackId; file.Tag.MusicBrainzReleaseType = song.MusicBrainzReleaseType; #endregion #region Detailed Information splitValues = song.Composer.Split(new[] { ';', '|' }); file.Tag.Composers = splitValues; file.Tag.Conductor = song.Conductor; file.Tag.Copyright = song.Copyright; file.Tag.Grouping = song.Grouping; splitValues = song.ArtistSortName.Split(new[] { ';', '|' }); file.Tag.PerformersSort = splitValues; splitValues = song.AlbumArtistSortName.Split(new[] { ';', '|' }); file.Tag.AlbumArtistsSort = splitValues; file.Tag.AlbumSort = song.AlbumSortName; file.Tag.TitleSort = song.TitleSortName; #endregion #region Picture List <TagLib.Picture> pics = new List <TagLib.Picture>(); foreach (Picture pic in song.Pictures) { TagLib.Picture tagPic = new TagLib.Picture(); try { byte[] byteArray = pic.Data; ByteVector data = new ByteVector(byteArray); tagPic.Data = data; tagPic.Description = pic.Description; tagPic.MimeType = "image/jpg"; tagPic.Type = pic.Type; pics.Add(tagPic); } catch (Exception ex) { log.Error("Error saving Picture: {0}", ex.Message); } file.Tag.Pictures = pics.ToArray(); } // Clear the picture if (song.Pictures.Count == 0) { file.Tag.Pictures = pics.ToArray(); } #endregion #region Lyrics if (song.Lyrics != null && song.Lyrics != "") { file.Tag.Lyrics = song.Lyrics; if (song.IsMp3) { id3v2tag.RemoveFrames("USLT"); foreach (Lyric lyric in song.LyricsFrames) { UnsynchronisedLyricsFrame lyricframe = UnsynchronisedLyricsFrame.Get(id3v2tag, lyric.Description, lyric.Language, true); lyricframe.Text = lyric.Text; lyricframe.Description = lyric.Description; lyricframe.Language = lyric.Language; } } else { file.Tag.Lyrics = song.Lyrics; } } else { file.Tag.Lyrics = ""; } #endregion #region Ratings if (song.IsMp3) { id3v2tag.RemoveFrames("POPM"); if (song.Ratings.Count > 0) { foreach (PopmFrame rating in song.Ratings) { PopularimeterFrame popmFrame = PopularimeterFrame.Get(id3v2tag, rating.User, true); popmFrame.Rating = Convert.ToByte(rating.Rating); popmFrame.PlayCount = Convert.ToUInt32(rating.PlayCount); } } } else if (song.TagType == "ogg" || song.TagType == "flac") { if (song.Ratings.Count > 0) { XiphComment xiph = file.GetTag(TagLib.TagTypes.Xiph, true) as XiphComment; xiph.SetField("RATING", song.Rating.ToString()); } } #endregion #region Non- Standard Taglib and User Defined Frames if (options.MainSettings.ClearUserFrames) { foreach (Frame frame in song.UserFrames) { ByteVector frameId = new ByteVector(frame.Id); if (frame.Id == "TXXX") { id3v2tag.SetUserTextAsString(frame.Description, "", true); } else { id3v2tag.SetTextFrame(frameId, ""); } } } List <Frame> allFrames = new List <Frame>(); allFrames.AddRange(song.Frames); // The only way to avoid duplicates of User Frames is to delete them by assigning blank values to them if (song.SavedUserFrames != null && !options.MainSettings.ClearUserFrames) { // Clean the previously saved Userframes, to avoid duplicates foreach (Frame frame in song.SavedUserFrames) { ByteVector frameId = new ByteVector(frame.Id); if (frame.Id == "TXXX") { id3v2tag.SetUserTextAsString(frame.Description, "", true); } else { id3v2tag.SetTextFrame(frameId, ""); } } allFrames.AddRange(song.UserFrames); } foreach (Frame frame in allFrames) { ByteVector frameId = new ByteVector(frame.Id); // The only way to avoid duplicates of User Frames is to delete them by assigning blank values to them if (frame.Id == "TXXX") { if (frame.Description != "") { id3v2tag.SetUserTextAsString(frame.Description, "", true); id3v2tag.SetUserTextAsString(frame.Description, frame.Value, true); } } else { id3v2tag.SetTextFrame(frameId, ""); id3v2tag.SetTextFrame(frameId, frame.Value); } } #endregion // Now, depending on which frames the user wants to save, we will remove the other Frames file = Util.FormatID3Tag(file); // Set the encoding for ID3 Tags if (song.IsMp3) { TagLib.Id3v2.Tag.ForceDefaultEncoding = true; switch (options.MainSettings.CharacterEncoding) { case "Latin1": TagLib.Id3v2.Tag.DefaultEncoding = StringType.Latin1; break; case "UTF16": TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16; break; case "UTF16-BE": TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16BE; break; case "UTF8": TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF8; break; case "UTF16-LE": TagLib.Id3v2.Tag.DefaultEncoding = StringType.UTF16LE; break; } } } // Save the file file.Save(); } catch (Exception ex) { log.Error("File Save: Error processing file: {0} {1}", song.FullFileName, ex.Message); errorMessage = string.Format(LocalizeDictionary.Instance.GetLocalizedObject("MPTagThat", "Strings", "message_ErrorSave", LocalizeDictionary.Instance.Culture).ToString(), song.FullFileName); error = true; } if (error) { return(false); } return(true); }
public void SetComment(XiphComment comment) { this.codec.SetCommentPacket(this.packets, comment); }
/// <summary> /// Stores a Xiph comment in the codec-specific comment /// packet. /// </summary> /// <param name="comment"> /// A <see cref="XiphComment" /> object to store in the /// comment packet. /// </param> public void SetComment (XiphComment comment) { codec.SetCommentPacket (packets, comment); }