comparison src/libid3tag/file.c @ 2503:10692383c103 trunk

[svn] first try for libid3tag integration. this improved libid3tag supports vfs operations and is capable of adding id3v2 tag to files which doesn't have id3v2 tag ever.
author yaz
date Sun, 11 Feb 2007 05:19:07 -0800
parents
children 1e6d2d719876
comparison
equal deleted inserted replaced
2502:b7be0af74307 2503:10692383c103
1 /*
2 * libid3tag - ID3 tag manipulation library
3 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 * $Id: file.c,v 1.21 2004/01/23 09:41:32 rob Exp $
20 */
21
22 # ifdef HAVE_CONFIG_H
23 # include "config.h"
24 # endif
25
26 # include "global.h"
27
28 # include <stdio.h>
29 # include <stdlib.h>
30 # include <string.h>
31
32 # ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 # endif
35
36 # ifdef HAVE_ASSERT_H
37 # include <assert.h>
38 # endif
39
40 # include "id3tag.h"
41 # include "file.h"
42 # include "tag.h"
43 # include "field.h"
44
45 #define AUDACIOUS 1
46 //#undef AUDACIOUS
47
48 #ifdef AUDACIOUS
49 #undef G_BEGIN_DECLS
50 #undef G_END_DECLS
51 #include <audacious/vfs.h>
52 #else
53 #define VFSFile FILE
54 #define vfs_fopen fopen
55 #define vfs_fclose fclose
56 #define vfs_fseek fseek
57 #define vfs_ftell ftell
58 #define vfs_rewind rewind
59 #define vfs_fread fread
60 #define vfs_fwrite fwrite
61 #define vfs_truncate(x, y) ftruncate((fileno(x)), (y))
62 #endif
63
64 struct filetag {
65 struct id3_tag *tag;
66 unsigned long location;
67 id3_length_t length;
68 };
69
70 struct id3_file {
71 VFSFile *iofile;
72 enum id3_file_mode mode;
73 char *path;
74
75 int flags;
76
77 struct id3_tag *primary;
78
79 unsigned int ntags;
80 struct filetag *tags;
81 };
82
83 enum {
84 ID3_FILE_FLAG_ID3V1 = 0x0001
85 };
86
87 /*
88 * NAME: query_tag()
89 * DESCRIPTION: check for a tag at a file's current position
90 */
91 static
92 signed long query_tag(VFSFile *iofile)
93 {
94 int save_position;
95 id3_byte_t query[ID3_TAG_QUERYSIZE];
96 signed long size;
97
98 save_position = vfs_ftell(iofile);
99 if (save_position == -1)
100 return 0;
101
102 size = id3_tag_query(query, vfs_fread(query, 1, sizeof(query), iofile));
103
104 if(vfs_fseek(iofile, save_position, SEEK_SET) == -1)
105 return 0;
106
107 return size;
108 }
109
110 /*
111 * NAME: read_tag()
112 * DESCRIPTION: read and parse a tag at a file's current position
113 */
114 static
115 struct id3_tag *read_tag(VFSFile *iofile, id3_length_t size)
116 {
117 id3_byte_t *data;
118 struct id3_tag *tag = 0;
119
120 data = malloc(size);
121 if (data) {
122 if (vfs_fread(data, size, 1, iofile) == 1)
123 tag = id3_tag_parse(data, size);
124
125 free(data);
126 }
127
128 return tag;
129 }
130
131 /*
132 * NAME: update_primary()
133 * DESCRIPTION: update the primary tag with data from a new tag
134 */
135 static
136 int update_primary(struct id3_tag *tag, struct id3_tag const *new)
137 {
138 unsigned int i;
139 struct id3_frame *frame;
140
141 if (new) {
142 if (!(new->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE))
143 id3_tag_clearframes(tag);
144
145 i = 0;
146 while ((frame = id3_tag_findframe(new, 0, i++))) {
147 if (id3_tag_attachframe(tag, frame) == -1)
148 return -1;
149 }
150 }
151
152 return 0;
153 }
154
155 /*
156 * NAME: tag_compare()
157 * DESCRIPTION: tag sort function for qsort()
158 */
159 static
160 int tag_compare(const void *a, const void *b)
161 {
162 struct filetag const *tag1 = a, *tag2 = b;
163
164 if (tag1->location < tag2->location)
165 return -1;
166 else if (tag1->location > tag2->location)
167 return +1;
168
169 return 0;
170 }
171
172 /*
173 * NAME: add_filetag()
174 * DESCRIPTION: add a new file tag entry
175 */
176 static
177 int add_filetag(struct id3_file *file, struct filetag const *filetag)
178 {
179 struct filetag *tags;
180
181 tags = realloc(file->tags, (file->ntags + 1) * sizeof(*tags));
182 if (tags == 0)
183 return -1;
184
185 file->tags = tags;
186 file->tags[file->ntags++] = *filetag;
187
188 /* sort tags by location */
189
190 if (file->ntags > 1)
191 qsort(file->tags, file->ntags, sizeof(file->tags[0]), tag_compare);
192
193 return 0;
194 }
195
196 /*
197 * NAME: del_filetag()
198 * DESCRIPTION: delete a file tag entry
199 */
200 static
201 void del_filetag(struct id3_file *file, unsigned int index)
202 {
203 assert(index < file->ntags);
204
205 while (index < file->ntags - 1) {
206 file->tags[index] = file->tags[index + 1];
207 ++index;
208 }
209
210 --file->ntags;
211 }
212
213 /*
214 * NAME: add_tag()
215 * DESCRIPTION: read, parse, and add a tag to a file structure
216 */
217 static
218 struct id3_tag *add_tag(struct id3_file *file, id3_length_t length)
219 {
220 long location;
221 unsigned int i;
222 struct filetag filetag;
223 struct id3_tag *tag;
224
225 location = vfs_ftell(file->iofile);
226 if (location == -1)
227 return 0;
228
229 /* check for duplication/overlap */
230 {
231 unsigned long begin1, end1, begin2, end2;
232
233 begin1 = location;
234 end1 = begin1 + length;
235
236 for (i = 0; i < file->ntags; ++i) {
237 begin2 = file->tags[i].location;
238 end2 = begin2 + file->tags[i].length;
239
240 if (begin1 == begin2 && end1 == end2)
241 return file->tags[i].tag; /* duplicate */
242
243 if (begin1 < end2 && end1 > begin2)
244 return 0; /* overlap */
245 }
246 }
247
248 tag = read_tag(file->iofile, length);
249
250 filetag.tag = tag;
251 filetag.location = location;
252 filetag.length = length;
253
254 if (add_filetag(file, &filetag) == -1 ||
255 update_primary(file->primary, tag) == -1) {
256 if (tag)
257 id3_tag_delete(tag);
258 return 0;
259 }
260
261 if (tag)
262 id3_tag_addref(tag);
263
264 return tag;
265 }
266
267 /*
268 * NAME: search_tags()
269 * DESCRIPTION: search for tags in a file
270 */
271 //static
272 int search_tags(struct id3_file *file)
273 {
274 int save_position;
275 signed long size;
276
277 /*
278 * save the current seek position
279 *
280 * We also verify the stream is seekable by calling fsetpos(), since
281 * fgetpos() alone is not reliable enough for this purpose.
282 *
283 * [Apparently not even fsetpos() is sufficient under Win32.]
284 */
285
286 // if (fgetpos(file->iofile, &save_position) == -1 ||
287 // fsetpos(file->iofile, &save_position) == -1)
288 // if (save_position = vfs_ftell(file->iofile) == -1 ||
289 // vfs_fseek(file->iofile, save_position, SEEK_SET) == -1)
290 if((save_position = vfs_ftell(file->iofile)) == -1)
291 return -1;
292
293 /* look for an ID3v1 tag */
294
295 if (vfs_fseek(file->iofile, -128, SEEK_END) == 0) {
296 size = query_tag(file->iofile);
297 if (size > 0) {
298 struct id3_tag const *tag;
299
300 tag = add_tag(file, size);
301
302 /* if this is indeed an ID3v1 tag, mark the file so */
303
304 if (tag && (ID3_TAG_VERSION_MAJOR(id3_tag_version(tag)) == 1))
305 file->flags |= ID3_FILE_FLAG_ID3V1;
306 }
307 }
308
309 /* look for a tag at the beginning of the file */
310
311 vfs_rewind(file->iofile);
312
313 size = query_tag(file->iofile);
314 if (size > 0) {
315 struct id3_tag const *tag;
316 struct id3_frame const *frame;
317
318 tag = add_tag(file, size);
319
320 /* locate tags indicated by SEEK frames */
321
322 while (tag && (frame = id3_tag_findframe(tag, "SEEK", 0))) {
323 long seek;
324
325 seek = id3_field_getint(id3_frame_field(frame, 0));
326 if (seek < 0 || vfs_fseek(file->iofile, seek, SEEK_CUR) == -1)
327 break;
328
329 size = query_tag(file->iofile);
330 tag = (size > 0) ? add_tag(file, size) : 0;
331 }
332 }
333
334 /* look for a tag at the end of the file (before any ID3v1 tag) */
335
336 if (vfs_fseek(file->iofile, ((file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0) +
337 -10, SEEK_END) == 0) {
338 size = query_tag(file->iofile);
339 if (size < 0 && vfs_fseek(file->iofile, size, SEEK_CUR) == 0) {
340 size = query_tag(file->iofile);
341 if (size > 0)
342 add_tag(file, size);
343 }
344 }
345
346 #ifndef AUDACIOUS
347 clearerr(file->iofile);
348 #endif
349
350 /* restore seek position */
351
352 // if (fsetpos(file->iofile, &save_position) == -1)
353 if (vfs_fseek(file->iofile, save_position, SEEK_SET) == -1)
354 return -1;
355
356 /* set primary tag options and target padded length for convenience */
357
358 if ((file->ntags > 0 && !(file->flags & ID3_FILE_FLAG_ID3V1)) ||
359 (file->ntags > 1 && (file->flags & ID3_FILE_FLAG_ID3V1))) {
360 if (file->tags[0].location == 0)
361 id3_tag_setlength(file->primary, file->tags[0].length);
362 else
363 id3_tag_options(file->primary, ID3_TAG_OPTION_APPENDEDTAG, ~0);
364 }
365
366 return 0;
367 }
368
369 /*
370 * NAME: finish_file()
371 * DESCRIPTION: release memory associated with a file
372 */
373 static
374 void finish_file(struct id3_file *file)
375 {
376 unsigned int i;
377
378 if (file->path)
379 free(file->path);
380
381 if (file->primary) {
382 id3_tag_delref(file->primary);
383 id3_tag_delete(file->primary);
384 }
385
386 for (i = 0; i < file->ntags; ++i) {
387 struct id3_tag *tag;
388
389 tag = file->tags[i].tag;
390 if (tag) {
391 id3_tag_delref(tag);
392 id3_tag_delete(tag);
393 }
394 }
395
396 if (file->tags)
397 free(file->tags);
398
399 free(file);
400 }
401
402 /*
403 * NAME: new_file()
404 * DESCRIPTION: create a new file structure and load tags
405 */
406 static
407 struct id3_file *new_file(VFSFile *iofile, enum id3_file_mode mode,
408 char const *path)
409 {
410 struct id3_file *file;
411
412 file = malloc(sizeof(*file));
413 if (file == 0)
414 goto fail;
415
416 file->iofile = iofile;
417 file->mode = mode;
418 file->path = path ? strdup(path) : 0;
419
420 file->flags = 0;
421
422 file->ntags = 0;
423 file->tags = 0;
424
425 file->primary = id3_tag_new();
426 if (file->primary == 0)
427 goto fail;
428
429 id3_tag_addref(file->primary);
430
431 /* load tags from the file */
432
433 if (search_tags(file) == -1)
434 goto fail;
435
436 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1,
437 (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0);
438
439 if (0) {
440 fail:
441 if (file) {
442 finish_file(file);
443 file = 0;
444 }
445 }
446
447 return file;
448 }
449
450
451 /*
452 * NAME: file->open()
453 * DESCRIPTION: open a file given its pathname
454 */
455 struct id3_file *id3_file_open(char const *path, enum id3_file_mode mode)
456 {
457 VFSFile *iofile;
458 struct id3_file *file;
459
460 assert(path);
461
462 iofile = vfs_fopen(path, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb");
463 if (iofile == 0){
464 printf("id3_file_open: iofile failed\n");
465 return 0;
466 }
467 file = new_file(iofile, mode, path);
468 if (file == 0){
469 printf("id3_file_open: file failed\n");
470 vfs_fclose(iofile);
471 }
472
473 return file;
474 }
475
476 /*
477 * NAME: file->fdopen()
478 * DESCRIPTION: open a file using an existing file descriptor
479 */
480 #ifndef AUDACIOUS
481 struct id3_file *id3_file_fdopen(int fd, enum id3_file_mode mode)
482 {
483 # if 1 || defined(HAVE_UNISTD_H)
484 VFSFile *iofile;
485 struct id3_file *file;
486
487 iofile = fdopen(fd, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb");
488 if (iofile == 0)
489 return 0;
490
491 file = new_file(iofile, mode, 0);
492 if (file == 0) {
493 int save_fd;
494
495 /* close iofile without closing fd */
496
497 save_fd = dup(fd);
498
499 fclose(iofile);
500
501 dup2(save_fd, fd);
502 close(save_fd);
503 }
504
505 return file;
506 # else
507 return 0;
508 # endif
509 }
510 #endif
511
512 /*
513 * NAME: file->close()
514 * DESCRIPTION: close a file and delete its associated tags
515 */
516 int id3_file_close(struct id3_file *file)
517 {
518 int result = 0;
519
520 assert(file);
521
522 if (vfs_fclose(file->iofile) == EOF)
523 result = -1;
524
525 finish_file(file);
526
527 return result;
528 }
529
530 /*
531 * NAME: file->tag()
532 * DESCRIPTION: return the primary tag structure for a file
533 */
534 struct id3_tag *id3_file_tag(struct id3_file const *file)
535 {
536 assert(file);
537
538 return file->primary;
539 }
540
541 /*
542 * NAME: v1_write()
543 * DESCRIPTION: write ID3v1 tag modifications to a file
544 */
545 static
546 int v1_write(struct id3_file *file,
547 id3_byte_t const *data, id3_length_t length)
548 {
549 assert(!data || length == 128);
550
551 if (data) {
552 long location;
553
554 if (vfs_fseek(file->iofile, (file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0,
555 SEEK_END) == -1 ||
556 (location = vfs_ftell(file->iofile)) == -1 ||
557 #ifdef AUDACIOUS
558 vfs_fwrite(data, 128, 1, file->iofile) != 1 )
559 #else
560 vfs_fwrite(data, 128, 1, file->iofile) != 1 ||
561 fflush(file->iofile) == EOF) //XXX
562 #endif
563 return -1;
564
565 /* add file tag reference */
566
567 if (!(file->flags & ID3_FILE_FLAG_ID3V1)) {
568 struct filetag filetag;
569
570 filetag.tag = 0;
571 filetag.location = location;
572 filetag.length = 128;
573
574 if (add_filetag(file, &filetag) == -1)
575 return -1;
576
577 file->flags |= ID3_FILE_FLAG_ID3V1;
578 }
579 }
580 # if defined(HAVE_FTRUNCATE)
581 else if (file->flags & ID3_FILE_FLAG_ID3V1) {
582 long length;
583
584 if (vfs_fseek(file->iofile, 0, SEEK_END) == -1)
585 return -1;
586
587 length = vfs_ftell(file->iofile);
588 if (length == -1 ||
589 (length >= 0 && length < 128))
590 return -1;
591
592 // if (ftruncate(fileno(file->iofile), length - 128) == -1) //XXX
593 if (vfs_truncate(file->iofile, length - 128) == -1)
594 return -1;
595
596 /* delete file tag reference */
597
598 del_filetag(file, file->ntags - 1);
599
600 file->flags &= ~ID3_FILE_FLAG_ID3V1;
601 }
602 # endif
603
604 return 0;
605 }
606
607 /*
608 * NAME: v2_write()
609 * DESCRIPTION: write ID3v2 tag modifications to a file
610 */
611 static
612 int v2_write(struct id3_file *file,
613 id3_byte_t const *data, id3_length_t length)
614 {
615 assert(!data || length > 0);
616
617 // append a new id3v2 tag to the file which doesn't have any tag or only have v1tag.
618 if(data &&
619 ((file->ntags == 0) || // no tag
620 (file->ntags == 1 && (file->flags & ID3_FILE_FLAG_ID3V1))) ) { // only v1 tag exists
621
622 struct filetag filetag;
623
624 printf("append v2tag\n");
625
626 filetag.tag = 0;
627 filetag.location = 0; // begining of the file.
628 filetag.length = 0;
629
630 if(add_filetag(file, &filetag) == -1)
631 return -1;
632
633 if(file->ntags == 1)
634 file->flags = 0;
635 if(file->ntags == 2)
636 file->flags |= ID3_FILE_FLAG_ID3V1;
637 }
638
639 if (!data
640 || (!(file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) &&
641 !(file->ntags == 2 && (file->flags & ID3_FILE_FLAG_ID3V1)))) {
642 /* no v2 tag. nothing to do */
643 goto done;
644 }
645
646 if (file->tags[0].length == length) {
647 /* easy special case: rewrite existing tag in-place */
648
649 if (vfs_fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 ||
650 #ifdef AUDACIOUS
651 vfs_fwrite(data, length, 1, file->iofile) != 1)
652 #else
653 vfs_fwrite(data, length, 1, file->iofile) != 1 ||
654 fflush(file->iofile) == EOF)
655 #endif
656 return -1;
657
658 goto done;
659 } else {
660 /* the new tag has a different size */
661 int file_size;
662 int remainder_size;
663 char *remainder;
664
665 /* read in the remainder of the file */
666 vfs_fseek(file->iofile, 0, SEEK_END);
667 file_size = vfs_ftell(file->iofile);
668 remainder_size = file_size - file->tags[0].location - file->tags[0].length;
669 remainder = (char*)malloc(remainder_size);
670 if (vfs_fseek(file->iofile, file->tags[0].location + file->tags[0].length, SEEK_SET) == -1 ||
671 vfs_fread(remainder, remainder_size, 1, file->iofile) != 1) {
672 free(remainder);
673 return -1;
674 }
675
676 /* write the tag where the old one was */
677 if (vfs_fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 ||
678 vfs_fwrite(data, length, 1, file->iofile) != 1) {
679 free(remainder);
680 return -1;
681 }
682
683 /* write the reaminder */
684 if (vfs_fwrite(remainder, remainder_size, 1, file->iofile) != 1) {
685 free(remainder);
686 return -1;
687 }
688
689 free(remainder);
690
691 /* flush the FILE */
692 #ifndef AUDACIOUS
693 if (fflush(file->iofile) == EOF)
694 return -1;
695 #endif
696 /* truncate if required */
697 if (vfs_ftell(file->iofile) < file_size)
698 vfs_truncate(file->iofile, vfs_ftell(file->iofile));
699 }
700
701 done:
702 return 0;
703 }
704
705 /*
706 * NAME: file->update()
707 * DESCRIPTION: rewrite tag(s) to a file
708 */
709 int id3_file_update(struct id3_file *file)
710 {
711 int options, result = 0;
712 id3_length_t v1size = 0, v2size = 0;
713 id3_byte_t id3v1_data[128], *id3v1 = 0, *id3v2 = 0;
714
715 assert(file);
716
717 if (file->mode != ID3_FILE_MODE_READWRITE)
718 return -1;
719
720 options = id3_tag_options(file->primary, 0, 0);
721
722 /* render ID3v1 */
723
724 if (options & ID3_TAG_OPTION_ID3V1) {
725 v1size = id3_tag_render(file->primary, 0);
726 if (v1size) {
727 assert(v1size == sizeof(id3v1_data));
728
729 v1size = id3_tag_render(file->primary, id3v1_data);
730 if (v1size) {
731 assert(v1size == sizeof(id3v1_data));
732 id3v1 = id3v1_data;
733 }
734 }
735 }
736
737 /* render ID3v2 */
738
739 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0);
740
741 v2size = id3_tag_render(file->primary, 0);
742 if (v2size) {
743 id3v2 = malloc(v2size);
744 if (id3v2 == 0)
745 goto fail;
746
747 v2size = id3_tag_render(file->primary, id3v2);
748 if (v2size == 0) {
749 free(id3v2);
750 id3v2 = 0;
751 }
752 }
753
754 /* write tags */
755
756 if (v2_write(file, id3v2, v2size) == -1 ||
757 v1_write(file, id3v1, v1size) == -1)
758 goto fail;
759
760 vfs_rewind(file->iofile);
761
762 /* update file tags array? ... */
763
764 if (0) {
765 fail:
766 result = -1;
767 }
768
769 /* clean up; restore tag options */
770
771 if (id3v2)
772 free(id3v2);
773
774 id3_tag_options(file->primary, ~0, options);
775
776 return result;
777 }