/// <summary> /// Fills the AudioFile properties from the APETag structure. /// </summary> /// <param name="tag">APETag structure</param> private void FillProperties(APETag tag) { ArtistName = tag.Artist; AlbumTitle = tag.Album; Title = tag.Title; Genre = tag.Genre; //DiscNumber = tag TrackNumber = (uint)tag.Track; //TrackCount = tag.tr //Lyrics = tag.ly Year = (uint)tag.Year.Year; }
/// <summary> /// Refreshes the metadata of the audio file. /// </summary> public void RefreshMetadata() { // Get file size #if WINDOWSSTORE fileSize = 0; #else var fileInfo = new FileInfo(filePath); fileSize = fileInfo.Length; #endif // Check what is the type of the audio file if (fileType == AudioFileFormat.MP3) { // Declare variables TagLib.Mpeg.AudioFile file = null; try { // Create a more specific type of class for MP3 files file = new TagLib.Mpeg.AudioFile(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Convert codec into a header TagLib.Mpeg.AudioHeader header = (TagLib.Mpeg.AudioHeader)codec; // Copy properties audioChannels = header.AudioChannels; frameLength = header.AudioFrameLength; audioLayer = header.AudioLayer; sampleRate = header.AudioSampleRate; bitsPerSample = 16; // always 16-bit channelMode = header.ChannelMode; bitrate = header.AudioBitrate; length = Conversion.TimeSpanToTimeString(header.Duration); } } catch (Exception ex) { // Throw exception TODO: Check if file exists when catching the exception (to make a better error description) throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } try { // // Check if there's a Xing header // XingInfoHeaderData xingHeader = XingInfoHeaderReader.ReadXingInfoHeader(filePath, firstBlockPosition); // // // Check if the read was successful // if (xingHeader.Status == XingInfoHeaderStatus.Successful) // { // // Set property value // //m_xingInfoHeader = xingHeader; // mp3EncoderDelay = xingHeader.EncoderDelay; // mp3EncoderPadding = xingHeader.EncoderPadding; // mp3EncoderVersion = xingHeader.EncoderVersion; // mp3HeaderType = xingHeader.HeaderType; // } } catch (Exception ex) { throw ex; } } else if (fileType == AudioFileFormat.FLAC) { // Declare variables TagLib.Flac.File file = null; try { // Read VorbisComment in FLAC file file = new TagLib.Flac.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Convert codec into a header TagLib.Flac.StreamHeader header = (TagLib.Flac.StreamHeader)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = header.BitsPerSample; length = Conversion.TimeSpanToTimeString(header.Duration); } } catch (Exception ex) { // Throw exception throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.OGG) { // Declare variables TagLib.Ogg.File file = null; try { // Read VorbisComment in OGG file file = new TagLib.Ogg.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Check what kind of codec is used if (codec is TagLib.Ogg.Codecs.Theora) { // Do nothing, this is useless for audio. } else if (codec is TagLib.Ogg.Codecs.Vorbis) { // Convert codec into a header TagLib.Ogg.Codecs.Vorbis header = (TagLib.Ogg.Codecs.Vorbis)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = 16; length = Conversion.TimeSpanToTimeString(header.Duration); } } } catch (Exception ex) { // Throw exception throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.APE) { // Monkey's Audio (APE) supports APEv2 tags. // http://en.wikipedia.org/wiki/Monkey's_Audio // Declare variables TagLib.Ape.File file = null; try { // Read APE metadata apeTag = APEMetadata.Read(filePath); // Get APEv1/v2 tags from APE file file = new TagLib.Ape.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Check what kind of codec is used if (codec is TagLib.Ape.StreamHeader) { // Convert codec into a header TagLib.Ape.StreamHeader header = (TagLib.Ape.StreamHeader)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = 16; length = Conversion.TimeSpanToTimeString(header.Duration); } } } catch (Exception ex) { // Throw exception throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.MPC) { // MusePack (MPC) supports APEv2 tags. // http://en.wikipedia.org/wiki/Musepack try { // Try to read SV8 header sv8Tag = SV8Metadata.Read(filePath); // Set audio properties audioChannels = sv8Tag.AudioChannels; sampleRate = sv8Tag.SampleRate; bitsPerSample = 16; length = sv8Tag.Length; bitrate = sv8Tag.Bitrate; } catch (SV8TagNotFoundException exSV8) { try { // Try to read the SV7 header sv7Tag = SV7Metadata.Read(filePath); // Set audio properties audioChannels = sv7Tag.AudioChannels; sampleRate = sv7Tag.SampleRate; bitsPerSample = 16; length = sv7Tag.Length; bitrate = sv7Tag.Bitrate; } catch (SV7TagNotFoundException exSV7) { // No headers have been found! SV8TagNotFoundException finalEx = new SV8TagNotFoundException(exSV8.Message, exSV7); throw new Exception("Error: The file is not in SV7/MPC or SV8/MPC format!", finalEx); } } try { // Read APE tag apeTag = APEMetadata.Read(filePath); // Copy tags FillProperties(apeTag); } catch (Exception ex) { // Check exception if (ex is APETagNotFoundException) { // Skip file } else { throw; } } } else if (fileType == AudioFileFormat.OFR) { // TagLib does not support OFR files... // OptimFROG (OFR) supports APEv2 tags. // http://en.wikipedia.org/wiki/OptimFROG } else if (fileType == AudioFileFormat.WV) { // WavPack supports APEv2 and ID3v1 tags. // http://www.wavpack.com/wavpack_doc.html // Declare variables TagLib.WavPack.File file = null; try { // Read WavPack tags file = new TagLib.WavPack.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Check what kind of codec is used if (codec is TagLib.WavPack.StreamHeader) { // Convert codec into a header TagLib.WavPack.StreamHeader header = (TagLib.WavPack.StreamHeader)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = 16; length = Conversion.TimeSpanToTimeString(header.Duration); } } } catch (Exception ex) { // Throw exception throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.TTA) { // The True Audio (TTA) format supports ID3v1, ID3v2 and APEv2 tags. // http://en.wikipedia.org/wiki/TTA_(codec) audioChannels = 2; sampleRate = 44100; bitsPerSample = 16; // TagLib doesn't work. } else if (fileType == AudioFileFormat.WAV) { // Declare variables TagLib.Riff.File file = null; try { // Get WAV file file = new TagLib.Riff.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Check what kind of codec is used if (codec is TagLib.Riff.WaveFormatEx) { // Convert codec into a header TagLib.Riff.WaveFormatEx header = (TagLib.Riff.WaveFormatEx)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = 16; length = Conversion.TimeSpanToTimeString(header.Duration); } } } catch (Exception ex) { // Throw exception throw new Exception("An error occured while reading the tags and properties of the file (" + filePath + ").", ex); } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.WMA) { // Declare variables TagLib.Asf.File file = null; try { // Read ASF/WMA tags file = new TagLib.Asf.File(filePath); // Get the position of the first and last block firstBlockPosition = file.InvariantStartPosition; lastBlockPosition = file.InvariantEndPosition; // Copy tags FillProperties(file.Tag); // The right length is here, not in the codec data structure length = Conversion.TimeSpanToTimeString(file.Properties.Duration); // Loop through codecs (usually just one) foreach (TagLib.ICodec codec in file.Properties.Codecs) { // Check what kind of codec is used if (codec is TagLib.Riff.WaveFormatEx) { // Convert codec into a header TagLib.Riff.WaveFormatEx header = (TagLib.Riff.WaveFormatEx)codec; // Copy properties bitrate = header.AudioBitrate; audioChannels = header.AudioChannels; sampleRate = header.AudioSampleRate; bitsPerSample = 16; } } } catch (Exception ex) { throw ex; } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } else if (fileType == AudioFileFormat.AAC) { // Declare variables TagLib.Aac.File file = null; try { // Read AAC tags file = new TagLib.Aac.File(filePath); // Doesn't seem to work very well... } catch (Exception ex) { throw ex; } finally { // Dispose file (if needed) if (file != null) file.Dispose(); } } // If the song has no name, give filename as the name if (String.IsNullOrEmpty(Title)) { Title = Path.GetFileNameWithoutExtension(filePath); } // If the artist has no name, give it "Unknown Artist" if (String.IsNullOrEmpty(ArtistName)) { ArtistName = "Unknown Artist"; } // If the song has no album title, give it "Unknown Album" if (String.IsNullOrEmpty(AlbumTitle)) { AlbumTitle = "Unknown Album"; } }
/// <summary> /// Reads the APEv1/APEv2 metadata from an audio file. /// Supports the following audio formats: APE. /// </summary> /// <param name="filePath">Audio file path</param> /// <returns>APE data structure</returns> public static APETag Read(string filePath) { if (String.IsNullOrEmpty(filePath)) throw new Exception("The file path cannot be null or empty!"); APETag data = new APETag(); #if WINDOWSSTORE #else BinaryReader reader = null; FileStream stream = null; try { // Open binary reader stream = File.OpenRead(filePath); reader = new BinaryReader(stream); // APEv1/APEv2 have footers, but only APEv2 has a header. // In fact, the header and footer contain the exact same information. // http://wiki.hydrogenaudio.org/index.php?title=APE_Tags_Header // APE metadata can always be found at the very end of the file. // TODO: maybe seek the reader near the end of file? //byte[] bytes = reader.ReadBytes(4); // The header/footer structure is as followed: // 1) Preamble (64-bits) APETAGEX // 2) Version number (32-bits) 1000 or 2000 // 3) Tag size (32-bits) // 4) Item count (32-bits) // 5) Tags flags (32-bits) // 6) Reserved (64-bits) Must be zero // Total: 32 bytes // Seek at the end of the file (length - 32 bytes) reader.BaseStream.Seek(-32, SeekOrigin.End); // Read Preamble (64-bits) byte[] bytesPreamble = reader.ReadBytes(8); string preamble = Encoding.UTF8.GetString(bytesPreamble, 0, bytesPreamble.Length); // Validate preamble if (preamble != "APETAGEX") { throw new APETagNotFoundException("The APE tag was not found."); } // Read Version number (32-bits) byte[] bytesVersionNumber = reader.ReadBytes(4); int versionNumber = BitConverter.ToInt32(bytesVersionNumber, 0); // Check version number if (versionNumber == 1000) { data.Version = APETagVersion.APEv1; } else if (versionNumber == 2000) { data.Version = APETagVersion.APEv2; } else { throw new APETagNotFoundException("The APE tag version is unknown (" + versionNumber.ToString() + ")."); } // Read Tag size (32-bits) // This is the total size of the items + header (if APEv2) byte[] bytesTagSize = reader.ReadBytes(4); data.TagSize = BitConverter.ToInt32(bytesTagSize, 0); // Read Item count (32-bits) byte[] bytesItemCount = reader.ReadBytes(4); int itemCount = BitConverter.ToInt32(bytesItemCount, 0); // Read Tags flags (32-bits) // http://wiki.hydrogenaudio.org/index.php?title=Ape_Tags_Flags byte[] bytesTagsFlags = reader.ReadBytes(4); // Read reserved (64-bits) byte[] bytesReserved = reader.ReadBytes(8); // Seek back to the start of the APE metadata (32 bytes - tag size) reader.BaseStream.Seek(-32 - data.TagSize, SeekOrigin.End); // Is this APEv2? if (data.Version == APETagVersion.APEv2) { // Skip header (32 bytes) reader.BaseStream.Seek(32, SeekOrigin.Current); } // Read items for (int a = 0; a < itemCount; a++) { // Read item value size byte[] bytesItemValueSize = reader.ReadBytes(4); int itemValueSize = BitConverter.ToInt32(bytesItemValueSize, 0); // Read item flags byte[] bytesItemFlags = reader.ReadBytes(4); // The key size is variable but always ends by a key terminator (0x00). // The key characters vary from 0x20 (space) to 0x7E (tilde). // Read key List<byte> bytesKey = new List<byte>(); while (true) { // Read characters until the terminator is found (0x00) byte byteChar = reader.ReadByte(); if (byteChar == 0x00) { // Exit loop break; } else { bytesKey.Add(byteChar); } } // Read value byte[] bytesValue = reader.ReadBytes(itemValueSize); // Cast key and value into UTF8 strings var bytesKeyArray = bytesKey.ToArray(); string key = Encoding.UTF8.GetString(bytesKeyArray, 0, bytesKeyArray.Length); string value = Encoding.UTF8.GetString(bytesValue, 0, bytesValue.Length); // Add key/value to dictionary data.Dictionary.Add(key, value); } // Go through dictionary items foreach (KeyValuePair<string, string> keyValue in data.Dictionary) { string key = keyValue.Key.ToUpper().Trim(); if (key == "TITLE") { data.Title = keyValue.Value; } else if (key == "SUBTITLE") { data.Subtitle = keyValue.Value; } else if (key == "ARTIST") { data.Artist = keyValue.Value; } else if (key == "ALBUM") { data.Album = keyValue.Value; } else if (key == "DEBUT ALBUM") { data.DebutAlbum = keyValue.Value; } else if (key == "PUBLISHER") { data.Publisher = keyValue.Value; } else if (key == "CONDUCTOR") { data.Conductor = keyValue.Value; } else if (key == "TRACK") { int value = 0; int.TryParse(keyValue.Value, out value); data.Track = value; } else if (key == "COMPOSER") { data.Composer = keyValue.Value; } else if (key == "COMMENT") { data.Comment = keyValue.Value; } else if (key == "COPYRIGHT") { data.Copyright = keyValue.Value; } else if (key == "PUBLICATIONRIGHT") { data.PublicationRight = keyValue.Value; } else if (key == "EAN/UPC") { int value = 0; int.TryParse(keyValue.Value, out value); data.EAN_UPC = value; } else if (key == "ISBN") { int value = 0; int.TryParse(keyValue.Value, out value); data.ISBN = value; } else if (key == "LC") { data.LC = keyValue.Value; } else if (key == "YEAR") { DateTime value = DateTime.MinValue; DateTime.TryParse(keyValue.Value, out value); data.Year = value; } else if (key == "RECORD DATE") { DateTime value = DateTime.MinValue; DateTime.TryParse(keyValue.Value, out value); data.RecordDate = value; } else if (key == "RECORD LOCATION") { data.RecordLocation = keyValue.Value; } else if (key == "GENRE") { data.Genre = keyValue.Value; } else if (key == "INDEX") { data.Index = keyValue.Value; } else if (key == "RELATED") { data.Related = keyValue.Value; } else if (key == "ABSTRACT") { data.Abstract = keyValue.Value; } else if (key == "LANGUAGE") { data.Language = keyValue.Value; } else if (key == "BIBLIOGRAPHY") { data.Bibliography = keyValue.Value; } } } catch { // Leave metadata empty } finally { // Dispose stream (reader will be automatically disposed too) stream.Close(); } #endif return data; }