* 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:
5
INSTALL
5
INSTALL
@ -5,10 +5,11 @@ Homepage : http://sourceforge.net/projects/minidlna/
|
|||||||
|
|
||||||
Prerequisites :
|
Prerequisites :
|
||||||
|
|
||||||
- taglib (including C bindings)
|
|
||||||
- libexif
|
- libexif
|
||||||
- libgd (either libgd-xpm or libgd-noxpm)
|
- libgd2 (either libgd2-xpm or libgd2-noxpm)
|
||||||
- libid3tag
|
- libid3tag
|
||||||
|
- libFLAC
|
||||||
|
- libvorbis
|
||||||
- sqlite3
|
- sqlite3
|
||||||
- libavformat (the ffmpeg libraries)
|
- libavformat (the ffmpeg libraries)
|
||||||
- libuuid
|
- libuuid
|
||||||
|
6
Makefile
6
Makefile
@ -30,11 +30,12 @@ BASEOBJS = minidlna.o upnphttp.o upnpdescgen.o upnpsoap.o \
|
|||||||
options.o minissdp.o upnpevents.o \
|
options.o minissdp.o upnpevents.o \
|
||||||
sql.o utils.o metadata.o albumart.o scanner.o inotify.o \
|
sql.o utils.o metadata.o albumart.o scanner.o inotify.o \
|
||||||
tivo_utils.o tivo_beacon.o tivo_commands.o \
|
tivo_utils.o tivo_beacon.o tivo_commands.o \
|
||||||
|
tagutils/textutils.o tagutils/misc.o tagutils/tagutils.o \
|
||||||
log.o
|
log.o
|
||||||
|
|
||||||
ALLOBJS = $(BASEOBJS) $(LNXOBJS)
|
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
|
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
|
upnpdescgen.o: minidlnatypes.h upnpdescstrings.h
|
||||||
scanner.o: upnpglobalvars.h metadata.h utils.h sql.h scanner.h log.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
|
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
|
sql.o: sql.h
|
||||||
log.o: log.h
|
log.o: log.h
|
||||||
|
7
TODO
7
TODO
@ -2,9 +2,8 @@ Things left to do:
|
|||||||
|
|
||||||
* Persistent HTTP connection (Keep-Alive) support
|
* Persistent HTTP connection (Keep-Alive) support
|
||||||
* PNG image support
|
* PNG image support
|
||||||
* DLNA Profile Name (DLNA.ORG_PN) support for more audio files
|
|
||||||
* SortCriteria support
|
* SortCriteria support
|
||||||
* Completely redo the logging scheme.
|
|
||||||
* Update scan support (do not require removing the database)
|
|
||||||
* Inotify support
|
|
||||||
* Upload support
|
* Upload support
|
||||||
|
|
||||||
|
Wishlist:
|
||||||
|
* Transcoding
|
||||||
|
263
albumart.c
263
albumart.c
@ -15,7 +15,6 @@
|
|||||||
* along with this program; if not, write to the Free Software
|
* along with this program; if not, write to the Free Software
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
#undef HAVE_LIBID3TAG
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -31,6 +30,7 @@
|
|||||||
#include "upnpglobalvars.h"
|
#include "upnpglobalvars.h"
|
||||||
#include "sql.h"
|
#include "sql.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
/* For libjpeg error handling */
|
/* For libjpeg error handling */
|
||||||
jmp_buf setjmp_buffer;
|
jmp_buf setjmp_buffer;
|
||||||
@ -41,6 +41,7 @@ static void libjpeg_error_handler(j_common_ptr cinfo)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0 // Not needed currently
|
||||||
int
|
int
|
||||||
check_res(int width, int height, char * dlna_pn)
|
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 0;
|
||||||
return 1;
|
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 *
|
char *
|
||||||
save_resized_album_art(void * ptr, const char * path, int srcw, int srch, int file, int size)
|
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);
|
fclose(dstfile);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
#if 0 // Try our box filter resizer for now
|
||||||
#ifdef __sparc__
|
#ifdef __sparc__
|
||||||
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
gdImageCopyResized(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
#else
|
#else
|
||||||
gdImageCopyResampled(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
gdImageCopyResampled(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
#endif
|
#endif
|
||||||
|
#else
|
||||||
|
boxfilter_resize(imdst, imsrc, 0, 0, 0, 0, dstw, dsth, imsrc->sx, imsrc->sy);
|
||||||
|
#endif
|
||||||
gdImageJpeg(imdst, dstfile, 96);
|
gdImageJpeg(imdst, dstfile, 96);
|
||||||
fclose(dstfile);
|
fclose(dstfile);
|
||||||
gdImageDestroy(imsrc);
|
gdImageDestroy(imsrc);
|
||||||
@ -120,10 +250,6 @@ error:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBID3TAG
|
|
||||||
#include <id3tag.h>
|
|
||||||
|
|
||||||
/* These next few functions are to allow loading JPEG data directly from memory for libjpeg.
|
/* 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.
|
* 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 */
|
* 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;
|
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 */
|
/* And our main album art functions */
|
||||||
char *
|
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_decompress_struct cinfo;
|
||||||
struct jpeg_error_mgr jerr;
|
struct jpeg_error_mgr jerr;
|
||||||
int width = 0, height = 0;
|
int width = 0, height = 0;
|
||||||
char * art_path = NULL;
|
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 the embedded image matches the embedded image from the last file we
|
||||||
if( !file )
|
* checked, just make a hard link. No use in storing it on the disk twice. */
|
||||||
return 0;
|
hash = DJBHash(image_data, image_size);
|
||||||
|
if( hash == last_hash )
|
||||||
pid3tag = id3_file_tag(file);
|
|
||||||
|
|
||||||
for( index=0; (pid3frame = id3_tag_findframe(pid3tag, "", index)); index++ )
|
|
||||||
{
|
{
|
||||||
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]);
|
return(art_path);
|
||||||
if( strcmp((char*)mime, "image/jpeg") && strcmp((char*)mime, "jpeg") )
|
}
|
||||||
continue;
|
else
|
||||||
image = id3_field_getbinarydata(&pid3frame->fields[4], &length);
|
{
|
||||||
|
printf("link failed\n");
|
||||||
cinfo.err = jpeg_std_error(&jerr);
|
free(art_path);
|
||||||
jerr.error_exit = libjpeg_error_handler;
|
art_path = NULL;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 )
|
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 )
|
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 *
|
char *
|
||||||
check_for_album_file(char * dir, char * dlna_pn)
|
check_for_album_file(char * dir)
|
||||||
{
|
{
|
||||||
char * file = malloc(PATH_MAX);
|
char * file = malloc(PATH_MAX);
|
||||||
struct album_art_name_s * album_art_name;
|
struct album_art_name_s * album_art_name;
|
||||||
@ -290,7 +447,7 @@ check_for_album_file(char * dir, char * dlna_pn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
sqlite_int64
|
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 * album_art = NULL;
|
||||||
char * sql;
|
char * sql;
|
||||||
@ -299,11 +456,8 @@ find_album_art(const char * path, char * dlna_pn)
|
|||||||
sqlite_int64 ret = 0;
|
sqlite_int64 ret = 0;
|
||||||
char * mypath = strdup(path);
|
char * mypath = strdup(path);
|
||||||
|
|
||||||
#ifdef HAVE_LIBID3TAG
|
if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) ||
|
||||||
if( check_embedded_art(path, dlna_pn) || (album_art = check_for_album_file(dirname(mypath), dlna_pn)) )
|
(album_art = check_for_album_file(dirname(mypath))) )
|
||||||
#else
|
|
||||||
if( (album_art = check_for_album_file(dirname(mypath), dlna_pn)) )
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
strcpy(dlna_pn, "JPEG_TN");
|
strcpy(dlna_pn, "JPEG_TN");
|
||||||
sql = sqlite3_mprintf("SELECT ID from ALBUM_ART where PATH = '%q'", album_art ? album_art : path);
|
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
|
else
|
||||||
{
|
{
|
||||||
sqlite3_free(sql);
|
sqlite3_free(sql);
|
||||||
sql = sqlite3_mprintf( "INSERT into ALBUM_ART"
|
sql = sqlite3_mprintf("INSERT into ALBUM_ART (PATH) VALUES ('%q')", album_art);
|
||||||
" (PATH, EMBEDDED) "
|
|
||||||
"VALUES"
|
|
||||||
" ('%s', %d);",
|
|
||||||
(album_art ? album_art : path),
|
|
||||||
(album_art ? 0 : 1) );
|
|
||||||
if( sql_exec(db, sql) == SQLITE_OK )
|
if( sql_exec(db, sql) == SQLITE_OK )
|
||||||
ret = sqlite3_last_insert_rowid(db);
|
ret = sqlite3_last_insert_rowid(db);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,6 @@
|
|||||||
#define __ALBUMART_H__
|
#define __ALBUMART_H__
|
||||||
|
|
||||||
sqlite_int64
|
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
|
#endif
|
||||||
|
6
log.c
6
log.c
@ -104,7 +104,7 @@ log_init(const char *fname, const char *debug)
|
|||||||
log_level[i] = default_log_level;
|
log_level[i] = default_log_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fname) // use default i.e. stderr
|
if (!fname) // use default i.e. stdout
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!(fp = fopen(fname, "a")))
|
if (!(fp = fopen(fname, "a")))
|
||||||
@ -126,7 +126,7 @@ log_err(int level, enum _log_facility facility, char *fname, int lineno, char *f
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!log_fp)
|
if (!log_fp)
|
||||||
log_fp = stderr;
|
log_fp = stdout;
|
||||||
|
|
||||||
// user log
|
// user log
|
||||||
va_start(ap, fmt);
|
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);
|
va_end(ap);
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
t = time(0);
|
t = time(NULL);
|
||||||
tm = localtime(&t);
|
tm = localtime(&t);
|
||||||
fprintf(log_fp, "[%04d/%02d/%02d %02d:%02d:%02d] ",
|
fprintf(log_fp, "[%04d/%02d/%02d %02d:%02d:%02d] ",
|
||||||
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
|
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
|
||||||
|
178
metadata.c
178
metadata.c
@ -26,14 +26,13 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#include <taglib/tag_c.h>
|
|
||||||
#include <libexif/exif-loader.h>
|
#include <libexif/exif-loader.h>
|
||||||
#include <jpeglib.h>
|
#include <jpeglib.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
#include <avutil.h>
|
#include <avutil.h>
|
||||||
#include <avcodec.h>
|
#include <avcodec.h>
|
||||||
#include <avformat.h>
|
#include <avformat.h>
|
||||||
|
#include "tagutils/tagutils.h"
|
||||||
|
|
||||||
#include "upnpglobalvars.h"
|
#include "upnpglobalvars.h"
|
||||||
#include "metadata.h"
|
#include "metadata.h"
|
||||||
@ -126,47 +125,65 @@ GetFolderMetadata(const char * name, const char * path, const char * artist, con
|
|||||||
sqlite_int64
|
sqlite_int64
|
||||||
GetAudioMetadata(const char * path, char * name)
|
GetAudioMetadata(const char * path, char * name)
|
||||||
{
|
{
|
||||||
off_t size = 0;
|
char duration[16], mime[16], type[4];
|
||||||
char date[16], duration[16], dlna_pn[24], mime[16];
|
|
||||||
struct stat file;
|
struct stat file;
|
||||||
int seconds, minutes;
|
|
||||||
sqlite_int64 ret;
|
sqlite_int64 ret;
|
||||||
TagLib_File *audio_file;
|
|
||||||
TagLib_Tag *tag;
|
|
||||||
const TagLib_AudioProperties *properties;
|
|
||||||
char *sql;
|
char *sql;
|
||||||
char *title, *artist, *album, *genre, *comment;
|
char *title, *artist = NULL, *album = NULL, *genre = NULL, *comment = NULL, *date = NULL;
|
||||||
int free_flags = 0;
|
int free_flags = 0;
|
||||||
sqlite_int64 album_art;
|
sqlite_int64 album_art = 0;
|
||||||
char art_dlna_pn[9];
|
char art_dlna_pn[9];
|
||||||
|
struct song_metadata song;
|
||||||
|
char *dlna_pn = NULL;
|
||||||
|
|
||||||
if ( stat(path, &file) == 0 )
|
if ( stat(path, &file) != 0 )
|
||||||
size = file.st_size;
|
|
||||||
else
|
|
||||||
return 0;
|
return 0;
|
||||||
strip_ext(name);
|
strip_ext(name);
|
||||||
|
|
||||||
taglib_set_strings_unicode(1);
|
if( ends_with(path, ".mp3") )
|
||||||
|
{
|
||||||
audio_file = taglib_file_new(path);
|
strcpy(type, "mp3");
|
||||||
if(audio_file == NULL)
|
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;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
tag = taglib_file_tag(audio_file);
|
if( readtags((char *)path, &song, &file, NULL, type) != 0 )
|
||||||
properties = taglib_file_audioproperties(audio_file);
|
{
|
||||||
if( !properties )
|
DPRINTF(E_WARN, L_GENERAL, "Cannot extract tags from %s\n", path);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
seconds = taglib_audioproperties_length(properties) % 60;
|
if( song.dlna_pn )
|
||||||
minutes = (taglib_audioproperties_length(properties) - seconds) / 60;
|
asprintf(&dlna_pn, "%s;DLNA.ORG_OP=01", song.dlna_pn);
|
||||||
|
if( song.year )
|
||||||
date[0] = '\0';
|
asprintf(&date, "%04d-01-01", song.year);
|
||||||
if( taglib_tag_year(tag) )
|
sprintf(duration, "%d:%02d:%02d.%03d",
|
||||||
sprintf(date, "%04d-01-01", taglib_tag_year(tag));
|
(song.song_length/3600000),
|
||||||
sprintf(duration, "%d:%02d:%02d.000", minutes/60, minutes, seconds);
|
(song.song_length/60000),
|
||||||
|
(song.song_length/1000%60),
|
||||||
title = taglib_tag_title(tag);
|
(song.song_length%1000));
|
||||||
if( strlen(title) )
|
title = song.title;
|
||||||
|
if( title )
|
||||||
{
|
{
|
||||||
title = trim(title);
|
title = trim(title);
|
||||||
if( index(title, '&') )
|
if( index(title, '&') )
|
||||||
@ -179,9 +196,9 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
{
|
{
|
||||||
title = name;
|
title = name;
|
||||||
}
|
}
|
||||||
artist = taglib_tag_artist(tag);
|
if( song.contributor[ROLE_ARTIST] )
|
||||||
if( strlen(artist) )
|
|
||||||
{
|
{
|
||||||
|
artist = song.contributor[ROLE_ARTIST];
|
||||||
artist = trim(artist);
|
artist = trim(artist);
|
||||||
if( index(artist, '&') )
|
if( index(artist, '&') )
|
||||||
{
|
{
|
||||||
@ -189,13 +206,9 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
artist = modifyString(strdup(artist), "&", "&amp;", 0);
|
artist = modifyString(strdup(artist), "&", "&amp;", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
if( song.album )
|
||||||
{
|
|
||||||
artist = NULL;
|
|
||||||
}
|
|
||||||
album = taglib_tag_album(tag);
|
|
||||||
if( strlen(album) )
|
|
||||||
{
|
{
|
||||||
|
album = song.album;
|
||||||
album = trim(album);
|
album = trim(album);
|
||||||
if( index(album, '&') )
|
if( index(album, '&') )
|
||||||
{
|
{
|
||||||
@ -203,13 +216,9 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
album = modifyString(strdup(album), "&", "&amp;", 0);
|
album = modifyString(strdup(album), "&", "&amp;", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
if( song.genre )
|
||||||
{
|
|
||||||
album = NULL;
|
|
||||||
}
|
|
||||||
genre = taglib_tag_genre(tag);
|
|
||||||
if( strlen(genre) )
|
|
||||||
{
|
{
|
||||||
|
genre = song.genre;
|
||||||
genre = trim(genre);
|
genre = trim(genre);
|
||||||
if( index(genre, '&') )
|
if( index(genre, '&') )
|
||||||
{
|
{
|
||||||
@ -217,13 +226,9 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
genre = modifyString(strdup(genre), "&", "&amp;", 0);
|
genre = modifyString(strdup(genre), "&", "&amp;", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
if( song.comment )
|
||||||
{
|
|
||||||
genre = NULL;
|
|
||||||
}
|
|
||||||
comment = taglib_tag_comment(tag);
|
|
||||||
if( strlen(comment) )
|
|
||||||
{
|
{
|
||||||
|
comment = song.comment;
|
||||||
comment = trim(comment);
|
comment = trim(comment);
|
||||||
if( index(comment, '&') )
|
if( index(comment, '&') )
|
||||||
{
|
{
|
||||||
@ -231,50 +236,28 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
comment = modifyString(strdup(comment), "&", "&amp;", 0);
|
comment = modifyString(strdup(comment), "&", "&amp;", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
comment = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
album_art = find_album_art(path, art_dlna_pn, song.image, song.image_size);
|
||||||
/* 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);
|
|
||||||
|
|
||||||
sql = sqlite3_mprintf( "INSERT into DETAILS"
|
sql = sqlite3_mprintf( "INSERT into DETAILS"
|
||||||
" (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
|
" (PATH, SIZE, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE,"
|
||||||
" TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME, ALBUM_ART, ART_DLNA_PN) "
|
" TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, TRACK, DLNA_PN, MIME, ALBUM_ART, ART_DLNA_PN) "
|
||||||
"VALUES"
|
"VALUES"
|
||||||
" (%Q, %llu, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, '%s', '%s', %lld, %Q);",
|
" (%Q, %d, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %Q, '%s', %lld, %Q);",
|
||||||
path, size, duration, taglib_audioproperties_channels(properties),
|
path, song.file_size, duration, song.channels, song.bitrate, song.samplerate, date,
|
||||||
taglib_audioproperties_bitrate(properties)*1024,
|
|
||||||
taglib_audioproperties_samplerate(properties),
|
|
||||||
strlen(date) ? date : NULL,
|
|
||||||
title,
|
title,
|
||||||
artist, artist,
|
artist, artist,
|
||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
comment,
|
comment,
|
||||||
taglib_tag_track(tag),
|
song.track,
|
||||||
dlna_pn, mime,
|
dlna_pn, mime,
|
||||||
album_art, album_art?art_dlna_pn:NULL );
|
album_art, album_art?art_dlna_pn:NULL );
|
||||||
taglib_tag_free_strings();
|
freetags(&song);
|
||||||
taglib_file_free(audio_file);
|
if( dlna_pn )
|
||||||
|
free(dlna_pn);
|
||||||
|
if( date )
|
||||||
|
free(date);
|
||||||
if( free_flags & FLAG_TITLE )
|
if( free_flags & FLAG_TITLE )
|
||||||
free(title);
|
free(title);
|
||||||
if( free_flags & FLAG_ARTIST )
|
if( free_flags & FLAG_ARTIST )
|
||||||
@ -297,6 +280,7 @@ GetAudioMetadata(const char * path, char * name)
|
|||||||
ret = sqlite3_last_insert_rowid(db);
|
ret = sqlite3_last_insert_rowid(db);
|
||||||
}
|
}
|
||||||
sqlite3_free(sql);
|
sqlite3_free(sql);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +317,7 @@ GetImageMetadata(const char * path, char * name)
|
|||||||
date[0] = '\0';
|
date[0] = '\0';
|
||||||
model[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 )
|
if ( stat(path, &file) == 0 )
|
||||||
size = file.st_size;
|
size = file.st_size;
|
||||||
else
|
else
|
||||||
@ -460,36 +444,6 @@ GetImageMetadata(const char * path, char * name)
|
|||||||
return ret;
|
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
|
sqlite_int64
|
||||||
GetVideoMetadata(const char * path, char * name)
|
GetVideoMetadata(const char * path, char * name)
|
||||||
{
|
{
|
||||||
|
30
metadata.h
30
metadata.h
@ -31,6 +31,36 @@ typedef struct tsinfo_s {
|
|||||||
int packet_size;
|
int packet_size;
|
||||||
} tsinfo_t;
|
} 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 {
|
typedef enum {
|
||||||
NONE,
|
NONE,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
|
@ -614,7 +614,7 @@ main(int argc, char * * argv)
|
|||||||
if( sql_get_table(db, "pragma user_version", &result, &rows, 0) == SQLITE_OK )
|
if( sql_get_table(db, "pragma user_version", &result, &rows, 0) == SQLITE_OK )
|
||||||
{
|
{
|
||||||
if( atoi(result[1]) != DB_VERSION ) {
|
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);
|
sqlite3_close(db);
|
||||||
unlink(DB_PATH "/files.db");
|
unlink(DB_PATH "/files.db");
|
||||||
system("rm -rf " DB_PATH "/art_cache");
|
system("rm -rf " DB_PATH "/art_cache");
|
||||||
|
45
scanner.c
45
scanner.c
@ -55,8 +55,10 @@ int
|
|||||||
is_audio(const char * file)
|
is_audio(const char * file)
|
||||||
{
|
{
|
||||||
return (ends_with(file, ".mp3") || ends_with(file, ".flac") ||
|
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, ".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
|
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 )
|
if( strcmp(last_date.name, date_taken) == 0 )
|
||||||
{
|
{
|
||||||
last_date.objectID++;
|
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
|
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);
|
sprintf(last_date.parentID, "3$12$%llX", container>>32);
|
||||||
last_date.objectID = (int)container;
|
last_date.objectID = (int)container;
|
||||||
strcpy(last_date.name, date_taken);
|
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"
|
sql = sqlite3_mprintf( "INSERT into OBJECTS"
|
||||||
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
|
" (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 )
|
if( strcmp(last_camdate.name, date_taken) == 0 )
|
||||||
{
|
{
|
||||||
last_camdate.objectID++;
|
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
|
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);
|
sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32);
|
||||||
last_camdate.objectID = (int)container;
|
last_camdate.objectID = (int)container;
|
||||||
strcpy(last_camdate.name, date_taken);
|
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"
|
sql = sqlite3_mprintf( "INSERT into OBJECTS"
|
||||||
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
|
" (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 )
|
if( strcmp(album, last_album.name) == 0 )
|
||||||
{
|
{
|
||||||
last_album.objectID++;
|
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
|
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);
|
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);
|
sprintf(last_album.parentID, "1$7$%llX", container>>32);
|
||||||
last_album.objectID = (int)container;
|
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"
|
sql = sqlite3_mprintf( "INSERT into OBJECTS"
|
||||||
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
|
" (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 )
|
if( strcmp(album?album:"Unknown", last_artistalbum.name) == 0 )
|
||||||
{
|
{
|
||||||
last_artistalbum.objectID++;
|
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
|
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);
|
sprintf(last_artistalbum.parentID, "%s$%llX", last_artist.parentID, container>>32);
|
||||||
last_artistalbum.objectID = (int)container;
|
last_artistalbum.objectID = (int)container;
|
||||||
strcpy(last_artistalbum.name, album?album:"Unknown");
|
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"
|
sql = sqlite3_mprintf( "INSERT into OBJECTS"
|
||||||
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
|
" (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 )
|
if( strcmp(genre, last_genre.name) == 0 )
|
||||||
{
|
{
|
||||||
last_genre.objectID++;
|
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
|
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);
|
container = insert_container(genre, "1$5", NULL, "genre.musicGenre", NULL, NULL, NULL, NULL);
|
||||||
sprintf(last_genre.parentID, "1$5$%llX", container>>32);
|
sprintf(last_genre.parentID, "1$5$%llX", container>>32);
|
||||||
last_genre.objectID = (int)container;
|
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"
|
sql = sqlite3_mprintf( "INSERT into OBJECTS"
|
||||||
" (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
|
" (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"
|
"VALUES"
|
||||||
" ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')",
|
" ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')",
|
||||||
base, parentID, objectID, base, parentID, refID, detailID, class, name);
|
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);
|
ret = sql_exec(db, sql);
|
||||||
sqlite3_free(sql);
|
sqlite3_free(sql);
|
||||||
if( refID )
|
if( refID )
|
||||||
@ -443,15 +445,17 @@ insert_file(char * name, const char * path, const char * parentID, int object)
|
|||||||
strcpy(class, "item.audioItem.musicTrack");
|
strcpy(class, "item.audioItem.musicTrack");
|
||||||
detailID = GetAudioMetadata(path, name);
|
detailID = GetAudioMetadata(path, name);
|
||||||
}
|
}
|
||||||
else if( is_video(name) )
|
if( !detailID && is_video(name) )
|
||||||
{
|
{
|
||||||
strcpy(base, VIDEO_DIR_ID);
|
strcpy(base, VIDEO_DIR_ID);
|
||||||
strcpy(class, "item.videoItem");
|
strcpy(class, "item.videoItem");
|
||||||
detailID = GetVideoMetadata(path, name);
|
detailID = GetVideoMetadata(path, name);
|
||||||
}
|
}
|
||||||
DPRINTF(E_DEBUG, L_SCANNER, "Got DetailID %lu!\n", detailID);
|
|
||||||
if( !detailID )
|
if( !detailID )
|
||||||
|
{
|
||||||
|
DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path);
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object);
|
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"
|
"VALUES"
|
||||||
" ('%s', '%s%s', '%s', %lu, '%q')",
|
" ('%s', '%s%s', '%s', %lu, '%q')",
|
||||||
objectID, BROWSEDIR_ID, parentID, class, detailID, name);
|
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);
|
sql_exec(db, sql);
|
||||||
sqlite3_free(sql);
|
sqlite3_free(sql);
|
||||||
|
|
||||||
@ -482,7 +486,7 @@ insert_file(char * name, const char * path, const char * parentID, int object)
|
|||||||
"VALUES"
|
"VALUES"
|
||||||
" ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')",
|
" ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')",
|
||||||
base, parentID, object, base, parentID, objectID, class, detailID, name);
|
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);
|
sql_exec(db, sql);
|
||||||
sqlite3_free(sql);
|
sqlite3_free(sql);
|
||||||
|
|
||||||
@ -556,19 +560,18 @@ CreateDatabase(void)
|
|||||||
"MIME TEXT, "
|
"MIME TEXT, "
|
||||||
"ALBUM_ART INTEGER DEFAULT 0, "
|
"ALBUM_ART INTEGER DEFAULT 0, "
|
||||||
"ART_DLNA_PN TEXT DEFAULT NULL"
|
"ART_DLNA_PN TEXT DEFAULT NULL"
|
||||||
");");
|
")");
|
||||||
if( ret != SQLITE_OK )
|
if( ret != SQLITE_OK )
|
||||||
goto sql_failed;
|
goto sql_failed;
|
||||||
ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( "
|
ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( "
|
||||||
"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
"PATH TEXT NOT NULL, "
|
"PATH TEXT NOT NULL"
|
||||||
"EMBEDDED BOOL DEFAULT 0"
|
")");
|
||||||
");");
|
|
||||||
if( ret != SQLITE_OK )
|
if( ret != SQLITE_OK )
|
||||||
goto sql_failed;
|
goto sql_failed;
|
||||||
ret = sql_exec(db, "CREATE TABLE SETTINGS ("
|
ret = sql_exec(db, "CREATE TABLE SETTINGS ("
|
||||||
"UPDATE_ID INTEGER PRIMARY KEY"
|
"UPDATE_ID INTEGER PRIMARY KEY"
|
||||||
");");
|
")");
|
||||||
if( ret != SQLITE_OK )
|
if( ret != SQLITE_OK )
|
||||||
goto sql_failed;
|
goto sql_failed;
|
||||||
ret = sql_exec(db, "INSERT into SETTINGS values (0)");
|
ret = sql_exec(db, "INSERT into SETTINGS values (0)");
|
||||||
|
118
tagutils/misc.c
Normal file
118
tagutils/misc.c
Normal 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
52
tagutils/misc.h
Normal 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
368
tagutils/tagutils-aac.c
Normal 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*)¤t_size, 1, sizeof(int), fin) != sizeof(int))
|
||||||
|
break;
|
||||||
|
|
||||||
|
current_size = ntohl(current_size);
|
||||||
|
|
||||||
|
if(current_size <= 7) // something not right
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(fread(current_atom, 1, 4, fin) != 4)
|
||||||
|
break;
|
||||||
|
|
||||||
|
len = current_size - 7; // too short
|
||||||
|
if(len < 22)
|
||||||
|
len = 22;
|
||||||
|
|
||||||
|
current_data = (char*)malloc(len); // extra byte
|
||||||
|
memset(current_data, 0x00, len);
|
||||||
|
|
||||||
|
if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(!memcmp(current_atom, "\xA9" "nam", 4))
|
||||||
|
psong->title = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
|
||||||
|
!memcmp(current_atom, "\xA9" "art", 4))
|
||||||
|
psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "alb", 4))
|
||||||
|
psong->album = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "cmt", 4))
|
||||||
|
psong->comment = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "dir", 4))
|
||||||
|
psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "wrt", 4))
|
||||||
|
psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "grp", 4))
|
||||||
|
psong->grouping = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "gen", 4))
|
||||||
|
psong->genre = strdup((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "\xA9" "day", 4))
|
||||||
|
psong->year = atoi((char*)¤t_data[16]);
|
||||||
|
else if(!memcmp(current_atom, "tmpo", 4))
|
||||||
|
psong->bpm = (current_data[16] << 8) | current_data[17];
|
||||||
|
else if(!memcmp(current_atom, "trkn", 4))
|
||||||
|
{
|
||||||
|
psong->track = (current_data[18] << 8) | current_data[19];
|
||||||
|
psong->total_tracks = (current_data[20] << 8) | current_data[21];
|
||||||
|
}
|
||||||
|
else if(!memcmp(current_atom, "disk", 4))
|
||||||
|
{
|
||||||
|
psong->disc = (current_data[18] << 8) | current_data[19];
|
||||||
|
psong->total_discs = (current_data[20] << 8) | current_data[21];
|
||||||
|
}
|
||||||
|
else if(!memcmp(current_atom, "gnre", 4))
|
||||||
|
{
|
||||||
|
genre = current_data[17] - 1;
|
||||||
|
if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
|
||||||
|
genre = WINAMP_GENRE_UNKNOWN;
|
||||||
|
psong->genre = strdup(winamp_genre[genre]);
|
||||||
|
}
|
||||||
|
else if(!memcmp(current_atom, "cpil", 4))
|
||||||
|
{
|
||||||
|
psong->compilation = current_data[16];
|
||||||
|
}
|
||||||
|
|
||||||
|
free(current_data);
|
||||||
|
current_offset += current_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fin);
|
||||||
|
|
||||||
|
if(atom_offset == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// aac_lookforatom
|
||||||
|
static off_t
|
||||||
|
_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length)
|
||||||
|
{
|
||||||
|
long atom_offset;
|
||||||
|
off_t file_size;
|
||||||
|
char *cur_p, *end_p;
|
||||||
|
char atom_name[5];
|
||||||
|
|
||||||
|
fseek(aac_fp, 0, SEEK_END);
|
||||||
|
file_size = ftell(aac_fp);
|
||||||
|
rewind(aac_fp);
|
||||||
|
|
||||||
|
end_p = atom_path;
|
||||||
|
while(*end_p != '\0')
|
||||||
|
{
|
||||||
|
end_p++;
|
||||||
|
}
|
||||||
|
atom_name[4] = '\0';
|
||||||
|
cur_p = atom_path;
|
||||||
|
|
||||||
|
while(cur_p)
|
||||||
|
{
|
||||||
|
if((end_p - cur_p) < 4)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
strncpy(atom_name, cur_p, 4);
|
||||||
|
atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length);
|
||||||
|
if(atom_offset == -1)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cur_p = strchr(cur_p, ':');
|
||||||
|
if(cur_p != NULL)
|
||||||
|
{
|
||||||
|
cur_p++;
|
||||||
|
|
||||||
|
if(!strcmp(atom_name, "meta"))
|
||||||
|
{
|
||||||
|
fseek(aac_fp, 4, SEEK_CUR);
|
||||||
|
}
|
||||||
|
else if(!strcmp(atom_name, "stsd"))
|
||||||
|
{
|
||||||
|
fseek(aac_fp, 8, SEEK_CUR);
|
||||||
|
}
|
||||||
|
else if(!strcmp(atom_name, "mp4a"))
|
||||||
|
{
|
||||||
|
fseek(aac_fp, 28, SEEK_CUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return position of 'size:atom'
|
||||||
|
return ftell(aac_fp) - 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _get_aacfileinfo
|
||||||
|
int
|
||||||
|
_get_aacfileinfo(char *file, struct song_metadata *psong)
|
||||||
|
{
|
||||||
|
FILE *infile;
|
||||||
|
long atom_offset;
|
||||||
|
int atom_length;
|
||||||
|
int sample_size;
|
||||||
|
int samples;
|
||||||
|
unsigned int bitrate;
|
||||||
|
off_t file_size;
|
||||||
|
int ms;
|
||||||
|
unsigned char buffer[2];
|
||||||
|
int time = 0;
|
||||||
|
aac_object_type_t profile_id = 0;
|
||||||
|
|
||||||
|
psong->vbr_scale = -1;
|
||||||
|
|
||||||
|
if(!(infile = fopen(file, "rb")))
|
||||||
|
{
|
||||||
|
DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(infile, 0, SEEK_END);
|
||||||
|
file_size = ftell(infile);
|
||||||
|
fseek(infile, 0, SEEK_SET);
|
||||||
|
|
||||||
|
// move to 'mvhd' atom
|
||||||
|
atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length);
|
||||||
|
if(atom_offset != -1)
|
||||||
|
{
|
||||||
|
fseek(infile, 8, SEEK_CUR);
|
||||||
|
fread((void *)&time, sizeof(int), 1, infile);
|
||||||
|
time = ntohl(time);
|
||||||
|
// slimserver prefer to use filesystem time
|
||||||
|
//psong->time_modified = _mac_to_unix_time(time);
|
||||||
|
fread((void*)&sample_size, 1, sizeof(int), infile);
|
||||||
|
fread((void*)&samples, 1, sizeof(int), infile);
|
||||||
|
|
||||||
|
sample_size = ntohl(sample_size);
|
||||||
|
samples = ntohl(samples);
|
||||||
|
|
||||||
|
// avoid overflowing on large sample_sizes (90000)
|
||||||
|
ms = 1000;
|
||||||
|
while((ms > 9) && (!(sample_size % 10)))
|
||||||
|
{
|
||||||
|
sample_size /= 10;
|
||||||
|
ms /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unit = ms
|
||||||
|
psong->song_length = (int)((samples * ms) / sample_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
psong->bitrate = 0;
|
||||||
|
|
||||||
|
// get samplerate from 'mp4a' (not from 'mdhd')
|
||||||
|
atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length);
|
||||||
|
if(atom_offset != -1)
|
||||||
|
{
|
||||||
|
fseek(infile, atom_offset + 32, SEEK_SET);
|
||||||
|
|
||||||
|
fread(buffer, sizeof(unsigned char), 2, infile);
|
||||||
|
|
||||||
|
psong->samplerate = (buffer[0] << 8) | (buffer[1]);
|
||||||
|
|
||||||
|
fseek(infile, 2, SEEK_CUR);
|
||||||
|
|
||||||
|
// get bitrate fomr 'esds'
|
||||||
|
atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
|
||||||
|
|
||||||
|
if(atom_offset != -1)
|
||||||
|
{
|
||||||
|
fseek(infile, atom_offset + 26, SEEK_CUR); // +22 is max bitrate, +26 is average bitrate
|
||||||
|
fread((void *)&bitrate, sizeof(unsigned int), 1, infile);
|
||||||
|
psong->bitrate = ntohl(bitrate);
|
||||||
|
|
||||||
|
fseek(infile, 5, SEEK_CUR); // 5 bytes past bitrate is setup data
|
||||||
|
fread((void *)&buffer, 2, 1, infile);
|
||||||
|
profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
|
||||||
|
/* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
|
||||||
|
samples = ((buffer[1] >> 3) & 0xF);
|
||||||
|
psong->channels = (samples == 7 ? 8 : samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
|
||||||
|
psong->audio_size = atom_length - 8;
|
||||||
|
psong->audio_offset = atom_offset;
|
||||||
|
|
||||||
|
if(!psong->bitrate)
|
||||||
|
{
|
||||||
|
DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate.\n");
|
||||||
|
if((atom_offset != -1) && (psong->song_length))
|
||||||
|
{
|
||||||
|
psong->bitrate = atom_length * 1000 / psong->song_length / 128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
|
||||||
|
switch( profile_id )
|
||||||
|
{
|
||||||
|
case AAC_LC:
|
||||||
|
case AAC_LC_ER:
|
||||||
|
if( psong->samplerate < 8000 || psong->samplerate > 48000 )
|
||||||
|
{
|
||||||
|
DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
|
||||||
|
psong->samplerate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* AAC @ Level 1/2 */
|
||||||
|
if( psong->channels <= 2 && psong->bitrate <= 320000 )
|
||||||
|
asprintf(&(psong->dlna_pn), "AAC_ISO_320");
|
||||||
|
else if( psong->channels <= 2 && psong->bitrate <= 576000 )
|
||||||
|
asprintf(&(psong->dlna_pn), "AAC_ISO");
|
||||||
|
else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
|
||||||
|
asprintf(&(psong->dlna_pn), "AAC_MULT5_ISO");
|
||||||
|
else
|
||||||
|
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
|
||||||
|
psong->channels, psong->bitrate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", profile_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(infile);
|
||||||
|
return 0;
|
||||||
|
}
|
27
tagutils/tagutils-aac.h
Normal file
27
tagutils/tagutils-aac.h
Normal 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
547
tagutils/tagutils-asf.c
Normal 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
352
tagutils/tagutils-asf.h
Normal 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
90
tagutils/tagutils-flc.c
Normal 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
24
tagutils/tagutils-flc.h
Normal 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
262
tagutils/tagutils-misc.c
Normal 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
776
tagutils/tagutils-mp3.c
Normal 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
64
tagutils/tagutils-mp3.h
Normal 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
542
tagutils/tagutils-ogg.c
Normal 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
24
tagutils/tagutils-ogg.h
Normal 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
140
tagutils/tagutils-plist.c
Normal 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
289
tagutils/tagutils.c
Normal 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
120
tagutils/tagutils.h
Normal 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
298
tagutils/textutils.c
Normal 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
29
tagutils/textutils.h
Normal 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, ...);
|
@ -26,6 +26,7 @@
|
|||||||
*
|
*
|
||||||
* See the file "COPYING" for more details.
|
* See the file "COPYING" for more details.
|
||||||
*/
|
*/
|
||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -99,35 +100,29 @@ OpenAndConfTivoBeaconSocket()
|
|||||||
*/
|
*/
|
||||||
uint32_t getBcastAddress( void )
|
uint32_t getBcastAddress( void )
|
||||||
{
|
{
|
||||||
struct ifreq ifr;
|
struct ifreq ifr;
|
||||||
struct sockaddr_in *sin;
|
struct sockaddr_in *sin;
|
||||||
int s, rval;
|
int s, rval;
|
||||||
static int ifacePrinted = 0;
|
|
||||||
|
|
||||||
s = socket( AF_INET, SOCK_DGRAM, 0 );
|
s = socket( AF_INET, SOCK_DGRAM, 0 );
|
||||||
if ( s < 0 )
|
if ( s < 0 )
|
||||||
{
|
{
|
||||||
return INADDR_BROADCAST;
|
return INADDR_BROADCAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
strcpy( ifr.ifr_name, "eth0" );
|
strcpy( ifr.ifr_name, "eth0" );
|
||||||
rval = ioctl( s, SIOCGIFBRDADDR, &ifr );
|
rval = ioctl( s, SIOCGIFBRDADDR, &ifr );
|
||||||
if ( rval < 0 )
|
if ( rval < 0 )
|
||||||
{
|
{
|
||||||
close(s);
|
close(s);
|
||||||
return INADDR_BROADCAST;
|
return INADDR_BROADCAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
|
sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
|
||||||
if( !ifacePrinted )
|
close(s);
|
||||||
{
|
DPRINTF(E_DEBUG, L_TIVO, "Interface: %s broadcast addr %s \n", "eth0", inet_ntoa(sin->sin_addr) );
|
||||||
printf( "Interface: %s broadcast addr %s \n", "eth0", inet_ntoa(sin->sin_addr) );
|
|
||||||
ifacePrinted = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
char * mesg;
|
||||||
int mesg_len;
|
int mesg_len;
|
||||||
time_t now = time(NULL);
|
|
||||||
|
|
||||||
mesg_len = asprintf(&mesg, "TiVoConnect=1\n"
|
mesg_len = asprintf(&mesg, "TiVoConnect=1\n"
|
||||||
"swversion=%s\n"
|
"swversion=%s\n"
|
||||||
@ -151,7 +145,7 @@ sendBeaconMessage(int fd, struct sockaddr_in * client, int len, int broadcast)
|
|||||||
"1.0",
|
"1.0",
|
||||||
broadcast ? "broadcast" : "connected",
|
broadcast ? "broadcast" : "connected",
|
||||||
uuidvalue, friendly_name, runtime_vars.port);
|
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);
|
sendto(fd, mesg, mesg_len, 0, (struct sockaddr*)client, len);
|
||||||
free(mesg);
|
free(mesg);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
/*
|
/*
|
||||||
* * A saved copy of a beacon from another tivo or another server
|
* * A saved copy of a beacon from another tivo or another server
|
||||||
|
273
tivo_commands.c
273
tivo_commands.c
@ -1,5 +1,7 @@
|
|||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "tivo_utils.h"
|
#include "tivo_utils.h"
|
||||||
@ -28,7 +30,248 @@ SendRootContainer(struct upnphttp * h)
|
|||||||
"<Details>"
|
"<Details>"
|
||||||
"<ContentType>x-container/tivo-photos</ContentType>"
|
"<ContentType>x-container/tivo-photos</ContentType>"
|
||||||
"<SourceFormat>x-container/folder</SourceFormat>"
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
||||||
"<Title>Pictures</Title>"
|
"<Title>Pictures on %s</Title>"
|
||||||
|
"</Details>"
|
||||||
|
"<Links>"
|
||||||
|
"<Content>"
|
||||||
|
"<Url>/TiVoConnect?Command=QueryContainer&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&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, "<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, ">"
|
||||||
|
"<dc:title>%s</dc:title>"
|
||||||
|
"<upnp:class>object.%s</upnp:class>",
|
||||||
|
title, class);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
if( comment && (!passed_args->filter || strstr(passed_args->filter, "dc:description")) ) {
|
||||||
|
sprintf(str_buf, "<dc:description>%s</dc:description>", comment);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
|
||||||
|
sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( date && (!passed_args->filter || strstr(passed_args->filter, "dc:date")) ) {
|
||||||
|
sprintf(str_buf, "<dc:date>%s</dc:date>", date);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", artist);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( album && (!passed_args->filter || strstr(passed_args->filter, "upnp:album")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:album>%s</upnp:album>", album);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( track && atoi(track) && (!passed_args->filter || strstr(passed_args->filter, "upnp:originalTrackNumber")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", 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, "<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, ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>",
|
||||||
|
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, "<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\">"
|
||||||
|
"http://%s:%d/MediaItems/%s.dat"
|
||||||
|
"</res>",
|
||||||
|
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, "<res "
|
||||||
|
"protocolInfo=\"http-get:*:%s:%s\">"
|
||||||
|
"http://%s:%d/Resized/%s"
|
||||||
|
"</res>",
|
||||||
|
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, "<res ");
|
||||||
|
sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\">"
|
||||||
|
"http://%s:%d/Thumbnails/%s.dat"
|
||||||
|
"</res>",
|
||||||
|
mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
|
||||||
|
}
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
strcpy(str_buf, "</item>");
|
||||||
|
}
|
||||||
|
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, "<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, ">"
|
||||||
|
"<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>"
|
||||||
|
"<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>"
|
||||||
|
"<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sprintf(str_buf, ">"
|
||||||
|
"<dc:title>%s</dc:title>"
|
||||||
|
"<upnp:class>object.%s</upnp:class>",
|
||||||
|
title, class);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
if( creator && (!passed_args->filter || strstr(passed_args->filter, "dc:creator")) ) {
|
||||||
|
sprintf(str_buf, "<dc:creator>%s</dc:creator>", creator);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( genre && (!passed_args->filter || strstr(passed_args->filter, "upnp:genre")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:genre>%s</upnp:genre>", genre);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
if( artist && (!passed_args->filter || strstr(passed_args->filter, "upnp:artist")) ) {
|
||||||
|
sprintf(str_buf, "<upnp:artist>%s</upnp:artist>", 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, "<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, ">http://%s:%d/AlbumArt/%s.jpg</upnp:albumArtURI>",
|
||||||
|
lan_addr[0].str, runtime_vars.port, album_art);
|
||||||
|
strcat(passed_args->resp, str_buf);
|
||||||
|
}
|
||||||
|
sprintf(str_buf, "</container>");
|
||||||
|
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>"
|
"</Details>"
|
||||||
"<Links>"
|
"<Links>"
|
||||||
"<Content>"
|
"<Content>"
|
||||||
@ -40,7 +283,7 @@ SendRootContainer(struct upnphttp * h)
|
|||||||
"<Details>"
|
"<Details>"
|
||||||
"<ContentType>x-container/tivo-music</ContentType>"
|
"<ContentType>x-container/tivo-music</ContentType>"
|
||||||
"<SourceFormat>x-container/folder</SourceFormat>"
|
"<SourceFormat>x-container/folder</SourceFormat>"
|
||||||
"<Title>Music</Title>"
|
"<Title>Music on %s</Title>"
|
||||||
"</Details>"
|
"</Details>"
|
||||||
"<Links>"
|
"<Links>"
|
||||||
"<Content>"
|
"<Content>"
|
||||||
@ -48,7 +291,9 @@ SendRootContainer(struct upnphttp * h)
|
|||||||
"</Content>"
|
"</Content>"
|
||||||
"</Links>"
|
"</Links>"
|
||||||
"</Item>"
|
"</Item>"
|
||||||
"</TiVoContainer>", friendly_name);
|
"</TiVoContainer>",
|
||||||
|
(objectID[0]=='1') ? "music":"photos",
|
||||||
|
title, itemStart, itemCount, title, title);
|
||||||
BuildResp_upnphttp(h, resp, len);
|
BuildResp_upnphttp(h, resp, len);
|
||||||
SendResp_upnphttp(h);
|
SendResp_upnphttp(h);
|
||||||
}
|
}
|
||||||
@ -60,6 +305,7 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
|
|||||||
char *key, *val;
|
char *key, *val;
|
||||||
char *saveptr, *item;
|
char *saveptr, *item;
|
||||||
char *command = NULL, *container = NULL;
|
char *command = NULL, *container = NULL;
|
||||||
|
int itemStart=0, itemCount=0;
|
||||||
|
|
||||||
path = decodeString(orig_path);
|
path = decodeString(orig_path);
|
||||||
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", 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;
|
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 );
|
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);
|
CloseSocket_upnphttp(h);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
void
|
void
|
||||||
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path);
|
ProcessTiVoCommand(struct upnphttp * h, const char * orig_path);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "config.h"
|
||||||
#ifdef ENABLE_TIVO
|
#ifdef ENABLE_TIVO
|
||||||
char *
|
char *
|
||||||
decodeString(const char * string);
|
decodeString(const char * string);
|
||||||
|
@ -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/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:*: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/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01," \
|
||||||
"http-get:*:audio/x-ms-wma:*," \
|
"http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01," \
|
||||||
"http-get:*:audio/wav:*," \
|
"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/mp4:*," \
|
||||||
|
"http-get:*:audio/wav:*," \
|
||||||
"http-get:*:audio/x-aiff:*," \
|
"http-get:*:audio/x-aiff:*," \
|
||||||
"http-get:*:audio/x-flac:*," \
|
"http-get:*:audio/x-flac:*," \
|
||||||
"http-get:*:application/ogg:*," \
|
"http-get:*:application/ogg:*," \
|
||||||
|
@ -478,7 +478,7 @@ static void
|
|||||||
ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
||||||
{
|
{
|
||||||
char HttpCommand[16];
|
char HttpCommand[16];
|
||||||
char HttpUrl[128];
|
char HttpUrl[256];
|
||||||
char * HttpVer;
|
char * HttpVer;
|
||||||
char * p;
|
char * p;
|
||||||
int i;
|
int i;
|
||||||
@ -496,7 +496,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h)
|
|||||||
while(*p!='/')
|
while(*p!='/')
|
||||||
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] = *(p++);
|
||||||
HttpUrl[i] = '\0';
|
HttpUrl[i] = '\0';
|
||||||
while(*p==' ')
|
while(*p==' ')
|
||||||
|
Reference in New Issue
Block a user