1 /*
2    Copyright (C) 2008 Annodex Association
3 
4    Redistribution and use in source and binary forms, with or without
5    modification, are permitted provided that the following conditions
6    are met:
7 
8    - Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 
11    - Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14 
15    - Neither the name of the Annodex Association nor the names of its
16    contributors may be used to endorse or promote products derived from
17    this software without specific prior written permission.
18 
19    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ASSOCIATION OR
23    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31 
32 #include "config.h"
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <fcntl.h>
38 
39 #include <getopt.h>
40 #include <errno.h>
41 
42 #include "oggz/oggz.h"
43 #include "oggz_tools.h"
44 
45 #define READ_SIZE 4096
46 
47 static char * progname;
48 
49 static void
usage(char * progname)50 usage (char * progname)
51 {
52   printf ("Usage: %s [options] filename ...\n", progname);
53   printf ("Sort the pages of an Ogg file in order of presentation time.\n");
54   printf ("\nMiscellaneous options\n");
55   printf ("  -o filename, --output filename\n");
56   printf ("                         Specify output filename\n");
57   printf ("  -h, --help             Display this help and exit\n");
58   printf ("  -v, --version          Output version information and exit\n");
59   printf ("  -V, --verbose          Verbose operation\n");
60   printf ("\n");
61   printf ("Please report bugs to <ogg-dev@xiph.org>\n");
62 }
63 
64 static void
exit_out_of_memory(void)65 exit_out_of_memory (void)
66 {
67   fprintf (stderr, "%s: Out of memory\n", progname);
68   exit (1);
69 }
70 
71 static void
checked_fwrite(const void * data,size_t size,size_t count,FILE * stream)72 checked_fwrite (const void *data, size_t size, size_t count, FILE *stream)
73 {
74   int n = fwrite (data, size, count, stream);
75   if ((size_t)n != count) {
76     perror ("write failed");
77     exit (1);
78   }
79 }
80 
81 typedef struct _OSData OSData;
82 typedef struct _OSInput OSInput;
83 typedef struct _OSITrack OSITrack;
84 
85 struct _OSData {
86   char * infilename;
87   OggzTable * inputs;
88   int verbose;
89 };
90 
91 struct _OSInput {
92   OSData * osdata;
93   OGGZ * reader;
94   long serialno;
95   const ogg_page * og;
96 };
97 
98 struct _OSITrack {
99   long output_serialno;
100 };
101 
102 static ogg_page *
_ogg_page_copy(const ogg_page * og)103 _ogg_page_copy (const ogg_page * og)
104 {
105   ogg_page * new_og;
106 
107   new_og = malloc (sizeof (*og));
108   if (new_og == NULL) return NULL;
109 
110   new_og->header = malloc (og->header_len);
111   if (new_og->header == NULL) {
112     free (new_og);
113     return NULL;
114   }
115   new_og->header_len = og->header_len;
116   memcpy (new_og->header, og->header, og->header_len);
117 
118   new_og->body = malloc (og->body_len);
119   if (new_og->body == NULL) {
120     free (new_og->header);
121     free (new_og);
122     return NULL;
123   }
124   new_og->body_len = og->body_len;
125   memcpy (new_og->body, og->body, og->body_len);
126 
127   return new_og;
128 }
129 
130 static int
_ogg_page_free(const ogg_page * og)131 _ogg_page_free (const ogg_page * og)
132 {
133   free (og->header);
134   free (og->body);
135   free ((ogg_page *)og);
136   return 0;
137 }
138 
139 static void
osinput_delete(OSInput * input)140 osinput_delete (OSInput * input)
141 {
142   oggz_close (input->reader);
143 
144   free (input);
145 }
146 
147 static OSData *
osdata_new(void)148 osdata_new (void)
149 {
150   OSData * osdata;
151 
152   osdata = (OSData *) malloc (sizeof (OSData));
153   if (osdata == NULL) return NULL;
154 
155   osdata->inputs = oggz_table_new ();
156   if (osdata->inputs == NULL) {
157     free (osdata);
158     return NULL;
159   }
160 
161   osdata->verbose = 0;
162 
163   return osdata;
164 }
165 
166 static void
osdata_delete(OSData * osdata)167 osdata_delete (OSData * osdata)
168 {
169   OSInput * input;
170   int i, ninputs;
171 
172   ninputs = oggz_table_size (osdata->inputs);
173   for (i = 0; i < ninputs; i++) {
174     input = (OSInput *) oggz_table_nth (osdata->inputs, i, NULL);
175     osinput_delete (input);
176   }
177   oggz_table_delete (osdata->inputs);
178 
179   free (osdata);
180 }
181 
182 static int
read_page(OGGZ * oggz,const ogg_page * og,long serialno,void * user_data)183 read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
184 {
185   OSInput * input = (OSInput *) user_data;
186 
187   /* If this is the serialno that this input is tracking, stash it;
188    * otherwise continue scanning the file */
189   if (serialno == input->serialno) {
190     ogg_page *iog;
191     iog = _ogg_page_copy (og);
192     if (iog == NULL) return OGGZ_STOP_ERR;
193 
194     /* If this page's granulepos should be -1 but isn't then fix that before
195      * storing and sorting the page. */
196     if(ogg_page_packets(iog)==0&&ogg_page_granulepos(iog)!=-1) {
197       memset(iog->header+6,0xFF,8);
198       ogg_page_checksum_set(iog);
199     }
200     input->og = iog;
201     return OGGZ_STOP_OK;
202   } else {
203     return OGGZ_CONTINUE;
204   }
205 }
206 
207 static int
read_page_add_input(OGGZ * oggz,const ogg_page * og,long serialno,void * user_data)208 read_page_add_input (OGGZ * oggz, const ogg_page * og, long serialno,
209                      void * user_data)
210 {
211   OSData * osdata = (OSData *)user_data;
212   OSInput * input;
213   int is_bos, nfiles;
214 
215 #ifdef OGG_H_CONST_CORRECT
216   is_bos = ogg_page_bos (og);
217 #else
218   is_bos = ogg_page_bos ((ogg_page *)og);
219 #endif
220 
221   if (is_bos) {
222     input = (OSInput *) malloc (sizeof (OSInput));
223     if (input == NULL) return OGGZ_STOP_ERR;
224 
225     input->osdata = osdata;
226     input->reader = oggz_open (osdata->infilename, OGGZ_READ|OGGZ_AUTO);
227     if (input->reader == NULL) {
228       free (input);
229       return OGGZ_STOP_ERR;
230     }
231 
232     input->serialno = serialno;
233     input->og = NULL;
234 
235     oggz_set_read_page (input->reader, -1, read_page, input);
236 
237     nfiles = oggz_table_size (osdata->inputs);
238     if (!oggz_table_insert (osdata->inputs, nfiles++, input)) {
239       osinput_delete (input);
240       return OGGZ_STOP_ERR;
241     }
242 
243     return OGGZ_CONTINUE;
244   } else {
245     return OGGZ_STOP_OK;
246   }
247 }
248 
249 static int
osdata_add_file(OSData * osdata,char * infilename)250 osdata_add_file (OSData * osdata, char * infilename)
251 {
252   OGGZ * reader;
253 
254   osdata->infilename = infilename;
255 
256   if ((reader = oggz_open (infilename, OGGZ_READ|OGGZ_AUTO)) != NULL) {
257     oggz_set_read_page (reader, -1, read_page_add_input, osdata);
258     oggz_run (reader);
259     oggz_close (reader);
260     return 0;
261   } else {
262     return -1;
263   }
264 }
265 
266 static int
oggz_sort(OSData * osdata,FILE * outfile)267 oggz_sort (OSData * osdata, FILE * outfile)
268 {
269   OSInput * input;
270   int ninputs, i, min_i;
271   long key, n;
272   ogg_int64_t units, min_units;
273   const ogg_page * og;
274   int active;
275 
276   /* For theora+vorbis, ensure theora bos is first */
277   int careful_for_theora = 0;
278 
279   int v;
280 
281   if (oggz_table_size (osdata->inputs) == 2)
282     careful_for_theora = 1;
283 
284   while ((ninputs = oggz_table_size (osdata->inputs)) > 0) {
285     min_units = -1;
286     min_i = -1;
287     active = 1;
288 
289     if (osdata->verbose)
290       printf ("------------------------------------------------------------\n");
291 
292     /* Reload all pages, and find the min (earliest) */
293     for (i = 0; active && i < oggz_table_size (osdata->inputs); i++) {
294       input = (OSInput *) oggz_table_nth (osdata->inputs, i, &key);
295       if (input != NULL) {
296 	while (input && input->og == NULL) {
297 	  n = oggz_read (input->reader, READ_SIZE);
298 	  if (n == 0) {
299 	    oggz_table_remove (osdata->inputs, key);
300 	    osinput_delete (input);
301 	    input = NULL;
302 	  } else if (n == OGGZ_ERR_STOP_ERR) {
303             exit_out_of_memory();
304 	  }
305 	}
306 	if (input && input->og) {
307 	  if (ogg_page_bos ((ogg_page *)input->og)) {
308 	    min_i = i;
309 
310 	    if (careful_for_theora) {
311 	      if (i == 0 && oggz_stream_get_content (input->reader, input->serialno) == OGGZ_CONTENT_VORBIS)
312 		careful_for_theora = 0;
313 	      else
314 		active = 0;
315 	    } else {
316 	      active = 0;
317 	    }
318           }
319 	  units = oggz_tell_units (input->reader);
320 
321 	  if (osdata->verbose) {
322 	    ot_fprint_time (stdout, (double)units/1000);
323 	    printf (": Got index %d serialno %010u %lld units: ",
324 		    i, ogg_page_serialno ((ogg_page *)input->og), (long long) units);
325 	  }
326 
327 	  if (min_units == -1 || units == 0 ||
328 	      (units > -1 && units < min_units)) {
329 	    min_units = units;
330 	    min_i = i;
331 	    if (osdata->verbose)
332 	      printf ("Min\n");
333 	  } else {
334 	    if (osdata->verbose)
335 	      printf ("Moo\n");
336 	  }
337 	} else if (osdata->verbose) {
338 	  if (input == NULL) {
339 	    printf ("*** index %d NULL\n", i);
340 	  } else {
341 	    printf ("*** No page from index %d\n", i);
342 	  }
343 	}
344       }
345     }
346 
347     if (osdata->verbose)
348       printf ("Min index %d\n", min_i);
349 
350     /* Write the earliest page */
351     if (min_i != -1) {
352       input = (OSInput *) oggz_table_nth (osdata->inputs, min_i, &key);
353       og = input->og;
354       checked_fwrite (og->header, 1, og->header_len, outfile);
355       checked_fwrite (og->body, 1, og->body_len, outfile);
356 
357       _ogg_page_free (og);
358       input->og = NULL;
359     }
360   }
361 
362   return 0;
363 }
364 
365 int
main(int argc,char * argv[])366 main (int argc, char * argv[])
367 {
368   int show_version = 0;
369   int show_help = 0;
370 
371   char * infilename = NULL, * outfilename = NULL;
372   FILE * infile = NULL, * outfile = NULL;
373   OSData * osdata;
374   int i;
375 
376   char * optstring = "hvVo:";
377 
378 #ifdef HAVE_GETOPT_LONG
379   static struct option long_options[] = {
380     {"help", no_argument, 0, 'h'},
381     {"version", no_argument, 0, 'v'},
382     {"verbose", no_argument, 0, 'V'},
383     {"output", required_argument, 0, 'o'},
384     {0,0,0,0}
385   };
386 #endif
387 
388   ot_init ();
389 
390   progname = argv[0];
391 
392   if (argc < 2) {
393     usage (progname);
394     return (1);
395   }
396 
397   if (!strncmp (argv[1], "-?", 2)) {
398 #ifdef HAVE_GETOPT_LONG
399     ot_print_options (long_options, optstring);
400 #else
401     ot_print_short_options (optstring);
402 #endif
403     exit (0);
404   }
405 
406   osdata = osdata_new();
407   if (osdata == NULL)
408     exit_out_of_memory();
409 
410   while (1) {
411 #ifdef HAVE_GETOPT_LONG
412     i = getopt_long (argc, argv, optstring, long_options, NULL);
413 #else
414     i = getopt (argc, argv, optstring);
415 #endif
416     if (i == -1) break;
417     if (i == ':') {
418       usage (progname);
419       goto exit_err;
420     }
421 
422     switch (i) {
423     case 'h': /* help */
424       show_help = 1;
425       break;
426     case 'v': /* version */
427       show_version = 1;
428       break;
429     case 'o': /* output */
430       outfilename = optarg;
431       break;
432     case 'V': /* verbose */
433       osdata->verbose = 1;
434     default:
435       break;
436     }
437   }
438 
439   if (show_version) {
440     printf ("%s version " VERSION "\n", progname);
441   }
442 
443   if (show_help) {
444     usage (progname);
445   }
446 
447   if (show_version || show_help) {
448     goto exit_ok;
449   }
450 
451   if (optind >= argc) {
452     usage (progname);
453     goto exit_err;
454   }
455 
456   infilename = argv[optind++];
457   if (osdata_add_file (osdata, infilename) == -1) {
458     fprintf (stderr, "%s: unable to open input file %s\n",
459              progname, infilename);
460     goto exit_err;
461   }
462 
463   if (outfilename == NULL) {
464     outfile = stdout;
465   } else {
466     outfile = fopen (outfilename, "wb");
467     if (outfile == NULL) {
468       fprintf (stderr, "%s: unable to open output file %s\n",
469 	       progname, outfilename);
470       goto exit_err;
471     }
472   }
473 
474   oggz_sort (osdata, outfile);
475 
476  exit_ok:
477   osdata_delete (osdata);
478   exit (0);
479 
480  exit_err:
481   osdata_delete (osdata);
482   exit (1);
483 }
484