109
|
1 #include <stdlib.h>
|
|
2 #include <stdio.h>
|
|
3 #include <string.h>
|
|
4 #include <unistd.h>
|
|
5 #include <fcntl.h>
|
|
6 #include <wchar.h>
|
|
7 #include <audacious/util.h>
|
265
|
8 #include <audacious/vfs.h>
|
109
|
9 #include "tags.h"
|
|
10
|
|
11 struct APETagFooterStruct {
|
|
12 unsigned char ID[8];
|
|
13 unsigned char Version[4];
|
|
14 unsigned char Length[4];
|
|
15 unsigned char TagCount[4];
|
|
16 unsigned char Flags[4];
|
|
17 unsigned char Reserved[8];
|
|
18 };
|
|
19
|
|
20 typedef struct {
|
|
21 char *key;
|
|
22 size_t keylen;
|
|
23 unsigned char *value;
|
|
24 size_t valuelen;
|
|
25 unsigned int flags;
|
|
26 } TagItem;
|
|
27
|
|
28 unsigned long
|
|
29 Read_LE_Uint32(const unsigned char *p)
|
|
30 {
|
|
31 return ((unsigned long) p[0] << 0) |
|
|
32 ((unsigned long) p[1] << 8) |
|
|
33 ((unsigned long) p[2] << 16) | ((unsigned long) p[3] << 24);
|
|
34 }
|
|
35
|
|
36 // Convert UTF-8 coded string to UNICODE
|
|
37 // Return number of characters converted
|
|
38 int
|
|
39 utf8ToUnicode(const char *lpMultiByteStr, wchar_t * lpWideCharStr,
|
|
40 int cmbChars)
|
|
41 {
|
|
42 const unsigned char *pmb = (unsigned char *) lpMultiByteStr;
|
|
43 unsigned short *pwc = (unsigned short *) lpWideCharStr;
|
|
44 const unsigned char *pmbe;
|
|
45 size_t cwChars = 0;
|
|
46
|
|
47 if (cmbChars >= 0) {
|
|
48 pmbe = pmb + cmbChars;
|
|
49 }
|
|
50 else {
|
|
51 pmbe = NULL;
|
|
52 }
|
|
53
|
|
54 while ((pmbe == NULL) || (pmb < pmbe)) {
|
|
55 char mb = *pmb++;
|
|
56 unsigned int cc = 0;
|
|
57 unsigned int wc;
|
|
58
|
|
59 while ((cc < 7) && (mb & (1 << (7 - cc)))) {
|
|
60 cc++;
|
|
61 }
|
|
62
|
|
63 if (cc == 1 || cc > 6) // illegal character combination for UTF-8
|
|
64 continue;
|
|
65
|
|
66 if (cc == 0) {
|
|
67 wc = mb;
|
|
68 }
|
|
69 else {
|
|
70 wc = (mb & ((1 << (7 - cc)) - 1)) << ((cc - 1) * 6);
|
|
71 while (--cc > 0) {
|
|
72 if (pmb == pmbe) // reached end of the buffer
|
|
73 return cwChars;
|
|
74 mb = *pmb++;
|
|
75 if (((mb >> 6) & 0x03) != 2) // not part of multibyte character
|
|
76 return cwChars;
|
|
77 wc |= (mb & 0x3F) << ((cc - 1) * 6);
|
|
78 }
|
|
79 }
|
|
80
|
|
81 if (wc & 0xFFFF0000)
|
|
82 wc = L'?';
|
|
83 *pwc++ = wc;
|
|
84 cwChars++;
|
|
85 if (wc == L'\0')
|
|
86 return cwChars;
|
|
87 }
|
|
88
|
|
89 return cwChars;
|
|
90 }
|
|
91
|
|
92 void
|
|
93 tag_insert(char *buffer, const char *value, long unsigned int len,
|
|
94 long unsigned int maxlen, bool decode_utf8)
|
|
95 {
|
|
96 char *p;
|
|
97 wchar_t wValue[MAX_LEN];
|
|
98 char temp[MAX_LEN];
|
|
99 long unsigned int c;
|
|
100 const wchar_t *src = wValue;
|
|
101
|
|
102 if (len >= maxlen)
|
|
103 len = maxlen - 1;
|
|
104 if (decode_utf8) {
|
|
105 if ((c = utf8ToUnicode(value, wValue, len)) <= 0)
|
|
106 return;
|
|
107 if (wValue[c] != L'\0')
|
|
108 wValue[c++] = L'\0';
|
|
109 if ((c = wcsrtombs(temp, &src, MAX_LEN, NULL)) == 0)
|
|
110 return;
|
|
111 }
|
|
112 else {
|
|
113 c = len;
|
|
114 strncpy(temp, value, len);
|
|
115 while (temp[len - 1] == 0x20 || len < 1) {
|
|
116 len--;
|
|
117 }
|
|
118 temp[len] = '\0';
|
|
119 }
|
|
120
|
|
121 //if ( *buffer == '\0' ) { // new value
|
|
122 p = buffer;
|
|
123 //} else { // append to existing value
|
|
124 // p = strchr (buffer, '\0' );
|
|
125 // p += sprintf ( p, ", " );
|
|
126 //}
|
|
127
|
|
128 if ((p - buffer) + c >= maxlen)
|
|
129 c = maxlen - (p - buffer) - 1;
|
|
130 strncpy(p, temp, c);
|
|
131 p[c] = '\0';
|
|
132 }
|
|
133
|
|
134 // Returns the Type of Tag (Ape or ID3)
|
|
135 int
|
265
|
136 GetTageType(VFSFile * fp)
|
109
|
137 {
|
|
138 struct APETagFooterStruct T;
|
|
139 unsigned char tagheader[3];
|
|
140 int size;
|
|
141
|
|
142 if (fp == NULL) {
|
|
143 return TAG_NONE;
|
|
144 }
|
|
145
|
265
|
146 if (vfs_fseek(fp, 0, SEEK_END) != 0)
|
109
|
147 return TAG_NONE;
|
265
|
148 size = vfs_ftell(fp);
|
|
149 if (vfs_fseek(fp, size - sizeof T, SEEK_SET) != 0)
|
109
|
150 return TAG_NONE;
|
265
|
151 if (vfs_fread(&T, 1, sizeof T, fp) != sizeof T)
|
109
|
152 return TAG_NONE;
|
|
153 if (memcmp(T.ID, "APETAGEX", sizeof T.ID) == 0)
|
|
154 return TAG_APE;
|
265
|
155 if (vfs_fseek(fp, -128L, SEEK_END) != 0)
|
109
|
156 return TAG_NONE;
|
265
|
157 if (vfs_fread(tagheader, 1, 3, fp) != 3)
|
109
|
158 return TAG_NONE;
|
|
159 if (0 == memcmp(tagheader, "TAG", 3))
|
|
160 return TAG_ID3;
|
|
161 return TAG_NONE;
|
|
162 }
|
|
163
|
|
164
|
|
165 int
|
265
|
166 ReadID3Tag(VFSFile * fp, ape_tag * Tag)
|
109
|
167 {
|
|
168 char *tag;
|
|
169 char *buff;
|
|
170 unsigned int genre;
|
|
171
|
|
172 buff = (char *) malloc(128);
|
|
173
|
|
174 *(Tag->title) = '\0';
|
|
175 *(Tag->artist) = '\0';
|
|
176 *(Tag->album) = '\0';
|
|
177 *(Tag->comment) = '\0';
|
|
178 *(Tag->genre) = '\0';
|
|
179 *(Tag->track) = '\0';
|
|
180 *(Tag->year) = '\0';
|
|
181
|
265
|
182 if (vfs_fseek(fp, -128L, SEEK_END) != 0)
|
109
|
183 return 0;
|
265
|
184 if (vfs_fread(buff, 1, 128, fp) != 128)
|
109
|
185 return 0;
|
|
186 tag = buff;
|
|
187 tag_insert(Tag->title, (tag + 3), 30, 32, false);
|
|
188 tag_insert(Tag->artist, (tag + 33), 30, 32, false);
|
|
189 tag_insert(Tag->album, (tag + 63), 30, 32, false);
|
|
190 tag_insert(Tag->year, (tag + 93), 4, 32, false);
|
|
191 tag_insert(Tag->comment, (tag + 97), 30, 32, false);
|
|
192 genre = (unsigned char) tag[127];
|
|
193 if (genre >= sizeof(GenreList) / sizeof(int))
|
|
194 genre = 12;
|
|
195 tag_insert(Tag->genre, GenreList[genre], 30, 32, false);
|
|
196 sprintf(tag, "%u", tag[126]);
|
|
197 tag_insert(Tag->track, tag, 30, 32, false);
|
|
198 free(buff);
|
|
199 return 1;
|
|
200 }
|
|
201
|
|
202 // Reads APE v2.0 tag
|
|
203 int
|
265
|
204 ReadAPE2Tag(VFSFile * fp, ape_tag * Tag)
|
109
|
205 {
|
|
206 unsigned long vsize;
|
|
207 unsigned long isize;
|
|
208 unsigned long flags;
|
|
209 unsigned char *buff;
|
|
210 unsigned char *p;
|
|
211 unsigned char *end;
|
|
212 struct APETagFooterStruct T;
|
|
213 unsigned long TagLen;
|
|
214 unsigned long TagCount;
|
|
215 long size;
|
|
216
|
|
217 *(Tag->title) = '\0';
|
|
218 *(Tag->artist) = '\0';
|
|
219 *(Tag->album) = '\0';
|
|
220 *(Tag->comment) = '\0';
|
|
221 *(Tag->genre) = '\0';
|
|
222 *(Tag->track) = '\0';
|
|
223 *(Tag->year) = '\0';
|
|
224
|
265
|
225 if (vfs_fseek(fp, 0, SEEK_END) != 0)
|
109
|
226 return 0;
|
265
|
227 size = vfs_ftell(fp);
|
|
228 if (vfs_fseek(fp, size - sizeof T, SEEK_SET) != 0)
|
109
|
229 return 0;
|
265
|
230 if (vfs_fread(&T, 1, sizeof T, fp) != sizeof T)
|
109
|
231 return 0;
|
|
232 if (memcmp(T.ID, "APETAGEX", sizeof T.ID) != 0)
|
|
233 return 0;
|
|
234 if (Read_LE_Uint32(T.Version) != 2000)
|
|
235 return 0;
|
|
236 TagLen = Read_LE_Uint32(T.Length);
|
|
237 if (TagLen < sizeof T)
|
|
238 return 0;
|
265
|
239 if (vfs_fseek(fp, size - TagLen, SEEK_SET) != 0)
|
109
|
240 return 0;
|
|
241 if ((buff = (unsigned char *) malloc(TagLen)) == NULL)
|
|
242 return 0;
|
265
|
243 if (vfs_fread(buff, 1, TagLen - sizeof T, fp) != TagLen - sizeof T) {
|
109
|
244 free(buff);
|
|
245 return 0;
|
|
246 }
|
|
247
|
|
248 TagCount = Read_LE_Uint32(T.TagCount);
|
|
249 end = buff + TagLen - sizeof(T);
|
|
250 for (p = buff; p < end && TagCount--;) {
|
|
251 vsize = Read_LE_Uint32(p);
|
|
252 p += 4;
|
|
253 flags = Read_LE_Uint32(p);
|
|
254 p += 4;
|
|
255 isize = strlen((char *) p);
|
|
256
|
|
257 if (isize > 0 && vsize > 0) {
|
|
258 if (!(flags & 1 << 1)) { // insert UTF-8 string (skip binary values)
|
|
259 if (!strcasecmp((char *) p, "Title")) {
|
|
260 tag_insert(Tag->title, (char *) (p + isize + 1), vsize,
|
|
261 MAX_LEN, false);
|
|
262 }
|
|
263 else if (!strcasecmp((char *) p, "Artist")) {
|
|
264 tag_insert(Tag->artist, (char *) (p + isize + 1), vsize,
|
|
265 MAX_LEN, false);
|
|
266 }
|
|
267 else if (!strcasecmp((char *) p, "Album")) {
|
|
268 tag_insert(Tag->album, (char *) (p + isize + 1), vsize,
|
|
269 MAX_LEN, false);
|
|
270 }
|
|
271 else if (!strcasecmp((char *) p, "Comment")) {
|
|
272 tag_insert(Tag->comment, (char *) (p + isize + 1), vsize,
|
|
273 MAX_LEN, false);
|
|
274 }
|
|
275 else if (!strcasecmp((char *) p, "Genre")) {
|
|
276 tag_insert(Tag->genre, (char *) (p + isize + 1), vsize,
|
|
277 MAX_LEN, false);
|
|
278 }
|
|
279 else if (!strcasecmp((char *) p, "Track")) {
|
|
280 tag_insert(Tag->track, (char *) (p + isize + 1), vsize,
|
|
281 128, false);
|
|
282 }
|
|
283 else if (!strcasecmp((char *) p, "Year")) {
|
|
284 tag_insert(Tag->year, (char *) (p + isize + 1), vsize,
|
|
285 128, false);
|
|
286 }
|
|
287 }
|
|
288 }
|
|
289 p += isize + 1 + vsize;
|
|
290 }
|
|
291 free(buff);
|
|
292 return 1;
|
|
293 }
|
|
294
|
|
295 int
|
|
296 DeleteTag(char *filename)
|
|
297 {
|
|
298
|
265
|
299 VFSFile *fp = vfs_fopen(filename, "rb+");
|
109
|
300 int tagtype;
|
|
301 int fd;
|
|
302 long filelength = 0;
|
|
303 long dellength = -1;
|
|
304 char *tagheader;
|
|
305 unsigned long *apelength;
|
|
306 int res = -1;
|
|
307
|
|
308 if (fp == NULL) {
|
|
309 char text[256];
|
|
310
|
|
311 sprintf(text, "File \"%s\" not found or is read protected!\n",
|
|
312 filename);
|
|
313 xmms_show_message("File-Error", (gchar *) text, "Ok", FALSE, NULL,
|
|
314 NULL);
|
|
315 return -1;
|
|
316 }
|
|
317 tagtype = GetTageType(fp);
|
|
318
|
|
319 // get Length of File
|
265
|
320 vfs_fseek(fp, 0L, SEEK_END);
|
|
321 filelength = vfs_ftell(fp);
|
109
|
322
|
|
323 apelength = (unsigned long *) malloc(4);
|
|
324 tagheader = (char *) malloc(9);
|
|
325
|
|
326 if (tagtype == TAG_ID3) {
|
|
327 dellength = 128L;
|
|
328 }
|
|
329 else if (tagtype == TAG_APE) {
|
265
|
330 vfs_fseek(fp, -32L, SEEK_END);
|
|
331 vfs_fread(tagheader, 8, 1, fp);
|
109
|
332 if (0 == memcmp(tagheader, "APETAGEX", 8)) {
|
265
|
333 vfs_fseek(fp, -20L, SEEK_END);
|
|
334 vfs_fread(apelength, 4, 1, fp);
|
109
|
335 dellength = *apelength + 32;
|
|
336 }
|
|
337 }
|
|
338
|
|
339
|
|
340 if (dellength > -1) //if TAG was found, delete it
|
|
341 {
|
|
342 fd = open(filename, O_RDWR);
|
|
343 res = ftruncate(fd, (off_t) (filelength - dellength));
|
|
344 close(fd);
|
|
345 }
|
|
346
|
|
347 free(tagheader);
|
|
348 free(apelength);
|
|
349
|
|
350 //returns 0 if everything is ok
|
|
351 return res;
|
|
352 }
|
|
353
|
|
354 // Returns bytes used in APE-Tag for this value
|
|
355 int
|
|
356 addValue(TagItem * item, char *key, char *value)
|
|
357 {
|
|
358 item->keylen = strlen(key);
|
|
359 item->valuelen = strlen(value);
|
|
360 item->key = (char *) malloc(item->keylen + 1);
|
|
361 item->value = (unsigned char *) malloc(item->valuelen + 1);
|
|
362 strcpy((char *) item->value, value);
|
|
363 strcpy(item->key, key);
|
|
364 item->flags = 0;
|
|
365 return (9 + item->keylen + item->valuelen);
|
|
366 }
|
|
367
|
|
368 int
|
|
369 WriteAPE2Tag(char *filename, ape_tag * Tag)
|
|
370 {
|
265
|
371 VFSFile *fp;
|
109
|
372 unsigned char H[32] = "APETAGEX";
|
|
373 unsigned long Version = 2000;
|
|
374 unsigned char dw[8];
|
|
375 unsigned long estimatedbytes = 32; // 32 byte footer + all items, these are the 32 bytes footer, the items are added later
|
|
376 long writtenbytes = -32; // actually writtenbytes-32, which should be equal to estimatedbytes (= footer + all items)
|
|
377 unsigned int TagCount = 0;
|
|
378 TagItem T[7];
|
|
379
|
|
380
|
|
381 // Delete Tag if there is one
|
265
|
382 fp = vfs_fopen(filename, "rb+");
|
109
|
383 if (fp == NULL) {
|
|
384 char text[256];
|
|
385
|
|
386 sprintf(text, "File \"%s\" not found or is read protected!\n",
|
|
387 filename);
|
|
388 xmms_show_message("File-Error", (gchar *) text, "Ok", FALSE, NULL,
|
|
389 NULL);
|
|
390 return -1;
|
|
391 }
|
|
392
|
|
393 int tagtype = GetTageType(fp);
|
|
394
|
|
395 if (tagtype != TAG_NONE)
|
|
396 if (DeleteTag(filename) != 0)
|
|
397 return 0;
|
|
398
|
|
399 // Produce TagItem-Array
|
|
400 if (strlen(Tag->title) > 0) {
|
|
401 char *value = (char *) malloc(strlen(Tag->title) + 1);
|
|
402
|
|
403 strcpy(value, Tag->title);
|
|
404 int res = addValue(&T[TagCount], "Title", value);
|
|
405
|
|
406 estimatedbytes += res;
|
|
407 if (res > 0)
|
|
408 TagCount++;
|
|
409 free(value);
|
|
410 }
|
|
411
|
|
412 if (strlen(Tag->artist) > 0) {
|
|
413 char *value = (char *) malloc(strlen(Tag->artist) + 1);
|
|
414
|
|
415 strcpy(value, Tag->artist);
|
|
416 int res = addValue(&T[TagCount], "Artist", value);
|
|
417
|
|
418 estimatedbytes += res;
|
|
419 if (res > 0)
|
|
420 TagCount++;
|
|
421 free(value);
|
|
422 }
|
|
423
|
|
424 if (strlen(Tag->album) > 0) {
|
|
425 char *value = (char *) malloc(strlen(Tag->album) + 1);
|
|
426
|
|
427 strcpy(value, Tag->album);
|
|
428 int res = addValue(&T[TagCount], "Album", value);
|
|
429
|
|
430 estimatedbytes += res;
|
|
431 if (res > 0)
|
|
432 TagCount++;
|
|
433 free(value);
|
|
434 }
|
|
435
|
|
436 if (strlen(Tag->comment) > 0) {
|
|
437 char *value = (char *) malloc(strlen(Tag->comment) + 1);
|
|
438
|
|
439 strcpy(value, Tag->comment);
|
|
440 int res = addValue(&T[TagCount], "Comment", value);
|
|
441
|
|
442 estimatedbytes += res;
|
|
443 if (res > 0)
|
|
444 TagCount++;
|
|
445 free(value);
|
|
446 }
|
|
447
|
|
448 if (strlen(Tag->genre) > 0) {
|
|
449 char *value = (char *) malloc(strlen(Tag->genre) + 1);
|
|
450
|
|
451 strcpy(value, Tag->genre);
|
|
452 int res = addValue(&T[TagCount], "Genre", value);
|
|
453
|
|
454 estimatedbytes += res;
|
|
455 if (res > 0)
|
|
456 TagCount++;
|
|
457 free(value);
|
|
458 }
|
|
459
|
|
460 if (strlen(Tag->track) > 0) {
|
|
461 char *value = (char *) malloc(strlen(Tag->track) + 1);
|
|
462
|
|
463 strcpy(value, Tag->track);
|
|
464 int res = addValue(&T[TagCount], "Track", value);
|
|
465
|
|
466 estimatedbytes += res;
|
|
467 if (res > 0)
|
|
468 TagCount++;
|
|
469 free(value);
|
|
470 }
|
|
471
|
|
472 if (strlen(Tag->year) > 0) {
|
|
473 char *value = (char *) malloc(strlen(Tag->year) + 1);
|
|
474
|
|
475 strcpy(value, Tag->year);
|
|
476 int res = addValue(&T[TagCount], "Year", value);
|
|
477
|
|
478 estimatedbytes += res;
|
|
479 if (res > 0)
|
|
480 TagCount++;
|
|
481 free(value);
|
|
482 }
|
|
483 // Start writing the new Ape2 Tag
|
265
|
484 vfs_fseek(fp, 0L, SEEK_END);
|
109
|
485
|
|
486 if (TagCount == 0) {
|
|
487 printf("no tag to write");
|
|
488 return 0;
|
|
489 }
|
|
490
|
|
491 if (estimatedbytes >= 8192 + 103) {
|
|
492 printf
|
|
493 ("\nTag is %.1f Kbyte long. This is longer than the maximum recommended 8 KByte.\n\a",
|
|
494 estimatedbytes / 1024.);
|
|
495 return 0;
|
|
496 }
|
|
497
|
|
498 H[8] = Version >> 0;
|
|
499 H[9] = Version >> 8;
|
|
500 H[10] = Version >> 16;
|
|
501 H[11] = Version >> 24;
|
|
502 H[12] = estimatedbytes >> 0;
|
|
503 H[13] = estimatedbytes >> 8;
|
|
504 H[14] = estimatedbytes >> 16;
|
|
505 H[15] = estimatedbytes >> 24;
|
|
506 H[16] = TagCount >> 0;
|
|
507 H[17] = TagCount >> 8;
|
|
508 H[18] = TagCount >> 16;
|
|
509 H[19] = TagCount >> 24;
|
|
510
|
|
511 H[23] = 0x80 | 0x20;
|
265
|
512 writtenbytes += vfs_fwrite(H, 1, 32, fp);
|
109
|
513
|
|
514 for (unsigned int i = 0; i < TagCount; i++) {
|
|
515 dw[0] = T[i].valuelen >> 0;
|
|
516 dw[1] = T[i].valuelen >> 8;
|
|
517 dw[2] = T[i].valuelen >> 16;
|
|
518 dw[3] = T[i].valuelen >> 24;
|
|
519 dw[4] = T[i].flags >> 0;
|
|
520 dw[5] = T[i].flags >> 8;
|
|
521 dw[6] = T[i].flags >> 16;
|
|
522 dw[7] = T[i].flags >> 24;
|
265
|
523 writtenbytes += vfs_fwrite(dw, 1, 8, fp);
|
|
524 writtenbytes += vfs_fwrite(T[i].key, 1, T[i].keylen, fp);
|
|
525 writtenbytes += vfs_fwrite("", 1, 1, fp);
|
109
|
526 if (T[i].valuelen > 0)
|
265
|
527 writtenbytes += vfs_fwrite(T[i].value, 1, T[i].valuelen, fp);
|
109
|
528 }
|
|
529
|
|
530 H[23] = 0x80;
|
265
|
531 writtenbytes += vfs_fwrite(H, 1, 32, fp);
|
109
|
532
|
|
533 if (estimatedbytes != (unsigned long) writtenbytes)
|
|
534 printf("\nError writing APE tag.\n");
|
265
|
535 vfs_fclose(fp);
|
109
|
536 TagCount = 0;
|
|
537 return 0;
|
|
538 }
|