#include <algorithm>
#include "All.h"
#include "APETag.h"
#include "ID3Genres.h"
#include "CharacterHelper.h"
#include "IO.h"
#include IO_HEADER_FILE
CAPETagField::CAPETagField(const str_utf16 * pFieldName, const void * pFieldValue, int nFieldBytes, int nFlags)
{
m_spFieldNameUTF16.Assign(new str_utf16 [wcslen(pFieldName) + 1], TRUE);
memcpy(m_spFieldNameUTF16, pFieldName, (wcslen(pFieldName) + 1) * sizeof(str_utf16));
m_nFieldValueBytes = max(nFieldBytes, 0);
m_spFieldValue.Assign(new char [m_nFieldValueBytes + 2], TRUE);
memset(m_spFieldValue, 0, m_nFieldValueBytes + 2);
if (m_nFieldValueBytes > 0)
memcpy(m_spFieldValue, pFieldValue, m_nFieldValueBytes);
m_nFieldFlags = nFlags;
}
CAPETagField::~CAPETagField()
{
}
int CAPETagField::GetFieldSize()
{
CSmartPtr<char> spFieldNameANSI(GetANSIFromUTF16(m_spFieldNameUTF16), TRUE);
return (strlen(spFieldNameANSI) + 1) + m_nFieldValueBytes + 4 + 4;
}
const str_utf16 * CAPETagField::GetFieldName()
{
return m_spFieldNameUTF16;
}
const char * CAPETagField::GetFieldValue()
{
return m_spFieldValue;
}
int CAPETagField::GetFieldValueSize()
{
return m_nFieldValueBytes;
}
int CAPETagField::GetFieldFlags()
{
return m_nFieldFlags;
}
int CAPETagField::SaveField(char * pBuffer)
{
*((int *) pBuffer) = m_nFieldValueBytes;
pBuffer += 4;
*((int *) pBuffer) = m_nFieldFlags;
pBuffer += 4;
CSmartPtr<char> spFieldNameANSI((char *) GetANSIFromUTF16(m_spFieldNameUTF16), TRUE);
strcpy(pBuffer, spFieldNameANSI);
pBuffer += strlen(spFieldNameANSI) + 1;
memcpy(pBuffer, m_spFieldValue, m_nFieldValueBytes);
return GetFieldSize();
}
CAPETag::CAPETag(const str_utf16 * pFilename, BOOL bAnalyze)
{
m_spIO.Assign(new IO_CLASS_NAME);
m_spIO->Open(pFilename);
m_bAnalyzed = FALSE;
m_nFields = 0;
m_nTagBytes = 0;
m_bIgnoreReadOnly = FALSE;
if (bAnalyze)
{
Analyze();
}
}
CAPETag::CAPETag(CIO * pIO, BOOL bAnalyze)
{
m_spIO.Assign(pIO, FALSE, FALSE);
m_bAnalyzed = FALSE;
m_nFields = 0;
m_nTagBytes = 0;
if (bAnalyze)
{
Analyze();
}
}
CAPETag::~CAPETag()
{
ClearFields();
}
int CAPETag::GetTagBytes()
{
if (m_bAnalyzed == FALSE) { Analyze(); }
return m_nTagBytes;
}
CAPETagField * CAPETag::GetTagField(int nIndex)
{
if (m_bAnalyzed == FALSE) { Analyze(); }
if ((nIndex >= 0) && (nIndex < m_nFields))
{
return m_aryFields[nIndex];
}
return NULL;
}
int CAPETag::Save(BOOL bUseOldID3)
{
if (Remove(FALSE) != ERROR_SUCCESS)
return -1;
if (m_nFields == 0) { return ERROR_SUCCESS; }
int nRetVal = -1;
if (bUseOldID3 == FALSE)
{
int z = 0;
int nFieldBytes = 0;
for (z = 0; z < m_nFields; z++)
nFieldBytes += m_aryFields[z]->GetFieldSize();
SortFields();
APE_TAG_FOOTER APETagFooter(m_nFields, nFieldBytes);
int nTotalTagBytes = APETagFooter.GetTotalTagBytes();
CSmartPtr<char> spRawTag(new char [nTotalTagBytes], TRUE);
int nLocation = 0;
for (z = 0; z < m_nFields; z++)
nLocation += m_aryFields[z]->SaveField(&spRawTag[nLocation]);
memcpy(&spRawTag[nLocation], &APETagFooter, APE_TAG_FOOTER_BYTES);
nLocation += APE_TAG_FOOTER_BYTES;
nRetVal = WriteBufferToEndOfIO(spRawTag, nTotalTagBytes);
}
else
{
ID3_TAG ID3Tag;
CreateID3Tag(&ID3Tag);
nRetVal = WriteBufferToEndOfIO(&ID3Tag, sizeof(ID3_TAG));
}
return nRetVal;
}
int CAPETag::WriteBufferToEndOfIO(void * pBuffer, int nBytes)
{
int nOriginalPosition = m_spIO->GetPosition();
unsigned int nBytesWritten = 0;
m_spIO->Seek(0, FILE_END);
int nRetVal = m_spIO->Write(pBuffer, nBytes, &nBytesWritten);
m_spIO->Seek(nOriginalPosition, FILE_BEGIN);
return nRetVal;
}
int CAPETag::Analyze()
{
ID3_TAG ID3Tag;
ClearFields();
m_nTagBytes = 0;
m_bAnalyzed = TRUE;
int nOriginalPosition = m_spIO->GetPosition();
unsigned int nBytesRead;
int nRetVal;
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;
}
}
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]);
}
if (m_bHasID3Tag == FALSE)
{
APE_TAG_FOOTER APETagFooter;
m_spIO->Seek(-int(APE_TAG_FOOTER_BYTES), FILE_END);
nRetVal = m_spIO->Read((unsigned char *) &APETagFooter, APE_TAG_FOOTER_BYTES, &nBytesRead);
if ((nBytesRead == APE_TAG_FOOTER_BYTES) && (nRetVal == 0))
{
if (APETagFooter.GetIsValid(FALSE))
{
m_bHasAPETag = TRUE;
m_nAPETagVersion = APETagFooter.GetVersion();
int nRawFieldBytes = APETagFooter.GetFieldBytes();
m_nTagBytes += APETagFooter.GetTotalTagBytes();
CSmartPtr<char> spRawTag(new char [nRawFieldBytes], TRUE);
m_spIO->Seek(-(APETagFooter.GetTotalTagBytes() - APETagFooter.GetFieldsOffset()), FILE_END);
nRetVal = m_spIO->Read((unsigned char *) spRawTag.GetPtr(), nRawFieldBytes, &nBytesRead);
if ((nRetVal == 0) && (nRawFieldBytes == int(nBytesRead)))
{
int nLocation = 0;
for (int z = 0; z < APETagFooter.GetNumberFields(); z++)
{
int nMaximumFieldBytes = nRawFieldBytes - nLocation;
int nBytes = 0;
if (LoadField(&spRawTag[nLocation], nMaximumFieldBytes, &nBytes) != ERROR_SUCCESS)
{
break;
}
nLocation += nBytes;
}
}
}
}
}
m_spIO->Seek(nOriginalPosition, FILE_BEGIN);
return ERROR_SUCCESS;
}
int CAPETag::ClearFields()
{
for (int z = 0; z < m_nFields; z++)
{
SAFE_DELETE(m_aryFields[z])
}
m_nFields = 0;
return ERROR_SUCCESS;
}
int CAPETag::GetTagFieldIndex(const str_utf16 * pFieldName)
{
if (m_bAnalyzed == FALSE) { Analyze(); }
if (pFieldName == NULL) return -1;
for (int z = 0; z < m_nFields; z++)
{
if (wcsicmp(m_aryFields[z]->GetFieldName(), pFieldName) == 0)
return z;
}
return -1;
}
CAPETagField * CAPETag::GetTagField(const str_utf16 * pFieldName)
{
int nIndex = GetTagFieldIndex(pFieldName);
return (nIndex != -1) ? m_aryFields[nIndex] : NULL;
}
#if 0
int CAPETag::GetFieldString(const str_utf16 * pFieldName, str_ansi * pBuffer, int * pBufferCharacters, BOOL bUTF8Encode)
{
int nOriginalCharacters = *pBufferCharacters;
str_utf16 * pUTF16 = new str_utf16 [*pBufferCharacters + 1];
pUTF16[0] = 0;
int nRetVal = GetFieldString(pFieldName, pUTF16, pBufferCharacters);
if (nRetVal == ERROR_SUCCESS)
{
CSmartPtr<str_ansi> spANSI(bUTF8Encode ? (str_ansi *) GetUTF8FromUTF16(pUTF16) : GetANSIFromUTF16(pUTF16), TRUE);
if (int(strlen(spANSI)) > nOriginalCharacters)
{
memset(pBuffer, 0, nOriginalCharacters * sizeof(str_ansi));
*pBufferCharacters = 0;
nRetVal = ERROR_UNDEFINED;
}
else
{
strcpy(pBuffer, spANSI);
*pBufferCharacters = strlen(spANSI);
}
}
delete [] pUTF16;
return nRetVal;
}
#endif
int CAPETag::GetFieldString(const str_utf16 * pFieldName, str_utf16 * pBuffer, int * pBufferCharacters)
{
if (m_bAnalyzed == FALSE) { Analyze(); }
int nRetVal = ERROR_UNDEFINED;
if (*pBufferCharacters > 0)
{
CAPETagField * pAPETagField = GetTagField(pFieldName);
if (pAPETagField == NULL)
{
memset(pBuffer, 0, *pBufferCharacters * sizeof(str_utf16));
*pBufferCharacters = 0;
}
else if (pAPETagField->GetIsUTF8Text() || (m_nAPETagVersion < 2000))
{
CSmartPtr<str_utf16> spUTF16;
if (m_nAPETagVersion >= 2000)
spUTF16.Assign(GetUTF16FromUTF8((str_utf8 *) pAPETagField->GetFieldValue()), TRUE);
else
spUTF16.Assign(GetUTF16FromANSI(pAPETagField->GetFieldValue()), TRUE);
int nCharacters = (wcslen(spUTF16) + 1);
if (nCharacters > *pBufferCharacters)
{
*pBufferCharacters = nCharacters;
}
else
{
*pBufferCharacters = nCharacters;
memcpy(pBuffer, spUTF16.GetPtr(), *pBufferCharacters * sizeof(str_utf16));
nRetVal = ERROR_SUCCESS;
}
}
else
{
memset(pBuffer, 0, *pBufferCharacters * sizeof(str_utf16));
int nBufferBytes = (*pBufferCharacters - 1) * sizeof(str_utf16);
nRetVal = GetFieldBinary(pFieldName, pBuffer, &nBufferBytes);
*pBufferCharacters = (nBufferBytes / sizeof(str_utf16)) + 1;
}
}
return nRetVal;
}
int CAPETag::GetFieldBinary(const str_utf16 * pFieldName, void * pBuffer, int * pBufferBytes)
{
if (m_bAnalyzed == FALSE) { Analyze(); }
int nRetVal = ERROR_UNDEFINED;
if (*pBufferBytes > 0)
{
CAPETagField * pAPETagField = GetTagField(pFieldName);
if (pAPETagField == NULL)
{
memset(pBuffer, 0, *pBufferBytes);
*pBufferBytes = 0;
}
else
{
if (pAPETagField->GetFieldValueSize() > *pBufferBytes)
{
memset(pBuffer, 0, *pBufferBytes);
*pBufferBytes = pAPETagField->GetFieldValueSize();
}
else
{
*pBufferBytes = pAPETagField->GetFieldValueSize();
memcpy(pBuffer, pAPETagField->GetFieldValue(), *pBufferBytes);
nRetVal = ERROR_SUCCESS;
}
}
}
return nRetVal;
}
int CAPETag::CreateID3Tag(ID3_TAG * pID3Tag)
{
if (pID3Tag == NULL) { return -1; }
if (m_bAnalyzed == FALSE) { Analyze(); }
if (m_nFields == 0) { return -1; }
ZeroMemory(pID3Tag, ID3_TAG_BYTES);
pID3Tag->Header[0] = 'T'; pID3Tag->Header[1] = 'A'; pID3Tag->Header[2] = 'G';
GetFieldID3String(APE_TAG_FIELD_ARTIST, pID3Tag->Artist, 30);
GetFieldID3String(APE_TAG_FIELD_ALBUM, pID3Tag->Album, 30);
GetFieldID3String(APE_TAG_FIELD_TITLE, pID3Tag->Title, 30);
GetFieldID3String(APE_TAG_FIELD_COMMENT, pID3Tag->Comment, 28);
GetFieldID3String(APE_TAG_FIELD_YEAR, pID3Tag->Year, 4);
str_utf16 cBuffer[256] = { 0 }; int nBufferCharacters = 255;
GetFieldString(APE_TAG_FIELD_TRACK, cBuffer, &nBufferCharacters);
pID3Tag->Track = (unsigned char) _wtoi(cBuffer);
cBuffer[0] = 0; nBufferCharacters = 255;
GetFieldString(APE_TAG_FIELD_GENRE, cBuffer, &nBufferCharacters);
pID3Tag->Genre = 255;
int nGenreIndex = 0;
BOOL bFound = FALSE;
while ((nGenreIndex < GENRE_COUNT) && (bFound == FALSE))
{
if (_wcsicmp(cBuffer, g_ID3Genre[nGenreIndex]) == 0)
{
pID3Tag->Genre = nGenreIndex;
bFound = TRUE;
}
nGenreIndex++;
}
return ERROR_SUCCESS;
}
int CAPETag::LoadField(const char * pBuffer, int nMaximumBytes, int * pBytes)
{
if (pBytes) *pBytes = 0;
int nLocation = 0;
int nFieldValueSize = *((int *) &pBuffer[nLocation]);
nLocation += 4;
int nFieldFlags = *((int *) &pBuffer[nLocation]);
nLocation += 4;
int nMaximumRead = nMaximumBytes - 8 - nFieldValueSize;
BOOL bSafe = TRUE;
for (int z = 0; (z < nMaximumRead) && (bSafe == TRUE); z++)
{
int nCharacter = pBuffer[nLocation + z];
if (nCharacter == 0)
break;
if ((nCharacter < 0x20) || (nCharacter > 0x7E))
bSafe = FALSE;
}
if (bSafe == FALSE)
return -1;
int nNameCharacters = strlen(&pBuffer[nLocation]);
CSmartPtr<str_utf8> spNameUTF8(new str_utf8 [nNameCharacters + 1], TRUE);
memcpy(spNameUTF8, &pBuffer[nLocation], (nNameCharacters + 1) * sizeof(str_utf8));
nLocation += nNameCharacters + 1;
CSmartPtr<str_utf16> spNameUTF16(GetUTF16FromUTF8(spNameUTF8.GetPtr()), TRUE);
CSmartPtr<char> spFieldBuffer(new char [nFieldValueSize], TRUE);
memcpy(spFieldBuffer, &pBuffer[nLocation], nFieldValueSize);
nLocation += nFieldValueSize;
if (pBytes) *pBytes = nLocation;
return SetFieldBinary(spNameUTF16.GetPtr(), spFieldBuffer, nFieldValueSize, nFieldFlags);
}
int CAPETag::SetFieldString(const str_utf16 * pFieldName, const str_utf16 * pFieldValue)
{
if ((pFieldValue == NULL) || (wcslen(pFieldValue) <= 0))
return RemoveField(pFieldName);
CSmartPtr<str_utf8> spFieldValueUTF8(GetUTF8FromUTF16((str_utf16 *) pFieldValue), TRUE);
return SetFieldString(pFieldName, (const char *) spFieldValueUTF8.GetPtr(), TRUE);
}
int CAPETag::SetFieldString(const str_utf16 * pFieldName, const char * pFieldValue, BOOL bAlreadyUTF8Encoded)
{
if ((pFieldValue == NULL) || (strlen(pFieldValue) <= 0))
return RemoveField(pFieldName);
if (bAlreadyUTF8Encoded == FALSE)
{
CSmartPtr<char> spUTF8((char *) GetUTF8FromANSI(pFieldValue), TRUE);
int nFieldBytes = strlen(spUTF8.GetPtr());
return SetFieldBinary(pFieldName, spUTF8.GetPtr(), nFieldBytes, TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8);
}
else
{
int nFieldBytes = strlen(pFieldValue);
return SetFieldBinary(pFieldName, pFieldValue, nFieldBytes, TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8);
}
}
int CAPETag::SetFieldBinary(const str_utf16 * pFieldName, const void * pFieldValue, int nFieldBytes, int nFieldFlags)
{
if (m_bAnalyzed == FALSE) { Analyze(); }
if (pFieldName == NULL) return -1;
BOOL bRemoving = (pFieldValue == NULL) || (nFieldBytes <= 0);
int nFieldIndex = GetTagFieldIndex(pFieldName);
if (nFieldIndex != -1)
{
if ((m_bIgnoreReadOnly == FALSE) && (m_aryFields[nFieldIndex]->GetIsReadOnly()))
return -1;
SAFE_DELETE(m_aryFields[nFieldIndex])
if (bRemoving)
{
return RemoveField(nFieldIndex);
}
}
else
{
if (bRemoving)
return ERROR_SUCCESS;
nFieldIndex = m_nFields;
m_nFields++;
}
m_aryFields[nFieldIndex] = new CAPETagField(pFieldName, pFieldValue, nFieldBytes, nFieldFlags);
return ERROR_SUCCESS;
}
int CAPETag::RemoveField(int nIndex)
{
if ((nIndex >= 0) && (nIndex < m_nFields))
{
SAFE_DELETE(m_aryFields[nIndex])
memmove(&m_aryFields[nIndex], &m_aryFields[nIndex + 1], (256 - nIndex - 1) * sizeof(CAPETagField *));
m_nFields--;
return ERROR_SUCCESS;
}
return -1;
}
int CAPETag::RemoveField(const str_utf16 * pFieldName)
{
return RemoveField(GetTagFieldIndex(pFieldName));
}
int CAPETag::Remove(BOOL bUpdate)
{
unsigned int nBytesRead = 0;
int nRetVal = 0;
int nOriginalPosition = m_spIO->GetPosition();
BOOL bID3Removed = TRUE;
BOOL bAPETagRemoved = TRUE;
BOOL bFailedToRemove = FALSE;
while (bID3Removed || bAPETagRemoved)
{
bID3Removed = FALSE;
bAPETagRemoved = FALSE;
if (m_spIO->GetSize() > ID3_TAG_BYTES)
{
char cTagHeader[3];
m_spIO->Seek(-ID3_TAG_BYTES, FILE_END);
nRetVal = m_spIO->Read(cTagHeader, 3, &nBytesRead);
if ((nRetVal == 0) && (nBytesRead == 3))
{
if (strncmp(cTagHeader, "TAG", 3) == 0)
{
m_spIO->Seek(-ID3_TAG_BYTES, FILE_END);
if (m_spIO->SetEOF() != 0)
bFailedToRemove = TRUE;
else
bID3Removed = TRUE;
}
}
}
if (m_spIO->GetSize() > APE_TAG_FOOTER_BYTES && bFailedToRemove == FALSE)
{
APE_TAG_FOOTER APETagFooter;
m_spIO->Seek(-int(APE_TAG_FOOTER_BYTES), FILE_END);
nRetVal = m_spIO->Read(&APETagFooter, APE_TAG_FOOTER_BYTES, &nBytesRead);
if ((nRetVal == 0) && (nBytesRead == APE_TAG_FOOTER_BYTES))
{
if (APETagFooter.GetIsValid(TRUE))
{
m_spIO->Seek(-APETagFooter.GetTotalTagBytes(), FILE_END);
if (m_spIO->SetEOF() != 0)
bFailedToRemove = TRUE;
else
bAPETagRemoved = TRUE;
}
}
}
}
m_spIO->Seek(nOriginalPosition, FILE_BEGIN);
if (bUpdate && bFailedToRemove == FALSE)
{
Analyze();
}
return bFailedToRemove ? -1 : 0;
}
int CAPETag::SetFieldID3String(const str_utf16 * pFieldName, const char * pFieldValue, int nBytes)
{
CSmartPtr<str_ansi> spBuffer(new str_ansi [nBytes + 1], TRUE);
spBuffer[nBytes] = 0;
memcpy(spBuffer.GetPtr(), pFieldValue, nBytes);
char * pEnd = &spBuffer[nBytes];
while (((*pEnd == ' ') || (*pEnd == 0)) && pEnd >= &spBuffer[0]) { *pEnd-- = 0; }
SetFieldString(pFieldName, spBuffer, FALSE);
return ERROR_SUCCESS;
}
int CAPETag::GetFieldID3String(const str_utf16 * pFieldName, char * pBuffer, int nBytes)
{
int nBufferCharacters = 255; str_utf16 cBuffer[256] = {0};
GetFieldString(pFieldName, cBuffer, &nBufferCharacters);
CSmartPtr<str_ansi> spBufferANSI(GetANSIFromUTF16(cBuffer), TRUE);
memset(pBuffer, 0, nBytes);
strncpy(pBuffer, spBufferANSI.GetPtr(), nBytes);
return ERROR_SUCCESS;
}
int CAPETag::SortFields()
{
qsort(m_aryFields, m_nFields, sizeof(CAPETagField *), CompareFields);
return ERROR_SUCCESS;
}
int CAPETag::CompareFields(const void * pA, const void * pB)
{
CAPETagField * pFieldA = *((CAPETagField **) pA);
CAPETagField * pFieldB = *((CAPETagField **) pB);
return (pFieldA->GetFieldSize() - pFieldB->GetFieldSize());
}