Get rid of "if (bla) free(bla);"
[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
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #define _XOPEN_SOURCE 600
32 #define _BSD_SOURCE
33 #include <lightmediascanner_plugin.h>
34 #include <lightmediascanner_db.h>
35 #include <endian.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44
45 #define NSEC100_PER_SEC  10000000ULL
46 #define MSEC_PER_SEC  1000ULL
47
48 enum StreamTypes {
49     STREAM_TYPE_UNKNOWN = 0,
50     STREAM_TYPE_AUDIO,
51     STREAM_TYPE_VIDEO
52 };
53
54 enum AttributeTypes {
55     ATTR_TYPE_UNICODE = 0,
56     ATTR_TYPE_BYTES,
57     ATTR_TYPE_BOOL,
58     ATTR_TYPE_DWORD,
59     ATTR_TYPE_QWORD,
60     ATTR_TYPE_WORD,
61     ATTR_TYPE_GUID
62 };
63
64 struct asf_info {
65     struct lms_string_size title;
66     struct lms_string_size artist;
67     struct lms_string_size album;
68     struct lms_string_size genre;
69     unsigned char trackno;
70 };
71
72 struct plugin {
73     struct lms_plugin plugin;
74     lms_db_audio_t *audio_db;
75     lms_db_video_t *video_db;
76     lms_charset_conv_t *cs_conv;
77 };
78
79 static const char _name[] = "asf";
80 static const struct lms_string_size _container = LMS_STATIC_STRING_SIZE("asf");
81 static const struct lms_string_size _exts[] = {
82     LMS_STATIC_STRING_SIZE(".wma"),
83     LMS_STATIC_STRING_SIZE(".wmv"),
84     LMS_STATIC_STRING_SIZE(".asf")
85 };
86 static const char *_cats[] = {
87     "multimedia",
88     "audio",
89     NULL
90 };
91 static const char *_authors[] = {
92     "Andre Moreira Magalhaes",
93     NULL
94 };
95
96 struct stream {
97     int id;
98     enum StreamTypes type;
99     struct stream *next;
100     union {
101         struct {
102             uint16_t codec_id;
103             uint16_t channels;
104             uint32_t sampling_rate;
105             uint32_t byterate;
106         } audio;
107     };
108 };
109
110 /* TODO: Add the gazillion of possible codecs -- possibly a task to gperf */
111 static const struct {
112     uint16_t id;
113     struct lms_string_size name;
114 } _codecs[] = {
115     { 0x0160, LMS_STATIC_STRING_SIZE("wmav1") },
116     { 0x0161, LMS_STATIC_STRING_SIZE("wmav2") },
117     { 0x0162, LMS_STATIC_STRING_SIZE("wmavpro") },
118     { 0x0163, LMS_STATIC_STRING_SIZE("wmavlossless") },
119     { 0x1600, LMS_STATIC_STRING_SIZE("aac") },
120     { 0x706d, LMS_STATIC_STRING_SIZE("aac") },
121     { 0x4143, LMS_STATIC_STRING_SIZE("aac") },
122     { 0xA106, LMS_STATIC_STRING_SIZE("aac") },
123     { 0xF1AC, LMS_STATIC_STRING_SIZE("flac") },
124     { 0x0055, LMS_STATIC_STRING_SIZE("mp3") },
125     { }
126 };
127
128 /* ASF GUIDs
129  *
130  * Microsoft defines these 16-byte (128-bit) GUIDs as:
131  * first 8 bytes are in little-endian order
132  * next 8 bytes are in big-endian order
133  *
134  * Eg.: AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp:
135  *
136  * to convert to byte string do as follow:
137  *
138  * $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp
139  *
140  * See http://www.microsoft.com/windows/windowsmedia/forpros/format/asfspec.aspx
141  */
142 static const char header_guid[16] = "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
143 static const char file_properties_guid[16] = "\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65";
144 static const char stream_properties_guid[16] = "\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65";
145 static const char stream_type_audio_guid[16] = "\x40\x9E\x69\xF8\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
146 static const char stream_type_video_guid[16] = "\xC0\xEF\x19\xBC\x4D\x5B\xCF\x11\xA8\xFD\x00\x80\x5F\x5C\x44\x2B";
147 static const char content_description_guid[16] = "\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C";
148 static const char extended_content_description_guid[16] = "\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50";
149 static const char header_extension_guid[16] = "\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se";
150 static const char metadata_guid[16] = "\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA";
151 static const char metadata_library_guid[16] = "\224\034#D\230\224\321I\241A\x1d\x13NEpT";
152 static const char content_encryption_object_guid[16] = "\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E";
153 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";
154
155 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";
156 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";
157 static const char attr_name_wm_genre[16] = "\x57\x00\x4d\x00\x2f\x00\x47\x00\x65\x00\x6e\x00\x72\x00\x65\x00";
158 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";
159
160 static long long
161 _to_number(const char *data, unsigned int type_size, unsigned int data_size)
162 {
163     long long sum = 0;
164     unsigned int last, i;
165
166     last = data_size > type_size ? type_size : data_size;
167
168     for (i = 0; i < last; i++)
169         sum |= (unsigned char) (data[i]) << (i * 8);
170
171     return sum;
172 }
173
174 static short
175 _read_word(int fd)
176 {
177     char v[2];
178     if (read(fd, &v, 2) != 2)
179         return 0;
180     return (short) _to_number(v, sizeof(unsigned short), 2);
181 }
182
183 static unsigned int
184 _read_dword(int fd)
185 {
186     char v[4];
187     if (read(fd, &v, 4) != 4)
188         return 0;
189     return (unsigned int) _to_number(v, sizeof(unsigned int), 4);
190 }
191
192 static long long
193 _read_qword(int fd)
194 {
195     char v[8];
196     if (read(fd, &v, 8) != 8)
197         return 0;
198     return _to_number(v, sizeof(unsigned long long), 8);
199 }
200
201 static int
202 _read_string(int fd, size_t count, char **str, unsigned int *len)
203 {
204     char *data;
205     ssize_t data_size, size;
206
207     data = malloc(sizeof(char) * count);
208     data_size = read(fd, data, count);
209     if (data_size == -1) {
210         free(data);
211         return -1;
212     }
213
214     size = data_size;
215     while (size >= 2) {
216         if (data[size - 1] != '\0' || data[size - 2] != '\0')
217             break;
218         size -= 2;
219     }
220
221     *str = data;
222     *len = size;
223
224     return 0;
225 }
226
227 static int
228 _parse_file_properties(lms_charset_conv_t *cs_conv, int fd,
229                        struct lms_audio_info *info)
230 {
231     struct {
232         char fileid[16];
233         uint64_t file_size;
234         uint64_t creation_date;
235         uint64_t data_packets_count;
236         uint64_t play_duration;
237         uint64_t send_duration;
238         uint64_t preroll;
239         uint32_t flags;
240         uint32_t min_data_packet_size;
241         uint32_t max_data_packet_size;
242         uint32_t max_bitrate;
243     } __attribute__((packed)) props;
244     int r;
245
246     r = read(fd, &props, sizeof(props));
247     if (r != sizeof(props))
248         return r;
249
250     /* Broadcast flag */
251     if (le32toh(props.flags) & 0x1)
252         return r;
253
254     /* ASF spec 01.20.06 sec. 3.2: we need to subtract the preroll value from
255      * the duration in order to obtain the real duration */
256     info->length = (unsigned int)((le64toh(props.play_duration) / NSEC100_PER_SEC)
257                                   - le64toh(props.preroll) / MSEC_PER_SEC);
258
259     return r;
260 }
261
262 static struct lms_string_size
263 _codec_id_to_str(uint16_t id)
264 {
265     unsigned int i;
266
267     for (i = 0; _codecs[i].name.str != NULL; i++)
268         if (_codecs[i].id == id)
269             return _codecs[i].name;
270
271     return _codecs[i].name;
272 }
273
274 static int
275 _parse_stream_properties(int fd, struct stream **pstream)
276 {
277     struct {
278         char stream_type[16];
279         char error_correction_type[16];
280         uint64_t time_offset;
281         uint32_t type_specific_len;
282         uint32_t error_correction_data_len;
283         uint16_t flags;
284         uint32_t reserved; /* don't use, unaligned */
285     }  __attribute__((packed)) props;
286
287     int r;
288     struct stream *s;
289
290     *pstream = NULL;
291
292     s = calloc(1, sizeof(struct stream));
293     if (!s)
294         return -ENOMEM;
295
296     r = read(fd, &props, sizeof(props));
297     if (r != sizeof(props))
298         goto done;
299
300     if (memcmp(props.stream_type, stream_type_audio_guid, 16) == 0)
301         s->type = STREAM_TYPE_AUDIO;
302     else if (memcmp(props.stream_type, stream_type_video_guid, 16) == 0)
303         s->type = STREAM_TYPE_VIDEO;
304     else {
305         /* ignore stream */
306         goto done;
307     }
308
309     s->id = le16toh(props.flags) & 0x7F;
310     /* Not a valid stream */
311     if (!s->id)
312         goto done;
313
314     if (s->type == STREAM_TYPE_AUDIO) {
315         if (le32toh(props.type_specific_len) < 18)
316             goto done;
317
318         s->audio.codec_id = _read_word(fd);
319         s->audio.channels = _read_word(fd);
320         s->audio.sampling_rate = _read_dword(fd);
321         s->audio.byterate = _read_dword(fd);
322         r += 12;
323     }
324
325     *pstream = s;
326
327     return r;
328
329 done:
330     free(s);
331     return r;
332 }
333
334 static void
335 _parse_content_description(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
336 {
337     int title_length = _read_word(fd);
338     int artist_length = _read_word(fd);
339     int copyright_length = _read_word(fd);
340     int comment_length = _read_word(fd);
341     int rating_length = _read_word(fd);
342
343     _read_string(fd, title_length, &info->title.str, &info->title.len);
344     lms_charset_conv_force(cs_conv, &info->title.str, &info->title.len);
345     _read_string(fd, artist_length, &info->artist.str, &info->artist.len);
346     lms_charset_conv_force(cs_conv, &info->artist.str, &info->artist.len);
347     /* ignore copyright, comment and rating */
348     lseek(fd, copyright_length + comment_length + rating_length, SEEK_CUR);
349 }
350
351 static void
352 _parse_attribute_name(int fd,
353                       char **attr_name,
354                       unsigned int *attr_name_len,
355                       int *attr_type,
356                       int *attr_size)
357 {
358     int attr_name_length;
359
360     attr_name_length = _read_word(fd);
361     _read_string(fd, attr_name_length, attr_name, attr_name_len);
362     *attr_type = _read_word(fd);
363     *attr_size = _read_word(fd);
364 }
365
366 static void
367 _parse_attribute_string_data(lms_charset_conv_t *cs_conv,
368                              int fd,
369                              int attr_size,
370                              char **attr_data,
371                              unsigned int *attr_data_len)
372 {
373     _read_string(fd, attr_size, attr_data, attr_data_len);
374     lms_charset_conv_force(cs_conv, attr_data, attr_data_len);
375 }
376
377 static void
378 _skip_attribute_data(int fd, int kind, int attr_type, int attr_size)
379 {
380     switch (attr_type) {
381     case ATTR_TYPE_WORD:
382         lseek(fd, 2, SEEK_CUR);
383         break;
384
385     case ATTR_TYPE_BOOL:
386         if (kind == 0)
387             lseek(fd, 4, SEEK_CUR);
388         else
389             lseek(fd, 2, SEEK_CUR);
390         break;
391
392     case ATTR_TYPE_DWORD:
393         lseek(fd, 4, SEEK_CUR);
394         break;
395
396     case ATTR_TYPE_QWORD:
397         lseek(fd, 8, SEEK_CUR);
398         break;
399
400     case ATTR_TYPE_UNICODE:
401     case ATTR_TYPE_BYTES:
402     case ATTR_TYPE_GUID:
403         lseek(fd, attr_size, SEEK_CUR);
404         break;
405
406     default:
407         break;
408     }
409 }
410
411 static void
412 _parse_extended_content_description_object(lms_charset_conv_t *cs_conv, int fd, struct asf_info *info)
413 {
414     int count = _read_word(fd);
415     char *attr_name;
416     unsigned int attr_name_len;
417     int attr_type, attr_size;
418     while (count--) {
419         attr_name = NULL;
420         _parse_attribute_name(fd,
421                               &attr_name, &attr_name_len,
422                               &attr_type, &attr_size);
423         if (attr_type == ATTR_TYPE_UNICODE) {
424             if (memcmp(attr_name, attr_name_wm_album_title, attr_name_len) == 0)
425                 _parse_attribute_string_data(cs_conv,
426                                              fd, attr_size,
427                                              &info->album.str,
428                                              &info->album.len);
429             else if (memcmp(attr_name, attr_name_wm_genre, attr_name_len) == 0)
430                 _parse_attribute_string_data(cs_conv,
431                                              fd, attr_size,
432                                              &info->genre.str,
433                                              &info->genre.len);
434             else if (memcmp(attr_name, attr_name_wm_album_artist, attr_name_len) == 0)
435                 _parse_attribute_string_data(cs_conv,
436                                              fd, attr_size,
437                                              &info->artist.str,
438                                              &info->artist.len);
439             else if (memcmp(attr_name, attr_name_wm_track_number, attr_name_len) == 0) {
440                 char *trackno;
441                 unsigned int trackno_len;
442                 _parse_attribute_string_data(cs_conv,
443                                              fd, attr_size,
444                                              &trackno,
445                                              &trackno_len);
446                 if (trackno) {
447                     info->trackno = atoi(trackno);
448                     free(trackno);
449                 }
450             }
451             else
452                 _skip_attribute_data(fd, 0, attr_type, attr_size);
453         }
454         else
455             _skip_attribute_data(fd, 0, attr_type, attr_size);
456         free(attr_name);
457     }
458 }
459
460 static void *
461 _match(struct plugin *p, const char *path, int len, int base)
462 {
463     long i;
464
465     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
466     if (i < 0)
467       return NULL;
468     else
469       return (void*)(i + 1);
470 }
471
472 static int
473 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
474 {
475     struct asf_info info = { };
476     struct lms_audio_info audio_info = { };
477     struct lms_video_info video_info = { };
478     int r, fd, num_objects, i;
479     char guid[16];
480     unsigned int size;
481     int stream_type = STREAM_TYPE_UNKNOWN;
482     struct stream *streams = NULL;
483
484     fd = open(finfo->path, O_RDONLY);
485     if (fd < 0) {
486         perror("open");
487         return -1;
488     }
489
490     if (read(fd, &guid, 16) != 16) {
491         perror("read");
492         r = -2;
493         goto done;
494     }
495     if (memcmp(guid, header_guid, 16) != 0) {
496         fprintf(stderr, "ERROR: invalid header (%s).\n", finfo->path);
497         r = -3;
498         goto done;
499     }
500
501     size = _read_qword(fd);
502     num_objects = _read_dword(fd);
503
504     lseek(fd, 2, SEEK_CUR);
505
506     for (i = 0; i < num_objects; ++i) {
507         read(fd, &guid, 16);
508         size = _read_qword(fd);
509
510         if (memcmp(guid, file_properties_guid, 16) == 0) {
511             r = _parse_file_properties(plugin->cs_conv, fd, &audio_info);
512             if (r < 0)
513                 goto done;
514             lseek(fd, size - (24 + r), SEEK_CUR);
515         } else if (memcmp(guid, stream_properties_guid, 16) == 0) {
516             struct stream *s;
517             r = _parse_stream_properties(fd, &s);
518             if (r < 0)
519                 goto done;
520
521             lseek(fd, size - (24 + r), SEEK_CUR);
522             if (!s)
523                 continue;
524
525             if (stream_type != STREAM_TYPE_VIDEO)
526                 stream_type = s->type;
527
528             s->next = streams;
529             streams = s;
530         } else if (memcmp(guid, content_description_guid, 16) == 0)
531             _parse_content_description(plugin->cs_conv, fd, &info);
532         else if (memcmp(guid, extended_content_description_guid, 16) == 0)
533             _parse_extended_content_description_object(plugin->cs_conv, fd, &info);
534         else if (memcmp(guid, content_encryption_object_guid, 16) == 0 ||
535                  memcmp(guid, extended_content_encryption_object_guid, 16) == 0) {
536             /* ignore DRM'd files */
537             fprintf(stderr, "ERROR: ignoring DRM'd file %s\n", finfo->path);
538             r = -4;
539             goto done;
540         } else
541             lseek(fd, size - 24, SEEK_CUR);
542     }
543
544     /* try to define stream type by extension */
545     if (stream_type == STREAM_TYPE_UNKNOWN) {
546         long ext_idx = ((long)match) - 1;
547         if (strcmp(_exts[ext_idx].str, ".wma") == 0)
548             stream_type = STREAM_TYPE_AUDIO;
549         /* consider wmv and asf as video */
550         else
551             stream_type = STREAM_TYPE_VIDEO;
552     }
553
554     lms_string_size_strip_and_free(&info.title);
555     lms_string_size_strip_and_free(&info.artist);
556     lms_string_size_strip_and_free(&info.album);
557     lms_string_size_strip_and_free(&info.genre);
558
559     if (!info.title.str) {
560         long ext_idx;
561         ext_idx = ((long)match) - 1;
562         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
563         info.title.str = malloc((info.title.len + 1) * sizeof(char));
564         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
565         info.title.str[info.title.len] = '\0';
566         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
567     }
568
569 #if 0
570     fprintf(stderr, "file %s info\n", finfo->path);
571     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
572     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
573     fprintf(stderr, "\talbum='%s'\n", info.album.str);
574     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
575     fprintf(stderr, "\ttrackno=%d\n", info.trackno);
576 #endif
577
578     audio_info.container = _container;
579
580     if (stream_type == STREAM_TYPE_AUDIO) {
581         audio_info.id = finfo->id;
582         audio_info.title = info.title;
583         audio_info.artist = info.artist;
584         audio_info.album = info.album;
585         audio_info.genre = info.genre;
586         audio_info.trackno = info.trackno;
587
588         audio_info.channels = streams->audio.channels;
589         audio_info.bitrate = streams->audio.byterate * 8;
590         audio_info.sampling_rate = streams->audio.sampling_rate;
591         audio_info.codec = _codec_id_to_str(streams->audio.codec_id);
592
593         r = lms_db_audio_add(plugin->audio_db, &audio_info);
594     } else {
595         video_info.id = finfo->id;
596         video_info.title = info.title;
597         video_info.artist = info.artist;
598         r = lms_db_video_add(plugin->video_db, &video_info);
599     }
600
601 done:
602     while (streams) {
603         struct stream *s = streams;
604         streams = s->next;
605         free(s);
606     }
607
608     free(info.title.str);
609     free(info.artist.str);
610     free(info.album.str);
611     free(info.genre.str);
612
613     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
614     close(fd);
615
616     return r;
617 }
618
619 static int
620 _setup(struct plugin *plugin, struct lms_context *ctxt)
621 {
622     plugin->audio_db = lms_db_audio_new(ctxt->db);
623     if (!plugin->audio_db)
624         return -1;
625     plugin->video_db = lms_db_video_new(ctxt->db);
626     if (!plugin->video_db)
627         return -1;
628     plugin->cs_conv = lms_charset_conv_new();
629     if (!plugin->cs_conv)
630         return -1;
631     lms_charset_conv_add(plugin->cs_conv, "UTF-16LE");
632
633     return 0;
634 }
635
636 static int
637 _start(struct plugin *plugin, struct lms_context *ctxt)
638 {
639     int r;
640     r = lms_db_audio_start(plugin->audio_db);
641     r |= lms_db_video_start(plugin->video_db);
642     return r;
643 }
644
645 static int
646 _finish(struct plugin *plugin, struct lms_context *ctxt)
647 {
648     if (plugin->audio_db)
649         lms_db_audio_free(plugin->audio_db);
650     if (plugin->video_db)
651         lms_db_video_free(plugin->video_db);
652     if (plugin->cs_conv)
653         lms_charset_conv_free(plugin->cs_conv);
654
655     return 0;
656 }
657
658 static int
659 _close(struct plugin *plugin)
660 {
661     free(plugin);
662     return 0;
663 }
664
665 API struct lms_plugin *
666 lms_plugin_open(void)
667 {
668     struct plugin *plugin;
669
670     plugin = (struct plugin *)malloc(sizeof(*plugin));
671     plugin->plugin.name = _name;
672     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
673     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
674     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
675     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
676     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
677     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
678
679     return (struct lms_plugin *)plugin;
680 }
681
682 API const struct lms_plugin_info *
683 lms_plugin_info(void)
684 {
685     static struct lms_plugin_info info = {
686         _name,
687         _cats,
688         "Microsoft WMA, WMV and ASF",
689         PACKAGE_VERSION,
690         _authors,
691         "http://lms.garage.maemo.org"
692     };
693
694     return &info;
695 }