2 * Copyright (C) 2008 by INdT
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.
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.
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.
18 * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
24 * asf/wma file parser.
27 * http://www.microsoft.com/en-us/download/details.aspx?id=14995
30 #include <lightmediascanner_plugin.h>
31 #include <lightmediascanner_db.h>
32 #include <shared/util.h>
36 #include <sys/types.h>
44 #define DECL_STR(cname, str) \
45 static const struct lms_string_size cname = LMS_STATIC_STRING_SIZE(str)
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");
55 DECL_STR(_codec_video_wmv1, "wmv1");
56 DECL_STR(_codec_video_wmv2, "wmv2");
57 DECL_STR(_codec_video_wmv3, "wmv3");
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");
64 DECL_STR(_str_unknown, "<UNKNOWN>");
68 _fill_audio_dlna_profile(struct lms_audio_info *info)
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;
77 info->dlna_profile = _dlna_wma_full;
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;
89 ATTR_TYPE_UNICODE = 0,
99 struct lms_stream base;
101 unsigned int sampling_rate;
102 unsigned int bitrate;
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;
114 unsigned char trackno;
116 struct stream *streams;
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;
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")
133 static const char *_cats[] = {
138 static const char *_authors[] = {
139 "Andre Moreira Magalhaes",
143 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
144 static const struct {
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 }
162 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
163 static const struct {
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 }
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
181 * Eg.: AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp:
183 * to convert to byte string do as follow:
185 * $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
187 * See http://www.microsoft.com/windows/windowsmedia/forpros/format/asfspec.aspx
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";
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";
210 _to_number(const char *data, unsigned int type_size, unsigned int data_size)
213 unsigned int last, i;
215 last = data_size > type_size ? type_size : data_size;
217 for (i = 0; i < last; i++)
218 sum |= (unsigned char) (data[i]) << (i * 8);
227 if (read(fd, &v, 2) != 2)
229 return (short) _to_number(v, sizeof(unsigned short), 2);
236 if (read(fd, &v, 4) != 4)
238 return (unsigned int) _to_number(v, sizeof(unsigned int), 4);
245 if (read(fd, &v, 8) != 8)
247 return _to_number(v, sizeof(unsigned long long), 8);
251 _read_string(int fd, size_t count, char **str, unsigned int *len)
254 ssize_t data_size, size;
256 data = malloc(sizeof(char) * count);
257 data_size = read(fd, data, count);
258 if (data_size == -1) {
265 if (data[size - 1] != '\0' || data[size - 2] != '\0')
277 _parse_file_properties(int fd, struct asf_info *info)
282 uint64_t creation_date;
283 uint64_t data_packets_count;
284 uint64_t play_duration;
285 uint64_t send_duration;
288 uint32_t min_data_packet_size;
289 uint32_t max_data_packet_size;
290 uint32_t max_bitrate;
291 } __attribute__((packed)) props;
294 r = read(fd, &props, sizeof(props));
295 if (r != sizeof(props))
299 if (le32toh(props.flags) & 0x1)
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);
311 static const struct lms_string_size *
312 _audio_codec_id_to_str(uint16_t id)
316 for (i = 0; _audio_codecs[i].name != &_str_unknown; i++)
317 if (_audio_codecs[i].id == id)
318 return _audio_codecs[i].name;
320 return _audio_codecs[i].name;
323 static const struct lms_string_size *
324 _video_codec_id_to_str(uint8_t id[4])
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;
332 return _video_codecs[i].name;
335 static struct stream * _stream_get_or_create(struct asf_info *info,
336 unsigned int stream_id)
340 for (s = info->streams; s; s = (struct stream *) s->base.next) {
341 if (s->base.stream_id == stream_id)
345 s = calloc(1, sizeof(*s));
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.
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
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;
365 s->base.next = (struct lms_stream *) info->streams;
371 static void _stream_copy_extension_properties(struct stream *s)
373 switch (s->base.type) {
374 case LMS_STREAM_TYPE_AUDIO:
375 s->base.audio.bitrate = s->priv.bitrate;
377 case LMS_STREAM_TYPE_VIDEO:
378 s->base.video.bitrate = s->priv.bitrate;
379 s->base.video.framerate = s->priv.framerate;
387 _parse_stream_properties(int fd, struct asf_info *info)
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;
396 uint32_t reserved; /* don't use, unaligned */
397 } __attribute__((packed)) props;
398 unsigned int stream_id;
402 r = read(fd, &props, sizeof(props));
403 if (r != sizeof(props))
406 stream_id = le16toh(props.flags) & 0x7F;
408 /* Not a valid stream */
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;
420 s = _stream_get_or_create(info, stream_id);
426 if (s->base.type == LMS_STREAM_TYPE_AUDIO) {
427 if (le32toh(props.type_specific_len) < 18)
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;
437 uint32_t width_unused;
438 uint32_t height_unused;
440 uint16_t data_size_unused;
446 uint16_t bits_per_pixel;
447 uint8_t compression_id[4];
450 /* other fields are ignored */
451 } __attribute__((packed)) video;
452 unsigned int num, den;
454 r = read(fd, &video, sizeof(video));
455 if (r != sizeof(video))
458 if ((unsigned int) r < get_le32(&video.size) -
459 (sizeof(video) - offsetof(typeof(video), width)))
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);
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;
472 _stream_copy_extension_properties(s);
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;
482 static int _parse_extended_stream_properties(lms_charset_conv_t *cs_conv,
483 int fd, struct asf_info *info)
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;
498 uint64_t avg_time_per_frame;
499 uint16_t stream_name_count;
500 uint16_t payload_extension_system_count;
501 } __attribute__((packed)) props;
503 unsigned int stream_id;
508 r = read(fd, &props, sizeof(props));
509 if (r != sizeof(props))
512 stream_id = get_le16(&props.stream_id);
513 s = _stream_get_or_create(info, stream_id);
515 bitrate = get_le32(&props.alt_data_bitrate); /* for vbr */
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--) {
523 lseek(fd, 2, SEEK_CUR);
525 lseek(fd, j, SEEK_CUR);
527 for (n = get_le16(&props.payload_extension_system_count); n; n--) {
529 lseek(fd, 18, SEEK_CUR);
531 lseek(fd, j, SEEK_CUR);
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)
544 lseek(fd, 22, SEEK_CUR);
549 _parse_content_description(lms_charset_conv_t *cs_conv, int fd,
550 struct asf_info *info)
552 int title_length = _read_word(fd);
553 int artist_length = _read_word(fd);
555 lseek(fd, 6, SEEK_CUR);
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);
562 /* ignore copyright, comment and rating */
567 _parse_attribute_name(int fd,
569 unsigned int *attr_name_len,
573 int attr_name_length;
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);
582 _parse_attribute_string_data(lms_charset_conv_t *cs_conv,
586 unsigned int *attr_data_len)
588 _read_string(fd, attr_size, attr_data, attr_data_len);
589 lms_charset_conv_force(cs_conv, attr_data, attr_data_len);
593 _skip_attribute_data(int fd, int kind, int attr_type, int attr_size)
597 lseek(fd, 2, SEEK_CUR);
602 lseek(fd, 4, SEEK_CUR);
604 lseek(fd, 2, SEEK_CUR);
607 case ATTR_TYPE_DWORD:
608 lseek(fd, 4, SEEK_CUR);
611 case ATTR_TYPE_QWORD:
612 lseek(fd, 8, SEEK_CUR);
615 case ATTR_TYPE_UNICODE:
616 case ATTR_TYPE_BYTES:
618 lseek(fd, attr_size, SEEK_CUR);
627 _parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd,
628 struct asf_info *info)
630 int count = _read_word(fd);
632 unsigned int attr_name_len;
633 int attr_type, attr_size;
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,
646 else if (memcmp(attr_name, attr_name_wm_genre, attr_name_len) == 0)
647 _parse_attribute_string_data(cs_conv,
651 else if (memcmp(attr_name, attr_name_wm_album_artist, attr_name_len) == 0)
652 _parse_attribute_string_data(cs_conv,
656 else if (memcmp(attr_name, attr_name_wm_track_number, attr_name_len) == 0) {
658 unsigned int trackno_len;
659 _parse_attribute_string_data(cs_conv,
664 info->trackno = atoi(trackno);
669 _skip_attribute_data(fd, 0, attr_type, attr_size);
672 _skip_attribute_data(fd, 0, attr_type, attr_size);
680 _match(struct plugin *p, const char *path, int len, int base)
684 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
688 return (void*)(i + 1);
691 static void streams_free(struct stream *streams)
694 struct stream *s = streams;
695 streams = (struct stream *) s->base.next;
697 switch (s->base.type) {
698 case LMS_STREAM_TYPE_VIDEO:
699 free(s->base.video.aspect_ratio.str);
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
713 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
715 struct asf_info info = { .type = LMS_STREAM_TYPE_UNKNOWN };
719 unsigned long long hdrsize;
720 off_t pos_end, pos = 0;
722 fd = open(finfo->path, O_RDONLY);
728 if (read(fd, &guid, 16) != 16) {
734 if (memcmp(guid, header_guid, 16) != 0) {
735 fprintf(stderr, "ERROR: invalid header (%s).\n", finfo->path);
740 hdrsize = _read_qword(fd);
741 pos_end = lseek(fd, 6, SEEK_CUR) - 24 + hdrsize;
745 pos = lseek(fd, 0, SEEK_CUR);
746 if (pos > pos_end - 24)
750 size = _read_qword(fd);
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)
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,
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 */
778 pos = lseek(fd, pos + size, SEEK_SET);
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 */
790 info.type = LMS_STREAM_TYPE_VIDEO;
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);
799 info.title = str_extract_name_from_path(finfo->path, finfo->path_len,
801 &_exts[((long) match) - 1],
804 if (info.type == LMS_STREAM_TYPE_AUDIO) {
805 struct lms_audio_info audio_info = { };
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;
816 /* ignore additional streams, use only the first one */
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;
825 _fill_audio_dlna_profile(&audio_info);
827 r = lms_db_audio_add(plugin->audio_db, &audio_info);
829 struct lms_video_info video_info = { };
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);
841 streams_free(info.streams);
843 free(info.title.str);
844 free(info.artist.str);
845 free(info.album.str);
846 free(info.genre.str);
848 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
855 _setup(struct plugin *plugin, struct lms_context *ctxt)
857 plugin->audio_db = lms_db_audio_new(ctxt->db);
858 if (!plugin->audio_db)
860 plugin->video_db = lms_db_video_new(ctxt->db);
861 if (!plugin->video_db)
863 plugin->cs_conv = lms_charset_conv_new();
864 if (!plugin->cs_conv)
866 lms_charset_conv_add(plugin->cs_conv, "UTF-16LE");
872 _start(struct plugin *plugin, struct lms_context *ctxt)
875 r = lms_db_audio_start(plugin->audio_db);
876 r |= lms_db_video_start(plugin->video_db);
881 _finish(struct plugin *plugin, struct lms_context *ctxt)
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);
888 lms_charset_conv_free(plugin->cs_conv);
894 _close(struct plugin *plugin)
900 API struct lms_plugin *
901 lms_plugin_open(void)
903 struct plugin *plugin;
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;
914 return (struct lms_plugin *)plugin;
917 API const struct lms_plugin_info *
918 lms_plugin_info(void)
920 static struct lms_plugin_info info = {
923 "Microsoft WMA, WMV and ASF",
926 "http://lms.garage.maemo.org"