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.

685 lines
22 KiB

/*
ogminfo -- utility for extracting raw streams from an OGG media file
Written by Moritz Bunkus <moritz@bunkus.org>
Integrated into transcode by Tilmann Bitterberg <transcode@tibit.org>
Distributed under the GPL
see the file COPYING for details
or visit http://www.gnu.org/copyleft/gpl.html
*/
#include "transcode.h"
#include "libtc/libtc.h"
#include "tcinfo.h"
#include <sys/types.h>
#include "ioaux.h"
#include "tc.h"
#define NOAUDIO 0
#define NOVIDEO 1
#define NOTEXT 2
unsigned char *xaudio = NULL;
unsigned char *xvideo = NULL;
unsigned char *xtext = NULL;
int no[3];
int xraw = 0;
static int verbose_flag = TC_QUIET;
#if (HAVE_OGG && HAVE_VORBIS)
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include "ogmstreams.h"
#define BLOCK_SIZE 4096
#define ACVORBIS 0xffff
#define ACPCM 0x0001
#define ACMP3 0x0055
#define ACAC3 0x2000
typedef struct stream_t {
int serial;
int fd;
double sample_rate;
int eos, comment;
int sno;
char stype;
ogg_stream_state instate;
struct stream_t *next;
int acodec;
ogg_int64_t bwritten;
ogg_stream_state outstate;
int packetno;
ogg_int64_t max_granulepos;
int subnr;
} stream_t;
stream_t *first;
int nastreams = 0, nvstreams = 0, ntstreams = 0, numstreams = 0;
char basename[] = "stdout";
static void add_stream(stream_t *ndmx) {
stream_t *cur = first;
if (first == NULL) {
first = ndmx;
first->next = NULL;
} else {
cur = first;
while (cur->next != NULL)
cur = cur->next;
cur->next = ndmx;
ndmx->next = NULL;
}
}
static stream_t *find_stream(int fserial) {
stream_t *cur = first;
while (cur != NULL) {
if (cur->serial == fserial)
break;
cur = cur->next;
}
return cur;
}
double highest_ts = 0;
static int extraction_requested(unsigned char *s, int stream, int type) {
int i;
if (no[type])
return 0;
if (strlen((char *)s) == 0)
return 1;
for (i = 0; i < strlen((char *)s); i++)
if (s[i] == stream)
return 1;
return 0;
}
static void flush_pages(stream_t *stream, ogg_packet *op) {
ogg_page page;
while (ogg_stream_flush(&stream->outstate, &page)) {
int ih, ib;
ih = tc_pwrite(stream->fd, page.header, page.header_len);
if (ih != page.header_len) {
tc_log_error(__FILE__, "error while writing page header");
import_exit(1); /* XXX ugly */
}
ib = tc_pwrite(stream->fd, page.body, page.body_len);
if (ib != page.body_len) {
tc_log_error(__FILE__, "error while writing page bofy");
import_exit(1); /* XXX ugly */
}
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/a%d: %d + %d written", stream->sno, ih, ib);
}
}
static void write_pages(stream_t *stream, ogg_packet *op) {
ogg_page page;
while (ogg_stream_pageout(&stream->outstate, &page)) {
int ih, ib;
ih = tc_pwrite(stream->fd, page.header, page.header_len);
if (ih != page.header_len) {
tc_log_error(__FILE__, "error while writing page header");
import_exit(1); /* XXX ugly */
}
ib = tc_pwrite(stream->fd, page.body, page.body_len);
if (ib != page.body_len) {
tc_log_error(__FILE__, "error while writing page bofy");
import_exit(1); /* XXX ugly */
}
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/a%d: %d + %d written", stream->sno, ih, ib);
}
}
static void handle_packet(stream_t *stream, ogg_packet *pack, ogg_page *page) {
int i, w, hdrlen, end;
long long lenbytes;
char *sub;
char out[100];
ogg_int64_t pgp, sst;
//tc_log_msg(__FILE__, "Going handle 1");
if (pack->e_o_s) {
stream->eos = 1;
pack->e_o_s = 1;
}
if (((double)pack->granulepos * 1000.0 / (double)stream->sample_rate) >
highest_ts)
highest_ts = (double)pack->granulepos * 1000.0 /
(double)stream->sample_rate;
switch (stream->stype) {
case 'v':
if (!extraction_requested(xvideo, stream->sno, NOVIDEO))
return;
break;
case 'a':
if (!extraction_requested(xaudio, stream->sno, NOAUDIO))
return;
break;
case 't':
if (!extraction_requested(xtext, stream->sno, NOTEXT))
return;
break;
}
hdrlen = (*pack->packet & OGM_PACKET_LEN_BITS01) >> 6;
hdrlen |= (*pack->packet & OGM_PACKET_LEN_BITS2) << 1;
for (i = 0, lenbytes = 0; i < hdrlen; i++) {
lenbytes = lenbytes << 8;
lenbytes += *((unsigned char *)pack->packet + hdrlen - i);
}
switch (stream->stype) {
case 'v':
if (((*pack->packet & 3) == OGM_PACKET_TYPE_HEADER) ||
((*pack->packet & 3) == OGM_PACKET_TYPE_COMMENT))
return;
i = tc_pwrite(stream->fd, (char *)&pack->packet[hdrlen + 1],
pack->bytes - 1 - hdrlen);
if (i != pack->bytes - 1 - hdrlen) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/v%d: %d written", stream->sno, i);
break;
case 't':
if (((*pack->packet & 3) == OGM_PACKET_TYPE_HEADER) ||
((*pack->packet & 3) == OGM_PACKET_TYPE_COMMENT))
return;
if (xraw) {
i = tc_pwrite(stream->fd, (char *)&pack->packet[hdrlen + 1],
pack->bytes - 1 - hdrlen);
if (i != pack->bytes - 1 - hdrlen) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/t%d: %d written", stream->sno, i);
return;
}
sub = (char *)&pack->packet[hdrlen + 1];
if ((strlen(sub) > 1) || (*sub != ' ')) {
sst = (pack->granulepos / stream->sample_rate) * 1000;
pgp = sst + lenbytes;
tc_snprintf(out,sizeof(out), "%d\r\n%02d:%02d:%02d,%03d --> " \
"%02d:%02d:%02d,%03d\r\n", stream->subnr + 1,
(int)(sst / 3600000),
(int)(sst / 60000) % 60,
(int)(sst / 1000) % 60,
(int)(sst % 1000),
(int)(pgp / 3600000),
(int)(pgp / 60000) % 60,
(int)(pgp / 1000) % 60,
(int)(pgp % 1000));
i = tc_pwrite(stream->fd, out, strlen(out));
if (i != strlen(out)) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
end = strlen(sub) - 1;
while ((end >= 0) && ((sub[end] == '\n') || (sub[end] == '\r'))) {
sub[end] = 0;
end--;
}
w = tc_pwrite(stream->fd, sub, strlen(sub));
if (i == strlen(sub)) {
i += w;
} else {
tc_log_error(__FILE__, "error while writing data");
import_exit(1);
}
w = tc_pwrite(stream->fd, "\r\n\r\n", 4);
if (w == 4) {
i += w;
} else {
tc_log_error(__FILE__, "error while writing data");
import_exit(1);
}
stream->subnr++;
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/t%d: %d written", stream->sno, i);
}
break;
case 'a':
switch (stream->acodec) {
case ACVORBIS:
if (xraw) {
if (stream->packetno == 0) {
i = tc_pwrite(stream->fd, (char *)pack->packet, pack->bytes);
if (i != pack->bytes) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
} else {
i = tc_pwrite(stream->fd, (char *)&pack->packet[1],
pack->bytes - 1);
if (i != pack->bytes - 1) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
}
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/a%d: %d written", stream->sno, i);
return;
}
stream->max_granulepos = (pack->granulepos > stream->max_granulepos ?
pack->granulepos : stream->max_granulepos);
if ((stream->packetno == 0) || (stream->packetno == 2)) {
ogg_stream_packetin(&stream->outstate, pack);
flush_pages(stream, pack);
} else {
ogg_stream_packetin(&stream->outstate, pack);
write_pages(stream, pack);
}
stream->packetno++;
break;
default:
if (((*pack->packet & 3) == OGM_PACKET_TYPE_HEADER) ||
((*pack->packet & 3) == OGM_PACKET_TYPE_COMMENT))
return;
i = tc_pwrite(stream->fd, pack->packet + 1 + hdrlen,
pack->bytes - 1 - hdrlen);
if (i != pack->bytes - 1 - hdrlen) {
tc_log_error(__FILE__, "error while writing data");
import_exit(1); /* XXX ugly */
}
stream->bwritten += i;
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "x/a%d: %d written", stream->sno, i);
break;
}
break;
}
}
static void process_ogm(int fdin, int fdout)
{
ogg_sync_state sync;
ogg_page page;
ogg_packet pack;
vorbis_info vi;
vorbis_comment vc;
char *buf, *new_name;
int nread, np, sno;
int endofstream = 0;
stream_t *stream = NULL;
ogg_sync_init(&sync);
while (1) {
np = ogg_sync_pageseek(&sync, &page);
if (np < 0) {
tc_log_error(__FILE__, "ogg_sync_pageseek failed");
return;
}
if (np == 0) {
buf = ogg_sync_buffer(&sync, BLOCK_SIZE);
if (!buf) {
tc_log_error(__FILE__, "ogg_sync_buffer failed");
return;
}
if ((nread = read(fdin, buf, BLOCK_SIZE)) <= 0) {
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "end of stream 1");
return;
}
ogg_sync_wrote(&sync, nread);
continue;
}
if (!ogg_page_bos(&page)) {
break;
} else {
ogg_stream_state sstate;
sno = ogg_page_serialno(&page);
if (ogg_stream_init(&sstate, sno)) {
tc_log_error(__FILE__, "ogg_stream_init failed");
return;
}
ogg_stream_pagein(&sstate, &page);
ogg_stream_packetout(&sstate, &pack);
if ((pack.bytes >= 7) && ! strncmp(&pack.packet[1], "vorbis", 6)) {
stream = tc_malloc(sizeof(stream_t));
if (stream == NULL) {
tc_log_error(__FILE__, "malloc failed.");
exit(1);
}
memset(stream, 0, sizeof(stream_t));
if (verbose_flag & TC_INFO) {
vorbis_info_init(&vi);
vorbis_comment_init(&vc);
if (vorbis_synthesis_headerin(&vi, &vc, &pack) >= 0) {
tc_log_info(__FILE__, "(a%d/%d) Vorbis audio (channels %d "
"rate %ld)", nastreams + 1, numstreams + 1,
vi.channels, vi.rate);
stream->sample_rate = vi.rate;
} else
tc_log_warn(__FILE__, "(a%d/%d) Vorbis audio stream indicated "
"but no Vorbis stream header found.",
nastreams + 1, numstreams + 1);
}
stream->serial = sno;
stream->acodec = ACVORBIS;
stream->sample_rate = -1;
stream->sno = nastreams + 1;
stream->stype = 'a';
ac_memcpy(&stream->instate, &sstate, sizeof(sstate));
if (extraction_requested(xaudio, nastreams + 1, NOAUDIO)) {
stream->fd = fdout;
if (stream->fd == -1) {
tc_log_error(__FILE__, "Failed to create \"%s\" (%d, %s).",
"new_name", errno, strerror(errno));
exit(1);
}
if (!xraw)
ogg_stream_init(&stream->outstate, rand());
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "Extracting a%d to \"%s\".",
nastreams + 1, "new_name");
do
handle_packet(stream, &pack, &page);
while (ogg_stream_packetout(&stream->instate, &pack) == 1);
}
add_stream(stream);
nastreams++;
numstreams++;
} else if ((pack.bytes >= 142) &&
!strncmp(&pack.packet[1],"Direct Show Samples embedded in Ogg",
35) ) {
if ((*(int32_t*)(pack.packet+96) == 0x05589f80) &&
(pack.bytes >= 184)) {
tc_log_warn(__FILE__, "(v%d/%d) Found old video header. Not "
"supported.", nvstreams + 1, numstreams + 1);
} else if (*(int32_t*)pack.packet+96 == 0x05589F81) {
tc_log_warn(__FILE__, "(a%d/%d) Found old audio header. Not "
"supported.", nastreams + 1, numstreams + 1);
} else {
if (verbose_flag & TC_INFO)
tc_log_warn(__FILE__, "OGG stream %d has an old header with an "
"unknown type.", numstreams + 1);
}
} else if (((*pack.packet & OGM_PACKET_TYPE_BITS ) == OGM_PACKET_TYPE_HEADER) &&
(pack.bytes >= (int)(sizeof(ogm_stream_header) + 1 - sizeof(int)))) {
ogm_stream_header *sth = (ogm_stream_header *)(pack.packet + 1);
if (!strncmp(sth->streamtype, "video", 5)) {
unsigned long codec;
char ccodec[5];
strncpy(ccodec, sth->subtype, 4);
ccodec[4] = 0;
codec = (sth->subtype[0] << 24) +
(sth->subtype[1] << 16) + (sth->subtype[2] << 8) + sth->subtype[3];
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "(v%d/%d) fps: %.3f width height: %dx%d "
"codec: %p (%s)", nvstreams + 1, numstreams + 1,
(double)10000000 / (double)sth->time_unit,
sth->sh.video.width, sth->sh.video.height,
(void *)codec, ccodec);
stream = tc_malloc(sizeof(stream_t));
if (stream == NULL) {
tc_log_error(__FILE__, "malloc failed.");
exit(1);
}
stream->stype = 'v';
stream->serial = sno;
stream->sample_rate = (double)10000000 / (double)sth->time_unit;
stream->sno = nvstreams + 1;
ac_memcpy(&stream->instate, &sstate, sizeof(sstate));
if (extraction_requested(xvideo, nvstreams + 1, NOVIDEO)) {
stream->fd = fdout;
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "Extracting v%d to \"%s\".",
nvstreams + 1, "new_name");
do {
handle_packet(stream, &pack, &page);
} while (ogg_stream_packetout(&stream->instate, &pack) == 1);
}
add_stream(stream);
nvstreams++;
numstreams++;
} else if (!strncmp(sth->streamtype, "audio", 5)) {
int codec;
char buf[5];
ac_memcpy(buf, sth->subtype, 4);
buf[4] = 0;
codec = strtoul(buf, NULL, 16);
if (verbose_flag & TC_INFO) {
tc_log_info(__FILE__, "(a%d/%d) codec: %d (0x%04x) (%s), bits per "
"sample: %d channels: %hd samples per second: %lld "
" avgbytespersec: %hd blockalign: %d",
nastreams + 1, numstreams + 1, codec, codec,
codec == ACPCM ? "PCM" : codec == 55 ? "MP3" :
codec == ACMP3 ? "MP3" :
codec == ACAC3 ? "AC3" : "unknown",
sth->bits_per_sample, sth->sh.audio.channels,
(long long)sth->samples_per_unit,
sth->sh.audio.avgbytespersec, sth->sh.audio.blockalign);
}
stream = tc_malloc(sizeof(stream_t));
if (stream == NULL) {
tc_log_error(__FILE__, "malloc failed.");
exit(1);
}
stream->sno = nastreams + 1;
stream->stype = 'a';
stream->sample_rate = sth->samples_per_unit *
sth->sh.audio.channels;
stream->serial = sno;
stream->acodec = codec;
ac_memcpy(&stream->instate, &sstate, sizeof(sstate));
if (extraction_requested(xaudio, nastreams + 1, NOAUDIO)) {
/*
codec == ACPCM ? "wav" :
codec == ACMP3 ? "mp3" :
codec == ACAC3 ? "ac3" : "audio");
*/
stream->fd = fdout;
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "Extracting a%d to \"%s\".",
nastreams + 1, "new_name");
do {
handle_packet(stream, &pack, &page);
} while (ogg_stream_packetout(&stream->instate, &pack) == 1);
}
add_stream(stream);
nastreams++;
numstreams++;
} else if (!strncmp(sth->streamtype, "text", 4)) {
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "(t%d/%d) text/subtitle stream",
ntstreams + 1, numstreams + 1);
stream = tc_malloc(sizeof(stream_t));
if (stream == NULL) {
tc_log_error(__FILE__, "malloc failed.");
exit(1);
}
stream->sno = ntstreams + 1;
stream->stype = 't';
stream->sample_rate = (double)10000000 / (double)sth->time_unit;
stream->serial = sno;
ac_memcpy(&stream->instate, &sstate, sizeof(sstate));
if (extraction_requested(xtext, ntstreams + 1, NOTEXT)) {
new_name = tc_malloc(strlen(basename) + 20);
if (!new_name) {
tc_log_error(__FILE__, "Failed to allocate %d bytes.",
(int)strlen(basename) + 20);
exit(1);
}
if (!xraw)
tc_snprintf(new_name, strlen(basename) + 20, "%s-t%d.srt", basename, ntstreams + 1);
else
tc_snprintf(new_name, strlen(basename) + 20, "%s-t%d.raw", basename, ntstreams + 1);
//stream->fd = open(new_name, O_WRONLY | O_CREAT | O_TRUNC,
// S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
stream->fd = fdout;
if (stream->fd == -1) {
tc_log_error(__FILE__, "Failed to create \"%s\" (%d, %s).",
new_name, errno, strerror(errno));
exit(1);
}
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "Extracting t%d to \"%s\".",
ntstreams + 1, new_name);
do
handle_packet(stream, &pack, &page);
while (ogg_stream_packetout(&stream->instate, &pack) == 1);
}
add_stream(stream);
ntstreams++;
numstreams++;
} else {
tc_log_warn(__FILE__, "(%d) found new header of unknown/"
"unsupported type\n", numstreams + 1);
}
} else {
tc_log_warn(__FILE__, "OGG stream %d is of an unknown type "
"(bad header?)\n", numstreams + 1);
}
}
}
endofstream = 0;
while (!endofstream) {
sno = ogg_page_serialno(&page);
stream = find_stream(sno);
if (stream == NULL) {
if (verbose_flag & TC_DEBUG)
tc_log_warn(__FILE__, "Encountered packet for an unknown serial "
"%d !?\n", sno);
} else {
if (verbose_flag & TC_DEBUG)
tc_log_msg(__FILE__, "%c%d: NEW PAGE", stream->stype, stream->sno);
ogg_stream_pagein(&stream->instate, &page);
while (ogg_stream_packetout(&stream->instate, &pack) == 1)
handle_packet(stream, &pack, &page);
}
while (ogg_sync_pageseek(&sync, &page) <= 0) {
buf = ogg_sync_buffer(&sync, BLOCK_SIZE);
nread = read(fdin, buf, BLOCK_SIZE);
if (nread <= 0) {
stream = first;
while (stream != NULL) {
switch (stream->stype) {
case 'v':
close(stream->fd);
break;
case 't':
if (stream->fd > 0)
close(stream->fd);
break;
case 'a':
if ((stream->fd > 0) && !xraw) {
switch (stream->acodec) {
case ACVORBIS:
if (!stream->eos) {
pack.b_o_s = 0;
pack.e_o_s = 1;
pack.packet = NULL;
pack.bytes = 0;
pack.granulepos = stream->max_granulepos;
pack.packetno = stream->packetno;
ogg_stream_packetin(&stream->outstate, &pack);
}
flush_pages(stream, &pack);
ogg_stream_clear(&stream->outstate);
break;
}
close(stream->fd);
} else if (stream->fd > 0)
close(stream->fd);
break;
}
stream = stream->next;
}
if (verbose_flag & TC_INFO)
tc_log_info(__FILE__, "end of stream");
endofstream = 1;
break;
} else
ogg_sync_wrote(&sync, nread);
}
}
}
#endif /* have OGG */
void extract_ogm (info_t *ipipe)
{
// track
no[NOTEXT] = 1;
no[NOAUDIO] = 0;
no[NOVIDEO] = 0;
xraw = 1;
xvideo = tc_zalloc (16); xaudio = tc_zalloc (16); xtext = tc_zalloc (16);
verbose_flag = ipipe->verbose;
if (ipipe->select == TC_VIDEO) {
no[NOAUDIO] = 1;
xvideo[0] = (unsigned char)(ipipe->track+1);
}
if (ipipe->select == TC_AUDIO) {
no[NOVIDEO] = 1;
xaudio[0] = (unsigned char)(ipipe->track+1);
// we need !xraw because no tool seems to be able to handle
// raw vorbis streams -- tibit
if (ipipe->codec == TC_CODEC_VORBIS) {
xraw = 0;
}
}
#if (HAVE_OGG && HAVE_VORBIS)
process_ogm(ipipe->fd_in, ipipe->fd_out);
#else
tc_log_error(__FILE__, "No support for Ogg/Vorbis compiled in");
import_exit(1);
#endif
}
/* vim: sw=2
*/