view src/libid3tag-0.15.1b/tag.c @ 83:ae30d91d2ffe

Replace these with versions built using an older toolset (so as to avoid ABI compatibilities when linking on Ubuntu 14.04 for packaging purposes)
author Chris Cannam
date Fri, 07 Feb 2020 11:51:13 +0000
parents c7265573341e
children
line wrap: on
line source
/*
 * libid3tag - ID3 tag manipulation library
 * Copyright (C) 2000-2004 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.20 2004/02/17 02:04:10 rob 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_compute(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_compute(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_compute(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;
}