Updated package version
[lms] / lightmediascanner / 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 #define ID3V1_NUM_GENRES 148
49
50 #define ID3_NUM_ENCODINGS 5
51
52 enum ID3Encodings {
53     ID3_ENCODING_LATIN1 = 0,
54     ID3_ENCODING_UTF16,
55     ID3_ENCODING_UTF16BE,
56     ID3_ENCODING_UTF8,
57     ID3_ENCODING_UTF16LE
58 };
59
60 static const char *id3v1_genres[ID3V1_NUM_GENRES] = {
61     "Blues",
62     "Classic Rock",
63     "Country",
64     "Dance",
65     "Disco",
66     "Funk",
67     "Grunge",
68     "Hip-Hop",
69     "Jazz",
70     "Metal",
71     "New Age",
72     "Oldies",
73     "Other",
74     "Pop",
75     "R&B",
76     "Rap",
77     "Reggae",
78     "Rock",
79     "Techno",
80     "Industrial",
81     "Alternative",
82     "Ska",
83     "Death Metal",
84     "Pranks",
85     "Soundtrack",
86     "Euro-Techno",
87     "Ambient",
88     "Trip-Hop",
89     "Vocal",
90     "Jazz+Funk",
91     "Fusion",
92     "Trance",
93     "Classical",
94     "Instrumental",
95     "Acid",
96     "House",
97     "Game",
98     "Sound Clip",
99     "Gospel",
100     "Noise",
101     "Alternative Rock",
102     "Bass",
103     "Soul",
104     "Punk",
105     "Space",
106     "Meditative",
107     "Instrumental Pop",
108     "Instrumental Rock",
109     "Ethnic",
110     "Gothic",
111     "Darkwave",
112     "Techno-Industrial",
113     "Electronic",
114     "Pop-Folk",
115     "Eurodance",
116     "Dream",
117     "Southern Rock",
118     "Comedy",
119     "Cult",
120     "Gangsta",
121     "Top 40",
122     "Christian Rap",
123     "Pop/Funk",
124     "Jungle",
125     "Native American",
126     "Cabaret",
127     "New Wave",
128     "Psychedelic",
129     "Rave",
130     "Showtunes",
131     "Trailer",
132     "Lo-Fi",
133     "Tribal",
134     "Acid Punk",
135     "Acid Jazz",
136     "Polka",
137     "Retro",
138     "Musical",
139     "Rock & Roll",
140     "Hard Rock",
141     "Folk",
142     "Folk/Rock",
143     "National Folk",
144     "Swing",
145     "Fusion",
146     "Bebob",
147     "Latin",
148     "Revival",
149     "Celtic",
150     "Bluegrass",
151     "Avantgarde",
152     "Gothic Rock",
153     "Progressive Rock",
154     "Psychedelic Rock",
155     "Symphonic Rock",
156     "Slow Rock",
157     "Big Band",
158     "Chorus",
159     "Easy Listening",
160     "Acoustic",
161     "Humour",
162     "Speech",
163     "Chanson",
164     "Opera",
165     "Chamber Music",
166     "Sonata",
167     "Symphony",
168     "Booty Bass",
169     "Primus",
170     "Porn Groove",
171     "Satire",
172     "Slow Jam",
173     "Club",
174     "Tango",
175     "Samba",
176     "Folklore",
177     "Ballad",
178     "Power Ballad",
179     "Rhythmic Soul",
180     "Freestyle",
181     "Duet",
182     "Punk Rock",
183     "Drum Solo",
184     "A Cappella",
185     "Euro-House",
186     "Dance Hall",
187     "Goa",
188     "Drum & Bass",
189     "Club-House",
190     "Hardcore",
191     "Terror",
192     "Indie",
193     "BritPop",
194     "Negerpunk",
195     "Polsk Punk",
196     "Beat",
197     "Christian Gangsta Rap",
198     "Heavy Metal",
199     "Black Metal",
200     "Crossover",
201     "Contemporary Christian",
202     "Christian Rock",
203     "Merengue",
204     "Salsa",
205     "Thrash Metal",
206     "Anime",
207     "Jpop",
208     "Synthpop"
209 };
210
211 struct id3v2_frame_header {
212     char frame_id[4];
213     unsigned int frame_size;
214     int compression;
215     int data_length_indicator;
216 };
217
218 struct id3v1_tag {
219     char title[30];
220     char artist[30];
221     char album[30];
222     char year[4];
223     char comments[30];
224     char genre;
225 } __attribute__((packed));
226
227 struct plugin {
228     struct lms_plugin plugin;
229     lms_db_audio_t *audio_db;
230     lms_charset_conv_t *cs_convs[ID3_NUM_ENCODINGS];
231 };
232
233 static const char _name[] = "id3";
234 static const struct lms_string_size _exts[] = {
235     LMS_STATIC_STRING_SIZE(".mp3"),
236     LMS_STATIC_STRING_SIZE(".aac")
237 };
238
239 static unsigned int
240 _to_uint(const char *data, int data_size)
241 {
242     unsigned int sum = 0;
243     unsigned int last, i;
244
245     last = data_size > 4 ? 3 : data_size - 1;
246
247     for (i = 0; i <= last; i++)
248         sum |= ((unsigned char) data[i]) << ((last - i) * 8);
249
250     return sum;
251 }
252
253 static inline int
254 _is_id3v2_second_synch_byte(unsigned char byte)
255 {
256     if (byte == 0xff)
257         return 0;
258     if ((byte & 0xE0) == 0xE0)
259         return 1;
260     return 0;
261 }
262
263 static long
264 _find_id3v2(int fd)
265 {
266     long buffer_offset = 0;
267     char buffer[3], *p;
268     int buffer_size = sizeof(buffer);
269     const char pattern[] = "ID3";
270     ssize_t nread;
271
272     /* These variables are used to keep track of a partial match that happens at
273      * the end of a buffer. */
274     int previous_partial_match = -1;
275     int previous_partial_synch_match = 0;
276     int first_synch_byte;
277
278     /* Start the search at the beginning of the file. */
279     lseek(fd, 0, SEEK_SET);
280
281     if ((nread = read(fd, &buffer, buffer_size)) != buffer_size)
282         return -1;
283
284     /* check if pattern is in the beggining of the file */
285     if (memcmp(buffer, pattern, 3) == 0)
286         return 0;
287
288     /* This loop is the crux of the find method.  There are three cases that we
289      * want to account for:
290      * (1) The previously searched buffer contained a partial match of the search
291      * pattern and we want to see if the next one starts with the remainder of
292      * that pattern.
293      *
294      * (2) The search pattern is wholly contained within the current buffer.
295      *
296      * (3) The current buffer ends with a partial match of the pattern.  We will
297      * note this for use in the next iteration, where we will check for the rest
298      * of the pattern. */
299     while (1) {
300         /* (1) previous partial match */
301         if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
302             return -1;
303
304         if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
305             const int pattern_offset = buffer_size - previous_partial_match;
306
307             if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
308                 return buffer_offset - buffer_size + previous_partial_match;
309         }
310
311         /* (2) pattern contained in current buffer */
312         p = buffer;
313         while ((p = memchr(p, 'I', buffer_size))) {
314             if (memcmp(p, pattern, 3) == 0)
315                 return buffer_offset + (p - buffer);
316             p += 1;
317         }
318
319         p = memchr(buffer, 255, buffer_size);
320         if (p)
321             first_synch_byte = p - buffer;
322         else
323             first_synch_byte = -1;
324
325         /* Here we have to loop because there could be several of the first
326          * (11111111) byte, and we want to check all such instances until we find
327          * a full match (11111111 111) or hit the end of the buffer. */
328         while (first_synch_byte >= 0) {
329             /* if this *is not* at the end of the buffer */
330             if (first_synch_byte < buffer_size - 1) {
331                 if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
332                     /* We've found the frame synch pattern. */
333                     return -1;
334                 else
335                     /* We found 11111111 at the end of the current buffer indicating a
336                      * partial match of the synch pattern.  The find() below should
337                      * return -1 and break out of the loop. */
338                     previous_partial_synch_match = 1;
339             }
340
341             /* Check in the rest of the buffer. */
342             p = memchr(p + 1, 255, buffer_size);
343             if (p)
344                 first_synch_byte = p - buffer;
345             else
346                 first_synch_byte = -1;
347         }
348
349         /* (3) partial match */
350         if (buffer[nread - 1] == pattern[1])
351             previous_partial_match = nread - 1;
352         else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
353             previous_partial_match = nread - 2;
354         buffer_offset += buffer_size;
355
356         if ((nread = read(fd, &buffer, sizeof(buffer))) == -1)
357             return -1;
358     }
359
360     return -1;
361 }
362
363 static unsigned int
364 _get_id3v2_frame_header_size(unsigned int version)
365 {
366     switch (version) {
367     case 0:
368     case 1:
369     case 2:
370         return 6;
371     case 3:
372     case 4:
373     default:
374         return 10;
375     }
376 }
377
378 static void
379 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
380 {
381     switch (version) {
382     case 0:
383     case 1:
384     case 2:
385         memcpy(fh->frame_id, data, 3);
386         fh->frame_id[3] = 0;
387         fh->frame_size = _to_uint(data + 3, 3);
388         fh->compression = 0;
389         fh->data_length_indicator = 0;
390         break;
391     case 3:
392         memcpy(fh->frame_id, data, 4);
393         fh->frame_size = _to_uint(data + 4, 4);
394         fh->compression = data[9] & 0x40;
395         fh->data_length_indicator = 0;
396         break;
397     case 4:
398     default:
399         memcpy(fh->frame_id, data, 4);
400         fh->frame_size = _to_uint(data + 4, 4);
401         fh->compression = data[9] & 0x4;
402         fh->data_length_indicator = data[9] & 0x1;
403         break;
404     }
405 }
406
407 static inline void
408 _get_id3v2_frame_info(const char *frame_data, unsigned int frame_size, struct lms_string_size *s, lms_charset_conv_t *cs_conv)
409 {
410     s->str = malloc(sizeof(char) * (frame_size + 1));
411     memcpy(s->str, frame_data, frame_size);
412     s->str[frame_size] = '\0';
413     s->len = frame_size;
414     if (cs_conv)
415         lms_charset_conv(cs_conv, &s->str, &s->len);
416 }
417
418 static void
419 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info, lms_charset_conv_t **cs_convs)
420 {
421     lms_charset_conv_t *cs_conv = NULL;
422     unsigned int text_encoding, frame_size;
423
424 #if 0
425     fprintf(stderr, "text encoding = %d\n", frame_data[0]);
426 #endif
427
428     /* Latin1  = 0
429      * UTF16   = 1
430      * UTF16BE = 2
431      * UTF8    = 3
432      * UTF16LE = 4
433      */
434     text_encoding = frame_data[0];
435     if (text_encoding >= 0 && text_encoding < ID3_NUM_ENCODINGS)
436         cs_conv = cs_convs[text_encoding];
437
438     /* skip first byte - text encoding */
439     frame_data += 1;
440     frame_size = fh->frame_size - 1;
441
442     /* ID3v2.2 used 3 bytes for the frame id, so let's check it */
443     if (memcmp(fh->frame_id, "TIT2", 4) == 0 ||
444         memcmp(fh->frame_id, "TT2", 3) == 0)
445         _get_id3v2_frame_info(frame_data, frame_size, &info->title, cs_conv);
446     else if (memcmp(fh->frame_id, "TPE1", 4) == 0 ||
447              memcmp(fh->frame_id, "TP1", 3) == 0)
448         _get_id3v2_frame_info(frame_data, frame_size, &info->artist, cs_conv);
449     /* TALB, TAL */
450     else if (memcmp(fh->frame_id, "TAL", 3) == 0)
451         _get_id3v2_frame_info(frame_data, frame_size, &info->album, cs_conv);
452     /* TCON, TCO */
453     else if (memcmp(fh->frame_id, "TCO", 3) == 0) {
454         /* TODO handle multiple genres */
455         int i, is_number;
456
457         _get_id3v2_frame_info(frame_data, frame_size, &info->genre, cs_conv);
458
459         is_number = 1;
460         for (i = 0; i < info->genre.len; ++i) {
461             if (!isdigit(info->genre.str[i]))
462                 is_number = 0;
463         }
464
465         if ((is_number) &&
466             (info->genre.str) &&
467             (info->genre.len > 0)) {
468             int genre = atoi(info->genre.str);
469             if (genre >= 0 && genre < ID3V1_NUM_GENRES) {
470                 free(info->genre.str);
471                 info->genre.str = strdup(id3v1_genres[(int) genre]);
472                 info->genre.len = strlen(info->genre.str);
473             }
474             else {
475                 /* ignore other genres */
476                 free(info->genre.str);
477                 info->genre.str = NULL;
478                 info->genre.len = 0;
479             }
480         }
481     }
482     else if (memcmp(fh->frame_id, "TRCK", 4) == 0 ||
483              memcmp(fh->frame_id, "TRK", 3) == 0) {
484         struct lms_string_size trackno;
485         _get_id3v2_frame_info(frame_data, frame_size, &trackno, cs_conv);
486         info->trackno = atoi(trackno.str);
487         free(trackno.str);
488     }
489 }
490
491 static int
492 _parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info, lms_charset_conv_t **cs_convs)
493 {
494     char header_data[10], frame_header_data[10];
495     unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
496     int extended_header, footer_present;
497     struct id3v2_frame_header fh;
498
499     lseek(fd, id3v2_offset, SEEK_SET);
500
501     /* parse header */
502     if (read(fd, header_data, ID3V2_HEADER_SIZE) != ID3V2_HEADER_SIZE)
503         return -1;
504
505     tag_size = _to_uint(header_data + 6, 4);
506     if (tag_size == 0)
507         return -1;
508
509     /* parse frames */
510     major_version = header_data[3];
511
512     frame_data_pos = 0;
513     frame_data_length = tag_size;
514
515     /* check for extended header */
516     extended_header = header_data[5] & 0x20; /* bit 6 */
517     if (extended_header) {
518         /* skip extended header */
519         unsigned int extended_header_size;
520         char extended_header_data[4];
521
522         if (read(fd, extended_header_data, 4) != 4)
523             return -1;
524         extended_header_size = _to_uint(extended_header_data, 4);
525         lseek(fd, extended_header_size - 4, SEEK_CUR);
526         frame_data_pos += extended_header_size;
527         frame_data_length -= extended_header_size;
528     }
529
530     footer_present = header_data[5] & 0x8;   /* bit 4 */
531     if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
532         frame_data_length -= ID3V2_FOOTER_SIZE;
533
534     frame_header_size = _get_id3v2_frame_header_size(major_version);
535     while (frame_data_pos < frame_data_length - frame_header_size) {
536         if (read(fd, frame_header_data, frame_header_size) != frame_header_size)
537             return -1;
538
539         if (frame_header_data[0] == 0)
540             break;
541
542         _parse_id3v2_frame_header(frame_header_data, major_version, &fh);
543
544         if (!fh.compression &&
545             fh.frame_id[0] == 'T' &&
546             memcmp(fh.frame_id, "TXXX", 4) != 0) {
547             char *frame_data;
548
549             if (fh.data_length_indicator)
550                 lseek(fd, 4, SEEK_CUR);
551
552             frame_data = malloc(sizeof(char) * fh.frame_size);
553             if (read(fd, frame_data, fh.frame_size) != fh.frame_size) {
554                 free(frame_data);
555                 return -1;
556             }
557
558             _parse_id3v2_frame(&fh, frame_data, info, cs_convs);
559             free(frame_data);
560         }
561         else {
562             if (fh.data_length_indicator)
563                 lseek(fd, fh.frame_size + 4, SEEK_CUR);
564             else
565                 lseek(fd, fh.frame_size, SEEK_CUR);
566         }
567
568         frame_data_pos += fh.frame_size + frame_header_size;
569     }
570
571     return 0;
572 }
573
574 static int
575 _parse_id3v1(int fd, struct lms_audio_info *info, lms_charset_conv_t *cs_conv)
576 {
577     struct id3v1_tag tag;
578     if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
579         return -1;
580
581     info->title.str = strndup(tag.title, 30);
582     info->title.len = strlen(info->title.str);
583     lms_charset_conv(cs_conv, &info->title.str, &info->title.len);
584     info->artist.str = strndup(tag.artist, 30);
585     info->artist.len = strlen(info->artist.str);
586     lms_charset_conv(cs_conv, &info->artist.str, &info->artist.len);
587     info->album.str = strndup(tag.album, 30);
588     info->album.len = strlen(info->album.str);
589     lms_charset_conv(cs_conv, &info->album.str, &info->album.len);
590     if ((unsigned char) tag.genre < ID3V1_NUM_GENRES) {
591         info->genre.str = strdup(id3v1_genres[(unsigned char) tag.genre]);
592         info->genre.len = strlen(info->genre.str);
593     }
594     if (tag.comments[28] == 0 && tag.comments[29] != 0)
595         info->trackno = (unsigned char) tag.comments[29];
596
597     return 0;
598 }
599
600 static void *
601 _match(struct plugin *p, const char *path, int len, int base)
602 {
603     int i;
604
605     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
606     if (i < 0)
607       return NULL;
608     else
609       return (void*)(i + 1);
610 }
611
612 static int
613 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
614 {
615     struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
616     int r, fd;
617     long id3v2_offset;
618
619     fd = open(finfo->path, O_RDONLY);
620     if (fd < 0) {
621         perror("open");
622         return -1;
623     }
624
625     id3v2_offset = _find_id3v2(fd);
626     if (id3v2_offset >= 0) {
627 #if 0
628         fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
629                 finfo->path, id3v2_offset);
630 #endif
631         if (_parse_id3v2(fd, id3v2_offset, &info, plugin->cs_convs) != 0) {
632             r = -2;
633             goto done;
634         }
635     }
636     else {
637         char tag[3];
638 #if 0
639         fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n", finfo->path);
640 #endif
641         /* check for id3v1 tag */
642         if (lseek(fd, -128, SEEK_END) == -1) {
643             r = -3;
644             goto done;
645         }
646
647         if (read(fd, &tag, 3) == -1) {
648             r = -4;
649             goto done;
650         }
651
652         if (memcmp(tag, "TAG", 3) == 0) {
653 #if 0
654             fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
655 #endif
656             if (_parse_id3v1(fd, &info, ctxt->cs_conv) != 0) {
657                 r = -5;
658                 goto done;
659             }
660         }
661     }
662
663     lms_string_size_strip_and_free(&info.title);
664     lms_string_size_strip_and_free(&info.artist);
665     lms_string_size_strip_and_free(&info.album);
666     lms_string_size_strip_and_free(&info.genre);
667
668     if (!info.title.str) {
669         int ext_idx;
670         ext_idx = ((int)match) - 1;
671         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
672         info.title.str = malloc((info.title.len + 1) * sizeof(char));
673         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
674         info.title.str[info.title.len] = '\0';
675         lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
676     }
677
678 #if 0
679     fprintf(stderr, "file %s info\n", finfo->path);
680     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
681     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
682     fprintf(stderr, "\talbum='%s'\n", info.album.str);
683     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
684     fprintf(stderr, "\ttrack number='%d'\n", info.trackno);
685 #endif
686
687     info.id = finfo->id;
688     r = lms_db_audio_add(plugin->audio_db, &info);
689
690   done:
691     close(fd);
692
693     if (info.title.str)
694         free(info.title.str);
695     if (info.artist.str)
696         free(info.artist.str);
697     if (info.album.str)
698         free(info.album.str);
699     if (info.genre.str)
700         free(info.genre.str);
701
702     return r;
703 }
704
705 static int
706 _setup(struct plugin *plugin, struct lms_context *ctxt)
707 {
708     int i;
709     const char *id3v2_encodings[ID3_NUM_ENCODINGS] = {
710         "Latin1",
711         "UTF-16",
712         "UTF-16BE",
713         NULL, /* UTF-8 */
714         "UTF-16LE",
715     };
716
717     plugin->audio_db = lms_db_audio_new(ctxt->db);
718     if (!plugin->audio_db)
719         return -1;
720
721     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
722         /* do not create charset conv for UTF-8 encoding */
723         if (i == ID3_ENCODING_UTF8) {
724             plugin->cs_convs[i] = NULL;
725             continue;
726         }
727         plugin->cs_convs[i] = lms_charset_conv_new_full(0, 0);
728         if (!plugin->cs_convs[i])
729             return -1;
730         lms_charset_conv_add(plugin->cs_convs[i], id3v2_encodings[i]);
731     }
732
733     return 0;
734 }
735
736 static int
737 _start(struct plugin *plugin, struct lms_context *ctxt)
738 {
739     return lms_db_audio_start(plugin->audio_db);
740 }
741
742 static int
743 _finish(struct plugin *plugin, struct lms_context *ctxt)
744 {
745     int i;
746
747     if (plugin->audio_db)
748         lms_db_audio_free(plugin->audio_db);
749
750     for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
751         if (plugin->cs_convs[i])
752             lms_charset_conv_free(plugin->cs_convs[i]);
753     }
754
755     return 0;
756 }
757
758 static int
759 _close(struct plugin *plugin)
760 {
761     free(plugin);
762     return 0;
763 }
764
765 API struct lms_plugin *
766 lms_plugin_open(void)
767 {
768     struct plugin *plugin;
769
770     plugin = (struct plugin *)malloc(sizeof(*plugin));
771     plugin->plugin.name = _name;
772     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
773     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
774     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
775     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
776     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
777     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
778
779     return (struct lms_plugin *)plugin;
780 }