root/grabbers/oztivo @ 786

Revision 786, 11.9 kB (checked in by max, 6 years ago)

Oztivo tries a bit harder on network failure

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