1 #pragma once
2 
3 namespace APE
4 {
5 
6 class CIO;
7 
8 /*****************************************************************************************
9 APETag version history / supported formats
10 
11 1.0 (1000) - Original APE tag spec.  Fully supported by this code.
12 2.0 (2000) - Refined APE tag spec (better streaming support, UTF encoding). Fully supported by this code.
13 
14 Notes:
15     - also supports reading of ID3v1.1 tags
16     - all saving done in the APE Tag format using CURRENT_APE_TAG_VERSION
17 *****************************************************************************************/
18 
19 /*****************************************************************************************
20 APETag layout
21 
22 1) Header - APE_TAG_FOOTER (optional) (32 bytes)
23 2) Fields (array):
24         Value Size (4 bytes)
25         Flags (4 bytes)
26         Field Name (? ANSI bytes -- requires NULL terminator -- in range of 0x20 (space) to 0x7E (tilde))
27         Value ([Value Size] bytes)
28 3) Footer - APE_TAG_FOOTER (32 bytes)
29 *****************************************************************************************/
30 
31 /*****************************************************************************************
32 Notes
33 
34 When saving images, store the filename (no directory -- i.e. Cover.jpg) in UTF-8 followed
35 by a null terminator, followed by the image data.
36 
37 What saving text lists, delimit the values with a NULL terminator.
38 *****************************************************************************************/
39 
40 /*****************************************************************************************
41 The version of the APE tag
42 *****************************************************************************************/
43 #define CURRENT_APE_TAG_VERSION                 2000
44 
45 /*****************************************************************************************
46 "Standard" APE tag fields
47 *****************************************************************************************/
48 #define APE_TAG_FIELD_TITLE                     L"Title"
49 #define APE_TAG_FIELD_ARTIST                    L"Artist"
50 #define APE_TAG_FIELD_ALBUM                     L"Album"
51 #define APE_TAG_FIELD_COMMENT                   L"Comment"
52 #define APE_TAG_FIELD_YEAR                      L"Year"
53 #define APE_TAG_FIELD_TRACK                     L"Track"
54 #define APE_TAG_FIELD_GENRE                     L"Genre"
55 #define APE_TAG_FIELD_COVER_ART_FRONT           L"Cover Art (front)"
56 #define APE_TAG_FIELD_NOTES                     L"Notes"
57 #define APE_TAG_FIELD_LYRICS                    L"Lyrics"
58 #define APE_TAG_FIELD_COPYRIGHT                 L"Copyright"
59 #define APE_TAG_FIELD_BUY_URL                   L"Buy URL"
60 #define APE_TAG_FIELD_ARTIST_URL                L"Artist URL"
61 #define APE_TAG_FIELD_PUBLISHER_URL             L"Publisher URL"
62 #define APE_TAG_FIELD_FILE_URL                  L"File URL"
63 #define APE_TAG_FIELD_COPYRIGHT_URL             L"Copyright URL"
64 #define APE_TAG_FIELD_TOOL_NAME                 L"Tool Name"
65 #define APE_TAG_FIELD_TOOL_VERSION              L"Tool Version"
66 #define APE_TAG_FIELD_PEAK_LEVEL                L"Peak Level"
67 #define APE_TAG_FIELD_REPLAY_GAIN_RADIO         L"Replay Gain (radio)"
68 #define APE_TAG_FIELD_REPLAY_GAIN_ALBUM         L"Replay Gain (album)"
69 #define APE_TAG_FIELD_COMPOSER                  L"Composer"
70 #define APE_TAG_FIELD_KEYWORDS                  L"Keywords"
71 
72 /*****************************************************************************************
73 Standard APE tag field values
74 *****************************************************************************************/
75 #define APE_TAG_GENRE_UNDEFINED                 L"Undefined"
76 
77 /*****************************************************************************************
78 ID3 v1.1 tag
79 *****************************************************************************************/
80 #define ID3_TAG_BYTES    128
81 struct ID3_TAG
82 {
83     char Header[3];             // should equal 'TAG'
84     char Title[30];             // title
85     char Artist[30];            // artist
86     char Album[30];             // album
87     char Year[4];               // year
88     char Comment[29];           // comment
89     unsigned char Track;        // track
90     unsigned char Genre;        // genre
91 };
92 
93 /*****************************************************************************************
94 Footer (and header) flags
95 *****************************************************************************************/
96 #define APE_TAG_FLAG_CONTAINS_HEADER            (1 << 31)
97 #define APE_TAG_FLAG_CONTAINS_FOOTER            (1 << 30)
98 #define APE_TAG_FLAG_IS_HEADER                  (1 << 29)
99 
100 #define APE_TAG_FLAGS_DEFAULT                   (APE_TAG_FLAG_CONTAINS_FOOTER)
101 
102 /*****************************************************************************************
103 Tag field flags
104 *****************************************************************************************/
105 #define TAG_FIELD_FLAG_READ_ONLY                (1 << 0)
106 
107 #define TAG_FIELD_FLAG_DATA_TYPE_MASK           (6)
108 #define TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8      (0 << 1)
109 #define TAG_FIELD_FLAG_DATA_TYPE_BINARY         (1 << 1)
110 #define TAG_FIELD_FLAG_DATA_TYPE_EXTERNAL_INFO  (2 << 1)
111 #define TAG_FIELD_FLAG_DATA_TYPE_RESERVED       (3 << 1)
112 
113 /*****************************************************************************************
114 The footer at the end of APE tagged files (can also optionally be at the front of the tag)
115 *****************************************************************************************/
116 #define APE_TAG_FOOTER_BYTES    32
117 
118 class APE_TAG_FOOTER
119 {
120 protected:
121     char m_cID[8];              // should equal 'APETAGEX'
122     int m_nVersion;             // equals CURRENT_APE_TAG_VERSION
123     int m_nSize;                // the complete size of the tag, including this footer (excludes header)
124     int m_nFields;              // the number of fields in the tag
125     int m_nFlags;               // the tag flags
126     char m_cReserved[8];        // reserved for later use (must be zero)
127 
128 public:
129     APE_TAG_FOOTER(int nFields = 0, int nFieldBytes = 0)
130     {
131         memcpy(m_cID, "APETAGEX", 8);
132         memset(m_cReserved, 0, 8);
133         m_nFields = nFields;
134         m_nFlags = APE_TAG_FLAGS_DEFAULT;
135         m_nSize = nFieldBytes + APE_TAG_FOOTER_BYTES;
136         m_nVersion = CURRENT_APE_TAG_VERSION;
137     }
138 
GetTotalTagBytes()139     int GetTotalTagBytes() { return m_nSize + (GetHasHeader() ? APE_TAG_FOOTER_BYTES : 0); }
GetFieldBytes()140     int GetFieldBytes() { return m_nSize - APE_TAG_FOOTER_BYTES; }
GetFieldsOffset()141     int GetFieldsOffset() { return GetHasHeader() ? APE_TAG_FOOTER_BYTES : 0; }
GetNumberFields()142     int GetNumberFields() { return m_nFields; }
GetHasHeader()143     bool GetHasHeader() { return (m_nFlags & APE_TAG_FLAG_CONTAINS_HEADER) ? true : false; }
GetIsHeader()144     bool GetIsHeader() { return (m_nFlags & APE_TAG_FLAG_IS_HEADER) ? true : false; }
GetVersion()145     int GetVersion() { return m_nVersion; }
146 
GetIsValid(bool bAllowHeader)147     bool GetIsValid(bool bAllowHeader)
148     {
149         bool bValid = (strncmp(m_cID, "APETAGEX", 8) == 0) &&
150             (m_nVersion <= CURRENT_APE_TAG_VERSION) &&
151             (m_nFields <= 65536) &&
152             (m_nSize >= APE_TAG_FOOTER_BYTES) &&
153             (GetFieldBytes() <= (1024 * 1024 * 16));
154 
155         if (bValid && !bAllowHeader && GetIsHeader())
156             bValid = false;
157 
158         return bValid ? true : false;
159     }
160 };
161 
162 /*****************************************************************************************
163 CAPETagField class (an APE tag is an array of these)
164 *****************************************************************************************/
165 class CAPETagField
166 {
167 public:
168     // create a tag field (use nFieldBytes = -1 for null-terminated strings)
169     CAPETagField(const str_utfn * pFieldName, const void * pFieldValue, int nFieldBytes = -1, int nFlags = 0);
170 
171     // destructor
172     ~CAPETagField();
173 
174     // gets the size of the entire field in bytes (name, value, and metadata)
175     int GetFieldSize();
176 
177     // get the name of the field
178     const str_utfn * GetFieldName();
179 
180     // get the value of the field
181     const char * GetFieldValue();
182 
183     // get the size of the value (in bytes)
184     int GetFieldValueSize();
185 
186     // get any special flags
187     int GetFieldFlags();
188 
189     // output the entire field to a buffer (GetFieldSize() bytes)
190     int SaveField(char * pBuffer);
191 
192     // checks to see if the field is read-only
GetIsReadOnly()193     bool GetIsReadOnly() { return (m_nFieldFlags & TAG_FIELD_FLAG_READ_ONLY) ? true : false; }
GetIsUTF8Text()194     bool GetIsUTF8Text() { return ((m_nFieldFlags & TAG_FIELD_FLAG_DATA_TYPE_MASK) == TAG_FIELD_FLAG_DATA_TYPE_TEXT_UTF8) ? true : false; }
195 
196     // set helpers (use with EXTREME caution)
SetFieldFlags(int nFlags)197     void SetFieldFlags(int nFlags) { m_nFieldFlags = nFlags; }
198 
199 private:
200     CSmartPtr<str_utfn> m_spFieldNameUTF16;
201     CSmartPtr<char> m_spFieldValue;
202     int m_nFieldFlags;
203     int m_nFieldValueBytes;
204 };
205 
206 /*****************************************************************************************
207 CAPETag class
208 *****************************************************************************************/
209 class CAPETag
210 {
211 public:
212     // create an APE tag
213     // bAnalyze determines whether it will analyze immediately or on the first request
214     // be careful with multiple threads / file pointer movement if you don't analyze immediately
215     CAPETag(CIO * pIO, bool bAnalyze = true);
216     CAPETag(const str_utfn * pFilename, bool bAnalyze = true);
217 
218     // destructor
219     ~CAPETag();
220 
221     // save the tag to the I/O source (bUseOldID3 forces it to save as an ID3v1.1 tag instead of an APE tag)
222     int Save(bool bUseOldID3 = false);
223 
224     // removes any tags from the file (bUpdate determines whether is should re-analyze after removing the tag)
225     int Remove(bool bUpdate = true);
226 
227     // sets the value of a field (use nFieldBytes = -1 for null terminated strings)
228     // note: using NULL or "" for a string type will remove the field
229     int SetFieldString(const str_utfn * pFieldName, const str_utfn * pFieldValue, const str_utfn * pListDelimiter = NULL);
230     int SetFieldString(const str_utfn * pFieldName, const char * pFieldValue, bool bAlreadyUTF8Encoded, const str_utfn * pListDelimiter = NULL);
231     int SetFieldBinary(const str_utfn * pFieldName, const void * pFieldValue, intn nFieldBytes, int nFieldFlags);
232 
233     // gets the value of a field (returns -1 and an empty buffer if the field doesn't exist)
234     int GetFieldBinary(const str_utfn * pFieldName, void * pBuffer, int * pBufferBytes);
235     int GetFieldString(const str_utfn * pFieldName, str_utfn * pBuffer, int * pBufferCharacters, const str_utfn * pListDelimiter = _T("; "));
236     int GetFieldString(const str_utfn * pFieldName, str_ansi * pBuffer, int * pBufferCharacters, bool bUTF8Encode = false);
237 
238     // remove a specific field
239     int RemoveField(const str_utfn * pFieldName);
240     int RemoveField(int nIndex);
241 
242     // clear all the fields
243     int ClearFields();
244 
245     // see if we've been analyzed (we do lazy analysis)
GetAnalyzed()246     bool GetAnalyzed() { return m_bAnalyzed; }
247 
248     // get the total tag bytes in the file from the last analyze
249     // need to call Save() then Analyze() to update any changes
250     int GetTagBytes();
251 
252     // fills in an ID3_TAG using the current fields (useful for quickly converting the tag)
253     int CreateID3Tag(ID3_TAG * pID3Tag);
254 
255     // see whether the file has an ID3 or APE tag
GetHasID3Tag()256     bool GetHasID3Tag() { if (!m_bAnalyzed) { Analyze(); } return m_bHasID3Tag; }
GetHasAPETag()257     bool GetHasAPETag() { if (!m_bAnalyzed) { Analyze(); } return m_bHasAPETag; }
GetAPETagVersion()258     int GetAPETagVersion() { return GetHasAPETag() ? m_nAPETagVersion : -1; }
259 
260     // gets a desired tag field (returns NULL if not found)
261     // again, be careful, because this a pointer to the actual field in this class
262     CAPETagField * GetTagField(const str_utfn * pFieldName);
263     CAPETagField * GetTagField(int nIndex);
264 
265     // options
SetIgnoreReadOnly(bool bIgnoreReadOnly)266     void SetIgnoreReadOnly(bool bIgnoreReadOnly) { m_bIgnoreReadOnly = bIgnoreReadOnly; }
267 
268 	// statics
269 	static const int s_nID3GenreUndefined = 255;
270 	static const int s_nID3GenreCount = 148;
271 	static const wchar_t * s_aryID3GenreNames[s_nID3GenreCount];
272 
273 private:
274     // private functions
275     int Analyze();
276     int GetTagFieldIndex(const str_utfn * pFieldName);
277     int WriteBufferToEndOfIO(void * pBuffer, int nBytes);
278     int LoadField(const char * pBuffer, int nMaximumBytes, int * pBytes);
279     int SortFields();
280     static int CompareFields(const void * pA, const void * pB);
281 
282     // helper set / get field functions
283     int SetFieldID3String(const str_utfn * pFieldName, const char * pFieldValue, int nBytes);
284     int GetFieldID3String(const str_utfn * pFieldName, char * pBuffer, int nBytes);
285 
286     // private data
287     CSmartPtr<CIO> m_spIO;
288     bool m_bAnalyzed;
289     int m_nTagBytes;
290     int m_nFields;
291     CAPETagField * m_aryFields[256];
292     bool m_bHasAPETag;
293     int m_nAPETagVersion;
294     bool m_bHasID3Tag;
295     bool m_bIgnoreReadOnly;
296 };
297 
298 }