File Coverage

blib/lib/TVDB/API.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             # Copyright (c) 2008 Behan Webster. All rights reserved. This program is free
2             # software; you can redistribute it and/or modify it under the same terms
3             # as Perl itself.
4              
5             package TVDB::API;
6              
7             require 5.008008;
8 1     1   47576 use strict;
  1         2  
  1         49  
9              
10 1     1   5395 use Compress::Zlib;
  1         195851  
  1         936  
11 1     1   2446 use DBM::Deep;
  1         39586  
  1         10  
12 1     1   5715 use Data::Dumper;
  1         9109  
  1         119  
13 1     1   1036 use Debug::Simple;
  1         14499  
  1         198  
14 1     1   1369 use Encode qw(encode decode);
  1         13787  
  1         276  
15 1     1   1556 use IO::Uncompress::Unzip;
  1         35519  
  1         66  
16 1     1   1311 use LWP;
  1         76689  
  1         46  
17 1     1   14 use Storable;
  1         3  
  1         91  
18 1     1   472 use XML::Simple;
  0            
  0            
19              
20             use vars qw($VERSION %Defaults %Url);
21              
22             $VERSION = "0.33";
23              
24             # TheTVDB Urls
25             %Url = (
26             defaultURL => 'http://thetvdb.com',
27             getSeriesID => '%s/api/GetSeries.php?seriesname=%s&language=%s', # defaultURL, series_name, language
28             getMirrors => '%s/api/%s/mirrors.xml', # defaultURL, apikey
29             bannerURL => '%s/banners/', # baseBannerURL, append bannerFilename.ext
30             apiURL => '%s/api/%s', # mirrorURL, apikey
31             getLanguages => '%s/languages.xml', # apiURL
32             getSeries => '%s/series/%s/%s.xml', # apiURL, seriesid, language
33             getSeriesAll => '%s/series/%s/all/%s.%s', # apiURL, seriesid, language, (xml|zip)
34             getSeriesActors => '%s/series/%s/actors.xml', # apiURL, seriesid
35             getSeriesBanner => '%s/series/%s/banners.xml', # apiURL, seriesid
36             getEpisode => '%s/series/%s/default/%s/%s/%s.xml', # apiURL, seriesid, season, episode, language
37             getEpisodeDVD => '%s/series/%s/dvd/%s/%s/%s.xml', # apiURL, seriesid, season, episode, language
38             getEpisodeAbs => '%s/series/%s/absolute/%s/%s.xml', # apiURL, seriesid, absolute_episode, language
39             getEpisodeID => '%s/episodes/%s/%s.xml', # apiURL, episodeid, language
40             getUpdates => '%s/updates/updates_%s.%s', # apiURL, (day|week|month|all), (xml|zip)
41              
42             getEpisodeByAirDate => '%s/api/GetEpisodeByAirDate.php?apikey=%s&seriesid=%s&airdate=%s&language=%s',
43             getRatingsForUser => '%s/api/GetRatingsForUser.php?apikey=%s&accountid=%s&seriesid=%s',
44             getRatingsForUserAll => '%s/api/GetRatingsForUser.php?apikey=%s&accountid=%s',
45             );
46              
47             %Defaults = (
48             maxSeason => 50,
49             maxEpisode => 50,
50             minUpdateTime => 3600*6, # 6 hours
51             minBannerTime => 3600*24*7, # 1 week
52             minEpisodeTime => 3600*24*7, # 1 week
53             );
54              
55             ###############################################################################
56             sub new {
57             my $self = bless {};
58             %{$self->{conf}} = %Defaults;
59              
60             my $args;
61             if (ref $_[0] eq 'HASH') {
62             # Subroutine arguments by hashref
63             $args = shift;
64             } else {
65             # Traditional subroutine arguments
66             $args = {};
67             ($args->{apikey}, $args->{lang}, $args->{cache}, $args->{banner}, @{$args->{mirrors}}) = @_;
68             }
69             # Argument defaults
70             $args->{cache} ||= "$ENV{HOME}/.tvdb.db";
71             $args->{apikey} ||= die 'You need to get an apikey from http://thetvdb.com/?tab=apiregister';
72             $args->{useragent} ||= "TVDB::API/$VERSION";
73              
74             $self->setCacheDB($args->{cache});
75             $self->setApiKey($args->{apikey});
76             $self->{ua} = LWP::UserAgent->new;
77             $self->{ua}->env_proxy();
78             $self->setUserAgent($args->{useragent});
79             $self->{xml} = XML::Simple->new(
80             ForceArray => ['Actor', 'Banner', 'Episode', 'Mirror', 'Series'],
81             SuppressEmpty => 1,
82             );
83              
84             if (@{$args->{mirrors}}) {
85             $self->setMirrors(@{$args->{mirrors}});
86             } else {
87             $self->chooseMirrors();
88             }
89              
90             # The following must be after setCacheDB/setApiKey/setUserAgent/xml/setMirrors
91             $self->setLang($args->{lang});
92             $self->setBannerPath($args->{banner}) if $args->{banner};
93              
94             return $self;
95             }
96              
97             ###############################################################################
98             sub setApiKey {
99             my ($self, $apikey) = @_;
100             $self->{apikey} = $apikey;
101             $self->_updateUrls();
102             }
103             sub setLang {
104             my $self = shift;
105             my $lang = shift || 'en';
106             my $langs = $self->getAvailableLanguages();
107             &verbose(3, "TVDB::API: Setting language to: $lang => $langs->{$lang}->{name}\n");
108             $self->{lang} = $lang;
109             }
110             sub setMirrors {
111             my $self = shift;
112             $self->{mirror} = shift || $Url{defaultURL};
113             $self->{banner} = shift || $self->{mirror} || '';
114             $self->{zip} = shift || $self->{mirror} || '';
115             &verbose(3, "TVDB::API: Setting mirrors to: xml:$self->{mirror} banner:$self->{banner} zip:$self->{zip}\n");
116             $self->_updateUrls();
117             }
118             sub _updateUrls {
119             my ($self) = @_;
120             $self->{apiURL} = sprintf $Url{apiURL}, $self->{mirror}, $self->{apikey};
121             $self->{bannerURL} = sprintf $Url{bannerURL}, $self->{banner};
122             $self->{zipURL} = sprintf $Url{apiURL}, $self->{zip}, $self->{apikey};
123             }
124             sub setUserAgent {
125             my ($self, $userAgent) = @_;
126             $self->{ua}->agent($userAgent);
127             }
128             sub setBannerPath {
129             my ($self, $path) = @_;
130             $self->{bannerPath} = $path;
131             mkdir $path;
132             return -d $path;
133             }
134              
135             ###############################################################################
136             sub setCacheDB {
137             my ($self, $cache) = @_;
138             $self->{cachefile} = $cache;
139             $self->{cache} = DBM::Deep->new(
140             file => $cache,
141             #filter_store_key => \&_compressCache,
142             filter_store_value => \&_compressCache,
143             #filter_fetch_key => \&_decompressCache,
144             filter_fetch_value => \&_decompressCache,
145             utf8 => 1,
146             );
147             }
148             sub _compressCache {
149             # Escape UTF-8 chars and gzip data
150             return Compress::Zlib::memGzip(encode('utf8',$_[0])) ;
151             }
152             sub _decompressCache {
153             # Decompress data and then unescape UTF-8 chars
154             return decode('utf8',Compress::Zlib::memGunzip($_[0])) ;
155             }
156             sub dumpCache {
157             my ($self) = @_;
158             my $cache = $self->{cache};
159             print Dumper($cache);
160             }
161              
162             ###############################################################################
163             sub setConf {
164             my ($self, $key, $value) = @_;
165             if (ref $key eq 'HASH') {
166             while (my ($k, $v) = each %$key) {
167             $self->{conf}->{$k} = $v;
168             }
169             } else {
170             $self->{conf}->{$key} = $value;
171             }
172             }
173             sub getConf {
174             my ($self, $key) = @_;
175             return $self->{conf}->{$key} if $key && defined $self->{conf}->{$key};
176             return $self->{conf};
177             }
178              
179             ###############################################################################
180             # Download binary data
181             sub _download {
182             my ($self, $fmt, $url, @parm) = @_;
183              
184             # Make URL
185             $url = sprintf($fmt, $url, @parm);
186             &verbose(2, "TVDB::API: download: $url\n");
187             utf8::encode($url);
188              
189             # Make sure we only download once even in a session
190             return $self->{dload}->{$url} if defined $self->{dload}->{$url};
191              
192             # Download URL
193             my $req = HTTP::Request->new(GET => $url);
194             my $res = $self->{ua}->request($req);
195              
196             if ($res->content =~ /(?:404 Not Found|The page your? requested does not exist)/i) {
197             &warning("TVDB::API: download $url, 404 Not Found\n");
198             $self->{dload}->{$url} = 0;
199             return undef;
200             }
201             $self->{dload}->{$url} = $res->content;
202             return $res->content;
203             }
204             # Download Xml, remove empty tags, parse XML, and return hashref
205             sub _downloadXml {
206             my ($self, $fmt, @parm) = @_;
207              
208             # Download XML file
209             my $xml = $self->_download($fmt, $self->{apiURL}, @parm, 'xml');
210             return undef unless $xml;
211              
212             # Remove empty tags
213             $xml =~ s/(<[^\/\s>]*\/>|<[^\/\s>]*><\/[^>]*>)//gs;
214              
215             # Return process XML into hashref
216             return $self->{xml}->XMLin($xml);
217             }
218             # Download Xml, remove empty tags, parse XML, and return hashref
219             sub _downloadApikeyXml {
220             my ($self, $fmt, @parm) = @_;
221              
222             # Download XML file
223             my $xml = $self->_download($fmt, $self->{mirror}, $self->{apikey}, @parm);
224             return undef unless $xml;
225              
226             $xml =~ s/seriesid>/id>/g;
227              
228             # Remove empty tags
229             $xml =~ s/(<[^\/\s>]*\/>|<[^\/\s>]*><\/[^>]*>)//gs;
230              
231             # Return process XML into hashref
232             return $self->{xml}->XMLin($xml);
233             }
234             # Download Zip file, decompress into one Xml file, remove empty tags, parse XML, and return hashref
235             sub _downloadZip {
236             my ($self, $fmt, @parm) = @_;
237              
238             # Download XML file
239             my $zip = $self->_download($fmt, $self->{zipURL}, @parm, 'zip');
240             return undef unless $zip;
241              
242             # Uncompress ZIP
243             my $url = sprintf($fmt, $self->{zipURL}, @parm, 'zip');
244             my $obj = new IO::Uncompress::Unzip \$zip, MultiStream => 1, Transparent => 1
245             or die "IO::Uncompress::Unzip failed: $url\n";
246             local $/ = undef;
247             my $xml = <$obj>;
248              
249             # Make en.xml/banners.xml/actors.xml into one xml file
250             if ($xml =~ s/<\/Data><\?xml.*?Banners>|<\/Banners><\?xml.*?Actors>//gs) {
251             $xml =~ s/<\/Actors>$/<\/Data>/s;
252             }
253              
254             # Remove empty tags
255             $xml =~ s/(<[^\/\s>]*\/>|<[^\/\s>]*><\/[^>]*>)//gs;
256              
257             &debug(4, "download Zip: $url\n", XML => \$xml);
258              
259             # Return process XML into hashref
260             return $self->{xml}->XMLin($xml);
261             }
262              
263             ###############################################################################
264             sub getAvailableMirrors {
265             my ($self, $nocache) = @_;
266              
267             my $cache = $self->{cache};
268             if ($nocache || not defined $cache->{Mirror}) {
269             # Get list of mirrors
270             my $xml = $self->_download($Url{getMirrors}, $Url{defaultURL}, $self->{apikey});
271             my $data = XMLin($xml, ForceArray=>['Mirror']);
272              
273             # Break into lists of mirror types: xml/banner/zip
274             $self->{cache}->{Mirror} = {};
275             while (my ($key,$value) = each %{$data->{Mirror}}) {
276             my ($typemask, $url) = ($value->{typemask}, $value->{mirrorpath});
277             if ($typemask >= 4) { $typemask -= 4; push @{$cache->{Mirror}->{xml}}, $url; }
278             if ($typemask >= 2) { $typemask -= 2; push @{$cache->{Mirror}->{banner}}, $url; }
279             if ($typemask >= 1) { $typemask -= 1; push @{$cache->{Mirror}->{zip}}, $url; }
280             }
281             }
282              
283             # Return hashref of arrays
284             return $cache->{Mirror};
285             }
286             sub _rand {
287             my ($list) = @_;
288             # Return random entry from array
289             return $list->[int(rand($#$list + 1))];
290             }
291             sub chooseMirrors {
292             my ($self, $nocache) = @_;
293             my $mirrors = $self->getAvailableMirrors($nocache);
294             $self->setMirrors(
295             &_rand($mirrors->{xml}),
296             &_rand($mirrors->{banner}),
297             &_rand($mirrors->{zip}),
298             );
299             }
300              
301             ###############################################################################
302             sub getAvailableLanguages {
303             my ($self, $nocache) = @_;
304              
305             if ($nocache || not defined $self->{cache}->{Language}) {
306             # Download languags XML and process into a hashref
307             my $xml = $self->_download($Url{getLanguages}, $self->{apiURL});
308             my $data = XMLin($xml, KeyAttr => 'abbreviation');
309             $self->{cache}->{Language} = $data->{Language};
310             }
311              
312             return $self->{cache}->{Language};
313             }
314              
315             sub _mtime {
316             my ($filename) = @_;
317             my @stat = stat($filename);
318             return $stat[9];
319             }
320              
321             ###############################################################################
322             sub getUpdates {
323             my $self = shift;
324             my $period = lc shift || 'guess';
325              
326             # Determin which update xml file to download
327             my $now = time;
328             if ($period =~ /^(guess|now)$/) {
329             my $diff = $now - $self->{cache}->{Update}->{lastupdated};
330             if ($period eq 'guess' && $diff <= $self->{conf}->{minUpdateTime}) {
331             # We've updated recently (within 6 hours)
332             return;
333             } elsif ($diff <= 86400) { # 1 day in seconds
334             $period = 'day';
335             } elsif ($diff <= 604800) { # 1 week in seconds
336             $period = 'week';
337             } elsif ($diff <= 2592000) { # 1 month in seconds
338             $period = 'month';
339             } else {
340             $period = 'all';
341             }
342             }
343             unless ($period =~/^(day|week|month|all)$/) {
344             die "Invalid period when calling getUpdates: $period\n";
345             }
346              
347             # Download appropriate update file
348             &verbose(1, "TVDB::API: Downloading $period updates\n");
349             my $updates = $self->_downloadZip($Url{getUpdates}, $period);
350             return undef unless $updates;
351              
352             # Series updates
353             my $series = $self->{cache}->{Series};
354             while (my ($sid,$data) = each %{$updates->{Series}}) {
355             # Don't update if we don't already have this series
356             next unless defined $series->{$sid};
357             # Only update if there is a more recent version
358             if ($data->{time} > $series->{$sid}->{lastupdated}) {
359             if ($period eq 'all') {
360             # all updates don't include Episodes, so the complete series record is downloaded
361             $self->getSeriesAll($sid, 1);
362             } else {
363             $self->getSeries($sid, 1);
364             }
365             }
366             }
367              
368             # Episodes updates
369             my $episodes = $self->{cache}->{Episode};
370             while (my ($eid,$ep) = each %{$updates->{Episode}}) {
371             # Don't update if we don't already have this series
372             next unless defined $series->{$ep->{Series}};
373             # Get it if we don't already have it
374             unless (defined $episodes->{$eid}
375             # Or, update if there is a more recent version
376             and $ep->{time} > $episodes->{$eid}->{lastupdated}
377             ) {
378             $self->getEpisodeId($eid, 1);
379             }
380             }
381              
382             # Banners updates
383             my $banners = $self->{cache}->{Banner};
384             if (defined $self->{bannerPath}) {
385             for my $banner (@{$updates->{Banner}}) {
386             # Don't update if we don't already have this series
387             next unless defined $series->{$banner->{Series}};
388             # Don't update if we haven't already downloaded this banner
389             my $filename = "$self->{bannerPath}/$banner->{path}";
390             next unless -f $filename;
391             # Don't update if it isn't newer
392             next unless -z $filename || $banner->{time} > &_mtime($filename);
393             $self->getBanner($banner->{path}, undef, 1);
394             }
395             }
396              
397             # Save when we last updated, now that we've successfully done so
398             $self->{cache}->{Update}->{lastupdated} = $now;
399             $self->{cache}->{Update}->{lasttime} = $updates->{time};
400             }
401              
402             ###############################################################################
403             # Fill in the blank
404             sub getPossibleSeriesId {
405             my ($self, $name) = @_;
406              
407             &verbose(2, "TVDB::API: Get possbile series id for $name\n");
408             my $xml = $self->_download($Url{getSeriesID}, $Url{defaultURL}, $name, $self->{lang});
409             return undef unless $xml;
410             my $data = XMLin($xml, ForceArray=>['Series'], KeyAttr=>{});
411              
412             # Build hashref to return
413             my $ret = {};
414             for my $series (@{$data->{Series}}) {
415             my $sid = $series->{id};
416             if (defined $ret->{$sid}) {
417             $ret->{$sid}->{altlanguage} = {};
418             $ret->{$sid}->{altlanguage}->{$series->{language}} = $series;
419             } else {
420             $ret->{$sid} = $series;
421             }
422             }
423              
424             return $ret;
425             }
426              
427             ###############################################################################
428             # Fill in the blank
429             sub getSeriesId {
430             my ($self, $name, $nocache) = @_;
431             return undef unless defined $name;
432              
433             # see if $name is a series id already
434             return $name if $name =~ /^\d+$/ && $name > 70000;
435              
436             # See if it's in the series cache
437             my $cache = $self->{cache};
438             if (!$nocache && defined $cache->{Name2Sid}->{$name}) {
439             #print "From SID Cache: $name -> $cache->{Name2Sid}->{$name}\n";
440             return undef unless $cache->{Name2Sid}->{$name};
441             return $cache->{Name2Sid}->{$name};
442             }
443              
444             my $data = $self->getPossibleSeriesId($name);
445              
446             # Look through list of possibilities
447             if ($data) {
448             while (my ($sid,$series) = each %$data) {
449             if ($series->{SeriesName} =~ /^(The )?\Q$name\E(, The)?$/i) {
450             $cache->{Name2Sid}->{$name} = $sid;
451             return $sid;
452             }
453             }
454             }
455              
456             # Nothing found, assign 0 to name so we cache this result
457             &warning("TBDB::API: No series id found for: $name\n");
458             $cache->{Name2Sid}->{$name} = 0; # Not undef as that messes up DBM::Deep
459             return undef;
460             }
461              
462             ###############################################################################
463             # Get series/lang.xml for series
464             sub getSeries {
465             my ($self, $name, $nocache) = @_;
466             &debug(2, "getSeries: $name, $nocache\n");
467              
468             my $sid = $self->getSeriesId($name, $nocache?$nocache-1:0);
469             return undef unless $sid;
470              
471             my $series = $self->{cache}->{Series};
472             if (defined $series->{$sid} && $series->{$sid}->{Seasons}) {
473             # Get updated series data
474             if ($nocache) {
475             &verbose(1, "TVDB::API: Updating series: $sid => $series->{$sid}->{SeriesName}\n");
476             my $data = $self->_downloadXml($Url{getSeries}, $sid, $self->{lang});
477             return undef unless $data;
478              
479             # Copy updated series into cache
480             while (my ($key,$value) = each %{$data->{Series}->{$sid}}) {
481             $series->{$sid}->{$key} = $value;
482             }
483              
484             # From cache
485             } else {
486             &debug(2, "From Series Cache: $sid\n");
487             }
488              
489             # Get full series data
490             } else {
491             $self->getSeriesAll($sid, 1);
492             }
493              
494             return $series->{$sid};
495             }
496              
497             ###############################################################################
498             # Get series/all/lang.zip for series
499             sub getSeriesAll {
500             my ($self, $name, $nocache) = @_;
501             &debug(2, "getSeriesAll: $name, $nocache\n");
502              
503             my $sid = $self->getSeriesId($name, $nocache?$nocache-1:0);
504             return undef unless $sid;
505              
506             # Get series data
507             my $series = $self->{cache}->{Series};
508             if (!$nocache && defined $series->{$sid} && $series->{$sid}->{Seasons}) {
509             &debug(2, "From Series Cache: $sid\n");
510              
511             # Download full series data
512             } else {
513             &verbose(1, "TVDB::API: Downloading full series: $sid".(defined $series->{$sid}?" => $series->{$sid}->{SeriesName}":'')."\n");
514             my $data = $self->_downloadZip($Url{getSeriesAll}, $sid, $self->{lang});
515             return undef unless $data;
516              
517             # Copy series into cache
518             #@{$series->{$sid}}{keys %{$data->{Series}->{$sid}}} = values %{$data->{Series}->{$sid}};
519             if (defined $series->{$sid}) {
520             while (my ($key,$value) = each %{$data->{Series}->{$sid}}) {
521             $series->{$sid}->{$key} = $value;
522             }
523             } else {
524             $self->{cache}->{Series}->{$sid} = $data->{Series}->{$sid};
525             }
526              
527             # Copy episodes into cache
528             while (my ($eid,$ep) = each %{$data->{Episode}}) {
529             $series->{$sid}->{Seasons} = [] unless $series->{$sid}->{Seasons};
530             #print "Season: $ep->{SeasonNumber} $series->{$sid}->{Seasons}->[$ep->{SeasonNumber}]\n";
531             $series->{$sid}->{Seasons}->[$ep->{SeasonNumber}]->[$ep->{EpisodeNumber}] = $eid;
532             $self->{cache}->{Episode}->{$eid} = $ep;
533             }
534              
535             # Save actors
536             $series->{$sid}->{Actor} = $data->{Actor};
537              
538             # Save banners
539             $series->{$sid}->{Banner} = $data->{Banner};
540             }
541              
542             return $series->{$sid};
543             }
544              
545             ###############################################################################
546             sub getSeriesName {
547             my ($self, $sid, $nocache) = @_;
548              
549             my $series = $self->getSeries($sid, $nocache);
550             return undef unless $series;
551              
552             return $series->{SeriesName};
553             }
554              
555             ###############################################################################
556             # Get series/actors.xml for Series
557             sub getSeriesActors {
558             my ($self, $name, $nocache) = @_;
559              
560             my $sid = $self->getSeriesId($name, $nocache?$nocache-2:0);
561             return undef unless $sid;
562             my $series = $self->getSeries($sid, $nocache?$nocache-1:0);
563             return undef unless $series;
564              
565             # Get actors data
566             if ($nocache or not $series->{Actor}) {
567             &verbose(1, "TVDB::API: Get actors: $series->{SeriesName}\n");
568             my $data = $self->_downloadXml($Url{getSeriesActors}, $sid);
569             return undef unless $data;
570              
571             # Copy updated series into cache
572             $self->{cache}->{Series}->{$sid}->{Actor} = $data->{Actor};
573              
574             # From cache
575             } else {
576             &debug(2, "From Actors Cache: $series->{SeriesName}\n");
577             }
578              
579             return $series->{Actor};
580             }
581              
582             ###############################################################################
583             sub getSeriesActorsSorted {
584             my ($self, $name, $nocache) = @_;
585             my $data = $self->getSeriesActors($name, $nocache);
586             my @sorted = sort {
587             $a->{SortOrder} <=> $b->{SortOrder}
588             && $a->{Role} cmp $b->{Role}
589             && $a->{Name} cmp $b->{Name}
590             } values %$data;
591             return \@sorted;
592             }
593              
594             ###############################################################################
595             # Get series/banners.xml for Series
596             sub getSeriesBanners {
597             my ($self, $name, $type, $type2, $value, $nocache) = @_;
598              
599             my $sid = $self->getSeriesId($name, $nocache?$nocache-2:0);
600             return undef unless $sid;
601             my $series = $self->getSeries($sid, $nocache?$nocache-1:0);
602             return undef unless $series;
603              
604             # Get banner data
605             if ($nocache or not $series->{Banner}) {
606             &verbose(1, "TVDB::API: Get banners: $series->{SeriesName}\n");
607             my $data = $self->_downloadXml($Url{getSeriesBanner}, $sid);
608             return undef unless $data;
609              
610             # Copy updated series into cache
611             $self->{cache}->{Series}->{$sid}->{Banner} = $data->{Banner};
612              
613             # From cache
614             } else {
615             &debug(2, "From Banners Cache: $series->{SeriesName}\n");
616             }
617              
618             # Search banners
619             my %banners;
620             while (my ($id,$banner) = each %{$series->{Banner}}) {
621             next unless $banner->{Language} =~ /$self->{lang}|en/;
622             next unless !$type || $banner->{BannerType} eq $type;
623             next unless !$type2 || $banner->{BannerType2} eq $type2;
624             next unless !$value || $type eq 'season' && $banner->{Season} eq $value;
625             $banners{$id} = $banner;
626             }
627              
628             return \%banners;
629             }
630              
631             ###############################################################################
632             # Get info for Series
633             sub getSeriesInfo {
634             my ($self, $name, $info, $nocache) = @_;
635              
636             my $data = $self->getSeries($name, $nocache);
637             return undef unless $data;
638              
639             # Check that info is available
640             unless (defined $data->{$info}) {
641             #&warning("TBDB::API: No $info found for series $name\n");
642             return undef;
643             }
644              
645             return $data->{$info};
646             }
647              
648             ###############################################################################
649             sub getSeriesBanner {
650             my ($self, $name, $buffer, $nocache) = @_;
651             my $banner = $self->getSeriesInfo($name, 'banner', $nocache?$nocache-1:0);
652             return undef unless $banner;
653             return $self->getBanner($banner, $buffer, $nocache);
654             }
655             sub getSeriesFanart {
656             my ($self, $name, $buffer, $nocache) = @_;
657             my $banner = $self->getSeriesInfo($name, 'fanart', $nocache?$nocache-1:0);
658             return undef unless $banner;
659             return $self->getBanner($banner, $buffer, $nocache);
660             }
661             sub getSeriesPoster {
662             my ($self, $name, $buffer, $nocache) = @_;
663             my $banner = $self->getSeriesInfo($name, 'poster', $nocache?$nocache-1:0);
664             return undef unless $banner;
665             return $self->getBanner($banner, $buffer, $nocache);
666             }
667             sub getSeriesOverview {
668             my ($self, $name, $nocache) = @_;
669             return $self->getSeriesInfo($name, 'Overview', $nocache);
670             }
671              
672             ###############################################################################
673             sub _makedir {
674             my $dir = shift;
675             return unless $dir;
676              
677             # mkdir piece at a time
678             unless( -d $dir ) {
679             my $path;
680             for my $part (split '/', $dir) {
681             $path .= "$part/";
682             unless (-e $path) {
683             &debug([2,2,1], "mkdir $path\n");
684             mkdir $path;
685             }
686             }
687             }
688             }
689              
690             ###############################################################################
691             # get named banner. Download if not already. Read from cache if buffer provided.
692             sub getBanner {
693             my ($self, $banner, $buffer, $nocache) = @_;
694              
695             return unless defined $self->{bannerPath};
696              
697             my $filename = "$self->{bannerPath}/$banner";
698              
699             # See if we tried to get this during the last week and failed
700             if (-z $filename && (time - &_mtime($filename) < $self->{conf}->{minBannerTime})) {
701             &verbose(2, "TVDB::API: download of $banner failed before\n");
702             return undef;
703             }
704              
705             if ($nocache || ! -s $filename) {
706             my $buf;
707             my $gfx = $buffer ? $buffer : \$buf;
708              
709             # Download banner (create zero length file if nothing downloaded)
710             &verbose(1, "TVDB::API: Get banner $banner\n");
711             $$gfx = $self->_download($self->{bannerURL}.$banner);
712             &_makedir($1) if $filename =~ m|^(.*)/[^/]+$|;
713             open(GFX, "> $filename") || die "$filename:$!";
714             print GFX $$gfx;
715             return undef unless $$gfx;
716              
717             } elsif ($buffer && -s $filename) {
718             # get Banner from cache
719             &debug(2, "From Banner Cache: $banner\n");
720             open(GFX, "< $filename") || die "$filename:$!";
721             local $/ = undef;
722             $$buffer = ;
723             }
724             close GFX;
725              
726             return $banner;
727             }
728              
729             ###############################################################################
730             sub getMaxSeason {
731             my ($self, $name, $nocache) = @_;
732             $self->getUpdates(); # Update available episodes/seasons
733             my $series = $self->getSeriesAll($name, $nocache?$nocache-1:0);
734             return undef unless $series;
735             return $#{$series->{Seasons}};
736             }
737              
738             ###############################################################################
739             sub getSeason {
740             my ($self, $name, $season, $nocache) = @_;
741             if ($season < 0 || $season > $self->{conf}->{maxSeason}) {
742             &warning("TBDB::API: Invalid season $season for $name\n");
743             return undef;
744             }
745             my $series = $self->getSeriesAll($name, $nocache?$nocache-1:0);
746             return undef unless $series && $series->{Seasons};
747             unless ($series->{Seasons}->[$season]) {
748             $self->getUpdates();
749             unless ($series->{Seasons}->[$season]) {
750             &warning("TBDB::API: No season $season found for $name\n");
751             #$series->{Seasons}->[$season] = 0;
752             return undef;
753             }
754             }
755             return $series->{Seasons}->[$season];
756             }
757              
758             ###############################################################################
759             sub getSeasonBanners {
760             my ($self, $name, $season, $nocache) = @_;
761             my $data = $self->getSeriesBanners($name, 'season', 'season', $season, $nocache);
762             my @banners;
763             while (my ($id,$banner) = each %$data) {
764             push @banners, $banner->{BannerPath};
765             }
766             return sort @banners;
767             }
768             sub getSeasonBanner {
769             my ($self, $name, $season, $buffer, $nocache) = @_;
770             my @banners = $self->getSeasonBanners($name, $season, $nocache?$nocache-1:0);
771             return undef unless @banners;
772             return $self->getBanner($banners[0], $buffer, $nocache);
773             }
774              
775             ###############################################################################
776             sub getSeasonBannersWide {
777             my ($self, $name, $season, $nocache) = @_;
778             my $data = $self->getSeriesBanners($name, 'season', 'seasonwide', $season, $nocache);
779             my @banners;
780             while (my ($id,$banner) = each %$data) {
781             push @banners, $banner->{BannerPath};
782             }
783             return sort @banners;
784             }
785             sub getSeasonBannerWide {
786             my ($self, $name, $season, $buffer, $nocache) = @_;
787             my @banners = $self->getSeasonBannersWide($name, $season, $nocache?$nocache-1:0);
788             return undef unless @banners;
789             return $self->getBanner($banners[0], $buffer, $nocache);
790             }
791              
792             ###############################################################################
793             sub getMaxEpisode {
794             my ($self, $name, $season, $nocache) = @_;
795             $self->getUpdates(); # Update available episodes/seasons
796             my $data = $self->getSeason($name, $season, $nocache);
797             return undef unless $data;
798             return $#$data;
799             }
800              
801             ###############################################################################
802             sub getEpisode {
803             my ($self, $name, $season, $episode, $nocache) = @_;
804             if ($episode < 0 || $episode > $self->{conf}->{maxEpisode}) {
805             &warning("TBDB::API: Invalid episode $episode in season $season for $name\n");
806             return undef;
807             }
808             my $sid = $self->getSeriesId($name);
809             my $data = $self->getSeason($sid, $season, $nocache?$nocache-1:0);
810             return undef unless $data;
811              
812             # See if we have to update the episode record
813             my $cache = $self->{cache};
814             my $series = $cache->{Series};
815             my $eid = $data->[$episode] if defined $data->[$episode];
816             if (ref($eid) ne '' && (time - $eid->{lasttried}) < $self->{conf}->{minEpisodeTime}) {
817             &verbose(2, "TBDB::API: No episode $episode found for season $season of $name (cached)\n");
818             return undef;
819             }
820             unless (!$nocache && $eid && !ref($eid) && $cache->{Episode}->{$eid}) {
821             # Download episode
822             &verbose(1, "TVDB::API: Updating episode $episode from season $season for $name\n");
823             my $new = $self->_downloadXml($Url{getEpisode}, $sid, $season, $episode, $self->{lang});
824              
825             if ($new) {
826             # Save episode in cache
827             ($eid, my $ep) = each %{$new->{Episode}};
828             $series->{$sid}->{Seasons} = [] unless $series->{$sid}->{Seasons};
829             $series->{$sid}->{Seasons}->[$season]->[$episode] = $eid;
830             $cache->{Episode}->{$eid} = $ep;
831             } else {
832             $eid = 0;
833             $series->{$sid}->{Seasons}->[$season]->[$episode] = {};
834             $series->{$sid}->{Seasons}->[$season]->[$episode]->{lasttried} = time;
835             }
836             }
837              
838             # Check again (if it's been updated)
839             unless ($eid && defined $cache->{Episode}->{$eid}) {
840             &warning("TBDB::API: No episode $episode found for season $season of $name\n");
841             return undef;
842             }
843              
844             return $cache->{Episode}->{$eid};
845             }
846              
847             ###############################################################################
848             sub getEpisodeAbs {
849             my ($self, $name, $abs, $nocache) = @_;
850             if ($abs < 0 || $abs > $self->{conf}->{maxEpisode}*$self->{conf}->{maxSeason}) {
851             &warning("TBDB::API: Invalid absolute episode $abs for $name\n");
852             return undef;
853             }
854             my $sid = $self->getSeriesId($name);
855             return undef unless $sid;
856             my $series = $self->getSeriesAll($sid, $nocache?$nocache-1:0);
857             return undef unless $series;
858              
859             # Look for episode in cache
860             my $cache = $self->{cache};
861             unless ($nocache) {
862             foreach my $season (@{$series->{Seasons}}) {
863             foreach my $eid (@$season) {
864             next unless $eid;
865             my $ep = $cache->{Episode}->{$eid};
866             return $ep if $ep->{absolute_number} eq $abs;
867             }
868             }
869             }
870              
871             # Download absolute episode
872             &verbose(1, "TVDB::API: Updating absolute episode $abs for $name\n");
873             my $new = $self->_downloadXml($Url{getEpisodeAbs}, $sid, $abs, $self->{lang});
874             if ($new) {
875             # Save episode in cache
876             my ($eid, $ep) = each %{$new->{Episode}};
877             $series->{$sid}->{Seasons} = [] unless $series->{$sid}->{Seasons};
878             $series->{$sid}->{Seasons}->[$ep->{SeasonNumber}]->[$ep->{EpisodeNumber}] = $eid;
879             $cache->{Episode}->{$eid} = $ep;
880             return $cache->{Episode}->{$eid};
881             }
882              
883             &warning("TBDB::API: No absolute episode $abs found for $name\n");
884             return undef;
885             }
886              
887             ###############################################################################
888             sub getEpisodeDVD {
889             my ($self, $name, $season, $episode, $nocache) = @_;
890             my $epmajor = int($episode);
891             if ($epmajor < 0 || $epmajor > $self->{conf}->{maxEpisode}) {
892             &warning("TBDB::API: Invalid DVD episode $episode in DVD season $season for $name\n");
893             return undef;
894             }
895             my $sid = $self->getSeriesId($name);
896             return undef unless $sid;
897             my $data = $self->getSeason($sid, $season, $nocache?$nocache-1:0);
898             return undef unless $data;
899              
900             # Look for episode in cache
901             my $cache = $self->{cache};
902             my $series = $cache->{Series};
903             unless ($nocache) {
904             foreach my $eid (@$data) {
905             next unless $eid;
906             my $ep = $cache->{Episode}->{$eid};
907             my $de = $ep->{DVD_episodenumber};
908             return $ep if $de eq $episode
909             || int($de) eq $episode
910             || int($de) eq $epmajor;
911             }
912             }
913              
914             # Download DVD episode
915             &verbose(1, "TVDB::API: Updating DVD episode $episode from DVD season $season for $name\n");
916             my $new = $self->_downloadXml($Url{getEpisodeDVD}, $sid, $season, $episode, $self->{lang});
917             if ($new) {
918             # Save episode in cache
919             my ($eid, $ep) = each %{$new->{Episode}};
920             $series->{$sid}->{Seasons} = [] unless $series->{$sid}->{Seasons};
921             $series->{$sid}->{Seasons}->[$ep->{SeasonNumber}]->[$ep->{EpisodeNumber}] = $eid;
922             $cache->{Episode}->{$eid} = $ep;
923             return $cache->{Episode}->{$eid};
924             }
925              
926             &warning("TBDB::API: No DVD episode $episode found for DVD season $season of $name\n");
927             return undef;
928             }
929              
930             ###############################################################################
931             sub getEpisodeId {
932             my ($self, $eid, $nocache) = @_;
933             my $cache = $self->{cache};
934             unless (!$nocache && defined $cache->{Episode}->{$eid}) {
935             # Download episode
936             &verbose(1, "TVDB::API: Updating episode id $eid\n");
937             my $new = $self->_downloadXml($Url{getEpisodeID}, $eid, $self->{lang});
938             return undef unless $new;
939              
940             # Save episode in cache
941             $cache->{Episode}->{$eid} = $new->{Episode}-{$eid};
942             }
943              
944             return $cache->{Episode}->{$eid};
945             }
946              
947             ###############################################################################
948             sub getEpisodeByAirDate {
949             my ($self, $name, $airdate, $nocache) = @_;
950             my $sid = $self->getSeriesId($name, $nocache?$nocache-1:0);
951              
952             my $cache = $self->{cache};
953              
954             # Download episode
955             &verbose(1, "TVDB::API: Get episode for $name ($sid) on $airdate\n");
956             my $new = $self->_downloadApikeyXml($Url{getEpisodeByAirDate}, $sid, $airdate, $self->{lang});
957             return undef unless $new;
958              
959             return $new->{Episode};
960             }
961              
962             ###############################################################################
963             sub getEpisodeInfo {
964             my ($self, $name, $season, $episode, $info, $nocache) = @_;
965              
966             my $data = $self->getEpisode($name, $season, $episode, $nocache);
967             return undef unless $data;
968              
969             # Check that info is available
970             unless (defined $data->{$info}) {
971             #&warning("TBDB::API: No $info found for episode $episode of season $season of $name\n");
972             return undef;
973             }
974              
975             return $data->{$info};
976             }
977              
978             ###############################################################################
979             sub getEpisodeBanner {
980             my ($self, $name, $season, $episode, $buffer, $nocache) = @_;
981             my $banner = $self->getEpisodeInfo($name, $season, $episode, 'filename', $nocache?$nocache-1:0);
982             return undef unless $banner;
983             return $self->getBanner($banner, $buffer, $nocache);
984             }
985             sub getEpisodeName {
986             my ($self, $name, $season, $episode, $nocache) = @_;
987             return $self->getEpisodeInfo($name, $season, $episode, 'EpisodeName', $nocache);
988             }
989             sub getEpisodeOverview {
990             my ($self, $name, $season, $episode, $nocache) = @_;
991             return $self->getEpisodeInfo($name, $season, $episode, 'Overview', $nocache);
992             }
993              
994             ###############################################################################
995             sub getRatingsForUser {
996             my ($self, $user, $name, $nocache) = @_;
997              
998             # Download ratings
999             my $data;
1000             if ($name) {
1001             my $sid = $self->getSeriesId($name, $nocache?$nocache-1:0);
1002             &verbose(1, "TVDB::API: Get rating for $user for $name ($sid)\n");
1003             $data = $self->_downloadApikeyXml($Url{getRatingsForUser}, $user, $sid);
1004             } else {
1005             &verbose(1, "TVDB::API: Get rating for $user\n");
1006             $data = $self->_downloadApikeyXml($Url{getRatingsForUserAll}, $user);
1007             }
1008             return undef unless $data;
1009              
1010             return $data;
1011             }
1012              
1013             ###############################################################################
1014             __END__