introduce and use helpers lms_string_size_strndup() and lms_string_size_dup()
[platform/upstream/lightmediascanner.git] / src / plugins / asf / asf.c
1 /**
2  * Copyright (C) 2008 by INdT
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
19  */
20
21 /**
22  * @brief
23  *
24  * asf/wma file parser.
25  *
26  * Reference:
27  *   http://www.microsoft.com/en-us/download/details.aspx?id=14995
28  */
29
30 #include <lightmediascanner_plugin.h>
31 #include <lightmediascanner_db.h>
32 #include <shared/util.h>
33
34 #include <endian.h>
35 #include <errno.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43
44 #define DECL_STR(cname, str)                                            \
45     static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
46
47 DECL_STR(_codec_audio_wmav1, "wmav1");
48 DECL_STR(_codec_audio_wmav2, "wmav2");
49 DECL_STR(_codec_audio_wmavpro, "wmavpro");
50 DECL_STR(_codec_audio_wmavlossless, "wmavlossless");
51 DECL_STR(_codec_audio_aac, "aac");
52 DECL_STR(_codec_audio_flac, "flac");
53 DECL_STR(_codec_audio_mp3, "mp3");
54
55 DECL_STR(_codec_video_wmv1, "wmv1");
56 DECL_STR(_codec_video_wmv2, "wmv2");
57 DECL_STR(_codec_video_wmv3, "wmv3");
58
59 DECL_STR(_dlna_wma_base, "WMABASE");
60 DECL_STR(_dlna_wma_full, "WMAFULL");
61 DECL_STR(_dlna_wma_pro, "WMAPRO");
62 DECL_STR(_dlna_wma_mime, "audio/x-ms-wma");
63
64 DECL_STR(_str_unknown, "<UNKNOWN>");
65 #undef DECL_STR
66
67 static void
68 _fill_audio_dlna_profile(struct lms_audio_info *info)
69 {
70     if ((info->codec.str == _codec_audio_wmav1.str ||
71          info->codec.str == _codec_audio_wmav2.str) &&
72         (info->sampling_rate <= 48000)) {
73         info->dlna_mime = _dlna_wma_mime;
74         if (info->bitrate <= 192999)
75             info->dlna_profile = _dlna_wma_base;
76         else
77             info->dlna_profile = _dlna_wma_full;
78     } else if (
79         info->codec.str == _codec_audio_wmavpro.str &&
80         info->sampling_rate <= 96000 &&
81         info->channels <= 8 &&
82         info->bitrate <= 1500000) {
83         info->dlna_mime = _dlna_wma_mime;
84         info->dlna_profile = _dlna_wma_pro;
85     }
86 }
87
88 enum AttributeTypes {
89     ATTR_TYPE_UNICODE = 0,
90     ATTR_TYPE_BYTES,
91     ATTR_TYPE_BOOL,
92     ATTR_TYPE_DWORD,
93     ATTR_TYPE_QWORD,
94     ATTR_TYPE_WORD,
95     ATTR_TYPE_GUID
96 };
97
98 struct stream {
99     struct lms_stream base;
100     struct {
101         unsigned int sampling_rate;
102         unsigned int bitrate;
103         double framerate;
104     } priv;
105 };
106
107 struct asf_info {
108     struct lms_string_size title;
109     struct lms_string_size artist;
110     struct lms_string_size album;
111     struct lms_string_size genre;
112     enum lms_stream_type type;
113     unsigned int length;
114     unsigned char trackno;
115
116     struct stream *streams;
117 };
118
119 struct plugin {
120     struct lms_plugin plugin;
121     lms_db_audio_t *audio_db;
122     lms_db_video_t *video_db;
123     lms_charset_conv_t *cs_conv;
124 };
125
126 static const char _name[] = "asf";
127 static const struct lms_string_size _container = LMS_STATIC_STRING_SIZE("asf");
128 static const struct lms_string_size _exts[] = {
129     LMS_STATIC_STRING_SIZE(".wma"),
130     LMS_STATIC_STRING_SIZE(".wmv"),
131     LMS_STATIC_STRING_SIZE(".asf")
132 };
133 static const char *_cats[] = {
134     "multimedia",
135     "audio",
136     NULL
137 };
138 static const char *_authors[] = {
139     "Andre Moreira Magalhaes",
140     NULL
141 };
142
143 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
144 static const struct {
145     uint16_t id;
146     const struct lms_string_size *name;
147 } _audio_codecs[] = {
148     /* id == 0  is special, check callers if it's needed */
149     { 0x0160, &_codec_audio_wmav1 },
150     { 0x0161, &_codec_audio_wmav2 },
151     { 0x0162, &_codec_audio_wmavpro },
152     { 0x0163, &_codec_audio_wmavlossless },
153     { 0x1600, &_codec_audio_aac },
154     { 0x706d, &_codec_audio_aac },
155     { 0x4143, &_codec_audio_aac },
156     { 0xA106, &_codec_audio_aac },
157     { 0xF1AC, &_codec_audio_flac },
158     { 0x0055, &_codec_audio_mp3 },
159     { 0x0, &_str_unknown }
160 };
161
162 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
163 static const struct {
164     uint8_t id[4];
165     const struct lms_string_size *name;
166 } _video_codecs[] = {
167     /* id == 0  is special, check callers if it's needed */
168     { "WMV1", &_codec_video_wmv1 },
169     { "WMV2", &_codec_video_wmv2 },
170     { "WMV3", &_codec_video_wmv3 },
171     { "XXXX", &_str_unknown }
172 };
173
174
175 /* ASF GUIDs
176  *
177  * Microsoft defines these 16-byte (128-bit) GUIDs as:
178  * first 8 bytes are in little-endian order
179  * next 8 bytes are in big-endian order
180  *
181  * Eg.: AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp:
182  *
183  * to convert to byte string do as follow:
184  *
185  * $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
186  *
187  * See http://www.microsoft.com/windows/windowsmedia/forpros/format/asfspec.aspx
188  */
189 static const char header_guid[16] = "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
190 static const char header_extension_guid[16] = "\xB5\x03\xBF\x5F\x2E\xA9\xCF\x11\x8E\xE3\x00\xC0\x0C\x20\x53\x65";
191 static const char extended_stream_properties_guid[16] = "\xCB\xA5\xE6\x14\x72\xC6\x32\x43\x83\x99\xA9\x69\x52\x06\x5B\x5A";
192 static const char language_list_guid[16] = "\xA9\x46\x43\x7C\xE0\xEF\xFC\x4B\xB2\x29\x39\x3E\xDE\x41\x5C\x85";
193 static const char file_properties_guid[16] = "\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65";
194 static const char stream_properties_guid[16] = "\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65";
195 static const char stream_type_audio_guid[16] = "\x40\x9E\x69\xF8\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
196 static const char stream_type_video_guid[16] = "\xC0\xEF\x19\xBC\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
197 static const char content_description_guid[16] = "\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
198 static const char extended_content_description_guid[16] = "\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50";
199 static const char metadata_guid[16] = "\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA";
200 static const char metadata_library_guid[16] = "\224\034#D\230\224\321I\241A\x1d\x13NEpT";
201 static const char content_encryption_object_guid[16] = "\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E";
202 static const char extended_content_encryption_object_guid[16] = "\x14\xE6\x8A\x29\x22\x26\x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C";
203
204 static const char attr_name_wm_album_artist[28] = "\x57\x00\x4d\x00\x2f\x00\x41\x00\x6c\x00\x62\x00\x75\x00\x6d\x00\x41\x00\x72\x00\x74\x00\x69\x00\x73\x00\x74\x00";
205 static const char attr_name_wm_album_title[26] = "\x57\x00\x4d\x00\x2f\x00\x41\x00\x6c\x00\x62\x00\x75\x00\x6d\x00\x54\x00\x69\x00\x74\x00\x6c\x00\x65\x00";
206 static const char attr_name_wm_genre[16] = "\x57\x00\x4d\x00\x2f\x00\x47\x00\x65\x00\x6e\x00\x72\x00\x65\x00";
207 static const char attr_name_wm_track_number[28] = "\x57\x00\x4d\x00\x2f\x00\x54\x00\x72\x00\x61\x00\x63\x00\x6b\x00\x4e\x00\x75\x00\x6d\x00\x62\x00\x65\x00\x72\x00";
208
209 static long long
210 _to_number(const char *data, unsigned int type_size, unsigned int data_size)
211 {
212     long long sum = 0;
213     unsigned int last, i;
214
215     last = data_size > type_size ? type_size : data_size;
216
217     for (i = 0; i < last; i++)
218         sum |= (unsigned char) (data[i]) << (i * 8);
219
220     return sum;
221 }
222
223 static short
224 _read_word(int fd)
225 {
226     char v[2];
227     if (read(fd, &v, 2) != 2)
228         return 0;
229     return (short) _to_number(v, sizeof(unsigned short), 2);
230 }
231
232 static unsigned int
233 _read_dword(int fd)
234 {
235     char v[4];
236     if (read(fd, &v, 4) != 4)
237         return 0;
238     return (unsigned int) _to_number(v, sizeof(unsigned int), 4);
239 }
240
241 static long long
242 _read_qword(int fd)
243 {
244     char v[8];
245     if (read(fd, &v, 8) != 8)
246         return 0;
247     return _to_number(v, sizeof(unsigned long long), 8);
248 }
249
250 static int
251 _read_string(int fd, size_t count, char **str, unsigned int *len)
252 {
253     char *data;
254     ssize_t data_size, size;
255
256     data = malloc(sizeof(char) * count);
257     data_size = read(fd, data, count);
258     if (data_size == -1) {
259         free(data);
260         return -1;
261     }
262
263     size = data_size;
264     while (size >= 2) {
265         if (data[size - 1] != '\0' || data[size - 2] != '\0')
266             break;
267         size -= 2;
268     }
269
270     *str = data;
271     *len = size;
272
273     return 0;
274 }
275
276 static int
277 _parse_file_properties(int fd, struct asf_info *info)
278 {
279     struct {
280         char fileid[16];
281         uint64_t file_size;
282         uint64_t creation_date;
283         uint64_t data_packets_count;
284         uint64_t play_duration;
285         uint64_t send_duration;
286         uint64_t preroll;
287         uint32_t flags;
288         uint32_t min_data_packet_size;
289         uint32_t max_data_packet_size;
290         uint32_t max_bitrate;
291     } __attribute__((packed)) props;
292     int r;
293
294     r = read(fd, &props, sizeof(props));
295     if (r != sizeof(props))
296         return r;
297
298     /* Broadcast flag */
299     if (le32toh(props.flags) & 0x1)
300         return r;
301
302     /* ASF spec 01.20.06 sec. 3.2: we need to subtract the preroll value from
303      * the duration in order to obtain the real duration */
304     info->length = (unsigned int)(
305         (le64toh(props.play_duration) / NSEC100_PER_SEC) -
306         le64toh(props.preroll) / MSEC_PER_SEC);
307
308     return r;
309 }
310
311 static const struct lms_string_size *
312 _audio_codec_id_to_str(uint16_t id)
313 {
314     unsigned int i;
315
316     for (i = 0; _audio_codecs[i].name != &_str_unknown; i++)
317         if (_audio_codecs[i].id == id)
318             return _audio_codecs[i].name;
319
320     return _audio_codecs[i].name;
321 }
322
323 static const struct lms_string_size *
324 _video_codec_id_to_str(uint8_t id[4])
325 {
326     unsigned int i;
327
328     for (i = 0; _video_codecs[i].name != &_str_unknown; i++)
329         if (memcmp(id, _video_codecs[i].id, 4) == 0)
330             return _video_codecs[i].name;
331
332     return _video_codecs[i].name;
333 }
334
335 static struct stream * _stream_get_or_create(struct asf_info *info,
336                                              unsigned int stream_id)
337 {
338     struct stream *s;
339
340     for (s = info->streams; s; s = (struct stream *) s->base.next) {
341         if (s->base.stream_id == stream_id)
342             return s;
343     }
344
345     s = calloc(1, sizeof(*s));
346     if (!s)
347         return NULL;
348
349     /* The Stream Properties Object can be anywhere inside the Header Object:
350      * before the Header Extension Object, after it or embedded into the
351      * Extended Stream Properties, inside the Header Extension Object.
352      *
353      * When parsing we either create a new stream and prepend it to the list or
354      * we return the one already created by a previous object (see the loop
355      * above).
356      *
357      * Note that the stream type is only available in the Stream Properties
358      * Object. A file with an Extended Stream Properties Object referring to a
359      * stream that doesn't have a corresponding Stream Properties is invalid. We
360      * let it into the list, but it won't have the stream_type set. In this case
361      * LMS will end up ignoring the stream when we try to add the file in the
362      * database -- this is why we set type to -1 here */
363     s->base.stream_id = stream_id;
364     s->base.type = -1;
365     s->base.next = (struct lms_stream *) info->streams;
366     info->streams = s;
367
368     return s;
369 }
370
371 static void _stream_copy_extension_properties(struct stream *s)
372 {
373     switch (s->base.type) {
374     case LMS_STREAM_TYPE_AUDIO:
375         s->base.audio.bitrate = s->priv.bitrate;
376         break;
377     case LMS_STREAM_TYPE_VIDEO:
378         s->base.video.bitrate = s->priv.bitrate;
379         s->base.video.framerate = s->priv.framerate;
380         break;
381     default:
382         break;
383     }
384 }
385
386 static int
387 _parse_stream_properties(int fd, struct asf_info *info)
388 {
389     struct {
390         char stream_type[16];
391         char error_correction_type[16];
392         uint64_t time_offset;
393         uint32_t type_specific_len;
394         uint32_t error_correction_data_len;
395         uint16_t flags;
396         uint32_t reserved; /* don't use, unaligned */
397     } __attribute__((packed)) props;
398     unsigned int stream_id;
399     struct stream *s;
400     int r, type;
401
402     r = read(fd, &props, sizeof(props));
403     if (r != sizeof(props))
404         return r;
405
406     stream_id = le16toh(props.flags) & 0x7F;
407
408     /* Not a valid stream */
409     if (!stream_id)
410         return r;
411
412     if (memcmp(props.stream_type, stream_type_audio_guid, 16) == 0)
413         type = LMS_STREAM_TYPE_AUDIO;
414     else if (memcmp(props.stream_type, stream_type_video_guid, 16) == 0)
415         type = LMS_STREAM_TYPE_VIDEO;
416     else
417         /* ignore stream */
418         return r;
419
420     s = _stream_get_or_create(info, stream_id);
421     if (!s)
422         return -ENOMEM;
423
424     s->base.type = type;
425
426     if (s->base.type == LMS_STREAM_TYPE_AUDIO) {
427         if (le32toh(props.type_specific_len) < 18)
428             goto done;
429
430         s->base.codec = *_audio_codec_id_to_str(_read_word(fd));
431         s->base.audio.channels = _read_word(fd);
432         s->priv.sampling_rate = _read_dword(fd);
433         s->base.audio.sampling_rate = s->priv.sampling_rate;
434         s->base.audio.bitrate = _read_dword(fd) * 8;
435     } else {
436         struct {
437             uint32_t width_unused;
438             uint32_t height_unused;
439             uint8_t reserved;
440             uint16_t data_size_unused;
441             /* data */
442             uint32_t size;
443             uint32_t width;
444             uint32_t height;
445             uint16_t reseved2;
446             uint16_t bits_per_pixel;
447             uint8_t compression_id[4];
448             uint32_t image_size;
449
450             /* other fields are ignored */
451         } __attribute__((packed)) video;
452         unsigned int num, den;
453
454         r = read(fd, &video, sizeof(video));
455         if (r != sizeof(video))
456             goto done;
457
458         if ((unsigned int) r < get_le32(&video.size) -
459             (sizeof(video) - offsetof(typeof(video), width)))
460             goto done;
461
462         s->base.codec = *_video_codec_id_to_str(video.compression_id);
463         s->base.video.width = get_le32(&video.width);
464         s->base.video.height = get_le32(&video.height);
465
466         reduce_gcd(s->base.video.width, s->base.video.height, &num, &den);
467         asprintf(&s->base.video.aspect_ratio.str, "%u:%u", num, den);
468         s->base.video.aspect_ratio.len = s->base.video.aspect_ratio.str ?
469             strlen(s->base.video.aspect_ratio.str) : 0;
470     }
471
472     _stream_copy_extension_properties(s);
473
474 done:
475     /* If there's any video stream, consider the file as video */
476     if (info->type != LMS_STREAM_TYPE_VIDEO)
477         info->type = s->base.type;
478
479     return r;
480 }
481
482 static int _parse_extended_stream_properties(lms_charset_conv_t *cs_conv,
483                                              int fd, struct asf_info *info)
484 {
485     struct {
486         uint64_t start_time;
487         uint64_t end_time;
488         uint32_t data_bitrate;
489         uint32_t buffer_size;
490         uint32_t init_buffer_fullness;
491         uint32_t alt_data_bitrate;
492         uint32_t alt_buffer_size;
493         uint32_t alt_init_buffer_fullness;
494         uint32_t max_obj_size;
495         uint32_t flags;
496         uint16_t stream_id;
497         uint16_t lang_id;
498         uint64_t avg_time_per_frame;
499         uint16_t stream_name_count;
500         uint16_t payload_extension_system_count;
501     } __attribute__((packed)) props;
502     struct stream *s;
503     unsigned int stream_id;
504     uint32_t bitrate;
505     uint16_t n;
506     int r;
507
508     r = read(fd, &props, sizeof(props));
509     if (r != sizeof(props))
510         return r;
511
512     stream_id = get_le16(&props.stream_id);
513     s = _stream_get_or_create(info, stream_id);
514
515     bitrate = get_le32(&props.alt_data_bitrate); /* for vbr */
516     if (!bitrate)
517         bitrate = get_le32(&props.data_bitrate);
518     s->priv.bitrate = bitrate;
519     s->priv.framerate = (NSEC100_PER_SEC /
520                          (double) get_le64(&props.avg_time_per_frame));
521     for (n = get_le16(&props.stream_name_count); n; n--) {
522         uint16_t j;
523         lseek(fd, 2, SEEK_CUR);
524         j = _read_word(fd);
525         lseek(fd, j, SEEK_CUR);
526     }
527     for (n = get_le16(&props.payload_extension_system_count); n; n--) {
528         uint32_t j;
529         lseek(fd, 18, SEEK_CUR);
530         j = _read_dword(fd);
531         lseek(fd, j, SEEK_CUR);
532     }
533
534     return 0;
535 }
536
537 /* Lazy implementation, let the parsing of subframes to the caller. Techically
538  * this is wrong, since it might parse objects in the extension header that
539  * should be in the header object, however this should parse ok all good files
540  * and eventually the bad ones. */
541 static int _parse_header_extension(lms_charset_conv_t *cs_conv, int fd,
542                                    struct asf_info *info)
543 {
544     lseek(fd, 22, SEEK_CUR);
545     return 0;
546 }
547
548 static int
549 _parse_content_description(lms_charset_conv_t *cs_conv, int fd,
550                            struct asf_info *info)
551 {
552     int title_length = _read_word(fd);
553     int artist_length = _read_word(fd);
554
555     lseek(fd, 6, SEEK_CUR);
556
557     _read_string(fd, title_length, &info->title.str, &info->title.len);
558     lms_charset_conv_force(cs_conv, &info->title.str, &info->title.len);
559     _read_string(fd, artist_length, &info->artist.str, &info->artist.len);
560     lms_charset_conv_force(cs_conv, &info->artist.str, &info->artist.len);
561
562     /* ignore copyright, comment and rating */
563     return 1;
564 }
565
566 static void
567 _parse_attribute_name(int fd,
568                       char **attr_name,
569                       unsigned int *attr_name_len,
570                       int *attr_type,
571                       int *attr_size)
572 {
573     int attr_name_length;
574
575     attr_name_length = _read_word(fd);
576     _read_string(fd, attr_name_length, attr_name, attr_name_len);
577     *attr_type = _read_word(fd);
578     *attr_size = _read_word(fd);
579 }
580
581 static void
582 _parse_attribute_string_data(lms_charset_conv_t *cs_conv,
583                              int fd,
584                              int attr_size,
585                              char **attr_data,
586                              unsigned int *attr_data_len)
587 {
588     _read_string(fd, attr_size, attr_data, attr_data_len);
589     lms_charset_conv_force(cs_conv, attr_data, attr_data_len);
590 }
591
592 static void
593 _skip_attribute_data(int fd, int kind, int attr_type, int attr_size)
594 {
595     switch (attr_type) {
596     case ATTR_TYPE_WORD:
597         lseek(fd, 2, SEEK_CUR);
598         break;
599
600     case ATTR_TYPE_BOOL:
601         if (kind == 0)
602             lseek(fd, 4, SEEK_CUR);
603         else
604             lseek(fd, 2, SEEK_CUR);
605         break;
606
607     case ATTR_TYPE_DWORD:
608         lseek(fd, 4, SEEK_CUR);
609         break;
610
611     case ATTR_TYPE_QWORD:
612         lseek(fd, 8, SEEK_CUR);
613         break;
614
615     case ATTR_TYPE_UNICODE:
616     case ATTR_TYPE_BYTES:
617     case ATTR_TYPE_GUID:
618         lseek(fd, attr_size, SEEK_CUR);
619         break;
620
621     default:
622         break;
623     }
624 }
625
626 static int
627 _parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd,
628                                            struct asf_info *info)
629 {
630     int count = _read_word(fd);
631     char *attr_name;
632     unsigned int attr_name_len;
633     int attr_type, attr_size;
634
635     while (count--) {
636         attr_name = NULL;
637         _parse_attribute_name(fd,
638                               &attr_name, &attr_name_len,
639                               &attr_type, &attr_size);
640         if (attr_type == ATTR_TYPE_UNICODE) {
641             if (memcmp(attr_name, attr_name_wm_album_title, attr_name_len) == 0)
642                 _parse_attribute_string_data(cs_conv,
643                                              fd, attr_size,
644                                              &info->album.str,
645                                              &info->album.len);
646             else if (memcmp(attr_name, attr_name_wm_genre, attr_name_len) == 0)
647                 _parse_attribute_string_data(cs_conv,
648                                              fd, attr_size,
649                                              &info->genre.str,
650                                              &info->genre.len);
651             else if (memcmp(attr_name, attr_name_wm_album_artist, attr_name_len) == 0)
652                 _parse_attribute_string_data(cs_conv,
653                                              fd, attr_size,
654                                              &info->artist.str,
655                                              &info->artist.len);
656             else if (memcmp(attr_name, attr_name_wm_track_number, attr_name_len) == 0) {
657                 char *trackno;
658                 unsigned int trackno_len;
659                 _parse_attribute_string_data(cs_conv,
660                                              fd, attr_size,
661                                              &trackno,
662                                              &trackno_len);
663                 if (trackno) {
664                     info->trackno = atoi(trackno);
665                     free(trackno);
666                 }
667             }
668             else
669                 _skip_attribute_data(fd, 0, attr_type, attr_size);
670         }
671         else
672             _skip_attribute_data(fd, 0, attr_type, attr_size);
673         free(attr_name);
674     }
675
676     return 1;
677 }
678
679 static void *
680 _match(struct plugin *p, const char *path, int len, int base)
681 {
682     long i;
683
684     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
685     if (i < 0)
686       return NULL;
687     else
688       return (void*)(i + 1);
689 }
690
691 static void streams_free(struct stream *streams)
692 {
693     while (streams) {
694         struct stream *s = streams;
695         streams = (struct stream *) s->base.next;
696
697         switch (s->base.type) {
698         case LMS_STREAM_TYPE_VIDEO:
699             free(s->base.video.aspect_ratio.str);
700             break;
701         default:
702             break;
703         }
704
705         free(s);
706     }
707 }
708
709 /* TODO: Parse "Language List Object" (sec 4.6) which contains an array with all
710  * the languages used (they are in UTF-16, so they need to be properly
711  * converted). */
712 static int
713 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
714 {
715     struct asf_info info = { .type = LMS_STREAM_TYPE_UNKNOWN };
716     int r, fd;
717     char guid[16];
718     unsigned int size;
719     unsigned long long hdrsize;
720     off_t pos_end, pos = 0;
721
722     fd = open(finfo->path, O_RDONLY);
723     if (fd < 0) {
724         perror("open");
725         return -1;
726     }
727
728     if (read(fd, &guid, 16) != 16) {
729         perror("read");
730         r = -2;
731         goto done;
732     }
733
734     if (memcmp(guid, header_guid, 16) != 0) {
735         fprintf(stderr, "ERROR: invalid header (%s).\n", finfo->path);
736         r = -3;
737         goto done;
738     }
739
740     hdrsize = _read_qword(fd);
741     pos_end = lseek(fd, 6, SEEK_CUR) - 24 + hdrsize;
742
743     while (1) {
744         if (!pos)
745             pos = lseek(fd, 0, SEEK_CUR);
746         if (pos > pos_end - 24)
747             break;
748
749         read(fd, &guid, 16);
750         size = _read_qword(fd);
751
752         if (memcmp(guid, header_extension_guid, 16) == 0)
753             r = _parse_header_extension(plugin->cs_conv, fd, &info);
754         else if (memcmp(guid, extended_stream_properties_guid, 16) == 0)
755             r = _parse_extended_stream_properties(plugin->cs_conv, fd, &info);
756         else if (memcmp(guid, file_properties_guid, 16) == 0)
757             r = _parse_file_properties(fd, &info);
758         else if (memcmp(guid, stream_properties_guid, 16) == 0)
759             r = _parse_stream_properties(fd, &info);
760         else if (memcmp(guid, language_list_guid, 16) == 0)
761             r = 1;
762         else if (memcmp(guid, content_description_guid, 16) == 0)
763             r = _parse_content_description(plugin->cs_conv, fd, &info);
764         else if (memcmp(guid, extended_content_description_guid, 16) == 0)
765             r = _parse_extended_content_description_object(plugin->cs_conv, fd,
766                                                            &info);
767         else if (memcmp(guid, content_encryption_object_guid, 16) == 0 ||
768                  memcmp(guid, extended_content_encryption_object_guid, 16) == 0)
769             /* ignore DRM'd files */
770             r = -4;
771         else
772             r = 1;
773
774         if (r < 0)
775             goto done;
776
777         if (r > 0)
778             pos = lseek(fd, pos + size, SEEK_SET);
779         else
780             pos = 0;
781     }
782
783     /* try to define stream type by extension */
784     if (info.type == LMS_STREAM_TYPE_UNKNOWN) {
785         long ext_idx = ((long)match) - 1;
786         if (strcmp(_exts[ext_idx].str, ".wma") == 0)
787             info.type = LMS_STREAM_TYPE_AUDIO;
788         /* consider wmv and asf as video */
789         else
790             info.type = LMS_STREAM_TYPE_VIDEO;
791     }
792
793     lms_string_size_strip_and_free(&info.title);
794     lms_string_size_strip_and_free(&info.artist);
795     lms_string_size_strip_and_free(&info.album);
796     lms_string_size_strip_and_free(&info.genre);
797
798     if (!info.title.str)
799         info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
800                                                 finfo->base,
801                                                 &_exts[((long) match) - 1],
802                                                 ctxt->cs_conv);
803
804     if (info.type == LMS_STREAM_TYPE_AUDIO) {
805         struct lms_audio_info audio_info = { };
806
807         audio_info.id = finfo->id;
808         audio_info.title = info.title;
809         audio_info.artist = info.artist;
810         audio_info.album = info.album;
811         audio_info.genre = info.genre;
812         audio_info.trackno = info.trackno;
813         audio_info.length = info.length;
814         audio_info.container = _container;
815
816         /* ignore additional streams, use only the first one */
817         if (info.streams) {
818             struct stream *s = info.streams;
819             audio_info.channels = s->base.audio.channels;
820             audio_info.bitrate = s->base.audio.bitrate;
821             audio_info.sampling_rate = s->priv.sampling_rate;
822             audio_info.codec = s->base.codec;
823         }
824
825         _fill_audio_dlna_profile(&audio_info);
826
827         r = lms_db_audio_add(plugin->audio_db, &audio_info);
828     } else {
829         struct lms_video_info video_info = { };
830
831         video_info.id = finfo->id;
832         video_info.title = info.title;
833         video_info.artist = info.artist;
834         video_info.length = info.length;
835         video_info.container = _container;
836         video_info.streams = (struct lms_stream *) info.streams;
837         r = lms_db_video_add(plugin->video_db, &video_info);
838     }
839
840 done:
841     streams_free(info.streams);
842
843     free(info.title.str);
844     free(info.artist.str);
845     free(info.album.str);
846     free(info.genre.str);
847
848     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
849     close(fd);
850
851     return r;
852 }
853
854 static int
855 _setup(struct plugin *plugin, struct lms_context *ctxt)
856 {
857     plugin->audio_db = lms_db_audio_new(ctxt->db);
858     if (!plugin->audio_db)
859         return -1;
860     plugin->video_db = lms_db_video_new(ctxt->db);
861     if (!plugin->video_db)
862         return -1;
863     plugin->cs_conv = lms_charset_conv_new();
864     if (!plugin->cs_conv)
865         return -1;
866     lms_charset_conv_add(plugin->cs_conv, "UTF-16LE");
867
868     return 0;
869 }
870
871 static int
872 _start(struct plugin *plugin, struct lms_context *ctxt)
873 {
874     int r;
875     r = lms_db_audio_start(plugin->audio_db);
876     r |= lms_db_video_start(plugin->video_db);
877     return r;
878 }
879
880 static int
881 _finish(struct plugin *plugin, struct lms_context *ctxt)
882 {
883     if (plugin->audio_db)
884         lms_db_audio_free(plugin->audio_db);
885     if (plugin->video_db)
886         lms_db_video_free(plugin->video_db);
887     if (plugin->cs_conv)
888         lms_charset_conv_free(plugin->cs_conv);
889
890     return 0;
891 }
892
893 static int
894 _close(struct plugin *plugin)
895 {
896     free(plugin);
897     return 0;
898 }
899
900 API struct lms_plugin *
901 lms_plugin_open(void)
902 {
903     struct plugin *plugin;
904
905     plugin = (struct plugin *)malloc(sizeof(*plugin));
906     plugin->plugin.name = _name;
907     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
908     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
909     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
910     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
911     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
912     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
913
914     return (struct lms_plugin *)plugin;
915 }
916
917 API const struct lms_plugin_info *
918 lms_plugin_info(void)
919 {
920     static struct lms_plugin_info info = {
921         _name,
922         _cats,
923         "Microsoft WMA, WMV and ASF",
924         PACKAGE_VERSION,
925         _authors,
926         "http://lms.garage.maemo.org"
927     };
928
929     return &info;
930 }