Project

General

Profile

RE: Scripting a channel change/start/finish ยป get_eit.pl

Anonymous, 2012-10-03 20:01

 
1
#!/usr/bin/perl
2
my $Version  = 'get_eit v0.01';
3

    
4
# Originally from myth_update_events, part of the package for myth_scanner
5
#
6
# Borrowed and heavily modified by me, Norm D.
7

    
8
sub usage
9
  {
10
    die <<"EndUsage";
11
$Version
12

    
13
Usage:
14
     -adapter <n>      : Use adapter number <num>.
15
     -pid <n>          : Read EIT PID <n>.
16
     -nice             : Process events more slowly.
17
     -ignore_versions  : Don't track nor use event versions numbers.
18
     -verbose          : Spit out a whole lot of stuff.
19
     -debug            : Place in debug mode - caches dvb table information for faster runs.
20

    
21
EndUsage
22
  }
23

    
24
use Getopt::Long;
25
use Carp qw(cluck);
26
use Time::Local;
27
use Time::HiRes qw( usleep );
28
use List::MoreUtils 'true';
29
use IO::Handle;
30
use IO::File;
31
use IO::Select;
32
use DBI;
33
use Data::Dumper;
34
use Encode;
35
use POSIX;
36
use strict;
37

    
38
use Linux::DVB;
39
use SI::Parse 0.32;
40

    
41
my $VERBOSE;
42
my %THEMECACHE;
43

    
44
use constant {
45
DMX_READ_BUF_SIZE => (32 * 1024),
46
TRUE              => 1,
47
FALSE             => 0,
48

    
49
# From libmyth/mythcontext.h
50
LP_EMERG          => 0,
51
LP_ALERT          => 1,
52
LP_CRITICAL       => 2,
53
LP_ERROR          => 3,
54
LP_WARNING        => 4,
55
LP_NOTICE         => 5,      
56
LP_INFO           => 6,  
57
LP_DEBUG          => 7,
58
};
59

    
60
# Debug stuff
61
my $_DEBUG_;
62
my $_TRANSCACHE_ = '/tmp/transports.dbg';
63
my $_EITCACHE_   = '/tmp/eit.dbg';
64
my $_EITRAW_     = '/tmp/eitraw.dbg';
65
my $_CHANCACHE_  = '/tmp/channels.dbg';
66
my $_MISSING_CATS_LOG_ = '/tmp/events_missing_categories.log';
67

    
68
# Timeout to get a complete table
69
my $TIMEOUT = 60 * 30; # PID 0x300 can take a while
70

    
71
# These are in order of preference.
72
my @EITPIDS = (SI::Parse::EIT_DISH9_PID,
73
	       SI::Parse::EIT_BEV9_PID,
74
	       SI::Parse::EIT_PID);
75

    
76
# Encoding overrides
77
my %ENCODINGS = (256 => 'iso-8859-1',
78
		 257 => 'iso-8859-1');
79

    
80
#These are the valid TID's for scanning channels on dish, which would be huge without this
81
my @valid_nids = 256, 257, 4098, 4101;
82

    
83
&main();
84

    
85
sub getArgs {
86
	my $adapter;
87
	my $iversions;
88
	my $pid;
89
	my $nice;
90
	my $always;
91
	my $log_missing_cats;
92
	my $help;
93
	my $xmlfile;
94
        my $p = new Getopt::Long::Parser;
95

    
96
        if (!$p->getoptions('adapter=i'		=> \$adapter,
97
			    'pid=s'		=> \$pid,
98
			    'nice'		=> \$nice,
99
			    'ignore_versions'	=> \$iversions,
100
			    'always'		=> \$always,
101
			    'log_missing_cats'	=> \$log_missing_cats,
102
			    'verbose'		=> \$VERBOSE,
103
			    'h'			=> \$help,
104
			    'xmlfile=s'		=> \$xmlfile,
105
			    'debug'		=> \$_DEBUG_)) {
106
		print "Invalid parameters given.\n";
107
		usage();
108
        }
109

    
110
	usage() if (defined($help));
111

    
112
	$VERBOSE = TRUE if (defined($VERBOSE));
113
	$_DEBUG_ = TRUE if (defined($_DEBUG_));
114
	$nice = TRUE if (defined($nice));
115
	$iversions = TRUE if (defined($iversions));
116

    
117
	$pid = hex($pid) if ($pid =~ /^0x/);
118
	$adapter = 0 if (!defined($adapter));
119
	$xmlfile = 'xmltv.xml' if (!defined($xmlfile));
120

    
121
        return ($adapter, $pid, $nice, $iversions, $always, $log_missing_cats, $xmlfile);
122
}
123

    
124
sub main {
125
        my($adapter, $pid, $nice, $iversions, $always, $log_missing_cats, $xmlfile) = getArgs();
126

    
127
	my $num_transports = 0;
128
	my $num_channels   = 0;
129
	my $num_eit        = 0;
130

    
131
	STDOUT->autoflush(1);
132

    
133
	foreach my $id (keys %SI::Parse::DISH_THEMES) {
134
		$THEMECACHE{$SI::Parse::DISH_THEMES{$id}} = '';
135
	}
136

    
137
	# Add some additional acceptions for BEV
138
	$THEMECACHE{'Series'}    = 'Series/Special';
139
	$THEMECACHE{"S\xe9ries"} = 'Series/Special';
140
	$THEMECACHE{'News'}      = 'News/Business';
141
	$THEMECACHE{'Nouvelles'} = 'News/Business';
142
	$THEMECACHE{'Film'}      = 'Movie';
143
	$THEMECACHE{'Music'}     = 'Music/Art';
144
	$THEMECACHE{'Children'}  = 'Family/Children';
145
	$THEMECACHE{'Enfants'}   = 'Family/Children';
146

    
147
#	createVersionTable();
148
	my $versions;
149

    
150
#	if (!$iversions) {
151
#		print "\n-> Retrieving event version information: ";
152
#		$versions = getVersions();
153
#		print "done.\n";
154
#	}
155
	
156
	my $t_offset = 0;
157
	
158
	preparefile($xmlfile);
159
	
160
	print "\n-> Using time offset of $t_offset.\n";
161

    
162
	print "\n-> Looking for network information: ";
163
	my($nid, $name) = getNetwork($adapter);
164
	print "done.\n";
165
	print "\t-> Found network ID $nid ($name).\n";
166

    
167
	if (!defined($pid)) {
168
		print "\n-> Finding best EIT PID: ";
169
		$pid = findBestPID($adapter);
170
		print "$pid .... done.\n";
171
		die("\n\nFATAL: Unable to find any available EIT PIDS, aborting!\n")
172
		  if (!defined($pid));
173
	} else {
174
		die("\n\nFATAL: Specified PID $pid is not available, aborting!\n")
175
		  if (!checkPID($adapter, $pid));
176
	}
177

    
178
	if (($pid == SI::Parse::EIT_PID) && (($name =~ /Nimiq/) || ($name =~ /EchoStar/))) {
179
		print sprintf("\n\nWARNING: Scanning on PID 0x%02x which only includes now/next guide data. ".
180
		  "If you're wanting to scan the 9-day guide, ensure your DVB adapter ".
181
		  "is tuned to the appropriate network/transport.\n\n", $pid);
182
	}
183

    
184
	print sprintf("\n-> Scanning on pid 0x%x.\n", $pid);
185

    
186
	print "\n-> Scanning for transports:  ";
187
	print "\n" if ($VERBOSE);
188
	my $transports = getTransports($adapter);
189
	print "\010done.\n";
190

    
191
	foreach my $nit (keys %{$transports}) {
192
		$num_transports += keys %{$$transports{$nit}};
193
	}
194

    
195
	print "\t-> Found $num_transports transport(s).\n\n";
196

    
197
	print "-> Scanning for channels:  ";
198
	print "\n" if ($VERBOSE);
199
	my ($channels, $miss) = getChannels($adapter, $transports);
200
	print "\010done.\n";
201

    
202
	foreach my $nit (keys %{$channels}) {
203
		foreach my $tid (keys %{$$channels{$nit}}) {
204
			$num_channels += keys %{$$channels{$nit}->{$tid}};
205
		}
206
	}
207
	print "\t-> Found $num_channels channel(s)";
208
	print ", $miss rescanned section(s).\n\n";
209

    
210
	my $cache;
211
	print "-> Scanning for EIT data:  ";
212
	print "\n" if ($VERBOSE);
213
	($cache, $versions) = getEvents($adapter, $channels, $versions, $pid, $iversions);
214

    
215
	my $n_items = $#{$cache};
216

    
217
	print "\010done, found ".($n_items + 1)." event tables to process.\n\n";
218

    
219
	print "-> Processing EIT data:  ";
220
	print "\n" if ($VERBOSE);
221
	my $updated = processEvents($cache, $t_offset, $nice, $always, $log_missing_cats);
222
	print "\010done, $updated programs added/updated.\n";
223

    
224
	print "\n";
225
	
226
	closefile();
227

    
228
}
229

    
230
sub cleanupString {
231
	my $string = shift;
232

    
233
	$string =~ s/^\s+//;
234
	$string =~ s/\s+$//;
235

    
236
	return $string;
237
}
238

    
239
############################################################################
240
# DVB functions                                                            #
241
############################################################################
242

    
243
sub getTransports {
244
	my $adapter = shift;
245
	my %transports;
246
	my $all_seen   = FALSE;
247
	my $scan_start = FALSE;
248
	my $count = 0;
249
	my $select = new IO::Select;
250
	my $nit_ver;
251
	my %networks;
252

    
253
	if ($_DEBUG_ && (-f $_TRANSCACHE_)) {
254
		return getHash($_TRANSCACHE_);
255
	}
256

    
257
	my @sparkle = qw( \ | / - );
258
	my $cur_sparkle = 0;
259

    
260
	my $dvb_sup = new SI::Parse($_DEBUG_);
261

    
262
	my $pid = SI::Parse::NIT_PID;
263

    
264
	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";
265

    
266
	$dmx->buffer(DMX_READ_BUF_SIZE);
267
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
268
	$dmx->blocking(0);
269

    
270
	$select->add($dmx->fh);
271

    
272
	my $now = time();
273

    
274
	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
275
		my @ready = $select->can_read(5);
276
		my $handle = pop @ready;
277

    
278
		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);
279

    
280
		my $section;
281

    
282
		sysread($handle, $section, DMX_READ_BUF_SIZE);
283

    
284
		my $table = $dvb_sup->decodeSection($section);
285

    
286
		next if (!defined($table));
287
		next if ($$table{'table_id'} == SI::Parse::STUFFING_TABLE);
288

    
289
		$nit_ver = $$table{'version_number'} if (!defined($nit_ver));
290

    
291
		die("\n\nFATAL: NIT version number changed while scanning!\n")
292
		  if ($nit_ver != $$table{'version_number'});
293

    
294
		my $nid = $$table{'network_id'};
295
		
296
		next if ($nid != 256 && $nid != 257 && $nid != 4098 && $nid != 4101 && $nid != 4102);
297
		
298
		foreach my $net (@{$$table{'network_descriptors'}}) {
299
			if ($$net{'dvb_descriptor_tag'} == SI::Parse::NETWORK_NAME_DESCRIPTOR) {
300
				$networks{$nid}++;
301
				$scan_start = TRUE;
302
			}
303
		}
304

    
305
		next if (!$scan_start);
306

    
307
		foreach my $trans (@{$$table{'transport_descriptors'}}) {
308
			my $tid  = $$trans{'transport_stream_id'};
309

    
310
			foreach my $desc (@{$$trans{'descriptors'}}) {
311
				next if ($$desc{'dvb_descriptor_tag'} !=
312
				  SI::Parse::SAT_DELIVERY_DESCRIPTOR);
313

    
314
				my $freq = $$desc{'frequency'};
315
				my $sr   = $$desc{'symbol_rate'};
316

    
317
				my $pol;
318
				for ($$desc{'polarization'}) {
319
					if ($_ == SI::Parse::POL_HORIZ) {$pol = 'h'}
320
					if ($_ == SI::Parse::POL_VERT)  {$pol = 'v'}
321
					if ($_ == SI::Parse::POL_LEFT)  {$pol = 'l'}
322
					if ($_ == SI::Parse::POL_RIGHT) {$pol = 'r'}
323
					if ($_ >  SI::Parse::POL_RIGHT) {
324
						print "WARNING: Unknown polarity '$_' found!\n";
325
					}
326
				}
327

    
328
				my $mod;
329
				for($$desc{'modulation'}) {
330
					if ($_ == SI::Parse::MOD_QPSK_0) {$mod = 'qpsk'}
331
					if ($_ == SI::Parse::MOD_QPSK_1) {$mod = 'qpsk'}
332
					if ($_ == SI::Parse::MOD_8PSK)  {$mod = '8psk'}
333
					if ($_ == SI::Parse::MOD_TQPSK) {$mod = 't_qpsk'}
334
					if ($_ >  SI::Parse::MOD_TQPSK) {
335
						print "WARNING: Unknown modulation '$_' found!\n";
336
					}
337
				}
338

    
339
				my $fec;
340
				for($$desc{'fec_inner'}) {
341
					if ($_ == SI::Parse::FEC_1_2) {$fec = '1/2'}
342
					if ($_ == SI::Parse::FEC_2_3) {$fec = '2/3'}
343
					if ($_ == SI::Parse::FEC_3_4) {$fec = '3/4'}
344
					if ($_ == SI::Parse::FEC_4_5) {$fec = '4/5'}
345
					if ($_ == SI::Parse::FEC_5_6) {$fec = '5/6'}
346
					if ($_ == SI::Parse::FEC_7_8) {$fec = '7/8'}
347
					if ($_ == SI::Parse::FEC_8_9) {$fec = '8/9'}
348
					if ($_ == SI::Parse::FEC_3_5) {$fec = '3/5'}
349
					if ($_ == SI::Parse::FEC_4_5) {$fec = '4/5'}
350
					if ($_ == SI::Parse::FEC_9_10) {$fec = '9/10'}
351
					if (!$_ || $_ >  SI::Parse::FEC_8_9) {
352
						print "WARNING: Unknown fec '$_' found!\n";
353
					}
354
				}
355

    
356
				$count++;
357

    
358
				if (!defined($transports{$nid}->{$freq}->{'tids'}->{$tid})) {
359
					if ($VERBOSE) {
360
						print "\t-> TRANSPORT: $nid $tid $freq $pol $mod $sr $fec\n";
361
					} else {
362
						$cur_sparkle = ($cur_sparkle + 1) % @sparkle;
363
						print "\010$sparkle[$cur_sparkle]" if (!($count % 10));
364
					}
365

    
366
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'polarity'} = $pol;
367
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'modulation'} = $mod;
368
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'symbolrate'} = $sr;
369
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'fec'} = $fec;
370
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'count'} = 0;
371
				} else {
372
					$transports{$nid}->{$freq}->{'tids'}->{$tid}->{'count'}++;
373

    
374
					if (!$VERBOSE) {
375
						$cur_sparkle = ($cur_sparkle + 1) % @sparkle;
376
						print "\010$sparkle[$cur_sparkle]" if (!($count % 10));
377
					}
378
				}
379
			}
380
		}
381

    
382
		$all_seen = TRUE;
383

    
384
		foreach my $_nid (keys %networks) {
385
			if ($networks{$_nid} < 3) {
386
				$all_seen = FALSE;
387
				last;
388
			}
389
		}
390
	}
391

    
392
	$dmx->stop();
393

    
394
	if (!$all_seen) {
395
		die("FATAL: Timeout while reading transport tables, aborting.\n");
396
	}
397

    
398
	saveHash($_TRANSCACHE_, \%transports) if ($_DEBUG_);
399

    
400
	return \%transports;
401
}
402

    
403
sub getChannels {
404
	my $adapter = shift;
405
	my $transports = shift;
406
	my $count = 0;
407
	my $num_chan = 0;
408
	my $num_found = 0;
409
	my %channels;
410
	my %_transports;
411
	my $all_seen   = FALSE;
412
	my $scan_start = FALSE;
413
	my $select = new IO::Select;
414
	my $last_section = 0;
415
	my $sdt_ver;
416
	my $miss_errors = 0;
417
	my $percent = 0;
418

    
419
	if ($_DEBUG_ && (-f $_CHANCACHE_)) {
420
		return (getHash($_CHANCACHE_), 0, 0);
421
	}
422

    
423
	foreach my $_nid (keys %{$transports}) {
424
		foreach my $_freq (keys %{$$transports{$_nid}}) {
425
			foreach my $_tid (keys %{$$transports{$_nid}->{$_freq}->{'tids'}}) {
426
				$_transports{$_nid}->{$_tid}->{'got_channels'} = FALSE;
427
				$num_chan++;
428
			}
429
		}
430
	}
431

    
432
	print "   ";
433

    
434
	my $dvb_sup = new SI::Parse($_DEBUG_);
435

    
436
	my $pid = SI::Parse::SDT_PID;
437

    
438
	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";
439

    
440
	$dmx->buffer(DMX_READ_BUF_SIZE);
441
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
442
	$dmx->blocking(0);
443

    
444
	$select->add($dmx->fh);
445

    
446
	my $now = time();
447

    
448
	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
449
		my @ready = $select->can_read(5);
450
		my $handle = pop @ready;
451

    
452
		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);
453

    
454
		$count++;
455

    
456
		my $section;
457

    
458
		sysread($handle, $section, DMX_READ_BUF_SIZE);
459

    
460
		my $table = $dvb_sup->decodeSection($section);
461

    
462
		next if (!defined($table));
463
		next if ($$table{'table_id'} == SI::Parse::STUFFING_TABLE);
464

    
465
		my $c_nid = $$table{'original_network_id'};
466
		my $c_tid = $$table{'transport_stream_id'};
467

    
468
		$sdt_ver = $$table{'version_number'} if (!defined($sdt_ver));
469

    
470
		die("\n\nFATAL: SDT version number changed while scanning!\n")
471
		  if ($sdt_ver != $$table{'version_number'});
472

    
473
		$scan_start = TRUE  if ($$table{'section_number'} == 0);
474
		next if (!$scan_start);
475

    
476
		if ($$table{'section_number'} &&
477
		    (($$table{'section_number'} - $last_section) != 1)) {
478
			$miss_errors++;
479
		}
480

    
481
		if ($$table{'section_number'} > $$table{'last_section_number'}) {
482
			$miss_errors++;
483
		}
484

    
485
		if (!defined($_transports{$c_nid}->{$c_tid})) {
486
			#print "\n\nWARNING: Found channel defined for non-existant ".
487
			 # "transport $c_nid/$c_tid, ignoring.\n";
488
			next;
489
		}
490

    
491
		$last_section = $$table{'section_number'};
492

    
493
		if (!$VERBOSE) {
494
			if (!($count % 10)) {
495
				my $_percent = ($num_found / $num_chan) * 100;
496
				if ($_percent != $percent) {
497
					print sprintf("\010\010\010\010%3i\%", ($_percent, 3));
498
					$percent = $_percent;
499
				}
500
			}
501
		}
502

    
503
		next if ($_transports{$c_nid}->{$c_tid}->{'got_channels'});
504

    
505
		if (!defined($_transports{$c_nid}->{$c_tid}->{'sections'})) {
506
			for (my $x = 0; $x <= $$table{'last_section_number'}; $x++) {
507
				$_transports{$c_nid}->{$c_tid}->{'sections'}->{$x} = TRUE;
508
			}
509
		}
510

    
511
		foreach my $svc (@{$$table{'services'}}) {
512
			my $sid = $$svc{'service_id'};
513
			my $eit_schedule_flag = $$svc{'eit_schedule_flag'};
514
			my $callsign;
515
			my $name;
516
			my $service_type;
517

    
518
			foreach my $desc (@{$$svc{'service_descriptors'}}) {
519
				if ($$desc{'dvb_descriptor_tag'} == SI::Parse::SERVICE_DESCRIPTOR) {
520
					$callsign = $$desc{'service_name'};
521
					$name = $$desc{'service_provider_name'};
522
					$service_type = $$desc{'service_type'};
523
				} # We don't care about the other descriptors - for now
524
			}
525

    
526
			if (!defined($channels{$c_nid}->{$c_tid}->{$sid})) {
527
				if ($VERBOSE) {
528
					print "\t-> Channel: $c_nid tp: $c_tid $sid ".
529
					  "'$callsign' \"$name\" Type $service_type\n";
530
				}
531
				$callsign =~ s/\&/&amp;/g;
532
				$name =~ s/\&/&amp;/g;
533
				print XMLFILE "\t<channel id=\"$sid-$c_nid.geteit\">\n";
534
				print XMLFILE "\t\t<display-name>$callsign</display-name>\n";
535
				print XMLFILE "\t\t<display-name>$sid $callsign</display-name>\n";
536
				print XMLFILE "\t\t<display-name>$sid</display-name>\n";
537
				print XMLFILE "\t\t<display-name>$name</display-name>\n";
538
				print XMLFILE "\t</channel>\n";
539
				$channels{$c_nid}->{$c_tid}->{$sid}->{'callsign'} = $callsign;
540
				$channels{$c_nid}->{$c_tid}->{$sid}->{'name'} = $name;
541
				$channels{$c_nid}->{$c_tid}->{$sid}->{'eit_schedule_flag'} = $eit_schedule_flag;
542
				$channels{$c_nid}->{$c_tid}->{$sid}->{'service_type'} = $service_type;
543
				$channels{$c_nid}->{$c_tid}->{$sid}->{'used'} = 0;
544
			} else {
545
				$channels{$c_nid}->{$c_tid}->{$sid}->{'used'}++;
546
			}
547
		}
548

    
549
		delete $_transports{$c_nid}->{$c_tid}->{'sections'}->{$$table{'section_number'}};
550

    
551
		if (defined($_transports{$c_nid}->{$c_tid}->{'sections'}) &&
552
		    !keys %{$_transports{$c_nid}->{$c_tid}->{'sections'}}) {
553
			$num_found++ if (defined($_transports{$c_nid}->{$c_tid}));
554
			$_transports{$c_nid}->{$c_tid}->{'got_channels'} = TRUE;
555
		}
556

    
557
		$all_seen = TRUE;
558

    
559
		foreach my $_nid (keys %_transports) {
560
			foreach my $_tid (keys %{$_transports{$_nid}}) {
561
				$all_seen = FALSE if (!$_transports{$_nid}->{$_tid}->{'got_channels'});
562
				last if (!$all_seen);
563
			}
564

    
565
			last if (!$all_seen);
566
		}
567
	}
568

    
569
	$dmx->stop();
570

    
571
	if (!$all_seen) {
572
		die("FATAL: Timeout while reading channel tables, aborting.\n");
573
	}
574

    
575
	saveHash($_CHANCACHE_, \%channels) if ($_DEBUG_);
576

    
577
	print "\010\010\010";
578

    
579
	return (\%channels, $miss_errors);
580
}
581

    
582
sub checkPID {
583
	my $adapter = shift;
584
	my $pid = shift;
585
	my $select = new IO::Select;
586

    
587
	my $dvb_sup = new SI::Parse($_DEBUG_);
588

    
589
	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";
590

    
591
	$dmx->buffer(DMX_READ_BUF_SIZE);
592
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
593
	$dmx->blocking(0);
594

    
595
	$select->add($dmx->fh);
596

    
597
	my @ready = $select->can_read(2);
598
	my $handle = pop @ready;
599

    
600
	$dmx->stop();
601

    
602
	return defined($handle) ? TRUE : FALSE;
603
}
604

    
605
sub findBestPID {
606
	my $adapter = shift;
607

    
608
	foreach my $pid (@EITPIDS) {
609
		return $pid if (checkPID($adapter, $pid));
610
	}
611

    
612
	return undef;
613
}
614

    
615
sub getEvents {
616
	my $adapter = shift;
617
	my $channels = shift;
618
	my $versions = shift;
619
	my $pid = shift;
620
	my $iversions = shift;
621
	my @cache;
622
	my $count = 0;
623
	my $num_chan = 0;
624
	my $num_found = 0;
625
	my $all_seen   = FALSE;
626
	my $has_eit    = FALSE;
627
	my %_channels;
628
	my %_events;
629
	my $select = new IO::Select;
630
	my $num = 0;
631
	my %versions_t;
632
	my $percent = 0;
633

    
634
	if ($_DEBUG_ && (-f $_EITRAW_)) {
635
		return getHash($_EITRAW_);
636
	}
637

    
638
	foreach my $nid (keys %{$channels}) {
639
		foreach my $tid (keys %{$$channels{$nid}}) {
640
			foreach my $sid (keys %{$$channels{$nid}->{$tid}}) {
641
				if ($$channels{$nid}->{$tid}->{$sid}->{'eit_schedule_flag'}) {
642
					my $nts = sprintf("%04x%04x%04x", $nid, $tid, $sid);
643
					my %empty = ('ZZ' => 1);
644
					$_channels{$nts} = \%empty;
645
					$num_chan++;
646
				}
647
			}
648
		}
649
	}
650

    
651
	print "   ";
652

    
653
	my $dvb_sup = new SI::Parse($_DEBUG_);
654

    
655
	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter0/demux0";
656

    
657
	$dmx->buffer(DMX_READ_BUF_SIZE);
658
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
659
	$dmx->blocking(0);
660

    
661
	$select->add($dmx->fh);
662
	
663
	my $now = time();
664
	
665
	while (!$all_seen && (time() <= ($now + $TIMEOUT))) {
666
		my $skip_event = FALSE;
667
		my @ready = $select->can_read(5);
668
		my $handle = pop @ready;
669

    
670
		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);
671

    
672
		my $section;
673
	
674
		sysread($handle, $section, DMX_READ_BUF_SIZE);
675
		next if (!$section);
676
		my $sec_a = to_hex($section);
677
		my $tableid = $$sec_a[0];
678
		my $sid   = ($$sec_a[3]  << 0x08) + $$sec_a[4];
679
		my $version = ($$sec_a[5] & 0x7c) >> 0x01;
680
		my $sec_num  = $$sec_a[6];
681
		my $sec_last = $$sec_a[7];
682
		my $c_tid = ($$sec_a[8]  << 0x08) + $$sec_a[9];
683
		my $c_nid = ($$sec_a[10] << 0x08) + $$sec_a[11];
684
		my $last_seg  = $$sec_a[12];
685
		my $last_tbid = $$sec_a[13];
686
		my $nts = sprintf("%04x%04x%04x", $c_nid, $c_tid, $sid);
687
		my $ts = sprintf("%02x%02x", $tableid, $sec_num);
688
		$count++;
689

    
690
		if (!$VERBOSE) {
691
			if (!($count % 10)) {
692
				my $_percent = ($num_found / $num_chan) * 100;
693
				if ($_percent != $percent) {
694
					print sprintf("\010\010\010\010%3i\%", ($_percent, 3));
695
					$percent = $_percent;
696
				}
697
			}
698
		}
699
		next if (!defined($_channels{$nts}));
700
		
701
		if (!$iversions) {
702
			$skip_event = TRUE if ($$versions{$c_nid}->{$c_tid}->{$sid}->{$tableid} == $version);
703
			$versions_t{$c_nid}->{$c_tid}->{$sid}->{$tableid} = $version;
704
		}
705

    
706
		if (defined($_channels{$nts}->{'ZZ'})) {
707
			my $low_id = 0;
708

    
709
			if (($tableid == SI::Parse::EIT_ACTUAL_TRANSPORT) ||
710
			    ($tableid == SI::Parse::EIT_OTHER_TRANSPORT)) {
711
				$low_id = $tableid;
712
			} elsif (($tableid >= SI::Parse::EITS_ACTUAL_LOW) &&
713
				 ($tableid <= SI::Parse::EITS_ACTUAL_HIGH)) {
714
				$low_id = SI::Parse::EITS_ACTUAL_LOW;
715
			} elsif (($tableid >= SI::Parse::EITS_OTHER_LOW) &&
716
				 ($tableid <= SI::Parse::EITS_OTHER_HIGH)) {
717
				$low_id = SI::Parse::EITS_OTHER_LOW
718
			} elsif (($tableid >= SI::Parse::DISH_EVENT_EXT_LOW) &&
719
				 ($tableid <= SI::Parse::DISH_EVENT_EXT_HIGH)) {
720
				$low_id = SI::Parse::DISH_EVENT_EXT_LOW
721
			} else {
722
				print "\n\nWARNING: Invalid table id $tableid found. Unable to continue.\n";
723
				next;
724
			}
725

    
726
			for (my $y = $low_id; $y <= $last_tbid; $y++) {
727
				for (my $x = 0; $x <= $sec_last; $x++) {
728
					my $_ts = sprintf("%02x%02x", $y, $x);
729
					$_channels{$nts}->{$_ts} = TRUE;
730
					delete $_channels{$nts}->{'ZZ'};
731
				}
732
			}
733
		}
734

    
735
		delete $_channels{$nts}->{$ts};
736

    
737
		if (!($count % 100)) {
738
			$all_seen = TRUE;
739
			$num_found = 0;
740

    
741
			foreach my $_nts (keys %_channels) {
742
				$all_seen = FALSE if (defined($_channels{$_nts}->{'ZZ'}));
743
				$num_found++ if (!keys %{$_channels{$_nts}});
744
			}
745
		}
746

    
747
		if (!defined($_events{$nts.$ts})) {
748
			push @cache, $section if (!$skip_event);
749
			$num++;
750
			$_events{$nts.$ts}++;
751
		}
752

    
753
		$has_eit = TRUE;
754
	}
755

    
756
	if (!$has_eit) {
757
		if ($VERBOSE) {
758
			print "WARNING: No EIT tables were found.\n";
759
		} else {
760
			print "\010\010\010 - no EIT tables were found:  ";
761
		}
762
	}
763

    
764
	$dmx->stop();
765

    
766
	if (!$all_seen && $has_eit) {
767
		die("FATAL: Timeout while reading event tables, aborting.\n");
768
	}
769

    
770
	print "\010\010\010";
771

    
772
	saveHash($_EITRAW_, \@cache) if ($_DEBUG_);
773

    
774
	return \@cache, \%versions_t;
775
}
776

    
777
sub processEvents {
778
	my $cache = shift;
779
	my $t_offset = shift;
780
	my $nice = shift;
781
	my $always = shift;
782
	my $log_missing_categories = shift;
783
	my $count = 0;
784
	my $updated = 0;
785
	my $log_missing = 0;
786

    
787
	my $num_entries = $#{$cache} + 1;
788

    
789
	print "   ";
790

    
791
	my $dvb_sup = new SI::Parse($_DEBUG_);
792

    
793
	foreach my $section (@{$cache}) {
794
		my $table = $dvb_sup->decodeSection($section);
795

    
796
		next if (!defined($table));
797
		next if ($#{$$table{'events'}} == -1);
798

    
799
		my $tableid     = $$table{'table_id'};
800
		my $serviceid   = $$table{'service_id'};
801
		my $networkid   = $$table{'original_network_id'};
802
		my $transportid = $$table{'transport_stream_id'};
803

    
804
		foreach my $_event (@{$$table{'events'}}) {
805
			next if (!$$_event{'descriptors_loop_length'});
806

    
807
			my $title;
808
			my $description;
809
			my $properties;
810
			my $programinfo;
811
			my $mpaainfo;
812
			my $vchipinfo;
813
			my $rightsinfo;
814
			my $c_theme;
815
			my $c_category;
816
			my $language;
817
			my $upped;
818

    
819
			my $starttime_t = timegm($$_event{'start_second'},
820
						    $$_event{'start_minute'},
821
						    $$_event{'start_hour'},
822
						    $$_event{'start_day'},
823
						    ($$_event{'start_month'}-1),
824
						    $$_event{'start_year'});
825

    
826
			my $duration = ($$_event{'duration_hour'} * 60 * 60) +
827
				       ($$_event{'duration_minute'} * 60) +
828
				       $$_event{'duration_second'};
829
			my $starttime = realDate($starttime_t);
830
			my $endtime = realDate($starttime_t + $duration);
831

    
832
			foreach my $desc (@{$$_event{'event_descriptors'}}) {
833
				my $tag = $$desc{'dvb_descriptor_tag'};
834

    
835
				if ($tag == SI::Parse::SHORT_EVENT_DESCRIPTOR) {
836
					$title = $$desc{'event_name'};
837
				} elsif ($tag == SI::Parse::EXTEND_EVENT_DESCRIPTOR) {
838
					$description = $$desc{'text'};
839
					$language = $$desc{'iso639_2_language_code'};
840
				} elsif ($tag == SI::Parse::DISH_EVENT_NAME) {
841
					$title = $dvb_sup->decompressDishEventName($tableid, $$desc{'descriptor_data'});
842
				} elsif ($tag == SI::Parse::DISH_EVENT_DESCRIPTION) {
843
					$description = $dvb_sup->decompressDishEventDescription($tableid, $$desc{'descriptor_data'});
844
				} elsif ($tag == SI::Parse::DISH_EVENT_PROPERTIES) {
845
					$properties = $dvb_sup->decompressDishEventProperties($tableid, $$desc{'descriptor_data'});
846
				} elsif ($tag == SI::Parse::CONTENT_DESCRIPTOR) {
847
					$c_theme = $dvb_sup->dishContentTheme($$desc{'content_nibble_level_2'});
848
					$c_category = $dvb_sup->dishContentCategory($$desc{'user_nibble'});
849
					#$c_category = substr($description,1,index($description,"."));
850
					if (!defined($c_category) && $log_missing_categories) {
851
						$log_missing = $$desc{'user_nibble'};
852
					}
853
				} elsif ($tag == SI::Parse::DISH_EVENT_PROGRAMID) {
854
					$programinfo = $desc;
855
				} elsif ($tag == SI::Parse::DISH_EVENT_MPAA_FLAGS) {
856
					$mpaainfo = $desc;
857
				} elsif ($tag == SI::Parse::DISH_EVENT_VCHIP_FLAGS) {
858
					$vchipinfo = $desc;
859
				} elsif ($tag == SI::Parse::TIER_EVENT_RIGHTS) {
860
					$rightsinfo = $desc;
861
				} 
862
			}
863

    
864
			if ($log_missing) {
865
				logMissingCategory($log_missing, $c_theme, $title, $description);
866
			}
867

    
868
			my $p_event = processEvent($title, $description);
869
			my $p_properties = processProperties($properties);
870

    
871
			my %event;
872

    
873
			$event{'networkid'}   = $networkid;
874
			#$event{'chanids'}     = ;
875
			$event{'starttime'}   = $starttime;
876
			$event{'endtime'}     = $endtime;
877
			$event{'title'}       = $$p_event{'title'};
878
			$event{'subtitle'}    = $$p_event{'subtitle'};
879
			$event{'description'} = $$p_event{'description'};
880
			$event{'language'}    = substr($language,0,2);
881
			#if (defined($$p_event{'actors'})) { my @actors = @{$$p_event{'actors'}};}
882
			
883
			if ($c_theme && $c_category) {
884
				$event{'category_type'} = $c_theme;
885
				$event{'category'} = $c_category;
886
			} elsif ($$p_event{'theme'}) {
887
				$event{'category'} = $event{'category_type'}
888
				  = $$p_event{'theme'};
889
			} else {
890
				$event{'category'} = $event{'category_type'}
891
				  = 'Unknown';
892
			}
893

    
894
			if ($programinfo) {
895
				if ($$programinfo{'originalairdate_day'}) {
896
					$event{'originalairdate'} = simpleDate($$programinfo{'originalairdate_month'},
897
									       $$programinfo{'originalairdate_day'},
898
									       $$programinfo{'originalairdate_year'});
899

    
900

    
901
					if (dateLessThan($$programinfo{'originalairdate_month'},
902
							 $$programinfo{'originalairdate_day'},
903
							 $$programinfo{'originalairdate_year'},
904
							 (time() + $t_offset))) {
905
						$event{'previouslyshown'} = 1;
906
					} else {
907
						$event{'previouslyshown'} = 0;
908
					}
909
					$event{'airdate'} = $$programinfo{'originalairdate_year'};
910
				}
911

    
912
				$event{'programid'} = $$programinfo{'programid'};
913
				$event{'syndicatedepisodenumber'} = $$programinfo{'episode'};
914
				if (($$programinfo{'seriesid'} !~ /^MV/) &&
915
				    ($$programinfo{'seriesid'} !~ /^SP/)) {
916
					$event{'seriesid'} = $$programinfo{'seriesid'};
917
				}
918
			} elsif ($$p_event{'tear'}) {
919
				$event{'airdate'} = $$p_event{'year'};
920
			}
921

    
922
			my $audioprop;
923
			my $videoprop;
924
			my $subtitletypes;
925
			if ($p_properties) {
926
				if ($$p_properties{'stereo'}) {
927
					$audioprop .= ',STEREO';
928
					$event{'stereo'} = 1;
929
				}
930
				if ($$p_properties{'closedcaptioned'}) {
931
					$audioprop .= ',HARDHEAR';
932
					$subtitletypes .= ',HARDHEAR';
933
					$event{'closecaptioned'} = 1;
934
				}
935
			} else {
936
				if ($$p_event{'stereo'}) {
937
					$audioprop .= ',STEREO';
938
					$event{'stereo'} = 1;
939
				}
940
				if ($$p_event{'closedcaptioned'}) {
941
					$audioprop .= ',HARDHEAR';
942
					$subtitletypes .= ',HARDHEAR';
943
					$event{'closecaptioned'} = 1;
944
				}
945
			}
946

    
947
			if ($$p_event{'hdtv'}) {
948
				$videoprop .= ',HDTV';
949
			}
950
			$audioprop =~ s/^\,//;
951
			$videoprop =~ s/^\,//;
952
			$subtitletypes =~ s/^\,//;
953

    
954
			$event{'audioprop'} = $audioprop;
955
			$event{'videoprop'} = $videoprop;
956
			$event{'subtitletypes'} = $subtitletypes;
957

    
958
			if ($mpaainfo) {
959
				if ($$mpaainfo{'star_raw'}) {
960
					$event{'stars'} = $$mpaainfo{'star_raw'};
961
				}
962
			}
963
			print XMLFILE "\t<programme start=\"$event{'starttime'} -0400\" stop=\"$event{'endtime'} -0400\" channel=\"$serviceid-$networkid.geteit\">\n";
964
			print XMLFILE "\t\t<title lang=\"$event{'language'}\">$event{'title'}</title>\n";
965
			print XMLFILE "\t\t<desc lang=\"$event{'language'}\">\n\t\t\t$event{'description'}\n\t\t</desc>\n";
966
			print XMLFILE "\t\t<category lang=\"$event{'language'}\">$event{'category'}</category>\n" if ($event{'category'});
967
			if ($$p_event{'actors'}) {
968
			    print XMLFILE "\t\t<credits>\n";
969
			    foreach my $actor (@{$$p_event{'actors'}}) {
970
			      print XMLFILE "\t\t\t<actor>\"$actor\"</actor>\n"
971
			    }
972
			    print XMLFILE "\t\t</credits>\n";
973
			}
974
			print XMLFILE "\t\t<date>$event{'originalairdate'}</date>\n" if ($event{'originalairdate'});
975
			print XMLFILE "\t\t<video>\n\t\t\t<quality>$event{'videoprop'}</quality>\n\t\t</video>\n" if ($event{'videoprop'});
976
			print XMLFILE "\t\t<episode-num system=\"xmltv_ns\">$event{'syndicatedepisodenumber'}</episode-num>\n" if ($event{'syndicatedepisodenumber'});
977
			print XMLFILE "\t\t<star-rating>\n\t\t\t<value>$event{'stars'}</value>\n\t\t</star-rating>\n" if ($event{'stars'});
978
			print XMLFILE "\t</programme>\n";
979
			
980
#print "channel id=$serviceid-$networkid.geteit\n";
981
#print "Program ID: $event{'programid'}\n";
982
#print "Start time: $event{'starttime'}\n";
983
#print "Duration:   $starttime_t    $duration\n";
984
#print "End Time:   $event{'endtime'}\n";
985
#print "Title: $event{'title'}\n";
986
#print "SubTitle: $event{'subtitle'}\n";
987
#print "Subtitle types: $event{'subtitletypes'}\n";
988
#print "Description: $event{'description'}\n";
989
#print "Language: $event{'language'}\n";
990
#print "Theme: $event{'theme'}\n";
991
#print "HDTV: $event{'hdtv'}\n";
992
#print "Year: $event{'year'}\n";
993
#print "Stereo: $event{'stereo'}\n";
994
#print "Closed Captioned: $event{'closedcaptioned'}\n";
995
#print "Category: $event{'category'}\n";
996
#print "Category types: $event{'category_types'}\n";
997
#print "Original Airdate: $event{'originalairdate'}\n";
998
#print "Previously Shown: $event{'previouslyshown'}\n";
999
#print "Air Date: $event{'airdate'}\n";
1000
#print "Syndicated episode ID: $event{'syndicatedepisodeid'}\n";
1001
#print "Series ID: $event{'seriesid'}\n";
1002
#print "Stars: $event{'stars'}\n";
1003
#print "MPAA info: $$mpaainfo{'star_raw'}\n";
1004
#print "VCHIP info: $vchipinfo\n";
1005
#print "Rights info: $rightsinfo\n";
1006
#print "Audio prop: $event{'audioprop'}\n";
1007
#print "video prop: $event{'videoprop'}\n";
1008

    
1009
#print "----------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n";
1010
			$updated++ if ($upped);
1011

    
1012
			usleep(50000) if ($nice);
1013
		}
1014

    
1015
		$count++;
1016

    
1017
		if ($VERBOSE) {
1018
			print "\t-> EIT Data: $networkid tp: $transportid $serviceid\n";
1019
		} else {
1020
			if (!($count % 10)) {
1021
				print sprintf("\010\010\010\010%3i\%",
1022
				  (($count / $num_entries) * 100), 3);
1023
			}
1024
		}
1025
	}
1026

    
1027
	print "\010\010\010";
1028
	return $updated;
1029
}
1030

    
1031
sub getNetwork {
1032
	my $adapter = shift;
1033
	my $dvb_sup = new SI::Parse;
1034
	my $network_name;
1035
	my $network_id;
1036
	my $found = FALSE;
1037
	my $select = new IO::Select;
1038

    
1039
	my $pid = SI::Parse::NIT_PID;
1040

    
1041
	my $dmx = new Linux::DVB::Demux "/dev/dvb/adapter$adapter/demux0";
1042

    
1043
	$dmx->buffer(DMX_READ_BUF_SIZE);
1044
	$dmx->sct_filter($pid, undef, undef, 0, DMX_CHECK_CRC|DMX_IMMEDIATE_START);
1045
	$dmx->blocking(0);
1046

    
1047
	$select->add($dmx->fh);
1048

    
1049
	my $now = time();
1050

    
1051
	while (!$found && (time() <= ($now + $TIMEOUT))) {
1052
		my @ready = $select->can_read(5);
1053
		my $handle = pop @ready;
1054

    
1055
		die(sprintf("FATAL: Unable to scan PID 0x%02x.\n", $pid)) if (!$handle);
1056

    
1057
		my $section;
1058

    
1059
		sysread($handle, $section, DMX_READ_BUF_SIZE);
1060

    
1061
		my $table = $dvb_sup->decodeSection($section);
1062

    
1063
		next if (!$table);
1064
		next if ($$table{'table_id'} != SI::Parse::NIT_ACTUAL_NETWORK);
1065

    
1066
		$network_id = $$table{'network_id'};
1067

    
1068
		foreach my $net (@{$$table{'network_descriptors'}}) {
1069
			if ($$net{'dvb_descriptor_tag'} == SI::Parse::NETWORK_NAME_DESCRIPTOR) {
1070
				$network_name = $$net{'network_name'};
1071
			}
1072

    
1073
			$found = TRUE;
1074
		}
1075
	}
1076

    
1077
	$dmx->stop();
1078

    
1079
	if (!$found) {
1080
		die("FATAL: Timeout while reading transport tables, aborting.\n");
1081
	}
1082

    
1083
	return($network_id, $network_name);
1084
}
1085

    
1086
sub hex_to_array {
1087
	my $string = shift;
1088
	my @array;
1089
	my $octet;
1090

    
1091
	foreach my $nibble (split(//, $string)) {
1092
		$octet .= $nibble;
1093

    
1094
		if (length($octet) == 2) {
1095
			push @array, hex($octet);
1096
			$octet = undef;
1097
		}
1098
	}
1099

    
1100
	return \@array;
1101
}
1102

    
1103
sub to_hex {       
1104
	my $string = shift;
1105
	my @array = map(ord, split(//, $string));
1106

    
1107
	return \@array;
1108
}
1109

    
1110
sub table {               
1111
	my $buf = shift;        
1112
	my $start = shift;
1113
	my $len = shift;        
1114
	my @table; 
1115
	my $overflow = 0;       
1116

    
1117
	for (my $i = $start; $i < ($start + $len); $i++) {
1118
		if ($i <= $#{$buf}) {          
1119
			push @table, $$buf[$i];
1120
		} else {
1121
			$overflow++;
1122
		}
1123
	}
1124

    
1125
	if ($overflow) {        
1126
		#print "WARNING: Table copy overflowed by $overflow: start == $start, len = $len:\n";
1127
		#print hex_ascii($buf);
1128
	}
1129

    
1130
	return \@table;         
1131
}
1132

    
1133

    
1134
# Real simple date calculator
1135
sub dateLessThan {
1136
	my $omonth = shift;
1137
	my $oday = shift;
1138
	my $oyear = shift;
1139
	my $now = shift;
1140

    
1141
	my @t = gmtime($now);
1142
	my $year = shift @t;
1143
	my $month = shift @t;
1144
	my $day = shift @t;
1145

    
1146
	my $a = sprintf("%04i%02i%02i", $year, $month, $day);
1147
	my $b = sprintf("%04i%02i%02i", $oyear, $omonth, $oday);
1148

    
1149
	return ($b < $a);
1150
}
1151

    
1152
sub simpleDate {
1153
	my $month = shift;
1154
	my $day = shift;
1155
	my $year = shift;
1156

    
1157
	return sprintf("%02i-%02i-%02i", $year, $month, $day);
1158
}
1159

    
1160
sub processEvent {
1161
	my $title = shift;
1162
	my $description = shift;
1163
	my %event;
1164

    
1165
	$title = cleanupString($title);
1166
	$description = cleanupString($description);
1167

    
1168
	if ($title =~ /\(HD\)/) {
1169
		$event{'hdtv'} = TRUE;
1170
		$title =~ s/\(HD\)//;
1171
	}
1172

    
1173
	if ($title =~ /^HD\s*\-\s*/) {
1174
		$event{'hdtv'} = TRUE;
1175
		$title =~ s/^HD\s*\-\s*//;
1176
	}
1177

    
1178
	if ($title =~ /^HD\s+/) {
1179
		$event{'hdtv'} = TRUE;
1180
#		$title =~ s/^HD\s+//;
1181
	}
1182

    
1183
	$title =~ s/\s\(All\sDay.*\)$//;
1184
	$title =~ s/\:\s*$//;
1185
	$title =~ s/\s(5|7)\.1$//;
1186
	$title =~ s/\s\-\sPremier\sRelease//;
1187
	$title =~ s/\s\-\sPremier//;
1188
	$title =~ s/\:\sSortie\sPrimeur$//;
1189
	$title =~ s/\:\sPrimeur$//; # Two different spellings?
1190
	$title =~ s/\:\sPrimuer$//;
1191
	$title =~ s/\:\sPrime$//;
1192
	$title =~ s/\s*\(RC\)//;
1193
	$title =~ s/\,\sv\..{1,3}$//;
1194
	$title =~ s/\(v\..{1,3}\.*\)//;
1195
	$title =~ s/\&/\&amp;/g;
1196
	$title =~ s/\s*\(INFO\)//;
1197

    
1198
	$event{'title'} = $title;
1199

    
1200
	($event{'subtitle'}, $description) = split(/\s*\x0d/, $description, 2)
1201
	  if ($description =~ /\x0d\s/);
1202

    
1203
	if ((length($event{'title'}) > 15) &&
1204
	    (length($event{'subtitle'}) > length($event{'title'})) &&
1205
	    (index($event{'subtitle'}, $event{'title'}) >= 0) &&
1206
	    (length($event{'subtitle'}) - length($event{'title'}) < 7)) {
1207
		$event{'subtitle'} = '';
1208
	}
1209

    
1210
	my $position = index($description, '.');
1211
	my $after = substr($description, ($position + 1), 1);
1212
	if ((($position < length($description)) && ($after eq ' ')) ||
1213
	    (($position + 1) == length($description))) {
1214
		if (($position > 0) && ($position <= 20)) {
1215
			my $old_description = $description;
1216
			($event{'theme'}, $description) = split(/\./, $description, 2);
1217
			$event{'theme'} =~ s/^\s//;
1218
			if ($event{'theme'} =~ /-/) {
1219
			  $description = $old_description;
1220
			  delete $event{'theme'}; }
1221
			elsif ($THEMECACHE{$event{'theme'}}) {
1222
			  $event{'theme'} = $THEMECACHE{$event{'theme'}};
1223
			 }
1224
		}
1225
	}
1226

    
1227
	$event{'subtitle'} =~ s/^All\sDay\s\(.*\sEastern\)\s*$//;
1228

    
1229
	if ($event{'subtitle'} =~ /Free\spreview\suntil/) {
1230
		$event{'subtitle'} = '';
1231
	};
1232
	$event{'subtitle'} =~ s/\&/\&amp;/g;
1233

    
1234
	my @actors;
1235

    
1236
	$description =~ s/.*SAME\sDAY\sAS\sDVD\!//;
1237
	$description =~ s/.*SAME\sAS\sDVD\!//;
1238
	$description =~ s/.*jour\sque\sle\sDVD\!//;
1239
	$description =~ s/^\s*\-\.\s*//;
1240
	$description =~ s/\&/\&amp;/g;
1241

    
1242
	if ($description =~ /\s\s\((\d{4})\)/) { # year
1243
		$event{'year'} = $1;
1244

    
1245
		my $left;
1246
		($left, $description) = split(/\(\d{4}\)/, $description, 2);
1247
		$left = cleanupString($left);
1248

    
1249
		$description = cleanupString($description);
1250

    
1251
		if ($left && ($left !~ /Off\sAir/) && ($left !~ /\s\-\s/) &&
1252
		    ($left !~ /^\d/) && ($left !~ /\!/) && ($left !~ /\$/)) { # actors
1253
			$left = cleanupString($left);
1254
			$left =~ s/\.$//;
1255
			my @_actors = split(/\,\s/, $left);
1256
			@actors = (@actors, @_actors);
1257
		}
1258
	}
1259

    
1260
	# We may have actors even without a year
1261
	if ($description =~ /Voice\sof\:\s/) {
1262
		my $actors;
1263
		($actors, $description) = split(/\./, $description, 2);
1264
		$description = cleanupString($description);
1265
		$actors = cleanupString($actors);
1266
		my @_actors = split(/\,\s/, $actors);
1267
		@actors = (@actors, @_actors);
1268
	}
1269

    
1270
	$description =~ s/^\s+//;
1271

    
1272
	my $position = index($description, '.');
1273
	my $init_test = substr($description, ($position - 2), 4);
1274

    
1275
	if ($init_test =~ /\s\w\.\s/) {
1276
		$position = index($description, '.', ($position + 1));
1277
	}
1278
	if (($position > 1) && (($position + 1) < length($description))) {
1279
		my $test = substr($description, 0, $position);
1280

    
1281
		if ($test =~ /\,\s/) {
1282
			my @_actors;
1283
			my $no_actors = FALSE;
1284
			foreach my $actor (split(/\,\s/, $test)) {
1285
				my $spaces = ($actor =~ tr/ //);
1286

    
1287
				if (($spaces == 1) || ($spaces == 2)) {
1288
					push @_actors, $actor;
1289
				} else {
1290
					# bale out!
1291
					$no_actors = TRUE;
1292
					last;
1293
				}
1294
			}
1295

    
1296
			if (!$no_actors) {
1297
				$description = substr($description, ($position + 1));
1298
				@actors = (@actors, @_actors);
1299
			}
1300
		}
1301
	}
1302

    
1303
	$event{'actors'} = \@actors if ($#actors > -1);
1304

    
1305
	if ($description =~ /\(Stereo\)/) {
1306
		$event{'stereo'} = TRUE;
1307
		$description =~ s/\(Stereo\)//;
1308
	}
1309

    
1310
	if ($description =~ /\(HD\)/) {
1311
		$event{'hdtv'} = TRUE;
1312
		$description =~ s/\(HD\)//;
1313
	}
1314

    
1315
	if ($description =~ /\(CC\)/) {
1316
		$event{'closedcaptioned'} = TRUE;
1317
		$description =~ s/\(CC\)//;
1318
	}
1319

    
1320
	if ($description =~ /\(SAP\)/) {
1321
		$event{'closedcaptioned'} = TRUE;
1322
		$description =~ s/\(SAP\)//;
1323
	}
1324

    
1325
	$description =~ s/\s*\([0-9|A-Z]{5}\)\s*$//;
1326
	$description =~ s/New\.\s*//;
1327
	$description =~ s/\(DD\)\s*//;
1328
	$description =~ s/\(All\sDay\s*//;
1329
	$description =~ s/(Series|Season)\s(Finale|Premier|Premiere)\.\s*//;
1330
	$description =~ s/Premiere*(\.|\!)\s*//;
1331
	$description =~ s/\-*Call\s1\-(\d+).*ORDER\..*\#*//;
1332
	$description =~ s/Event\sID\s*\#//;
1333
	$description =~ s/Event\s*\#//;
1334
	$description =~ s/Appelez\s1\-\d+.*\s\#*//;
1335
	$description =~ s/\[Avail\.\sin\sHD\son\s.*\]//;
1336

    
1337
	$event{'description'} = cleanupString($description);
1338
		
1339
	return \%event;
1340
}
1341

    
1342
sub processProperties {
1343
	my $raw = shift;
1344
	my %properties;
1345

    
1346
	foreach my $property ($raw =~ m/\{(.+?)\}/g) {
1347
		my($numeric, undef) = split(/\|/, $property, 2);
1348

    
1349
		SWITCH: {
1350
			$numeric == 6 && do {
1351
				$properties{'closedcaptioned'} = TRUE;
1352
				last SWITCH;
1353
			};
1354
			$numeric == 7 && do {
1355
				$properties{'stereo'} = TRUE;
1356
				last SWITCH;
1357
			};
1358
		}
1359
	}
1360

    
1361
	return \%properties;
1362
}
1363

    
1364
sub processStars {
1365
	my $raw = shift;
1366

    
1367
	my @stars = (undef, 1, 1.5, 2, 2.5, 3, 3.5, 4);
1368
	return @stars[$raw] / 4;
1369
}
1370

    
1371
# Ported from MythTV
1372
sub utcOffset {
1373
	my $t = time();
1374
	my $utc = mktime(gmtime($t));
1375
	my $loc = mktime(localtime($t));
1376

    
1377
	my $utc_offset = $loc - $utc;
1378

    
1379
	# clamp to nearest minute if within 10 seconds
1380
	my $off = $utc_offset % 60;
1381
	$utc_offset -= $off if (abs($off) < 10);
1382
	$utc_offset -= 60 + $off if (($off < -50) && ($off > -60));
1383
	$utc_offset += 60 - $off if (($off > +50) && ($off < +60));
1384

    
1385
	return $utc_offset;
1386
}
1387

    
1388
############################################################################
1389
# Debugging functions                                                      #
1390
############################################################################
1391

    
1392
sub logMissingCategory {
1393
	my $nibble = shift;
1394
	my $theme = shift;
1395
	my $title = shift;
1396
	my $description = shift;
1397
	my $io = new IO::File $_MISSING_CATS_LOG_, O_WRONLY|O_APPEND;
1398

    
1399
	return if (!$io);
1400

    
1401
	print $io sprintf("0x%02x: %s: %s: %s\n", $nibble, $theme, $title, $description);
1402

    
1403
	close $io;
1404
}
1405

    
1406
sub saveHash {
1407
	my $file = shift;
1408
	my $hash = shift;
1409

    
1410
	$Data::Dumper::Purity = 1;
1411

    
1412
	my $fd = new IO::File $file, O_CREAT|O_WRONLY;
1413

    
1414
	return TRUE if (!defined($fd));
1415

    
1416
	print $fd (Data::Dumper->Dump([$hash], [qw(foo)]));
1417

    
1418
	close $fd;
1419

    
1420
	return FALSE;
1421
}                       
1422

    
1423
sub getHash {
1424
	my $file = shift;
1425
	my $foo;
1426
	my $buffer;
1427

    
1428
	my $fd = new IO::File $file, O_RDONLY;
1429

    
1430
	return undef if (!defined($fd));
1431
                
1432
	while (my $line = <$fd>) {
1433
		$buffer .= $line;
1434
	}
1435

    
1436
	close $fd;
1437

    
1438
	eval $buffer;
1439

    
1440
	return $foo;
1441
}
1442

    
1443
sub to_hex {
1444
	my $string = shift;
1445
	my @array = map(ord, split(//, $string));
1446

    
1447
	return \@array;
1448
}
1449

    
1450
sub hex_ascii {
1451
	my $section_a = shift;
1452
	my $hex;
1453
	my $ascii;
1454
	my @section_a;
1455
	my $buf;
1456

    
1457
	$buf = "\n";
1458

    
1459
	for (my $i = 0; $i <= $#{$section_a}; $i++) {
1460
		$hex = $ascii = undef;
1461
		my $i_t = sprintf("%04x", $i);
1462
		for (my $x = 0; $x < 16; $x++) {
1463
			if ($i > $#{$section_a}) {
1464
				$hex .= '   ';
1465
				$ascii .= ' ';
1466
			} else {
1467
				my $char = $$section_a[$i++];
1468
				$hex .= ' '.sprintf("%02x", $char);
1469
				if (($char >= 32) && ($char < 127)) {
1470
					$ascii .= chr($char);
1471
				} else {
1472
					$ascii .= '.';
1473
				}
1474
			}
1475

    
1476
			$hex .= '  ' if ($x == 7);
1477
		}
1478

    
1479
		$i--;
1480

    
1481
		$buf .= "$i_t: $hex   $ascii\n";
1482
	}
1483

    
1484
	$buf .= "\n";
1485

    
1486
	return $buf;
1487
}
1488

    
1489
sub preparefile {
1490
	my $xmlfile_s = shift;
1491
	open(XMLFILE,">".$xmlfile_s) or die "could not open file $xmlfile_s: $!\n";
1492
	print XMLFILE "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n";
1493
	print XMLFILE "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\n\n";
1494
	print XMLFILE "<tv source-info-name=\"EIT\" generator-info-name=\"geteit\" generator-info-url=\"mailto:me\@mine.com\">\n";
1495
	return;
1496
}
1497

    
1498
sub closefile {
1499
	print XMLFILE "</tv>\n";
1500
	close XMLFILE;
1501
}
1502

    
1503
sub realDate {
1504
	my $seconds = shift;
1505
	my ($second, $minute, $hour, $day, $month, $year) = localtime($seconds);
1506

    
1507
	return sprintf("%02i%02i%02i%02i%02i%02i",
1508
	  ($year + 1900), ($month + 1), $day, $hour, $minute, $second);
1509
}
1510

    
    (1-1/1)