root/trunk/postprocessors/augment_timezone

Revision 1390, 9.9 kB (checked in by max, 8 months ago)

shepherd 1.6.0: Change shebang lines from '/usr/bin/perl' to '/usr/bin/env perl', which is more portable (i.e. works on OSX).

  • Property svn:executable set to *
Line 
1#!/usr/bin/env perl
2
3# Augment XMLTV start/stop times with the local timezone if MythTV's
4# TimeOffset setting is anything other than "None"
5#  * to be used as a postprocessor for XMLTV data
6#  * can be used in conjunction with 'shepherd' XMLTV reconciler or standalone
7#    (pipe-through)
8#  * no configuration necessary
9#
10#  input XMLTV files will either have programme start/stop with or without
11#  timezones.  If no timezone is present, Shepherd assumes the input
12#  start/stop times are in 'localtime'.
13#
14#  If MythTV's "TimeOffset" setting is set to anything other than 'None',
15#  this can cause programming information to be out:
16#   - if set to 'All', all programs will be out by the difference between
17#     GMT and locatime ('All' means MythTV is expecting all start/stop times
18#     in GMT)
19#   - if explicitly set to GMT +/- XX then this will cause programming to
20#     be out whenever there is a switchover to/from daylight savings
21#
22#  this postprocessor addresses this by explicitly putting a timezone
23#  on every programme that doesn't already have one, BUT ONLY IF
24#  MythTV is configured to anything other than 'None'.
25#
26#  provided your unix system is configured into the correct timezone,
27#  this will work just fine including boundaries crossing daylight savings.
28#
29#  it means that it doesn't matter if MythTV's "TimeOffset" is set to
30#  'All' or 'None', or something inbetween, the data will be right regardless.
31
32# Specific enhancement for Broken Hill:
33#  it seems ABC1 broadcasts to Broken Hill with incorrect times. They're using
34#  a satellite feed from NSW so Broken Hill is always out by 30 minutes _just_
35#  for ABC1 (not ABC2 or any other stations).
36#  Since this seems to happen across all ABC1 data from all grabbers (i.e. all
37#  data sources get this wrong), fix it up in this postprocessor
38
39
40use strict;
41use warnings;
42my $progname = "augment_timezone";
43my $version = "0.22";
44
45use XMLTV;
46use POSIX qw(strftime mktime);
47use Getopt::Long;
48use IO::File;
49
50$| = 1;
51my %stats;
52my $channels, my $opt_channels;
53
54my $opt = { };
55$opt->{output_file} =   "output.xmltv";
56$opt->{debug} =                 0;
57
58# parse command line
59GetOptions(
60        'output=s'              => \$opt->{output_file},
61        'mysql_file=s'          => \$opt->{mysql_file},
62        'timeoffset=s'          => \$opt->{timeoffset},
63        'chanadjust=s'          => \$opt->{chanadjust},
64
65        'region=i'              => \$opt->{region},
66        'days=i'                => \$opt->{days},               # ignored
67        'offset=i'              => \$opt->{offset},             # ignored
68        'timezone=s'            => \$opt->{timezone},           # ignored
69        'channels_file=s'       => \$opt->{channels_file},
70        'config-file=s'         => \$opt->{configfile},         # ignored
71
72        'help'                  => \$opt->{help},
73        'verbose'               => \$opt->{help},
74        'version'               => \$opt->{version},
75        'ready'                 => \$opt->{ready},
76        'desc'                  => \$opt->{desc},
77        'v'                     => \$opt->{version});
78
79printf "%s v%s\n",$progname,$version;
80
81# Check if Shepherd::MythTV is working
82my $mythtv_access;
83unless ($opt->{timeoffset})
84{
85    eval { require Shepherd::MythTV; };
86    if ($@)
87    {
88        print "\nWARNING: No support for Shepherd::MythTV!\n".
89              "Continuing without MythTV access. Target timeoffset will be \"None\".\n".
90              "(You may override this with --timeoffset <offset>)\n".
91              "Reason: $@\n";
92    } else {
93        $mythtv_access = 1;
94    }
95}
96
97if ($opt->{version} || $opt->{desc} || $opt->{help} || $opt->{ready} ||
98    $opt->{output_file} eq "") {
99        printf "Automatically adjust the XMLTV start/stop timezone based on MythTV's\n".
100            "TImeOffset setting.\n" if $opt->{desc};
101
102        printf "$progname is ready for operation.\n" if ($opt->{ready});
103
104        printf "No --output file specified.\n" if ($opt->{output_file} eq "");
105
106        if ($opt->{help} || $opt->{output_file} eq "") {
107                my $default_loc = ($mythtv_access ? &Shepherd::MythTV::standard_mysql_locations : 'none');
108                print<<EOF
109
110usage: $0 [options] {FILE(s)}
111
112Supported options include:
113  --output={file}      Send final XMLTV output to {file} (default: $opt->{output_file})
114  --mysql_file={file}  File where we look for mythtv database user/pass/dbi (default: $default_loc)
115  --timeoffset={s}     Specify MythTV's setting, rather than try to look it up
116                       in MythTV's database. (E.g. "Auto", "None", "+1000")
117  --chanadjust={s}     Specify a specific channel to be time-adjusted
118                       (e.g. "SBS,30" will add 30 minutes to all SBS programs)
119EOF
120;
121        }
122        exit(0);
123}
124
125if (!$opt->{timeoffset} and $mythtv_access) {
126        # Specify a non-standard location for mysql.txt
127        Shepherd::MythTV::setup($opt->{mysql_file}) if ($opt->{mysql_file});
128
129        my $sql = "SELECT data FROM settings WHERE value LIKE 'TimeOffset'";
130        ($opt->{timeoffset}) = Shepherd::MythTV::query($sql);
131        if ($opt->{timeoffset})
132        {
133                printf "         MythTV's TimeOffset is \"%s\".\n", $opt->{timeoffset};
134        } else {
135                print  "         No valid response from MythTV.\n".
136                       "         Assuming MythTV's timezone is \"None\".\n".
137                       "         *** If this is wrong, guide data may be in wrong timezone! ***\n\n";
138        }
139}
140
141$opt->{timeoffset} = "None" if (!defined $opt->{timeoffset});
142if ($opt->{timeoffset} eq "None") {
143        print " - Target timezone is \"None\". No need to do anything.\n";
144} else {
145        printf " - Target timezone is \"%s\". Adding timezones.\n",$opt->{timeoffset};
146}
147
148# ABC1 fixup for Broken Hill; also chanadjust
149if ($opt->{chanadjust} or (defined $opt->{region}) && ($opt->{region} == 63)) {
150        die "no channel file specified\n", if (!$opt->{channels_file});
151
152        # read channels file
153        if (-r $opt->{channels_file}) {
154                local (@ARGV, $/) = ($opt->{channels_file});
155                no warnings 'all'; eval <>; die "$@" if $@;
156        } else {
157                die "WARNING: channels file $opt->{channels_file} could not be read\n";
158        }
159
160        if ($opt->{region} and $opt->{region} == 63 and $channels->{ABC1}) {
161            print " - System is in Broken Hill. Adjusting ABC1 guide data by 30 minutes!\n";
162        }
163}
164
165if ($opt->{chanadjust} and $opt->{chanadjust} =~ /(.*),(\d+)/)
166{
167    $opt->{'chanadjust-channel'} = $channels->{$1};
168    $opt->{'chanadjust-time'} = $2;
169    printf " - Manual channel adjustment: will add %d minutes to shows on %s.\n",
170            $opt->{'chanadjust-time'}, $opt->{'chanadjust-channel'};
171}
172
173my %writer_args = ( encoding => 'ISO-8859-1' );
174my $fh = new IO::File(">".$opt->{output_file}) || die "can't open $opt->{output_file} for writing: $!";
175$writer_args{OUTPUT} = $fh;
176
177my $writer = new XMLTV::Writer(%writer_args);
178$writer->start( {
179        'source-info-name' => "$progname $version",
180        'generator-info-name' => "$progname $version"} );
181
182foreach my $file (@ARGV) {
183        printf " - parsing: %s\n", ($file eq "-" ? "(from-stdin, hit control-D to finish)" : $file);
184        XMLTV::parsefiles_callback(undef, undef, \&channel_cb,\&programme_cb, $file);
185}
186
187$writer->end();
188
189printf "Finished parsing, output in $opt->{output_file}\n";
190printf "STATS: TimeOffset=".$opt->{timeoffset};
191foreach my $k (keys %stats) {
192        printf ", %d %s", $stats{$k}, $k;
193}
194printf "\n";
195
196exit(0);
197
198##############################################################################
199
200sub channel_cb( $ )
201{
202        my $c = shift;
203        # printf "got channel ".Dumper($c);
204        $writer->write_channel($c);
205}
206
207##############################################################################
208
209sub programme_cb( $ )
210{
211        my $prog=shift;
212
213        # ABC1 fixup for Broken Hill
214        if ((defined $opt->{region}) && ($opt->{region} == 63) &&
215            (defined $channels->{ABC1}) && ($prog->{channel} eq $channels->{ABC1})) {
216                $prog->{start} = POSIX::strftime("%Y%m%d%H%M00",localtime(parse_xmltv_date($prog->{start})-(30*60)));
217                $prog->{stop} = POSIX::strftime("%Y%m%d%H%M00",localtime(parse_xmltv_date($prog->{stop})-(30*60)));
218        }
219
220        if ($opt->{'chanadjust-channel'} and $prog->{channel} eq $opt->{'chanadjust-channel'})
221        {
222            $prog->{start} = POSIX::strftime("%Y%m%d%H%M00",localtime(parse_xmltv_date($prog->{start})+(60 * $opt->{'chanadjust-time'})));
223            $prog->{stop}  = POSIX::strftime("%Y%m%d%H%M00",localtime(parse_xmltv_date($prog->{stop})+(60 * $opt->{'chanadjust-time'})));
224        }
225
226        if ($opt->{timeoffset} ne "None") {
227                # if there is no timezone present in start time, put one there
228                if (($prog->{start} !~ /\+/) && ($prog->{start} !~ /\-/)) {
229                        $prog->{start} = POSIX::strftime("%Y%m%d%H%M00 %z",localtime(parse_xmltv_date($prog->{start})));
230                        $stats{start_tz_added}++;
231                }
232
233                # if there is no timezone present in stop time, put one there
234                if (($prog->{stop} !~ /\+/) && ($prog->{stop} !~ /\-/)) {
235                        $prog->{stop} = POSIX::strftime("%Y%m%d%H%M00 %z",localtime(parse_xmltv_date($prog->{stop})));
236                        $stats{stop_tz_added}++;
237                }
238        }
239
240        $writer->write_programme($prog);
241}
242
243##############################################################################
244
245# strptime type date parsing - BUT - if no timezone is present, treat time
246# as being in localtime rather than the various other perl implementation
247# which treat it as being in UTC/GMT
248
249sub parse_xmltv_date
250{
251        my $datestring = shift;
252        my @t; # 0=sec,1=min,2=hour,3=day,4=month,5=year,6=wday,7=yday,8=isdst
253        my $tz_offset = 0;
254
255        if ($datestring =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/) {
256                ($t[5],$t[4],$t[3],$t[2],$t[1],$t[0]) = (int($1)-1900,int($2)-1,int($3),int($4),int($5),0);
257                ($t[6],$t[7],$t[8]) = (-1,-1,-1);
258
259                my $e = mktime(@t);
260
261                # if input data has a timezone offset, then offset by that
262                if ($datestring =~ /\+(\d{2})(\d{2})/) {
263                        $tz_offset = calc_gmt_offset($e) - (($1*(60*60)) + ($2*60));
264                } elsif ($datestring =~ /\-(\d{2})(\d{2})/) {
265                        $tz_offset = calc_gmt_offset($e) + (($1*(60*60)) + ($2*60));
266                }
267
268                return ($e+$tz_offset) if ($e > 1);
269        }
270        return undef;
271}
272
273##############################################################################
274
275# given a particular date (in epoch time), return the local timezone offset
276# on that date in -/+ seconds from GMT
277
278sub calc_gmt_offset
279{
280        my $e = shift;
281        my $gmt_offset;
282
283        my $tzstring = strftime("%z", localtime($e));
284        $gmt_offset = (60*60) * int(substr($tzstring,1,2));     # hr
285        $gmt_offset += (60 * int(substr($tzstring,3,2)));       # min
286        $gmt_offset *= -1 if (substr($tzstring,0,1) eq "-");    # +/-
287
288        return $gmt_offset;
289}
Note: See TracBrowser for help on using the browser.