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