Files
2026-08-24 10:03:24 +08:00

1384 lines
29 KiB
C
Executable File

/*
* libzvbi test
*
* Copyright (C) 2004, 2005, 2007 Michael H. Schimek
*
* This program 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 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
/* $Id: export.c,v 1.24 2008/03/01 07:36:51 mschimek Exp $ */
#undef NDEBUG
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <locale.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_GETOPT_LONG
# include <getopt.h>
#endif
#include "src/version.h"
#if 2 == VBI_VERSION_MINOR
# include "src/cache.h"
# include "src/decoder.h"
# include "src/export.h"
# include "src/page_table.h"
# include "src/vbi.h"
# include "src/vt.h"
# define vbi_decoder_feed(vbi, sliced, n_lines, ts) \
vbi_decode (vbi, sliced, n_lines, ts)
# define vbi_export_info_from_export(ex) \
vbi_export_info_export (ex)
/* Not available. */
# define vbi_export_set_timestamp(ex, ts) ((void) 0)
# define vbi_export_set_link_cb(ex, cb, ud) ((void) 0)
# define vbi_export_set_pdc_cb(ex, cb, ud) ((void) 0)
#elif 3 == VBI_VERSION_MINOR
# include "src/misc.h"
# include "src/zvbi.h"
#else
# error VBI_VERSION_MINOR == ?
#endif
#include "sliced.h"
#undef _
#define _(x) x /* later */
#define PROGRAM_NAME "zvbi-export"
static const char * option_in_file_name;
static enum file_format option_in_file_format;
static unsigned int option_in_ts_pid;
static vbi_bool option_dcc;
static unsigned int option_delay;
static vbi_bool have_option_default_cs;
static vbi_ttx_charset_code option_default_cs;
static vbi_ttx_charset_code option_override_cs;
static vbi_bool option_dump_pg;
static vbi_bool option_fast;
static vbi_bool option_header_only;
static vbi_bool option_hyperlinks;
static vbi_bool option_navigation;
static vbi_bool option_padding;
static vbi_bool option_panels;
static vbi_bool option_pdc_enum;
static vbi_bool option_pdc_links;
static vbi_bool option_row_update;
static vbi_bool option_subtitles;
static vbi_rgba option_default_bg;
static vbi_rgba option_default_fg;
static unsigned int option_target = 3;
static struct stream * rst;
static vbi_decoder * vbi;
static vbi_export * ex;
static vbi_page_table * pt;
static vbi_pgno cc_chan;
static char * out_file_name_prefix;
static char * out_file_name_suffix;
static int cr;
static vbi_bool quit;
static void
close_output_file (FILE * fp)
{
if (fp != stdout) {
if (0 != fclose (fp))
write_error_exit (/* msg: errno */ NULL);
} else {
fflush (fp);
}
}
static char *
output_file_name (vbi_pgno pgno,
vbi_subno subno)
{
char *name;
int r;
if (NULL == out_file_name_prefix) {
error_exit ("This target requires "
"an output file name.\n");
}
r = asprintf (&name, "%s-%03x-%02x.%s",
out_file_name_prefix, pgno, subno,
out_file_name_suffix);
if (r < 0 || NULL == name)
no_mem_exit ();
return name;
}
static FILE *
open_output_file (vbi_pgno pgno,
vbi_subno subno)
{
char *name;
FILE *fp;
if (NULL == out_file_name_prefix)
return stdout;
name = output_file_name (pgno, subno);
fp = fopen (name, "w");
if (NULL == fp) {
error_exit (_("Could not open "
"output file '%s': %s."),
name, strerror (errno));
}
free (name);
return fp;
}
static void
page_dump (vbi_page * pg)
{
unsigned int row;
for (row = 0; row < (unsigned int) pg->rows; ++row) {
const vbi_char *cp;
unsigned int column;
fprintf (stderr, "%2d: >", row);
cp = pg->text + row * pg->columns;
for (column = 0; column < (unsigned int) pg->columns;
++column) {
int c;
c = cp[column].unicode;
if (c < 0x20 || c > 0x7E)
c = '.';
fputc (c, stderr);
}
fputs ("<\n", stderr);
}
}
#if 2 == VBI_VERSION_MINOR
static void
do_export (vbi_pgno pgno,
vbi_subno subno)
{
vbi_page page;
vbi_bool success;
int page_type;
if (option_delay > 1) {
--option_delay;
return;
}
success = vbi_fetch_vt_page (vbi, &page,
pgno, subno,
VBI_WST_LEVEL_3p5,
/* n_rows */ 25,
/* navigation */ TRUE, &page_type);
if (!success) {
/* Shouldn't happen. */
error_exit (_("Unknown error."));
}
if (option_dump_pg) {
page_dump (&page);
}
switch (option_target) {
char *file_name;
void *buffer;
void *buffer2;
FILE *fp;
size_t size;
ssize_t ssize;
case 1:
buffer = malloc (1 << 20);
if (NULL == buffer)
no_mem_exit ();
ssize = vbi_export_mem (ex, buffer, 1 << 20, &page);
success = (ssize >= 0);
if (success) {
ssize_t ssize2;
fp = open_output_file (pgno, subno);
if (1 != fwrite (buffer, ssize, 1, fp))
write_error_exit (/* msg: errno */ NULL);
close_output_file (fp);
/* Test. */
ssize2 = vbi_export_mem (ex, buffer, 0, &page);
assert (ssize == ssize2);
assert (ssize > 0);
ssize2 = vbi_export_mem (ex, buffer, ssize - 1, &page);
assert (ssize == ssize2);
}
free (buffer);
break;
case 2:
buffer = NULL;
buffer2 = vbi_export_alloc (ex, &buffer, &size, &page);
/* Test. */
assert (buffer == buffer2);
success = (NULL != buffer);
if (success) {
fp = open_output_file (pgno, subno);
if (1 != fwrite (buffer, size, 1, fp))
write_error_exit (/* msg: errno */ NULL);
close_output_file (fp);
free (buffer);
}
break;
case 3:
/* This is the default target. The other cases are only
implemented for tests and will be removed when I
wrote proper unit tests. */
fp = open_output_file (pgno, subno);
success = vbi_export_stdio (ex, fp, &page);
close_output_file (fp);
break;
case 5:
file_name = output_file_name (pgno, subno);
success = vbi_export_file (ex, file_name, &page);
free (file_name);
break;
default:
error_exit ("Invalid target %u.", option_target);
break;
}
if (!success) {
error_exit (_("Export of page %x failed: %s"),
pgno,
vbi_export_errstr (ex));
}
vbi_unref_page (&page);
}
static void
event_handler (vbi_event * ev,
void * user_data)
{
vbi_pgno pgno;
vbi_subno subno;
user_data = user_data; /* unused */
if (quit)
return;
switch (ev->type) {
case VBI_EVENT_TTX_PAGE:
pgno = ev->ev.ttx_page.pgno;
subno = ev->ev.ttx_page.subno;
if (option_log_mask & VBI_LOG_INFO) {
fprintf (stderr,
"Teletext page %03x.%02x %c",
pgno, subno, cr);
}
if (0 == vbi_page_table_num_pages (pt)) {
do_export (pgno, subno);
} else if (vbi_page_table_contains_page (pt, pgno)) {
do_export (pgno, subno);
if (!option_subtitles) {
vbi_page_table_remove_page (pt, pgno);
quit = (0 == vbi_page_table_num_pages (pt));
}
}
break;
default:
assert (0);
}
}
static void
finalize (void)
{
/* Nothing to do. */
}
static void
init_vbi_decoder (void)
{
vbi_bool success;
vbi = vbi_decoder_new ();
if (NULL == vbi)
no_mem_exit ();
if (have_option_default_cs) {
vbi_teletext_set_default_region (vbi, option_default_cs);
}
success = vbi_event_handler_add (vbi,
VBI_EVENT_TTX_PAGE,
event_handler,
/* user_data */ NULL);
if (!success)
no_mem_exit ();
}
#elif 3 == VBI_VERSION_MINOR
#define ev_timestamp ev->timestamp
extern void
vbi_preselection_dump (const vbi_preselection *pl,
FILE * fp);
static void
pdc_dump (vbi_page * pg)
{
const vbi_preselection *pl;
unsigned int size;
unsigned int i;
pl = vbi_page_get_preselections (pg, &size);
assert (NULL != pl);
for (i = 0; i < size; ++i) {
fprintf (stderr, "%02u: ", i);
_vbi_preselection_dump (pl + i, stderr);
fputc ('\n', stderr);
}
if (0 == i)
fputs ("No PDC data\n", stderr);
}
static vbi_bool
export_link (vbi_export * e,
void * user_data,
const vbi_link * link)
{
vbi_bool success;
user_data = user_data; /* unused */
if (0)
fprintf (stderr, "link text: \"%s\"\n", link->name);
switch (link->type) {
case VBI_LINK_HTTP:
case VBI_LINK_FTP:
case VBI_LINK_EMAIL:
success = vbi_export_printf (e, "<a href=\"%s\">%s</a>",
link->url, link->name);
break;
case VBI_LINK_PAGE:
case VBI_LINK_SUBPAGE:
success = vbi_export_printf (e, "<a href=\"%s-%3x-%02x"
".%s\">%s</a>",
out_file_name_prefix ?
out_file_name_prefix : "ttx",
link->pgno,
(VBI_ANY_SUBNO == link->subno) ?
0 : link->subno,
out_file_name_suffix ?
out_file_name_suffix : "html",
link->name);
break;
default:
success = vbi_export_puts (e, link->name);
break;
}
return success;
}
static vbi_bool
export_pdc (vbi_export * e,
void * user_data,
const vbi_preselection *pl,
const char * text)
{
unsigned int end;
vbi_bool success;
user_data = user_data; /* unused */
end = pl->at1_hour * 60 + pl->at1_minute + pl->length;
/* XXX pl->title uses locale encoding but the html page may not.
(export charset parameter) */
success = vbi_export_printf (e, "<acronym title=\"%04u-%02u-%02u "
"%02u:%02u-%02u:%02u "
"VPS/PDC: %02u%02u TTX: %x Title: %s"
"\">%s</acronym>",
pl->year, pl->month, pl->day,
pl->at1_hour, pl->at1_minute,
(end / 60 % 24), end % 60,
pl->at2_hour, pl->at2_minute,
pl->_pgno, pl->title, text);
return success;
}
static void
do_export (vbi_pgno pgno,
vbi_subno subno,
double timestamp)
{
vbi_page *pg;
vbi_bool success;
if (option_delay > 1) {
--option_delay;
return;
}
if (pgno >= 0x100) {
if (0 != option_override_cs) {
pg = vbi_decoder_get_page
(vbi, NULL /* current network */,
pgno, subno,
VBI_HEADER_ONLY, option_header_only,
VBI_PADDING, option_padding,
VBI_PANELS, option_panels,
VBI_NAVIGATION, option_navigation,
VBI_HYPERLINKS, option_hyperlinks,
VBI_PDC_LINKS, option_pdc_links,
VBI_WST_LEVEL, VBI_WST_LEVEL_3p5,
VBI_OVERRIDE_CHARSET_0, option_override_cs,
VBI_END);
} else {
pg = vbi_decoder_get_page
(vbi, NULL /* current network */,
pgno, subno,
VBI_HEADER_ONLY, option_header_only,
VBI_PADDING, option_padding,
VBI_PANELS, option_panels,
VBI_NAVIGATION, option_navigation,
VBI_HYPERLINKS, option_hyperlinks,
VBI_PDC_LINKS, option_pdc_links,
VBI_WST_LEVEL, VBI_WST_LEVEL_3p5,
VBI_DEFAULT_CHARSET_0, option_default_cs,
VBI_END);
}
} else {
pg = vbi_decoder_get_page
(vbi, NULL /* current network */,
pgno, subno,
VBI_PADDING, option_padding,
VBI_DEFAULT_FOREGROUND, option_default_fg,
VBI_DEFAULT_BACKGROUND, option_default_bg,
VBI_ROW_CHANGE, option_row_update,
VBI_END);
}
assert (NULL != pg);
if (option_dump_pg) {
page_dump (pg);
}
switch (option_target) {
char *file_name;
void *buffer;
void *buffer2;
FILE *fp;
size_t size;
ssize_t ssize;
case 1:
buffer = malloc (1 << 20);
if (NULL == buffer)
no_mem_exit ();
ssize = vbi_export_mem (ex, buffer, 1 << 20, pg);
success = (ssize >= 0);
if (success) {
ssize_t ssize2;
fp = open_output_file (pgno, subno);
if (1 != fwrite (buffer, ssize, 1, fp))
write_error_exit (/* msg: errno */ NULL);
close_output_file (fp);
/* Test. */
ssize2 = vbi_export_mem (ex, buffer, 0, pg);
assert (ssize == ssize2);
assert (ssize > 0);
ssize2 = vbi_export_mem (ex, buffer, ssize - 1, pg);
assert (ssize == ssize2);
}
free (buffer);
break;
case 2:
buffer = NULL;
buffer2 = vbi_export_alloc (ex, &buffer, &size, pg);
/* Test. */
assert (buffer == buffer2);
success = (NULL != buffer);
if (success) {
fp = open_output_file (pgno, subno);
if (1 != fwrite (buffer, size, 1, fp))
write_error_exit (/* msg: errno */ NULL);
close_output_file (fp);
free (buffer);
}
break;
case 3:
/* This is the default target. The other cases are only
implemented for tests and will be removed when I
wrote proper unit tests. */
fp = open_output_file (pgno, subno);
/* For proper timing of subtitles. */
vbi_export_set_timestamp (ex, timestamp);
success = vbi_export_stdio (ex, fp, pg);
close_output_file (fp);
break;
case 5:
file_name = output_file_name (pgno, subno);
success = vbi_export_file (ex, file_name, pg);
free (file_name);
break;
default:
error_exit ("Invalid target %u.", option_target);
break;
}
if (!success) {
error_exit (_("Export of page %x failed: %s"),
pgno,
vbi_export_errstr (ex));
}
if (option_pdc_enum) {
pdc_dump (pg);
}
vbi_page_delete (pg);
pg = NULL;
}
static void
update_page_table (void)
{
vbi_pgno pgno;
if (0 == vbi_page_table_num_pages (pt))
return;
pgno = 0;
while (vbi_page_table_next_page (pt, &pgno)) {
vbi_ttx_page_stat ps;
if (!vbi_teletext_decoder_get_ttx_page_stat
(vbi_decoder_cast_to_teletext_decoder (vbi),
&ps, /* nk: current */ NULL, pgno)) {
continue;
}
/* XXX what are the defaults in ps until we receive the page inventory? */
if (VBI_NO_PAGE == ps.page_type) {
vbi_page_table_remove_page (pt, pgno);
} else if (0 && ps.subpages > 0) {
if (!vbi_page_table_contains_all_subpages (pt, pgno))
vbi_page_table_remove_subpages (pt,
pgno,
ps.subpages,
0x3F7E);
}
}
quit = (0 == vbi_page_table_num_pages (pt));
}
static vbi_bool
event_handler (const vbi_event * ev,
void * user_data)
{
vbi_pgno pgno;
vbi_subno subno;
user_data = user_data; /* unused */
if (quit)
return TRUE;
switch (ev->type) {
case VBI_EVENT_TTX_PAGE:
pgno = ev->ev.ttx_page.pgno;
subno = ev->ev.ttx_page.subno;
if (option_log_mask & VBI_LOG_INFO) {
fprintf (stderr,
"Teletext page %03x.%02x %c",
pgno, subno, cr);
}
if (0 == vbi_page_table_num_pages (pt)) {
do_export (pgno, subno, ev->timestamp);
} else if (vbi_page_table_contains_page (pt, pgno)) {
do_export (pgno, subno, ev->timestamp);
if (!option_subtitles) {
vbi_page_table_remove_page (pt, pgno);
quit = (0 == vbi_page_table_num_pages (pt));
}
}
break;
case VBI_EVENT_CC_PAGE:
if (option_row_update
&& !(ev->ev.caption.flags & VBI_ROW_UPDATE))
break;
pgno = ev->ev.caption.channel;
if (option_log_mask & VBI_LOG_INFO) {
fprintf (stderr,
"Caption channel %u %c",
pgno, cr);
}
if (pgno != cc_chan)
break;
do_export (pgno, VBI_ANY_SUBNO, ev->timestamp);
break;
case VBI_EVENT_PAGE_TYPE:
update_page_table ();
break;
default:
assert (0);
}
return TRUE; /* handled */
}
static void
finalize (void)
{
const vbi_export_info *xi;
xi = vbi_export_info_from_export (ex);
if (xi->open_format && NULL == out_file_name_prefix) {
if (!vbi_export_stdio (ex, stdout, NULL))
write_error_exit (vbi_export_errstr (ex));
}
}
static void
init_vbi_decoder (void)
{
vbi_event_mask event_mask;
vbi_bool success;
/* XXX videostd? */
vbi = vbi_decoder_new (/* cache: allocate one */ NULL,
/* network: current */ NULL,
VBI_VIDEOSTD_SET_625_50);
if (NULL == vbi)
no_mem_exit ();
vbi_decoder_detect_channel_change (vbi, option_dcc);
event_mask = (VBI_EVENT_TTX_PAGE |
VBI_EVENT_CC_PAGE);
if (option_fast)
event_mask |= VBI_EVENT_PAGE_TYPE;
success = vbi_decoder_add_event_handler (vbi, event_mask,
event_handler,
/* user_data */ NULL);
if (!success)
no_mem_exit ();
}
#else
# error VBI_VERSION_MINOR == ?
#endif
static vbi_bool
decode_frame (const vbi_sliced * sliced,
unsigned int n_lines,
const uint8_t * raw,
const vbi_sampling_par *sp,
double sample_time,
int64_t stream_time)
{
static vbi_bool have_start_timestamp = FALSE;
raw = raw; /* unused */
sp = sp;
stream_time = stream_time;
/* To calculate the delay of the first subtitle page. */
if (!have_start_timestamp) {
vbi_export_set_timestamp (ex, sample_time);
have_start_timestamp = TRUE;
}
vbi_decoder_feed (vbi, sliced, n_lines, sample_time);
return !quit;
}
static void
init_export_module (const char * module_name)
{
const vbi_export_info *xi;
char *errstr;
errstr = NULL; /* just in case */
ex = vbi_export_new (module_name, &errstr);
if (NULL == ex) {
error_exit (_("Cannot open export module '%s': %s"),
module_name, errstr);
/* NB. free (errstr); here if you don't exit(). */
}
if (0 == strncmp (module_name, "html", 4)) {
if (option_hyperlinks)
vbi_export_set_link_cb (ex, export_link,
/* user_data */ NULL);
if (option_pdc_links)
vbi_export_set_pdc_cb (ex, export_pdc,
/* user_data */ NULL);
}
xi = vbi_export_info_from_export (ex);
if (NULL == xi)
no_mem_exit ();
if (NULL == out_file_name_suffix) {
char *end = NULL;
out_file_name_suffix = strdup (xi->extension);
if (NULL == out_file_name_suffix)
no_mem_exit ();
out_file_name_suffix = strtok_r (out_file_name_suffix,
",", &end);
}
#if 3 == VBI_VERSION_MINOR
if (xi->open_format)
option_subtitles ^= TRUE;
#endif
}
static void
list_options (vbi_export * ex)
{
const vbi_option_info *oi;
unsigned int i;
for (i = 0; (oi = vbi_export_option_info_enum (ex, i)); ++i) {
char buf[32];
switch (oi->type) {
case VBI_OPTION_BOOL:
case VBI_OPTION_INT:
case VBI_OPTION_MENU:
snprintf (buf, sizeof (buf), "%d", oi->def.num);
break;
case VBI_OPTION_REAL:
snprintf (buf, sizeof (buf), "%f", oi->def.dbl);
break;
case VBI_OPTION_STRING:
break;
}
if (NULL == oi->tooltip)
continue;
if (NULL == oi->tooltip) {
printf (" Option '%s' (%s)\n",
oi->keyword,
VBI_OPTION_STRING == oi->type ?
oi->def.str : buf);
} else {
printf (" Option '%s' - %s (%s)\n",
oi->keyword,
_(oi->tooltip),
VBI_OPTION_STRING == oi->type ?
oi->def.str : buf);
}
if (VBI_OPTION_MENU == oi->type) {
int i;
for (i = oi->min.num; i <= oi->max.num; ++i) {
printf (" %d - %s\n",
i, _(oi->menu.str[i]));
}
}
}
}
static void
list_modules (void)
{
const vbi_export_info *xi;
unsigned int i;
for (i = 0; (xi = vbi_export_info_enum (i)); ++i) {
vbi_export *ex;
printf ("'%s' - %s\n",
_(xi->keyword),
_(xi->tooltip));
ex = vbi_export_new (xi->keyword, /* errstr */ NULL);
if (NULL == ex)
no_mem_exit ();
list_options (ex);
vbi_export_delete (ex);
ex = NULL;
}
}
static void
usage (FILE * fp)
{
/* FIXME Supposed to be _(localized) but we can't use #ifs
within the _() macro. */
fprintf (fp, "\
%s %s -- Teletext and Closed Caption export utility\n\n\
Copyright (C) 2004, 2005, 2007 Michael H. Schimek\n\
This program is licensed under GPLv2. NO WARRANTIES.\n\n\
Usage: %s [options] format [page number(s)] < sliced vbi data > file\n\
-h | --help | --usage Print this message and exit\n\
-q | --quiet Suppress progress and error messages\n\
-v | --verbose Increase verbosity\n\
-V | --version Print the program version and exit\n\
Input options:\n\
-i | --input name Read the VBI data from this file instead of\n\
standard input\n\
-P | --pes Source is a DVB PES stream\n\
-T | --ts pid Source is a DVB TS stream\n\
Scan options:\n"
#if 3 == VBI_VERSION_MINOR
"-f | --fast Do not wait for Teletext pages which are\n\
currently not in transmission\n"
#endif
"-w | --wait n Export the second (third, fourth, ...)\n\
transmission of the requested page\n"
#if 3 == VBI_VERSION_MINOR
"Formatting options:\n\
-n | --nav Add TOP or FLOF navigation elements to Teletext\n\
pages\n\
-d | --pad Add an extra column to Teletext pages for a more\n\
balanced view, spaces to Closed Caption pages for\n\
readability\n"
#endif
"Export options:\n"
#if 3 == VBI_VERSION_MINOR
"-e | --pdc-enum Print additional Teletext PDC information\n"
#endif
"-g | --dump-pg For debugging dump the vbi_page being exported\n"
#if 3 == VBI_VERSION_MINOR
"-l | --links Turn HTTP, SMTP, etc. links on a Teletext page\n\
into hyperlinks in HTML output\n"
#endif
"-o | --output name Write the page to this file instead of standard\n\
output. The page number and a suitable .extension\n\
will be appended as necessary.\n"
#if 3 == VBI_VERSION_MINOR
"-p | --pdc Turn PDC markup on a Teletext page into hyperlinks\n\
in HTML output\n\
-r | --row-update Export a Closed Caption page only when a row is\n\
complete, not on every new character. Has only an\n\
effect on roll-up and paint-on style caption.\n\
-s | --stream Export all (rather than just one) transmissions\n\
of this page to a single file. This is the default\n\
for caption/subtitle formats.\n"
#endif
"Formats:\n\
-m | --list List available output formats and their options.\n\
Append options to the format name separated by\n\
commas: text,charset=UTF-8\n\
Valid page numbers are:\n"
#if 3 == VBI_VERSION_MINOR
"1 ... 8 Closed Caption channel 1 ... 4, text channel\n\
1 ... 4\n"
#endif
"100 ... 899 Teletext page. The program can export multiple\n\
Teletext pages: 100 110 200-299. If no page\n\
numbers are given it exports all received Teletext\n\
pages until it is terminated.\n\
",
PROGRAM_NAME, VERSION, program_invocation_name);
}
static const char
short_options [] = "1a:cdefghi:lmno:pqrsvwAB:C:F:H:O:PT:V";
#ifdef HAVE_GETOPT_LONG
static const struct option
long_options [] = {
{ "all-pages", no_argument, NULL, '1' },
{ "target", required_argument, NULL, 'a' },
{ "dcc", no_argument, NULL, 'c' },
{ "pad", no_argument, NULL, 'd' },
{ "pdc-enum", no_argument, NULL, 'e' },
{ "fast", no_argument, NULL, 'f' },
{ "dump-pg", no_argument, NULL, 'g' },
{ "help", no_argument, NULL, 'h' },
{ "usage", no_argument, NULL, 'h' },
{ "input", required_argument, NULL, 'i' },
{ "links", no_argument, NULL, 'l' },
{ "list", no_argument, NULL, 'm' },
{ "nav", no_argument, NULL, 'n' },
{ "output", required_argument, NULL, 'o' },
{ "pdc", no_argument, NULL, 'p' },
{ "quiet", no_argument, NULL, 'q' },
{ "row-update", no_argument, NULL, 'r' },
{ "stream", no_argument, NULL, 's' },
{ "verbose", no_argument, NULL, 'v' },
{ "wait", no_argument, NULL, 'w' },
{ "side-panels", required_argument, NULL, 'A' },
{ "default-bg", required_argument, NULL, 'B' },
{ "default-cs", required_argument, NULL, 'C' },
{ "default-fg", required_argument, NULL, 'F' },
{ "header-only", required_argument, NULL, 'H' },
{ "override-cs", required_argument, NULL, 'O' },
{ "pes", no_argument, NULL, 'P' },
{ "ts", required_argument, NULL, 'T' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, 0, 0 }
};
#else
# define getopt_long(ac, av, s, l, i) getopt(ac, av, s)
#endif
static int option_index;
static void
parse_output_option (void)
{
assert (NULL != optarg);
free (out_file_name_prefix);
out_file_name_prefix = NULL;
free (out_file_name_suffix);
out_file_name_suffix = NULL;
if (0 == strcmp (optarg, "-")) {
/* Write to stdout. */
} else {
char *s;
s = strrchr (optarg, '.');
if (NULL == s) {
out_file_name_prefix = strdup (optarg);
if (NULL == out_file_name_prefix)
no_mem_exit ();
} else {
out_file_name_prefix = strndup (optarg, s - optarg);
if (NULL == out_file_name_prefix)
no_mem_exit ();
if (0 != s[1]) {
out_file_name_suffix = strdup (s + 1);
if (NULL == out_file_name_suffix)
no_mem_exit ();
}
}
}
}
static vbi_bool
valid_pgno (vbi_pgno pgno)
{
return (vbi_is_bcd (pgno)
&& pgno >= 0x100
&& pgno <= 0x899);
}
static void
invalid_pgno_exit (const char * arg)
{
error_exit (_("Invalid page number '%s'."), arg);
}
static void
parse_page_numbers (unsigned int argc,
char ** argv)
{
unsigned int i;
for (i = 0; i < argc; ++i) {
vbi_pgno first_pgno;
vbi_pgno last_pgno;
vbi_bool success;
const char *s;
char *end;
s = argv[i];
first_pgno = strtoul (s, &end, 16);
s = end;
if (first_pgno >= 1 && first_pgno <= 8) {
if (0 != cc_chan) {
error_exit (_("Can export only one "
"Closed Caption channel."));
}
cc_chan = first_pgno;
while (*s && isspace (*s))
++s;
if (0 != *s)
invalid_pgno_exit (argv[i]);
continue;
}
if (!valid_pgno (first_pgno))
invalid_pgno_exit (argv[i]);
last_pgno = first_pgno;
while (*s && isspace (*s))
++s;
if ('-' == *s) {
++s;
while (*s && isspace (*s))
++s;
last_pgno = strtoul (s, &end, 16);
s = end;
if (!valid_pgno (last_pgno))
invalid_pgno_exit (argv[i]);
} else if (0 != *s) {
invalid_pgno_exit (argv[i]);
}
success = vbi_page_table_add_pages (pt, first_pgno,
last_pgno);
if (!success)
no_mem_exit ();
}
}
int
main (int argc,
char ** argv)
{
const char *module_name;
unsigned int n_pages;
vbi_bool all_pages;
init_helpers (argc, argv);
option_in_file_format = FILE_FORMAT_SLICED;
option_default_fg = (vbi_rgba) 0xFFFFFF;
option_default_bg = (vbi_rgba) 0x000000;
all_pages = FALSE;
for (;;) {
int c;
c = getopt_long (argc, argv, short_options,
long_options, &option_index);
if (-1 == c)
break;
switch (c) {
case 0: /* getopt_long() flag */
break;
case '1':
/* Compatibility (used to be pgno -1). */
all_pages = TRUE;
break;
case 'a':
/* For debugging. */
assert (NULL != optarg);
option_target = strtoul (optarg, NULL, 0);
break;
case 'c':
option_dcc = TRUE;
break;
case 'd':
option_padding = TRUE;
break;
case 'e':
option_pdc_enum = TRUE;
break;
case 'f':
option_fast = TRUE;
break;
case 'g':
option_dump_pg = TRUE;
break;
case 'h':
usage (stdout);
exit (EXIT_SUCCESS);
case 'i':
assert (NULL != optarg);
option_in_file_name = optarg;
break;
case 'l':
option_hyperlinks = TRUE;
break;
case 'm':
list_modules ();
exit (EXIT_SUCCESS);
case 'n':
option_navigation = TRUE;
break;
case 'o':
parse_output_option ();
break;
case 'p':
option_pdc_links = TRUE;
break;
case 'q':
parse_option_quiet ();
break;
case 'r':
option_row_update = TRUE;
break;
case 's':
option_subtitles = TRUE;
break;
case 'v':
parse_option_verbose ();
break;
case 'w':
option_delay += 1;
break;
case 'A':
option_panels = TRUE;
break;
case 'B':
assert (NULL != optarg);
option_default_bg = strtoul (optarg, NULL, 0);
break;
case 'C':
assert (NULL != optarg);
option_default_cs = strtoul (optarg, NULL, 0);
have_option_default_cs = TRUE;
break;
case 'F':
assert (NULL != optarg);
option_default_fg = strtoul (optarg, NULL, 0);
break;
case 'H':
option_header_only = TRUE;
break;
case 'O':
assert (NULL != optarg);
option_override_cs = strtoul (optarg, NULL, 0);
break;
case 'P':
option_in_file_format = FILE_FORMAT_DVB_PES;
break;
case 'T':
option_in_ts_pid = parse_option_ts ();
option_in_file_format = FILE_FORMAT_DVB_TS;
break;
case 'V':
printf (PROGRAM_NAME " " VERSION "\n");
exit (EXIT_SUCCESS);
default:
usage (stderr);
exit (EXIT_FAILURE);
}
}
option_pdc_links |= option_pdc_enum;
if (argc - optind < 1) {
usage (stderr);
exit (EXIT_FAILURE);
}
module_name = argv[optind++];
pt = vbi_page_table_new ();
if (NULL == pt)
no_mem_exit ();
if (all_pages) {
/* Compatibility. */
out_file_name_prefix = strdup ("test");
if (NULL == out_file_name_prefix)
no_mem_exit ();
} else {
parse_page_numbers (argc - optind, &argv[optind]);
}
n_pages = vbi_page_table_num_pages (pt);
if (1 != n_pages && option_delay > 0) {
error_exit (_("The --wait option requires "
"a single page number."));
}
if (NULL == out_file_name_prefix) {
switch (n_pages) {
case 0: /* all pages? */
error_exit (_("No page number or "
"output file name specified."));
break;
case 1: /* one page to stdout */
break;
default: /* multiple pages */
error_exit (_("No output file name specified."));
break;
}
}
init_export_module (module_name);
init_vbi_decoder ();
cr = isatty (STDERR_FILENO) ? '\r' : '\n';
rst = read_stream_new (option_in_file_name,
option_in_file_format,
option_in_ts_pid,
decode_frame);
stream_loop (rst);
stream_delete (rst);
rst = NULL;
vbi_decoder_delete (vbi);
vbi = NULL;
finalize ();
free (out_file_name_prefix);
out_file_name_prefix = NULL;
free (out_file_name_suffix);
out_file_name_suffix = NULL;
if (!option_subtitles) {
n_pages = vbi_page_table_num_pages (pt);
if (1 == n_pages) {
vbi_pgno pgno = 0;
vbi_page_table_next_page (pt, &pgno);
error_exit (_("End of stream. Page %03x not found."),
pgno);
} else if (n_pages > 0) {
error_exit (_("End of stream. %u pages not found."));
}
}
vbi_page_table_delete (pt);
pt = NULL;
vbi_export_delete (ex);
ex = NULL;
exit (EXIT_SUCCESS);
}