diff --git a/configure.in b/configure.in index b52b79d3..b8d7c113 100644 --- a/configure.in +++ b/configure.in @@ -49,12 +49,16 @@ AC_ARG_ENABLE(howl,[ --enable-howl Use howl 0.9.2 or later], AC_ARG_ENABLE(oggvorbis,[ --enable-oggvorbis Enable Ogg/Vorbis support], use_oggvorbis=true; -# LDFLAGS="${LDFLAGS} -logg -lvorbis"; CPPFLAGS="${CPPFLAGS} -DOGGVORBIS") +AC_ARG_ENABLE(flac,[ --enable-flac Enable FLAC support], + use_flac=true; + CPPFLAGS="${CPPFLAGS} -DFLAC") + AM_CONDITIONAL(COND_REND_HOWL, test x$rend_howl = xtrue) AM_CONDITIONAL(COND_REND_POSIX, test x$rend_posix = xtrue) AM_CONDITIONAL(COND_OGGVORBIS, test x$use_oggvorbis = xtrue) +AM_CONDITIONAL(COND_FLAC, test x$use_flac = xtrue) AM_CONDITIONAL(COND_NEED_STRCASESTR,false) AM_CONDITIONAL(COND_NEED_STRSEP,false) @@ -184,16 +188,32 @@ if test x$use_oggvorbis = xtrue; then AC_CHECK_HEADERS(vorbis/codec.h,, [ AC_MSG_ERROR([vorbis/codec.h not found... Must have libvorbis installed for Ogg/Vorbis support])]) AC_CHECK_LIB(vorbis,vorbis_info_init,echo "Have vorbis",echo "Must have libvorbis for Ogg/Vorbis support";exit) + AC_CHECK_LIB(vorbisfile,ov_open,echo "Have vorbisfile",echo "Must have libvorbisfile for Ogg/Vorbis support";exit) if test x"$STATIC_LIBS" != x"no"; then - LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libvorbis.a" + LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libvorbis.a ${STATIC_LIBS}/libvorbisfile.a" echo "Adding static libvorbis" else - LDFLAGS="${LDFLAGS} -lvorbis" + LDFLAGS="${LDFLAGS} -lvorbis -lvorbisfile" echo "Adding dynamic libvorbis" fi fi +echo USE_FLAC is $use_flac + +if test x$use_flac = xtrue; then + AC_CHECK_HEADERS(FLAC/metadata.h,, [ + AC_MSG_ERROR([FLAC/metadata.h not found... Must have libFLAC installed for FLAC support])]) + AC_CHECK_LIB(FLAC,FLAC__metadata_chain_read,echo "Have FLAC",echo "Must have libFLAC for FLAC support";exit) + + if test x"$STATIC_LIBS" != x"no"; then + LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libFLAC.a" + echo "Adding static libFLAC" + else + LDFLAGS="${LDFLAGS} -lFLAC" + echo "Adding dynamic libFLAC" + fi +fi AC_REPLACE_FUNCS(strcasestr) AC_REPLACE_FUNCS(strsep) diff --git a/contrib/mt-daapd.conf b/contrib/mt-daapd.conf index 2b917941..f3cbe8b6 100644 --- a/contrib/mt-daapd.conf +++ b/contrib/mt-daapd.conf @@ -141,13 +141,15 @@ ssc_extensions .ogg # # Program that is used in server side format conversion. # Program must accept following command line syntax: -# ssc_prog filename offset +# ssc_prog filename offset length ... # Parameter filename is the real name of the file that is # to be converted and streamed, offset is number of bytes # that are skipped from the beginning of the _output_ file -# before streaming is started. The resulting wav file (or -# rest of the file after initial seek) is written to the -# standard output by the ssc_prog program. This is typically +# before streaming is started, length is length of the song +# in seconds (or zero). All other possible arguments must +# be ignored. The resulting wav file (or the rest of +# the file after initial seek) is written to the standard +# output by the ssc_prog program. This is typically # a script that is a front end for different conversion tools # handling different formats. # diff --git a/src/Makefile.am b/src/Makefile.am index 44008cc7..f72b41b3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,10 @@ if COND_OGGVORBIS OGGVORBISSRC=ogg.c endif +if COND_FLAC +FLACSRC=flac.c +endif + mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \ daap-proto.c daap-proto.h daap.c daap.h db-gdbm.c db-memory.h \ @@ -29,10 +33,10 @@ mt_daapd_SOURCES = main.c daapd.h rend.h uici.c uici.h webserver.c \ rend-unix.h lexer.l parser.y strcasestr.c strcasestr.h strsep.c \ redblack.c redblack.h dynamic-art.c dynamic-art.h query.c query.h \ xml-rpc.h xml-rpc.c ssc.c ssc.h \ - $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) + $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) $(FLACSRC) EXTRA_DIST = mDNS.c mDNSClientAPI.h mDNSDebug.h mDNSPosix.c \ mDNSUNP.c mDNSPlatformFunctions.h mDNSPosix.h mDNSUNP.h \ rend-howl.c rend-posix.c rend-osx.c db-memory.c \ db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c \ - parser.h ogg.c + parser.h ogg.c flac.c diff --git a/src/configfile.c b/src/configfile.c index 61da71f4..7f250114 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -1263,6 +1263,10 @@ void config_emit_flags(WS_CONNINFO *pwsc, void *value, char *arg) { ws_writefd(pwsc,"%s ","--enable-oggvorbis"); #endif +#ifdef FLAC + ws_writefd(pwsc,"%s ","--enable-flac"); +#endif + #ifdef WITH_GDBM ws_writefd(pwsc,"%s ","--with-gdbm"); #endif diff --git a/src/main.c b/src/main.c index 4b711d5c..05343236 100644 --- a/src/main.c +++ b/src/main.c @@ -392,7 +392,9 @@ void daap_handler(WS_CONNINFO *pwsc) { // The file should be converted in the server side. DPRINTF(E_WARN,L_WS,"Thread %d: Autoconvert file %s for client\n", pwsc->threadno,real_path); - file_ptr=server_side_convert_open(real_path,offset); + file_ptr = server_side_convert_open(real_path, + offset, + pmp3->song_length); if (file_ptr) { file_fd = fileno(file_ptr); } else { diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index 258aae49..ac84d452 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -281,6 +281,7 @@ static int scan_get_nultags(char *file, MP3FILE *pmp3) { return 0; }; static int scan_get_fileinfo(char *file, MP3FILE *pmp3); static int scan_get_mp3fileinfo(char *file, MP3FILE *pmp3); static int scan_get_aacfileinfo(char *file, MP3FILE *pmp3); +static int scan_get_wavfileinfo(char *file, MP3FILE *pmp3); static int scan_get_nulfileinfo(char *file, MP3FILE *pmp3) { return 0; }; static int scan_get_urlfileinfo(char *file, MP3FILE *pmp3); @@ -295,6 +296,11 @@ static time_t mac_to_unix_time(int t); extern int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3); #endif +#ifdef FLAC +extern int scan_get_flacfileinfo(char *filename, MP3FILE *pmp3); +extern int scan_get_flactags(char *filename, MP3FILE *pmp3); +#endif + /* * Typedefs */ @@ -311,9 +317,14 @@ static taghandler taghandlers[] = { { "m4a", scan_get_aactags, scan_get_aacfileinfo }, { "m4p", scan_get_aactags, scan_get_aacfileinfo }, { "mp3", scan_get_mp3tags, scan_get_mp3fileinfo }, + { "wav", scan_get_nultags, scan_get_wavfileinfo }, { "url", scan_get_nultags, scan_get_urlfileinfo }, #ifdef OGGVORBIS { "ogg", scan_get_nultags, scan_get_oggfileinfo }, +#endif +#ifdef FLAC + { "flac", scan_get_flactags, scan_get_flacfileinfo }, + { "fla", scan_get_flactags, scan_get_flacfileinfo }, #endif { NULL, 0 } }; @@ -1231,6 +1242,84 @@ int scan_get_aacfileinfo(char *file, MP3FILE *pmp3) { return 0; } +#define GET_WAV_INT32(p) ((((unsigned long)((p)[3])) << 24) | \ + (((unsigned long)((p)[2])) << 16) | \ + (((unsigned long)((p)[1])) << 8) | \ + (((unsigned long)((p)[0])))) + +#define GET_WAV_INT16(p) ((((unsigned long)((p)[1])) << 8) | \ + (((unsigned long)((p)[0])))) + +/* + * scan_get_wavfileinfo + * + * Get info from the actual wav headers + */ +int scan_get_wavfileinfo(char *file, MP3FILE *pmp3) { + FILE *infile; + size_t rl; + unsigned char hdr[44]; + unsigned long chunk_data_length; + unsigned long format_data_length; + unsigned long compression_code; + unsigned long channel_count; + unsigned long sample_rate; + unsigned long sample_bit_length; + unsigned long bit_rate; + unsigned long data_length; + unsigned long sec, ms; + + DPRINTF(E_DBG,L_SCAN,"Getting WAV file info\n"); + + if(!(infile=fopen(file,"rb"))) { + DPRINTF(E_WARN,L_SCAN,"Could not open %s for reading\n",file); + return -1; + } + + fseek(infile,0,SEEK_END); + pmp3->file_size = ftell(infile); + fseek(infile,0,SEEK_SET); + + rl = fread(hdr, 1, 44, infile); + fclose(infile); + if (rl != 44) { + DPRINTF(E_WARN,L_SCAN,"Could not read wav header from %s\n",file); + return -1; + } + + if (strncmp(hdr + 0, "RIFF", 4) || + strncmp(hdr + 8, "WAVE", 4) || + strncmp(hdr + 12, "fmt ", 4) || + strncmp(hdr + 36, "data", 4)) { + DPRINTF(E_WARN,L_SCAN,"Invalid wav header in %s\n",file); + return -1; + } + + chunk_data_length = GET_WAV_INT32(hdr + 4); + format_data_length = GET_WAV_INT32(hdr + 16); + compression_code = GET_WAV_INT16(hdr + 20); + channel_count = GET_WAV_INT16(hdr + 22); + sample_rate = GET_WAV_INT32(hdr + 24); + sample_bit_length = GET_WAV_INT16(hdr + 34); + data_length = GET_WAV_INT32(hdr + 40); + + if ((format_data_length != 16) || + (compression_code != 1) || + (channel_count < 1)) { + DPRINTF(E_WARN,L_SCAN,"Invalid wav header in %s\n",file); + return -1; + } + + bit_rate = sample_rate * channel_count * ((sample_bit_length + 7) / 8) * 8; + pmp3->bitrate = bit_rate / 1000; + pmp3->samplerate = sample_rate; + sec = data_length / (bit_rate / 8); + ms = ((data_length % (bit_rate / 8)) * 1000) / (bit_rate / 8); + pmp3->song_length = (sec * 1000) + ms; + + return 0; +} + /** * Decode an mp3 frame header. Determine layer, bitrate, diff --git a/src/ogg.c b/src/ogg.c index ee0205a1..ec2a6bac 100644 --- a/src/ogg.c +++ b/src/ogg.c @@ -2,12 +2,6 @@ * $Id$ * Ogg parsing routines. * - * This file has been modified from the 'ogginfo' code in the vorbistools - * distribution. The original copyright appears below: - * - * Ogginfo - * - * A tool to describe ogg file contents and metadata. * * Copyright 2002 Michael Smith * Licensed under the GNU GPL, distributed with this program. @@ -17,776 +11,77 @@ #include #include #include -#include -#include -/* -#include -#include "utf8.h" -#include "i18n.h" -*/ +#include #include "err.h" #include "mp3-scanner.h" -#define CHUNK 4500 - -struct vorbis_release { - char *vendor_string; - char *desc; -} releases[] = { - {"Xiphophorus libVorbis I 20000508", "1.0 beta 1 or beta 2"}, - {"Xiphophorus libVorbis I 20001031", "1.0 beta 3"}, - {"Xiphophorus libVorbis I 20010225", "1.0 beta 4"}, - {"Xiphophorus libVorbis I 20010615", "1.0 rc1"}, - {"Xiphophorus libVorbis I 20010813", "1.0 rc2"}, - {"Xiphophorus libVorbis I 20011217", "1.0 rc3"}, - {"Xiphophorus libVorbis I 20011231", "1.0 rc3"}, - {"Xiph.Org libVorbis I 20020717", "1.0"}, - {"Xiph.Org libVorbis I 20030909", "1.0.1"}, - {NULL, NULL}, - }; - - -/* TODO: - * - * - detect violations of muxing constraints - * - detect granulepos 'gaps' (possibly vorbis-specific). (seperate from - * serial-number gaps) - */ - -typedef struct _stream_processor { - void (*process_page)(struct _stream_processor *, ogg_page *, MP3FILE *); - void (*process_end)(struct _stream_processor *, MP3FILE *); - 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; /* must be 32 bit unsigned */ - ogg_stream_state os; - void *data; -} stream_processor; - -typedef struct { - stream_processor *streams; - int allocated; - int used; - - int in_headers; -} stream_set; - -typedef struct { - vorbis_info vi; - vorbis_comment vc; - - ogg_int64_t bytes; - ogg_int64_t lastgranulepos; - ogg_int64_t firstgranulepos; - - int doneheaders; -} misc_vorbis_info; - -#define CONSTRAINT_PAGE_AFTER_EOS 1 -#define CONSTRAINT_MUXING_VIOLATED 2 - -static stream_set *create_stream_set(void) { - stream_set *set = calloc(1, sizeof(stream_set)); - - set->streams = calloc(5, sizeof(stream_processor)); - set->allocated = 5; - set->used = 0; - - return set; -} - -static void vorbis_process(stream_processor *stream, ogg_page *page, - MP3FILE *pmp3) -{ - ogg_packet packet; - misc_vorbis_info *inf = stream->data; - int i, header=0; - int k; - - 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_SCAN, "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_SCAN, "Vorbis stream %d does not have headers " - "correctly framed. Terminal header page contains " - "additional packets or has non-zero granulepos\n", - stream->num); - DPRINTF(E_DBG, L_SCAN, "Vorbis headers parsed for stream %d, " - "information follows...\n", stream->num); - /* - DPRINTF(E_INF, L_SCAN, "Version: %d\n", inf->vi.version); - k = 0; - while(releases[k].vendor_string) { - if(!strcmp(inf->vc.vendor, releases[k].vendor_string)) { - //PENDING: - DPRINTF(E_INF, L_SCAN, "Vendor: %s (%s)\n", - inf->vc.vendor, releases[k].desc); - break; - } - k++; - } - - if(!releases[k].vendor_string) - DPRINTF(E_INF, L_SCAN, "Vendor: %s\n", inf->vc.vendor);*/ - - DPRINTF(E_DBG, L_SCAN, "Channels: %d\n", inf->vi.channels); - DPRINTF(E_DBG, L_SCAN, "Rate: %ld\n\n", inf->vi.rate); - - pmp3->samplerate = inf->vi.rate; - - if(inf->vi.bitrate_nominal > 0) { - DPRINTF(E_DBG, L_SCAN, "Nominal bitrate: %f kb/s\n", - (double)inf->vi.bitrate_nominal / 1000.0); - pmp3->bitrate = inf->vi.bitrate_nominal / 1000; - } - else { - int upper_rate, lower_rate; - - DPRINTF(E_DBG, L_SCAN, "Nominal bitrate not set\n"); - - /* Average the lower and upper bitrates if set */ - - upper_rate = 0; - lower_rate = 0; - - if(inf->vi.bitrate_upper > 0) { - DPRINTF(E_DBG, L_SCAN, "Upper bitrate: %f kb/s\n", - (double)inf->vi.bitrate_upper / 1000.0); - upper_rate = inf->vi.bitrate_upper; - } else { - DPRINTF(E_DBG, L_SCAN, "Upper bitrate not set\n"); - } - - if(inf->vi.bitrate_lower > 0) { - DPRINTF(E_DBG, L_SCAN,"Lower bitrate: %f kb/s\n", - (double)inf->vi.bitrate_lower / 1000.0); - lower_rate = inf->vi.bitrate_lower;; - } else { - DPRINTF(E_DBG, L_SCAN, "Lower bitrate not set\n"); - } - - if (upper_rate && lower_rate) { - pmp3->bitrate = (upper_rate + lower_rate) / 2; - } else { - pmp3->bitrate = upper_rate + lower_rate; - } - } - - if(inf->vc.comments > 0) - DPRINTF(E_DBG, L_SCAN, - "User comments section follows...\n"); - - for(i=0; i < inf->vc.comments; i++) { - char *sep = strchr(inf->vc.user_comments[i], '='); - char *decoded; - int j; - int broken = 0; - unsigned char *val; - int bytes; - int remaining; - - if(sep == NULL) { - DPRINTF(E_WARN, L_SCAN, - "Comment %d in stream %d is invalidly " - "formatted, does not contain '=': \"%s\"\n", - i, stream->num, inf->vc.user_comments[i]); - continue; - } - - for(j=0; j < sep-inf->vc.user_comments[i]; j++) { - if(inf->vc.user_comments[i][j] < 0x20 || - inf->vc.user_comments[i][j] > 0x7D) { - DPRINTF(E_WARN, L_SCAN, - "Warning: Invalid comment fieldname in " - "comment %d (stream %d): \"%s\"\n", - i, stream->num, inf->vc.user_comments[i]); - broken = 1; - break; - } - } - - if(broken) - continue; - - val = inf->vc.user_comments[i]; - - j = sep-inf->vc.user_comments[i]+1; - while(j < inf->vc.comment_lengths[i]) - { - remaining = inf->vc.comment_lengths[i] - j; - if((val[j] & 0x80) == 0) - bytes = 1; - else if((val[j] & 0x40) == 0x40) { - if((val[j] & 0x20) == 0) - bytes = 2; - else if((val[j] & 0x10) == 0) - bytes = 3; - else if((val[j] & 0x08) == 0) - bytes = 4; - else if((val[j] & 0x04) == 0) - bytes = 5; - else if((val[j] & 0x02) == 0) - bytes = 6; - else { - DPRINTF(E_WARN, L_SCAN, - "Illegal UTF-8 sequence in " - "comment %d (stream %d): length " - "marker wrong\n", - i, stream->num); - broken = 1; - break; - } - } - else { - DPRINTF(E_WARN, L_SCAN, - "Illegal UTF-8 sequence in comment " - "%d (stream %d): length marker wrong\n", - i, stream->num); - broken = 1; - break; - } - - if(bytes > remaining) { - DPRINTF(E_WARN, L_SCAN, - "Illegal UTF-8 sequence in comment " - "%d (stream %d): too few bytes\n", - i, stream->num); - broken = 1; - break; - } - - switch(bytes) { - case 1: - /* No more checks needed */ - break; - case 2: - if((val[j+1] & 0xC0) != 0x80) - broken = 1; - if((val[j] & 0xFE) == 0xC0) - broken = 1; - break; - case 3: - if(!((val[j] == 0xE0 && val[j+1] >= 0xA0 && - val[j+1] <= 0xBF && - (val[j+2] & 0xC0) == 0x80) || - (val[j] >= 0xE1 && val[j] <= 0xEC && - (val[j+1] & 0xC0) == 0x80 && - (val[j+2] & 0xC0) == 0x80) || - (val[j] == 0xED && val[j+1] >= 0x80 && - val[j+1] <= 0x9F && - (val[j+2] & 0xC0) == 0x80) || - (val[j] >= 0xEE && val[j] <= 0xEF && - (val[j+1] & 0xC0) == 0x80 && - (val[j+2] & 0xC0) == 0x80))) - broken = 1; - if(val[j] == 0xE0 && (val[j+1] & 0xE0) == 0x80) - broken = 1; - break; - case 4: - if(!((val[j] == 0xF0 && val[j+1] >= 0x90 && - val[j+1] <= 0xBF && - (val[j+2] & 0xC0) == 0x80 && - (val[j+3] & 0xC0) == 0x80) || - (val[j] >= 0xF1 && val[j] <= 0xF3 && - (val[j+1] & 0xC0) == 0x80 && - (val[j+2] & 0xC0) == 0x80 && - (val[j+3] & 0xC0) == 0x80) || - (val[j] == 0xF4 && val[j+1] >= 0x80 && - val[j+1] <= 0x8F && - (val[j+2] & 0xC0) == 0x80 && - (val[j+3] & 0xC0) == 0x80))) - broken = 1; - if(val[j] == 0xF0 && (val[j+1] & 0xF0) == 0x80) - broken = 1; - break; - /* 5 and 6 aren't actually allowed at this point*/ - case 5: - broken = 1; - break; - case 6: - broken = 1; - break; - } - - if(broken) { - DPRINTF(E_WARN, L_SCAN, - "Illegal UTF-8 sequence in comment " - "%d (stream %d): invalid sequence\n", - i, stream->num); - broken = 1; - break; - } - - j += bytes; - } - - if(!broken) { - /* if(utf8_decode(sep+1, &decoded) < 0) { - DPRINTF(E_WARN, L_SCAN, - "Failure in utf8 decoder. This " - "should be impossible\n"); - continue; - }*/ - - DPRINTF(E_DBG, L_SCAN, - "\t%s\n", inf->vc.user_comments[i]); - - if (!strncasecmp(inf->vc.user_comments[i],"TITLE",5)) { - pmp3->title = strdup(sep + 1); - DPRINTF(E_DBG, L_SCAN, "Mapping %s to title.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], "ARTIST", 6)) { - pmp3->artist = strdup(sep + 1); - DPRINTF(E_DBG, L_SCAN, "Mapping %s to artist.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], "ALBUM", 5)) { - pmp3->album = strdup(sep + 1); - DPRINTF(E_DBG, L_SCAN, "Mapping %s to album.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], - "TRACKNUMBER", 11)) { - pmp3->track = atoi(sep + 1); - DPRINTF(E_INF, L_SCAN, "Mapping %s to track.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], "GENRE", 5)) { - pmp3->genre = strdup(sep + 1); - DPRINTF(E_INF, L_SCAN, "Mapping %s to genre.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], "DATE", 4)) { - //PENDING: Should only parse first 4 characters - pmp3->year = atoi(sep + 1); - DPRINTF(E_INF, L_SCAN, "Mapping %s to year.\n", - sep + 1); - } else if (!strncasecmp(inf->vc.user_comments[i], "COMMENT", 7)) { - pmp3->comment = strdup(sep + 1); - DPRINTF(E_INF, L_SCAN, "Mapping %s to comment.\n", - sep + 1); - } - - *sep = 0; - /* free(decoded);*/ - } - } - } - } - } - - if(!header) { - ogg_int64_t gp = ogg_page_granulepos(page); - if(gp > 0) { - if(gp < inf->lastgranulepos) -#ifdef _WIN32 - DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %I64d to %I64d", - stream->num, inf->lastgranulepos, gp); -#else - DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %lld to %lld", - stream->num, inf->lastgranulepos, gp); -#endif - inf->lastgranulepos = gp; - } - else { - DPRINTF(E_WARN, L_SCAN, "Negative granulepos on vorbis stream outside of headers. This file was created by a buggy encoder\n"); - } - if(inf->firstgranulepos < 0) { /* Not set yet */ - } - inf->bytes += page->header_len + page->body_len; - } -} - -static void vorbis_end(stream_processor *stream, MP3FILE *pmp3) -{ - misc_vorbis_info *inf = stream->data; - long minutes, seconds; - double bitrate, time; - - /* This should be lastgranulepos - startgranulepos, or something like that*/ - time = (double)inf->lastgranulepos / inf->vi.rate; - bitrate = inf->bytes*8 / time / 1000.0; - - if (pmp3 != NULL) { - if (pmp3->bitrate <= 0) { - pmp3->bitrate = bitrate; - } - pmp3->song_length = time * 1000; - pmp3->file_size = inf->bytes; - } - - minutes = (long)time / 60; - seconds = (long)time - minutes*60; - -#ifdef _WIN32 - DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n" - "\tTotal data length: %I64d bytes\n" - "\tPlayback length: %ldm:%02lds\n" - "\tAverage bitrate: %f kbps\n", - stream->num,inf->bytes, minutes, seconds, bitrate); -#else - DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n" - "\tTotal data length: %lld bytes\n" - "\tPlayback length: %ldm:%02lds\n" - "\tAverage bitrate: %f kbps\n", - stream->num,inf->bytes, minutes, seconds, bitrate); -#endif - - vorbis_comment_clear(&inf->vc); - vorbis_info_clear(&inf->vi); - - free(stream->data); -} - -static void process_null(stream_processor *stream, ogg_page *page, MP3FILE *pmp) -{ - /* This is for invalid streams. */ -} - -static void process_other(stream_processor *stream, ogg_page *page, MP3FILE *pmp) -{ - ogg_packet packet; - - ogg_stream_pagein(&stream->os, page); - - while(ogg_stream_packetout(&stream->os, &packet) > 0) { - /* Should we do anything here? Currently, we don't */ - } -} - - -static void free_stream_set(stream_set *set) -{ - int i; - for(i=0; i < set->used; i++) { - if(!set->streams[i].end) { - DPRINTF(E_WARN, L_SCAN, "Warning: EOS not set on stream %d\n", - set->streams[i].num); - //PENDING: - 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 streams_open(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 null_start(stream_processor *stream) -{ - stream->process_end = NULL; - stream->type = "invalid"; - stream->process_page = process_null; -} - -static void other_start(stream_processor *stream, char *type) -{ - if(type) - stream->type = type; - else - stream->type = "unknown"; - stream->process_page = process_other; - stream->process_end = NULL; -} - -static void vorbis_start(stream_processor *stream) -{ - misc_vorbis_info *info; - - stream->type = "vorbis"; - stream->process_page = vorbis_process; - stream->process_end = vorbis_end; - - stream->data = calloc(1, sizeof(misc_vorbis_info)); - - info = stream->data; - - vorbis_comment_init(&info->vc); - vorbis_info_init(&info->vi); - -} - -static stream_processor *find_stream_processor(stream_set *set, ogg_page *page) -{ - ogg_uint32_t serial = ogg_page_serialno(page); - int i, found = 0; - int invalid = 0; - int constraint = 0; - stream_processor *stream; - - for(i=0; i < set->used; i++) { - if(serial == set->streams[i].serial) { - /* We have a match! */ - found = 1; - stream = &(set->streams[i]); - - set->in_headers = 0; - /* if we have detected EOS, then this can't occur here. */ - 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 there are streams open, and we've reached the end of the - * headers, then we can't be starting a new stream. - * XXX: might this sometimes catch ok streams if EOS flag is missing, - * but the stream is otherwise ok? - */ - if(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(stream_processor)* - set->allocated); - stream = &set->streams[set->used]; - } - set->used++; - stream->num = set->used; /* We count from 1 */ - - stream->isnew = 1; - stream->isillegal = invalid; - stream->constraint_violated = constraint; - - { - int res; - ogg_packet packet; - - /* We end up processing the header page twice, but that's ok. */ - 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_SCAN, "Invalid header page, no packet found\n"); - null_start(stream); - } - else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7)==0) - vorbis_start(stream); - else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8)==0) - other_start(stream, "MIDI"); - else - other_start(stream, NULL); - - res = ogg_stream_packetout(&stream->os, &packet); - if(res > 0) { - DPRINTF(E_WARN, L_SCAN, "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; - - /* if(stream->serial == 0 || stream->serial == -1) { - info(_("Note: Stream %d has serial number %d, which is legal but may " - "cause problems with some tools."), stream->num, stream->serial); - }*/ - - return stream; -} - -static int 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) -#ifdef _WIN32 - DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %I64d bytes. Corrupted ogg.\n", *written); -#else - DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written); -#endif - - buffer = ogg_sync_buffer(sync, CHUNK); - bytes = fread(buffer, 1, CHUNK, f); - if(bytes <= 0) { - ogg_sync_wrote(sync, 0); - return 0; - } - ogg_sync_wrote(sync, bytes); - *written += bytes; - } - - return 1; -} - int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3) { - FILE *file = fopen(filename, "rb"); - ogg_sync_state sync; - ogg_page page; - stream_set *processors = create_stream_set(); - int gotpage = 0; - ogg_int64_t written = 0; - struct stat psb; + FILE *f; + OggVorbis_File vf; + vorbis_comment *comment = NULL; + vorbis_info *vi = NULL; + char *val; - if(!file) { + f = fopen(filename, "rb"); + if (f == NULL) { DPRINTF(E_FATAL, L_SCAN, "Error opening input file \"%s\": %s\n", filename, strerror(errno)); - return -1; + return 0; } - DPRINTF(E_INF, L_SCAN, "Processing file \"%s\"...\n\n", filename); - - if (!stat(filename, &psb)) { - pmp3->time_added = psb.st_mtime; - if (psb.st_ctime < pmp3->time_added) { - pmp3->time_added = psb.st_ctime; - } - pmp3->time_modified = psb.st_mtime; - } else { - DPRINTF(E_WARN, L_SCAN, "Error statting: %s\n", strerror(errno)); + if(!fseek(f,0,SEEK_END)) { + pmp3->file_size=ftell(f); + fseek(f,0,SEEK_SET); } - ogg_sync_init(&sync); - - while(get_next_page(file, &sync, &page, &written)) { - stream_processor *p = find_stream_processor(processors, &page); - gotpage = 1; - - if(!p) { - DPRINTF(E_FATAL, L_SCAN, "Could not find a processor for stream, bailing\n"); - 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_SCAN, - "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 it's a new stream, we want to continue processing this page - * anyway to suppress additional spurious errors - */ - if(!p->isnew) - continue; - } - - if(p->isnew) { - DPRINTF(E_DBG, L_SCAN, "New logical stream (#%d, serial: %08x): type %s\n", - p->num, p->serial, p->type); - if(!p->start) - DPRINTF(E_WARN, L_SCAN, - "stream start flag not set on stream %d\n", - p->num); - } - else if(p->start) - DPRINTF(E_WARN, L_SCAN, "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_SCAN, - "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, pmp3); - - if(p->end) { - if(p->process_end) - p->process_end(p, pmp3); - DPRINTF(E_DBG, L_SCAN, "Logical stream %d ended\n", p->num); - p->isillegal = 1; - p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; - } - } + if (ov_open(f, &vf, NULL, 0) != 0) { + fclose(f); + DPRINTF(E_FATAL, L_SCAN, + "Error opening Vorbis stream in \"%s\"\n", filename); + return 0; } - free_stream_set(processors); + vi=ov_info(&vf,-1); + if(vi) { + DPRINTF(E_DBG,L_SCAN," Bitrates: %d %d %d\n",vi->bitrate_upper, + vi->bitrate_nominal,vi->bitrate_lower); + if(vi->bitrate_nominal) { + pmp3->bitrate=vi->bitrate_nominal / 1000; + } else if(vi->bitrate_upper) { + pmp3->bitrate=vi->bitrate_upper / 1000; + } else if(vi->bitrate_lower) { + pmp3->bitrate=vi->bitrate_lower / 1000; + } - ogg_sync_clear(&sync); - - fclose(file); - - if(!gotpage) { - DPRINTF(E_FATAL, L_SCAN, "No ogg data found in file \"%s\".\n" - "Input probably not ogg.\n", filename); - return -1; + DPRINTF(E_DBG,L_SCAN," Bitrates: %d",pmp3->bitrate); + pmp3->samplerate=vi->rate; } + pmp3->song_length=ov_time_total(&vf,-1) * 1000; + + comment = ov_comment(&vf, -1); + if (comment != NULL) { + if ((val = vorbis_comment_query(comment, "artist", 0)) != NULL) + pmp3->artist = strdup(val); + if ((val = vorbis_comment_query(comment, "title", 0)) != NULL) + pmp3->title = strdup(val); + if ((val = vorbis_comment_query(comment, "album", 0)) != NULL) + pmp3->album = strdup(val); + if ((val = vorbis_comment_query(comment, "genre", 0)) != NULL) + pmp3->genre = strdup(val); + if ((val = vorbis_comment_query(comment, "composer", 0)) != NULL) + pmp3->composer = strdup(val); + if ((val = vorbis_comment_query(comment, "comment", 0)) != NULL) + pmp3->comment = strdup(val); + if ((val = vorbis_comment_query(comment, "tracknumber", 0)) != NULL) + pmp3->track = atoi(val); + if ((val = vorbis_comment_query(comment, "discnumber", 0)) != NULL) + pmp3->disc = atoi(val); + if ((val = vorbis_comment_query(comment, "year", 0)) != NULL) + pmp3->year = atoi(val); + } + ov_clear(&vf); + /*fclose(f);*/ return 0; } - -/*int main(int argc, char **argv) { - int f, ret; - - setlocale(LC_ALL, ""); - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); - - process_file(argv[f]); - }*/ - diff --git a/src/ssc.c b/src/ssc.c index 111e337d..92b9b819 100644 --- a/src/ssc.c +++ b/src/ssc.c @@ -134,7 +134,7 @@ int server_side_convert_set(MP3FILE *pmp3) * @param path char * to the real filename. * @param offset off_t to the point in file where the streaming starts. */ -FILE *server_side_convert_open(char *path, off_t offset) +FILE *server_side_convert_open(char *path, off_t offset, unsigned long len_ms) { char *cmd; FILE *f; @@ -142,8 +142,8 @@ FILE *server_side_convert_open(char *path, off_t offset) cmd=(char *)malloc(strlen(config.ssc_prog) + strlen(path) + 64); - sprintf(cmd, "%s \"%s\" %ld", - config.ssc_prog, path, (long)offset); + sprintf(cmd, "%s \"%s\" %ld %lu.%03lu", + config.ssc_prog, path, (long)offset, len_ms / 1000, len_ms % 1000); f = popen(cmd, "r"); return f; } diff --git a/src/ssc.h b/src/ssc.h index 1479e4c0..4c8893f6 100644 --- a/src/ssc.h +++ b/src/ssc.h @@ -27,7 +27,9 @@ extern int server_side_convert_set(MP3FILE *pmp3); extern char *server_side_convert_path(char *path); -extern FILE *server_side_convert_open(char *path, off_t offset); +extern FILE *server_side_convert_open(char *path, + off_t offset, + unsigned long len_ms); extern void server_side_convert_close(FILE *f); #endif /* _SCC_H_ */