MythTV  0.26-pre
ttvdb.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: UTF-8 -*-
00003 # ----------------------
00004 # Name: ttvdb.py
00005 # Python Script
00006 # Author: R.D. Vaughan
00007 # Purpose:
00008 #   This python script is intended to perform TV series data lookups
00009 #   based on information found on the http://thetvdb.com/ website. It
00010 #   follows the MythTV standards set for the movie data
00011 #   lookups. e.g. the perl script "tmdb.pl" used to access themoviedb.com
00012 #   This script uses the python module tvdb_api.py (v1.0 or higher) found at
00013 #   http://pypi.python.org/pypi?%3Aaction=search&term=tvnamer&submit=search
00014 #   thanks to the authors of this excellant module.
00015 #   The tvdb_api.py module uses the full access XML api published by
00016 #   thetvdb.com see:
00017 #     http://thetvdb.com/wiki/index.php?title=Programmers_API
00018 #   Users of this script are encouraged to populate thetvdb.com with TV show
00019 #   information, posters, fan art and banners. The richer the source the more
00020 #   valuable the script.
00021 #   This python script was modified based on the "tvnamer.py" created by
00022 #   "dbr/Ben" who is also
00023 #   the author of the "tvdb_api.py" module. "tvnamer.py" is used to rename avi
00024 #   files with series/episode information found at thetvdb.com
00025 # Command example:
00026 # See help (-u and -h) options
00027 #
00028 # Design:
00029 #   1) Verify the command line options (display help or version and exit)
00030 #   2) Verify that thetvdb.com has the series or series_season_ep being
00031 #      requested exit if does not exit
00032 #   3) Find the requested information and send to stdout if any found
00033 #
00034 #
00035 # License:Creative Commons GNU GPL v2
00036 # (http://creativecommons.org/licenses/GPL/2.0/)
00037 #-------------------------------------
00038 __title__ ="TheTVDB.com";
00039 __author__="R.D.Vaughan"
00040 __version__="1.1.5"
00041 # Version .1    Initial development
00042 # Version .2    Add an option to get season and episode numbers from ep name
00043 # Version .3    Cleaned up the documentation and added a usage display option
00044 # Version .4    Added override formating of the number option (-N)
00045 # Version .5    Added a check that tvdb_api.py dependancies are installed
00046 # Version .6    Added -M Series List functionality
00047 # Version .7    Added -o Series name override file functionality
00048 # Version .8    Removed a dependancy, fixed caching for multiple users and
00049 #               used better method of supporting the -M option with tvdb_api
00050 # Version .8.1  Cleaned up documentation errors.
00051 # Version .8.2  Added the series name to output of meta data -D
00052 # Version .8.3  Patched tv_api so even episode image is fully qualified URL
00053 # Version .8.4  Fixed bug in -N when multiple episodes are returned from
00054 #               a search on episode name.
00055 # Version .8.5  Made option -N more flexible in finding a matching episode
00056 # Version .8.6  Add option -t for top rated graphics (-P,B,F) for a series
00057 #               Add option -l to filter graphics on the local language
00058 #               Add season level granularity to Poster and Banner URLs
00059 #               Changed the override file location to be specified on the
00060 #               command line along with the option -o.
00061 #               Increased the amount of massaging of episode data to improve
00062 #               compatiblilty with data bases.
00063 #               Changed the default season episode number format to SxxExx
00064 #               Add an option (-n) to return the season numbers for a series
00065 #               Added passing either a thetvdb.com SID (series identifcation
00066 #               number) or series name for all functions except -M list.
00067 #               Now ALL available episode meta data is returned. This
00068 #               includes meta data that MythTv DB does not currently store.
00069 #               Meta data 'Year' now derived from year in release date.
00070 # Version .8.7  Fixed a bug with unicode meta data handling
00071 # Version .8.8  Replaced the old configuration file with a true conf file
00072 # Version .8.9  Add option -m to better conform to mythvideo standards
00073 # Version .9.0  Now when a season level Banner is not found then the
00074 #               top rated series level graphics is returned instead. This
00075 #               feature had previously been available for posters only.
00076 #               Add runtime to episode meta data (-D). It is always the
00077 #               same for each episode as the information is only available
00078 #               from the series itself.
00079 #               Added the TV Series cast members as part of episode
00080 #               meta data. Option (-D).
00081 #               Added TV Series series genres as part of episode
00082 #               meta data. Option (-D).
00083 #               Resync with tvdb_api getting bug fixes and new features.
00084 #               Add episode data download to a specific language see
00085 #               -l 'es' option. If there is no data for a languages episode
00086 #               then any engish episode data is returned. English is default.
00087 #               The -M is still only in English. Waiting on tvdb_api fix.
00088 # Version .9.1  Bug fix stdio abort when no genre exists for TV series
00089 # Version .9.2  Bug fix stdio abort when no cast exists for TV series
00090 # Version .9.3  Changed option -N when episodes partially match each
00091 #               combination of season/episode numbers are returned. This was
00092 #               added to deal with episodes which have a "(1)" trailing
00093 #               the episode name. An episode in more than one part.
00094 # Version .9.4  Option -M can now list multi-language TV Series
00095 # Version .9.5  "Director" metadata field returned as "None" is changed to
00096 #               "Unknown".
00097 #               File name parsing was changed to use multi-language capable
00098 #               regex patterns
00099 # Version .9.6  Synced up to the 1.0 release of tvdb_api
00100 #               Added a tvdb_api version check and abort if not at least v1.0
00101 #               Changed to new tvdb_api's method of assigning the tvdb api key
00102 # Version .9.7  Account for TVDB increasing the number of digits in their
00103 #               SID number (now greater then 5
00104 #               e,g, "Defying Gravity" is SID 104581)
00105 # Version .9.8  Added a (-S) option for requesting a thetvdb
00106 #               episode screen shot
00107 # Version .9.9  Fixed the -S option when NO episode image exists
00108 # Version 1.0.  Removed LF and replace with a space for all TVDB metatdata
00109 #               fields
00110 # Version 1.0.1 Return all graphics (series and season) in the order
00111 #               highest to lowest as rated by users
00112 # Version 1.0.2 Added better error messages to config file checking. Updated to
00113 #               v1.0.2 tvdb_api which contained fixes for concurrent instances
00114 #               of ttvdb.py generated by MythVideo.
00115 # Version 1.0.3 Conform to new -D standards which return all graphics URLs along
00116 #               with text meta data. Also Posters, Banners and Fan art have one or
00117 #               comma separated URLs as one continuous string.
00118 # Version 1.0.4 Poster Should be Coverart instead.
00119 # Version 1.0.5 Added the TVDB URL to the episode metadata
00120 # Version 1.0.6 When the language is invalid ignore language and continue processing
00121 #               Changed all exit codes to be 0 for passed and 1 for failed
00122 #               Removed duplicates when using the -M option and the -l option
00123 # Version 1.0.7 Change over to the installed TTVDB api library
00124 #               Fixed cast name bad data abort reported in ticket #7957
00125 #               Fixed ticket #7900 - There is now fall back graphics when a
00126 #               language is specified and there is no images for that language.
00127 #               New default location and name of the ttvdb.conf file is '~/.mythtv/ttvdb.conf'. The command
00128 #               line option "-c" can still override the default location and name.
00129 # Version 1.0.8 Removed any stderr messages for non-critical events. They cause dual pop-ups in MythVideo.
00130 # Version 1.0.9 Removed another stderr messages when a non-supported language code is passed.
00131 # Version 1.1.0 Added support for XML output. See:
00132 #               http://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
00133 # Version 1.1.1 Make XML output the default.
00134 # Version 1.1.2 Convert version information to XML
00135 # Version 1.1.3 Implement fuzzy matching for episode name lookup
00136 # Version 1.1.4 Add test mode (replaces --toprated)
00137 # Version 1.1.5 Add the -C (collection option) with corresponding XML output
00138 #               and add a <collectionref> XML tag to Search and Query XML output
00139 
00140 usage_txt='''
00141 Usage: ttvdb.py usage: ttvdb -hdruviomMPFBDSC [parameters]
00142  <series name or 'series and season number' or 'series and season number and episode number'>
00143 
00144 For details on using ttvdb with Mythvideo see the ttvdb wiki page at:
00145 http://www.mythtv.org/wiki/Ttvdb.py
00146 
00147 Options:
00148   -h, --help            show this help message and exit
00149   -d, --debug           Show debugging info
00150   -r, --raw             Dump raw data only
00151   -u, --usage           Display examples for executing the ttvdb script
00152   -v, --version         Display version and author
00153   -t                    Test mode, to check for installed dependencies
00154   -i, --interactive     Interaction mode (allows selection of a specific
00155                         Series)
00156   -c FILE, --configure=FILE
00157                         Use configuration settings
00158   -l LANGUAGE, --language=LANGUAGE
00159                         Select data that matches the specified language fall
00160                         back to english if nothing found (e.g. 'es' Español,
00161                         'de' Deutsch ... etc)
00162   -n, --num_seasons     Return the season numbers for a series
00163   -m, --mythvideo       Conform to mythvideo standards when processing -M, -P,
00164                         -F and -D
00165   -M, --list            Get matching TV Series list
00166   -P, --poster          Get Series Poster URL(s)
00167   -F, --fanart          Get Series fan art URL(s)
00168   -B, --backdrop        Get Series banner/backdrop URL(s)
00169   -S, --screenshot      Get Series episode screenshot URL
00170   -D, --data            Get Series episode data
00171   -N, --numbers         Get Season and Episode numbers
00172   -C, --collection      Get A TV Series (collection) series specific information
00173 
00174 Command examples:
00175 (Return the banner graphics for a series)
00176 > ttvdb -B "Sanctuary"
00177 Banner:http://www.thetvdb.com/banners/graphical/80159-g2.jpg,http://www.thetvdb.com/banners/graphical/80159-g3.jpg,http://www.thetvdb.com/banners/graphical/80159-g.jpg
00178 
00179 (Return the banner graphics for a Series specific to a season)
00180 > ttvdb -B "SG-1" 1
00181 Banner:http://www.thetvdb.com/banners/graphical/72449-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g3.jpg,http://www.thetvdb.com/banners/graphical/185-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g.jpg,http://www.thetvdb.com/banners/graphical/72449-g.jpg,http://www.thetvdb.com/banners/text/185.jpg
00182 
00183 (Return the screen shot graphic for a Series Episode)
00184 > ttvdb -S "SG-1" 1 10
00185 http://www.thetvdb.com/banners/episodes/72449/85759.jpg
00186 
00187 (Return the banner graphics for a SID (series ID) specific to a season)
00188 (SID "72449" is specific for the series "SG-1")
00189 > ttvdb -B 72449 1
00190 Banner:http://www.thetvdb.com/banners/graphical/72449-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g3.jpg,http://www.thetvdb.com/banners/graphical/185-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g.jpg,http://www.thetvdb.com/banners/graphical/72449-g.jpg,http://www.thetvdb.com/banners/text/185.jpg
00191 
00192 (Return the banner graphics for a file name)
00193 > ttvdb -B "Stargate SG-1 - S08E03 - Lockdown"
00194 Banner:http://www.thetvdb.com/banners/graphical/72449-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g3.jpg,http://www.thetvdb.com/banners/graphical/185-g2.jpg,http://www.thetvdb.com/banners/graphical/185-g.jpg,http://www.thetvdb.com/banners/graphical/72449-g.jpg,http://www.thetvdb.com/banners/text/185.jpg
00195 
00196 (Return the posters, banners and fan art for a series)
00197 > ttvdb -PFB "Sanctuary"
00198 Coverart:http://www.thetvdb.com/banners/posters/80159-2.jpg,http://www.thetvdb.com/banners/posters/80159-1.jpg
00199 Fanart:http://www.thetvdb.com/banners/fanart/original/80159-2.jpg,http://www.thetvdb.com/banners/fanart/original/80159-1.jpg,http://www.thetvdb.com/banners/fanart/original/80159-8.jpg,http://www.thetvdb.com/banners/fanart/original/80159-6.jpg,http://www.thetvdb.com/banners/fanart/original/80159-5.jpg,http://www.thetvdb.com/banners/fanart/original/80159-9.jpg,http://www.thetvdb.com/banners/fanart/original/80159-3.jpg,http://www.thetvdb.com/banners/fanart/original/80159-7.jpg,http://www.thetvdb.com/banners/fanart/original/80159-4.jpg
00200 Banner:http://www.thetvdb.com/banners/graphical/80159-g2.jpg,http://www.thetvdb.com/banners/graphical/80159-g3.jpg,http://www.thetvdb.com/banners/graphical/80159-g.jpg
00201 
00202 (Return thetvdb.com's top rated poster, banner and fan art for a TV Series)
00203 (NOTE: If there is no graphic for a type or any graphics at all then those types are not returned)
00204 > ttvdb -tPFB "Stargate SG-1"
00205 Coverart:http://www.thetvdb.com/banners/posters/72449-1.jpg
00206 Fanart:http://www.thetvdb.com/banners/fanart/original/72449-1.jpg
00207 Banner:http://www.thetvdb.com/banners/graphical/185-g3.jpg
00208 > ttvdb -tB "Night Gallery"
00209 http://www.thetvdb.com/banners/blank/70382.jpg
00210 
00211 (Return graphics only matching the local language for a TV series)
00212 (In this case banner 73739-g9.jpg is not included because it does not match the language 'en')
00213 > ttvdb -Bl en "Lost"
00214 Banner:http://www.thetvdb.com/banners/graphical/73739-g4.jpg,http://www.thetvdb.com/banners/graphical/73739-g.jpg,http://www.thetvdb.com/banners/graphical/73739-g6.jpg,http://www.thetvdb.com/banners/graphical/73739-g8.jpg,http://www.thetvdb.com/banners/graphical/73739-g3.jpg,http://www.thetvdb.com/banners/graphical/73739-g7.jpg,http://www.thetvdb.com/banners/graphical/73739-g5.jpg,http://www.thetvdb.com/banners/graphical/24313-g2.jpg,http://www.thetvdb.com/banners/graphical/24313-g.jpg,http://www.thetvdb.com/banners/graphical/73739-g10.jpg,http://www.thetvdb.com/banners/graphical/73739-g2.jpg
00215 
00216 (Return a season and episode numbers using the override file to identify the series as the US version)
00217 > ttvdb -N --configure="/home/user/.tvdb/tvdb.conf" "Eleventh Hour" "H2O"
00218 <?xml version='1.0' encoding='UTF-8'?>
00219 <metadata>
00220   <item>
00221     <title>Eleventh Hour (US)</title>
00222     <subtitle>H2O</subtitle>
00223     <language>en</language>
00224     <description>An epidemic of sudden, violent outbursts by law-abiding citizens draws Dr. Jacob Hood to a quiet Texas community to investigate - but he soon succumbs to the same erratic behavior.</description>
00225     <season>1</season>
00226     <episode>10</episode>
00227 ...
00228       <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-4.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-4.jpg" width="1280" height="720"/>
00229       <image type="banner" url="http://www.thetvdb.com/banners/graphical/83066-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/83066-g.jpg"/>
00230     </images>
00231   </item>
00232 </metadata>
00233 
00234 (Return the season numbers for a series)
00235 > ttvdb --configure="/home/user/.tvdb/tvdb.conf" -n "SG-1"
00236 0,1,2,3,4,5,6,7,8,9,10
00237 
00238 (Return the meta data for a specific series/season/episode)
00239 > ttvdb.py -D 80159 2 2
00240 <?xml version='1.0' encoding='UTF-8'?>
00241 <metadata>
00242   <item>
00243     <title>Sanctuary</title>
00244     <subtitle>End of Nights (2)</subtitle>
00245     <language>en</language>
00246     <description>Furious at being duped into a trap, Magnus (AMANDA TAPPING) takes on Kate (AGAM DARSHI), demanding information and complete access to her Cabal contacts. The Cabal’s true agenda is revealed and Magnus realizes that they are not only holding Ashley (EMILIE ULLERUP) as ransom to obtain complete control of the Sanctuary Network, but turning her into the ultimate weapon. Now transformed into a Super Abnormal with devastating powers, Ashley and her newly cloned fighters begin their onslaught, destroying Sanctuaries in cities around the world. Tesla (JONATHON YOUNG) and Henry (RYAN ROBBINS) attempt to create a weapon that can stop the attacks…without killing Ashley. As the team prepares to defend the Sanctuary with Tesla’s new weapon, Magnus must come to the realization that they may not be able to stop the Cabal’s attacks without harming Ashley. She realizes she might have to choose between saving her only daughter, or losing the Sanctuary and all the lives and secrets within it.</description>
00247     <season>2</season>
00248     <episode>2</episode>
00249     <certifications>
00250       <certification locale="us" name="TV-PG"/>
00251     </certifications>
00252     <categories>
00253       <category type="genre" name="Action and Adventure"/>
00254       <category type="genre" name="Science-Fiction"/>
00255     </categories>
00256     <studios>
00257       <studio name="SciFi"/>
00258     </studios>
00259 ...
00260       <image type="banner" url="http://www.thetvdb.com/banners/graphical/80159-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/80159-g.jpg"/>
00261     </images>
00262   </item>
00263 </metadata>
00264 
00265 (Return a list of "thetv.com series id and series name" that contain specific search word(s) )
00266 (!! Be careful with this option as poorly defined search words can result in large lists being returned !!)
00267 > ttvdb.py -M "night a"
00268 <?xml version='1.0' encoding='UTF-8'?>
00269 <metadata>
00270   <item>
00271     <language>en</language>
00272     <title>Love on a Saturday Night</title>
00273     <inetref>74382</inetref>
00274     <releasedate>2004-02-01</releasedate>
00275   </item>
00276   <item>
00277     <language>en</language>
00278     <title>A Night on Mount Edna</title>
00279     <inetref>108281</inetref>
00280   </item>
00281   <item>
00282     <language>en</language>
00283     <title>A Night at the Office</title>
00284     <inetref>118511</inetref>
00285     <description>On August 11th 2009, it was announced that the cast of The Office would be reuniting for a special, called "A Night at The Office", available at BBC2 and online.</description>
00286     <releasedate>2009-01-01</releasedate>
00287     <images>
00288       <image type="banner" url="http://www.thetvdb.com/banners/graphical/118511-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/118511-g.jpg"/>
00289     </images>
00290   </item>
00291   <item>
00292     <language>en</language>
00293     <title>Star For A Night</title>
00294     <inetref>71476</inetref>
00295     <releasedate>1999-01-01</releasedate>
00296   </item>
00297 </metadata>
00298 
00299 (Return TV series collection data of "thetv.com series id" for a specified language)
00300 > ttvdb.py -l de -C 80159
00301 <?xml version='1.0' encoding='UTF-8'?>
00302 <metadata>
00303   <item>
00304     <language>de</language>
00305     <title>Sanctuary</title>
00306     <network>Syfy</network>
00307     <airday>Friday</airday>
00308     <airtime>22:00</airtime>
00309     <description>Dr. Helen Magnus ist eine so brillante wie geheimnisvolle Wissenschaftlerin die sich mit den Kreaturen der Nacht beschäftigt. In ihrem Unterschlupf - genannt "Sanctuary" - hat sie ein Team versammelt, das seltsame und furchteinflößende Ungeheuer untersucht, die mit den Menschen auf der Erde leben. Konfrontiert mit ihren düstersten Ängsten und ihren schlimmsten Alpträumen versucht das Sanctuary-Team, die Welt vor den Monstern - und die Monster vor der Welt zu schützen.</description>
00310     <certifications>
00311       <certification locale="us" name="TV-PG"/>
00312     </certifications>
00313     <categories>
00314       <category type="genre" name="Action and Adventure"/>
00315       <category type="genre" name="Science-Fiction"/>
00316     </categories>
00317     <studios>
00318       <studio name="Syfy"/>
00319     </studios>
00320     <runtime>60</runtime>
00321     <inetref>80159</inetref>
00322     <imdb>0965394</imdb>
00323     <tmsref>EP01085421</tmsref>
00324     <userrating>8.0</userrating>
00325     <ratingcount>128</ratingcount>
00326     <year>2007</year>
00327     <releasedate>2007-05-14</releasedate>
00328     <lastupdated>Fri, 17 Feb 2012 16:57:02 GMT</lastupdated>
00329     <status>Continuing</status>
00330     <images>
00331       <image type="coverart" url="http://www.thetvdb.com/banners/posters/80159-4.jpg" thumb="http://www.thetvdb.com/banners/_cache/posters/80159-4.jpg"/>
00332       <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-8.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-8.jpg"/>
00333       <image type="banner" url="http://www.thetvdb.com/banners/graphical/80159-g6.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/80159-g6.jpg"/>
00334     </images>
00335   </item>
00336 </metadata>
00337 '''
00338 # Episode keys that can be used in a episode data/information search.
00339 # All keys are currently being used.
00340 '''
00341 'episodenumber'
00342 'rating'
00343 'overview'
00344 'dvd_episodenumber'
00345 'dvd_discid'
00346 'combined_episodenumber'
00347 'epimgflag'
00348 'id'
00349 'seasonid'
00350 'seasonnumber'
00351 'writer'
00352 'lastupdated'
00353 'filename'
00354 'absolute_number'
00355 'combined_season'
00356 'imdb_id'
00357 'director'
00358 'dvd_chapter'
00359 'dvd_season'
00360 'gueststars'
00361 'seriesid'
00362 'language'
00363 'productioncode'
00364 'firstaired'
00365 'episodename'
00366 '''
00367 
00368 
00369 # System modules
00370 import sys, os, re, locale, ConfigParser
00371 from optparse import OptionParser
00372 from copy import deepcopy
00373 
00374 # Verify that tvdb_api.py, tvdb_ui.py and tvdb_exceptions.py are available
00375 try:
00376     # thetvdb.com specific modules
00377     import MythTV.ttvdb.tvdb_ui as tvdb_ui
00378     # from tvdb_api import Tvdb
00379     import MythTV.ttvdb.tvdb_api as tvdb_api
00380     from MythTV.ttvdb.tvdb_exceptions import (tvdb_error, tvdb_shownotfound, tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_episodenotfound, tvdb_attributenotfound, tvdb_userabort)
00381 
00382     # verify version of tvdbapi to make sure it is at least 1.0
00383     if tvdb_api.__version__ < '1.0':
00384         print "\nYour current installed tvdb_api.py version is (%s)\n" % tvdb_api.__version__
00385         raise
00386 except Exception, e:
00387     print '''
00388 The modules tvdb_api.py (v1.0.0 or greater), tvdb_ui.py, tvdb_exceptions.py and cache.py.
00389 They should have been installed along with the MythTV python bindings.
00390 Error:(%s)
00391 ''' %  e
00392     sys.exit(1)
00393 
00394 try:
00395     from MythTV.utility import levenshtein
00396 except Exception, e:
00397     print """Could not import levenshtein string distance method from MythTV Python Bindings
00398 Error:(%s)
00399 """ % e
00400     sys.exit(1)
00401 
00402 try:
00403     from StringIO import StringIO
00404     from lxml import etree as etree
00405 except Exception, e:
00406     sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
00407     sys.exit(1)
00408 
00409 # Check that the lxml library is current enough
00410 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
00411 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
00412 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
00413 version = ''
00414 for digit in etree.LIBXML_VERSION:
00415     version+=str(digit)+'.'
00416 version = version[:-1]
00417 if version < '2.7.2':
00418     sys.stderr.write(u'''
00419 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
00420           At least "libxml" version 2.7.2 must be installed. Your version is (%s).
00421 ''' % version)
00422     sys.exit(1)
00423 
00424 
00425 # Global variables
00426 http_find="http://www.thetvdb.com"
00427 http_replace="http://www.thetvdb.com" #Keep replace code "just in case"
00428 
00429 logfile="/tmp/ttvdb.log"
00430 
00431 name_parse=[
00432             # foo_[s01]_[e01]
00433             re.compile('''^(.+?)[ \._\-]\[[Ss]([0-9]+?)\]_\[[Ee]([0-9]+?)\]?[^\\/]*$'''),
00434             # foo.1x09*
00435             re.compile('''^(.+?)[ \._\-]\[?([0-9]+)x([0-9]+)[^\\/]*$'''),
00436             # foo.s01.e01, foo.s01_e01
00437             re.compile('''^(.+?)[ \._\-][Ss]([0-9]+)[\.\- ]?[Ee]([0-9]+)[^\\/]*$'''),
00438             # foo.103*
00439             re.compile('''^(.+)[ \._\-]([0-9]{1})([0-9]{2})[\._ -][^\\/]*$'''),
00440             # foo.0103*
00441             re.compile('''^(.+)[ \._\-]([0-9]{2})([0-9]{2,3})[\._ -][^\\/]*$'''),
00442 ] # contains regex parsing filename parsing strings used to extract info from video filenames
00443 
00444 # Episode meta data that is massaged
00445 massage={'writer':'|','director':'|', 'overview':'&', 'gueststars':'|' }
00446 # Keys and titles used for episode data (option '-D')
00447 data_keys =['seasonnumber','episodenumber','episodename','firstaired','director','overview','rating','writer','filename','language' ]
00448 data_titles=['Season:','Episode:','Subtitle:','ReleaseDate:','Director:','Plot:','UserRating:','Writers:','Screenshot:','Language:' ]
00449 # High level dictionay keys for select graphics URL(s)
00450 fanart_key='fanart'
00451 banner_key='series'
00452 poster_key='poster'
00453 season_key='season'
00454 # Lower level dictionay keys for select graphics URL(s)
00455 poster_series_key='680x1000'
00456 poster_season_key='season'
00457 fanart_hires_key='1920x1080'
00458 fanart_lowres_key='1280x720'
00459 banner_series_key='graphical'
00460 banner_season_key='seasonwide'
00461 # Type of graphics being requested
00462 poster_type='Poster'
00463 fanart_type='Fanart'
00464 banner_type='Banner'
00465 screenshot_request = False
00466 
00467 # Cache directory name specific to the user. This avoids permission denied error with a common cache dirs
00468 cache_dir="/tmp/tvdb_api_%s/" % os.geteuid()
00469 
00470 def _can_int(x):
00471     """Takes a string, checks if it is numeric.
00472     >>> _can_int("2")
00473     True
00474     >>> _can_int("A test")
00475     False
00476     """
00477     try:
00478         int(x)
00479     except ValueError:
00480         return False
00481     else:
00482         return True
00483 # end _can_int
00484 
00485 def debuglog(message):
00486     message+='\n'
00487     target_socket = open(logfile, "a")
00488     target_socket.write(message)
00489     target_socket.close()
00490     return
00491 # end debuglog
00492 
00493 class OutStreamEncoder(object):
00494     """Wraps a stream with an encoder"""
00495     def __init__(self, outstream, encoding=None):
00496         self.out = outstream
00497         if not encoding:
00498             self.encoding = sys.getfilesystemencoding()
00499         else:
00500             self.encoding = encoding
00501 
00502     def write(self, obj):
00503         """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00504         if isinstance(obj, unicode):
00505             self.out.write(obj.encode(self.encoding))
00506         else:
00507             self.out.write(obj)
00508 
00509     def __getattr__(self, attr):
00510         """Delegate everything but write to the stream"""
00511         return getattr(self.out, attr)
00512 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00513 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00514 
00515 # modified Show class implementing a fuzzy search
00516 class Show( tvdb_api.Show ):
00517     def fuzzysearch(self, term = None, key = None):
00518         results = []
00519         for cur_season in self.values():
00520             searchresult = cur_season.fuzzysearch(term = term, key = key)
00521             if len(searchresult) != 0:
00522                 results.extend(searchresult)
00523         return results
00524 # end Show
00525 
00526 # modified Season class implementing a fuzzy search
00527 class Season( tvdb_api.Season ):
00528     def fuzzysearch(self, term = None, key = None):
00529         results = []
00530         for episode in self.values():
00531             searchresult = episode.fuzzysearch(term = term, key = key)
00532             if searchresult is not None:
00533                 results.append(searchresult)
00534         return results
00535 # end Season
00536 
00537 # modified Episode class implementing a fuzzy search
00538 class Episode( tvdb_api.Episode ):
00539     _re_strippart = re.compile('(.*) \([0-9]+\)')
00540     def fuzzysearch(self, term = None, key = None):
00541         if term == None:
00542             raise TypeError("must supply string to search for (contents)")
00543 
00544         term = unicode(term).lower()
00545         for cur_key, cur_value in self.items():
00546             cur_key, cur_value = [unicode(a).lower() for a in [cur_key, cur_value]]
00547             if key is not None and cur_key != key:
00548                 continue
00549             distance = levenshtein(cur_value, term)
00550             if distance <= 3:
00551                 # handle most matches
00552                 self.distance = distance
00553                 return self
00554             if distance <= 5:
00555                 # handle part numbers, 'subtitle (nn)'
00556                 match = self._re_strippart.match(cur_value)
00557                 if match:
00558                     tmp = match.group(1)
00559                     if levenshtein(tmp, term) <= 3:
00560                         self.distance = distance
00561                         return self
00562         return None
00563 #end Episode
00564 
00565 # modified Tvdb API class using modified show classes
00566 class Tvdb( tvdb_api.Tvdb ):
00567     def series_by_sid(self, sid):
00568         "Lookup a series via it's sid"
00569         seriesid = 'sid:' + sid
00570         if not self.corrections.has_key(seriesid):
00571             self._getShowData(sid)
00572             self.corrections[seriesid] = sid
00573         return self.shows[sid]
00574     #end series_by_sid
00575 
00576     # override the existing method, using modified show classes
00577     def _setItem(self, sid, seas, ep, attrib, value):
00578         if sid not in self.shows:
00579             self.shows[sid] = Show()
00580         if seas not in self.shows[sid]:
00581             self.shows[sid][seas] = Season()
00582         if ep not in self.shows[sid][seas]:
00583             self.shows[sid][seas][ep] = Episode()
00584         self.shows[sid][seas][ep][attrib] = value
00585     #end _setItem
00586 
00587     # override the existing method, using modified show class
00588     def _setShowData(self, sid, key, value):
00589         if sid not in self.shows:
00590             self.shows[sid] = Show()
00591         self.shows[sid].data[key] = value
00592     #end _setShowData
00593 #end Tvdb
00594 
00595 # Search for a series by SID or Series name
00596 def search_for_series(tvdb, sid_or_name):
00597     "Get series data by sid or series name of the Tv show"
00598     if SID == True:
00599         return tvdb.series_by_sid(sid_or_name)
00600     else:
00601         return tvdb[sid_or_name]
00602 # end search_for_series
00603 
00604 # Verify that a Series or Series and Season exists on thetvdb.com
00605 def searchseries(t, opts, series_season_ep):
00606     global SID
00607     series_name=''
00608     if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
00609         series_name=override[series_season_ep[0].lower()][0] # Override series name
00610     else:
00611         series_name=series_season_ep[0] # Leave the series name alone
00612     try:
00613         # Search for the series or series & season or series & season & episode
00614         if len(series_season_ep)>1:
00615             if len(series_season_ep)>2: # series & season & episode
00616                 seriesfound=search_for_series(t, series_name)[ int(series_season_ep[1]) ][ int(series_season_ep[2]) ]
00617             else:
00618                 seriesfound=search_for_series(t, series_name)[ int(series_season_ep[1]) ] # series & season
00619         else:
00620             seriesfound=search_for_series(t, series_name) # Series only
00621     except tvdb_shownotfound:
00622         # No such show found.
00623         # Use the show-name from the files name, and None as the ep name
00624         sys.exit(0)
00625     except (tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound):
00626         # The season, episode or name wasn't found, but the show was.
00627         # Use the corrected show-name, but no episode name.
00628         sys.exit(0)
00629     except tvdb_error, errormsg:
00630         # Error communicating with thetvdb.com
00631         if SID == True: # Maybe the digits were a series name (e.g. 90210)
00632             SID = False
00633             return searchseries(t, opts, series_season_ep)
00634         sys.exit(0)
00635     except tvdb_userabort, errormsg:
00636         # User aborted selection (q or ^c)
00637         print "\n", errormsg
00638         sys.exit(0)
00639     else:
00640         if opts.raw==True:
00641             print "="*20
00642             print "Raw Series Data:\n"
00643             if len(series_season_ep)>1:
00644                 print t[ series_name ][ int(series_season_ep[1]) ]
00645             else:
00646                 print t[ series_name ]
00647             print "="*20
00648         return(seriesfound)
00649 # end searchseries
00650 
00651 # Retrieve Poster or Fan Art or Banner graphics URL(s)
00652 def get_graphics(t, opts, series_season_ep, graphics_type, single_option, language=False):
00653     banners='_banners'
00654     series_name=''
00655     graphics=[]
00656     if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
00657         series_name=override[series_season_ep[0].lower()][0] # Override series name
00658     else:
00659         series_name=series_season_ep[0] # Leave the series name alone
00660 
00661     if SID == True:
00662         URLs = t.ttvdb_parseBanners(series_name)
00663     else:
00664         URLs = t.ttvdb_parseBanners(t._nameToSid(series_name))
00665 
00666     if graphics_type == fanart_type: # Series fanart graphics
00667         if not len(URLs[u'fanart']):
00668             return []
00669         for url in URLs[u'fanart']:
00670             graphics.append(url)
00671     elif len(series_season_ep) == 1:
00672         if not len(URLs[u'series']):
00673             return []
00674         if graphics_type == banner_type: # Series Banners
00675             for url in URLs[u'series']:
00676                 graphics.append(url)
00677         else: # Series Posters
00678             for url in URLs[u'poster']:
00679                 graphics.append(url)
00680     else:
00681         if not len(URLs[u'season']):
00682             return []
00683         if graphics_type == banner_type: # Season Banners
00684             season_banners=[]
00685             for url in URLs[u'season']:
00686                 if url[u'bannertype2'] == u'seasonwide' and url[u'season'] == series_season_ep[1]:
00687                     season_banners.append(url)
00688             if not len(season_banners):
00689                 return []
00690             graphics = season_banners
00691         else: # Season Posters
00692             season_posters=[]
00693             for url in URLs[u'season']:
00694                 if url[u'bannertype2'] == u'season' and url[u'season'] == series_season_ep[1]:
00695                     season_posters.append(url)
00696             if not len(season_posters):
00697                 return []
00698             graphics = season_posters
00699 
00700     graphicsURLs=[]
00701     if single_option==False:
00702         graphicsURLs.append(graphics_type+':')
00703 
00704     count = 0
00705     wasanythingadded = 0
00706     anyotherlanguagegraphics=[]
00707     englishlanguagegraphics=[]
00708     for URL in graphics:
00709         if graphics_type == 'filename':
00710             if URL[graphics_type] == None:
00711                 continue
00712         if language:        # Is there a language to filter URLs on?
00713             if language == URL['language']:
00714                 graphicsURLs.append((URL['_bannerpath']).replace(http_find, http_replace))
00715             else: # Check for fall back graphics in case there are no selected language graphics
00716                 if u'en' == URL['language']:
00717                     englishlanguagegraphics.append((URL['_bannerpath']).replace(http_find, http_replace))
00718                 else:
00719                     anyotherlanguagegraphics.append((URL['_bannerpath']).replace(http_find, http_replace))
00720         else:
00721             graphicsURLs.append((URL['_bannerpath']).replace(http_find, http_replace))
00722         if wasanythingadded == len(graphicsURLs):
00723             continue
00724         wasanythingadded = len(graphicsURLs)
00725 
00726     if not len(graphicsURLs):
00727         if len(englishlanguagegraphics): # Fall back to English graphics
00728             graphicsURLs = englishlanguagegraphics
00729         elif len(anyotherlanguagegraphics):  # Fall back-back to any available graphics
00730             graphicsURLs = anyotherlanguagegraphics
00731 
00732     if opts.debug == True:
00733         print u"\nGraphics:\n", graphicsURLs
00734 
00735     if len(graphicsURLs) == 1 and graphicsURLs[0] == graphics_type+':':
00736         return [] # Due to the language filter there may not be any URLs
00737     return(graphicsURLs)
00738 # end get_graphics
00739 
00740 # Massage episode name to match those in thetvdb.com for this series
00741 def massageEpisode_name(ep_name, series_season_ep):
00742     for edit in override[series_season_ep[0].lower()][1]:
00743         ep_name=ep_name.replace(edit[0],edit[1]) # Edit episode name for each set of strings
00744     return ep_name
00745 # end massageEpisode_name
00746 
00747 # Remove '|' and replace with commas
00748 def change_to_commas(meta_data):
00749     if not meta_data: return meta_data
00750     meta_data = (u'|'.join([d for d in meta_data.split('| ') if d]))
00751     return (u', '.join([d for d in meta_data.split('|') if d]))
00752 # end change_to_commas
00753 
00754 # Change &amp; values to ascii equivalents
00755 def change_amp(text):
00756     if not text: return text
00757     text = text.replace("&quot;", "'").replace("\r\n", " ")
00758     text = text.replace(r"\'", "'")
00759     return text
00760 # end change_amp
00761 
00762 # Prepare for includion into a DB
00763 def make_db_ready(text):
00764     if not text: return text
00765     text = text.replace(u'\u2013', "-")
00766     text = text.replace(u'\u2014', "-")
00767     text = text.replace(u'\u2018', "'")
00768     text = text.replace(u'\u2019', "'")
00769     text = text.replace(u'\u2026', "...")
00770     text = text.replace(u'\u201c', '"')
00771     text = text.replace(u'\u201d', '"')
00772     text = text.encode('latin-1', 'backslashreplace')
00773     return text
00774 # end make_db_ready
00775 
00776 # Get Series Episode data by season
00777 def Getseries_episode_data(t, opts, series_season_ep, language = None):
00778     global screenshot_request, http_find, http_replace
00779 
00780     args = len(series_season_ep)
00781     series_name=''
00782     if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
00783         series_name=override[series_season_ep[0].lower()][0] # Override series name
00784     else:
00785         series_name=series_season_ep[0] # Leave the series name alone
00786 
00787     # Get Cast members
00788     cast_members=''
00789     try:
00790         tmp_cast = search_for_series(t, series_name)['_actors']
00791     except:
00792         cast_members=''
00793     if len(tmp_cast):
00794         cast_members=''
00795         for cast in tmp_cast:
00796             if cast['name']:
00797                 cast_members+=(cast['name']+u', ').encode('utf8')
00798         if cast_members != '':
00799             try:
00800                 cast_members = cast_members[:-2].encode('utf8')
00801             except UnicodeDecodeError:
00802                 cast_members = unicode(cast_members[:-2],'utf8')
00803             cast_members = change_amp(cast_members)
00804             cast_members = change_to_commas(cast_members)
00805             cast_members=cast_members.replace('\n',' ')
00806 
00807     # Get genre(s)
00808     genres=''
00809     try:
00810         genres_string = search_for_series(t, series_name)[u'genre'].encode('utf-8')
00811     except:
00812         genres_string=''
00813     if genres_string != None and genres_string != '':
00814         genres = change_amp(genres_string)
00815         genres = change_to_commas(genres)
00816 
00817     seasons=search_for_series(t, series_name).keys() # Get the seasons for this series
00818     for season in seasons:
00819         if args > 1: # If a season was specified skip other seasons
00820             if season != int(series_season_ep[1]):
00821                 continue
00822         episodes=search_for_series(t, series_name)[season].keys() # Get the episodes for this season
00823         for episode in episodes: # If an episode was specified skip other episodes
00824             if args > 2:
00825                 if episode != int(series_season_ep[2]):
00826                     continue
00827             extra_ep_data=[]
00828             available_keys=search_for_series(t, series_name)[season][episode].keys()
00829             if screenshot_request:
00830                 if u'filename' in available_keys:
00831                     screenshot = search_for_series(t, series_name)[season][episode][u'filename']
00832                     if screenshot:
00833                         print screenshot.replace(http_find, http_replace)
00834                     return
00835                 else:
00836                     return
00837             key_values=[]
00838             for values in data_keys: # Initialize an array for each possible data element for
00839                 key_values.append('') # each episode within a season
00840             for key in available_keys:
00841                 try:
00842                     i = data_keys.index(key) # Include only specific episode data
00843                 except ValueError:
00844                     if search_for_series(t, series_name)[season][episode][key] != None:
00845                         text = search_for_series(t, series_name)[season][episode][key]
00846                         text = change_amp(text)
00847                         text = change_to_commas(text)
00848                         if text == 'None' and key.title() == 'Director':
00849                             text = u"Unknown"
00850                         try:
00851                             extra_ep_data.append(u"%s:%s" % (key.title(), text))
00852                         except UnicodeDecodeError:
00853                             extra_ep_data.append(u"%s:%s" % (key.title(), unicode(text, "utf8")))
00854                     continue
00855                 text = search_for_series(t, series_name)[season][episode][key]
00856 
00857                 if text == None and key.title() == 'Director':
00858                     text = u"Unknown"
00859                 if text == None or text == 'None':
00860                     continue
00861                 else:
00862                     text = change_amp(text)
00863                     value = change_to_commas(text)
00864                     value = value.replace(u'\n', u' ')
00865                 key_values[i]=value
00866             index = 0
00867             if SID == False:
00868                 print u"Title:%s" % series_name # Ouput the full series name
00869             else:
00870                 print u"Title:%s" % search_for_series(t, series_name)[u'seriesname']
00871 
00872             for key in data_titles:
00873                 if key_values[index] != None:
00874                     if data_titles[index] == u'ReleaseDate:' and len(key_values[index]) > 4:
00875                         print u'%s%s'% (u'Year:', key_values[index][:4])
00876                     if key_values[index] != 'None':
00877                         print u'%s%s' % (data_titles[index], key_values[index])
00878                 index+=1
00879             cast_print=False
00880             for extra_data in extra_ep_data:
00881                 if extra_data[:extra_data.index(':')] == u'Gueststars':
00882                     extra_cast = extra_data[extra_data.index(':')+1:]
00883                     if (len(extra_cast)>128) and not extra_cast.count(','):
00884                         continue
00885                     if cast_members:
00886                         extra_data=(u"Cast:%s" % cast_members)+', '+extra_cast
00887                     else:
00888                         extra_data=u"Cast:%s" % extra_cast
00889                     cast_print=True
00890                 print extra_data
00891             if cast_print == False:
00892                 print u"Cast:%s" % cast_members
00893             if genres != '':
00894                 print u"Genres:%s" % genres
00895             print u"Runtime:%s" % search_for_series(t, series_name)[u'runtime']
00896 
00897             # URL to TVDB web site episode web page for this series
00898             for url_data in [u'seriesid', u'seasonid', u'id']:
00899                 if not url_data in available_keys:
00900                     break
00901             else:
00902                 print u'URL:http://www.thetvdb.com/?tab=episode&seriesid=%s&seasonid=%s&id=%s' % (search_for_series(t, series_name)[season][episode][u'seriesid'], search_for_series(t, series_name)[season][episode][u'seasonid'],search_for_series(t, series_name)[season][episode][u'id'])
00903 # end Getseries_episode_data
00904 
00905 # Get Series Season and Episode numbers
00906 def Getseries_episode_numbers(t, opts, series_season_ep):
00907     def _episode_sort(episode):
00908         seasonnumber = 0
00909         episodenumber = 0
00910         try: seasonnumber = int(episode['seasonnumber'])
00911         except: pass
00912         try: episodenumber = int(episode['episodenumber'])
00913         except: pass
00914         return (-episode.distance, seasonnumber, episodenumber)
00915 
00916     global xmlFlag
00917     series_name=''
00918     ep_name=''
00919     if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
00920         series_name=override[series_season_ep[0].lower()][0] # Override series name
00921         ep_name=series_season_ep[1]
00922         if len(override[series_season_ep[0].lower()][1]) != 0: # Are there search-replace strings?
00923             ep_name=massageEpisode_name(ep_name, series_season_ep)
00924     else:
00925         series_name=series_season_ep[0] # Leave the series name alone
00926         ep_name=series_season_ep[1] # Leave the episode name alone
00927 
00928     season_ep_num=search_for_series(t, series_name).fuzzysearch(ep_name, 'episodename')
00929     if len(season_ep_num) != 0:
00930         for episode in sorted(season_ep_num, key=lambda ep: _episode_sort(ep), reverse=True):
00931 #            if episode.distance == 0: # exact match
00932                 if xmlFlag:
00933                     displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
00934                     sys.exit(0)
00935                 print season_and_episode_num.replace('\\n', '\n') % (int(episode['seasonnumber']), int(episode['episodenumber']))
00936 #            elif (episode['episodename'].lower()).startswith(ep_name.lower()):
00937 #                if len(episode['episodename']) > (len(ep_name)+1):
00938 #                    if episode['episodename'][len(ep_name):len(ep_name)+2] != ' (':
00939 #                        continue # Skip episodes the are not part of a set of (1), (2) ... etc
00940 #                    if xmlFlag:
00941 #                        displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
00942 #                        sys.exit(0)
00943 #                    print season_and_episode_num.replace('\\n', '\n') % (int(episode['seasonnumber']), int(episode['episodenumber']))
00944 # end Getseries_episode_numbers
00945 
00946 # Set up a custom interface to get all series matching a partial series name
00947 class returnAllSeriesUI(tvdb_ui.BaseUI):
00948     def __init__(self, config, log):
00949         self.config = config
00950         self.log = log
00951 
00952     def selectSeries(self, allSeries):
00953         return allSeries
00954 # ends returnAllSeriesUI
00955 
00956 def initialize_override_dictionary(useroptions):
00957     """ Change variables through a user supplied configuration file
00958     return False and exit the script if there are issues with the configuration file values
00959     """
00960     if useroptions[0]=='~':
00961         useroptions=os.path.expanduser("~")+useroptions[1:]
00962     if os.path.isfile(useroptions) == False:
00963         sys.stderr.write(
00964             "! The specified user configuration file (%s) is not a file\n" % useroptions
00965         )
00966         sys.exit(1)
00967     massage = {}
00968     overrides = {}
00969     cfg = ConfigParser.SafeConfigParser()
00970     cfg.read(useroptions)
00971 
00972     for section in cfg.sections():
00973         if section == 'regex':
00974             # Change variables per user config file
00975             for option in cfg.options(section):
00976                 name_parse.append(re.compile(cfg.get(section, option)))
00977             continue
00978         if section =='ep_name_massage':
00979             for option in cfg.options(section):
00980                 tmp =cfg.get(section, option).split(',')
00981                 if len(tmp)%2 and len(cfg.get(section, option)) != 0:
00982                     sys.stderr.write("! For (%s) 'ep_name_massage' values must be in pairs\n" % option)
00983                     sys.exit(1)
00984                 tmp_array=[]
00985                 i=0
00986                 while i != len(tmp):
00987                     tmp_array.append([tmp[i].replace('"',''), tmp[i+1].replace('"','')])
00988                     i+=2
00989                 massage[option]=tmp_array
00990             continue
00991         if section =='series_name_override':
00992             for option in cfg.options(section):
00993                 overrides[option] = cfg.get(section, option)
00994             tvdb = Tvdb(banners=False, debug = False, interactive = False, cache = cache_dir, custom_ui=returnAllSeriesUI, apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV
00995             for key in overrides.keys():
00996                 sid = overrides[key]
00997                 if len(sid) == 0:
00998                     continue
00999                 try: # Check that the SID (Series id) is numeric
01000                     dummy = int(sid)
01001                 except:
01002                     sys.stdout.write("! Series (%s) Invalid SID (not numeric) [%s] in config file\n" % (key, sid))
01003                     sys.exit(1)
01004                 # Make sure that the series name is not empty or all blanks
01005                 if len(key.replace(' ','')) == 0:
01006                     sys.stdout.write("! Invalid Series name (must have some non-blank characters) [%s] in config file\n" % key)
01007                     print parts
01008                     sys.exit(1)
01009 
01010                 try:
01011                     series_name_sid=tvdb.series_by_sid(sid)
01012                 except:
01013                     sys.stdout.write("! Invalid Series (no matches found in thetvdb,com) (%s) sid (%s) in config file\n" % (key, sid))
01014                     sys.exit(1)
01015                 overrides[key]=series_name_sid[u'seriesname'].encode('utf-8')
01016             continue
01017 
01018     for key in overrides.keys():
01019         override[key] = [overrides[key],[]]
01020 
01021     for key in massage.keys():
01022         if override.has_key(key):
01023             override[key][1]=massage[key]
01024         else:
01025             override[key]=[key, massage[key]]
01026     return
01027 # END initialize_override_dictionary
01028 
01029 def initializeXslt(language):
01030     ''' Initalize all data and functions for XSLT stylesheet processing
01031     return nothing
01032     '''
01033     global xslt, tvdbXpath
01034     try:
01035         import MythTV.ttvdb.tvdbXslt as tvdbXslt
01036     except Exception, errmsg:
01037         sys.stderr.write('! Error: Importing tvdbXslt error(%s)\n' % errmsg)
01038         sys.exit(1)
01039 
01040     xslt = tvdbXslt.xpathFunctions()
01041     xslt.language = language
01042     xslt.buildFuncDict()
01043     tvdbXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format')
01044     tvdbXpath.prefix = 'tvdbXpath'
01045     for key in xslt.FuncDict.keys():
01046         tvdbXpath[key] = xslt.FuncDict[key]
01047     return
01048 # end initializeXslt()
01049 
01050 def displaySearchXML(tvdb_api):
01051     '''Using a XSLT style sheet translate TVDB search results into the MythTV Universal Query format
01052     return nothing
01053     '''
01054     global xslt, tvdbXpath
01055 
01056     # Remove duplicates when a non-English language code is specified
01057     if xslt.language != 'en':
01058         compareFilter = etree.XPath('//id[text()=$Id]')
01059         idLangFilter = etree.XPath('//id[text()=$Id]/../language[text()="en"]/..')
01060         tmpTree = deepcopy(tvdb_api.searchTree)
01061         for seriesId in tmpTree.xpath('//Series/id/text()'):
01062             if len(compareFilter(tvdb_api.searchTree, Id=seriesId)) > 1:
01063                 tmpList = idLangFilter(tvdb_api.searchTree, Id=seriesId)
01064                 if len(tmpList):
01065                     tvdb_api.searchTree.remove(tmpList[0])
01066 
01067     tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbQuery.xsl')))
01068     items = tvdbQueryXslt(tvdb_api.searchTree)
01069     if items.getroot() != None:
01070         if len(items.xpath('//item')):
01071             sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
01072     sys.exit(0)
01073 # end displaySearchXML()
01074 
01075 def displaySeriesXML(tvdb_api, series_season_ep):
01076     '''Using a XSLT style sheet translate TVDB Series data results into the
01077     MythTV Universal Query format
01078     return nothing
01079     '''
01080     global xslt, tvdbXpath
01081     allDataElement = etree.XML(u'<allData></allData>')
01082     requestDetails = etree.XML(u'<requestDetails></requestDetails>')
01083     requestDetails.attrib['lang'] = xslt.language
01084     requestDetails.attrib['series'] = series_season_ep[0]
01085     requestDetails.attrib['season'] = series_season_ep[1]
01086     requestDetails.attrib['episode'] = series_season_ep[2]
01087     allDataElement.append(requestDetails)
01088 
01089     # Combine the various XML inputs into a single XML element and send to the XSLT stylesheet
01090     if tvdb_api.epInfoTree != None:
01091         allDataElement.append(tvdb_api.epInfoTree)
01092     else:
01093         sys.exit(0)
01094     if tvdb_api.actorsInfoTree != None:
01095         allDataElement.append(tvdb_api.actorsInfoTree)
01096     else:
01097         allDataElement.append(etree.XML(u'<Actors></Actors>'))
01098     if tvdb_api.imagesInfoTree != None:
01099         allDataElement.append(tvdb_api.imagesInfoTree)
01100     else:
01101         allDataElement.append(etree.XML(u'<Banners></Banners>'))
01102 
01103     tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbVideo.xsl')))
01104     items = tvdbQueryXslt(allDataElement)
01105     if items.getroot() != None:
01106         if len(items.xpath('//item')):
01107             sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
01108     sys.exit(0)
01109 # end displaySeriesXML()
01110 
01111 def displayCollectionXML(tvdb_api):
01112     '''Using a XSLT style sheet translate TVDB series results into the MythTV Universal Query format
01113     return nothing
01114     '''
01115     global xslt, tvdbXpath
01116 
01117     # Remove duplicates when non-English language code is specified
01118     if xslt.language != 'en':
01119         compareFilter = etree.XPath('//id[text()=$Id]')
01120         idLangFilter = etree.XPath('//id[text()=$Id]/../language[text()="en"]/..')
01121         tmpTree = deepcopy(tvdb_api.seriesInfoTree)
01122         for seriesId in tmpTree.xpath('//Series/id/text()'):
01123             if len(compareFilter(tvdb_api.seriesInfoTree, Id=seriesId)) > 1:
01124                 tmpList = idLangFilter(tvdb_api.seriesInfoTree, Id=seriesId)
01125                 if len(tmpList):
01126                     tvdb_api.seriesInfoTree.remove(tmpList[0])
01127 
01128     tvdbCollectionXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbCollection.xsl')))
01129     items = tvdbCollectionXslt(tvdb_api.seriesInfoTree)
01130     if items.getroot() != None:
01131         if len(items.xpath('//item')):
01132             sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
01133     sys.exit(0)
01134 # end displayCollectionXML()
01135 
01136 def main():
01137     parser = OptionParser(usage=u"%prog usage: ttvdb -hdruviomMPFBDS [parameters]\n <series name or 'series and season number' or 'series and season number and episode number'>\n\nFor details on using ttvdb with Mythvideo see the ttvdb wiki page at:\nhttp://www.mythtv.org/wiki/Ttvdb.py")
01138 
01139     parser.add_option(  "-d", "--debug", action="store_true", default=False, dest="debug",
01140                         help=u"Show debugging info")
01141     parser.add_option(  "-r", "--raw", action="store_true",default=False, dest="raw",
01142                         help=u"Dump raw data only")
01143     parser.add_option(  "-u", "--usage", action="store_true", default=False, dest="usage",
01144                         help=u"Display examples for executing the ttvdb script")
01145     parser.add_option(  "-v", "--version", action="store_true", default=False, dest="version",
01146                         help=u"Display version and author")
01147     parser.add_option(  "-i", "--interactive", action="store_true", default=False, dest="interactive",
01148                         help=u"Interaction mode (allows selection of a specific Series)")
01149     parser.add_option(  "-c", "--configure", metavar="FILE", default="", dest="configure",
01150                         help=u"Use configuration settings")
01151     parser.add_option(  "-l", "--language", metavar="LANGUAGE", default=u'en', dest="language",
01152                         help=u"Select data that matches the specified language fall back to english if nothing found (e.g. 'es' Español, 'de' Deutsch ... etc)")
01153     parser.add_option(  "-n", "--num_seasons", action="store_true", default=False, dest="num_seasons",
01154                         help=u"Return the season numbers for a series")
01155     parser.add_option(  "-t", action="store_true", default=False, dest="test",
01156                         help=u"Test for the availability of runtime dependencies")
01157     parser.add_option(  "-m", "--mythvideo", action="store_true", default=False, dest="mythvideo",
01158                         help=u"Conform to mythvideo standards when processing -M, -P, -F and -D")
01159     parser.add_option(  "-M", "--list", action="store_true", default=False, dest="list",
01160                         help=u"Get matching TV Series list")
01161     parser.add_option(  "-P", "--poster", action="store_true", default=False, dest="poster",
01162                         help=u"Get Series Poster URL(s)")
01163     parser.add_option(  "-F", "--fanart", action="store_true", default=False, dest="fanart",
01164                         help=u"Get Series fan art URL(s)")
01165     parser.add_option(  "-B", "--backdrop", action="store_true", default=False, dest="banner",
01166                         help=u"Get Series banner/backdrop URL(s)")
01167     parser.add_option(  "-S", "--screenshot", action="store_true", default=False, dest="screenshot",
01168                         help=u"Get Series episode screenshot URL")
01169     parser.add_option(  "-D", "--data", action="store_true", default=False, dest="data",
01170                         help=u"Get Series episode data")
01171     parser.add_option(  "-N", "--numbers", action="store_true", default=False, dest="numbers",
01172                         help=u"Get Season and Episode numbers")
01173     parser.add_option(  "-C", "--collection", action="store_true", default=False, dest="collection",
01174                         help=u'Get a TV Series (collection) "series" level information')
01175 
01176     opts, series_season_ep = parser.parse_args()
01177 
01178 
01179     # Test mode, if we've made it here, everything is ok
01180     if opts.test:
01181         print "Everything appears to be in order"
01182         sys.exit(0)
01183 
01184     # Make everything unicode utf8
01185     for index in range(len(series_season_ep)):
01186         series_season_ep[index] = unicode(series_season_ep[index], 'utf8')
01187 
01188     if opts.debug == True:
01189         print "opts", opts
01190         print "\nargs", series_season_ep
01191 
01192     # Process version command line requests
01193     if opts.version == True:
01194         version = etree.XML(u'<grabber></grabber>')
01195         etree.SubElement(version, "name").text = __title__
01196         etree.SubElement(version, "author").text = __author__
01197         etree.SubElement(version, "thumbnail").text = 'ttvdb.png'
01198         etree.SubElement(version, "command").text = 'ttvdb.py'
01199         etree.SubElement(version, "type").text = 'television'
01200         etree.SubElement(version, "description").text = 'Search and metadata downloads for thetvdb.com'
01201         etree.SubElement(version, "version").text = __version__
01202         sys.stdout.write(etree.tostring(version, encoding='UTF-8', pretty_print=True))
01203         sys.exit(0)
01204 
01205     # Process usage command line requests
01206     if opts.usage == True:
01207         sys.stdout.write(usage_txt)
01208         sys.exit(0)
01209 
01210     if len(series_season_ep) == 0:
01211         parser.error("! No series or series season episode supplied")
01212         sys.exit(1)
01213 
01214     # Default output format of season and episode numbers
01215     global season_and_episode_num, screenshot_request
01216     season_and_episode_num='S%02dE%02d' # Format output example "S04E12"
01217 
01218     if opts.numbers == False:
01219         if len(series_season_ep) > 1:
01220             if not _can_int(series_season_ep[1]):
01221                 parser.error("! Season is not numeric")
01222                 sys.exit(1)
01223         if len(series_season_ep) > 2:
01224             if not _can_int(series_season_ep[2]):
01225                 parser.error("! Episode is not numeric")
01226                 sys.exit(1)
01227     else:
01228         if len(series_season_ep) < 2:
01229             parser.error("! An Episode name must be included")
01230             sys.exit(1)
01231         if len(series_season_ep) == 3:
01232             season_and_episode_num = series_season_ep[2] # Override default output format
01233 
01234     if opts.screenshot:
01235         if len(series_season_ep) > 1:
01236             if not _can_int(series_season_ep[1]):
01237                 parser.error("! Season is not numeric")
01238                 sys.exit(1)
01239         if len(series_season_ep) > 2:
01240             if not _can_int(series_season_ep[2]):
01241                 parser.error("! Episode is not numeric")
01242                 sys.exit(1)
01243         if not len(series_season_ep) > 2:
01244             parser.error("! Option (-S), episode screenshot search requires Season and Episode numbers")
01245             sys.exit(1)
01246         screenshot_request = True
01247 
01248     if opts.debug == True:
01249         print series_season_ep
01250 
01251     if opts.debug == True:
01252         print "#"*20
01253         print "# series_season_ep array(",series_season_ep,")"
01254 
01255     if opts.debug == True:
01256         print "#"*20
01257         print "# Starting tvtvb"
01258         print "# Processing (%s) Series" % ( series_season_ep[0] )
01259 
01260     # List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
01261     # Hard-coded here as it is realtively static, and saves another HTTP request, as
01262     # recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
01263     valid_languages = ["da", "fi", "nl", "de", "it", "es", "fr","pl", "hu","el","tr", "ru","he","ja","pt","zh","cs","sl", "hr","ko","en","sv","no"]
01264 
01265     # Validate language as specified by user
01266     if not opts.language in valid_languages:
01267         opts.language = 'en'    # Set the default to English when an invalid language was specified
01268 
01269     # Set XML to be the default display mode for -N, -M, -D, -C
01270     opts.xml = True
01271     initializeXslt(opts.language)
01272 
01273     # Access thetvdb.com API with banners (Posters, Fanart, banners, screenshots) data retrieval enabled
01274     if opts.list ==True:
01275         t = Tvdb(banners=False, debug = opts.debug, cache = cache_dir, custom_ui=returnAllSeriesUI, language = opts.language, apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV
01276         if opts.xml:
01277             t.xml = True
01278     elif opts.interactive == True:
01279         t = Tvdb(banners=True, debug=opts.debug, interactive=True,  select_first=False, cache=cache_dir, actors = True, language = opts.language, apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV
01280         if opts.xml:
01281             t.xml = True
01282     else:
01283         t = Tvdb(banners=True, debug = opts.debug, cache = cache_dir, actors = True, language = opts.language, apikey="0BB856A59C51D607")  # thetvdb.com API key requested by MythTV
01284         if opts.xml:
01285             t.xml = True
01286 
01287     # Determine if there is a SID or a series name to search with
01288     global SID
01289     SID = False
01290     if _can_int(series_season_ep[0]): # if it is numeric then assume it is a series ID number
01291         SID = True
01292     else:
01293         SID = False
01294 
01295     # The -C collections options only supports a SID as input
01296     if opts.collection:
01297         if SID:
01298             pass
01299         else:
01300             parser.error("! Option (-C), collection requires an inetref number")
01301             sys.exit(1)
01302 
01303     if opts.debug == True:
01304         print "# ..got tvdb mirrors"
01305         print "# Start to process series or series_season_ep"
01306         print "#"*20
01307 
01308     global override
01309     override={} # Initialize series name override dictionary
01310     # If the user wants Series name overrides and a override file exists then create an overide dictionary
01311     if opts.configure != '':    # Did the user want to override the default config file name/location
01312         if opts.configure[0]=='~':
01313             opts.configure=os.path.expanduser("~")+opts.configure[1:]
01314         if os.path.exists(opts.configure) == 1: # Do overrides exist?
01315             initialize_override_dictionary(opts.configure)
01316         else:
01317             debuglog("! The specified override file (%s) does not exist" % opts.configure)
01318             sys.exit(1)
01319     else: # Check if there is a default configuration file
01320         default_config = u"%s/%s" % (os.path.expanduser(u"~"), u".mythtv/ttvdb.conf")
01321         if os.path.isfile(default_config):
01322             opts.configure = default_config
01323             initialize_override_dictionary(opts.configure)
01324 
01325     if len(override) == 0:
01326         opts.configure = False # Turn off the override option as there is nothing to override
01327 
01328     # Check if a video name was passed and if so parse it for series name, season and episode numbers
01329     if not opts.collection and len(series_season_ep) == 1:
01330         for r in name_parse:
01331             match = r.match(series_season_ep[0])
01332             if match:
01333                 seriesname, seasno, epno = match.groups()
01334                 #remove ._- characters from name (- removed only if next to end of line)
01335                 seriesname = re.sub("[\._]|\-(?=$)", " ", seriesname).strip()
01336                 series_season_ep = [seriesname, seasno, epno]
01337                 break # Matched - to the next file!
01338 
01339     # Fetch a list of matching series names
01340     if opts.list ==True:
01341         try:
01342             allSeries=t._getSeries(series_season_ep[0])
01343         except tvdb_shownotfound:
01344             sys.exit(0) # No matching series
01345         except Exception, e:
01346             sys.stderr.write("! Error: %s\n" % (e))
01347             sys.exit(1) # Most likely a communications error
01348         if opts.xml:
01349             displaySearchXML(t)
01350             sys.exit(0)
01351         match_list = []
01352         for series_name_sid in allSeries: # list search results
01353             key_value = u"%s:%s" % (series_name_sid['sid'], series_name_sid['name'])
01354             if not key_value in match_list: # Do not add duplicates
01355                 match_list.append(key_value)
01356                 print key_value
01357         sys.exit(0) # The Series list option (-M) is the only option honoured when used
01358 
01359     # Fetch TV series collection information
01360     if opts.collection:
01361         try:
01362             t._getShowData(series_season_ep[0])
01363         except tvdb_shownotfound:
01364             sys.exit(0) # No matching series
01365         except Exception, e:
01366             sys.stderr.write("! Error: %s\n" % (e))
01367             sys.exit(1) # Most likely a communications error
01368         displayCollectionXML(t)
01369         sys.exit(0) # The TV Series collection option (-C) is the only option honoured when used
01370 
01371     # Verify that thetvdb.com has the desired series_season_ep.
01372     # Exit this module if series_season_ep is not found
01373     if opts.numbers == False and opts.num_seasons == False:
01374         seriesfound=searchseries(t, opts, series_season_ep)
01375         x=1
01376     else:
01377         x=[]
01378         x.append(series_season_ep[0]) # Only use series name in check
01379         seriesfound=searchseries(t, opts, x)
01380 
01381     # Return the season numbers for a series
01382     if opts.num_seasons == True:
01383         season_numbers=''
01384         for x in seriesfound.keys():
01385             season_numbers+='%d,' % x
01386         print season_numbers[:-1]
01387         sys.exit(0) # Option (-n) is the only option honoured when used
01388 
01389     # Dump information accessable for a Series and ONLY first season of episoded data
01390     if opts.debug == True:
01391         print "#"*20
01392         print "# Starting Raw keys call"
01393         print "Lvl #1:" # Seasons for series
01394         x = t[series_season_ep[0]].keys()
01395         print t[series_season_ep[0]].keys()
01396         print "#"*20
01397         print "Lvl #2:" # Episodes for each season
01398         for y in x:
01399             print t[series_season_ep[0]][y].keys()
01400         print "#"*20
01401         print "Lvl #3:" # Keys for each episode within the 1st season
01402         z = t[series_season_ep[0]][1].keys()
01403         for aa in z:
01404             print t[series_season_ep[0]][1][aa].keys()
01405         print "#"*20
01406         print "Lvl #4:" # Available data for each episode in 1st season
01407         for aa in z:
01408             codes = t[series_season_ep[0]][1][aa].keys()
01409             print "\n\nStart:"
01410             for c in codes:
01411                 print "="*50
01412                 print 'Key Name=('+c+'):'
01413                 print t[series_season_ep[0]][1][aa][c]
01414                 print "="*50
01415         print "#"*20
01416         sys.exit (True)
01417 
01418     if opts.numbers == True: # Fetch and output season and episode numbers
01419         global xmlFlag
01420         if opts.xml:
01421             xmlFlag = True
01422         else:
01423             xmlFlag = False
01424         Getseries_episode_numbers(t, opts, series_season_ep)
01425         sys.exit(0) # The Numbers option (-N) is the only option honoured when used
01426 
01427     if opts.data or screenshot_request: # Fetch and output episode data
01428         if opts.mythvideo:
01429             if len(series_season_ep) != 3:
01430                 print u"Season and Episode numbers required."
01431             else:
01432                 if opts.xml:
01433                     displaySeriesXML(t, series_season_ep)
01434                     sys.exit(0)
01435                 Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
01436         else:
01437             if opts.xml and len(series_season_ep) == 3:
01438                 displaySeriesXML(t, series_season_ep)
01439                 sys.exit(0)
01440             Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
01441 
01442     # Fetch the requested graphics URL(s)
01443     if opts.debug == True:
01444         print "#"*20
01445         print "# Checking if Posters, Fanart or Banners are available"
01446         print "#"*20
01447 
01448     if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
01449         banners_keys = search_for_series(t, override[series_season_ep[0].lower()][0])['_banners'].keys()
01450     else:
01451         banners_keys = search_for_series(t, series_season_ep[0])['_banners'].keys()
01452 
01453     banner= False
01454     poster= False
01455     fanart= False
01456 
01457     for x in banners_keys: # Determine what type of graphics is available
01458         if x == fanart_key:
01459             fanart=True
01460         elif x== poster_key:
01461             poster=True
01462         elif x==season_key or x==banner_key:
01463             banner=True
01464 
01465     # Make sure that some graphics URL(s) (Posters, FanArt or Banners) are available
01466     if ( fanart!=True and poster!=True and banner!=True ):
01467         sys.exit(0)
01468 
01469     if opts.debug == True:
01470         print "#"*20
01471         print "# One or more of Posters, Fanart or Banners are available"
01472         print "#"*20
01473 
01474     # Determine if graphic URL identification output is required
01475     if opts.data:    # Along with episode data get all graphics
01476         opts.poster = True
01477         opts.fanart = True
01478         opts.banner = True
01479         single_option = True
01480         fanart, banner, poster = (True, True, True)
01481     else:
01482         y=0
01483         single_option=True
01484         if opts.poster==True:
01485             y+=1
01486         if opts.fanart==True:
01487             y+=1
01488         if opts.banner==True:
01489             y+=1
01490 
01491     if (poster==True and opts.poster==True and opts.raw!=True): # Get posters and send to stdout
01492         season_poster_found = False
01493         if opts.mythvideo:
01494             if len(series_season_ep) < 2:
01495                 print u"Season and Episode numbers required."
01496                 sys.exit(0)
01497         all_posters = u'Coverart:'
01498         all_empty = len(all_posters)
01499         for p in get_graphics(t, opts, series_season_ep, poster_type, single_option, opts.language):
01500             all_posters = all_posters+p+u','
01501             season_poster_found = True
01502         if season_poster_found == False: # If there were no season posters get the series top poster
01503             series_name=''
01504             if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
01505                 series_name=override[series_season_ep[0].lower()][0] # Override series name
01506             else:
01507                 series_name=series_season_ep[0] # Leave the series name alone
01508             for p in get_graphics(t, opts, [series_name], poster_type, single_option, opts.language):
01509                 all_posters = all_posters+p+u','
01510         if len(all_posters) > all_empty:
01511             if all_posters[-1] == u',':
01512                 print all_posters[:-1]
01513             else:
01514                 print all_posters
01515 
01516     if (fanart==True and opts.fanart==True and opts.raw!=True): # Get Fan Art and send to stdout
01517         all_fanart = u'Fanart:'
01518         all_empty = len(all_fanart)
01519         for f in get_graphics(t, opts, series_season_ep, fanart_type, single_option, opts.language):
01520             all_fanart = all_fanart+f+u','
01521         if len(all_fanart) > all_empty:
01522             if all_fanart[-1] == u',':
01523                 print all_fanart[:-1]
01524             else:
01525                 print all_fanart
01526 
01527     if (banner==True and opts.banner==True and opts.raw!=True): # Also change to get ALL Series graphics
01528         season_banner_found = False
01529         if opts.mythvideo:
01530             if len(series_season_ep) < 2:
01531                 print u"Season and Episode numbers required."
01532                 sys.exit(0)
01533         all_banners = u'Banner:'
01534         all_empty = len(all_banners)
01535         for b in get_graphics(t, opts, series_season_ep, banner_type, single_option, opts.language):
01536             all_banners = all_banners+b+u','
01537             season_banner_found = True
01538         if not season_banner_found: # If there were no season banner get the series top banner
01539             series_name=''
01540             if opts.configure != "" and override.has_key(series_season_ep[0].lower()):
01541                 series_name=override[series_season_ep[0].lower()][0] # Override series name
01542             else:
01543                 series_name=series_season_ep[0] # Leave the series name alone
01544             for b in get_graphics(t, opts, [series_name], banner_type, single_option, opts.language):
01545                 all_banners = all_banners+b+u','
01546         if len(all_banners) > all_empty:
01547             if all_banners[-1] == u',':
01548                 print all_banners[:-1]
01549             else:
01550                 print all_banners
01551 
01552     if opts.debug == True:
01553         print "#"*20
01554         print "# Processing complete"
01555         print "#"*20
01556     sys.exit(0)
01557 #end main
01558 
01559 if __name__ == "__main__":
01560     main()
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends