Missing tags in APE is normal situation, so don't print an error.
[ffmpeg.git] / libavformat / ape.c
index 3368714..9d7ee6e 100644 (file)
 
 #include "avformat.h"
 
+#define ENABLE_DEBUG 0
+
 /* The earliest and latest file formats supported by this library */
-#define APE_MIN_VERSION 3970
+#define APE_MIN_VERSION 3950
 #define APE_MAX_VERSION 3990
 
 #define MAC_FORMAT_FLAG_8_BIT                 1 // is 8-bit [OBSOLETE]
 
 #define APE_EXTRADATA_SIZE 6
 
+/* APE tags */
+#define APE_TAG_VERSION               2000
+#define APE_TAG_FOOTER_BYTES          32
+#define APE_TAG_FLAG_CONTAINS_HEADER  (1 << 31)
+#define APE_TAG_FLAG_IS_HEADER        (1 << 29)
+
+#define TAG(name, field)  {name, offsetof(AVFormatContext, field), sizeof(((AVFormatContext *)0)->field)}
+
+static const struct {
+    const char *name;
+    int offset;
+    int size;
+} tags[] = {
+    TAG("Title"    , title    ),
+    TAG("Artist"   , author   ),
+    TAG("Copyright", copyright),
+    TAG("Comment"  , comment  ),
+    TAG("Album"    , album    ),
+    TAG("Year"     , year     ),
+    TAG("Track"    , track    ),
+    TAG("Genre"    , genre    ),
+    { NULL }
+};
+
 typedef struct {
     int64_t pos;
     int nblocks;
@@ -82,6 +108,102 @@ typedef struct {
     uint32_t *seektable;
 } APEContext;
 
+static void ape_tag_read_field(AVFormatContext *s)
+{
+    ByteIOContext *pb = s->pb;
+    uint8_t buf[1024];
+    uint32_t size;
+    int i;
+
+    memset(buf, 0, 1024);
+    size = get_le32(pb);  /* field size */
+    url_fskip(pb, 4);     /* skip field flags */
+
+    for (i=0; pb->buf_ptr[i]!='0' && pb->buf_ptr[i]>=0x20 && pb->buf_ptr[i]<=0x7E; i++);
+
+    get_buffer(pb, buf, FFMIN(i, 1024));
+    url_fskip(pb, 1);
+
+    for (i=0; tags[i].name; i++)
+        if (!strcmp (buf, tags[i].name)) {
+            if (tags[i].size == sizeof(int)) {
+                char tmp[16];
+                get_buffer(pb, tmp, FFMIN(sizeof(tmp), size));
+                *(int *)(((char *)s)+tags[i].offset) = atoi(tmp);
+            } else {
+                get_buffer(pb, ((char *)s) + tags[i].offset,
+                           FFMIN(tags[i].size, size));
+            }
+            break;
+        }
+
+    if (!tags[i].name)
+        url_fskip(pb, size);
+}
+
+static void ape_parse_tag(AVFormatContext *s)
+{
+    ByteIOContext *pb = s->pb;
+    int file_size = url_fsize(pb);
+    uint32_t val, fields, tag_bytes;
+    uint8_t buf[8];
+    int i;
+
+    if (file_size < APE_TAG_FOOTER_BYTES)
+        return;
+
+    url_fseek(pb, file_size - APE_TAG_FOOTER_BYTES, SEEK_SET);
+
+    get_buffer(pb, buf, 8);    /* APETAGEX */
+    if (strncmp(buf, "APETAGEX", 8)) {
+        return;
+    }
+
+    val = get_le32(pb);        /* APE tag version */
+    if (val > APE_TAG_VERSION) {
+        av_log(NULL, AV_LOG_ERROR, "Unsupported tag version. (>=%d)\n", APE_TAG_VERSION);
+        return;
+    }
+
+    tag_bytes = get_le32(pb);  /* tag size */
+    if (tag_bytes - APE_TAG_FOOTER_BYTES > (1024 * 1024 * 16)) {
+        av_log(NULL, AV_LOG_ERROR, "Tag size is way too big\n");
+        return;
+    }
+
+    fields = get_le32(pb);     /* number of fields */
+    if (fields > 65536) {
+        av_log(NULL, AV_LOG_ERROR, "Too many tag fields (%d)\n", fields);
+        return;
+    }
+
+    val = get_le32(pb);        /* flags */
+    if (val & APE_TAG_FLAG_IS_HEADER) {
+        av_log(NULL, AV_LOG_ERROR, "APE Tag is a header\n");
+        return;
+    }
+
+    if (val & APE_TAG_FLAG_CONTAINS_HEADER)
+        tag_bytes += 2*APE_TAG_FOOTER_BYTES;
+
+    url_fseek(pb, file_size - tag_bytes, SEEK_SET);
+
+    for (i=0; i<fields; i++)
+        ape_tag_read_field(s);
+
+#if ENABLE_DEBUG
+    av_log(NULL, AV_LOG_DEBUG, "\nAPE Tags:\n\n");
+    av_log(NULL, AV_LOG_DEBUG, "title     = %s\n", s->title);
+    av_log(NULL, AV_LOG_DEBUG, "author    = %s\n", s->author);
+    av_log(NULL, AV_LOG_DEBUG, "copyright = %s\n", s->copyright);
+    av_log(NULL, AV_LOG_DEBUG, "comment   = %s\n", s->comment);
+    av_log(NULL, AV_LOG_DEBUG, "album     = %s\n", s->album);
+    av_log(NULL, AV_LOG_DEBUG, "year      = %d\n", s->year);
+    av_log(NULL, AV_LOG_DEBUG, "track     = %d\n", s->track);
+    av_log(NULL, AV_LOG_DEBUG, "genre     = %s\n", s->genre);
+#endif
+}
+
 static int ape_probe(AVProbeData * p)
 {
     if (p->buf[0] == 'M' && p->buf[1] == 'A' && p->buf[2] == 'C' && p->buf[3] == ' ')
@@ -92,6 +214,7 @@ static int ape_probe(AVProbeData * p)
 
 static void ape_dumpinfo(APEContext * ape_ctx)
 {
+#if ENABLE_DEBUG
     int i;
 
     av_log(NULL, AV_LOG_DEBUG, "Descriptor Block:\n\n");
@@ -141,11 +264,12 @@ static void ape_dumpinfo(APEContext * ape_ctx)
     av_log(NULL, AV_LOG_DEBUG, "junklength           = %d\n", ape_ctx->junklength);
     av_log(NULL, AV_LOG_DEBUG, "firstframe           = %d\n", ape_ctx->firstframe);
     av_log(NULL, AV_LOG_DEBUG, "totalsamples         = %d\n", ape_ctx->totalsamples);
+#endif
 }
 
 static int ape_read_header(AVFormatContext * s, AVFormatParameters * ap)
 {
-    ByteIOContext *pb = &s->pb;
+    ByteIOContext *pb = s->pb;
     APEContext *ape = s->priv_data;
     AVStream *st;
     uint32_t tag;
@@ -280,6 +404,12 @@ static int ape_read_header(AVFormatContext * s, AVFormatParameters * ap)
 
     ape_dumpinfo(ape);
 
+    /* try to read APE tags */
+    if (!url_is_streamed(pb)) {
+        ape_parse_tag(s);
+        url_fseek(pb, 0, SEEK_SET);
+    }
+
     av_log(s, AV_LOG_DEBUG, "Decoding file - v%d.%02d, compression level %d\n", ape->fileversion / 1000, (ape->fileversion % 1000) / 10, ape->compressiontype);
 
     /* now we are ready: build format streams */
@@ -325,12 +455,12 @@ static int ape_read_packet(AVFormatContext * s, AVPacket * pkt)
     APEContext *ape = s->priv_data;
     uint32_t extra_size = 8;
 
-    if (url_feof(&s->pb))
+    if (url_feof(s->pb))
         return AVERROR_IO;
     if (ape->currentframe > ape->totalframes)
         return AVERROR_IO;
 
-    url_fseek (&s->pb, ape->frames[ape->currentframe].pos, SEEK_SET);
+    url_fseek (s->pb, ape->frames[ape->currentframe].pos, SEEK_SET);
 
     /* Calculate how many blocks there are in this frame */
     if (ape->currentframe == (ape->totalframes - 1))
@@ -343,7 +473,7 @@ static int ape_read_packet(AVFormatContext * s, AVPacket * pkt)
 
     AV_WL32(pkt->data    , nblocks);
     AV_WL32(pkt->data + 4, ape->frames[ape->currentframe].skip);
-    ret = get_buffer(&s->pb, pkt->data + extra_size, ape->frames[ape->currentframe].size);
+    ret = get_buffer(s->pb, pkt->data + extra_size, ape->frames[ape->currentframe].size);
 
     pkt->pts = ape->frames[ape->currentframe].pts;
     pkt->stream_index = 0;