/*
 *  zvbi-decode -- decode sliced VBI data using low-level
 *		   libzvbi functions
 *
 *  Copyright (C) 2004, 2006 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $Id: decode.c,v 1.19 2006/10/06 19:23:15 mschimek Exp $ */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <locale.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#ifdef HAVE_GETOPT_LONG
#  include <getopt.h>
#endif

#include "src/version.h"
#if 2 == VBI_VERSION_MINOR
#  include "src/bcd.h"
#  include "src/conv.h"
#  include "src/pfc_demux.h"
#  include "src/dvb_demux.h"
#  include "src/idl_demux.h"
#  include "src/xds_demux.h"
#  include "src/vps.h"
#  include "src/hamm.h"
#  include "src/lang.h"
#  include "sliced.h"		/* sliced data from file */
#else /* 0.3 */
#  include "src/zvbi.h"
#  include "src/misc.h"		/* _vbi_to_ascii() */
#endif

#define _(x) x /* i18n TODO */

/* Will be installed one day. */
#define PROGRAM_NAME "zvbi-decode"

static vbi_bool			source_is_pes; /* ATSC/DVB */

static vbi_pgno			option_pfc_pgno;
static unsigned int		option_pfc_stream;

static vbi_bool			option_decode_ttx;
static vbi_bool			option_decode_8301;
static vbi_bool			option_decode_8302;
static vbi_bool			option_decode_caption;
static vbi_bool			option_decode_xds;
static vbi_bool			option_decode_idl;
static vbi_bool			option_decode_vps;
static vbi_bool			option_decode_vps_other;
static vbi_bool			option_decode_wss;

static vbi_bool			option_dump_network;
static vbi_bool			option_dump_hex;
static vbi_bool			option_dump_bin;
static vbi_bool			option_dump_time;

static vbi_pgno			option_pfc_pgno	= 0;
static unsigned int		option_pfc_stream = 0;

static unsigned int		option_idl_channel = 0;
static unsigned int		option_idl_address = 0;

/* Demultiplexers. */

static vbi_pfc_demux *		pfc;
static vbi_dvb_demux *		dvb;
static vbi_idl_demux *		idl;
static vbi_xds_demux *		xds;

#ifndef HAVE_PROGRAM_INVOCATION_NAME
static char *			program_invocation_name;
static char *			program_invocation_short_name;
#endif

extern void
_vbi_pfc_block_dump		(const vbi_pfc_block *	pb,
				 FILE *			fp,
				 vbi_bool		binary);

static void
error_exit			(const char *		template,
				 ...)
{
	va_list ap;

	fprintf (stderr, "%s: ", program_invocation_short_name);

	va_start (ap, template);
	vfprintf (stderr, template, ap);
	va_end (ap);         

	fputc ('\n', stderr);

	exit (EXIT_FAILURE);
}

static void
no_mem_exit			(void)
{
	error_exit (_("Out of memory."));
}

static void
put_cc_char			(unsigned int		c1,
				 unsigned int		c2)
{
	uint16_t ucs2_str[1];

	/* All caption characters are representable
	   in UTF-8, but not necessarily in ASCII. */
	ucs2_str[0] = vbi_caption_unicode ((c1 * 256 + c2) & 0x777F,
					   /* to_upper */ FALSE);

	vbi_fputs_iconv_ucs2 (stdout,
			       vbi_locale_codeset (),
			       ucs2_str, 1,
			       /* repl_char */ '?');
}

static void
caption_command			(unsigned int		line,
				 unsigned int		c1,
				 unsigned int		c2)
{
	unsigned int ch;
	unsigned int a7;
	unsigned int f;
	unsigned int b7;
	unsigned int u;

	printf ("CC line=%3u cmd 0x%02x 0x%02x ", line, c1, c2);

	if (0 == c1) {
		printf ("null\n");
		return;
	} else if (c2 < 0x20) {
		printf ("invalid\n");
		return;
	}

	/* Some common bit groups. */

	ch = (c1 >> 3) & 1; /* channel */
	a7 = c1 & 7;
	f = c1 & 1; /* field */
	b7 = (c2 >> 1) & 7;
	u = c2 & 1; /* underline */

	if (c2 >= 0x40) {
		static const int row [16] = {
			/* 0 */ 10,			/* 0x1040 */
			/* 1 */ -1,			/* unassigned */
			/* 2 */ 0, 1, 2, 3,		/* 0x1140 ... 0x1260 */
			/* 6 */ 11, 12, 13, 14,		/* 0x1340 ... 0x1460 */
			/* 10 */ 4, 5, 6, 7, 8, 9	/* 0x1540 ... 0x1760 */
		};
		unsigned int rrrr;

		/* Preamble Address Codes -- 001 crrr  1ri bbbu */
  
		rrrr = a7 * 2 + ((c2 >> 5) & 1);

		if (c2 & 0x10)
			printf ("PAC ch=%u row=%u column=%u u=%u\n",
				ch, row[rrrr], b7 * 4, u);
		else
			printf ("PAC ch=%u row=%u color=%u u=%u\n",
				ch, row[rrrr], b7, u);
		return;
	}

	/* Control codes -- 001 caaa  01x bbbu */

	switch (a7) {
	case 0:
		if (c2 < 0x30) {
			const char *mnemo [16] = {
				"BWO", "BWS", "BGO", "BGS",
				"BBO", "BBS", "BCO", "BCS",
				"BRO", "BRS", "BYO", "BYS",
				"BMO", "BMS", "BAO", "BAS"
			};

			printf ("%s ch=%u\n", mnemo[c2 & 0xF], ch);
			return;
		}

		break;

	case 1:
		if (c2 < 0x30) {
			printf ("mid-row ch=%u color=%u u=%u\n", ch, b7, u);
		} else {
			printf ("special character ch=%u 0x%02x%02x='",
				ch, c1, c2);
			put_cc_char (c1, c2);
			puts ("'");
		}

		return;

	case 2: /* first group */
	case 3: /* second group */
		printf ("extended character ch=%u 0x%02x%02x='", ch, c1, c2);
		put_cc_char (c1, c2);
		puts ("'");
		return;

	case 4: /* f=0 */
	case 5: /* f=1 */
		if (c2 < 0x30) {
			const char *mnemo [16] = {
				"RCL", "BS",  "AOF", "AON",
				"DER", "RU2", "RU3", "RU4",
				"FON", "RDC", "TR",  "RTD",
				"EDM", "CR",  "ENM", "EOC"
			};

			printf ("%s ch=%u f=%u\n", mnemo[c2 & 0xF], ch, f);
			return;
		}

		break;

	case 6: /* reserved */
		break;

	case 7:
		switch (c2) {
		case 0x21 ... 0x23:
			printf ("TO%u ch=%u\n", c2 - 0x20, ch);
			return;

		case 0x2D:
			printf ("BT ch=%u\n", ch);
			return;

		case 0x2E:
			printf ("FA ch=%u\n", ch);
			return;

		case 0x2F:
			printf ("FAU ch=%u\n", ch);
			return;

		default:
			break;
		}

		break;
	}

	printf ("unknown\n");
}

static vbi_bool
xds_cb				(vbi_xds_demux *	xd,
				 const vbi_xds_packet *	xp,
				 void *			user_data)
{
	xd = xd;
	user_data = user_data;

	_vbi_xds_packet_dump (xp, stdout);

	return TRUE; /* no errors */
}

static void
caption				(const uint8_t		buffer[2],
				 unsigned int		line)
{
	if (option_decode_xds && 284 == line) {
		if (!vbi_xds_demux_feed (xds, buffer)) {
			printf (_("Parity error in XDS data.\n"));
		}
	}

	if (option_decode_caption
	    && (21 == line || 284 == line /* NTSC */
		|| 22 == line /* PAL */)) {
		int c1;
		int c2;

		c1 = vbi_unpar8 (buffer[0]);
		c2 = vbi_unpar8 (buffer[1]);

		if ((c1 | c2) < 0) {
			printf (_("Parity error in CC line=%u "
				  " %s0x%02x %s0x%02x.\n"),
				line,
				(c1 < 0) ? ">" : "", buffer[0] & 0xFF,
				(c2 < 0) ? ">" : "", buffer[1] & 0xFF);
		} else if (c1 >= 0x20) {
			char text[2];

			printf ("CC line=%3u text 0x%02x 0x%02x '",
				line, c1, c2);

			/* All caption characters are representable
			   in UTF-8, but not necessarily in ASCII. */
			text[0] = c1;
			text[1] = c2; /* may be zero */

			/* Error ignored. */
			vbi_fputs_iconv (stdout,
					 vbi_locale_codeset (),
					 /* src_codeset */ "EIA-608",
					 text, 2, /* repl_char */ '?');

			puts ("'");
		} else if (0 == c1 || c1 >= 0x10) {
			caption_command (line, c1, c2);
		} else if (option_decode_xds) {
			printf ("CC line=%3u cmd 0x%02x 0x%02x ",
				line, c1, c2);
			if (0x0F == c1)
				puts ("XDS packet end");
			else
				puts ("XDS packet start/continue");
		}
	}
}

#if 3 == VBI_VERSION_MINOR /* XXX port me back */

static void
dump_cni			(vbi_cni_type		type,
				 unsigned int		cni)
{
	vbi_network nk;
	vbi_bool success;

	if (!option_dump_network)
		return;

	success = vbi_network_init (&nk);
	if (!success)
		no_mem_exit ();

	success = vbi_network_set_cni (&nk, type, cni);
	if (!success)
		no_mem_exit ();

	_vbi_network_dump (&nk, stdout);
	putchar ('\n');

	vbi_network_destroy (&nk);
}

#endif /* 3 == VBI_VERSION_MINOR */

static void
dump_bytes			(const uint8_t *	buffer,
				 unsigned int		n_bytes)
{
	unsigned int j;

	if (option_dump_bin) {
		fwrite (buffer, 1, n_bytes, stdout);
		return;
	}

	if (option_dump_hex) {
		for (j = 0; j < n_bytes; ++j)
			printf ("%02x ", buffer[j]);
	}

	putchar ('>');

	for (j = 0; j < n_bytes; ++j) {
		/* For Teletext: Not all characters are representable
		   in ASCII or even UTF-8, but at this stage we don't
		   know the Teletext code page for a proper conversion. */
		char c = _vbi_to_ascii (buffer[j]);

		putchar (c);
	}

	puts ("<");
}

#if 3 == VBI_VERSION_MINOR /* XXX port me back */

static void
packet_8301			(const uint8_t		buffer[42],
				 unsigned int		designation)
{
	unsigned int cni;
	time_t time;
	int gmtoff;
	struct tm tm;

	if (!option_decode_8301)
		return;

	if (!vbi_decode_teletext_8301_cni (&cni, buffer)) {
		printf (_("Error in Teletext "
			  "packet 8/30 format 1 CNI.\n"));
		return;
	}

	if (!vbi_decode_teletext_8301_local_time (&time, &gmtoff, buffer)) {
		printf (_("Error in Teletext "
			  "packet 8/30 format 1 local time.\n"));
		return;
	}

	printf ("Teletext packet 8/30/%u cni=%x time=%u gmtoff=%d ",
		designation, cni, (unsigned int) time, gmtoff);

	gmtime_r (&time, &tm);

	printf ("(%4u-%02u-%02u %02u:%02u:%02u UTC)\n",
		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
		tm.tm_hour, tm.tm_min, tm.tm_sec);

	if (0 != cni)
		dump_cni (VBI_CNI_TYPE_8301, cni);
}

static void
packet_8302			(const uint8_t		buffer[42],
				 unsigned int		designation)
{
	unsigned int cni;
	vbi_program_id pi;

	if (!option_decode_8302)
		return;

	if (!vbi_decode_teletext_8302_cni (&cni, buffer)) {
		printf (_("Error in Teletext "
			  "packet 8/30 format 2 CNI.\n"));
		return;
	}

	if (!vbi_decode_teletext_8302_pdc (&pi, buffer)) {
		printf (_("Error in Teletext "
			  "packet 8/30 format 2 PDC data.\n"));
		return;
	}

	printf ("Teletext packet 8/30/%u cni=%x ", designation, cni);

	_vbi_program_id_dump (&pi, stdout);

	putchar ('\n');

	if (0 != pi.cni)
		dump_cni (pi.cni_type, pi.cni);
}

#endif /* 3 == VBI_VERSION_MINOR */

static vbi_bool
page_function_clear_cb		(vbi_pfc_demux *	dx,
		                 const vbi_pfc_block *	block,
				 void *			user_data)
{
	dx = dx; /* unused */
	user_data = user_data;

	_vbi_pfc_block_dump (block, stdout, option_dump_bin);

	return TRUE;
}

static vbi_bool
idl_format_a_cb			(vbi_idl_demux *	idl,
				 const uint8_t *	buffer,
				 unsigned int		n_bytes,
				 unsigned int		flags,
				 void *			user_data)
{
	idl = idl;
	user_data = user_data;

	if (!option_dump_bin) {
		printf ("IDL-A%s%s ",
			(flags & VBI_IDL_DATA_LOST) ? " <data lost>" : "",
			(flags & VBI_IDL_DEPENDENT) ? " <dependent>" : "");
	}

	dump_bytes (buffer, n_bytes);

	return TRUE;
}

static void
packet_idl			(const uint8_t		buffer[42],
				 unsigned int		channel)
{
	int pa; /* packet address */
	int ft; /* format type */

	printf ("IDL ch=%u ", channel);

	switch (channel) {
	case 0:
		assert (0);

	case 4:
	case 12:
		printf (_("(Low bit rate audio) "));

		dump_bytes (buffer, 42);

		break;

	case 5:
	case 6:
	case 13:
	case 14:
		pa = vbi_unham8 (buffer[3]);
		pa |= vbi_unham8 (buffer[4]) << 4;
		pa |= vbi_unham8 (buffer[5]) << 8;

		if (pa < 0) {
			printf (_("Hamming error in Datavideo "
				  "packet-address byte.\n"));
			return;
		}

		printf ("(Datavideo) pa=0x%x ", pa);

		dump_bytes (buffer, 42);

		break;

	case 8:
	case 9:
	case 10:
	case 11:
	case 15:
		if ((ft = vbi_unham8 (buffer[2])) < 0) {
			printf (_("Hamming error in IDL format "
				  "A or B format-type byte.\n"));
			return;
		}

		if (0 == (ft & 1)) {
			int ial; /* interpretation and address length */
			unsigned int spa_length;
			int spa; /* service packet address */
			unsigned int i;

			if ((ial = vbi_unham8 (buffer[3])) < 0) {
				printf (_("Hamming error in IDL format "
					  "A interpretation-and-address-"
					  "length byte.\n"));
				return;
			}

			spa_length = (unsigned int) ial & 7;
			if (7 == spa_length) {
				printf ("(Format A?) ");
				dump_bytes (buffer, 42);
				return;
			}

			spa = 0;

			for (i = 0; i < spa_length; ++i)
				spa |= vbi_unham8 (buffer[4 + i]) << (4 * i);

			if (spa < 0) {
				printf (_("Hamming error in IDL format "
					  "A service-packet-address byte.\n"));
				return;
			}

			printf ("(Format A) spa=0x%x ", spa);
		} else if (1 == (ft & 3)) {
			int an; /* application number */
			int ai; /* application identifier */

			an = (ft >> 2);

			if ((ai = vbi_unham8 (buffer[3])) < 0) {
				printf (_("Hamming error in IDL format "
					  "B application-number byte.\n"));
				return;
			}

			printf ("(Format B) an=%d ai=%d ", an, ai);
		}

		dump_bytes (buffer, 42);

		break;

	default:
		dump_bytes (buffer, 42);

		break;
	}
}

static void
teletext			(const uint8_t		buffer[42],
				 unsigned int		line)
{
	int pmag;
	unsigned int magazine;
	unsigned int packet;

	if (NULL != pfc) {
		if (!vbi_pfc_demux_feed (pfc, buffer)) {
			printf (_("Error in Teletext "
				  "PFC packet.\n"));
			return;
		}
	}

	if (!(option_decode_ttx |
	      option_decode_8301 |
	      option_decode_8302 |
	      option_decode_idl))
		return;

	pmag = vbi_unham16p (buffer);
	if (pmag < 0) {
		printf (_("Hamming error in Teletext "
			  "packet number.\n"));
		return;
	}

	magazine = pmag & 7;
	if (0 == magazine)
		magazine = 8;

	packet = pmag >> 3;

	if (8 == magazine && 30 == packet) {
		int designation;

		designation = vbi_unham8 (buffer[2]);
		if (designation < 0 ) {
			printf (_("Hamming error in Teletext "
				  "packet 8/30 designation byte.\n"));
			return;
		}

		if (designation >= 0 && designation <= 1) {
#if 3 == VBI_VERSION_MINOR /* XXX port me back */
			packet_8301 (buffer, designation);
#endif
			return;
		}

		if (designation >= 2 && designation <= 3) {
#if 3 == VBI_VERSION_MINOR /* XXX port me back */
			packet_8302 (buffer, designation);
#endif
			return;
		}
	}

	if (30 == packet || 31 == packet) {
		if (option_decode_idl) {
#if 1
			packet_idl (buffer, pmag & 15);
#else
			printf ("Teletext IDL packet %u/%2u ",
				magazine, packet);
			dump_bytes (buffer, /* n_bytes */ 42);
#endif
			return;
		}
	}

	if (option_decode_ttx) {
		printf ("Teletext line=%3u %x/%2u ",
			line, magazine, packet);
		dump_bytes (buffer, /* n_bytes */ 42);
		return;
	}
}

static void
vps				(const uint8_t		buffer[13],
				 unsigned int		line)
{
	if (option_decode_vps) {
		unsigned int cni;
#if 3 == VBI_VERSION_MINOR
		vbi_program_id pi;
#endif
		if (option_dump_bin) {
			printf ("VPS line=%3u ", line);
			fwrite (buffer, 1, 13, stdout);
			fflush (stdout);
			return;
		}

		if (!vbi_decode_vps_cni (&cni, buffer)) {
			printf (_("Error in VPS packet CNI.\n"));
			return;
		}

#if 3 == VBI_VERSION_MINOR
		if (!vbi_decode_vps_pdc (&pi, buffer)) {
			printf (_("Error in VPS packet PDC data.\n"));
			return;
		}
		
		printf ("VPS line=%3u ", line);

		_vbi_program_id_dump (&pi, stdout);

		putchar ('\n');

		if (0 != pi.cni)
			dump_cni (pi.cni_type, pi.cni);
#else
		printf ("VPS line=%3u CNI=%x\n", line, cni);
#endif
	}

	if (option_decode_vps_other) {
		static char pr_label[2][20];
		static char label[2][20];
		static int l[2] = { 0, 0 };
		unsigned int i;
		int c;

		i = (line != 16);

		c = vbi_rev8 (buffer[1]);

		if (c & 0x80) {
			label[i][l[i]] = 0;
			strcpy (pr_label[i], label[i]);
			l[i] = 0;
		}

		label[i][l[i]] = _vbi_to_ascii (c);

		l[i] = (l[i] + 1) % 16;
		
		printf ("VPS line=%3u bytes 3-10: "
			"%02x %02x (%02x='%c') %02x %02x "
			"%02x %02x %02x %02x (\"%s\")\n",
			line,
			buffer[0], buffer[1],
			c, _vbi_to_ascii (c),
			buffer[2], buffer[3],
			buffer[4], buffer[5], buffer[6], buffer[7],
			pr_label[i]);
	}
}

#if 3 == VBI_VERSION_MINOR /* XXX port me back */

static void
wss_625				(const uint8_t		buffer[2])
{
	if (option_decode_wss) {  
		vbi_aspect_ratio ar;

		if (!vbi_decode_wss_625 (&ar, buffer)) {
			printf (_("Error in WSS packet.\n"));
			return;
		}

		fputs ("WSS ", stdout);

		_vbi_aspect_ratio_dump (&ar, stdout);

		putchar ('\n');
	}
}

#endif /* 3 == VBI_VERSION_MINOR */

static void
decode				(const vbi_sliced *	s,
				 unsigned int		n_lines,
				 double			sample_time,
				 int64_t		stream_time)
{
	static double last_sample_time = 0.0;
	static int64_t last_stream_time = 0;

	if (option_dump_time) {
		/* Sample time: When we captured the data, in
		   		seconds since 1970-01-01 (gettimeofday()).
		   Stream time: For ATSC/DVB the Presentation TimeStamp.
				For analog the frame number multiplied by
				the nominal frame period (1/25 or
				1001/30000 s). Both given in 90000 kHz units.
		   Note this isn't fully implemented yet. */
		printf ("ST %f (%+f) PTS %" PRId64 " (%+" PRId64 ")\n",
			sample_time, sample_time - last_sample_time,
			stream_time, stream_time - last_stream_time);

		last_sample_time = sample_time;
		last_stream_time = stream_time;
	}

	while (n_lines > 0) {
		switch (s->id) {
		case VBI_SLICED_TELETEXT_B_L10_625:
		case VBI_SLICED_TELETEXT_B_L25_625:
		case VBI_SLICED_TELETEXT_B_625:
			teletext (s->data, s->line);
			break;

		case VBI_SLICED_VPS:
		case VBI_SLICED_VPS_F2:
			vps (s->data, s->line);
			break;

		case VBI_SLICED_CAPTION_625_F1:
		case VBI_SLICED_CAPTION_625_F2:
		case VBI_SLICED_CAPTION_625:
		case VBI_SLICED_CAPTION_525_F1:
		case VBI_SLICED_CAPTION_525_F2:
		case VBI_SLICED_CAPTION_525:
			caption (s->data, s->line);
			break;

		case VBI_SLICED_WSS_625:
#if 3 == VBI_VERSION_MINOR /* XXX port me back */
			wss_625 (s->data);
#endif
			break;

		case VBI_SLICED_WSS_CPR1204:
			break;
		}

		++s;
		--n_lines;
	}
}

static void
pes_mainloop			(void)
{
	uint8_t buffer[2048];

	while (1 == fread (buffer, sizeof (buffer), 1, stdin)) {
		const uint8_t *bp;
		unsigned int left;

		bp = buffer;
		left = sizeof (buffer);

		while (left > 0) {
			vbi_sliced sliced[64];
			unsigned int n_lines;
			int64_t pts;

			n_lines = vbi_dvb_demux_cor (dvb, sliced, 64,
						     &pts, &bp, &left);
			if (n_lines > 0)
				decode (sliced, n_lines,
					/* sample_time */ 0,
					/* stream_time */ pts);
		}
	}

	fprintf (stderr, _("\rEnd of stream\n"));
}

#if 3 == VBI_VERSION_MINOR /* XXX replace me, I'm redundant */

static void
old_mainloop			(void)
{
	double time;

	time = 0.0;

	for (;;) {
		char buf[256];
		double dt;
		unsigned int n_items;
		vbi_sliced sliced[40];
		vbi_sliced *s;

		if (ferror (stdin) || !fgets (buf, 255, stdin))
			goto abort;

		dt = strtod (buf, NULL);
		n_items = fgetc (stdin);

		assert (n_items < 40);

		s = sliced;

		while (n_items-- > 0) {
			int index;

			index = fgetc (stdin);

			s->line = (fgetc (stdin)
				   + 256 * fgetc (stdin)) & 0xFFF;

			if (index < 0)
				goto abort;

			switch (index) {
			case 0:
				s->id = VBI_SLICED_TELETEXT_B;
				fread (s->data, 1, 42, stdin);
				break;

			case 1:
				s->id = VBI_SLICED_CAPTION_625; 
				fread (s->data, 1, 2, stdin);
				break; 

			case 2:
				s->id = VBI_SLICED_VPS;
				fread (s->data, 1, 13, stdin);
				break;

			case 3:
				s->id = VBI_SLICED_WSS_625; 
				fread (s->data, 1, 2, stdin);
				break;

			case 4:
				s->id = VBI_SLICED_WSS_CPR1204; 
				fread (s->data, 1, 3, stdin);
				break;

			case 7:
				s->id = VBI_SLICED_CAPTION_525; 
				fread(s->data, 1, 2, stdin);
				break;

			default:
				fprintf (stderr,
					 "\nOops! Unknown data type %d "
					 "in sample file\n", index);
				exit (EXIT_FAILURE);
			}

			++s;
		}

		decode (sliced, s - sliced, time, 0);

		if (feof (stdin) || ferror (stdin))
			goto abort;

		time += dt;
	}

	return;

abort:
	fprintf (stderr, "\rEnd of stream\n");
}

#else /* 2 == VBI_VERSION_MINOR */

static void
old_mainloop			(void)
{
	for (;;) {
		vbi_sliced sliced[40];
		double timestamp;
		int n_lines;

		n_lines = read_sliced (sliced, &timestamp,
				       /* max_lines */ 40);
		if (n_lines < 0)
			break;

		decode (sliced, n_lines,
			/* sample_time */ timestamp,
			/* stream_time */ 0);
	}

	fprintf (stderr, "\rEnd of stream\n");
}

#endif /* 2 == VBI_VERSION_MINOR */

static void
usage				(FILE *			fp)
{
	fprintf (fp, "\
%s %s -- low-level VBI decoder\n\n\
Copyright (C) 2004, 2006 Michael H. Schimek\n\
This program is licensed under GPL 2 or later. NO WARRANTIES.\n\n\
Usage: %s [options] < sliced VBI data\n\n\
-h | --help | --usage  Print this message and exit\n\
-V | --version         Print the program version and exit\n\
Input options:\n\
-P | --pes             Source is a DVB PES stream [auto-detected]\n\
Decoding options:\n"
#if 3 == VBI_VERSION_MINOR /* XXX port me back */
"-1 | --8301            Teletext packet 8/30 format 1 (local time)\n\
-2 | --8302            Teletext packet 8/30 format 2 (PDC)\n"
#endif
"-c | --cc              Closed Caption\n\
-i | --idl             Any Teletext IDL packets (M/30, M/31)\n\
-t | --ttx             Decode any Teletext packet\n\
-v | --vps             Video Programming System (PDC)\n"
#if 3 == VBI_VERSION_MINOR /* XXX port me back */
"-w | --wss             Wide Screen Signalling\n"
#endif
"-x | --xds             Decode eXtended Data Service (NTSC line 284)\n\
-a | --all             Everything above, e.g.\n\
                       -i     decode IDL packets\n\
                       -a     decode everything\n\
                       -a -i  everything except IDL\n\
-c | --idl-ch N\n\
-d | --idl-addr NNN\n\
                       Decode Teletext IDL format A data from channel N,\n\
                       service packet address NNN [0]\n\
-r | --vps-other       Decode VPS data unrelated to PDC\n\
-p | --pfc-pgno NNN\n\
-s | --pfc-stream NN   Decode Teletext Page Function Clear data\n\
                         from page NNN (for example 1DF), stream NN [0]\n\
Modifying options:\n\
-e | --hex             With -t dump packets in hex and ASCII,\n\
                         otherwise only ASCII\n\
-n | --network         With -1, -2, -v decode CNI and print\n\
                         available information about the network\n\
-b | --bin             With -t, -p, -v dump data in binary format\n\
                         instead of ASCII\n\
-T | --time            Dump capture timestamps\n\
",
		 PROGRAM_NAME, VERSION, program_invocation_name);
}

static const char
short_options [] = "12abcd:ehil:np:rs:tvwxPTV";

#ifdef HAVE_GETOPT_LONG
static const struct option
long_options [] = {
	{ "8301",	no_argument,		NULL,		'1' },
	{ "8302",	no_argument,		NULL,		'2' },
	{ "all",	no_argument,		NULL,		'a' },
	{ "bin",	no_argument,		NULL,		'b' },
	{ "cc",		no_argument,		NULL,		'c' },
	{ "idl-addr",	required_argument,	NULL,		'd' },
	{ "hex",	no_argument,		NULL,		'e' },
	{ "help",	no_argument,		NULL,		'h' },
	{ "usage",	no_argument,		NULL,		'h' },
	{ "idl",	no_argument,		NULL,		'i' },
	{ "idl-ch",	required_argument,	NULL,		'l' },
	{ "network",	no_argument,		NULL,		'n' },
	{ "pfc-pgno",	required_argument,	NULL,		'p' },
	{ "vps-other",	no_argument,		NULL,		'r' },
	{ "pfc-stream",	required_argument,	NULL,		's' },
	{ "ttx",	no_argument,		NULL,		't' },
	{ "vps",	no_argument,		NULL,		'v' },
	{ "wss",	no_argument,		NULL,		'w' },
	{ "xds",	no_argument,		NULL,		'x' },
	{ "pes",	no_argument,		NULL,		'P' },
	{ "time",	no_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;

int
main				(int			argc,
				 char **		argv)
{
	int c;

#ifndef HAVE_PROGRAM_INVOCATION_NAME
	{
		unsigned int i;

		for (i = strlen (argv[0]); i > 0; --i) {
			if ('/' == argv[0][i - 1])
				break;
		}

		program_invocation_short_name = &argv[0][i];
	}
#endif

	setlocale (LC_ALL, "");

	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':
			option_decode_8301 ^= TRUE;
			break;

		case '2':
			option_decode_8302 ^= TRUE;
			break;

		case 'b':
			option_dump_bin ^= TRUE;
			break;

		case 'c':
			option_decode_caption ^= TRUE;
			break;

		case 'd':
			option_idl_address = strtol (optarg, NULL, 10);
			break;

		case 'e':
			option_dump_hex ^= TRUE;
			break;

		case 'a':
			option_decode_ttx = TRUE;
			option_decode_8301 = TRUE;
			option_decode_8302 = TRUE;
			option_decode_caption = TRUE;
			option_decode_idl = TRUE;
			option_decode_vps = TRUE;
			option_decode_wss = TRUE;
			option_decode_xds = TRUE;
			option_pfc_pgno = 0x1DF;
			break;

		case 'h':
			usage (stdout);
			exit (EXIT_SUCCESS);

		case 'i':
			option_decode_idl ^= TRUE;
			break;

		case 'l':
			option_idl_channel = strtol (optarg, NULL, 10);
			break;

		case 'n':
			option_dump_network ^= TRUE;
			break;

		case 'p':
			option_pfc_pgno = strtol (optarg, NULL, 16);
			break;

		case 'r':
			option_decode_vps_other ^= TRUE;
			break;

		case 's':
			option_pfc_stream = strtol (optarg, NULL, 10);
			break;

		case 't':
			option_decode_ttx ^= TRUE;
			break;

		case 'v':
			option_decode_vps ^= TRUE;
			break;

		case 'w':
			option_decode_wss ^= TRUE;
			break;

		case 'x':
			option_decode_xds ^= TRUE;
			break;

		case 'P':
			source_is_pes ^= TRUE;
			break;

		case 'T':
			option_dump_time ^= TRUE;
			break;

		case 'V':
			printf (PROGRAM_NAME " " VERSION "\n");
			exit (EXIT_SUCCESS);

		default:
			usage (stderr);
			exit (EXIT_FAILURE);
		}
	}

	if (isatty (STDIN_FILENO))
		error_exit (_("No VBI data on standard input."));

	if (0 != option_pfc_pgno) {
		pfc = vbi_pfc_demux_new (option_pfc_pgno,
					 option_pfc_stream,
					 page_function_clear_cb,
					 /* user_data */ NULL);
		if (NULL == pfc)
			no_mem_exit ();
	}

	if (0 != option_idl_channel) {
		idl = vbi_idl_a_demux_new (option_idl_channel,
					   option_idl_address,
					   idl_format_a_cb,
					   /* user_data */ NULL);
		if (NULL == idl)
			no_mem_exit ();
	}

	if (option_decode_xds) {
		xds = vbi_xds_demux_new (xds_cb,
					 /* used_data */ NULL);
		if (NULL == xds)
			no_mem_exit ();
	}

	c = getchar ();
	ungetc (c, stdin);

	if (0 == c || source_is_pes) {
		dvb = vbi_dvb_pes_demux_new (/* callback */ NULL,
					     /* user_data */ NULL);
		if (NULL == dvb)
			no_mem_exit ();

		pes_mainloop ();
	} else {
#if 2 == VBI_VERSION_MINOR /* XXX port me */
		open_sliced_read (stdin);
#endif
		old_mainloop ();
	}

	vbi_dvb_demux_delete (dvb);
	vbi_idl_demux_delete (idl);
	vbi_pfc_demux_delete (pfc);
	vbi_xds_demux_delete (xds);

	exit (EXIT_SUCCESS);

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1