7
tv_grab_eu_epgdata - Grab TV listings for parts of Europe.
11
tv_grab_eu_epgdata --help
13
tv_grab_eu_epgdata --version
15
tv_grab_eu_epgdata --capabilities
17
tv_grab_eu_epgdata --description
20
tv_grab_eu_epgdata [--config-file FILE]
21
[--days N] [--offset N]
22
[--output FILE] [--quiet] [--debug]
24
tv_grab_eu_epgdata --configure [--config-file FILE]
26
tv_grab_eu_epgdata --configure-api [--stage NAME]
30
tv_grab_eu_epgdata --list-channels [--config-file FILE]
31
[--output FILE] [--quiet] [--debug]
33
tv_grab_eu_epgdata --preferredmethod
37
Output TV and listings in XMLTV format for many stations
40
First you must run B<tv_grab_eu_epgdata --configure> to choose which stations
43
Then running B<tv_grab_eu_epgdata> with no arguments will get a listings for
44
the stations you chose for five days including today.
46
This is a commercial grabber.
47
Go to http://wiki.xmltv.org/index.php/EU_epgdata to sign up or
48
send an e-mail to service@epgdata.com for further information.
49
It's also possible to ask for a test account.
53
B<--configure> Prompt for which stations to download and write the
56
B<--config-file FILE> Set the name of the configuration file, the
57
default is B<~/.xmltv/tv_grab_eu_epgdata.conf>. This is the file written by
58
B<--configure> and read when grabbing.
60
B<--gui OPTION> Use this option to enable a graphical interface to be used.
61
OPTION may be 'Tk', or left blank for the best available choice.
62
Additional allowed values of OPTION are 'Term' for normal terminal output
63
(default) and 'TermNoProgressBar' to disable the use of Term::ProgressBar.
65
B<--output FILE> When grabbing, write output to FILE rather than
68
B<--days N> When grabbing, grab N days rather than 5.
70
B<--offset N> Start grabbing at today + N days.
72
B<--quiet> Do not show status messages.
74
B<--debug> Provide more information on progress to stderr to help in
77
B<--list-channels> Output a list of all channels that data is available
78
for. The list is in xmltv-format.
80
B<--version> Show the version of the grabber.
82
B<--help> Print a help message and exit.
84
B<--preferredmethod> Shows the preferred method for downloading data
85
(see http://xmltv.org/wiki/xmltvcapabilities.html)
86
=head1 ENVIRONMENT VARIABLES
88
The environment variable HOME can be set to change where configuration
89
files are stored. All configuration is stored in $HOME/.xmltv/. On Windows,
90
it might be necessary to set HOME to a path without spaces in it.
92
=head1 SUPPORTED CHANNELS
94
For a list of supported channels, see the channel_ids file distributed with this grabber.
95
If additional channels are available, you will receive a warning when you run --configure.
97
Once I am aware that new channels are available, the channel_ids file will be updated and
98
this grabber will automatically fetch an updated copy.
102
The channel ids used in this grabber aim to be mostly possible with other grabbers, eg
103
tv_grab_de_prisma and some other grabbers for other countries.
104
NOTE: Retaining compatibility was not always possible or practicable.
105
You can get a list of channel ids using --list-channels
109
Michael Haas, laga -at- laga -dot- ath -dot - cx. This documentation is copied
110
from tv_grab_se_swedb by Mattias Holmlund, which in turn was copied from tv_grab_uk by Ed Avis.
111
Parts of the code are copied from tv_grab_se_swedb and tv_grab_na_dd (in XMLTV 0.5.45) as well
112
as various other sources.
116
There's no proper support for channels with locally different schedules. For example,
117
if your EPG package is a German one, you'll get the EPG schedule for Germany
118
even if you preferred the Swiss schedule which is also available in the data (for some channels at least).
120
Timezones are not handled correctly. Currently, you have to enter your
121
time zone manually during the configure step. You have to do this every
122
time your time zone changes, eg for daylight saving time
123
("Sommerzeit" and "Normalzeit" for my fellow Germans).
124
I'll try to have this fixed for the next XMLTV release.
125
Please see this thread for further discussion and some additional issues:
126
http://thread.gmane.org/gmane.comp.tv.xmltv.devel/7919
127
FYI: you can modify the time zone directly in the config file which is
128
usually located at ~/.xmltv/tv_grab_eu_epgdata.conf or
129
~/.mythtv/FOO.xmltv where FOO is the name of your video source in MythTV.
131
If the data source gives us data for one day, they'll also cover a part of the following day.
132
Maybe this should be fixed. Please note: data is not overlapping! So if we want to get data for
133
today, we might as well grab yesterday because that'll give us EPG till ~5am for today.
135
I'm sure this list is not complete. Let me know if you encounter additional problems.
142
use LWP::Simple qw($ua getstore);
144
use File::Temp qw/ tempdir /;
148
use XMLTV::Options qw/ParseOptions/;
149
use XMLTV::Configure::Writer;
150
use XMLTV::Supplement qw/GetSupplement/;
151
use HTTP::Request::Common;
156
# to parse expiry dates
159
use XMLTV::Memoize; XMLTV::Memoize::check_argv('getstore');
162
$ua->agent("xmltv/$XMLTV::VERSION");
164
our $tmp= tempdir( CLEANUP => 1 ) . '/';
165
#our $tmp= tempdir() . '/';
167
our $epg= new XML::Twig( twig_handlers => { data => \&printepg } );
168
our $channels = new XML::Twig( twig_handlers => { data => \&printchannels } );
170
our $genre = new XML::Twig( twig_handlers => { data => \&makegenrehash } );
172
# build a hash: epgdata.com channel id -> xmltv channel id
173
my $chanids = GetSupplement( 'tv_grab_eu_epgdata', 'channel_ids' );
179
my @lines = split( /[\n\r]+/, $chanids );
180
foreach my $line (@lines) {
182
my @chanid_array = split(';', $line);
183
chomp($chanid_array[1]);
184
$chanid{$chanid_array[0]}= $chanid_array[1] unless $line =~ '^#';
188
my( $opt, $conf ) = ParseOptions( {
189
grabber_name => "tv_grab_eu_epgdata",
190
capabilities => [qw/baseline manualconfig tkconfig apiconfig cache preferredmethod/],
191
stage_sub => \&config_stage,
192
listchannels_sub => \&list_channels,
193
version => '$Id: tv_grab_eu_epgdata,v 1.24 2008/06/06 10:38:42 mihaas Exp $',
194
description => "Parts of Europe (commercial) (www.epgdata.com)",
195
preferredmethod => "allatonce",
198
my $pin = $conf->{pin}->[0];
199
die 'Sorry, your PIN is not defined. Run tv_grab_eu_epgdata --configure to fix this.\n' unless $pin;
201
our $tz = $conf->{tz}->[0];
204
warn 'Time zone is not set. Run tv_grab_eu_epgdata --configure to fix this.\n' unless $opt->{quiet};
209
# shamelessly stolen from http://xmltv.org/wiki/howtowriteagrabber.html
211
my( $stage, $conf ) = @_;
212
# Sample stage_sub that only needs a single stage.
213
die "Unknown stage $stage" if $stage ne "start";
216
my $configwriter = new XMLTV::Configure::Writer( OUTPUT => \$result,
217
encoding => 'ISO-8859-1' );
218
$configwriter->start( { grabber => 'tv_grab_eu_epgdata' } );
219
$configwriter->write_string( {
221
title => [ [ 'Enter your PIN for epgdata.com', 'en' ] ],
223
[ 'This alphanumeric string is used for authentication with epgdata.com.
224
Go to http://wiki.xmltv.org/index.php/EU_epgdata to sign up or
225
send an e-mail to service@epgdata.com for further information',
229
$configwriter->write_string( {
231
title => [ [ 'Time zone for your EPG data', 'en' ] ],
233
[ 'Enter the time offset from UTC here. Think of it as your time zone.
234
For example: during winter in Germany, you should enter "+0100". During summer, use "+0200". (without quotation marks) ',
239
$configwriter->end( 'select-channels' );
245
# construct writer object
246
# taken from tv_grab_na_dd (XMLTV 0.4.45)
247
# XMLTV::Options does not redirect stdout properly for us
248
# XML::Twig probably messes it up, I don't know. :/
250
if (defined $opt->{output}) {
251
my $fh = new IO::File(">$opt->{output}");
252
die "ERROR: cannot write to $opt->{output}: $!" if not defined $fh;
253
$w_args{OUTPUT} = $fh;
255
$w_args{encoding} = 'ISO-8859-1';
257
our $writer = new XMLTV::Writer(%w_args);
260
our @xmlfiles = downloadepg($opt->{days},$opt->{offset},$pin);
261
prepareinclude($conf,$opt);
262
# it looks like we can also extract the language from the file
263
# name of the epg data
264
processxml(@xmlfiles);
273
# we've got to start counting at 0
274
# if we did "$i <= $days", we'd end up with one zip file too much
275
while ( $i < $days) {
276
my $dataoffset = $i +$offset;
277
my $baseurl="http://www.epgdata.com";
278
my $url=$baseurl . '/index.php?action=sendPackage&iOEM=&pin=' . $pin . '&dayOffset=' . $dataoffset . '&dataType=xml';
279
# get file name from content-disposition header
280
my $response = $ua->request(GET $url);
281
my $filename = $response->{"_headers"}{"content-disposition"};
282
$expiry_date = $response->{"_headers"}{"x-epgdata-timeout"};
283
$channelgroup = $response->{"_headers"}{"x-epgdata-channelgroup"};
284
$filename =~ s/^.*=//;
285
if ($response->{"_headers"}{"x-epgdata-packageavailable"} == 1) {
286
warn "Downloading zip file for day ", $dataoffset + 1, "\n" unless $opt->{quiet};
287
open(F,">$tmp" . $filename);
288
print F $response->content;
290
push @filenames, $tmp . $filename;
293
warn "No more zip files available for download\n" unless $opt->{quiet};
294
return unzip(@filenames);
298
return unzip(@filenames);
301
# for simplicity's sake, always call with $conf as argument at least
303
my ( $conf, $opt ) = @_;
304
my $baseurl="http://www.epgdata.com";
305
my $pin = $conf->{pin}->[0];
306
my $includeurl=$baseurl . "/index.php?action=sendInclude&iOEM=&pin=" . $pin
308
warn "Downloading include zip file\n" unless $opt->{quiet};
309
getstore($includeurl, $tmp . "includezip");
310
# make sure we know $channelgroup
311
downloadepg('1','0',$pin);
312
warn "Your PIN will expire around ", time2str("%C", $expiry_date),"\n" unless $opt->{quiet};
313
my @zipfiles=( $tmp . "includezip");
317
# returns list of *.xml files
320
foreach my $zipfile (@_) {
321
warn "Extracting *.dtd and *.xml from ", $zipfile, "\n" if $opt->{debug};
322
my $zip = Archive::Zip->new( $zipfile );
323
my @filelist = $zip->memberNames;
324
foreach my $filename (@filelist) {
325
# we only care about .dtd and .xml right now
326
my $isdtd = 1 if $filename =~ /\.dtd/;
327
my $isxml = 1 if $filename =~ /\.xml/;
328
$zip->extractMember( $filename, $tmp . $filename ) if ($isdtd or $isxml);
329
push @xmlfilelist, ( $tmp . $filename) if $isxml;
337
$writer->start({ 'generator-info-name' => 'tv_grab_eu_epgdata' });
338
$genre->parsefile($tmp . 'genre.xml');
339
$channels->parsefile($tmp . 'channel_' . $channelgroup . '.xml');
340
foreach my $xmlfile (@_) {
341
warn "Processing ". $xmlfile, "\n" if $opt->{debug};
342
$epg->parsefile($xmlfile);
349
my( $twig, $genre)= @_;
350
my $genreid = $genre->first_child('g0')->text;
351
my $genrename = decode_entities($genre->first_child('g1')->text);
352
$genre{$genreid}= $genrename;
359
my( $twig, $sendung)= @_;
360
my $internalchanid = $sendung->first_child('d2')->text;
361
my $internalregionid = $sendung->first_child('d3')->text;
363
if (defined $main::chanid{$internalchanid}) {
364
$chanid = $main::chanid{$internalchanid};
367
$chanid = $internalchanid;
368
# FIXME: not sure if this is correct.
369
# Maybe we should behave differently if we encounter an unknown ID,
370
# but this ought to be OK for now
372
# alright, let's try this:
373
# push the channel ids we want to grab in an array
374
# http://effectiveperl.blogspot.com/
375
my %configuredchannels = map { $_, 1 } @{$conf->{channel}};
376
# does the channel we're currently processing exist in the hash?
377
# BTW: this is not a lot more efficient in our case than looping over a list
378
# but a few seconds are better than nothing :)
379
if($configuredchannels{$chanid} && $internalregionid == '0') {
380
my $title = decode_entities($sendung->first_child('d19')->text);
381
my $subtitle = decode_entities($sendung->first_child('d20')->text);
382
my $desc = decode_entities($sendung->first_child('d23')->text);
383
my $start = $sendung->first_child('d4')->text;
384
my $internalgenreid = $sendung->first_child('d25')->text;
385
my $rating = $sendung->first_child('d30')->text;
386
my $wide_aspect = $sendung->first_child('d29')->text;
388
my $series_id = $sendung->first_child('d1')->text;
391
my $presenter = decode_entities($sendung->first_child('d34')->text);
392
my $studio_guest = decode_entities($sendung->first_child('d35')->text);
393
my $director = decode_entities($sendung->first_child('d36')->text);
394
my $actor = decode_entities($sendung->first_child('d37')->text);
398
my $bw_colour = $sendung->first_child('d11')->text;
399
my $stereo_audio = $sendung->first_child('d27')->text;
400
my $dolby_audio = $sendung->first_child('d28')->text;
401
# I was told that technics_hd is supposed to exist
402
# However, it's not listed in qy.dtd
403
# my $hd_video = $sendung->first_child('XXX')->text;
408
our %prog = ("channel" => $chanid, "start" => "$start $tz",
409
"title" => [ [ $title ] ]);
411
if ( length($subtitle) > 0 ) {
412
push @{$prog{'sub-title'}}, [$subtitle];
416
if (exists $genre{$internalgenreid} ) {
417
push @{$prog{'category'}}, [$genre{$internalgenreid}];
420
if (length($desc) > 0 ) {
421
push @{$prog{'desc'}}, [$desc];
425
if ( length($actor)>0 ) {
426
push @{$prog{'credits'}{'actor'}}, $actor;
429
if ( length($director)>0 ) {
430
push @{$prog{'credits'}{'director'}}, $director;
433
if ( length($studio_guest)>0) {
434
push @{$prog{'credits'}{'guest'}}, $studio_guest;
437
if ( length($presenter)>0) {
438
push @{$prog{'credits'}{'presenter'}}, $presenter;
442
# star-rating: the data source seems to say <d30>0</d30>
443
# if they mean "unknown"
444
# valid values seem to be 1 to 5
446
if ( $rating gt 0 ) {
447
$prog{'star-rating'} = ["$rating/5"];
450
if ($wide_aspect == 1 ) {
451
$prog{'video'}->{'aspect'} = '16:9';
454
if ($bw_colour == 1 ) {
455
$prog{'video'}->{'colour'} = '0';
459
$prog{'episode-num'} = [ [$series_id, 'http://fix.me/' ] ];
461
# check for dolby first
462
# not sure if dolby_audio and stereo_audio can be true
463
# simultaneously in the source data, but it's better to be
465
# If stereo_audio is false, is it safe to assume the programme
466
# will be broadcast in mono?
467
# I mean, this is the 21th century, right?
468
# Also, what does dolby mean in this context?
469
# How does it apply to analog broadcasts?
470
if ($dolby_audio == 1) {
471
$prog{'audio'}->{'stereo'} = 'dolby';
473
elsif ($stereo_audio == 1) {
474
$prog{'audio'}->{'stereo'} = 'stereo';
477
$writer->write_programme(\%main::prog);
485
# this is called as a handler for the channels twig
486
# which is in turn called by processxml()
488
my( $twig, $sendung)= @_;
489
my $internalchanid = $sendung->first_child('ch4')->text;
491
if (defined $main::chanid{$internalchanid}) {
492
$chanid = $main::chanid{$internalchanid};
495
$chanid = $internalchanid;
496
# FIXME: not sure if this is correct.
497
# Maybe we should just return if we don't know the channel id
499
my $name = decode_entities($sendung->first_child('ch0')->text);
500
foreach my $channel (@{$conf->{channel}}) {
501
if($channel eq $chanid) {
502
my %ch = (id => $chanid, 'display-name' => [ [ $name ] ]);
503
$writer->write_channel(\%ch);
508
# this lists all _available_ channels
509
# used for --configure
510
# independent from printchannels which will print list of configured channels
512
my ( $conf, $opt ) = @_;
513
prepareinclude($conf, $opt);
514
# borrowed from http://www.xmltwig.com/xmltwig/ex_fm1
515
$channels->parsefile($tmp . 'channel_' . $channelgroup . '.xml');
516
my $channel_list= $channels->root;
517
my @channels= $channel_list->children;
519
my $xmltv_channel_list = "<tv generator-info-name=\"tv_grab_eu_epgdata\">\n";
521
foreach my $channel (@channels) {
522
my $internalchanid = $channel->first_child('ch4')->text;
524
if (defined $main::chanid{$internalchanid}) {
525
$chanid = $main::chanid{$internalchanid};
528
$chanid = $internalchanid;
529
warn "New channel with ID $internalchanid found. Please update chann
530
el_ids file!" unless $opt->{quiet};
533
my $name = decode_entities($channel->first_child('ch0')->text);
534
$xmltv_channel_list = <<END;
536
<channel id="$chanid">
537
<display-name>$name</display-name>
541
$xmltv_channel_list = $xmltv_channel_list . "</tv>";
542
return $xmltv_channel_list