* 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.
This commit is contained in:
Justin Maggard
2009-02-25 21:16:51 +00:00
parent 2da2f6d4a1
commit b74e2d33f3
36 changed files with 4766 additions and 238 deletions

View File

@ -5,10 +5,11 @@ Homepage : http://sourceforge.net/projects/minidlna/
Prerequisites :
- taglib (including C bindings)
- libexif
- libgd (either libgd-xpm or libgd-noxpm)
- libgd2 (either libgd2-xpm or libgd2-noxpm)
- libid3tag
- libFLAC
- libvorbis
- sqlite3
- libavformat (the ffmpeg libraries)
- libuuid

View File

@ -30,11 +30,12 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \
options.o minissdp.o upnpevents.o \
sql.o utils.o metadata.o albumart.o scanner.o inotify.o \
tivo_utils.o tivo_beacon.o tivo_commands.o \
tagutils/textutils.o tagutils/misc.o tagutils/tagutils.o \
log.o
ALLOBJS = $(BASEOBJS) $(LNXOBJS)
LIBS = -lexif -ljpeg -ltag_c -lid3tag -lsqlite3 -lavformat -luuid -lgd
LIBS = -lexif -ljpeg -lsqlite3 -lavformat -lid3tag -lFLAC -luuid -lgd
TESTUPNPDESCGENOBJS = testupnpdescgen.o upnpdescgen.o
@ -104,5 +105,8 @@ upnpdescgen.o: config.h upnpdescgen.h minidlnapath.h upnpglobalvars.h
upnpdescgen.o: minidlnatypes.h upnpdescstrings.h
scanner.o: upnpglobalvars.h metadata.h utils.h sql.h scanner.h log.h
metadata.o: upnpglobalvars.h metadata.h albumart.h utils.h sql.h log.h
tagutils/misc.o: tagutils/misc.h
tagutils/textutils.o: tagutils/misc.h tagutils/textutils.h log.h
tagutils/tagutils.o: tagutils/tagutils-asf.c tagutils/tagutils-flc.c tagutils/tagutils-plist.c tagutils/tagutils-aac.c tagutils/tagutils-asf.h tagutils/tagutils-flc.h tagutils/tagutils-mp3.c tagutils/tagutils-ogg.c tagutils/tagutils-aac.h tagutils/tagutils.h tagutils/tagutils-mp3.h tagutils/tagutils-ogg.h log.h
sql.o: sql.h
log.o: log.h

7
TODO
View File

@ -2,9 +2,8 @@ Things left to do:
* Persistent HTTP connection (Keep-Alive) support
* PNG image support
* DLNA Profile Name (DLNA.ORG_PN) support for more audio files
* SortCriteria support
* Completely redo the logging scheme.
* Update scan support (do not require removing the database)
* Inotify support
* Upload support
Wishlist:
* Transcoding

View File

@ -15,7 +15,6 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#undef HAVE_LIBID3TAG
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -31,6 +30,7 @@
#include "upnpglobalvars.h"
#include "sql.h"
#include "utils.h"
#include "log.h"
/* For libjpeg error handling */
jmp_buf setjmp_buffer;
@ -41,6 +41,7 @@ static void libjpeg_error_handler(j_common_ptr cinfo)
return;
}
#if 0 // Not needed currently
int
check_res(int width, int height, char * dlna_pn)
{
@ -58,6 +59,131 @@ check_res(int width, int height, char * dlna_pn)
return 0;
return 1;
}
#endif
/* Use our own boxfilter resizer, because gdCopyImageResampled is slow,
* and gdCopyImageResized looks horrible when you downscale this much. */
#define N_FRAC 8
#define MASK_FRAC ((1 << N_FRAC) - 1)
#define ROUND2(v) (((v) + (1 << (N_FRAC - 1))) >> N_FRAC)
#define DIV(x, y) ( ((x) << (N_FRAC - 3)) / ((y) >> 3) )
static void
boxfilter_resize(gdImagePtr dst, gdImagePtr src,
int dstX, int dstY, int srcX, int srcY,
int dstW, int dstH, int srcW, int srcH)
{
int x, y;
int sy1, sy2, sx1, sx2;
if(!dst->trueColor)
{
gdImageCopyResized(dst, src, dstX, dstY, srcX, srcY, dstW, dstH,
srcW, srcH);
return;
}
for(y = dstY; y < (dstY + dstH); y++)
{
sy1 = (((y - dstY) * srcH) << N_FRAC) / dstH;
sy2 = (((y - dstY + 1) * srcH) << N_FRAC) / dstH;
for(x = dstX; x < (dstX + dstW); x++)
{
int sx, sy;
int spixels = 0;
int red = 0, green = 0, blue = 0, alpha = 0;
sx1 = (((x - dstX) * srcW) << N_FRAC) / dstW;
sx2 = (((x - dstX + 1) * srcW) << N_FRAC) / dstW;
sy = sy1;
do {
int yportion;
if((sy >> N_FRAC) == (sy1 >> N_FRAC))
{
yportion = (1 << N_FRAC) - (sy & MASK_FRAC);
if(yportion > sy2 - sy1)
{
yportion = sy2 - sy1;
}
sy = sy & ~MASK_FRAC;
}
else if(sy == (sy2 & ~MASK_FRAC))
{
yportion = sy2 & MASK_FRAC;
}
else
{
yportion = (1 << N_FRAC);
}
sx = sx1;
do {
int xportion;
int pcontribution;
int p;
if((sx >> N_FRAC) == (sx1 >> N_FRAC))
{
xportion = (1 << N_FRAC) - (sx & MASK_FRAC);
if(xportion > sx2 - sx1)
{
xportion = sx2 - sx1;
}
sx = sx & ~MASK_FRAC;
}
else if(sx == (sx2 & ~MASK_FRAC))
{
xportion = sx2 & MASK_FRAC;
}
else
{
xportion = (1 << N_FRAC);
}
if(xportion && yportion)
{
pcontribution = (xportion * yportion) >> N_FRAC;
p = gdImageGetTrueColorPixel(src, ROUND2(sx) + srcX, ROUND2(sy) + srcY);
if(pcontribution == (1 << N_FRAC))
{
// optimization for down-scaler, which many pixel has pcontribution=1
red += gdTrueColorGetRed(p) << N_FRAC;
green += gdTrueColorGetGreen(p) << N_FRAC;
blue += gdTrueColorGetBlue(p) << N_FRAC;
alpha += gdTrueColorGetAlpha(p) << N_FRAC;
spixels += (1 << N_FRAC);
}
else
{
red += gdTrueColorGetRed(p) * pcontribution;
green += gdTrueColorGetGreen(p) * pcontribution;
blue += gdTrueColorGetBlue(p) * pcontribution;
alpha += gdTrueColorGetAlpha(p) * pcontribution;
spixels += pcontribution;
}
}
sx += (1 << N_FRAC);
}
while(sx < sx2);
sy += (1 << N_FRAC);
}
while(sy < sy2);
if(spixels != 0)
{
red = DIV(red, spixels);
green = DIV(green, spixels);
blue = DIV(blue, spixels);
alpha = DIV(alpha, spixels);
}
/* Clamping to allow for rounding errors above */
if(red > (255 << N_FRAC))
red = (255 << N_FRAC);
if(green > (255 << N_FRAC))
green = (255 << N_FRAC);
if(blue > (255 << N_FRAC))
blue = (255 << N_FRAC);
if(alpha > (gdAlphaMax << N_FRAC))
alpha = (gdAlphaMax << N_FRAC);
gdImageSetPixel(dst, x, y,
gdTrueColorAlpha(ROUND2(red), ROUND2(green), ROUND2(blue), ROUND2(alpha)));
}
}
}
char *
save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int file, int size)
@ -104,11 +230,15 @@ save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int fi
fclose(dstfile);
goto error;
}
#if 0 // Try our box filter resizer for now
#ifdef __sparc__
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
#else
gdImageCopyResampled(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
#endif
#else
boxfilter_resize(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
#endif
gdImageJpeg(imdst, dstfile, 96);
fclose(dstfile);
gdImageDestroy(imsrc);
@ -120,10 +250,6 @@ error:
return NULL;
}
#ifdef HAVE_LIBID3TAG
#include <id3tag.h>
/* These next few functions are to allow loading JPEG data directly from memory for libjpeg.
* The standard functions only allow you to read from a file.
* This code comes from the JpgAlleg library, at http://wiki.allegro.cc/index.php?title=Libjpeg */
@ -184,68 +310,99 @@ jpeg_memory_src(j_decompress_ptr cinfo, unsigned char const *buffer, size_t bufs
src->pub.bytes_in_buffer = bufsize;
}
/* Simple, efficient hash function from Daniel J. Bernstein */
unsigned int DJBHash(const char* str, int len)
{
unsigned int hash = 5381;
unsigned int i = 0;
for(i = 0; i < len; str++, i++)
{
hash = ((hash << 5) + hash) + (*str);
}
return hash;
}
/* And our main album art functions */
char *
check_embedded_art(const char * path, char * dlna_pn)
check_embedded_art(const char * path, const char * image_data, int image_size)
{
struct id3_file *file;
struct id3_tag *pid3tag;
struct id3_frame *pid3frame;
id3_byte_t const *image;
id3_latin1_t const *mime;
id3_length_t length;
int index;
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
int width = 0, height = 0;
char * art_path = NULL;
char * cache_dir;
FILE * dstfile;
size_t nwritten;
static char last_path[PATH_MAX];
static unsigned int last_hash = 0;
unsigned int hash;
file = id3_file_open(path, ID3_FILE_MODE_READONLY);
if( !file )
return 0;
pid3tag = id3_file_tag(file);
for( index=0; (pid3frame = id3_tag_findframe(pid3tag, "", index)); index++ )
/* If the embedded image matches the embedded image from the last file we
* checked, just make a hard link. No use in storing it on the disk twice. */
hash = DJBHash(image_data, image_size);
if( hash == last_hash )
{
if( strcmp(pid3frame->id, "APIC") == 0 )
asprintf(&art_path, DB_PATH "/art_cache%s", path);
if( link(last_path, art_path) == 0 )
{
mime = id3_field_getlatin1(&pid3frame->fields[1]);
if( strcmp((char*)mime, "image/jpeg") && strcmp((char*)mime, "jpeg") )
continue;
image = id3_field_getbinarydata(&pid3frame->fields[4], &length);
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = libjpeg_error_handler;
jpeg_create_decompress(&cinfo);
if( setjmp(setjmp_buffer) )
goto error;
jpeg_memory_src(&cinfo, image, length);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
width = cinfo.output_width;
height = cinfo.output_height;
error:
jpeg_destroy_decompress(&cinfo);
break;
return(art_path);
}
else
{
printf("link failed\n");
free(art_path);
art_path = NULL;
}
}
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = libjpeg_error_handler;
jpeg_create_decompress(&cinfo);
if( setjmp(setjmp_buffer) )
goto error;
jpeg_memory_src(&cinfo, (unsigned char *)image_data, image_size);
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
width = cinfo.output_width;
height = cinfo.output_height;
error:
jpeg_destroy_decompress(&cinfo);
if( width > 160 || height > 160 )
{
art_path = save_resized_album_art((void *)image, path, width, height, 0, length);
art_path = save_resized_album_art((void *)image_data, path, width, height, 0, image_size);
}
else if( width > 0 && height > 0 )
{
art_path = path;
asprintf(&art_path, DB_PATH "/art_cache%s", path);
if( access(art_path, F_OK) == 0 )
return art_path;
cache_dir = strdup(art_path);
make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
free(cache_dir);
dstfile = fopen(art_path, "w");
if( !dstfile )
return NULL;
nwritten = fwrite((void *)image_data, image_size, 1, dstfile);
fclose(dstfile);
if( nwritten != image_size )
{
free(art_path);
remove(art_path);
return NULL;
}
}
id3_file_close(file);
DPRINTF(E_DEBUG, L_METADATA, "Found new embedded album art in %s\n", basename((char *)path));
last_hash = hash;
strcpy(last_path, art_path);
return(art_file);
return(art_path);
}
#endif // HAVE_LIBID3TAG
char *
check_for_album_file(char * dir, char * dlna_pn)
check_for_album_file(char * dir)
{
char * file = malloc(PATH_MAX);
struct album_art_name_s * album_art_name;
@ -290,7 +447,7 @@ check_for_album_file(char * dir, char * dlna_pn)
}
sqlite_int64
find_album_art(const char * path, char * dlna_pn)
find_album_art(const char * path, char * dlna_pn, const char * image_data, int image_size)
{
char * album_art = NULL;
char * sql;
@ -299,11 +456,8 @@ find_album_art(const char * path, char * dlna_pn)
sqlite_int64 ret = 0;
char * mypath = strdup(path);
#ifdef HAVE_LIBID3TAG
if( check_embedded_art(path, dlna_pn) || (album_art = check_for_album_file(dirname(mypath), dlna_pn)) )
#else
if( (album_art = check_for_album_file(dirname(mypath), dlna_pn)) )
#endif
if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) ||
(album_art = check_for_album_file(dirname(mypath))) )
{
strcpy(dlna_pn, "JPEG_TN");
sql = sqlite3_mprintf("SELECT ID from ALBUM_ART where PATH = '%q'", album_art ? album_art : path);
@ -314,12 +468,7 @@ find_album_art(const char * path, char * dlna_pn)
else
{
sqlite3_free(sql);
sql = sqlite3_mprintf( "INSERT into ALBUM_ART"
" (PATH, EMBEDDED) "
"VALUES"
" ('%s', %d);",
(album_art ? album_art : path),
(album_art ? 0 : 1) );
sql = sqlite3_mprintf("INSERT into ALBUM_ART (PATH) VALUES ('%q')", album_art);
if( sql_exec(db, sql) == SQLITE_OK )
ret = sqlite3_last_insert_rowid(db);
}

View File

@ -11,6 +11,6 @@
#define __ALBUMART_H__
sqlite_int64
find_album_art(const char * path, char * dlna_pn);
find_album_art(const char * path, char * dlna_pn, const char * image_data, int image_size);
#endif

6
log.c
View File

@ -104,7 +104,7 @@ log_init(const char *fname, const char *debug)
log_level[i] = default_log_level;
}
if (!fname) // use default i.e. stderr
if (!fname) // use default i.e. stdout
return 0;
if (!(fp = fopen(fname, "a")))
@ -126,7 +126,7 @@ log_err(int level, enum _log_facility facility, char *fname, int lineno, char *f
return;
if (!log_fp)
log_fp = stderr;
log_fp = stdout;
// user log
va_start(ap, fmt);
@ -135,7 +135,7 @@ log_err(int level, enum _log_facility facility, char *fname, int lineno, char *f
va_end(ap);
// timestamp
t = time(0);
t = time(NULL);
tm = localtime(&t);
fprintf(log_fp, "[%04d/%02d/%02d %02d:%02d:%02d] ",
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,

View File

@ -26,14 +26,13 @@
#include <sys/stat.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <taglib/tag_c.h>
#include <libexif/exif-loader.h>
#include <jpeglib.h>
#include <setjmp.h>
#include <avutil.h>
#include <avcodec.h>
#include <avformat.h>
#include "tagutils/tagutils.h"
#include "upnpglobalvars.h"
#include "metadata.h"
@ -126,47 +125,65 @@ GetFolderMetadata(const char * name, const char * path, const char * artist, con
sqlite_int64
GetAudioMetadata(const char * path, char * name)
{
off_t size = 0;
char date[16], duration[16], dlna_pn[24], mime[16];
char duration[16], mime[16], type[4];
struct stat file;
int seconds, minutes;
sqlite_int64 ret;
TagLib_File *audio_file;
TagLib_Tag *tag;
const TagLib_AudioProperties *properties;
char *sql;
char *title, *artist, *album, *genre, *comment;
char *title, *artist = NULL, *album = NULL, *genre = NULL, *comment = NULL, *date = NULL;
int free_flags = 0;
sqlite_int64 album_art;
sqlite_int64 album_art = 0;
char art_dlna_pn[9];
struct song_metadata song;
char *dlna_pn = NULL;
if ( stat(path, &file) == 0 )
size = file.st_size;
else
if ( stat(path, &file) != 0 )
return 0;
strip_ext(name);
taglib_set_strings_unicode(1);
audio_file = taglib_file_new(path);
if(audio_file == NULL)
if( ends_with(path, ".mp3") )
{
strcpy(type, "mp3");
strcpy(mime, "audio/mpeg");
}
else if( ends_with(path, ".m4a") || ends_with(path, ".mp4") ||
ends_with(path, ".aac") || ends_with(path, ".m4p") )
{
strcpy(type, "aac");
strcpy(mime, "audio/mp4");
}
else if( ends_with(path, ".wma") || ends_with(path, ".asf") )
{
strcpy(type, "asf");
strcpy(mime, "audio/x-ms-wma");
}
else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") )
{
strcpy(type, "flc");
strcpy(mime, "audio/x-flac");
}
else
{
DPRINTF(E_WARN, L_GENERAL, "Unhandled file extension on %s\n", path);
return 0;
}
tag = taglib_file_tag(audio_file);
properties = taglib_file_audioproperties(audio_file);
if( !properties )
if( readtags((char *)path, &song, &file, NULL, type) != 0 )
{
DPRINTF(E_WARN, L_GENERAL, "Cannot extract tags from %s\n", path);
return 0;
}
seconds = taglib_audioproperties_length(properties) % 60;
minutes = (taglib_audioproperties_length(properties) - seconds) / 60;
date[0] = '\0';
if( taglib_tag_year(tag) )
sprintf(date, "%04d-01-01", taglib_tag_year(tag));
sprintf(duration, "%d:%02d:%02d.000", minutes/60, minutes, seconds);
title = taglib_tag_title(tag);
if( strlen(title) )
if( song.dlna_pn )
asprintf(&dlna_pn, "%s;DLNA.ORG_OP=01", song.dlna_pn);
if( song.year )
asprintf(&date, "%04d-01-01", song.year);
sprintf(duration, "%d:%02d:%02d.%03d",
(song.song_length/3600000),
(song.song_length/60000),
(song.song_length/1000%60),
(song.song_length%1000));
title = song.title;
if( title )
{
title = trim(title);
if( index(title, '&') )
@ -179,9 +196,9 @@ GetAudioMetadata(const char * path, char * name)
{
title = name;
}
artist = taglib_tag_artist(tag);
if( strlen(artist) )
if( song.contributor[ROLE_ARTIST] )
{
artist = song.contributor[ROLE_ARTIST];
artist = trim(artist);
if( index(artist, '&') )
{
@ -189,13 +206,9 @@ GetAudioMetadata(const char * path, char * name)
artist = modifyString(strdup(artist), "&", "&amp;amp;", 0);
}
}
else
{
artist = NULL;
}
album = taglib_tag_album(tag);
if( strlen(album) )
if( song.album )
{
album = song.album;
album = trim(album);
if( index(album, '&') )
{
@ -203,13 +216,9 @@ GetAudioMetadata(const char * path, char * name)
album = modifyString(strdup(album), "&", "&amp;amp;", 0);
}
}
else
{
album = NULL;
}
genre = taglib_tag_genre(tag);
if( strlen(genre) )
if( song.genre )
{
genre = song.genre;
genre = trim(genre);
if( index(genre, '&') )
{
@ -217,13 +226,9 @@ GetAudioMetadata(const char * path, char * name)
genre = modifyString(strdup(genre), "&", "&amp;amp;", 0);
}
}
else
{
genre = NULL;
}
comment = taglib_tag_comment(tag);
if( strlen(comment) )
if( song.comment )
{
comment = song.comment;
comment = trim(comment);
if( index(comment, '&') )
{
@ -231,50 +236,28 @@ GetAudioMetadata(const char * path, char * name)
comment = modifyString(strdup(comment), "&", "&amp;amp;", 0);
}
}
else
{
comment = NULL;
}
/* Switch on audio file type */
if( ends_with(path, ".mp3") )
{
strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01");
strcpy(mime, "audio/mpeg");
}
else if( ends_with(path, ".flac") || ends_with(path, ".fla") || ends_with(path, ".flc") )
{
strcpy(mime, "audio/x-flac");
}
else if( ends_with(path, ".m4a") || ends_with(path, ".aac") )
{
//strcpy(dlna_pn, "MP3;DLNA.ORG_OP=01");
strcpy(mime, "audio/mp4");
}
album_art = find_album_art(path, art_dlna_pn);
album_art = find_album_art(path, art_dlna_pn, song.image, song.image_size);
sql = sqlite3_mprintf( "INSERT into DETAILS"
" (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
" TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME, ALBUM_ART, ART_DLNA_PN) "
"VALUES"
" (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s', %lld, %Q);",
path, size, duration, taglib_audioproperties_channels(properties),
taglib_audioproperties_bitrate(properties)*1024,
taglib_audioproperties_samplerate(properties),
strlen(date) ? date : NULL,
" (%Q, %d, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %Q, '%s', %lld, %Q);",
path, song.file_size, duration, song.channels, song.bitrate, song.samplerate, date,
title,
artist, artist,
album,
genre,
comment,
taglib_tag_track(tag),
song.track,
dlna_pn, mime,
album_art, album_art?art_dlna_pn:NULL );
taglib_tag_free_strings();
taglib_file_free(audio_file);
freetags(&song);
if( dlna_pn )
free(dlna_pn);
if( date )
free(date);
if( free_flags & FLAG_TITLE )
free(title);
if( free_flags & FLAG_ARTIST )
@ -297,6 +280,7 @@ GetAudioMetadata(const char * path, char * name)
ret = sqlite3_last_insert_rowid(db);
}
sqlite3_free(sql);
return ret;
}
@ -333,7 +317,7 @@ GetImageMetadata(const char * path, char * name)
date[0] = '\0';
model[0] = '\0';
DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path);
//DEBUG DPRINTF(E_DEBUG, L_METADATA, "Parsing %s...\n", path);
if ( stat(path, &file) == 0 )
size = file.st_size;
else
@ -460,36 +444,6 @@ GetImageMetadata(const char * path, char * name)
return ret;
}
typedef enum {
AAC_INVALID = 0,
AAC_MAIN = 1, /* AAC Main */
AAC_LC = 2, /* AAC Low complexity */
AAC_SSR = 3, /* AAC SSR */
AAC_LTP = 4, /* AAC Long term prediction */
AAC_HE = 5, /* AAC High efficiency (SBR) */
AAC_SCALE = 6, /* Scalable */
AAC_TWINVQ = 7, /* TwinVQ */
AAC_CELP = 8, /* CELP */
AAC_HVXC = 9, /* HVXC */
AAC_TTSI = 12, /* TTSI */
AAC_MS = 13, /* Main synthetic */
AAC_WAVE = 14, /* Wavetable synthesis */
AAC_MIDI = 15, /* General MIDI */
AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */
AAC_LC_ER = 17, /* AAC Low complexity with error recovery */
AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */
AAC_SCALE_ER = 20, /* AAC scalable with error recovery */
AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */
AAC_BSAC_ER = 22, /* BSAC with error recovery */
AAC_LD_ER = 23, /* AAC LD with error recovery */
AAC_CELP_ER = 24, /* CELP with error recovery */
AAC_HXVC_ER = 25, /* HXVC with error recovery */
AAC_HILN_ER = 26, /* HILN with error recovery */
AAC_PARAM_ER = 27, /* Parametric with error recovery */
AAC_SSC = 28, /* AAC SSC */
AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */
} aac_object_type_t;
sqlite_int64
GetVideoMetadata(const char * path, char * name)
{

View File

@ -31,6 +31,36 @@ typedef struct tsinfo_s {
int packet_size;
} tsinfo_t;
typedef enum {
AAC_INVALID = 0,
AAC_MAIN = 1, /* AAC Main */
AAC_LC = 2, /* AAC Low complexity */
AAC_SSR = 3, /* AAC SSR */
AAC_LTP = 4, /* AAC Long term prediction */
AAC_HE = 5, /* AAC High efficiency (SBR) */
AAC_SCALE = 6, /* Scalable */
AAC_TWINVQ = 7, /* TwinVQ */
AAC_CELP = 8, /* CELP */
AAC_HVXC = 9, /* HVXC */
AAC_TTSI = 12, /* TTSI */
AAC_MS = 13, /* Main synthetic */
AAC_WAVE = 14, /* Wavetable synthesis */
AAC_MIDI = 15, /* General MIDI */
AAC_FX = 16, /* Algorithmic Synthesis and Audio FX */
AAC_LC_ER = 17, /* AAC Low complexity with error recovery */
AAC_LTP_ER = 19, /* AAC Long term prediction with error recovery */
AAC_SCALE_ER = 20, /* AAC scalable with error recovery */
AAC_TWINVQ_ER = 21, /* TwinVQ with error recovery */
AAC_BSAC_ER = 22, /* BSAC with error recovery */
AAC_LD_ER = 23, /* AAC LD with error recovery */
AAC_CELP_ER = 24, /* CELP with error recovery */
AAC_HXVC_ER = 25, /* HXVC with error recovery */
AAC_HILN_ER = 26, /* HILN with error recovery */
AAC_PARAM_ER = 27, /* Parametric with error recovery */
AAC_SSC = 28, /* AAC SSC */
AAC_HE_L3 = 31, /* Reserved : seems to be HeAAC L3 */
} aac_object_type_t;
typedef enum {
NONE,
EMPTY,

View File

@ -614,7 +614,7 @@ main(int argc, char * * argv)
if( sql_get_table(db, "pragma user_version", &result, &rows, 0) == SQLITE_OK )
{
if( atoi(result[1]) != DB_VERSION ) {
printf("Database version mismatch; need to recreate...\n");
DPRINTF(E_WARN, L_GENERAL, "Database version mismatch; need to recreate...\n");
sqlite3_close(db);
unlink(DB_PATH "/files.db");
system("rm -rf " DB_PATH "/art_cache");

View File

@ -55,8 +55,10 @@ int
is_audio(const char * file)
{
return (ends_with(file, ".mp3") || ends_with(file, ".flac") ||
ends_with(file, ".wma") || ends_with(file, ".asf") ||
ends_with(file, ".fla") || ends_with(file, ".flc") ||
ends_with(file, ".m4a") || ends_with(file, ".aac"));
ends_with(file, ".m4a") || ends_with(file, ".aac") ||
ends_with(file, ".mp4") || ends_with(file, ".m4p"));
}
int
@ -156,7 +158,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strcmp(last_date.name, date_taken) == 0 )
{
last_date.objectID++;
DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
}
else
{
@ -164,7 +166,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
sprintf(last_date.parentID, "3$12$%llX", container>>32);
last_date.objectID = (int)container;
strcpy(last_date.name, date_taken);
DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
}
sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
@ -186,7 +188,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strcmp(last_camdate.name, date_taken) == 0 )
{
last_camdate.objectID++;
DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
}
else
{
@ -194,7 +196,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32);
last_camdate.objectID = (int)container;
strcpy(last_camdate.name, date_taken);
DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", cam, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
}
sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
@ -233,7 +235,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strcmp(album, last_album.name) == 0 )
{
last_album.objectID++;
DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
}
else
{
@ -241,7 +243,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
container = insert_container(album, "1$7", NULL, "album.musicAlbum", artist, genre, album_art, art_dlna_pn);
sprintf(last_album.parentID, "1$7$%llX", container>>32);
last_album.objectID = (int)container;
DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
}
sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
@ -263,7 +265,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strcmp(album?album:"Unknown", last_artistalbum.name) == 0 )
{
last_artistalbum.objectID++;
DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
}
else
{
@ -271,7 +273,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
sprintf(last_artistalbum.parentID, "%s$%llX", last_artist.parentID, container>>32);
last_artistalbum.objectID = (int)container;
strcpy(last_artistalbum.name, album?album:"Unknown");
DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
}
sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
@ -286,7 +288,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
if( strcmp(genre, last_genre.name) == 0 )
{
last_genre.objectID++;
DPRINTF(E_DEBUG, L_SCANNER, "Using last genre item: %s/%s/%X\n", last_genre.name, last_genre.parentID, last_genre.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last genre item: %s/%s/%X\n", last_genre.name, last_genre.parentID, last_genre.objectID);
}
else
{
@ -294,7 +296,7 @@ insert_containers(const char * name, const char *path, const char * refID, const
container = insert_container(genre, "1$5", NULL, "genre.musicGenre", NULL, NULL, NULL, NULL);
sprintf(last_genre.parentID, "1$5$%llX", container>>32);
last_genre.objectID = (int)container;
DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre item: %s/%s/%X\n", last_genre.name, last_genre.parentID, last_genre.objectID);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre item: %s/%s/%X\n", last_genre.name, last_genre.parentID, last_genre.objectID);
}
sql = sqlite3_mprintf( "INSERT into OBJECTS"
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
@ -410,7 +412,7 @@ insert_directory(const char * name, const char * path, const char * base, const
"VALUES"
" ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')",
base, parentID, objectID, base, parentID, refID, detailID, class, name);
DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
ret = sql_exec(db, sql);
sqlite3_free(sql);
if( refID )
@ -443,15 +445,17 @@ insert_file(char * name, const char * path, const char * parentID, int object)
strcpy(class, "item.audioItem.musicTrack");
detailID = GetAudioMetadata(path, name);
}
else if( is_video(name) )
if( !detailID && is_video(name) )
{
strcpy(base, VIDEO_DIR_ID);
strcpy(class, "item.videoItem");
detailID = GetVideoMetadata(path, name);
}
DPRINTF(E_DEBUG, L_SCANNER, "Got DetailID %lu!\n", detailID);
if( !detailID )
{
DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path);
return -1;
}
sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object);
@ -460,7 +464,7 @@ insert_file(char * name, const char * path, const char * parentID, int object)
"VALUES"
" ('%s', '%s%s', '%s', %lu, '%q')",
objectID, BROWSEDIR_ID, parentID, class, detailID, name);
DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
sql_exec(db, sql);
sqlite3_free(sql);
@ -482,7 +486,7 @@ insert_file(char * name, const char * path, const char * parentID, int object)
"VALUES"
" ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')",
base, parentID, object, base, parentID, objectID, class, detailID, name);
DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "SQL: %s\n", sql);
sql_exec(db, sql);
sqlite3_free(sql);
@ -556,19 +560,18 @@ CreateDatabase(void)
"MIME TEXT, "
"ALBUM_ART INTEGER DEFAULT 0, "
"ART_DLNA_PN TEXT DEFAULT NULL"
");");
")");
if( ret != SQLITE_OK )
goto sql_failed;
ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( "
"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
"PATH TEXT NOT NULL, "
"EMBEDDED BOOL DEFAULT 0"
");");
"PATH TEXT NOT NULL"
")");
if( ret != SQLITE_OK )
goto sql_failed;
ret = sql_exec(db, "CREATE TABLE SETTINGS ("
"UPDATE_ID INTEGER PRIMARY KEY"
");");
")");
if( ret != SQLITE_OK )
goto sql_failed;
ret = sql_exec(db, "INSERT into SETTINGS values (0)");

118
tagutils/misc.c Normal file
View File

@ -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
}

52
tagutils/misc.h Normal file
View File

@ -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

368
tagutils/tagutils-aac.c Normal file
View File

@ -0,0 +1,368 @@
//=========================================================================
// FILENAME : tagutils-aac.c
// DESCRIPTION : AAC metadata reader
//=========================================================================
// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
//=========================================================================
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* This file is derived from mt-daap project.
*/
// _mac_to_unix_time
static time_t
_mac_to_unix_time(int t)
{
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return (t - (365L * 66L * 24L * 60L * 60L + 17L * 60L * 60L * 24L) +
(tz.tz_minuteswest * 60));
}
// _aac_findatom:
static long
_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size)
{
long current_offset = 0;
int size;
char atom[4];
while(current_offset < max_offset)
{
if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int))
return -1;
size = ntohl(size);
if(size <= 7)
return -1;
if(fread(atom, 1, 4, fin) != 4)
return -1;
if(strncasecmp(atom, which_atom, 4) == 0)
{
*atom_size = size;
return current_offset;
}
fseek(fin, size - 8, SEEK_CUR);
current_offset += size;
}
return -1;
}
// _get_aactags
static int
_get_aactags(char *file, struct song_metadata *psong)
{
FILE *fin;
long atom_offset;
unsigned int atom_length;
long current_offset = 0;
int current_size;
char current_atom[4];
char *current_data;
int genre;
int len;
if(!(fin = fopen(file, "rb")))
{
DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file);
return -1;
}
fseek(fin, 0, SEEK_SET);
atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length);
if(atom_offset != -1)
{
while(current_offset < atom_length)
{
if(fread((void*)&current_size, 1, sizeof(int), fin) != sizeof(int))
break;
current_size = ntohl(current_size);
if(current_size <= 7) // something not right
break;
if(fread(current_atom, 1, 4, fin) != 4)
break;
len = current_size - 7; // too short
if(len < 22)
len = 22;
current_data = (char*)malloc(len); // extra byte
memset(current_data, 0x00, len);
if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
break;
if(!memcmp(current_atom, "\xA9" "nam", 4))
psong->title = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
!memcmp(current_atom, "\xA9" "art", 4))
psong->contributor[ROLE_ARTIST] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "alb", 4))
psong->album = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "cmt", 4))
psong->comment = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "dir", 4))
psong->contributor[ROLE_CONDUCTOR] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "wrt", 4))
psong->contributor[ROLE_COMPOSER] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "grp", 4))
psong->grouping = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "gen", 4))
psong->genre = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "day", 4))
psong->year = atoi((char*)&current_data[16]);
else if(!memcmp(current_atom, "tmpo", 4))
psong->bpm = (current_data[16] << 8) | current_data[17];
else if(!memcmp(current_atom, "trkn", 4))
{
psong->track = (current_data[18] << 8) | current_data[19];
psong->total_tracks = (current_data[20] << 8) | current_data[21];
}
else if(!memcmp(current_atom, "disk", 4))
{
psong->disc = (current_data[18] << 8) | current_data[19];
psong->total_discs = (current_data[20] << 8) | current_data[21];
}
else if(!memcmp(current_atom, "gnre", 4))
{
genre = current_data[17] - 1;
if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
genre = WINAMP_GENRE_UNKNOWN;
psong->genre = strdup(winamp_genre[genre]);
}
else if(!memcmp(current_atom, "cpil", 4))
{
psong->compilation = current_data[16];
}
free(current_data);
current_offset += current_size;
}
}
fclose(fin);
if(atom_offset == -1)
return -1;
return 0;
}
// aac_lookforatom
static off_t
_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length)
{
long atom_offset;
off_t file_size;
char *cur_p, *end_p;
char atom_name[5];
fseek(aac_fp, 0, SEEK_END);
file_size = ftell(aac_fp);
rewind(aac_fp);
end_p = atom_path;
while(*end_p != '\0')
{
end_p++;
}
atom_name[4] = '\0';
cur_p = atom_path;
while(cur_p)
{
if((end_p - cur_p) < 4)
{
return -1;
}
strncpy(atom_name, cur_p, 4);
atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length);
if(atom_offset == -1)
{
return -1;
}
cur_p = strchr(cur_p, ':');
if(cur_p != NULL)
{
cur_p++;
if(!strcmp(atom_name, "meta"))
{
fseek(aac_fp, 4, SEEK_CUR);
}
else if(!strcmp(atom_name, "stsd"))
{
fseek(aac_fp, 8, SEEK_CUR);
}
else if(!strcmp(atom_name, "mp4a"))
{
fseek(aac_fp, 28, SEEK_CUR);
}
}
}
// return position of 'size:atom'
return ftell(aac_fp) - 8;
}
// _get_aacfileinfo
int
_get_aacfileinfo(char *file, struct song_metadata *psong)
{
FILE *infile;
long atom_offset;
int atom_length;
int sample_size;
int samples;
unsigned int bitrate;
off_t file_size;
int ms;
unsigned char buffer[2];
int time = 0;
aac_object_type_t profile_id = 0;
psong->vbr_scale = -1;
if(!(infile = fopen(file, "rb")))
{
DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
return -1;
}
fseek(infile, 0, SEEK_END);
file_size = ftell(infile);
fseek(infile, 0, SEEK_SET);
// move to 'mvhd' atom
atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length);
if(atom_offset != -1)
{
fseek(infile, 8, SEEK_CUR);
fread((void *)&time, sizeof(int), 1, infile);
time = ntohl(time);
// slimserver prefer to use filesystem time
//psong->time_modified = _mac_to_unix_time(time);
fread((void*)&sample_size, 1, sizeof(int), infile);
fread((void*)&samples, 1, sizeof(int), infile);
sample_size = ntohl(sample_size);
samples = ntohl(samples);
// avoid overflowing on large sample_sizes (90000)
ms = 1000;
while((ms > 9) && (!(sample_size % 10)))
{
sample_size /= 10;
ms /= 10;
}
// unit = ms
psong->song_length = (int)((samples * ms) / sample_size);
}
psong->bitrate = 0;
// get samplerate from 'mp4a' (not from 'mdhd')
atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length);
if(atom_offset != -1)
{
fseek(infile, atom_offset + 32, SEEK_SET);
fread(buffer, sizeof(unsigned char), 2, infile);
psong->samplerate = (buffer[0] << 8) | (buffer[1]);
fseek(infile, 2, SEEK_CUR);
// get bitrate fomr 'esds'
atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
if(atom_offset != -1)
{
fseek(infile, atom_offset + 26, SEEK_CUR); // +22 is max bitrate, +26 is average bitrate
fread((void *)&bitrate, sizeof(unsigned int), 1, infile);
psong->bitrate = ntohl(bitrate);
fseek(infile, 5, SEEK_CUR); // 5 bytes past bitrate is setup data
fread((void *)&buffer, 2, 1, infile);
profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
/* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
samples = ((buffer[1] >> 3) & 0xF);
psong->channels = (samples == 7 ? 8 : samples);
}
}
atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
psong->audio_size = atom_length - 8;
psong->audio_offset = atom_offset;
if(!psong->bitrate)
{
DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate.\n");
if((atom_offset != -1) && (psong->song_length))
{
psong->bitrate = atom_length * 1000 / psong->song_length / 128;
}
}
//DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
switch( profile_id )
{
case AAC_LC:
case AAC_LC_ER:
if( psong->samplerate < 8000 || psong->samplerate > 48000 )
{
DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
psong->samplerate);
break;
}
/* AAC @ Level 1/2 */
if( psong->channels <= 2 && psong->bitrate <= 320000 )
asprintf(&(psong->dlna_pn), "AAC_ISO_320");
else if( psong->channels <= 2 && psong->bitrate <= 576000 )
asprintf(&(psong->dlna_pn), "AAC_ISO");
else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
asprintf(&(psong->dlna_pn), "AAC_MULT5_ISO");
else
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
psong->channels, psong->bitrate);
break;
default:
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", profile_id);
break;
}
fclose(infile);
return 0;
}

27
tagutils/tagutils-aac.h Normal file
View File

@ -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));

547
tagutils/tagutils-asf.c Normal file
View File

@ -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;
}

352
tagutils/tagutils-asf.h Normal file
View File

@ -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);

90
tagutils/tagutils-flc.c Normal file
View File

@ -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;
}

24
tagutils/tagutils-flc.h Normal file
View File

@ -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);

262
tagutils/tagutils-misc.c Normal file
View File

@ -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);
}
}

776
tagutils/tagutils-mp3.c Normal file
View File

@ -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;
}

64
tagutils/tagutils-mp3.h Normal file
View File

@ -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 */
};

542
tagutils/tagutils-ogg.c Normal file
View File

@ -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;
}

24
tagutils/tagutils-ogg.h Normal file
View File

@ -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);

140
tagutils/tagutils-plist.c Normal file
View File

@ -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;
}

289
tagutils/tagutils.c Normal file
View File

@ -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;
}
}

120
tagutils/tagutils.h Normal file
View File

@ -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

298
tagutils/textutils.c Normal file
View File

@ -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);
}

29
tagutils/textutils.h Normal file
View File

@ -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, ...);

View File

@ -26,6 +26,7 @@
*
* See the file "COPYING" for more details.
*/
#include "config.h"
#ifdef ENABLE_TIVO
#include <stdlib.h>
#include <stdio.h>
@ -99,35 +100,29 @@ OpenAndConfTivoBeaconSocket()
*/
uint32_t getBcastAddress( void )
{
struct ifreq ifr;
struct sockaddr_in *sin;
int s, rval;
static int ifacePrinted = 0;
struct ifreq ifr;
struct sockaddr_in *sin;
int s, rval;
s = socket( AF_INET, SOCK_DGRAM, 0 );
if ( s < 0 )
{
return INADDR_BROADCAST;
}
s = socket( AF_INET, SOCK_DGRAM, 0 );
if ( s < 0 )
{
return INADDR_BROADCAST;
}
strcpy( ifr.ifr_name, "eth0" );
rval = ioctl( s, SIOCGIFBRDADDR, &ifr );
if ( rval < 0 )
{
close(s);
return INADDR_BROADCAST;
}
strcpy( ifr.ifr_name, "eth0" );
rval = ioctl( s, SIOCGIFBRDADDR, &ifr );
if ( rval < 0 )
{
close(s);
return INADDR_BROADCAST;
}
sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
if( !ifacePrinted )
{
printf( "Interface: %s broadcast addr %s \n", "eth0", inet_ntoa(sin->sin_addr) );
ifacePrinted = 1;
}
sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
close(s);
DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s \n", "eth0", inet_ntoa(sin->sin_addr) );
close(s);
return ntohl((uint32_t)(sin->sin_addr.s_addr));
return ntohl((uint32_t)(sin->sin_addr.s_addr));
}
/*
@ -139,7 +134,6 @@ sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast)
{
char * mesg;
int mesg_len;
time_t now = time(NULL);
mesg_len = asprintf(&mesg, "TiVoConnect=1\n"
"swversion=%s\n"
@ -151,7 +145,7 @@ sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast)
"1.0",
broadcast ? "broadcast" : "connected",
uuidvalue, friendly_name, runtime_vars.port);
printf("Sending beacon at %s%s", ctime(&now), mesg);
DPRINTF(E_DEBUG, L_TIVO, "Sending TiVo beacon\n");
sendto(fd, mesg, mesg_len, 0, (struct sockaddr*)client, len);
free(mesg);
}

View File

@ -1,3 +1,4 @@
#include "config.h"
#ifdef ENABLE_TIVO
/*
* * A saved copy of a beacon from another tivo or another server

View File

@ -1,5 +1,7 @@
#include "config.h"
#ifdef ENABLE_TIVO
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tivo_utils.h"
@ -28,7 +30,248 @@ SendRootContainer(struct upnphttp * h)
"<Details>"
"<ContentType>x-container/tivo-photos</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>Pictures</Title>"
"<Title>Pictures on %s</Title>"
"</Details>"
"<Links>"
"<Content>"
"<Url>/TiVoConnect?Command=QueryContainer&amp;Container=3/Pictures on %s</Url>"
"</Content>"
"</Links>"
"</Item>"
"<Item>"
"<Details>"
"<ContentType>x-container/tivo-music</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>Music on %s</Title>"
"</Details>"
"<Links>"
"<Content>"
"<Url>/TiVoConnect?Command=QueryContainer&amp;Container=1</Url>"
"</Content>"
"</Links>"
"</Item>"
"</TiVoContainer>", friendly_name, friendly_name, friendly_name, friendly_name);
BuildResp_upnphttp(h, resp, len);
SendResp_upnphttp(h);
}
#if 0
int callback(void *args, int argc, char **argv, char **azColName)
{
struct Response *passed_args = (struct Response *)args;
char *id = argv[1], *parent = argv[2], *refID = argv[3], *class = argv[4], *detailID = argv[5], *size = argv[9], *title = argv[10],
*duration = argv[11], *bitrate = argv[12], *sampleFrequency = argv[13], *artist = argv[14], *album = argv[15],
*genre = argv[16], *comment = argv[17], *nrAudioChannels = argv[18], *track = argv[19], *date = argv[20], *resolution = argv[21],
*tn = argv[22], *creator = argv[23], *dlna_pn = argv[24], *mime = argv[25], *album_art = argv[26], *art_dlna_pn = argv[27];
char dlna_buf[64];
char str_buf[4096];
char **result;
int ret;
passed_args->total++;
if( passed_args->requested && (passed_args->returned >= passed_args->requested) )
return 0;
passed_args->returned++;
if( dlna_pn )
sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
else
strcpy(dlna_buf, "*");
if( strncmp(class, "item", 4) == 0 )
{
if( passed_args->client == EXbox )
{
if( strcmp(mime, "video/divx") == 0 )
{
mime[6] = 'a';
mime[7] = 'v';
mime[8] = 'i';
mime[9] = '\0';
}
}
sprintf(str_buf, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
strcat(passed_args->resp, str_buf);
if( refID && (!passed_args->filter || strstr(passed_args->filter, "@refID")) ) {
sprintf(str_buf, " refID=\"%s\"", refID);
strcat(passed_args->resp, str_buf);
}
sprintf(str_buf, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;"
"&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
title, class);
strcat(passed_args->resp, str_buf);
if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) {
sprintf(str_buf, "&lt;dc:description&gt;%s&lt;/dc:description&gt;", comment);
strcat(passed_args->resp, str_buf);
}
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
sprintf(str_buf, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
strcat(passed_args->resp, str_buf);
}
if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) {
sprintf(str_buf, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
strcat(passed_args->resp, str_buf);
}
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
sprintf(str_buf, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
strcat(passed_args->resp, str_buf);
}
if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) {
sprintf(str_buf, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
strcat(passed_args->resp, str_buf);
}
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
sprintf(str_buf, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
strcat(passed_args->resp, str_buf);
}
if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) {
sprintf(str_buf, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
strcat(passed_args->resp, str_buf);
}
if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) {
strcat(passed_args->resp, "&lt;upnp:albumArtURI ");
if( !passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID") ) {
sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"", art_dlna_pn);
strcat(passed_args->resp, str_buf);
}
sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s.jpg&lt;/upnp:albumArtURI&gt;",
lan_addr[0].str, runtime_vars.port, album_art);
strcat(passed_args->resp, str_buf);
}
if( !passed_args->filter || strstr(passed_args->filter, "res") ) {
strcat(passed_args->resp, "&lt;res ");
if( size && (!passed_args->filter || strstr(passed_args->filter, "res@size")) ) {
sprintf(str_buf, "size=\"%s\" ", size);
strcat(passed_args->resp, str_buf);
}
if( duration && (!passed_args->filter || strstr(passed_args->filter, "res@duration")) ) {
sprintf(str_buf, "duration=\"%s\" ", duration);
strcat(passed_args->resp, str_buf);
}
if( bitrate && (!passed_args->filter || strstr(passed_args->filter, "res@bitrate")) ) {
sprintf(str_buf, "bitrate=\"%s\" ", bitrate);
strcat(passed_args->resp, str_buf);
}
if( sampleFrequency && (!passed_args->filter || strstr(passed_args->filter, "res@sampleFrequency")) ) {
sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency);
strcat(passed_args->resp, str_buf);
}
if( nrAudioChannels && (!passed_args->filter || strstr(passed_args->filter, "res@nrAudioChannels")) ) {
sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels);
strcat(passed_args->resp, str_buf);
}
if( resolution && (!passed_args->filter || strstr(passed_args->filter, "res@resolution")) ) {
sprintf(str_buf, "resolution=\"%s\" ", resolution);
strcat(passed_args->resp, str_buf);
}
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
"http://%s:%d/MediaItems/%s.dat"
"&lt;/res&gt;",
mime, dlna_buf, lan_addr[0].str, runtime_vars.port, detailID);
#if 0 //JPEG_RESIZE
if( dlna_pn && (strncmp(dlna_pn, "JPEG_LRG", 8) == 0) ) {
strcat(passed_args->resp, str_buf);
sprintf(str_buf, "&lt;res "
"protocolInfo=\"http-get:*:%s:%s\"&gt;"
"http://%s:%d/Resized/%s"
"&lt;/res&gt;",
mime, "DLNA.ORG_PN=JPEG_SM", lan_addr[0].str, runtime_vars.port, id);
}
#endif
if( tn && atoi(tn) && dlna_pn ) {
strcat(passed_args->resp, str_buf);
strcat(passed_args->resp, "&lt;res ");
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
"http://%s:%d/Thumbnails/%s.dat"
"&lt;/res&gt;",
mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
}
strcat(passed_args->resp, str_buf);
}
strcpy(str_buf, "&lt;/item&gt;");
}
else if( strncmp(class, "container", 9) == 0 )
{
sprintf(str_buf, "SELECT count(ID) from OBJECTS where PARENT_ID = '%s';", id);
ret = sql_get_table(db, str_buf, &result, NULL, NULL);
sprintf(str_buf, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
strcat(passed_args->resp, str_buf);
if( !passed_args->filter || strstr(passed_args->filter, "@childCount"))
{
sprintf(str_buf, "childCount=\"%s\"", result[1]);
strcat(passed_args->resp, str_buf);
}
/* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
{
if( !passed_args->filter || strstr(passed_args->filter, "upnp:searchClass") )
{
strcat(passed_args->resp, "&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
"&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
}
}
sprintf(str_buf, "&gt;"
"&lt;dc:title&gt;%s&lt;/dc:title&gt;"
"&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
title, class);
strcat(passed_args->resp, str_buf);
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
sprintf(str_buf, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
strcat(passed_args->resp, str_buf);
}
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
sprintf(str_buf, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
strcat(passed_args->resp, str_buf);
}
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
sprintf(str_buf, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
strcat(passed_args->resp, str_buf);
}
if( album_art && atoi(album_art) && (!passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI")) ) {
strcat(passed_args->resp, "&lt;upnp:albumArtURI ");
if( !passed_args->filter || strstr(passed_args->filter, "upnp:albumArtURI@dlna:profileID") ) {
sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlnaorg:metadata-1-0/\"", art_dlna_pn);
strcat(passed_args->resp, str_buf);
}
sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s.jpg&lt;/upnp:albumArtURI&gt;",
lan_addr[0].str, runtime_vars.port, album_art);
strcat(passed_args->resp, str_buf);
}
sprintf(str_buf, "&lt;/container&gt;");
sqlite3_free_table(result);
}
strcat(passed_args->resp, str_buf);
return 0;
}
#endif
void
SendContainer(struct upnphttp * h, const char * objectID, const char * title, int itemStart, int itemCount)
{
char * resp;
int len;
len = asprintf(&resp, "<?xml version='1.0' encoding='UTF-8' ?>\n"
"<TiVoContainer>"
"<Details>"
"<ContentType>x-container/tivo-%s</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<TotalDuration>0</TotalDuration>"
"<TotalItems>2</TotalItems>"
"<Title>%s</Title>"
"</Details>"
"<ItemStart>%d</ItemStart>"
"<ItemCount>%d</ItemCount>"
"<Item>"
"<Details>"
"<ContentType>x-container/tivo-photos</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>Pictures on %s</Title>"
"</Details>"
"<Links>"
"<Content>"
@ -40,7 +283,7 @@ SendRootContainer(struct upnphttp * h)
"<Details>"
"<ContentType>x-container/tivo-music</ContentType>"
"<SourceFormat>x-container/folder</SourceFormat>"
"<Title>Music</Title>"
"<Title>Music on %s</Title>"
"</Details>"
"<Links>"
"<Content>"
@ -48,7 +291,9 @@ SendRootContainer(struct upnphttp * h)
"</Content>"
"</Links>"
"</Item>"
"</TiVoContainer>", friendly_name);
"</TiVoContainer>",
(objectID[0]=='1') ? "music":"photos",
title, itemStart, itemCount, title, title);
BuildResp_upnphttp(h, resp, len);
SendResp_upnphttp(h);
}
@ -60,6 +305,7 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
char *key, *val;
char *saveptr, *item;
char *command = NULL, *container = NULL;
int itemStart=0, itemCount=0;
path = decodeString(orig_path);
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
@ -83,12 +329,29 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
{
container = val;
}
else if( strcasecmp(key, "itemstart") == 0 )
{
itemStart = atoi(val);
}
else if( strcasecmp(key, "itemcount") == 0 )
{
itemCount = atoi(val);
}
item = strtok_r( NULL, "&", &saveptr );
}
if( !container || (strcmp(container, "/") == 0) )
if( strcmp(command, "QueryContainer") == 0 )
{
SendRootContainer(h);
if( !container || (strcmp(container, "/") == 0) )
{
SendRootContainer(h);
}
else
{
val = container;
key = strsep(&val, "/");
SendContainer(h, container, val, itemStart, itemCount);
}
}
CloseSocket_upnphttp(h);
}

View File

@ -1,3 +1,4 @@
#include "config.h"
#ifdef ENABLE_TIVO
void
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path);

View File

@ -1,3 +1,4 @@
#include "config.h"
#ifdef ENABLE_TIVO
#include <stdlib.h>
#include <string.h>

View File

@ -1,3 +1,4 @@
#include "config.h"
#ifdef ENABLE_TIVO
char *
decodeString(const char * string);

View File

@ -30,9 +30,14 @@
"http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AC3_T;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \
"http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=01;DLNA.ORG_CI=0," \
"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," \
"http-get:*:audio/x-ms-wma:*," \
"http-get:*:audio/wav:*," \
"http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01," \
"http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01," \
"http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAPRO;DLNA.ORG_OP=01," \
"http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01," \
"http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01," \
"http-get:*:audio/mp4:DLNA.ORG_PN=AAC_MULT5_ISO;DLNA.ORG_OP=01," \
"http-get:*:audio/mp4:*," \
"http-get:*:audio/wav:*," \
"http-get:*:audio/x-aiff:*," \
"http-get:*:audio/x-flac:*," \
"http-get:*:application/ogg:*," \

View File

@ -478,7 +478,7 @@ static void
ProcessHttpQuery_upnphttp(struct upnphttp * h)
{
char HttpCommand[16];
char HttpUrl[128];
char HttpUrl[256];
char * HttpVer;
char * p;
int i;
@ -496,7 +496,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
while(*p!='/')
p++;
}
for(i = 0; i<127 && *p != ' ' && *p != '\r'; i++)
for(i = 0; i<255 && *p != ' ' && *p != '\r'; i++)
HttpUrl[i] = *(p++);
HttpUrl[i] = '\0';
while(*p==' ')