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>
43 #define ID3V2_GET_FRAME_INFO(frame_data, frame_size, str, len) { \
44 str = malloc(sizeof(char) * (frame_size + 1)); \
45 memcpy(str, frame_data, frame_size); \
46 str[frame_size] = '\0'; \
50 #define ID3V2_HEADER_SIZE 10
51 #define ID3V2_FOOTER_SIZE 10
52 #define ID3V2_FRAME_HEADER_SIZE 10
54 #define ID3V1_NUM_GENRES 148
56 static const char *id3v1_genres[ID3V1_NUM_GENRES] = {
193 "Christian Gangsta Rap",
197 "Contemporary Christian",
207 struct id3v2_frame_header {
209 unsigned int frame_size;
211 int data_length_indicator;
221 } __attribute__((packed));
224 struct lms_plugin plugin;
225 lms_db_audio_t *audio_db;
228 static const char _name[] = "id3";
229 static const struct lms_string_size _exts[] = {
230 LMS_STATIC_STRING_SIZE(".mp3"),
231 LMS_STATIC_STRING_SIZE(".aac")
235 _is_id3v2_second_synch_byte(unsigned char byte)
239 if ((byte & 0xE0) == 0xE0)
247 long buffer_offset = 0;
249 int buffer_size = sizeof(buffer);
250 const char pattern[] = "ID3";
253 /* These variables are used to keep track of a partial match that happens at
254 * the end of a buffer. */
255 int previous_partial_match = -1;
256 int previous_partial_synch_match = 0;
257 int first_synch_byte;
259 /* Start the search at the beginning of the file. */
260 lseek(fd, 0, SEEK_SET);
262 if ((nread = read(fd, &buffer, buffer_size)) != buffer_size)
265 /* check if pattern is in the beggining of the file */
266 if (memcmp(buffer, pattern, 3) == 0)
269 /* This loop is the crux of the find method. There are three cases that we
270 * want to account for:
271 * (1) The previously searched buffer contained a partial match of the search
272 * pattern and we want to see if the next one starts with the remainder of
275 * (2) The search pattern is wholly contained within the current buffer.
277 * (3) The current buffer ends with a partial match of the pattern. We will
278 * note this for use in the next iteration, where we will check for the rest
281 /* (1) previous partial match */
282 if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
285 if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
286 const int pattern_offset = buffer_size - previous_partial_match;
288 if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
289 return buffer_offset - buffer_size + previous_partial_match;
292 /* (2) pattern contained in current buffer */
294 while ((p = memchr(p, 'I', buffer_size))) {
295 if (memcmp(p, pattern, 3) == 0)
296 return buffer_offset + (p - buffer);
300 p = memchr(buffer, 255, buffer_size);
302 first_synch_byte = p - buffer;
304 first_synch_byte = -1;
306 /* Here we have to loop because there could be several of the first
307 * (11111111) byte, and we want to check all such instances until we find
308 * a full match (11111111 111) or hit the end of the buffer. */
309 while (first_synch_byte >= 0) {
310 /* if this *is not* at the end of the buffer */
311 if (first_synch_byte < buffer_size - 1) {
312 if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
313 /* We've found the frame synch pattern. */
316 /* We found 11111111 at the end of the current buffer indicating a
317 * partial match of the synch pattern. The find() below should
318 * return -1 and break out of the loop. */
319 previous_partial_synch_match = 1;
322 /* Check in the rest of the buffer. */
323 p = memchr(p + 1, 255, buffer_size);
325 first_synch_byte = p - buffer;
327 first_synch_byte = -1;
330 /* (3) partial match */
331 if (buffer[nread - 1] == pattern[1])
332 previous_partial_match = nread - 1;
333 else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
334 previous_partial_match = nread - 2;
335 buffer_offset += buffer_size;
337 if ((nread = read(fd, &buffer, sizeof(buffer))) == -1)
345 _to_uint(const char *data, int data_size)
347 unsigned int sum = 0;
348 int last = data_size > 4 ? 3 : data_size - 1;
351 for (i = 0; i <= last; i++)
352 sum |= (data[i] & 0x7f) << ((last - i) * 7);
358 _get_id3v2_frame_header_size(unsigned int version)
373 _parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
379 memcpy(fh->frame_id, data, 3);
381 fh->frame_size = _to_uint(data + 3, 3);
383 fh->data_length_indicator = 0;
386 memcpy(fh->frame_id, data, 4);
387 fh->frame_size = _to_uint(data + 4, 4);
388 fh->compression = data[9] & 0x40;
389 fh->data_length_indicator = 0;
393 memcpy(fh->frame_id, data, 4);
394 fh->frame_size = _to_uint(data + 4, 4);
395 fh->compression = data[9] & 0x4;
396 fh->data_length_indicator = data[9] & 0x1;
402 _parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
404 unsigned int frame_size;
406 /* TODO proper handle text encoding
415 /* skip first byte - text encoding */
417 frame_size = fh->frame_size - 1;
419 if (memcmp(fh->frame_id, "TIT2", 4) == 0)
420 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->title.str, info->title.len)
421 else if (memcmp(fh->frame_id, "TPE1", 4) == 0)
422 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->artist.str, info->artist.len)
423 else if (memcmp(fh->frame_id, "TALB", 4) == 0)
424 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->album.str, info->album.len)
425 else if (memcmp(fh->frame_id, "TCON", 4) == 0) {
426 /* TODO handle multiple genres */
429 ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->genre.str, info->genre.len)
432 for (i = 0; i < info->genre.len; ++i) {
433 if (!isdigit(info->genre.str[i]))
439 (info->genre.len > 0)) {
440 int genre = atoi(info->genre.str);
441 if (genre >= 0 && genre < ID3V1_NUM_GENRES) {
442 free(info->genre.str);
443 info->genre.str = strdup(id3v1_genres[(int) genre]);
444 info->genre.len = strlen(info->genre.str);
447 /* ignore other genres */
448 free(info->genre.str);
449 info->genre.str = NULL;
454 else if (memcmp(fh->frame_id, "TRCK", 4) == 0) {
456 unsigned int trackno_len;
457 ID3V2_GET_FRAME_INFO(frame_data, frame_size, trackno, trackno_len);
458 info->trackno = atoi(trackno);
463 _parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
465 char header_data[10], frame_header_data[10];
466 unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
467 int extended_header, footer_present;
468 struct id3v2_frame_header fh;
470 lseek(fd, id3v2_offset, SEEK_SET);
473 if (read(fd, header_data, ID3V2_HEADER_SIZE) != ID3V2_HEADER_SIZE)
476 tag_size = _to_uint(header_data + 6, 4);
481 major_version = header_data[3];
484 frame_data_length = tag_size;
486 /* check for extended header */
487 extended_header = header_data[5] & 0x20; /* bit 6 */
488 if (extended_header) {
489 /* skip extended header */
490 unsigned int extended_header_size;
491 char extended_header_data[4];
493 if (read(fd, extended_header_data, 4) != 4)
495 extended_header_size = _to_uint(extended_header_data, 4);
496 lseek(fd, extended_header_size - 4, SEEK_CUR);
497 frame_data_pos += extended_header_size;
498 frame_data_length -= extended_header_size;
501 footer_present = header_data[5] & 0x8; /* bit 4 */
502 if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
503 frame_data_length -= ID3V2_FOOTER_SIZE;
505 frame_header_size = _get_id3v2_frame_header_size(major_version);
506 while (frame_data_pos < frame_data_length - frame_header_size) {
507 if (read(fd, frame_header_data, ID3V2_FRAME_HEADER_SIZE) != ID3V2_FRAME_HEADER_SIZE)
510 if (frame_header_data[0] == 0)
513 _parse_id3v2_frame_header(frame_header_data, major_version, &fh);
515 if (!fh.compression &&
516 fh.frame_id[0] == 'T' &&
517 memcmp(fh.frame_id, "TXXX", 4) != 0) {
520 if (fh.data_length_indicator)
521 lseek(fd, 4, SEEK_CUR);
523 frame_data = malloc(sizeof(char) * fh.frame_size);
524 if (read(fd, frame_data, fh.frame_size) != fh.frame_size) {
529 _parse_id3v2_frame(&fh, frame_data, info);
533 if (fh.data_length_indicator)
534 lseek(fd, fh.frame_size + 4, SEEK_CUR);
536 lseek(fd, fh.frame_size, SEEK_CUR);
539 frame_data_pos += fh.frame_size + frame_header_size;
546 _parse_id3v1(int fd, struct lms_audio_info *info)
548 struct id3v1_tag tag;
549 if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
552 info->title.str = strdup(tag.title);
553 info->title.len = strlen(info->title.str);
554 info->artist.str = strdup(tag.artist);
555 info->artist.len = strlen(info->artist.str);
556 info->album.str = strdup(tag.album);
557 info->album.len = strlen(info->album.str);
558 info->genre.str = strdup(id3v1_genres[(int) tag.genre]);
559 info->genre.len = strlen(info->genre.str);
560 if (tag.comments[28] == 0 && tag.comments[29] != 0)
561 info->trackno = (unsigned char) tag.comments[29];
567 _match(struct plugin *p, const char *path, int len, int base)
571 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
575 return (void*)(i + 1);
579 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
581 struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
585 fd = open(finfo->path, O_RDONLY);
591 id3v2_offset = _find_id3v2(fd);
592 if (id3v2_offset >= 0) {
594 fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
595 finfo->path, id3v2_offset);
597 if (_parse_id3v2(fd, id3v2_offset, &info) != 0) {
605 fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n", finfo->path);
607 /* check for id3v1 tag */
608 if (lseek(fd, -128, SEEK_END) == -1) {
613 if (read(fd, &tag, 3) == -1) {
618 if (memcmp(tag, "TAG", 3) == 0) {
620 fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
622 if (_parse_id3v1(fd, &info) != 0) {
629 lms_string_size_strip_and_free(&info.title);
630 lms_string_size_strip_and_free(&info.artist);
631 lms_string_size_strip_and_free(&info.album);
632 lms_string_size_strip_and_free(&info.genre);
634 if (!info.title.str) {
636 ext_idx = ((int)match) - 1;
637 info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
638 info.title.str = malloc((info.title.len + 1) * sizeof(char));
639 memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
640 info.title.str[info.title.len] = '\0';
642 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
645 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
647 lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
649 lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
652 fprintf(stderr, "file %s info\n", finfo->path);
653 fprintf(stderr, "\ttitle='%s'\n", info.title.str);
654 fprintf(stderr, "\tartist='%s'\n", info.artist.str);
655 fprintf(stderr, "\talbum='%s'\n", info.album.str);
656 fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
657 fprintf(stderr, "\ttrack number='%d'\n", info.trackno);
661 r = lms_db_audio_add(plugin->audio_db, &info);
667 free(info.title.str);
669 free(info.artist.str);
671 free(info.album.str);
673 free(info.genre.str);
679 _setup(struct plugin *plugin, struct lms_context *ctxt)
681 plugin->audio_db = lms_db_audio_new(ctxt->db);
682 if (!plugin->audio_db)
688 _start(struct plugin *plugin, struct lms_context *ctxt)
690 return lms_db_audio_start(plugin->audio_db);
694 _finish(struct plugin *plugin, struct lms_context *ctxt)
696 if (plugin->audio_db)
697 lms_db_audio_free(plugin->audio_db);
702 _close(struct plugin *plugin)
708 API struct lms_plugin *
709 lms_plugin_open(void)
711 struct plugin *plugin;
713 plugin = (struct plugin *)malloc(sizeof(*plugin));
714 plugin->plugin.name = _name;
715 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
716 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
717 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
718 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
719 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
720 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
722 return (struct lms_plugin *)plugin;