root/postprocessors/flag_aus_hdtv @ 165

Revision 165, 16.6 kB (checked in by lincoln, 7 years ago)

flag_aus_hdtv postprocessor initial commit

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl
2
3# flag_aus_hdtv
4#  checks the digital broadcasting australia (www.dba.org.au) site for
5#  their condensed program guide information for information such as
6#  widescreen, "high definition" (notwithstanding channel seven's
7#  definition as 576p), DD5.1 and updates supplied xmltv
8#  to map SD to SD+HD channels
9#  - Richard Dale <richard@interlink.com.au>, September 2006
10
11my $progname = "flag_aus_hdtv";
12my $version = "0.10.1";
13$| = 1;
14printf "%s v%s\n",$progname,$version;
15
16use strict;
17use Getopt::Long;
18use LWP::UserAgent;
19use HTML::TreeBuilder;
20use IO::File;
21use XMLTV;
22use Data::Dumper;
23
24#
25# customizations
26#
27
28# Add overrides for HD - sometimes DBA doesn't always have the latest HD information about TV shows so set them here in the override
29my $hdoverride;
30$hdoverride->{TEN} = [ "The Handler", "N.Y.P.D. Blue", "The Office", "Battlestar Galactica" ];
31
32# Add translations for show names that differ between DBA and our normal tv_grab_au names
33my $translation;
34$translation->{'ACA'} = 'A Current Affair';
35$translation->{'CSI'} = 'CSI: Crime Scene Investigation';
36$translation->{'CSI-Miami'} = 'CSI: Miami';
37
38my $chan_name_translations;
39$chan_name_translations->{"Ten"} = "TEN";
40$chan_name_translations->{"ABC Main"} = "ABC";
41
42#
43# options
44#
45
46my $ua;
47$ua = LWP::UserAgent->new('timeout' => 30, 'keep_alive' => 1, 'agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-us)' );
48$ua->env_proxy;
49my %stats;
50my $channels, my $opt_channels;
51my $hdprog;
52my %amp = ( nbsp => ' ', qw{ amp & lt < gt > apos ' quot " } );
53
54my $opt;
55$opt->{dbaurl} = "http://www.dba.org.au/dtvprograms"; # This URL seems to work fine Sep 2006
56$opt->{output_file} = "output.xmltv";
57$opt->{region} = 94;
58$opt->{debug} = 1;
59
60&parse_command_line;
61&show_help if ($opt->{help});
62die "'--channels_file {file}' must be specified. see --help for details.\n" if (!$opt->{channels_file});
63die "'--dontflagalldays' not yet implemented.  sorry!\n" if ($opt->{dontflagalldays});
64
65&read_config_file($opt->{channels_file},1);
66
67$opt->{locality} = decode_region($opt->{region});
68die "no HDTV data available for region $opt->{region}" if (!$opt->{locality});
69
70my $data = &get_url($opt->{dbaurl},"Obtaining DBA data",0);
71&parse_data($data) if ($data);
72
73#
74# now eat up the XMLTV inputs we were given!
75#
76
77@ARGV = ('-') if not @ARGV;
78
79my %writer_args = ( encoding => 'ISO-8859-1' );
80my $fh = new IO::File(">".$opt->{output_file}) ||
81  die "can't open $opt->{output_file} for writing: $!";
82$writer_args{OUTPUT} = $fh;
83
84my $writer = new XMLTV::Writer(%writer_args);
85$writer->start( {
86        'source-info-name' => "$progname $version",
87        'generator-info-name' => "$progname $version"} );
88
89foreach my $file (@ARGV) {
90        printf "Parsing: %s\n",($file eq "-" ? "(from-stdin, hit control-D to finiah)" : $file);
91        XMLTV::parsefiles_callback(undef, undef, \&channel_cb,\&programme_cb, $file);
92}
93
94$writer->end();
95printf "Finished parsing, output in $opt->{output_file}\n";
96
97&print_stats;
98exit(0);   
99
100######################################################################################################
101# read settings
102
103sub read_config_file
104{
105        my($file,$die_on_failure) = @_;
106        if (!(-r $file)) {
107                die "file $file could not be read.  aborting.\n" if $die_on_failure;
108                return;
109        }
110        local (@ARGV, $/) = ($file);
111        no warnings 'all'; eval <>; die "$@" if $@;
112}
113
114######################################################################################################
115
116sub parse_command_line
117{
118        GetOptions(
119                'channels_file=s' => \$opt->{channels_file},
120                'config=s'      => \$opt->{config_file},
121                'region=i'      => \$opt->{region},
122                'dbaurl=s'      => \$opt->{dbaurl},
123                'output=s'      => \$opt->{output_file},
124                'dontflagalldays' => \$opt->{dontflagalldays},
125                'debug+'        => \$opt->{debug},
126                'help'          => \$opt->{help},
127                'h'             => \$opt->{help},
128                'v'             => \$opt->{help});
129}
130
131######################################################################################################
132
133sub show_help
134{
135        print<<EOF
136$progname $version
137
138options:
139  --channels_file=(f)  shepherd channels file (mandatory)
140  --region=(region)    region where we are (mandatory) (default: $opt->{region})
141                         (VIC)     90,93,94,95,98
142                         (NSW/ACT) 63,66,67,69,71,73,106,184
143                         (QLD)     75,78,79,114
144                         (SA)      81,82,83,85,86,107
145                         (WA)      101,102
146                       (no HDTV in TAS or NT apparently)
147  --dbaurl=(url)       URL where dba lists HDTV programming (default: $opt->{dbaurl})
148
149  --dontflagalldays    don't flag a programme as being in HD across all days
150                       if we see it in HD on one day.  (dba website is often
151                       incorrect with regard to what day a HD programme is on)
152                       (default: do)
153
154  --output=(f)         output XMLTV filename (default: $opt->{output})
155
156  --debug              increase debug level
157
158EOF
159;
160        exit(0);
161}
162
163######################################################################################################
164
165sub decode_region
166{
167        my $r = shift;
168
169        return "M" if (($r==90)||($r==93)||($r==94)||($r==95)||($r==98));
170        return "W" if (($r==101)||($r==102));
171        return "S" if (($r==63)||($r==66)||($r==67)||($r==69)||($r==71)||($r==73)||($r==76)||($r==106)||($r==184));
172        return "B" if (($r==75)||($r==78)||($r==79)||($r==114));
173        return "A" if (($r==81)||($r==82)||($r==83)||($r==85)||($r==86)||($r==107));
174        return undef;
175}
176
177######################################################################################################
178
179sub parse_data
180{
181        my $tree = HTML::TreeBuilder->new_from_content($data); 
182
183        # each daily section starts with:
184        # <TABLE class=tv...
185        # so we'll search for that and populate an array
186        my @chan_map;
187        for ($tree->look_down('_tag' => 'table', 'class' => 'tv')) {
188                my $firstrowdone=0;
189                my $dayname = "";
190
191                foreach my $row ($_->look_down('_tag' => 'tr')) {
192                        if ($firstrowdone == 0) { # Channel list is the first row
193                                $firstrowdone = 1;
194                                my $colnum = 0;
195                                for my $channel ($row->look_down('_tag' => 'td')) {
196                                        my $this_chan = $channel->as_text;
197                                        $colnum++;
198                                        $this_chan =~ s/[^\x20-\x7f]/ /g;
199                                        $this_chan =~ s/(^\s+|\s+$)//g;
200                                        next if ($this_chan eq "");
201
202                                        $this_chan = $chan_name_translations->{$this_chan}
203                                          if (defined $chan_name_translations->{$this_chan});
204
205                                        if (!defined $channels->{$this_chan}) {
206                                                printf "DBA listed unkown channel '$this_chan'! Ignored.\n";
207                                                $stats{unknown_channels}++;
208                                                next;
209                                        }
210
211                                        $chan_map[$colnum] = $this_chan;
212                                        #printf "got channel '$this_chan' in column $colnum\n";
213                                }
214                                next;
215                        }
216
217                        # These must be the program details
218                        my $colnum = 0;
219                        foreach my $session ($row->look_down('_tag' => 'td')) {
220                                $colnum++;
221                                if ($colnum == 1) {
222                                        # This session has the day name in it
223                                        my $newdayname = $session->as_text();
224                                        $newdayname =~ s/Night//;
225                                        $newdayname =~ s/Day//;
226                                        $newdayname =~ s/Sat/Saturday/;
227                                        $newdayname = "" if ($dayname =~ m/Subject to avail/i);
228
229                                        $dayname = $newdayname if ($newdayname =~ m/day$/);
230                                        next;
231                                }
232                 
233                                my $this_chan = $chan_map[$colnum]; 
234
235                                my $sessionshows = $session->as_HTML();
236                                $sessionshows =~ s/<p>/<br \/>/g; # Convert new paragraphs to newlines because they're not always consistent
237                                $sessionshows =~ s/<\/p>//g;
238                                $sessionshows =~ s/<u>//g;
239                                $sessionshows =~ s/<\/u>//g;
240                                $sessionshows =~ s/<\/td>//g;
241                                $sessionshows =~ s/<\/font>//g;
242                                $sessionshows =~ s/<font.*?>//g;
243                                $sessionshows =~ s/<td.*?>//g;
244                                #print "\nFound " . $session->as_HTML() . "\n\n";
245                                my @shows = split(/<br[\s\/]*>/,$sessionshows);
246                                foreach my $show (@shows) {
247                                        # Bodgy stuff to fix html markup
248                                        $show =~ s/\&amp;/\&/g; $show =~ s/\&apos;/\'/g; $show =~ s/\&quot;/\"/g; 
249                                        $show =~ s/\&#184;/\,/g; 
250                                        $show =~ s/\&#39;/\'/g; 
251                                        $show =~ s/\&nbsp;/\ /g; 
252
253                                        # Remove trailing asterisk - just means could be widescreen if content is in widescreen - duh!
254                                        $show =~ s/\*$//;
255
256                                        # High Definition and locality
257                                        my $hd = 0;
258                                        if ($show =~ m/\(HD\)\s*? \(([SMAPB])\)/) {
259                                                $hd = 1 if ($1 eq $opt->{locality});
260                                                $show =~ s/\(HD\)\s*? \([SMAPB]\)//;
261                                        }
262
263
264                                        # High Definition
265                                        if ($show =~ m/\(HD\)/ || $show =~ m/\(JHD\)/ ) {
266                                                $hd = 1;
267                                                $show =~ s/\(HD\)//;
268                                                $show =~ s/\(JHD\)//; # JHD was probably a typo but we've included it here because they'll probably have fat fingers again
269                                        }
270
271                                        # Dolby Digital 5.1
272                                        my $dd51 = 0;
273                                        if ($show =~ m/DD 5.1/ || $show =~ m/DD5.1/) {
274                                                $dd51 = 1;
275                                                $show =~ s/\[\+ DD 5.1 sound\]//;
276                                                $show =~ s/\[\+ DD 5.1 Audio\]//;
277                                                $show =~ s/\[\+ DD 5.1 audio\]//;
278                                                $show =~ s/\[\+DD 5.1 audio\]//;
279                                                $show =~ s/\[\+DD 5.1 Audio\]//;
280                                                $show =~ s/\[\+DD 5.1 sound\]//;
281                                                $show =~ s/\[\+DD 5.1 sound\]//;
282                                                $show =~ s/\[\+DD5.1 sound\]//;
283                                                $show =~ s/\[\+DD5.1\]//;
284                                                $show =~ s/\[\+ DD 5.1\]//;
285                                                $show =~ s/\[\DD5.1\]//;
286                                                $show =~ s/\[\DD 5.1\]//;
287                                        }
288                                        $show =~ s/\s+$//;
289                                        $show =~ s/^\s+//;
290
291                                        if (defined($translation->{$show})) {
292                                                $show = $translation->{$show};
293                                                $stats{manual_translations}++;
294                                        }
295   
296                                        next if (length($show) == 0);
297
298                                        # Widescreen handling
299                                        # Now we're here, this show must be in widescreen - we don't (yet) do anything with widescreen in mythtv
300
301                                        # DD5.1
302                                        if ($dd51 == 1) {
303                                                # This show is is in DD5.1 - we don't (yet) do anything wiht DD5.1 in mythtv
304                                                ;
305                                        }
306 
307                                        # High Definition Handling
308                                        if ($hd == 1) {
309                                                #print "HD Channel $this_chan $dayname \"$show\"\n" if $opt->{debug};
310                                                my $xmlid = $channels->{$this_chan};
311                                                $hdprog->{$xmlid}->{$show}->{chan} = $this_chan;
312                                                $hdprog->{$xmlid}->{$show}->{days}->{$dayname} = 1;
313                                                $stats{parsed_hd_progs}++;
314                                        } else {
315                                                #print "SD Widescreen Channel $channels[$channelnumber]: $dayname $show \n" if $opt->{debug};
316                                                $stats{parsed_sd_widescreen_progs}++;
317                                        }
318                                }
319                        }
320                }
321        }
322}
323
324######################################################################################################
325# logic to fetch a page via http
326#  retries up to 3 times to get a page with 5 second pauses inbetween
327
328sub get_url
329{
330        my($url,$status,$dontretry) = (@_);
331        my $response;
332        my $attempts = 0;
333        my ($raw, $page, $base);
334
335        my $request = HTTP::Request->new(GET => $url);
336        $request->header('Accept-Encoding' => 'gzip');
337
338        print "$status $url\n";
339
340        my $max_retries = 1 if ($dontretry);
341
342        for (1..3) {
343                $response = $ua->request($request);
344                last if ($response->is_success || $dontretry);
345
346                $stats{http_failed_requests}++;
347                $attempts++;
348                &sleepy("attempt $attempts failed (url $url), sleeping for 10 seconds",10);
349        }
350        if (!($response->is_success)) {
351                if ($dontretry == 0) {
352                        printf "ERROR: could not open url %s in %d attempts\n",$url,$attempts;
353                }
354                return undef;
355        }
356
357        $stats{bytes_fetched} += do {use bytes; length($response->content)};
358        $stats{http_successful_requests}++;
359
360        if ($response->header('Content-Encoding') &&
361            $response->header('Content-Encoding') eq 'gzip') {
362                $stats{compressed_pages} += do {use bytes; length($response->content)};
363                $response->content(Compress::Zlib::memGunzip($response->content));
364        }
365
366        if ($response->header('Content-type') eq 'xapplication/ywe-octet-stream') {
367                $stats{transformed_pages}++;
368                $base = &transform_output(length($response->content), $response->content);
369        } else {
370                $base = $response->content;
371        }
372        return $base;
373}
374
375######################################################################################################
376
377sub print_stats
378{
379        printf "%s v%s completed", $progname, $version;
380        foreach my $key (sort keys %stats) {
381                printf ", %d %s",$stats{$key},$key;
382        }
383        printf "\n";
384}
385
386######################################################################################################
387
388sub channel_cb( $ )
389{
390        my $c = shift;
391        # printf "got channel ".Dumper($c);
392        $writer->write_channel($c);
393}
394
395######################################################################################################
396
397sub programme_cb( $ )
398{
399        my $prog=shift;
400        my $this_chan = $prog->{channel};
401        my $this_title;
402        my $hd_chan_base;
403
404        $this_title = $prog->{title}->[0]->[0]
405          if (($prog->{title}) && ($prog->{title}->[0]) && ($prog->{title}->[0]->[0]));
406        # print "got programme ".Dumper($prog);
407
408        goto NOMATCH if (!defined $this_chan);
409        goto NOMATCH if (!defined $this_title);
410        goto NOMATCH if (!defined $hdprog->{$this_chan});
411
412        # see if title matches everything we have seen on this channel
413        foreach my $t (keys %{($hdprog->{$this_chan})}) {
414                if (canonicalizeTitles_match($t,$this_title)) {
415                        # match!
416                        printf "Matched '$t' to '$this_title'\n";
417                        $stats{matched_prog}++;
418                        $hd_chan_base = $hdprog->{$this_chan}->{$t}->{chan}."HD";
419                        goto MATCH;
420                }
421        }
422
423        # see if title matches something in our override table
424        foreach my $ch (keys %{($hdoverride)}) {
425                next if ($channels->{$ch} ne $this_chan);
426                foreach my $t (@{($hdoverride->{$ch})}) {
427                        if (canonicalizeTitles_match($t,$this_title)) {
428                                # match!
429                                printf "Matched '$t' to '$this_title' (hd override)\n";
430                                $stats{override_matched_prog}++;
431                                $hd_chan_base = $ch."HD";
432                                goto MATCH;
433                        }
434                }
435        }
436
437NOMATCH:
438        $writer->write_programme($prog);
439        return;
440
441MATCH:
442        $prog->{video}->{aspect} = "16:9"; # widescreen
443        $prog->{video}->{quality} = "HDTV"; # HD
444        $writer->write_programme($prog);
445        $stats{rewrote_prog_as_hdtv_inplace}++;
446
447        # do we have a HD version of this known in opt_channels table?
448        if (defined $opt_channels->{$hd_chan_base}) {
449                # populate this programme in HD channel also
450                $prog->{channel} = $opt_channels->{$hd_chan_base};
451                $writer->write_programme($prog);
452                $stats{wrote_prog_into_hdtv_channel}++
453        }
454        return;
455}
456
457######################################################################################################
458
459sub canonicalizeTitle
460{
461        my $title=shift;
462        $title =~ s/^\s+//;
463        $title =~ s/\s+$//;
464        $title =~ s/&(#(\d+)|(.*?));/ $2 ? chr($2) : $amp{$3}||' ' /eg;
465        $title =~ s/ *\& */ and /g;
466        $title =~ s/[^ a-zA-Z0-9]//g;
467        $title =~ s/\s+/ /;
468        return(lc($title));
469}
470
471my %alternatives = ( one => "1",
472                     two => "2",
473                     to  => "2",
474                     too => "2",
475                     three => "3",
476                     four => "4",
477                     for => "4",
478                     five => "5",
479                     six => "6",
480                     seven => "7",
481                     eight => "8",
482                     nine => "9"
483                   );
484
485sub forgivingMatch
486{
487        my $word1 = shift;
488        my $word2 = shift;
489
490        # exact match
491        return 1 if $word1 eq $word2;
492        # they match according to our alternatives lookup table
493        return 1 if $alternatives{$word1} && $alternatives{$word1} eq $word2 ||
494                    $alternatives{$word2} && $alternatives{$word2} eq $word1;
495        # irreconcilable differences
496        return 0 if abs(length($word1) - length($word2)) > 1 ||
497                    length($word1) < 3;
498
499        my @list1 = split(//,$word1);
500        my @list2 = split(//,$word2);
501        my $i = 0;
502        my $j = 0;
503
504        # find first difference
505        while ($i < @list1 && $j < @list2 && $list1[$i] eq $list2[$j]) {
506                ++$i; ++$j;
507        }
508        if ($i+1 < @list1 && $j+1 < @list2) {
509                # at least 2 chars to go in both words
510                if ($list1[$i+1] eq $list2[$j] && $list1[$i] eq $list2[$j+1]) {
511                        # characters transposed
512                        $i += 2;
513                        $j += 2;
514                } elsif ($list1[$i+1] eq $list2[$j]) {
515                        # extra character inserted into @list1
516                        $i += 2;
517                        ++$j;
518                } elsif ($list1[$i] eq $list2[$j+1]) {
519                        # extra character inserted into @list2
520                        ++$i;
521                        $j += 2;
522                } else {
523                        # single character difference
524                        ++$i;
525                        ++$j;
526                }
527                # we forgave one difference; now do rest of strings match exactly?
528                while ($i < @list1 && $j < @list2 && $list1[$i] eq $list2[$j]) {
529                        ++$i; ++$j;
530                }
531                return($i == @list1 && $j == @list2);
532        } elsif ($i == @list1 || $j == @list2) {
533                # only difference is one word has one extra letter, or last char
534                # of each word differ.  That's still only one one-char difference
535                return(1);
536        }
537}
538
539sub canonicalizeTitles_match
540{
541        my $word1=canonicalizeTitle(shift);
542        my $word2 =canonicalizeTitle(shift);
543        my @longer;
544        my @shorter;
545
546        if (length($word1) > length($word2)) {
547                @longer  = split(/\s+/, $word1);
548                @shorter = split(/\s+/, $word2);
549        } else {
550                @shorter = split(/\s+/, $word1);
551                @longer  = split(/\s+/, $word2);
552        }
553
554        WORD: for my $word (@shorter) {
555                for(my $i=0; $i < @longer; ++$i) {
556                        if (forgivingMatch($longer[$i], $word)) {
557                                splice(@longer,$i,1);
558                                next WORD;
559                        } elsif ($i+1 < @longer &&
560                                 $word eq "$longer[$i]$longer[$i+1]") {
561                                splice(@longer,$i,2);
562                                next WORD;
563                        }
564                }
565                return(0);
566        }
567        return(1);
568}
569
570##############################################################################
Note: See TracBrowser for help on using the browser.