mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-30 23:59:41 +02: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;
|
|
}
|