Blame src/tools/oggz-merge.c

Packit a38265
/*
Packit a38265
   Copyright (C) 2003 Commonwealth Scientific and Industrial Research
Packit a38265
   Organisation (CSIRO) Australia
Packit a38265
Packit a38265
   Redistribution and use in source and binary forms, with or without
Packit a38265
   modification, are permitted provided that the following conditions
Packit a38265
   are met:
Packit a38265
Packit a38265
   - Redistributions of source code must retain the above copyright
Packit a38265
   notice, this list of conditions and the following disclaimer.
Packit a38265
Packit a38265
   - Redistributions in binary form must reproduce the above copyright
Packit a38265
   notice, this list of conditions and the following disclaimer in the
Packit a38265
   documentation and/or other materials provided with the distribution.
Packit a38265
Packit a38265
   - Neither the name of CSIRO Australia nor the names of its
Packit a38265
   contributors may be used to endorse or promote products derived from
Packit a38265
   this software without specific prior written permission.
Packit a38265
Packit a38265
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
Packit a38265
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
Packit a38265
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
Packit a38265
   PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE ORGANISATION OR
Packit a38265
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
Packit a38265
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
Packit a38265
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
Packit a38265
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
Packit a38265
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
Packit a38265
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
Packit a38265
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Packit a38265
*/
Packit a38265
Packit a38265
#include "config.h"
Packit a38265
Packit a38265
#include <stdio.h>
Packit a38265
#include <stdlib.h>
Packit a38265
#include <string.h>
Packit a38265
#include <fcntl.h>
Packit a38265
Packit a38265
#include <getopt.h>
Packit a38265
#include <errno.h>
Packit a38265
Packit a38265
#ifdef HAVE_INTTYPES_H
Packit a38265
#  include <inttypes.h>
Packit a38265
#else
Packit a38265
#  define PRId64 "I64d"
Packit a38265
#endif
Packit a38265
Packit a38265
#include "oggz/oggz.h"
Packit a38265
#include "oggz_tools.h"
Packit a38265
Packit a38265
#define READ_SIZE 4096
Packit a38265
Packit a38265
#define ALL_VORBIS_WARNING \
Packit a38265
  "oggz-merge: WARNING: Merging Ogg Vorbis I files. The resulting file will\n" \
Packit a38265
  "  contain %d tracks in parallel, interleaved for simultaneous playback.\n"\
Packit a38265
  "  If you want to sequence these files one after another, use cat instead.\n"
Packit a38265
Packit a38265
static char * progname;
Packit a38265
Packit a38265
static void
Packit a38265
usage (char * progname)
Packit a38265
{
Packit a38265
  printf ("Usage: %s [options] filename ...\n", progname);
Packit a38265
  printf ("Merge Ogg files together, interleaving pages in order of presentation time.\n");
Packit a38265
  printf ("\nMiscellaneous options\n");
Packit a38265
  printf ("  -o filename, --output filename\n");
Packit a38265
  printf ("                         Specify output filename\n");
Packit a38265
  printf ("  -h, --help             Display this help and exit\n");
Packit a38265
  printf ("  -v, --version          Output version information and exit\n");
Packit a38265
  printf ("  -V, --verbose          Verbose operation\n");
Packit a38265
  printf ("\n");
Packit a38265
  printf ("Please report bugs to <ogg-dev@xiph.org>\n");
Packit a38265
}
Packit a38265
Packit a38265
static void
Packit a38265
exit_out_of_memory (void)
Packit a38265
{
Packit a38265
  fprintf (stderr, "%s: Out of memory\n", progname);
Packit a38265
  exit (1);
Packit a38265
}
Packit a38265
Packit a38265
static void
Packit a38265
checked_fwrite (const void *data, size_t size, size_t count, FILE *stream)
Packit a38265
{
Packit a38265
  int n = fwrite (data, size, count, stream);
Packit a38265
  if ((size_t)n != count) {
Packit a38265
    perror ("write failed");
Packit a38265
    exit (1);
Packit a38265
  }
Packit a38265
}
Packit a38265
Packit a38265
typedef struct _OMData OMData;
Packit a38265
typedef struct _OMInput OMInput;
Packit a38265
typedef struct _OMITrack OMITrack;
Packit a38265
Packit a38265
struct _OMData {
Packit a38265
  OggzTable * inputs;
Packit a38265
  int verbose;
Packit a38265
};
Packit a38265
Packit a38265
struct _OMInput {
Packit a38265
  OMData * omdata;
Packit a38265
  OGGZ * reader;
Packit a38265
  const ogg_page * og;
Packit a38265
};
Packit a38265
Packit a38265
struct _OMITrack {
Packit a38265
  long output_serialno;
Packit a38265
};
Packit a38265
Packit a38265
static ogg_page *
Packit a38265
_ogg_page_copy (const ogg_page * og)
Packit a38265
{
Packit a38265
  ogg_page * new_og;
Packit a38265
Packit a38265
  new_og = malloc (sizeof (*og));
Packit a38265
  if (new_og == NULL) return NULL;
Packit a38265
Packit a38265
  new_og->header = malloc (og->header_len);
Packit a38265
  if (new_og->header == NULL) {
Packit a38265
    free (new_og);
Packit a38265
    return NULL;
Packit a38265
  }
Packit a38265
  new_og->header_len = og->header_len;
Packit a38265
  memcpy (new_og->header, og->header, og->header_len);
Packit a38265
Packit a38265
  new_og->body = malloc (og->body_len);
Packit a38265
  if (new_og->body == NULL) {
Packit a38265
    free (new_og->header);
Packit a38265
    free (new_og);
Packit a38265
    return NULL;
Packit a38265
  }
Packit a38265
  new_og->body_len = og->body_len;
Packit a38265
  memcpy (new_og->body, og->body, og->body_len);
Packit a38265
Packit a38265
  return new_og;
Packit a38265
}
Packit a38265
Packit a38265
static int
Packit a38265
_ogg_page_free (const ogg_page * og)
Packit a38265
{
Packit a38265
  free (og->header);
Packit a38265
  free (og->body);
Packit a38265
  free ((ogg_page *)og);
Packit a38265
  return 0;
Packit a38265
}
Packit a38265
Packit a38265
static void
Packit a38265
ominput_delete (OMInput * input)
Packit a38265
{
Packit a38265
  oggz_close (input->reader);
Packit a38265
Packit a38265
  free (input);
Packit a38265
}
Packit a38265
Packit a38265
static OMData *
Packit a38265
omdata_new (void)
Packit a38265
{
Packit a38265
  OMData * omdata;
Packit a38265
Packit a38265
  omdata = (OMData *) malloc (sizeof (OMData));
Packit a38265
  if (omdata == NULL) return NULL;
Packit a38265
Packit a38265
  omdata->inputs = oggz_table_new ();
Packit a38265
  omdata->verbose = 0;
Packit a38265
Packit a38265
  return omdata;
Packit a38265
}
Packit a38265
Packit a38265
static void
Packit a38265
omdata_delete (OMData * omdata)
Packit a38265
{
Packit a38265
  OMInput * input;
Packit a38265
  int i, ninputs;
Packit a38265
Packit a38265
  ninputs = oggz_table_size (omdata->inputs);
Packit a38265
  for (i = 0; i < ninputs; i++) {
Packit a38265
    input = (OMInput *) oggz_table_nth (omdata->inputs, i, NULL);
Packit a38265
    ominput_delete (input);
Packit a38265
  }
Packit a38265
  oggz_table_delete (omdata->inputs);
Packit a38265
Packit a38265
  free (omdata);
Packit a38265
}
Packit a38265
Packit a38265
static int
Packit a38265
read_page (OGGZ * oggz, const ogg_page * og, long serialno, void * user_data)
Packit a38265
{
Packit a38265
  OMInput * input = (OMInput *) user_data;
Packit a38265
Packit a38265
  input->og = _ogg_page_copy (og);
Packit a38265
  if (input->og == NULL) return OGGZ_STOP_ERR;
Packit a38265
Packit a38265
  return OGGZ_STOP_OK;
Packit a38265
}
Packit a38265
Packit a38265
static int
Packit a38265
omdata_add_input (OMData * omdata, FILE * infile)
Packit a38265
{
Packit a38265
  OMInput * input;
Packit a38265
  int nfiles;
Packit a38265
Packit a38265
  input = (OMInput *) malloc (sizeof (OMInput));
Packit a38265
  if (input == NULL) return -1;
Packit a38265
Packit a38265
  input->omdata = omdata;
Packit a38265
  input->reader = oggz_open_stdio (infile, OGGZ_READ|OGGZ_AUTO);
Packit a38265
  input->og = NULL;
Packit a38265
Packit a38265
  oggz_set_read_page (input->reader, -1, read_page, input);
Packit a38265
Packit a38265
  nfiles = oggz_table_size (omdata->inputs);
Packit a38265
  if (!oggz_table_insert (omdata->inputs, nfiles++, input)) {
Packit a38265
    ominput_delete (input);
Packit a38265
    return -1;
Packit a38265
  }
Packit a38265
Packit a38265
  return 0;
Packit a38265
}
Packit a38265
Packit a38265
static int
Packit a38265
oggz_merge (OMData * omdata, FILE * outfile)
Packit a38265
{
Packit a38265
  OMInput * input;
Packit a38265
  int ninputs, i, min_i;
Packit a38265
  long key, n;
Packit a38265
  ogg_int64_t units, min_units;
Packit a38265
  const ogg_page * og;
Packit a38265
  int active;
Packit a38265
Packit a38265
  /* For theora+vorbis, or dirac+vorbis, ensure video bos is first */
Packit a38265
  int careful_for_video = 0;
Packit a38265
Packit a38265
  /* If all input files are Ogg Vorbis I, warn that the output will not be
Packit a38265
   * a valid Ogg Vorbis I file as it will be multitrack. This is in response
Packit a38265
   * to Debian bug 280550: http://bugs.debian.org/280550
Packit a38265
   */
Packit a38265
  int v, warn_all_vorbis = 1;
Packit a38265
Packit a38265
  if (oggz_table_size (omdata->inputs) == 2)
Packit a38265
    careful_for_video = 1;
Packit a38265
Packit a38265
  while ((ninputs = oggz_table_size (omdata->inputs)) > 0) {
Packit a38265
    min_units = -1;
Packit a38265
    min_i = -1;
Packit a38265
    active = 1;
Packit a38265
Packit a38265
    if (omdata->verbose)
Packit a38265
      printf ("------------------------------------------------------------\n");
Packit a38265
Packit a38265
    /* Reload all pages, and find the min (earliest) */
Packit a38265
    for (i = 0; active && i < oggz_table_size (omdata->inputs); i++) {
Packit a38265
      input = (OMInput *) oggz_table_nth (omdata->inputs, i, &key);
Packit a38265
      if (input != NULL) {
Packit a38265
	while (input && input->og == NULL) {
Packit a38265
	  n = oggz_read (input->reader, READ_SIZE);
Packit a38265
	  if (n == 0) {
Packit a38265
	    oggz_table_remove (omdata->inputs, key);
Packit a38265
	    ominput_delete (input);
Packit a38265
	    input = NULL;
Packit a38265
	  } else if (n == OGGZ_ERR_STOP_ERR) {
Packit a38265
            exit_out_of_memory();
Packit a38265
          }
Packit a38265
	}
Packit a38265
	if (input && input->og) {
Packit a38265
	  if (ogg_page_bos ((ogg_page *)input->og)) {
Packit a38265
	    min_i = i;
Packit a38265
Packit a38265
	    if (careful_for_video || warn_all_vorbis) {
Packit a38265
              int is_vorbis;
Packit a38265
              long serialno = ogg_page_serialno ((ogg_page *)input->og);
Packit a38265
Packit a38265
              is_vorbis = (oggz_stream_get_content (input->reader, serialno) == OGGZ_CONTENT_VORBIS);
Packit a38265
Packit a38265
	      if (i == 0 && is_vorbis)
Packit a38265
		careful_for_video = 0;
Packit a38265
	      else
Packit a38265
		active = 0;
Packit a38265
Packit a38265
              if (!is_vorbis) warn_all_vorbis = 0;
Packit a38265
Packit a38265
	    } else {
Packit a38265
	      active = 0;
Packit a38265
	    }
Packit a38265
	  } else if (warn_all_vorbis) {
Packit a38265
            int all_inputs_are_beyond_bos = 1;
Packit a38265
Packit a38265
            /* All BOS pages seen so far are Ogg Vorbis. The following loop
Packit a38265
             * checks if all input files are single-track, ie. Ogg Vorbis I.
Packit a38265
             * We can only rely on this information if all inputs are beyond
Packit a38265
             * bos, ie. all BOS pages have been seen. */
Packit a38265
            for (v = 0; v < oggz_table_size (omdata->inputs); v++) {
Packit a38265
              OMInput * input_v;
Packit a38265
              OGGZ * oggz;
Packit a38265
Packit a38265
              input_v = (OMInput *) oggz_table_nth (omdata->inputs, i, &key);
Packit a38265
              oggz = input_v->reader;
Packit a38265
Packit a38265
              if (oggz_get_bos(oggz, -1)) all_inputs_are_beyond_bos = 0;
Packit a38265
              else if (oggz_get_numtracks(oggz) > 1) warn_all_vorbis = 0;
Packit a38265
            }
Packit a38265
Packit a38265
            if (all_inputs_are_beyond_bos && warn_all_vorbis) {
Packit a38265
              fprintf (stderr, ALL_VORBIS_WARNING, v);
Packit a38265
              warn_all_vorbis = 0;
Packit a38265
            }
Packit a38265
          }
Packit a38265
	  units = oggz_tell_units (input->reader);
Packit a38265
Packit a38265
	  if (omdata->verbose) {
Packit a38265
	    ot_fprint_time (stdout, (double)units/1000);
Packit a38265
	    printf (": Got index %d serialno %010u %" PRId64 " units: ",
Packit a38265
		    i, ogg_page_serialno ((ogg_page *)input->og), units);
Packit a38265
	  }
Packit a38265
Packit a38265
	  if (min_units == -1 || units == 0 ||
Packit a38265
	      (units > -1 && units < min_units)) {
Packit a38265
	    min_units = units;
Packit a38265
	    min_i = i;
Packit a38265
	    if (omdata->verbose)
Packit a38265
	      printf ("Min\n");
Packit a38265
	  } else {
Packit a38265
	    if (omdata->verbose)
Packit a38265
	      printf ("Moo\n");
Packit a38265
	  }
Packit a38265
	} else if (omdata->verbose) {
Packit a38265
	  if (input == NULL) {
Packit a38265
	    printf ("*** index %d NULL\n", i);
Packit a38265
	  } else {
Packit a38265
	    printf ("*** No page from index %d\n", i);
Packit a38265
	  }
Packit a38265
	}
Packit a38265
      }
Packit a38265
    }
Packit a38265
Packit a38265
    if (omdata->verbose)
Packit a38265
      printf ("Min index %d\n", min_i);
Packit a38265
Packit a38265
    /* Write the earliest page */
Packit a38265
    if (min_i != -1) {
Packit a38265
      input = (OMInput *) oggz_table_nth (omdata->inputs, min_i, &key);
Packit a38265
      og = input->og;
Packit a38265
      checked_fwrite (og->header, 1, og->header_len, outfile);
Packit a38265
      checked_fwrite (og->body, 1, og->body_len, outfile);
Packit a38265
Packit a38265
      _ogg_page_free (og);
Packit a38265
      input->og = NULL;
Packit a38265
    }
Packit a38265
  }
Packit a38265
Packit a38265
  return 0;
Packit a38265
}
Packit a38265
Packit a38265
int
Packit a38265
main (int argc, char * argv[])
Packit a38265
{
Packit a38265
  int show_version = 0;
Packit a38265
  int show_help = 0;
Packit a38265
Packit a38265
  char * infilename = NULL, * outfilename = NULL;
Packit a38265
  FILE * infile = NULL, * outfile = NULL;
Packit a38265
  int used_stdin = 0; /* Flag usage of stdin, only use it once */
Packit a38265
  OMData * omdata;
Packit a38265
  int i;
Packit a38265
Packit a38265
  char * optstring = "hvVo:";
Packit a38265
Packit a38265
#ifdef HAVE_GETOPT_LONG
Packit a38265
  static struct option long_options[] = {
Packit a38265
    {"help", no_argument, 0, 'h'},
Packit a38265
    {"version", no_argument, 0, 'v'},
Packit a38265
    {"verbose", no_argument, 0, 'V'},
Packit a38265
    {"output", required_argument, 0, 'o'},
Packit a38265
    {0,0,0,0}
Packit a38265
  };
Packit a38265
#endif
Packit a38265
Packit a38265
  ot_init ();
Packit a38265
Packit a38265
  progname = argv[0];
Packit a38265
Packit a38265
  if (argc < 2) {
Packit a38265
    usage (progname);
Packit a38265
    return (1);
Packit a38265
  }
Packit a38265
Packit a38265
  if (!strncmp (argv[1], "-?", 2)) {
Packit a38265
#ifdef HAVE_GETOPT_LONG
Packit a38265
    ot_print_options (long_options, optstring);
Packit a38265
#else
Packit a38265
    ot_print_short_options (optstring);
Packit a38265
#endif
Packit a38265
    exit (0);
Packit a38265
  }
Packit a38265
Packit a38265
  omdata = omdata_new();
Packit a38265
  if (omdata == NULL)
Packit a38265
    exit_out_of_memory();
Packit a38265
Packit a38265
  while (1) {
Packit a38265
#ifdef HAVE_GETOPT_LONG
Packit a38265
    i = getopt_long (argc, argv, optstring, long_options, NULL);
Packit a38265
#else
Packit a38265
    i = getopt (argc, argv, optstring);
Packit a38265
#endif
Packit a38265
    if (i == -1) break;
Packit a38265
    if (i == ':') {
Packit a38265
      usage (progname);
Packit a38265
      goto exit_err;
Packit a38265
    }
Packit a38265
Packit a38265
    switch (i) {
Packit a38265
    case 'h': /* help */
Packit a38265
      show_help = 1;
Packit a38265
      break;
Packit a38265
    case 'v': /* version */
Packit a38265
      show_version = 1;
Packit a38265
      break;
Packit a38265
    case 'o': /* output */
Packit a38265
      outfilename = optarg;
Packit a38265
      break;
Packit a38265
    case 'V': /* verbose */
Packit a38265
      omdata->verbose = 1;
Packit a38265
    default:
Packit a38265
      break;
Packit a38265
    }
Packit a38265
  }
Packit a38265
Packit a38265
  if (show_version) {
Packit a38265
    printf ("%s version " VERSION "\n", progname);
Packit a38265
  }
Packit a38265
Packit a38265
  if (show_help) {
Packit a38265
    usage (progname);
Packit a38265
  }
Packit a38265
Packit a38265
  if (show_version || show_help) {
Packit a38265
    goto exit_ok;
Packit a38265
  }
Packit a38265
Packit a38265
  if (optind >= argc) {
Packit a38265
    usage (progname);
Packit a38265
    goto exit_err;
Packit a38265
  }
Packit a38265
Packit a38265
  if (optind >= argc) {
Packit a38265
    usage (progname);
Packit a38265
    goto exit_err;
Packit a38265
  }
Packit a38265
Packit a38265
  while (optind < argc) {
Packit a38265
    infilename = argv[optind++];
Packit a38265
    if (strcmp (infilename, "-") == 0) {
Packit a38265
      if (used_stdin) continue;
Packit a38265
Packit a38265
      infile = stdin;
Packit a38265
      used_stdin = 1;
Packit a38265
    } else {
Packit a38265
      infile = fopen (infilename, "rb");
Packit a38265
    }
Packit a38265
Packit a38265
    if (infile == NULL) {
Packit a38265
      fprintf (stderr, "%s: unable to open input file %s\n", progname,
Packit a38265
	       infilename);
Packit a38265
    } else {
Packit a38265
      if (omdata_add_input (omdata, infile) < 0)
Packit a38265
        exit_out_of_memory();
Packit a38265
    }
Packit a38265
  }
Packit a38265
Packit a38265
  if (outfilename == NULL) {
Packit a38265
    outfile = stdout;
Packit a38265
  } else {
Packit a38265
    outfile = fopen (outfilename, "wb");
Packit a38265
    if (outfile == NULL) {
Packit a38265
      fprintf (stderr, "%s: unable to open output file %s\n",
Packit a38265
	       progname, outfilename);
Packit a38265
      goto exit_err;
Packit a38265
    }
Packit a38265
  }
Packit a38265
Packit a38265
  oggz_merge (omdata, outfile);
Packit a38265
Packit a38265
 exit_ok:
Packit a38265
  omdata_delete (omdata);
Packit a38265
  exit (0);
Packit a38265
Packit a38265
 exit_err:
Packit a38265
  omdata_delete (omdata);
Packit a38265
  exit (1);
Packit a38265
}