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