asf: register new fields (container, sampling_rate)
[platform/upstream/lightmediascanner.git] / src / plugins / asf / asf.c
index 88bb3df..1cb6dea 100644 (file)
  * @brief
  *
  * asf/wma file parser.
+ *
+ * Reference:
+ *   http://www.microsoft.com/en-us/download/details.aspx?id=14995
  */
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#define _XOPEN_SOURCE 600
-#define _BSD_SOURCE
 #include <lightmediascanner_plugin.h>
 #include <lightmediascanner_db.h>
+#include <shared/util.h>
+
 #include <endian.h>
 #include <errno.h>
 #include <sys/types.h>
 #include <string.h>
 #include <unistd.h>
 
-#define NSEC100_PER_SEC  10000000ULL
-#define MSEC_PER_SEC  1000ULL
+#define DECL_STR(cname, str)                                            \
+    static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
 
-enum StreamTypes {
-    STREAM_TYPE_UNKNOWN = 0,
-    STREAM_TYPE_AUDIO,
-    STREAM_TYPE_VIDEO
-};
+DECL_STR(_codec_audio_wmav1, "wmav1");
+DECL_STR(_codec_audio_wmav2, "wmav2");
+DECL_STR(_codec_audio_wmavpro, "wmavpro");
+DECL_STR(_codec_audio_wmavlossless, "wmavlossless");
+DECL_STR(_codec_audio_aac, "aac");
+DECL_STR(_codec_audio_flac, "flac");
+DECL_STR(_codec_audio_mp3, "mp3");
+
+DECL_STR(_codec_video_wmv1, "wmv1");
+DECL_STR(_codec_video_wmv2, "wmv2");
+DECL_STR(_codec_video_wmv3, "wmv3");
+
+DECL_STR(_dlna_wma_base, "WMABASE");
+DECL_STR(_dlna_wma_full, "WMAFULL");
+DECL_STR(_dlna_wma_pro, "WMAPRO");
+DECL_STR(_dlna_wma_mime, "audio/x-ms-wma");
+
+DECL_STR(_str_unknown, "<UNKNOWN>");
+#undef DECL_STR
+
+static void
+_fill_audio_dlna_profile(struct lms_audio_info *info)
+{
+    if ((info->codec.str == _codec_audio_wmav1.str ||
+         info->codec.str == _codec_audio_wmav2.str) &&
+        (info->sampling_rate <= 48000)) {
+        info->dlna_mime = _dlna_wma_mime;
+        if (info->bitrate <= 192999)
+            info->dlna_profile = _dlna_wma_base;
+        else
+            info->dlna_profile = _dlna_wma_full;
+    } else if (
+        info->codec.str == _codec_audio_wmavpro.str &&
+        info->sampling_rate <= 96000 &&
+        info->channels <= 8 &&
+        info->bitrate <= 1500000) {
+        info->dlna_mime = _dlna_wma_mime;
+        info->dlna_profile = _dlna_wma_pro;
+    }
+}
 
 enum AttributeTypes {
     ATTR_TYPE_UNICODE = 0,
@@ -61,12 +95,25 @@ enum AttributeTypes {
     ATTR_TYPE_GUID
 };
 
+struct stream {
+    struct lms_stream base;
+    struct {
+        unsigned int sampling_rate;
+        unsigned int bitrate;
+        double framerate;
+    } priv;
+};
+
 struct asf_info {
     struct lms_string_size title;
     struct lms_string_size artist;
     struct lms_string_size album;
     struct lms_string_size genre;
+    enum lms_stream_type type;
+    unsigned int length;
     unsigned char trackno;
+
+    struct stream *streams;
 };
 
 struct plugin {
@@ -93,38 +140,38 @@ static const char *_authors[] = {
     NULL
 };
 
-struct stream {
-    int id;
-    enum StreamTypes type;
-    struct stream *next;
-    union {
-        struct {
-            uint16_t codec_id;
-            uint16_t channels;
-            uint32_t sampling_rate;
-            uint32_t byterate;
-        } audio;
-    };
+/* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
+static const struct {
+    uint16_t id;
+    const struct lms_string_size *name;
+} _audio_codecs[] = {
+    /* id == 0  is special, check callers if it's needed */
+    { 0x0160, &_codec_audio_wmav1 },
+    { 0x0161, &_codec_audio_wmav2 },
+    { 0x0162, &_codec_audio_wmavpro },
+    { 0x0163, &_codec_audio_wmavlossless },
+    { 0x1600, &_codec_audio_aac },
+    { 0x706d, &_codec_audio_aac },
+    { 0x4143, &_codec_audio_aac },
+    { 0xA106, &_codec_audio_aac },
+    { 0xF1AC, &_codec_audio_flac },
+    { 0x0055, &_codec_audio_mp3 },
+    { 0x0, &_str_unknown }
 };
 
 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
 static const struct {
-    uint16_t id;
-    struct lms_string_size name;
-} _codecs[] = {
-    { 0x0160, LMS_STATIC_STRING_SIZE("wmav1") },
-    { 0x0161, LMS_STATIC_STRING_SIZE("wmav2") },
-    { 0x0162, LMS_STATIC_STRING_SIZE("wmavpro") },
-    { 0x0163, LMS_STATIC_STRING_SIZE("wmavlossless") },
-    { 0x1600, LMS_STATIC_STRING_SIZE("aac") },
-    { 0x706d, LMS_STATIC_STRING_SIZE("aac") },
-    { 0x4143, LMS_STATIC_STRING_SIZE("aac") },
-    { 0xA106, LMS_STATIC_STRING_SIZE("aac") },
-    { 0xF1AC, LMS_STATIC_STRING_SIZE("flac") },
-    { 0x0055, LMS_STATIC_STRING_SIZE("mp3") },
-    { }
+    uint8_t id[4];
+    const struct lms_string_size *name;
+} _video_codecs[] = {
+    /* id == 0  is special, check callers if it's needed */
+    { "WMV1", &_codec_video_wmv1 },
+    { "WMV2", &_codec_video_wmv2 },
+    { "WMV3", &_codec_video_wmv3 },
+    { "XXXX", &_str_unknown }
 };
 
+
 /* ASF GUIDs
  *
  * Microsoft defines these 16-byte (128-bit) GUIDs as:
@@ -140,13 +187,15 @@ static const struct {
  * See http://www.microsoft.com/windows/windowsmedia/forpros/format/asfspec.aspx
  */
 static const char header_guid[16] = "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
+static const char header_extension_guid[16] = "\xB5\x03\xBF\x5F\x2E\xA9\xCF\x11\x8E\xE3\x00\xC0\x0C\x20\x53\x65";
+static const char extended_stream_properties_guid[16] = "\xCB\xA5\xE6\x14\x72\xC6\x32\x43\x83\x99\xA9\x69\x52\x06\x5B\x5A";
+static const char language_list_guid[16] = "\xA9\x46\x43\x7C\xE0\xEF\xFC\x4B\xB2\x29\x39\x3E\xDE\x41\x5C\x85";
 static const char file_properties_guid[16] = "\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65";
 static const char stream_properties_guid[16] = "\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65";
 static const char stream_type_audio_guid[16] = "\x40\x9E\x69\xF8\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
 static const char stream_type_video_guid[16] = "\xC0\xEF\x19\xBC\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
 static const char content_description_guid[16] = "\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
 static const char extended_content_description_guid[16] = "\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50";
-static const char header_extension_guid[16] = "\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se";
 static const char metadata_guid[16] = "\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA";
 static const char metadata_library_guid[16] = "\224\034#D\230\224\321I\241A\x1d\x13NEpT";
 static const char content_encryption_object_guid[16] = "\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E";
@@ -225,8 +274,7 @@ _read_string(int fd, size_t count, char **str, unsigned int *len)
 }
 
 static int
-_parse_file_properties(lms_charset_conv_t *cs_conv, int fd,
-                       struct lms_audio_info *info)
+_parse_file_properties(int fd, struct asf_info *info)
 {
     struct {
         char fileid[16];
@@ -253,26 +301,90 @@ _parse_file_properties(lms_charset_conv_t *cs_conv, int fd,
 
     /* ASF spec 01.20.06 sec. 3.2: we need to subtract the preroll value from
      * the duration in order to obtain the real duration */
-    info->length = (unsigned int)((le64toh(props.play_duration) / NSEC100_PER_SEC)
-                                  - le64toh(props.preroll) / MSEC_PER_SEC);
+    info->length = (unsigned int)(
+        (le64toh(props.play_duration) / NSEC100_PER_SEC) -
+        le64toh(props.preroll) / MSEC_PER_SEC);
 
     return r;
 }
 
-static struct lms_string_size
-_codec_id_to_str(uint16_t id)
+static const struct lms_string_size *
+_audio_codec_id_to_str(uint16_t id)
 {
     unsigned int i;
 
-    for (i = 0; _codecs[i].name.str != NULL; i++)
-        if (_codecs[i].id == id)
-            return _codecs[i].name;
+    for (i = 0; _audio_codecs[i].name != &_str_unknown; i++)
+        if (_audio_codecs[i].id == id)
+            return _audio_codecs[i].name;
+
+    return _audio_codecs[i].name;
+}
+
+static const struct lms_string_size *
+_video_codec_id_to_str(uint8_t id[4])
+{
+    unsigned int i;
+
+    for (i = 0; _video_codecs[i].name != &_str_unknown; i++)
+        if (memcmp(id, _video_codecs[i].id, 4) == 0)
+            return _video_codecs[i].name;
+
+    return _video_codecs[i].name;
+}
+
+static struct stream * _stream_get_or_create(struct asf_info *info,
+                                             unsigned int stream_id)
+{
+    struct stream *s;
+
+    for (s = info->streams; s; s = (struct stream *) s->base.next) {
+        if (s->base.stream_id == stream_id)
+            return s;
+    }
 
-    return _codecs[i].name;
+    s = calloc(1, sizeof(*s));
+    if (!s)
+        return NULL;
+
+    /* The Stream Properties Object can be anywhere inside the Header Object:
+     * before the Header Extension Object, after it or embedded into the
+     * Extended Stream Properties, inside the Header Extension Object.
+     *
+     * When parsing we either create a new stream and prepend it to the list or
+     * we return the one already created by a previous object (see the loop
+     * above).
+     *
+     * Note that the stream type is only available in the Stream Properties
+     * Object. A file with an Extended Stream Properties Object referring to a
+     * stream that doesn't have a corresponding Stream Properties is invalid. We
+     * let it into the list, but it won't have the stream_type set. In this case
+     * LMS will end up ignoring the stream when we try to add the file in the
+     * database -- this is why we set type to -1 here */
+    s->base.stream_id = stream_id;
+    s->base.type = -1;
+    s->base.next = (struct lms_stream *) info->streams;
+    info->streams = s;
+
+    return s;
+}
+
+static void _stream_copy_extension_properties(struct stream *s)
+{
+    switch (s->base.type) {
+    case LMS_STREAM_TYPE_AUDIO:
+        s->base.audio.bitrate = s->priv.bitrate;
+        break;
+    case LMS_STREAM_TYPE_VIDEO:
+        s->base.video.bitrate = s->priv.bitrate;
+        s->base.video.framerate = s->priv.framerate;
+        break;
+    default:
+        break;
+    }
 }
 
 static int
-_parse_stream_properties(int fd, struct stream **pstream)
+_parse_stream_properties(int fd, struct asf_info *info)
 {
     struct {
         char stream_type[16];
@@ -282,70 +394,173 @@ _parse_stream_properties(int fd, struct stream **pstream)
         uint32_t error_correction_data_len;
         uint16_t flags;
         uint32_t reserved; /* don't use, unaligned */
-    }  __attribute__((packed)) props;
-
-    int r;
+    } __attribute__((packed)) props;
+    unsigned int stream_id;
     struct stream *s;
-
-    *pstream = NULL;
-
-    s = calloc(1, sizeof(struct stream));
-    if (!s)
-        return -ENOMEM;
+    int r, type;
 
     r = read(fd, &props, sizeof(props));
     if (r != sizeof(props))
-        goto done;
+        return r;
+
+    stream_id = le16toh(props.flags) & 0x7F;
+
+    /* Not a valid stream */
+    if (!stream_id)
+        return r;
 
     if (memcmp(props.stream_type, stream_type_audio_guid, 16) == 0)
-        s->type = STREAM_TYPE_AUDIO;
+        type = LMS_STREAM_TYPE_AUDIO;
     else if (memcmp(props.stream_type, stream_type_video_guid, 16) == 0)
-        s->type = STREAM_TYPE_VIDEO;
-    else {
+        type = LMS_STREAM_TYPE_VIDEO;
+    else
         /* ignore stream */
-        goto done;
-    }
+        return r;
 
-    s->id = le16toh(props.flags) & 0x7F;
-    /* Not a valid stream */
-    if (!s->id)
-        goto done;
+    s = _stream_get_or_create(info, stream_id);
+    if (!s)
+        return -ENOMEM;
+
+    s->base.type = type;
 
-    if (s->type == STREAM_TYPE_AUDIO) {
+    if (s->base.type == LMS_STREAM_TYPE_AUDIO) {
         if (le32toh(props.type_specific_len) < 18)
             goto done;
 
-        s->audio.codec_id = _read_word(fd);
-        s->audio.channels = _read_word(fd);
-        s->audio.sampling_rate = _read_dword(fd);
-        s->audio.byterate = _read_dword(fd);
-        r += 12;
-    }
+        s->base.codec = *_audio_codec_id_to_str(_read_word(fd));
+        s->base.audio.channels = _read_word(fd);
+        s->priv.sampling_rate = _read_dword(fd);
+        s->base.audio.sampling_rate = s->priv.sampling_rate;
+        s->base.audio.bitrate = _read_dword(fd) * 8;
+    } else {
+        struct {
+            uint32_t width_unused;
+            uint32_t height_unused;
+            uint8_t reserved;
+            uint16_t data_size_unused;
+            /* data */
+            uint32_t size;
+            uint32_t width;
+            uint32_t height;
+            uint16_t reseved2;
+            uint16_t bits_per_pixel;
+            uint8_t compression_id[4];
+            uint32_t image_size;
+
+            /* other fields are ignored */
+        } __attribute__((packed)) video;
+        unsigned int num, den;
+
+        r = read(fd, &video, sizeof(video));
+        if (r != sizeof(video))
+            goto done;
 
-    *pstream = s;
+        if ((unsigned int) r < get_le32(&video.size) -
+            (sizeof(video) - offsetof(typeof(video), width)))
+            goto done;
 
-    return r;
+        s->base.codec = *_video_codec_id_to_str(video.compression_id);
+        s->base.video.width = get_le32(&video.width);
+        s->base.video.height = get_le32(&video.height);
+
+        reduce_gcd(s->base.video.width, s->base.video.height, &num, &den);
+        asprintf(&s->base.video.aspect_ratio.str, "%u:%u", num, den);
+        s->base.video.aspect_ratio.len = s->base.video.aspect_ratio.str ?
+            strlen(s->base.video.aspect_ratio.str) : 0;
+    }
+
+    _stream_copy_extension_properties(s);
 
 done:
-    free(s);
+    /* If there's any video stream, consider the file as video */
+    if (info->type != LMS_STREAM_TYPE_VIDEO)
+        info->type = s->base.type;
+
     return r;
 }
 
-static void
-_parse_content_description(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
+static int _parse_extended_stream_properties(lms_charset_conv_t *cs_conv,
+                                             int fd, struct asf_info *info)
+{
+    struct {
+        uint64_t start_time;
+        uint64_t end_time;
+        uint32_t data_bitrate;
+        uint32_t buffer_size;
+        uint32_t init_buffer_fullness;
+        uint32_t alt_data_bitrate;
+        uint32_t alt_buffer_size;
+        uint32_t alt_init_buffer_fullness;
+        uint32_t max_obj_size;
+        uint32_t flags;
+        uint16_t stream_id;
+        uint16_t lang_id;
+        uint64_t avg_time_per_frame;
+        uint16_t stream_name_count;
+        uint16_t payload_extension_system_count;
+    } __attribute__((packed)) props;
+    struct stream *s;
+    unsigned int stream_id;
+    uint32_t bitrate;
+    uint16_t n;
+    int r;
+
+    r = read(fd, &props, sizeof(props));
+    if (r != sizeof(props))
+        return r;
+
+    stream_id = get_le16(&props.stream_id);
+    s = _stream_get_or_create(info, stream_id);
+
+    bitrate = get_le32(&props.alt_data_bitrate); /* for vbr */
+    if (!bitrate)
+        bitrate = get_le32(&props.data_bitrate);
+    s->priv.bitrate = bitrate;
+    s->priv.framerate = (NSEC100_PER_SEC /
+                         (double) get_le64(&props.avg_time_per_frame));
+    for (n = get_le16(&props.stream_name_count); n; n--) {
+        uint16_t j;
+        lseek(fd, 2, SEEK_CUR);
+        j = _read_word(fd);
+        lseek(fd, j, SEEK_CUR);
+    }
+    for (n = get_le16(&props.payload_extension_system_count); n; n--) {
+        uint32_t j;
+        lseek(fd, 18, SEEK_CUR);
+        j = _read_dword(fd);
+        lseek(fd, j, SEEK_CUR);
+    }
+
+    return 0;
+}
+
+/* Lazy implementation, let the parsing of subframes to the caller. Techically
+ * this is wrong, since it might parse objects in the extension header that
+ * should be in the header object, however this should parse ok all good files
+ * and eventually the bad ones. */
+static int _parse_header_extension(lms_charset_conv_t *cs_conv, int fd,
+                                   struct asf_info *info)
+{
+    lseek(fd, 22, SEEK_CUR);
+    return 0;
+}
+
+static int
+_parse_content_description(lms_charset_conv_t *cs_conv, int fd,
+                           struct asf_info *info)
 {
     int title_length = _read_word(fd);
     int artist_length = _read_word(fd);
-    int copyright_length = _read_word(fd);
-    int comment_length = _read_word(fd);
-    int rating_length = _read_word(fd);
+
+    lseek(fd, 6, SEEK_CUR);
 
     _read_string(fd, title_length, &info->title.str, &info->title.len);
     lms_charset_conv_force(cs_conv, &info->title.str, &info->title.len);
     _read_string(fd, artist_length, &info->artist.str, &info->artist.len);
     lms_charset_conv_force(cs_conv, &info->artist.str, &info->artist.len);
+
     /* ignore copyright, comment and rating */
-    lseek(fd, copyright_length + comment_length + rating_length, SEEK_CUR);
+    return 1;
 }
 
 static void
@@ -408,13 +623,15 @@ _skip_attribute_data(int fd, int kind, int attr_type, int attr_size)
     }
 }
 
-static void
-_parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
+static int
+_parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd,
+                                           struct asf_info *info)
 {
     int count = _read_word(fd);
     char *attr_name;
     unsigned int attr_name_len;
     int attr_type, attr_size;
+
     while (count--) {
         attr_name = NULL;
         _parse_attribute_name(fd,
@@ -455,6 +672,8 @@ _parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd,
             _skip_attribute_data(fd, 0, attr_type, attr_size);
         free(attr_name);
     }
+
+    return 1;
 }
 
 static void *
@@ -469,17 +688,36 @@ _match(struct plugin *p, const char *path, int len, int base)
       return (void*)(i + 1);
 }
 
+static void streams_free(struct stream *streams)
+{
+    while (streams) {
+        struct stream *s = streams;
+        streams = (struct stream *) s->base.next;
+
+        switch (s->base.type) {
+        case LMS_STREAM_TYPE_VIDEO:
+            free(s->base.video.aspect_ratio.str);
+            break;
+        default:
+            break;
+        }
+
+        free(s);
+    }
+}
+
+/* TODO: Parse "Language List Object" (sec 4.6) which contains an array with all
+ * the languages used (they are in UTF-16, so they need to be properly
+ * converted). */
 static int
 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
 {
-    struct asf_info info = { };
-    struct lms_audio_info audio_info = { };
-    struct lms_video_info video_info = { };
-    int r, fd, num_objects, i;
+    struct asf_info info = { .type = LMS_STREAM_TYPE_UNKNOWN };
+    int r, fd;
     char guid[16];
     unsigned int size;
-    int stream_type = STREAM_TYPE_UNKNOWN;
-    struct stream *streams = NULL;
+    unsigned long long hdrsize;
+    off_t pos_end, pos = 0;
 
     fd = open(finfo->path, O_RDONLY);
     if (fd < 0) {
@@ -492,63 +730,64 @@ _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_in
         r = -2;
         goto done;
     }
+
     if (memcmp(guid, header_guid, 16) != 0) {
         fprintf(stderr, "ERROR: invalid header (%s).\n", finfo->path);
         r = -3;
         goto done;
     }
 
-    size = _read_qword(fd);
-    num_objects = _read_dword(fd);
+    hdrsize = _read_qword(fd);
+    pos_end = lseek(fd, 6, SEEK_CUR) - 24 + hdrsize;
 
-    lseek(fd, 2, SEEK_CUR);
+    while (1) {
+        if (!pos)
+            pos = lseek(fd, 0, SEEK_CUR);
+        if (pos > pos_end - 24)
+            break;
 
-    for (i = 0; i < num_objects; ++i) {
         read(fd, &guid, 16);
         size = _read_qword(fd);
 
-        if (memcmp(guid, file_properties_guid, 16) == 0) {
-            r = _parse_file_properties(plugin->cs_conv, fd, &audio_info);
-            if (r < 0)
-                goto done;
-            lseek(fd, size - (24 + r), SEEK_CUR);
-        } else if (memcmp(guid, stream_properties_guid, 16) == 0) {
-            struct stream *s;
-            r = _parse_stream_properties(fd, &s);
-            if (r < 0)
-                goto done;
-
-            lseek(fd, size - (24 + r), SEEK_CUR);
-            if (!s)
-                continue;
-
-            if (stream_type != STREAM_TYPE_VIDEO)
-                stream_type = s->type;
-
-            s->next = streams;
-            streams = s;
-        } else if (memcmp(guid, content_description_guid, 16) == 0)
-            _parse_content_description(plugin->cs_conv, fd, &info);
+        if (memcmp(guid, header_extension_guid, 16) == 0)
+            r = _parse_header_extension(plugin->cs_conv, fd, &info);
+        else if (memcmp(guid, extended_stream_properties_guid, 16) == 0)
+            r = _parse_extended_stream_properties(plugin->cs_conv, fd, &info);
+        else if (memcmp(guid, file_properties_guid, 16) == 0)
+            r = _parse_file_properties(fd, &info);
+        else if (memcmp(guid, stream_properties_guid, 16) == 0)
+            r = _parse_stream_properties(fd, &info);
+        else if (memcmp(guid, language_list_guid, 16) == 0)
+            r = 1;
+        else if (memcmp(guid, content_description_guid, 16) == 0)
+            r = _parse_content_description(plugin->cs_conv, fd, &info);
         else if (memcmp(guid, extended_content_description_guid, 16) == 0)
-            _parse_extended_content_description_object(plugin->cs_conv, fd, &info);
+            r = _parse_extended_content_description_object(plugin->cs_conv, fd,
+                                                           &info);
         else if (memcmp(guid, content_encryption_object_guid, 16) == 0 ||
-                 memcmp(guid, extended_content_encryption_object_guid, 16) == 0) {
+                 memcmp(guid, extended_content_encryption_object_guid, 16) == 0)
             /* ignore DRM'd files */
-            fprintf(stderr, "ERROR: ignoring DRM'd file %s\n", finfo->path);
             r = -4;
+        else
+            r = 1;
+
+        if (r < 0)
             goto done;
-        } else
-            lseek(fd, size - 24, SEEK_CUR);
+
+        if (r > 0)
+            pos = lseek(fd, pos + size, SEEK_SET);
+        else
+            pos = 0;
     }
 
     /* try to define stream type by extension */
-    if (stream_type == STREAM_TYPE_UNKNOWN) {
+    if (info.type == LMS_STREAM_TYPE_UNKNOWN) {
         long ext_idx = ((long)match) - 1;
         if (strcmp(_exts[ext_idx].str, ".wma") == 0)
-            stream_type = STREAM_TYPE_AUDIO;
+            info.type = LMS_STREAM_TYPE_AUDIO;
         /* consider wmv and asf as video */
         else
-            stream_type = STREAM_TYPE_VIDEO;
+            info.type = LMS_STREAM_TYPE_VIDEO;
     }
 
     lms_string_size_strip_and_free(&info.title);
@@ -556,54 +795,50 @@ _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_in
     lms_string_size_strip_and_free(&info.album);
     lms_string_size_strip_and_free(&info.genre);
 
-    if (!info.title.str) {
-        long ext_idx;
-        ext_idx = ((long)match) - 1;
-        info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
-        info.title.str = malloc((info.title.len + 1) * sizeof(char));
-        memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
-        info.title.str[info.title.len] = '\0';
-        lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
-    }
-
-#if 0
-    fprintf(stderr, "file %s info\n", finfo->path);
-    fprintf(stderr, "\ttitle='%s'\n", info.title.str);
-    fprintf(stderr, "\tartist='%s'\n", info.artist.str);
-    fprintf(stderr, "\talbum='%s'\n", info.album.str);
-    fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
-    fprintf(stderr, "\ttrackno=%d\n", info.trackno);
-#endif
+    if (!info.title.str)
+        info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
+                                                finfo->base,
+                                                &_exts[((long) match) - 1],
+                                                ctxt->cs_conv);
 
-    audio_info.container = _container;
+    if (info.type == LMS_STREAM_TYPE_AUDIO) {
+        struct lms_audio_info audio_info = { };
 
-    if (stream_type == STREAM_TYPE_AUDIO) {
         audio_info.id = finfo->id;
         audio_info.title = info.title;
         audio_info.artist = info.artist;
         audio_info.album = info.album;
         audio_info.genre = info.genre;
         audio_info.trackno = info.trackno;
+        audio_info.length = info.length;
+        audio_info.container = _container;
+
+        /* ignore additional streams, use only the first one */
+        if (info.streams) {
+            struct stream *s = info.streams;
+            audio_info.channels = s->base.audio.channels;
+            audio_info.bitrate = s->base.audio.bitrate;
+            audio_info.sampling_rate = s->priv.sampling_rate;
+            audio_info.codec = s->base.codec;
+        }
 
-        audio_info.channels = streams->audio.channels;
-        audio_info.bitrate = streams->audio.byterate * 8;
-        audio_info.sampling_rate = streams->audio.sampling_rate;
-        audio_info.codec = _codec_id_to_str(streams->audio.codec_id);
+        _fill_audio_dlna_profile(&audio_info);
 
         r = lms_db_audio_add(plugin->audio_db, &audio_info);
     } else {
+        struct lms_video_info video_info = { };
+
         video_info.id = finfo->id;
         video_info.title = info.title;
         video_info.artist = info.artist;
+        video_info.length = info.length;
+        video_info.container = _container;
+        video_info.streams = (struct lms_stream *) info.streams;
         r = lms_db_video_add(plugin->video_db, &video_info);
     }
 
 done:
-    while (streams) {
-        struct stream *s = streams;
-        streams = s->next;
-        free(s);
-    }
+    streams_free(info.streams);
 
     free(info.title.str);
     free(info.artist.str);