summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/demuxers/demux_mpgaudio.c11
-rw-r--r--src/demuxers/id3.c297
-rw-r--r--src/demuxers/id3.h40
3 files changed, 317 insertions, 31 deletions
diff --git a/src/demuxers/demux_mpgaudio.c b/src/demuxers/demux_mpgaudio.c
index 47b49d4de..6e354f625 100644
--- a/src/demuxers/demux_mpgaudio.c
+++ b/src/demuxers/demux_mpgaudio.c
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*
- * $Id: demux_mpgaudio.c,v 1.140 2005/02/07 18:47:12 tmattern Exp $
+ * $Id: demux_mpgaudio.c,v 1.141 2005/09/15 18:45:15 tmattern Exp $
*
* demultiplexer for mpeg audio (i.e. mp3) streams
*
@@ -567,8 +567,13 @@ static int demux_mpgaudio_next (demux_mpgaudio_t *this, int decoder_flags) {
} else if ((BE_32(header_buf)) == ID3V24_TAG) {
xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
"demux_mpgaudio: ID3V2.4 tag\n");
- /* TODO: add parsing here */
- bytes = 1; /* resync */
+ if (!id3v24_parse_tag(this->input, this->stream, header_buf)) {
+ xprintf(this->stream->xine, XINE_VERBOSITY_LOG,
+ "demux_mpgaudio: ID3V2.4 tag parsing error\n");
+ bytes = 1; /* resync */
+ } else {
+ bytes = 4;
+ }
} else {
/* skip */
diff --git a/src/demuxers/id3.c b/src/demuxers/id3.c
index 229bf5588..8cef0b523 100644
--- a/src/demuxers/id3.c
+++ b/src/demuxers/id3.c
@@ -19,20 +19,17 @@
*
* ID3 tag parser
*
- * Supported versions: v1, v2.2, v2.3
+ * Supported versions: v1, v1.1, v2.2, v2.3, v2.4
* TODO:
- * v1: genre
- * v1.1: track info
- * v2.2: unicode
- * unsynchronize ?
- * v2.3: unicode
- * unsynchronize ?
- * unzip support ?
- * v2.4: parsing
+ * v2.2: unsynchronize
+ * v2.3: unsynchronize
+ * unzip support
+ * v2.4: unsynchronize
+ * unzip support
*
* ID3v2 specs: http://www.id3.org/
*
- * $Id: id3.c,v 1.7 2004/12/14 20:45:24 miguelfreitas Exp $
+ * $Id: id3.c,v 1.8 2005/09/15 18:45:15 tmattern Exp $
*/
#ifdef HAVE_CONFIG_H
@@ -230,6 +227,36 @@ static int id3v2_parse_genre(char* dest, char *src, int len) {
return 1;
}
+/* parse an unsynchronized 16bits integer */
+static uint16_t BE_16_unsynch(uint8_t buf[2]) {
+ return ((uint16_t)(buf[0] & 0x7F) << 7) |
+ (uint16_t)(buf[1] & 0x7F);
+}
+
+/* parse an unsynchronized 24bits integer */
+static uint32_t BE_24_unsynch(uint8_t buf[3]) {
+ return ((uint32_t)(buf[0] & 0x7F) << 14) |
+ ((uint32_t)(buf[1] & 0x7F) << 7) |
+ (uint32_t)(buf[2] & 0x7F);
+}
+
+/* parse an unsynchronized 32bits integer */
+static uint32_t BE_32_unsynch(uint8_t buf[4]) {
+ return ((uint32_t)(buf[0] & 0x7F) << 21) |
+ ((uint32_t)(buf[1] & 0x7F) << 14) |
+ ((uint32_t)(buf[2] & 0x7F) << 7) |
+ (uint32_t)(buf[3] & 0x7F);
+}
+
+/* parse an unsynchronized 35bits integer */
+static uint32_t BE_35_unsynch(uint8_t buf[5]) {
+ return ((uint32_t)(buf[0] & 0x07) << 28) |
+ ((uint32_t)(buf[1] & 0x7F) << 21) |
+ ((uint32_t)(buf[2] & 0x7F) << 14) |
+ ((uint32_t)(buf[3] & 0x7F) << 7) |
+ (uint32_t)(buf[4] & 0x7F);
+}
+
static int id3v2_parse_header(input_plugin_t *input, uint8_t *mp3_frame_header,
id3v2_header_t *tag_header) {
uint8_t buf[6];
@@ -238,9 +265,8 @@ static int id3v2_parse_header(input_plugin_t *input, uint8_t *mp3_frame_header,
if (input->read (input, buf, 6) == 6) {
tag_header->revision = buf[0];
tag_header->flags = buf[1];
+ tag_header->size = BE_32_unsynch(&buf[2]);
- /* only 7 bits per byte (unsynch) */
- tag_header->size = (buf[2] << 21) + (buf[3] << 14) + (buf[4] << 7) + buf[5];
lprintf("tag: ID3 v2.%d.%d\n", mp3_frame_header[3], tag_header->revision);
lprintf("flags: %d\n", tag_header->flags);
lprintf("size: %d\n", tag_header->size);
@@ -259,10 +285,10 @@ static int id3v22_parse_frame_header(input_plugin_t *input,
len = input->read (input, buf, ID3V22_FRAME_HEADER_SIZE);
if (len == ID3V22_FRAME_HEADER_SIZE) {
- frame_header->id = (buf[0] << 16) + (buf[1] << 8) + buf[2];
+ frame_header->id = (buf[0] << 16) + (buf[1] << 8) + buf[2];
/* only 7 bits per byte (unsynch) */
- frame_header->size = (buf[3] << 14) + (buf[4] << 7) + buf[5];
+ frame_header->size = BE_24_unsynch(&buf[3]);
lprintf("frame: %c%c%c: size: %d\n", buf[0], buf[1], buf[2],
frame_header->size);
@@ -276,9 +302,6 @@ static int id3v22_parse_frame_header(input_plugin_t *input,
static int id3v22_interp_frame(input_plugin_t *input,
xine_stream_t *stream,
id3v22_frame_header_t *frame_header) {
- /*
- * FIXME: supports unicode
- */
char buf[4096];
int enc;
@@ -415,10 +438,7 @@ static int id3v23_parse_frame_header(input_plugin_t *input,
len = input->read (input, buf, ID3V23_FRAME_HEADER_SIZE);
if (len == ID3V23_FRAME_HEADER_SIZE) {
frame_header->id = BE_32(buf);
-
- /* only 7 bits per byte (unsynch) */
- frame_header->size = (buf[4] << 21) + (buf[5] << 14) + (buf[6] << 7) + buf[7];
-
+ frame_header->size = BE_32_unsynch(&buf[4]);
frame_header->flags = BE_16(buf + 8);
lprintf("frame: %c%c%c%c, size: %d, flags: %X\n", buf[0], buf[1], buf[2], buf[3],
@@ -436,7 +456,7 @@ static int id3v23_parse_frame_ext_header(input_plugin_t *input,
if (input->read (input, buf, 4) == 4) {
/* only 7 bits per byte (unsynch) */
- frame_ext_header->size = (buf[0] << 21) + (buf[1] << 14) + (buf[2] << 7) + buf[3];
+ frame_ext_header->size = BE_32_unsynch(&buf[0]);
if (frame_ext_header->size == 6) {
if (input->read (input, buf + 4, 6) == 6) {
@@ -600,3 +620,236 @@ int id3v23_parse_tag(input_plugin_t *input,
return 0;
}
}
+
+/* id3 v2.4 */
+
+static int id3v24_parse_frame_header(input_plugin_t *input,
+ id3v24_frame_header_t *frame_header) {
+ uint8_t buf[ID3V24_FRAME_HEADER_SIZE];
+ int len;
+
+ len = input->read (input, buf, ID3V24_FRAME_HEADER_SIZE);
+ if (len == ID3V24_FRAME_HEADER_SIZE) {
+ frame_header->id = BE_32(buf);
+ frame_header->size = BE_32_unsynch(&buf[4]);
+ frame_header->flags = BE_16(&buf[8]);
+
+ lprintf("frame: %c%c%c%c, size: %d, flags: %X\n", buf[0], buf[1], buf[2], buf[3],
+ frame_header->size, frame_header->flags);
+
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static int id3v24_parse_ext_header(input_plugin_t *input,
+ id3v24_frame_ext_header_t *frame_ext_header) {
+ uint8_t buf[5];
+
+ if (input->read (input, buf, 4) == 4) {
+
+ frame_ext_header->size = BE_32_unsynch(&buf[0]);
+
+ if (input->read (input, buf, 2) == 2) {
+ uint8_t flags_size = buf[0];
+ if (flags_size != 1) {
+ lprintf(
+ "id3: invalid extended header, flag size must be 1\n");
+ return 0;
+ }
+ frame_ext_header->flags = buf[1];
+ if (frame_ext_header->flags & ID3V24_EXT_ZERO_FLAG) {
+ lprintf(
+ "id3: invalid extended header, unknown flags\n");
+ return 0;
+ }
+ /* Update flag */
+ if (frame_ext_header->flags & ID3V24_EXT_UPDATE_FLAG) {
+ if (input->read (input, buf, 1) == 1) {
+ uint8_t data_length = buf[0];
+ if (data_length != 0) {
+ lprintf(
+ "id3: invalid extended header, unexpected update flag data length\n");
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ /* Crc flag */
+ if (frame_ext_header->flags & ID3V24_EXT_CRC_FLAG) {
+ if (input->read (input, buf, 1) == 1) {
+ uint8_t data_length = buf[0];
+ if (data_length != 5) {
+ lprintf(
+ "id3: invalid extended header, unexpected crc flag data length\n");
+ return 0;
+ }
+ if (input->read (input, buf, data_length) == data_length) {
+ /* ignore crc */
+ frame_ext_header->crc = BE_35_unsynch(buf);
+ }
+ } else {
+ return 0;
+ }
+ }
+ /* Restriction flag */
+ if (frame_ext_header->flags & ID3V24_EXT_RESTRICTIONS_FLAG) {
+ if (input->read (input, buf, 1) == 1) {
+ uint8_t data_length = buf[0];
+ if (data_length != 1) {
+ lprintf(
+ "id3: invalid extended header, unexpected restriction flag data length\n");
+ return 0;
+ }
+ if (input->read (input, buf, data_length) == data_length) {
+ /* ignore restrictions */
+ frame_ext_header->restrictions = buf[0];
+ }
+ } else {
+ return 0;
+ }
+ }
+ } else {
+ return 0;
+ }
+ lprintf("ext header: size: %d, flags: %X, crc: %d, restrictions: %8X\n",
+ frame_ext_header->size, frame_ext_header->flags,
+ frame_ext_header->crc, frame_ext_header->restrictions);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static int id3v24_interp_frame(input_plugin_t *input,
+ xine_stream_t *stream,
+ id3v24_frame_header_t *frame_header) {
+ char buf[4096];
+ int enc;
+
+ if (frame_header->size >= 4096) {
+ lprintf("too long\n");
+ return 1;
+ }
+
+ if (input->read (input, buf, frame_header->size) == frame_header->size) {
+ buf[frame_header->size] = 0;
+ enc = buf[0];
+ if( enc >= ID3_ENCODING_COUNT )
+ enc = 0;
+
+ switch (frame_header->id) {
+ case ( FOURCC_TAG('T', 'C', 'O', 'N') ):
+ {
+ char tmp[1024];
+
+ if (id3v2_parse_genre(tmp, buf + 1, 1024)) {
+ _x_meta_info_set(stream, XINE_META_INFO_GENRE, tmp);
+ }
+ }
+ break;
+
+ case ( FOURCC_TAG('T', 'I', 'T', '2') ):
+ _x_meta_info_set_generic(stream, XINE_META_INFO_TITLE, buf + 1, id3_encoding[enc]);
+ break;
+
+ case ( FOURCC_TAG('T', 'P', 'E', '1') ):
+ _x_meta_info_set_generic(stream, XINE_META_INFO_ARTIST, buf + 1, id3_encoding[enc]);
+ break;
+
+ case ( FOURCC_TAG('T', 'A', 'L', 'B') ):
+ _x_meta_info_set_generic(stream, XINE_META_INFO_ALBUM, buf + 1, id3_encoding[enc]);
+ break;
+
+ case ( FOURCC_TAG('T', 'Y', 'E', 'R') ):
+ _x_meta_info_set_generic(stream, XINE_META_INFO_YEAR, buf + 1, id3_encoding[enc]);
+ break;
+
+ case ( FOURCC_TAG('C', 'O', 'M', 'M') ):
+ _x_meta_info_set_generic(stream, XINE_META_INFO_COMMENT, buf + 1 + 3, id3_encoding[enc]);
+ break;
+
+ case ( FOURCC_TAG('T', 'R', 'C', 'K') ):
+ _x_meta_info_set(stream, XINE_META_INFO_TRACK_NUMBER, buf + 1);
+ break;
+
+ default:
+ lprintf("unhandled frame\n");
+ }
+
+ return 1;
+ } else {
+ lprintf("read error\n");
+ return 0;
+ }
+}
+
+int id3v24_parse_tag(input_plugin_t *input,
+ xine_stream_t *stream,
+ int8_t *mp3_frame_header) {
+ id3v2_header_t tag_header;
+ id3v24_frame_header_t tag_frame_header;
+ id3v24_frame_ext_header_t tag_frame_ext_header;
+ int pos = 0;
+
+ if (id3v2_parse_header(input, mp3_frame_header, &tag_header)) {
+
+ if (tag_header.flags & ID3V24_ZERO_FLAG) {
+ /* invalid flags */
+ xprintf(stream->xine, XINE_VERBOSITY_DEBUG,
+ "id3: invalid header flags\n");
+ input->seek (input, tag_header.size - pos, SEEK_CUR);
+ return 0;
+ }
+ if (tag_header.flags & ID3V24_UNSYNCH_FLAG) {
+ /* it just means that all frames must be flagged UNSYNCH */
+ }
+ if (tag_header.flags & ID3V24_EXPERIMENTAL_FLAG) {
+ /* it just means that the tag is in a experimental stage */
+ }
+ if (tag_header.flags & ID3V24_EXT_HEADER_FLAG) {
+ /* extended header */
+ if (!id3v24_parse_ext_header(input, &tag_frame_ext_header)) {
+ return 0;
+ }
+ }
+ /* frame parsing */
+ while ((pos + ID3V24_FRAME_HEADER_SIZE) <= tag_header.size) {
+ if (id3v24_parse_frame_header(input, &tag_frame_header)) {
+ pos += ID3V24_FRAME_HEADER_SIZE;
+ if (tag_frame_header.id && tag_frame_header.size) {
+ if ((pos + tag_frame_header.size) <= tag_header.size) {
+ if (!id3v24_interp_frame(input, stream, &tag_frame_header)) {
+ xprintf(stream->xine, XINE_VERBOSITY_DEBUG,
+ "id3: invalid frame content\n");
+ }
+ } else {
+ xprintf(stream->xine, XINE_VERBOSITY_DEBUG,
+ "id3: invalid frame header\n");
+ input->seek (input, tag_header.size - pos, SEEK_CUR);
+ return 1;
+ }
+ pos += tag_frame_header.size;
+ } else {
+ /* end of frames, the rest is padding */
+ input->seek (input, tag_header.size - pos, SEEK_CUR);
+ return 1;
+ }
+ } else {
+ xprintf(stream->xine, XINE_VERBOSITY_DEBUG,
+ "id3: id3v2_parse_frame_header problem\n");
+ return 0;
+ }
+ }
+ if (tag_header.flags & ID3V24_FOOTER_FLAG) {
+ /* ignore footer */
+ input->seek (input, ID3V24_FOOTER_SIZE, SEEK_CUR);
+ }
+ return 1;
+ } else {
+ xprintf(stream->xine, XINE_VERBOSITY_DEBUG, "id3v23: id3v2_parse_header problem\n");
+ return 0;
+ }
+}
diff --git a/src/demuxers/id3.h b/src/demuxers/id3.h
index 570663e9c..2d8970ea7 100644
--- a/src/demuxers/id3.h
+++ b/src/demuxers/id3.h
@@ -19,10 +19,9 @@
*
* ID3 tag parser
*
- * Supported versions: v1, v2.2
- * TODO: v2.3, v2.4
+ * Supported versions: v1, v1.1, v2.2, v2.3, v2.4
*
- * $Id: id3.h,v 1.3 2003/12/08 23:20:16 tmattern Exp $
+ * $Id: id3.h,v 1.4 2005/09/15 18:45:15 tmattern Exp $
*/
#ifndef ID3_H
@@ -34,9 +33,10 @@
/* id3v2 */
#define FOURCC_TAG BE_FOURCC
-#define ID3V22_TAG FOURCC_TAG('I', 'D', '3', 2) /* id3 v2.2 tags */
-#define ID3V23_TAG FOURCC_TAG('I', 'D', '3', 3) /* id3 v2.3 tags */
-#define ID3V24_TAG FOURCC_TAG('I', 'D', '3', 4) /* id3 v2.4 tags */
+#define ID3V22_TAG FOURCC_TAG('I', 'D', '3', 2) /* id3 v2.2 header tag */
+#define ID3V23_TAG FOURCC_TAG('I', 'D', '3', 3) /* id3 v2.3 header tag */
+#define ID3V24_TAG FOURCC_TAG('I', 'D', '3', 4) /* id3 v2.4 header tag */
+#define ID3V24_FOOTER_TAG FOURCC_TAG('3', 'D', 'I', 0) /* id3 v2.4 footer tag */
/*
* ID3 v2.2
@@ -78,6 +78,12 @@
#define ID3V24_FOOTER_FLAG 0x10
#define ID3V24_ZERO_FLAG 0x0F
+/* extended header */
+#define ID3V24_EXT_UPDATE_FLAG 0x40
+#define ID3V24_EXT_CRC_FLAG 0x20
+#define ID3V24_EXT_RESTRICTIONS_FLAG 0x10
+#define ID3V24_EXT_ZERO_FLAG 0x8F
+
/* frame header */
#define ID3V24_FRAME_HEADER_SIZE 10
#define ID3V24_FRAME_TAG_PRESERV_FLAG 0x4000
@@ -90,6 +96,10 @@
#define ID3V24_FRAME_DATA_LEN_FLAG 0x0001
#define ID3V24_FRAME_ZERO_FLAG 0x8FB0
+/* footer */
+#define ID3V24_FOOTER_SIZE 10
+
+
typedef struct {
uint32_t id;
uint8_t revision;
@@ -115,6 +125,21 @@ typedef struct {
uint32_t crc;
} id3v23_frame_ext_header_t;
+typedef id3v2_header_t id3v24_footer_t;
+
+typedef struct {
+ uint32_t id;
+ uint32_t size;
+ uint16_t flags;
+} id3v24_frame_header_t;
+
+typedef struct {
+ uint32_t size;
+ uint8_t flags;
+ uint32_t crc;
+ uint8_t restrictions;
+} id3v24_frame_ext_header_t;
+
typedef struct {
char tag[3];
char title[30];
@@ -135,5 +160,8 @@ int id3v23_parse_tag(input_plugin_t *input,
xine_stream_t *stream,
int8_t *mp3_frame_header);
+int id3v24_parse_tag(input_plugin_t *input,
+ xine_stream_t *stream,
+ int8_t *mp3_frame_header);
#endif /* ID3_H */