#!/usr/bin/perl
my $Version  = 'get_eit v0.01';

# Originally from myth_update_events, part of the package for myth_scanner
#
# Borrowed and heavily modified by me, Norm D.

sub usage
  {
    die <<"EndUsage";
$Version

Usage:
     -adapter <n>      : Use adapter number <num>.
     -pid <n>          : Read EIT PID <n>.
     -nice             : Process events more slowly.
     -ignore_versions  : Don't track nor use event versions numbers.
     -verbose          : Spit out a whole lot of stuff.
     -debug            : Place in debug mode - caches dvb table information for faster runs.

EndUsage
  }

use Getopt::Long;
use Carp qw(cluck);
use Time::Local;
use Time::HiRes qw( usleep );
use List::MoreUtils 'true';
use IO::Handle;
use IO::File;
use IO::Select;
use DBI;
use Data::Dumper;
use Encode;
use POSIX;
use strict;

use Linux::DVB;
use SI::Parse 0.32;

my $VERBOSE;
my %THEMECACHE;

use constant {
DMX_READ_BUF_SIZE => (32 * 1024),
TRUE              => 1,
FALSE             => 0,

# From libmyth/mythcontext.h
LP_EMERG          => 0,
LP_ALERT          => 1,
LP_CRITICAL       => 2,
LP_ERROR          => 3,
LP_WARNING        => 4,
LP_NOTICE         => 5,      
LP_INFO           => 6,  
LP_DEBUG          => 7,
};

# Debug stuff
my $_DEBUG_;
my $_TRANSCACHE_ = '/tmp/transports.dbg';
my $_EITCACHE_   = '/tmp/eit.dbg';
my $_EITRAW_     = '/tmp/eitraw.dbg';
my $_CHANCACHE_  = '/tmp/channels.dbg';
my $_MISSING_CATS_LOG_ = '/tmp/events_missing_categories.log';

# Timeout to get a complete table
my $TIMEOUT = 60 * 30; # PID 0x300 can take a while

# These are in order of preference.
my @EITPIDS = (SI::Parse::EIT_DISH9_PID,
	       SI::Parse::EIT_BEV9_PID,
	       SI::Parse::EIT_PID);

# Encoding overrides
my %ENCODINGS = (256 => 'iso-8859-1',
		 257 => 'iso-8859-1');

#These are the valid TID's for scanning channels on dish, which would be huge without this
my @valid_nids = 256, 257, 4098, 4101;

&main();

sub getArgs {
	my $adapter;
	my $iversions;
	my $pid;
	my $nice;
	my $always;
	my $log_missing_cats;
	my $help;
	my $xmlfile;
        my $p = new Getopt::Long::Parser;

        if (!$p->getoptions('adapter=i'		=> \$adapter,
			    'pid=s'		=> \$pid,
			    'nice'		=> \$nice,
			    'ignore_versions'	=> \$iversions,
			    'always'		=> \$always,
			    'log_missing_cats'	=> \$log_missing_cats,
			    'verbose'		=> \$VERBOSE,
			    'h'			=> \$help,
			    'xmlfile=s'		=> \$xmlfile,
			    'debug'		=> \$_DEBUG_)) {
		print "Invalid parameters given.\n";
		usage();
        }

	usage() if (defined($help));

	$VERBOSE = TRUE if (defined($VERBOSE));
	$_DEBUG_ = TRUE if (defined($_DEBUG_));
	$nice = TRUE if (defined($nice));
	$iversions = TRUE if (defined($iversions));

	$pid = hex($pid) if ($pid =~ /^0x/);
	$adapter = 0 if (!defined($adapter));
	$xmlfile = 'xmltv.xml' if (!defined($xmlfile));

        return ($adapter, $pid, $nice, $iversions, $always, $log_missing_cats, $xmlfile);
}

sub main {
        my($adapter, $pid, $nice, $iversions, $always, $log_missing_cats, $xmlfile) = getArgs();

	my $num_transports = 0;
	my $num_channels   = 0;
	my $num_eit        = 0;

	STDOUT->autoflush(1);

	foreach my $id (keys %SI::Parse::DISH_THEMES) {
		$THEMECACHE{$SI::Parse::DISH_THEMES{$id}} = '';
	}

	# Add some additional acceptions for BEV
	$THEMECACHE{'Series'}    = 'Series/Special';
	$THEMECACHE{"S\xe9ries"} = 'Series/Special';
	$THEMECACHE{'News'}      = 'News/Business';
	$THEMECACHE{'Nouvelles'} = 'News/Business';
	$THEMECACHE{'Film'}      = 'Movie';
	$THEMECACHE{'Music'}     = 'Music/Art';
	$THEMECACHE{'Children'}  = 'Family/Children';
	$THEMECACHE{'Enfants'}   = 'Family/Children';

#	createVersionTable();
	my $versions;

#	if (!$iversions) {
#		print "\n-> Retrieving event version information: ";
#		$versions = getVersions();
#		print "done.\n";
#	}
	
	my $t_offset = 0;
	
	preparefile($xmlfile);
	
	print "\n-> Using time offset of $t_offset.\n";

	print "\n-> Looking for network information: ";
	my($nid, $name) = getNetwork($adapter);
	print "done.\n";
	print "\t-> Found network ID $nid ($name).\n";

	if (!defined($pid)) {
		print "\n-> Finding best EIT PID: ";
		$pid = findBestPID($adapter);
		print "$pid .... done.\n";
		die("\n\nFATAL: Unable to find any available EIT PIDS, aborting!\n")
		  if (!defined($pid));
	} else {
		die("\n\nFATAL: Specified PID $pid is not available, aborting!\n")
		  if (!checkPID($adapter, $pid));
	}

	if (($pid == SI::Parse::EIT_PID) && (($name =~ /Nimiq/) || ($name =~ /EchoStar/))) {
		print sprintf("\n\nWARNING: Scanning on PID 0x%02x which only includes now/next guide data. ".
		  "If you're wanting to scan the 9-day guide, ensure your DVB adapter ".
		  "is tuned to the appropriate network/transport.\n\n", $pid);
	}

	print sprintf("\n-> Scanning on pid 0x%x.\n", $pid);

	print "\n-> Scanning for transports:  ";
	print "\n" if ($VERBOSE);
	my $transports = getTransports($adapter);
	print "\010done.\n";

	foreach my $nit (keys %{$transports}) {
		$num_transports += keys %{$$transports{$nit}};
	}

	print "\t-> Found $num_transports transport(s).\n\n";

	print "-> Scanning for channels:  ";
	print "\n" if ($VERBOSE);
	my ($channels, $miss) = getChannels($adapter, $transports);
	print "\010done.\n";

	foreach my $nit (keys %{$channels}) {
		foreach my $tid (keys %{$$channels{$nit}}) {
			$num_channels += keys %{$$channels{$nit}->{$tid}};
		}
	}
	print "\t-> Found $num_channels channel(s)";
	print ", $miss rescanned section(s).\n\n";

	my $cache;
	print "-> Scanning for EIT data:  ";
	print "\n" if ($VERBOSE);
	($cache, $versions) = getEvents($adapter, $channels, $versions, $pid, $iversions);

	my $n_items = $#{$cache};

	print "\010done, found ".($n_items + 1)." event tables to process.\n\n";

	print "-> Processing EIT data:  ";
	print "\n" if ($VERBOSE);
	my $updated = processEvents($cache, $t_offset, $nice, $always, $log_missing_cats);
	print "\010done, $updated programs added/updated.\n";

	print "\n";
	
	closefile();

}

sub cleanupString {
	my $string = shift;

	$string =~ s/^\s+//;
	$string =~ s/\s+$//;

	return $string;
}

############################################################################
# DVB functions                                                            #
############################################################################

sub getTransports {
	my $adapter = shift;
	my %transports;
	my $all_seen   = FALSE;
	my $scan_start = FALSE;
	my $count = 0;
	my $select = new IO::Select;
	my $nit_ver;
	my %networks;

	if ($_DEBUG_ && (-f $_TRANSCACHE_)) {
		return getHash($_TRANSCACHE_);
	}

	my @sparkle = qw( \ | / - );
	my $cur_sparkle = 0;

	my $dvb_sup = new SI::Parse($_DEBUG_);

	my $pid = SI::Parse::NIT_PID;

	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";

	$dmx->buffer(DMX_READ_BUF_SIZE);
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
	$dmx->blocking(0);

	$select->add($dmx->fh);

	my $now = time();

	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
		my @ready = $select->can_read(5);
		my $handle = pop @ready;

		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);

		my $section;

		sysread($handle, $section, DMX_READ_BUF_SIZE);

		my $table = $dvb_sup->decodeSection($section);

		next if (!defined($table));
		next if ($$table{'table_id'} == SI::Parse::STUFFING_TABLE);

		$nit_ver = $$table{'version_number'} if (!defined($nit_ver));

		die("\n\nFATAL: NIT version number changed while scanning!\n")
		  if ($nit_ver != $$table{'version_number'});

		my $nid = $$table{'network_id'};
		
		next if ($nid != 256 && $nid != 257 && $nid != 4098 && $nid != 4101 && $nid != 4102);
		
		foreach my $net (@{$$table{'network_descriptors'}}) {
			if ($$net{'dvb_descriptor_tag'} == SI::Parse::NETWORK_NAME_DESCRIPTOR) {
				$networks{$nid}++;
				$scan_start = TRUE;
			}
		}

		next if (!$scan_start);

		foreach my $trans (@{$$table{'transport_descriptors'}}) {
			my $tid  = $$trans{'transport_stream_id'};

			foreach my $desc (@{$$trans{'descriptors'}}) {
				next if ($$desc{'dvb_descriptor_tag'} !=
				  SI::Parse::SAT_DELIVERY_DESCRIPTOR);

				my $freq = $$desc{'frequency'};
				my $sr   = $$desc{'symbol_rate'};

				my $pol;
				for ($$desc{'polarization'}) {
					if ($_ == SI::Parse::POL_HORIZ) {$pol = 'h'}
					if ($_ == SI::Parse::POL_VERT)  {$pol = 'v'}
					if ($_ == SI::Parse::POL_LEFT)  {$pol = 'l'}
					if ($_ == SI::Parse::POL_RIGHT) {$pol = 'r'}
					if ($_ >  SI::Parse::POL_RIGHT) {
						print "WARNING: Unknown polarity '$_' found!\n";
					}
				}

				my $mod;
				for($$desc{'modulation'}) {
					if ($_ == SI::Parse::MOD_QPSK_0) {$mod = 'qpsk'}
					if ($_ == SI::Parse::MOD_QPSK_1) {$mod = 'qpsk'}
					if ($_ == SI::Parse::MOD_8PSK)  {$mod = '8psk'}
					if ($_ == SI::Parse::MOD_TQPSK) {$mod = 't_qpsk'}
					if ($_ >  SI::Parse::MOD_TQPSK) {
						print "WARNING: Unknown modulation '$_' found!\n";
					}
				}

				my $fec;
				for($$desc{'fec_inner'}) {
					if ($_ == SI::Parse::FEC_1_2) {$fec = '1/2'}
					if ($_ == SI::Parse::FEC_2_3) {$fec = '2/3'}
					if ($_ == SI::Parse::FEC_3_4) {$fec = '3/4'}
					if ($_ == SI::Parse::FEC_4_5) {$fec = '4/5'}
					if ($_ == SI::Parse::FEC_5_6) {$fec = '5/6'}
					if ($_ == SI::Parse::FEC_7_8) {$fec = '7/8'}
					if ($_ == SI::Parse::FEC_8_9) {$fec = '8/9'}
					if ($_ == SI::Parse::FEC_3_5) {$fec = '3/5'}
					if ($_ == SI::Parse::FEC_4_5) {$fec = '4/5'}
					if ($_ == SI::Parse::FEC_9_10) {$fec = '9/10'}
					if (!$_ || $_ >  SI::Parse::FEC_8_9) {
						print "WARNING: Unknown fec '$_' found!\n";
					}
				}

				$count++;

				if (!defined($transports{$nid}->{$freq}->{'tids'}->{$tid})) {
					if ($VERBOSE) {
						print "\t-> TRANSPORT: $nid $tid $freq $pol $mod $sr $fec\n";
					} else {
						$cur_sparkle = ($cur_sparkle + 1) % @sparkle;
						print "\010$sparkle[$cur_sparkle]" if (!($count % 10));
					}

					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'polarity'} = $pol;
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'modulation'} = $mod;
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'symbolrate'} = $sr;
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'fec'} = $fec;
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'count'} = 0;
				} else {
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'count'}++;

					if (!$VERBOSE) {
						$cur_sparkle = ($cur_sparkle + 1) % @sparkle;
						print "\010$sparkle[$cur_sparkle]" if (!($count % 10));
					}
				}
			}
		}

		$all_seen = TRUE;

		foreach my $_nid (keys %networks) {
			if ($networks{$_nid} < 3) {
				$all_seen = FALSE;
				last;
			}
		}
	}

	$dmx->stop();

	if (!$all_seen) {
		die("FATAL: Timeout while reading transport tables, aborting.\n");
	}

	saveHash($_TRANSCACHE_, \%transports) if ($_DEBUG_);

	return \%transports;
}

sub getChannels {
	my $adapter = shift;
	my $transports = shift;
	my $count = 0;
	my $num_chan = 0;
	my $num_found = 0;
	my %channels;
	my %_transports;
	my $all_seen   = FALSE;
	my $scan_start = FALSE;
	my $select = new IO::Select;
	my $last_section = 0;
	my $sdt_ver;
	my $miss_errors = 0;
	my $percent = 0;

	if ($_DEBUG_ && (-f $_CHANCACHE_)) {
		return (getHash($_CHANCACHE_), 0, 0);
	}

	foreach my $_nid (keys %{$transports}) {
		foreach my $_freq (keys %{$$transports{$_nid}}) {
			foreach my $_tid (keys %{$$transports{$_nid}->{$_freq}->{'tids'}}) {
				$_transports{$_nid}->{$_tid}->{'got_channels'} = FALSE;
				$num_chan++;
			}
		}
	}

	print "   ";

	my $dvb_sup = new SI::Parse($_DEBUG_);

	my $pid = SI::Parse::SDT_PID;

	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";

	$dmx->buffer(DMX_READ_BUF_SIZE);
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
	$dmx->blocking(0);

	$select->add($dmx->fh);

	my $now = time();

	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
		my @ready = $select->can_read(5);
		my $handle = pop @ready;

		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);

		$count++;

		my $section;

		sysread($handle, $section, DMX_READ_BUF_SIZE);

		my $table = $dvb_sup->decodeSection($section);

		next if (!defined($table));
		next if ($$table{'table_id'} == SI::Parse::STUFFING_TABLE);

		my $c_nid = $$table{'original_network_id'};
		my $c_tid = $$table{'transport_stream_id'};

		$sdt_ver = $$table{'version_number'} if (!defined($sdt_ver));

		die("\n\nFATAL: SDT version number changed while scanning!\n")
		  if ($sdt_ver != $$table{'version_number'});

		$scan_start = TRUE  if ($$table{'section_number'} == 0);
		next if (!$scan_start);

		if ($$table{'section_number'} &&
		    (($$table{'section_number'} - $last_section) != 1)) {
			$miss_errors++;
		}

		if ($$table{'section_number'} > $$table{'last_section_number'}) {
			$miss_errors++;
		}

		if (!defined($_transports{$c_nid}->{$c_tid})) {
			#print "\n\nWARNING: Found channel defined for non-existant ".
			 # "transport $c_nid/$c_tid, ignoring.\n";
			next;
		}

		$last_section = $$table{'section_number'};

		if (!$VERBOSE) {
			if (!($count % 10)) {
				my $_percent = ($num_found / $num_chan) * 100;
				if ($_percent != $percent) {
					print sprintf("\010\010\010\010%3i\%", ($_percent, 3));
					$percent = $_percent;
				}
			}
		}

		next if ($_transports{$c_nid}->{$c_tid}->{'got_channels'});

		if (!defined($_transports{$c_nid}->{$c_tid}->{'sections'})) {
			for (my $x = 0; $x <= $$table{'last_section_number'}; $x++) {
				$_transports{$c_nid}->{$c_tid}->{'sections'}->{$x} = TRUE;
			}
		}

		foreach my $svc (@{$$table{'services'}}) {
			my $sid = $$svc{'service_id'};
			my $eit_schedule_flag = $$svc{'eit_schedule_flag'};
			my $callsign;
			my $name;
			my $service_type;

			foreach my $desc (@{$$svc{'service_descriptors'}}) {
				if ($$desc{'dvb_descriptor_tag'} == SI::Parse::SERVICE_DESCRIPTOR) {
					$callsign = $$desc{'service_name'};
					$name = $$desc{'service_provider_name'};
					$service_type = $$desc{'service_type'};
				} # We don't care about the other descriptors - for now
			}

			if (!defined($channels{$c_nid}->{$c_tid}->{$sid})) {
				if ($VERBOSE) {
					print "\t-> Channel: $c_nid tp: $c_tid $sid ".
					  "'$callsign' \"$name\" Type $service_type\n";
				}
				$callsign =~ s/\&/&amp;/g;
				$name =~ s/\&/&amp;/g;
				print XMLFILE "\t<channel id=\"$sid-$c_nid.geteit\">\n";
				print XMLFILE "\t\t<display-name>$callsign</display-name>\n";
				print XMLFILE "\t\t<display-name>$sid $callsign</display-name>\n";
				print XMLFILE "\t\t<display-name>$sid</display-name>\n";
				print XMLFILE "\t\t<display-name>$name</display-name>\n";
				print XMLFILE "\t</channel>\n";
				$channels{$c_nid}->{$c_tid}->{$sid}->{'callsign'} = $callsign;
				$channels{$c_nid}->{$c_tid}->{$sid}->{'name'} = $name;
				$channels{$c_nid}->{$c_tid}->{$sid}->{'eit_schedule_flag'} = $eit_schedule_flag;
				$channels{$c_nid}->{$c_tid}->{$sid}->{'service_type'} = $service_type;
				$channels{$c_nid}->{$c_tid}->{$sid}->{'used'} = 0;
			} else {
				$channels{$c_nid}->{$c_tid}->{$sid}->{'used'}++;
			}
		}

		delete $_transports{$c_nid}->{$c_tid}->{'sections'}->{$$table{'section_number'}};

		if (defined($_transports{$c_nid}->{$c_tid}->{'sections'}) &&
		    !keys %{$_transports{$c_nid}->{$c_tid}->{'sections'}}) {
			$num_found++ if (defined($_transports{$c_nid}->{$c_tid}));
			$_transports{$c_nid}->{$c_tid}->{'got_channels'} = TRUE;
		}

		$all_seen = TRUE;

		foreach my $_nid (keys %_transports) {
			foreach my $_tid (keys %{$_transports{$_nid}}) {
				$all_seen = FALSE if (!$_transports{$_nid}->{$_tid}->{'got_channels'});
				last if (!$all_seen);
			}

			last if (!$all_seen);
		}
	}

	$dmx->stop();

	if (!$all_seen) {
		die("FATAL: Timeout while reading channel tables, aborting.\n");
	}

	saveHash($_CHANCACHE_, \%channels) if ($_DEBUG_);

	print "\010\010\010";

	return (\%channels, $miss_errors);
}

sub checkPID {
	my $adapter = shift;
	my $pid = shift;
	my $select = new IO::Select;

	my $dvb_sup = new SI::Parse($_DEBUG_);

	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";

	$dmx->buffer(DMX_READ_BUF_SIZE);
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
	$dmx->blocking(0);

	$select->add($dmx->fh);

	my @ready = $select->can_read(2);
	my $handle = pop @ready;

	$dmx->stop();

	return defined($handle) ? TRUE : FALSE;
}

sub findBestPID {
	my $adapter = shift;

	foreach my $pid (@EITPIDS) {
		return $pid if (checkPID($adapter, $pid));
	}

	return undef;
}

sub getEvents {
	my $adapter = shift;
	my $channels = shift;
	my $versions = shift;
	my $pid = shift;
	my $iversions = shift;
	my @cache;
	my $count = 0;
	my $num_chan = 0;
	my $num_found = 0;
	my $all_seen   = FALSE;
	my $has_eit    = FALSE;
	my %_channels;
	my %_events;
	my $select = new IO::Select;
	my $num = 0;
	my %versions_t;
	my $percent = 0;

	if ($_DEBUG_ && (-f $_EITRAW_)) {
		return getHash($_EITRAW_);
	}

	foreach my $nid (keys %{$channels}) {
		foreach my $tid (keys %{$$channels{$nid}}) {
			foreach my $sid (keys %{$$channels{$nid}->{$tid}}) {
				if ($$channels{$nid}->{$tid}->{$sid}->{'eit_schedule_flag'}) {
					my $nts = sprintf("%04x%04x%04x", $nid, $tid, $sid);
					my %empty = ('ZZ' => 1);
					$_channels{$nts} = \%empty;
					$num_chan++;
				}
			}
		}
	}

	print "   ";

	my $dvb_sup = new SI::Parse($_DEBUG_);

	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter0/demux0";

	$dmx->buffer(DMX_READ_BUF_SIZE);
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
	$dmx->blocking(0);

	$select->add($dmx->fh);
	
	my $now = time();
	
	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
		my $skip_event = FALSE;
		my @ready = $select->can_read(5);
		my $handle = pop @ready;

		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);

		my $section;
	
		sysread($handle, $section, DMX_READ_BUF_SIZE);
		next if (!$section);
		my $sec_a = to_hex($section);
		my $tableid = $$sec_a[0];
		my $sid   = ($$sec_a[3]  << 0x08) + $$sec_a[4];
		my $version = ($$sec_a[5] & 0x7c) >> 0x01;
		my $sec_num  = $$sec_a[6];
		my $sec_last = $$sec_a[7];
		my $c_tid = ($$sec_a[8]  << 0x08) + $$sec_a[9];
		my $c_nid = ($$sec_a[10] << 0x08) + $$sec_a[11];
		my $last_seg  = $$sec_a[12];
		my $last_tbid = $$sec_a[13];
		my $nts = sprintf("%04x%04x%04x", $c_nid, $c_tid, $sid);
		my $ts = sprintf("%02x%02x", $tableid, $sec_num);
		$count++;

		if (!$VERBOSE) {
			if (!($count % 10)) {
				my $_percent = ($num_found / $num_chan) * 100;
				if ($_percent != $percent) {
					print sprintf("\010\010\010\010%3i\%", ($_percent, 3));
					$percent = $_percent;
				}
			}
		}
		next if (!defined($_channels{$nts}));
		
		if (!$iversions) {
			$skip_event = TRUE if ($$versions{$c_nid}->{$c_tid}->{$sid}->{$tableid} == $version);
			$versions_t{$c_nid}->{$c_tid}->{$sid}->{$tableid} = $version;
		}

		if (defined($_channels{$nts}->{'ZZ'})) {
			my $low_id = 0;

			if (($tableid == SI::Parse::EIT_ACTUAL_TRANSPORT) ||
			    ($tableid == SI::Parse::EIT_OTHER_TRANSPORT)) {
				$low_id = $tableid;
			} elsif (($tableid >= SI::Parse::EITS_ACTUAL_LOW) &&
				 ($tableid <= SI::Parse::EITS_ACTUAL_HIGH)) {
				$low_id = SI::Parse::EITS_ACTUAL_LOW;
			} elsif (($tableid >= SI::Parse::EITS_OTHER_LOW) &&
				 ($tableid <= SI::Parse::EITS_OTHER_HIGH)) {
				$low_id = SI::Parse::EITS_OTHER_LOW
			} elsif (($tableid >= SI::Parse::DISH_EVENT_EXT_LOW) &&
				 ($tableid <= SI::Parse::DISH_EVENT_EXT_HIGH)) {
				$low_id = SI::Parse::DISH_EVENT_EXT_LOW
			} else {
				print "\n\nWARNING: Invalid table id $tableid found. Unable to continue.\n";
				next;
			}

			for (my $y = $low_id; $y <= $last_tbid; $y++) {
				for (my $x = 0; $x <= $sec_last; $x++) {
					my $_ts = sprintf("%02x%02x", $y, $x);
					$_channels{$nts}->{$_ts} = TRUE;
					delete $_channels{$nts}->{'ZZ'};
				}
			}
		}

		delete $_channels{$nts}->{$ts};

		if (!($count % 100)) {
			$all_seen = TRUE;
			$num_found = 0;

			foreach my $_nts (keys %_channels) {
				$all_seen = FALSE if (defined($_channels{$_nts}->{'ZZ'}));
				$num_found++ if (!keys %{$_channels{$_nts}});
			}
		}

		if (!defined($_events{$nts.$ts})) {
			push @cache, $section if (!$skip_event);
			$num++;
			$_events{$nts.$ts}++;
		}

		$has_eit = TRUE;
	}

	if (!$has_eit) {
		if ($VERBOSE) {
			print "WARNING: No EIT tables were found.\n";
		} else {
			print "\010\010\010 - no EIT tables were found:  ";
		}
	}

	$dmx->stop();

	if (!$all_seen && $has_eit) {
		die("FATAL: Timeout while reading event tables, aborting.\n");
	}

	print "\010\010\010";

	saveHash($_EITRAW_, \@cache) if ($_DEBUG_);

	return \@cache, \%versions_t;
}

sub processEvents {
	my $cache = shift;
	my $t_offset = shift;
	my $nice = shift;
	my $always = shift;
	my $log_missing_categories = shift;
	my $count = 0;
	my $updated = 0;
	my $log_missing = 0;

	my $num_entries = $#{$cache} + 1;

	print "   ";

	my $dvb_sup = new SI::Parse($_DEBUG_);

	foreach my $section (@{$cache}) {
		my $table = $dvb_sup->decodeSection($section);

		next if (!defined($table));
		next if ($#{$$table{'events'}} == -1);

		my $tableid     = $$table{'table_id'};
		my $serviceid   = $$table{'service_id'};
		my $networkid   = $$table{'original_network_id'};
		my $transportid = $$table{'transport_stream_id'};

		foreach my $_event (@{$$table{'events'}}) {
			next if (!$$_event{'descriptors_loop_length'});

			my $title;
			my $description;
			my $properties;
			my $programinfo;
			my $mpaainfo;
			my $vchipinfo;
			my $rightsinfo;
			my $c_theme;
			my $c_category;
			my $language;
			my $upped;

			my $starttime_t = timegm($$_event{'start_second'},
						    $$_event{'start_minute'},
						    $$_event{'start_hour'},
						    $$_event{'start_day'},
						    ($$_event{'start_month'}-1),
						    $$_event{'start_year'});

			my $duration = ($$_event{'duration_hour'} * 60 * 60) +
				       ($$_event{'duration_minute'} * 60) +
				       $$_event{'duration_second'};
			my $starttime = realDate($starttime_t);
			my $endtime = realDate($starttime_t + $duration);

			foreach my $desc (@{$$_event{'event_descriptors'}}) {
				my $tag = $$desc{'dvb_descriptor_tag'};

				if ($tag == SI::Parse::SHORT_EVENT_DESCRIPTOR) {
					$title = $$desc{'event_name'};
				} elsif ($tag == SI::Parse::EXTEND_EVENT_DESCRIPTOR) {
					$description = $$desc{'text'};
					$language = $$desc{'iso639_2_language_code'};
				} elsif ($tag == SI::Parse::DISH_EVENT_NAME) {
					$title = $dvb_sup->decompressDishEventName($tableid, $$desc{'descriptor_data'});
				} elsif ($tag == SI::Parse::DISH_EVENT_DESCRIPTION) {
					$description = $dvb_sup->decompressDishEventDescription($tableid, $$desc{'descriptor_data'});
				} elsif ($tag == SI::Parse::DISH_EVENT_PROPERTIES) {
					$properties = $dvb_sup->decompressDishEventProperties($tableid, $$desc{'descriptor_data'});
				} elsif ($tag == SI::Parse::CONTENT_DESCRIPTOR) {
					$c_theme = $dvb_sup->dishContentTheme($$desc{'content_nibble_level_2'});
					$c_category = $dvb_sup->dishContentCategory($$desc{'user_nibble'});
					#$c_category = substr($description,1,index($description,"."));
					if (!defined($c_category) && $log_missing_categories) {
						$log_missing = $$desc{'user_nibble'};
					}
				} elsif ($tag == SI::Parse::DISH_EVENT_PROGRAMID) {
					$programinfo = $desc;
				} elsif ($tag == SI::Parse::DISH_EVENT_MPAA_FLAGS) {
					$mpaainfo = $desc;
				} elsif ($tag == SI::Parse::DISH_EVENT_VCHIP_FLAGS) {
					$vchipinfo = $desc;
				} elsif ($tag == SI::Parse::TIER_EVENT_RIGHTS) {
					$rightsinfo = $desc;
				} 
			}

			if ($log_missing) {
				logMissingCategory($log_missing, $c_theme, $title, $description);
			}

			my $p_event = processEvent($title, $description);
			my $p_properties = processProperties($properties);

			my %event;

			$event{'networkid'}   = $networkid;
			#$event{'chanids'}     = ;
			$event{'starttime'}   = $starttime;
			$event{'endtime'}     = $endtime;
			$event{'title'}       = $$p_event{'title'};
			$event{'subtitle'}    = $$p_event{'subtitle'};
			$event{'description'} = $$p_event{'description'};
			$event{'language'}    = substr($language,0,2);
			#if (defined($$p_event{'actors'})) { my @actors = @{$$p_event{'actors'}};}
			
			if ($c_theme && $c_category) {
				$event{'category_type'} = $c_theme;
				$event{'category'} = $c_category;
			} elsif ($$p_event{'theme'}) {
				$event{'category'} = $event{'category_type'}
				  = $$p_event{'theme'};
			} else {
				$event{'category'} = $event{'category_type'}
				  = 'Unknown';
			}

			if ($programinfo) {
				if ($$programinfo{'originalairdate_day'}) {
					$event{'originalairdate'} = simpleDate($$programinfo{'originalairdate_month'},
									       $$programinfo{'originalairdate_day'},
									       $$programinfo{'originalairdate_year'});


					if (dateLessThan($$programinfo{'originalairdate_month'},
							 $$programinfo{'originalairdate_day'},
							 $$programinfo{'originalairdate_year'},
							 (time() + $t_offset))) {
						$event{'previouslyshown'} = 1;
					} else {
						$event{'previouslyshown'} = 0;
					}
					$event{'airdate'} = $$programinfo{'originalairdate_year'};
				}

				$event{'programid'} = $$programinfo{'programid'};
				$event{'syndicatedepisodenumber'} = $$programinfo{'episode'};
				if (($$programinfo{'seriesid'} !~ /^MV/) &&
				    ($$programinfo{'seriesid'} !~ /^SP/)) {
					$event{'seriesid'} = $$programinfo{'seriesid'};
				}
			} elsif ($$p_event{'tear'}) {
				$event{'airdate'} = $$p_event{'year'};
			}

			my $audioprop;
			my $videoprop;
			my $subtitletypes;
			if ($p_properties) {
				if ($$p_properties{'stereo'}) {
					$audioprop .= ',STEREO';
					$event{'stereo'} = 1;
				}
				if ($$p_properties{'closedcaptioned'}) {
					$audioprop .= ',HARDHEAR';
					$subtitletypes .= ',HARDHEAR';
					$event{'closecaptioned'} = 1;
				}
			} else {
				if ($$p_event{'stereo'}) {
					$audioprop .= ',STEREO';
					$event{'stereo'} = 1;
				}
				if ($$p_event{'closedcaptioned'}) {
					$audioprop .= ',HARDHEAR';
					$subtitletypes .= ',HARDHEAR';
					$event{'closecaptioned'} = 1;
				}
			}

			if ($$p_event{'hdtv'}) {
				$videoprop .= ',HDTV';
			}
			$audioprop =~ s/^\,//;
			$videoprop =~ s/^\,//;
			$subtitletypes =~ s/^\,//;

			$event{'audioprop'} = $audioprop;
			$event{'videoprop'} = $videoprop;
			$event{'subtitletypes'} = $subtitletypes;

			if ($mpaainfo) {
				if ($$mpaainfo{'star_raw'}) {
					$event{'stars'} = $$mpaainfo{'star_raw'};
				}
			}
			print XMLFILE "\t<programme start=\"$event{'starttime'} -0400\" stop=\"$event{'endtime'} -0400\" channel=\"$serviceid-$networkid.geteit\">\n";
			print XMLFILE "\t\t<title lang=\"$event{'language'}\">$event{'title'}</title>\n";
			print XMLFILE "\t\t<desc lang=\"$event{'language'}\">\n\t\t\t$event{'description'}\n\t\t</desc>\n";
			print XMLFILE "\t\t<category lang=\"$event{'language'}\">$event{'category'}</category>\n" if ($event{'category'});
			if ($$p_event{'actors'}) {
			    print XMLFILE "\t\t<credits>\n";
			    foreach my $actor (@{$$p_event{'actors'}}) {
			      print XMLFILE "\t\t\t<actor>\"$actor\"</actor>\n"
			    }
			    print XMLFILE "\t\t</credits>\n";
			}
			print XMLFILE "\t\t<date>$event{'originalairdate'}</date>\n" if ($event{'originalairdate'});
			print XMLFILE "\t\t<video>\n\t\t\t<quality>$event{'videoprop'}</quality>\n\t\t</video>\n" if ($event{'videoprop'});
			print XMLFILE "\t\t<episode-num system=\"xmltv_ns\">$event{'syndicatedepisodenumber'}</episode-num>\n" if ($event{'syndicatedepisodenumber'});
			print XMLFILE "\t\t<star-rating>\n\t\t\t<value>$event{'stars'}</value>\n\t\t</star-rating>\n" if ($event{'stars'});
			print XMLFILE "\t</programme>\n";
			
#print "channel id=$serviceid-$networkid.geteit\n";
#print "Program ID: $event{'programid'}\n";
#print "Start time: $event{'starttime'}\n";
#print "Duration:   $starttime_t    $duration\n";
#print "End Time:   $event{'endtime'}\n";
#print "Title: $event{'title'}\n";
#print "SubTitle: $event{'subtitle'}\n";
#print "Subtitle types: $event{'subtitletypes'}\n";
#print "Description: $event{'description'}\n";
#print "Language: $event{'language'}\n";
#print "Theme: $event{'theme'}\n";
#print "HDTV: $event{'hdtv'}\n";
#print "Year: $event{'year'}\n";
#print "Stereo: $event{'stereo'}\n";
#print "Closed Captioned: $event{'closedcaptioned'}\n";
#print "Category: $event{'category'}\n";
#print "Category types: $event{'category_types'}\n";
#print "Original Airdate: $event{'originalairdate'}\n";
#print "Previously Shown: $event{'previouslyshown'}\n";
#print "Air Date: $event{'airdate'}\n";
#print "Syndicated episode ID: $event{'syndicatedepisodeid'}\n";
#print "Series ID: $event{'seriesid'}\n";
#print "Stars: $event{'stars'}\n";
#print "MPAA info: $$mpaainfo{'star_raw'}\n";
#print "VCHIP info: $vchipinfo\n";
#print "Rights info: $rightsinfo\n";
#print "Audio prop: $event{'audioprop'}\n";
#print "video prop: $event{'videoprop'}\n";

#print "----------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n";
			$updated++ if ($upped);

			usleep(50000) if ($nice);
		}

		$count++;

		if ($VERBOSE) {
			print "\t-> EIT Data: $networkid tp: $transportid $serviceid\n";
		} else {
			if (!($count % 10)) {
				print sprintf("\010\010\010\010%3i\%",
				  (($count / $num_entries) * 100), 3);
			}
		}
	}

	print "\010\010\010";
	return $updated;
}

sub getNetwork {
	my $adapter = shift;
	my $dvb_sup = new SI::Parse;
	my $network_name;
	my $network_id;
	my $found = FALSE;
	my $select = new IO::Select;

	my $pid = SI::Parse::NIT_PID;

	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";

	$dmx->buffer(DMX_READ_BUF_SIZE);
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
	$dmx->blocking(0);

	$select->add($dmx->fh);

	my $now = time();

	while (!$found && (time() <= ($now + $TIMEOUT))) {
		my @ready = $select->can_read(5);
		my $handle = pop @ready;

		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);

		my $section;

		sysread($handle, $section, DMX_READ_BUF_SIZE);

		my $table = $dvb_sup->decodeSection($section);

		next if (!$table);
		next if ($$table{'table_id'} != SI::Parse::NIT_ACTUAL_NETWORK);

		$network_id = $$table{'network_id'};

		foreach my $net (@{$$table{'network_descriptors'}}) {
			if ($$net{'dvb_descriptor_tag'} == SI::Parse::NETWORK_NAME_DESCRIPTOR) {
				$network_name = $$net{'network_name'};
			}

			$found = TRUE;
		}
	}

	$dmx->stop();

	if (!$found) {
		die("FATAL: Timeout while reading transport tables, aborting.\n");
	}

	return($network_id, $network_name);
}

sub hex_to_array {
	my $string = shift;
	my @array;
	my $octet;

	foreach my $nibble (split(//, $string)) {
		$octet .= $nibble;

		if (length($octet) == 2) {
			push @array, hex($octet);
			$octet = undef;
		}
	}

	return \@array;
}

sub to_hex {       
	my $string = shift;
	my @array = map(ord, split(//, $string));

	return \@array;
}

sub table {               
	my $buf = shift;        
	my $start = shift;
	my $len = shift;        
	my @table; 
	my $overflow = 0;       

	for (my $i = $start; $i < ($start + $len); $i++) {
		if ($i <= $#{$buf}) {          
			push @table, $$buf[$i];
		} else {
			$overflow++;
		}
	}

	if ($overflow) {        
		#print "WARNING: Table copy overflowed by $overflow: start == $start, len = $len:\n";
		#print hex_ascii($buf);
	}

	return \@table;         
}


# Real simple date calculator
sub dateLessThan {
	my $omonth = shift;
	my $oday = shift;
	my $oyear = shift;
	my $now = shift;

	my @t = gmtime($now);
	my $year = shift @t;
	my $month = shift @t;
	my $day = shift @t;

	my $a = sprintf("%04i%02i%02i", $year, $month, $day);
	my $b = sprintf("%04i%02i%02i", $oyear, $omonth, $oday);

	return ($b < $a);
}

sub simpleDate {
	my $month = shift;
	my $day = shift;
	my $year = shift;

	return sprintf("%02i-%02i-%02i", $year, $month, $day);
}

sub processEvent {
	my $title = shift;
	my $description = shift;
	my %event;

	$title = cleanupString($title);
	$description = cleanupString($description);

	if ($title =~ /\(HD\)/) {
		$event{'hdtv'} = TRUE;
		$title =~ s/\(HD\)//;
	}

	if ($title =~ /^HD\s*\-\s*/) {
		$event{'hdtv'} = TRUE;
		$title =~ s/^HD\s*\-\s*//;
	}

	if ($title =~ /^HD\s+/) {
		$event{'hdtv'} = TRUE;
#		$title =~ s/^HD\s+//;
	}

	$title =~ s/\s\(All\sDay.*\)$//;
	$title =~ s/\:\s*$//;
	$title =~ s/\s(5|7)\.1$//;
	$title =~ s/\s\-\sPremier\sRelease//;
	$title =~ s/\s\-\sPremier//;
	$title =~ s/\:\sSortie\sPrimeur$//;
	$title =~ s/\:\sPrimeur$//; # Two different spellings?
	$title =~ s/\:\sPrimuer$//;
	$title =~ s/\:\sPrime$//;
	$title =~ s/\s*\(RC\)//;
	$title =~ s/\,\sv\..{1,3}$//;
	$title =~ s/\(v\..{1,3}\.*\)//;
	$title =~ s/\&/\&amp;/g;
	$title =~ s/\s*\(INFO\)//;

	$event{'title'} = $title;

	($event{'subtitle'}, $description) = split(/\s*\x0d/, $description, 2)
	  if ($description =~ /\x0d\s/);

	if ((length($event{'title'}) > 15) &&
	    (length($event{'subtitle'}) > length($event{'title'})) &&
	    (index($event{'subtitle'}, $event{'title'}) >= 0) &&
	    (length($event{'subtitle'}) - length($event{'title'}) < 7)) {
		$event{'subtitle'} = '';
	}

	my $position = index($description, '.');
	my $after = substr($description, ($position + 1), 1);
	if ((($position < length($description)) && ($after eq ' ')) ||
	    (($position + 1) == length($description))) {
		if (($position > 0) && ($position <= 20)) {
			my $old_description = $description;
			($event{'theme'}, $description) = split(/\./, $description, 2);
			$event{'theme'} =~ s/^\s//;
			if ($event{'theme'} =~ /-/) {
			  $description = $old_description;
			  delete $event{'theme'}; }
			elsif ($THEMECACHE{$event{'theme'}}) {
			  $event{'theme'} = $THEMECACHE{$event{'theme'}};
			 }
		}
	}

	$event{'subtitle'} =~ s/^All\sDay\s\(.*\sEastern\)\s*$//;

	if ($event{'subtitle'} =~ /Free\spreview\suntil/) {
		$event{'subtitle'} = '';
	};
	$event{'subtitle'} =~ s/\&/\&amp;/g;

	my @actors;

	$description =~ s/.*SAME\sDAY\sAS\sDVD\!//;
	$description =~ s/.*SAME\sAS\sDVD\!//;
	$description =~ s/.*jour\sque\sle\sDVD\!//;
	$description =~ s/^\s*\-\.\s*//;
	$description =~ s/\&/\&amp;/g;

	if ($description =~ /\s\s\((\d{4})\)/) { # year
		$event{'year'} = $1;

		my $left;
		($left, $description) = split(/\(\d{4}\)/, $description, 2);
		$left = cleanupString($left);

		$description = cleanupString($description);

		if ($left && ($left !~ /Off\sAir/) && ($left !~ /\s\-\s/) &&
		    ($left !~ /^\d/) && ($left !~ /\!/) && ($left !~ /\$/)) { # actors
			$left = cleanupString($left);
			$left =~ s/\.$//;
			my @_actors = split(/\,\s/, $left);
			@actors = (@actors, @_actors);
		}
	}

	# We may have actors even without a year
	if ($description =~ /Voice\sof\:\s/) {
		my $actors;
		($actors, $description) = split(/\./, $description, 2);
		$description = cleanupString($description);
		$actors = cleanupString($actors);
		my @_actors = split(/\,\s/, $actors);
		@actors = (@actors, @_actors);
	}

	$description =~ s/^\s+//;

	my $position = index($description, '.');
	my $init_test = substr($description, ($position - 2), 4);

	if ($init_test =~ /\s\w\.\s/) {
		$position = index($description, '.', ($position + 1));
	}
	if (($position > 1) && (($position + 1) < length($description))) {
		my $test = substr($description, 0, $position);

		if ($test =~ /\,\s/) {
			my @_actors;
			my $no_actors = FALSE;
			foreach my $actor (split(/\,\s/, $test)) {
				my $spaces = ($actor =~ tr/ //);

				if (($spaces == 1) || ($spaces == 2)) {
					push @_actors, $actor;
				} else {
					# bale out!
					$no_actors = TRUE;
					last;
				}
			}

			if (!$no_actors) {
				$description = substr($description, ($position + 1));
				@actors = (@actors, @_actors);
			}
		}
	}

	$event{'actors'} = \@actors if ($#actors > -1);

	if ($description =~ /\(Stereo\)/) {
		$event{'stereo'} = TRUE;
		$description =~ s/\(Stereo\)//;
	}

	if ($description =~ /\(HD\)/) {
		$event{'hdtv'} = TRUE;
		$description =~ s/\(HD\)//;
	}

	if ($description =~ /\(CC\)/) {
		$event{'closedcaptioned'} = TRUE;
		$description =~ s/\(CC\)//;
	}

	if ($description =~ /\(SAP\)/) {
		$event{'closedcaptioned'} = TRUE;
		$description =~ s/\(SAP\)//;
	}

	$description =~ s/\s*\([0-9|A-Z]{5}\)\s*$//;
	$description =~ s/New\.\s*//;
	$description =~ s/\(DD\)\s*//;
	$description =~ s/\(All\sDay\s*//;
	$description =~ s/(Series|Season)\s(Finale|Premier|Premiere)\.\s*//;
	$description =~ s/Premiere*(\.|\!)\s*//;
	$description =~ s/\-*Call\s1\-(\d+).*ORDER\..*\#*//;
	$description =~ s/Event\sID\s*\#//;
	$description =~ s/Event\s*\#//;
	$description =~ s/Appelez\s1\-\d+.*\s\#*//;
	$description =~ s/\[Avail\.\sin\sHD\son\s.*\]//;

	$event{'description'} = cleanupString($description);
		
	return \%event;
}

sub processProperties {
	my $raw = shift;
	my %properties;

	foreach my $property ($raw =~ m/\{(.+?)\}/g) {
		my($numeric, undef) = split(/\|/, $property, 2);

		SWITCH: {
			$numeric == 6 && do {
				$properties{'closedcaptioned'} = TRUE;
				last SWITCH;
			};
			$numeric == 7 && do {
				$properties{'stereo'} = TRUE;
				last SWITCH;
			};
		}
	}

	return \%properties;
}

sub processStars {
	my $raw = shift;

	my @stars = (undef, 1, 1.5, 2, 2.5, 3, 3.5, 4);
	return @stars[$raw] / 4;
}

# Ported from MythTV
sub utcOffset {
	my $t = time();
	my $utc = mktime(gmtime($t));
	my $loc = mktime(localtime($t));

	my $utc_offset = $loc - $utc;

	# clamp to nearest minute if within 10 seconds
	my $off = $utc_offset % 60;
	$utc_offset -= $off if (abs($off) < 10);
	$utc_offset -= 60 + $off if (($off < -50) && ($off > -60));
	$utc_offset += 60 - $off if (($off > +50) && ($off < +60));

	return $utc_offset;
}

############################################################################
# Debugging functions                                                      #
############################################################################

sub logMissingCategory {
	my $nibble = shift;
	my $theme = shift;
	my $title = shift;
	my $description = shift;
	my $io = new IO::File $_MISSING_CATS_LOG_, O_WRONLY|O_APPEND;

	return if (!$io);

	print $io sprintf("0x%02x: %s: %s: %s\n", $nibble, $theme, $title, $description);

	close $io;
}

sub saveHash {
	my $file = shift;
	my $hash = shift;

	$Data::Dumper::Purity = 1;

	my $fd = new IO::File $file, O_CREAT|O_WRONLY;

	return TRUE if (!defined($fd));

	print $fd (Data::Dumper->Dump([$hash], [qw(foo)]));

	close $fd;

	return FALSE;
}                       

sub getHash {
	my $file = shift;
	my $foo;
	my $buffer;

	my $fd = new IO::File $file, O_RDONLY;

	return undef if (!defined($fd));
                
	while (my $line = <$fd>) {
		$buffer .= $line;
	}

	close $fd;

	eval $buffer;

	return $foo;
}

sub to_hex {
	my $string = shift;
	my @array = map(ord, split(//, $string));

	return \@array;
}

sub hex_ascii {
	my $section_a = shift;
	my $hex;
	my $ascii;
	my @section_a;
	my $buf;

	$buf = "\n";

	for (my $i = 0; $i <= $#{$section_a}; $i++) {
		$hex = $ascii = undef;
		my $i_t = sprintf("%04x", $i);
		for (my $x = 0; $x < 16; $x++) {
			if ($i > $#{$section_a}) {
				$hex .= '   ';
				$ascii .= ' ';
			} else {
				my $char = $$section_a[$i++];
				$hex .= ' '.sprintf("%02x", $char);
				if (($char >= 32) && ($char < 127)) {
					$ascii .= chr($char);
				} else {
					$ascii .= '.';
				}
			}

			$hex .= '  ' if ($x == 7);
		}

		$i--;

		$buf .= "$i_t: $hex   $ascii\n";
	}

	$buf .= "\n";

	return $buf;
}

sub preparefile {
	my $xmlfile_s = shift;
	open(XMLFILE,">".$xmlfile_s) or die "could not open file $xmlfile_s: $!\n";
	print XMLFILE "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
	print XMLFILE "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\n\n";
	print XMLFILE "<tv source-info-name=\"EIT\" generator-info-name=\"geteit\" generator-info-url=\"mailto:me\@mine.com\">\n";
	return;
}

sub closefile {
	print XMLFILE "</tv>\n";
	close XMLFILE;
}

sub realDate {
	my $seconds = shift;
	my ($second, $minute, $hour, $day, $month, $year) = localtime($seconds);

	return sprintf("%02i%02i%02i%02i%02i%02i",
	  ($year + 1900), ($month + 1), $day, $hour, $minute, $second);
}

