Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/azatoth/minidlna.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Maggard <jmaggard@users.sourceforge.net>2009-02-26 00:16:51 +0300
committerJustin Maggard <jmaggard@users.sourceforge.net>2009-02-26 00:16:51 +0300
commite5f3e012485a696175181ac53d5bd9f85ca4be75 (patch)
tree364d6fa38b92dfbb24c6a3a2c5cc8df4903b1d7b /tagutils
parent0212b7ced1220936dc7c14f4358bed5dc65dea17 (diff)
* Use internal music metadata functions intead of taglib.
1) Taglib does not support MP4 or WMA/ASF without hacking it in there. 2) Taglib is C++, so it's nice to remove that dependency. * Use embedded album art where available.
Diffstat (limited to 'tagutils')
-rw-r--r--tagutils/misc.c118
-rw-r--r--tagutils/misc.h52
-rw-r--r--tagutils/tagutils-aac.c368
-rw-r--r--tagutils/tagutils-aac.h27
-rw-r--r--tagutils/tagutils-asf.c547
-rw-r--r--tagutils/tagutils-asf.h352
-rw-r--r--tagutils/tagutils-flc.c90
-rw-r--r--tagutils/tagutils-flc.h24
-rw-r--r--tagutils/tagutils-misc.c262
-rw-r--r--tagutils/tagutils-mp3.c776
-rw-r--r--tagutils/tagutils-mp3.h64
-rw-r--r--tagutils/tagutils-ogg.c542
-rw-r--r--tagutils/tagutils-ogg.h24
-rw-r--r--tagutils/tagutils-plist.c140
-rw-r--r--tagutils/tagutils.c289
-rw-r--r--tagutils/tagutils.h120
-rw-r--r--tagutils/textutils.c298
-rw-r--r--tagutils/textutils.h29
18 files changed, 4122 insertions, 0 deletions
diff --git a/tagutils/misc.c b/tagutils/misc.c
new file mode 100644
index 0000000..4fd6220
--- /dev/null
+++ b/tagutils/misc.c
@@ -0,0 +1,118 @@
+//=========================================================================
+// FILENAME : misc.c
+// DESCRIPTION : Miscelleneous funcs
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <endian.h>
+
+#include "misc.h"
+
+inline __u16
+le16_to_cpu(__u16 le16)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ return le16;
+#else
+ __u16 be16 = ((le16 << 8) & 0xff00) | ((le16 >> 8) & 0x00ff);
+ return be16;
+#endif
+}
+
+inline __u32
+le32_to_cpu(__u32 le32)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ return le32;
+#else
+ __u32 be32 =
+ ((le32 << 24) & 0xff000000) |
+ ((le32 << 8) & 0x00ff0000) |
+ ((le32 >> 8) & 0x0000ff00) |
+ ((le32 >> 24) & 0x000000ff);
+ return be32;
+#endif
+}
+
+inline __u64
+le64_to_cpu(__u64 le64)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ return le64;
+#else
+ __u64 be64;
+ __u8 *le64p = (__u8*)&le64;
+ __u8 *be64p = (__u8*)&be64;
+ be64p[0] = le64p[7];
+ be64p[1] = le64p[6];
+ be64p[2] = le64p[5];
+ be64p[3] = le64p[4];
+ be64p[4] = le64p[3];
+ be64p[5] = le64p[2];
+ be64p[6] = le64p[1];
+ be64p[7] = le64p[0];
+ return be64;
+#endif
+}
+
+inline __u8
+fget_byte(FILE *fp)
+{
+ __u8 d;
+
+ (void)fread(&d, sizeof(d), 1, fp);
+ return d;
+}
+
+inline __u16
+fget_le16(FILE *fp)
+{
+ __u16 d;
+
+ (void)fread(&d, sizeof(d), 1, fp);
+ d = le16_to_cpu(d);
+ return d;
+}
+
+inline __u32
+fget_le32(FILE *fp)
+{
+ __u32 d;
+
+ (void)fread(&d, sizeof(d), 1, fp);
+ d = le32_to_cpu(d);
+ return d;
+}
+
+inline __u32
+cpu_to_be32(__u32 cpu32)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ __u32 be32 =
+ ((cpu32 << 24) & 0xff000000) |
+ ((cpu32 << 8) & 0x00ff0000) |
+ ((cpu32 >> 8) & 0x0000ff00) |
+ ((cpu32 >> 24) & 0x000000ff);
+ return be32;
+#else
+ return cpu32;
+#endif
+}
diff --git a/tagutils/misc.h b/tagutils/misc.h
new file mode 100644
index 0000000..59704b6
--- /dev/null
+++ b/tagutils/misc.h
@@ -0,0 +1,52 @@
+//=========================================================================
+// FILENAME : misc.h
+// DESCRIPTION : Header for misc.c
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+#ifndef _SCANNER_MISC_H
+#define _SCANNER_MISC_H
+
+typedef unsigned char __u8;
+typedef signed char __s8;
+typedef unsigned short __u16;
+typedef signed short __s16;
+typedef unsigned int __u32;
+typedef signed int __s32;
+#if __WORDSIZE == 64
+typedef unsigned long __u64;
+typedef signed long __s64;
+#else
+typedef unsigned long long __u64;
+typedef signed long long __s64;
+#endif
+
+
+inline __u16 le16_to_cpu(__u16 le16);
+inline __u32 le32_to_cpu(__u32 le32);
+inline __u64 le64_to_cpu(__u64 le64);
+inline __u8 fget_byte(FILE *fp);
+inline __u16 fget_le16(FILE *fp);
+inline __u32 fget_le32(FILE *fp);
+
+inline __u32 cpu_to_be32(__u32 cpu32);
+
+extern char * sha1_hex(char *key);
+
+#endif
diff --git a/tagutils/tagutils-aac.c b/tagutils/tagutils-aac.c
new file mode 100644
index 0000000..1107454
--- /dev/null
+++ b/tagutils/tagutils-aac.c
@@ -0,0 +1,368 @@
+//=========================================================================
+// FILENAME : tagutils-aac.c
+// DESCRIPTION : AAC metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/*
+ * This file is derived from mt-daap project.
+ */
+
+// _mac_to_unix_time
+static time_t
+_mac_to_unix_time(int t)
+{
+ struct timeval tv;
+ struct timezone tz;
+
+ gettimeofday(&tv, &tz);
+
+ return (t - (365L * 66L * 24L * 60L * 60L + 17L * 60L * 60L * 24L) +
+ (tz.tz_minuteswest * 60));
+}
+
+
+
+// _aac_findatom:
+static long
+_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size)
+{
+ long current_offset = 0;
+ int size;
+ char atom[4];
+
+ while(current_offset < max_offset)
+ {
+ if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int))
+ return -1;
+
+ size = ntohl(size);
+
+ if(size <= 7)
+ return -1;
+
+ if(fread(atom, 1, 4, fin) != 4)
+ return -1;
+
+ if(strncasecmp(atom, which_atom, 4) == 0)
+ {
+ *atom_size = size;
+ return current_offset;
+ }
+
+ fseek(fin, size - 8, SEEK_CUR);
+ current_offset += size;
+ }
+
+ return -1;
+}
+
+// _get_aactags
+static int
+_get_aactags(char *file, struct song_metadata *psong)
+{
+ FILE *fin;
+ long atom_offset;
+ unsigned int atom_length;
+
+ long current_offset = 0;
+ int current_size;
+ char current_atom[4];
+ char *current_data;
+ int genre;
+ int len;
+
+ if(!(fin = fopen(file, "rb")))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file);
+ return -1;
+ }
+
+ fseek(fin, 0, SEEK_SET);
+
+ atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length);
+ if(atom_offset != -1)
+ {
+ while(current_offset < atom_length)
+ {
+ if(fread((void*)&current_size, 1, sizeof(int), fin) != sizeof(int))
+ break;
+
+ current_size = ntohl(current_size);
+
+ if(current_size <= 7) // something not right
+ break;
+
+ if(fread(current_atom, 1, 4, fin) != 4)
+ break;
+
+ len = current_size - 7; // too short
+ if(len < 22)
+ len = 22;
+
+ current_data = (char*)malloc(len); // extra byte
+ memset(current_data, 0x00, len);
+
+ if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
+ break;
+
+ if(!memcmp(current_atom, "\xA9" "nam", 4))
+ psong->title = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
+ !memcmp(current_atom, "\xA9" "art", 4))
+ psong->contributor[ROLE_ARTIST] = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "alb", 4))
+ psong->album = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "cmt", 4))
+ psong->comment = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "dir", 4))
+ psong->contributor[ROLE_CONDUCTOR] = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "wrt", 4))
+ psong->contributor[ROLE_COMPOSER] = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "grp", 4))
+ psong->grouping = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "gen", 4))
+ psong->genre = strdup((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "\xA9" "day", 4))
+ psong->year = atoi((char*)&current_data[16]);
+ else if(!memcmp(current_atom, "tmpo", 4))
+ psong->bpm = (current_data[16] << 8) | current_data[17];
+ else if(!memcmp(current_atom, "trkn", 4))
+ {
+ psong->track = (current_data[18] << 8) | current_data[19];
+ psong->total_tracks = (current_data[20] << 8) | current_data[21];
+ }
+ else if(!memcmp(current_atom, "disk", 4))
+ {
+ psong->disc = (current_data[18] << 8) | current_data[19];
+ psong->total_discs = (current_data[20] << 8) | current_data[21];
+ }
+ else if(!memcmp(current_atom, "gnre", 4))
+ {
+ genre = current_data[17] - 1;
+ if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
+ genre = WINAMP_GENRE_UNKNOWN;
+ psong->genre = strdup(winamp_genre[genre]);
+ }
+ else if(!memcmp(current_atom, "cpil", 4))
+ {
+ psong->compilation = current_data[16];
+ }
+
+ free(current_data);
+ current_offset += current_size;
+ }
+ }
+
+ fclose(fin);
+
+ if(atom_offset == -1)
+ return -1;
+
+ return 0;
+}
+
+// aac_lookforatom
+static off_t
+_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length)
+{
+ long atom_offset;
+ off_t file_size;
+ char *cur_p, *end_p;
+ char atom_name[5];
+
+ fseek(aac_fp, 0, SEEK_END);
+ file_size = ftell(aac_fp);
+ rewind(aac_fp);
+
+ end_p = atom_path;
+ while(*end_p != '\0')
+ {
+ end_p++;
+ }
+ atom_name[4] = '\0';
+ cur_p = atom_path;
+
+ while(cur_p)
+ {
+ if((end_p - cur_p) < 4)
+ {
+ return -1;
+ }
+ strncpy(atom_name, cur_p, 4);
+ atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length);
+ if(atom_offset == -1)
+ {
+ return -1;
+ }
+ cur_p = strchr(cur_p, ':');
+ if(cur_p != NULL)
+ {
+ cur_p++;
+
+ if(!strcmp(atom_name, "meta"))
+ {
+ fseek(aac_fp, 4, SEEK_CUR);
+ }
+ else if(!strcmp(atom_name, "stsd"))
+ {
+ fseek(aac_fp, 8, SEEK_CUR);
+ }
+ else if(!strcmp(atom_name, "mp4a"))
+ {
+ fseek(aac_fp, 28, SEEK_CUR);
+ }
+ }
+ }
+
+ // return position of 'size:atom'
+ return ftell(aac_fp) - 8;
+}
+
+// _get_aacfileinfo
+int
+_get_aacfileinfo(char *file, struct song_metadata *psong)
+{
+ FILE *infile;
+ long atom_offset;
+ int atom_length;
+ int sample_size;
+ int samples;
+ unsigned int bitrate;
+ off_t file_size;
+ int ms;
+ unsigned char buffer[2];
+ int time = 0;
+ aac_object_type_t profile_id = 0;
+
+ psong->vbr_scale = -1;
+
+ if(!(infile = fopen(file, "rb")))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
+ return -1;
+ }
+
+ fseek(infile, 0, SEEK_END);
+ file_size = ftell(infile);
+ fseek(infile, 0, SEEK_SET);
+
+ // move to 'mvhd' atom
+ atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length);
+ if(atom_offset != -1)
+ {
+ fseek(infile, 8, SEEK_CUR);
+ fread((void *)&time, sizeof(int), 1, infile);
+ time = ntohl(time);
+ // slimserver prefer to use filesystem time
+ //psong->time_modified = _mac_to_unix_time(time);
+ fread((void*)&sample_size, 1, sizeof(int), infile);
+ fread((void*)&samples, 1, sizeof(int), infile);
+
+ sample_size = ntohl(sample_size);
+ samples = ntohl(samples);
+
+ // avoid overflowing on large sample_sizes (90000)
+ ms = 1000;
+ while((ms > 9) && (!(sample_size % 10)))
+ {
+ sample_size /= 10;
+ ms /= 10;
+ }
+
+ // unit = ms
+ psong->song_length = (int)((samples * ms) / sample_size);
+ }
+
+ psong->bitrate = 0;
+
+ // get samplerate from 'mp4a' (not from 'mdhd')
+ atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length);
+ if(atom_offset != -1)
+ {
+ fseek(infile, atom_offset + 32, SEEK_SET);
+
+ fread(buffer, sizeof(unsigned char), 2, infile);
+
+ psong->samplerate = (buffer[0] << 8) | (buffer[1]);
+
+ fseek(infile, 2, SEEK_CUR);
+
+ // get bitrate fomr 'esds'
+ atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
+
+ if(atom_offset != -1)
+ {
+ fseek(infile, atom_offset + 26, SEEK_CUR); // +22 is max bitrate, +26 is average bitrate
+ fread((void *)&bitrate, sizeof(unsigned int), 1, infile);
+ psong->bitrate = ntohl(bitrate);
+
+ fseek(infile, 5, SEEK_CUR); // 5 bytes past bitrate is setup data
+ fread((void *)&buffer, 2, 1, infile);
+ profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
+ /* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
+ samples = ((buffer[1] >> 3) & 0xF);
+ psong->channels = (samples == 7 ? 8 : samples);
+ }
+ }
+
+ atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
+ psong->audio_size = atom_length - 8;
+ psong->audio_offset = atom_offset;
+
+ if(!psong->bitrate)
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate.\n");
+ if((atom_offset != -1) && (psong->song_length))
+ {
+ psong->bitrate = atom_length * 1000 / psong->song_length / 128;
+ }
+ }
+
+ //DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
+ switch( profile_id )
+ {
+ case AAC_LC:
+ case AAC_LC_ER:
+ if( psong->samplerate < 8000 || psong->samplerate > 48000 )
+ {
+ DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
+ psong->samplerate);
+ break;
+ }
+ /* AAC @ Level 1/2 */
+ if( psong->channels <= 2 && psong->bitrate <= 320000 )
+ asprintf(&(psong->dlna_pn), "AAC_ISO_320");
+ else if( psong->channels <= 2 && psong->bitrate <= 576000 )
+ asprintf(&(psong->dlna_pn), "AAC_ISO");
+ else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
+ asprintf(&(psong->dlna_pn), "AAC_MULT5_ISO");
+ else
+ DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
+ psong->channels, psong->bitrate);
+ break;
+ default:
+ DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", profile_id);
+ break;
+ }
+
+ fclose(infile);
+ return 0;
+}
diff --git a/tagutils/tagutils-aac.h b/tagutils/tagutils-aac.h
new file mode 100644
index 0000000..1f6ff2b
--- /dev/null
+++ b/tagutils/tagutils-aac.h
@@ -0,0 +1,27 @@
+//=========================================================================
+// FILENAME : tagutils-aac.h
+// DESCRIPTION : AAC metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+static int _get_aactags(char *file, struct song_metadata *psong);
+static int _get_aacfileinfo(char *file, struct song_metadata *psong);
+static off_t _aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length);
+static time_t _mac_to_unix_time(int t) __attribute__((unused));
diff --git a/tagutils/tagutils-asf.c b/tagutils/tagutils-asf.c
new file mode 100644
index 0000000..5ec2896
--- /dev/null
+++ b/tagutils/tagutils-asf.c
@@ -0,0 +1,547 @@
+//=========================================================================
+// FILENAME : tagutils-asf.c
+// DESCRIPTION : ASF (wma/wmv) metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+static int
+_asf_read_file_properties(FILE *fp, asf_file_properties_t *p, __u32 size)
+{
+ int len;
+
+ len = sizeof(*p) - offsetof(asf_file_properties_t, FileID);
+ if(size < len)
+ return -1;
+
+ memset(p, 0, sizeof(*p));
+ p->ID = ASF_FileProperties;
+ p->Size = size;
+
+ if(len != fread(&p->FileID, 1, len, fp))
+ return -1;
+
+ return 0;
+}
+
+static int
+_asf_read_audio_stream(FILE *fp, struct song_metadata *psong, int size)
+{
+ asf_audio_stream_t s;
+ int len;
+
+ len = sizeof(s) - sizeof(s.Hdr);
+ if(len > size)
+ len = size;
+
+ if(len != fread(&s.wfx, 1, len, fp))
+ return -1;
+
+ psong->channels = le16_to_cpu(s.wfx.nChannels);
+ psong->bitrate = le32_to_cpu(s.wfx.nAvgBytesPerSec) * 8;
+ psong->samplerate = le32_to_cpu(s.wfx.nSamplesPerSec);
+ /* DLNA Profile Name */
+ switch( le16_to_cpu(s.wfx.wFormatTag) )
+ {
+ case WMAV1:
+ case WMAV2:
+ if( psong->bitrate < 193000 )
+ asprintf(&(psong->dlna_pn), "WMABASE");
+ else
+ asprintf(&(psong->dlna_pn), "WMAFULL");
+ break;
+ case WMAPRO:
+ asprintf(&(psong->dlna_pn), "WMAPRO");
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int
+_asf_read_media_stream(FILE *fp, struct song_metadata *psong, __u32 size)
+{
+ asf_media_stream_t s;
+ avi_audio_format_t wfx;
+ int len;
+
+ len = sizeof(s) - sizeof(s.Hdr);
+ if(len > size)
+ len = size;
+
+ if(len != fread(&s.MajorType, 1, len, fp))
+ return -1;
+
+ if(IsEqualGUID(&s.MajorType, &ASF_MediaTypeAudio) &&
+ IsEqualGUID(&s.FormatType, &ASF_FormatTypeWave) && s.FormatSize >= sizeof(wfx))
+ {
+
+ if(sizeof(wfx) != fread(&wfx, 1, sizeof(wfx), fp))
+ return -1;
+
+ psong->channels = le16_to_cpu(wfx.nChannels);
+ psong->bitrate = le32_to_cpu(wfx.nAvgBytesPerSec) * 8;
+ psong->samplerate = le32_to_cpu(wfx.nSamplesPerSec);
+ /* DLNA Profile Name */
+ switch( le16_to_cpu(wfx.wFormatTag) )
+ {
+ case WMAV1:
+ case WMAV2:
+ printf("***** JM: bitrate: %d\n", psong->bitrate);
+ if( psong->bitrate < 193000 )
+ asprintf(&(psong->dlna_pn), "WMABASE");
+ else
+ asprintf(&(psong->dlna_pn), "WMAFULL");
+ break;
+ case WMAPRO:
+ asprintf(&(psong->dlna_pn), "WMAPRO");
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+_asf_read_stream_object(FILE *fp, struct song_metadata *psong, __u32 size)
+{
+ asf_stream_object_t s;
+ int len;
+
+ len = sizeof(s) - sizeof(asf_object_t);
+ if(size < len)
+ return -1;
+
+ if(len != fread(&s.StreamType, 1, len, fp))
+ return -1;
+
+ if(IsEqualGUID(&s.StreamType, &ASF_AudioStream))
+ _asf_read_audio_stream(fp, psong, s.TypeSpecificSize);
+ else if(IsEqualGUID(&s.StreamType, &ASF_StreamBufferStream))
+ _asf_read_media_stream(fp, psong, s.TypeSpecificSize);
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Unknown asf stream type.\n");
+ }
+
+ return 0;
+}
+
+static int
+_asf_read_extended_stream_object(FILE *fp, struct song_metadata *psong, __u32 size)
+{
+ int i, len;
+ long off;
+ asf_object_t tmp;
+ asf_extended_stream_object_t xs;
+ asf_stream_name_t nm;
+ asf_payload_extension_t pe;
+
+ if(size < sizeof(asf_extended_stream_object_t))
+ return -1;
+
+ len = sizeof(xs) - offsetof(asf_extended_stream_object_t, StartTime);
+ if(len != fread(&xs.StartTime, 1, len, fp))
+ return -1;
+ off = sizeof(xs);
+
+ for(i = 0; i < xs.StreamNameCount; i++)
+ {
+ if(off + sizeof(nm) > size)
+ return -1;
+ if(sizeof(nm) != fread(&nm, 1, sizeof(nm), fp))
+ return -1;
+ off += sizeof(nm);
+ if(off + nm.Length > sizeof(asf_extended_stream_object_t))
+ return -1;
+ if(nm.Length > 0)
+ fseek(fp, nm.Length, SEEK_CUR);
+ off += nm.Length;
+ }
+
+ for(i = 0; i < xs.PayloadExtensionSystemCount; i++)
+ {
+ if(off + sizeof(pe) > size)
+ return -1;
+ if(sizeof(pe) != fread(&pe, 1, sizeof(pe), fp))
+ return -1;
+ off += sizeof(pe);
+ if(pe.InfoLength > 0)
+ fseek(fp, pe.InfoLength, SEEK_CUR);
+ off += pe.InfoLength;
+ }
+
+ if(off < size)
+ {
+ if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
+ return -1;
+ if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
+ _asf_read_stream_object(fp, psong, tmp.Size);
+ }
+
+ return 0;
+}
+
+static int
+_asf_read_header_extension(FILE *fp, struct song_metadata *psong, __u32 size)
+{
+ off_t pos;
+ long off;
+ asf_header_extension_t ext;
+ asf_object_t tmp;
+
+ if(size < sizeof(asf_header_extension_t))
+ return -1;
+
+ fread(&ext.Reserved1, 1, sizeof(ext.Reserved1), fp);
+ ext.Reserved2 = fget_le16(fp);
+ ext.DataSize = fget_le32(fp);
+
+ pos = ftell(fp);
+ off = 0;
+ while(off < ext.DataSize)
+ {
+ if(sizeof(asf_header_extension_t) + off > size)
+ break;
+ if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
+ break;
+ if(off + tmp.Size > ext.DataSize)
+ break;
+ if(IsEqualGUID(&tmp.ID, &ASF_ExtendedStreamPropertiesObject))
+ _asf_read_extended_stream_object(fp, psong, tmp.Size);
+
+ off += tmp.Size;
+ fseek(fp, pos + off, SEEK_SET);
+ }
+
+ return 0;
+}
+
+static int
+_asf_load_string(FILE *fp, int type, int size, char *buf, int len)
+{
+ unsigned char data[2048];
+ __u16 wc;
+ int i, j;
+
+ i = 0;
+ if(size && (size <= sizeof(data)) && (size == fread(data, 1, size, fp)))
+ {
+
+ switch(type)
+ {
+ case ASF_VT_UNICODE:
+ for(j = 0; j < size; j += 2)
+ {
+ wc = *(__s16*)&data[j];
+ i += utf16le_to_utf8(&buf[i], len - i, wc);
+ }
+ break;
+ case ASF_VT_BYTEARRAY:
+ for(i = 0; i < size; i++)
+ {
+ if(i + 1 >= len)
+ break;
+ buf[i] = data[i];
+ }
+ break;
+ case ASF_VT_BOOL:
+ case ASF_VT_DWORD:
+ if(size >= 4)
+ i = snprintf(buf, len, "%d", le32_to_cpu(*(__s32*)&data[0]));
+ break;
+ case ASF_VT_QWORD:
+ if(size >= 8)
+ {
+#if __WORDSIZE == 64
+ i = snprintf(buf, len, "%ld", le64_to_cpu(*(__s64*)&data[0]));
+#else
+ i = snprintf(buf, len, "%lld", le64_to_cpu(*(__s64*)&data[0]));
+#endif
+ }
+ break;
+ case ASF_VT_WORD:
+ if(size >= 2)
+ i = snprintf(buf, len, "%d", le16_to_cpu(*(__s16*)&data[0]));
+ break;
+ }
+
+ size = 0;
+ }
+ else fseek(fp, size, SEEK_CUR);
+
+ buf[i] = 0;
+ return i;
+}
+
+static void
+_asf_load_picture(FILE *fp, int size, void *bm, int *bm_size)
+{
+ int i;
+ char buf[256];
+ char pic_type;
+ long pic_size;
+
+ //
+ // Picture type $xx
+ // Data length $xx $xx $xx $xx
+ // MIME type <text string> $00
+ // Description <text string> $00
+ // Picture data <binary data>
+
+ pic_type = fget_byte(fp); size -= 1;
+ pic_size = fget_le32(fp); size -= 2;
+
+ i = 0;
+ buf[i] = 0;
+ if(!strcasecmp(buf, "image/jpeg") ||
+ !strcasecmp(buf, "image/jpg") ||
+ !strcasecmp(buf, "image/peg"))
+ {
+
+ while(0 != fget_le16(fp))
+ size -= 2;
+
+ if(size > 0)
+ {
+ if(!(bm = malloc(size)))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Couldn't allocate %d bytes\n", size);
+ }
+ else
+ {
+ *bm_size = size;
+ if(size <= *bm_size)
+ {
+ fread(bm, 1, size, fp);
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Overrun %d bytes required\n", size);
+ free(bm);
+ bm = NULL;
+ }
+ }
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "No binary data\n");
+ size = 0;
+ bm = NULL;
+ }
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Invalid mime type %s\n", buf);
+ }
+
+ *bm_size = size;
+}
+
+static int
+_get_asffileinfo(char *file, struct song_metadata *psong)
+{
+ FILE *fp;
+ asf_object_t hdr;
+ asf_object_t tmp;
+ unsigned long NumObjects;
+ unsigned short Reserved;
+ unsigned short TitleLength;
+ unsigned short AuthorLength;
+ unsigned short CopyrightLength;
+ unsigned short DescriptionLength;
+ unsigned short RatingLength;
+ unsigned short NumEntries;
+ unsigned short NameLength;
+ unsigned short ValueType;
+ unsigned short ValueLength;
+ off_t pos;
+ char buf[2048];
+ int mask;
+ asf_file_properties_t FileProperties;
+
+ psong->vbr_scale = -1;
+
+ if(!(fp = fopen(file, "rb")))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
+ return -1;
+ }
+
+ if(sizeof(hdr) != fread(&hdr, 1, sizeof(hdr), fp))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Error reading %s\n", file);
+ fclose(fp);
+ return -1;
+ }
+ hdr.Size = le64_to_cpu(hdr.Size);
+
+ if(!IsEqualGUID(&hdr.ID, &ASF_HeaderObject))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Not a valid header\n");
+ fclose(fp);
+ return -1;
+ }
+ NumObjects = fget_le32(fp);
+ Reserved = fget_le16(fp);
+
+ pos = ftell(fp);
+ mask = 0;
+ while(NumObjects > 0)
+ {
+ if(sizeof(tmp) != fread(&tmp, 1, sizeof(tmp), fp))
+ break;
+ tmp.Size = le64_to_cpu(tmp.Size);
+
+ if(pos + tmp.Size > hdr.Size)
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Size overrun reading header object %I64x\n", tmp.Size);
+ break;
+ }
+
+ if(IsEqualGUID(&tmp.ID, &ASF_FileProperties))
+ {
+ _asf_read_file_properties(fp, &FileProperties, tmp.Size);
+ psong->song_length = le64_to_cpu(FileProperties.PlayDuration) / 10000;
+ psong->bitrate = le64_to_cpu(FileProperties.MaxBitrate);
+ }
+ else if(IsEqualGUID(&tmp.ID, &ASF_ContentDescription))
+ {
+ TitleLength = fget_le16(fp);
+ AuthorLength = fget_le16(fp);
+ CopyrightLength = fget_le16(fp);
+ DescriptionLength = fget_le16(fp);
+ RatingLength = fget_le16(fp);
+
+ if(_asf_load_string(fp, ASF_VT_UNICODE, TitleLength, buf, sizeof(buf)))
+ {
+ if(buf[0])
+ psong->title = strdup(buf);
+ }
+ if(_asf_load_string(fp, ASF_VT_UNICODE, AuthorLength, buf, sizeof(buf)))
+ {
+ if(buf[0])
+ psong->contributor[ROLE_TRACKARTIST] = strdup(buf);
+ }
+ if(CopyrightLength)
+ fseek(fp, CopyrightLength, SEEK_CUR);
+ if(DescriptionLength)
+ fseek(fp, DescriptionLength, SEEK_CUR);
+ if(RatingLength)
+ fseek(fp, RatingLength, SEEK_CUR);
+ }
+ else if(IsEqualGUID(&tmp.ID, &ASF_ExtendedContentDescription))
+ {
+ NumEntries = fget_le16(fp);
+ while(NumEntries > 0)
+ {
+ NameLength = fget_le16(fp);
+ _asf_load_string(fp, ASF_VT_UNICODE, NameLength, buf, sizeof(buf));
+ ValueType = fget_le16(fp);
+ ValueLength = fget_le16(fp);
+
+ if(!strcasecmp(buf, "AlbumTitle") || !strcasecmp(buf, "WM/AlbumTitle"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->album = strdup(buf);
+ }
+ else if(!strcasecmp(buf, "AlbumArtist") || !strcasecmp(buf, "WM/AlbumArtist"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ {
+ if(buf[0])
+ psong->contributor[ROLE_ALBUMARTIST] = strdup(buf);
+ }
+ }
+ else if(!strcasecmp(buf, "Description") || !strcasecmp(buf, "WM/Track"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->track = atoi(buf);
+ }
+ else if(!strcasecmp(buf, "Genre") || !strcasecmp(buf, "WM/Genre"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->genre = strdup(buf);
+ }
+ else if(!strcasecmp(buf, "Year") || !strcasecmp(buf, "WM/Year"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->year = atoi(buf);
+ }
+ else if(!strcasecmp(buf, "WM/Director"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->contributor[ROLE_CONDUCTOR] = strdup(buf);
+ }
+ else if(!strcasecmp(buf, "WM/Picture") && (ValueType == ASF_VT_BYTEARRAY))
+ {
+ _asf_load_picture(fp, ValueLength, psong->image, &psong->image_size);
+ }
+ else if(!strcasecmp(buf, "TrackNumber") || !strcasecmp(buf, "WM/TrackNumber"))
+ {
+ if(_asf_load_string(fp, ValueType, ValueLength, buf, sizeof(buf)))
+ if(buf[0])
+ psong->track = atoi(buf);
+ }
+ else if(!strcasecmp(buf, "isVBR"))
+ {
+ fseek(fp, ValueLength, SEEK_CUR);
+ psong->vbr_scale = 0;
+ }
+ else if(ValueLength)
+ {
+ fseek(fp, ValueLength, SEEK_CUR);
+ }
+ NumEntries--;
+ }
+ }
+ else if(IsEqualGUID(&tmp.ID, &ASF_StreamHeader))
+ {
+ _asf_read_stream_object(fp, psong, tmp.Size);
+ }
+ else if(IsEqualGUID(&tmp.ID, &ASF_HeaderExtension))
+ {
+ _asf_read_header_extension(fp, psong, tmp.Size);
+ }
+ pos += tmp.Size;
+ fseek(fp, pos, SEEK_SET);
+ NumObjects--;
+ }
+
+#if 0
+ if(sizeof(hdr) == fread(&hdr, 1, sizeof(hdr), fp) && IsEqualGUID(&hdr.ID, &ASF_DataObject))
+ {
+ if(psong->song_length)
+ {
+ psong->bitrate = (hdr.Size * 8000) / psong->song_length;
+ }
+ }
+#endif
+
+ fclose(fp);
+ return 0;
+}
diff --git a/tagutils/tagutils-asf.h b/tagutils/tagutils-asf.h
new file mode 100644
index 0000000..254a5a3
--- /dev/null
+++ b/tagutils/tagutils-asf.h
@@ -0,0 +1,352 @@
+//=========================================================================
+// FILENAME : tagutils-asf.h
+// DESCRIPTION : ASF (wma/wmv) metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+
+#define __PACKED__ __attribute__((packed))
+
+#include <endian.h>
+
+typedef struct _GUID {
+ __u32 l;
+ __u16 w[2];
+ __u8 b[8];
+} __PACKED__ GUID;
+
+#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
+ GUID name = { l, { w1, w2 }, { b1, b2, b3, b4, b5, b6, b7, b8 } }
+#define IsEqualGUID(rguid1, rguid2) (!memcmp(rguid1, rguid2, sizeof(GUID)))
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define SWAP32(l) (l)
+#define SWAP16(w) (w)
+#else
+#define SWAP32(l) ( (((l) >> 24) & 0x000000ff) | (((l) >> 8) & 0x0000ff00) | (((l) << 8) & 0x00ff0000) | (((l) << 24) & 0xff000000) )
+#define SWAP16(w) ( (((w) >> 8) & 0x00ff) | (((w) << 8) & 0xff00) )
+#endif
+
+DEFINE_GUID(ASF_StreamHeader, SWAP32(0xb7dc0791), SWAP16(0xa9b7), SWAP16(0x11cf),
+ 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65);
+
+DEFINE_GUID(ASF_VideoStream, SWAP32(0xbc19efc0), SWAP16(0x5b4d), SWAP16(0x11cf),
+ 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b);
+
+DEFINE_GUID(ASF_AudioStream, SWAP32(0xf8699e40), SWAP16(0x5b4d), SWAP16(0x11cf),
+ 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b);
+
+DEFINE_GUID(ASF_HeaderObject, SWAP32(0x75b22630), SWAP16(0x668e), SWAP16(0x11cf),
+ 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c);
+
+DEFINE_GUID(ASF_FileProperties, SWAP32(0x8cabdca1), SWAP16(0xa947), SWAP16(0x11cf),
+ 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65);
+
+DEFINE_GUID(ASF_ContentDescription, SWAP32(0x75b22633), SWAP16(0x668e), SWAP16(0x11cf),
+ 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c);
+
+DEFINE_GUID(ASF_ExtendedContentDescription, SWAP32(0xd2d0a440), SWAP16(0xe307), SWAP16(0x11d2),
+ 0x97, 0xf0, 0x00, 0xa0, 0xc9, 0x5e, 0xa8, 0x50);
+
+DEFINE_GUID(ASF_ClientGuid, SWAP32(0x8d262e32), SWAP16(0xfc28), SWAP16(0x11d7),
+ 0xa9, 0xea, 0x00, 0x04, 0x5a, 0x6b, 0x76, 0xc2);
+
+DEFINE_GUID(ASF_HeaderExtension, SWAP32(0x5fbf03b5), SWAP16(0xa92e), SWAP16(0x11cf),
+ 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65);
+
+DEFINE_GUID(ASF_CodecList, SWAP32(0x86d15240), SWAP16(0x311d), SWAP16(0x11d0),
+ 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6);
+
+DEFINE_GUID(ASF_DataObject, SWAP32(0x75b22636), SWAP16(0x668e), SWAP16(0x11cf),
+ 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c);
+
+DEFINE_GUID(ASF_PaddingObject, SWAP32(0x1806d474), SWAP16(0xcadf), SWAP16(0x4509),
+ 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8);
+
+DEFINE_GUID(ASF_SimpleIndexObject, SWAP32(0x33000890), SWAP16(0xe5b1), SWAP16(0x11cf),
+ 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb);
+
+DEFINE_GUID(ASF_NoErrorCorrection, SWAP32(0x20fb5700), SWAP16(0x5b55), SWAP16(0x11cf),
+ 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b);
+
+DEFINE_GUID(ASF_AudioSpread, SWAP32(0xbfc3cd50), SWAP16(0x618f), SWAP16(0x11cf),
+ 0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20);
+
+DEFINE_GUID(ASF_Reserved1, SWAP32(0xabd3d211), SWAP16(0xa9ba), SWAP16(0x11cf),
+ 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65);
+
+DEFINE_GUID(ASF_Reserved2, SWAP32(0x86d15241), SWAP16(0x311d), SWAP16(0x11d0),
+ 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6);
+
+DEFINE_GUID(ASF_ContentEncryptionObject, SWAP32(0x2211B3FB), SWAP16(0xBD23), SWAP16(0x11D2),
+ 0xB4, 0xB7, 0x00, 0xA0, 0xC9, 0x55, 0xFC, 0x6E);
+
+DEFINE_GUID(ASF_ExtendedContentEncryptionObject, SWAP32(0x298AE614), SWAP16(0x2622), SWAP16(0x4C17),
+ 0xB9, 0x35, 0xDA, 0xE0, 0x7E, 0xE9, 0x28, 0x9C);
+
+DEFINE_GUID(ASF_ExtendedStreamPropertiesObject, SWAP32(0x14E6A5CB), SWAP16(0xC672), SWAP16(0x4332),
+ 0x83, 0x99, 0xA9, 0x69, 0x52, 0x06, 0x5B, 0x5A);
+
+DEFINE_GUID(ASF_MediaTypeAudio, SWAP32(0x31178C9D), SWAP16(0x03E1), SWAP16(0x4528),
+ 0xB5, 0x82, 0x3D, 0xF9, 0xDB, 0x22, 0xF5, 0x03);
+
+DEFINE_GUID(ASF_FormatTypeWave, SWAP32(0xC4C4C4D1), SWAP16(0x0049), SWAP16(0x4E2B),
+ 0x98, 0xFB, 0x95, 0x37, 0xF6, 0xCE, 0x51, 0x6D);
+
+DEFINE_GUID(ASF_StreamBufferStream, SWAP32(0x3AFB65E2), SWAP16(0x47EF), SWAP16(0x40F2),
+ 0xAC, 0x2C, 0x70, 0xA9, 0x0D, 0x71, 0xD3, 0x43);
+
+typedef struct _BITMAPINFOHEADER {
+ __u32 biSize;
+ __s32 biWidth;
+ __s32 biHeight;
+ __u16 biPlanes;
+ __u16 biBitCount;
+ __u32 biCompression;
+ __u32 biSizeImage;
+ __s32 biXPelsPerMeter;
+ __s32 biYPelsPerMeter;
+ __u32 biClrUsed;
+ __u32 biClrImportant;
+} __PACKED__ BITMAPINFOHEADER;
+
+typedef struct _WAVEFORMATEX {
+ __u16 wFormatTag;
+ __u16 nChannels;
+ __u32 nSamplesPerSec;
+ __u32 nAvgBytesPerSec;
+ __u16 nBlockAlign;
+ __u16 wBitsPerSample;
+ __u16 cbSize;
+} __PACKED__ WAVEFORMATEX;
+
+typedef struct _asf_stream_object_t {
+ GUID ID;
+ __u64 Size;
+ GUID StreamType;
+ GUID ErrorCorrectionType;
+ __u64 TimeOffset;
+ __u32 TypeSpecificSize;
+ __u32 ErrorCorrectionSize;
+ __u16 StreamNumber;
+ __u32 Reserved;
+} __PACKED__ asf_stream_object_t;
+
+typedef struct _asf_media_stream_t {
+ asf_stream_object_t Hdr;
+ GUID MajorType;
+ GUID SubType;
+ __u32 FixedSizeSamples;
+ __u32 TemporalCompression;
+ __u32 SampleSize;
+ GUID FormatType;
+ __u32 FormatSize;
+} __PACKED__ asf_media_stream_t;
+
+typedef struct _avi_audio_format_t {
+ __u16 wFormatTag;
+ __u16 nChannels;
+ __u32 nSamplesPerSec;
+ __u32 nAvgBytesPerSec;
+ __u16 nBlockAlign;
+ __u16 wBitsPerSample;
+ __u16 cbSize;
+} __PACKED__ avi_audio_format_t;
+
+typedef struct _asf_extended_stream_object_t {
+ GUID ID;
+ __u64 Size;
+ __u64 StartTime;
+ __u64 EndTime;
+ __u32 DataBitrate;
+ __u32 BufferSize;
+ __u32 InitialBufferFullness;
+ __u32 AltDataBitrate;
+ __u32 AltBufferSize;
+ __u32 AltInitialBufferFullness;
+ __u32 MaximumObjectSize;
+ __u32 Flags;
+ __u16 StreamNumber;
+ __u16 LanguageIDIndex;
+ __u64 AvgTimePerFrame;
+ __u16 StreamNameCount;
+ __u16 PayloadExtensionSystemCount;
+} __PACKED__ asf_extended_stream_object_t;
+
+typedef struct _asf_stream_name_t {
+ __u16 ID;
+ __u16 Length;
+} __PACKED__ asf_stream_name_t;
+
+typedef struct _asf_payload_extension_t {
+ GUID ID;
+ __u16 Size;
+ __u32 InfoLength;
+} __PACKED__ asf_payload_extension_t;
+
+
+
+typedef struct _asf_object_t {
+ GUID ID;
+ __u64 Size;
+} __PACKED__ asf_object_t;
+
+typedef struct _asf_codec_entry_t {
+ __u16 Type;
+ __u16 NameLen;
+ __u32 Name;
+ __u16 DescLen;
+ __u32 Desc;
+ __u16 InfoLen;
+ __u32 Info;
+} __PACKED__ asf_codec_entry_t;
+
+typedef struct _asf_codec_list_t {
+ GUID ID;
+ __u64 Size;
+ GUID Reserved;
+ __u64 NumEntries;
+ asf_codec_entry_t Entries[2];
+ asf_codec_entry_t VideoCodec;
+} __PACKED__ asf_codec_list_t;
+
+typedef struct _asf_content_description_t {
+ GUID ID;
+ __u64 Size;
+ __u16 TitleLength;
+ __u16 AuthorLength;
+ __u16 CopyrightLength;
+ __u16 DescriptionLength;
+ __u16 RatingLength;
+ __u32 Title;
+ __u32 Author;
+ __u32 Copyright;
+ __u32 Description;
+ __u32 Rating;
+} __PACKED__ asf_content_description_t;
+
+typedef struct _asf_file_properties_t {
+ GUID ID;
+ __u64 Size;
+ GUID FileID;
+ __u64 FileSize;
+ __u64 CreationTime;
+ __u64 TotalPackets;
+ __u64 PlayDuration;
+ __u64 SendDuration;
+ __u64 Preroll;
+ __u32 Flags;
+ __u32 MinPacketSize;
+ __u32 MaxPacketSize;
+ __u32 MaxBitrate;
+} __PACKED__ asf_file_properties_t;
+
+typedef struct _asf_header_extension_t {
+ GUID ID;
+ __u64 Size;
+ GUID Reserved1;
+ __u16 Reserved2;
+ __u32 DataSize;
+} __PACKED__ asf_header_extension_t;
+
+typedef struct _asf_video_stream_t {
+ asf_stream_object_t Hdr;
+ __u32 Width;
+ __u32 Height;
+ __u8 ReservedFlags;
+ __u16 FormatSize;
+ BITMAPINFOHEADER bmi;
+ __u8 ebih[1];
+} __PACKED__ asf_video_stream_t;
+
+typedef struct _asf_audio_stream_t {
+ asf_stream_object_t Hdr;
+ WAVEFORMATEX wfx;
+} __PACKED__ asf_audio_stream_t;
+
+typedef struct _asf_payload_t {
+ __u8 StreamNumber;
+ __u8 MediaObjectNumber;
+ __u32 MediaObjectOffset;
+ __u8 ReplicatedDataLength;
+ __u32 ReplicatedData[2];
+ __u32 PayloadLength;
+} __PACKED__ asf_payload_t;
+
+typedef struct _asf_packet_t {
+ __u8 TypeFlags;
+ __u8 ECFlags;
+ __u8 ECType;
+ __u8 ECCycle;
+ __u8 PropertyFlags;
+ __u32 PacketLength;
+ __u32 Sequence;
+ __u32 PaddingLength;
+ __u32 SendTime;
+ __u16 Duration;
+ __u8 PayloadFlags;
+ asf_payload_t Payload;
+} __PACKED__ asf_packet_t;
+
+typedef struct _asf_data_object_t {
+ GUID ID;
+ __u64 Size;
+ GUID FileID;
+ __u64 TotalPackets;
+ unsigned short Reserved;
+} __PACKED__ asf_data_object_t;
+
+typedef struct _asf_padding_object_t {
+ GUID ID;
+ __u64 Size;
+} __PACKED__ asf_padding_object_t;
+
+typedef struct _asf_simple_index_object_t {
+ GUID ID;
+ __u64 Size;
+ GUID FileID;
+ __u32 IndexEntryTimeInterval;
+ __u32 MaximumPacketCount;
+ __u32 IndexEntriesCount;
+} __PACKED__ asf_simple_index_object_t;
+
+typedef struct _asf_header_object_t {
+ GUID ID;
+ __u64 Size;
+ __u32 NumObjects;
+ __u16 Reserved;
+ asf_header_extension_t HeaderExtension;
+ asf_content_description_t ContentDescription;
+ asf_file_properties_t FileProperties;
+ asf_video_stream_t * VideoStream;
+ asf_audio_stream_t * AudioStream;
+ asf_codec_list_t CodecList;
+ asf_padding_object_t PaddingObject;
+} __PACKED__ asf_header_object_t;
+
+
+#define ASF_VT_UNICODE (0)
+#define ASF_VT_BYTEARRAY (1)
+#define ASF_VT_BOOL (2)
+#define ASF_VT_DWORD (3)
+#define ASF_VT_QWORD (4)
+#define ASF_VT_WORD (5)
+
+static int _get_asffileinfo(char *file, struct song_metadata *psong);
diff --git a/tagutils/tagutils-flc.c b/tagutils/tagutils-flc.c
new file mode 100644
index 0000000..d6b2dff
--- /dev/null
+++ b/tagutils/tagutils-flc.c
@@ -0,0 +1,90 @@
+//=========================================================================
+// FILENAME : tagutils-flc.c
+// DESCRIPTION : FLAC metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+static int
+_get_flctags(char *filename, struct song_metadata *psong)
+{
+ FLAC__Metadata_SimpleIterator *iterator = 0;
+ FLAC__StreamMetadata *block;
+ int block_number;
+ int i;
+ int err = 0;
+
+ if(!(iterator = FLAC__metadata_simple_iterator_new()))
+ {
+ DPRINTF(E_FATAL, L_SCANNER, "Out of memory while FLAC__metadata_simple_iterator_new()\n");
+ return -1;
+ }
+
+ block_number = 0;
+ if(!FLAC__metadata_simple_iterator_init(iterator, filename, true, true))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Cannot extract tag from %s\n", filename);
+ return -1;
+ }
+
+ do {
+ if(!(block = FLAC__metadata_simple_iterator_get_block(iterator)))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Cannot extract tag from %s\n", filename);
+ err = -1;
+ goto _exit;
+ }
+
+ switch(block->type)
+ {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ psong->samplerate = block->data.stream_info.sample_rate;
+ psong->channels = block->data.stream_info.channels;
+ break;
+
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ for(i = 0; i < block->data.vorbis_comment.num_comments; i++)
+ {
+ vc_scan(psong,
+ (char*)block->data.vorbis_comment.comments[i].entry,
+ block->data.vorbis_comment.comments[i].length);
+ }
+ break;
+ default:
+ break;
+ }
+ FLAC__metadata_object_delete(block);
+ }
+ while(FLAC__metadata_simple_iterator_next(iterator));
+
+ _exit:
+ if(iterator)
+ FLAC__metadata_simple_iterator_delete(iterator);
+
+ return err;
+}
+
+static int
+_get_flcfileinfo(char *filename, struct song_metadata *psong)
+{
+ psong->lossless = 1;
+ psong->vbr_scale = 1;
+
+ return 0;
+}
diff --git a/tagutils/tagutils-flc.h b/tagutils/tagutils-flc.h
new file mode 100644
index 0000000..843442c
--- /dev/null
+++ b/tagutils/tagutils-flc.h
@@ -0,0 +1,24 @@
+//=========================================================================
+// FILENAME : tagutils-flc.h
+// DESCRIPTION : FLAC metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+static int _get_flcfileinfo(char *file, struct song_metadata *psong);
+static int _get_flctags(char *file, struct song_metadata *psong);
diff --git a/tagutils/tagutils-misc.c b/tagutils/tagutils-misc.c
new file mode 100644
index 0000000..eb91ff6
--- /dev/null
+++ b/tagutils/tagutils-misc.c
@@ -0,0 +1,262 @@
+//=========================================================================
+// FILENAME : tagutils-misc.c
+// DESCRIPTION : Misc routines for supporting tagutils
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/**************************************************************************
+* Language
+**************************************************************************/
+
+#define MAX_ICONV_BUF 1024
+
+typedef enum {
+ ICONV_OK,
+ ICONV_TRYNEXT,
+ ICONV_FATAL
+} iconv_result;
+
+static iconv_result
+do_iconv(const char* to_ces, const char* from_ces,
+ char *inbuf, size_t inbytesleft,
+ char *outbuf_orig, size_t outbytesleft_orig)
+{
+ size_t rc;
+ iconv_result ret = ICONV_OK;
+
+ size_t outbytesleft = outbytesleft_orig - 1;
+ char* outbuf = outbuf_orig;
+
+ iconv_t cd = iconv_open(to_ces, from_ces);
+
+ if(cd == (iconv_t)-1)
+ {
+ return ICONV_FATAL;
+ }
+ rc = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
+ if(rc == (size_t)-1)
+ {
+ if(errno == E2BIG)
+ {
+ ret = ICONV_FATAL;
+ }
+ else
+ {
+ ret = ICONV_TRYNEXT;
+ memset(outbuf_orig, '\0', outbytesleft_orig);
+ }
+ }
+ iconv_close(cd);
+
+ return ret;
+}
+
+#define N_LANG_ALT 8
+struct {
+ char *lang;
+ char *cpnames[N_LANG_ALT];
+} iconv_map[] = {
+ { "JA", { "ISO-8859-1", "CP932", "ISO8859-1", "CP950", "CP936", 0 } },
+ { "ZH_CN", { "ISO-8859-1", "CP936", "CP950", "CP932", 0 } },
+ { "ZH_TW", { "ISO-8859-1", "CP950", "CP936", "CP932", 0 } },
+ { 0, { 0 } }
+};
+static int lang_index = -1;
+
+static int
+_lang2cp(char *lang)
+{
+ int cp;
+
+ if(!lang || lang[0] == '\0')
+ return -1;
+ for(cp = 0; iconv_map[cp].lang; cp++)
+ {
+ if(!strcasecmp(iconv_map[cp].lang, lang))
+ return cp;
+ }
+ return -1;
+}
+
+static unsigned char*
+_get_utf8_text(const id3_ucs4_t* native_text)
+{
+ unsigned char *utf8_text = NULL;
+ char *in, *in8, *iconv_buf;
+ iconv_result rc;
+ int i, n;
+
+ in = (char*)id3_ucs4_latin1duplicate(native_text);
+ if(!in)
+ {
+ goto out;
+ }
+
+ in8 = (char*)id3_ucs4_utf8duplicate(native_text);
+ if(!in8)
+ {
+ free(in);
+ goto out;
+ }
+
+ iconv_buf = (char*)calloc(MAX_ICONV_BUF, sizeof(char));
+ if(!iconv_buf)
+ {
+ free(in); free(in8);
+ goto out;
+ }
+
+ i = lang_index;
+ // (1) try utf8 -> default
+ rc = do_iconv(iconv_map[i].cpnames[0], "UTF-8", in8, strlen(in8), iconv_buf, MAX_ICONV_BUF);
+ if(rc == ICONV_OK)
+ {
+ utf8_text = (unsigned char*)in8;
+ free(iconv_buf);
+ }
+ else if(rc == ICONV_TRYNEXT)
+ {
+ // (2) try default -> utf8
+ rc = do_iconv("UTF-8", iconv_map[i].cpnames[0], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
+ if(rc == ICONV_OK)
+ {
+ utf8_text = (unsigned char*)iconv_buf;
+ }
+ else if(rc == ICONV_TRYNEXT)
+ {
+ // (3) try other encodes
+ for(n = 1; n < N_LANG_ALT && iconv_map[i].cpnames[n]; n++)
+ {
+ rc = do_iconv("UTF-8", iconv_map[i].cpnames[n], in, strlen(in), iconv_buf, MAX_ICONV_BUF);
+ if(rc == ICONV_OK)
+ {
+ utf8_text = (unsigned char*)iconv_buf;
+ break;
+ }
+ }
+ if(!utf8_text)
+ {
+ // cannot iconv
+ utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
+ free(iconv_buf);
+ }
+ }
+ free(in8);
+ }
+ free(in);
+
+ out:
+ if(!utf8_text)
+ {
+ utf8_text = (unsigned char*)strdup("UNKNOWN");
+ }
+
+ return utf8_text;
+}
+
+
+static void
+vc_scan(struct song_metadata *psong, const char *comment, const size_t length)
+{
+ char strbuf[1024];
+
+ if(length > (sizeof(strbuf) - 1))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Vorbis Comment too long\n");
+ return;
+ }
+ strncpy(strbuf, comment, length);
+ strbuf[length] = '\0';
+
+ // ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO,
+ // LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING,
+ // -- foollowing tags are muliples
+ // COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART
+ // PARTNUMBER, GENRE, DATE, LOCATION, COMMENT
+ if(!strncasecmp(strbuf, "ALBUM=", 6))
+ {
+ psong->album = strdup(strbuf + 6);
+ }
+ else if(!strncasecmp(strbuf, "ARTIST=", 7))
+ {
+ psong->contributor[ROLE_ARTIST] = strdup(strbuf + 7);
+ }
+ else if(!strncasecmp(strbuf, "ARTISTSORT=", 11))
+ {
+ psong->contributor_sort[ROLE_ARTIST] = strdup(strbuf + 11);
+ }
+ else if(!strncasecmp(strbuf, "TITLE=", 6))
+ {
+ psong->title = strdup(strbuf + 6);
+ }
+ else if(!strncasecmp(strbuf, "TRACKNUMBER=", 12))
+ {
+ psong->track = atoi(strbuf + 12);
+ }
+ else if(!strncasecmp(strbuf, "DISCNUMBER=", 11))
+ {
+ psong->disc = atoi(strbuf + 11);
+ }
+ else if(!strncasecmp(strbuf, "GENRE=", 6))
+ {
+ psong->genre = strdup(strbuf + 6);
+ }
+ else if(!strncasecmp(strbuf, "DATE=", 5))
+ {
+ if(length >= (5 + 10) &&
+ isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) &&
+ isdigit(strbuf[5 + 3]) && isdigit(strbuf[5 + 4]) && ispunct(strbuf[5 + 5]) &&
+ isdigit(strbuf[5 + 6]) && isdigit(strbuf[5 + 7]) && isdigit(strbuf[5 + 8]) && isdigit(strbuf[5 + 9]))
+ {
+ // nn-nn-yyyy
+ strbuf[5 + 10] = '\0';
+ psong->year = atoi(strbuf + 5 + 6);
+ }
+ else
+ {
+ // year first. year is at most 4 digit.
+ strbuf[5 + 4] = '\0';
+ psong->year = atoi(strbuf + 5);
+ }
+ }
+ else if(!strncasecmp(strbuf, "COMMENT=", 8))
+ {
+ psong->comment = strdup(strbuf + 8);
+ }
+ else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMID=", 20))
+ {
+ psong->musicbrainz_albumid = strdup(strbuf + 20);
+ }
+ else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
+ {
+ psong->musicbrainz_trackid = strdup(strbuf + 20);
+ }
+ else if(!strncasecmp(strbuf, "MUSICBRAINZ_TRACKID=", 20))
+ {
+ psong->musicbrainz_trackid = strdup(strbuf + 20);
+ }
+ else if(!strncasecmp(strbuf, "MUSICBRAINZ_ARTISTID=", 21))
+ {
+ psong->musicbrainz_artistid = strdup(strbuf + 21);
+ }
+ else if(!strncasecmp(strbuf, "MUSICBRAINZ_ALBUMARTISTID=", 26))
+ {
+ psong->musicbrainz_albumartistid = strdup(strbuf + 26);
+ }
+}
diff --git a/tagutils/tagutils-mp3.c b/tagutils/tagutils-mp3.c
new file mode 100644
index 0000000..f9c7f04
--- /dev/null
+++ b/tagutils/tagutils-mp3.c
@@ -0,0 +1,776 @@
+//=========================================================================
+// FILENAME : tagutils-mp3.c
+// DESCRIPTION : MP3 metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/*
+ * This file is derived from mt-daap project.
+ */
+
+// _get_mp3tags
+static int
+_get_mp3tags(char *file, struct song_metadata *psong)
+{
+ struct id3_file *pid3file;
+ struct id3_tag *pid3tag;
+ struct id3_frame *pid3frame;
+ int err;
+ int index;
+ int used;
+ unsigned char *utf8_text;
+ int genre = WINAMP_GENRE_UNKNOWN;
+ int have_utf8;
+ int have_text;
+ id3_ucs4_t const *native_text;
+ char *tmp;
+ int got_numeric_genre;
+ id3_byte_t const *image;
+ id3_length_t image_size = 0;
+
+ pid3file = id3_file_open(file, ID3_FILE_MODE_READONLY);
+ if(!pid3file)
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Cannot open %s\n", file);
+ return -1;
+ }
+
+ pid3tag = id3_file_tag(pid3file);
+
+ if(!pid3tag)
+ {
+ err = errno;
+ id3_file_close(pid3file);
+ errno = err;
+ DPRINTF(E_WARN, L_SCANNER, "Cannot get ID3 tag for %s\n", file);
+ return -1;
+ }
+
+ index = 0;
+ while((pid3frame = id3_tag_findframe(pid3tag, "", index)))
+ {
+ used = 0;
+ utf8_text = NULL;
+ native_text = NULL;
+ have_utf8 = 0;
+ have_text = 0;
+
+ if(!strcmp(pid3frame->id, "YTCP")) /* for id3v2.2 */
+ {
+ psong->compilation = 1;
+ DPRINTF(E_DEBUG, L_SCANNER, "Compilation: %d\n", psong->compilation);
+ }
+ else if(!strcmp(pid3frame->id, "APIC") && !image_size)
+ {
+ if( (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "image/jpeg") == 0) ||
+ (strcmp((char*)id3_field_getlatin1(&pid3frame->fields[1]), "jpeg") == 0) )
+ {
+ image = id3_field_getbinarydata(&pid3frame->fields[4], &image_size);
+ if( image_size )
+ {
+ psong->image = malloc(image_size);
+ memcpy(psong->image, image, image_size);
+ psong->image_size = image_size;
+ //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Found thumbnail: %d\n", psong->image_size);
+ }
+ }
+ }
+
+ if(((pid3frame->id[0] == 'T') || (strcmp(pid3frame->id, "COMM") == 0)) &&
+ (id3_field_getnstrings(&pid3frame->fields[1])))
+ have_text = 1;
+
+ if(have_text)
+ {
+ native_text = id3_field_getstrings(&pid3frame->fields[1], 0);
+
+ if(native_text)
+ {
+ have_utf8 = 1;
+ if(lang_index >= 0)
+ utf8_text = _get_utf8_text(native_text); // through iconv
+ else
+ utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
+
+ if(!strcmp(pid3frame->id, "TIT2"))
+ {
+ used = 1;
+ psong->title = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TPE1"))
+ {
+ used = 1;
+ psong->contributor[ROLE_ARTIST] = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TALB"))
+ {
+ used = 1;
+ psong->album = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TCOM"))
+ {
+ used = 1;
+ psong->contributor[ROLE_COMPOSER] = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TIT1"))
+ {
+ used = 1;
+ psong->grouping = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TPE2"))
+ {
+ used = 1;
+ psong->contributor[ROLE_BAND] = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TPE3"))
+ {
+ used = 1;
+ psong->contributor[ROLE_CONDUCTOR] = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TCON"))
+ {
+ used = 1;
+ psong->genre = (char*)utf8_text;
+ got_numeric_genre = 0;
+ if(psong->genre)
+ {
+ if(!strlen(psong->genre))
+ {
+ genre = WINAMP_GENRE_UNKNOWN;
+ got_numeric_genre = 1;
+ }
+ else if(isdigit(psong->genre[0]))
+ {
+ genre = atoi(psong->genre);
+ got_numeric_genre = 1;
+ }
+ else if((psong->genre[0] == '(') && (isdigit(psong->genre[1])))
+ {
+ genre = atoi((char*)&psong->genre[1]);
+ got_numeric_genre = 1;
+ }
+
+ if(got_numeric_genre)
+ {
+ if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
+ genre = WINAMP_GENRE_UNKNOWN;
+ free(psong->genre);
+ psong->genre = strdup(winamp_genre[genre]);
+ }
+ }
+ }
+ else if(!strcmp(pid3frame->id, "COMM"))
+ {
+ used = 1;
+ psong->comment = (char*)utf8_text;
+ }
+ else if(!strcmp(pid3frame->id, "TPOS"))
+ {
+ tmp = (char*)utf8_text;
+ strsep(&tmp, "/");
+ if(tmp)
+ {
+ psong->total_discs = atoi(tmp);
+ }
+ psong->disc = atoi((char*)utf8_text);
+ }
+ else if(!strcmp(pid3frame->id, "TRCK"))
+ {
+ tmp = (char*)utf8_text;
+ strsep(&tmp, "/");
+ if(tmp)
+ {
+ psong->total_tracks = atoi(tmp);
+ }
+ psong->track = atoi((char*)utf8_text);
+ }
+ else if(!strcmp(pid3frame->id, "TDRC"))
+ {
+ psong->year = atoi((char*)utf8_text);
+ }
+ else if(!strcmp(pid3frame->id, "TLEN"))
+ {
+ psong->song_length = atoi((char*)utf8_text);
+ }
+ else if(!strcmp(pid3frame->id, "TBPM"))
+ {
+ psong->bpm = atoi((char*)utf8_text);
+ }
+ else if(!strcmp(pid3frame->id, "TCMP"))
+ {
+ psong->compilation = (char)atoi((char*)utf8_text);
+ }
+ }
+ }
+
+ // check if text tag
+ if((!used) && (have_utf8) && (utf8_text))
+ free(utf8_text);
+
+ // v2 COMM
+ if((!strcmp(pid3frame->id, "COMM")) && (pid3frame->nfields == 4))
+ {
+ native_text = id3_field_getstring(&pid3frame->fields[2]);
+ if(native_text)
+ {
+ utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
+ if((utf8_text) && (strncasecmp((char*)utf8_text, "iTun", 4) != 0))
+ {
+ // read comment
+ if(utf8_text)
+ free(utf8_text);
+
+ native_text = id3_field_getfullstring(&pid3frame->fields[3]);
+ if(native_text)
+ {
+ //if (psong->comment)
+ // free(psong->comment);
+ utf8_text = (unsigned char*)id3_ucs4_utf8duplicate(native_text);
+ if(utf8_text)
+ {
+ psong->comment = (char*)utf8_text;
+ }
+ }
+ }
+ else
+ {
+ if(utf8_text)
+ free(utf8_text);
+ }
+ }
+ }
+
+ index++;
+ }
+
+ id3_file_close(pid3file);
+ //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got id3 tag successfully for file=%s\n", file);
+ return 0;
+}
+
+// _decode_mp3_frame
+static int
+_decode_mp3_frame(unsigned char *frame, struct mp3_frameinfo *pfi)
+{
+ int ver;
+ int layer_index;
+ int sample_index;
+ int bitrate_index;
+ int samplerate_index;
+
+ if((frame[0] != 0xFF) || (frame[1] < 224))
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+ ver = (frame[1] & 0x18) >> 3;
+ pfi->layer = 4 - ((frame[1] & 0x6) >> 1);
+
+ layer_index = sample_index = -1;
+
+ switch(ver)
+ {
+ case 0:
+ pfi->mpeg_version = 0x25; // 2.5
+ sample_index = 2;
+ if(pfi->layer == 1)
+ layer_index = 3;
+ if((pfi->layer == 2) || (pfi->layer == 3))
+ layer_index = 4;
+ break;
+ case 2:
+ pfi->mpeg_version = 0x20; // 2.0
+ sample_index = 1;
+ if(pfi->layer == 1)
+ layer_index = 3;
+ if((pfi->layer == 2) || (pfi->layer == 3))
+ layer_index = 4;
+ break;
+ case 3:
+ pfi->mpeg_version = 0x10; // 1.0
+ sample_index = 0;
+ if(pfi->layer == 1)
+ layer_index = 0;
+ if(pfi->layer == 2)
+ layer_index = 1;
+ if(pfi->layer == 3)
+ layer_index = 2;
+ break;
+ }
+
+ if((layer_index < 0) || (layer_index > 4))
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+ if((sample_index < 0) || (sample_index >= 2))
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+ if(pfi->layer == 1) pfi->samples_per_frame = 384;
+ if(pfi->layer == 2) pfi->samples_per_frame = 1152;
+ if(pfi->layer == 3)
+ {
+ if(pfi->mpeg_version == 0x10)
+ pfi->samples_per_frame = 1152;
+ else
+ pfi->samples_per_frame = 576;
+ }
+
+ bitrate_index = (frame[2] & 0xF0) >> 4;
+ samplerate_index = (frame[2] & 0x0C) >> 2;
+
+ if((bitrate_index == 0xF) || (bitrate_index == 0x0))
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+ if(samplerate_index == 3)
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+
+ pfi->bitrate = bitrate_tbl[layer_index][bitrate_index];
+ pfi->samplerate = sample_rate_tbl[sample_index][samplerate_index];
+
+ if((frame[3] & 0xC0 >> 6) == 3)
+ pfi->stereo = 0;
+ else
+ pfi->stereo = 1;
+
+ if(frame[2] & 0x02)
+ pfi->padding = 1;
+ else
+ pfi->padding = 0;
+
+ if(pfi->mpeg_version == 0x10)
+ {
+ if(pfi->stereo)
+ pfi->xing_offset = 32;
+ else
+ pfi->xing_offset = 17;
+ }
+ else
+ {
+ if(pfi->stereo)
+ pfi->xing_offset = 17;
+ else
+ pfi->xing_offset = 9;
+ }
+
+ pfi->crc_protected = frame[1] & 0xFE;
+
+ if(pfi->layer == 1)
+ pfi->frame_length = (12 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding) * 4;
+ else
+ pfi->frame_length = 144 * pfi->bitrate * 1000 / pfi->samplerate + pfi->padding;
+
+ if((pfi->frame_length > 2880) || (pfi->frame_length <= 0))
+ {
+ pfi->is_valid = 0;
+ return -1;
+ }
+
+ pfi->is_valid = 1;
+ return 0;
+}
+
+// _mp3_get_average_bitrate
+// read from midle of file, and estimate
+static void _mp3_get_average_bitrate(FILE *infile, struct mp3_frameinfo *pfi, const char *fname)
+{
+ off_t file_size;
+ unsigned char frame_buffer[2900];
+ unsigned char header[4];
+ int index = 0;
+ int found = 0;
+ off_t pos;
+ struct mp3_frameinfo fi;
+ int frame_count = 0;
+ int bitrate_total = 0;
+
+ fseek(infile, 0, SEEK_END);
+ file_size = ftell(infile);
+
+ pos = file_size >> 1;
+
+ /* now, find the first frame */
+ fseek(infile, pos, SEEK_SET);
+ if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) != sizeof(frame_buffer))
+ return;
+
+ while(!found)
+ {
+ while((frame_buffer[index] != 0xFF) && (index < (sizeof(frame_buffer) - 4)))
+ index++;
+
+ if(index >= (sizeof(frame_buffer) - 4)) // max mp3 framesize = 2880
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Could not find frame for %s\n", basename((char *)fname));
+ return;
+ }
+
+ if(!_decode_mp3_frame(&frame_buffer[index], &fi))
+ {
+ /* see if next frame is valid */
+ fseek(infile, pos + index + fi.frame_length, SEEK_SET);
+ if(fread(header, 1, sizeof(header), infile) != sizeof(header))
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname));
+ return;
+ }
+
+ if(!_decode_mp3_frame(header, &fi))
+ found = 1;
+ }
+
+ if(!found)
+ index++;
+ }
+
+ pos += index;
+
+ // got first frame
+ while(frame_count < 10)
+ {
+ fseek(infile, pos, SEEK_SET);
+ if(fread(header, 1, sizeof(header), infile) != sizeof(header))
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Could not read frame header for %s\n", basename((char *)fname));
+ return;
+ }
+ if(_decode_mp3_frame(header, &fi))
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Invalid frame header while averaging %s\n", basename((char *)fname));
+ return;
+ }
+
+ bitrate_total += fi.bitrate;
+ frame_count++;
+ pos += fi.frame_length;
+ }
+
+ pfi->bitrate = bitrate_total / frame_count;
+
+ return;
+}
+
+// _mp3_get_frame_count
+// do brute scan
+static void __attribute__((unused))
+_mp3_get_frame_count(FILE *infile, struct mp3_frameinfo *pfi)
+{
+ int pos;
+ int frames = 0;
+ unsigned char frame_buffer[4];
+ struct mp3_frameinfo fi;
+ off_t file_size;
+ int err = 0;
+ int cbr = 1;
+ int last_bitrate = 0;
+
+ fseek(infile, 0, SEEK_END);
+ file_size = ftell(infile);
+
+ pos = pfi->frame_offset;
+
+ while(1)
+ {
+ err = 1;
+
+ fseek(infile, pos, SEEK_SET);
+ if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer))
+ {
+ // valid frame?
+ if(!_decode_mp3_frame(frame_buffer, &fi))
+ {
+ frames++;
+ pos += fi.frame_length;
+ err = 0;
+
+ if((last_bitrate) && (fi.bitrate != last_bitrate))
+ cbr = 0;
+ last_bitrate = fi.bitrate;
+
+ // no sense to scan cbr
+ if(cbr && (frames > 100))
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "File appears to be CBR... quitting frame _mp3_get_frame_count()\n");
+ return;
+ }
+ }
+ }
+
+ if(err)
+ {
+ if(pos > (file_size - 4096))
+ {
+ pfi->number_of_frames = frames;
+ return;
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Frame count aborted on error. Pos=%d, Count=%d\n",
+ pos, frames);
+ return;
+ }
+ }
+ }
+}
+
+// _get_mp3fileinfo
+static int
+_get_mp3fileinfo(char *file, struct song_metadata *psong)
+{
+ FILE *infile;
+ struct id3header *pid3;
+ struct mp3_frameinfo fi;
+ unsigned int size = 0;
+ unsigned int n_read;
+ off_t fp_size = 0;
+ off_t file_size;
+ unsigned char buffer[1024];
+ int index;
+
+ int xing_flags;
+ int found;
+
+ int first_check = 0;
+ char frame_buffer[4];
+
+ char id3v1taghdr[4];
+
+ if(!(infile = fopen(file, "rb")))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
+ return -1;
+ }
+
+ memset((void*)&fi, 0, sizeof(fi));
+
+ fseek(infile, 0, SEEK_END);
+ file_size = ftell(infile);
+ fseek(infile, 0, SEEK_SET);
+
+ if(fread(buffer, 1, sizeof(buffer), infile) != sizeof(buffer))
+ {
+ if(ferror(infile))
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Error reading: %s\n", strerror(errno));
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "File too small. Probably corrupted.\n");
+ }
+ fclose(infile);
+ return -1;
+ }
+
+ pid3 = (struct id3header*)buffer;
+
+ found = 0;
+ fp_size = 0;
+
+ if(strncmp((char*)pid3->id, "ID3", 3) == 0)
+ {
+ char tagversion[16];
+
+ /* found an ID3 header... */
+ size = (pid3->size[0] << 21 | pid3->size[1] << 14 |
+ pid3->size[2] << 7 | pid3->size[3]);
+ fp_size = size + sizeof(struct id3header);
+ first_check = 1;
+
+ snprintf(tagversion, sizeof(tagversion), "ID3v2.%d.%d",
+ pid3->version[0], pid3->version[1]);
+ psong->tagversion = strdup(tagversion);
+ }
+
+ index = 0;
+
+ /* Here we start the brute-force header seeking. Sure wish there
+ * weren't so many crappy mp3 files out there
+ */
+
+ while(!found)
+ {
+ fseek(infile, fp_size, SEEK_SET);
+ if((n_read = fread(buffer, 1, sizeof(buffer), infile)) < 4) // at least mp3 frame header size (i.e. 4 bytes)
+ {
+ fclose(infile);
+ return 0;
+ }
+
+ index = 0;
+ while(!found)
+ {
+ while((buffer[index] != 0xFF) && (index < (n_read - 50)))
+ index++;
+
+ if((first_check) && (index))
+ {
+ fp_size = 0;
+ first_check = 0;
+ if(n_read < sizeof(buffer))
+ {
+ fclose(infile);
+ return 0;
+ }
+ break;
+ }
+
+ if(index > (n_read - 50))
+ {
+ fp_size += index;
+ if(n_read < sizeof(buffer))
+ {
+ fclose(infile);
+ return 0;
+ }
+ break;
+ }
+
+ if(!_decode_mp3_frame(&buffer[index], &fi))
+ {
+ if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4))
+ {
+ /* no need to check further... if there is a xing header there,
+ * this is definately a valid frame */
+ found = 1;
+ fp_size += index;
+ }
+ else
+ {
+ /* No Xing... check for next frame to validate current fram is correct */
+ fseek(infile, fp_size + index + fi.frame_length, SEEK_SET);
+ if(fread(frame_buffer, 1, sizeof(frame_buffer), infile) == sizeof(frame_buffer))
+ {
+ if(!_decode_mp3_frame((unsigned char*)frame_buffer, &fi))
+ {
+ found = 1;
+ fp_size += index;
+ }
+ }
+ else
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "Could not read frame header: %s\n", file);
+ fclose(infile);
+ return 0;
+ }
+
+ if(!found)
+ {
+ // cannot find second frame. Song may be too short. So assume first frame is valid.
+ found = 1;
+ fp_size += index;
+ }
+ }
+ }
+
+ if(!found)
+ {
+ index++;
+ if(first_check)
+ {
+ DPRINTF(E_WARN, L_SCANNER, "Bad header... dropping back for full frame search\n");
+ first_check = 0;
+ fp_size = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ fi.frame_offset = fp_size;
+
+ psong->audio_offset = fp_size;
+ psong->audio_size = file_size - fp_size;
+ // check if last 128 bytes is ID3v1.0 ID3v1.1 tag
+ fseek(infile, file_size - 128, SEEK_SET);
+ if(fread(id3v1taghdr, 1, 4, infile) == 4)
+ {
+ if(id3v1taghdr[0] == 'T' && id3v1taghdr[1] == 'A' && id3v1taghdr[2] == 'G')
+ {
+ psong->audio_size -= 128;
+ }
+ }
+
+ if(_decode_mp3_frame(&buffer[index], &fi))
+ {
+ fclose(infile);
+ DPRINTF(E_ERROR, L_SCANNER, "Could not find sync frame: %s\n", file);
+ return 0;
+ }
+
+ /* now check for an XING header */
+ psong->vbr_scale = -1;
+ if(!strncasecmp((char*)&buffer[index + fi.xing_offset + 4], "XING", 4))
+ {
+ xing_flags = *((int*)&buffer[index + fi.xing_offset + 4 + 4]);
+ xing_flags = ntohs(xing_flags);
+ psong->vbr_scale = 78;
+
+ if(xing_flags & 0x1)
+ {
+ /* Frames field is valid... */
+ fi.number_of_frames = *((int*)&buffer[index + fi.xing_offset + 4 + 8]);
+ fi.number_of_frames = ntohs(fi.number_of_frames);
+ }
+ }
+
+ if((fi.number_of_frames == 0) && (!psong->song_length))
+ {
+ _mp3_get_average_bitrate(infile, &fi, file);
+ }
+
+ psong->bitrate = fi.bitrate * 1000;
+ psong->samplerate = fi.samplerate;
+
+ if(!psong->song_length)
+ {
+ if(fi.number_of_frames)
+ {
+ psong->song_length = (int)((double)(fi.number_of_frames * fi.samples_per_frame * 1000.) /
+ (double)fi.samplerate);
+ psong->vbr_scale = 78;
+ }
+ else
+ {
+ psong->song_length = (int)((double)(file_size - fp_size) * 8. /
+ (double)fi.bitrate);
+ }
+ }
+ psong->channels = fi.stereo ? 2 : 1;
+
+ fclose(infile);
+ //DEBUG DPRINTF(E_INFO, L_SCANNER, "Got fileinfo successfully for file=%s song_length=%d\n", file, psong->song_length);
+
+ psong->blockalignment = 1;
+ asprintf(&(psong->dlna_pn), "MP3");
+
+ return 0;
+}
diff --git a/tagutils/tagutils-mp3.h b/tagutils/tagutils-mp3.h
new file mode 100644
index 0000000..f6ad9ed
--- /dev/null
+++ b/tagutils/tagutils-mp3.h
@@ -0,0 +1,64 @@
+//=========================================================================
+// FILENAME : tagutils-mp3.h
+// DESCRIPTION : MP3 metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+
+struct mp3_frameinfo {
+ int layer; // 1,2,3
+ int bitrate; // unit=kbps
+ int samplerate; // samp/sec
+ int stereo; // flag
+
+ int frame_length; // bytes
+ int crc_protected; // flag
+ int samples_per_frame; // calculated
+ int padding; // flag
+ int xing_offset; // for xing hdr
+ int number_of_frames;
+
+ int frame_offset;
+
+ short mpeg_version;
+ short id3_version;
+
+ int is_valid;
+};
+
+static int _get_mp3tags(char *file, struct song_metadata *psong);
+static int _get_mp3fileinfo(char *file, struct song_metadata *psong);
+static int _decode_mp3_frame(unsigned char *frame, struct mp3_frameinfo *pfi);
+
+// bitrate_tbl[layer_index][bitrate_index]
+static int bitrate_tbl[5][16] = {
+ { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, /* MPEG1, L1 */
+ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, /* MPEG1, L2 */
+ { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, /* MPEG1, L3 */
+ { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, /* MPEG2/2.5, L1 */
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } /* MPEG2/2.5, L2/L3 */
+};
+
+// sample_rate[sample_index][samplerate_index]
+static int sample_rate_tbl[3][4] = {
+ { 44100, 48000, 32000, 0 }, /* MPEG 1 */
+ { 22050, 24000, 16000, 0 }, /* MPEG 2 */
+ { 11025, 12000, 8000, 0 } /* MPEG 2.5 */
+};
diff --git a/tagutils/tagutils-ogg.c b/tagutils/tagutils-ogg.c
new file mode 100644
index 0000000..b978b72
--- /dev/null
+++ b/tagutils/tagutils-ogg.c
@@ -0,0 +1,542 @@
+//=========================================================================
+// FILENAME : tagutils-ogg.c
+// DESCRIPTION : Ogg metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/*
+ * This file is derived from mt-daap project.
+ */
+
+typedef struct _ogg_stream_processor {
+ void (*process_page)(struct _ogg_stream_processor *, ogg_page *, struct song_metadata *);
+ void (*process_end)(struct _ogg_stream_processor *, struct song_metadata *);
+ int isillegal;
+ int constraint_violated;
+ int shownillegal;
+ int isnew;
+ long seqno;
+ int lostseq;
+
+ int start;
+ int end;
+
+ int num;
+ char *type;
+
+ ogg_uint32_t serial;
+ ogg_stream_state os;
+ void *data;
+} ogg_stream_processor;
+
+typedef struct {
+ ogg_stream_processor *streams;
+ int allocated;
+ int used;
+
+ int in_headers;
+} ogg_stream_set;
+
+typedef struct {
+ vorbis_info vi;
+ vorbis_comment vc;
+
+ ogg_int64_t bytes;
+ ogg_int64_t lastgranulepos;
+ ogg_int64_t firstgranulepos;
+
+ int doneheaders;
+} ogg_misc_vorbis_info;
+
+#define CONSTRAINT_PAGE_AFTER_EOS 1
+#define CONSTRAINT_MUXING_VIOLATED 2
+
+static ogg_stream_set *
+_ogg_create_stream_set(void)
+{
+ ogg_stream_set *set = calloc(1, sizeof(ogg_stream_set));
+
+ set->streams = calloc(5, sizeof(ogg_stream_processor));
+ set->allocated = 5;
+ set->used = 0;
+
+ return set;
+}
+
+static void
+_ogg_vorbis_process(ogg_stream_processor *stream, ogg_page *page,
+ struct song_metadata *psong)
+{
+ ogg_packet packet;
+ ogg_misc_vorbis_info *inf = stream->data;
+ int i, header = 0;
+
+ ogg_stream_pagein(&stream->os, page);
+ if(inf->doneheaders < 3)
+ header = 1;
+
+ while(ogg_stream_packetout(&stream->os, &packet) > 0)
+ {
+ if(inf->doneheaders < 3)
+ {
+ if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0)
+ {
+ DPRINTF(E_WARN, L_SCANNER, "Could not decode vorbis header "
+ "packet - invalid vorbis stream (%d)\n", stream->num);
+ continue;
+ }
+ inf->doneheaders++;
+ if(inf->doneheaders == 3)
+ {
+ if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
+ DPRINTF(E_WARN, L_SCANNER, "No header in vorbis stream %d\n", stream->num);
+ DPRINTF(E_DEBUG, L_SCANNER, "Vorbis headers parsed for stream %d, "
+ "information follows...\n", stream->num);
+ DPRINTF(E_DEBUG, L_SCANNER, "Channels: %d\n", inf->vi.channels);
+ DPRINTF(E_DEBUG, L_SCANNER, "Rate: %ld\n\n", inf->vi.rate);
+
+ psong->samplerate = inf->vi.rate;
+ psong->channels = inf->vi.channels;
+
+ if(inf->vi.bitrate_nominal > 0)
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Nominal bitrate: %f kb/s\n",
+ (double)inf->vi.bitrate_nominal / 1000.0);
+ psong->bitrate = inf->vi.bitrate_nominal / 1000;
+ }
+ else
+ {
+ int upper_rate, lower_rate;
+
+ DPRINTF(E_DEBUG, L_SCANNER, "Nominal bitrate not set\n");
+
+ //
+ upper_rate = 0;
+ lower_rate = 0;
+
+ if(inf->vi.bitrate_upper > 0)
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Upper bitrate: %f kb/s\n",
+ (double)inf->vi.bitrate_upper / 1000.0);
+ upper_rate = inf->vi.bitrate_upper;
+ }
+ else
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Upper bitrate not set\n");
+ }
+
+ if(inf->vi.bitrate_lower > 0)
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Lower bitrate: %f kb/s\n",
+ (double)inf->vi.bitrate_lower / 1000.0);
+ lower_rate = inf->vi.bitrate_lower;;
+ }
+ else
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "Lower bitrate not set\n");
+ }
+
+ if(upper_rate && lower_rate)
+ {
+ psong->bitrate = (upper_rate + lower_rate) / 2;
+ }
+ else
+ {
+ psong->bitrate = upper_rate + lower_rate;
+ }
+ }
+
+ if(inf->vc.comments > 0)
+ DPRINTF(E_DEBUG, L_SCANNER,
+ "User comments section follows...\n");
+
+ for(i = 0; i < inf->vc.comments; i++)
+ {
+ vc_scan(psong, inf->vc.user_comments[i], inf->vc.comment_lengths[i]);
+ }
+ }
+ }
+ }
+
+ if(!header)
+ {
+ ogg_int64_t gp = ogg_page_granulepos(page);
+ if(gp > 0)
+ {
+ if(gp < inf->lastgranulepos)
+ DPRINTF(E_WARN, L_SCANNER, "granulepos in stream %d decreases from %lld to %lld",
+ stream->num, inf->lastgranulepos, gp);
+ inf->lastgranulepos = gp;
+ }
+ else
+ {
+ DPRINTF(E_WARN, L_SCANNER, "Malformed vorbis strem.\n");
+ }
+ inf->bytes += page->header_len + page->body_len;
+ }
+}
+
+static void
+_ogg_vorbis_end(ogg_stream_processor *stream, struct song_metadata *psong)
+{
+ ogg_misc_vorbis_info *inf = stream->data;
+ long minutes, seconds;
+ double bitrate, time;
+
+ time = (double)inf->lastgranulepos / inf->vi.rate;
+ bitrate = inf->bytes * 8 / time / 1000;
+
+ if(psong != NULL)
+ {
+ if(psong->bitrate <= 0)
+ {
+ psong->bitrate = bitrate * 1000;
+ }
+ psong->song_length = time * 1000;
+ }
+
+ minutes = (long)time / 60;
+ seconds = (long)time - minutes * 60;
+
+ vorbis_comment_clear(&inf->vc);
+ vorbis_info_clear(&inf->vi);
+
+ free(stream->data);
+}
+
+static void
+_ogg_process_null(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
+{
+ // invalid stream
+}
+
+static void
+_ogg_process_other(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
+{
+ ogg_stream_pagein(&stream->os, page);
+}
+
+static void
+_ogg_free_stream_set(ogg_stream_set *set)
+{
+ int i;
+
+ for(i = 0; i < set->used; i++)
+ {
+ if(!set->streams[i].end)
+ {
+ // no EOS
+ if(set->streams[i].process_end)
+ set->streams[i].process_end(&set->streams[i], NULL);
+ }
+ ogg_stream_clear(&set->streams[i].os);
+ }
+
+ free(set->streams);
+ free(set);
+}
+
+static int
+_ogg_streams_open(ogg_stream_set *set)
+{
+ int i;
+ int res = 0;
+
+ for(i = 0; i < set->used; i++)
+ {
+ if(!set->streams[i].end)
+ res++;
+ }
+
+ return res;
+}
+
+static void
+_ogg_null_start(ogg_stream_processor *stream)
+{
+ stream->process_end = NULL;
+ stream->type = "invalid";
+ stream->process_page = _ogg_process_null;
+}
+
+static void
+_ogg_other_start(ogg_stream_processor *stream, char *type)
+{
+ if(type)
+ stream->type = type;
+ else
+ stream->type = "unknown";
+ stream->process_page = _ogg_process_other;
+ stream->process_end = NULL;
+}
+
+static void
+_ogg_vorbis_start(ogg_stream_processor *stream)
+{
+ ogg_misc_vorbis_info *info;
+
+ stream->type = "vorbis";
+ stream->process_page = _ogg_vorbis_process;
+ stream->process_end = _ogg_vorbis_end;
+
+ stream->data = calloc(1, sizeof(ogg_misc_vorbis_info));
+
+ info = stream->data;
+
+ vorbis_comment_init(&info->vc);
+ vorbis_info_init(&info->vi);
+}
+
+static ogg_stream_processor *
+_ogg_find_stream_processor(ogg_stream_set *set, ogg_page *page)
+{
+ ogg_uint32_t serial = ogg_page_serialno(page);
+ int i, found = 0;
+ int invalid = 0;
+ int constraint = 0;
+ ogg_stream_processor *stream;
+
+ for(i = 0; i < set->used; i++)
+ {
+ if(serial == set->streams[i].serial)
+ {
+ found = 1;
+ stream = &(set->streams[i]);
+
+ set->in_headers = 0;
+
+ if(stream->end)
+ {
+ stream->isillegal = 1;
+ stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
+ return stream;
+ }
+
+ stream->isnew = 0;
+ stream->start = ogg_page_bos(page);
+ stream->end = ogg_page_eos(page);
+ stream->serial = serial;
+ return stream;
+ }
+ }
+ if(_ogg_streams_open(set) && !set->in_headers)
+ {
+ constraint = CONSTRAINT_MUXING_VIOLATED;
+ invalid = 1;
+ }
+
+ set->in_headers = 1;
+
+ if(set->allocated < set->used)
+ stream = &set->streams[set->used];
+ else
+ {
+ set->allocated += 5;
+ set->streams = realloc(set->streams, sizeof(ogg_stream_processor) * set->allocated);
+ stream = &set->streams[set->used];
+ }
+ set->used++;
+ stream->num = set->used; // count from 1
+
+ stream->isnew = 1;
+ stream->isillegal = invalid;
+ stream->constraint_violated = constraint;
+
+ {
+ int res;
+ ogg_packet packet;
+
+ ogg_stream_init(&stream->os, serial);
+ ogg_stream_pagein(&stream->os, page);
+ res = ogg_stream_packetout(&stream->os, &packet);
+ if(res <= 0)
+ {
+ DPRINTF(E_WARN, L_SCANNER, "Invalid header page, no packet found\n");
+ _ogg_null_start(stream);
+ }
+ else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7) == 0)
+ _ogg_vorbis_start(stream);
+ else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8) == 0)
+ _ogg_other_start(stream, "MIDI");
+ else
+ _ogg_other_start(stream, NULL);
+
+ res = ogg_stream_packetout(&stream->os, &packet);
+ if(res > 0)
+ {
+ DPRINTF(E_WARN, L_SCANNER, "Invalid header page in stream %d, "
+ "contains multiple packets\n", stream->num);
+ }
+
+ /* re-init, ready for processing */
+ ogg_stream_clear(&stream->os);
+ ogg_stream_init(&stream->os, serial);
+ }
+
+ stream->start = ogg_page_bos(page);
+ stream->end = ogg_page_eos(page);
+ stream->serial = serial;
+
+ return stream;
+}
+
+static int
+_ogg_get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
+ ogg_int64_t *written)
+{
+ int ret;
+ char *buffer;
+ int bytes;
+
+ while((ret = ogg_sync_pageout(sync, page)) <= 0)
+ {
+ if(ret < 0)
+ DPRINTF(E_WARN, L_SCANNER, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written);
+
+ buffer = ogg_sync_buffer(sync, 4500); // chunk=4500
+ bytes = fread(buffer, 1, 4500, f);
+ if(bytes <= 0)
+ {
+ ogg_sync_wrote(sync, 0);
+ return 0;
+ }
+ ogg_sync_wrote(sync, bytes);
+ *written += bytes;
+ }
+
+ return 1;
+}
+
+
+static int
+_get_oggfileinfo(char *filename, struct song_metadata *psong)
+{
+ FILE *file = fopen(filename, "rb");
+ ogg_sync_state sync;
+ ogg_page page;
+ ogg_stream_set *processors = _ogg_create_stream_set();
+ int gotpage = 0;
+ ogg_int64_t written = 0;
+
+ if(!file)
+ {
+ DPRINTF(E_FATAL, L_SCANNER,
+ "Error opening input file \"%s\": %s\n", filename, strerror(errno));
+ _ogg_free_stream_set(processors);
+ return -1;
+ }
+
+ DPRINTF(E_INFO, L_SCANNER, "Processing file \"%s\"...\n\n", filename);
+
+ ogg_sync_init(&sync);
+
+ while(_ogg_get_next_page(file, &sync, &page, &written))
+ {
+ ogg_stream_processor *p = _ogg_find_stream_processor(processors, &page);
+ gotpage = 1;
+
+ if(!p)
+ {
+ DPRINTF(E_FATAL, L_SCANNER, "Could not find a processor for stream, bailing\n");
+ _ogg_free_stream_set(processors);
+ return -1;
+ }
+
+ if(p->isillegal && !p->shownillegal)
+ {
+ char *constraint;
+ switch(p->constraint_violated)
+ {
+ case CONSTRAINT_PAGE_AFTER_EOS:
+ constraint = "Page found for stream after EOS flag";
+ break;
+ case CONSTRAINT_MUXING_VIOLATED:
+ constraint = "Ogg muxing constraints violated, new "
+ "stream before EOS of all previous streams";
+ break;
+ default:
+ constraint = "Error unknown.";
+ }
+
+ DPRINTF(E_WARN, L_SCANNER,
+ "Warning: illegally placed page(s) for logical stream %d\n"
+ "This indicates a corrupt ogg file: %s.\n",
+ p->num, constraint);
+ p->shownillegal = 1;
+
+ if(!p->isnew)
+ continue;
+ }
+
+ if(p->isnew)
+ {
+ DPRINTF(E_DEBUG, L_SCANNER, "New logical stream (#%d, serial: %08x): type %s\n",
+ p->num, p->serial, p->type);
+ if(!p->start)
+ DPRINTF(E_WARN, L_SCANNER,
+ "stream start flag not set on stream %d\n",
+ p->num);
+ }
+ else if(p->start)
+ DPRINTF(E_WARN, L_SCANNER, "stream start flag found in mid-stream "
+ "on stream %d\n", p->num);
+
+ if(p->seqno++ != ogg_page_pageno(&page))
+ {
+ if(!p->lostseq)
+ DPRINTF(E_WARN, L_SCANNER,
+ "sequence number gap in stream %d. Got page %ld "
+ "when expecting page %ld. Indicates missing data.\n",
+ p->num, ogg_page_pageno(&page), p->seqno - 1);
+ p->seqno = ogg_page_pageno(&page);
+ p->lostseq = 1;
+ }
+ else
+ p->lostseq = 0;
+
+ if(!p->isillegal)
+ {
+ p->process_page(p, &page, psong);
+
+ if(p->end)
+ {
+ if(p->process_end)
+ p->process_end(p, psong);
+ DPRINTF(E_DEBUG, L_SCANNER, "Logical stream %d ended\n", p->num);
+ p->isillegal = 1;
+ p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
+ }
+ }
+ }
+
+ _ogg_free_stream_set(processors);
+
+ ogg_sync_clear(&sync);
+
+ fclose(file);
+
+ if(!gotpage)
+ {
+ DPRINTF(E_ERROR, L_SCANNER, "No ogg data found in file \"%s\".\n", filename);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/tagutils/tagutils-ogg.h b/tagutils/tagutils-ogg.h
new file mode 100644
index 0000000..2c78a09
--- /dev/null
+++ b/tagutils/tagutils-ogg.h
@@ -0,0 +1,24 @@
+//=========================================================================
+// FILENAME : tagutils-ogg.h
+// DESCRIPTION : Ogg metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+static int _get_oggfileinfo(char *filename, struct song_metadata *psong);
diff --git a/tagutils/tagutils-plist.c b/tagutils/tagutils-plist.c
new file mode 100644
index 0000000..1c4f1cf
--- /dev/null
+++ b/tagutils/tagutils-plist.c
@@ -0,0 +1,140 @@
+//=========================================================================
+// FILENAME : playlist.c
+// DESCRIPTION : Playlist
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "misc.h"
+#include "tagutils.h"
+#include "textutils.h"
+#include "log.h"
+
+
+#define MAX_BUF 1024
+
+static FILE *fp = 0;
+static int _utf8bom = 0;
+
+static int (*_next_track)(struct song_metadata*, struct stat*, char*, char*);
+static int _m3u_next_track(struct song_metadata*, struct stat*, char*, char*);
+
+int
+start_plist(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type)
+{
+ char *fname, *suffix;
+
+ _next_track = 0;
+ _utf8bom = 0;
+
+ if(!strcmp(type, "m3u"))
+ _next_track = _m3u_next_track;
+
+ if(!_next_track)
+ {
+ DPRINTF(E_ERROR, L_SCAN_SCANNER, "Non supported type of playlist <%s>\n", type);
+ return -1;
+ }
+
+ if(!(fp = fopen(path, "rb")))
+ {
+ DPRINTF(E_ERROR, L_SCAN_SCANNER, "Cannot open %s\n", path);
+ return -1;
+ }
+
+ //
+ memset((void*)psong, 0, sizeof(struct song_metadata));
+ psong->is_plist = 1;
+ psong->path = strdup(path);
+ psong->type = type;
+
+ fname = strrchr(psong->path, '/');
+ psong->basename = fname ? fname + 1 : psong->path;
+
+ psong->title = strdup(psong->basename);
+ suffix = strrchr(psong->title, '.');
+ if(suffix) *suffix = '\0';
+
+ if(stat)
+ {
+ if(!psong->time_modified)
+ psong->time_modified = stat->st_mtime;
+ psong->file_size = stat->st_size;
+ }
+
+ return 0;
+}
+
+int
+_m3u_next_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type)
+{
+ char buf[MAX_BUF], *p;
+ int len;
+
+ //
+ memset((void*)psong, 0, sizeof(struct song_metadata));
+
+ // read first line, check BOM
+ p = fgets(buf, MAX_BUF, fp);
+ if(!p)
+ {
+ fclose(fp);
+ return 1;
+ }
+
+ if(!_utf8bom && p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
+ {
+ _utf8bom = 1;
+ p += 3;
+ }
+
+ while(p)
+ {
+ while(isspace(*p)) p++;
+ if(*p && *p != '#')
+ {
+ // check dos format
+ len = strlen((char*)p);
+
+ while(p[len - 1] == '\r' || p[len - 1] == '\n')
+ {
+ p[--len] = '\0';
+ }
+ psong->path = strdup(p);
+ return 0;
+ }
+ p = fgets(buf, MAX_BUF, fp);
+ continue;
+ }
+
+ fclose(fp);
+ return -1;
+}
+
+int
+next_plist_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type)
+{
+ if(_next_track)
+ return _next_track(psong, stat, lang, type);
+ return -1;
+}
diff --git a/tagutils/tagutils.c b/tagutils/tagutils.c
new file mode 100644
index 0000000..e5783b9
--- /dev/null
+++ b/tagutils/tagutils.c
@@ -0,0 +1,289 @@
+//=========================================================================
+// FILENAME : tagutils.c
+// DESCRIPTION : MP3/MP4/Ogg/FLAC metadata reader
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/* This file is derived from mt-daapd project */
+
+#include <ctype.h>
+#include <errno.h>
+#include <id3tag.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <time.h>
+#include <iconv.h>
+#include <sys/time.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+#include <FLAC/metadata.h>
+
+#include <netinet/in.h>
+
+#include <sqlite3.h>
+#include "tagutils.h"
+#include "misc.h"
+#include "textutils.h"
+#include "../metadata.h"
+#include "../log.h"
+
+struct id3header {
+ unsigned char id[3];
+ unsigned char version[2];
+ unsigned char flags;
+ unsigned char size[4];
+} __attribute((packed));
+
+char *winamp_genre[] = {
+ /*00*/ "Blues", "Classic Rock", "Country", "Dance",
+ "Disco", "Funk", "Grunge", "Hip-Hop",
+ /*08*/ "Jazz", "Metal", "New Age", "Oldies",
+ "Other", "Pop", "R&B", "Rap",
+ /*10*/ "Reggae", "Rock", "Techno", "Industrial",
+ "Alternative", "Ska", "Death Metal", "Pranks",
+ /*18*/ "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
+ "Vocal", "Jazz+Funk", "Fusion", "Trance",
+ /*20*/ "Classical", "Instrumental", "Acid", "House",
+ "Game", "Sound Clip", "Gospel", "Noise",
+ /*28*/ "AlternRock", "Bass", "Soul", "Punk",
+ "Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
+ /*30*/ "Ethnic", "Gothic", "Darkwave", "Techno-Industrial",
+ "Electronic", "Pop-Folk", "Eurodance", "Dream",
+ /*38*/ "Southern Rock", "Comedy", "Cult", "Gangsta",
+ "Top 40", "Christian Rap", "Pop/Funk", "Jungle",
+ /*40*/ "Native American", "Cabaret", "New Wave", "Psychadelic",
+ "Rave", "Showtunes", "Trailer", "Lo-Fi",
+ /*48*/ "Tribal", "Acid Punk", "Acid Jazz", "Polka",
+ "Retro", "Musical", "Rock & Roll", "Hard Rock",
+ /*50*/ "Folk", "Folk/Rock", "National folk", "Swing",
+ "Fast-fusion", "Bebob", "Latin", "Revival",
+ /*58*/ "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
+ "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
+ /*60*/ "Big Band", "Chorus", "Easy Listening", "Acoustic",
+ "Humour", "Speech", "Chanson", "Opera",
+ /*68*/ "Chamber Music", "Sonata", "Symphony", "Booty Bass",
+ "Primus", "Porn Groove", "Satire", "Slow Jam",
+ /*70*/ "Club", "Tango", "Samba", "Folklore",
+ "Ballad", "Powder Ballad", "Rhythmic Soul", "Freestyle",
+ /*78*/ "Duet", "Punk Rock", "Drum Solo", "A Capella",
+ "Euro-House", "Dance Hall", "Goa", "Drum & Bass",
+ /*80*/ "Club House", "Hardcore", "Terror", "Indie",
+ "BritPop", "NegerPunk", "Polsk Punk", "Beat",
+ /*88*/ "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover",
+ "Contemporary C", "Christian Rock", "Merengue", "Salsa",
+ /*90*/ "Thrash Metal", "Anime", "JPop", "SynthPop",
+ "Unknown"
+};
+
+#define WINAMP_GENRE_UNKNOWN ((sizeof(winamp_genre) / sizeof(winamp_genre[0])) - 1)
+
+
+/*
+ * Prototype
+ */
+#include "tagutils-mp3.h"
+#include "tagutils-aac.h"
+#include "tagutils-ogg.h"
+#include "tagutils-flc.h"
+#include "tagutils-asf.h"
+
+static int _get_tags(char *file, struct song_metadata *psong);
+static int _get_fileinfo(char *file, struct song_metadata *psong);
+
+
+/*
+ * Typedefs
+ */
+
+typedef struct {
+ char* type;
+ int (*get_tags)(char* file, struct song_metadata* psong);
+ int (*get_fileinfo)(char* file, struct song_metadata* psong);
+} taghandler;
+
+static taghandler taghandlers[] = {
+ { "aac", _get_aactags, _get_aacfileinfo },
+ { "mp3", _get_mp3tags, _get_mp3fileinfo },
+ { "flc", _get_flctags, _get_flcfileinfo },
+ { "ogg", 0, _get_oggfileinfo },
+ { "asf", 0, _get_asffileinfo },
+ { NULL, 0 }
+};
+
+
+
+//*********************************************************************************
+#include "tagutils-misc.c"
+#include "tagutils-mp3.c"
+#include "tagutils-aac.c"
+#include "tagutils-ogg.c"
+#include "tagutils-flc.c"
+#include "tagutils-asf.c"
+
+//*********************************************************************************
+// freetags()
+#define MAYBEFREE(a) { if((a)) free((a)); };
+void
+freetags(struct song_metadata *psong)
+{
+ int role;
+
+ MAYBEFREE(psong->path);
+ MAYBEFREE(psong->image);
+ MAYBEFREE(psong->title);
+ MAYBEFREE(psong->album);
+ MAYBEFREE(psong->genre);
+ MAYBEFREE(psong->comment);
+ for(role = ROLE_START; role <= ROLE_LAST; role++)
+ {
+ MAYBEFREE(psong->contributor[role]);
+ MAYBEFREE(psong->contributor_sort[role]);
+ }
+ MAYBEFREE(psong->grouping);
+ MAYBEFREE(psong->dlna_pn);
+ MAYBEFREE(psong->tagversion);
+ MAYBEFREE(psong->musicbrainz_albumid);
+ MAYBEFREE(psong->musicbrainz_trackid);
+ MAYBEFREE(psong->musicbrainz_artistid);
+ MAYBEFREE(psong->musicbrainz_albumartistid);
+}
+
+// _get_fileinfo
+static int
+_get_fileinfo(char *file, struct song_metadata *psong)
+{
+ taghandler *hdl;
+
+ // dispatch to appropriate tag handler
+ for(hdl = taghandlers; hdl->type; ++hdl)
+ if(!strcmp(hdl->type, psong->type))
+ break;
+
+ if(hdl->get_fileinfo)
+ return hdl->get_fileinfo(file, psong);
+
+ return 0;
+}
+
+
+static void
+_make_composite_tags(struct song_metadata *psong)
+{
+ int len;
+
+ len = 0;
+
+ if(!psong->contributor[ROLE_ARTIST] &&
+ (psong->contributor[ROLE_BAND] || psong->contributor[ROLE_CONDUCTOR]))
+ {
+ if(psong->contributor[ROLE_BAND])
+ len += strlen(psong->contributor[ROLE_BAND]);
+ if(psong->contributor[ROLE_CONDUCTOR])
+ len += strlen(psong->contributor[ROLE_CONDUCTOR]);
+
+ len += 3;
+
+ psong->contributor[ROLE_ARTIST] = (char*)calloc(len, 1);
+ if(psong->contributor[ROLE_ARTIST])
+ {
+ if(psong->contributor[ROLE_BAND])
+ strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_BAND]);
+
+ if(psong->contributor[ROLE_BAND] && psong->contributor[ROLE_CONDUCTOR])
+ strcat(psong->contributor[ROLE_ARTIST], " - ");
+
+ if(psong->contributor[ROLE_CONDUCTOR])
+ strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_CONDUCTOR]);
+ }
+ }
+
+
+ if(!psong->title)
+ {
+ char *suffix;
+ psong->title = strdup(psong->basename);
+ suffix = strrchr(psong->title, '.');
+ if(suffix) *suffix = '\0';
+ }
+}
+
+
+/*****************************************************************************/
+// _get_tags
+static int
+_get_tags(char *file, struct song_metadata *psong)
+{
+ taghandler *hdl;
+
+ // dispatch
+ for(hdl = taghandlers ; hdl->type ; ++hdl)
+ if(!strcasecmp(hdl->type, psong->type))
+ break;
+
+ if(hdl->get_tags)
+ {
+ return hdl->get_tags(file, psong);
+ }
+
+ return 0;
+}
+
+/*****************************************************************************/
+// readtags
+int
+readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type)
+{
+ char *fname;
+ int found = 0;
+
+ if(lang_index == -1)
+ lang_index = _lang2cp(lang);
+
+ memset((void*)psong, 0, sizeof(struct song_metadata));
+ psong->path = strdup(path);
+ psong->type = type;
+
+ fname = strrchr(psong->path, '/');
+ psong->basename = fname ? fname + 1 : psong->path;
+
+ // get tag
+ found |= _get_tags(path, psong);
+
+ if(stat)
+ {
+ if(!psong->time_modified)
+ psong->time_modified = stat->st_mtime;
+ psong->file_size = stat->st_size;
+ }
+
+ // get fileinfo
+ found |= _get_fileinfo(path, psong);
+
+ //
+ if(!found)
+ {
+ _make_composite_tags(psong);
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+}
diff --git a/tagutils/tagutils.h b/tagutils/tagutils.h
new file mode 100644
index 0000000..7307601
--- /dev/null
+++ b/tagutils/tagutils.h
@@ -0,0 +1,120 @@
+//=========================================================================
+// FILENAME : taguilts.h
+// DESCRIPTION : Header for tagutils.c
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+/*
+ * This file is derived from mt-daap project.
+ */
+
+#ifndef _TAG_H_
+#define _TAG_H_
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define ROLE_NOUSE 0
+#define ROLE_START 1
+#define ROLE_ARTIST 1
+#define ROLE_COMPOSER 2
+#define ROLE_CONDUCTOR 3
+#define ROLE_BAND 4
+#define ROLE_ALBUMARTIST 5
+#define ROLE_TRACKARTIST 6
+#define ROLE_LAST 6
+#define N_ROLE 7
+
+struct song_metadata {
+ int file_size;
+ char *dirpath;
+ char *path;
+ char *basename; // basename is part of path
+ char *type;
+ int time_modified;
+
+ char *image; // coverart
+ int image_size;
+
+ char *title; // TIT2
+ char *album; // TALB
+ char *genre; // TCON
+ char *comment; // COMM
+
+ char *contributor[N_ROLE]; // TPE1 (artist)
+ // TCOM (composer)
+ // TPE3 (conductor)
+ // TPE2 (orchestra)
+ char *contributor_sort[N_ROLE];
+
+
+ char *grouping; // TIT1
+ int year; // TDRC
+ int track; // TRCK
+ int total_tracks; // TRCK
+ int disc; // TPOS
+ int total_discs; // TPOS
+ int bpm; // TBPM
+ char compilation; // YTCP
+
+ int bitrate;
+ int samplerate;
+ int samplesize;
+ int channels;
+ int song_length; // TLEN
+ int audio_size;
+ int audio_offset;
+ int vbr_scale;
+ int lossless;
+ int blockalignment;
+
+ char *dlna_pn; // DLNA Profile Name
+
+ char *tagversion;
+
+ unsigned long album_id;
+ unsigned long track_id;
+ unsigned long genre_id;
+ unsigned long contributor_id[N_ROLE];
+
+ char *musicbrainz_albumid;
+ char *musicbrainz_trackid;
+ char *musicbrainz_artistid;
+ char *musicbrainz_albumartistid;
+
+ int is_plist;
+ int plist_position;
+ int plist_id;
+};
+
+#define WMAV1 0x161
+#define WMAV2 0x162
+#define WMAPRO 0x163
+
+extern int scan_init(char *path);
+extern void make_composite_tags(struct song_metadata *psong);
+extern int readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type);
+extern void freetags(struct song_metadata *psong);
+
+extern int start_plist(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type);
+extern int next_plist_track(struct song_metadata *psong, struct stat *stat, char *lang, char *type);
+
+#endif
diff --git a/tagutils/textutils.c b/tagutils/textutils.c
new file mode 100644
index 0000000..9e55181
--- /dev/null
+++ b/tagutils/textutils.c
@@ -0,0 +1,298 @@
+//=========================================================================
+// FILENAME : textutils.c
+// DESCRIPTION : Misc. text utilities
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "misc.h"
+#include "textutils.h"
+#include "../log.h"
+
+static unsigned int
+_char_htoi(char h)
+{
+ if (h<'0')
+ return 0;
+ if (h<='9')
+ return h-'0';
+ if (h<'A')
+ return 0;
+ if (h<='F')
+ return h-'A'+10;
+ if (h<'a')
+ return 0;
+ if (h<='f')
+ return h-'a'+10;
+ return 0;
+}
+
+void
+urldecode(char *src)
+{
+ char c, *s, *d;
+
+ for (d=s=src; *s; s++, d++) {
+ c = *s;
+ if (c=='%') {
+ c = *++s;
+ if (c=='%')
+ c = '%';
+ else {
+ c = _char_htoi(c)<<4 | _char_htoi(*++s);
+ }
+ *d = c;
+ }
+ else {
+ *d = c;
+ }
+ }
+ *d = '\0';
+}
+
+#if 0
+static int
+is_ignoredword(const char *str)
+{
+ int i;
+
+ if (!prefs.ignoredwords)
+ return 0;
+
+ for (i=0; prefs.ignoredwords[i].n; i++) {
+ if (!(strncasecmp(prefs.ignoredwords[i].word, str, prefs.ignoredwords[i].n))) {
+ char next_char = str[prefs.ignoredwords[i].n];
+ if (isalnum(next_char))
+ continue;
+ return prefs.ignoredwords[i].n;
+ }
+ }
+ return 0;
+}
+#endif
+
+char *
+skipspaces(const char *str)
+{
+ while (isspace(*str)) str++;
+ return (char*) str;
+}
+
+/*
+U+0040 (40): @ A B C D E F G H I J K L M N O
+U+0050 (50): P Q R S T U V W X Y Z [ \ ] ^ _
+U+0060 (60): ` a b c d e f g h i j k l m n o
+U+0070 (70): p q r s t u v w x y z { | } ~
+
+U+00c0 (c3 80): À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï
+U+00d0 (c3 90): Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß
+U+00e0 (c3 a0): à á â ã ä å æ ç è é ê ë ì í î ï
+U+00f0 (c3 b0): ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
+U+0100 (c4 80): Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď
+U+0110 (c4 90): Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ
+U+0120 (c4 a0): Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į
+U+0130 (c4 b0): İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ
+U+0140 (c5 80): ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ
+U+0150 (c5 90): Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş
+U+0160 (c5 a0): Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů
+U+0170 (c5 b0): Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž ſ
+ */
+
+// conversion table for latin diacritical char to ascii one char or two chars.
+unsigned short UtoAscii[] = {
+ // U+00c0
+ 0x0041,0x0041,0x0041,0x0041, 0x0041,0x0041,0x4145,0x0043, 0x0045,0x0045,0x0045,0x0045, 0x0049,0x0049,0x0049,0x0049,
+ 0x0044,0x004e,0x004f,0x004f, 0x004f,0x004f,0x004f,0xc397, 0xc398,0x0055,0x0055,0x0055, 0x0055,0x0059,0x0050,0x5353,
+ // U+00e0
+ 0x0041,0x0041,0x0041,0x0041, 0x0041,0x0041,0x4145,0x0043, 0x0045,0x0045,0x0045,0x0045, 0x0049,0x0049,0x0049,0x0049,
+ 0x0044,0x004e,0x004f,0x004f, 0x004f,0x004f,0x004f,0xc397, 0xc398,0x0055,0x0055,0x0055, 0x0055,0x0059,0x0050,0x5353,
+ // U+0100
+ 0x0041,0x0041,0x0041,0x0041, 0x0041,0x0041,0x0043,0x0043, 0x0043,0x0043,0x0043,0x0043, 0x0043,0x0043,0x0044,0x0044,
+ 0x0044,0x0044,0x0045,0x0045, 0x0045,0x0045,0x0045,0x0045, 0x0045,0x0045,0x0045,0x0045, 0x0047,0x0047,0x0047,0x0047,
+ // U+0120
+ 0x0047,0x0047,0x0047,0x0047, 0x0048,0x0048,0x0048,0x0048, 0x0049,0x0049,0x0049,0x0049, 0x0049,0x0049,0x0049,0x0049,
+ 0x0049,0x0049,0x494a,0x494a, 0x004a,0x004a,0x004b,0x004b, 0x004b,0x004c,0x004c,0x004c, 0x004c,0x004c,0x004c,0x004c,
+ // U+0140
+ 0x004c,0x004c,0x004c,0x004e, 0x004e,0x004e,0x004e,0x004e, 0x004e,0x004e,0x004e,0x004e, 0x004f,0x004f,0x004f,0x004f,
+ 0x004f,0x004f,0x4f45,0x4f45, 0x0052,0x0052,0x0052,0x0052, 0x0052,0x0052,0x0053,0x0053, 0x0053,0x0053,0x0053,0x0053,
+ // U+0160
+ 0x0053,0x0053,0x0054,0x0054, 0x0054,0x0054,0x0054,0x0054, 0x0055,0x0055,0x0055,0x0055, 0x0055,0x0055,0x0055,0x0055,
+ 0x0055,0x0055,0x0055,0x0055, 0x0057,0x0057,0x0059,0x0059, 0x0059,0x005a,0x005a,0x005a, 0x005a,0x005a,0x005a,0xc5bf
+};
+
+// conversion table for toupper() function for latin diacritical char
+unsigned short UtoUpper[] = {
+ // U+00c0
+ 0xc380,0xc381,0xc382,0xc383, 0xc384,0xc385,0xc386,0xc387, 0xc388,0xc389,0xc38a,0xc38b, 0xc38c,0xc38d,0xc38e,0xc38f,
+ 0xc390,0xc391,0xc392,0xc393, 0xc394,0xc395,0xc396,0xc397, 0xc398,0xc399,0xc39a,0xc39b, 0xc39c,0xc39d,0xc39e,0x5353,
+ // U+00e0
+ 0xc380,0xc381,0xc382,0xc383, 0xc384,0xc385,0xc386,0xc387, 0xc388,0xc389,0xc38a,0xc38b, 0xc38c,0xc38d,0xc38e,0xc38f,
+ 0xc390,0xc391,0xc392,0xc393, 0xc394,0xc395,0xc396,0xc397, 0xc398,0xc399,0xc39a,0xc39b, 0xc39c,0xc39d,0xc39e,0xc39f,
+ // U+0100
+ 0xc480,0xc480,0xc482,0xc482, 0xc484,0xc484,0xc486,0xc486, 0xc488,0xc488,0xc48a,0xc48a, 0xc48c,0xc48c,0xc48e,0xc48e,
+ 0xc490,0xc490,0xc492,0xc492, 0xc494,0xc494,0xc496,0xc496, 0xc498,0xc498,0xc49a,0xc49a, 0xc49c,0xc49c,0xc49e,0xc49e,
+ // U+0120
+ 0xc4a0,0xc4a0,0xc4a2,0xc4a2, 0xc4a4,0xc4a4,0xc4a6,0xc4a6, 0xc4a8,0xc4a8,0xc4aa,0xc4aa, 0xc4ac,0xc4ac,0xc4ae,0xc4ae,
+ 0xc4b0,0xc4b0,0xc4b2,0xc4b2, 0xc4b4,0xc4b4,0xc4b6,0xc4b6, 0xc4b8,0xc4b9,0xc4b9,0xc4bb, 0xc4bb,0xc4bd,0xc4bd,0xc4bf,
+ // U+0140
+ 0xc4bf,0xc581,0xc581,0xc583, 0xc583,0xc585,0xc585,0xc587, 0xc587,0xc589,0xc58a,0xc58a, 0xc58c,0xc58c,0xc58e,0xc58e,
+ 0xc590,0xc591,0xc592,0xc593, 0xc594,0xc595,0xc596,0xc597, 0xc598,0xc599,0xc59a,0xc59b, 0xc59c,0xc59d,0xc59e,0xc59f,
+ // U+0160
+ 0xc5a0,0xc5a0,0xc5a2,0xc5a2, 0xc5a4,0xc5a4,0xc5a6,0xc5a6, 0xc5a8,0xc5a8,0xc5aa,0xc5aa, 0xc5ac,0xc5ac,0xc5ae,0xc5ae,
+ 0xc5b0,0xc5b1,0xc5b2,0xc5b3, 0xc5b4,0xc5b5,0xc5b6,0xc5b7, 0xc5b8,0xc5b9,0xc5b9,0xc5bb, 0xc5bc,0xc5bd,0xc5bd,0xc5bf,
+};
+
+
+int
+safe_atoi(char *s)
+{
+ if (!s)
+ return 0;
+ if ((s[0]>='0' && s[0]<='9') || s[0]=='-' || s[0]=='+')
+ return atoi(s);
+ return 0;
+}
+
+// NOTE: support U+0000 ~ U+FFFF only.
+int
+utf16le_to_utf8(char *dst, int n, __u16 utf16le)
+{
+ __u16 wc = le16_to_cpu(utf16le);
+ if (wc < 0x80) {
+ if (n<1) return 0;
+ *dst++ = wc & 0xff;
+ return 1;
+ }
+ else if (wc < 0x800) {
+ if (n<2) return 0;
+ *dst++ = 0xc0 | (wc>>6);
+ *dst++ = 0x80 | (wc & 0x3f);
+ return 2;
+ }
+ else {
+ if (n<3) return 0;
+ *dst++ = 0xe0 | (wc>>12);
+ *dst++ = 0x80 | ((wc>>6) & 0x3f);
+ *dst++ = 0x80 | (wc & 0x3f);
+ return 3;
+ }
+}
+
+void
+fetch_string_txt(char *fname, char *lang, int n, ...)
+{
+ va_list args;
+ char **keys;
+ char ***strs;
+ char **defstr;
+ int i;
+ FILE *fp;
+ char buf[4096];
+ int state;
+ char *p;
+ char *langid;
+ const char *lang_en = "EN";
+
+ if (!(keys = malloc(sizeof(keys) * n))) {
+ DPRINTF(E_FATAL, L_SCANNER, "Out of memory\n");
+ }
+ if (!(strs = malloc(sizeof(strs) * n))) {
+ DPRINTF(E_FATAL, L_SCANNER, "Out of memory\n");
+ }
+ if (!(defstr = malloc(sizeof(defstr) * n))) {
+ DPRINTF(E_FATAL, L_SCANNER, "Out of memory\n");
+ }
+
+ va_start(args, n);
+ for (i=0; i<n; i++) {
+ keys[i] = va_arg(args, char *);
+ strs[i] = va_arg(args, char **);
+ defstr[i] = va_arg(args, char *);
+ }
+ va_end(args);
+
+ if (!(fp = fopen(fname, "rb"))) {
+ DPRINTF(E_ERROR, L_SCANNER, "Cannot open <%s>\n", fname);
+ goto _exit;
+ }
+
+ state = -1;
+ while (fgets(buf, sizeof(buf), fp)) {
+ int len = strlen(buf);
+
+ if (buf[len-1]=='\n') buf[len-1] = '\0';
+
+ if (state<0) {
+ if (isalpha(buf[0])) {
+ for (i=0; i<n; i++) {
+ if (!(strcmp(keys[i], buf))) {
+ state = i;
+ break;
+ }
+ }
+ }
+ }
+ else {
+ int found = 0;
+
+ if (isalpha(buf[0]) || buf[0]=='\0') {
+ state = -1;
+ continue;
+ }
+
+ p = buf;
+ while (isspace(*p)) p++;
+ if (*p == '\0') {
+ state = -1;
+ continue;
+ }
+ langid = p;
+ while (!isspace(*p)) p++;
+ *p++ = '\0';
+
+ if (!strcmp(lang, langid))
+ found = 1;
+ else if (strcmp(lang_en, langid))
+ continue;
+
+ while (isspace(*p)) p++;
+ if (*strs[state])
+ free(*strs[state]);
+ *strs[state] = strdup(p);
+
+ if (found)
+ state = -1;
+ }
+ }
+
+ for (i=0; i<n; i++) {
+ if (!*strs[i])
+ *strs[i] = defstr[i];
+ }
+
+ _exit:
+ free(keys);
+ free(strs);
+ free(defstr);
+}
diff --git a/tagutils/textutils.h b/tagutils/textutils.h
new file mode 100644
index 0000000..43e20e5
--- /dev/null
+++ b/tagutils/textutils.h
@@ -0,0 +1,29 @@
+//=========================================================================
+// FILENAME : textutils.h
+// DESCRIPTION : Header for textutils.c
+//=========================================================================
+// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
+//=========================================================================
+
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 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
+ */
+
+#include <stdarg.h>
+
+extern void urldecode(char *src);
+extern char * skipspaces(const char *src);
+extern int safe_atoi(char *s);
+extern int utf16le_to_utf8(char *dst, int n, __u16 utf16le);
+extern void fetch_string_txt(char *fname, char *lang, int n, ...);