Mercurial > hg > sv-dependency-builds
comparison src/libid3tag-0.15.1b/file.c @ 85:545efbb81310
Import initial set of sources
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Mon, 18 Mar 2013 14:12:14 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 85:545efbb81310 |
---|---|
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 struct filetag { | |
46 struct id3_tag *tag; | |
47 unsigned long location; | |
48 id3_length_t length; | |
49 }; | |
50 | |
51 struct id3_file { | |
52 FILE *iofile; | |
53 enum id3_file_mode mode; | |
54 char *path; | |
55 | |
56 int flags; | |
57 | |
58 struct id3_tag *primary; | |
59 | |
60 unsigned int ntags; | |
61 struct filetag *tags; | |
62 }; | |
63 | |
64 enum { | |
65 ID3_FILE_FLAG_ID3V1 = 0x0001 | |
66 }; | |
67 | |
68 /* | |
69 * NAME: query_tag() | |
70 * DESCRIPTION: check for a tag at a file's current position | |
71 */ | |
72 static | |
73 signed long query_tag(FILE *iofile) | |
74 { | |
75 fpos_t save_position; | |
76 id3_byte_t query[ID3_TAG_QUERYSIZE]; | |
77 signed long size; | |
78 | |
79 if (fgetpos(iofile, &save_position) == -1) | |
80 return 0; | |
81 | |
82 size = id3_tag_query(query, fread(query, 1, sizeof(query), iofile)); | |
83 | |
84 if (fsetpos(iofile, &save_position) == -1) | |
85 return 0; | |
86 | |
87 return size; | |
88 } | |
89 | |
90 /* | |
91 * NAME: read_tag() | |
92 * DESCRIPTION: read and parse a tag at a file's current position | |
93 */ | |
94 static | |
95 struct id3_tag *read_tag(FILE *iofile, id3_length_t size) | |
96 { | |
97 id3_byte_t *data; | |
98 struct id3_tag *tag = 0; | |
99 | |
100 data = malloc(size); | |
101 if (data) { | |
102 if (fread(data, size, 1, iofile) == 1) | |
103 tag = id3_tag_parse(data, size); | |
104 | |
105 free(data); | |
106 } | |
107 | |
108 return tag; | |
109 } | |
110 | |
111 /* | |
112 * NAME: update_primary() | |
113 * DESCRIPTION: update the primary tag with data from a new tag | |
114 */ | |
115 static | |
116 int update_primary(struct id3_tag *tag, struct id3_tag const *new) | |
117 { | |
118 unsigned int i; | |
119 struct id3_frame *frame; | |
120 | |
121 if (new) { | |
122 if (!(new->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)) | |
123 id3_tag_clearframes(tag); | |
124 | |
125 i = 0; | |
126 while ((frame = id3_tag_findframe(new, 0, i++))) { | |
127 if (id3_tag_attachframe(tag, frame) == -1) | |
128 return -1; | |
129 } | |
130 } | |
131 | |
132 return 0; | |
133 } | |
134 | |
135 /* | |
136 * NAME: tag_compare() | |
137 * DESCRIPTION: tag sort function for qsort() | |
138 */ | |
139 static | |
140 int tag_compare(const void *a, const void *b) | |
141 { | |
142 struct filetag const *tag1 = a, *tag2 = b; | |
143 | |
144 if (tag1->location < tag2->location) | |
145 return -1; | |
146 else if (tag1->location > tag2->location) | |
147 return +1; | |
148 | |
149 return 0; | |
150 } | |
151 | |
152 /* | |
153 * NAME: add_filetag() | |
154 * DESCRIPTION: add a new file tag entry | |
155 */ | |
156 static | |
157 int add_filetag(struct id3_file *file, struct filetag const *filetag) | |
158 { | |
159 struct filetag *tags; | |
160 | |
161 tags = realloc(file->tags, (file->ntags + 1) * sizeof(*tags)); | |
162 if (tags == 0) | |
163 return -1; | |
164 | |
165 file->tags = tags; | |
166 file->tags[file->ntags++] = *filetag; | |
167 | |
168 /* sort tags by location */ | |
169 | |
170 if (file->ntags > 1) | |
171 qsort(file->tags, file->ntags, sizeof(file->tags[0]), tag_compare); | |
172 | |
173 return 0; | |
174 } | |
175 | |
176 /* | |
177 * NAME: del_filetag() | |
178 * DESCRIPTION: delete a file tag entry | |
179 */ | |
180 static | |
181 void del_filetag(struct id3_file *file, unsigned int index) | |
182 { | |
183 assert(index < file->ntags); | |
184 | |
185 while (index < file->ntags - 1) { | |
186 file->tags[index] = file->tags[index + 1]; | |
187 ++index; | |
188 } | |
189 | |
190 --file->ntags; | |
191 } | |
192 | |
193 /* | |
194 * NAME: add_tag() | |
195 * DESCRIPTION: read, parse, and add a tag to a file structure | |
196 */ | |
197 static | |
198 struct id3_tag *add_tag(struct id3_file *file, id3_length_t length) | |
199 { | |
200 long location; | |
201 unsigned int i; | |
202 struct filetag filetag; | |
203 struct id3_tag *tag; | |
204 | |
205 location = ftell(file->iofile); | |
206 if (location == -1) | |
207 return 0; | |
208 | |
209 /* check for duplication/overlap */ | |
210 { | |
211 unsigned long begin1, end1, begin2, end2; | |
212 | |
213 begin1 = location; | |
214 end1 = begin1 + length; | |
215 | |
216 for (i = 0; i < file->ntags; ++i) { | |
217 begin2 = file->tags[i].location; | |
218 end2 = begin2 + file->tags[i].length; | |
219 | |
220 if (begin1 == begin2 && end1 == end2) | |
221 return file->tags[i].tag; /* duplicate */ | |
222 | |
223 if (begin1 < end2 && end1 > begin2) | |
224 return 0; /* overlap */ | |
225 } | |
226 } | |
227 | |
228 tag = read_tag(file->iofile, length); | |
229 | |
230 filetag.tag = tag; | |
231 filetag.location = location; | |
232 filetag.length = length; | |
233 | |
234 if (add_filetag(file, &filetag) == -1 || | |
235 update_primary(file->primary, tag) == -1) { | |
236 if (tag) | |
237 id3_tag_delete(tag); | |
238 return 0; | |
239 } | |
240 | |
241 if (tag) | |
242 id3_tag_addref(tag); | |
243 | |
244 return tag; | |
245 } | |
246 | |
247 /* | |
248 * NAME: search_tags() | |
249 * DESCRIPTION: search for tags in a file | |
250 */ | |
251 static | |
252 int search_tags(struct id3_file *file) | |
253 { | |
254 fpos_t save_position; | |
255 signed long size; | |
256 | |
257 /* | |
258 * save the current seek position | |
259 * | |
260 * We also verify the stream is seekable by calling fsetpos(), since | |
261 * fgetpos() alone is not reliable enough for this purpose. | |
262 * | |
263 * [Apparently not even fsetpos() is sufficient under Win32.] | |
264 */ | |
265 | |
266 if (fgetpos(file->iofile, &save_position) == -1 || | |
267 fsetpos(file->iofile, &save_position) == -1) | |
268 return -1; | |
269 | |
270 /* look for an ID3v1 tag */ | |
271 | |
272 if (fseek(file->iofile, -128, SEEK_END) == 0) { | |
273 size = query_tag(file->iofile); | |
274 if (size > 0) { | |
275 struct id3_tag const *tag; | |
276 | |
277 tag = add_tag(file, size); | |
278 | |
279 /* if this is indeed an ID3v1 tag, mark the file so */ | |
280 | |
281 if (tag && (ID3_TAG_VERSION_MAJOR(id3_tag_version(tag)) == 1)) | |
282 file->flags |= ID3_FILE_FLAG_ID3V1; | |
283 } | |
284 } | |
285 | |
286 /* look for a tag at the beginning of the file */ | |
287 | |
288 rewind(file->iofile); | |
289 | |
290 size = query_tag(file->iofile); | |
291 if (size > 0) { | |
292 struct id3_tag const *tag; | |
293 struct id3_frame const *frame; | |
294 | |
295 tag = add_tag(file, size); | |
296 | |
297 /* locate tags indicated by SEEK frames */ | |
298 | |
299 while (tag && (frame = id3_tag_findframe(tag, "SEEK", 0))) { | |
300 long seek; | |
301 | |
302 seek = id3_field_getint(id3_frame_field(frame, 0)); | |
303 if (seek < 0 || fseek(file->iofile, seek, SEEK_CUR) == -1) | |
304 break; | |
305 | |
306 size = query_tag(file->iofile); | |
307 tag = (size > 0) ? add_tag(file, size) : 0; | |
308 } | |
309 } | |
310 | |
311 /* look for a tag at the end of the file (before any ID3v1 tag) */ | |
312 | |
313 if (fseek(file->iofile, ((file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0) + | |
314 -10, SEEK_END) == 0) { | |
315 size = query_tag(file->iofile); | |
316 if (size < 0 && fseek(file->iofile, size, SEEK_CUR) == 0) { | |
317 size = query_tag(file->iofile); | |
318 if (size > 0) | |
319 add_tag(file, size); | |
320 } | |
321 } | |
322 | |
323 clearerr(file->iofile); | |
324 | |
325 /* restore seek position */ | |
326 | |
327 if (fsetpos(file->iofile, &save_position) == -1) | |
328 return -1; | |
329 | |
330 /* set primary tag options and target padded length for convenience */ | |
331 | |
332 if ((file->ntags > 0 && !(file->flags & ID3_FILE_FLAG_ID3V1)) || | |
333 (file->ntags > 1 && (file->flags & ID3_FILE_FLAG_ID3V1))) { | |
334 if (file->tags[0].location == 0) | |
335 id3_tag_setlength(file->primary, file->tags[0].length); | |
336 else | |
337 id3_tag_options(file->primary, ID3_TAG_OPTION_APPENDEDTAG, ~0); | |
338 } | |
339 | |
340 return 0; | |
341 } | |
342 | |
343 /* | |
344 * NAME: finish_file() | |
345 * DESCRIPTION: release memory associated with a file | |
346 */ | |
347 static | |
348 void finish_file(struct id3_file *file) | |
349 { | |
350 unsigned int i; | |
351 | |
352 if (file->path) | |
353 free(file->path); | |
354 | |
355 if (file->primary) { | |
356 id3_tag_delref(file->primary); | |
357 id3_tag_delete(file->primary); | |
358 } | |
359 | |
360 for (i = 0; i < file->ntags; ++i) { | |
361 struct id3_tag *tag; | |
362 | |
363 tag = file->tags[i].tag; | |
364 if (tag) { | |
365 id3_tag_delref(tag); | |
366 id3_tag_delete(tag); | |
367 } | |
368 } | |
369 | |
370 if (file->tags) | |
371 free(file->tags); | |
372 | |
373 free(file); | |
374 } | |
375 | |
376 /* | |
377 * NAME: new_file() | |
378 * DESCRIPTION: create a new file structure and load tags | |
379 */ | |
380 static | |
381 struct id3_file *new_file(FILE *iofile, enum id3_file_mode mode, | |
382 char const *path) | |
383 { | |
384 struct id3_file *file; | |
385 | |
386 file = malloc(sizeof(*file)); | |
387 if (file == 0) | |
388 goto fail; | |
389 | |
390 file->iofile = iofile; | |
391 file->mode = mode; | |
392 file->path = path ? strdup(path) : 0; | |
393 | |
394 file->flags = 0; | |
395 | |
396 file->ntags = 0; | |
397 file->tags = 0; | |
398 | |
399 file->primary = id3_tag_new(); | |
400 if (file->primary == 0) | |
401 goto fail; | |
402 | |
403 id3_tag_addref(file->primary); | |
404 | |
405 /* load tags from the file */ | |
406 | |
407 if (search_tags(file) == -1) | |
408 goto fail; | |
409 | |
410 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, | |
411 (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0); | |
412 | |
413 if (0) { | |
414 fail: | |
415 if (file) { | |
416 finish_file(file); | |
417 file = 0; | |
418 } | |
419 } | |
420 | |
421 return file; | |
422 } | |
423 | |
424 /* | |
425 * NAME: file->open() | |
426 * DESCRIPTION: open a file given its pathname | |
427 */ | |
428 struct id3_file *id3_file_open(char const *path, enum id3_file_mode mode) | |
429 { | |
430 FILE *iofile; | |
431 struct id3_file *file; | |
432 | |
433 assert(path); | |
434 | |
435 iofile = fopen(path, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb"); | |
436 if (iofile == 0) | |
437 return 0; | |
438 | |
439 file = new_file(iofile, mode, path); | |
440 if (file == 0) | |
441 fclose(iofile); | |
442 | |
443 return file; | |
444 } | |
445 | |
446 /* | |
447 * NAME: file->fdopen() | |
448 * DESCRIPTION: open a file using an existing file descriptor | |
449 */ | |
450 struct id3_file *id3_file_fdopen(int fd, enum id3_file_mode mode) | |
451 { | |
452 # if 1 || defined(HAVE_UNISTD_H) | |
453 FILE *iofile; | |
454 struct id3_file *file; | |
455 | |
456 iofile = fdopen(fd, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb"); | |
457 if (iofile == 0) | |
458 return 0; | |
459 | |
460 file = new_file(iofile, mode, 0); | |
461 if (file == 0) { | |
462 int save_fd; | |
463 | |
464 /* close iofile without closing fd */ | |
465 | |
466 save_fd = dup(fd); | |
467 | |
468 fclose(iofile); | |
469 | |
470 dup2(save_fd, fd); | |
471 close(save_fd); | |
472 } | |
473 | |
474 return file; | |
475 # else | |
476 return 0; | |
477 # endif | |
478 } | |
479 | |
480 /* | |
481 * NAME: file->close() | |
482 * DESCRIPTION: close a file and delete its associated tags | |
483 */ | |
484 int id3_file_close(struct id3_file *file) | |
485 { | |
486 int result = 0; | |
487 | |
488 assert(file); | |
489 | |
490 if (fclose(file->iofile) == EOF) | |
491 result = -1; | |
492 | |
493 finish_file(file); | |
494 | |
495 return result; | |
496 } | |
497 | |
498 /* | |
499 * NAME: file->tag() | |
500 * DESCRIPTION: return the primary tag structure for a file | |
501 */ | |
502 struct id3_tag *id3_file_tag(struct id3_file const *file) | |
503 { | |
504 assert(file); | |
505 | |
506 return file->primary; | |
507 } | |
508 | |
509 /* | |
510 * NAME: v1_write() | |
511 * DESCRIPTION: write ID3v1 tag modifications to a file | |
512 */ | |
513 static | |
514 int v1_write(struct id3_file *file, | |
515 id3_byte_t const *data, id3_length_t length) | |
516 { | |
517 assert(!data || length == 128); | |
518 | |
519 if (data) { | |
520 long location; | |
521 | |
522 if (fseek(file->iofile, (file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0, | |
523 SEEK_END) == -1 || | |
524 (location = ftell(file->iofile)) == -1 || | |
525 fwrite(data, 128, 1, file->iofile) != 1 || | |
526 fflush(file->iofile) == EOF) | |
527 return -1; | |
528 | |
529 /* add file tag reference */ | |
530 | |
531 if (!(file->flags & ID3_FILE_FLAG_ID3V1)) { | |
532 struct filetag filetag; | |
533 | |
534 filetag.tag = 0; | |
535 filetag.location = location; | |
536 filetag.length = 128; | |
537 | |
538 if (add_filetag(file, &filetag) == -1) | |
539 return -1; | |
540 | |
541 file->flags |= ID3_FILE_FLAG_ID3V1; | |
542 } | |
543 } | |
544 # if defined(HAVE_FTRUNCATE) | |
545 else if (file->flags & ID3_FILE_FLAG_ID3V1) { | |
546 long length; | |
547 | |
548 if (fseek(file->iofile, 0, SEEK_END) == -1) | |
549 return -1; | |
550 | |
551 length = ftell(file->iofile); | |
552 if (length == -1 || | |
553 (length >= 0 && length < 128)) | |
554 return -1; | |
555 | |
556 if (ftruncate(fileno(file->iofile), length - 128) == -1) | |
557 return -1; | |
558 | |
559 /* delete file tag reference */ | |
560 | |
561 del_filetag(file, file->ntags - 1); | |
562 | |
563 file->flags &= ~ID3_FILE_FLAG_ID3V1; | |
564 } | |
565 # endif | |
566 | |
567 return 0; | |
568 } | |
569 | |
570 /* | |
571 * NAME: v2_write() | |
572 * DESCRIPTION: write ID3v2 tag modifications to a file | |
573 */ | |
574 static | |
575 int v2_write(struct id3_file *file, | |
576 id3_byte_t const *data, id3_length_t length) | |
577 { | |
578 assert(!data || length > 0); | |
579 | |
580 if (data && | |
581 ((file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) || | |
582 (file->ntags == 2 && (file->flags & ID3_FILE_FLAG_ID3V1))) && | |
583 file->tags[0].length == length) { | |
584 /* easy special case: rewrite existing tag in-place */ | |
585 | |
586 if (fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 || | |
587 fwrite(data, length, 1, file->iofile) != 1 || | |
588 fflush(file->iofile) == EOF) | |
589 return -1; | |
590 | |
591 goto done; | |
592 } | |
593 | |
594 /* hard general case: rewrite entire file */ | |
595 | |
596 /* ... */ | |
597 | |
598 done: | |
599 return 0; | |
600 } | |
601 | |
602 /* | |
603 * NAME: file->update() | |
604 * DESCRIPTION: rewrite tag(s) to a file | |
605 */ | |
606 int id3_file_update(struct id3_file *file) | |
607 { | |
608 int options, result = 0; | |
609 id3_length_t v1size = 0, v2size = 0; | |
610 id3_byte_t id3v1_data[128], *id3v1 = 0, *id3v2 = 0; | |
611 | |
612 assert(file); | |
613 | |
614 if (file->mode != ID3_FILE_MODE_READWRITE) | |
615 return -1; | |
616 | |
617 options = id3_tag_options(file->primary, 0, 0); | |
618 | |
619 /* render ID3v1 */ | |
620 | |
621 if (options & ID3_TAG_OPTION_ID3V1) { | |
622 v1size = id3_tag_render(file->primary, 0); | |
623 if (v1size) { | |
624 assert(v1size == sizeof(id3v1_data)); | |
625 | |
626 v1size = id3_tag_render(file->primary, id3v1_data); | |
627 if (v1size) { | |
628 assert(v1size == sizeof(id3v1_data)); | |
629 id3v1 = id3v1_data; | |
630 } | |
631 } | |
632 } | |
633 | |
634 /* render ID3v2 */ | |
635 | |
636 id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0); | |
637 | |
638 v2size = id3_tag_render(file->primary, 0); | |
639 if (v2size) { | |
640 id3v2 = malloc(v2size); | |
641 if (id3v2 == 0) | |
642 goto fail; | |
643 | |
644 v2size = id3_tag_render(file->primary, id3v2); | |
645 if (v2size == 0) { | |
646 free(id3v2); | |
647 id3v2 = 0; | |
648 } | |
649 } | |
650 | |
651 /* write tags */ | |
652 | |
653 if (v2_write(file, id3v2, v2size) == -1 || | |
654 v1_write(file, id3v1, v1size) == -1) | |
655 goto fail; | |
656 | |
657 rewind(file->iofile); | |
658 | |
659 /* update file tags array? ... */ | |
660 | |
661 if (0) { | |
662 fail: | |
663 result = -1; | |
664 } | |
665 | |
666 /* clean up; restore tag options */ | |
667 | |
668 if (id3v2) | |
669 free(id3v2); | |
670 | |
671 id3_tag_options(file->primary, ~0, options); | |
672 | |
673 return result; | |
674 } |