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