#include "config.h"
#endif
+#define _GNU_SOURCE
#define _XOPEN_SOURCE 600
#include <lightmediascanner_plugin.h>
#include <lightmediascanner_db.h>
+#include <lightmediascanner_charset_conv.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
-#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
#define ID3V2_FRAME_HEADER_SIZE 10
#define ID3V1_NUM_GENRES 148
+#define ID3_NUM_ENCODINGS 5
+
+enum ID3Encodings {
+ ID3_ENCODING_LATIN1 = 0,
+ ID3_ENCODING_UTF16,
+ ID3_ENCODING_UTF16BE,
+ ID3_ENCODING_UTF8,
+ ID3_ENCODING_UTF16LE
+};
+
static const char *id3v1_genres[ID3V1_NUM_GENRES] = {
"Blues",
"Classic Rock",
struct plugin {
struct lms_plugin plugin;
lms_db_audio_t *audio_db;
+ lms_charset_conv_t *cs_convs[ID3_NUM_ENCODINGS];
};
static const char _name[] = "id3";
LMS_STATIC_STRING_SIZE(".aac")
};
-static int
+static unsigned int
+_to_uint(const char *data, int data_size)
+{
+ unsigned int sum = 0;
+ unsigned int last, i;
+
+ last = data_size > 4 ? 3 : data_size - 1;
+
+ for (i = 0; i <= last; i++)
+ sum |= ((unsigned char) data[i]) << ((last - i) * 8);
+
+ return sum;
+}
+
+static inline int
_is_id3v2_second_synch_byte(unsigned char byte)
{
if (byte == 0xff)
}
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) {
}
}
+static inline void
+_get_id3v2_frame_info(const char *frame_data, unsigned int frame_size, struct lms_string_size *s, lms_charset_conv_t *cs_conv)
+{
+ s->str = malloc(sizeof(char) * (frame_size + 1));
+ memcpy(s->str, frame_data, frame_size);
+ s->str[frame_size] = '\0';
+ s->len = frame_size;
+ if (cs_conv)
+ lms_charset_conv(cs_conv, &s->str, &s->len);
+}
+
static void
-_parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info)
+_parse_id3v2_frame(struct id3v2_frame_header *fh, const char *frame_data, struct lms_audio_info *info, lms_charset_conv_t **cs_convs)
{
- unsigned int frame_size;
+ lms_charset_conv_t *cs_conv = NULL;
+ unsigned int text_encoding, frame_size;
- /* TODO proper handle text encoding
- *
- * Latin1 = 0
+#if 0
+ fprintf(stderr, "text encoding = %d\n", frame_data[0]);
+#endif
+
+ /* Latin1 = 0
* UTF16 = 1
* UTF16BE = 2
* UTF8 = 3
* UTF16LE = 4
*/
+ text_encoding = frame_data[0];
+ if (text_encoding >= 0 && text_encoding < ID3_NUM_ENCODINGS)
+ cs_conv = cs_convs[text_encoding];
/* 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)
+ _get_id3v2_frame_info(frame_data, frame_size, &info->title, cs_conv);
else if (memcmp(fh->frame_id, "TPE1", 4) == 0)
- ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->artist.str, info->artist.len)
+ _get_id3v2_frame_info(frame_data, frame_size, &info->artist, cs_conv);
else if (memcmp(fh->frame_id, "TALB", 4) == 0)
- ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->album.str, info->album.len)
+ _get_id3v2_frame_info(frame_data, frame_size, &info->album, cs_conv);
else if (memcmp(fh->frame_id, "TCON", 4) == 0) {
/* TODO handle multiple genres */
int i, is_number;
- ID3V2_GET_FRAME_INFO(frame_data, frame_size, info->genre.str, info->genre.len)
+ _get_id3v2_frame_info(frame_data, frame_size, &info->genre, cs_conv);
is_number = 1;
for (i = 0; i < info->genre.len; ++i) {
}
}
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);
+ struct lms_string_size trackno;
+ _get_id3v2_frame_info(frame_data, frame_size, &trackno, cs_conv);
+ info->trackno = atoi(trackno.str);
+ free(trackno.str);
}
}
static int
-_parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info)
+_parse_id3v2(int fd, long id3v2_offset, struct lms_audio_info *info, lms_charset_conv_t **cs_convs)
{
char header_data[10], frame_header_data[10];
unsigned int tag_size, major_version, frame_data_pos, frame_data_length, frame_header_size;
return -1;
}
- _parse_id3v2_frame(&fh, frame_data, info);
+ _parse_id3v2_frame(&fh, frame_data, info, cs_convs);
free(frame_data);
}
else {
}
static int
-_parse_id3v1(int fd, struct lms_audio_info *info)
+_parse_id3v1(int fd, struct lms_audio_info *info, lms_charset_conv_t *cs_conv)
{
struct id3v1_tag tag;
if (read(fd, &tag, sizeof(struct id3v1_tag)) == -1)
return -1;
- info->title.str = strdup(tag.title);
+ info->title.str = strndup(tag.title, 30);
info->title.len = strlen(info->title.str);
- info->artist.str = strdup(tag.artist);
+ lms_charset_conv(cs_conv, &info->title.str, &info->title.len);
+ info->artist.str = strndup(tag.artist, 30);
info->artist.len = strlen(info->artist.str);
- info->album.str = strdup(tag.album);
+ lms_charset_conv(cs_conv, &info->artist.str, &info->artist.len);
+ info->album.str = strndup(tag.album, 30);
info->album.len = strlen(info->album.str);
- info->genre.str = strdup(id3v1_genres[(int) tag.genre]);
- info->genre.len = strlen(info->genre.str);
+ lms_charset_conv(cs_conv, &info->album.str, &info->album.len);
+ if ((unsigned char) tag.genre < ID3V1_NUM_GENRES) {
+ info->genre.str = strdup(id3v1_genres[(unsigned char) tag.genre]);
+ info->genre.len = strlen(info->genre.str);
+ }
if (tag.comments[28] == 0 && tag.comments[29] != 0)
info->trackno = (unsigned char) tag.comments[29];
fprintf(stderr, "id3v2 tag found in file %s with offset %ld\n",
finfo->path, id3v2_offset);
#endif
- if (_parse_id3v2(fd, id3v2_offset, &info) != 0) {
+ if (_parse_id3v2(fd, id3v2_offset, &info, plugin->cs_convs) != 0) {
r = -2;
goto done;
}
#if 0
fprintf(stderr, "id3v1 tag found in file %s\n", finfo->path);
#endif
- if (_parse_id3v1(fd, &info) != 0) {
+ if (_parse_id3v1(fd, &info, ctxt->cs_conv) != 0) {
r = -5;
goto done;
}
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);
}
- 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);
static int
_setup(struct plugin *plugin, struct lms_context *ctxt)
{
+ int i;
+ const char *id3v2_encodings[ID3_NUM_ENCODINGS] = {
+ "Latin1",
+ "UTF-16",
+ "UTF-16BE",
+ NULL, /* UTF-8 */
+ "UTF-16LE",
+ };
+
plugin->audio_db = lms_db_audio_new(ctxt->db);
if (!plugin->audio_db)
return -1;
+
+ for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
+ /* do not create charset conv for UTF-8 encoding */
+ if (i == ID3_ENCODING_UTF8) {
+ plugin->cs_convs[i] = NULL;
+ continue;
+ }
+ plugin->cs_convs[i] = lms_charset_conv_new_full(0, 0);
+ if (!plugin->cs_convs[i])
+ return -1;
+ lms_charset_conv_add(plugin->cs_convs[i], id3v2_encodings[i]);
+ }
+
return 0;
}
static int
_finish(struct plugin *plugin, struct lms_context *ctxt)
{
+ int i;
+
if (plugin->audio_db)
lms_db_audio_free(plugin->audio_db);
+
+ for (i = 0; i < ID3_NUM_ENCODINGS; ++i) {
+ if (plugin->cs_convs[i])
+ lms_charset_conv_free(plugin->cs_convs[i]);
+ }
+
return 0;
}