diff --git a/configure.in b/configure.in index 93c17a18..5a8cdfff 100644 --- a/configure.in +++ b/configure.in @@ -47,8 +47,14 @@ AC_ARG_ENABLE(howl,[ --enable-howl Use howl 0.9.2 or later], *) AC_MSG_ERROR(bad value ${enableval} for --enable-howl);; esac ]) +AC_ARG_ENABLE(oggvorbis,[ --enable-oggvorbis Enable Ogg/Vorbis support], + use_oggvorbis=true; +# LDFLAGS="${LDFLAGS} -logg -lvorbis"; + CPPFLAGS="${CPPFLAGS} -DOGGVORBIS") + 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_NEED_STRCASESTR,false) AM_CONDITIONAL(COND_NEED_STRSEP,false) @@ -157,6 +163,33 @@ else echo "Adding dynamic libid3tag" fi +if test x"$use_oggvorbis" == x"true"; then + AC_CHECK_HEADERS(ogg/ogg.h,, [ + AC_MSG_ERROR([ogg/ogg.h not found... Must have libogg installed for Ogg/Vorbis support])]) + AC_CHECK_LIB(ogg,ogg_sync_init,echo "Have ogg",echo "Must have libogg for Ogg/Vorbis support";exit) + + if test x"$STATIC_LIBS" != x"no"; then + LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libogg.a" + echo "Adding static libogg" + else + LDFLAGS="${LDFLAGS} -logg" + echo "Adding dynamic libogg" + fi + + 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) + + if test x"$STATIC_LIBS" != x"no"; then + LDFLAGS="${LDFLAGS} ${STATIC_LIBS}/libvorbis.a" + echo "Adding static libvorbis" + else + LDFLAGS="${LDFLAGS} -lvorbis" + echo "Adding dynamic libvorbis" + fi +fi + + AC_REPLACE_FUNCS(strcasestr) AC_REPLACE_FUNCS(strsep) diff --git a/src/Makefile.am b/src/Makefile.am index bef55ae3..1115ba96 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,16 +18,20 @@ if COND_REND_OSX ORENDSRC=rend-osx.c rend-unix.c endif +if COND_OGGVORBIS +OGGVORBISSRC=ogg.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 \ mp3-scanner.h mp3-scanner.c playlist.c playlist.h \ 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 \ - $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) + $(PRENDSRC) $(ORENDSRC) $(HRENDSRC) $(OGGVORBISSRC) 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 strcasestr.c strsep.c db-memory.c \ db-gdbm.c strcasestr.h redblack.c redblack.h db-memory.c \ - parser.h + parser.h ogg.c diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index 769b94cb..d53c2a67 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -290,6 +290,9 @@ static void scan_music_file(char *path, struct dirent *pde, struct stat *psb); static int scan_decode_mp3_frame(unsigned char *frame, SCAN_FRAMEINFO *pfi); static time_t mac_to_unix_time(int t); +#ifdef OGGVORBIS +extern int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3); +#endif /* * Typedefs @@ -308,6 +311,9 @@ static taghandler taghandlers[] = { { "m4p", scan_get_aactags, scan_get_aacfileinfo }, { "mp3", scan_get_mp3tags, scan_get_mp3fileinfo }, { "url", scan_get_nultags, scan_get_urlfileinfo }, +#ifdef OGGVORBIS + { "ogg", scan_get_nultags, scan_get_oggfileinfo }, +#endif { NULL, 0 } }; @@ -1708,7 +1714,7 @@ void make_composite_tags(MP3FILE *song) if(!strcasecmp(song->type,"ogg")) { song->description = strdup("QuickTime movie file"); } else { - char fdescr[50]; + char fdescr[50]; sprintf(fdescr,"%s audio file",song->type); song->description = strdup(fdescr); @@ -1728,8 +1734,10 @@ void make_composite_tags(MP3FILE *song) if(!song->title) song->title = strdup(song->fname); - if(!strcmp(song->type, "ogg")) + /* PENDING: treat ogg just like any audio file? Doing so allows it + to show up in iTunes but it still can't be streamed. */ + /*if(!strcmp(song->type, "ogg")) song->item_kind = 4; - else + else*/ song->item_kind = 2; } diff --git a/src/ogg.c b/src/ogg.c new file mode 100644 index 00000000..ee0205a1 --- /dev/null +++ b/src/ogg.c @@ -0,0 +1,792 @@ +/* + * $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. + */ +#include +#include +#include +#include +#include +#include +#include +/* +#include +#include "utf8.h" +#include "i18n.h" +*/ +#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; + + if(!file) { + DPRINTF(E_FATAL, L_SCAN, + "Error opening input file \"%s\": %s\n", filename, + strerror(errno)); + return -1; + } + + 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)); + } + + 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; + } + } + } + + free_stream_set(processors); + + 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; + } + + return 0; +} + +/*int main(int argc, char **argv) { + int f, ret; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + process_file(argv[f]); + }*/ +