diff --git a/APEDotNet/APEDotNet.vcproj b/APEDotNet/APEDotNet.vcproj index 634179d..ca5d499 100644 --- a/APEDotNet/APEDotNet.vcproj +++ b/APEDotNet/APEDotNet.vcproj @@ -332,6 +332,10 @@ RelativePath="System.XML.dll" AssemblyName="System.Xml, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL" /> + GetStringTags (true); return _tags; } void set (NameValueCollection ^tags) { @@ -180,32 +179,7 @@ namespace APEDotNet { int nBlockAlign; bool _samplesWaiting; unsigned char * pBuffer; - const wchar_t * _path; - - void GetTags (void) - { - _tags = gcnew NameValueCollection(); - CAPETag apeTag (_path, TRUE); - for (int i = 0; ; i++) - { - CAPETagField * field = apeTag.GetTagField (i); - if (!field) - break; - if (field->GetIsUTF8Text()) - { - int valueSize = field->GetFieldValueSize(); - while (valueSize && field->GetFieldValue()[valueSize-1]=='\0') - valueSize --; - const wchar_t * fieldName = field->GetFieldName (); - if (!wcsicmp (fieldName, L"YEAR")) - fieldName = L"DATE"; - if (!wcsicmp (fieldName, L"TRACK")) - fieldName = L"TRACKNUMBER"; - _tags->Add (gcnew String (fieldName), - gcnew String (field->GetFieldValue(), 0, valueSize, System::Text::Encoding::UTF8)); - } - } - } + String^ _path; #if 0 APE__StreamDecoderWriteStatus WriteCallback(const APE__StreamDecoder *decoder, diff --git a/APETagDotNet/APETagDotNet.cs b/APETagDotNet/APETagDotNet.cs new file mode 100644 index 0000000..8d89619 --- /dev/null +++ b/APETagDotNet/APETagDotNet.cs @@ -0,0 +1,608 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text; +using System.IO; + +namespace APETagsDotNet +{ + public class LittleEndian + { + static public Int32 Read32 (byte[] buffer, int offset) + { + return buffer[offset] + (buffer[offset + 1] << 8) + (buffer[offset + 2] << 16) + (buffer[offset + 3] << 24); + } + static public void Write32(byte[] buffer, int offset, Int32 value) + { + buffer[offset++] = (byte) (value & 0xff); value >>= 8; + buffer[offset++] = (byte) (value & 0xff); value >>= 8; + buffer[offset++] = (byte) (value & 0xff); value >>= 8; + buffer[offset++] = (byte) (value & 0xff); value >>= 8; + } + } + + public class APE_TAG_FOOTER + { + public APE_TAG_FOOTER(int nFields, int nFieldBytes) + { + m_cID = new ASCIIEncoding().GetBytes ("APETAGEX"); + m_cReserved = new byte[8]; + m_nFields = nFields; + m_nFlags = APETagDotNet.APE_TAG_FLAGS_DEFAULT; + m_nSize = nFieldBytes + APETagDotNet.APE_TAG_FOOTER_BYTES; + m_nVersion = APETagDotNet.CURRENT_APE_TAG_VERSION; + } + + public APE_TAG_FOOTER(byte[] buffer) + { + m_cID = new byte[8]; + m_cReserved = new byte[8]; + + Array.Copy(buffer, m_cID, 8); + m_nVersion = LittleEndian.Read32(buffer, 8); + m_nSize = LittleEndian.Read32(buffer, 12); + m_nFields = LittleEndian.Read32(buffer, 16); + m_nFlags = LittleEndian.Read32(buffer, 20); + Array.Copy (buffer, 24, m_cReserved, 0, 8); + } + + public int Save(byte[] spRawTag, int nLocation) + { + Array.Copy(m_cID, 0, spRawTag, nLocation, 8); + LittleEndian.Write32(spRawTag, nLocation + 8, m_nVersion); + LittleEndian.Write32(spRawTag, nLocation + 12, m_nSize); + LittleEndian.Write32(spRawTag, nLocation + 16, m_nFields); + LittleEndian.Write32(spRawTag, nLocation + 20, m_nFlags); + Array.Copy(m_cReserved, 0, spRawTag, nLocation + 24, 8); + return APETagDotNet.APE_TAG_FOOTER_BYTES; + } + + public int TotalTagBytes { get { return m_nSize + (HasHeader ? APETagDotNet.APE_TAG_FOOTER_BYTES : 0); } } + public int FieldBytes { get { return m_nSize - APETagDotNet.APE_TAG_FOOTER_BYTES; } } + public int FieldsOffset { get { return HasHeader ? APETagDotNet.APE_TAG_FOOTER_BYTES : 0; } } + public int NumberFields { get { return m_nFields; } } + public bool HasHeader { get { return (m_nFlags & APETagDotNet.APE_TAG_FLAG_CONTAINS_HEADER) != 0; } } + public bool IsHeader { get { return (m_nFlags & APETagDotNet.APE_TAG_FLAG_IS_HEADER) != 0; } } + public int Version { get { return m_nVersion; } } + public bool IsValid + { + get + { + return new ASCIIEncoding().GetString (m_cID) == "APETAGEX" && + (m_nVersion <= APETagDotNet.CURRENT_APE_TAG_VERSION) && + (m_nFields <= 65536) && + (FieldBytes <= (1024 * 1024 * 16)); + } + } + + private byte[] m_cID; // should equal 'APETAGEX' + private int m_nVersion; // equals CURRENT_APE_TAG_VERSION + private int m_nSize; // the complete size of the tag, including this footer (excludes header) + private int m_nFields; // the number of fields in the tag + private int m_nFlags; // the tag flags + private byte[] m_cReserved; // reserved for later use (must be zero) + }; + + + public class APETagField + { + // create a tag field (use nFieldBytes = -1 for null-terminated strings) + public APETagField (string fieldName, byte[] fieldValue, int fieldFlags) { + _fieldName = fieldName; + _fieldValue = fieldValue; + _fieldFlags = fieldFlags; + } + + // destructor + ~APETagField() { + } + + // gets the size of the entire field in bytes (name, value, and metadata) + public int GetFieldSize() + { + return _fieldName.Length + 1 + _fieldValue.Length + 4 + 4; + } + + // get the name of the field + public string FieldName { get { return _fieldName; } } + + // get the value of the field + public byte[] FieldValue { get { return _fieldValue; } } + + // output the entire field to a buffer (GetFieldSize() bytes) + public int SaveField(byte[] pBuffer, int pos) + { + LittleEndian.Write32(pBuffer, pos, _fieldValue.Length); + LittleEndian.Write32(pBuffer, pos + 4, _fieldFlags); + Array.Copy (new ASCIIEncoding().GetBytes(_fieldName), pBuffer, pos + 8); + pBuffer[pos + 8 + _fieldName.Length] = 0; + Array.Copy(_fieldValue, pBuffer, pos + 8 + _fieldName.Length + 1); + return GetFieldSize(); + } + + // checks to see if the field is read-only + public bool IsReadOnly { get { return (_fieldFlags & APETagDotNet.TAG_FIELD_FLAG_READ_ONLY) != 0; } } + public bool IsUTF8Text { get { return (_fieldFlags & APETagDotNet.TAG_FIELD_FLAG_DATA_TYPE_MASK) == APETagDotNet.TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8; } } + + // set helpers (use with EXTREME caution) + public int FieldFlags { + get { return _fieldFlags; } + set { _fieldFlags = value; } + } + + private string _fieldName; + private byte[] _fieldValue; + private int _fieldFlags; + }; + + public class APETagDotNet + { + /***************************************************************************************** + The version of the APE tag + *****************************************************************************************/ + public const int CURRENT_APE_TAG_VERSION = 2000; + + public const int ID3_TAG_BYTES = 128; + + /***************************************************************************************** + Footer (and header) flags + *****************************************************************************************/ + public const int APE_TAG_FLAG_CONTAINS_HEADER = (1 << 31); + public const int APE_TAG_FLAG_CONTAINS_FOOTER = (1 << 30); + public const int APE_TAG_FLAG_IS_HEADER = (1 << 29); + + public const int APE_TAG_FLAGS_DEFAULT = (APE_TAG_FLAG_CONTAINS_FOOTER); + + /***************************************************************************************** + Tag field flags + *****************************************************************************************/ + public const int TAG_FIELD_FLAG_READ_ONLY = (1 << 0); + + public const int TAG_FIELD_FLAG_DATA_TYPE_MASK = (6); + public const int TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8 = (0 << 1); + public const int TAG_FIELD_FLAG_DATA_TYPE_BINARY = (1 << 1); + public const int TAG_FIELD_FLAG_DATA_TYPE_EXTERNAL_INFO = (2 << 1); + public const int TAG_FIELD_FLAG_DATA_TYPE_RESERVED = (3 << 1); + + /***************************************************************************************** + The footer at the end of APE tagged files (can also optionally be at the front of the tag) + *****************************************************************************************/ + public const int APE_TAG_FOOTER_BYTES = 32; + + + // create an APE tags object + // bAnalyze determines whether it will analyze immediately or on the first request + // be careful with multiple threads / file pointer movement if you don't analyze immediately + public APETagDotNet (string filename, bool analyze) + { + m_spIO = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read); + m_bAnalyzed = false; + m_aryFields = new APETagField[0]; + m_nTagBytes = 0; + m_bIgnoreReadOnly = false; + if (analyze) Analyze (); + } + + // destructor + ~APETagDotNet () { ClearFields (); } + + // save the tag to the I/O source (bUseOldID3 forces it to save as an ID3v1.1 tag instead of an APE tag) + int Save () + { + if (!Remove(false)) + return -1; + + if (m_aryFields.Length == 0) { return 0; } + + int z = 0; + + // calculate the size of the whole tag + int nFieldBytes = 0; + for (z = 0; z < m_aryFields.Length; z++) + nFieldBytes += m_aryFields[z].GetFieldSize(); + + // sort the fields + SortFields(); + + // build the footer + APE_TAG_FOOTER APETagFooter = new APE_TAG_FOOTER(m_aryFields.Length, nFieldBytes); + + // make a buffer for the tag + int nTotalTagBytes = APETagFooter.TotalTagBytes; + byte[] spRawTag = new byte[APETagFooter.TotalTagBytes]; + + // save the fields + int nLocation = 0; + for (z = 0; z < m_aryFields.Length; z++) + nLocation += m_aryFields[z].SaveField (spRawTag, nLocation); + + // add the footer to the buffer + nLocation += APETagFooter.Save(spRawTag, nLocation); + + // dump the tag to the I/O source + WriteBufferToEndOfIO (spRawTag); + return 0; + } + + // removes any tags from the file (bUpdate determines whether is should re-analyze after removing the tag) + bool Remove(bool bUpdate) + { + // variables + int nBytesRead = 0; + long nOriginalPosition = m_spIO.Position; + + bool bID3Removed = true; + bool bAPETagRemoved = true; + + bool bFailedToRemove = false; + + while (bID3Removed || bAPETagRemoved) + { + bID3Removed = false; + bAPETagRemoved = false; + + // ID3 tag + if (m_spIO.Length > ID3_TAG_BYTES) + { + byte[] cTagHeader = new byte[3]; + m_spIO.Seek (-ID3_TAG_BYTES, SeekOrigin.End); + nBytesRead = m_spIO.Read (cTagHeader, 0, 3); + if (nBytesRead == 3) + { + if (cTagHeader[0]=='T' && cTagHeader[1]=='A' && cTagHeader[2]=='G') + { + try { m_spIO.SetLength(m_spIO.Length - ID3_TAG_BYTES); } + catch { bFailedToRemove = true; } + if (!bFailedToRemove) + bID3Removed = true; + } + } + } + + // APE Tag + if (m_spIO.Length > APE_TAG_FOOTER_BYTES && bFailedToRemove == false) + { + APE_TAG_FOOTER APETagFooter; + m_spIO.Seek(-APE_TAG_FOOTER_BYTES, SeekOrigin.End); + byte [] buf = new byte[APE_TAG_FOOTER_BYTES]; + nBytesRead = m_spIO.Read(buf, 0, APE_TAG_FOOTER_BYTES); + if (nBytesRead == APE_TAG_FOOTER_BYTES) + { + APETagFooter = new APE_TAG_FOOTER(buf); + if (APETagFooter.IsValid) + { + try { m_spIO.SetLength(m_spIO.Length - APETagFooter.TotalTagBytes); } + catch { bFailedToRemove = true; } + if (!bFailedToRemove) + bAPETagRemoved = true; + } + } + } + + } + + m_spIO.Seek(nOriginalPosition, SeekOrigin.Begin); + + if (bUpdate && bFailedToRemove == false) + { + m_bAnalyzed = false; + Analyze(); + } + return !bFailedToRemove; + + } + + // sets the value of a field (use nFieldBytes = -1 for null terminated strings) + // note: using NULL or "" for a string type will remove the field + void SetFieldString(string fieldName, string fieldValue) + { + // remove if empty + if (fieldValue == "") + { + RemoveField(fieldName); + return; + } + UTF8Encoding enc = new UTF8Encoding(); + // UTF-8 encode the value and call the binary SetField(...) + SetFieldBinary (fieldName, enc.GetBytes (fieldValue), TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8); + } + //int SetFieldString(string pFieldName, const char * pFieldValue, bool bAlreadyUTF8Encoded); + void SetFieldBinary(string fieldName, byte[] fieldValue, int fieldFlags) + { + Analyze(); + + // check to see if we're trying to remove the field (by setting it to NULL or an empty string) + bool bRemoving = (fieldValue.Length == 0); + + // get the index + int nFieldIndex = GetTagFieldIndex (fieldName); + if (nFieldIndex != -1) + { + // existing field + + // fail if we're read-only (and not ignoring the read-only flag) + if (!m_bIgnoreReadOnly && (m_aryFields[nFieldIndex].IsReadOnly)) + throw new Exception("read only"); + + // erase the existing field + //SAFE_DELETE(m_aryFields[nFieldIndex]) + + if (bRemoving) + { + RemoveField(nFieldIndex); + return; + } + } + else + { + if (bRemoving) + return; + nFieldIndex = m_aryFields.Length; + Array.Resize (ref m_aryFields, nFieldIndex + 1); + } + + // create the field and add it to the field array + m_aryFields[nFieldIndex] = new APETagField (fieldName, fieldValue, fieldFlags); + } + + // gets the value of a field (returns -1 and an empty buffer if the field doesn't exist) + byte[] GetFieldBinary(string pFieldName) + { + Analyze(); + APETagField pAPETagField = GetTagField (pFieldName); + return (pAPETagField == null) ? null : pAPETagField.FieldValue; + } + public int Count { get { Analyze(); return m_aryFields.Length; } } + public string GetFieldString(string pFieldName) { return GetFieldString(GetTagFieldIndex(pFieldName)); } + public string GetFieldString(int Index) + { + Analyze(); + APETagField pAPETagField = GetTagField (Index); + if (pAPETagField == null) + return null; + if (m_nAPETagVersion < 2000) + return new ASCIIEncoding().GetString(pAPETagField.FieldValue); + if (!pAPETagField.IsUTF8Text) + return null; + return new UTF8Encoding().GetString(pAPETagField.FieldValue); + } + + public NameValueCollection GetStringTags(bool mapToFlac) + { + Analyze(); + NameValueCollection tags = new NameValueCollection (); + for (int i = 0; i < Count; i++) + { + string fieldName = m_aryFields[i].FieldName; + string fieldValue = GetFieldString (i); + if (fieldName != null && fieldValue != null) + { + if (mapToFlac) + { + if (fieldName.ToUpper() == "YEAR") + fieldName = "DATE"; + if (fieldName.ToUpper() == "TRACK") + fieldName = "TRACKNUMBER"; + } + tags.Add (fieldName, fieldValue); + } + } + return tags; + } + + + // remove a specific field + void RemoveField(string pFieldName) + { + RemoveField(GetTagFieldIndex(pFieldName)); + } + + void RemoveField(int nIndex) + { + for (int i = nIndex; i < m_aryFields.Length-1; i++) + m_aryFields[i] = m_aryFields[i+1]; + Array.Resize (ref m_aryFields, m_aryFields.Length-1); + } + + // clear all the fields + void ClearFields() { m_aryFields = new APETagField[0]; } + + // get the total tag bytes in the file from the last analyze + // need to call Save() then Analyze() to update any changes + int GetTagBytes() { + Analyze(); + return m_nTagBytes; + } + + // fills in an ID3_TAG using the current fields (useful for quickly converting the tag) + //int CreateID3Tag(ID3_TAG * pID3Tag); + + // see whether the file has an ID3 or APE tag + //bool GetHasID3Tag() { if (!m_bAnalyzed) { Analyze(); } return m_bHasID3Tag; } + bool GetHasAPETag() { Analyze(); return m_bHasAPETag; } + int GetAPETagVersion() { return GetHasAPETag() ? m_nAPETagVersion : -1; } + + // gets a desired tag field (returns NULL if not found) + // again, be careful, because this a pointer to the actual field in this class + APETagField GetTagField(string pFieldName) + { + int nIndex = GetTagFieldIndex (pFieldName); + return (nIndex != -1) ? m_aryFields[nIndex] : null; + } + public APETagField GetTagField(int nIndex) + { + Analyze(); + if (nIndex >= 0 && nIndex < m_aryFields.Length) + return m_aryFields[nIndex]; + return null; + } + + // options + void SetIgnoreReadOnly(bool bIgnoreReadOnly) { m_bIgnoreReadOnly = bIgnoreReadOnly; } + + // private functions + private int Analyze() + { + if (m_bAnalyzed) + return 0; + // clean-up + // ID3_TAG ID3Tag; + ClearFields(); + m_nTagBytes = 0; + + m_bAnalyzed = true; + + // store the original location + long nOriginalPosition = m_spIO.Position; + + // check for a tag + int nBytesRead; + //m_bHasID3Tag = false; + m_bHasAPETag = false; + m_nAPETagVersion = -1; + + //m_spIO->Seek(-ID3_TAG_BYTES, FILE_END); + //nRetVal = m_spIO->Read((unsigned char *) &ID3Tag, sizeof(ID3_TAG), &nBytesRead); + // + //if ((nBytesRead == sizeof(ID3_TAG)) && (nRetVal == 0)) + //{ + // if (ID3Tag.Header[0] == 'T' && ID3Tag.Header[1] == 'A' && ID3Tag.Header[2] == 'G') + // { + // m_bHasID3Tag = true; + // m_nTagBytes += ID3_TAG_BYTES; + // } + //} + // + //// set the fields + //if (m_bHasID3Tag) + //{ + // SetFieldID3String(APE_TAG_FIELD_ARTIST, ID3Tag.Artist, 30); + // SetFieldID3String(APE_TAG_FIELD_ALBUM, ID3Tag.Album, 30); + // SetFieldID3String(APE_TAG_FIELD_TITLE, ID3Tag.Title, 30); + // SetFieldID3String(APE_TAG_FIELD_COMMENT, ID3Tag.Comment, 28); + // SetFieldID3String(APE_TAG_FIELD_YEAR, ID3Tag.Year, 4); + // + // char cTemp[16]; sprintf(cTemp, "%d", ID3Tag.Track); + // SetFieldString(APE_TAG_FIELD_TRACK, cTemp, false); + + // if ((ID3Tag.Genre == GENRE_UNDEFINED) || (ID3Tag.Genre >= GENRE_COUNT)) + // SetFieldString(APE_TAG_FIELD_GENRE, APE_TAG_GENRE_UNDEFINED); + // else + // SetFieldString(APE_TAG_FIELD_GENRE, g_ID3Genre[ID3Tag.Genre]); + //} + + // try loading the APE tag + //if (m_bHasID3Tag == false) + { + byte[] pBuffer = new byte[APE_TAG_FOOTER_BYTES]; + m_spIO.Seek (-APE_TAG_FOOTER_BYTES, SeekOrigin.End); + nBytesRead = m_spIO.Read (pBuffer, 0, APE_TAG_FOOTER_BYTES); + if (nBytesRead == APE_TAG_FOOTER_BYTES) + { + APE_TAG_FOOTER APETagFooter = new APE_TAG_FOOTER(pBuffer); + if (APETagFooter.IsValid && !APETagFooter.IsHeader) + { + m_bHasAPETag = true; + m_nAPETagVersion = APETagFooter.Version; + + int nRawFieldBytes = APETagFooter.FieldBytes; + m_nTagBytes += APETagFooter.TotalTagBytes; + + byte[] spRawTag = new byte[nRawFieldBytes]; + m_spIO.Seek(-(APETagFooter.TotalTagBytes - APETagFooter.FieldsOffset), SeekOrigin.End); + nBytesRead = m_spIO.Read(spRawTag, 0, nRawFieldBytes); + + if (nRawFieldBytes == nBytesRead) + { + // parse out the raw fields + int nLocation = 0; + for (int z = 0; z < APETagFooter.NumberFields; z++) + { + if (!LoadField(spRawTag, ref nLocation)) + { + // if LoadField(...) fails, it means that the tag is corrupt (accidently or intentionally) + // we'll just bail out -- leaving the fields we've already set + break; + } + } + } + } + } + } + // restore the file pointer + m_spIO.Seek(nOriginalPosition, SeekOrigin.Begin); + + return 0; + } + private int GetTagFieldIndex(string pFieldName) + { + Analyze(); + for (int z = 0; z < m_aryFields.Length; z++) + if (m_aryFields[z].FieldName.ToUpper() == pFieldName.ToUpper()) + return z; + return -1; + } + private void WriteBufferToEndOfIO (byte[] pBuffer) + { + Int64 nOriginalPosition = m_spIO.Position; + m_spIO.Seek(0, SeekOrigin.End); + m_spIO.Write(pBuffer, 0, pBuffer.Length); + m_spIO.Seek(nOriginalPosition, SeekOrigin.Begin); + } + private bool LoadField(byte[] pBuffer, ref int nLocation) + { + // size and flags + int nFieldValueSize = LittleEndian.Read32(pBuffer, nLocation); + int nFieldFlags = LittleEndian.Read32(pBuffer, nLocation+4); + nLocation += 8; + + // safety check (so we can't get buffer overflow attacked) + int nMaximumRead = pBuffer.Length - nLocation - nFieldValueSize; + for (int z = 0; z < nMaximumRead; z++) + { + int nCharacter = pBuffer[nLocation + z]; + if (nCharacter == 0) + break; + if ((nCharacter < 0x20) || (nCharacter > 0x7E)) + return false; + } + + // name + ASCIIEncoding ascii = new ASCIIEncoding(); + int nNameCharacters = 0; + for ( nNameCharacters = nLocation; nNameCharacters < pBuffer.Length && pBuffer[nNameCharacters] != 0; nNameCharacters++) + ; + string spName = ascii.GetString (pBuffer, nLocation, nNameCharacters-nLocation); + byte [] spFieldBuffer = new byte[nFieldValueSize]; + + nLocation = nNameCharacters + 1; + + // value + Array.Copy(pBuffer, nLocation, spFieldBuffer, 0, nFieldValueSize); + nLocation += nFieldValueSize; + + // set + SetFieldBinary(spName, spFieldBuffer, nFieldFlags); + return true; + } + private void SortFields() + { + SortedList list = new SortedList(); + } + // TODO private static int CompareFields(const void * pA, const void * pB); + + // helper set / get field functions + //int SetFieldID3String(string pFieldName, const char * pFieldValue, int nBytes); + //int GetFieldID3String(string pFieldName, char * pBuffer, int nBytes); + + // private data + private FileStream m_spIO; + private bool m_bAnalyzed; + private int m_nTagBytes; + private APETagField[] m_aryFields; + private bool m_bHasAPETag; + private int m_nAPETagVersion; + //private bool m_bHasID3Tag; + private bool m_bIgnoreReadOnly; + }; +} diff --git a/APETagDotNet/APETagDotNet.csproj b/APETagDotNet/APETagDotNet.csproj new file mode 100644 index 0000000..1fe6da0 --- /dev/null +++ b/APETagDotNet/APETagDotNet.csproj @@ -0,0 +1,91 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B} + Library + Properties + APETagDotNet + APETagDotNet + + + true + full + false + ..\bin\Win32\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\bin\x64\Release\ + TRACE + prompt + 4 + + + true + bin\win32\Debug\ + DEBUG;TRACE + full + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + + + bin\win32\Release\ + TRACE + true + pdbonly + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + + + + + + + + + + + + + \ No newline at end of file diff --git a/APETagDotNet/Properties/AssemblyInfo.cs b/APETagDotNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..55e8543 --- /dev/null +++ b/APETagDotNet/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("APETagDotNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("APETagDotNet")] +[assembly: AssemblyCopyright("Copyright © Matthew T. Ashland, Gregory S. Chudov")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d3dd931a-3cd8-4288-b171-ae9d6e51fed1")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools/CUETools.sln b/CUETools/CUETools.sln index 90c5f7f..f4f867a 100644 --- a/CUETools/CUETools.sln +++ b/CUETools/CUETools.sln @@ -15,6 +15,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FLACDotNet", "..\FLACDotNet EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "APEDotNet", "..\APEDotNet\APEDotNet.vcproj", "{9AE965C4-301E-4C01-B90F-297AF341ACC6}" ProjectSection(ProjectDependencies) = postProject + {CA200BCB-DFC6-4153-9BD4-785BC768B26B} = {CA200BCB-DFC6-4153-9BD4-785BC768B26B} {0B9C97D4-61B8-4294-A1DF-BA90752A1779} = {0B9C97D4-61B8-4294-A1DF-BA90752A1779} EndProjectSection EndProject @@ -22,7 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodecLibs", "CodecLibs", "{ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WavPackDotNet", "..\WavPackDotNet\WavPackDotNet.vcproj", "{CC2E74B6-534A-43D8-9F16-AC03FE955000}" ProjectSection(ProjectDependencies) = postProject - {0B9C97D4-61B8-4294-A1DF-BA90752A1779} = {0B9C97D4-61B8-4294-A1DF-BA90752A1779} + {CA200BCB-DFC6-4153-9BD4-785BC768B26B} = {CA200BCB-DFC6-4153-9BD4-785BC768B26B} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MACLib", "..\MAC_SDK\Source\MACLib\MACLib.vcproj", "{0B9C97D4-61B8-4294-A1DF-BA90752A1779}" @@ -31,7 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodecWrappers", "CodecWrapp EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libFLAC_static", "..\flac\src\libFLAC\libFLAC_static.vcproj", "{4CEFBC84-C215-11DB-8314-0800200C9A66}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "APETagsDotNet", "..\APETagsDotNet\APETagsDotNet.vcproj", "{A8662B81-5B09-487E-B8F6-28251A8C46AF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APETagDotNet", "..\APETagDotNet\APETagDotNet.csproj", "{CA200BCB-DFC6-4153-9BD4-785BC768B26B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -129,20 +130,22 @@ Global {4CEFBC84-C215-11DB-8314-0800200C9A66}.Release|x64.Build.0 = Release|x64 {4CEFBC84-C215-11DB-8314-0800200C9A66}.Release|x86.ActiveCfg = Release|Win32 {4CEFBC84-C215-11DB-8314-0800200C9A66}.Release|x86.Build.0 = Release|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|Win32.ActiveCfg = Debug|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|Win32.Build.0 = Debug|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|x64.ActiveCfg = Debug|x64 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|x64.Build.0 = Debug|x64 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|x86.ActiveCfg = Debug|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Debug|x86.Build.0 = Debug|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|Any CPU.ActiveCfg = Release|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|Win32.ActiveCfg = Release|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|Win32.Build.0 = Release|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|x64.ActiveCfg = Release|x64 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|x64.Build.0 = Release|x64 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|x86.ActiveCfg = Release|Win32 - {A8662B81-5B09-487E-B8F6-28251A8C46AF}.Release|x86.Build.0 = Release|Win32 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|Win32.ActiveCfg = Debug|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|Win32.Build.0 = Debug|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|x64.ActiveCfg = Debug|x64 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|x64.Build.0 = Debug|x64 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|x86.ActiveCfg = Debug|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Debug|x86.Build.0 = Debug|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|Any CPU.Build.0 = Release|Any CPU + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|Win32.ActiveCfg = Release|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|Win32.Build.0 = Release|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|x64.ActiveCfg = Release|x64 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|x64.Build.0 = Release|x64 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|x86.ActiveCfg = Release|x86 + {CA200BCB-DFC6-4153-9BD4-785BC768B26B}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -151,7 +154,6 @@ Global {9AE965C4-301E-4C01-B90F-297AF341ACC6} = {85F88959-C9E9-4989-ACB1-67BA9D1BEFE7} {CC2E74B6-534A-43D8-9F16-AC03FE955000} = {85F88959-C9E9-4989-ACB1-67BA9D1BEFE7} {E70FA90A-7012-4A52-86B5-362B699D1540} = {85F88959-C9E9-4989-ACB1-67BA9D1BEFE7} - {A8662B81-5B09-487E-B8F6-28251A8C46AF} = {85F88959-C9E9-4989-ACB1-67BA9D1BEFE7} {0B9C97D4-61B8-4294-A1DF-BA90752A1779} = {8B179853-B7D6-479C-B8B2-6CBCE835D040} {4CEFBC84-C215-11DB-8314-0800200C9A66} = {8B179853-B7D6-479C-B8B2-6CBCE835D040} EndGlobalSection diff --git a/MAC_SDK/Source/MACLib/Assembly/Assembly.obj b/MAC_SDK/Source/MACLib/Assembly/Assembly.obj index 87d67a1..e025056 100644 Binary files a/MAC_SDK/Source/MACLib/Assembly/Assembly.obj and b/MAC_SDK/Source/MACLib/Assembly/Assembly.obj differ diff --git a/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj b/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj index 2bbd3ee..cf0008d 100644 Binary files a/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj and b/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj differ diff --git a/WavPackDotNet/WavPackDotNet.cpp b/WavPackDotNet/WavPackDotNet.cpp index e5f781d..885b11f 100644 --- a/WavPackDotNet/WavPackDotNet.cpp +++ b/WavPackDotNet/WavPackDotNet.cpp @@ -32,18 +32,19 @@ using namespace System; using namespace System::Runtime::InteropServices; using namespace System::Collections::Specialized; +using namespace APETagsDotNet; #include #include #include "WavPack\wavpack.h" #include -typedef char str_ansi; -typedef wchar_t str_utf16; -#define BOOL int -#define TRUE 1 -#define FALSE 0 -#include "..\MAC_SDK\Shared\SmartPtr.h" -#include "..\MAC_SDK\Shared\APETag.h" +//typedef char str_ansi; +//typedef wchar_t str_utf16; +//#define BOOL int +//#define TRUE 1 +//#define FALSE 0 +//#include "..\MAC_SDK\Shared\SmartPtr.h" +//#include "..\MAC_SDK\Shared\APETag.h" namespace WavPackDotNet { int write_block(void *id, void *data, int32_t length); @@ -54,15 +55,15 @@ namespace WavPackDotNet { IntPtr pathChars; char errorMessage[256]; - _path = NULL; + _path = path; pathChars = Marshal::StringToHGlobalUni(path); size_t pathLen = wcslen ((const wchar_t*)pathChars.ToPointer())+1; - _path = new wchar_t[pathLen]; - memcpy ((void*) _path, (const wchar_t*)pathChars.ToPointer(), pathLen*sizeof(wchar_t)); + wchar_t * pPath = new wchar_t[pathLen]; + memcpy ((void*) pPath, (const wchar_t*)pathChars.ToPointer(), pathLen*sizeof(wchar_t)); Marshal::FreeHGlobal(pathChars); - _wpc = WavpackOpenFileInput(_path, errorMessage, OPEN_WVC, 0); + _wpc = WavpackOpenFileInput(pPath, errorMessage, OPEN_WVC, 0); if (_wpc == NULL) { throw gcnew Exception("Unable to initialize the decoder."); } @@ -76,7 +77,6 @@ namespace WavPackDotNet { ~WavPackReader() { - if (_path) delete [] _path; } property Int32 BitsPerSample { @@ -123,7 +123,7 @@ namespace WavPackDotNet { property NameValueCollection^ Tags { NameValueCollection^ get () { - if (!_tags) GetTags (); + if (!_tags) _tags = (gcnew APETagDotNet (_path, true))->GetStringTags (true); return _tags; } void set (NameValueCollection ^tags) { @@ -152,32 +152,7 @@ namespace WavPackDotNet { NameValueCollection^ _tags; Int32 _sampleCount, _sampleOffset; Int32 _bitsPerSample, _channelCount, _sampleRate; - const wchar_t * _path; - - void GetTags (void) - { - _tags = gcnew NameValueCollection(); - CAPETag apeTag (_path, TRUE); - for (int i = 0; ; i++) - { - CAPETagField * field = apeTag.GetTagField (i); - if (!field) - break; - if (field->GetIsUTF8Text()) - { - int valueSize = field->GetFieldValueSize(); - while (valueSize && field->GetFieldValue()[valueSize-1]=='\0') - valueSize --; - const wchar_t * fieldName = field->GetFieldName (); - if (!_wcsicmp (fieldName, L"YEAR")) - fieldName = L"DATE"; - if (!_wcsicmp (fieldName, L"TRACK")) - fieldName = L"TRACKNUMBER"; - _tags->Add (gcnew String (fieldName), - gcnew String (field->GetFieldValue(), 0, valueSize, System::Text::Encoding::UTF8)); - } - } - } + String^ _path; }; public ref class WavPackWriter { diff --git a/WavPackDotNet/WavPackDotNet.vcproj b/WavPackDotNet/WavPackDotNet.vcproj index c92c5ea..398d30d 100644 --- a/WavPackDotNet/WavPackDotNet.vcproj +++ b/WavPackDotNet/WavPackDotNet.vcproj @@ -115,6 +115,7 @@ /> @@ -176,6 +178,10 @@ RelativePath="System.XML.dll" AssemblyName="System.Xml, Version=2.0.0.0, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL" /> +