2 * libid3tag - ID3 tag manipulation library
3 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
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.
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.
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
19 * $Id: file.c,v 1.21 2004/01/23 09:41:32 rob Exp $
47 unsigned long location
;
53 enum id3_file_mode mode
;
58 struct id3_tag
*primary
;
65 ID3_FILE_FLAG_ID3V1
= 0x0001
70 * DESCRIPTION: check for a tag at a file's current position
73 signed long query_tag(FILE *iofile
)
76 id3_byte_t query
[ID3_TAG_QUERYSIZE
];
79 if (fgetpos(iofile
, &save_position
) == -1)
82 size
= id3_tag_query(query
, fread(query
, 1, sizeof(query
), iofile
));
84 if (fsetpos(iofile
, &save_position
) == -1)
92 * DESCRIPTION: read and parse a tag at a file's current position
95 struct id3_tag
*read_tag(FILE *iofile
, id3_length_t size
)
98 struct id3_tag
*tag
= 0;
102 if (fread(data
, size
, 1, iofile
) == 1)
103 tag
= id3_tag_parse(data
, size
);
112 * NAME: update_primary()
113 * DESCRIPTION: update the primary tag with data from a new tag
116 int update_primary(struct id3_tag
*tag
, struct id3_tag
const *new)
119 struct id3_frame
*frame
;
122 if (!(new->extendedflags
& ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE
))
123 id3_tag_clearframes(tag
);
126 while ((frame
= id3_tag_findframe(new, 0, i
++))) {
127 if (id3_tag_attachframe(tag
, frame
) == -1)
136 * NAME: tag_compare()
137 * DESCRIPTION: tag sort function for qsort()
140 int tag_compare(const void *a
, const void *b
)
142 struct filetag
const *tag1
= a
, *tag2
= b
;
144 if (tag1
->location
< tag2
->location
)
146 else if (tag1
->location
> tag2
->location
)
153 * NAME: add_filetag()
154 * DESCRIPTION: add a new file tag entry
157 int add_filetag(struct id3_file
*file
, struct filetag
const *filetag
)
159 struct filetag
*tags
;
161 tags
= realloc(file
->tags
, (file
->ntags
+ 1) * sizeof(*tags
));
166 file
->tags
[file
->ntags
++] = *filetag
;
168 /* sort tags by location */
171 qsort(file
->tags
, file
->ntags
, sizeof(file
->tags
[0]), tag_compare
);
177 * NAME: del_filetag()
178 * DESCRIPTION: delete a file tag entry
181 void del_filetag(struct id3_file
*file
, unsigned int index
)
183 assert(index
< file
->ntags
);
185 while (index
< file
->ntags
- 1) {
186 file
->tags
[index
] = file
->tags
[index
+ 1];
195 * DESCRIPTION: read, parse, and add a tag to a file structure
198 struct id3_tag
*add_tag(struct id3_file
*file
, id3_length_t length
)
202 struct filetag filetag
;
205 location
= ftell(file
->iofile
);
209 /* check for duplication/overlap */
211 unsigned long begin1
, end1
, begin2
, end2
;
214 end1
= begin1
+ length
;
216 for (i
= 0; i
< file
->ntags
; ++i
) {
217 begin2
= file
->tags
[i
].location
;
218 end2
= begin2
+ file
->tags
[i
].length
;
220 if (begin1
== begin2
&& end1
== end2
)
221 return file
->tags
[i
].tag
; /* duplicate */
223 if (begin1
< end2
&& end1
> begin2
)
224 return 0; /* overlap */
228 tag
= read_tag(file
->iofile
, length
);
231 filetag
.location
= location
;
232 filetag
.length
= length
;
234 if (add_filetag(file
, &filetag
) == -1 ||
235 update_primary(file
->primary
, tag
) == -1) {
248 * NAME: search_tags()
249 * DESCRIPTION: search for tags in a file
252 int search_tags(struct id3_file
*file
)
254 fpos_t save_position
;
258 * save the current seek position
260 * We also verify the stream is seekable by calling fsetpos(), since
261 * fgetpos() alone is not reliable enough for this purpose.
263 * [Apparently not even fsetpos() is sufficient under Win32.]
266 if (fgetpos(file
->iofile
, &save_position
) == -1 ||
267 fsetpos(file
->iofile
, &save_position
) == -1)
270 /* look for an ID3v1 tag */
272 if (fseek(file
->iofile
, -128, SEEK_END
) == 0) {
273 size
= query_tag(file
->iofile
);
275 struct id3_tag
const *tag
;
277 tag
= add_tag(file
, size
);
279 /* if this is indeed an ID3v1 tag, mark the file so */
281 if (tag
&& (ID3_TAG_VERSION_MAJOR(id3_tag_version(tag
)) == 1))
282 file
->flags
|= ID3_FILE_FLAG_ID3V1
;
286 /* look for a tag at the beginning of the file */
288 rewind(file
->iofile
);
290 size
= query_tag(file
->iofile
);
292 struct id3_tag
const *tag
;
293 struct id3_frame
const *frame
;
295 tag
= add_tag(file
, size
);
297 /* locate tags indicated by SEEK frames */
299 while (tag
&& (frame
= id3_tag_findframe(tag
, "SEEK", 0))) {
302 seek
= id3_field_getint(id3_frame_field(frame
, 0));
303 if (seek
< 0 || fseek(file
->iofile
, seek
, SEEK_CUR
) == -1)
306 size
= query_tag(file
->iofile
);
307 tag
= (size
> 0) ? add_tag(file
, size
) : 0;
311 /* look for a tag at the end of the file (before any ID3v1 tag) */
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
);
323 clearerr(file
->iofile
);
325 /* restore seek position */
327 if (fsetpos(file
->iofile
, &save_position
) == -1)
330 /* set primary tag options and target padded length for convenience */
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
);
337 id3_tag_options(file
->primary
, ID3_TAG_OPTION_APPENDEDTAG
, ~0);
344 * NAME: finish_file()
345 * DESCRIPTION: release memory associated with a file
348 void finish_file(struct id3_file
*file
)
356 id3_tag_delref(file
->primary
);
357 id3_tag_delete(file
->primary
);
360 for (i
= 0; i
< file
->ntags
; ++i
) {
363 tag
= file
->tags
[i
].tag
;
378 * DESCRIPTION: create a new file structure and load tags
381 struct id3_file
*new_file(FILE *iofile
, enum id3_file_mode mode
,
384 struct id3_file
*file
;
386 file
= malloc(sizeof(*file
));
390 file
->iofile
= iofile
;
392 file
->path
= path
? strdup(path
) : 0;
399 file
->primary
= id3_tag_new();
400 if (file
->primary
== 0)
403 id3_tag_addref(file
->primary
);
405 /* load tags from the file */
407 if (search_tags(file
) == -1)
410 id3_tag_options(file
->primary
, ID3_TAG_OPTION_ID3V1
,
411 (file
->flags
& ID3_FILE_FLAG_ID3V1
) ? ~0 : 0);
426 * DESCRIPTION: open a file given its pathname
428 struct id3_file
*id3_file_open(char const *path
, enum id3_file_mode mode
)
431 struct id3_file
*file
;
435 iofile
= fopen(path
, (mode
== ID3_FILE_MODE_READWRITE
) ? "r+b" : "rb");
439 file
= new_file(iofile
, mode
, path
);
447 * NAME: file->fdopen()
448 * DESCRIPTION: open a file using an existing file descriptor
450 struct id3_file
*id3_file_fdopen(int fd
, enum id3_file_mode mode
)
452 # if 1 || defined(HAVE_UNISTD_H)
454 struct id3_file
*file
;
456 iofile
= fdopen(fd
, (mode
== ID3_FILE_MODE_READWRITE
) ? "r+b" : "rb");
460 file
= new_file(iofile
, mode
, 0);
464 /* close iofile without closing fd */
481 * NAME: file->close()
482 * DESCRIPTION: close a file and delete its associated tags
484 int id3_file_close(struct id3_file
*file
)
490 if (fclose(file
->iofile
) == EOF
)
500 * DESCRIPTION: return the primary tag structure for a file
502 struct id3_tag
*id3_file_tag(struct id3_file
const *file
)
506 return file
->primary
;
511 * DESCRIPTION: write ID3v1 tag modifications to a file
514 int v1_write(struct id3_file
*file
,
515 id3_byte_t
const *data
, id3_length_t length
)
517 assert(!data
|| length
== 128);
522 if (fseek(file
->iofile
, (file
->flags
& ID3_FILE_FLAG_ID3V1
) ? -128 : 0,
524 (location
= ftell(file
->iofile
)) == -1 ||
525 fwrite(data
, 128, 1, file
->iofile
) != 1 ||
526 fflush(file
->iofile
) == EOF
)
529 /* add file tag reference */
531 if (!(file
->flags
& ID3_FILE_FLAG_ID3V1
)) {
532 struct filetag filetag
;
535 filetag
.location
= location
;
536 filetag
.length
= 128;
538 if (add_filetag(file
, &filetag
) == -1)
541 file
->flags
|= ID3_FILE_FLAG_ID3V1
;
544 # if defined(HAVE_FTRUNCATE)
545 else if (file
->flags
& ID3_FILE_FLAG_ID3V1
) {
548 if (fseek(file
->iofile
, 0, SEEK_END
) == -1)
551 length
= ftell(file
->iofile
);
553 (length
>= 0 && length
< 128))
556 if (ftruncate(fileno(file
->iofile
), length
- 128) == -1)
559 /* delete file tag reference */
561 del_filetag(file
, file
->ntags
- 1);
563 file
->flags
&= ~ID3_FILE_FLAG_ID3V1
;
572 * DESCRIPTION: write ID3v2 tag modifications to a file
575 int v2_write(struct id3_file
*file
,
576 id3_byte_t
const *data
, id3_length_t length
)
578 assert(!data
|| length
> 0);
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 */
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
)
594 /* hard general case: rewrite entire file */
603 * NAME: file->update()
604 * DESCRIPTION: rewrite tag(s) to a file
606 int id3_file_update(struct id3_file
*file
)
608 int options
, result
= 0;
609 id3_length_t v1size
= 0, v2size
= 0;
610 id3_byte_t id3v1_data
[128], *id3v1
= 0, *id3v2
= 0;
614 if (file
->mode
!= ID3_FILE_MODE_READWRITE
)
617 options
= id3_tag_options(file
->primary
, 0, 0);
621 if (options
& ID3_TAG_OPTION_ID3V1
) {
622 v1size
= id3_tag_render(file
->primary
, 0);
624 assert(v1size
== sizeof(id3v1_data
));
626 v1size
= id3_tag_render(file
->primary
, id3v1_data
);
628 assert(v1size
== sizeof(id3v1_data
));
636 id3_tag_options(file
->primary
, ID3_TAG_OPTION_ID3V1
, 0);
638 v2size
= id3_tag_render(file
->primary
, 0);
640 id3v2
= malloc(v2size
);
644 v2size
= id3_tag_render(file
->primary
, id3v2
);
653 if (v2_write(file
, id3v2
, v2size
) == -1 ||
654 v1_write(file
, id3v1
, v1size
) == -1)
657 rewind(file
->iofile
);
659 /* update file tags array? ... */
666 /* clean up; restore tag options */
671 id3_tag_options(file
->primary
, ~0, options
);