root/postprocessors/flag_aus_hdtv @ 187

Revision 187, 16.7 kB (checked in by max, 7 years ago)

Respect option_ready; version bump for split tv_grab_au

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