#!/usr/bin/perl -w
# $Id: mmchan,v 1.8 2000/10/01 16:18:03 dick Exp $
# ----------------------------------------------------------------------
# mmchan - Convert a DVB2000 channel settings file to/from ASCII
#
# The ASCII representation is mostly compatible with DVBEdit, but not
# exactly. The differences are:
# * An additional first colum is used to mark a channel as television
#   (T) or radio (R) channel.
# * The "channel link", if present, is displayed in columns 2 and 3.
# * The fields usFlags & usTSID are interpreted as big endian 16-bit
#   numbers instead of the little (!) endian interpretation of DVBEdit.
# You can use the -c option if you want the ASCII format to be compatible
# with DVBEdit.
#
# (c) 1999-2000, Dick Streefland
# ----------------------------------------------------------------------

require "getopts.pl";

my $name = $0;
$name =~ s/.*\///;
$usage = "\
Usage: $name [<options>] <file> ...
Convert DVB2000 channel settings file(s) to/from ASCII.
Options:
   -u        convert ASCII to back to binary
   -c        use a DVBEdit compatible ASCII format
   -o<file>  specify output file

When converting to ASCII, and multiple input files are specified, the
channel settings of the first file are updated with the settings from
subsequent files. Channels that are not present in these files, will
be commented out. New channels will be added at the end of the list.
";

$opt_u = 0;
$opt_c = 0;
$opt_o = "";
if	( ! &Getopts( "uco:" ) )
{
	print STDERR "$usage";
	exit;
}

@fec = ("AUT", "1/1", "1/2", "2/3", "3/4", "???", "5/6", "7/8");
%fec = (
	"AUT" => 0,
	"1/1" => 1,
	"1/2" => 2,
	"2/3" => 3,
	"3/4" => 4,
	"???" => 5,
	"5/6" => 6,
	"7/8" => 7
	);
@spec = ("Auto", "Off", "On", "???");
%spec = (
	"Auto" => 0,
	"Off"  => 1,
	"On"   => 2,
	"???"  => 3
	);

$chan_fmt = "a4 n4 C4 n3vn C2 nv a20 a4 C a3 C2 n";
$chan_fmt =~ s/v/n/g unless $opt_c;

#  0 char szFlag[4];                 // always DVSO              0-3
#  1 unsigned short usChannelID;     // channel ID               4-5
#  2 unsigned short usPMTPID;        // PMT PID                  6-7
#  3 unsigned short usFreq;          // frequency                8-9
#  4 unsigned short usSR;            // symbol rate              a-b
#  5 unsigned char ucFEC;            // FEC flag                 c
#  6 unsigned char ucAcqBW;          // Aquisition bandwidth     d
#  7 unsigned char ucPolarity;       // polarity                 e
#  8 unsigned char ucDiseqc;         // diseqc mode              f
#  9 unsigned short usVPID;          // video PID                10-11
# 10 unsigned short usAPID;          // audio PID                12-13
# 11 unsigned short usPCRPID;        // PCR PID                  14-15
# 12 unsigned short usFlags;         // scrambling flags         16-17
# 13 unsigned short usPMC;           // PMC for CA               18-19
# 14 unsigned char ucVideo;          // video/audio mode         1a
# 15 unsigned char ucServiceType;    // service type             1b
# 16 unsigned short usTPID;          // teletext PID             1c-1d
# 17 unsigned short usTSID;          // Transport stream ID      1e-1f
# 18 unsigned char szName[20];       // channel name             20-33
# 19 unsigned char szReserved3[4];   // reserved                 34-37
# 20 unsigned char autoPIDPMT;       // auto PID(0), auto PMT(1) 38
# 21 unsigned char szReserved3[3];   // reserved                 39-3b
# 22 unsigned char link;             // channel link ?           3c
# 23 unsigned char unknown;          // unknown                  3d
# 24 unsigned short usNetworkID;     // Network ID               3e-3f
format STDOUT =
#as Erste            12603 22000 5/6 30 H 0065 0066 0064 0065 1FFF 0005 0000 PAL  N 0v  1 Auto 01 4D04 6DCA 0001
@<<<<<<<<<<<<<<<<<<< @#### @#### @<< @# @ @>>> @>>> @>>> @>>> @>>> @>>> @>>> @<<< @ @<< @ @<<< @< @>>> @>>> @>>>
{
	$c[18],					# channel name
	$c[3],					# frequency
	$c[4],					# symbol rate
	$fec[$c[5]],				# FEC flag
	$c[6],					# BW
	($c[7] ? "V" : "H"),			# polarity
	&hex4(9),				# video PID
	&hex4(10),				# audio PID
	&hex4(2),				# PMT PID & auto PID/PMT
	&hex4(11),				# PCR PID
	&hex4(16),				# teletext PID
	&hex4(13),				# PMC
	&hex4(12),				# scrambling flags
	(($c[14] & 3) == 3 ? "NTSC" : "PAL"),	# video mode
	substr( "NVLR", $c[14] >> 6, 1 ),	# audio mode
	($c[14] & 0x10 ? "12v" : "0v"),		# 12v setting
	$c[8] & 0x03,				# DiSEqC
	$spec[($c[8] >> 2) & 3],		# spec. inv.
	&hex2(15),				# service type
	&hex4(17),				# transport stream ID
	&hex4(1),				# service ID
	&hex4(24),				# network ID
}
.

if	( $opt_o ne "" )
{
	open( STDOUT, ">$opt_o" ) || die( "cannot create \"%s\"" );
}
if	( ! $opt_u )
{
	$updating = 0;
	$deleted_channels = 0;
	if	( @ARGV == 0 )
	{
		&read_chan( "-" );
	}
	else
	{
		&read_chan( shift @ARGV );
		$new_channels = 0;
		foreach $file ( @ARGV )
		{
			&update_chan( $file );
		}
		if	( $new_channels > 0 )
		{
			printf STDERR "%d channels added\n", $new_channels;
		}
		else
		{
			$count = $old_channels;
		}
	}
	&dump_chan;
	if	( $deleted_channels > 0 )
	{
		printf STDERR "%d channels deleted\n", $deleted_channels;
	}
}
else
{
	if	( @ARGV == 0 )
	{
		&undump( "-" );
	}
	else
	{
		foreach $file ( @ARGV )
		{
			&undump( $file );
		}
	}
}

sub	read_chan ()
{
	open( FP, $_[0] ) || die( "cannot open \"$_[0]\"" );
	$count = 0;
	while	( read( FP, $chan, 64 ) == 64 )
	{
		@c = unpack $chan_fmt, $chan;
		die if $c[0] ne "DVSO";
		$chan[$count] = $chan;
		$old{ &key( @c ) } = $count;
		$count++;
	}
	$old_channels = $count;
	close( FP );
}

sub	update_chan ()
{
	open( FP, $_[0] ) || die( "cannot open \"$_[0]\"" );
	$filename[$count] = $_[0];
	while	( read( FP, $chan, 64 ) == 64 )
	{
		@c = unpack $chan_fmt, $chan;
		die if $c[0] ne "DVSO";
		if	( $c[3] == 0 )		# frequency == 0 ==> marker
		{
			$chan[$count++] = $chan;
		}
		else
		{
			$key = &key( @c );
			$new{ $key } = $count;
			if	( ! defined $old{ $key } )
			{
				$chan[$count++] = $chan;
				$new_channels++;
			}
		}
	}
	close( FP );
	$updating = 1;
}

sub	key ()
{
	# Create a hashkey that identifies the channel. Things like the
	# channel name and DiSEqC setting are deliberately not included,
	# because they can be changed. Note that this key is not
	# guaranteed to be unique!
	return sprintf "%5d %d %04X %04X  %02X %04X %04X %04X",
		$_[ 3],		# frequency
		$_[ 7],		# polarity
		$_[ 2],		# PMT PID
		$_[11],		# PCR PID
		$_[15],		# service type
		$_[17],		# transport stream ID
		$_[ 1],		# service ID
		$_[24];		# network ID
}

sub	dump_chan ()
{
	for	( $i = 0; $i < $count; $i++ )
	{
		if	( defined $filename[$i] )
		{
			printf "\n";
			printf "# ----- new channels from \"%s\":\n", $filename[$i];
		}
		@c = unpack $chan_fmt, $chan[$i];
		$c[18] =~ s/\x86/[/g;
		$c[18] =~ s/\x87/]/g;
		$c[18] =~ s/\x00.*//;
		$delete = (  $updating
			  && $i < $old_channels
			  && $c[3] != 0
			  && ! defined $new{ &key( @c ) }
			  );
		$deleted_channels += $delete;
		if	( ! $opt_c )
		{
			printf ($c[15] & 2 ? "R" : "T");
			if	( $delete )
			{
				printf "#D#";
			}
			else
			{
				printf ($c[22] > 0 ? &hex2(22) : "  ");
				printf " ";
			}
		}
		elsif	( $delete )
		{
			next;
		}
		if	(  $c[3]  == 0		# frequency == 0
			&& $c[9]  == 0		# video PID == 0
			&& $c[10] == 0		# audio PID == 0
			&& $c[2]  == 0		# PMT PID == 0
			&& $c[11] == 0		# PCR PID == 0
			&& $c[16] == 0		# teletext PID == 0
			&& $c[13] == 0		# PMC == 0
			)
		{	# marker without info for positioner
			printf "%-20s\n", $c[18];
		}
		else
		{	# normal channel
			$c[2] &= 0x3fff;
			$c[2] |= $c[20] << 14;	# add auto PID/PMT bits to PMT
			write;
		}
	}
	close( FP );
}

sub	undump ()
{
	open( FP, $_[0] ) || die( "cannot open \"$_[0]\"" );
	while	( <FP> )
	{
		chomp;
		next if /^$/;
		$def_vpid = "0";
		$chanlink = 0;
		unless	( $opt_c )
		{
			next if /^#/;		# skip comment line
			next if /^.#D#/;	# skip deleted channel
			$def_vpid = "1FFF" if /^R/;
			$def_servid = (/^R/ ? 2 : 1);
			s/^[RT](..) //;
			$chanlink = hex( $1 ) if $1 ne "  ";
		}
		s/^(.{1,20})//;
		$name = $1;
		$name =~ s/\s+$//;
		$name =~ s/\[/\x86/g;
		$name =~ s/\]/\x87/g;
		$_ = "0 0 AUT 0 H $def_vpid 0 0 0 0 0 0 PAL N 0v 0 Auto $def_servid 0 0 0" if $_ eq "";
		@f = split;
# 12603 22000 5/6 30 H 0065 0066 0064 0065 1FFF 0005 0000 PAL  N 0v  0 Auto 01 4D04 6DCA 0001
# 0     1     2   3  4 5    6    7    8    9    10   11   12   1314  1516   17 18   19   20
		@c =
		(
			"DVSO",
			hex $f[19],		# service id
			hex( $f[7] ) & 0x3fff,	# PMT PID
			$f[0],			# frequency
			$f[1],			# SR
			$fec{$f[2]},		# FEC
			$f[3],			# BW
			$f[4] eq "V",		# polarity
			($spec{$f[16]} << 2)	# spec.
			| $f[15],		# DiSEqC
			hex $f[5],		# video PID
			hex $f[6],		# audio PID
			hex $f[8],		# PCR PID
			hex $f[11],		# scrambling flags
			hex $f[10],		# PCM
			3 * ($f[12] ne "PAL")		# video mode
			| index("NVLR", $f[13]) << 6	# audio mode
			| ($f[14] eq "12v") << 4,	# 12v
			hex $f[17],		# service type
			hex $f[9],		# teletext PID
			hex $f[18],		# transport stream ID
			$name,			# channel name
			"",			# reserved
			hex( $f[7] ) >> 14,	# auto PID/PMT
			"",			# reserved
			$chanlink,
			0,
			hex $f[20],		# NID
		);
		$c = pack $chan_fmt, @c;
		syswrite( STDOUT, $c, 64 );
	}
	close( FP );
}

sub	hex2
{
	sprintf "%02X", $c[$_[0]];
}
sub	hex4
{
	sprintf "%04X", $c[$_[0]];
}
