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