]> 4ch.mooo.com Git - 16.git/blob - src/lib/dl/ext/vorbtool/ogginfo2.c
meh did some cleanings and i will work on mapread to mm thingy sometime soon! oops...
[16.git] / src / lib / dl / ext / vorbtool / ogginfo2.c
1 /* Ogginfo
2  *
3  * A tool to describe ogg file contents and metadata.
4  *
5  * Copyright 2002-2005 Michael Smith <msmith@xiph.org>
6  * Licensed under the GNU GPL, distributed with this program.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <stdarg.h>
18 #include "getopt.h" /* TODO: eventually getopt.h and this getopt.c implementation should be it's own library */
19 #include <math.h>
20
21 #include <ext/libogg/ogg.h>
22 #include <ext/vorbis/codec.h>
23
24 #ifdef HAVE_KATE
25 #endif
26
27 #include <locale.h>
28 #include "utf8.h"
29 #include "i18n.h"
30
31 #include "theora.h"
32
33 #define CHUNK 4500
34
35 #ifdef _WIN32
36 #define I64FORMAT "I64d"
37 #else
38 #define I64FORMAT "lld"
39 #endif
40
41 struct vorbis_release {
42     char *vendor_string;
43     char *desc;
44 } releases[] = {
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"},
59         {NULL, NULL},
60     };
61
62
63 /* TODO:
64  *
65  * - detect violations of muxing constraints
66  * - detect granulepos 'gaps' (possibly vorbis-specific). (seperate from
67  *   serial-number gaps)
68  */
69
70 typedef struct _stream_processor {
71     void (*process_page)(struct _stream_processor *, ogg_page *);
72     void (*process_end)(struct _stream_processor *);
73     int isillegal;
74     int constraint_violated;
75     int shownillegal;
76     int isnew;
77     long seqno;
78     int lostseq;
79
80     int start;
81     int end;
82
83     int num;
84     char *type;
85
86     ogg_uint32_t serial; /* must be 32 bit unsigned */
87     ogg_stream_state os;
88     void *data;
89 } stream_processor;
90
91 typedef struct {
92     stream_processor *streams;
93     int allocated;
94     int used;
95
96     int in_headers;
97 } stream_set;
98
99 typedef struct {
100     vorbis_info vi;
101     vorbis_comment vc;
102
103     ogg_int64_t bytes;
104     ogg_int64_t lastgranulepos;
105     ogg_int64_t firstgranulepos;
106
107     int doneheaders;
108 } misc_vorbis_info;
109
110 typedef struct {
111     theora_info ti;
112     theora_comment tc;
113
114     ogg_int64_t bytes;
115     ogg_int64_t lastgranulepos;
116     ogg_int64_t firstgranulepos;
117
118     int doneheaders;
119
120     ogg_int64_t framenum_expected;
121 } misc_theora_info;
122
123 typedef struct {
124 #ifdef HAVE_KATE
125     kate_info ki;
126     kate_comment kc;
127 #else
128     int num_headers;
129 #endif
130
131     int major;
132     int minor;
133     char language[16];
134     char category[16];
135
136     ogg_int64_t bytes;
137     ogg_int64_t lastgranulepos;
138     ogg_int64_t firstgranulepos;
139
140     int doneheaders;
141 } misc_kate_info;
142
143 static int printlots = 0;
144 static int printinfo = 1;
145 static int printwarn = 1;
146 static int verbose = 1;
147
148 static int flawed;
149
150 #define CONSTRAINT_PAGE_AFTER_EOS   1
151 #define CONSTRAINT_MUXING_VIOLATED  2
152
153 static stream_set *create_stream_set(void) {
154     stream_set *set = calloc(1, sizeof(stream_set));
155
156     set->streams = calloc(5, sizeof(stream_processor));
157     set->allocated = 5;
158     set->used = 0;
159
160     return set;
161 }
162
163 static void info(char *format, ...) 
164 {
165     va_list ap;
166
167     if(!printinfo)
168         return;
169
170     va_start(ap, format);
171     vfprintf(stdout, format, ap);
172     va_end(ap);
173 }
174
175 static void warn(char *format, ...)
176 {
177     va_list ap;
178
179     flawed = 1;
180     if(!printwarn)
181         return;
182
183     va_start(ap, format);
184     vfprintf(stdout, format, ap);
185     va_end(ap);
186 }
187
188 static void error(char *format, ...)
189 {
190     va_list ap;
191
192     flawed = 1;
193
194     va_start(ap, format);
195     vfprintf(stdout, format, ap);
196     va_end(ap);
197 }
198
199 static void check_xiph_comment(stream_processor *stream, int i, const char *comment,
200     int comment_length)
201 {
202     char *sep = strchr(comment, '=');
203     int j;
204     int broken = 0;
205     unsigned char *val;
206     int bytes;
207     int remaining;
208
209     if(sep == NULL) {
210         warn(_("WARNING: Comment %d in stream %d has invalid "
211               "format, does not contain '=': \"%s\"\n"), 
212               i, stream->num, comment);
213              return;
214     }
215
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);
221             broken = 1;
222             break;
223         }
224     }
225
226     if(broken)
227         return;
228
229     val = (unsigned char *)comment;
230
231     j = sep-comment+1;
232     while(j < comment_length)
233     {
234         remaining = comment_length - j;
235         if((val[j] & 0x80) == 0)
236             bytes = 1;
237         else if((val[j] & 0x40) == 0x40) {
238             if((val[j] & 0x20) == 0)
239                 bytes = 2;
240             else if((val[j] & 0x10) == 0)
241                 bytes = 3;
242             else if((val[j] & 0x08) == 0)
243                 bytes = 4;
244             else if((val[j] & 0x04) == 0)
245                 bytes = 5;
246             else if((val[j] & 0x02) == 0)
247                 bytes = 6;
248             else {
249                 warn(_("WARNING: Illegal UTF-8 sequence in "
250                     "comment %d (stream %d): length marker wrong\n"),
251                     i, stream->num);
252                 broken = 1;
253                 break;
254             }
255         }
256         else {
257             warn(_("WARNING: Illegal UTF-8 sequence in comment "
258                 "%d (stream %d): length marker wrong\n"), i, stream->num);
259             broken = 1;
260             break;
261         }
262
263         if(bytes > remaining) {
264             warn(_("WARNING: Illegal UTF-8 sequence in comment "
265                 "%d (stream %d): too few bytes\n"), i, stream->num);
266             broken = 1;
267             break;
268         }
269
270         switch(bytes) {
271             case 1:
272                 /* No more checks needed */
273                 break;
274             case 2:
275                 if((val[j+1] & 0xC0) != 0x80)
276                     broken = 1;
277                 if((val[j] & 0xFE) == 0xC0)
278                     broken = 1;
279                 break;
280             case 3:
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 &&
287                          val[j+1] <= 0x9F &&
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)))
292                      broken = 1;
293                  if(val[j] == 0xE0 && (val[j+1] & 0xE0) == 0x80)
294                      broken = 1;
295                  break;
296             case 4:
297                  if(!((val[j] == 0xF0 && val[j+1] >= 0x90 &&
298                          val[j+1] <= 0xBF &&
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 &&
306                          val[j+1] <= 0x8F &&
307                          (val[j+2] & 0xC0) == 0x80 &&
308                          (val[j+3] & 0xC0) == 0x80)))
309                      broken = 1;
310                  if(val[j] == 0xF0 && (val[j+1] & 0xF0) == 0x80)
311                      broken = 1;
312                  break;
313              /* 5 and 6 aren't actually allowed at this point */
314              case 5:
315                  broken = 1;
316                  break;
317              case 6:
318                  broken = 1;
319                  break;
320          }
321
322          if(broken) {
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];
331                seq[c1++] = ' ';
332
333                if(comment[i] < 0x20 || comment[i] > 0x7D)
334                  simple[c2++] = '?';
335                else
336                  simple[c2++] = comment[i];
337              }
338              seq[c1] = 0;
339              simple[c2] = 0;
340              warn(_("WARNING: Illegal UTF-8 sequence in comment "
341                    "%d (stream %d): invalid sequence \"%s\": %s\n"), i, 
342                    stream->num, simple, seq);
343              broken = 1;
344              free (simple);
345              free (seq);
346              break;
347          }
348
349          j += bytes;
350      }
351
352      if(!broken) {
353          *sep = 0;
354          info("\t%s=%s\n", comment, sep+1);
355      }
356 }
357
358 static void theora_process(stream_processor *stream, ogg_page *page)
359 {
360     ogg_packet packet;
361     misc_theora_info *inf = stream->data;
362     int i, header=0;
363     int res;
364
365     ogg_stream_pagein(&stream->os, page);
366     if(inf->doneheaders < 3)
367         header = 1;
368
369     while(1) {
370         res = ogg_stream_packetout(&stream->os, &packet);
371         if(res < 0) {
372            warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
373            continue;
374         }
375         else if (res == 0)
376             break;
377
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);
382                 continue;
383             }
384             inf->doneheaders++;
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"),
390                             stream->num);
391                 info(_("Theora headers parsed for stream %d, "
392                        "information follows...\n"), stream->num);
393
394                 info(_("Version: %d.%d.%d\n"), inf->ti.version_major, inf->ti.version_minor, inf->ti.version_subminor);
395
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"));
405
406                 if(inf->ti.fps_numerator == 0 || inf->ti.fps_denominator == 0) 
407                    warn(_("Invalid zero framerate\n"));
408                 else
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);
410                 
411                 if(inf->ti.aspect_numerator == 0 || inf->ti.aspect_denominator == 0) 
412                 {
413                     info(_("Aspect ratio undefined\n"));
414                 }       
415                 else
416                 {
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"));
423                     else
424                         info(_("Frame aspect %f:1\n"), frameaspect);
425                 }
426
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")); 
431                 else
432                     info(_("Colourspace unspecified\n"));
433
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"));
440                 else
441                     warn(_("Pixel format invalid\n"));
442
443                 info(_("Target bitrate: %d kbps\n"), inf->ti.target_bitrate/1000);
444                 info(_("Nominal quality setting (0-63): %d\n"), inf->ti.quality);
445
446                 if(inf->tc.comments > 0)
447                     info(_("User comments section follows...\n"));
448
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]);
453                 }
454             }
455         }
456         else {
457             ogg_int64_t framenum;
458             ogg_int64_t iframe,pframe;
459             ogg_int64_t gp = packet.granulepos;
460
461             if(gp > 0) {
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)
467                 {
468                     warn(_("WARNING: Expected frame %" I64FORMAT 
469                            ", got %" I64FORMAT "\n"), 
470                            inf->framenum_expected, framenum);
471                 }
472                 inf->framenum_expected = framenum + 1;
473             }
474             else if (inf->framenum_expected >= 0) {
475                 inf->framenum_expected++;
476             }
477         }
478     }
479
480     if(!header) {
481         ogg_int64_t gp = ogg_page_granulepos(page);
482         if(gp > 0) {
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;
488         }
489         if(inf->firstgranulepos < 0) { /* Not set yet */
490         }
491         inf->bytes += page->header_len + page->body_len;
492     }
493 }
494
495 static void theora_end(stream_processor *stream) 
496 {
497     misc_theora_info *inf = stream->data;
498     long minutes, seconds, milliseconds;
499     double bitrate, time;
500
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;
510
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);
516
517     theora_comment_clear(&inf->tc);
518     theora_info_clear(&inf->ti);
519
520     free(stream->data);
521 }
522
523
524 static void vorbis_process(stream_processor *stream, ogg_page *page )
525 {
526     ogg_packet packet;
527     misc_vorbis_info *inf = stream->data;
528     int i, header=0, packets=0;
529     int k;
530     int res;
531
532     ogg_stream_pagein(&stream->os, page);
533     if(inf->doneheaders < 3)
534         header = 1;
535
536     while(1) {
537         res = ogg_stream_packetout(&stream->os, &packet);
538         if(res < 0) {
539            warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
540            continue;
541         }
542         else if (res == 0)
543             break;
544
545         packets++;
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);
551                 continue;
552             }
553             inf->doneheaders++;
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"),
559                             stream->num);
560                 info(_("Vorbis headers parsed for stream %d, "
561                        "information follows...\n"), stream->num);
562
563                 info(_("Version: %d\n"), inf->vi.version);
564                 k = 0;
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, 
568                                     releases[k].desc);
569                         break;
570                     }
571                     k++;
572                 }
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);
577
578                 if(inf->vi.bitrate_nominal > 0)
579                     info(_("Nominal bitrate: %f kb/s\n"), 
580                             (double)inf->vi.bitrate_nominal / 1000.0);
581                 else
582                     info(_("Nominal bitrate not set\n"));
583
584                 if(inf->vi.bitrate_upper > 0)
585                     info(_("Upper bitrate: %f kb/s\n"), 
586                             (double)inf->vi.bitrate_upper / 1000.0);
587                 else
588                     info(_("Upper bitrate not set\n"));
589
590                 if(inf->vi.bitrate_lower > 0)
591                     info(_("Lower bitrate: %f kb/s\n"), 
592                             (double)inf->vi.bitrate_lower / 1000.0);
593                 else
594                     info(_("Lower bitrate not set\n"));
595
596                 if(inf->vc.comments > 0)
597                     info(_("User comments section follows...\n"));
598
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]);
603                 }
604             }
605         }
606     }
607
608     if(!header) {
609         ogg_int64_t gp = ogg_page_granulepos(page);
610         if(gp > 0) {
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;
616         }
617         else if(packets) {
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);
622         }
623         if(inf->firstgranulepos < 0) { /* Not set yet */
624         }
625         inf->bytes += page->header_len + page->body_len;
626     }
627 }
628
629 static void vorbis_end(stream_processor *stream) 
630 {
631     misc_vorbis_info *inf = stream->data;
632     long minutes, seconds, milliseconds;
633     double bitrate, time;
634
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;
641
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);
647
648     vorbis_comment_clear(&inf->vc);
649     vorbis_info_clear(&inf->vi);
650
651     free(stream->data);
652 }
653
654 static void kate_process(stream_processor *stream, ogg_page *page )
655 {
656     ogg_packet packet;
657     misc_kate_info *inf = stream->data;
658     int header=0, packets=0;
659     int res;
660 #ifdef HAVE_KATE
661 #endif
662
663     ogg_stream_pagein(&stream->os, page);
664     if(!inf->doneheaders)
665         header = 1;
666
667     while(1) {
668         res = ogg_stream_packetout(&stream->os, &packet);
669         if(res < 0) {
670            warn(_("WARNING: discontinuity in stream (%d)\n"), stream->num);
671            continue;
672         }
673         else if (res == 0)
674             break;
675
676         packets++;
677         if(!inf->doneheaders) {
678 #ifdef HAVE_KATE
679 #else
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);
685                 continue;
686             }
687             if (packet.packetno==inf->num_headers) {
688                 inf->doneheaders=1;
689             }
690 #endif
691
692             if (packet.packetno==0) {
693 #ifdef HAVE_KATE
694 #else
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;
702 #endif
703             }
704
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"),
710                             stream->num);
711                 info(_("Kate headers parsed for stream %d, "
712                        "information follows...\n"), stream->num);
713
714                 info(_("Version: %d.%d\n"), inf->major, inf->minor);
715 #ifdef HAVE_KATE
716 #endif
717
718                 if (*inf->language) {
719                     info(_("Language: %s\n"), inf->language);
720                 }
721                 else {
722                     info(_("No language set\n"));
723                 }
724                 if (*inf->category) {
725                     info(_("Category: %s\n"), inf->category);
726                 }
727                 else {
728                     info(_("No category set\n"));
729                 }
730
731 #ifdef HAVE_KATE
732 #endif
733                 info(_("\n"));
734             }
735         }
736     }
737
738     if(!header) {
739         ogg_int64_t gp = ogg_page_granulepos(page);
740         if(gp > 0) {
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;
746         }
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);
752         }
753         if(inf->firstgranulepos < 0) { /* Not set yet */
754         }
755         inf->bytes += page->header_len + page->body_len;
756     }
757 }
758
759 #ifdef HAVE_KATE
760 #else
761 static void kate_end(stream_processor *stream) 
762 {
763 }
764 #endif
765
766
767 static void process_null(stream_processor *stream, ogg_page *page)
768 {
769     /* This is for invalid streams. */
770 }
771
772 static void process_other(stream_processor *stream, ogg_page *page )
773 {
774     ogg_packet packet;
775
776     ogg_stream_pagein(&stream->os, page);
777
778     while(ogg_stream_packetout(&stream->os, &packet) > 0) {
779         /* Should we do anything here? Currently, we don't */
780     }
781 }
782
783
784 static void free_stream_set(stream_set *set)
785 {
786     int i;
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]);
793         }
794         ogg_stream_clear(&set->streams[i].os);
795     }
796
797     free(set->streams);
798     free(set);
799 }
800
801 static int streams_open(stream_set *set)
802 {
803     int i;
804     int res=0;
805     for(i=0; i < set->used; i++) {
806         if(!set->streams[i].end)
807             res++;
808     }
809
810     return res;
811 }
812
813 static void null_start(stream_processor *stream)
814 {
815     stream->process_end = NULL;
816     stream->type = "invalid";
817     stream->process_page = process_null;
818 }
819
820 static void other_start(stream_processor *stream, char *type)
821 {
822     if(type)
823         stream->type = type;
824     else
825         stream->type = "unknown";
826     stream->process_page = process_other;
827     stream->process_end = NULL;
828 }
829
830 static void theora_start(stream_processor *stream)
831 {
832     misc_theora_info *info;
833
834     stream->type = "theora";
835     stream->process_page = theora_process;
836     stream->process_end = theora_end;
837
838     stream->data = calloc(1, sizeof(misc_theora_info));
839     info = stream->data;
840     info->framenum_expected = -1;
841 }
842
843 static void vorbis_start(stream_processor *stream)
844 {
845     misc_vorbis_info *info;
846
847     stream->type = "vorbis";
848     stream->process_page = vorbis_process;
849     stream->process_end = vorbis_end;
850
851     stream->data = calloc(1, sizeof(misc_vorbis_info));
852
853     info = stream->data;
854
855     vorbis_comment_init(&info->vc);
856     vorbis_info_init(&info->vi);
857
858 }
859
860 static void kate_start(stream_processor *stream)
861 {
862     misc_kate_info *info;
863
864     stream->type = "kate";
865     stream->process_page = kate_process;
866     stream->process_end = kate_end;
867
868     stream->data = calloc(1, sizeof(misc_kate_info));
869
870     info = stream->data;
871
872 #ifdef HAVE_KATE
873 #endif
874 }
875
876 static stream_processor *find_stream_processor(stream_set *set, ogg_page *page)
877 {
878     ogg_uint32_t serial = ogg_page_serialno(page);
879     int i;
880     int invalid = 0;
881     int constraint = 0;
882     stream_processor *stream;
883
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]);
888
889             set->in_headers = 0;
890             /* if we have detected EOS, then this can't occur here. */
891             if(stream->end) {
892                 stream->isillegal = 1;
893                 stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
894                 return stream;
895             }
896
897             stream->isnew = 0;
898             stream->start = ogg_page_bos(page);
899             stream->end = ogg_page_eos(page);
900             stream->serial = serial;
901             return stream;
902         }
903     }
904
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?
909      */
910     if(streams_open(set) && !set->in_headers) {
911         constraint = CONSTRAINT_MUXING_VIOLATED;
912         invalid = 1;
913     }
914
915     set->in_headers = 1;
916
917     if(set->allocated < set->used)
918         stream = &set->streams[set->used];
919     else {
920         set->allocated += 5;
921         set->streams = realloc(set->streams, sizeof(stream_processor)*
922                 set->allocated);
923         stream = &set->streams[set->used];
924     }
925     set->used++;
926     stream->num = set->used; /* We count from 1 */
927
928     stream->isnew = 1;
929     stream->isillegal = invalid;
930     stream->constraint_violated = constraint;
931
932     {
933         int res;
934         ogg_packet packet;
935
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);
940         if(res <= 0) {
941             warn(_("WARNING: Invalid header page, no packet found\n"));
942             null_start(stream);
943         }
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)
963             kate_start(stream);
964         else
965             other_start(stream, NULL);
966
967         res = ogg_stream_packetout(&stream->os, &packet);
968         if(res > 0) {
969             warn(_("WARNING: Invalid header page in stream %d, "
970                               "contains multiple packets\n"), stream->num);
971         }
972
973         /* re-init, ready for processing */
974         ogg_stream_clear(&stream->os);
975         ogg_stream_init(&stream->os, serial);
976    }
977
978    stream->start = ogg_page_bos(page);
979    stream->end = ogg_page_eos(page);
980    stream->serial = serial;
981
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, 
985                stream->serial);
986    }
987
988    return stream;
989 }
990
991 static int get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page, 
992         ogg_int64_t *written)
993 {
994     int ret;
995     char *buffer;
996     int bytes;
997
998     while((ret = ogg_sync_pageseek(sync, page)) <= 0) {
999         if(ret < 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);
1002             continue;
1003         }
1004
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);
1008         if(bytes <= 0) {
1009             ogg_sync_wrote(sync, 0);
1010             return 0;
1011         }
1012         ogg_sync_wrote(sync, bytes);
1013         *written += bytes;
1014     }
1015
1016     return 1;
1017 }
1018
1019 static void process_file(char *filename) {
1020     FILE *file = fopen(filename, "rb");
1021     ogg_sync_state sync;
1022     ogg_page page;
1023     stream_set *processors = create_stream_set();
1024     int gotpage = 0;
1025     ogg_int64_t written = 0;
1026
1027     if(!file) {
1028         error(_("Error opening input file \"%s\": %s\n"), filename,
1029                     strerror(errno));
1030         return;
1031     }
1032
1033     printf(_("Processing file \"%s\"...\n\n"), filename);
1034
1035     ogg_sync_init(&sync);
1036
1037     while(get_next_page(file, &sync, &page, &written)) {
1038         stream_processor *p = find_stream_processor(processors, &page);
1039         gotpage = 1;
1040
1041         if(!p) {
1042             error(_("Could not find a processor for stream, bailing\n"));
1043             return;
1044         }
1045
1046         if(p->isillegal && !p->shownillegal) {
1047             char *constraint;
1048             switch(p->constraint_violated) {
1049                 case CONSTRAINT_PAGE_AFTER_EOS:
1050                     constraint = _("Page found for stream after EOS flag");
1051                     break;
1052                 case CONSTRAINT_MUXING_VIOLATED:
1053                     constraint = _("Ogg muxing constraints violated, new "
1054                                    "stream before EOS of all previous streams");
1055                     break;
1056                 default:
1057                     constraint = _("Error unknown.");
1058             }
1059
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
1066              */
1067             if(!p->isnew)
1068                 continue;
1069         }
1070
1071         if(p->isnew) {
1072             info(_("New logical stream (#%d, serial: %08x): type %s\n"), 
1073                     p->num, p->serial, p->type);
1074             if(!p->start)
1075                 warn(_("WARNING: stream start flag not set on stream %d\n"),
1076                         p->num);
1077         }
1078         else if(p->start)
1079             warn(_("WARNING: stream start flag found in mid-stream "
1080                       "on stream %d\n"), p->num);
1081
1082         if(p->seqno++ != ogg_page_pageno(&page)) {
1083             if(!p->lostseq) 
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);
1088             p->lostseq = 1;
1089         }
1090         else
1091             p->lostseq = 0;
1092
1093         if(!p->isillegal) {
1094             p->process_page(p, &page);
1095
1096             if(p->end) {
1097                 if(p->process_end)
1098                     p->process_end(p);
1099                 info(_("Logical stream %d ended\n"), p->num);
1100                 p->isillegal = 1;
1101                 p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
1102             }
1103         }
1104     }
1105
1106     if(!gotpage)
1107         error(_("ERROR: No Ogg data found in file \"%s\".\n"
1108                 "Input probably not Ogg.\n"), filename);
1109
1110     free_stream_set(processors);
1111
1112     ogg_sync_clear(&sync);
1113
1114     fclose(file);
1115 }
1116
1117 static void version (void) {
1118     printf (_("ogginfo from %s %s\n"), PACKAGE, VERSION);
1119 }
1120
1121 static void usage(void) {
1122     version ();
1123     printf (_(" by the Xiph.Org Foundation (http://www.xiph.org/)\n\n"));
1124     printf(_("(c) 2003-2005 Michael Smith <msmith@xiph.org>\n"
1125              "\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"));
1134 }
1135
1136 int main(int argc, char **argv) {
1137     int f, ret;
1138
1139     setlocale(LC_ALL, "");
1140     bindtextdomain(PACKAGE, LOCALEDIR);
1141     textdomain(PACKAGE);
1142
1143     if(argc < 2) {
1144         fprintf(stdout, 
1145                 _("Usage: ogginfo [flags] file1.ogg [file2.ogx ... fileN.ogv]\n"
1146                   "\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"));
1150         exit(1);
1151     }
1152
1153     while((ret = getopt(argc, argv, "hqvV")) >= 0) {
1154         switch(ret) {
1155             case 'h':
1156                 usage();
1157                 return 0;
1158             case 'V':
1159                 version();
1160                 return 0;
1161             case 'v':
1162                 verbose++;
1163                 break;
1164             case 'q':
1165                 verbose--;
1166                 break;
1167         }
1168     }
1169
1170     if(verbose > 1)
1171         printlots = 0;
1172     if(verbose < 1)
1173         printinfo = 0;
1174     if(verbose < 0) 
1175         printwarn = 0;
1176
1177     if(optind >= argc) {
1178         fprintf(stderr, 
1179                 _("No input files specified. \"ogginfo -h\" for help\n"));
1180         return 1;
1181     }
1182
1183     ret = 0;
1184
1185     for(f=optind; f < argc; f++) {
1186         flawed = 0;
1187         process_file(argv[f]);
1188         if(flawed != 0)
1189             ret = flawed;
1190     }
1191
1192     return ret;
1193 }