mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-24 20:01:53 +00:00
1363 lines
44 KiB
C++
1363 lines
44 KiB
C++
/*
|
|
mkvextract -- extract tracks from Matroska files into other files
|
|
|
|
mkvextract.cpp
|
|
|
|
Written by Moritz Bunkus <moritz@bunkus.org>
|
|
|
|
Distributed under the GPL
|
|
see the file COPYING for details
|
|
or visit http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
|
|
/*!
|
|
\file
|
|
\version $Id$
|
|
\brief extracts tracks from Matroska files into other files
|
|
\author Moritz Bunkus <moritz@bunkus.org>
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#if defined(COMP_MSC)
|
|
#include <assert.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#include <ogg/ogg.h>
|
|
#include <vorbis/codec.h>
|
|
|
|
extern "C" {
|
|
#include "avilib/avilib.h"
|
|
}
|
|
|
|
#include "EbmlHead.h"
|
|
#include "EbmlSubHead.h"
|
|
#include "EbmlStream.h"
|
|
#include "EbmlVoid.h"
|
|
#include "FileKax.h"
|
|
|
|
#include "KaxAttachements.h"
|
|
#include "KaxBlock.h"
|
|
#include "KaxBlockData.h"
|
|
#include "KaxChapters.h"
|
|
#include "KaxCluster.h"
|
|
#include "KaxClusterData.h"
|
|
#include "KaxCues.h"
|
|
#include "KaxCuesData.h"
|
|
#include "KaxInfo.h"
|
|
#include "KaxInfoData.h"
|
|
#include "KaxSeekHead.h"
|
|
#include "KaxSegment.h"
|
|
#include "KaxTags.h"
|
|
#include "KaxTracks.h"
|
|
#include "KaxTrackEntryData.h"
|
|
#include "KaxTrackAudio.h"
|
|
#include "KaxTrackVideo.h"
|
|
|
|
#include "common.h"
|
|
#include "matroska.h"
|
|
#include "mm_io.h"
|
|
|
|
using namespace LIBMATROSKA_NAMESPACE;
|
|
using namespace std;
|
|
|
|
#define NAME "mkvextract"
|
|
|
|
class ssa_line_c {
|
|
public:
|
|
char *line;
|
|
int num;
|
|
|
|
bool operator < (const ssa_line_c &cmp) const;
|
|
};
|
|
|
|
bool ssa_line_c::operator < (const ssa_line_c &cmp) const {
|
|
return num < cmp.num;
|
|
}
|
|
|
|
typedef struct {
|
|
char *out_name;
|
|
|
|
mm_io_c *mm_io;
|
|
avi_t *avi;
|
|
ogg_stream_state osstate;
|
|
|
|
int64_t tid;
|
|
bool in_use;
|
|
|
|
char track_type;
|
|
int type;
|
|
|
|
char *codec_id;
|
|
void *private_data;
|
|
int private_size;
|
|
|
|
float a_sfreq;
|
|
int a_channels, a_bps;
|
|
|
|
float v_fps;
|
|
int v_width, v_height;
|
|
|
|
int srt_num;
|
|
int conv_handle;
|
|
vector<ssa_line_c> ssa_lines;
|
|
|
|
wave_header wh;
|
|
int64_t bytes_written;
|
|
|
|
unsigned char *buffered_data;
|
|
int buffered_size;
|
|
int64_t packetno, last_end;
|
|
int header_sizes[3];
|
|
unsigned char *headers[3];
|
|
|
|
int aac_id, aac_profile, aac_srate_idx;
|
|
} mkv_track_t;
|
|
|
|
vector<mkv_track_t> tracks;
|
|
|
|
mkv_track_t *find_track(int tid) {
|
|
int i;
|
|
|
|
for (i = 0; i < tracks.size(); i++)
|
|
if (tracks[i].tid == tid)
|
|
return &tracks[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char typenames[14][20] = {"unknown", "Ogg" "AVI", "WAV", "SRT", "MP3", "AC3",
|
|
"chapter", "MicroDVD", "VobSub", "Matroska", "DTS",
|
|
"AAC", "SSA/ASS"};
|
|
|
|
void usage() {
|
|
fprintf(stdout,
|
|
"Usage: mkvextract -i inname [TID1:outname1 [TID2:outname2 ...]]\n\n"
|
|
" -i inname Use 'inname' as the source.\n"
|
|
" -c charset Convert text subtitles to this charset.\n"
|
|
" TID:outname Write track with the ID TID to 'outname'.\n"
|
|
"\n"
|
|
" -v, --verbose Increase verbosity.\n"
|
|
" -h, --help Show this help.\n"
|
|
" -V, --version Show version information.\n");
|
|
}
|
|
|
|
void parse_args(int argc, char **argv, char *&file_name) {
|
|
int i, conv_handle;
|
|
char *colon, *copy;
|
|
int64_t tid;
|
|
mkv_track_t track;
|
|
|
|
file_name = NULL;
|
|
verbose = 0;
|
|
|
|
// Find options that directly end the program.
|
|
for (i = 1; i < argc; i++)
|
|
if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) {
|
|
fprintf(stdout, VERSIONINFO "\n");
|
|
exit(0);
|
|
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?") ||
|
|
!strcmp(argv[i], "--help")) {
|
|
usage();
|
|
exit(0);
|
|
}
|
|
|
|
conv_handle = 0;
|
|
|
|
// Now process all the other options.
|
|
for (i = 1; i < argc; i++)
|
|
if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
|
|
verbose++;
|
|
else if (!strcmp(argv[i], "-i")) {
|
|
if ((i + 1) >= argc) {
|
|
fprintf(stderr, "Error: -i lacks a file name.\n");
|
|
exit(1);
|
|
} else if (file_name != NULL) {
|
|
fprintf(stderr, "Error: Only one input file is allowed.\n");
|
|
exit(1);
|
|
}
|
|
file_name = argv[i + 1];
|
|
i++;
|
|
} else if (!strcmp(argv[i], "-c")) {
|
|
if ((i + 1) >= argc) {
|
|
fprintf(stderr, "Error: -c lacks a charset.\n");
|
|
exit(1);
|
|
}
|
|
conv_handle = utf8_init(argv[i + 1]);
|
|
i++;
|
|
} else {
|
|
copy = safestrdup(argv[i]);
|
|
colon = strchr(copy, ':');
|
|
if (colon == NULL) {
|
|
fprintf(stderr, "Error: Missing track ID in argument '%s'.\n",
|
|
argv[i]);
|
|
exit(1);
|
|
}
|
|
*colon = 0;
|
|
if (!parse_int(copy, tid) || (tid < 0)) {
|
|
fprintf(stderr, "Error: Invalid track ID in argument '%s'.\n",
|
|
argv[i]);
|
|
exit(1);
|
|
}
|
|
colon++;
|
|
if (*colon == 0) {
|
|
fprintf(stderr, "Error: Missing output filename in argument '%s'.\n",
|
|
argv[i]);
|
|
exit(1);
|
|
}
|
|
memset(&track, 0, sizeof(mkv_track_t));
|
|
track.tid = tid;
|
|
track.out_name = safestrdup(colon);
|
|
track.conv_handle = conv_handle;
|
|
tracks.push_back(track);
|
|
safefree(copy);
|
|
}
|
|
|
|
if (file_name == NULL) {
|
|
fprintf(stderr, "Error: No input file given.\n\n");
|
|
usage();
|
|
exit(0);
|
|
}
|
|
|
|
if (tracks.size() == 0) {
|
|
fprintf(stdout, "Nothing to do.\n\n");
|
|
usage();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
#define ARGS_BUFFER_LEN (200 * 1024) // Ok let's be ridiculous here :)
|
|
static char args_buffer[ARGS_BUFFER_LEN];
|
|
|
|
void show_element(EbmlElement *l, int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
char level_buffer[10];
|
|
|
|
if (level > 9)
|
|
die("mkvextract.cpp/show_element(): level > 9: %d", level);
|
|
|
|
if (verbose == 0)
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
args_buffer[ARGS_BUFFER_LEN - 1] = 0;
|
|
vsnprintf(args_buffer, ARGS_BUFFER_LEN - 1, fmt, ap);
|
|
va_end(ap);
|
|
|
|
memset(&level_buffer[1], ' ', 9);
|
|
level_buffer[0] = '|';
|
|
level_buffer[level] = 0;
|
|
fprintf(stdout, "(%s) %s+ %s", NAME, level_buffer, args_buffer);
|
|
if (l != NULL)
|
|
fprintf(stdout, " at %llu", l->GetElementPosition());
|
|
fprintf(stdout, "\n");
|
|
}
|
|
|
|
void show_error(const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
args_buffer[ARGS_BUFFER_LEN - 1] = 0;
|
|
vsnprintf(args_buffer, ARGS_BUFFER_LEN - 1, fmt, ap);
|
|
va_end(ap);
|
|
|
|
fprintf(stdout, "(%s) %s\n", NAME, args_buffer);
|
|
}
|
|
|
|
void flush_ogg_pages(mkv_track_t &track) {
|
|
ogg_page page;
|
|
|
|
while (ogg_stream_flush(&track.osstate, &page)) {
|
|
track.mm_io->write(page.header, page.header_len);
|
|
track.mm_io->write(page.body, page.body_len);
|
|
}
|
|
}
|
|
|
|
void write_ogg_pages(mkv_track_t &track) {
|
|
ogg_page page;
|
|
|
|
while (ogg_stream_pageout(&track.osstate, &page)) {
|
|
track.mm_io->write(page.header, page.header_len);
|
|
track.mm_io->write(page.body, page.body_len);
|
|
}
|
|
}
|
|
|
|
void create_output_files() {
|
|
int i, k, offset;
|
|
bool something_to_do, is_ok;
|
|
unsigned char *c;
|
|
ogg_packet op;
|
|
|
|
something_to_do = false;
|
|
|
|
for (i = 0; i < tracks.size(); i++) {
|
|
if (tracks[i].in_use) {
|
|
tracks[i].in_use = false;
|
|
|
|
if (tracks[i].codec_id == NULL) {
|
|
fprintf(stdout, "Warning: Track ID %lld is missing the CodecID. "
|
|
"Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
// Video tracks:
|
|
if (tracks[i].track_type == 'v') {
|
|
tracks[i].type = TYPEAVI;
|
|
if ((tracks[i].v_width == 0) || (tracks[i].v_height == 0) ||
|
|
(tracks[i].v_fps == 0.0) || (tracks[i].private_data == NULL) ||
|
|
(tracks[i].private_size < sizeof(alBITMAPINFOHEADER))) {
|
|
fprintf(stdout, "Warning: Track ID %lld is missing some critical "
|
|
"information. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(tracks[i].codec_id, MKV_V_MSCOMP)) {
|
|
fprintf(stdout, "Warning: Extraction of video tracks with a CodecId "
|
|
"other than " MKV_V_MSCOMP " is not supported at the "
|
|
"moment. Skipping track %lld.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
} else if (tracks[i].track_type == 'a') {
|
|
if ((tracks[i].a_sfreq == 0.0) || (tracks[i].a_channels == 0)) {
|
|
fprintf(stdout, "Warning: Track ID %lld is missing some critical "
|
|
"information. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(tracks[i].codec_id, MKV_A_VORBIS)) {
|
|
tracks[i].type = TYPEOGM; // Yeah, I know, I know...
|
|
if (tracks[i].private_data == NULL) {
|
|
fprintf(stdout, "Warning: Track ID %lld is missing some critical "
|
|
"information. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
c = (unsigned char *)tracks[i].private_data;
|
|
if (c[0] != 2) {
|
|
fprintf(stderr, "Error: Vorbis track ID %lld does not contain "
|
|
"valid headers. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
is_ok = true;
|
|
offset = 1;
|
|
for (k = 0; k < 2; k++) {
|
|
int length = 0;
|
|
while ((c[offset] == (unsigned char)255) &&
|
|
(length < tracks[i].private_size)) {
|
|
length += 255;
|
|
offset++;
|
|
}
|
|
if (offset >= (tracks[i].private_size - 1)) {
|
|
fprintf(stderr, "Error: Vorbis track ID %lld does not contain "
|
|
"valid headers. Skipping track.\n", tracks[i].tid);
|
|
is_ok = false;
|
|
break;
|
|
}
|
|
length += c[offset];
|
|
offset++;
|
|
tracks[i].header_sizes[k] = length;
|
|
}
|
|
|
|
if (!is_ok)
|
|
continue;
|
|
|
|
tracks[i].headers[0] = &c[offset];
|
|
tracks[i].headers[1] = &c[offset + tracks[i].header_sizes[0]];
|
|
tracks[i].headers[2] = &c[offset + tracks[i].header_sizes[0] +
|
|
tracks[i].header_sizes[1]];
|
|
tracks[i].header_sizes[2] = tracks[i].private_size -
|
|
offset - tracks[i].header_sizes[0] - tracks[i].header_sizes[1];
|
|
|
|
} else if (!strcmp(tracks[i].codec_id, MKV_A_MP3))
|
|
tracks[i].type = TYPEMP3;
|
|
else if (!strcmp(tracks[i].codec_id, MKV_A_AC3))
|
|
tracks[i].type = TYPEAC3;
|
|
else if (!strcmp(tracks[i].codec_id, MKV_A_PCM)) {
|
|
tracks[i].type = TYPEWAV; // Yeah, I know, I know...
|
|
if (tracks[i].a_bps == 0) {
|
|
fprintf(stdout, "Warning: Track ID %lld is missing some critical "
|
|
"information. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
} else if (!strncmp(tracks[i].codec_id, "A_AAC", 5)) {
|
|
if (strlen(tracks[i].codec_id) < strlen("A_AAC/MPEG4/LC")) {
|
|
fprintf(stdout, "Warning: Track ID %lld has an unknown AAC "
|
|
"type. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
// A_AAC/MPEG4/MAIN
|
|
// 0123456789012345
|
|
if (tracks[i].codec_id[10] == '4')
|
|
tracks[i].aac_id = 0;
|
|
else if (tracks[i].codec_id[10] == '2')
|
|
tracks[i].aac_id = 1;
|
|
else {
|
|
fprintf(stdout, "Warning: Track ID %lld has an unknown AAC "
|
|
"type. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(&tracks[i].codec_id[12], "MAIN"))
|
|
tracks[i].aac_profile = 0;
|
|
else if (!strcmp(&tracks[i].codec_id[12], "LC"))
|
|
tracks[i].aac_profile = 1;
|
|
else if (!strcmp(&tracks[i].codec_id[12], "SSR"))
|
|
tracks[i].aac_profile = 2;
|
|
else if (!strcmp(&tracks[i].codec_id[12], "LTP"))
|
|
tracks[i].aac_profile = 3;
|
|
else {
|
|
fprintf(stdout, "Warning: Track ID %lld has an unknown AAC "
|
|
"type. Skipping track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
if (92017 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 0;
|
|
else if (75132 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 1;
|
|
else if (55426 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 2;
|
|
else if (46009 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 3;
|
|
else if (37566 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 4;
|
|
else if (27713 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 5;
|
|
else if (23004 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 6;
|
|
else if (18783 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 7;
|
|
else if (13856 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 8;
|
|
else if (11502 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 9;
|
|
else if (9391 <= tracks[i].a_sfreq)
|
|
tracks[i].aac_srate_idx = 10;
|
|
else
|
|
tracks[i].aac_srate_idx = 11;
|
|
|
|
tracks[i].type = TYPEAAC;
|
|
|
|
} else if (!strcmp(tracks[i].codec_id, MKV_A_DTS)) {
|
|
fprintf(stdout, "Warning: Extraction of DTS is not supported - yet. "
|
|
"I promise I'll implement it. Really Soon Now (tm)! "
|
|
"Skipping track.\n");
|
|
continue;
|
|
|
|
} else {
|
|
fprintf(stdout, "Warning: Unsupported CodecID '%s' for ID %lld. "
|
|
"Skipping track.\n", tracks[i].codec_id, tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
} else if (tracks[i].track_type == 's') {
|
|
if (!strcmp(tracks[i].codec_id, MKV_S_TEXTUTF8))
|
|
tracks[i].type = TYPESRT;
|
|
else if (!strcmp(tracks[i].codec_id, MKV_S_TEXTSSA) ||
|
|
!strcmp(tracks[i].codec_id, MKV_S_TEXTASS))
|
|
tracks[i].type = TYPESSA;
|
|
else {
|
|
fprintf(stdout, "Warning: Unsupported CodecID '%s' for ID %lld. "
|
|
"Skipping track.\n", tracks[i].codec_id, tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
} else {
|
|
fprintf(stdout, "Warning: Unknown track type for ID %lld. Skipping "
|
|
"track.\n", tracks[i].tid);
|
|
continue;
|
|
}
|
|
|
|
tracks[i].in_use = true;
|
|
something_to_do = true;
|
|
} else
|
|
fprintf(stdout, "Warning: There is no track with the ID '%lld' in the "
|
|
"input file.\n", tracks[i].tid);
|
|
}
|
|
|
|
if (!something_to_do) {
|
|
fprintf(stdout, "Nothing to do. Exiting.\n");
|
|
exit(0);
|
|
}
|
|
|
|
// Now finally create some files. Hell, I *WANT* to create something now.
|
|
// RIGHT NOW! Or I'll go insane...
|
|
for (i = 0; i < tracks.size(); i++) {
|
|
if (tracks[i].in_use) {
|
|
if (tracks[i].type == TYPEAVI) {
|
|
alBITMAPINFOHEADER *bih;
|
|
char ccodec[5];
|
|
|
|
tracks[i].avi = AVI_open_output_file(tracks[i].out_name);
|
|
if (tracks[i].avi == NULL) {
|
|
fprintf(stderr, "Error: Could not create '%s'. Reason: %s.\n",
|
|
tracks[i].out_name, AVI_strerror());
|
|
exit(1);
|
|
}
|
|
|
|
bih = (alBITMAPINFOHEADER *)tracks[i].private_data;
|
|
memcpy(ccodec, &bih->bi_compression, 4);
|
|
ccodec[4] = 0;
|
|
AVI_set_video(tracks[i].avi, tracks[i].v_width, tracks[i].v_height,
|
|
tracks[i].v_fps, ccodec);
|
|
|
|
fprintf(stdout, "Extracting track ID %lld to an AVI file '%s'.\n",
|
|
tracks[i].tid, tracks[i].out_name);
|
|
|
|
} else {
|
|
|
|
try {
|
|
tracks[i].mm_io = new mm_io_c(tracks[i].out_name, MODE_CREATE);
|
|
} catch (exception &ex) {
|
|
fprintf(stderr, "Error: Could not create '%s'. Reason: %d (%s). "
|
|
"Aborting.\n", tracks[i].out_name, errno, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
fprintf(stdout, "Extracting track ID %lld to a %s file '%s'.\n",
|
|
tracks[i].tid, typenames[tracks[i].type - 1],
|
|
tracks[i].out_name);
|
|
|
|
if (tracks[i].type == TYPEOGM) {
|
|
ogg_stream_init(&tracks[i].osstate, rand());
|
|
|
|
// Handle the three header packets: Headers, comments, codec
|
|
// setup data.
|
|
op.b_o_s = 1;
|
|
op.e_o_s = 0;
|
|
op.packetno = 0;
|
|
op.packet = tracks[i].headers[0];
|
|
op.bytes = tracks[i].header_sizes[0];
|
|
op.granulepos = 0;
|
|
ogg_stream_packetin(&tracks[i].osstate, &op);
|
|
flush_ogg_pages(tracks[i]);
|
|
op.b_o_s = 0;
|
|
op.packetno = 1;
|
|
op.packet = tracks[i].headers[1];
|
|
op.bytes = tracks[i].header_sizes[1];
|
|
ogg_stream_packetin(&tracks[i].osstate, &op);
|
|
op.packetno = 2;
|
|
op.packet = tracks[i].headers[2];
|
|
op.bytes = tracks[i].header_sizes[2];
|
|
ogg_stream_packetin(&tracks[i].osstate, &op);
|
|
flush_ogg_pages(tracks[i]);
|
|
tracks[i].packetno = 3;
|
|
|
|
} else if (tracks[i].type == TYPEWAV) {
|
|
wave_header *wh = &tracks[i].wh;
|
|
|
|
// Write the WAV header.
|
|
memcpy(&wh->riff.id, "RIFF", 4);
|
|
memcpy(&wh->riff.wave_id, "WAVE", 4);
|
|
memcpy(&wh->format.id, "fmt ", 4);
|
|
put_uint32(&wh->format.len, 16);
|
|
put_uint16(&wh->common.wFormatTag, 1);
|
|
put_uint16(&wh->common.wChannels, tracks[i].a_channels);
|
|
put_uint32(&wh->common.dwSamplesPerSec, (int)tracks[i].a_sfreq);
|
|
put_uint32(&wh->common.dwAvgBytesPerSec,
|
|
tracks[i].a_channels * (int)tracks[i].a_sfreq *
|
|
tracks[i].a_bps / 8);
|
|
put_uint16(&wh->common.wBlockAlign, 4);
|
|
put_uint16(&wh->common.wBitsPerSample, tracks[i].a_bps);
|
|
memcpy(&wh->data.id, "data", 4);
|
|
|
|
tracks[i].mm_io->write(wh, sizeof(wave_header));
|
|
|
|
} else if (tracks[i].type == TYPESRT)
|
|
tracks[i].srt_num = 1;
|
|
|
|
else if (tracks[i].type == TYPESSA) {
|
|
char *s;
|
|
|
|
s = (char *)safemalloc(tracks[i].private_size + 1);
|
|
memcpy(s, tracks[i].private_data, tracks[i].private_size);
|
|
s[tracks[i].private_size] = 0;
|
|
tracks[i].mm_io->puts_unl(s);
|
|
tracks[i].mm_io->puts_unl("\n[Events]\nFormat: Marked, Start, End, "
|
|
"Style, Name, MarginL, MarginR, MarginV, "
|
|
"Effect, Text\n");
|
|
|
|
safefree(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void handle_data(KaxBlock *block, int64_t block_duration, bool has_ref) {
|
|
mkv_track_t *track;
|
|
int i, len, num;
|
|
int64_t start, end;
|
|
char *s, buffer[200], *s2, adts[56 / 8];
|
|
vector<string> fields;
|
|
string line, comma = ",";
|
|
ssa_line_c ssa_line;
|
|
|
|
track = find_track(block->TrackNum());
|
|
if ((track == NULL) || !track->in_use){
|
|
delete block;
|
|
return;
|
|
}
|
|
|
|
start = block->GlobalTimecode() / 1000000; // in ms
|
|
end = start + block_duration;
|
|
|
|
for (i = 0; i < block->NumberFrames(); i++) {
|
|
DataBuffer &data = block->GetBuffer(i);
|
|
switch (track->type) {
|
|
case TYPEAVI:
|
|
AVI_write_frame(track->avi, (char *)data.Buffer(), data.Size(),
|
|
has_ref ? 0 : 1);
|
|
break;
|
|
|
|
case TYPEOGM:
|
|
if (track->buffered_data != NULL) {
|
|
ogg_packet op;
|
|
|
|
op.b_o_s = 0;
|
|
op.e_o_s = 0;
|
|
op.packetno = track->packetno;
|
|
op.packet = track->buffered_data;
|
|
op.bytes = track->buffered_size;
|
|
op.granulepos = start * (int64_t)track->a_sfreq / 1000;
|
|
ogg_stream_packetin(&track->osstate, &op);
|
|
safefree(track->buffered_data);
|
|
|
|
write_ogg_pages(*track);
|
|
|
|
track->packetno++;
|
|
}
|
|
|
|
track->buffered_data = (unsigned char *)safememdup(data.Buffer(),
|
|
data.Size());
|
|
track->buffered_size = data.Size();
|
|
track->last_end = end;
|
|
|
|
break;
|
|
|
|
case TYPESRT:
|
|
// Do the charset conversion.
|
|
s = (char *)safemalloc(data.Size() + 1);
|
|
memcpy(s, data.Buffer(), data.Size());
|
|
s[data.Size()] = 0;
|
|
s2 = from_utf8(tracks[i].conv_handle, s);
|
|
safefree(s);
|
|
len = strlen(s2);
|
|
s = (char *)safemalloc(len + 3);
|
|
strcpy(s, s2);
|
|
safefree(s2);
|
|
s[len] = '\n';
|
|
s[len + 1] = '\n';
|
|
s[len + 2] = 0;
|
|
|
|
// Print the entry's number.
|
|
sprintf(buffer, "%d\n", tracks[i].srt_num);
|
|
tracks[i].srt_num++;
|
|
tracks[i].mm_io->write(buffer, strlen(buffer));
|
|
|
|
// Print the timestamps.
|
|
sprintf(buffer, "%02lld:%02lld:%02lld,%03lld --> %02lld:%02lld:%02lld,"
|
|
"%03lld\n",
|
|
start / 1000 / 60 / 60, (start / 1000 / 60) % 60,
|
|
(start / 1000) % 60, start % 1000,
|
|
end / 1000 / 60 / 60, (end / 1000 / 60) % 60,
|
|
(end / 1000) % 60, end % 1000);
|
|
tracks[i].mm_io->write(buffer, strlen(buffer));
|
|
|
|
// Print the text itself.
|
|
tracks[i].mm_io->puts_unl(s);
|
|
safefree(s);
|
|
break;
|
|
|
|
case TYPESSA:
|
|
s = (char *)safemalloc(data.Size() + 1);
|
|
memcpy(s, data.Buffer(), data.Size());
|
|
s[data.Size()] = 0;
|
|
|
|
// Split the line into the fields.
|
|
// Specs say that the following fields are to put into the block:
|
|
// 0: ReadOrder, 1: Layer, 2: Style, 3: Name, 4: MarginL, 5: MarginR,
|
|
// 6: MarginV, 7: Effect, 8: Text
|
|
fields = split(s, ",", 9);
|
|
safefree(s);
|
|
if (fields.size() != 9) {
|
|
fprintf(stdout, "Warning: Invalid format for a SSA line ('%s'). "
|
|
"Ignoring this entry.\n", s);
|
|
continue;
|
|
}
|
|
|
|
// Convert the ReadOrder entry so that we can re-order the entries
|
|
// later.
|
|
if (!parse_int(fields[0].c_str(), num)) {
|
|
fprintf(stdout, "Warning: Invalid format for a SSA line ('%s'). "
|
|
"Ignoring this entry.\n", s);
|
|
continue;
|
|
}
|
|
|
|
// Reconstruct the 'original' line. It'll look like this for SSA:
|
|
// Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect,
|
|
// Text
|
|
|
|
line = string("Dialogue: Marked=0,");
|
|
|
|
// Append the start and end time.
|
|
sprintf(buffer, "%lld:%02lld:%02lld.%02lld",
|
|
start / 1000 / 60 / 60, (start / 1000 / 60) % 60,
|
|
(start / 1000) % 60, (start % 1000) / 10);
|
|
line += string(buffer) + comma;
|
|
|
|
sprintf(buffer, "%lld:%02lld:%02lld.%02lld",
|
|
end / 1000 / 60 / 60, (end / 1000 / 60) % 60,
|
|
(end / 1000) % 60, (end % 1000) / 10);
|
|
line += string(buffer) + comma;
|
|
|
|
// Append the other fields.
|
|
line += fields[2] + comma + // Style
|
|
fields[3] + comma + // Name
|
|
fields[4] + comma + // MarginL
|
|
fields[5] + comma + // MarginR
|
|
fields[6] + comma + // MarginV
|
|
fields[7] + comma; // Effect
|
|
|
|
// Do the charset conversion.
|
|
s = from_utf8(tracks[i].conv_handle, fields[8].c_str());
|
|
line += string(s) + "\n";
|
|
safefree(s);
|
|
|
|
// Now store that entry.
|
|
ssa_line.num = num;
|
|
ssa_line.line = safestrdup(line.c_str());
|
|
tracks[i].ssa_lines.push_back(ssa_line);
|
|
|
|
break;
|
|
|
|
case TYPEAAC:
|
|
// Recreate the ADTS headers. What a fun. Like runing headlong into
|
|
// a solid wall. But less painful. Well such is life, you know.
|
|
// But then again I've just seen a beautiful girl walking by my
|
|
// window, and suddenly the world is a bright place. Everything's
|
|
// a matter of perspective. And if I didn't enjoy writing even this
|
|
// code then I wouldn't do it at all. So let's get to it!
|
|
|
|
memset(adts, 0, 56 / 8);
|
|
|
|
// sync word, 12 bits
|
|
adts[0] = 0xff;
|
|
adts[1] = 0xf0;
|
|
|
|
// ID, 1 bit
|
|
adts[1] |= track->aac_id << 3;
|
|
// layer: 2 bits = 00
|
|
|
|
// protection absent: 1 bit = 1 (ASSUMPTION!)
|
|
adts[1] |= 1;
|
|
|
|
// profile, 2 bits
|
|
adts[2] = track->aac_profile << 6;
|
|
|
|
// sampling frequency index, 4 bits
|
|
adts[2] |= track->aac_srate_idx << 2;
|
|
|
|
// private, 1 bit = 0 (ASSUMPTION!)
|
|
|
|
// channels, 3 bits
|
|
adts[2] |= (track->a_channels & 4) >> 2;
|
|
adts[3] |= (track->a_channels & 3) << 6;
|
|
|
|
// original/copy, 1 bit = 0(ASSUMPTION!)
|
|
|
|
// home, 1 bit = 0 (ASSUMPTION!)
|
|
|
|
// copyright id bit, 1 bit = 0 (ASSUMPTION!)
|
|
|
|
// copyright id start, 1 bit = 0 (ASSUMPTION!)
|
|
|
|
// frame length, 13 bits
|
|
len = data.Size() + 7;
|
|
adts[3] |= len >> 11;
|
|
adts[4] = (len >> 3) & 0xff;
|
|
adts[5] = (len & 7) << 5;
|
|
|
|
// adts buffer fullness, 11 bits, 0x7ff = VBR (ASSUMPTION!)
|
|
adts[5] |= 0x1f;
|
|
adts[6] = 0xfc;
|
|
|
|
// number of raw frames, 2 bits, 0 (meaning 1 frame) (ASSUMPTION!)
|
|
|
|
// Write the ADTS header and the data itself.
|
|
track->mm_io->write(adts, 56 / 8);
|
|
track->mm_io->write(data.Buffer(), data.Size());
|
|
|
|
break;
|
|
|
|
default:
|
|
track->mm_io->write(data.Buffer(), data.Size());
|
|
track->bytes_written += data.Size();
|
|
|
|
}
|
|
}
|
|
|
|
delete block;
|
|
}
|
|
|
|
void close_files() {
|
|
int i, k;
|
|
ogg_packet op;
|
|
|
|
for (i = 0; i < tracks.size(); i++) {
|
|
if (tracks[i].in_use) {
|
|
switch (tracks[i].type) {
|
|
case TYPEAVI:
|
|
AVI_close(tracks[i].avi);
|
|
break;
|
|
|
|
case TYPEOGM:
|
|
// Set the "end of stream" marker on the last packet, handle it
|
|
// and flush all remaining Ogg pages.
|
|
op.b_o_s = 0;
|
|
op.e_o_s = 1;
|
|
op.packetno = tracks[i].packetno;
|
|
op.packet = tracks[i].buffered_data;
|
|
op.bytes = tracks[i].buffered_size;
|
|
op.granulepos = tracks[i].last_end * (int64_t)tracks[i].a_sfreq /
|
|
1000;
|
|
ogg_stream_packetin(&tracks[i].osstate, &op);
|
|
safefree(tracks[i].buffered_data);
|
|
|
|
flush_ogg_pages(tracks[i]);
|
|
|
|
delete tracks[i].mm_io;
|
|
|
|
break;
|
|
|
|
case TYPEWAV:
|
|
// Fix the header with the real number of bytes written.
|
|
tracks[i].mm_io->setFilePointer(0);
|
|
tracks[i].wh.riff.len = tracks[i].bytes_written + 36;
|
|
tracks[i].wh.data.len = tracks[i].bytes_written;
|
|
tracks[i].mm_io->write(&tracks[i].wh, sizeof(wave_header));
|
|
delete tracks[i].mm_io;
|
|
|
|
break;
|
|
|
|
case TYPESSA:
|
|
// Sort the SSA lines according to their ReadOrder number and
|
|
// write them.
|
|
sort(tracks[i].ssa_lines.begin(), tracks[i].ssa_lines.end());
|
|
for (k = 0; k < tracks[i].ssa_lines.size(); k++) {
|
|
tracks[i].mm_io->puts_unl(tracks[i].ssa_lines[k].line);
|
|
safefree(tracks[i].ssa_lines[k].line);
|
|
}
|
|
delete tracks[i].mm_io;
|
|
|
|
break;
|
|
|
|
default:
|
|
delete tracks[i].mm_io;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool process_file(const char *file_name) {
|
|
int upper_lvl_el;
|
|
// Elements for different levels
|
|
EbmlElement *l0 = NULL, *l1 = NULL, *l2 = NULL, *l3 = NULL, *l4 = NULL;
|
|
EbmlStream *es;
|
|
KaxCluster *cluster;
|
|
KaxBlock *block;
|
|
mkv_track_t *track;
|
|
uint64_t cluster_tc, tc_scale = TIMECODE_SCALE, file_size;
|
|
char mkv_track_type;
|
|
bool ms_compat, delete_element, has_reference;
|
|
int64_t block_duration;
|
|
mm_io_c *in;
|
|
|
|
// open input file
|
|
try {
|
|
in = new mm_io_c(file_name, MODE_READ);
|
|
} catch (std::exception &ex) {
|
|
show_error("Error: Couldn't open input file %s (%s).", file_name,
|
|
strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
in->setFilePointer(0, seek_end);
|
|
file_size = in->getFilePointer();
|
|
in->setFilePointer(0, seek_beginning);
|
|
|
|
try {
|
|
es = new EbmlStream(*in);
|
|
|
|
// Find the EbmlHead element. Must be the first one.
|
|
l0 = es->FindNextID(EbmlHead::ClassInfos, 0xFFFFFFFFL);
|
|
if (l0 == NULL) {
|
|
show_error("Error: No EBML head found.");
|
|
delete es;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Don't verify its data for now.
|
|
l0->SkipData(*es, l0->Generic().Context);
|
|
delete l0;
|
|
|
|
while (1) {
|
|
// Next element must be a segment
|
|
l0 = es->FindNextID(KaxSegment::ClassInfos, 0xFFFFFFFFL);
|
|
if (l0 == NULL) {
|
|
show_error("No segment/level 0 element found.");
|
|
return false;
|
|
}
|
|
if (EbmlId(*l0) == KaxSegment::ClassInfos.GlobalId) {
|
|
show_element(l0, 0, "Segment");
|
|
break;
|
|
}
|
|
|
|
show_element(l0, 0, "Next level 0 element is not a segment but %s",
|
|
typeid(*l0).name());
|
|
|
|
l0->SkipData(*es, l0->Generic().Context);
|
|
delete l0;
|
|
}
|
|
|
|
upper_lvl_el = 0;
|
|
// We've got our segment, so let's find the tracks
|
|
l1 = es->FindNextElement(l0->Generic().Context, upper_lvl_el, 0xFFFFFFFFL,
|
|
true, 1);
|
|
while (l1 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l1) == KaxInfo::ClassInfos.GlobalId) {
|
|
// General info about this Matroska file
|
|
show_element(l1, 1, "Segment information");
|
|
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l2 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l2) == KaxTimecodeScale::ClassInfos.GlobalId) {
|
|
KaxTimecodeScale &ktc_scale = *static_cast<KaxTimecodeScale *>(l2);
|
|
ktc_scale.ReadData(es->I_O());
|
|
tc_scale = uint64(ktc_scale);
|
|
show_element(l2, 2, "Timecode scale: %llu", tc_scale);
|
|
}
|
|
|
|
l2->SkipData(*es, l2->Generic().Context);
|
|
delete l2;
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
}
|
|
|
|
} else if (EbmlId(*l1) == KaxTracks::ClassInfos.GlobalId) {
|
|
// Yep, we've found our KaxTracks element. Now find all tracks
|
|
// contained in this segment.
|
|
show_element(l1, 1, "Segment tracks");
|
|
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l2 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l2) == KaxTrackEntry::ClassInfos.GlobalId) {
|
|
// We actually found a track entry :) We're happy now.
|
|
show_element(l2, 2, "A track");
|
|
|
|
track = NULL;
|
|
mkv_track_type = '?';
|
|
ms_compat = false;
|
|
|
|
l3 = es->FindNextElement(l2->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l3 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
// Now evaluate the data belonging to this track
|
|
if (EbmlId(*l3) == KaxTrackNumber::ClassInfos.GlobalId) {
|
|
char *msg;
|
|
|
|
KaxTrackNumber &tnum = *static_cast<KaxTrackNumber *>(l3);
|
|
tnum.ReadData(es->I_O());
|
|
if ((track = find_track(uint32(tnum))) == NULL)
|
|
msg = "extraction not requested";
|
|
else {
|
|
msg = "will extract this track";
|
|
track->in_use = true;
|
|
}
|
|
show_element(l3, 3, "Track number: %u (%s)", uint32(tnum),
|
|
msg);
|
|
|
|
} else if (EbmlId(*l3) == KaxTrackType::ClassInfos.GlobalId) {
|
|
KaxTrackType &ttype = *static_cast<KaxTrackType *>(l3);
|
|
ttype.ReadData(es->I_O());
|
|
|
|
switch (uint8(ttype)) {
|
|
case track_audio:
|
|
mkv_track_type = 'a';
|
|
break;
|
|
case track_video:
|
|
mkv_track_type = 'v';
|
|
break;
|
|
case track_subtitle:
|
|
mkv_track_type = 's';
|
|
break;
|
|
default:
|
|
mkv_track_type = '?';
|
|
break;
|
|
}
|
|
show_element(l3, 3, "Track type: %s",
|
|
mkv_track_type == 'a' ? "audio" :
|
|
mkv_track_type == 'v' ? "video" :
|
|
mkv_track_type == 's' ? "subtitles" :
|
|
"unknown");
|
|
if (track != NULL)
|
|
track->track_type = mkv_track_type;
|
|
|
|
} else if (EbmlId(*l3) == KaxTrackAudio::ClassInfos.GlobalId) {
|
|
show_element(l3, 3, "Audio track");
|
|
|
|
l4 = es->FindNextElement(l3->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l4 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l4) ==
|
|
KaxAudioSamplingFreq::ClassInfos.GlobalId) {
|
|
KaxAudioSamplingFreq &freq =
|
|
*static_cast<KaxAudioSamplingFreq*>(l4);
|
|
freq.ReadData(es->I_O());
|
|
show_element(l4, 4, "Sampling frequency: %f",
|
|
float(freq));
|
|
if (track != NULL)
|
|
track->a_sfreq = float(freq);
|
|
|
|
} else if (EbmlId(*l4) ==
|
|
KaxAudioChannels::ClassInfos.GlobalId) {
|
|
KaxAudioChannels &channels =
|
|
*static_cast<KaxAudioChannels*>(l4);
|
|
channels.ReadData(es->I_O());
|
|
show_element(l4, 4, "Channels: %u", uint8(channels));
|
|
if (track != NULL)
|
|
track->a_channels = uint8(channels);
|
|
|
|
} else if (EbmlId(*l4) ==
|
|
KaxAudioBitDepth::ClassInfos.GlobalId) {
|
|
KaxAudioBitDepth &bps =
|
|
*static_cast<KaxAudioBitDepth*>(l4);
|
|
bps.ReadData(es->I_O());
|
|
show_element(l4, 4, "Bit depth: %u", uint8(bps));
|
|
if (track != NULL)
|
|
track->a_bps = uint8(bps);
|
|
}
|
|
|
|
l4->SkipData(*es,
|
|
l4->Generic().Context);
|
|
delete l4;
|
|
l4 = es->FindNextElement(l3->Generic().Context,
|
|
upper_lvl_el, 0xFFFFFFFFL, true, 1);
|
|
} // while (l4 != NULL)
|
|
|
|
} else if (EbmlId(*l3) == KaxTrackVideo::ClassInfos.GlobalId) {
|
|
show_element(l3, 3, "Video track");
|
|
|
|
l4 = es->FindNextElement(l3->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l4 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l4) == KaxVideoPixelWidth::ClassInfos.GlobalId) {
|
|
KaxVideoPixelWidth &width =
|
|
*static_cast<KaxVideoPixelWidth *>(l4);
|
|
width.ReadData(es->I_O());
|
|
show_element(l4, 4, "Pixel width: %u", uint16(width));
|
|
if (track != NULL)
|
|
track->v_width = uint16(width);
|
|
|
|
} else if (EbmlId(*l4) ==
|
|
KaxVideoPixelHeight::ClassInfos.GlobalId) {
|
|
KaxVideoPixelHeight &height =
|
|
*static_cast<KaxVideoPixelHeight *>(l4);
|
|
height.ReadData(es->I_O());
|
|
show_element(l4, 4, "Pixel height: %u", uint16(height));
|
|
if (track != NULL)
|
|
track->v_height = uint16(height);
|
|
|
|
} else if (EbmlId(*l4) ==
|
|
KaxVideoFrameRate::ClassInfos.GlobalId) {
|
|
KaxVideoFrameRate &framerate =
|
|
*static_cast<KaxVideoFrameRate *>(l4);
|
|
framerate.ReadData(es->I_O());
|
|
show_element(l4, 4, "Frame rate: %f", float(framerate));
|
|
if (track != NULL)
|
|
track->v_fps = float(framerate);
|
|
|
|
}
|
|
|
|
l4->SkipData(*es, l4->Generic().Context);
|
|
delete l4;
|
|
l4 = es->FindNextElement(l3->Generic().Context,
|
|
upper_lvl_el, 0xFFFFFFFFL, true, 1);
|
|
} // while (l4 != NULL)
|
|
|
|
} else if (EbmlId(*l3) == KaxCodecID::ClassInfos.GlobalId) {
|
|
KaxCodecID &codec_id = *static_cast<KaxCodecID*>(l3);
|
|
codec_id.ReadData(es->I_O());
|
|
show_element(l3, 3, "Codec ID: %s", string(codec_id).c_str());
|
|
if ((!strcmp(string(codec_id).c_str(), MKV_V_MSCOMP) &&
|
|
(mkv_track_type == 'v')) ||
|
|
(!strcmp(string(codec_id).c_str(), MKV_A_ACM) &&
|
|
(mkv_track_type == 'a')))
|
|
ms_compat = true;
|
|
if (track != NULL)
|
|
track->codec_id = safestrdup(string(codec_id).c_str());
|
|
|
|
} else if (EbmlId(*l3) == KaxCodecPrivate::ClassInfos.GlobalId) {
|
|
char pbuffer[100];
|
|
KaxCodecPrivate &c_priv = *static_cast<KaxCodecPrivate*>(l3);
|
|
c_priv.ReadData(es->I_O());
|
|
if (ms_compat && (mkv_track_type == 'v') &&
|
|
(c_priv.GetSize() >= sizeof(alBITMAPINFOHEADER))) {
|
|
alBITMAPINFOHEADER *bih =
|
|
(alBITMAPINFOHEADER *)&binary(c_priv);
|
|
unsigned char *fcc = (unsigned char *)&bih->bi_compression;
|
|
sprintf(pbuffer, " (FourCC: %c%c%c%c, 0x%08x)",
|
|
fcc[0], fcc[1], fcc[2], fcc[3],
|
|
get_uint32(&bih->bi_compression));
|
|
} else if (ms_compat && (mkv_track_type == 'a') &&
|
|
(c_priv.GetSize() >= sizeof(alWAVEFORMATEX))) {
|
|
alWAVEFORMATEX *wfe = (alWAVEFORMATEX *)&binary(c_priv);
|
|
sprintf(pbuffer, " (format tag: 0x%04x)",
|
|
get_uint16(&wfe->w_format_tag));
|
|
} else
|
|
pbuffer[0] = 0;
|
|
show_element(l3, 3, "CodecPrivate, length %llu%s",
|
|
c_priv.GetSize(), pbuffer);
|
|
if (track != NULL) {
|
|
track->private_data = safememdup(&binary(c_priv),
|
|
c_priv.GetSize());
|
|
track->private_size = c_priv.GetSize();
|
|
}
|
|
|
|
} else if (EbmlId(*l3) ==
|
|
KaxTrackDefaultDuration::ClassInfos.GlobalId) {
|
|
KaxTrackDefaultDuration &def_duration =
|
|
*static_cast<KaxTrackDefaultDuration*>(l3);
|
|
def_duration.ReadData(es->I_O());
|
|
show_element(l3, 3, "Default duration: %.3fms (%.3f fps for "
|
|
"a video track)",
|
|
(float)uint64(def_duration) / 1000000.0,
|
|
1000000000.0 / (float)uint64(def_duration));
|
|
if (track != NULL)
|
|
track->v_fps = 1000000000.0 / (float)uint64(def_duration);
|
|
|
|
}
|
|
|
|
if (upper_lvl_el > 0) { // we're coming from l4
|
|
upper_lvl_el--;
|
|
delete l3;
|
|
l3 = l4;
|
|
if (upper_lvl_el > 0)
|
|
break;
|
|
} else {
|
|
l3->SkipData(*es,
|
|
l3->Generic().Context);
|
|
delete l3;
|
|
l3 = es->FindNextElement(l2->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
}
|
|
} // while (l3 != NULL)
|
|
|
|
}
|
|
|
|
if (upper_lvl_el > 0) { // we're coming from l3
|
|
upper_lvl_el--;
|
|
delete l2;
|
|
l2 = l3;
|
|
if (upper_lvl_el > 0)
|
|
break;
|
|
} else {
|
|
l2->SkipData(*es,
|
|
l2->Generic().Context);
|
|
delete l2;
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
}
|
|
} // while (l2 != NULL)
|
|
|
|
// Headers have been parsed completely. Now create the output files
|
|
// and stuff.
|
|
create_output_files();
|
|
|
|
} else if (EbmlId(*l1) == KaxCluster::ClassInfos.GlobalId) {
|
|
show_element(l1, 1, "Cluster");
|
|
cluster = (KaxCluster *)l1;
|
|
|
|
if (verbose == 0) {
|
|
fprintf(stdout, "Progress: %d%%\r", (int)(in->getFilePointer() *
|
|
100 / file_size));
|
|
fflush(stdout);
|
|
}
|
|
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
while (l2 != NULL) {
|
|
if (upper_lvl_el != 0)
|
|
break;
|
|
|
|
if (EbmlId(*l2) == KaxClusterTimecode::ClassInfos.GlobalId) {
|
|
KaxClusterTimecode &ctc = *static_cast<KaxClusterTimecode *>(l2);
|
|
ctc.ReadData(es->I_O());
|
|
cluster_tc = uint64(ctc);
|
|
show_element(l2, 2, "Cluster timecode: %.3fs",
|
|
(float)cluster_tc * (float)tc_scale / 1000000000.0);
|
|
cluster->InitTimecode(cluster_tc, tc_scale);
|
|
|
|
} else if (EbmlId(*l2) == KaxBlockGroup::ClassInfos.GlobalId) {
|
|
show_element(l2, 2, "Block group");
|
|
|
|
block_duration = 0;
|
|
has_reference = false;
|
|
l3 = es->FindNextElement(l2->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, false, 1);
|
|
while (l3 != NULL) {
|
|
if (upper_lvl_el > 0)
|
|
break;
|
|
|
|
delete_element = true;
|
|
|
|
if (EbmlId(*l3) == KaxBlock::ClassInfos.GlobalId) {
|
|
block = (KaxBlock *)l3;
|
|
block->ReadData(es->I_O());
|
|
block->SetParent(*cluster);
|
|
show_element(l3, 3, "Block (track number %u, %d frame(s), "
|
|
"timecode %.3fs)", block->TrackNum(),
|
|
block->NumberFrames(),
|
|
(float)block->GlobalTimecode() / 1000000000.0);
|
|
delete_element = false;
|
|
|
|
} else if (EbmlId(*l3) ==
|
|
KaxBlockDuration::ClassInfos.GlobalId) {
|
|
KaxBlockDuration &duration =
|
|
*static_cast<KaxBlockDuration *>(l3);
|
|
duration.ReadData(es->I_O());
|
|
show_element(l3, 3, "Block duration: %.3fms",
|
|
((float)uint64(duration)) * tc_scale / 1000000.0);
|
|
block_duration = uint64(duration) * tc_scale / 1000000;
|
|
|
|
} else if (EbmlId(*l3) ==
|
|
KaxReferenceBlock::ClassInfos.GlobalId) {
|
|
KaxReferenceBlock &reference =
|
|
*static_cast<KaxReferenceBlock *>(l3);
|
|
reference.ReadData(es->I_O());
|
|
show_element(l3, 3, "Reference block: %.3fms",
|
|
((float)int64(reference)) * tc_scale / 1000000.0);
|
|
|
|
has_reference = true;
|
|
}
|
|
|
|
l3->SkipData(*es, l3->Generic().Context);
|
|
if (delete_element)
|
|
delete l3;
|
|
l3 = es->FindNextElement(l2->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
} // while (l3 != NULL)
|
|
|
|
// Now write the stuff to the file. Or not. Or get something to
|
|
// drink. Or read a good book. Dunno, your choice, really.
|
|
handle_data(block, block_duration, has_reference);
|
|
|
|
}
|
|
|
|
if (upper_lvl_el > 0) { // we're coming from l3
|
|
upper_lvl_el--;
|
|
delete l2;
|
|
l2 = l3;
|
|
if (upper_lvl_el > 0)
|
|
break;
|
|
|
|
} else {
|
|
l2->SkipData(*es,
|
|
l2->Generic().Context);
|
|
delete l2;
|
|
l2 = es->FindNextElement(l1->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
}
|
|
} // while (l2 != NULL)
|
|
}
|
|
|
|
if (upper_lvl_el > 0) { // we're coming from l2
|
|
upper_lvl_el--;
|
|
delete l1;
|
|
l1 = l2;
|
|
if (upper_lvl_el > 0)
|
|
break;
|
|
} else {
|
|
l1->SkipData(*es, l1->Generic().Context);
|
|
delete l1;
|
|
l1 = es->FindNextElement(l0->Generic().Context, upper_lvl_el,
|
|
0xFFFFFFFFL, true, 1);
|
|
}
|
|
} // while (l1 != NULL)
|
|
|
|
delete l0;
|
|
delete es;
|
|
delete in;
|
|
|
|
// Now just close the files and go to sleep. Mummy will sing you a
|
|
// lullaby. Just close your eyes, listen to her sweet voice, singing,
|
|
// singing, fading... fad... ing...
|
|
close_files();
|
|
|
|
return true;
|
|
} catch (exception &ex) {
|
|
show_error("Caught exception: %s", ex.what());
|
|
delete in;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
char *input_file;
|
|
|
|
#if defined(SYS_UNIX)
|
|
nice(2);
|
|
#endif
|
|
|
|
utf8_init(NULL);
|
|
|
|
parse_args(argc, argv, input_file);
|
|
process_file(input_file);
|
|
|
|
if (verbose == 0)
|
|
fprintf(stdout, "Progress: 100%%\n");
|
|
|
|
utf8_done();
|
|
|
|
return 0;
|
|
}
|