1/* 2 * $Id: ogg.c,v 1.1 2009-06-30 02:31:08 steven Exp $ 3 * Ogg parsing routines. 4 * 5 * This file has been modified from the 'ogginfo' code in the vorbistools 6 * distribution. The original copyright appears below: 7 * 8 * Ogginfo 9 * 10 * A tool to describe ogg file contents and metadata. 11 * 12 * Copyright 2002 Michael Smith <msmith@xiph.org> 13 * Licensed under the GNU GPL, distributed with this program. 14 */ 15#include <stdio.h> 16#include <stdlib.h> 17#include <errno.h> 18#include <string.h> 19#include <sys/stat.h> 20#include <ogg/ogg.h> 21#include <vorbis/codec.h> 22/* 23#include <locale.h> 24#include "utf8.h" 25#include "i18n.h" 26*/ 27#include "err.h" 28#include "mp3-scanner.h" 29 30#define CHUNK 4500 31 32struct vorbis_release { 33 char *vendor_string; 34 char *desc; 35} releases[] = { 36 {"Xiphophorus libVorbis I 20000508", "1.0 beta 1 or beta 2"}, 37 {"Xiphophorus libVorbis I 20001031", "1.0 beta 3"}, 38 {"Xiphophorus libVorbis I 20010225", "1.0 beta 4"}, 39 {"Xiphophorus libVorbis I 20010615", "1.0 rc1"}, 40 {"Xiphophorus libVorbis I 20010813", "1.0 rc2"}, 41 {"Xiphophorus libVorbis I 20011217", "1.0 rc3"}, 42 {"Xiphophorus libVorbis I 20011231", "1.0 rc3"}, 43 {"Xiph.Org libVorbis I 20020717", "1.0"}, 44 {"Xiph.Org libVorbis I 20030909", "1.0.1"}, 45 {NULL, NULL}, 46 }; 47 48 49/* TODO: 50 * 51 * - detect violations of muxing constraints 52 * - detect granulepos 'gaps' (possibly vorbis-specific). (seperate from 53 * serial-number gaps) 54 */ 55 56typedef struct _stream_processor { 57 void (*process_page)(struct _stream_processor *, ogg_page *, MP3FILE *); 58 void (*process_end)(struct _stream_processor *, MP3FILE *); 59 int isillegal; 60 int constraint_violated; 61 int shownillegal; 62 int isnew; 63 long seqno; 64 int lostseq; 65 66 int start; 67 int end; 68 69 int num; 70 char *type; 71 72 ogg_uint32_t serial; /* must be 32 bit unsigned */ 73 ogg_stream_state os; 74 void *data; 75} stream_processor; 76 77typedef struct { 78 stream_processor *streams; 79 int allocated; 80 int used; 81 82 int in_headers; 83} stream_set; 84 85typedef struct { 86 vorbis_info vi; 87 vorbis_comment vc; 88 89 ogg_int64_t bytes; 90 ogg_int64_t lastgranulepos; 91 ogg_int64_t firstgranulepos; 92 93 int doneheaders; 94} misc_vorbis_info; 95 96#define CONSTRAINT_PAGE_AFTER_EOS 1 97#define CONSTRAINT_MUXING_VIOLATED 2 98 99static stream_set *create_stream_set(void) { 100 stream_set *set = calloc(1, sizeof(stream_set)); 101 102 set->streams = calloc(5, sizeof(stream_processor)); 103 set->allocated = 5; 104 set->used = 0; 105 106 return set; 107} 108 109static void vorbis_process(stream_processor *stream, ogg_page *page, 110 MP3FILE *pmp3) 111{ 112 ogg_packet packet; 113 misc_vorbis_info *inf = stream->data; 114 int i, header=0; 115 int k; 116 117 ogg_stream_pagein(&stream->os, page); 118 if(inf->doneheaders < 3) 119 header = 1; 120 121 while(ogg_stream_packetout(&stream->os, &packet) > 0) { 122 if(inf->doneheaders < 3) { 123 if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0) { 124 DPRINTF(E_WARN, L_SCAN, "Could not decode vorbis header " 125 "packet - invalid vorbis stream (%d)\n", stream->num); 126 continue; 127 } 128 inf->doneheaders++; 129 if(inf->doneheaders == 3) { 130 if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1) 131 DPRINTF(E_WARN, L_SCAN, "Vorbis stream %d does not have headers " 132 "correctly framed. Terminal header page contains " 133 "additional packets or has non-zero granulepos\n", 134 stream->num); 135 DPRINTF(E_DBG, L_SCAN, "Vorbis headers parsed for stream %d, " 136 "information follows...\n", stream->num); 137 /* 138 DPRINTF(E_INF, L_SCAN, "Version: %d\n", inf->vi.version); 139 k = 0; 140 while(releases[k].vendor_string) { 141 if(!strcmp(inf->vc.vendor, releases[k].vendor_string)) { 142 //PENDING: 143 DPRINTF(E_INF, L_SCAN, "Vendor: %s (%s)\n", 144 inf->vc.vendor, releases[k].desc); 145 break; 146 } 147 k++; 148 } 149 150 if(!releases[k].vendor_string) 151 DPRINTF(E_INF, L_SCAN, "Vendor: %s\n", inf->vc.vendor);*/ 152 153 DPRINTF(E_DBG, L_SCAN, "Channels: %d\n", inf->vi.channels); 154 DPRINTF(E_DBG, L_SCAN, "Rate: %ld\n\n", inf->vi.rate); 155 156 pmp3->samplerate = inf->vi.rate; 157 158 if(inf->vi.bitrate_nominal > 0) { 159 DPRINTF(E_DBG, L_SCAN, "Nominal bitrate: %f kb/s\n", 160 (double)inf->vi.bitrate_nominal / 1000.0); 161 pmp3->bitrate = inf->vi.bitrate_nominal / 1000; 162 } 163 else { 164 int upper_rate, lower_rate; 165 166 DPRINTF(E_DBG, L_SCAN, "Nominal bitrate not set\n"); 167 168 /* Average the lower and upper bitrates if set */ 169 170 upper_rate = 0; 171 lower_rate = 0; 172 173 if(inf->vi.bitrate_upper > 0) { 174 DPRINTF(E_DBG, L_SCAN, "Upper bitrate: %f kb/s\n", 175 (double)inf->vi.bitrate_upper / 1000.0); 176 upper_rate = inf->vi.bitrate_upper; 177 } else { 178 DPRINTF(E_DBG, L_SCAN, "Upper bitrate not set\n"); 179 } 180 181 if(inf->vi.bitrate_lower > 0) { 182 DPRINTF(E_DBG, L_SCAN,"Lower bitrate: %f kb/s\n", 183 (double)inf->vi.bitrate_lower / 1000.0); 184 lower_rate = inf->vi.bitrate_lower;; 185 } else { 186 DPRINTF(E_DBG, L_SCAN, "Lower bitrate not set\n"); 187 } 188 189 if (upper_rate && lower_rate) { 190 pmp3->bitrate = (upper_rate + lower_rate) / 2; 191 } else { 192 pmp3->bitrate = upper_rate + lower_rate; 193 } 194 } 195 196 if(inf->vc.comments > 0) 197 DPRINTF(E_DBG, L_SCAN, 198 "User comments section follows...\n"); 199 200 for(i=0; i < inf->vc.comments; i++) { 201 char *sep = strchr(inf->vc.user_comments[i], '='); 202 char *decoded; 203 int j; 204 int broken = 0; 205 unsigned char *val; 206 int bytes; 207 int remaining; 208 209 if(sep == NULL) { 210 DPRINTF(E_WARN, L_SCAN, 211 "Comment %d in stream %d is invalidly " 212 "formatted, does not contain '=': \"%s\"\n", 213 i, stream->num, inf->vc.user_comments[i]); 214 continue; 215 } 216 217 for(j=0; j < sep-inf->vc.user_comments[i]; j++) { 218 if(inf->vc.user_comments[i][j] < 0x20 || 219 inf->vc.user_comments[i][j] > 0x7D) { 220 DPRINTF(E_WARN, L_SCAN, 221 "Warning: Invalid comment fieldname in " 222 "comment %d (stream %d): \"%s\"\n", 223 i, stream->num, inf->vc.user_comments[i]); 224 broken = 1; 225 break; 226 } 227 } 228 229 if(broken) 230 continue; 231 232 val = inf->vc.user_comments[i]; 233 234 j = sep-inf->vc.user_comments[i]+1; 235 while(j < inf->vc.comment_lengths[i]) 236 { 237 remaining = inf->vc.comment_lengths[i] - j; 238 if((val[j] & 0x80) == 0) 239 bytes = 1; 240 else if((val[j] & 0x40) == 0x40) { 241 if((val[j] & 0x20) == 0) 242 bytes = 2; 243 else if((val[j] & 0x10) == 0) 244 bytes = 3; 245 else if((val[j] & 0x08) == 0) 246 bytes = 4; 247 else if((val[j] & 0x04) == 0) 248 bytes = 5; 249 else if((val[j] & 0x02) == 0) 250 bytes = 6; 251 else { 252 DPRINTF(E_WARN, L_SCAN, 253 "Illegal UTF-8 sequence in " 254 "comment %d (stream %d): length " 255 "marker wrong\n", 256 i, stream->num); 257 broken = 1; 258 break; 259 } 260 } 261 else { 262 DPRINTF(E_WARN, L_SCAN, 263 "Illegal UTF-8 sequence in comment " 264 "%d (stream %d): length marker wrong\n", 265 i, stream->num); 266 broken = 1; 267 break; 268 } 269 270 if(bytes > remaining) { 271 DPRINTF(E_WARN, L_SCAN, 272 "Illegal UTF-8 sequence in comment " 273 "%d (stream %d): too few bytes\n", 274 i, stream->num); 275 broken = 1; 276 break; 277 } 278 279 switch(bytes) { 280 case 1: 281 /* No more checks needed */ 282 break; 283 case 2: 284 if((val[j+1] & 0xC0) != 0x80) 285 broken = 1; 286 if((val[j] & 0xFE) == 0xC0) 287 broken = 1; 288 break; 289 case 3: 290 if(!((val[j] == 0xE0 && val[j+1] >= 0xA0 && 291 val[j+1] <= 0xBF && 292 (val[j+2] & 0xC0) == 0x80) || 293 (val[j] >= 0xE1 && val[j] <= 0xEC && 294 (val[j+1] & 0xC0) == 0x80 && 295 (val[j+2] & 0xC0) == 0x80) || 296 (val[j] == 0xED && val[j+1] >= 0x80 && 297 val[j+1] <= 0x9F && 298 (val[j+2] & 0xC0) == 0x80) || 299 (val[j] >= 0xEE && val[j] <= 0xEF && 300 (val[j+1] & 0xC0) == 0x80 && 301 (val[j+2] & 0xC0) == 0x80))) 302 broken = 1; 303 if(val[j] == 0xE0 && (val[j+1] & 0xE0) == 0x80) 304 broken = 1; 305 break; 306 case 4: 307 if(!((val[j] == 0xF0 && val[j+1] >= 0x90 && 308 val[j+1] <= 0xBF && 309 (val[j+2] & 0xC0) == 0x80 && 310 (val[j+3] & 0xC0) == 0x80) || 311 (val[j] >= 0xF1 && val[j] <= 0xF3 && 312 (val[j+1] & 0xC0) == 0x80 && 313 (val[j+2] & 0xC0) == 0x80 && 314 (val[j+3] & 0xC0) == 0x80) || 315 (val[j] == 0xF4 && val[j+1] >= 0x80 && 316 val[j+1] <= 0x8F && 317 (val[j+2] & 0xC0) == 0x80 && 318 (val[j+3] & 0xC0) == 0x80))) 319 broken = 1; 320 if(val[j] == 0xF0 && (val[j+1] & 0xF0) == 0x80) 321 broken = 1; 322 break; 323 /* 5 and 6 aren't actually allowed at this point*/ 324 case 5: 325 broken = 1; 326 break; 327 case 6: 328 broken = 1; 329 break; 330 } 331 332 if(broken) { 333 DPRINTF(E_WARN, L_SCAN, 334 "Illegal UTF-8 sequence in comment " 335 "%d (stream %d): invalid sequence\n", 336 i, stream->num); 337 broken = 1; 338 break; 339 } 340 341 j += bytes; 342 } 343 344 if(!broken) { 345 /* if(utf8_decode(sep+1, &decoded) < 0) { 346 DPRINTF(E_WARN, L_SCAN, 347 "Failure in utf8 decoder. This " 348 "should be impossible\n"); 349 continue; 350 }*/ 351 352 DPRINTF(E_DBG, L_SCAN, 353 "\t%s\n", inf->vc.user_comments[i]); 354 355 if (!strncasecmp(inf->vc.user_comments[i],"TITLE",5)) { 356 pmp3->title = strdup(sep + 1); 357 DPRINTF(E_DBG, L_SCAN, "Mapping %s to title.\n", 358 sep + 1); 359 } else if (!strncasecmp(inf->vc.user_comments[i], "ARTIST", 6)) { 360 pmp3->artist = strdup(sep + 1); 361 DPRINTF(E_DBG, L_SCAN, "Mapping %s to artist.\n", 362 sep + 1); 363 } else if (!strncasecmp(inf->vc.user_comments[i], "ALBUM", 5)) { 364 pmp3->album = strdup(sep + 1); 365 DPRINTF(E_DBG, L_SCAN, "Mapping %s to album.\n", 366 sep + 1); 367 } else if (!strncasecmp(inf->vc.user_comments[i], 368 "TRACKNUMBER", 11)) { 369 pmp3->track = atoi(sep + 1); 370 DPRINTF(E_INF, L_SCAN, "Mapping %s to track.\n", 371 sep + 1); 372 } else if (!strncasecmp(inf->vc.user_comments[i], "GENRE", 5)) { 373 pmp3->genre = strdup(sep + 1); 374 DPRINTF(E_INF, L_SCAN, "Mapping %s to genre.\n", 375 sep + 1); 376 } else if (!strncasecmp(inf->vc.user_comments[i], "DATE", 4)) { 377 //PENDING: Should only parse first 4 characters 378 pmp3->year = atoi(sep + 1); 379 DPRINTF(E_INF, L_SCAN, "Mapping %s to year.\n", 380 sep + 1); 381 } else if (!strncasecmp(inf->vc.user_comments[i], "COMMENT", 7)) { 382 pmp3->comment = strdup(sep + 1); 383 DPRINTF(E_INF, L_SCAN, "Mapping %s to comment.\n", 384 sep + 1); 385 } 386 387 *sep = 0; 388 /* free(decoded);*/ 389 } 390 } 391 } 392 } 393 } 394 395 if(!header) { 396 ogg_int64_t gp = ogg_page_granulepos(page); 397 if(gp > 0) { 398 if(gp < inf->lastgranulepos) 399#ifdef _WIN32 400 DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %I64d to %I64d", 401 stream->num, inf->lastgranulepos, gp); 402#else 403 DPRINTF(E_WARN, L_SCAN, "granulepos in stream %d decreases from %lld to %lld", 404 stream->num, inf->lastgranulepos, gp); 405#endif 406 inf->lastgranulepos = gp; 407 } 408 else { 409 DPRINTF(E_WARN, L_SCAN, "Negative granulepos on vorbis stream outside of headers. This file was created by a buggy encoder\n"); 410 } 411 if(inf->firstgranulepos < 0) { /* Not set yet */ 412 } 413 inf->bytes += page->header_len + page->body_len; 414 } 415} 416 417static void vorbis_end(stream_processor *stream, MP3FILE *pmp3) 418{ 419 misc_vorbis_info *inf = stream->data; 420 long minutes, seconds; 421 double bitrate, time; 422 423 /* This should be lastgranulepos - startgranulepos, or something like that*/ 424 time = (double)inf->lastgranulepos / inf->vi.rate; 425 bitrate = inf->bytes*8 / time / 1000.0; 426 427 if (pmp3 != NULL) { 428 if (pmp3->bitrate <= 0) { 429 pmp3->bitrate = bitrate; 430 } 431 pmp3->song_length = time * 1000; 432 pmp3->file_size = inf->bytes; 433 } 434 435 minutes = (long)time / 60; 436 seconds = (long)time - minutes*60; 437 438#ifdef _WIN32 439 DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n" 440 "\tTotal data length: %I64d bytes\n" 441 "\tPlayback length: %ldm:%02lds\n" 442 "\tAverage bitrate: %f kbps\n", 443 stream->num,inf->bytes, minutes, seconds, bitrate); 444#else 445 DPRINTF(E_DBG, L_SCAN, "Vorbis stream %d:\n" 446 "\tTotal data length: %lld bytes\n" 447 "\tPlayback length: %ldm:%02lds\n" 448 "\tAverage bitrate: %f kbps\n", 449 stream->num,inf->bytes, minutes, seconds, bitrate); 450#endif 451 452 vorbis_comment_clear(&inf->vc); 453 vorbis_info_clear(&inf->vi); 454 455 free(stream->data); 456} 457 458static void process_null(stream_processor *stream, ogg_page *page, MP3FILE *pmp) 459{ 460 /* This is for invalid streams. */ 461} 462 463static void process_other(stream_processor *stream, ogg_page *page, MP3FILE *pmp) 464{ 465 ogg_packet packet; 466 467 ogg_stream_pagein(&stream->os, page); 468 469 while(ogg_stream_packetout(&stream->os, &packet) > 0) { 470 /* Should we do anything here? Currently, we don't */ 471 } 472} 473 474 475static void free_stream_set(stream_set *set) 476{ 477 int i; 478 for(i=0; i < set->used; i++) { 479 if(!set->streams[i].end) { 480 DPRINTF(E_WARN, L_SCAN, "Warning: EOS not set on stream %d\n", 481 set->streams[i].num); 482 //PENDING: 483 if(set->streams[i].process_end) 484 set->streams[i].process_end(&set->streams[i], NULL); 485 } 486 ogg_stream_clear(&set->streams[i].os); 487 } 488 489 free(set->streams); 490 free(set); 491} 492 493static int streams_open(stream_set *set) 494{ 495 int i; 496 int res=0; 497 for(i=0; i < set->used; i++) { 498 if(!set->streams[i].end) 499 res++; 500 } 501 502 return res; 503} 504 505static void null_start(stream_processor *stream) 506{ 507 stream->process_end = NULL; 508 stream->type = "invalid"; 509 stream->process_page = process_null; 510} 511 512static void other_start(stream_processor *stream, char *type) 513{ 514 if(type) 515 stream->type = type; 516 else 517 stream->type = "unknown"; 518 stream->process_page = process_other; 519 stream->process_end = NULL; 520} 521 522static void vorbis_start(stream_processor *stream) 523{ 524 misc_vorbis_info *info; 525 526 stream->type = "vorbis"; 527 stream->process_page = vorbis_process; 528 stream->process_end = vorbis_end; 529 530 stream->data = calloc(1, sizeof(misc_vorbis_info)); 531 532 info = stream->data; 533 534 vorbis_comment_init(&info->vc); 535 vorbis_info_init(&info->vi); 536 537} 538 539static stream_processor *find_stream_processor(stream_set *set, ogg_page *page) 540{ 541 ogg_uint32_t serial = ogg_page_serialno(page); 542 int i, found = 0; 543 int invalid = 0; 544 int constraint = 0; 545 stream_processor *stream; 546 547 for(i=0; i < set->used; i++) { 548 if(serial == set->streams[i].serial) { 549 /* We have a match! */ 550 found = 1; 551 stream = &(set->streams[i]); 552 553 set->in_headers = 0; 554 /* if we have detected EOS, then this can't occur here. */ 555 if(stream->end) { 556 stream->isillegal = 1; 557 stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; 558 return stream; 559 } 560 561 stream->isnew = 0; 562 stream->start = ogg_page_bos(page); 563 stream->end = ogg_page_eos(page); 564 stream->serial = serial; 565 return stream; 566 } 567 } 568 569 /* If there are streams open, and we've reached the end of the 570 * headers, then we can't be starting a new stream. 571 * XXX: might this sometimes catch ok streams if EOS flag is missing, 572 * but the stream is otherwise ok? 573 */ 574 if(streams_open(set) && !set->in_headers) { 575 constraint = CONSTRAINT_MUXING_VIOLATED; 576 invalid = 1; 577 } 578 579 set->in_headers = 1; 580 581 if(set->allocated < set->used) 582 stream = &set->streams[set->used]; 583 else { 584 set->allocated += 5; 585 set->streams = realloc(set->streams, sizeof(stream_processor)* 586 set->allocated); 587 stream = &set->streams[set->used]; 588 } 589 set->used++; 590 stream->num = set->used; /* We count from 1 */ 591 592 stream->isnew = 1; 593 stream->isillegal = invalid; 594 stream->constraint_violated = constraint; 595 596 { 597 int res; 598 ogg_packet packet; 599 600 /* We end up processing the header page twice, but that's ok. */ 601 ogg_stream_init(&stream->os, serial); 602 ogg_stream_pagein(&stream->os, page); 603 res = ogg_stream_packetout(&stream->os, &packet); 604 if(res <= 0) { 605 DPRINTF(E_WARN, L_SCAN, "Invalid header page, no packet found\n"); 606 null_start(stream); 607 } 608 else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7)==0) 609 vorbis_start(stream); 610 else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8)==0) 611 other_start(stream, "MIDI"); 612 else 613 other_start(stream, NULL); 614 615 res = ogg_stream_packetout(&stream->os, &packet); 616 if(res > 0) { 617 DPRINTF(E_WARN, L_SCAN, "Invalid header page in stream %d, " 618 "contains multiple packets\n", stream->num); 619 } 620 621 /* re-init, ready for processing */ 622 ogg_stream_clear(&stream->os); 623 ogg_stream_init(&stream->os, serial); 624 } 625 626 stream->start = ogg_page_bos(page); 627 stream->end = ogg_page_eos(page); 628 stream->serial = serial; 629 630 /* if(stream->serial == 0 || stream->serial == -1) { 631 info(_("Note: Stream %d has serial number %d, which is legal but may " 632 "cause problems with some tools."), stream->num, stream->serial); 633 }*/ 634 635 return stream; 636} 637 638static int get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page, 639 ogg_int64_t *written) 640{ 641 int ret; 642 char *buffer; 643 int bytes; 644 645 while((ret = ogg_sync_pageout(sync, page)) <= 0) { 646 if(ret < 0) 647#ifdef _WIN32 648 DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %I64d bytes. Corrupted ogg.\n", *written); 649#else 650 DPRINTF(E_WARN, L_SCAN, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written); 651#endif 652 653 buffer = ogg_sync_buffer(sync, CHUNK); 654 bytes = fread(buffer, 1, CHUNK, f); 655 if(bytes <= 0) { 656 ogg_sync_wrote(sync, 0); 657 return 0; 658 } 659 ogg_sync_wrote(sync, bytes); 660 *written += bytes; 661 } 662 663 return 1; 664} 665 666int scan_get_oggfileinfo(char *filename, MP3FILE *pmp3) { 667 FILE *file = fopen(filename, "rb"); 668 ogg_sync_state sync; 669 ogg_page page; 670 stream_set *processors = create_stream_set(); 671 int gotpage = 0; 672 ogg_int64_t written = 0; 673 struct stat psb; 674 675 if(!file) { 676 DPRINTF(E_FATAL, L_SCAN, 677 "Error opening input file \"%s\": %s\n", filename, 678 strerror(errno)); 679 return -1; 680 } 681 682 DPRINTF(E_INF, L_SCAN, "Processing file \"%s\"...\n\n", filename); 683 684 if (!stat(filename, &psb)) { 685 pmp3->time_added = psb.st_mtime; 686 if (psb.st_ctime < pmp3->time_added) { 687 pmp3->time_added = psb.st_ctime; 688 } 689 pmp3->time_modified = psb.st_mtime; 690 } else { 691 DPRINTF(E_WARN, L_SCAN, "Error statting: %s\n", strerror(errno)); 692 } 693 694 ogg_sync_init(&sync); 695 696 while(get_next_page(file, &sync, &page, &written)) { 697 stream_processor *p = find_stream_processor(processors, &page); 698 gotpage = 1; 699 700 if(!p) { 701 DPRINTF(E_FATAL, L_SCAN, "Could not find a processor for stream, bailing\n"); 702 return -1; 703 } 704 705 if(p->isillegal && !p->shownillegal) { 706 char *constraint; 707 switch(p->constraint_violated) { 708 case CONSTRAINT_PAGE_AFTER_EOS: 709 constraint = "Page found for stream after EOS flag"; 710 break; 711 case CONSTRAINT_MUXING_VIOLATED: 712 constraint = "Ogg muxing constraints violated, new " 713 "stream before EOS of all previous streams"; 714 break; 715 default: 716 constraint = "Error unknown."; 717 } 718 719 DPRINTF(E_WARN, L_SCAN, 720 "Warning: illegally placed page(s) for logical stream %d\n" 721 "This indicates a corrupt ogg file: %s.\n", 722 p->num, constraint); 723 p->shownillegal = 1; 724 /* If it's a new stream, we want to continue processing this page 725 * anyway to suppress additional spurious errors 726 */ 727 if(!p->isnew) 728 continue; 729 } 730 731 if(p->isnew) { 732 DPRINTF(E_DBG, L_SCAN, "New logical stream (#%d, serial: %08x): type %s\n", 733 p->num, p->serial, p->type); 734 if(!p->start) 735 DPRINTF(E_WARN, L_SCAN, 736 "stream start flag not set on stream %d\n", 737 p->num); 738 } 739 else if(p->start) 740 DPRINTF(E_WARN, L_SCAN, "stream start flag found in mid-stream " 741 "on stream %d\n", p->num); 742 743 if(p->seqno++ != ogg_page_pageno(&page)) { 744 if(!p->lostseq) 745 DPRINTF(E_WARN, L_SCAN, 746 "sequence number gap in stream %d. Got page %ld " 747 "when expecting page %ld. Indicates missing data.\n", 748 p->num, ogg_page_pageno(&page), p->seqno - 1); 749 p->seqno = ogg_page_pageno(&page); 750 p->lostseq = 1; 751 } 752 else 753 p->lostseq = 0; 754 755 if(!p->isillegal) { 756 p->process_page(p, &page, pmp3); 757 758 if(p->end) { 759 if(p->process_end) 760 p->process_end(p, pmp3); 761 DPRINTF(E_DBG, L_SCAN, "Logical stream %d ended\n", p->num); 762 p->isillegal = 1; 763 p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS; 764 } 765 } 766 } 767 768 free_stream_set(processors); 769 770 ogg_sync_clear(&sync); 771 772 fclose(file); 773 774 if(!gotpage) { 775 DPRINTF(E_FATAL, L_SCAN, "No ogg data found in file \"%s\".\n" 776 "Input probably not ogg.\n", filename); 777 return -1; 778 } 779 780 return 0; 781} 782 783/*int main(int argc, char **argv) { 784 int f, ret; 785 786 setlocale(LC_ALL, ""); 787 bindtextdomain(PACKAGE, LOCALEDIR); 788 textdomain(PACKAGE); 789 790 process_file(argv[f]); 791 }*/ 792 793