You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

825 lines
18 KiB

/*
* avimerge.c
*
* Copyright (C) Thomas Oestreich - June 2001
*
* This file is part of transcode, a video stream processing tool
*
* transcode is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* transcode is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
// TODO: Simplify this code. Immediatly
#include "transcode.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "aud_scan.h"
#include "aud_scan_avi.h"
#define EXE "avimerge"
/* AVI_info is no longer in avilib */
void AVI_info(avi_t *avifile);
void version(void)
{
printf("%s (%s v%s) (C) 2001-2004 Thomas Oestreich, T. Bitterberg"
" 2004-2010 Transcode Team\n",
EXE, PACKAGE, VERSION);
}
static void usage(int status)
{
version();
printf("\nUsage: %s [options]\n", EXE);
printf(" -o file output file name\n");
printf(" -i file1 [file2 [...]] input file(s)\n");
printf(" -p file multiplex additional audio track from file\n");
printf(" -a num select audio track number from input file [0]\n");
printf(" -A num select audio track number in output file [next]\n");
printf(" -b n handle vbr audio [autodetect]\n");
printf(" -c drop video frames in case audio is missing [off]\n");
printf(" -f FILE read AVI comments from FILE [off]\n");
printf(" -x FILE read AVI index from FILE [off] (see aviindex(1))\n");
exit(status);
}
static char data[SIZE_RGB_FRAME];
static char *comfile = NULL;
static char *indexfile = NULL;
long sum_frames = 0;
int is_vbr=1;
int drop_video=0;
static int merger(avi_t *out, char *file)
{
avi_t *in;
long frames, n, bytes;
int key, j, aud_tracks;
static int init = 0;
static int vid_chunks = 0;
double fps;
static double vid_ms;
static double aud_ms[AVI_MAX_TRACKS];
int have_printed=0;
int do_drop_video=0;
if (!init) {
for (j=0; j<AVI_MAX_TRACKS; j++)
aud_ms[j] = 0.0;
vid_ms = 0;
vid_chunks = 0;
init = 1;
}
if(indexfile) {
if (NULL == (in = AVI_open_input_indexfile(file, 0, indexfile))) {
AVI_print_error("AVI open with indexfile");
return(-1);
}
}
else if(NULL == (in = AVI_open_input_file(file,1))) {
AVI_print_error("AVI open");
return(-1);
}
AVI_seek_start(in);
fps = AVI_frame_rate(in);
frames = AVI_video_frames(in);
aud_tracks = AVI_audio_tracks(in);
for (n=0; n<frames; ++n) {
++vid_chunks;
vid_ms = vid_chunks*1000.0/fps;
// audio
for(j=0; j<aud_tracks; ++j) {
int ret;
double old_ms = aud_ms[j];
AVI_set_audio_track(in, j);
AVI_set_audio_track(out, j);
ret = sync_audio_video_avi2avi (vid_ms, &aud_ms[j], in, out);
if (ret<0) {
if (ret==-2) {
if (aud_ms[j] == old_ms) {
do_drop_video = 1;
if (!have_printed) {
fprintf(stderr, "\nNo audiodata left for track %d->%d (%.2f=%.2f) %s ..\n",
AVI_get_audio_track(in), AVI_get_audio_track(out),
old_ms, aud_ms[j], (do_drop_video && drop_video)?"breaking (-c)":"continuing");
have_printed++;
}
}
} else {
fprintf(stderr, "\nAn error happend at frame %ld track %d\n", n, j);
}
}
}
if (do_drop_video && drop_video) {
fprintf(stderr, "\n[avimerge] Dropping %ld frames\n", frames-n-1);
goto out;
}
// video
bytes = AVI_read_frame(in, data, &key);
if(bytes < 0) {
AVI_print_error("AVI read video frame");
return(-1);
}
if(AVI_write_frame(out, data, bytes, key)<0) {
AVI_print_error("AVI write video frame");
return(-1);
}
// progress
fprintf(stderr, "[%s] (%06ld-%06ld) (%.2f <-> %.2f)\r", file, sum_frames, sum_frames + n, vid_ms, aud_ms[0]);
}
out:
fprintf(stderr, "\n");
AVI_close(in);
sum_frames += n;
return(0);
}
int main(int argc, char *argv[])
{
avi_t *avifile, *avifile1, *avifile2;
char *outfile=NULL, *infile=NULL, *audfile=NULL;
long rate, mp3rate;
int j, ch, cc=0, track_num=0, out_track_num=-1;
int width, height, format=0, format_add, chan, bits, aud_error=0;
double fps;
char *codec;
long offset, frames, n, bytes, aud_offset=0;
int key;
int aud_tracks;
// for mp3 audio
FILE *f=NULL;
int len, headlen, chan_i, rate_i, mp3rate_i;
unsigned long vid_chunks=0;
char head[8];
off_t pos;
double aud_ms = 0.0, vid_ms = 0.0;
double aud_ms_w[AVI_MAX_TRACKS];
ac_init(AC_ALL);
if(argc==1) usage(EXIT_FAILURE);
while ((ch = getopt(argc, argv, "A:a:b:ci:o:p:f:x:?hv")) != -1) {
switch (ch) {
case 'i':
if(optarg[0]=='-') usage(EXIT_FAILURE);
infile = optarg;
break;
case 'A':
if(optarg[0]=='-') usage(EXIT_FAILURE);
out_track_num = atoi(optarg);
if(out_track_num<-1) usage(EXIT_FAILURE);
break;
case 'a':
if(optarg[0]=='-') usage(EXIT_FAILURE);
track_num = atoi(optarg);
if(track_num<0) usage(EXIT_FAILURE);
break;
case 'b':
if(optarg[0]=='-') usage(EXIT_FAILURE);
is_vbr = atoi(optarg);
if(is_vbr<0) usage(EXIT_FAILURE);
break;
case 'c':
drop_video = 1;
break;
case 'o':
if(optarg[0]=='-') usage(EXIT_FAILURE);
outfile = optarg;
break;
case 'p':
if(optarg[0]=='-') usage(EXIT_FAILURE);
audfile = optarg;
break;
case 'f':
if(optarg[0]=='-') usage(EXIT_FAILURE);
comfile = optarg;
break;
case 'x':
if(optarg[0]=='-') usage(EXIT_FAILURE);
indexfile = optarg;
break;
case 'v':
version();
exit(EXIT_SUCCESS);
case 'h':
usage(EXIT_SUCCESS);
default:
usage(EXIT_FAILURE);
}
}
if(outfile == NULL || infile == NULL) usage(EXIT_FAILURE);
printf("scanning file %s for video/audio parameter\n", infile);
// open first file for video/audio info read only
if(indexfile) {
if (NULL == (avifile1 = AVI_open_input_indexfile(infile,0,indexfile))) {
AVI_print_error("AVI open with index file");
}
}
else if(NULL == (avifile1 = AVI_open_input_file(infile,1))) {
AVI_print_error("AVI open");
exit(1);
}
AVI_info(avifile1);
// safety checks
if(strcmp(infile, outfile)==0) {
printf("error: output filename conflicts with input filename\n");
exit(1);
}
ch = optind;
while (ch < argc) {
if(tc_file_check(argv[ch]) != 0) {
printf("error: file not found\n");
exit(1);
}
if(strcmp(argv[ch++], outfile)==0) {
printf("error: output filename conflicts with input filename\n");
exit(1);
}
}
// open output file
if(NULL == (avifile = AVI_open_output_file(outfile))) {
AVI_print_error("AVI open");
exit(1);
}
// read video info;
width = AVI_video_width(avifile1);
height = AVI_video_height(avifile1);
fps = AVI_frame_rate(avifile1);
codec = AVI_video_compressor(avifile1);
//set video in outputfile
AVI_set_video(avifile, width, height, fps, codec);
if (comfile!=NULL)
AVI_set_comment_fd(avifile, open(comfile, O_RDONLY));
//multi audio tracks?
aud_tracks = AVI_audio_tracks(avifile1);
if (out_track_num < 0) out_track_num = aud_tracks;
for(j=0; j<aud_tracks; ++j) {
if (out_track_num == j) continue;
AVI_set_audio_track(avifile1, j);
rate = AVI_audio_rate(avifile1);
chan = AVI_audio_channels(avifile1);
bits = AVI_audio_bits(avifile1);
format = AVI_audio_format(avifile1);
mp3rate= AVI_audio_mp3rate(avifile1);
//printf("TRACK %d MP3RATE %ld VBR %ld\n", j, mp3rate, AVI_get_audio_vbr(avifile1));
//set next track of output file
AVI_set_audio_track(avifile, j);
AVI_set_audio(avifile, chan, rate, bits, format, mp3rate);
AVI_set_audio_vbr(avifile, AVI_get_audio_vbr(avifile1));
}
if(audfile!=NULL) goto audio_merge;
// close reopen in merger function
AVI_close(avifile1);
//-------------------------------------------------------------
printf("merging multiple AVI-files (concatenating) ...\n");
// extract and write to new files
printf ("file %02d %s\n", ++cc, infile);
merger(avifile, infile);
while (optind < argc) {
printf ("file %02d %s\n", ++cc, argv[optind]);
merger(avifile, argv[optind++]);
}
// close new AVI file
AVI_close(avifile);
printf("... done merging %d file(s) in %s\n", cc, outfile);
// reopen file for video/audio info
if(NULL == (avifile = AVI_open_input_file(outfile,1))) {
AVI_print_error("AVI open");
exit(1);
}
AVI_info(avifile);
return(0);
//-------------------------------------------------------------
// *************************************************
// Merge the audio track of an additional AVI file
// *************************************************
audio_merge:
printf("merging audio %s track %d (multiplexing) into %d ...\n", audfile, track_num, out_track_num);
// open audio file read only
if(NULL == (avifile2 = AVI_open_input_file(audfile,1))) {
int f=open(audfile, O_RDONLY), ret=0;
char head[1024], *c;
c = head;
if (f>0 && (1024 == read(f, head, 1024)) ) {
while ((c-head<1024-8) && (ret = tc_probe_audio_header(c, 8))<=0 ) {
c++;
}
close(f);
if (ret > 0) {
aud_offset = c-head;
//printf("found atrack 0x%x off=%ld\n", ret, aud_offset);
goto merge_mp3;
}
}
AVI_print_error("AVI open");
exit(1);
}
AVI_info(avifile2);
//switch to requested track
if(AVI_set_audio_track(avifile2, track_num)<0) {
fprintf(stderr, "invalid audio track\n");
}
rate = AVI_audio_rate(avifile2);
chan = AVI_audio_channels(avifile2);
bits = AVI_audio_bits(avifile2);
format = AVI_audio_format(avifile2);
mp3rate= AVI_audio_mp3rate(avifile2);
//set next track
AVI_set_audio_track(avifile, out_track_num);
AVI_set_audio(avifile, chan, rate, bits, format, mp3rate);
AVI_set_audio_vbr(avifile, AVI_get_audio_vbr(avifile2));
AVI_seek_start(avifile1);
frames = AVI_video_frames(avifile1);
offset = 0;
printf ("file %02d %s\n", ++cc, infile);
for (n=0; n<AVI_MAX_TRACKS; n++)
aud_ms_w[n] = 0.0;
vid_chunks=0;
for (n=0; n<frames; ++n) {
// video
bytes = AVI_read_frame(avifile1, data, &key);
if(bytes < 0) {
AVI_print_error("AVI read video frame");
return(-1);
}
if(AVI_write_frame(avifile, data, bytes, key)<0) {
AVI_print_error("AVI write video frame");
return(-1);
}
++vid_chunks;
vid_ms = vid_chunks*1000.0/fps;
for(j=0; j<aud_tracks; ++j) {
if (j == out_track_num) continue;
AVI_set_audio_track(avifile1, j);
AVI_set_audio_track(avifile, j);
chan = AVI_audio_channels(avifile1);
// audio
chan = AVI_audio_channels(avifile1);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms_w[j], avifile1, avifile);
}
}
// merge additional track
// audio
chan = AVI_audio_channels(avifile2);
AVI_set_audio_track(avifile, out_track_num);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms, avifile2, avifile);
}
// progress
fprintf(stderr, "[%s] (%06ld-%06ld)\r", outfile, offset, offset + n);
}
fprintf(stderr,"\n");
offset = frames;
//more files to merge?
AVI_close(avifile1);
while (optind < argc) {
printf ("file %02d %s\n", ++cc, argv[optind]);
if(NULL == ( avifile1 = AVI_open_input_file(argv[optind++],1))) {
AVI_print_error("AVI open");
goto finish;
}
AVI_seek_start(avifile1);
frames = AVI_video_frames(avifile1);
for (n=0; n<frames; ++n) {
// video
bytes = AVI_read_frame(avifile1, data, &key);
if(bytes < 0) {
AVI_print_error("AVI read video frame");
return(-1);
}
if(AVI_write_frame(avifile, data, bytes, key)<0) {
AVI_print_error("AVI write video frame");
return(-1);
}
++vid_chunks;
vid_ms = vid_chunks*1000.0/fps;
// audio
for(j=0; j<aud_tracks; ++j) {
if (j == out_track_num) continue;
AVI_set_audio_track(avifile1, j);
AVI_set_audio_track(avifile, j);
chan = AVI_audio_channels(avifile1);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms_w[j], avifile1, avifile);
}
}
// merge additional track
chan = AVI_audio_channels(avifile2);
AVI_set_audio_track(avifile, out_track_num);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms, avifile2, avifile);
} // chan
// progress
fprintf(stderr, "[%s] (%06ld-%06ld)\r", outfile, offset, offset + n);
}
fprintf(stderr, "\n");
offset += frames;
AVI_close(avifile1);
}
finish:
// close new AVI file
printf("... done multiplexing in %s\n", outfile);
AVI_info(avifile);
AVI_close(avifile);
return(0);
// *************************************************
// Merge a raw audio file which is either MP3 or AC3
// *************************************************
merge_mp3:
f = fopen(audfile,"rb");
if (!f) { perror ("fopen"); exit(1); }
fseek(f, aud_offset, SEEK_SET);
len = fread(head, 1, 8, f);
format_add = tc_probe_audio_header(head, len);
headlen = tc_get_audio_header(head, len, format_add, &chan_i, &rate_i, &mp3rate_i);
fprintf(stderr, "... this looks like a %s track ...\n", (format_add==0x55)?"MP3":"AC3");
fseek(f, aud_offset, SEEK_SET);
//set next track
AVI_set_audio_track(avifile, out_track_num);
AVI_set_audio(avifile, chan_i, rate_i, 16, format_add, mp3rate_i);
AVI_set_audio_vbr(avifile, is_vbr);
AVI_seek_start(avifile1);
frames = AVI_video_frames(avifile1);
offset = 0;
for (n=0; n<AVI_MAX_TRACKS; ++n)
aud_ms_w[n] = 0.0;
for (n=0; n<frames; ++n) {
// video
bytes = AVI_read_frame(avifile1, data, &key);
if(bytes < 0) {
AVI_print_error("AVI read video frame");
return(-1);
}
if(AVI_write_frame(avifile, data, bytes, key)<0) {
AVI_print_error("AVI write video frame");
return(-1);
}
vid_chunks++;
vid_ms = vid_chunks*1000.0/fps;
for(j=0; j<aud_tracks; ++j) {
if (j == out_track_num) continue;
AVI_set_audio_track(avifile1, j);
AVI_set_audio_track(avifile, j);
chan = AVI_audio_channels(avifile1);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms_w[j], avifile1, avifile);
}
}
// merge additional track
if(headlen>4 && !aud_error) {
while (aud_ms < vid_ms) {
//printf("reading Audio Chunk ch(%ld) vms(%lf) ams(%lf)\n", vid_chunks, vid_ms, aud_ms);
pos = ftell(f);
len = fread (head, 1, 8, f);
if (len<=0) { //eof
fprintf(stderr, "EOF in %s; continuing ..\n", audfile);
aud_error=1;
break;
}
if ( (headlen = tc_get_audio_header(head, len, format_add, NULL, NULL, &mp3rate_i))<0) {
fprintf(stderr, "Broken %s track #(%d)? skipping\n", (format_add==0x55?"MP3":"AC3"), aud_tracks);
aud_ms = vid_ms;
aud_error=1;
} else { // look in import/tcscan.c for explanation
aud_ms += (headlen*8.0)/(mp3rate_i);
}
fseek (f, pos, SEEK_SET);
len = fread (data, headlen, 1, f);
if (len<=0) { //eof
fprintf(stderr, "EOF in %s; continuing ..\n", audfile);
aud_error=1;
break;
}
AVI_set_audio_track(avifile, out_track_num);
if(AVI_write_audio(avifile, data, headlen)<0) {
AVI_print_error("AVI write audio frame");
return(-1);
}
}
}
// progress
fprintf(stderr, "[%s] (%06ld-%06ld)\r", outfile, offset, offset + n);
}
fprintf(stderr,"\n");
offset = frames;
// more files?
while (optind < argc) {
printf ("file %02d %s\n", ++cc, argv[optind]);
if(NULL == ( avifile1 = AVI_open_input_file(argv[optind++],1))) {
AVI_print_error("AVI open");
goto finish;
}
AVI_seek_start(avifile1);
frames = AVI_video_frames(avifile1);
for (n=0; n<frames; ++n) {
// video
bytes = AVI_read_frame(avifile1, data, &key);
if(bytes < 0) {
AVI_print_error("AVI read video frame");
return(-1);
}
if(AVI_write_frame(avifile, data, bytes, key)<0) {
AVI_print_error("AVI write video frame");
return(-1);
}
vid_chunks++;
vid_ms = vid_chunks*1000.0/fps;
for(j=0; j<aud_tracks; ++j) {
if (j == out_track_num) continue;
AVI_set_audio_track(avifile1, j);
AVI_set_audio_track(avifile, j);
chan = AVI_audio_channels(avifile1);
if(chan) {
sync_audio_video_avi2avi(vid_ms, &aud_ms_w[j], avifile1, avifile);
}
}
// merge additional track
// audio
if(headlen>4 && !aud_error) {
while (aud_ms < vid_ms) {
//printf("reading Audio Chunk ch(%ld) vms(%lf) ams(%lf)\n", vid_chunks, vid_ms, aud_ms);
pos = ftell(f);
len = fread (head, 8, 1, f);
if (len<=0) { //eof
fprintf(stderr, "EOF in %s; continuing ..\n", audfile);
aud_error=1; break;
}
if ( (headlen = tc_get_audio_header(head, len, format_add, NULL, NULL, &mp3rate_i))<0) {
fprintf(stderr, "Broken %s track #(%d)?\n", (format_add==0x55?"MP3":"AC3"), aud_tracks);
aud_ms = vid_ms;
aud_error=1;
} else { // look in import/tcscan.c for explanation
aud_ms += (headlen*8.0)/(mp3rate_i);
}
fseek (f, pos, SEEK_SET);
len = fread (data, headlen, 1, f);
if (len<=0) { //eof
fprintf(stderr, "EOF in %s; continuing ..\n", audfile);
aud_error=1; break;
}
AVI_set_audio_track(avifile, out_track_num);
if(AVI_write_audio(avifile, data, headlen)<0) {
AVI_print_error("AVI write audio frame");
return(-1);
}
}
}
// progress
fprintf(stderr, "[%s] (%06ld-%06ld)\r", outfile, offset, offset + n);
}
fprintf(stderr, "\n");
offset += frames;
AVI_close(avifile1);
}
if (f) fclose(f);
printf("... done multiplexing in %s\n", outfile);
AVI_close(avifile);
return(0);
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/