Move logic to get specific frame contents to separate functions.
[platform/upstream/lightmediascanner.git] / src / plugins / id3 / id3.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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
19  */
20
21 /**
22  * @brief
23  *
24  * id3 file parser.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #define _GNU_SOURCE
32 #define _XOPEN_SOURCE 600
33 #include <lightmediascanner_plugin.h>
34 #include <lightmediascanner_db.h>
35 #include <lightmediascanner_charset_conv.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 #include <ctype.h>
44
45 #define ID3V2_HEADER_SIZE       10
46 #define ID3V2_FOOTER_SIZE       10
47
48 enum ID3Encodings {
49     ID3_ENCODING_LATIN1 = 0,
50     ID3_ENCODING_UTF16,
51     ID3_ENCODING_UTF16BE,
52     ID3_ENCODING_UTF8,
53     ID3_ENCODING_UTF16LE,
54     ID3_ENCODING_LAST
55 };
56 #define ID3_NUM_ENCODINGS ID3_ENCODING_LAST
57
58
59 #include "id3v1_genres.c"
60
61 struct id3_info {
62     struct lms_string_size title;
63     struct lms_string_size artist;
64     struct lms_string_size album;
65     struct lms_string_size genre;
66     unsigned char trackno;
67     int cur_artist_priority;
68 };
69
70 struct id3v2_frame_header {
71     char frame_id[4];
72     unsigned int frame_size;
73     int compression;
74     int data_length_indicator;
75 };
76
77 struct id3v1_tag {
78     char title[30];
79     char artist[30];
80     char album[30];
81     char year[4];
82     char comments[30];
83     char genre;
84 } __attribute__((packed));
85
86 struct plugin {
87     struct lms_plugin plugin;
88     lms_db_audio_t *audio_db;
89     lms_charset_conv_t *cs_convs[ID3_NUM_ENCODINGS];
90 };
91
92 static const char _name[] = "id3";
93 static const struct lms_string_size _exts[] = {
94     LMS_STATIC_STRING_SIZE(".mp3"),
95     LMS_STATIC_STRING_SIZE(".aac")
96 };
97
98 static unsigned int
99 _to_uint(const char *data, int data_size)
100 {
101     unsigned int sum = 0;
102     unsigned int last, i;
103
104     last = data_size > 4 ? 3 : data_size - 1;
105
106     for (i = 0; i <= last; i++)
107         sum |= ((unsigned char) data[i]) << ((last - i) * 8);
108
109     return sum;
110 }
111
112 static inline int
113 _is_id3v2_second_synch_byte(unsigned char byte)
114 {
115     if (byte == 0xff)
116         return 0;
117     if ((byte & 0xE0) == 0xE0)
118         return 1;
119     return 0;
120 }
121
122 static long
123 _find_id3v2(int fd)
124 {
125     long buffer_offset = 0;
126     char buffer[3], *p;
127     int buffer_size = sizeof(buffer);
128     const char pattern[] = "ID3";
129     ssize_t nread;
130
131     /* These variables are used to keep track of a partial match that happens at
132      * the end of a buffer. */
133     int previous_partial_match = -1;
134     int previous_partial_synch_match = 0;
135     int first_synch_byte;
136
137     /* Start the search at the beginning of the file. */
138     lseek(fd, 0, SEEK_SET);
139
140     if ((nread = read(fd, &buffer, buffer_size)) != buffer_size)
141         return -1;
142
143     /* check if pattern is in the beggining of the file */
144     if (memcmp(buffer, pattern, 3) == 0)
145         return 0;
146
147     /* This loop is the crux of the find method.  There are three cases that we
148      * want to account for:
149      * (1) The previously searched buffer contained a partial match of the
150      * search pattern and we want to see if the next one starts with the
151      * remainder of that pattern.
152      *
153      * (2) The search pattern is wholly contained within the current buffer.
154      *
155      * (3) The current buffer ends with a partial match of the pattern.  We will
156      * note this for use in the next iteration, where we will check for the rest
157      * of the pattern.
158      */
159     while (1) {
160         /* (1) previous partial match */
161         if (previous_partial_synch_match &&
162             _is_id3v2_second_synch_byte(buffer[0]))
163             return -1;
164
165         if (previous_partial_match >= 0 &&
166             previous_partial_match < buffer_size) {
167             const int pat_offset = buffer_size - previous_partial_match;
168
169             if (memcmp(buffer, pattern + pat_offset, 3 - pat_offset) == 0)
170                 return buffer_offset - buffer_size + previous_partial_match;
171         }
172
173         /* (2) pattern contained in current buffer */
174         p = buffer;
175         while ((p = memchr(p, 'I', buffer_size - (p - buffer)))) {
176             if (buffer_size - (p - buffer) < 3)
177                 break;
178
179             if (memcmp(p, pattern, 3) == 0)
180                 return buffer_offset + (p - buffer);
181
182             p += 1;
183         }
184
185         p = memchr(buffer, 255, buffer_size);
186         if (p)
187             first_synch_byte = p - buffer;
188         else
189             first_synch_byte = -1;
190
191         /* Here we have to loop because there could be several of the first
192          * (11111111) byte, and we want to check all such instances until we
193          * find a full match (11111111 111) or hit the end of the buffer.
194          */
195         while (first_synch_byte >= 0) {
196             /* if this *is not* at the end of the buffer */
197             if (first_synch_byte < buffer_size - 1) {
198                 if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
199                     /* We've found the frame synch pattern. */
200                     return -1;
201                 else
202                     /* We found 11111111 at the end of the current buffer
203                      * indicating a partial match of the synch pattern.
204                      * The find() below should return -1 and break out of
205                      * the loop.
206                      */
207                     previous_partial_synch_match = 1;
208             }
209
210             /* Check in the rest of the buffer. */
211             p = memchr(p + 1, 255, buffer_size - (p + 1 - buffer));
212             if (p)
213                 first_synch_byte = p - buffer;
214             else
215                 first_synch_byte = -1;
216         }
217
218         /* (3) partial match */
219         if (buffer[nread - 1] == pattern[1])
220             previous_partial_match = nread - 1;
221         else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
222             previous_partial_match = nread - 2;
223         buffer_offset += buffer_size;
224
225         if ((nread = read(fd, &buffer, sizeof(buffer))) == -1)
226             return -1;
227     }
228
229     return -1;
230 }
231
232 static unsigned int
233 _get_id3v2_frame_header_size(unsigned int version)
234 {
235     switch (version) {
236     case 0:
237     case 1:
238     case 2:
239         return 6;
240     case 3:
241     case 4:
242     default:
243         return 10;
244     }
245 }
246
247 static void
248 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
249 {
250     switch (version) {
251     case 0:
252     case 1:
253     case 2:
254         memcpy(fh->frame_id, data, 3);
255         fh->frame_id[3] = 0;
256         fh->frame_size = _to_uint(data + 3, 3);
257         fh->compression = 0;
258         fh->data_length_indicator = 0;
259         break;
260     case 3:
261         memcpy(fh->frame_id, data, 4);
262         fh->frame_size = _to_uint(data + 4, 4);
263         fh->compression = data[9] & 0x40;
264         fh->data_length_indicator = 0;
265         break;
266     case 4:
267     default:
268         memcpy(fh->frame_id, data, 4);
269         fh->frame_size = _to_uint(data + 4, 4);
270         fh->compression = data[9] & 0x4;
271         fh->data_length_indicator = data[9] & 0x1;
272         break;
273     }
274 }
275
276 static inline void
277 _get_id3v2_frame_info(const char *frame_data, unsigned int frame_size, struct lms_string_size *s, lms_charset_conv_t *cs_conv, int strip)
278 {
279     if (frame_size > s->len)
280         s->str = realloc(s->str, sizeof(char) * (frame_size + 1));
281     memcpy(s->str, frame_data, frame_size);
282     s->str[frame_size] = '\0';
283     s->len = frame_size;
284     if (cs_conv)
285         lms_charset_conv(cs_conv, &s->str, &s->len);
286     if (strip)
287         lms_string_size_strip_and_free(s);
288 }
289
290 static int
291 _get_id3v2_artist(unsigned int index, const char *frame_data, unsigned int frame_size, struct id3_info *info, lms_charset_conv_t *cs_conv)
292 {
293     static const unsigned char artist_priorities[] = {3, 4, 2, 1};
294     const unsigned int index_max = sizeof(artist_priorities) / sizeof(*artist_priorities);
295
296     if (index >= index_max)
297         return 1;
298
299     if (artist_priorities[index] > info->cur_artist_priority) {
300         struct lms_string_size artist = {0};
301
302         _get_id3v2_frame_info(frame_data, frame_size, &artist, cs_conv, 1);
303         if (artist.str) {
304             if (info->artist.str)
305                 free(info->artist.str);
306             info->artist = artist;
307             info->cur_artist_priority = artist_priorities[index];
308         }
309     }
310     return 0;
311 }
312
313 static int
314 _get_id3v1_genre(unsigned int genre, struct lms_string_size *out)
315 {
316     if (genre < ID3V1_NUM_GENRES) {
317         unsigned int size, base, len;
318
319         base = id3v1_genres_offsets[genre];
320         size = id3v1_genres_offsets[genre + 1] - base;
321         len = size - 1;
322
323         if (len > out->len) {
324             char *p = realloc(out->str, size);
325             if (!p)
326                 return -2;
327             out->str = p;
328         }
329
330         out->len = len;
331         memcpy(out->str, id3v1_genres_mem + base, size);
332
333         return 0;
334     }
335     return -1;
336 }
337
338 static inline int
339 _parse_id3v1_genre(const char *str_genre, struct lms_string_size *out)
340 {
341     return _get_id3v1_genre(atoi(str_genre), out);
342 }
343
344 static void
345 _get_id3v2_genre(const char *frame_data, unsigned int frame_size, struct lms_string_size *out, lms_charset_conv_t *cs_conv)
346 {
347     int i, is_number;
348     struct lms_string_size genre = {0};
349
350     _get_id3v2_frame_info(frame_data, frame_size, &genre, cs_conv, 1);
351
352     if (!genre.str)
353         return;
354
355     is_number = (genre.len != 0 && genre.str[0] != '(');
356     if (is_number) {
357         for (i = 0; i < genre.len; ++i) {
358             if (!isdigit(genre.str[i])) {
359                 is_number = 0;
360                 break;
361             }
362         }
363     }
364
365     if (is_number && _parse_id3v1_genre(genre.str, out) == 0) {
366         /* id3v1 genre found */
367         free(genre.str);
368         return;
369     }
370
371     /* ID3v2.3 "content type" can contain a ID3v1 genre number in parenthesis at
372      * the beginning of the field. If this is all that the field contains, do a
373      * translation from that number to the name and return that.  If there is a
374      * string folloing the ID3v1 genre number, that is considered to be
375      * authoritative and we return that instead. Or finally, the field may
376      * simply be free text, in which case we just return the value. */
377
378     if (genre.len > 1 && genre.str[0] == '(') {
379         char *closing = NULL;
380
381         if (genre.str[genre.len - 1] == ')') {
382             closing = strchr(genre.str, ')');
383             if (closing == genre.str + genre.len - 1) {
384                 /* ) is the last character and only appears once in the
385                  * string get the id3v1 genre enclosed by parentheses
386                  */
387                 if (_parse_id3v1_genre(genre.str + 1, out) == 0) {
388                     free(genre.str);
389                     return;
390                 }
391             }
392         }
393
394         /* get the string followed by the id3v1 genre */
395         if (!closing)
396             closing = strchr(genre.str, ')');
397
398         if (closing) {
399             closing++;
400             out->len = genre.len - (closing - genre.str);
401             out->str = genre.str;
402             memmove(out->str, closing, out->len + 1); /* includes '\0' */
403             lms_string_size_strip_and_free(out);
404             return;
405         }
406     }
407
408     /* pure text */
409     *out = genre;
410 }
411
412 static void
413 _get_id3v2_trackno(const char *frame_data, unsigned int frame_size, struct id3_info *info, lms_charset_conv_t *cs_conv)
414 {
415     struct lms_string_size trackno = {0};
416
417     _get_id3v2_frame_info(frame_data, frame_size, &trackno, cs_conv, 0);
418     info->trackno = atoi(trackno.str);
419     free(trackno.str);
420 }
421
422 static void
423 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct id3_info *info, lms_charset_conv_t **cs_convs)
424 {
425     lms_charset_conv_t *cs_conv = NULL;
426     unsigned int text_encoding, frame_size;
427
428 #if 0
429     fprintf(stderr, "frame id = %.4s frame size = %d text encoding = %d\n",
430             fh->frame_id, fh->frame_size, frame_data[0]);
431 #endif
432
433     /* Latin1  = 0
434      * UTF16   = 1
435      * UTF16BE = 2
436      * UTF8    = 3
437      * UTF16LE = 4
438      */
439     text_encoding = frame_data[0];
440
441     /* skip first byte - text encoding */
442     frame_data += 1;
443     frame_size = fh->frame_size - 1;
444
445     if (text_encoding >= 0 && text_encoding < ID3_NUM_ENCODINGS) {
446         if (text_encoding == ID3_ENCODING_UTF16) {
447             if (memcmp(frame_data, "\xfe\xff", 2) == 0)
448                 text_encoding = ID3_ENCODING_UTF16BE;
449             else
450                 text_encoding = ID3_ENCODING_UTF16LE;
451             frame_data += 2;
452             frame_size -= 2;
453         }
454         cs_conv = cs_convs[text_encoding];
455     }
456
457     /* ID3v2.2 used 3 bytes for the frame id, so let's check it */
458     if (memcmp(fh->frame_id, "TIT2", 4) == 0 ||
459         memcmp(fh->frame_id, "TT2", 3) == 0)
460         _get_id3v2_frame_info(frame_data, frame_size, &info->title, cs_conv, 1);
461     else if (memcmp(fh->frame_id, "TP", 2) == 0) {
462         unsigned int index;
463
464         if (fh->frame_id[2] == 'E')
465             index = fh->frame_id[3] - '1';
466         else
467             index = fh->frame_id[2] - '1';
468
469         _get_id3v2_artist(index, frame_data, frame_size, info, cs_conv);
470     }
471     /* TALB, TAL */
472     else if (memcmp(fh->frame_id, "TAL", 3) == 0)
473         _get_id3v2_frame_info(frame_data, frame_size, &info->album, cs_conv, 1);
474     /* TCON, TCO */
475     else if (memcmp(fh->frame_id, "TCO", 3) == 0)
476         _get_id3v2_genre(frame_data, frame_size, &info->genre, cs_conv);
477     else if (memcmp(fh->frame_id, "TRCK", 4) == 0 ||
478              memcmp(fh->frame_id, "TRK", 3) == 0)
479         _get_id3v2_trackno(frame_data, frame_size, info, cs_conv);
480 }
481
482 static int
483 _parse_id3v2(int fd, long id3v2_offset, struct id3_info *info, lms_charset_conv_t **cs_convs)
484 {
485     char header_data[10], frame_header_data[10];
486     unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
487     int extended_header, footer_present;
488     struct id3v2_frame_header fh;
489     size_t nread;
490
491     lseek(fd, id3v2_offset, SEEK_SET);
492
493     /* parse header */
494     if (read(fd, header_data, ID3V2_HEADER_SIZE) != ID3V2_HEADER_SIZE)
495         return -1;
496
497     tag_size = _to_uint(header_data + 6, 4);
498     if (tag_size == 0)
499         return -1;
500
501     /* parse frames */
502     major_version = header_data[3];
503
504     frame_data_pos = 0;
505     frame_data_length = tag_size;
506
507     /* check for extended header */
508     extended_header = header_data[5] & 0x20; /* bit 6 */
509     if (extended_header) {
510         /* skip extended header */
511         unsigned int extended_header_size;
512         char extended_header_data[4];
513
514         if (read(fd, extended_header_data, 4) != 4)
515             return -1;
516         extended_header_size = _to_uint(extended_header_data, 4);
517         lseek(fd, extended_header_size - 4, SEEK_CUR);
518         frame_data_pos += extended_header_size;
519         frame_data_length -= extended_header_size;
520     }
521
522     footer_present = header_data[5] & 0x8;   /* bit 4 */
523     if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
524         frame_data_length -= ID3V2_FOOTER_SIZE;
525
526     frame_header_size = _get_id3v2_frame_header_size(major_version);
527     while (frame_data_pos < frame_data_length - frame_header_size) {
528         nread = read(fd, frame_header_data, frame_header_size);
529         if (nread == 0)
530             break;
531
532         if (nread != frame_header_size)
533             return -1;
534
535         if (frame_header_data[0] == 0)
536             break;
537
538         _parse_id3v2_frame_header(frame_header_data, major_version, &fh);
539         if (!fh.frame_size)
540             break;
541
542         if (!fh.compression &&
543             fh.frame_id[0] == 'T' &&
544             memcmp(fh.frame_id, "TXXX", 4) != 0) {
545             char *frame_data;
546
547             if (fh.data_length_indicator)
548                 lseek(fd, 4, SEEK_CUR);
549
550             frame_data = malloc(sizeof(char) * fh.frame_size);
551             if (read(fd, frame_data, fh.frame_size) != fh.frame_size) {
552                 free(frame_data);
553                 return -1;
554             }
555
556             _parse_id3v2_frame(&fh, frame_data, info, cs_convs);
557             free(frame_data);
558         }
559         else {
560             if (fh.data_length_indicator)
561                 lseek(fd, fh.frame_size + 4, SEEK_CUR);
562             else
563                 lseek(fd, fh.frame_size, SEEK_CUR);
564         }
565
566         frame_data_pos += fh.frame_size + frame_header_size;
567     }
568
569     return 0;
570 }
571
572 static int
573 _parse_id3v1(int fd, struct id3_info *info, lms_charset_conv_t *cs_conv)
574 {
575     struct id3v1_tag tag;
576     if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
577         return -1;
578
579     info->title.str = strndup(tag.title, 30);
580     info->title.len = strlen(info->title.str);
581     lms_charset_conv(cs_conv, &info->title.str, &info->title.len);
582     info->artist.str = strndup(tag.artist, 30);
583     info->artist.len = strlen(info->artist.str);
584     lms_charset_conv(cs_conv, &info->artist.str, &info->artist.len);
585     info->album.str = strndup(tag.album, 30);
586     info->album.len = strlen(info->album.str);
587     lms_charset_conv(cs_conv, &info->album.str, &info->album.len);
588     _get_id3v1_genre(tag.genre, &info->genre);
589     if (tag.comments[28] == 0 && tag.comments[29] != 0)
590         info->trackno = (unsigned char) tag.comments[29];
591
592     return 0;
593 }
594
595 static void *
596 _match(struct plugin *p, const char *path, int len, int base)
597 {
598     int i;
599
600     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
601     if (i < 0)
602       return NULL;
603     else
604       return (void*)(i + 1);
605 }
606
607 static int
608 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
609 {
610     struct id3_info info = {{0}, {0}, {0}, {0}, 0, -1};
611     struct lms_audio_info audio_info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
612     int r, fd;
613     long id3v2_offset;
614
615     fd = open(finfo->path, O_RDONLY);
616     if (fd < 0) {
617         perror("open");
618         return -1;
619     }
620
621     id3v2_offset = _find_id3v2(fd);
622     if (id3v2_offset >= 0) {
623 #if 0
624         fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
625                 finfo->path, id3v2_offset);
626 #endif
627         if (_parse_id3v2(fd, id3v2_offset, &info, plugin->cs_convs) != 0) {
628             r = -2;
629             goto done;
630         }
631     }
632     else {
633         char tag[3];
634 #if 0
635         fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n",
636                 finfo->path);
637 #endif
638         /* check for id3v1 tag */
639         if (lseek(fd, -128, SEEK_END) == -1) {
640             r = -3;
641             goto done;
642         }
643
644         if (read(fd, &tag, 3) == -1) {
645             r = -4;
646             goto done;
647         }
648
649         if (memcmp(tag, "TAG", 3) == 0) {
650 #if 0
651             fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
652 #endif
653             if (_parse_id3v1(fd, &info, ctxt->cs_conv) != 0) {
654                 r = -5;
655                 goto done;
656             }
657         }
658     }
659
660     if (!info.title.str) {
661         int ext_idx;
662         ext_idx = ((int)match) - 1;
663         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
664         info.title.str = malloc((info.title.len + 1) * sizeof(char));
665         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
666         info.title.str[info.title.len] = '\0';
667         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
668     }
669
670 #if 0
671     fprintf(stderr, "file %s info\n", finfo->path);
672     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
673     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
674     fprintf(stderr, "\talbum='%s'\n", info.album.str);
675     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
676     fprintf(stderr, "\ttrack number='%d'\n", info.trackno);
677 #endif
678
679     audio_info.id = finfo->id;
680     audio_info.title = info.title;
681     audio_info.artist = info.artist;
682     audio_info.album = info.album;
683     audio_info.genre = info.genre;
684     audio_info.trackno = info.trackno;
685     r = lms_db_audio_add(plugin->audio_db, &audio_info);
686
687   done:
688     posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
689     close(fd);
690
691     if (info.title.str)
692         free(info.title.str);
693     if (info.artist.str)
694         free(info.artist.str);
695     if (info.album.str)
696         free(info.album.str);
697     if (info.genre.str)
698         free(info.genre.str);
699
700     return r;
701 }
702
703 static int
704 _setup(struct plugin *plugin, struct lms_context *ctxt)
705 {
706     int i;
707     const char *id3_encodings[ID3_NUM_ENCODINGS] = {
708         "Latin1",
709         NULL, /* UTF-16 */
710         "UTF-16BE",
711         NULL, /* UTF-8 */
712         "UTF-16LE",
713     };
714
715     plugin->audio_db = lms_db_audio_new(ctxt->db);
716     if (!plugin->audio_db)
717         return -1;
718
719     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
720         /* do not create charset conv for UTF-8 encoding */
721         if (!id3_encodings[i]) {
722             plugin->cs_convs[i] = NULL;
723             continue;
724         }
725         plugin->cs_convs[i] = lms_charset_conv_new_full(0, 0);
726         if (!plugin->cs_convs[i])
727             return -1;
728         lms_charset_conv_add(plugin->cs_convs[i], id3_encodings[i]);
729     }
730
731     return 0;
732 }
733
734 static int
735 _start(struct plugin *plugin, struct lms_context *ctxt)
736 {
737     return lms_db_audio_start(plugin->audio_db);
738 }
739
740 static int
741 _finish(struct plugin *plugin, struct lms_context *ctxt)
742 {
743     int i;
744
745     if (plugin->audio_db)
746         lms_db_audio_free(plugin->audio_db);
747
748     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
749         if (plugin->cs_convs[i])
750             lms_charset_conv_free(plugin->cs_convs[i]);
751     }
752
753     return 0;
754 }
755
756 static int
757 _close(struct plugin *plugin)
758 {
759     free(plugin);
760     return 0;
761 }
762
763 API struct lms_plugin *
764 lms_plugin_open(void)
765 {
766     struct plugin *plugin;
767
768     plugin = (struct plugin *)malloc(sizeof(*plugin));
769     plugin->plugin.name = _name;
770     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
771     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
772     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
773     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
774     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
775     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
776
777     return (struct lms_plugin *)plugin;
778 }