cannam@85: /* cannam@85: * libid3tag - ID3 tag manipulation library cannam@85: * Copyright (C) 2000-2004 Underbit Technologies, Inc. cannam@85: * cannam@85: * This program is free software; you can redistribute it and/or modify cannam@85: * it under the terms of the GNU General Public License as published by cannam@85: * the Free Software Foundation; either version 2 of the License, or cannam@85: * (at your option) any later version. cannam@85: * cannam@85: * This program is distributed in the hope that it will be useful, cannam@85: * but WITHOUT ANY WARRANTY; without even the implied warranty of cannam@85: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the cannam@85: * GNU General Public License for more details. cannam@85: * cannam@85: * You should have received a copy of the GNU General Public License cannam@85: * along with this program; if not, write to the Free Software cannam@85: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA cannam@85: * cannam@85: * $Id: tag.c,v 1.20 2004/02/17 02:04:10 rob Exp $ cannam@85: */ cannam@85: cannam@85: # ifdef HAVE_CONFIG_H cannam@85: # include "config.h" cannam@85: # endif cannam@85: cannam@85: # include "global.h" cannam@85: cannam@85: # include cannam@85: # include cannam@85: cannam@85: # ifdef HAVE_ASSERT_H cannam@85: # include cannam@85: # endif cannam@85: cannam@85: # include "id3tag.h" cannam@85: # include "tag.h" cannam@85: # include "frame.h" cannam@85: # include "compat.h" cannam@85: # include "parse.h" cannam@85: # include "render.h" cannam@85: # include "latin1.h" cannam@85: # include "ucs4.h" cannam@85: # include "genre.h" cannam@85: # include "crc.h" cannam@85: # include "field.h" cannam@85: # include "util.h" cannam@85: cannam@85: /* cannam@85: * NAME: tag->new() cannam@85: * DESCRIPTION: allocate and return a new, empty tag cannam@85: */ cannam@85: struct id3_tag *id3_tag_new(void) cannam@85: { cannam@85: struct id3_tag *tag; cannam@85: cannam@85: tag = malloc(sizeof(*tag)); cannam@85: if (tag) { cannam@85: tag->refcount = 0; cannam@85: tag->version = ID3_TAG_VERSION; cannam@85: tag->flags = 0; cannam@85: tag->extendedflags = 0; cannam@85: tag->restrictions = 0; cannam@85: tag->options = /* ID3_TAG_OPTION_UNSYNCHRONISATION | */ cannam@85: ID3_TAG_OPTION_COMPRESSION | ID3_TAG_OPTION_CRC; cannam@85: tag->nframes = 0; cannam@85: tag->frames = 0; cannam@85: tag->paddedsize = 0; cannam@85: } cannam@85: cannam@85: return tag; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->delete() cannam@85: * DESCRIPTION: destroy a tag and deallocate all associated memory cannam@85: */ cannam@85: void id3_tag_delete(struct id3_tag *tag) cannam@85: { cannam@85: assert(tag); cannam@85: cannam@85: if (tag->refcount == 0) { cannam@85: id3_tag_clearframes(tag); cannam@85: cannam@85: if (tag->frames) cannam@85: free(tag->frames); cannam@85: cannam@85: free(tag); cannam@85: } cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->addref() cannam@85: * DESCRIPTION: add an external reference to a tag cannam@85: */ cannam@85: void id3_tag_addref(struct id3_tag *tag) cannam@85: { cannam@85: assert(tag); cannam@85: cannam@85: ++tag->refcount; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->delref() cannam@85: * DESCRIPTION: remove an external reference to a tag cannam@85: */ cannam@85: void id3_tag_delref(struct id3_tag *tag) cannam@85: { cannam@85: assert(tag && tag->refcount > 0); cannam@85: cannam@85: --tag->refcount; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->version() cannam@85: * DESCRIPTION: return the tag's original ID3 version number cannam@85: */ cannam@85: unsigned int id3_tag_version(struct id3_tag const *tag) cannam@85: { cannam@85: assert(tag); cannam@85: cannam@85: return tag->version; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->options() cannam@85: * DESCRIPTION: get or set tag options cannam@85: */ cannam@85: int id3_tag_options(struct id3_tag *tag, int mask, int values) cannam@85: { cannam@85: assert(tag); cannam@85: cannam@85: if (mask) cannam@85: tag->options = (tag->options & ~mask) | (values & mask); cannam@85: cannam@85: return tag->options; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->setlength() cannam@85: * DESCRIPTION: set the minimum rendered tag size cannam@85: */ cannam@85: void id3_tag_setlength(struct id3_tag *tag, id3_length_t length) cannam@85: { cannam@85: assert(tag); cannam@85: cannam@85: tag->paddedsize = length; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->clearframes() cannam@85: * DESCRIPTION: detach and delete all frames associated with a tag cannam@85: */ cannam@85: void id3_tag_clearframes(struct id3_tag *tag) cannam@85: { cannam@85: unsigned int i; cannam@85: cannam@85: assert(tag); cannam@85: cannam@85: for (i = 0; i < tag->nframes; ++i) { cannam@85: id3_frame_delref(tag->frames[i]); cannam@85: id3_frame_delete(tag->frames[i]); cannam@85: } cannam@85: cannam@85: tag->nframes = 0; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->attachframe() cannam@85: * DESCRIPTION: attach a frame to a tag cannam@85: */ cannam@85: int id3_tag_attachframe(struct id3_tag *tag, struct id3_frame *frame) cannam@85: { cannam@85: struct id3_frame **frames; cannam@85: cannam@85: assert(tag && frame); cannam@85: cannam@85: frames = realloc(tag->frames, (tag->nframes + 1) * sizeof(*frames)); cannam@85: if (frames == 0) cannam@85: return -1; cannam@85: cannam@85: tag->frames = frames; cannam@85: tag->frames[tag->nframes++] = frame; cannam@85: cannam@85: id3_frame_addref(frame); cannam@85: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->detachframe() cannam@85: * DESCRIPTION: detach (but don't delete) a frame from a tag cannam@85: */ cannam@85: int id3_tag_detachframe(struct id3_tag *tag, struct id3_frame *frame) cannam@85: { cannam@85: unsigned int i; cannam@85: cannam@85: assert(tag && frame); cannam@85: cannam@85: for (i = 0; i < tag->nframes; ++i) { cannam@85: if (tag->frames[i] == frame) cannam@85: break; cannam@85: } cannam@85: cannam@85: if (i == tag->nframes) cannam@85: return -1; cannam@85: cannam@85: --tag->nframes; cannam@85: while (i++ < tag->nframes) cannam@85: tag->frames[i - 1] = tag->frames[i]; cannam@85: cannam@85: id3_frame_delref(frame); cannam@85: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->findframe() cannam@85: * DESCRIPTION: find in a tag the nth (0-based) frame with the given frame ID cannam@85: */ cannam@85: struct id3_frame *id3_tag_findframe(struct id3_tag const *tag, cannam@85: char const *id, unsigned int index) cannam@85: { cannam@85: unsigned int len, i; cannam@85: cannam@85: assert(tag); cannam@85: cannam@85: if (id == 0 || *id == 0) cannam@85: return (index < tag->nframes) ? tag->frames[index] : 0; cannam@85: cannam@85: len = strlen(id); cannam@85: cannam@85: if (len == 4) { cannam@85: struct id3_compat const *compat; cannam@85: cannam@85: compat = id3_compat_lookup(id, len); cannam@85: if (compat && compat->equiv && !compat->translate) { cannam@85: id = compat->equiv; cannam@85: len = strlen(id); cannam@85: } cannam@85: } cannam@85: cannam@85: for (i = 0; i < tag->nframes; ++i) { cannam@85: if (strncmp(tag->frames[i]->id, id, len) == 0 && index-- == 0) cannam@85: return tag->frames[i]; cannam@85: } cannam@85: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: enum tagtype { cannam@85: TAGTYPE_NONE = 0, cannam@85: TAGTYPE_ID3V1, cannam@85: TAGTYPE_ID3V2, cannam@85: TAGTYPE_ID3V2_FOOTER cannam@85: }; cannam@85: cannam@85: static cannam@85: enum tagtype tagtype(id3_byte_t const *data, id3_length_t length) cannam@85: { cannam@85: if (length >= 3 && cannam@85: data[0] == 'T' && data[1] == 'A' && data[2] == 'G') cannam@85: return TAGTYPE_ID3V1; cannam@85: cannam@85: if (length >= 10 && cannam@85: ((data[0] == 'I' && data[1] == 'D' && data[2] == '3') || cannam@85: (data[0] == '3' && data[1] == 'D' && data[2] == 'I')) && cannam@85: data[3] < 0xff && data[4] < 0xff && cannam@85: data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80) cannam@85: return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER; cannam@85: cannam@85: return TAGTYPE_NONE; cannam@85: } cannam@85: cannam@85: static cannam@85: void parse_header(id3_byte_t const **ptr, cannam@85: unsigned int *version, int *flags, id3_length_t *size) cannam@85: { cannam@85: *ptr += 3; cannam@85: cannam@85: *version = id3_parse_uint(ptr, 2); cannam@85: *flags = id3_parse_uint(ptr, 1); cannam@85: *size = id3_parse_syncsafe(ptr, 4); cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->query() cannam@85: * DESCRIPTION: if a tag begins at the given location, return its size cannam@85: */ cannam@85: signed long id3_tag_query(id3_byte_t const *data, id3_length_t length) cannam@85: { cannam@85: unsigned int version; cannam@85: int flags; cannam@85: id3_length_t size; cannam@85: cannam@85: assert(data); cannam@85: cannam@85: switch (tagtype(data, length)) { cannam@85: case TAGTYPE_ID3V1: cannam@85: return 128; cannam@85: cannam@85: case TAGTYPE_ID3V2: cannam@85: parse_header(&data, &version, &flags, &size); cannam@85: cannam@85: if (flags & ID3_TAG_FLAG_FOOTERPRESENT) cannam@85: size += 10; cannam@85: cannam@85: return 10 + size; cannam@85: cannam@85: case TAGTYPE_ID3V2_FOOTER: cannam@85: parse_header(&data, &version, &flags, &size); cannam@85: return -size - 10; cannam@85: cannam@85: case TAGTYPE_NONE: cannam@85: break; cannam@85: } cannam@85: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: static cannam@85: void trim(char *str) cannam@85: { cannam@85: char *ptr; cannam@85: cannam@85: ptr = str + strlen(str); cannam@85: while (ptr > str && ptr[-1] == ' ') cannam@85: --ptr; cannam@85: cannam@85: *ptr = 0; cannam@85: } cannam@85: cannam@85: static cannam@85: int v1_attachstr(struct id3_tag *tag, char const *id, cannam@85: char *text, unsigned long number) cannam@85: { cannam@85: struct id3_frame *frame; cannam@85: id3_ucs4_t ucs4[31]; cannam@85: cannam@85: if (text) { cannam@85: trim(text); cannam@85: if (*text == 0) cannam@85: return 0; cannam@85: } cannam@85: cannam@85: frame = id3_frame_new(id); cannam@85: if (frame == 0) cannam@85: return -1; cannam@85: cannam@85: if (id3_field_settextencoding(&frame->fields[0], cannam@85: ID3_FIELD_TEXTENCODING_ISO_8859_1) == -1) cannam@85: goto fail; cannam@85: cannam@85: if (text) cannam@85: id3_latin1_decode(text, ucs4); cannam@85: else cannam@85: id3_ucs4_putnumber(ucs4, number); cannam@85: cannam@85: if (strcmp(id, ID3_FRAME_COMMENT) == 0) { cannam@85: if (id3_field_setlanguage(&frame->fields[1], "XXX") == -1 || cannam@85: id3_field_setstring(&frame->fields[2], id3_ucs4_empty) == -1 || cannam@85: id3_field_setfullstring(&frame->fields[3], ucs4) == -1) cannam@85: goto fail; cannam@85: } cannam@85: else { cannam@85: id3_ucs4_t *ptr = ucs4; cannam@85: cannam@85: if (id3_field_setstrings(&frame->fields[1], 1, &ptr) == -1) cannam@85: goto fail; cannam@85: } cannam@85: cannam@85: if (id3_tag_attachframe(tag, frame) == -1) cannam@85: goto fail; cannam@85: cannam@85: return 0; cannam@85: cannam@85: fail: cannam@85: id3_frame_delete(frame); cannam@85: return -1; cannam@85: } cannam@85: cannam@85: static cannam@85: struct id3_tag *v1_parse(id3_byte_t const *data) cannam@85: { cannam@85: struct id3_tag *tag; cannam@85: cannam@85: tag = id3_tag_new(); cannam@85: if (tag) { cannam@85: char title[31], artist[31], album[31], year[5], comment[31]; cannam@85: unsigned int genre, track; cannam@85: cannam@85: tag->version = 0x0100; cannam@85: cannam@85: tag->options |= ID3_TAG_OPTION_ID3V1; cannam@85: tag->options &= ~ID3_TAG_OPTION_COMPRESSION; cannam@85: cannam@85: tag->restrictions = cannam@85: ID3_TAG_RESTRICTION_TEXTENCODING_LATIN1_UTF8 | cannam@85: ID3_TAG_RESTRICTION_TEXTSIZE_30_CHARS; cannam@85: cannam@85: title[30] = artist[30] = album[30] = year[4] = comment[30] = 0; cannam@85: cannam@85: memcpy(title, &data[3], 30); cannam@85: memcpy(artist, &data[33], 30); cannam@85: memcpy(album, &data[63], 30); cannam@85: memcpy(year, &data[93], 4); cannam@85: memcpy(comment, &data[97], 30); cannam@85: cannam@85: genre = data[127]; cannam@85: cannam@85: track = 0; cannam@85: if (comment[28] == 0 && comment[29] != 0) { cannam@85: track = comment[29]; cannam@85: tag->version = 0x0101; cannam@85: } cannam@85: cannam@85: /* populate tag frames */ cannam@85: cannam@85: if (v1_attachstr(tag, ID3_FRAME_TITLE, title, 0) == -1 || cannam@85: v1_attachstr(tag, ID3_FRAME_ARTIST, artist, 0) == -1 || cannam@85: v1_attachstr(tag, ID3_FRAME_ALBUM, album, 0) == -1 || cannam@85: v1_attachstr(tag, ID3_FRAME_YEAR, year, 0) == -1 || cannam@85: (track && v1_attachstr(tag, ID3_FRAME_TRACK, 0, track) == -1) || cannam@85: (genre < 0xff && v1_attachstr(tag, ID3_FRAME_GENRE, 0, genre) == -1) || cannam@85: v1_attachstr(tag, ID3_FRAME_COMMENT, comment, 0) == -1) { cannam@85: id3_tag_delete(tag); cannam@85: tag = 0; cannam@85: } cannam@85: } cannam@85: cannam@85: return tag; cannam@85: } cannam@85: cannam@85: static cannam@85: struct id3_tag *v2_parse(id3_byte_t const *ptr) cannam@85: { cannam@85: struct id3_tag *tag; cannam@85: id3_byte_t *mem = 0; cannam@85: cannam@85: tag = id3_tag_new(); cannam@85: if (tag) { cannam@85: id3_byte_t const *end; cannam@85: id3_length_t size; cannam@85: cannam@85: parse_header(&ptr, &tag->version, &tag->flags, &size); cannam@85: cannam@85: tag->paddedsize = 10 + size; cannam@85: cannam@85: if ((tag->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) && cannam@85: ID3_TAG_VERSION_MAJOR(tag->version) < 4) { cannam@85: mem = malloc(size); cannam@85: if (mem == 0) cannam@85: goto fail; cannam@85: cannam@85: memcpy(mem, ptr, size); cannam@85: cannam@85: size = id3_util_deunsynchronise(mem, size); cannam@85: ptr = mem; cannam@85: } cannam@85: cannam@85: end = ptr + size; cannam@85: cannam@85: if (tag->flags & ID3_TAG_FLAG_EXTENDEDHEADER) { cannam@85: switch (ID3_TAG_VERSION_MAJOR(tag->version)) { cannam@85: case 2: cannam@85: goto fail; cannam@85: cannam@85: case 3: cannam@85: { cannam@85: id3_byte_t const *ehptr, *ehend; cannam@85: id3_length_t ehsize; cannam@85: cannam@85: enum { cannam@85: EH_FLAG_CRC = 0x8000 /* CRC data present */ cannam@85: }; cannam@85: cannam@85: if (end - ptr < 4) cannam@85: goto fail; cannam@85: cannam@85: ehsize = id3_parse_uint(&ptr, 4); cannam@85: cannam@85: if (ehsize > end - ptr) cannam@85: goto fail; cannam@85: cannam@85: ehptr = ptr; cannam@85: ehend = ptr + ehsize; cannam@85: cannam@85: ptr = ehend; cannam@85: cannam@85: if (ehend - ehptr >= 6) { cannam@85: int ehflags; cannam@85: id3_length_t padsize; cannam@85: cannam@85: ehflags = id3_parse_uint(&ehptr, 2); cannam@85: padsize = id3_parse_uint(&ehptr, 4); cannam@85: cannam@85: if (padsize > end - ptr) cannam@85: goto fail; cannam@85: cannam@85: end -= padsize; cannam@85: cannam@85: if (ehflags & EH_FLAG_CRC) { cannam@85: unsigned long crc; cannam@85: cannam@85: if (ehend - ehptr < 4) cannam@85: goto fail; cannam@85: cannam@85: crc = id3_parse_uint(&ehptr, 4); cannam@85: cannam@85: if (crc != id3_crc_compute(ptr, end - ptr)) cannam@85: goto fail; cannam@85: cannam@85: tag->extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT; cannam@85: } cannam@85: } cannam@85: } cannam@85: break; cannam@85: cannam@85: case 4: cannam@85: { cannam@85: id3_byte_t const *ehptr, *ehend; cannam@85: id3_length_t ehsize; cannam@85: unsigned int bytes; cannam@85: cannam@85: if (end - ptr < 4) cannam@85: goto fail; cannam@85: cannam@85: ehptr = ptr; cannam@85: ehsize = id3_parse_syncsafe(&ptr, 4); cannam@85: cannam@85: if (ehsize < 6 || ehsize > end - ehptr) cannam@85: goto fail; cannam@85: cannam@85: ehend = ehptr + ehsize; cannam@85: cannam@85: bytes = id3_parse_uint(&ptr, 1); cannam@85: cannam@85: if (bytes < 1 || bytes > ehend - ptr) cannam@85: goto fail; cannam@85: cannam@85: ehptr = ptr + bytes; cannam@85: cannam@85: /* verify extended header size */ cannam@85: { cannam@85: id3_byte_t const *flagsptr = ptr, *dataptr = ehptr; cannam@85: unsigned int datalen; cannam@85: int ehflags; cannam@85: cannam@85: while (bytes--) { cannam@85: for (ehflags = id3_parse_uint(&flagsptr, 1); ehflags; cannam@85: ehflags = (ehflags << 1) & 0xff) { cannam@85: if (ehflags & 0x80) { cannam@85: if (dataptr == ehend) cannam@85: goto fail; cannam@85: datalen = id3_parse_uint(&dataptr, 1); cannam@85: if (datalen > 0x7f || datalen > ehend - dataptr) cannam@85: goto fail; cannam@85: dataptr += datalen; cannam@85: } cannam@85: } cannam@85: } cannam@85: } cannam@85: cannam@85: tag->extendedflags = id3_parse_uint(&ptr, 1); cannam@85: cannam@85: ptr = ehend; cannam@85: cannam@85: if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) { cannam@85: bytes = id3_parse_uint(&ehptr, 1); cannam@85: ehptr += bytes; cannam@85: } cannam@85: cannam@85: if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) { cannam@85: unsigned long crc; cannam@85: cannam@85: bytes = id3_parse_uint(&ehptr, 1); cannam@85: if (bytes < 5) cannam@85: goto fail; cannam@85: cannam@85: crc = id3_parse_syncsafe(&ehptr, 5); cannam@85: ehptr += bytes - 5; cannam@85: cannam@85: if (crc != id3_crc_compute(ptr, end - ptr)) cannam@85: goto fail; cannam@85: } cannam@85: cannam@85: if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) { cannam@85: bytes = id3_parse_uint(&ehptr, 1); cannam@85: if (bytes < 1) cannam@85: goto fail; cannam@85: cannam@85: tag->restrictions = id3_parse_uint(&ehptr, 1); cannam@85: ehptr += bytes - 1; cannam@85: } cannam@85: } cannam@85: break; cannam@85: } cannam@85: } cannam@85: cannam@85: /* frames */ cannam@85: cannam@85: while (ptr < end) { cannam@85: struct id3_frame *frame; cannam@85: cannam@85: if (*ptr == 0) cannam@85: break; /* padding */ cannam@85: cannam@85: frame = id3_frame_parse(&ptr, end - ptr, tag->version); cannam@85: if (frame == 0 || id3_tag_attachframe(tag, frame) == -1) cannam@85: goto fail; cannam@85: } cannam@85: cannam@85: if (ID3_TAG_VERSION_MAJOR(tag->version) < 4 && cannam@85: id3_compat_fixup(tag) == -1) cannam@85: goto fail; cannam@85: } cannam@85: cannam@85: if (0) { cannam@85: fail: cannam@85: id3_tag_delete(tag); cannam@85: tag = 0; cannam@85: } cannam@85: cannam@85: if (mem) cannam@85: free(mem); cannam@85: cannam@85: return tag; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->parse() cannam@85: * DESCRIPTION: parse a complete ID3 tag cannam@85: */ cannam@85: struct id3_tag *id3_tag_parse(id3_byte_t const *data, id3_length_t length) cannam@85: { cannam@85: id3_byte_t const *ptr; cannam@85: unsigned int version; cannam@85: int flags; cannam@85: id3_length_t size; cannam@85: cannam@85: assert(data); cannam@85: cannam@85: switch (tagtype(data, length)) { cannam@85: case TAGTYPE_ID3V1: cannam@85: return (length < 128) ? 0 : v1_parse(data); cannam@85: cannam@85: case TAGTYPE_ID3V2: cannam@85: break; cannam@85: cannam@85: case TAGTYPE_ID3V2_FOOTER: cannam@85: case TAGTYPE_NONE: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: /* ID3v2.x */ cannam@85: cannam@85: ptr = data; cannam@85: parse_header(&ptr, &version, &flags, &size); cannam@85: cannam@85: switch (ID3_TAG_VERSION_MAJOR(version)) { cannam@85: case 4: cannam@85: if (flags & ID3_TAG_FLAG_FOOTERPRESENT) cannam@85: size += 10; cannam@85: case 2: cannam@85: case 3: cannam@85: return (length < 10 + size) ? 0 : v2_parse(data); cannam@85: } cannam@85: cannam@85: return 0; cannam@85: } cannam@85: cannam@85: static cannam@85: void v1_renderstr(struct id3_tag const *tag, char const *frameid, cannam@85: id3_byte_t **buffer, id3_length_t length) cannam@85: { cannam@85: struct id3_frame *frame; cannam@85: id3_ucs4_t const *string; cannam@85: cannam@85: frame = id3_tag_findframe(tag, frameid, 0); cannam@85: if (frame == 0) cannam@85: string = id3_ucs4_empty; cannam@85: else { cannam@85: if (strcmp(frameid, ID3_FRAME_COMMENT) == 0) cannam@85: string = id3_field_getfullstring(&frame->fields[3]); cannam@85: else cannam@85: string = id3_field_getstrings(&frame->fields[1], 0); cannam@85: } cannam@85: cannam@85: id3_render_paddedstring(buffer, string, length); cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: v1->render() cannam@85: * DESCRIPTION: render an ID3v1 (or ID3v1.1) tag cannam@85: */ cannam@85: static cannam@85: id3_length_t v1_render(struct id3_tag const *tag, id3_byte_t *buffer) cannam@85: { cannam@85: id3_byte_t data[128], *ptr; cannam@85: struct id3_frame *frame; cannam@85: unsigned int i; cannam@85: int genre = -1; cannam@85: cannam@85: ptr = data; cannam@85: cannam@85: id3_render_immediate(&ptr, "TAG", 3); cannam@85: cannam@85: v1_renderstr(tag, ID3_FRAME_TITLE, &ptr, 30); cannam@85: v1_renderstr(tag, ID3_FRAME_ARTIST, &ptr, 30); cannam@85: v1_renderstr(tag, ID3_FRAME_ALBUM, &ptr, 30); cannam@85: v1_renderstr(tag, ID3_FRAME_YEAR, &ptr, 4); cannam@85: v1_renderstr(tag, ID3_FRAME_COMMENT, &ptr, 30); cannam@85: cannam@85: /* ID3v1.1 track number */ cannam@85: cannam@85: frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0); cannam@85: if (frame) { cannam@85: unsigned int track; cannam@85: cannam@85: track = id3_ucs4_getnumber(id3_field_getstrings(&frame->fields[1], 0)); cannam@85: if (track > 0 && track <= 0xff) { cannam@85: ptr[-2] = 0; cannam@85: ptr[-1] = track; cannam@85: } cannam@85: } cannam@85: cannam@85: /* ID3v1 genre number */ cannam@85: cannam@85: frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0); cannam@85: if (frame) { cannam@85: unsigned int nstrings; cannam@85: cannam@85: nstrings = id3_field_getnstrings(&frame->fields[1]); cannam@85: cannam@85: for (i = 0; i < nstrings; ++i) { cannam@85: genre = id3_genre_number(id3_field_getstrings(&frame->fields[1], i)); cannam@85: if (genre != -1) cannam@85: break; cannam@85: } cannam@85: cannam@85: if (i == nstrings && nstrings > 0) cannam@85: genre = ID3_GENRE_OTHER; cannam@85: } cannam@85: cannam@85: id3_render_int(&ptr, genre, 1); cannam@85: cannam@85: /* make sure the tag is not empty */ cannam@85: cannam@85: if (genre == -1) { cannam@85: for (i = 3; i < 127; ++i) { cannam@85: if (data[i] != ' ') cannam@85: break; cannam@85: } cannam@85: cannam@85: if (i == 127) cannam@85: return 0; cannam@85: } cannam@85: cannam@85: if (buffer) cannam@85: memcpy(buffer, data, 128); cannam@85: cannam@85: return 128; cannam@85: } cannam@85: cannam@85: /* cannam@85: * NAME: tag->render() cannam@85: * DESCRIPTION: render a complete ID3 tag cannam@85: */ cannam@85: id3_length_t id3_tag_render(struct id3_tag const *tag, id3_byte_t *buffer) cannam@85: { cannam@85: id3_length_t size = 0; cannam@85: id3_byte_t **ptr, cannam@85: *header_ptr = 0, *tagsize_ptr = 0, *crc_ptr = 0, *frames_ptr = 0; cannam@85: int flags, extendedflags; cannam@85: unsigned int i; cannam@85: cannam@85: assert(tag); cannam@85: cannam@85: if (tag->options & ID3_TAG_OPTION_ID3V1) cannam@85: return v1_render(tag, buffer); cannam@85: cannam@85: /* a tag must contain at least one (renderable) frame */ cannam@85: cannam@85: for (i = 0; i < tag->nframes; ++i) { cannam@85: if (id3_frame_render(tag->frames[i], 0, 0) > 0) cannam@85: break; cannam@85: } cannam@85: cannam@85: if (i == tag->nframes) cannam@85: return 0; cannam@85: cannam@85: ptr = buffer ? &buffer : 0; cannam@85: cannam@85: /* get flags */ cannam@85: cannam@85: flags = tag->flags & ID3_TAG_FLAG_KNOWNFLAGS; cannam@85: extendedflags = tag->extendedflags & ID3_TAG_EXTENDEDFLAG_KNOWNFLAGS; cannam@85: cannam@85: extendedflags &= ~ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT; cannam@85: if (tag->options & ID3_TAG_OPTION_CRC) cannam@85: extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT; cannam@85: cannam@85: extendedflags &= ~ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS; cannam@85: if (tag->restrictions) cannam@85: extendedflags |= ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS; cannam@85: cannam@85: flags &= ~ID3_TAG_FLAG_UNSYNCHRONISATION; cannam@85: if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) cannam@85: flags |= ID3_TAG_FLAG_UNSYNCHRONISATION; cannam@85: cannam@85: flags &= ~ID3_TAG_FLAG_EXTENDEDHEADER; cannam@85: if (extendedflags) cannam@85: flags |= ID3_TAG_FLAG_EXTENDEDHEADER; cannam@85: cannam@85: flags &= ~ID3_TAG_FLAG_FOOTERPRESENT; cannam@85: if (tag->options & ID3_TAG_OPTION_APPENDEDTAG) cannam@85: flags |= ID3_TAG_FLAG_FOOTERPRESENT; cannam@85: cannam@85: /* header */ cannam@85: cannam@85: if (ptr) cannam@85: header_ptr = *ptr; cannam@85: cannam@85: size += id3_render_immediate(ptr, "ID3", 3); cannam@85: size += id3_render_int(ptr, ID3_TAG_VERSION, 2); cannam@85: size += id3_render_int(ptr, flags, 1); cannam@85: cannam@85: if (ptr) cannam@85: tagsize_ptr = *ptr; cannam@85: cannam@85: size += id3_render_syncsafe(ptr, 0, 4); cannam@85: cannam@85: /* extended header */ cannam@85: cannam@85: if (flags & ID3_TAG_FLAG_EXTENDEDHEADER) { cannam@85: id3_length_t ehsize = 0; cannam@85: id3_byte_t *ehsize_ptr = 0; cannam@85: cannam@85: if (ptr) cannam@85: ehsize_ptr = *ptr; cannam@85: cannam@85: ehsize += id3_render_syncsafe(ptr, 0, 4); cannam@85: ehsize += id3_render_int(ptr, 1, 1); cannam@85: ehsize += id3_render_int(ptr, extendedflags, 1); cannam@85: cannam@85: if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) cannam@85: ehsize += id3_render_int(ptr, 0, 1); cannam@85: cannam@85: if (extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) { cannam@85: ehsize += id3_render_int(ptr, 5, 1); cannam@85: cannam@85: if (ptr) cannam@85: crc_ptr = *ptr; cannam@85: cannam@85: ehsize += id3_render_syncsafe(ptr, 0, 5); cannam@85: } cannam@85: cannam@85: if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) { cannam@85: ehsize += id3_render_int(ptr, 1, 1); cannam@85: ehsize += id3_render_int(ptr, tag->restrictions, 1); cannam@85: } cannam@85: cannam@85: if (ehsize_ptr) cannam@85: id3_render_syncsafe(&ehsize_ptr, ehsize, 4); cannam@85: cannam@85: size += ehsize; cannam@85: } cannam@85: cannam@85: /* frames */ cannam@85: cannam@85: if (ptr) cannam@85: frames_ptr = *ptr; cannam@85: cannam@85: for (i = 0; i < tag->nframes; ++i) cannam@85: size += id3_frame_render(tag->frames[i], ptr, tag->options); cannam@85: cannam@85: /* padding */ cannam@85: cannam@85: if (!(flags & ID3_TAG_FLAG_FOOTERPRESENT)) { cannam@85: if (size < tag->paddedsize) cannam@85: size += id3_render_padding(ptr, 0, tag->paddedsize - size); cannam@85: else if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) { cannam@85: if (ptr == 0) cannam@85: size += 1; cannam@85: else { cannam@85: if ((*ptr)[-1] == 0xff) cannam@85: size += id3_render_padding(ptr, 0, 1); cannam@85: } cannam@85: } cannam@85: } cannam@85: cannam@85: /* patch tag size and CRC */ cannam@85: cannam@85: if (tagsize_ptr) cannam@85: id3_render_syncsafe(&tagsize_ptr, size - 10, 4); cannam@85: cannam@85: if (crc_ptr) { cannam@85: id3_render_syncsafe(&crc_ptr, cannam@85: id3_crc_compute(frames_ptr, *ptr - frames_ptr), 5); cannam@85: } cannam@85: cannam@85: /* footer */ cannam@85: cannam@85: if (flags & ID3_TAG_FLAG_FOOTERPRESENT) { cannam@85: size += id3_render_immediate(ptr, "3DI", 3); cannam@85: size += id3_render_binary(ptr, header_ptr + 3, 7); cannam@85: } cannam@85: cannam@85: return size; cannam@85: }