root/grabbers/oztivo @ 840

Revision 840, 12.9 kB (checked in by paul, 6 years ago)

oztivo: remove number of line

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