mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 08:04:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1062 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1062 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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.4 2007-04-29 05:10:24 llucius 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:	v2_3_render()
 | 
						|
 * DESCRIPTION:	render a v2.3 ID3 tag for compatibility
 | 
						|
 * AUTHOR: Dominic Mazzoni
 | 
						|
 */
 | 
						|
 | 
						|
#define ID3V2_3_TAG_VERSION 0x0300
 | 
						|
#define ID3V2_3_TAG_FLAG_KNOWNFLAGS 0xc0
 | 
						|
 | 
						|
id3_length_t v2_3_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);
 | 
						|
 | 
						|
  /* 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         & ID3V2_3_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;
 | 
						|
 | 
						|
 | 
						|
  extendedflags = 0;
 | 
						|
 | 
						|
 | 
						|
  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;
 | 
						|
 | 
						|
  /* header */
 | 
						|
 | 
						|
  if (ptr)
 | 
						|
    header_ptr = *ptr;
 | 
						|
 | 
						|
  size += id3_render_immediate(ptr, "ID3", 3);
 | 
						|
  size += id3_render_int(ptr, ID3V2_3_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;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * 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);
 | 
						|
 | 
						|
  if (tag->options & ID3_TAG_OPTION_ID3V2_3)
 | 
						|
    return v2_3_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;
 | 
						|
}
 |