3 * A tool to describe ogg file contents and metadata.
5 * Copyright 2002-2005 Michael Smith <msmith@xiph.org>
6 * Licensed under the GNU GPL, distributed with this program.
18 #include "getopt.h" /* TODO: eventually getopt.h and this getopt.c implementation should be it's own library */
21 #include <ext/libogg/ogg.h>
22 #include <ext/vorbis/codec.h>
36 #define I64FORMAT "I64d"
38 #define I64FORMAT "lld"
41 struct vorbis_release {
45 {"Xiphophorus libVorbis I 20000508", "1.0 beta 1 or beta 2"},
46 {"Xiphophorus libVorbis I 20001031", "1.0 beta 3"},
47 {"Xiphophorus libVorbis I 20010225", "1.0 beta 4"},
48 {"Xiphophorus libVorbis I 20010615", "1.0 rc1"},
49 {"Xiphophorus libVorbis I 20010813", "1.0 rc2"},
50 {"Xiphophorus libVorbis I 20011217", "1.0 rc3"},
51 {"Xiphophorus libVorbis I 20011231", "1.0 rc3"},
52 {"Xiph.Org libVorbis I 20020717", "1.0"},
53 {"Xiph.Org libVorbis I 20030909", "1.0.1"},
54 {"Xiph.Org libVorbis I 20040629", "1.1.0"},
55 {"Xiph.Org libVorbis I 20050304", "1.1.1"},
56 {"Xiph.Org libVorbis I 20050304", "1.1.2"},
57 {"Xiph.Org libVorbis I 20070622", "1.2.0"},
58 {"Xiph.Org libVorbis I 20080501", "1.2.1"},
65 * - detect violations of muxing constraints
66 * - detect granulepos 'gaps' (possibly vorbis-specific). (seperate from
70 typedef struct _stream_processor {
71 void (*process_page)(struct _stream_processor *, ogg_page *);
72 void (*process_end)(struct _stream_processor *);
74 int constraint_violated;
86 ogg_uint32_t serial; /* must be 32 bit unsigned */
92 stream_processor *streams;
104 ogg_int64_t lastgranulepos;
105 ogg_int64_t firstgranulepos;
115 ogg_int64_t lastgranulepos;
116 ogg_int64_t firstgranulepos;
120 ogg_int64_t framenum_expected;
137 ogg_int64_t lastgranulepos;
138 ogg_int64_t firstgranulepos;
143 static int printlots = 0;
144 static int printinfo = 1;
145 static int printwarn = 1;
146 static int verbose = 1;
150 #define CONSTRAINT_PAGE_AFTER_EOS 1
151 #define CONSTRAINT_MUXING_VIOLATED 2
153 static stream_set *create_stream_set(void) {
154 stream_set *set = calloc(1, sizeof(stream_set));
156 set->streams = calloc(5, sizeof(stream_processor));
163 static void info(char *format, ...)
170 va_start(ap, format);
171 vfprintf(stdout, format, ap);
175 static void warn(char *format, ...)
183 va_start(ap, format);
184 vfprintf(stdout, format, ap);
188 static void error(char *format, ...)
194 va_start(ap, format);
195 vfprintf(stdout, format, ap);
199 static void check_xiph_comment(stream_processor *stream, int i, const char *comment,
202 char *sep = strchr(comment, '=');
210 warn(_("WARNING: Comment %d in stream %d has invalid "
211 "format, does not contain '=': \"%s\"\n"),
212 i, stream->num, comment);
216 for(j=0; j < sep-comment; j++) {
217 if(comment[j] < 0x20 || comment[j] > 0x7D) {
218 warn(_("WARNING: Invalid comment fieldname in "
219 "comment %d (stream %d): \"%s\"\n"),
220 i, stream->num, comment);
229 val = (unsigned char *)comment;
232 while(j < comment_length)
234 remaining = comment_length - j;
235 if((val[j] & 0x80) == 0)
237 else if((val[j] & 0x40) == 0x40) {
238 if((val[j] & 0x20) == 0)
240 else if((val[j] & 0x10) == 0)
242 else if((val[j] & 0x08) == 0)
244 else if((val[j] & 0x04) == 0)
246 else if((val[j] & 0x02) == 0)
249 warn(_("WARNING: Illegal UTF-8 sequence in "
250 "comment %d (stream %d): length marker wrong\n"),
257 warn(_("WARNING: Illegal UTF-8 sequence in comment "
258 "%d (stream %d): length marker wrong\n"), i, stream->num);
263 if(bytes > remaining) {
264 warn(_("WARNING: Illegal UTF-8 sequence in comment "
265 "%d (stream %d): too few bytes\n"), i, stream->num);
272 /* No more checks needed */
275 if((val[j+1] & 0xC0) != 0x80)
277 if((val[j] & 0xFE) == 0xC0)
281 if(!((val[j] == 0xE0 && val[j+1] >= 0xA0 && val[j+1] <= 0xBF &&
282 (val[j+2] & 0xC0) == 0x80) ||
283 (val[j] >= 0xE1 && val[j] <= 0xEC &&
284 (val[j+1] & 0xC0) == 0x80 &&
285 (val[j+2] & 0xC0) == 0x80) ||
286 (val[j] == 0xED && val[j+1] >= 0x80 &&
288 (val[j+2] & 0xC0) == 0x80) ||
289 (val[j] >= 0xEE && val[j] <= 0xEF &&
290 (val[j+1] & 0xC0) == 0x80 &&
291 (val[j+2] & 0xC0) == 0x80)))
293 if(val[j] == 0xE0 && (val[j+1] & 0xE0) == 0x80)
297 if(!((val[j] == 0xF0 && val[j+1] >= 0x90 &&
299 (val[j+2] & 0xC0) == 0x80 &&
300 (val[j+3] & 0xC0) == 0x80) ||
301 (val[j] >= 0xF1 && val[j] <= 0xF3 &&
302 (val[j+1] & 0xC0) == 0x80 &&
303 (val[j+2] & 0xC0) == 0x80 &&
304 (val[j+3] & 0xC0) == 0x80) ||
305 (val[j] == 0xF4 && val[j+1] >= 0x80 &&
307 (val[j+2] & 0xC0) == 0x80 &&
308 (val[j+3] & 0xC0) == 0x80)))
310 if(val[j] == 0xF0 && (val[j+1] & 0xF0) == 0x80)
313 /* 5 and 6 aren't actually allowed at this point */
323 char *simple = malloc (comment_length + 1);
324 char *seq = malloc (comment_length * 3 + 1);
325 static char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
326 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
327 int i, c1 = 0, c2 = 0;
328 for (i = 0; i < comment_length; i++) {
329 seq[c1++] = hex[((unsigned char)comment[i]) >> 4];
330 seq[c1++] = hex[((unsigned char)comment[i]) & 0xf];
333 if(comment[i] < 0x20 || comment[i] > 0x7D)
336 simple[c2++] = comment[i];
340 warn(_("WARNING: Illegal UTF-8 sequence in comment "
341 "%d (stream %d): invalid sequence \"%s\": %s\n"), i,
342 stream->num, simple, seq);
354 info("\t%s=%s\n", comment, sep+1);
358 static void theora_process(stream_processor *stream, ogg_page *page)
361 misc_theora_info *inf = stream->data;
365 ogg_stream_pagein(&stream->os, page);
366 if(inf->doneheaders < 3)
370 res = ogg_stream_packetout(&stream->os, &packet);
372 warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
378 if(inf->doneheaders < 3) {
379 if(theora_decode_header(&inf->ti, &inf->tc, &packet) < 0) {
380 warn(_("WARNING: Could not decode Theora header "
381 "packet - invalid Theora stream (%d)\n"), stream->num);
385 if(inf->doneheaders == 3) {
386 if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
387 warn(_("WARNING: Theora stream %d does not have headers "
388 "correctly framed. Terminal header page contains "
389 "additional packets or has non-zero granulepos\n"),
391 info(_("Theora headers parsed for stream %d, "
392 "information follows...\n"), stream->num);
394 info(_("Version: %d.%d.%d\n"), inf->ti.version_major, inf->ti.version_minor, inf->ti.version_subminor);
396 info(_("Vendor: %s\n"), inf->tc.vendor);
397 info(_("Width: %d\n"), inf->ti.frame_width);
398 info(_("Height: %d\n"), inf->ti.frame_height);
399 info(_("Total image: %d by %d, crop offset (%d, %d)\n"),
400 inf->ti.width, inf->ti.height, inf->ti.offset_x, inf->ti.offset_y);
401 if(inf->ti.offset_x + inf->ti.frame_width > inf->ti.width)
402 warn(_("Frame offset/size invalid: width incorrect\n"));
403 if(inf->ti.offset_y + inf->ti.frame_height > inf->ti.height)
404 warn(_("Frame offset/size invalid: height incorrect\n"));
406 if(inf->ti.fps_numerator == 0 || inf->ti.fps_denominator == 0)
407 warn(_("Invalid zero framerate\n"));
409 info(_("Framerate %d/%d (%.02f fps)\n"), inf->ti.fps_numerator, inf->ti.fps_denominator, (float)inf->ti.fps_numerator/(float)inf->ti.fps_denominator);
411 if(inf->ti.aspect_numerator == 0 || inf->ti.aspect_denominator == 0)
413 info(_("Aspect ratio undefined\n"));
417 float frameaspect = (float)inf->ti.frame_width/(float)inf->ti.frame_height * (float)inf->ti.aspect_numerator/(float)inf->ti.aspect_denominator;
418 info(_("Pixel aspect ratio %d:%d (%f:1)\n"), inf->ti.aspect_numerator, inf->ti.aspect_denominator, (float)inf->ti.aspect_numerator/(float)inf->ti.aspect_denominator);
419 if(fabs(frameaspect - 4.0/3.0) < 0.02)
420 info(_("Frame aspect 4:3\n"));
421 else if(fabs(frameaspect - 16.0/9.0) < 0.02)
422 info(_("Frame aspect 16:9\n"));
424 info(_("Frame aspect %f:1\n"), frameaspect);
427 if(inf->ti.colorspace == OC_CS_ITU_REC_470M)
428 info(_("Colourspace: Rec. ITU-R BT.470-6 System M (NTSC)\n"));
429 else if(inf->ti.colorspace == OC_CS_ITU_REC_470BG)
430 info(_("Colourspace: Rec. ITU-R BT.470-6 Systems B and G (PAL)\n"));
432 info(_("Colourspace unspecified\n"));
434 if(inf->ti.pixelformat == OC_PF_420)
435 info(_("Pixel format 4:2:0\n"));
436 else if(inf->ti.pixelformat == OC_PF_422)
437 info(_("Pixel format 4:2:2\n"));
438 else if(inf->ti.pixelformat == OC_PF_444)
439 info(_("Pixel format 4:4:4\n"));
441 warn(_("Pixel format invalid\n"));
443 info(_("Target bitrate: %d kbps\n"), inf->ti.target_bitrate/1000);
444 info(_("Nominal quality setting (0-63): %d\n"), inf->ti.quality);
446 if(inf->tc.comments > 0)
447 info(_("User comments section follows...\n"));
449 for(i=0; i < inf->tc.comments; i++) {
450 char *comment = inf->tc.user_comments[i];
451 check_xiph_comment(stream, i, comment,
452 inf->tc.comment_lengths[i]);
457 ogg_int64_t framenum;
458 ogg_int64_t iframe,pframe;
459 ogg_int64_t gp = packet.granulepos;
462 iframe=gp>>inf->ti.granule_shift;
463 pframe=gp-(iframe<<inf->ti.granule_shift);
464 framenum = iframe+pframe;
465 if(inf->framenum_expected >= 0 &&
466 inf->framenum_expected != framenum)
468 warn(_("WARNING: Expected frame %" I64FORMAT
469 ", got %" I64FORMAT "\n"),
470 inf->framenum_expected, framenum);
472 inf->framenum_expected = framenum + 1;
474 else if (inf->framenum_expected >= 0) {
475 inf->framenum_expected++;
481 ogg_int64_t gp = ogg_page_granulepos(page);
483 if(gp < inf->lastgranulepos)
484 warn(_("WARNING: granulepos in stream %d decreases from %"
485 I64FORMAT " to %" I64FORMAT "\n"),
486 stream->num, inf->lastgranulepos, gp);
487 inf->lastgranulepos = gp;
489 if(inf->firstgranulepos < 0) { /* Not set yet */
491 inf->bytes += page->header_len + page->body_len;
495 static void theora_end(stream_processor *stream)
497 misc_theora_info *inf = stream->data;
498 long minutes, seconds, milliseconds;
499 double bitrate, time;
501 /* This should be lastgranulepos - startgranulepos, or something like that*/
502 ogg_int64_t iframe=inf->lastgranulepos>>inf->ti.granule_shift;
503 ogg_int64_t pframe=inf->lastgranulepos-(iframe<<inf->ti.granule_shift);
504 time = (double)(iframe+pframe) /
505 ((float)inf->ti.fps_numerator/(float)inf->ti.fps_denominator);
506 minutes = (long)time / 60;
507 seconds = (long)time - minutes*60;
508 milliseconds = (long)((time - minutes*60 - seconds)*1000);
509 bitrate = inf->bytes*8 / time / 1000.0;
511 info(_("Theora stream %d:\n"
512 "\tTotal data length: %" I64FORMAT " bytes\n"
513 "\tPlayback length: %ldm:%02ld.%03lds\n"
514 "\tAverage bitrate: %f kb/s\n"),
515 stream->num,inf->bytes, minutes, seconds, milliseconds, bitrate);
517 theora_comment_clear(&inf->tc);
518 theora_info_clear(&inf->ti);
524 static void vorbis_process(stream_processor *stream, ogg_page *page )
527 misc_vorbis_info *inf = stream->data;
528 int i, header=0, packets=0;
532 ogg_stream_pagein(&stream->os, page);
533 if(inf->doneheaders < 3)
537 res = ogg_stream_packetout(&stream->os, &packet);
539 warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
546 if(inf->doneheaders < 3) {
547 if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0) {
548 warn(_("WARNING: Could not decode Vorbis header "
549 "packet %d - invalid Vorbis stream (%d)\n"),
550 inf->doneheaders, stream->num);
554 if(inf->doneheaders == 3) {
555 if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
556 warn(_("WARNING: Vorbis stream %d does not have headers "
557 "correctly framed. Terminal header page contains "
558 "additional packets or has non-zero granulepos\n"),
560 info(_("Vorbis headers parsed for stream %d, "
561 "information follows...\n"), stream->num);
563 info(_("Version: %d\n"), inf->vi.version);
565 while(releases[k].vendor_string) {
566 if(!strcmp(inf->vc.vendor, releases[k].vendor_string)) {
567 info(_("Vendor: %s (%s)\n"), inf->vc.vendor,
573 if(!releases[k].vendor_string)
574 info(_("Vendor: %s\n"), inf->vc.vendor);
575 info(_("Channels: %d\n"), inf->vi.channels);
576 info(_("Rate: %ld\n\n"), inf->vi.rate);
578 if(inf->vi.bitrate_nominal > 0)
579 info(_("Nominal bitrate: %f kb/s\n"),
580 (double)inf->vi.bitrate_nominal / 1000.0);
582 info(_("Nominal bitrate not set\n"));
584 if(inf->vi.bitrate_upper > 0)
585 info(_("Upper bitrate: %f kb/s\n"),
586 (double)inf->vi.bitrate_upper / 1000.0);
588 info(_("Upper bitrate not set\n"));
590 if(inf->vi.bitrate_lower > 0)
591 info(_("Lower bitrate: %f kb/s\n"),
592 (double)inf->vi.bitrate_lower / 1000.0);
594 info(_("Lower bitrate not set\n"));
596 if(inf->vc.comments > 0)
597 info(_("User comments section follows...\n"));
599 for(i=0; i < inf->vc.comments; i++) {
600 char *comment = inf->vc.user_comments[i];
601 check_xiph_comment(stream, i, comment,
602 inf->vc.comment_lengths[i]);
609 ogg_int64_t gp = ogg_page_granulepos(page);
611 if(gp < inf->lastgranulepos)
612 warn(_("WARNING: granulepos in stream %d decreases from %"
613 I64FORMAT " to %" I64FORMAT "\n" ),
614 stream->num, inf->lastgranulepos, gp);
615 inf->lastgranulepos = gp;
618 /* Only do this if we saw at least one packet ending on this page.
619 * It's legal (though very unusual) to have no packets in a page at
620 * all - this is occasionally used to have an empty EOS page */
621 warn(_("Negative or zero granulepos (%" I64FORMAT ") on Vorbis stream outside of headers. This file was created by a buggy encoder\n"), gp);
623 if(inf->firstgranulepos < 0) { /* Not set yet */
625 inf->bytes += page->header_len + page->body_len;
629 static void vorbis_end(stream_processor *stream)
631 misc_vorbis_info *inf = stream->data;
632 long minutes, seconds, milliseconds;
633 double bitrate, time;
635 /* This should be lastgranulepos - startgranulepos, or something like that*/
636 time = (double)inf->lastgranulepos / inf->vi.rate;
637 minutes = (long)time / 60;
638 seconds = (long)time - minutes*60;
639 milliseconds = (long)((time - minutes*60 - seconds)*1000);
640 bitrate = inf->bytes*8 / time / 1000.0;
642 info(_("Vorbis stream %d:\n"
643 "\tTotal data length: %" I64FORMAT " bytes\n"
644 "\tPlayback length: %ldm:%02ld.%03lds\n"
645 "\tAverage bitrate: %f kb/s\n"),
646 stream->num,inf->bytes, minutes, seconds, milliseconds, bitrate);
648 vorbis_comment_clear(&inf->vc);
649 vorbis_info_clear(&inf->vi);
654 static void kate_process(stream_processor *stream, ogg_page *page )
657 misc_kate_info *inf = stream->data;
658 int header=0, packets=0;
663 ogg_stream_pagein(&stream->os, page);
664 if(!inf->doneheaders)
668 res = ogg_stream_packetout(&stream->os, &packet);
670 warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
677 if(!inf->doneheaders) {
680 /* if we're not building against libkate, do some limited checks */
681 if (packet.bytes<64 || memcmp(packet.packet+1, "kate\0\0\0", 7)) {
682 warn(_("WARNING: packet %d does not seem to be a Kate header - "
683 "invalid Kate stream (%d)\n"),
684 packet.packetno, stream->num);
687 if (packet.packetno==inf->num_headers) {
692 if (packet.packetno==0) {
695 inf->major = packet.packet[9];
696 inf->minor = packet.packet[10];
697 inf->num_headers = packet.packet[11];
698 memcpy(inf->language, packet.packet+32, 16);
699 inf->language[15] = 0;
700 memcpy(inf->category, packet.packet+48, 16);
701 inf->category[15] = 0;
705 if(inf->doneheaders) {
706 if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
707 warn(_("WARNING: Kate stream %d does not have headers "
708 "correctly framed. Terminal header page contains "
709 "additional packets or has non-zero granulepos\n"),
711 info(_("Kate headers parsed for stream %d, "
712 "information follows...\n"), stream->num);
714 info(_("Version: %d.%d\n"), inf->major, inf->minor);
718 if (*inf->language) {
719 info(_("Language: %s\n"), inf->language);
722 info(_("No language set\n"));
724 if (*inf->category) {
725 info(_("Category: %s\n"), inf->category);
728 info(_("No category set\n"));
739 ogg_int64_t gp = ogg_page_granulepos(page);
741 if(gp < inf->lastgranulepos)
742 warn(_("WARNING: granulepos in stream %d decreases from %"
743 I64FORMAT " to %" I64FORMAT "\n" ),
744 stream->num, inf->lastgranulepos, gp);
745 inf->lastgranulepos = gp;
747 else if(packets && gp<0) { /* zero granpos on data is valid for kate */
748 /* Only do this if we saw at least one packet ending on this page.
749 * It's legal (though very unusual) to have no packets in a page at
750 * all - this is occasionally used to have an empty EOS page */
751 warn(_("Negative granulepos (%" I64FORMAT ") on Kate stream outside of headers. This file was created by a buggy encoder\n"), gp);
753 if(inf->firstgranulepos < 0) { /* Not set yet */
755 inf->bytes += page->header_len + page->body_len;
761 static void kate_end(stream_processor *stream)
767 static void process_null(stream_processor *stream, ogg_page *page)
769 /* This is for invalid streams. */
772 static void process_other(stream_processor *stream, ogg_page *page )
776 ogg_stream_pagein(&stream->os, page);
778 while(ogg_stream_packetout(&stream->os, &packet) > 0) {
779 /* Should we do anything here? Currently, we don't */
784 static void free_stream_set(stream_set *set)
787 for(i=0; i < set->used; i++) {
788 if(!set->streams[i].end) {
789 warn(_("WARNING: EOS not set on stream %d\n"),
790 set->streams[i].num);
791 if(set->streams[i].process_end)
792 set->streams[i].process_end(&set->streams[i]);
794 ogg_stream_clear(&set->streams[i].os);
801 static int streams_open(stream_set *set)
805 for(i=0; i < set->used; i++) {
806 if(!set->streams[i].end)
813 static void null_start(stream_processor *stream)
815 stream->process_end = NULL;
816 stream->type = "invalid";
817 stream->process_page = process_null;
820 static void other_start(stream_processor *stream, char *type)
825 stream->type = "unknown";
826 stream->process_page = process_other;
827 stream->process_end = NULL;
830 static void theora_start(stream_processor *stream)
832 misc_theora_info *info;
834 stream->type = "theora";
835 stream->process_page = theora_process;
836 stream->process_end = theora_end;
838 stream->data = calloc(1, sizeof(misc_theora_info));
840 info->framenum_expected = -1;
843 static void vorbis_start(stream_processor *stream)
845 misc_vorbis_info *info;
847 stream->type = "vorbis";
848 stream->process_page = vorbis_process;
849 stream->process_end = vorbis_end;
851 stream->data = calloc(1, sizeof(misc_vorbis_info));
855 vorbis_comment_init(&info->vc);
856 vorbis_info_init(&info->vi);
860 static void kate_start(stream_processor *stream)
862 misc_kate_info *info;
864 stream->type = "kate";
865 stream->process_page = kate_process;
866 stream->process_end = kate_end;
868 stream->data = calloc(1, sizeof(misc_kate_info));
876 static stream_processor *find_stream_processor(stream_set *set, ogg_page *page)
878 ogg_uint32_t serial = ogg_page_serialno(page);
882 stream_processor *stream;
884 for(i=0; i < set->used; i++) {
885 if(serial == set->streams[i].serial) {
886 /* We have a match! */
887 stream = &(set->streams[i]);
890 /* if we have detected EOS, then this can't occur here. */
892 stream->isillegal = 1;
893 stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
898 stream->start = ogg_page_bos(page);
899 stream->end = ogg_page_eos(page);
900 stream->serial = serial;
905 /* If there are streams open, and we've reached the end of the
906 * headers, then we can't be starting a new stream.
907 * XXX: might this sometimes catch ok streams if EOS flag is missing,
908 * but the stream is otherwise ok?
910 if(streams_open(set) && !set->in_headers) {
911 constraint = CONSTRAINT_MUXING_VIOLATED;
917 if(set->allocated < set->used)
918 stream = &set->streams[set->used];
921 set->streams = realloc(set->streams, sizeof(stream_processor)*
923 stream = &set->streams[set->used];
926 stream->num = set->used; /* We count from 1 */
929 stream->isillegal = invalid;
930 stream->constraint_violated = constraint;
936 /* We end up processing the header page twice, but that's ok. */
937 ogg_stream_init(&stream->os, serial);
938 ogg_stream_pagein(&stream->os, page);
939 res = ogg_stream_packetout(&stream->os, &packet);
941 warn(_("WARNING: Invalid header page, no packet found\n"));
944 else if(packet.bytes >= 7 && memcmp(packet.packet, "\x01vorbis", 7)==0)
945 vorbis_start(stream);
946 else if(packet.bytes >= 7 && memcmp(packet.packet, "\x80theora", 7)==0)
947 theora_start(stream);
948 else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8)==0)
949 other_start(stream, "MIDI");
950 else if(packet.bytes >= 5 && memcmp(packet.packet, "\177FLAC", 5)==0)
951 other_start(stream, "FLAC");
952 else if(packet.bytes == 4 && memcmp(packet.packet, "fLaC", 4)==0)
953 other_start(stream, "FLAC (legacy)");
954 else if(packet.bytes >= 8 && memcmp(packet.packet, "Speex ", 8)==0)
955 other_start(stream, "speex");
956 else if(packet.bytes >= 8 && memcmp(packet.packet, "fishead\0", 8)==0)
957 other_start(stream, "skeleton");
958 else if(packet.bytes >= 5 && memcmp(packet.packet, "BBCD\0", 5)==0)
959 other_start(stream, "dirac");
960 else if(packet.bytes >= 8 && memcmp(packet.packet, "KW-DIRAC", 8)==0)
961 other_start(stream, "dirac (legacy)");
962 else if(packet.bytes >= 8 && memcmp(packet.packet, "\x80kate\0\0\0", 8)==0)
965 other_start(stream, NULL);
967 res = ogg_stream_packetout(&stream->os, &packet);
969 warn(_("WARNING: Invalid header page in stream %d, "
970 "contains multiple packets\n"), stream->num);
973 /* re-init, ready for processing */
974 ogg_stream_clear(&stream->os);
975 ogg_stream_init(&stream->os, serial);
978 stream->start = ogg_page_bos(page);
979 stream->end = ogg_page_eos(page);
980 stream->serial = serial;
982 if(stream->serial == 0 || stream->serial == -1) {
983 info(_("Note: Stream %d has serial number %d, which is legal but may "
984 "cause problems with some tools.\n"), stream->num,
991 static int get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
992 ogg_int64_t *written)
998 while((ret = ogg_sync_pageseek(sync, page)) <= 0) {
1000 /* unsynced, we jump over bytes to a possible capture - we don't need to read more just yet */
1001 warn(_("WARNING: Hole in data (%d bytes) found at approximate offset %" I64FORMAT " bytes. Corrupted Ogg.\n"), -ret, *written);
1005 /* zero return, we didn't have enough data to find a whole page, read */
1006 buffer = ogg_sync_buffer(sync, CHUNK);
1007 bytes = fread(buffer, 1, CHUNK, f);
1009 ogg_sync_wrote(sync, 0);
1012 ogg_sync_wrote(sync, bytes);
1019 static void process_file(char *filename) {
1020 FILE *file = fopen(filename, "rb");
1021 ogg_sync_state sync;
1023 stream_set *processors = create_stream_set();
1025 ogg_int64_t written = 0;
1028 error(_("Error opening input file \"%s\": %s\n"), filename,
1033 printf(_("Processing file \"%s\"...\n\n"), filename);
1035 ogg_sync_init(&sync);
1037 while(get_next_page(file, &sync, &page, &written)) {
1038 stream_processor *p = find_stream_processor(processors, &page);
1042 error(_("Could not find a processor for stream, bailing\n"));
1046 if(p->isillegal && !p->shownillegal) {
1048 switch(p->constraint_violated) {
1049 case CONSTRAINT_PAGE_AFTER_EOS:
1050 constraint = _("Page found for stream after EOS flag");
1052 case CONSTRAINT_MUXING_VIOLATED:
1053 constraint = _("Ogg muxing constraints violated, new "
1054 "stream before EOS of all previous streams");
1057 constraint = _("Error unknown.");
1060 warn(_("WARNING: illegally placed page(s) for logical stream %d\n"
1061 "This indicates a corrupt Ogg file: %s.\n"),
1062 p->num, constraint);
1063 p->shownillegal = 1;
1064 /* If it's a new stream, we want to continue processing this page
1065 * anyway to suppress additional spurious errors
1072 info(_("New logical stream (#%d, serial: %08x): type %s\n"),
1073 p->num, p->serial, p->type);
1075 warn(_("WARNING: stream start flag not set on stream %d\n"),
1079 warn(_("WARNING: stream start flag found in mid-stream "
1080 "on stream %d\n"), p->num);
1082 if(p->seqno++ != ogg_page_pageno(&page)) {
1084 warn(_("WARNING: sequence number gap in stream %d. Got page "
1085 "%ld when expecting page %ld. Indicates missing data.\n"
1086 ), p->num, ogg_page_pageno(&page), p->seqno - 1);
1087 p->seqno = ogg_page_pageno(&page);
1094 p->process_page(p, &page);
1099 info(_("Logical stream %d ended\n"), p->num);
1101 p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
1107 error(_("ERROR: No Ogg data found in file \"%s\".\n"
1108 "Input probably not Ogg.\n"), filename);
1110 free_stream_set(processors);
1112 ogg_sync_clear(&sync);
1117 static void version (void) {
1118 printf (_("ogginfo from %s %s\n"), PACKAGE, VERSION);
1121 static void usage(void) {
1123 printf (_(" by the Xiph.Org Foundation (http://www.xiph.org/)\n\n"));
1124 printf(_("(c) 2003-2005 Michael Smith <msmith@xiph.org>\n"
1126 "Usage: ogginfo [flags] file1.ogg [file2.ogx ... fileN.ogv]\n"
1127 "Flags supported:\n"
1128 "\t-h Show this help message\n"
1129 "\t-q Make less verbose. Once will remove detailed informative\n"
1130 "\t messages, two will remove warnings\n"
1131 "\t-v Make more verbose. This may enable more detailed checks\n"
1132 "\t for some stream types.\n"));
1133 printf (_("\t-V Output version information and exit\n"));
1136 int main(int argc, char **argv) {
1139 setlocale(LC_ALL, "");
1140 bindtextdomain(PACKAGE, LOCALEDIR);
1141 textdomain(PACKAGE);
1145 _("Usage: ogginfo [flags] file1.ogg [file2.ogx ... fileN.ogv]\n"
1147 "ogginfo is a tool for printing information about Ogg files\n"
1148 "and for diagnosing problems with them.\n"
1149 "Full help shown with \"ogginfo -h\".\n"));
1153 while((ret = getopt(argc, argv, "hqvV")) >= 0) {
1177 if(optind >= argc) {
1179 _("No input files specified. \"ogginfo -h\" for help\n"));
1185 for(f=optind; f < argc; f++) {
1187 process_file(argv[f]);