Added aac plugin.
[lms] / lightmediascanner / src / plugins / aac / aac.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  * aac file parser.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #define _XOPEN_SOURCE 600
32 #include <lightmediascanner_plugin.h>
33 #include <lightmediascanner_db.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 #define ID3V1_NUM_GENRES 126
43
44 static const char *id3v1_genres[126] = {
45     "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
46     "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop",
47     "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative",
48     "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
49     "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
50     "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
51     "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
52     "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
53     "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance",
54     "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40",
55     "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
56     "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
57     "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
58     "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion",
59     "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
60     "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
61     "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
62     "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
63     "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore",
64     "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock",
65     "Drum Solo", "A capella", "Euro-House", "Dance Hall",
66 };
67
68 struct id3v2_frame_header {
69     char frame_id[4];
70     unsigned int frame_size;
71     int compression;
72     int data_length_indicator;
73 };
74
75 struct id3v1_tag {
76     char title[30];
77     char artist[30];
78     char album[30];
79     char year[4];
80     char comments[30];
81     char genre;
82 } __attribute__((packed));
83
84 struct plugin {
85     struct lms_plugin plugin;
86     lms_db_audio_t *audio_db;
87 };
88
89 static const char _name[] = "aac";
90 static const struct lms_string_size _exts[] = {
91     LMS_STATIC_STRING_SIZE(".aac")
92 };
93
94 #define ID3V2_GET_FRAME_INFO(frame_data, frame_size, str, len) { \
95     str = malloc(sizeof(char) * (frame_size + 1)); \
96     memcpy(str, frame_data, frame_size); \
97     str[frame_size] = '\0'; \
98     len = frame_size; \
99 }
100
101 #define ID3V2_HEADER_SIZE 10
102 #define ID3V2_FOOTER_SIZE 10
103
104 static int
105 _is_id3v2_second_synch_byte(unsigned char byte)
106 {
107     if (byte == 0xff)
108         return 0;
109     if ((byte & 0xE0) == 0xE0)
110         return 1;
111     return 0;
112 }
113
114 static long
115 _find_id3v2(int fd)
116 {
117     long buffer_offset = 0;
118     char buffer[1025];
119     char *p;
120     int buffer_size = sizeof(buffer);
121     const char pattern[] = "ID3";
122     ssize_t nread;
123
124     /* These variables are used to keep track of a partial match that happens at
125      * the end of a buffer. */
126     int previous_partial_match = -1;
127     int previous_partial_synch_match = 0;
128     int first_synch_byte;
129
130     /* Start the search at the beginning of the file. */
131     lseek(fd, 0, SEEK_SET);
132
133     /* This loop is the crux of the find method.  There are three cases that we
134      * want to account for:
135      * (1) The previously searched buffer contained a partial match of the search
136      * pattern and we want to see if the next one starts with the remainder of
137      * that pattern.
138      *
139      * (2) The search pattern is wholly contained within the current buffer.
140      *
141      * (3) The current buffer ends with a partial match of the pattern.  We will
142      * note this for use in the next iteration, where we will check for the rest
143      * of the pattern. */
144
145     if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
146         return -1;
147     while (1) {
148         /* (1) previous partial match */
149         if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
150             return -1;
151
152         if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
153             const int pattern_offset = buffer_size - previous_partial_match;
154
155             if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
156                 return buffer_offset - buffer_size + previous_partial_match;
157         }
158
159         /* (2) pattern contained in current buffer */
160         p = buffer;
161         while ((p = memchr(p, 'I', buffer_size))) {
162             if (memcmp(p, pattern, 3) == 0)
163                 return buffer_offset + (p - buffer);
164             p += 1;
165         }
166
167         p = memchr(buffer, 255, buffer_size);
168         if (p)
169             first_synch_byte = p - buffer;
170         else
171             first_synch_byte = -1;
172
173         /* Here we have to loop because there could be several of the first
174          * (11111111) byte, and we want to check all such instances until we find
175          * a full match (11111111 111) or hit the end of the buffer. */
176         while (first_synch_byte >= 0) {
177             /* if this *is not* at the end of the buffer */
178             if (first_synch_byte < buffer_size - 1) {
179                 if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
180                     /* We've found the frame synch pattern. */
181                     return -1;
182                 else
183                     /* We found 11111111 at the end of the current buffer indicating a
184                      * partial match of the synch pattern.  The find() below should
185                      * return -1 and break out of the loop. */
186                     previous_partial_synch_match = 1;
187             }
188
189             /* Check in the rest of the buffer. */
190             p = memchr(p + 1, 255, buffer_size);
191             if (p)
192                 first_synch_byte = p - buffer;
193             else
194                 first_synch_byte = -1;
195         }
196
197         /* (3) partial match */
198         if (buffer[nread - 1] == pattern[1])
199             previous_partial_match = nread - 1;
200         else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
201             previous_partial_match = nread - 2;
202         buffer_offset += buffer_size;
203
204         if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
205             return -1;
206     }
207
208     return -1;
209 }
210
211 static unsigned int
212 _to_uint(const char *data, int data_size)
213 {
214     unsigned int sum = 0;
215     int last = data_size > 4 ? 3 : data_size - 1;
216     int i;
217
218     for (i = 0; i <= last; i++)
219         sum |= (data[i] & 0x7f) << ((last - i) * 7);
220
221     return sum;
222 }
223
224 static unsigned int
225 _get_id3v2_frame_header_size(unsigned int version)
226 {
227     switch (version) {
228     case 0:
229     case 1:
230     case 2:
231         return 6;
232     case 3:
233     case 4:
234     default:
235         return 10;
236     }
237 }
238
239 static void
240 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
241 {
242     switch (version) {
243     case 0:
244     case 1:
245     case 2:
246         memcpy(fh->frame_id, data, 3);
247         fh->frame_id[3] = 0;
248         fh->frame_size = _to_uint(data + 3, 3);
249         fh->compression = 0;
250         fh->data_length_indicator = 0;
251         break;
252     case 3:
253         memcpy(fh->frame_id, data, 4);
254         fh->frame_size = _to_uint(data + 4, 4);
255         fh->compression = data[9] & 0x40;
256         fh->data_length_indicator = 0;
257         break;
258     case 4:
259     default:
260         memcpy(fh->frame_id, data, 4);
261         fh->frame_size = _to_uint(data + 4, 4);
262         fh->compression = data[9] & 0x4;
263         fh->data_length_indicator = data[9] & 0x1;
264         break;
265     }
266 }
267
268 static void
269 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
270 {
271     unsigned int frame_size;
272
273     /* TODO proper handle text encoding
274      *
275      * Latin1  = 0
276      * UTF16   = 1
277      * UTF16BE = 2
278      * UTF8    = 3
279      * UTF16LE = 4
280      */
281
282     /* skip first byte - text encoding */
283     frame_data += 1;
284     frame_size = fh->frame_size - 1;
285
286     if (memcmp(fh->frame_id, "TIT2", 4) == 0)
287         ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->title.str, info->title.len)
288     else if (memcmp(fh->frame_id, "TPE1", 4) == 0)
289         ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->artist.str, info->artist.len)
290     else if (memcmp(fh->frame_id, "TALB", 4) == 0)
291         ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->album.str, info->album.len)
292     else if (memcmp(fh->frame_id, "TRCK", 4) == 0) {
293         char *trackno;
294         unsigned int trackno_len;
295         ID3V2_GET_FRAME_INFO(frame_data, frame_size, trackno, trackno_len);
296         info->trackno = atoi(trackno);
297     }
298 }
299
300 static int
301 _parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
302 {
303     char header[10];
304     char *data;
305     unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
306     int extended_header, footer_present;
307     struct id3v2_frame_header fh;
308
309     lseek(fd, id3v2_offset, SEEK_SET);
310
311     /* parse header */
312     if (read(fd, header, ID3V2_HEADER_SIZE) != 10)
313         return -1;
314
315     tag_size = _to_uint(header + 6, 4);
316     if (tag_size == 0)
317         return -1;
318
319     /* parse frames */
320     data = malloc(sizeof(char) * tag_size);
321     if (read(fd, data, tag_size) != tag_size) {
322         free(data);
323         return -1;
324     }
325
326     major_version = header[3];
327
328     frame_data_pos = 0;
329     frame_data_length = tag_size;
330
331     /* check for extended header */
332     extended_header = header[5] & 0x20; /* bit 6 */
333     if (extended_header) {
334         /* skip extended header */
335         unsigned int extended_header_size = _to_uint(data, 4);
336         frame_data_pos += extended_header_size;
337         frame_data_length -= extended_header_size;
338     }
339
340     footer_present = header[5] & 0x8;   /* bit 4 */
341     if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
342         frame_data_length -= ID3V2_FOOTER_SIZE;
343
344     frame_header_size = _get_id3v2_frame_header_size(major_version);
345     while (frame_data_pos < frame_data_length - frame_header_size) {
346         if (data[frame_data_pos] == 0)
347             break;
348
349         _parse_id3v2_frame_header(data + frame_data_pos, major_version, &fh);
350
351         if (!fh.compression &&
352             fh.frame_id[0] == 'T' &&
353             memcmp(fh.frame_id, "TXXX", 4) != 0) {
354             unsigned int offset = frame_header_size;
355             unsigned int length = fh.frame_size;
356
357             if (fh.data_length_indicator) {
358                 length = _to_uint(data + frame_header_size, 4);
359                 offset += 4;
360             }
361
362             _parse_id3v2_frame(&fh, data + frame_data_pos + offset, info);
363         }
364
365         frame_data_pos += fh.frame_size + frame_header_size;
366     }
367
368     free(data);
369
370     return 0;
371 }
372
373 static int
374 _parse_id3v1(int fd, struct lms_audio_info *info)
375 {
376     struct id3v1_tag tag;
377     if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
378         return -1;
379
380     info->title.str = strdup(tag.title);
381     info->title.len = strlen(info->title.str);
382     info->artist.str = strdup(tag.artist);
383     info->artist.len = strlen(info->artist.str);
384     info->album.str = strdup(tag.album);
385     info->album.len = strlen(info->album.str);
386     if (tag.genre >= 0 && tag.genre < ID3V1_NUM_GENRES) {
387         info->genre.str = strdup(id3v1_genres[(int) tag.genre]);
388         info->genre.len = strlen(info->genre.str);
389     }
390
391     return 0;
392 }
393
394 static void *
395 _match(struct plugin *p, const char *path, int len, int base)
396 {
397     int i;
398
399     i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
400     if (i < 0)
401       return NULL;
402     else
403       return (void*)(i + 1);
404 }
405
406 static int
407 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
408 {
409     struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
410     int r, fd;
411     long id3v2_offset;
412
413     fd = open(finfo->path, O_RDONLY);
414     if (fd < 0) {
415         perror("open");
416         return -1;
417     }
418
419     id3v2_offset = _find_id3v2(fd);
420     if (id3v2_offset >= 0) {
421         fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
422                 finfo->path, id3v2_offset);
423         if (_parse_id3v2(fd, id3v2_offset, &info) != 0) {
424             r = -2;
425             goto done;
426         }
427     }
428     else {
429         char tag[3];
430
431         fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n", finfo->path);
432         /* check for id3v1 tag */
433         if (lseek(fd, -128, SEEK_END) == -1) {
434             r = -3;
435             goto done;
436         }
437
438         if (read(fd, &tag, 3) == -1) {
439             r = -4;
440             goto done;
441         }
442
443         if (memcmp(tag, "TAG", 3) == 0) {
444             fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
445             if (_parse_id3v1(fd, &info) != 0) {
446                 r = -5;
447                 goto done;
448             }
449         }
450     }
451
452     lms_string_size_strip_and_free(&info.title);
453     lms_string_size_strip_and_free(&info.artist);
454     lms_string_size_strip_and_free(&info.album);
455     lms_string_size_strip_and_free(&info.genre);
456
457     if (!info.title.str) {
458         int ext_idx;
459         ext_idx = ((int)match) - 1;
460         info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
461         info.title.str = malloc((info.title.len + 1) * sizeof(char));
462         memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
463         info.title.str[info.title.len] = '\0';
464     }
465     lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
466
467     if (info.artist.str)
468         lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
469     if (info.album.str)
470         lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
471     if (info.genre.str)
472         lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
473
474 #if 0
475     fprintf(stderr, "file %s info\n", finfo->path);
476     fprintf(stderr, "\ttitle='%s'\n", info.title.str);
477     fprintf(stderr, "\tartist='%s'\n", info.artist.str);
478     fprintf(stderr, "\talbum='%s'\n", info.album.str);
479     fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
480 #endif
481
482     info.id = finfo->id;
483     r = lms_db_audio_add(plugin->audio_db, &info);
484
485   done:
486     close(fd);
487
488     if (info.title.str)
489         free(info.title.str);
490     if (info.artist.str)
491         free(info.artist.str);
492     if (info.album.str)
493         free(info.album.str);
494     if (info.genre.str)
495         free(info.genre.str);
496
497     return r;
498 }
499
500 static int
501 _setup(struct plugin *plugin, struct lms_context *ctxt)
502 {
503     plugin->audio_db = lms_db_audio_new(ctxt->db);
504     if (!plugin->audio_db)
505         return -1;
506     return 0;
507 }
508
509 static int
510 _start(struct plugin *plugin, struct lms_context *ctxt)
511 {
512     return lms_db_audio_start(plugin->audio_db);
513 }
514
515 static int
516 _finish(struct plugin *plugin, struct lms_context *ctxt)
517 {
518     if (plugin->audio_db)
519         lms_db_audio_free(plugin->audio_db);
520     return 0;
521 }
522
523 static int
524 _close(struct plugin *plugin)
525 {
526     free(plugin);
527     return 0;
528 }
529
530 API struct lms_plugin *
531 lms_plugin_open(void)
532 {
533     struct plugin *plugin;
534
535     plugin = (struct plugin *)malloc(sizeof(*plugin));
536     plugin->plugin.name = _name;
537     plugin->plugin.match = (lms_plugin_match_fn_t)_match;
538     plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
539     plugin->plugin.close = (lms_plugin_close_fn_t)_close;
540     plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
541     plugin->plugin.start = (lms_plugin_start_fn_t)_start;
542     plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
543
544     return (struct lms_plugin *)plugin;
545 }