/// <summary> /// Reads the SV8 metadata from a MPC (MusePack) audio file. /// </summary> /// <param name="filePath">Audio file path</param> /// <returns>SV8 data structure</returns> public static SV8Tag Read(string filePath) { SV8Tag data = new SV8Tag(); // Check for nulls or empty if (String.IsNullOrEmpty(filePath)) throw new Exception("The file path cannot be null or empty!"); #if WINDOWSSTORE #else BinaryReader reader = null; FileStream stream = null; try { // Open binary reader stream = File.OpenRead(filePath); reader = new BinaryReader(stream); // Get file length long fileLength = reader.BaseStream.Length; long headerOffset = 0; // Read "File magic number" byte[] bytesMagicNumber = reader.ReadBytes(4); string magicNumber = Encoding.UTF8.GetString(bytesMagicNumber, 0, bytesMagicNumber.Length); // Validate number if (magicNumber.ToUpper() != "MPCK") { throw new SV8TagNotFoundException("The file is not in MPC/SV8 format!", null); } // Loop through header keys while (true) { // Read key (16-bits) byte[] bytesKey = reader.ReadBytes(2); string key = Encoding.UTF8.GetString(bytesKey, 0, bytesKey.Length); // Read variable integer int intSize = 0; long size = SV8Metadata.ReadVariableLengthInteger(ref reader, ref intSize); // Check key if (key.ToUpper() == "SH") { // Read value (size - 3 bytes) byte[] bytesValue = reader.ReadBytes((int)size - 3); // Stream header byte[] bytesCRC = new byte[4] { bytesValue[0], bytesValue[1], bytesValue[2], bytesValue[3] }; byte byteStreamVersion = bytesValue[4]; // Check if the header version is 8 if (byteStreamVersion != 8) { throw new SV8TagNotFoundException("This file header version is not SV8!", null); } // Get sample count (variable integer) int intLength = 0; byte[] bytesRemainingSampleCount = new byte[bytesValue.Length - 5]; Array.Copy(bytesValue, 5, bytesRemainingSampleCount, 0, bytesValue.Length - 5); long sampleCount = SV8Metadata.GetVariableLengthInteger(bytesRemainingSampleCount, ref intLength); // Get beginning silence (variable integer) byte[] bytesRemainingBeginSilence = new byte[bytesRemainingSampleCount.Length - intLength]; Array.Copy(bytesRemainingSampleCount, intLength, bytesRemainingBeginSilence, 0, bytesRemainingSampleCount.Length - intLength); long beginSilence = SV8Metadata.GetVariableLengthInteger(bytesRemainingBeginSilence, ref intLength); byte[] bytesRemaining = new byte[bytesRemainingBeginSilence.Length - intLength]; Array.Copy(bytesRemainingBeginSilence, intLength, bytesRemaining, 0, bytesRemainingBeginSilence.Length - intLength); // There should be 3 bytes left, but only the first two seem useful. // Convert to big endian int bigEndian = GetInt32(bytesRemaining, 0, false); int sampleFrequency = ((bigEndian & 0xE000) >> 13); data.MaxUsedBands = ((bigEndian & 0x1F00) >> 8); data.AudioChannels = ((bigEndian & 0x00F0) >> 4) + 1; int midSideStereoUsed = ((bigEndian & 0x0008) >> 3); data.AudioBlockFrames = ((bigEndian & 0x0007) >> 0); // Set metadata // Sample rate if (sampleFrequency == 0) { data.SampleRate = 44100; } else if (sampleFrequency == 1) { data.SampleRate = 48000; } else if (sampleFrequency == 2) { data.SampleRate = 37800; } else if (sampleFrequency == 3) { data.SampleRate = 32000; } // Set other metadata data.LengthSamples = sampleCount; data.BeginningSilence = beginSilence; data.MidSideStereoEnabled = (midSideStereoUsed == 1) ? true : false; } else if (key.ToUpper() == "RG") { // Replay Gain byte byteReplayGainVersion = reader.ReadByte(); byte[] bytesTitleGain = reader.ReadBytes(2); byte[] bytesTitlePeak = reader.ReadBytes(2); byte[] bytesAlbumGain = reader.ReadBytes(2); byte[] bytesAlbumPeak = reader.ReadBytes(2); // Read dummy byte (I don't know its use) //reader.ReadByte(); // Set metadata data.ReplayGainVersion = (int)byteReplayGainVersion; data.TitleGain = BitConverter.ToInt16(bytesTitleGain, 0); data.TitlePeak = BitConverter.ToInt16(bytesTitlePeak, 0); data.AlbumGain = BitConverter.ToInt16(bytesAlbumGain, 0); data.AlbumPeak = BitConverter.ToInt16(bytesAlbumPeak, 0); } else if (key.ToUpper() == "EI") { // Encoder Info // Profile // 0 = NA // 1 = Unstable // 2 = NA // 3 = NA // 4 = NA // 5 = Quality 0 // 6 = Quality 1 // 7 = Telephone // 8 = Thumb (Q3) // 9 = Radio (Q4) // 10 = Standard (Q5) // 11 = Extreme (Q6) // 12 = Insane (Q7) // 13 = BrainDead (Q8) // 14 = Quality 9 (Q9) // 15 = Quality 10 (Q10) short intProfile_PNS = (short)reader.ReadByte(); data.EncoderProfile = (intProfile_PNS >> 1) / 8; data.EncoderPNSTool = ((intProfile_PNS & 0x01) >> 0) == 1 ? true : false; // Encoder version data.EncoderMajor = reader.ReadByte(); data.EncoderMinor = reader.ReadByte(); data.EncoderBuild = reader.ReadByte(); } else if (key.ToUpper() == "SO") { // Read variable integer and byte. We don't actually store this int offsetSize = 0; data.SeekTableOffset = SV8Metadata.ReadVariableLengthInteger(ref reader, ref offsetSize); reader.ReadByte(); } else if (key.ToUpper() == "AP") { // This is an audio packet; no more header information headerOffset = reader.BaseStream.Position; break; } } // Calculate length data.LengthMS = ConvertAudio.ToMS(data.LengthSamples, (uint)data.SampleRate); data.Length = Conversion.MillisecondsToTimeString((ulong)data.LengthMS); long audioLengthBytes = fileLength - headerOffset; data.Bitrate = (int)(audioLengthBytes / data.LengthMS) * 8; } catch { throw; } finally { // Dispose stream (reader will be automatically disposed too) stream.Close(); } #endif return data; }
/// <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"; } }