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: tag.c,v 1.20 2004/02/17 02:04:10 rob Exp $
20 */
21
22# ifdef HAVE_CONFIG_H
23#  include "config.h"
24# endif
25
26# include "global.h"
27
28# include <string.h>
29# include <stdlib.h>
30
31# ifdef HAVE_ASSERT_H
32#  include <assert.h>
33# endif
34
35# include "id3tag.h"
36# include "tag.h"
37# include "frame.h"
38# include "compat.h"
39# include "parse.h"
40# include "render.h"
41# include "latin1.h"
42# include "ucs4.h"
43# include "genre.h"
44# include "crc.h"
45# include "field.h"
46# include "util.h"
47
48/*
49 * NAME:	tag->new()
50 * DESCRIPTION:	allocate and return a new, empty tag
51 */
52struct id3_tag *id3_tag_new(void)
53{
54  struct id3_tag *tag;
55
56  tag = malloc(sizeof(*tag));
57  if (tag) {
58    tag->refcount      = 0;
59    tag->version       = ID3_TAG_VERSION;
60    tag->flags         = 0;
61    tag->extendedflags = 0;
62    tag->restrictions  = 0;
63    tag->options       = /* ID3_TAG_OPTION_UNSYNCHRONISATION | */
64                         ID3_TAG_OPTION_COMPRESSION | ID3_TAG_OPTION_CRC;
65    tag->nframes       = 0;
66    tag->frames        = 0;
67    tag->paddedsize    = 0;
68  }
69
70  return tag;
71}
72
73/*
74 * NAME:	tag->delete()
75 * DESCRIPTION:	destroy a tag and deallocate all associated memory
76 */
77void id3_tag_delete(struct id3_tag *tag)
78{
79  assert(tag);
80
81  if (tag->refcount == 0) {
82    id3_tag_clearframes(tag);
83
84    if (tag->frames)
85      free(tag->frames);
86
87    free(tag);
88  }
89}
90
91/*
92 * NAME:	tag->addref()
93 * DESCRIPTION:	add an external reference to a tag
94 */
95void id3_tag_addref(struct id3_tag *tag)
96{
97  assert(tag);
98
99  ++tag->refcount;
100}
101
102/*
103 * NAME:	tag->delref()
104 * DESCRIPTION:	remove an external reference to a tag
105 */
106void id3_tag_delref(struct id3_tag *tag)
107{
108  assert(tag && tag->refcount > 0);
109
110  --tag->refcount;
111}
112
113/*
114 * NAME:	tag->version()
115 * DESCRIPTION:	return the tag's original ID3 version number
116 */
117unsigned int id3_tag_version(struct id3_tag const *tag)
118{
119  assert(tag);
120
121  return tag->version;
122}
123
124/*
125 * NAME:	tag->options()
126 * DESCRIPTION:	get or set tag options
127 */
128int id3_tag_options(struct id3_tag *tag, int mask, int values)
129{
130  assert(tag);
131
132  if (mask)
133    tag->options = (tag->options & ~mask) | (values & mask);
134
135  return tag->options;
136}
137
138/*
139 * NAME:	tag->setlength()
140 * DESCRIPTION:	set the minimum rendered tag size
141 */
142void id3_tag_setlength(struct id3_tag *tag, id3_length_t length)
143{
144  assert(tag);
145
146  tag->paddedsize = length;
147}
148
149/*
150 * NAME:	tag->clearframes()
151 * DESCRIPTION:	detach and delete all frames associated with a tag
152 */
153void id3_tag_clearframes(struct id3_tag *tag)
154{
155  unsigned int i;
156
157  assert(tag);
158
159  for (i = 0; i < tag->nframes; ++i) {
160    id3_frame_delref(tag->frames[i]);
161    id3_frame_delete(tag->frames[i]);
162  }
163
164  tag->nframes = 0;
165}
166
167/*
168 * NAME:	tag->attachframe()
169 * DESCRIPTION:	attach a frame to a tag
170 */
171int id3_tag_attachframe(struct id3_tag *tag, struct id3_frame *frame)
172{
173  struct id3_frame **frames;
174
175  assert(tag && frame);
176
177  frames = realloc(tag->frames, (tag->nframes + 1) * sizeof(*frames));
178  if (frames == 0)
179    return -1;
180
181  tag->frames = frames;
182  tag->frames[tag->nframes++] = frame;
183
184  id3_frame_addref(frame);
185
186  return 0;
187}
188
189/*
190 * NAME:	tag->detachframe()
191 * DESCRIPTION:	detach (but don't delete) a frame from a tag
192 */
193int id3_tag_detachframe(struct id3_tag *tag, struct id3_frame *frame)
194{
195  unsigned int i;
196
197  assert(tag && frame);
198
199  for (i = 0; i < tag->nframes; ++i) {
200    if (tag->frames[i] == frame)
201      break;
202  }
203
204  if (i == tag->nframes)
205    return -1;
206
207  --tag->nframes;
208  while (i++ < tag->nframes)
209    tag->frames[i - 1] = tag->frames[i];
210
211  id3_frame_delref(frame);
212
213  return 0;
214}
215
216/*
217 * NAME:	tag->findframe()
218 * DESCRIPTION:	find in a tag the nth (0-based) frame with the given frame ID
219 */
220struct id3_frame *id3_tag_findframe(struct id3_tag const *tag,
221				    char const *id, unsigned int index)
222{
223  unsigned int len, i;
224
225  assert(tag);
226
227  if (id == 0 || *id == 0)
228    return (index < tag->nframes) ? tag->frames[index] : 0;
229
230  len = strlen(id);
231
232  if (len == 4) {
233    struct id3_compat const *compat;
234
235    compat = id3_compat_lookup(id, len);
236    if (compat && compat->equiv && !compat->translate) {
237      id  = compat->equiv;
238      len = strlen(id);
239    }
240  }
241
242  for (i = 0; i < tag->nframes; ++i) {
243    if (strncmp(tag->frames[i]->id, id, len) == 0 && index-- == 0)
244      return tag->frames[i];
245  }
246
247  return 0;
248}
249
250enum tagtype {
251  TAGTYPE_NONE = 0,
252  TAGTYPE_ID3V1,
253  TAGTYPE_ID3V2,
254  TAGTYPE_ID3V2_FOOTER
255};
256
257static
258enum tagtype tagtype(id3_byte_t const *data, id3_length_t length)
259{
260  if (length >= 3 &&
261      data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
262    return TAGTYPE_ID3V1;
263
264  if (length >= 10 &&
265      ((data[0] == 'I' && data[1] == 'D' && data[2] == '3') ||
266       (data[0] == '3' && data[1] == 'D' && data[2] == 'I')) &&
267      data[3] < 0xff && data[4] < 0xff &&
268      data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
269    return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER;
270
271  return TAGTYPE_NONE;
272}
273
274static
275void parse_header(id3_byte_t const **ptr,
276		  unsigned int *version, int *flags, id3_length_t *size)
277{
278  *ptr += 3;
279
280  *version = id3_parse_uint(ptr, 2);
281  *flags   = id3_parse_uint(ptr, 1);
282  *size    = id3_parse_syncsafe(ptr, 4);
283}
284
285/*
286 * NAME:	tag->query()
287 * DESCRIPTION:	if a tag begins at the given location, return its size
288 */
289signed long id3_tag_query(id3_byte_t const *data, id3_length_t length)
290{
291  unsigned int version;
292  int flags;
293  id3_length_t size;
294
295  assert(data);
296
297  switch (tagtype(data, length)) {
298  case TAGTYPE_ID3V1:
299    return 128;
300
301  case TAGTYPE_ID3V2:
302    parse_header(&data, &version, &flags, &size);
303
304    if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
305      size += 10;
306
307    return 10 + size;
308
309  case TAGTYPE_ID3V2_FOOTER:
310    parse_header(&data, &version, &flags, &size);
311    return -size - 10;
312
313  case TAGTYPE_NONE:
314    break;
315  }
316
317  return 0;
318}
319
320static
321void trim(char *str)
322{
323  char *ptr;
324
325  ptr = str + strlen(str);
326  while (ptr > str && ptr[-1] == ' ')
327    --ptr;
328
329  *ptr = 0;
330}
331
332static
333int v1_attachstr(struct id3_tag *tag, char const *id,
334		 char *text, unsigned long number)
335{
336  struct id3_frame *frame;
337  id3_ucs4_t ucs4[31];
338
339  if (text) {
340    trim(text);
341    if (*text == 0)
342      return 0;
343  }
344
345  frame = id3_frame_new(id);
346  if (frame == 0)
347    return -1;
348
349  if (id3_field_settextencoding(&frame->fields[0],
350				ID3_FIELD_TEXTENCODING_ISO_8859_1) == -1)
351    goto fail;
352
353  if (text)
354    id3_latin1_decode(text, ucs4);
355  else
356    id3_ucs4_putnumber(ucs4, number);
357
358  if (strcmp(id, ID3_FRAME_COMMENT) == 0) {
359    if (id3_field_setlanguage(&frame->fields[1], "XXX") == -1 ||
360	id3_field_setstring(&frame->fields[2], id3_ucs4_empty) == -1 ||
361	id3_field_setfullstring(&frame->fields[3], ucs4) == -1)
362      goto fail;
363  }
364  else {
365    id3_ucs4_t *ptr = ucs4;
366
367    if (id3_field_setstrings(&frame->fields[1], 1, &ptr) == -1)
368      goto fail;
369  }
370
371  if (id3_tag_attachframe(tag, frame) == -1)
372    goto fail;
373
374  return 0;
375
376 fail:
377  id3_frame_delete(frame);
378  return -1;
379}
380
381static
382struct id3_tag *v1_parse(id3_byte_t const *data)
383{
384  struct id3_tag *tag;
385
386  tag = id3_tag_new();
387  if (tag) {
388    char title[31], artist[31], album[31], year[5], comment[31];
389    unsigned int genre, track;
390
391    tag->version = 0x0100;
392
393    tag->options |=  ID3_TAG_OPTION_ID3V1;
394    tag->options &= ~ID3_TAG_OPTION_COMPRESSION;
395
396    tag->restrictions =
397      ID3_TAG_RESTRICTION_TEXTENCODING_LATIN1_UTF8 |
398      ID3_TAG_RESTRICTION_TEXTSIZE_30_CHARS;
399
400    title[30] = artist[30] = album[30] = year[4] = comment[30] = 0;
401
402    memcpy(title,   &data[3],  30);
403    memcpy(artist,  &data[33], 30);
404    memcpy(album,   &data[63], 30);
405    memcpy(year,    &data[93],  4);
406    memcpy(comment, &data[97], 30);
407
408    genre = data[127];
409
410    track = 0;
411    if (comment[28] == 0 && comment[29] != 0) {
412      track = comment[29];
413      tag->version = 0x0101;
414    }
415
416    /* populate tag frames */
417
418    if (v1_attachstr(tag, ID3_FRAME_TITLE,  title,  0) == -1 ||
419	v1_attachstr(tag, ID3_FRAME_ARTIST, artist, 0) == -1 ||
420	v1_attachstr(tag, ID3_FRAME_ALBUM,  album,  0) == -1 ||
421	v1_attachstr(tag, ID3_FRAME_YEAR,   year,   0) == -1 ||
422	(track        && v1_attachstr(tag, ID3_FRAME_TRACK, 0, track) == -1) ||
423	(genre < 0xff && v1_attachstr(tag, ID3_FRAME_GENRE, 0, genre) == -1) ||
424	v1_attachstr(tag, ID3_FRAME_COMMENT, comment, 0) == -1) {
425      id3_tag_delete(tag);
426      tag = 0;
427    }
428  }
429
430  return tag;
431}
432
433static
434struct id3_tag *v2_parse(id3_byte_t const *ptr)
435{
436  struct id3_tag *tag;
437  id3_byte_t *mem = 0;
438
439  tag = id3_tag_new();
440  if (tag) {
441    id3_byte_t const *end;
442    id3_length_t size;
443
444    parse_header(&ptr, &tag->version, &tag->flags, &size);
445
446    tag->paddedsize = 10 + size;
447
448    if ((tag->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) &&
449	ID3_TAG_VERSION_MAJOR(tag->version) < 4) {
450      mem = malloc(size);
451      if (mem == 0)
452	goto fail;
453
454      memcpy(mem, ptr, size);
455
456      size = id3_util_deunsynchronise(mem, size);
457      ptr  = mem;
458    }
459
460    end = ptr + size;
461
462    if (tag->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
463      switch (ID3_TAG_VERSION_MAJOR(tag->version)) {
464      case 2:
465	goto fail;
466
467      case 3:
468	{
469	  id3_byte_t const *ehptr, *ehend;
470	  id3_length_t ehsize;
471
472	  enum {
473	    EH_FLAG_CRC = 0x8000  /* CRC data present */
474	  };
475
476	  if (end - ptr < 4)
477	    goto fail;
478
479	  ehsize = id3_parse_uint(&ptr, 4);
480
481	  if (ehsize > end - ptr)
482	    goto fail;
483
484	  ehptr = ptr;
485	  ehend = ptr + ehsize;
486
487	  ptr = ehend;
488
489	  if (ehend - ehptr >= 6) {
490	    int ehflags;
491	    id3_length_t padsize;
492
493	    ehflags = id3_parse_uint(&ehptr, 2);
494	    padsize = id3_parse_uint(&ehptr, 4);
495
496	    if (padsize > end - ptr)
497	      goto fail;
498
499	    end -= padsize;
500
501	    if (ehflags & EH_FLAG_CRC) {
502	      unsigned long crc;
503
504	      if (ehend - ehptr < 4)
505		goto fail;
506
507	      crc = id3_parse_uint(&ehptr, 4);
508
509	      if (crc != id3_crc_compute(ptr, end - ptr))
510		goto fail;
511
512	      tag->extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
513	    }
514	  }
515	}
516	break;
517
518      case 4:
519	{
520	  id3_byte_t const *ehptr, *ehend;
521	  id3_length_t ehsize;
522	  unsigned int bytes;
523
524	  if (end - ptr < 4)
525	    goto fail;
526
527	  ehptr  = ptr;
528	  ehsize = id3_parse_syncsafe(&ptr, 4);
529
530	  if (ehsize < 6 || ehsize > end - ehptr)
531	    goto fail;
532
533	  ehend = ehptr + ehsize;
534
535	  bytes = id3_parse_uint(&ptr, 1);
536
537	  if (bytes < 1 || bytes > ehend - ptr)
538	    goto fail;
539
540	  ehptr = ptr + bytes;
541
542	  /* verify extended header size */
543	  {
544	    id3_byte_t const *flagsptr = ptr, *dataptr = ehptr;
545	    unsigned int datalen;
546	    int ehflags;
547
548	    while (bytes--) {
549	      for (ehflags = id3_parse_uint(&flagsptr, 1); ehflags;
550		   ehflags = (ehflags << 1) & 0xff) {
551		if (ehflags & 0x80) {
552		  if (dataptr == ehend)
553		    goto fail;
554		  datalen = id3_parse_uint(&dataptr, 1);
555		  if (datalen > 0x7f || datalen > ehend - dataptr)
556		    goto fail;
557		  dataptr += datalen;
558		}
559	      }
560	    }
561	  }
562
563	  tag->extendedflags = id3_parse_uint(&ptr, 1);
564
565	  ptr = ehend;
566
567	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) {
568	    bytes  = id3_parse_uint(&ehptr, 1);
569	    ehptr += bytes;
570	  }
571
572	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
573	    unsigned long crc;
574
575	    bytes = id3_parse_uint(&ehptr, 1);
576	    if (bytes < 5)
577	      goto fail;
578
579	    crc = id3_parse_syncsafe(&ehptr, 5);
580	    ehptr += bytes - 5;
581
582	    if (crc != id3_crc_compute(ptr, end - ptr))
583	      goto fail;
584	  }
585
586	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
587	    bytes = id3_parse_uint(&ehptr, 1);
588	    if (bytes < 1)
589	      goto fail;
590
591	    tag->restrictions = id3_parse_uint(&ehptr, 1);
592	    ehptr += bytes - 1;
593	  }
594	}
595	break;
596      }
597    }
598
599    /* frames */
600
601    while (ptr < end) {
602      struct id3_frame *frame;
603
604      if (*ptr == 0)
605	break;  /* padding */
606
607      frame = id3_frame_parse(&ptr, end - ptr, tag->version);
608      if (frame == 0 || id3_tag_attachframe(tag, frame) == -1)
609	goto fail;
610    }
611
612    if (ID3_TAG_VERSION_MAJOR(tag->version) < 4 &&
613	id3_compat_fixup(tag) == -1)
614      goto fail;
615  }
616
617  if (0) {
618  fail:
619    id3_tag_delete(tag);
620    tag = 0;
621  }
622
623  if (mem)
624    free(mem);
625
626  return tag;
627}
628
629/*
630 * NAME:	tag->parse()
631 * DESCRIPTION:	parse a complete ID3 tag
632 */
633struct id3_tag *id3_tag_parse(id3_byte_t const *data, id3_length_t length)
634{
635  id3_byte_t const *ptr;
636  unsigned int version;
637  int flags;
638  id3_length_t size;
639
640  assert(data);
641
642  switch (tagtype(data, length)) {
643  case TAGTYPE_ID3V1:
644    return (length < 128) ? 0 : v1_parse(data);
645
646  case TAGTYPE_ID3V2:
647    break;
648
649  case TAGTYPE_ID3V2_FOOTER:
650  case TAGTYPE_NONE:
651    return 0;
652  }
653
654  /* ID3v2.x */
655
656  ptr = data;
657  parse_header(&ptr, &version, &flags, &size);
658
659  switch (ID3_TAG_VERSION_MAJOR(version)) {
660  case 4:
661    if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
662      size += 10;
663  case 2:
664  case 3:
665    return (length < 10 + size) ? 0 : v2_parse(data);
666  }
667
668  return 0;
669}
670
671static
672void v1_renderstr(struct id3_tag const *tag, char const *frameid,
673		  id3_byte_t **buffer, id3_length_t length)
674{
675  struct id3_frame *frame;
676  id3_ucs4_t const *string;
677
678  frame = id3_tag_findframe(tag, frameid, 0);
679  if (frame == 0)
680    string = id3_ucs4_empty;
681  else {
682    if (strcmp(frameid, ID3_FRAME_COMMENT) == 0)
683      string = id3_field_getfullstring(&frame->fields[3]);
684    else
685      string = id3_field_getstrings(&frame->fields[1], 0);
686  }
687
688  id3_render_paddedstring(buffer, string, length);
689}
690
691/*
692 * NAME:	v1->render()
693 * DESCRIPTION:	render an ID3v1 (or ID3v1.1) tag
694 */
695static
696id3_length_t v1_render(struct id3_tag const *tag, id3_byte_t *buffer)
697{
698  id3_byte_t data[128], *ptr;
699  struct id3_frame *frame;
700  unsigned int i;
701  int genre = -1;
702
703  ptr = data;
704
705  id3_render_immediate(&ptr, "TAG", 3);
706
707  v1_renderstr(tag, ID3_FRAME_TITLE,   &ptr, 30);
708  v1_renderstr(tag, ID3_FRAME_ARTIST,  &ptr, 30);
709  v1_renderstr(tag, ID3_FRAME_ALBUM,   &ptr, 30);
710  v1_renderstr(tag, ID3_FRAME_YEAR,    &ptr,  4);
711  v1_renderstr(tag, ID3_FRAME_COMMENT, &ptr, 30);
712
713  /* ID3v1.1 track number */
714
715  frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0);
716  if (frame) {
717    unsigned int track;
718
719    track = id3_ucs4_getnumber(id3_field_getstrings(&frame->fields[1], 0));
720    if (track > 0 && track <= 0xff) {
721      ptr[-2] = 0;
722      ptr[-1] = track;
723    }
724  }
725
726  /* ID3v1 genre number */
727
728  frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0);
729  if (frame) {
730    unsigned int nstrings;
731
732    nstrings = id3_field_getnstrings(&frame->fields[1]);
733
734    for (i = 0; i < nstrings; ++i) {
735      genre = id3_genre_number(id3_field_getstrings(&frame->fields[1], i));
736      if (genre != -1)
737	break;
738    }
739
740    if (i == nstrings && nstrings > 0)
741      genre = ID3_GENRE_OTHER;
742  }
743
744  id3_render_int(&ptr, genre, 1);
745
746  /* make sure the tag is not empty */
747
748  if (genre == -1) {
749    for (i = 3; i < 127; ++i) {
750      if (data[i] != ' ')
751	break;
752    }
753
754    if (i == 127)
755      return 0;
756  }
757
758  if (buffer)
759    memcpy(buffer, data, 128);
760
761  return 128;
762}
763
764/*
765 * NAME:	tag->render()
766 * DESCRIPTION:	render a complete ID3 tag
767 */
768id3_length_t id3_tag_render(struct id3_tag const *tag, id3_byte_t *buffer)
769{
770  id3_length_t size = 0;
771  id3_byte_t **ptr,
772    *header_ptr = 0, *tagsize_ptr = 0, *crc_ptr = 0, *frames_ptr = 0;
773  int flags, extendedflags;
774  unsigned int i;
775
776  assert(tag);
777
778  if (tag->options & ID3_TAG_OPTION_ID3V1)
779    return v1_render(tag, buffer);
780
781  /* a tag must contain at least one (renderable) frame */
782
783  for (i = 0; i < tag->nframes; ++i) {
784    if (id3_frame_render(tag->frames[i], 0, 0) > 0)
785      break;
786  }
787
788  if (i == tag->nframes)
789    return 0;
790
791  ptr = buffer ? &buffer : 0;
792
793  /* get flags */
794
795  flags         = tag->flags         & ID3_TAG_FLAG_KNOWNFLAGS;
796  extendedflags = tag->extendedflags & ID3_TAG_EXTENDEDFLAG_KNOWNFLAGS;
797
798  extendedflags &= ~ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
799  if (tag->options & ID3_TAG_OPTION_CRC)
800    extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
801
802  extendedflags &= ~ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
803  if (tag->restrictions)
804    extendedflags |= ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
805
806  flags &= ~ID3_TAG_FLAG_UNSYNCHRONISATION;
807  if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION)
808    flags |= ID3_TAG_FLAG_UNSYNCHRONISATION;
809
810  flags &= ~ID3_TAG_FLAG_EXTENDEDHEADER;
811  if (extendedflags)
812    flags |= ID3_TAG_FLAG_EXTENDEDHEADER;
813
814  flags &= ~ID3_TAG_FLAG_FOOTERPRESENT;
815  if (tag->options & ID3_TAG_OPTION_APPENDEDTAG)
816    flags |= ID3_TAG_FLAG_FOOTERPRESENT;
817
818  /* header */
819
820  if (ptr)
821    header_ptr = *ptr;
822
823  size += id3_render_immediate(ptr, "ID3", 3);
824  size += id3_render_int(ptr, ID3_TAG_VERSION, 2);
825  size += id3_render_int(ptr, flags, 1);
826
827  if (ptr)
828    tagsize_ptr = *ptr;
829
830  size += id3_render_syncsafe(ptr, 0, 4);
831
832  /* extended header */
833
834  if (flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
835    id3_length_t ehsize = 0;
836    id3_byte_t *ehsize_ptr = 0;
837
838    if (ptr)
839      ehsize_ptr = *ptr;
840
841    ehsize += id3_render_syncsafe(ptr, 0, 4);
842    ehsize += id3_render_int(ptr, 1, 1);
843    ehsize += id3_render_int(ptr, extendedflags, 1);
844
845    if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)
846      ehsize += id3_render_int(ptr, 0, 1);
847
848    if (extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
849      ehsize += id3_render_int(ptr, 5, 1);
850
851      if (ptr)
852	crc_ptr = *ptr;
853
854      ehsize += id3_render_syncsafe(ptr, 0, 5);
855    }
856
857    if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
858      ehsize += id3_render_int(ptr, 1, 1);
859      ehsize += id3_render_int(ptr, tag->restrictions, 1);
860    }
861
862    if (ehsize_ptr)
863      id3_render_syncsafe(&ehsize_ptr, ehsize, 4);
864
865    size += ehsize;
866  }
867
868  /* frames */
869
870  if (ptr)
871    frames_ptr = *ptr;
872
873  for (i = 0; i < tag->nframes; ++i)
874    size += id3_frame_render(tag->frames[i], ptr, tag->options);
875
876  /* padding */
877
878  if (!(flags & ID3_TAG_FLAG_FOOTERPRESENT)) {
879    if (size < tag->paddedsize)
880      size += id3_render_padding(ptr, 0, tag->paddedsize - size);
881    else if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) {
882      if (ptr == 0)
883	size += 1;
884      else {
885	if ((*ptr)[-1] == 0xff)
886	  size += id3_render_padding(ptr, 0, 1);
887      }
888    }
889  }
890
891  /* patch tag size and CRC */
892
893  if (tagsize_ptr)
894    id3_render_syncsafe(&tagsize_ptr, size - 10, 4);
895
896  if (crc_ptr) {
897    id3_render_syncsafe(&crc_ptr,
898			id3_crc_compute(frames_ptr, *ptr - frames_ptr), 5);
899  }
900
901  /* footer */
902
903  if (flags & ID3_TAG_FLAG_FOOTERPRESENT) {
904    size += id3_render_immediate(ptr, "3DI", 3);
905    size += id3_render_binary(ptr, header_ptr + 3, 7);
906  }
907
908  return size;
909}
910