diff options
author | Justin Maggard <jmaggard@users.sourceforge.net> | 2009-02-26 00:16:51 +0300 |
---|---|---|
committer | Justin Maggard <jmaggard@users.sourceforge.net> | 2009-02-26 00:16:51 +0300 |
commit | e5f3e012485a696175181ac53d5bd9f85ca4be75 (patch) | |
tree | 364d6fa38b92dfbb24c6a3a2c5cc8df4903b1d7b /tagutils | |
parent | 0212b7ced1220936dc7c14f4358bed5dc65dea17 (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.c | 118 | ||||
-rw-r--r-- | tagutils/misc.h | 52 | ||||
-rw-r--r-- | tagutils/tagutils-aac.c | 368 | ||||
-rw-r--r-- | tagutils/tagutils-aac.h | 27 | ||||
-rw-r--r-- | tagutils/tagutils-asf.c | 547 | ||||
-rw-r--r-- | tagutils/tagutils-asf.h | 352 | ||||
-rw-r--r-- | tagutils/tagutils-flc.c | 90 | ||||
-rw-r--r-- | tagutils/tagutils-flc.h | 24 | ||||
-rw-r--r-- | tagutils/tagutils-misc.c | 262 | ||||
-rw-r--r-- | tagutils/tagutils-mp3.c | 776 | ||||
-rw-r--r-- | tagutils/tagutils-mp3.h | 64 | ||||
-rw-r--r-- | tagutils/tagutils-ogg.c | 542 | ||||
-rw-r--r-- | tagutils/tagutils-ogg.h | 24 | ||||
-rw-r--r-- | tagutils/tagutils-plist.c | 140 | ||||
-rw-r--r-- | tagutils/tagutils.c | 289 | ||||
-rw-r--r-- | tagutils/tagutils.h | 120 | ||||
-rw-r--r-- | tagutils/textutils.c | 298 | ||||
-rw-r--r-- | tagutils/textutils.h | 29 |
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*)¤t_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*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "ART", 4) || + !memcmp(current_atom, "\xA9" "art", 4)) + psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "alb", 4)) + psong->album = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "cmt", 4)) + psong->comment = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "dir", 4)) + psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "wrt", 4)) + psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "grp", 4)) + psong->grouping = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "gen", 4)) + psong->genre = strdup((char*)¤t_data[16]); + else if(!memcmp(current_atom, "\xA9" "day", 4)) + psong->year = atoi((char*)¤t_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, ...); |