#include "mpg123lib_intern.h"
#include "id3.h"
#include "../common/debug.h"
#ifndef NO_ID3V2
#define KNOWN_FRAMES 5
static const char frame_type[KNOWN_FRAMES][5] = { "COMM", "TXXX", "RVA2", "USLT", "APIC" };
enum frame_types { unknown = -2, text = -1, comment, extra, rva2, uslt, picture };
typedef void (*text_converter)(mpg123_string *sb, const unsigned char* source, size_t len, const int noquiet);
static void convert_latin1 (mpg123_string *sb, const unsigned char* source, size_t len, const int noquiet);
static void convert_utf16bom(mpg123_string *sb, const unsigned char* source, size_t len, const int noquiet);
static void convert_utf8 (mpg123_string *sb, const unsigned char* source, size_t len, const int noquiet);
static const text_converter text_converters[4] =
{
convert_latin1,
convert_utf16bom,
convert_utf16bom,
convert_utf8
};
static const unsigned int encoding_widths[4] = { 1, 2, 2, 1 };
static void null_id3_links(mpg123_handle *fr)
{
fr->id3v2.title = NULL;
fr->id3v2.artist = NULL;
fr->id3v2.album = NULL;
fr->id3v2.year = NULL;
fr->id3v2.genre = NULL;
fr->id3v2.comment = NULL;
}
void INT123_init_id3(mpg123_handle *fr)
{
fr->id3v2.version = 0;
null_id3_links(fr);
fr->id3v2.comments = 0;
fr->id3v2.comment_list = NULL;
fr->id3v2.texts = 0;
fr->id3v2.text = NULL;
fr->id3v2.extras = 0;
fr->id3v2.extra = NULL;
fr->id3v2.pictures = 0;
fr->id3v2.picture = NULL;
}
static void init_mpg123_text(mpg123_text *txt)
{
mpg123_init_string(&txt->text);
mpg123_init_string(&txt->description);
txt->id[0] = 0;
txt->id[1] = 0;
txt->id[2] = 0;
txt->id[3] = 0;
txt->lang[0] = 0;
txt->lang[1] = 0;
txt->lang[2] = 0;
}
static void init_mpg123_picture(mpg123_picture *pic)
{
mpg123_init_string(&pic->mime_type);
mpg123_init_string(&pic->description);
pic->type = 0;
pic->size = 0;
pic->data = NULL;
}
static void free_mpg123_text(mpg123_text *txt)
{
mpg123_free_string(&txt->text);
mpg123_free_string(&txt->description);
}
static void free_mpg123_picture(mpg123_picture * pic)
{
mpg123_free_string(&pic->mime_type);
mpg123_free_string(&pic->description);
if (pic->data != NULL)
free(pic->data);
}
#define free_comment(mh) free_id3_text(&((mh)->id3v2.comment_list), &((mh)->id3v2.comments))
#define free_text(mh) free_id3_text(&((mh)->id3v2.text), &((mh)->id3v2.texts))
#define free_extra(mh) free_id3_text(&((mh)->id3v2.extra), &((mh)->id3v2.extras))
#define free_picture(mh) free_id3_picture(&((mh)->id3v2.picture), &((mh)->id3v2.pictures))
static void free_id3_text(mpg123_text **list, size_t *size)
{
size_t i;
for(i=0; i<*size; ++i) free_mpg123_text(&((*list)[i]));
free(*list);
*list = NULL;
*size = 0;
}
static void free_id3_picture(mpg123_picture **list, size_t *size)
{
size_t i;
for(i=0; i<*size; ++i) free_mpg123_picture(&((*list)[i]));
free(*list);
*list = NULL;
*size = 0;
}
#define add_comment(mh, l, d) add_id3_text(&((mh)->id3v2.comment_list), &((mh)->id3v2.comments), NULL, l, d)
#define add_text(mh, id) add_id3_text(&((mh)->id3v2.text), &((mh)->id3v2.texts), id, NULL, NULL)
#define add_uslt(mh, l, d) add_id3_text(&((mh)->id3v2.text), &((mh)->id3v2.texts), id, l, d)
#define add_extra(mh, d) add_id3_text(&((mh)->id3v2.extra), &((mh)->id3v2.extras), NULL, NULL, d)
#define add_picture(mh, t, d) add_id3_picture(&((mh)->id3v2.picture), &((mh)->id3v2.pictures), t, d)
static mpg123_text *add_id3_text( mpg123_text **list, size_t *size
, char id[4], char lang[3], mpg123_string *description )
{
mdebug( "add_id3_text id=%s lang=%s, desc=%s"
, id ? (char[5]) { id[0], id[1], id[2], id[3], 0 } : "(nil)"
, lang ? (char[4]) { lang[0], lang[1], lang[2], 0 } : "(nil)"
, description ? (description->fill ? description->p : "(empty)") : "(nil)" );
if(lang && !description)
return NULL;
if(id || description)
{
for(size_t i=0; i<*size; ++i)
{
mpg123_text *entry = *list+i;
if(description)
{
if( (!id || !memcmp(id, entry->id, 4))
&& (!lang || !memcmp(entry->lang, lang, 3))
&& mpg123_same_string(&(entry->description), description)
)
return entry;
} else if(id && !memcmp(id, entry->id, 4))
return entry;
mdebug("add_id3_text: entry %zu was no match", i);
}
}
mdebug("add_id3_text: append to list of %zu", *size);
mpg123_text *x = INT123_safe_realloc(*list, sizeof(mpg123_text)*(*size+1));
if(x == NULL) return NULL;
*list = x;
*size += 1;
init_mpg123_text(&((*list)[*size-1]));
return &((*list)[*size-1]);
}
static mpg123_picture *add_id3_picture(mpg123_picture **list, size_t *size, char type, mpg123_string *description)
{
if(!description)
return NULL;
for(size_t i=0; i<*size; ++i)
{
mpg123_picture *entry = *list+i;
if( type == entry->type
&& ( type == 1 || type == 2 ||
mpg123_same_string(&entry->description, description)
)
)
return entry;
}
mpg123_picture *x = INT123_safe_realloc(*list, sizeof(mpg123_picture)*(*size+1));
if(x == NULL) return NULL;
*list = x;
*size += 1;
init_mpg123_picture(&((*list)[*size-1]));
return &((*list)[*size-1]);
}
void INT123_exit_id3(mpg123_handle *fr)
{
free_picture(fr);
free_comment(fr);
free_extra(fr);
free_text(fr);
}
void INT123_reset_id3(mpg123_handle *fr)
{
INT123_exit_id3(fr);
INT123_init_id3(fr);
}
void INT123_id3_link(mpg123_handle *fr)
{
size_t i;
mpg123_id3v2 *v2 = &fr->id3v2;
debug("linking ID3v2");
null_id3_links(fr);
for(i=0; i<v2->texts; ++i)
{
mpg123_text *entry = &v2->text[i];
if (!strncmp("TIT2", entry->id, 4)) v2->title = &entry->text;
else if(!strncmp("TALB", entry->id, 4)) v2->album = &entry->text;
else if(!strncmp("TPE1", entry->id, 4)) v2->artist = &entry->text;
else if(!strncmp("TYER", entry->id, 4)) v2->year = &entry->text;
else if(!strncmp("TCON", entry->id, 4)) v2->genre = &entry->text;
}
for(i=0; i<v2->comments; ++i)
{
mpg123_text *entry = &v2->comment_list[i];
if(entry->description.fill == 0 || entry->description.p[0] == 0)
v2->comment = &entry->text;
}
if(v2->comment == NULL && v2->comments > 0)
v2->comment = &v2->comment_list[v2->comments-1].text;
}
static void store_id3_text(mpg123_string *sb, unsigned char *source, size_t source_size, const int noquiet, const int notranslate)
{
unsigned char encoding;
if(sb)
sb->fill = 0;
if(!source_size)
{
debug("Empty id3 data!");
return;
}
if(notranslate)
{
if(!mpg123_grow_string(sb, source_size))
{
if(noquiet) error("Cannot resize target string, out of memory?");
return;
}
memcpy(sb->p, source, source_size);
sb->fill = source_size;
debug1("stored undecoded ID3 text of size %zu", source_size);
return;
}
encoding = source[0];
if(encoding > mpg123_id3_enc_max)
{
if(noquiet)
error1("Unknown text encoding %u, I take no chances, sorry!", encoding);
return;
}
INT123_id3_to_utf8(sb, encoding, source+1, source_size-1, noquiet);
if(sb->fill) debug1("UTF-8 string (the first one): %s", sb->p);
else if(noquiet) error("unable to convert string to UTF-8 (out of memory, junk input?)!");
}
void INT123_id3_to_utf8(mpg123_string *sb, unsigned char encoding, const unsigned char *source, size_t source_size, int noquiet)
{
unsigned int bwidth;
if(sb)
sb->fill = 0;
debug1("encoding: %u", encoding);
bwidth = encoding_widths[encoding];
if(encoding != mpg123_id3_utf16be)
while(source_size > bwidth && source[0] == 0)
{
--source_size;
++source;
debug("skipped leading zero");
}
if(source_size % bwidth)
{
if(noquiet) warning2("Weird tag size %d for encoding %u - I will probably trim too early or something but I think the MP3 is broken.", (int)source_size, encoding);
source_size -= source_size % bwidth;
}
text_converters[encoding](sb, source, source_size, noquiet);
}
static unsigned char *next_text(unsigned char* prev, unsigned char encoding, size_t limit)
{
unsigned char *text = prev;
size_t width = encoding_widths[encoding];
if(limit > PTRDIFF_MAX)
return NULL;
while(text-prev < (ptrdiff_t)limit)
{
if(text[0] == 0)
{
if(width <= limit-(text-prev))
{
size_t i = 1;
for(; i<width; ++i) if(text[i] != 0) break;
if(i == width)
{
text += width;
break;
}
}
else return NULL;
}
text += width;
}
if((size_t)(text-prev) >= limit) text = NULL;
return text;
}
static const char *enc_name(unsigned char enc)
{
switch(enc)
{
case 0: return "Latin 1";
case 1: return "UTF-16 BOM";
case 2: return "UTF-16 BE";
case 3: return "UTF-8";
default: return "unknown!";
}
}
static void process_text(mpg123_handle *fr, unsigned char *realdata, size_t realsize, char *id)
{
mpg123_text *t = add_text(fr, id);
if(VERBOSE4) fprintf(stderr, "Note: Storing text from %s encoding\n", enc_name(realdata[0]));
if(t == NULL)
{
if(NOQUIET) error("Unable to attach new text!");
return;
}
mdebug("process_text: (over)writing entry with ID %s", t->id[0]
? (char[5]) { t->id[0], t->id[1], t->id[2], t->id[3], 0 }
: "(nil)" );
memcpy(t->id, id, 4);
store_id3_text(&t->text, realdata, realsize, NOQUIET, fr->p.flags & MPG123_PLAIN_ID3TEXT);
if(VERBOSE4)
fprintf(stderr, "Note: ID3v2 %c%c%c%c text frame stored\n", id[0], id[1], id[2], id[3]);
}
static void process_picture(mpg123_handle *fr, unsigned char *realdata, size_t realsize)
{
unsigned char encoding;
mpg123_picture *i = NULL;
unsigned char* workpoint = NULL;
mpg123_string mime; mpg123_init_string(&mime);
unsigned char image_type = 0;
mpg123_string description; mpg123_init_string(&description);
unsigned char *image_data = NULL;
if(realsize < 1)
{
debug("Empty id3 data!");
return;
}
encoding = realdata[0];
realdata++; realsize--;
if(encoding > mpg123_id3_enc_max)
{
if(NOQUIET)
error1("Unknown text encoding %u, I take no chances, sorry!", encoding);
return;
}
if(VERBOSE4) fprintf(stderr, "Note: Storing picture from APIC frame.\n");
workpoint = next_text(realdata, 0, realsize);
if(!workpoint)
{
if(NOQUIET)
error("Unable to get mime type for picture; skipping picture.");
return;
}
INT123_id3_to_utf8(&mime, 0, realdata, workpoint - realdata, NOQUIET);
realsize -= workpoint - realdata;
realdata = workpoint;
image_type = realdata[0];
realdata++; realsize--;
workpoint = next_text(realdata, encoding, realsize);
if(!workpoint)
{
if(NOQUIET)
error("Unable to get description for picture; skipping picture.");
mpg123_free_string(&mime);
return;
}
INT123_id3_to_utf8(&description, encoding, realdata, workpoint - realdata, NOQUIET);
realsize -= workpoint - realdata;
if(realsize)
image_data = (unsigned char*)malloc(realsize);
if(!realsize || !image_data) {
if(NOQUIET)
error("No picture data or malloc failure; skipping picture.");
mpg123_free_string(&description);
mpg123_free_string(&mime);
return;
}
memcpy(image_data, workpoint, realsize);
i = add_picture(fr, image_type, &description);
if(!i)
{
if(NOQUIET)
error("Unable to attach new picture!");
free(image_data);
mpg123_free_string(&description);
mpg123_free_string(&mime);
return;
}
free_mpg123_picture(i);
i->type = image_type;
i->size = realsize;
i->data = image_data;
mpg123_move_string(&mime, &i->mime_type);
mpg123_move_string(&description, &i->description);
if(VERBOSE4)
fprintf(stderr, "Note: ID3v2 APIC picture frame of type: %d\n", i->type);
}
static void process_comment(mpg123_handle *fr, enum frame_types tt, unsigned char *realdata, size_t realsize, int rva_level, char *id)
{
unsigned char encoding = realdata[0];
char lang[3];
unsigned char *descr = realdata+4;
unsigned char *text = NULL;
mpg123_text *xcom = NULL;
mpg123_text localcom;
init_mpg123_text(&localcom);
if(realsize < (size_t)(descr-realdata))
{
if(NOQUIET) error1("Invalid frame size of %zu (too small for anything).", realsize);
return;
}
if(encoding > mpg123_id3_enc_max)
{
if(NOQUIET)
error1("Unknown text encoding %u, I take no chances, sorry!", encoding);
return;
}
memcpy(lang, realdata+1, 3);
descr[-1] = encoding;
text = next_text(descr, encoding, realsize-(descr-realdata));
if(text == NULL)
{
if(NOQUIET)
error("No comment text / valid description?");
return;
}
{
mpg123_string description;
mpg123_init_string(&description);
store_id3_text( &description, descr-1, text-descr+1
, NOQUIET, fr->p.flags & MPG123_PLAIN_ID3TEXT );
if(tt == comment)
store_id3_text( &localcom.description, descr-1, text-descr+1
, NOQUIET, 0 );
if(VERBOSE4)
fprintf( stderr, "Note: Storing comment from %s encoding\n"
, enc_name(realdata[0]) );
xcom = tt == uslt
? add_uslt(fr, lang, &description)
: add_comment(fr, lang, &description);
if(xcom == NULL)
{
if(NOQUIET)
error("Unable to attach new comment!");
mpg123_free_string(&description);
free_mpg123_text(&localcom);
return;
}
memcpy(xcom->id, id, 4);
memcpy(xcom->lang, lang, 3);
mpg123_move_string(&description, &xcom->description);
}
text[-1] = encoding;
store_id3_text(&xcom->text, text-1, realsize+1-(text-realdata), NOQUIET, fr->p.flags & MPG123_PLAIN_ID3TEXT);
if(VERBOSE4)
{
fprintf(stderr, "Note: ID3 comm/uslt desc of length %zu.\n", xcom->description.fill);
fprintf(stderr, "Note: ID3 comm/uslt text of length %zu.\n", xcom->text.fill);
}
if(tt == comment && localcom.description.fill > 0)
{
int rva_mode = -1;
if( !strcasecmp(localcom.description.p, "rva")
|| !strcasecmp(localcom.description.p, "rva_mix")
|| !strcasecmp(localcom.description.p, "rva_track")
|| !strcasecmp(localcom.description.p, "rva_radio") )
rva_mode = 0;
else if( !strcasecmp(localcom.description.p, "rva_album")
|| !strcasecmp(localcom.description.p, "rva_audiophile")
|| !strcasecmp(localcom.description.p, "rva_user") )
rva_mode = 1;
if((rva_mode > -1) && (fr->rva.level[rva_mode] <= rva_level))
{
store_id3_text(&localcom.text, text-1, realsize+1-(text-realdata), NOQUIET, 0);
if(localcom.text.fill > 0)
{
fr->rva.gain[rva_mode] = (float) atof(localcom.text.p);
if(VERBOSE3) fprintf(stderr, "Note: RVA value %fdB\n", fr->rva.gain[rva_mode]);
fr->rva.peak[rva_mode] = 0;
fr->rva.level[rva_mode] = rva_level;
}
}
}
free_mpg123_text(&localcom);
}
static void process_extra(mpg123_handle *fr, unsigned char* realdata, size_t realsize, int rva_level, char *id)
{
unsigned char encoding = realdata[0];
unsigned char *descr = realdata+1;
unsigned char *text;
mpg123_text *xex;
mpg123_text localex;
if((int)realsize < descr-realdata)
{
if(NOQUIET) error1("Invalid frame size of %lu (too small for anything).", (unsigned long)realsize);
return;
}
if(encoding > mpg123_id3_enc_max)
{
if(NOQUIET)
error1("Unknown text encoding %u, I take no chances, sorry!", encoding);
return;
}
text = next_text(descr, encoding, realsize-(descr-realdata));
if(VERBOSE4) fprintf(stderr, "Note: Storing extra from %s encoding\n", enc_name(realdata[0]));
if(text == NULL)
{
if(NOQUIET) error("No extra frame text / valid description?");
return;
}
{
mpg123_string description;
mpg123_init_string(&description);
store_id3_text( &description, descr-1, text-descr+1
, NOQUIET, fr->p.flags & MPG123_PLAIN_ID3TEXT );
xex = add_extra(fr, &description);
if(xex)
mpg123_move_string(&description, &xex->description);
else
mpg123_free_string(&description);
}
if(xex == NULL)
{
if(NOQUIET) error("Unable to attach new extra text!");
return;
}
memcpy(xex->id, id, 4);
init_mpg123_text(&localex);
store_id3_text(&localex.description, descr-1, text-descr+1, NOQUIET, 0);
text[-1] = encoding;
store_id3_text(&xex->text, text-1, realsize-(text-realdata)+1, NOQUIET, fr->p.flags & MPG123_PLAIN_ID3TEXT);
if(localex.description.fill > 0)
{
int is_peak = 0;
int rva_mode = -1;
if(!strncasecmp(localex.description.p, "replaygain_track_",17))
{
if(VERBOSE3) fprintf(stderr, "Note: RVA ReplayGain track gain/peak\n");
rva_mode = 0;
if(!strcasecmp(localex.description.p, "replaygain_track_peak")) is_peak = 1;
else if(strcasecmp(localex.description.p, "replaygain_track_gain")) rva_mode = -1;
}
else
if(!strncasecmp(localex.description.p, "replaygain_album_",17))
{
if(VERBOSE3) fprintf(stderr, "Note: RVA ReplayGain album gain/peak\n");
rva_mode = 1;
if(!strcasecmp(localex.description.p, "replaygain_album_peak")) is_peak = 1;
else if(strcasecmp(localex.description.p, "replaygain_album_gain")) rva_mode = -1;
}
if((rva_mode > -1) && (fr->rva.level[rva_mode] <= rva_level))
{
store_id3_text(&localex.text, text-1, realsize-(text-realdata)+1, NOQUIET, 0);
if(localex.text.fill > 0)
{
if(is_peak)
{
fr->rva.peak[rva_mode] = (float) atof(localex.text.p);
if(VERBOSE3) fprintf(stderr, "Note: RVA peak %f\n", fr->rva.peak[rva_mode]);
}
else
{
fr->rva.gain[rva_mode] = (float) atof(localex.text.p);
if(VERBOSE3) fprintf(stderr, "Note: RVA gain %fdB\n", fr->rva.gain[rva_mode]);
}
fr->rva.level[rva_mode] = rva_level;
}
}
}
free_mpg123_text(&localex);
}
static int promote_framename(mpg123_handle *fr, char *id)
{
size_t i;
char *old[] =
{
"COM", "TAL", "TBP", "TCM", "TCO", "TCR", "TDA", "TDY", "TEN", "TFT",
"TIM", "TKE", "TLA", "TLE", "TMT", "TOA", "TOF", "TOL", "TOR", "TOT",
"TP1", "TP2", "TP3", "TP4", "TPA", "TPB", "TRC", "TDA", "TRK", "TSI",
"TSS", "TT1", "TT2", "TT3", "TXT", "TXX", "TYE"
};
char *new[] =
{
"COMM", "TALB", "TBPM", "TCOM", "TCON", "TCOP", "TDAT", "TDLY", "TENC", "TFLT",
"TIME", "TKEY", "TLAN", "TLEN", "TMED", "TOPE", "TOFN", "TOLY", "TORY", "TOAL",
"TPE1", "TPE2", "TPE3", "TPE4", "TPOS", "TPUB", "TSRC", "TRDA", "TRCK", "TSIZ",
"TSSE", "TIT1", "TIT2", "TIT3", "TEXT", "TXXX", "TYER"
};
for(i=0; i<sizeof(old)/sizeof(char*); ++i)
{
if(!strncmp(id, old[i], 3))
{
memcpy(id, new[i], 4);
if(VERBOSE3) fprintf(stderr, "Translated ID3v2.2 frame %s to %s\n", old[i], new[i]);
return 0;
}
}
if(VERBOSE3) fprintf(stderr, "Ignoring untranslated ID3v2.2 frame %c%c%c\n", id[0], id[1], id[2]);
return -1;
}
#endif
static int store_id3v2( mpg123_handle *fr
, unsigned long first4bytes, unsigned char buf[6], unsigned long length )
{
int ret = 1;
int64_t ret2;
unsigned long fullen = 10+length;
if(fr->id3v2_raw)
free(fr->id3v2_raw);
fr->id3v2_size = 0;
fr->id3v2_raw = malloc(fullen+1);
if(!fr->id3v2_raw)
{
fr->err = MPG123_OUT_OF_MEM;
if(NOQUIET)
error1("ID3v2: Arrg! Unable to allocate %lu bytes"
" for ID3v2 data - trying to skip instead.", length+1);
if((ret2=fr->rd->skip_bytes(fr,length)) < 0)
ret = ret2;
else
ret = 0;
}
else
{
fr->id3v2_raw[0] = (first4bytes>>24) & 0xff;
fr->id3v2_raw[1] = (first4bytes>>16) & 0xff;
fr->id3v2_raw[2] = (first4bytes>>8) & 0xff;
fr->id3v2_raw[3] = first4bytes & 0xff;
memcpy(fr->id3v2_raw+4, buf, 6);
if((ret2=fr->rd->read_frame_body(fr, fr->id3v2_raw+10, length)) < 0)
{
ret=ret2;
free(fr->id3v2_raw);
fr->id3v2_raw = NULL;
}
else
{
fr->id3v2_raw[fullen] = 0;
fr->id3v2_size = fullen;
}
}
return ret;
}
int INT123_parse_new_id3(mpg123_handle *fr, unsigned long first4bytes)
{
#define UNSYNC_FLAG 128
#define EXTHEAD_FLAG 64
#define COMPRESS_FLAG 64
#define EXP_FLAG 32
#define FOOTER_FLAG 16
#define EXT_UPDATE_FLAG 64
#define UNKNOWN_FLAGS 15
unsigned char buf[6];
unsigned long length=0;
unsigned char flags = 0;
int ret = 1;
int64_t ret2;
int storetag = 0;
unsigned int footlen = 0;
#ifndef NO_ID3V2
int skiptag = 0;
#endif
unsigned char major = first4bytes & 0xff;
debug1("ID3v2: major tag version: %i", major);
if(major == 0xff) return 0;
if((ret2 = fr->rd->read_frame_body(fr, buf, 6)) < 0)
return ret2;
if(buf[0] == 0xff) return 0;
if(fr->p.flags & MPG123_STORE_RAW_ID3)
storetag = 1;
flags = buf[1];
debug1("ID3v2: flags 0x%08x", flags);
#define synchsafe_to_long(buf,res) \
( \
(((buf)[0]|(buf)[1]|(buf)[2]|(buf)[3]) & 0x80) ? 0 : \
(res = (((unsigned long) (buf)[0]) << 21) \
| (((unsigned long) (buf)[1]) << 14) \
| (((unsigned long) (buf)[2]) << 7) \
| ((unsigned long) (buf)[3]) \
,1) \
)
#define bytes_to_long(buf,res) \
( \
major == 3 ? \
(res = (((unsigned long) (buf)[0]) << 24) \
| (((unsigned long) (buf)[1]) << 16) \
| (((unsigned long) (buf)[2]) << 8) \
| ((unsigned long) (buf)[3]) \
,1) : synchsafe_to_long(buf,res) \
)
#define threebytes_to_long(buf,res) \
( \
res = (((unsigned long) (buf)[0]) << 16) \
| (((unsigned long) (buf)[1]) << 8) \
| ((unsigned long) (buf)[2]) \
)
if(!synchsafe_to_long(buf+2,length))
{
if(NOQUIET) error4("Bad tag length (not synchsafe): 0x%02x%02x%02x%02x; You got a bad ID3 tag here.", buf[2],buf[3],buf[4],buf[5]);
return 0;
}
if(flags & FOOTER_FLAG)
footlen = 10;
debug1("ID3v2: tag data length %lu", length);
#ifndef NO_ID3V2
if(VERBOSE2) fprintf(stderr,"Note: ID3v2.%i rev %i tag of %lu bytes\n", major, buf[0], length);
if(fr->p.flags & MPG123_SKIP_ID3V2)
{
if(VERBOSE3)
fprintf(stderr, "Note: Skipping ID3v2 tag per user request.\n");
skiptag = 1;
}
if((flags & UNKNOWN_FLAGS) || (major > 4) || (major < 2))
{
if(NOQUIET)
warning2( "ID3v2: Won't parse the ID3v2 tag with major version"
" %u and flags 0x%xu - some extra code may be needed"
, major, flags );
skiptag = 1;
}
if(major == 2 && flags & COMPRESS_FLAG)
{
if(NOQUIET)
warning("ID3v2: ignoring compressed ID3v2.2 tag");
skiptag = 1;
}
if(length < 10)
{
if(NOQUIET)
warning1("ID3v2: unrealistic small tag lengh %lu, skipping", length);
skiptag = 1;
}
if(!skiptag)
storetag = 1;
#endif
if(storetag)
{
if((ret2 = store_id3v2(fr, first4bytes, buf, length+footlen)) <= 0)
return ret2;
}
#ifndef NO_ID3V2
if(skiptag)
{
if(VERBOSE3)
fprintf(stderr, "Note: skipped tag clearing possibly existing ID3v2 data");
INT123_reset_id3(fr);
#endif
if(!storetag && (ret2=fr->rd->skip_bytes(fr,length+footlen))<0)
ret=ret2;
#ifndef NO_ID3V2
}
else
{
unsigned char* tagdata = fr->id3v2_raw+10;
debug("ID3v2: analysing frames...");
if(length > 0)
{
unsigned char extflags = 0;
unsigned long tagpos = 0;
unsigned int head_part = major > 2 ? 4 : 3;
unsigned int flag_part = major > 2 ? 2 : 0;
unsigned int framebegin = head_part+head_part+flag_part;
debug1("ID3v2: have read at all %lu bytes for the tag now", (unsigned long)length+6);
if(flags & EXTHEAD_FLAG)
{
debug("ID3v2: extended header");
if(!bytes_to_long(tagdata, tagpos) || tagpos >= length)
{
ret = 0;
if(NOQUIET)
error4( "Bad (non-synchsafe/too large) tag offset from extended header:"
"0x%02x%02x%02x%02x"
, tagdata[0], tagdata[1], tagdata[2], tagdata[3] );
} else if(tagpos < 6)
{
ret = 0;
if(NOQUIET)
merror("Extended header too small (%lu).", tagpos);
}
if(major == 3)
{
tagpos += 4;
if(tagpos >= length)
{
ret = 0;
if(NOQUIET)
error("Too much extended v2.3 header.");
}
} else if(ret)
{
if(tagdata[4] == 1 && tagdata[5] & EXT_UPDATE_FLAG)
{
if(VERBOSE3)
fprintf(stderr, "Note: ID3v2.4 update tag\n");
extflags |= EXT_UPDATE_FLAG;
}
}
}
if(!(extflags & EXT_UPDATE_FLAG))
{
if(VERBOSE3)
fprintf(stderr, "Note: non-update tag replacing existing ID3v2 data\n");
INT123_reset_id3(fr);
}
if(ret > 0)
{
char id[5];
unsigned long framesize;
unsigned long fflags;
id[4] = 0;
fr->id3v2.version = major;
while(length >= tagpos+framebegin)
{
int i = 0;
unsigned long pos = tagpos;
enum frame_types tt = unknown;
for(i=0; i< head_part; ++i)
if( !( ((tagdata[tagpos+i] > 47) && (tagdata[tagpos+i] < 58))
|| ((tagdata[tagpos+i] > 64) && (tagdata[tagpos+i] < 91)) ) )
{
debug5("ID3v2: real tag data apparently ended after %lu bytes with 0x%02x%02x%02x%02x", tagpos, tagdata[tagpos], tagdata[tagpos+1], tagdata[tagpos+2], tagdata[tagpos+3]);
goto tagparse_cleanup;
}
if(ret > 0)
{
strncpy(id, (char*) tagdata+pos, head_part);
id[head_part] = 0;
pos += head_part;
tagpos += head_part;
if(fr->id3v2.version == 2) threebytes_to_long(tagdata+pos, framesize);
else
if(!bytes_to_long(tagdata+pos, framesize))
{
if(NOQUIET) error1("ID3v2: non-syncsafe size of %s frame, skipping the remainder of tag", id);
break;
}
if(VERBOSE3) fprintf(stderr, "Note: ID3v2 %s frame of size %lu\n", id, framesize);
tagpos += head_part;
pos += head_part;
if(fr->id3v2.version > 2)
{
fflags = (((unsigned long) tagdata[pos]) << 8) | ((unsigned long) tagdata[pos+1]);
pos += 2;
tagpos += 2;
}
else fflags = 0;
if(length - tagpos < framesize)
{
if(NOQUIET) error("Whoa! ID3v2 frame claims to be larger than the whole rest of the tag.");
break;
}
tagpos += framesize;
#define V3 (major == 3)
#define BAD_FFLAGS (unsigned long) (V3 ? 7967 : 36784)
#define PRES_TAG_FFLAG (unsigned long) (V3 ? 32768 : 16384)
#define PRES_FILE_FFLAG (unsigned long) (V3 ? 16384 : 8192)
#define READ_ONLY_FFLAG (unsigned long) (V3 ? 8192 : 4096)
#define GROUP_FFLAG (unsigned long) (V3 ? 32 : 64)
#define COMPR_FFLAG (unsigned long) (V3 ? 128 : 8)
#define ENCR_FFLAG (unsigned long) (V3 ? 64 : 4)
#define UNSYNC_FFLAG (unsigned long) (V3 ? 0 : 2)
#define DATLEN_FFLAG (unsigned long) (V3 ? 0 : 1)
if(head_part < 4 && promote_framename(fr, id) != 0) continue;
if(fflags & (BAD_FFLAGS | COMPR_FFLAG | ENCR_FFLAG))
{
if(NOQUIET) warning("ID3v2: skipping invalid/unsupported frame");
continue;
}
for(i = 0; i < KNOWN_FRAMES; ++i)
if(!strncmp(frame_type[i], id, 4)){ tt = i; break; }
if(id[0] == 'T' && tt != extra) tt = text;
if(tt != unknown)
{
int rva_mode = -1;
unsigned long realsize = framesize;
unsigned char* realdata = tagdata+pos;
unsigned char* unsyncbuffer = NULL;
if(((flags & UNSYNC_FLAG) || (fflags & UNSYNC_FFLAG)) && framesize > 0)
{
unsigned long ipos = 0;
unsigned long opos = 0;
debug("Id3v2: going to de-unsync the frame data");
realdata = unsyncbuffer = malloc(framesize+1);
if(realdata == NULL)
{
if(NOQUIET) error("ID3v2: unable to allocate working buffer for de-unsync");
continue;
}
realdata[0] = tagdata[pos];
opos = 1;
for(ipos = pos+1; ipos < pos+framesize; ++ipos)
{
if(!((tagdata[ipos] == 0) && (tagdata[ipos-1] == 0xff)))
{
realdata[opos++] = tagdata[ipos];
}
}
realsize = opos;
realdata[realsize] = 0;
debug2("ID3v2: de-unsync made %lu out of %lu bytes", realsize, framesize);
}
if(fflags & GROUP_FFLAG)
{
if(realsize)
{
if(VERBOSE3)
fprintf(stderr, "Note: frame of group %d\n", realdata[0]);
--realsize;
++realdata;
} else if(NOQUIET)
error("Grouped frame without group byte, even.");
}
if(fflags & DATLEN_FFLAG)
{
if(realsize >= 4)
{
unsigned long datlen;
if(bytes_to_long(realdata, datlen) && datlen == realsize-4)
{
realsize -= 4;
realdata += 4;
} else
{
if(NOQUIET)
error("frame data length bad, skipping");
realsize = 0;
}
} else
{
realsize = 0;
if(NOQUIET)
error("frame truncated at frame data length, skipping");
}
}
pos = 0;
if(realsize) switch(tt)
{
case comment:
case uslt:
process_comment(fr, tt, realdata, realsize, comment+1, id);
break;
case extra:
process_extra(fr, realdata, realsize, extra+1, id);
break;
case rva2:
{
if(VERBOSE3) fprintf(stderr, "Note: RVA2 identification \"%s\"\n", realdata);
rva_mode = 0;
if( !strncasecmp((char*)realdata, "album", 5)
|| !strncasecmp((char*)realdata, "audiophile", 10)
|| !strncasecmp((char*)realdata, "user", 4))
rva_mode = 1;
if(fr->rva.level[rva_mode] <= rva2+1)
{
pos += strlen((char*) realdata) + 1;
debug2("got my pos: %lu - %lu", realsize, pos);
if(pos > realsize || realsize-pos < 3)
{
if(NOQUIET)
error("bad RVA2 tag (truncated?)");
}
else if(realdata[pos] == 1)
{
++pos;
debug("ID3v2: it is for the master channel");
fr->rva.gain[rva_mode] = (float) (
((short)((signed char*)realdata)[pos]) * 256 + (short)realdata[pos+1] ) / 512;
pos += 2;
if(VERBOSE3) fprintf(stderr, "Note: RVA value %fdB\n", fr->rva.gain[rva_mode]);
fr->rva.peak[rva_mode] = 0;
fr->rva.level[rva_mode] = rva2+1;
}
}
}
break;
case text:
process_text(fr, realdata, realsize, id);
break;
case picture:
if (fr->p.flags & MPG123_PICTURE)
process_picture(fr, realdata, realsize);
break;
default: if(NOQUIET) error1("ID3v2: unknown frame type %i", tt);
}
if(unsyncbuffer)
free(unsyncbuffer);
}
#undef V3
#undef BAD_FFLAGS
#undef PRES_TAG_FFLAG
#undef PRES_FILE_FFLAG
#undef READ_ONLY_FFLAG
#undef GROUP_FFLAG
#undef COMPR_FFLAG
#undef ENCR_FFLAG
#undef UNSYNC_FFLAG
#undef DATLEN_FFLAG
}
else break;
#undef KNOWN_FRAMES
}
} else
{
if(VERBOSE3)
fprintf(stderr, "Note: faulty ID3v2 tag still clearing old data\n");
INT123_reset_id3(fr);
}
} else
{
if(VERBOSE3)
fprintf(stderr, "Note: empty ID3v2 clearing old data\n");
INT123_reset_id3(fr);
}
tagparse_cleanup:
if(!(fr->p.flags & MPG123_STORE_RAW_ID3))
{
free(fr->id3v2_raw);
fr->id3v2_raw = NULL;
fr->id3v2_size = 0;
}
}
#endif
return ret;
#undef UNSYNC_FLAG
#undef EXTHEAD_FLAG
#undef COMPRESS_FLAG
#undef EXP_FLAG
#undef FOOTER_FLAG
#undef EXT_UPDATE_FLAG
#undef UNKOWN_FLAGS
}
#ifndef NO_ID3V2
static void convert_latin1(mpg123_string *sb, const unsigned char* s, size_t l, const int noquiet)
{
size_t length = l;
size_t i;
unsigned char *p;
for(i=0; i<l; ++i)
if(s[i] >= 0x80) ++length;
debug1("UTF-8 length: %lu", (unsigned long)length);
if(!mpg123_grow_string(sb, length+1))
return;
p = (unsigned char*) sb->p;
for(i=0; i<l; ++i)
if(s[i] < 0x80){ *p = s[i]; ++p; }
else
{
*p = 0xc0 | (s[i]>>6);
*(p+1) = 0x80 | (s[i] & 0x3f);
p+=2;
}
sb->p[length] = 0;
sb->fill = length+1;
}
static int check_bom(const unsigned char** source, size_t *len)
{
int last_bom = 0;
while(*len >= 2)
{
int this_bom = 0;
if((*source)[0] == 0xff && (*source)[1] == 0xfe)
this_bom = -1;
if((*source)[0] == 0xfe && (*source)[1] == 0xff)
this_bom = 1;
if(this_bom == 0)
break;
last_bom = this_bom;
*source += 2;
*len -= 2;
}
return last_bom;
}
#define FULLPOINT(f,s) ( (((f)&0x3ff)<<10) + ((s)&0x3ff) + 0x10000 )
#define UTF8LEN(x) ( (x)<0x80 ? 1 : ((x)<0x800 ? 2 : ((x)<0x10000 ? 3 : 4)))
static void convert_utf16bom(mpg123_string *sb, const unsigned char* s, size_t l, const int noquiet)
{
size_t i;
size_t n;
unsigned char *p;
size_t length = 0;
size_t high = 0;
size_t low = 1;
int bom_endian;
debug1("convert_utf16 with length %lu", (unsigned long)l);
bom_endian = check_bom(&s, &l);
debug1("UTF16 endianness check: %i", bom_endian);
if(bom_endian == -1)
{
high = 1;
low = 0;
}
n = (l/2)*2;
for(i=0; i < n; i+=2)
{
unsigned long point = ((unsigned long) s[i+high]<<8) + s[i+low];
if((point & 0xfc00) == 0xd800)
{
unsigned short second = (i+3 < l) ? (s[i+2+high]<<8) + s[i+2+low] : 0;
if((second & 0xfc00) == 0xdc00)
{
point = FULLPOINT(point,second);
length += UTF8LEN(point);
i+=2;
}
else
{
if(noquiet) error2("Invalid UTF16 surrogate pair at %li (0x%04lx).", (unsigned long)i, point);
n = i;
break;
}
}
else length += UTF8LEN(point);
}
if(!mpg123_grow_string(sb, length+1))
return;
p = (unsigned char*) sb->p;
for(i=0; i < n; i+=2)
{
unsigned long codepoint = ((unsigned long) s[i+high]<<8) + s[i+low];
if((codepoint & 0xfc00) == 0xd800)
{
unsigned short second = (s[i+2+high]<<8) + s[i+2+low];
codepoint = FULLPOINT(codepoint,second);
i+=2;
}
if(codepoint < 0x80) *p++ = (unsigned char) codepoint;
else if(codepoint < 0x800)
{
*p++ = (unsigned char) (0xc0 | (codepoint>>6));
*p++ = (unsigned char) (0x80 | (codepoint & 0x3f));
}
else if(codepoint < 0x10000)
{
*p++ = (unsigned char) (0xe0 | (codepoint>>12));
*p++ = 0x80 | ((codepoint>>6) & 0x3f);
*p++ = 0x80 | (codepoint & 0x3f);
}
else if (codepoint < 0x200000)
{
*p++ = (unsigned char) (0xf0 | codepoint>>18);
*p++ = (unsigned char) (0x80 | ((codepoint>>12) & 0x3f));
*p++ = (unsigned char) (0x80 | ((codepoint>>6) & 0x3f));
*p++ = (unsigned char) (0x80 | (codepoint & 0x3f));
}
}
sb->p[sb->size-1] = 0;
sb->fill = sb->size;
}
#undef UTF8LEN
#undef FULLPOINT
static void convert_utf8(mpg123_string *sb, const unsigned char* source, size_t len, const int noquiet)
{
if(mpg123_grow_string(sb, len+1))
{
memcpy(sb->p, source, len);
sb->p[len] = 0;
sb->fill = len+1;
}
}
#endif