comparison src/libaudtag/id3/id3v2.c @ 4887:0ddbd0025174 default tip

added libaudtag. (not used yet.)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 05 May 2010 18:26:06 +0900
parents
children
comparison
equal deleted inserted replaced
4886:54b4f7aaca24 4887:0ddbd0025174
1 /*
2 * Copyright 2009 Paula Stanciu
3 *
4 * This file is part of Audacious.
5 *
6 * Audacious is free software: you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License as published by the Free Software
8 * Foundation, version 3 of the License.
9 *
10 * Audacious is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * Audacious. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * The Audacious team does not consider modular code linking to Audacious or
18 * using our public API to be a derived work.
19 */
20
21 #include <glib.h>
22 #include <glib/gstdio.h>
23
24 #include "id3v2.h"
25 #include "../util.h"
26 #include <inttypes.h>
27 #include "../tag_module.h"
28 #include "frame.h"
29
30 #define TAG_SIZE 1
31
32 guint32 read_syncsafe_int32(VFSFile * fd)
33 {
34 guint32 val = read_BEuint32(fd);
35 guint32 mask = 0x7f;
36 guint32 intVal = 0;
37 intVal = ((intVal) | (val & mask));
38 int i;
39 for (i = 0; i < 3; i++)
40 {
41 mask = mask << 8;
42 guint32 tmp = (val & mask);
43 tmp = tmp >> 1;
44 intVal = intVal | tmp;
45 };
46 return intVal;
47 }
48
49 ID3v2Header *readHeader(VFSFile * fd)
50 {
51 ID3v2Header *header = g_new0(ID3v2Header, 1);
52 header->id3 = read_char_data(fd, 3);
53 header->version = read_LEuint16(fd);
54 header->flags = *read_char_data(fd, 1);
55 header->size = read_syncsafe_int32(fd);
56 return header;
57 }
58
59 ExtendedHeader *readExtendedHeader(VFSFile * fd)
60 {
61 ExtendedHeader *header = g_new0(ExtendedHeader, 1);
62 header->header_size = read_syncsafe_int32(fd);
63 header->flags = read_LEuint16(fd);
64 header->padding_size = read_BEuint32(fd);
65 return header;
66 }
67
68 ID3v2FrameHeader *readID3v2FrameHeader(VFSFile * fd)
69 {
70 ID3v2FrameHeader *frameheader = g_new0(ID3v2FrameHeader, 1);
71 frameheader->frame_id = read_char_data(fd, 4);
72 frameheader->size = read_syncsafe_int32(fd);
73 frameheader->flags = read_LEuint16(fd);
74 if ((frameheader->flags & 0x100) == 0x100)
75 frameheader->size = read_syncsafe_int32(fd);
76 return frameheader;
77 }
78
79 static gint unsyncsafe (gchar * data, gint size)
80 {
81 gchar * get = data, * set = data;
82
83 while (size --)
84 {
85 gchar c = * set ++ = * get ++;
86
87 if (c == (gchar) 0xff && size)
88 {
89 size --;
90 get ++;
91 }
92 }
93
94 return set - data;
95 }
96
97 static gchar * read_text_frame (VFSFile * handle, ID3v2FrameHeader * header)
98 {
99 gint size = header->size;
100 gchar data[size];
101
102 if (vfs_fread (data, 1, size, handle) != size)
103 return NULL;
104
105 if (header->flags & 0x200)
106 size = unsyncsafe (data, size);
107
108 switch (data[0])
109 {
110 case 0:
111 return g_convert (data + 1, size - 1, "UTF-8", "ISO-8859-1", NULL, NULL,
112 NULL);
113 case 1:
114 if (data[1] == (gchar) 0xff)
115 return g_convert (data + 3, size - 3, "UTF-8", "UTF-16LE", NULL,
116 NULL, NULL);
117 else
118 return g_convert (data + 3, size - 3, "UTF-8", "UTF-16BE", NULL,
119 NULL, NULL);
120 case 2:
121 return g_convert (data + 1, size - 1, "UTF-8", "UTF-16BE", NULL, NULL,
122 NULL);
123 case 3:
124 return g_strndup (data + 1, size - 1);
125 default:
126 AUDDBG ("Throwing away %i bytes of text due to invalid encoding %d\n",
127 size - 1, (gint) data[0]);
128 return NULL;
129 }
130 }
131
132 static gboolean read_comment_frame (VFSFile * handle, ID3v2FrameHeader * header,
133 gchar * * lang, gchar * * type, gchar * * value)
134 {
135 gint size = header->size;
136 gchar data[size];
137 gchar * pair, * sep;
138 gsize converted;
139
140 if (vfs_fread (data, 1, size, handle) != size)
141 return FALSE;
142
143 if (header->flags & 0x200)
144 size = unsyncsafe (data, size);
145
146 switch (data[0])
147 {
148 case 0:
149 pair = g_convert (data + 4, size - 4, "UTF-8", "ISO-8859-1", NULL,
150 & converted, NULL);
151 break;
152 case 1:
153 if (data[4] == (gchar) 0xff)
154 pair = g_convert (data + 6, size - 6, "UTF-8", "UTF-16LE", NULL,
155 & converted, NULL);
156 else
157 pair = g_convert (data + 6, size - 6, "UTF-8", "UTF-16BE", NULL,
158 & converted, NULL);
159 break;
160 case 2:
161 pair = g_convert (data + 4, size - 4, "UTF-8", "UTF-16BE", NULL,
162 & converted, NULL);
163 break;
164 case 3:
165 pair = g_malloc (size - 3);
166 memcpy (pair, data + 4, size - 4);
167 pair[size - 4] = 0;
168 converted = size - 4;
169 break;
170 default:
171 AUDDBG ("Throwing away %i bytes of text due to invalid encoding %d\n",
172 size - 4, (gint) data[0]);
173 pair = NULL;
174 break;
175 }
176
177 if (pair == NULL || (sep = memchr (pair, 0, converted)) == NULL)
178 return FALSE;
179
180 * lang = g_strndup (data + 1, 3);
181 * type = g_strdup (pair);
182 * value = g_strdup (sep + 1);
183
184 g_free (pair);
185 return TRUE;
186 }
187
188 GenericFrame *readGenericFrame(VFSFile * fd, GenericFrame * gf)
189 {
190 gf->header = readID3v2FrameHeader(fd);
191 gf->frame_body = read_char_data(fd, gf->header->size);
192
193 return gf;
194 }
195
196
197 void readAllFrames(VFSFile * fd, int framesSize)
198 {
199 int pos = 0;
200 int i = 0;
201 while (pos < framesSize)
202 {
203 GenericFrame *gframe = g_new0(GenericFrame, 1);
204 gframe = readGenericFrame(fd, gframe);
205 if (isValidFrame(gframe))
206 {
207 mowgli_dictionary_add(frames, gframe->header->frame_id, gframe);
208 mowgli_node_add(gframe->header->frame_id, mowgli_node_create(), frameIDs);
209 pos += gframe->header->size;
210 i++;
211 }
212 else
213 break;
214 }
215
216 }
217
218 void write_int32(VFSFile * fd, guint32 val)
219 {
220 guint32 be_val = GUINT32_TO_BE(val);
221 vfs_fwrite(&be_val, 4, 1, fd);
222 }
223
224 void write_syncsafe_int32(VFSFile * fd, guint32 val)
225 {
226 //TODO write the correct function - this is just for testing
227 int i = 0;
228 guint32 tmp = 0x0;
229 guint32 mask = 0x7f;
230 guint32 syncVal = 0;
231 tmp = val & mask;
232 syncVal = tmp;
233 for (i = 0; i < 3; i++)
234 {
235 tmp = 0;
236 mask <<= 7;
237 tmp = val & mask;
238 tmp <<= 1;
239 syncVal |= tmp;
240 }
241 guint32 be_val = GUINT32_TO_BE(syncVal);
242 vfs_fwrite(&be_val, 4, 1, fd);
243 }
244
245
246 void write_ASCII(VFSFile * fd, int size, gchar * value)
247 {
248 vfs_fwrite(value, size, 1, fd);
249 }
250
251
252 void write_utf8(VFSFile * fd, int size, gchar * value)
253 {
254 GError *error = NULL;
255 gsize bytes_read = 0, bytes_write = 0;
256 gchar *isoVal = g_convert(value, size, "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error);
257 vfs_fwrite(isoVal, size, 1, fd);
258 }
259
260 guint32 writeAllFramesToFile(VFSFile * fd)
261 {
262 guint32 size = 0;
263 mowgli_node_t *n, *tn;
264 MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head)
265 {
266 GenericFrame *frame = (GenericFrame *) mowgli_dictionary_retrieve(frames, (gchar *) (n->data));
267 if (frame)
268 {
269 writeGenericFrame(fd, frame);
270 size += frame->header->size + 10;
271 }
272 }
273 return size;
274 }
275
276 void writeID3HeaderToFile(VFSFile * fd, ID3v2Header * header)
277 {
278 vfs_fwrite(header->id3, 3, 1, fd);
279 vfs_fwrite(&header->version, 2, 1, fd);
280 vfs_fwrite(&header->flags, 1, 1, fd);
281 write_syncsafe_int32(fd, header->size);
282 }
283
284 void writePaddingToFile(VFSFile * fd, int ksize)
285 {
286 gchar padding = 0;
287 int i = 0;
288 for (i = 0; i < ksize; i++)
289 vfs_fwrite(&padding, 1, 1, fd);
290 }
291
292
293 void writeID3FrameHeaderToFile(VFSFile * fd, ID3v2FrameHeader * header)
294 {
295 vfs_fwrite(header->frame_id, 4, 1, fd);
296 write_int32(fd, header->size);
297 vfs_fwrite(&header->flags, 2, 1, fd);
298 }
299
300 void writeGenericFrame(VFSFile * fd, GenericFrame * frame)
301 {
302 writeID3FrameHeaderToFile(fd, frame->header);
303 vfs_fwrite(frame->frame_body, frame->header->size, 1, fd);
304 }
305
306 gboolean isExtendedHeader(ID3v2Header * header)
307 {
308 if ((header->flags & 0x40) == (0x40))
309 return TRUE;
310 else
311 return FALSE;
312 }
313
314 int getFrameID(ID3v2FrameHeader * header)
315 {
316 int i = 0;
317 for (i = 0; i < ID3_TAGS_NO; i++)
318 {
319 if (!strcmp(header->frame_id, id3_frames[i]))
320 return i;
321 }
322 return -1;
323 }
324
325
326 void skipFrame(VFSFile * fd, guint32 size)
327 {
328 vfs_fseek(fd, size, SEEK_CUR);
329 }
330
331 static void associate_string (Tuple * tuple, VFSFile * handle, gint field,
332 const gchar * customfield, ID3v2FrameHeader * header)
333 {
334 gchar * text = read_text_frame (handle, header);
335
336 if (text == NULL)
337 return;
338
339 if (customfield != NULL)
340 AUDDBG ("custom field %s = %s\n", customfield, text);
341 else
342 AUDDBG ("field %i = %s\n", field, text);
343
344 tuple_associate_string (tuple, field, customfield, text);
345 g_free (text);
346 }
347
348 static void associate_int (Tuple * tuple, VFSFile * handle, gint field,
349 const gchar * customfield, ID3v2FrameHeader * header)
350 {
351 gchar * text = read_text_frame (handle, header);
352
353 if (text == NULL)
354 return;
355
356 if (customfield != NULL)
357 AUDDBG ("custom field %s = %s\n", customfield, text);
358 else
359 AUDDBG ("field %i = %s\n", field, text);
360
361 tuple_associate_int (tuple, field, customfield, atoi (text));
362 g_free (text);
363 }
364
365 static void decode_private_info(Tuple * tuple, VFSFile * fd, ID3v2FrameHeader * header)
366 {
367 gchar *value = read_char_data(fd, header->size);
368 if (!strncmp(value, "WM/", 3))
369 {
370 AUDDBG("Windows Media tag: %s\n", value);
371 } else {
372 AUDDBG("Unable to decode private data, skipping: %s\n", value);
373 }
374 }
375
376 static void decode_comment (Tuple * tuple, VFSFile * handle, ID3v2FrameHeader *
377 header)
378 {
379 gchar * lang, * type, * value;
380
381 if (! read_comment_frame (handle, header, & lang, & type, & value))
382 return;
383
384 AUDDBG ("comment: lang = %s, type = %s, value = %s\n", lang, type, value);
385
386 if (! type[0]) /* blank type == actual comment */
387 tuple_associate_string (tuple, FIELD_COMMENT, NULL, value);
388
389 g_free (lang);
390 g_free (type);
391 g_free (value);
392 }
393
394 static void decode_txxx (Tuple * tuple, VFSFile * handle, ID3v2FrameHeader * header)
395 {
396 gchar * text = read_text_frame (handle, header);
397
398 if (text == NULL)
399 return;
400
401 gchar * separator = strchr(text, 0);
402
403 if (separator == NULL)
404 return;
405
406 gchar * value = separator + 1;
407 AUDDBG ("Field '%s' has value '%s'\n", text, value);
408 tuple_associate_string (tuple, -1, text, value);
409
410 g_free (text);
411 }
412
413 Tuple *decodeGenre(Tuple * tuple, VFSFile * fd, ID3v2FrameHeader header)
414 {
415 gint numericgenre;
416 gchar * text = read_text_frame (fd, & header);
417
418 if (text == NULL)
419 return tuple;
420
421 if (text[0] == '(')
422 numericgenre = atoi (text + 1);
423 else
424 numericgenre = atoi (text);
425
426 if (numericgenre > 0)
427 {
428 tuple_associate_string(tuple, FIELD_GENRE, NULL, convert_numericgenre_to_text(numericgenre));
429 return tuple;
430 }
431 tuple_associate_string(tuple, FIELD_GENRE, NULL, text);
432 g_free (text);
433 return tuple;
434 }
435
436 gboolean isValidFrame(GenericFrame * frame)
437 {
438 if (strlen(frame->header->frame_id) != 0)
439 return TRUE;
440 else
441 return FALSE;
442 }
443
444
445
446 void add_newISO8859_1FrameFromString(const gchar * value, int id3_field)
447 {
448 GError *error = NULL;
449 gsize bytes_read = 0, bytes_write = 0;
450 gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error);
451 ID3v2FrameHeader *header = g_new0(ID3v2FrameHeader, 1);
452 header->frame_id = id3_frames[id3_field];
453 header->flags = 0;
454 header->size = strlen(retVal) + 1;
455 gchar *buf = g_new0(gchar, header->size + 1);
456 memcpy(buf + 1, retVal, header->size);
457 GenericFrame *frame = g_new0(GenericFrame, 1);
458 frame->header = header;
459 frame->frame_body = buf;
460 mowgli_dictionary_add(frames, header->frame_id, frame);
461 mowgli_node_add(frame->header->frame_id, mowgli_node_create(), frameIDs);
462
463 }
464
465
466 void add_newFrameFromTupleStr(Tuple * tuple, int field, int id3_field)
467 {
468 const gchar *value = tuple_get_string(tuple, field, NULL);
469 add_newISO8859_1FrameFromString(value, id3_field);
470 }
471
472
473 void add_newFrameFromTupleInt(Tuple * tuple, int field, int id3_field)
474 {
475 int intvalue = tuple_get_int(tuple, field, NULL);
476 gchar *value = g_strdup_printf("%d", intvalue);
477 add_newISO8859_1FrameFromString(value, id3_field);
478
479 }
480
481
482
483 void add_frameFromTupleStr(Tuple * tuple, int field, int id3_field)
484 {
485 const gchar *value = tuple_get_string(tuple, field, NULL);
486 GError *error = NULL;
487 gsize bytes_read = 0, bytes_write = 0;
488 gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error);
489
490 GenericFrame *frame = mowgli_dictionary_retrieve(frames, id3_frames[id3_field]);
491 if (frame != NULL)
492 {
493 frame->header->size = strlen(retVal) + 1;
494 gchar *buf = g_new0(gchar, frame->header->size + 1);
495 memcpy(buf + 1, retVal, frame->header->size);
496 frame->frame_body = buf;
497 }
498 else
499 add_newFrameFromTupleStr(tuple, field, id3_field);
500
501 }
502
503 void add_frameFromTupleInt(Tuple * tuple, int field, int id3_field)
504 {
505 int intvalue = tuple_get_int(tuple, field, NULL);
506 gchar *value = g_strdup_printf("%d", intvalue);
507 GError *error = NULL;
508 gsize bytes_read = 0, bytes_write = 0;
509 gchar *retVal = g_convert(value, strlen(value), "ISO-8859-1", "UTF-8", &bytes_read, &bytes_write, &error);
510
511 GenericFrame *frame = mowgli_dictionary_retrieve(frames, id3_frames[id3_field]);
512 if (frame != NULL)
513 {
514 frame->header->size = strlen(retVal) + 1;
515 gchar *buf = g_new0(gchar, frame->header->size + 1);
516 memcpy(buf + 1, retVal, frame->header->size);
517 frame->frame_body = buf;
518 }
519 else
520 add_newFrameFromTupleStr(tuple, field, id3_field);
521
522 }
523
524 gboolean id3v2_can_handle_file(VFSFile * f)
525 {
526 ID3v2Header *header = readHeader(f);
527 if (!strcmp(header->id3, "ID3"))
528 return TRUE;
529 return FALSE;
530 }
531
532
533
534 Tuple *id3v2_populate_tuple_from_file(Tuple * tuple, VFSFile * f)
535 {
536 vfs_fseek(f, 0, SEEK_SET);
537 ExtendedHeader *extHeader;
538 ID3v2Header *header = readHeader(f);
539 int pos = 0;
540 if (isExtendedHeader(header))
541 {
542 extHeader = readExtendedHeader(f);
543 vfs_fseek(f, 10 + extHeader->header_size, SEEK_SET);
544 }
545
546 while (pos < header->size)
547 {
548 ID3v2FrameHeader *frame = readID3v2FrameHeader(f);
549 if (frame->size == 0)
550 break;
551 int id = getFrameID(frame);
552 pos = pos + frame->size + 10;
553 if (pos > header->size)
554 break;
555 switch (id)
556 {
557 case ID3_ALBUM:
558 associate_string (tuple, f, FIELD_ALBUM, NULL, frame);
559 break;
560 case ID3_TITLE:
561 associate_string (tuple, f, FIELD_TITLE, NULL, frame);
562 break;
563 case ID3_COMPOSER:
564 associate_string (tuple, f, FIELD_ARTIST, NULL, frame);
565 break;
566 case ID3_COPYRIGHT:
567 associate_string (tuple, f, FIELD_COPYRIGHT, NULL, frame);
568 break;
569 case ID3_DATE:
570 associate_string (tuple, f, FIELD_DATE, NULL, frame);
571 break;
572 case ID3_TIME:
573 associate_int (tuple, f, FIELD_LENGTH, NULL, frame);
574 break;
575 case ID3_LENGTH:
576 associate_int (tuple, f, FIELD_LENGTH, NULL, frame);
577 break;
578 case ID3_ARTIST:
579 associate_string (tuple, f, FIELD_ARTIST, NULL, frame);
580 break;
581 case ID3_TRACKNR:
582 associate_int (tuple, f, FIELD_TRACK_NUMBER, NULL, frame);
583 break;
584 case ID3_YEAR:
585 case ID3_RECORDING_TIME:
586 associate_int (tuple, f, FIELD_YEAR, NULL, frame);
587 break;
588 case ID3_GENRE:
589 tuple = decodeGenre(tuple, f, *frame);
590 break;
591 case ID3_COMMENT:
592 decode_comment (tuple, f, frame);
593 break;
594 case ID3_PRIVATE:
595 decode_private_info (tuple, f, frame);
596 break;
597 case ID3_ENCODER:
598 associate_string (tuple, f, -1, "encoder", frame);
599 break;
600 case ID3_TXXX:
601 decode_txxx (tuple, f, frame);
602 break;
603 default:
604 AUDDBG("Skipping %i bytes over unsupported ID3 frame %s\n", frame->size, frame->frame_id);
605 skipFrame(f, frame->size);
606 }
607 }
608 return tuple;
609 }
610
611
612 gboolean id3v2_write_tuple_to_file(Tuple * tuple, VFSFile * f)
613 {
614 VFSFile *tmp;
615 vfs_fseek(f, 0, SEEK_SET);
616
617 ExtendedHeader *extHeader;
618 if (frameIDs != NULL)
619 {
620 mowgli_node_t *n, *tn;
621 MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head)
622 {
623 mowgli_node_delete(n, frameIDs);
624 }
625 }
626 frameIDs = mowgli_list_create();
627 ID3v2Header *header = readHeader(f);
628 int framesSize = header->size;
629
630 if (isExtendedHeader(header))
631 {
632 extHeader = readExtendedHeader(f);
633 framesSize -= 10;
634 framesSize -= extHeader->padding_size;
635 }
636
637 //read all frames into generic frames;
638 frames = mowgli_dictionary_create(strcasecmp);
639 readAllFrames(f, header->size);
640
641 //make the new frames from tuple and replace in the dictionary the old frames with the new ones
642 if (tuple_get_string(tuple, FIELD_ARTIST, NULL))
643 add_frameFromTupleStr(tuple, FIELD_ARTIST, ID3_ARTIST);
644
645 if (tuple_get_string(tuple, FIELD_TITLE, NULL))
646 add_frameFromTupleStr(tuple, FIELD_TITLE, ID3_TITLE);
647
648 if (tuple_get_string(tuple, FIELD_ALBUM, NULL))
649 add_frameFromTupleStr(tuple, FIELD_ALBUM, ID3_ALBUM);
650
651 if (tuple_get_string(tuple, FIELD_COMMENT, NULL))
652 add_frameFromTupleStr(tuple, FIELD_COMMENT, ID3_COMMENT);
653
654 if (tuple_get_string(tuple, FIELD_GENRE, NULL))
655 add_frameFromTupleStr(tuple, FIELD_GENRE, ID3_GENRE);
656
657 if (tuple_get_int(tuple, FIELD_YEAR, NULL) != 0)
658 add_frameFromTupleInt(tuple, FIELD_YEAR, ID3_YEAR);
659
660 if (tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL) != 0)
661 add_frameFromTupleInt(tuple, FIELD_TRACK_NUMBER, ID3_TRACKNR);
662
663 const gchar *tmpdir = g_get_tmp_dir();
664 gchar *tmp_path = g_strdup_printf("file://%s/%s", tmpdir, "tmp.mpc");
665 tmp = vfs_fopen(tmp_path, "w+");
666
667 int oldSize = header->size;
668 header->size = TAG_SIZE * 1024;
669
670 writeID3HeaderToFile(tmp, header);
671
672 int size = writeAllFramesToFile(tmp);
673 writePaddingToFile(tmp, TAG_SIZE * 1024 - size - 10);
674
675 copyAudioToFile(f, tmp, oldSize);
676
677
678 gchar *uri = g_strdup(f->uri);
679 vfs_fclose(tmp);
680 gchar *f1 = g_filename_from_uri(tmp_path, NULL, NULL);
681 gchar *f2 = g_filename_from_uri(uri, NULL, NULL);
682 if (g_rename(f1, f2) == 0)
683 {
684 AUDDBG("the tag was updated successfully\n");
685 }
686 else
687 {
688 AUDDBG("an error has occured\n");
689 }
690 return TRUE;
691 }