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 ID3V2_GET_FRAME_INFO(frame_data, frame_size, str, len) { \
43 str = malloc(sizeof(char) * (frame_size + 1)); \
44 memcpy(str, frame_data, frame_size); \
45 str[frame_size] = '\0'; \
49 #define ID3V2_HEADER_SIZE 10
50 #define ID3V2_FOOTER_SIZE 10
51 #define ID3V2_FRAME_HEADER_SIZE 10
53 #define ID3V1_NUM_GENRES 126
55 static const char *id3v1_genres[126] = {
56 "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
57 "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop",
58 "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative",
59 "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
60 "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
61 "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
62 "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
63 "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
64 "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance",
65 "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40",
66 "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
67 "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
68 "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
69 "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion",
70 "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
71 "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
72 "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
73 "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
74 "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore",
75 "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock",
76 "Drum Solo", "A capella", "Euro-House", "Dance Hall",
79 struct id3v2_frame_header {
81 unsigned int frame_size;
83 int data_length_indicator;
93 } __attribute__((packed));
96 struct lms_plugin plugin;
97 lms_db_audio_t *audio_db;
100 static const char _name[] = "aac";
101 static const struct lms_string_size _exts[] = {
102 LMS_STATIC_STRING_SIZE(".aac")
106 _is_id3v2_second_synch_byte(unsigned char byte)
110 if ((byte & 0xE0) == 0xE0)
118 long buffer_offset = 0;
121 int buffer_size = sizeof(buffer);
122 const char pattern[] = "ID3";
125 /* These variables are used to keep track of a partial match that happens at
126 * the end of a buffer. */
127 int previous_partial_match = -1;
128 int previous_partial_synch_match = 0;
129 int first_synch_byte;
131 /* Start the search at the beginning of the file. */
132 lseek(fd, 0, SEEK_SET);
134 /* This loop is the crux of the find method. There are three cases that we
135 * want to account for:
136 * (1) The previously searched buffer contained a partial match of the search
137 * pattern and we want to see if the next one starts with the remainder of
140 * (2) The search pattern is wholly contained within the current buffer.
142 * (3) The current buffer ends with a partial match of the pattern. We will
143 * note this for use in the next iteration, where we will check for the rest
146 if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
149 /* (1) previous partial match */
150 if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
153 if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
154 const int pattern_offset = buffer_size - previous_partial_match;
156 if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
157 return buffer_offset - buffer_size + previous_partial_match;
160 /* (2) pattern contained in current buffer */
162 while ((p = memchr(p, 'I', buffer_size))) {
163 if (memcmp(p, pattern, 3) == 0)
164 return buffer_offset + (p - buffer);
168 p = memchr(buffer, 255, buffer_size);
170 first_synch_byte = p - buffer;
172 first_synch_byte = -1;
174 /* Here we have to loop because there could be several of the first
175 * (11111111) byte, and we want to check all such instances until we find
176 * a full match (11111111 111) or hit the end of the buffer. */
177 while (first_synch_byte >= 0) {
178 /* if this *is not* at the end of the buffer */
179 if (first_synch_byte < buffer_size - 1) {
180 if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
181 /* We've found the frame synch pattern. */
184 /* We found 11111111 at the end of the current buffer indicating a
185 * partial match of the synch pattern. The find() below should
186 * return -1 and break out of the loop. */
187 previous_partial_synch_match = 1;
190 /* Check in the rest of the buffer. */
191 p = memchr(p + 1, 255, buffer_size);
193 first_synch_byte = p - buffer;
195 first_synch_byte = -1;
198 /* (3) partial match */
199 if (buffer[nread - 1] == pattern[1])
200 previous_partial_match = nread - 1;
201 else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
202 previous_partial_match = nread - 2;
203 buffer_offset += buffer_size;
205 if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
213 _to_uint(const char *data, int data_size)
215 unsigned int sum = 0;
216 int last = data_size > 4 ? 3 : data_size - 1;
219 for (i = 0; i <= last; i++)
220 sum |= (data[i] & 0x7f) << ((last - i) * 7);
226 _get_id3v2_frame_header_size(unsigned int version)
241 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
247 memcpy(fh->frame_id, data, 3);
249 fh->frame_size = _to_uint(data + 3, 3);
251 fh->data_length_indicator = 0;
254 memcpy(fh->frame_id, data, 4);
255 fh->frame_size = _to_uint(data + 4, 4);
256 fh->compression = data[9] & 0x40;
257 fh->data_length_indicator = 0;
261 memcpy(fh->frame_id, data, 4);
262 fh->frame_size = _to_uint(data + 4, 4);
263 fh->compression = data[9] & 0x4;
264 fh->data_length_indicator = data[9] & 0x1;
270 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
272 unsigned int frame_size;
274 /* TODO proper handle text encoding
283 /* skip first byte - text encoding */
285 frame_size = fh->frame_size - 1;
287 if (memcmp(fh->frame_id, "TIT2", 4) == 0)
288 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->title.str, info->title.len)
289 else if (memcmp(fh->frame_id, "TPE1", 4) == 0)
290 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->artist.str, info->artist.len)
291 else if (memcmp(fh->frame_id, "TALB", 4) == 0)
292 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->album.str, info->album.len)
293 else if (memcmp(fh->frame_id, "TRCK", 4) == 0) {
295 unsigned int trackno_len;
296 ID3V2_GET_FRAME_INFO(frame_data, frame_size, trackno, trackno_len);
297 info->trackno = atoi(trackno);
302 _parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
304 char header_data[10], frame_header_data[10];
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_data, ID3V2_HEADER_SIZE) != ID3V2_HEADER_SIZE)
315 tag_size = _to_uint(header_data + 6, 4);
320 major_version = header_data[3];
323 frame_data_length = tag_size;
325 /* check for extended header */
326 extended_header = header_data[5] & 0x20; /* bit 6 */
327 if (extended_header) {
328 /* skip extended header */
329 unsigned int extended_header_size;
330 char extended_header_data[4];
332 if (read(fd, extended_header_data, 4) != 4)
334 extended_header_size = _to_uint(extended_header_data, 4);
335 lseek(fd, extended_header_size - 4, SEEK_CUR);
336 frame_data_pos += extended_header_size;
337 frame_data_length -= extended_header_size;
340 footer_present = header_data[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 (read(fd, frame_header_data, ID3V2_FRAME_HEADER_SIZE) != ID3V2_FRAME_HEADER_SIZE)
349 if (frame_header_data[0] == 0)
352 _parse_id3v2_frame_header(frame_header_data, major_version, &fh);
354 if (!fh.compression &&
355 fh.frame_id[0] == 'T' &&
356 memcmp(fh.frame_id, "TXXX", 4) != 0) {
359 if (fh.data_length_indicator)
360 lseek(fd, 4, SEEK_CUR);
362 frame_data = malloc(sizeof(char) * fh.frame_size);
363 if (read(fd, frame_data, fh.frame_size) != fh.frame_size) {
368 _parse_id3v2_frame(&fh, frame_data, info);
372 if (fh.data_length_indicator)
373 lseek(fd, fh.frame_size + 4, SEEK_CUR);
375 lseek(fd, fh.frame_size, SEEK_CUR);
378 frame_data_pos += fh.frame_size + frame_header_size;
385 _parse_id3v1(int fd, struct lms_audio_info *info)
387 struct id3v1_tag tag;
388 if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
391 info->title.str = strdup(tag.title);
392 info->title.len = strlen(info->title.str);
393 info->artist.str = strdup(tag.artist);
394 info->artist.len = strlen(info->artist.str);
395 info->album.str = strdup(tag.album);
396 info->album.len = strlen(info->album.str);
397 if (tag.genre >= 0 && tag.genre < ID3V1_NUM_GENRES) {
398 info->genre.str = strdup(id3v1_genres[(int) tag.genre]);
399 info->genre.len = strlen(info->genre.str);
406 _match(struct plugin *p, const char *path, int len, int base)
410 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
414 return (void*)(i + 1);
418 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
420 struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
424 fd = open(finfo->path, O_RDONLY);
430 id3v2_offset = _find_id3v2(fd);
431 if (id3v2_offset >= 0) {
432 fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
433 finfo->path, id3v2_offset);
434 if (_parse_id3v2(fd, id3v2_offset, &info) != 0) {
442 fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n", finfo->path);
443 /* check for id3v1 tag */
444 if (lseek(fd, -128, SEEK_END) == -1) {
449 if (read(fd, &tag, 3) == -1) {
454 if (memcmp(tag, "TAG", 3) == 0) {
455 fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
456 if (_parse_id3v1(fd, &info) != 0) {
463 lms_string_size_strip_and_free(&info.title);
464 lms_string_size_strip_and_free(&info.artist);
465 lms_string_size_strip_and_free(&info.album);
466 lms_string_size_strip_and_free(&info.genre);
468 if (!info.title.str) {
470 ext_idx = ((int)match) - 1;
471 info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
472 info.title.str = malloc((info.title.len + 1) * sizeof(char));
473 memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
474 info.title.str[info.title.len] = '\0';
476 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
479 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
481 lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
483 lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
486 fprintf(stderr, "file %s info\n", finfo->path);
487 fprintf(stderr, "\ttitle='%s'\n", info.title.str);
488 fprintf(stderr, "\tartist='%s'\n", info.artist.str);
489 fprintf(stderr, "\talbum='%s'\n", info.album.str);
490 fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
494 r = lms_db_audio_add(plugin->audio_db, &info);
500 free(info.title.str);
502 free(info.artist.str);
504 free(info.album.str);
506 free(info.genre.str);
512 _setup(struct plugin *plugin, struct lms_context *ctxt)
514 plugin->audio_db = lms_db_audio_new(ctxt->db);
515 if (!plugin->audio_db)
521 _start(struct plugin *plugin, struct lms_context *ctxt)
523 return lms_db_audio_start(plugin->audio_db);
527 _finish(struct plugin *plugin, struct lms_context *ctxt)
529 if (plugin->audio_db)
530 lms_db_audio_free(plugin->audio_db);
535 _close(struct plugin *plugin)
541 API struct lms_plugin *
542 lms_plugin_open(void)
544 struct plugin *plugin;
546 plugin = (struct plugin *)malloc(sizeof(*plugin));
547 plugin->plugin.name = _name;
548 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
549 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
550 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
551 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
552 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
553 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
555 return (struct lms_plugin *)plugin;