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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
31 #define _XOPEN_SOURCE 600
32 #include <lightmediascanner_plugin.h>
33 #include <lightmediascanner_db.h>
34 #include <sys/types.h>
42 #define ID3V1_NUM_GENRES 126
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",
68 struct id3v2_frame_header {
70 unsigned int frame_size;
72 int data_length_indicator;
82 } __attribute__((packed));
85 struct lms_plugin plugin;
86 lms_db_audio_t *audio_db;
89 static const char _name[] = "aac";
90 static const struct lms_string_size _exts[] = {
91 LMS_STATIC_STRING_SIZE(".aac")
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'; \
101 #define ID3V2_HEADER_SIZE 10
102 #define ID3V2_FOOTER_SIZE 10
105 _is_id3v2_second_synch_byte(unsigned char byte)
109 if ((byte & 0xE0) == 0xE0)
117 long buffer_offset = 0;
120 int buffer_size = sizeof(buffer);
121 const char pattern[] = "ID3";
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;
130 /* Start the search at the beginning of the file. */
131 lseek(fd, 0, SEEK_SET);
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
139 * (2) The search pattern is wholly contained within the current buffer.
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
145 if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
148 /* (1) previous partial match */
149 if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
152 if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
153 const int pattern_offset = buffer_size - previous_partial_match;
155 if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
156 return buffer_offset - buffer_size + previous_partial_match;
159 /* (2) pattern contained in current buffer */
161 while ((p = memchr(p, 'I', buffer_size))) {
162 if (memcmp(p, pattern, 3) == 0)
163 return buffer_offset + (p - buffer);
167 p = memchr(buffer, 255, buffer_size);
169 first_synch_byte = p - buffer;
171 first_synch_byte = -1;
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. */
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;
189 /* Check in the rest of the buffer. */
190 p = memchr(p + 1, 255, buffer_size);
192 first_synch_byte = p - buffer;
194 first_synch_byte = -1;
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;
204 if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
212 _to_uint(const char *data, int data_size)
214 unsigned int sum = 0;
215 int last = data_size > 4 ? 3 : data_size - 1;
218 for (i = 0; i <= last; i++)
219 sum |= (data[i] & 0x7f) << ((last - i) * 7);
225 _get_id3v2_frame_header_size(unsigned int version)
240 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
246 memcpy(fh->frame_id, data, 3);
248 fh->frame_size = _to_uint(data + 3, 3);
250 fh->data_length_indicator = 0;
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;
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;
269 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
271 unsigned int frame_size;
273 /* TODO proper handle text encoding
282 /* skip first byte - text encoding */
284 frame_size = fh->frame_size - 1;
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) {
294 unsigned int trackno_len;
295 ID3V2_GET_FRAME_INFO(frame_data, frame_size, trackno, trackno_len);
296 info->trackno = atoi(trackno);
301 _parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
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;
309 lseek(fd, id3v2_offset, SEEK_SET);
312 if (read(fd, header, ID3V2_HEADER_SIZE) != 10)
315 tag_size = _to_uint(header + 6, 4);
320 data = malloc(sizeof(char) * tag_size);
321 if (read(fd, data, tag_size) != tag_size) {
326 major_version = header[3];
329 frame_data_length = tag_size;
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;
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;
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)
349 _parse_id3v2_frame_header(data + frame_data_pos, major_version, &fh);
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;
357 if (fh.data_length_indicator) {
358 length = _to_uint(data + frame_header_size, 4);
362 _parse_id3v2_frame(&fh, data + frame_data_pos + offset, info);
365 frame_data_pos += fh.frame_size + frame_header_size;
374 _parse_id3v1(int fd, struct lms_audio_info *info)
376 struct id3v1_tag tag;
377 if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
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);
395 _match(struct plugin *p, const char *path, int len, int base)
399 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
403 return (void*)(i + 1);
407 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
409 struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
413 fd = open(finfo->path, O_RDONLY);
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) {
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) {
438 if (read(fd, &tag, 3) == -1) {
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) {
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);
457 if (!info.title.str) {
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';
465 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
468 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
470 lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
472 lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
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);
483 r = lms_db_audio_add(plugin->audio_db, &info);
489 free(info.title.str);
491 free(info.artist.str);
493 free(info.album.str);
495 free(info.genre.str);
501 _setup(struct plugin *plugin, struct lms_context *ctxt)
503 plugin->audio_db = lms_db_audio_new(ctxt->db);
504 if (!plugin->audio_db)
510 _start(struct plugin *plugin, struct lms_context *ctxt)
512 return lms_db_audio_start(plugin->audio_db);
516 _finish(struct plugin *plugin, struct lms_context *ctxt)
518 if (plugin->audio_db)
519 lms_db_audio_free(plugin->audio_db);
524 _close(struct plugin *plugin)
530 API struct lms_plugin *
531 lms_plugin_open(void)
533 struct plugin *plugin;
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;
544 return (struct lms_plugin *)plugin;