Added aac plugin.
authorbarbieri <barbieri@gmail.com>
Thu, 14 Feb 2008 21:36:32 +0000 (21:36 +0000)
committerbarbieri <barbieri@gmail.com>
Thu, 14 Feb 2008 21:36:32 +0000 (21:36 +0000)
lightmediascanner/configure.ac
lightmediascanner/src/plugins/Makefile.am
lightmediascanner/src/plugins/aac/Makefile.am [new file with mode: 0644]
lightmediascanner/src/plugins/aac/aac.c [new file with mode: 0644]

index 2ead7f8..694ecb3 100644 (file)
@@ -74,6 +74,7 @@ AC_LMS_OPTIONAL_MODULE([pls], true)
 AC_LMS_OPTIONAL_MODULE([asf], true)
 AC_LMS_OPTIONAL_MODULE([rm], true)
 AC_LMS_OPTIONAL_MODULE([mp4], true, [CHECK_MODULE_MP4])
+AC_LMS_OPTIONAL_MODULE([aac], true)
 
 AC_OUTPUT([
 lightmediascanner.pc
@@ -95,6 +96,7 @@ src/plugins/pls/Makefile
 src/plugins/asf/Makefile
 src/plugins/rm/Makefile
 src/plugins/mp4/Makefile
+src/plugins/aac/Makefile
 ])
 
 
index c43537f..76fa13c 100644 (file)
@@ -50,6 +50,10 @@ if USE_MODULE_MP4
 SUBDIRS += mp4
 endif
 
+if USE_MODULE_AAC
+SUBDIRS += aac
+endif
+
 DIST_SUBDIRS = \
        dummy \
        jpeg \
@@ -62,4 +66,5 @@ DIST_SUBDIRS = \
        pls \
        asf \
        rm \
-       mp4
+       mp4 \
+       aac
diff --git a/lightmediascanner/src/plugins/aac/Makefile.am b/lightmediascanner/src/plugins/aac/Makefile.am
new file mode 100644 (file)
index 0000000..6c1151d
--- /dev/null
@@ -0,0 +1,10 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_srcdir)/src/plugins/aac
+
+pkgdir = $(pluginsdir)
+pkg_LTLIBRARIES = aac.la
+aac_la_SOURCES = aac.c
+aac_la_DEPENDENCIES = $(top_builddir)/config.h
+aac_la_LIBADD = $(top_builddir)/src/lib/liblightmediascanner.la
+aac_la_LDFLAGS = -module -avoid-version
diff --git a/lightmediascanner/src/plugins/aac/aac.c b/lightmediascanner/src/plugins/aac/aac.c
new file mode 100644 (file)
index 0000000..286463e
--- /dev/null
@@ -0,0 +1,545 @@
+/**
+ * Copyright (C) 2008 by INdT
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * @author Andre Moreira Magalhaes <andre.magalhaes@openbossa.org>
+ */
+
+/**
+ * @brief
+ *
+ * aac file parser.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define _XOPEN_SOURCE 600
+#include <lightmediascanner_plugin.h>
+#include <lightmediascanner_db.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ID3V1_NUM_GENRES 126
+
+static const char *id3v1_genres[126] = {
+    "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
+    "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop",
+    "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative",
+    "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
+    "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
+    "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
+    "Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
+    "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
+    "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance",
+    "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40",
+    "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
+    "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
+    "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
+    "Hard Rock", "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion",
+    "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
+    "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
+    "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
+    "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
+    "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore",
+    "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock",
+    "Drum Solo", "A capella", "Euro-House", "Dance Hall",
+};
+
+struct id3v2_frame_header {
+    char frame_id[4];
+    unsigned int frame_size;
+    int compression;
+    int data_length_indicator;
+};
+
+struct id3v1_tag {
+    char title[30];
+    char artist[30];
+    char album[30];
+    char year[4];
+    char comments[30];
+    char genre;
+} __attribute__((packed));
+
+struct plugin {
+    struct lms_plugin plugin;
+    lms_db_audio_t *audio_db;
+};
+
+static const char _name[] = "aac";
+static const struct lms_string_size _exts[] = {
+    LMS_STATIC_STRING_SIZE(".aac")
+};
+
+#define ID3V2_GET_FRAME_INFO(frame_data, frame_size, str, len) { \
+    str = malloc(sizeof(char) * (frame_size + 1)); \
+    memcpy(str, frame_data, frame_size); \
+    str[frame_size] = '\0'; \
+    len = frame_size; \
+}
+
+#define ID3V2_HEADER_SIZE 10
+#define ID3V2_FOOTER_SIZE 10
+
+static int
+_is_id3v2_second_synch_byte(unsigned char byte)
+{
+    if (byte == 0xff)
+        return 0;
+    if ((byte & 0xE0) == 0xE0)
+        return 1;
+    return 0;
+}
+
+static long
+_find_id3v2(int fd)
+{
+    long buffer_offset = 0;
+    char buffer[1025];
+    char *p;
+    int buffer_size = sizeof(buffer);
+    const char pattern[] = "ID3";
+    ssize_t nread;
+
+    /* These variables are used to keep track of a partial match that happens at
+     * the end of a buffer. */
+    int previous_partial_match = -1;
+    int previous_partial_synch_match = 0;
+    int first_synch_byte;
+
+    /* Start the search at the beginning of the file. */
+    lseek(fd, 0, SEEK_SET);
+
+    /* This loop is the crux of the find method.  There are three cases that we
+     * want to account for:
+     * (1) The previously searched buffer contained a partial match of the search
+     * pattern and we want to see if the next one starts with the remainder of
+     * that pattern.
+     *
+     * (2) The search pattern is wholly contained within the current buffer.
+     *
+     * (3) The current buffer ends with a partial match of the pattern.  We will
+     * note this for use in the next iteration, where we will check for the rest
+     * of the pattern. */
+
+    if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
+        return -1;
+    while (1) {
+        /* (1) previous partial match */
+        if (previous_partial_synch_match && _is_id3v2_second_synch_byte(buffer[0]))
+            return -1;
+
+        if (previous_partial_match >= 0 && previous_partial_match < buffer_size) {
+            const int pattern_offset = buffer_size - previous_partial_match;
+
+            if (memcmp(buffer, pattern + pattern_offset, 3 - pattern_offset) == 0)
+                return buffer_offset - buffer_size + previous_partial_match;
+        }
+
+        /* (2) pattern contained in current buffer */
+        p = buffer;
+        while ((p = memchr(p, 'I', buffer_size))) {
+            if (memcmp(p, pattern, 3) == 0)
+                return buffer_offset + (p - buffer);
+            p += 1;
+        }
+
+        p = memchr(buffer, 255, buffer_size);
+        if (p)
+            first_synch_byte = p - buffer;
+        else
+            first_synch_byte = -1;
+
+        /* Here we have to loop because there could be several of the first
+         * (11111111) byte, and we want to check all such instances until we find
+         * a full match (11111111 111) or hit the end of the buffer. */
+        while (first_synch_byte >= 0) {
+            /* if this *is not* at the end of the buffer */
+            if (first_synch_byte < buffer_size - 1) {
+                if(_is_id3v2_second_synch_byte(buffer[first_synch_byte + 1]))
+                    /* We've found the frame synch pattern. */
+                    return -1;
+                else
+                    /* We found 11111111 at the end of the current buffer indicating a
+                     * partial match of the synch pattern.  The find() below should
+                     * return -1 and break out of the loop. */
+                    previous_partial_synch_match = 1;
+            }
+
+            /* Check in the rest of the buffer. */
+            p = memchr(p + 1, 255, buffer_size);
+            if (p)
+                first_synch_byte = p - buffer;
+            else
+                first_synch_byte = -1;
+        }
+
+        /* (3) partial match */
+        if (buffer[nread - 1] == pattern[1])
+            previous_partial_match = nread - 1;
+        else if (memcmp(&buffer[nread - 2], pattern, 2) == 0)
+            previous_partial_match = nread - 2;
+        buffer_offset += buffer_size;
+
+        if ((nread = read(fd, &buffer, sizeof(buffer))) <= 0)
+            return -1;
+    }
+
+    return -1;
+}
+
+static unsigned int
+_to_uint(const char *data, int data_size)
+{
+    unsigned int sum = 0;
+    int last = data_size > 4 ? 3 : data_size - 1;
+    int i;
+
+    for (i = 0; i <= last; i++)
+        sum |= (data[i] & 0x7f) << ((last - i) * 7);
+
+    return sum;
+}
+
+static unsigned int
+_get_id3v2_frame_header_size(unsigned int version)
+{
+    switch (version) {
+    case 0:
+    case 1:
+    case 2:
+        return 6;
+    case 3:
+    case 4:
+    default:
+        return 10;
+    }
+}
+
+static void
+_parse_id3v2_frame_header(char *data, unsigned int version, struct id3v2_frame_header *fh)
+{
+    switch (version) {
+    case 0:
+    case 1:
+    case 2:
+        memcpy(fh->frame_id, data, 3);
+        fh->frame_id[3] = 0;
+        fh->frame_size = _to_uint(data + 3, 3);
+        fh->compression = 0;
+        fh->data_length_indicator = 0;
+        break;
+    case 3:
+        memcpy(fh->frame_id, data, 4);
+        fh->frame_size = _to_uint(data + 4, 4);
+        fh->compression = data[9] & 0x40;
+        fh->data_length_indicator = 0;
+        break;
+    case 4:
+    default:
+        memcpy(fh->frame_id, data, 4);
+        fh->frame_size = _to_uint(data + 4, 4);
+        fh->compression = data[9] & 0x4;
+        fh->data_length_indicator = data[9] & 0x1;
+        break;
+    }
+}
+
+static void
+_parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
+{
+    unsigned int frame_size;
+
+    /* TODO proper handle text encoding
+     *
+     * Latin1  = 0
+     * UTF16   = 1
+     * UTF16BE = 2
+     * UTF8    = 3
+     * UTF16LE = 4
+     */
+
+    /* skip first byte - text encoding */
+    frame_data += 1;
+    frame_size = fh->frame_size - 1;
+
+    if (memcmp(fh->frame_id, "TIT2", 4) == 0)
+        ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->title.str, info->title.len)
+    else if (memcmp(fh->frame_id, "TPE1", 4) == 0)
+        ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->artist.str, info->artist.len)
+    else if (memcmp(fh->frame_id, "TALB", 4) == 0)
+        ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->album.str, info->album.len)
+    else if (memcmp(fh->frame_id, "TRCK", 4) == 0) {
+        char *trackno;
+        unsigned int trackno_len;
+        ID3V2_GET_FRAME_INFO(frame_data, frame_size, trackno, trackno_len);
+        info->trackno = atoi(trackno);
+    }
+}
+
+static int
+_parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
+{
+    char header[10];
+    char *data;
+    unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
+    int extended_header, footer_present;
+    struct id3v2_frame_header fh;
+
+    lseek(fd, id3v2_offset, SEEK_SET);
+
+    /* parse header */
+    if (read(fd, header, ID3V2_HEADER_SIZE) != 10)
+        return -1;
+
+    tag_size = _to_uint(header + 6, 4);
+    if (tag_size == 0)
+        return -1;
+
+    /* parse frames */
+    data = malloc(sizeof(char) * tag_size);
+    if (read(fd, data, tag_size) != tag_size) {
+        free(data);
+        return -1;
+    }
+
+    major_version = header[3];
+
+    frame_data_pos = 0;
+    frame_data_length = tag_size;
+
+    /* check for extended header */
+    extended_header = header[5] & 0x20; /* bit 6 */
+    if (extended_header) {
+        /* skip extended header */
+        unsigned int extended_header_size = _to_uint(data, 4);
+        frame_data_pos += extended_header_size;
+        frame_data_length -= extended_header_size;
+    }
+
+    footer_present = header[5] & 0x8;   /* bit 4 */
+    if (footer_present && frame_data_length > ID3V2_FOOTER_SIZE)
+        frame_data_length -= ID3V2_FOOTER_SIZE;
+
+    frame_header_size = _get_id3v2_frame_header_size(major_version);
+    while (frame_data_pos < frame_data_length - frame_header_size) {
+        if (data[frame_data_pos] == 0)
+            break;
+
+        _parse_id3v2_frame_header(data + frame_data_pos, major_version, &fh);
+
+        if (!fh.compression &&
+            fh.frame_id[0] == 'T' &&
+            memcmp(fh.frame_id, "TXXX", 4) != 0) {
+            unsigned int offset = frame_header_size;
+            unsigned int length = fh.frame_size;
+
+            if (fh.data_length_indicator) {
+                length = _to_uint(data + frame_header_size, 4);
+                offset += 4;
+            }
+
+            _parse_id3v2_frame(&fh, data + frame_data_pos + offset, info);
+        }
+
+        frame_data_pos += fh.frame_size + frame_header_size;
+    }
+
+    free(data);
+
+    return 0;
+}
+
+static int
+_parse_id3v1(int fd, struct lms_audio_info *info)
+{
+    struct id3v1_tag tag;
+    if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
+        return -1;
+
+    info->title.str = strdup(tag.title);
+    info->title.len = strlen(info->title.str);
+    info->artist.str = strdup(tag.artist);
+    info->artist.len = strlen(info->artist.str);
+    info->album.str = strdup(tag.album);
+    info->album.len = strlen(info->album.str);
+    if (tag.genre >= 0 && tag.genre < ID3V1_NUM_GENRES) {
+        info->genre.str = strdup(id3v1_genres[(int) tag.genre]);
+        info->genre.len = strlen(info->genre.str);
+    }
+
+    return 0;
+}
+
+static void *
+_match(struct plugin *p, const char *path, int len, int base)
+{
+    int i;
+
+    i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
+    if (i < 0)
+      return NULL;
+    else
+      return (void*)(i + 1);
+}
+
+static int
+_parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
+{
+    struct lms_audio_info info = {0, {0}, {0}, {0}, {0}, 0, 0, 0};
+    int r, fd;
+    long id3v2_offset;
+
+    fd = open(finfo->path, O_RDONLY);
+    if (fd < 0) {
+        perror("open");
+        return -1;
+    }
+
+    id3v2_offset = _find_id3v2(fd);
+    if (id3v2_offset >= 0) {
+        fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
+                finfo->path, id3v2_offset);
+        if (_parse_id3v2(fd, id3v2_offset, &info) != 0) {
+            r = -2;
+            goto done;
+        }
+    }
+    else {
+        char tag[3];
+
+        fprintf(stderr, "id3v2 tag not found in file %s. trying id3v1\n", finfo->path);
+        /* check for id3v1 tag */
+        if (lseek(fd, -128, SEEK_END) == -1) {
+            r = -3;
+            goto done;
+        }
+
+        if (read(fd, &tag, 3) == -1) {
+            r = -4;
+            goto done;
+        }
+
+        if (memcmp(tag, "TAG", 3) == 0) {
+            fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
+            if (_parse_id3v1(fd, &info) != 0) {
+                r = -5;
+                goto done;
+            }
+        }
+    }
+
+    lms_string_size_strip_and_free(&info.title);
+    lms_string_size_strip_and_free(&info.artist);
+    lms_string_size_strip_and_free(&info.album);
+    lms_string_size_strip_and_free(&info.genre);
+
+    if (!info.title.str) {
+        int ext_idx;
+        ext_idx = ((int)match) - 1;
+        info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
+        info.title.str = malloc((info.title.len + 1) * sizeof(char));
+        memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
+        info.title.str[info.title.len] = '\0';
+    }
+    lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
+
+    if (info.artist.str)
+        lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
+    if (info.album.str)
+        lms_charset_conv(ctxt->cs_conv, &info.album.str, &info.album.len);
+    if (info.genre.str)
+        lms_charset_conv(ctxt->cs_conv, &info.genre.str, &info.genre.len);
+
+#if 0
+    fprintf(stderr, "file %s info\n", finfo->path);
+    fprintf(stderr, "\ttitle='%s'\n", info.title.str);
+    fprintf(stderr, "\tartist='%s'\n", info.artist.str);
+    fprintf(stderr, "\talbum='%s'\n", info.album.str);
+    fprintf(stderr, "\tgenre='%s'\n", info.genre.str);
+#endif
+
+    info.id = finfo->id;
+    r = lms_db_audio_add(plugin->audio_db, &info);
+
+  done:
+    close(fd);
+
+    if (info.title.str)
+        free(info.title.str);
+    if (info.artist.str)
+        free(info.artist.str);
+    if (info.album.str)
+        free(info.album.str);
+    if (info.genre.str)
+        free(info.genre.str);
+
+    return r;
+}
+
+static int
+_setup(struct plugin *plugin, struct lms_context *ctxt)
+{
+    plugin->audio_db = lms_db_audio_new(ctxt->db);
+    if (!plugin->audio_db)
+        return -1;
+    return 0;
+}
+
+static int
+_start(struct plugin *plugin, struct lms_context *ctxt)
+{
+    return lms_db_audio_start(plugin->audio_db);
+}
+
+static int
+_finish(struct plugin *plugin, struct lms_context *ctxt)
+{
+    if (plugin->audio_db)
+        lms_db_audio_free(plugin->audio_db);
+    return 0;
+}
+
+static int
+_close(struct plugin *plugin)
+{
+    free(plugin);
+    return 0;
+}
+
+API struct lms_plugin *
+lms_plugin_open(void)
+{
+    struct plugin *plugin;
+
+    plugin = (struct plugin *)malloc(sizeof(*plugin));
+    plugin->plugin.name = _name;
+    plugin->plugin.match = (lms_plugin_match_fn_t)_match;
+    plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
+    plugin->plugin.close = (lms_plugin_close_fn_t)_close;
+    plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
+    plugin->plugin.start = (lms_plugin_start_fn_t)_start;
+    plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
+
+    return (struct lms_plugin *)plugin;
+}