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