root/postprocessors/tvdb_augment_data @ 725

Revision 725, 30.8 kB (checked in by lincoln, 6 years ago)

more commits for postprocessors/tvdb_augment_data, not yet ready though

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl -w
2
3# tvdb (thetvdb.com / tv.com) XMLTV data augmenter  <ltd@interlink.com.au>
4#
5#  * to be used as a postprocessor for XMLTV data
6#  * uses data from thetvdb.com to augment TV guide data
7#  * this should only be used for non-commercial use.
8#  * can be used in conjunction with 'shepherd' XMLTV reconciler or standalone
9#    (pipe-through)
10#  * no configuration necessary
11#
12#  thanks to Scott Zsori, Paul Taylor, Josh Walters and the other thetvdb.com
13#  folks for providing the data and an easy interface/schema to search for data!
14#
15#  changelog:
16#    0.01 31may07 ltd   initial version
17#                       as per trac ticket #31
18
19use strict;
20
21my $progname = "tvdb_augment_data";
22my $version = "0.01";
23my $mirrorlist_url = 'http://thetvdb.com/interfaces/GetMirrors.php';
24
25use XMLTV;
26use XML::DOM;
27use Getopt::Long;
28use HTML::TreeBuilder;
29use Data::Dumper;
30use Storable;
31use Shepherd::Common;
32
33#
34# some initial cruft
35#
36
37my $script_start_time = time;
38my %stats;
39my $data_cache;
40my $settings_override = { };
41my $d;
42my $parser = new XML::DOM::Parser;
43
44$| = 1;
45
46#
47# parse command line
48#
49
50my $opt = { };
51$opt->{output_file} =           "output.xmltv";
52$opt->{cache_file} =            $progname.".storable.cache";
53$opt->{lang} =                  "en";
54$opt->{debug} =                 0;
55$opt->{min_duration} =          25;     # 25 mins
56$opt->{max_duration} =          140;    # 2 hrs 20 mins
57$opt->{skip_categories} =       "Shopping,Business and Finance,Game Show,News,Parliament,sports,Sport,Weather,live,Education,Movies,Movie,Documentary,Society and Culture";
58$opt->{cache_details_for} =     60;     # cache series details for up to 2 months
59$opt->{cache_title_for} =       90;     # cache title lookups for 3 months
60
61GetOptions(
62        'region=i'              => \$opt->{region},             # ignored
63        'days=i'                => \$opt->{days},               # ignored
64        'offset=i'              => \$opt->{offset},             # ignored
65        'timezone=s'            => \$opt->{timezone},           # ignored
66        'channels_file=s'       => \$opt->{channels_file},      # ignored
67        'config-file=s'         => \$opt->{configfile},         # ignored
68
69        'min_duration=i'        => \$opt->{min_duration},
70        'max_duration=i'        => \$opt->{max_duration},
71        'skip_categories=s'     => \$opt->{skip_categories},
72        'cache_details_for=i'   => \$opt->{cache_details_for},
73        'cache_title_for=i'     => \$opt->{cache_title_for},
74        'dont-augment-desc'     => \$opt->{dont_augment_desc},
75
76        'output=s'              => \$opt->{output_file},
77        'cache-file=s'          => \$opt->{cache_file},
78        'fast'                  => \$opt->{fast},
79        'debug+'                => \$opt->{debug},
80        'lang=s'                => \$opt->{lang},
81        'help'                  => \$opt->{help},
82        'test=s'                => \$opt->{test},
83        'set=s'                 => \$opt->{set},
84        'verbose'               => \$opt->{help},
85        'version'               => \$opt->{version},
86        'ready'                 => \$opt->{ready},
87        'desc'                  => \$opt->{desc},
88        'v'                     => \$opt->{version});
89
90if ($opt->{version} || $opt->{desc} || $opt->{help} || $opt->{ready} ||
91    $opt->{output_file} eq "" || (scalar @ARGV == 0)) {
92        printf "%s v%s\n",$progname,$version;
93        printf "Augments XMLTV data with programme information from thetvdb.com\n" if $opt->{desc};
94        printf "$progname is ready for operation.\n" if ($opt->{ready});
95        printf "No --output file specified.\n" if ($opt->{output_file} eq "");
96        printf "No input XMLTV files specified.\n" if (scalar @ARGV == 0);
97
98        &help if ($opt->{help} || $opt->{output_file} eq "" || (scalar @ARGV == 0));
99        exit(0);
100}
101
102&run_test if (defined $opt->{test});
103&set_settings if (defined $opt->{set});
104
105# set defaults
106Shepherd::Common::set_default("debug", ((defined $opt->{debug} && $opt->{debug} > 0) ? 2 : 0));
107Shepherd::Common::set_default("retry_delay", 10);
108# Shepherd::Common::set_default("delay", int(rand(4) + 3)) unless (defined $opt->{fast});
109Shepherd::Common::set_default('fake' => 0);
110
111# go go go!
112
113Shepherd::Common::log(sprintf "%s v%s started: %s%soutput %s\n",
114        $progname, $version,
115        ($opt->{fast} ? "fast-override, " : ""),
116        ($opt->{debug} ? "debug enabled, " : ""),
117        ($opt->{output_file}));
118
119&read_cache;
120
121Shepherd::Common::log("Stage 1/6: reading input xmltv files...");
122foreach my $file (@ARGV) {
123        &read_xmltv($file);
124}
125
126&perform_lookups;
127&write_xmltv;
128&write_cache;
129Shepherd::Common::print_stats($progname, $version, $script_start_time, %stats);
130exit(0);
131
132##############################################################################
133
134sub help
135{
136        print<<EOF
137usage: $0 [options] {FILE(s)}
138
139Supported options include:
140  --min_duration={min} ignore programs under {min} duration (default: $opt->{min_duration} min)
141  --max_duration={min} ignore programs over {min} duration (default: $opt->{max_duration} min)
142  --skip_categories={list} don't try to look up programmes in these categories (default: $opt->{skip_categories})
143
144  --dont-augment-desc  don't add IMDb data to programme description,
145                       only update the data fields (default: do)
146
147  --cache_details_for={days}  cache programme details for {days} (def: $opt->{cache_details_for} days)
148  --cache_title_for={days}    cache IMDb URLs for {days} (def: $opt->{cache_title_for} days)
149
150  --lang={lang}        set language to {lang} (default: $opt->{lang})
151  --output={file}      send final XMLTV output to {file} (default: $opt->{output_file})
152  --debug              enable debugging
153  --fast               don't pause between requests to www.imdb.com
154
155  --cache-file={file}  local file to use as our data cache (default: $opt->{cache_file})
156  --no-cache           don't use local cache to reduce network load on www.imdb.com
157
158  --test=(string)      operate in 'test mode', look up prog named (string)
159
160  --set=(setting):(value) save setting override: (value) 1=enable, 0=disable
161        dont_augment_desc:1/0 (don't / do)
162
163EOF
164;
165}
166
167##############################################################################
168
169sub set_settings
170{
171        &read_cache;
172        my ($setting, $val) = split(/:/,$opt->{set});
173
174        die "--set format is (setting):(value) where value is 0 for disable, 1 for enable.\n"
175          if ((!defined $val) || (($val ne "0") && ($val ne "1")));
176
177        die "unknown '--set' parameter '$setting', see --help for details.\n"
178          if ($setting ne "dont_augment_desc");
179
180        $settings_override->{$setting} = $val;
181        printf "%s: override parameter %s: %s\n", $progname, $setting, ($val eq "0" ? "disabled" : "enabled");
182
183        &write_cache;
184        exit(0);
185}
186
187##############################################################################
188
189sub get_url
190{
191        my %cnf = @_;
192        my ($html_data, $success, $status_msg, $bytes_fetched, $seconds_slept, $failed_attempts) = Shepherd::Common::get_url(%cnf);
193
194        $stats{failed_requests} += $failed_attempts;
195        $stats{slept_for} += $seconds_slept;
196        $stats{bytes_fetched} += $bytes_fetched;
197
198        return undef if ((!$html_data) || (!$success));
199        return $html_data;
200}
201
202##############################################################################
203# populate cache
204
205sub read_cache
206{
207        if (-r $opt->{cache_file}) {
208                my $store = Storable::retrieve($opt->{cache_file});
209                $data_cache = $store->{data_cache};
210                $settings_override = $store->{settings_override};
211
212                foreach my $setting (keys %$settings_override) {
213                        $opt->{$setting} = 1 if ($settings_override->{$setting} != 0);
214                }
215        } else {
216                printf "WARNING: no cache $opt->{cache_file} - ".
217                  "have to fetch all details.\n";
218                &write_cache; # try to write to it - failure will cause an error & barf
219        }
220
221        #
222        # age our caches on startup
223        #
224
225        # age our programme cache on startup
226        foreach my $key (keys %{($data_cache->{prog})}) {
227                my $num_items = 0;
228                foreach my $key2 (keys %{($data_cache->{prog}->{$key})}) {
229                        if ($data_cache->{prog}->{$key}->{$key2}->{expires} < time) {
230                                delete $data_cache->{prog}->{$key}->{$key2};
231                                $stats{removed_prog_from_cache}++
232                        } else {
233                                $num_items++;
234                        }
235                }
236                delete $data_cache->{prog}->{$key} if ($num_items == 0);
237        }
238
239        # age our title cache on startup
240        foreach my $key (keys %{($data_cache->{title})}) {
241                if ($data_cache->{title}->{$key}->{expires} < time) {
242                        delete $data_cache->{title}->{$key};
243                        $stats{removed_title_from_cache}++
244                }
245        }
246}
247
248##############################################################################
249# write out updated cache
250
251sub write_cache
252{
253        my $store;
254        $store->{data_cache} = $data_cache;
255        $store->{settings_override} = $settings_override;
256        Storable::store($store, $opt->{cache_file});
257}
258
259##############################################################################
260
261sub read_xmltv
262{
263        my $filename = shift;
264        $d->{files}++;
265
266        Shepherd::Common::log((sprintf "    parsing: (%d) %s",$d->{files},$filename));
267        $d->{data}->[($d->{files}-1)] = XMLTV::parsefile($filename);
268
269        $d->{progcount}->[($d->{files}-1)] = scalar(@{$d->{data}->[($d->{files}-1)][3]});
270        $stats{programmes} += $d->{progcount}->[($d->{files}-1)];
271}
272
273##############################################################################
274
275sub write_xmltv
276{
277        Shepherd::Common::log("\nStage 6/6: writing output XMLTV into ".$opt->{output_file});
278
279        my %writer_args = ( encoding => 'ISO-8859-1' );
280        my $fh = new IO::File(">".$opt->{output_file}) ||
281          die "can't open $opt->{output_file} for writing: $!";
282        $writer_args{OUTPUT} = $fh;
283
284        my $writer = new XMLTV::Writer(%writer_args);
285        $writer->start( {
286                'source-info-url' => "http://thetvdb.com",
287                'source-info-name' => "$progname $version",
288                'generator-info-name' => "$progname $version"} );
289
290        for (my $i=0; $i < $d->{files}; $i++) {
291                for (my $j = 0; $j < $d->{progcount}->[$i]; $j++) {
292                        my $prog = $d->{data}->[$i][3][$j];
293                        Shepherd::Common::cleanup($prog);
294                        $writer->write_programme($prog);
295                }
296        }
297
298        $writer->end();
299}
300
301##############################################################################
302# process all xmltv files
303
304sub perform_lookups
305{
306        $d->{series_lookup_requests} = 0;
307        $d->{episode_lookup_requests} = 0;
308
309        Shepherd::Common::log("\nStage 2/6: processing ".$stats{programmes}." programmes ...");
310        my $prog_count = 0;
311        my $last_updated;
312        for (my $i=0; $i < $d->{files}; $i++) {
313                for (my $j = 0; $j < $d->{progcount}->[$i]; $j++) {
314                        $prog_count++;
315                        if ((!$last_updated) || ((time - $last_updated) > 20) || ($prog_count == $stats{programmes})) {
316                                $last_updated = time;
317                                my $percent_complete = (($prog_count+1) / ($stats{programmes}+1));
318                                my $estimaged_time = ((time - $script_start_time) / $percent_complete);
319                                Shepherd::Common::log((sprintf "  .. at programme %d of %d (%0.1f%%) [%s elapsed] ..",
320                                        $prog_count, $stats{programmes}, ($percent_complete * 100),
321                                        Shepherd::Common::pretty_duration((time - $script_start_time))));
322                        }
323
324                        my $prog = $d->{data}->[$i][3][$j];
325                        my $title, my $subtitle;
326                        $title = $prog->{title}->[0]->[0] if ((defined $prog->{title}) && (defined $prog->{title}->[0]) && (defined $prog->{title}->[0]->[0]));
327                        $subtitle = $prog->{'sub-title'}->[0]->[0] if ((defined $prog->{'sub-title'}) && (defined $prog->{'sub-title'}->[0]) && (defined $prog->{'sub-title'}->[0]->[0]));
328
329                        if ((defined $title) && (include_prog($prog))) {
330                                my $lc_title = lc($title);
331                                &lookup_title_web($title,$prog_count) if (!defined $data_cache->{prog}->{$lc_title});
332
333                                # proceed to episode lookup if we have something in our title cache
334                                if ((defined $data_cache->{title}->{$lc_title}) && ($data_cache->{title}->{$lc_title}->{notfound} == 0)) {
335                                        $subtitle = "" if (!defined $subtitle);
336                                        &lookup_episode($lc_title, $subtitle);
337                                }
338                        }
339                }
340        }
341
342        Shepherd::Common::log("\nStage 3/6: processing ".$d->{series_lookup_requests}." series detail lookup requests ...");
343        &lookup_series_updates if ($d->{series_lookup_requests} > 0);
344
345        Shepherd::Common::log("\nStage 4/6: processing ".$d->{episode_lookup_requests}." episode detail lookup requests ...");
346        &lookup_episode_updates if ($d->{episode_lookup_requests} > 0);
347
348        Shepherd::Common::log("\nStage 5/6: processing ".$d->{episode_lookup_requests}." episode detail lookup requests ...");
349}
350
351##############################################################################
352# helper routine: returns 1 if we should look up programme, 0 if not
353
354sub include_prog
355{
356        my $prog = shift;
357
358        if ((!defined $prog->{title}) || (!defined $prog->{title}->[0]) || (!defined $prog->{title}->[0]->[0])) {
359                $stats{skipped_due_to_title}++;
360                next;
361        }
362        my $title = $prog->{title}->[0]->[0];
363
364        # skip station close
365        if (($title =~ /^close$/i) || ($title =~ /^station close$/i)) {
366                $stats{skipped_due_to_title}++;
367                return 0;
368        }
369
370        # skip categories
371        if (defined $prog->{category}) {
372                foreach my $prog_category (@{($prog->{category})}) {
373                        foreach my $prog_cat2 (@$prog_category) {
374                                foreach my $skip_category (split(/,/,$opt->{skip_categories})) {
375                                        if (lc($prog_cat2) eq lc($skip_category)) {
376                                                $stats{skipped_due_to_category}++;
377                                                return 0;
378                                        }
379                                }
380                        }
381                }
382        }
383
384        # only lookup if  min_duration < prog_duration > min_duration
385        my $t1 = Shepherd::Common::parse_xmltv_date($prog->{start});
386        my $t2 = Shepherd::Common::parse_xmltv_date($prog->{stop});
387        if ((!$t1) || (!$t2)) {
388                $stats{excluded_couldnt_parse_time}++;
389                return 0;
390        }
391
392        # ensure prog is within duration limits
393        my $prog_duration = (($t2 - $t1) / 60);
394        if (($prog_duration < $opt->{min_duration}) || ($prog_duration > $opt->{max_duration})) {
395                $stats{excluded_prog_duration}++;
396                return 0;
397        }
398
399        return 1;
400}
401
402##############################################################################
403# fill in $d->{tvdb}->{mirrors} with a list of tvdb mirror sites
404
405sub lookup_mirrors
406{
407        my $data = get_url(url => $mirrorlist_url);
408        die "could not gather list of mirrors from $mirrorlist_url\n" if (!$data);
409
410        my $xml_tree = $parser->parse($data);
411        $d->{tvdb}->{num_mirrors} = $xml_tree->getElementsByTagName("Item")->getLength;
412        for (my $i = 0; $i < $d->{tvdb}->{num_mirrors}; $i++) {
413                push(@{($d->{tvdb}->{mirrors})},
414                        $xml_tree->getElementsByTagName("Item")->item($i)->getElementsByTagName("interface")->item(0)->getFirstChild->getNodeValue);
415        }
416        $xml_tree->dispose;
417
418        die "no mirrors found from $mirrorlist_url\n" if ($d->{tvdb}->{num_mirrors} == 0);
419
420        $d->{tvdb}->{mirror} = $d->{tvdb}->{mirrors}[int(rand($d->{tvdb}->{num_mirrors}))];
421        Shepherd::Common::log("    chose mirror ".$d->{tvdb}->{mirror}." for data");
422
423}
424
425##############################################################################
426# find a 'seriesid' associated with this title
427#  (actually just populates $data_cache->{title}->{$lc_title})
428
429sub lookup_title_web
430{
431        my ($title, $prog_count) = @_;
432        my $lc_title = lc($title);
433
434        my $letter = substr($lc_title,0,1);
435        $letter = "OTHER" if ($letter !~ /[a-z]/);
436
437        my $found_title = 0;
438
439        # only lookup if we have passed our caching threshold for this letter...
440        if ((!defined $data_cache->{title}->{"WEB_LOOKUP_".$letter}->{expires}) ||
441            ($data_cache->{title}->{"WEB_LOOKUP_".$letter}->{expires} < time)) {
442                Shepherd::Common::log("    fetching series tables for '".$letter."' ...");
443
444                my $url = "http://thetvdb.com/?tab=listseries&letter=".$letter;
445                my $data = get_url(url => $url);
446                if (!$data) {
447                        Shepherd::Common::log("      ".$url." didn't return any valid data!  skipping...");
448
449                        # try again in 7 days
450                        $data_cache->{title}->{"WEB_LOOKUP_".$letter}->{expires} = time + (7 * 86400);
451                        $stats{failed_title_web_fetch}++;
452                        return;
453                }
454
455                my $expires_in = time + ($opt->{cache_title_for} * 86400);
456                $data_cache->{title}->{"WEB_LOOKUP_".$letter}->{expires} = $expires_in;
457
458                my $tree = HTML::TreeBuilder->new_from_content($data);
459                my $tree_table = $tree->look_down('_tag' => 'table', 'id' => 'listtable');
460
461                foreach my $tree_tr ($tree_table->look_down('_tag' => 'tr')) {
462                        my @tree_td = $tree_tr->look_down('_tag' => 'td');
463
464                        if (((scalar @tree_td) == 3) && ($tree_td[2]->as_text() =~ /^(\d+)$/)) {
465                                my $series_name = lc($tree_td[0]->as_text());
466                                $data_cache->{title}->{$series_name}->{SeriesName} = $tree_td[0]->as_text();
467                                $data_cache->{title}->{$series_name}->{SeriesID} = $tree_td[2]->as_text();
468                                $data_cache->{title}->{$series_name}->{expires} = $expires_in;
469                                $data_cache->{title}->{$series_name}->{notfound} = 0;
470                                $stats{inserted_title_into_cache}++;
471                        }
472                }
473
474                &write_cache;
475                $tree->delete;
476        }
477}
478
479
480##############################################################################
481# find an 'episode' associated with a series
482
483sub lookup_episode
484{
485        my ($lc_title, $subtitle) = @_;
486        my $seriesid = $data_cache->{title}->{$lc_title}->{SeriesID};
487        my $lc_subtitle = lc($subtitle);
488        return if (!defined $seriesid);
489
490        $stats{used_title_cache_item}++;
491
492        my $url = "/GetEpisodes.php?seriesid=".$seriesid."&IncludeSeriesInfo=1";
493        if ($lc_subtitle eq "") {
494                $url .= "&season=1&episode=1";
495                $lc_subtitle = "NONE";
496        } else {
497                $url .= "&episodename=".Shepherd::Common::urlify($lc_subtitle);
498        }
499
500        # return without doing anything if the entry already exists in the cache
501        if (defined $data_cache->{prog}->{$lc_title}->{$lc_subtitle}) {
502                $stats{used_prog_cache_item}++;
503                goto CHECK_EPISODE if ($lc_subtitle ne "NONE");
504                return;
505        }
506
507        Shepherd::Common::log("    fetching series '".$data_cache->{title}->{$lc_title}->{SeriesName}."' ".($lc_subtitle ne "NONE" ? "(episode '$subtitle')" : ""). " ...");
508
509        &lookup_mirrors if (!defined $d->{tvdb}->{mirror});
510        my $data = get_url(url => $d->{tvdb}->{mirror}.$url);
511        if (!$data) {
512                Shepherd::Common::log("   series lookup of ".$d->{tvdb}->{mirror}.$url." failed");
513                $stats{prog_lookup_failed}++;
514                return;
515        }
516
517        my $xml_tree = $parser->parse($data);
518        my $num_items = $xml_tree->getElementsByTagName("Item")->getLength;
519
520        if ($num_items == 1) {
521                $stats{negatively_cached_prog}++;
522               
523                $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{notfound} = 1;
524                $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{expires} = time + (($opt->{cache_details_for} / 2) * 86400);
525
526                # on an episode-lookup failure, thetvdb.com doesn't return any SeriesData,
527                # so schedule that for a bulk get.
528                if ((!defined $data_cache->{prog}->{$lc_title}->{SERIES}) &&
529                    (!defined $d->{series_lookup}->{$lc_title})) {
530                        $d->{series_lookup}->{$lc_title} = $seriesid;
531                        $d->{series_lookup_requests}++;
532                }
533                return;
534        }
535
536        my $item = $xml_tree->getElementsByTagName("Item")->item(1);
537
538        # remove existing entries
539        delete $data_cache->{prog}->{$lc_title}->{SERIES} if (defined $data_cache->{prog}->{$lc_title}->{SERIES});
540        delete $data_cache->{prog}->{$lc_title}->{$lc_subtitle} if (defined $data_cache->{prog}->{$lc_title}->{$lc_subtitle});
541
542        # set expiry on these
543        $data_cache->{prog}->{$lc_title}->{SERIES}->{expires} = time + ($opt->{cache_details_for} * 86400);
544        $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{expires} = time + ($opt->{cache_details_for} * 86400);
545
546        foreach my $field ("SeriesData-Status", "SeriesData-FirstAired", "SeriesData-Network", "SeriesData-Genre", "SeriesData-Actors", "SeriesData-Overview", "id", "SeasonNumber", "EpisodeNumber", "EpisodeName") {
547                my $fieldtag = $item->getElementsByTagName($field)->item(0)->getFirstChild;
548                if (defined $fieldtag) {
549                        if ($field =~ /^SeriesData-(.*)$/) {
550                                $data_cache->{prog}->{$lc_title}->{SERIES}->{$1} = $fieldtag->getNodeValue;
551                        } else {
552                                $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{$field} = $fieldtag->getNodeValue;
553                        }
554                }
555        }
556        $xml_tree->dispose;
557        $stats{inserted_prog_into_cache}++;
558        &write_cache if (($stats{inserted_prog_into_cache} % 15) == 0);
559
560CHECK_EPISODE:
561        if ((defined $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{id}) &&
562            (!defined $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{have_episode_details})) {
563                my $ep;
564                $ep->{id} = $data_cache->{prog}->{$lc_title}->{$lc_subtitle}->{id};
565                $ep->{title} = $lc_title;
566                $ep->{subtitle} = $lc_subtitle;
567
568                push(@{($d->{episode_lookup})}, $ep);
569                $d->{episode_lookup_requests}++;
570        }
571}
572
573##############################################################################
574# used to lookup SeriesData for known (good) seriesid's
575
576sub lookup_series_updates
577{
578        my @id_list = values %{($d->{series_lookup})};
579
580        while ((scalar @id_list) > 0) {
581                Shepherd::Common::log("   ".(scalar @id_list)." remaining...");
582
583                &lookup_mirrors if (!defined $d->{tvdb}->{mirror});
584                my $url = $d->{tvdb}->{mirror}."/SeriesUpdates.php?lasttime=0&idlist=";
585
586                # grab up to 20 at a time
587                foreach my $count (1..20) {
588                        my $id = pop(@id_list);
589
590                        if (defined $id) {
591                                $url .= ',' if ($count > 1);
592                                $url .= $id;
593                        }
594                }
595
596                my $data = get_url(url => $url);
597                if (!$data) {
598                        Shepherd::Common::log("   series detail lookup request of ".$url." failed");
599                        $stats{series_update_lookup_failed}++;
600                        return;
601                }
602
603                my $xml_tree = $parser->parse($data);
604                my $num_items = $xml_tree->getElementsByTagName("Item")->getLength;
605
606                for (my $i=1; $i < $num_items; $i++) {
607                        my $item = $xml_tree->getElementsByTagName("Item")->item($i);
608                        my $namefield = $item->getElementsByTagName("SeriesName")->item(0)->getFirstChild;
609
610                        if (defined $namefield) {
611                                my $name = lc($namefield->getNodeValue);
612
613                                foreach my $field ("Status", "FirstAired", "Network", "Genre", "Actors", "Overview") {
614                                        my $fieldtag = $item->getElementsByTagName($field)->item(0)->getFirstChild;
615                                        if (defined $fieldtag) {
616                                                $data_cache->{prog}->{$name}->{SERIES}->{$field} = $fieldtag->getNodeValue;
617                                        }
618                                }
619
620                                $data_cache->{prog}->{$name}->{SERIES}->{expires} = time + ($opt->{cache_details_for} * 86400);
621                                $stats{inserted_prog_into_cache}++;
622                                &write_cache if (($stats{inserted_prog_into_cache} % 15) == 0);
623                        }
624                }
625
626                $xml_tree->dispose;
627        }
628
629        &write_cache;
630}
631
632##############################################################################
633# used to lookup episode details
634
635sub lookup_episode_updates
636{
637        my $episodes;
638
639        while ((scalar @{($d->{episode_lookup})}) > 0) {
640                Shepherd::Common::log("   ".(scalar @{($d->{episode_lookup})})." remaining...");
641
642                &lookup_mirrors if (!defined $d->{tvdb}->{mirror});
643                my $url = $d->{tvdb}->{mirror}."/EpisodeUpdates.php?lasttime=0&idlist=";
644
645                # grab up to 20 at a time
646                foreach my $count (1..20) {
647                        my $ep = pop(@{($d->{episode_lookup})});
648
649                        if (defined $ep) {
650                                my $id = $ep->{id};
651                                $episodes->{$id}->{title} = $ep->{title};
652                                $episodes->{$id}->{subtitle} = $ep->{subtitle};
653
654                                $url .= ',' if ($count > 1);
655                                $url .= $id;
656                        }
657                }
658
659                my $data = get_url(url => $url);
660                if (!$data) {
661                        Shepherd::Common::log("   epside detail lookup request of ".$url." failed");
662                        $stats{episode_update_lookup_failed}++;
663                        return;
664                }
665
666                my $xml_tree = $parser->parse($data);
667                my $num_items = $xml_tree->getElementsByTagName("Item")->getLength;
668
669                for (my $i=1; $i < $num_items; $i++) {
670                        my $item = $xml_tree->getElementsByTagName("Item")->item($i);
671                        my $id_field = $item->getElementsByTagName("id")->item(0);
672
673                        if ((defined $id_field) && (defined $id_field->getFirstChild)) {
674                                my $id = $id_field->getFirstChild->getNodeValue;
675                                my $title = $episodes->{$id}->{title};
676                                my $subtitle = $episodes->{$id}->{subtitle};
677
678                                foreach my $field ("id", "EpisodeNumber", "EpisodeName", "FirstAired", "GuestStars", "Director", "Writer", "Overview", "ShowURL", "DVD_discid", "DVD_season", "DVD_episodenumber", "DVD_chapter", "IncorrectID") {
679# print "looking for $field in item $i $url\n";
680                                        my $fieldtag = $item->getElementsByTagName($field)->item(0);
681                                        if ((defined $fieldtag) && (defined $fieldtag->getFirstChild)) {
682                                                $data_cache->{prog}->{$title}->{$subtitle}->{$field} = $fieldtag->getFirstChild->getNodeValue;
683                                        }
684                                }
685                                $data_cache->{prog}->{$title}->{$subtitle}->{have_episode_details} = 1;
686                                $data_cache->{prog}->{$title}->{$subtitle}->{expires} = time + (($opt->{cache_details_for} / 2) * 86400);
687
688                                $stats{inserted_episode_into_cache}++;
689                                &write_cache if (($stats{inserted_episode_into_cache} % 15) == 0);
690                        }
691                }
692
693                $xml_tree->dispose;
694        }
695
696        &write_cache;
697}
698
699
700##############################################################################
701# helper routine: TODO
702
703sub augment_prog
704{
705        my ($xmltv_file, $prognum, $progcount) = @_;
706        my $prog = $d->{data}->[$xmltv_file][3][$prognum];
707        my $title = $prog->{title}->[0]->[0];
708
709#       if (!defined $data_cache->{prog}->{$title}) {
710
711
712        Shepherd::Common::log("programme ".$progcount." of ".$stats{programmes}.": \"$title\"");
713        print Dumper($prog);
714        return;
715
716        #
717        # find movie url
718        # (either via a cached previous search or via IMDb "power search")
719        #
720        my $movie_title, my $prog_duration;
721
722        my @search_fields;
723        push(@search_fields, "words=".Shepherd::Common::urlify($movie_title));
724        push(@search_fields, "&countries=".Shepherd::Common::urlify($prog->{country}->[0][0]))
725          if ((defined $prog->{country}) && (defined $prog->{country}->[0][0]));
726        push(@search_fields, "&year=".Shepherd::Common::urlify($prog->{date}))
727          if ((defined $prog->{date}) && ($prog->{date} > 0));
728        push(@search_fields, "&language=".Shepherd::Common::urlify($prog->{language}->[0] =~ /(^[^,]*)/))
729          if ((defined $prog->{language}) && (defined $prog->{language}->[0]));
730        # &exact=y
731        # cast/crew
732
733        Shepherd::Common::log("programme ".$stats{programmes}.": \"$movie_title\" ($prog_duration minutes)");
734
735        #
736        # augment data
737        #
738
739#       unless ($opt->{dont_augment_desc}) {
740#               my $imdb_desc = "IMDb augmented data:\n";
741#               $imdb_desc .= sprintf " Title: %s",$imdb->{title};
742#               $imdb_desc .= sprintf "  (%s)",$imdb->{year}
743#                 if (($imdb->{year}) && ($imdb->{year} > 0));
744#
745#               $imdb_desc .= sprintf "\n Rating: %s",$imdb->{rating}
746#                 if (defined $imdb->{rating} && $imdb->{rating} =~ /^\d+/);
747#               $imdb_desc .= sprintf "\n aka: %s",$imdb->{aka}
748#                 if (defined $imdb->{aka} && $imdb->{aka} ne "");
749#               $imdb_desc .= sprintf "\n Tagline: %s",$imdb->{tagline}
750#                 if (defined $imdb->{tagline} && $imdb->{tagline} ne "");
751#               $imdb_desc .= sprintf "\n Summary: %s",$imdb->{summary}
752#                 if (defined $imdb->{summary} && $imdb->{summary} ne "");
753#               $imdb_desc .= sprintf "\n Plot: %s",$imdb->{plot}
754#                 if (defined $imdb->{plot} && $imdb->{plot} ne "");
755#
756#               my $num = 0;
757#               if (defined $imdb->{certifications}) {
758#                       foreach my $c (sort keys %{($imdb->{certifications})}) {
759#                               $imdb_desc .= sprintf "%s%s (%s)",
760#                                 ($num > 0 ? ", " : "\n Certifications: "),
761#                                 $imdb->{certifications}->{$c}, $c;
762#                               $num++;
763#                       }
764#               }
765#
766#               $num = 0;
767#               if (defined $imdb->{cast}) {
768#                       foreach my $c (sort keys %{($imdb->{cast})}) {
769#                               $imdb_desc .= sprintf "%s%s%s",
770#                                 ($num > 0 ? ", " : "\n Cast: "),
771#                                 $c,
772#                                 ($imdb->{cast}->{$c} ? " as $imdb->{cast}->{$c}" : "");
773#                               $num++;
774#                       }
775#               }
776#
777#               $imdb_desc .= sprintf "\n Directors: %s",
778#                 join(", ",keys %{($imdb->{directors})})
779#                 if ($imdb->{directors});
780#               $imdb_desc .= sprintf "\n Writers: %s",
781#                 join(", ",keys %{($imdb->{writers})})
782#                 if ($imdb->{writers});
783#
784#               $imdb_desc .= sprintf "\n Awards: %s",$imdb->{awards}
785#                 if (defined $imdb->{awards} && $imdb->{awards} ne "");
786#               $imdb_desc .= sprintf "\n Runtime: %s",$imdb->{runtime}
787#                 if (defined $imdb->{runtime} && $imdb->{runtime} ne "");
788#               $imdb_desc .= sprintf "\n Countries: %s",
789#                 join(", ",@{$imdb->{countries}})
790#                 if (defined $imdb->{countries});
791#               $imdb_desc .= sprintf "\n Languages: %s",
792#                 join(", ",@{$imdb->{languages}})
793#                 if (defined $imdb->{languages});;
794#               $imdb_desc .= sprintf "\n Genres: %s",
795#                 join(", ",@{$imdb->{genres}})
796#                 if (defined $imdb->{genres});;
797#
798#               $imdb_desc .= sprintf "\n Trivia: %s",$imdb->{trivia}
799#                 if (defined $imdb->{trivia} && $imdb->{trivia} ne "");
800#               $imdb_desc .= sprintf "\n Goofs: %s",$imdb->{goofs}
801#                 if (defined $imdb->{goofs} && $imdb->{goofs} ne "");
802#               $imdb_desc .= sprintf "\n Cover: %s",$imdb->{cover}
803#                 if (defined $imdb->{cover} && $imdb->{cover} ne "");
804#
805#               $prog->{desc}->[0]->[0] = "" if (!defined $prog->{desc}->[0]->[0]);
806#               $prog->{desc}->[0]->[0] .= "\n\n" if ($prog->{desc}->[0]->[0] ne "");
807#               $prog->{desc}->[0]->[0] .= $imdb_desc;
808#       }
809#
810#       $prog->{date} = $imdb->{year} if ($imdb->{year});
811#       # $prog->{length} = $imdb->{runtime} if ($imdb->{runtime});
812#
813#       my $found_url = 0, my $found_cover = 0;
814#       if (defined $prog->{url}) {
815#               foreach my $url (@{($prog->{url})}) {
816#                       $found_url++ if (lc($url) eq lc($movie_url));
817#                       $found_cover++ if (($imdb->{cover}) && (lc($url) eq lc($imdb->{cover})));
818#               }
819#       }
820#       push (@{($prog->{url})},$movie_url) if (!$found_url);
821#       push (@{($prog->{url})},$imdb->{cover}) if (($imdb->{cover}) && (!$found_cover));
822#
823#       if ($imdb->{rating}) {
824#               my ($rating,$votes) = split(/ /,$imdb->{rating});
825#               push (@{($prog->{'star-rating'})},$rating);
826#       }
827#
828#       if ($imdb->{languages}) {
829#               foreach my $lang (@{($imdb->{languages})}) {
830#                       if (defined $prog->{language}) {
831#                               $prog->{language}->[0] .= ", " . $lang
832#                                       if ($prog->{language}->[0] !~ /$lang/i);
833#                       } else {
834#                               $prog->{language}->[0] = $lang;
835#                       }
836#               }
837#       }
838#       # don't fill in XMLTV orig-language - mythtv ignores it
839#
840#       if (($imdb->{plot}) && ((!defined $prog->{desc}->[0]->[0]) ||
841#           ($prog->{desc}->[0]->[0] eq ""))) {
842#               $prog->{desc}->[0]->[0] = "";
843#               $prog->{desc}->[0]->[0] .= $imdb->{tagline}."\n" if ($imdb->{tagline});
844#               $prog->{desc}->[0]->[0] .= $imdb->{plot};
845#       }
846#
847#       if (defined $imdb->{genres}) {
848#               foreach my $genre (@{($imdb->{genres})}) {
849#                       my $found_genre = 0;
850#                       foreach my $category (@{($prog->{category})}) {
851#                               $found_genre++ if (lc($genre) eq lc($category->[0]));
852#                       }
853#                       push(@{($prog->{category})},[$genre]) if (!$found_genre);
854#               }
855#       }
856#
857#       if (defined $imdb->{countries}) {
858#               foreach my $country (@{($imdb->{countries})}) {
859#                       my $found_country = 0;
860#                       foreach my $c (@{($prog->{country})}) {
861#                               $found_country++ if (lc($country) eq lc($c->[0]));
862#                       }
863#                       push(@{($prog->{country})},[$country]) if (!$found_country);
864#               }
865#       }
866#
867#       if (defined $imdb->{certifications}) {
868#               push(@{($prog->{rating})},[$imdb->{certifications}->{Australia},'Australia',undef])
869#                       if ($imdb->{certifications}->{Australia});
870#               foreach my $cert (sort keys %{($imdb->{certifications})}) {
871#                       my $found_cert = 0;
872#                       foreach my $c (@{($prog->{rating})}) {
873#                               $found_cert++ if (lc($cert) eq lc($c->[1]));
874#                       }
875#                       push(@{($prog->{rating})},[$imdb->{certifications}->{$cert},$cert,undef])
876#                         if (!$found_cert);
877#               }
878#       }
879#
880#       if (defined $imdb->{cast}) {
881#               foreach my $cast (sort keys %{($imdb->{cast})}) {
882#                       my $found_cast = 0;
883#                       if ((defined $prog->{credits}) && (defined $prog->{credits}->{actor})) {
884#                               foreach my $a (@{($prog->{credits}->{actor})}) {
885#                                       $found_cast++ if (lc($cast) eq lc($a));
886#                               }
887#                       }
888#                       push(@{($prog->{credits}->{actor})},$cast) if (!$found_cast);
889#               }
890#       }
891#
892#       if (defined $imdb->{writers}) {
893#               foreach my $cast (keys %{($imdb->{writers})}) {
894#                       my $found_cast = 0;
895#                       if ((defined $prog->{credits}) && (defined $prog->{credits}->{writer})) {
896#                               foreach my $w (@{($prog->{credits}->{writer})}) {
897#                                       $found_cast++ if (lc($cast) eq lc($w));
898#                               }
899#                       }
900#                       push(@{($prog->{credits}->{writer})},$cast) if (!$found_cast);
901#               }
902#       }
903#
904#       if (defined $imdb->{directors}) {
905#               foreach my $cast (keys %{($imdb->{directors})}) {
906#                       my $found_cast = 0;
907#                       if ((defined $prog->{credits}) && (defined $prog->{credits}->{director})) {
908#                               foreach my $d (@{($prog->{credits}->{director})}) {
909#                                       $found_cast++ if (lc($cast) eq lc($d));
910#                               }
911#                       }
912#                       push(@{($prog->{credits}->{director})},$cast) if (!$found_cast);
913#               }
914#       }
915#
916#       if (defined $imdb->{cover}) {
917#               $found_cover = 0;
918#               if (defined $prog->{icon}) {
919#                       foreach my $cover (@{($prog->{icon})}) {
920#                               $found_cover++ if (lc($cover->{src}) eq lc($imdb->{cover}));
921#                       }
922#               }
923#               $prog->{icon}->[0]->{src} = $imdb->{cover} if (!$found_cover);
924#       }
925
926        #print "prog now ".Dumper($prog);
927}
928
929##############################################################################
930
931sub run_test
932{
933        $opt->{debug} = 1;
934        $opt->{fast} = 1;
935        Shepherd::Common::log("running test for: ".$opt->{test});
936
937        # TODO
938
939        exit(0);
940}
941
942##############################################################################
Note: See TracBrowser for help on using the browser.