root/grabbers/oztivo @ 787

Revision 787, 12.4 kB (checked in by paul, 6 years ago)

oztivo: checks for messages and retries

Line 
1#!/usr/bin/perl -w
2
3# OzTivo grabber
4
5my $version = '1.02';
6
7# Requires configuration!
8# 1. Register at http://www.tvguide.org.au/
9# 2. Run "./oztivo --configure" to create "oztivo.pw" file.
10
11use strict;
12
13use Getopt::Long;
14use HTML::Entities;
15use POSIX;
16use Shepherd::Common;
17
18my $progname = 'oztivo';
19my $nicename = 'OzTivo';
20my $config_file = "$progname.pw";
21my $output_file = "output.xmltv";
22my $channels_file;
23my $channels, my $opt_channels;
24my @clist;
25my $ver;
26my $ready;
27my $configure;
28my $raw_input;
29my $raw_output;
30my $d;
31
32print "$nicename Grabber v$version\n";
33
34$| = 1;
35
36GetOptions( 
37            'channels_file=s'   => \$channels_file,
38            'output=s'          => \$output_file,
39            'rawoutput=s'       => \$raw_output,
40            'rawinput=s'        => \$raw_input,
41            'version'           => \$ver,
42            'ready'             => \$ready,
43            'configure'         => \$configure
44          );
45
46exit 0 if ($ver);
47
48configure() if ($configure);
49
50unless (-r $config_file)
51{
52    print "Can't find $config_file!\n";
53    please_configure_me();
54    exit 1;
55}
56
57print "Reading configuration file $config_file.\n";
58
59unless (open(CONF, $config_file))
60{
61    print "Unable to read config file $config_file: $!\n";
62    please_configure_me();
63    exit 1;
64}
65my $line = <CONF>;
66close CONF;
67
68unless ($line =~ /^(.*):(.*)$/)
69{
70    print "Unable to parse config file!\n" .
71          "It should be in the format: username:password\n";
72    please_configure_me();
73    exit 1;
74}
75my ($user, $pw) = ($1, $2);
76
77unless ($user and $pw)
78{
79    print "Failed to extract a sensible username and password from config file.\n";
80    please_configure_me();
81    exit 1;
82}
83
84exit 0 if ($ready);
85
86unless ($channels_file)
87{
88    die "No --channels_file specified.\n";
89}
90
91unless( -r $channels_file)
92{
93    die "Unable to read channels file $channels_file: $!";
94}
95local (@ARGV, $/) = ($channels_file);
96eval <>;
97die "\nError in channels file!\nDetails:\n$@" if ($@);
98
99my $shortchannels;
100while (my ($name, $chanid) = each %$channels) 
101{
102  # Ignore differences between rural stations like
103  # Seven (Rockhampton) and Seven (Cairns) -- this is not a great
104  # solution and should be fixed.
105  $name =~ s/ *\(.*?\)//g;
106  $shortchannels->{$name} = $chanid;
107}
108
109# Create a list of channel names from longest to shortest
110@clist = sort { length $b <=> length $a } keys %$shortchannels;
111
112my $data;
113
114if (!defined $raw_input) {
115    my ($success, $status);
116    foreach my $tries (1 .. 3) {
117        ($data, $success, $status) = Shepherd::Common::get_url(
118                url => "http://$user:$pw\@minnie.tuhs.org/tivo-bin/xmlguide.pl",
119                retries => 5, retry_delay => 20);
120
121        unless ($success)
122        {
123            if ($status =~ /401/)
124            {
125                print "Your OzTivo username and/or password may be incorrect!\n".
126                        "The username and password you supplied when configuring the oztivo grabber\n" .
127                        "must match your registration details on the OzTivo.com site.\n".
128                        "If this error persists, try reconfiguring: ".
129                        "  tv_grab_au --configure $progname\n";
130            }
131            print "Download failed: $status\n";
132            exit 5;
133        }
134
135        my $bytes = do { use bytes; length($data) };
136        last if $bytes > 1000 or $data !~ /<message>/i;
137        $data =~ /<message>(.*?)<\/message>/si;
138        print "OzTivo message: $1\n";
139        exit 6 if $tries == 3;
140        my $sleep = int(30 + rand(30));
141        print "Sleeping for $sleep\n";
142        sleep($sleep);
143    }
144
145    if (defined $raw_output) {
146        open(F,">$raw_output") || die "could not write raw output to $raw_output: $!\n";
147        print F $data;
148        close(F);
149        print "Raw output saved in $raw_output.\n";
150    }
151} else {
152    open(F,"<$raw_input") || die "could not read raw input from $raw_input: $!\n";
153    while(<F>) {
154        $data .= $_;
155    }
156    close(F);
157    print "Raw input read from $raw_input.\n";
158}
159
160print "Converting apostrophes.\n";
161$data =~ s/\&#39;/'/g;
162
163# Adjust ABC2 times if we're in a non-DST zone but Sydney is in DST.
164# Apparently ABC2 times are set off Sydney's clock.
165adjust_abc2();
166
167print "Transforming XMLTVIDs.\n";
168&setup_channel_mappings;
169$data =~ s/channel="(.*)"/'channel="'.subme($1).'"'/ge;
170
171print "Writing output.\n";
172open (OUT, ">$output_file") || die "could not write to $output_file: $!\n";
173
174#
175# oztivo generates invalid XMLTV with fields out of order and
176# some blank fields.
177# the standard XMLTV.pm perl module gets very unhappy about these
178# write output in a manner which addresses the bad input
179#
180
181my @xmltv_tag_order = qw [ title sub-title desc credits date category language
182        orig-language length icon url country episode-num video audio
183        previously-shown permiere last-chance new subtitles rating
184        star-rating ];
185my %xmltv_tags = map { $_ => "" } @xmltv_tag_order;
186$xmltv_tags{"programme"}=""; $xmltv_tags{"/programme"}="";
187my $linenum = 0;
188my $cur_field = "";
189
190foreach my $line (split/\n/,$data) {
191    $linenum++;
192
193    # oztivo generates blank data for these fields - skip if blank
194    next if ($line =~ /<director><\/director>/);
195    next if ($line =~ /<desc><\/desc>/);
196    next if ($line =~ /^\s*$/);
197
198    if ($line =~ /\s*<([\/a-zA-Z\-]+)/) {
199        my $field = lc($1);
200        # do we know about this tag?
201        $cur_field = $field if (defined $xmltv_tags{$field});
202    }
203
204    if ($cur_field eq "programme") {
205        # if we have a start="(time)" and/or stop="(time)" make sure they
206        # have a timezone on them.
207        $line = $1."start=\"".$2." +0000\"".$3 if ($line =~ /^(.*)start="([0-9]+)"(.*)/);
208        $line = $1."stop=\"".$2." +0000\"".$3 if ($line =~ /^(.*)stop="([0-9]+)"(.*)/);
209
210        print OUT $line."\n"; # programme tag
211        $cur_field = "";
212    } elsif ($cur_field eq "/programme") {
213        print "Program with no title!\n" 
214                if (($xmltv_tags{title} eq "") or ($xmltv_tags{title} =~ />\s*</));
215        # print all previously seen tags in xmltv_tag_order
216        foreach my $xmltag (@xmltv_tag_order) {
217            if ($xmltv_tags{$xmltag} ne "") {
218                print OUT $xmltv_tags{$xmltag};
219                $xmltv_tags{$xmltag} = "";
220            }
221        }
222        print OUT $line."\n"; # /programme tag
223        $cur_field = "";
224    } else {
225        if ($cur_field eq "") {
226            print OUT $line."\n"; # ?xml, tv and /tv tags
227        } else {
228            $xmltv_tags{$cur_field} .= $line."\n"; # xmltv_tags tags
229        }
230    }
231}
232
233close OUT;
234
235print "Done.\n";
236
237
238
239sub please_configure_me
240{
241    print "If you wish to use $nicename, please run \"" .
242          "tv_grab_au --configure $progname\".\n" .
243          "Configuration of $progname is required.\n";
244}
245
246sub configure
247{
248    print "Configuring...\n\n" .
249          "Before you can use the $nicename grabber, you must create an\n" .
250          "account here:\n\n" .
251          '  http://minnie.tuhs.org/twiki/bin/view/TWiki/TWikiRegistration' .
252          "\n\n" .
253          "When you're done, you'll have a username and a password. Enter\n" .
254          "these here.\n\n" .
255          "Username?\n";
256    my $username = <>;
257    chomp $username;
258    unless ($username)
259    {
260        print "No username supplied. Exiting configuration.\n";
261        exit 3;
262    }
263    print "Password?\n";
264    my $pw = <>;
265    chomp $pw;
266    unless ($pw)
267    {
268        print "No password supplied. Exiting configuration.\n";
269        exit 3;
270    }
271    print "Creating config file $config_file...\n";
272    open (CONF, ">$config_file")
273        or die "Unable to create $config_file: $!";
274    print CONF "$username:$pw";
275    close CONF;
276
277    print "Done.\n";
278    exit 0;
279}
280
281sub subme
282{
283    my $station = shift;
284
285    $station = $d->{chan_map}->{$station} if (defined $d->{chan_map}->{$station});
286    $station =~ s/10/TEN/g;
287
288    my $num = $1 if ($station =~ /(\d{2,})/);
289    foreach my $ch (@clist)
290    {
291        if ($station =~ /$ch/i
292                or
293            ($num and $ch =~ /$num/))
294        {
295            return $shortchannels->{$ch};
296        }
297    }
298
299    return $opt_channels->{$station} if ((defined $opt_channels) && (defined $opt_channels->{$station}));
300   
301    if (!defined $d->{ignored_channels}->{$station}) {
302        $d->{ignored_channels}->{$station} = 1;
303        print "Warning: station \"$station\" unknown.\n";
304    }
305    return $station;
306}
307
308sub setup_channel_mappings
309{
310    my %map = (
311        "SBS-NEWS" => "SBS NEWS",
312        "ADV1" => "A1",
313        "ANIMAL" => "AnimalPlanet",
314        "ANT" => "AntennaPacific",
315        "ARNA" => "ARENATV",
316        "ARNA+2" => "ARENATV2",
317        "AUR" => "Aurora",
318        "BBC" => "BBCWorld",
319        "BIOG" => "Bio",
320        "BLM" => "BloombergTelevision",
321        "BOOM" => "Boomerang",
322        "CART" => "CartoonNetwork",
323        "CLAS" => "FOXClassics",
324        "CLAS+2" => "FOXClassics2",
325        "CMC" => "CountryMusicChannel",
326        "CMDY" => "THECOMEDYCHANNEL",
327        "CMDY+2" => "THECOMEDYCHANNEL2",
328        "CNBC" => "CNBCAustralia",
329        "CNNI" => "CNN",
330        "CRIME" => "CrimeandInvestigation",
331        "DISC" => "DiscoveryChannel",
332        "DISN" => "DisneyChannel",
333        "E!" => "E!Entertainment",
334        "ESPN" => "ESPN",
335        "EUROSPORT" => "Eurosportnews",
336        "EXPO" => "EXPO",
337        "FASH" => "FashionTV",
338        "FOOD" => "LifeStyleFOOD",
339        "FOX8" => "FOX8",
340        "FOX8+2" => "FOX82",
341        "FOXN" => "FOXNews",
342        "FS1" => "FOXSPORTS1",
343        "FS2" => "FOXSPORTS2",
344        "FS3" => "FOXSPORTS3",
345        "FSN" => "FOXSPORTSNews",
346        "FUEL" => "FUELTV",
347        "HALL" => "Hallmark",
348        "HEALTH" => "DiscoveryHealth",
349        "HIST" => "TheHistoryChannel",
350        "HOWTO" => "HOWTOChannel",
351        "LIFE" => "TheLifeStyleChannel",
352        "LIFE+2" => "LifestyleChannel2",
353        "MOV1" => "MOVIEONE",
354        "MOV1+2" => "MOVIETWO",
355        "MOVG" => "MOVIEGREATS",
356        "MOVX" => "MOVIEEXTRA",
357        "MTV" => "MTV",
358        "NGEO" => "NationalGeographic",
359        "NICK" => "Nickelodeon",
360        "NICKJR" => "NickJr",
361        "OVAT" => "Ovation",
362        "PHDISN" => "PlayhouseDisney",
363        "RAI" => "RAIInternational",
364        "SCIENCE" => "DiscoveryScience",
365        "SHOW" => "SHOWTIME",
366        "SHW2" => "SHOWTIME2",
367        "SHWGRTS" => "SHOWTIMEGreats",
368        "SKYN" => "SkyNewsAustralia",
369        "SKYR" => "SkyRacing",
370        "Sci-Fi" => "SCIFI",
371        "TCM" => "TCM",
372        "TRAVEL" => "DiscoveryTravel",
373        "TV1" => "TV1",
374        "TV1+2" => "TV12",
375        "TVSN" => "TVSN",
376        "TWC" => "TheWeatherChannel",
377        "UKTV" => "UKTV",
378        "UKTV+2" => "UKTV2",
379        "V" => "ChannelV",
380        "V2" => "ChannelV2",
381        "VH1" => "VH1",
382        "W" => "W",
383        "W+2" => "W2",
384        "WMOV" => "WORLDMOVIES",
385        "max" => "MAX"
386    );
387
388    foreach (keys %map)
389    {
390        $d->{chan_map}->{$_} = $map{$_};
391    }
392}
393
394# Wrapper sub to ensure TZ gets reset properly
395sub adjust_abc2
396{
397    adjust_abc2_times();
398    POSIX::tzset();
399}
400
401sub adjust_abc2_times
402{
403    my $local_tz = POSIX::strftime("%z",localtime);
404
405    local %ENV;
406    $ENV{TZ} = 'Australia/Sydney';
407    my $sydney_tz = POSIX::strftime("%z",localtime);
408
409    # Okay, I can't believe how hard this is... compute difference
410    # between two TZ strings.
411    my $l_min = int($local_tz / 100) * 60 + $local_tz % 100 % 60; 
412    my $s_min = int($sydney_tz / 100) * 60 + $sydney_tz % 100 % 60; 
413    my $tz_diff = int(($l_min - $s_min) / 60) * 100 + (($l_min - $s_min) % 60);
414
415    # Are we in DST?
416    my $local_dst = (localtime)[8];
417    my $sydney_dst = 0;
418    if (!$local_dst)
419    {
420        # Is Sydney in DST?
421        $sydney_dst = compare_sydney_dst();
422
423        if ($sydney_dst)
424        {
425            print "DST in Sydney but not here: adding 1hr to ABC2 times" .
426                  ($sydney_dst != 1 ? ($sydney_dst > 1 ? 
427                          " from $sydney_dst GMT" : 
428                          " until " . ($sydney_dst*-1) . ' GMT') : '') .
429                  ".\n";
430        }
431    }
432    $data =~ s/(?!=")(\d+)(?=".* channel="ABC2")/$1.add_offset($1,$tz_diff,$sydney_dst)/ge;
433}
434
435# Return '+0100' for any times that are in a period when Sydney's in DST
436# but we're not.
437sub add_offset
438{
439    my ($timestamp, $tz_diff, $sydney_dst) = @_;
440
441    if ($sydney_dst == 1
442            or
443        ($sydney_dst > 1 and $timestamp >= $sydney_dst)
444            or
445        ($sydney_dst < 0 and $timestamp <= ($sydney_dst*-1)))
446    {
447        $tz_diff += 0100;
448    }
449    return sprintf " %+0.4d", $tz_diff;
450}
451
452# Returns:
453# 0         : Sydney is not in DST in the next 7 days
454# 1         : Sydney is in DST for the next 7 days
455# <n>       : Sydney is in DST from <n> GMT, where n is (eg) 20070321030000
456# <-n>      : Sydney is in DST until <n> GMT, where n is (eg) -20070321030000
457# This func assumes that TZ has been set to Sydney by the caller (adjust_abc2_times)
458sub compare_sydney_dst
459{
460    my $sydney_dst = (localtime)[8];
461
462    # Normalize $start to 3AM Sydney time, or 2AM if it's in DST
463    my $start = time - (time % (60*60*24)) - (($sydney_dst ? 11 : 10) * 60*60) + (3*60*60);
464
465    # Check Sydney's DST status each day for 7 days
466    for (my $day = 0; $day < 7; $day++)
467    {
468        my $t = $start + ($day * 24*60*60);
469        my $sydney_dst_thisday = (localtime($t))[8];
470
471        # Any changeover?
472        if ($sydney_dst_thisday != $sydney_dst)
473        {
474            # Convert $t to GMT
475            $t -= (($sydney_dst ? 11 : 10) * 60*60);
476            my $changeoverday = POSIX::strftime("%Y%m%d%H%M%S", localtime($t));
477            $changeoverday *= -1 if ($sydney_dst);
478            return $changeoverday;
479        }
480    }
481    return $sydney_dst;
482}
Note: See TracBrowser for help on using the browser.