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"
/>
+