oggz-merge.c 11 KB
Newer Older
conrad's avatar
conrad committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
   Copyright (C) 2003 Commonwealth Scientific and Industrial Research
   Organisation (CSIRO) Australia

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

   - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

   - Neither the name of CSIRO Australia nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
conrad's avatar
conrad committed
38
#include <fcntl.h>
conrad's avatar
conrad committed
39
40
41
42

#include <getopt.h>
#include <errno.h>

43
44
45
46
47
48
#ifdef HAVE_INTTYPES_H
#  include <inttypes.h>
#else
#  define PRId64 "I64d"
#endif

conrad's avatar
conrad committed
49
#include <oggz/oggz.h>
conrad's avatar
conrad committed
50
#include "oggz_tools.h"
51

conrad's avatar
conrad committed
52
53
#define READ_SIZE 4096

54
#define ALL_VORBIS_WARNING \
conrad's avatar
conrad committed
55
  "oggz-merge: WARNING: Merging Ogg Vorbis I files. The resulting file will\n" \
56
57
58
  "  contain %d tracks in parallel, interleaved for simultaneous playback.\n"\
  "  If you want to sequence these files one after another, use cat instead.\n"

conrad's avatar
conrad committed
59
60
61
static void
usage (char * progname)
{
62
  printf ("Usage: %s [options] filename ...\n", progname);
63
  printf ("Merge Ogg files together, interleaving pages in order of presentation time.\n");
64
65
66
67
68
  printf ("\nMiscellaneous options\n");
  printf ("  -o filename, --output filename\n");
  printf ("                         Specify output filename\n");
  printf ("  -h, --help             Display this help and exit\n");
  printf ("  -v, --version          Output version information and exit\n");
69
  printf ("  -V, --verbose          Verbose operation\n");
70
  printf ("\n");
conrad's avatar
conrad committed
71
  printf ("Please report bugs to <ogg-dev@xiph.org>\n");
conrad's avatar
conrad committed
72
73
74
75
76
77
78
79
}

typedef struct _OMData OMData;
typedef struct _OMInput OMInput;
typedef struct _OMITrack OMITrack;

struct _OMData {
  OggzTable * inputs;
80
  int verbose;
conrad's avatar
conrad committed
81
82
83
84
85
};

struct _OMInput {
  OMData * omdata;
  OGGZ * reader;
86
  const ogg_page * og;
conrad's avatar
conrad committed
87
88
89
90
91
92
};

struct _OMITrack {
  long output_serialno;
};

93
static ogg_page *
94
_ogg_page_copy (const ogg_page * og)
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
{
  ogg_page * new_og;

  new_og = malloc (sizeof (*og));
  new_og->header = malloc (og->header_len);
  new_og->header_len = og->header_len;
  memcpy (new_og->header, og->header, og->header_len);
  new_og->body = malloc (og->body_len);
  new_og->body_len = og->body_len;
  memcpy (new_og->body, og->body, og->body_len);

  return new_og;
}

static int
110
_ogg_page_free (const ogg_page * og)
111
112
113
{
  free (og->header);
  free (og->body);
114
  free ((ogg_page *)og);
115
116
117
  return 0;
}

conrad's avatar
conrad committed
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
static void
ominput_delete (OMInput * input)
{
  oggz_close (input->reader);

  free (input);
}

static OMData *
omdata_new (void)
{
  OMData * omdata;

  omdata = (OMData *) malloc (sizeof (OMData));

  omdata->inputs = oggz_table_new ();
134
  omdata->verbose = 0;
conrad's avatar
conrad committed
135
136
137
138
139
140
141
142
143
144
145
146

  return omdata;
}

static void
omdata_delete (OMData * omdata)
{
  OMInput * input;
  int i, ninputs;

  ninputs = oggz_table_size (omdata->inputs);
  for (i = 0; i < ninputs; i++) {
conrad's avatar
conrad committed
147
    input = (OMInput *) oggz_table_nth (omdata->inputs, i, NULL);
conrad's avatar
conrad committed
148
149
150
151
    ominput_delete (input);
  }
  oggz_table_delete (omdata->inputs);

152
  free (omdata);
conrad's avatar
conrad committed
153
154
}

155
156
157
158
159
160
161
162
163
164
static int
read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
{
  OMInput * input = (OMInput *) user_data;

  input->og = _ogg_page_copy (og);

  return OGGZ_STOP_OK;
}

conrad's avatar
conrad committed
165
166
167
168
169
170
171
172
173
174
static int
omdata_add_input (OMData * omdata, FILE * infile)
{
  OMInput * input;
  int nfiles;

  input = (OMInput *) malloc (sizeof (OMInput));
  if (input == NULL) return -1;

  input->omdata = omdata;
175
176
  input->reader = oggz_open_stdio (infile, OGGZ_READ|OGGZ_AUTO);
  input->og = NULL;
conrad's avatar
conrad committed
177

178
  oggz_set_read_page (input->reader, -1, read_page, input);
conrad's avatar
conrad committed
179
180
181
182
183
184

  nfiles = oggz_table_size (omdata->inputs);
  if (!oggz_table_insert (omdata->inputs, nfiles++, input)) {
    ominput_delete (input);
    return -1;
  }
185

conrad's avatar
conrad committed
186
187
188
189
190
191
192
  return 0;
}

static int
oggz_merge (OMData * omdata, FILE * outfile)
{
  OMInput * input;
193
  int ninputs, i, min_i;
conrad's avatar
conrad committed
194
  long key, n;
195
196
197
  ogg_int64_t units, min_units;
  const ogg_page * og;
  int active;
conrad's avatar
conrad committed
198

199
200
  /* For theora+vorbis, or dirac+vorbis, ensure video bos is first */
  int careful_for_video = 0;
201

202
203
204
205
206
207
  /* If all input files are Ogg Vorbis I, warn that the output will not be
   * a valid Ogg Vorbis I file as it will be multitrack. This is in response
   * to Debian bug 280550: http://bugs.debian.org/280550
   */
  int v, warn_all_vorbis = 1;

208
  if (oggz_table_size (omdata->inputs) == 2)
209
    careful_for_video = 1;
210

conrad's avatar
conrad committed
211
  while ((ninputs = oggz_table_size (omdata->inputs)) > 0) {
212
213
214
215
    min_units = -1;
    min_i = -1;
    active = 1;

216
217
218
    if (omdata->verbose)
      printf ("------------------------------------------------------------\n");

219
220
    /* Reload all pages, and find the min (earliest) */
    for (i = 0; active && i < oggz_table_size (omdata->inputs); i++) {
conrad's avatar
conrad committed
221
222
      input = (OMInput *) oggz_table_nth (omdata->inputs, i, &key);
      if (input != NULL) {
223
	while (input && input->og == NULL) {
224
225
226
227
228
229
230
231
	  n = oggz_read (input->reader, READ_SIZE);
	  if (n == 0) {
	    oggz_table_remove (omdata->inputs, key);
	    ominput_delete (input);
	    input = NULL;
	  }
	}
	if (input && input->og) {
232
	  if (ogg_page_bos ((ogg_page *)input->og)) {
233
	    min_i = i;
234

235
	    if (careful_for_theora || warn_all_vorbis) {
236
237
              int is_vorbis;
              long serialno = ogg_page_serialno ((ogg_page *)input->og);
238

239
              is_vorbis = (oggz_stream_get_content (input->reader, serialno) == OGGZ_CONTENT_VORBIS);
240
241

	      if (i == 0 && is_vorbis)
242
		careful_for_video = 0;
243
244
245
	      else
		active = 0;

246
247
              if (!is_vorbis) warn_all_vorbis = 0;

248
249
250
	    } else {
	      active = 0;
	    }
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
	  } else if (warn_all_vorbis) {
            int all_inputs_are_beyond_bos = 1;

            /* All BOS pages seen so far are Ogg Vorbis. The following loop
             * checks if all input files are single-track, ie. Ogg Vorbis I.
             * We can only rely on this information if all inputs are beyond
             * bos, ie. all BOS pages have been seen. */
            for (v = 0; v < oggz_table_size (omdata->inputs); v++) {
              OMInput * input_v;
              OGGZ * oggz;

              input_v = (OMInput *) oggz_table_nth (omdata->inputs, i, &key);
              oggz = input_v->reader;

              if (oggz_get_bos(oggz, -1)) all_inputs_are_beyond_bos = 0;
              else if (oggz_get_numtracks(oggz) > 1) warn_all_vorbis = 0;
            }

            if (all_inputs_are_beyond_bos && warn_all_vorbis) {
              fprintf (stderr, ALL_VORBIS_WARNING, v);
              warn_all_vorbis = 0;
            }
          }
274
	  units = oggz_tell_units (input->reader);
275
276
277

	  if (omdata->verbose) {
	    ot_fprint_time (stdout, (double)units/1000);
278
	    printf (": Got index %d serialno %010d %" PRId64 " units: ",
279
280
281
		    i, ogg_page_serialno ((ogg_page *)input->og), units);
	  }

282
283
284
285
	  if (min_units == -1 || units == 0 ||
	      (units > -1 && units < min_units)) {
	    min_units = units;
	    min_i = i;
286
287
288
289
290
291
292
293
294
295
296
	    if (omdata->verbose)
	      printf ("Min\n");
	  } else {
	    if (omdata->verbose)
	      printf ("Moo\n");
	  }
	} else if (omdata->verbose) {
	  if (input == NULL) {
	    printf ("*** index %d NULL\n", i);
	  } else {
	    printf ("*** No page from index %d\n", i);
297
	  }
conrad's avatar
conrad committed
298
299
300
301
	}
      }
    }

302
303
304
    if (omdata->verbose)
      printf ("Min index %d\n", min_i);

305
306
307
308
309
310
    /* Write the earliest page */
    if (min_i != -1) {
      input = (OMInput *) oggz_table_nth (omdata->inputs, min_i, &key);
      og = input->og;
      fwrite (og->header, 1, og->header_len, outfile);
      fwrite (og->body, 1, og->body_len, outfile);
conrad's avatar
conrad committed
311

312
313
314
      _ogg_page_free (og);
      input->og = NULL;
    }
conrad's avatar
conrad committed
315
316
317
318
319
320
321
322
  }

  return 0;
}

int
main (int argc, char * argv[])
{
323
324
325
  int show_version = 0;
  int show_help = 0;

conrad's avatar
conrad committed
326
327
328
  char * progname;
  char * infilename = NULL, * outfilename = NULL;
  FILE * infile = NULL, * outfile = NULL;
329
  int used_stdin = 0; /* Flag usage of stdin, only use it once */
conrad's avatar
conrad committed
330
331
332
  OMData * omdata;
  int i;

333
334
335
336
337
338
339
340
341
342
343
344
  char * optstring = "hvVo:";

#ifdef HAVE_GETOPT_LONG
  static struct option long_options[] = {
    {"help", no_argument, 0, 'h'},
    {"version", no_argument, 0, 'v'},
    {"verbose", no_argument, 0, 'V'},
    {"output", required_argument, 0, 'o'},
    {0,0,0,0}
  };
#endif

conrad's avatar
conrad committed
345
  ot_init ();
346

conrad's avatar
conrad committed
347
  progname = argv[0];
348
349
350
351
352
353

  if (argc < 2) {
    usage (progname);
    return (1);
  }

354
355
356
357
358
359
360
361
362
  if (!strncmp (argv[1], "-?", 2)) {
#ifdef HAVE_GETOPT_LONG
    ot_print_options (long_options, optstring);
#else
    ot_print_short_options (optstring);
#endif
    exit (0);
  }

conrad's avatar
conrad committed
363
364
365
366
367
368
369
370
371
372
373
  omdata = omdata_new();

  while (1) {
#ifdef HAVE_GETOPT_LONG
    i = getopt_long (argc, argv, optstring, long_options, NULL);
#else
    i = getopt (argc, argv, optstring);
#endif
    if (i == -1) break;
    if (i == ':') {
      usage (progname);
374
      goto exit_err;
conrad's avatar
conrad committed
375
376
377
378
    }

    switch (i) {
    case 'h': /* help */
379
380
381
382
      show_help = 1;
      break;
    case 'v': /* version */
      show_version = 1;
conrad's avatar
conrad committed
383
384
385
386
      break;
    case 'o': /* output */
      outfilename = optarg;
      break;
387
388
    case 'V': /* verbose */
      omdata->verbose = 1;
conrad's avatar
conrad committed
389
390
391
392
393
    default:
      break;
    }
  }

394
395
396
397
398
399
400
401
402
403
404
405
  if (show_version) {
    printf ("%s version " VERSION "\n", progname);
  }

  if (show_help) {
    usage (progname);
  }

  if (show_version || show_help) {
    goto exit_ok;
  }

conrad's avatar
conrad committed
406
407
  if (optind >= argc) {
    usage (progname);
408
409
410
411
412
413
    goto exit_err;
  }

  if (optind >= argc) {
    usage (progname);
    goto exit_err;
conrad's avatar
conrad committed
414
415
416
417
  }

  while (optind < argc) {
    infilename = argv[optind++];
418
419
420
421
422
423
424
425
426
    if (strcmp (infilename, "-") == 0) {
      if (used_stdin) continue;

      infile = stdin;
      used_stdin = 1;
    } else {
      infile = fopen (infilename, "rb");
    }

conrad's avatar
conrad committed
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
    if (infile == NULL) {
      fprintf (stderr, "%s: unable to open input file %s\n", progname,
	       infilename);
    } else {
      omdata_add_input (omdata, infile);
    }
  }

  if (outfilename == NULL) {
    outfile = stdout;
  } else {
    outfile = fopen (outfilename, "wb");
    if (outfile == NULL) {
      fprintf (stderr, "%s: unable to open output file %s\n",
	       progname, outfilename);
442
      goto exit_err;
conrad's avatar
conrad committed
443
444
445
446
447
    }
  }

  oggz_merge (omdata, outfile);

448
 exit_ok:
conrad's avatar
conrad committed
449
  omdata_delete (omdata);
450
  exit (0);
conrad's avatar
conrad committed
451

452
453
454
 exit_err:
  omdata_delete (omdata);
  exit (1);
conrad's avatar
conrad committed
455
}