MythTV  master
ttvdb.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: UTF-8 -*-
3 # ----------------------
4 # Name: ttvdb.py
5 # Python Script
6 # Author: R.D. Vaughan
7 # Purpose:
8 # This python script is intended to perform TV series data lookups
9 # based on information found on the http://thetvdb.com/ website. It
10 # follows the MythTV standards set for the movie data
11 # lookups. e.g. the perl script "tmdb.pl" used to access themoviedb.com
12 # This script uses the python module tvdb_api.py (v1.0 or higher) found at
13 # http://pypi.python.org/pypi?%3Aaction=search&term=tvnamer&submit=search
14 # thanks to the authors of this excellant module.
15 # The tvdb_api.py module uses the full access XML api published by
16 # thetvdb.com see:
17 # http://thetvdb.com/wiki/index.php?title=Programmers_API
18 # Users of this script are encouraged to populate thetvdb.com with TV show
19 # information, posters, fan art and banners. The richer the source the more
20 # valuable the script.
21 # This python script was modified based on the "tvnamer.py" created by
22 # "dbr/Ben" who is also
23 # the author of the "tvdb_api.py" module. "tvnamer.py" is used to rename avi
24 # files with series/episode information found at thetvdb.com
25 # Command example:
26 # See help (-u and -h) options
27 #
28 # Design:
29 # 1) Verify the command line options (display help or version and exit)
30 # 2) Verify that thetvdb.com has the series or series_season_ep being
31 # requested exit if does not exit
32 # 3) Find the requested information and send to stdout if any found
33 #
34 #
35 # License:Creative Commons GNU GPL v2
36 # (http://creativecommons.org/licenses/GPL/2.0/)
37 # -------------------------------------
38 """
39 Doctests
40 
41 >>> sys.argv = shlex.split('./ttvdb.py -B Sanctuary')
42 >>> main()
43 Banner:http://thetvdb.com/banners/graphical/80159-g4.jpg,http://thetvdb.com/banners/graphical/80159-g5.jpg,http://thetvdb.com/banners/graphical/80159-g3.jpg,http://thetvdb.com/banners/graphical/80159-g6.jpg,http://thetvdb.com/banners/graphical/80159-g2.jpg,http://thetvdb.com/banners/graphical/80159-g.jpg,http://thetvdb.com/banners/graphical/80159-g8.jpg
44 0
45 >>> sys.argv = shlex.split('./ttvdb.py -S SG-1 1 10')
46 >>> main()
47 <?xml version='1.0' encoding='UTF-8'?>
48 <metadata>
49  <item>
50  <title>Stargate SG-1</title>
51  <subtitle>Thor's Hammer</subtitle>
52  <description>Teal'c and O'Neill are transported to an underground cage designed by the Asgard to protect an alien world from the Goa'uld.</description>
53  <season>1</season>
54  <episode>10</episode>
55  <certifications>
56  <certification locale="us" name="TV-PG"/>
57  </certifications>
58  <categories>
59  <category type="genre" name="Action"/>
60  <category type="genre" name="Adventure"/>
61  <category type="genre" name="Fantasy"/>
62  <category type="genre" name="Science-Fiction"/>
63  </categories>
64  <studios>
65  <studio name="Syfy"/>
66  </studios>
67  <runtime>45</runtime>
68  <inetref>72449</inetref>
69  <collectionref>72449</collectionref>
70  <imdb>0118480</imdb>
71  <tmsref>EP00225421</tmsref>
72  <language>en</language>
73  <year>1997</year>
74  <releasedate>1997-09-26</releasedate>
75  <people>
76  <person job="Actor" name="Richard Dean Anderson" character="Jack O'Neill" url="http://thetvdb.com/banners/actors/17720.jpg" thumb="http://thetvdb.com/banners/actors/17720.jpg"/>
77 ...
78  <person job="Guest Star" name="James Earl Jones"/>
79  <person job="Guest Star" name="Galyn Gorg"/>
80  <person job="Guest Star" name="Tamsin Kelsey"/>
81  <person job="Guest Star" name="Vincent Hammond"/>
82  <person job="Guest Star" name="Mark Gibbon"/>
83  <person job="Director" name="Brad Turner"/>
84  <person job="Author" name="Katharyn Michaelian Powers"/>
85  </people>
86  <images>
87  <image type="screenshot" url="http://thetvdb.com/banners/episodes/72449/85759.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/72449/85759.jpg"/>
88  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72449-1-9.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72449-1-9.jpg"/>
89  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72449-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72449-1.jpg"/>
90  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72449-1-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72449-1-2.jpg"/>
91  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72449-1-8.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72449-1-8.jpg"/>
92  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/185-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/185-1.jpg"/>
93  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72449-55.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72449-55.jpg" width="1920" height="1080"/>
94  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72449-34.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72449-34.jpg" width="1280" height="720"/>
95 ...
96  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72449-75.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72449-75.jpg" width="1280" height="720"/>
97  </images>
98  </item>
99 </metadata>
100 0
101 >>> sys.argv = shlex.split('ttvdb -PFB "Stargate SG-1"')
102 >>> main()
103 Coverart:http://thetvdb.com/banners/posters/72449....jpg
104 Fanart:http://thetvdb.com/banners/fanart/original/72449-....jpg
105 Banner:http://thetvdb.com/banners/graphical/72449-....jpg
106 0
107 
108 # Coverart:http://www.thetvdb.com/banners/posters/72449-1.jpg
109 # Fanart:http://www.thetvdb.com/banners/fanart/original/72449-1.jpg
110 # Banner:http://www.thetvdb.com/banners/graphical/185-g3.jpg
111 >>> sys.argv = shlex.split('ttvdb -B "Night Gallery"')
112 >>> main()
113 Banner:http://thetvdb.com/banners/graphical/70382-g4.jpg,http://thetvdb.com/banners/graphical/1013-g.jpg,http://thetvdb.com/banners/blank/70382.jpg,http://thetvdb.com/banners/graphical/70382-g.jpg,http://thetvdb.com/banners/graphical/70382-g2.jpg,http://thetvdb.com/banners/graphical/70382-g3.jpg
114 0
115 
116 # http://www.thetvdb.com/banners/blank/70382.jpg
117 >>> sys.argv = shlex.split('ttvdb -Bl en Lost')
118 >>> main()
119 Banner:http://thetvdb.com/banners/graphical/73739-g4.jpg,http://thetvdb.com/banners/graphical/73739-g13.jpg,http://thetvdb.com/banners/graphical/73739-g18.jpg,http://thetvdb.com/banners/graphical/73739-g6.jpg,http://thetvdb.com/banners/graphical/73739-g12.jpg,http://thetvdb.com/banners/graphical/73739-g3.jpg,http://thetvdb.com/banners/graphical/24313-g2.jpg,http://thetvdb.com/banners/graphical/73739-g8.jpg,http://thetvdb.com/banners/graphical/73739-g.jpg,http://thetvdb.com/banners/graphical/73739-g5.jpg,http://thetvdb.com/banners/graphical/73739-g7.jpg,http://thetvdb.com/banners/graphical/73739-g10.jpg,http://thetvdb.com/banners/graphical/73739-g11.jpg,http://thetvdb.com/banners/graphical/24313-g.jpg,http://thetvdb.com/banners/graphical/73739-g2.jpg,http://thetvdb.com/banners/blank/73739.jpg
120 0
121 
122 # 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
123 > ttvdb -N --configure="/home/user/.tvdb/tvdb.conf" "Eleventh Hour" "H2O"
124 >>> sys.argv = shlex.split('ttvdb -N --configure=./tvdb_test.conf "Eleventh Hour" H2O')
125 >>> main()
126 <?xml version='1.0' encoding='UTF-8'?>
127 <metadata>
128  <item>
129  <title>Eleventh Hour (US)</title>
130  <subtitle>H2O</subtitle>
131  <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>
132  <season>1</season>
133  <episode>10</episode>
134  <certifications>
135  <certification locale="us" name="TV-14"/>
136  </certifications>
137  <categories>
138  <category type="genre" name="Drama"/>
139  <category type="genre" name="Science-Fiction"/>
140  <category type="genre" name="Thriller"/>
141  </categories>
142  <studios>
143  <studio name="CBS"/>
144  </studios>
145  <runtime>45</runtime>
146  <inetref>83066</inetref>
147  <collectionref>83066</collectionref>
148  <imdb>1118697</imdb>
149  <language>en</language>
150  <year>2009</year>
151  <releasedate>2009-01-15</releasedate>
152  <people>
153  <person job="Actor" name="Rufus Sewell" character="Jacob Hood" url="http://thetvdb.com/banners/actors/78899.jpg" thumb="http://thetvdb.com/banners/actors/78899.jpg"/>
154  <person job="Actor" name="Marley Shelton" character="Rachel Young" url="http://thetvdb.com/banners/actors/78898.jpg" thumb="http://thetvdb.com/banners/actors/78898.jpg"/>
155  <person job="Actor" name="Omar Benson Miller" character="Felix Lee" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
156  <person job="Actor" name="Chris Krauser" character="EMT" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
157  <person job="Actor" name="Erica Frene" character="Receptionist" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
158  <person job="Actor" name="Lei'lah Star" character="Sick Kid" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
159  <person job="Actor" name="Mark C. Baldwin" character="Infomercial Announcer" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
160  <person job="Director" name="McDonough"/>
161  <person job="Author" name="Kim Newton"/>
162  </people>
163  <images>
164  <image type="screenshot" url="http://thetvdb.com/banners/episodes/83066/416216.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/83066/416216.jpg"/>
165  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/83066-1-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/83066-1-2.jpg"/>
166  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/83066-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/83066-1.jpg"/>
167  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-1.jpg" width="1280" height="720"/>
168  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-3.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-3.jpg" width="1280" height="720"/>
169  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-5.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-5.jpg" width="1920" height="1080"/>
170  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-2.jpg" width="1280" height="720"/>
171  <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"/>
172  </images>
173  </item>
174 </metadata>
175 0
176 
177 # <language>en</language>
178 # <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"/>
179 # <image type="banner" url="http://www.thetvdb.com/banners/graphical/83066-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/83066-g.jpg"/>
180 (Return the season numbers for a series)
181 > ttvdb --configure="./tvdb_test.conf" -n "SG-1"
182 >>> sys.argv = shlex.split('ttvdb --configure=./tvdb_test.conf -n SG-1')
183 >>> main()
184 0,1,2,3,4,5,6,7,8,9,10
185 0
186 
187 (Return the meta data for a specific series/season/episode)
188 > ttvdb.py -D 80159 2 2
189 >>> sys.argv = shlex.split('ttvdb -D 80159 2 2')
190 >>> main()
191 <?xml version='1.0' encoding='UTF-8'?>
192 <metadata>
193  <item>
194  <title>Sanctuary</title>
195  <subtitle>End of Nights (2)</subtitle>
196  <description>Furious at being duped into a trap, Magnus takes on Kate, 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 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 and Henry 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>
197  <season>2</season>
198  <episode>2</episode>
199  <certifications>
200  <certification locale="us" name="TV-PG"/>
201  </certifications>
202  <categories>
203  <category type="genre" name="Action"/>
204  <category type="genre" name="Adventure"/>
205  <category type="genre" name="Crime"/>
206  <category type="genre" name="Mystery"/>
207  <category type="genre" name="Science-Fiction"/>
208  </categories>
209  <studios>
210  <studio name="Space"/>
211  </studios>
212  <runtime>60</runtime>
213  <inetref>80159</inetref>
214  <collectionref>80159</collectionref>
215  <imdb>0965394</imdb>
216  <tmsref>EP01085421</tmsref>
217  <language>en</language>
218  <year>2009</year>
219  <releasedate>2009-10-16</releasedate>
220  <people>
221  <person job="Actor" name="Amanda Tapping" character="Dr. Helen Magnus" url="http://thetvdb.com/banners/actors/73053.jpg" thumb="http://thetvdb.com/banners/actors/73053.jpg"/>
222  <person job="Actor" name="Robin Dunne" character="Will Zimmerman" url="http://thetvdb.com/banners/actors/73054.jpg" thumb="http://thetvdb.com/banners/actors/73054.jpg"/>
223  <person job="Actor" name="Emilie Ullerup" character="Ashley Magnus" url="http://thetvdb.com/banners/actors/73055.jpg" thumb="http://thetvdb.com/banners/actors/73055.jpg"/>
224  <person job="Actor" name="Christopher Heyerdahl" character="John Druitt" url="http://thetvdb.com/banners/actors/73056.jpg" thumb="http://thetvdb.com/banners/actors/73056.jpg"/>
225  <person job="Actor" name="Christopher Heyerdahl" character="Bigfoot" url="http://thetvdb.com/banners/actors/309797.jpg" thumb="http://thetvdb.com/banners/actors/309797.jpg"/>
226  <person job="Actor" name="Ryan Robbins" character="Henry Foss" url="http://thetvdb.com/banners/actors/80072.jpg" thumb="http://thetvdb.com/banners/actors/80072.jpg"/>
227  <person job="Actor" name="Agam Darshi" character="Kate Freelander" url="http://thetvdb.com/banners/actors/118211.jpg" thumb="http://thetvdb.com/banners/actors/118211.jpg"/>
228  <person job="Actor" name="Vincent Gale" character="Nigel Griffin" url="http://thetvdb.com/banners/actors/372548.jpg" thumb="http://thetvdb.com/banners/actors/372548.jpg"/>
229  <person job="Actor" name="Peter Wingfield" character="James Watson" url="http://thetvdb.com/banners/actors/372549.jpg" thumb="http://thetvdb.com/banners/actors/372549.jpg"/>
230  <person job="Actor" name="Jonathon Young" character="Nikola Tesla" url="http://thetvdb.com/banners/actors/372550.jpg" thumb="http://thetvdb.com/banners/actors/372550.jpg"/>
231  <person job="Actor" name="Ian Tracey" character="Adam Worth" url="http://thetvdb.com/banners/actors/372551.jpg" thumb="http://thetvdb.com/banners/actors/372551.jpg"/>
232  <person job="Actor" name="Jim Byrnes" character="Gregory Magnus" url="http://thetvdb.com/banners/actors/372552.jpg" thumb="http://thetvdb.com/banners/actors/372552.jpg"/>
233  <person job="Actor" name="Polly Walker" character="Ranna Seneschal" url="http://thetvdb.com/banners/actors/372553.jpg" thumb="http://thetvdb.com/banners/actors/372553.jpg"/>
234  <person job="Actor" name="Robert Lawrenson" character="Declan Macrae" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
235  <person job="Actor" name="Pascale Hutton" character="Abby Corrigan" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
236  <person job="Actor" name="Lynda Boyd" character="Dana Whitcomb" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
237  <person job="Actor" name="Shekhar Paleja" character="Ravi" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
238  <person job="Actor" name="Chuck Campbell" character="Two Faced Guy" url="http://thetvdb.com/banners/" thumb="http://thetvdb.com/banners/"/>
239  <person job="Guest Star" name="Jonathon Young"/>
240  <person job="Guest Star" name="Christine Chatelain"/>
241  <person job="Guest Star" name="Robert Lawrenson"/>
242  <person job="Guest Star" name="Maiko Yamamoto"/>
243  <person job="Guest Star" name="Stanley Tsang"/>
244  <person job="Guest Star" name="Darren A. Hebert"/>
245  <person job="Guest Star" name="Lynda Boyd"/>
246  <person job="Director" name="Martin Wood"/>
247  <person job="Author" name="Damian Kindler"/>
248  </people>
249  <images>
250  <image type="screenshot" url="http://thetvdb.com/banners/episodes/80159/998441.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/80159/998441.jpg"/>
251  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/80159-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/80159-2.jpg"/>
252  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/80159-2-3.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/80159-2-3.jpg"/>
253  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/80159-2-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/80159-2-2.jpg"/>
254  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-10.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-10.jpg" width="1280" height="720"/>
255  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-6.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-6.jpg" width="1920" height="1080"/>
256  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-3.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-3.jpg" width="1920" height="1080"/>
257  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-9.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-9.jpg" width="1920" height="1080"/>
258  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-7.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-7.jpg" width="1920" height="1080"/>
259  <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" width="1920" height="1080"/>
260  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-2.jpg" width="1920" height="1080"/>
261  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-4.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-4.jpg" width="1280" height="720"/>
262  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-5.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-5.jpg" width="1920" height="1080"/>
263  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-21.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-21.jpg" width="1920" height="1080"/>
264  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-16.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-16.jpg" width="1280" height="720"/>
265  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-1.jpg" width="1280" height="720"/>
266  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-15.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-15.jpg" width="1920" height="1080"/>
267  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-17.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-17.jpg" width="1920" height="1080"/>
268  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-18.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-18.jpg" width="1920" height="1080"/>
269  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-19.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-19.jpg" width="1920" height="1080"/>
270  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-20.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-20.jpg" width="1920" height="1080"/>
271  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-22.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-22.jpg" width="1920" height="1080"/>
272  </images>
273  </item>
274 </metadata>
275 0
276 
277 (Return a list of "thetv.com series id and series name" that contain specific search word(s) )
278 (!! Be careful with this option as poorly defined search words can result in large lists being returned !!)
279 > ttvdb.py -M "night a"
280 >>> sys.argv = shlex.split('ttvdb -M "night a"')
281 >>> main()
282 <?xml version='1.0' encoding='UTF-8'?>
283 <metadata>
284  <item>
285  <language>en</language>
286  <title>A Night of Numbers</title>
287  <inetref>249306</inetref>
288  <collectionref>249306</collectionref>
289  <description>BBC FOUR celebrates mathematics and the beauty of numbers with a series of programmes about this most precise and exacting of all intellectual disciplines. Throughout the night, the channel will show films that offer insights into the minds of great mathematicians, and reveal the stories behind some of the great mathematical breakthroughs.</description>
290  <releasedate>2005-12-06</releasedate>
291  <images>
292  <image type="banner" url="http://www.thetvdb.com/banners/graphical/249306-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/249306-g.jpg"/>
293  </images>
294  </item>
295  <item>
296  <language>en</language>
297  <title>A night at The Classic</title>
298  <inetref>224951</inetref>
299  <collectionref>224951</collectionref>
300  <description>Each episode of A Night at The Classic follows MC Brendhan Lovegrove and guest comedians as they perform for a different crowd on a different "night" at The Classic. Along with stand-up comedy recorded in front of a live audience, viewers are given a glimpse of what the comedians are like backstage, providing a rare insight into the rivalries and rituals of stand-up comedians.</description>
301  <releasedate>2010-11-03</releasedate>
302  <images>
303  <image type="banner" url="http://www.thetvdb.com/banners/graphical/224951-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/224951-g.jpg"/>
304  </images>
305  </item>
306  <item>
307  <language>en</language>
308  <title>A Night at the Rijksmuseum</title>
309  <inetref>268908</inetref>
310  <collectionref>268908</collectionref>
311  <releasedate>2013-04-18</releasedate>
312  </item>
313  <item>
314  <language>en</language>
315  <title>A Night of Heroes: The Sun Military Awards</title>
316  <inetref>270984</inetref>
317  <collectionref>270984</collectionref>
318  <description>Annual celebration, A Night of Heroes: Also known as The Millies, the awards recognize the excellence and sacrifice made by Britain's Armed Forces</description>
319  <images>
320  <image type="banner" url="http://www.thetvdb.com/banners/graphical/270984-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/270984-g.jpg"/>
321  </images>
322  </item>
323  <item>
324  <language>en</language>
325  <title>A Night of Exploration</title>
326  <inetref>271528</inetref>
327  <collectionref>271528</collectionref>
328  <description>For well over a century the National Geographic Society has been synonymous with pioneering expeditions, groundbreaking discoveries and breathtaking imagery of world cultures and exotic locations. In celebration of the iconic yellow border’s 125th anniversary, National Geographic Channel pays tribute to the hotshots, the mavericks and the best in their field who have devoted their lives to exploring the world around us and the groundbreaking discoveries that are making a difference.</description>
329  <images>
330  <image type="banner" url="http://www.thetvdb.com/banners/graphical/271528-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/271528-g.jpg"/>
331  </images>
332  </item>
333  <item>
334  <language>en</language>
335  <title>A Night at the Office</title>
336  <inetref>118511</inetref>
337  <collectionref>118511</collectionref>
338  <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, it was the entire first series of the seminal BBC comedy 'The Office' with new comments from the writers and celebrity fans shown between each episode.</description>
339  <releasedate>2009-08-17</releasedate>
340  <images>
341  <image type="banner" url="http://www.thetvdb.com/banners/graphical/118511-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/118511-g.jpg"/>
342  </images>
343  </item>
344  <item>
345  <language>en</language>
346  <title>A Night With The Stars</title>
347  <inetref>256045</inetref>
348  <collectionref>256045</collectionref>
349  <description>For one night only, Professor Brian Cox goes unplugged in a specially recorded programme from the lecture theatre of the Royal Institution of Great Britain. In his own inimitable style, Brian takes an audience of famous faces, scientists and members of the public on a journey through some of the most challenging concepts in physics. With the help of Jonathan Ross, Simon Pegg, Sarah Millican and James May, Brian shows how diamonds - the hardest material in nature - are made up of nothingness; how things can be in an infinite number of places at once; why everything we see or touch in the universe exists; and how a diamond in the heart of London is in communication with the largest diamond in the cosmos.</description>
350  <releasedate>2011-12-18</releasedate>
351  <images>
352  <image type="banner" url="http://www.thetvdb.com/banners/graphical/256045-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/256045-g.jpg"/>
353  </images>
354  </item>
355  <item>
356  <language>en</language>
357  <title>A Night at the Festival Club</title>
358  <inetref>268969</inetref>
359  <collectionref>268969</collectionref>
360  <description>A Night at the Festival Club is an Australian stand-up comedy television event created and executive produced by the Comedy Channel programming director Darren Chau, produced by Ted Robinson and GNW TV Productions for the Comedy Channel as part of the Melbourne International Comedy Festival. The series centres around bottling the unique comedic live performances and moments that occur late night in the Festival Club during the Melbourne International Comedy Festival.</description>
361  <releasedate>2008-05-02</releasedate>
362  </item>
363  <item>
364  <language>en</language>
365  <title>A Clear Midsummer Night</title>
366  <inetref>286538</inetref>
367  <collectionref>286538</collectionref>
368  <description>The daughter of a real estate mogul Xia Wan Qing, has seemingly no way of retreating after a friend's betrayal and her boyfriend backing out of their wedding. Fortunately, she's saved by business genius Qiao Jin Fan. Jin Fan is a "playboy" and the future successor for Qiao corporation. He extends an offering hand and together they embark on a path of revenge. Each for reasons of their own, begin a love with "uncertain motives." After enduring circumstances because of their families' competing interests and a number of conspiracies the two find true love.</description>
369  </item>
370  <item>
371  <language>en</language>
372  <title>A Christmas Night with the Stars</title>
373  <inetref>248911</inetref>
374  <collectionref>248911</collectionref>
375  <description>Christmas Night with the Stars was a television show broadcast each Christmas night by the BBC from 1958 to 1972 (with the exception of 1961, 1965 and 1966) and also revived in 1994. The show was hosted each year by a leading star of BBC TV and featured specially made short seasonal editions (typically about 10 minutes long) of the previous year's most popular BBC sitcoms and light entertainment programs. The show was voted 24th in the Channel 4 100 Greatest Christmas Moments. Most of the variety segments no longer exist.</description>
376  <releasedate>1958-12-25</releasedate>
377  <images>
378  <image type="banner" url="http://www.thetvdb.com/banners/graphical/248911-g2.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/248911-g2.jpg"/>
379  </images>
380  </item>
381  <item>
382  <language>en</language>
383  <title>A Night With My Ex</title>
384  <inetref>331751</inetref>
385  <collectionref>331751</collectionref>
386  <description>Do you have unfinished business with a partner from a previous relationship? All of the onetime couples featured on ``A Night With My Ex'' do, and the show is letting them tie up loose ends from the past. In each episode, a pair of exes spend a night together in a one-bedroom apartment complete with a multiple-camera setup. They are left to their own devices -- with no producers and no interruptions -- to try to hash things out. The participants get things off their chests, ask hard-hitting questions and face accusations of infidelity with the ultimate goal of achieving closure on the relationship. Sometimes that closure means a clean break, and other times it leads to renewing the spark and rekindling the romance. Regardless of the outcome, anything goes on the road to reaching that point as the couples confront their pasts -- and their futures.</description>
387  <releasedate>2017-07-18</releasedate>
388  </item>
389  <item>
390  <language>en</language>
391  <title>On a Lustful Night Mingling with a Priest...</title>
392  <inetref>325375</inetref>
393  <collectionref>325375</collectionref>
394  <description>The reunion of Kujo with his old female classmate. He has inherited his parents' temple and became a priest. However, after the two became drunk, he does something unexpected of him to her!</description>
395  <releasedate>2017-04-03</releasedate>
396  </item>
397  <item>
398  <language>en</language>
399  <title>Love on a Saturday Night</title>
400  <inetref>74382</inetref>
401  <collectionref>74382</collectionref>
402  <releasedate>2004-02-01</releasedate>
403  </item>
404  <item>
405  <language>en</language>
406  <title>Britain's Tudor Treasure A Night At Hampton Court</title>
407  <inetref>332440</inetref>
408  <collectionref>332440</collectionref>
409  <description>Lucy Worsley and David Starkey celebrate the 500th anniversary of Britain's finest surviving Tudor building, Hampton Court. As Henry VIII's pleasure palace, Hampton Court was a showcase for royal magnificence and ceremony - and the most important event of all was the christening of Henry's long-awaited son, Prince Edward, on October 15th, 1537. Lucy and David explore how Tudor art, architecture and ritual came together for this momentous occasion. Drawing on historical records and with the help of a team of experts, they recreate key elements of the christening ceremony - including a magnificent set-piece procession through Hampton Court involving nearly 100 people in full Tudor costume.</description>
410  </item>
411 </metadata>
412 0
413 
414 (Return TV series collection data of "thetv.com series id" for a specified language)
415 >>> sys.argv = shlex.split('ttvdb -l de -C 80159')
416 >>> main()
417 <?xml version='1.0' encoding='UTF-8'?>
418 <metadata>
419  <item>
420  <language>de</language>
421  <title>Sanctuary</title>
422  <network>Space</network>
423  <airday>Friday</airday>
424  <airtime>10:00 PM</airtime>
425  <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>
426  <certifications>
427  <certification locale="us" name="TV-PG"/>
428  </certifications>
429  <categories>
430  <category type="genre" name="Action"/>
431  <category type="genre" name="Adventure"/>
432  <category type="genre" name="Crime"/>
433  <category type="genre" name="Mystery"/>
434  <category type="genre" name="Science-Fiction"/>
435  </categories>
436  <studios>
437  <studio name="Space"/>
438  </studios>
439  <runtime>60</runtime>
440  <inetref>80159</inetref>
441  <imdb>0965394</imdb>
442  <userrating>8.1</userrating>
443  <ratingcount>168</ratingcount>
444  <year>2007</year>
445  <releasedate>2007-03-14</releasedate>
446  <lastupdated>...</lastupdated>
447  <status>Ended</status>
448  <images>
449  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/80159-11.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/80159-11.jpg"/>
450  <image type="banner" url="http://thetvdb.com/banners/graphical/80159-g4.jpg" thumb="http://thetvdb.com/banners/_cache/graphical/80159-g4.jpg"/>
451  </images>
452  </item>
453 </metadata>
454 0
455 
456 # test match is loose due ordering differences between py2 and 3
457 # i.e. dict key ordering
458 # key ordering is not sorted so that Title is first for existing client
459 # compatability
460 >>> sys.argv = shlex.split('ttvdb -l en -a US -D 281053')
461 >>> main()
462 Title:Fixer Upper
463 Season:0
464 Episode:1
465 Subtitle:The Waco Way of Life
466 Year:2014
467 ReleaseDate:2014-07-16
468 Director:
469 Plot:Chip and Joanna Gaines tell why they love raising a family in Waco, Texas.
470 UserRating:
471 Writers:
472 Screenshot:
473 Language:en
474 Airedseasonid:583817
475 Dvddiscid:
476 Id:5463514
477 Imdbid:
478 Lastupdated:...
479 Lastupdatedby:447800
480 Productioncode:
481 Seriesid:281053
482 Showurl:
483 Siterating:0
484 Siteratingcount:0
485 Thumbadded:
486 Thumbauthor:...
487 Cast:Chip Gaines, Joanna Gaines
488 Runtime:45
489 Title:Fixer Upper
490 ...
491 Coverart:http://thetvdb.com/banners/posters/281053-....jpg
492 Fanart:http://thetvdb.com/banners/fanart/original/281053-....jpg
493 Banner:http://thetvdb.com/banners/graphical/281053-....jpg
494 0
495 
496 >>> sys.argv = shlex.split('ttvdb.py -l en -a US -N 72108 Pyramid')
497 >>> main()
498 <?xml version='1.0' encoding='UTF-8'?>
499 <metadata>
500  <item>
501  <title>NCIS</title>
502  <subtitle>Pyramid</subtitle>
503  <description>The lives of NCIS members are in jeopardy when they come face-to-face with the infamous Port-to-Port killer, on the eighth season finale of NCIS.</description>
504  <season>8</season>
505  <episode>24</episode>
506  <certifications>
507  <certification locale="us" name="TV-14"/>
508  </certifications>
509  <categories>
510  <category type="genre" name="Action"/>
511  <category type="genre" name="Adventure"/>
512  <category type="genre" name="Crime"/>
513  <category type="genre" name="Drama"/>
514  </categories>
515  <studios>
516  <studio name="CBS"/>
517  </studios>
518  <runtime>45</runtime>
519  <inetref>72108</inetref>
520  <collectionref>72108</collectionref>
521  <tmsref>EP00681911</tmsref>
522  <imdb/>
523  <language>en</language>
524  <year>2011</year>
525  <releasedate>2011-05-17</releasedate>
526  <people>
527  <person job="Actor" name="Mark Harmon" character="Leroy Jethro Gibbs" url="http://thetvdb.com/banners/actors/70164.jpg" thumb="http://thetvdb.com/banners/actors/70164.jpg"/>
528  <person job="Actor" name="Sean Murray" character="Timothy McGee" url="http://thetvdb.com/banners/actors/70163.jpg" thumb="http://thetvdb.com/banners/actors/70163.jpg"/>
529  <person job="Actor" name="David McCallum" character="Donald &quot;Ducky&quot; Mallard" url="http://thetvdb.com/banners/actors/70159.jpg" thumb="http://thetvdb.com/banners/actors/70159.jpg"/>
530  <person job="Actor" name="Pauley Perrette" character="Abby Sciuto" url="http://thetvdb.com/banners/actors/70161.jpg" thumb="http://thetvdb.com/banners/actors/70161.jpg"/>
531  <person job="Actor" name="Rocky Carroll" character="Leon Vance" url="http://thetvdb.com/banners/actors/127861.jpg" thumb="http://thetvdb.com/banners/actors/127861.jpg"/>
532  <person job="Actor" name="Brian Dietzen" character="Jimmy Palmer" url="http://thetvdb.com/banners/actors/219761.jpg" thumb="http://thetvdb.com/banners/actors/219761.jpg"/>
533  <person job="Actor" name="Wilmer Valderrama" character="Nicholas &quot;Nick&quot; Torres" url="http://thetvdb.com/banners/actors/394895.jpg" thumb="http://thetvdb.com/banners/actors/394895.jpg"/>
534  <person job="Actor" name="Michael Weatherly" character="Anthony &quot;Tony&quot; DiNozzo" url="http://thetvdb.com/banners/actors/70160.jpg" thumb="http://thetvdb.com/banners/actors/70160.jpg"/>
535  <person job="Actor" name="Sasha Alexander" character="Caitlin &quot;Kate&quot; Todd" url="http://thetvdb.com/banners/actors/70162.jpg" thumb="http://thetvdb.com/banners/actors/70162.jpg"/>
536  <person job="Actor" name="Cote de Pablo" character="Ziva David" url="http://thetvdb.com/banners/actors/70165.jpg" thumb="http://thetvdb.com/banners/actors/70165.jpg"/>
537  <person job="Actor" name="Lauren Holly" character="Jennifer &quot;Jenny&quot; Shepard" url="http://thetvdb.com/banners/actors/77850.jpg" thumb="http://thetvdb.com/banners/actors/77850.jpg"/>
538  <person job="Actor" name="Jennifer Esposito" character="Alex Quinn" url="http://thetvdb.com/banners/actors/402126.jpg" thumb="http://thetvdb.com/banners/actors/402126.jpg"/>
539  <person job="Actor" name="Emily Wickersham" character="Ellie Bishop" url="http://thetvdb.com/banners/actors/321201.jpg" thumb="http://thetvdb.com/banners/actors/321201.jpg"/>
540  <person job="Actor" name="Duane Henry" character="Clayton Reeves" url="http://thetvdb.com/banners/actors/418530.jpg" thumb="http://thetvdb.com/banners/actors/418530.jpg"/>
541  <person job="Actor" name="Maria Bello" character="Jacqueline “Jack” Sloane" url="http://thetvdb.com/banners/actors/461645.jpg" thumb="http://thetvdb.com/banners/actors/461645.jpg"/>
542  <person job="Guest Star" name="Kerr Smith"/>
543  <person job="Guest Star" name="Sarah Jane Morris"/>
544  <person job="Guest Star" name="Matt Craven"/>
545  <person job="Guest Star" name="David Dayan Fisher"/>
546  <person job="Guest Star" name="Muse Watson"/>
547  <person job="Guest Star" name="Alimi Ballard"/>
548  <person job="Guest Star" name="Matthew Willig"/>
549  <person job="Guest Star" name="Tehmina Sunny"/>
550  <person job="Guest Star" name="Enrique Murciano"/>
551  <person job="Guest Star" name="Jude Ciccolella"/>
552  <person job="Guest Star" name="Vera Miao"/>
553  <person job="Director" name="Dennis Smith"/>
554  <person job="Author" name="Gary Glasberg"/>
555  </people>
556  <images>
557  <image type="screenshot" url="http://thetvdb.com/banners/episodes/72108/4078484.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/72108/4078484.jpg"/>
558  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72108-8-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72108-8-2.jpg"/>
559  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72108-8.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72108-8.jpg"/>
560  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/72108-8-7.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/72108-8-7.jpg"/>
561  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72108-20.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72108-20.jpg" width="1280" height="720"/>
562  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72108-28.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72108-28.jpg" width="1920" height="1080"/>
563  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72108-29.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72108-29.jpg" width="1920" height="1080"/>
564  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72108-31.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72108-31.jpg" width="1920" height="1080"/>
565 ...
566  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/72108-33.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/72108-33.jpg" width="1920" height="1080"/>
567  </images>
568  </item>
569 </metadata>
570 0
571 
572 >>> sys.argv = shlex.split('ttvdb.py -l en -a US -N 283661 "Egg Hunt"')
573 >>> main()
574 <?xml version='1.0' encoding='UTF-8'?>
575 <metadata>
576  <item>
577  <title>Jack Hanna's Wild Countdown</title>
578  <subtitle>Egg Hunt</subtitle>
579  <description>Jungle Jack takes off on a very special Egg Hunt, looking for creatures big and small that hatch from eggs! Crocodiles, Bald Eagles, Sea Turtles, Ostriches, Penguins, and more!</description>
580  <season>6</season>
581  <episode>17</episode>
582  <certifications>
583  <certification locale="us" name="TV-G"/>
584  </certifications>
585  <categories>
586  <category type="genre" name="Family"/>
587  <category type="genre" name="Special Interest"/>
588  </categories>
589  <studios>
590  <studio name="ABC (US)"/>
591  </studios>
592  <runtime>30</runtime>
593  <inetref>283661</inetref>
594  <collectionref>283661</collectionref>
595  <imdb>3062384</imdb>
596  <tmsref>EP01441760</tmsref>
597  <language>en</language>
598  <year>2017</year>
599  <releasedate>2017-04-15</releasedate>
600  <images>
601  <image type="screenshot" url="http://thetvdb.com/banners/episodes/283661/6050716.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/283661/6050716.jpg"/>
602  <image type="coverart" url="http://www.thetvdb.com/banners/posters/283661-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/posters/283661-1.jpg" width="680" height="1000"/>
603  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/283661-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/283661-1.jpg" width="1920" height="1080"/>
604  </images>
605  </item>
606 </metadata>
607 0
608 
609 >>> sys.argv = shlex.split('ttvdb.py -l en -a US -N 76119 "Eclipse Over America"')
610 >>> main()
611 <?xml version='1.0' encoding='UTF-8'?>
612 <metadata>
613  <item>
614  <title>NOVA</title>
615  <subtitle>Eclipse Over America</subtitle>
616  <description>...</description>
617  <season>44</season>
618  <episode>11</episode>
619  <certifications>
620  <certification locale="us" name="TV-PG"/>
621  </certifications>
622  <categories>
623  <category type="genre" name="Documentary"/>
624  </categories>
625  <studios>
626  <studio name="PBS"/>
627  </studios>
628  <runtime>60</runtime>
629  <inetref>76119</inetref>
630  <collectionref>76119</collectionref>
631  <imdb>0206501</imdb>
632  <tmsref>EP00003163</tmsref>
633  <language>en</language>
634  <year>2017</year>
635  <releasedate>2017-08-21</releasedate>
636  <images>
637  <image type="screenshot" url="http://thetvdb.com/banners/episodes/76119/....jpg" thumb="http://thetvdb.com/banners/_cache/episodes/76119/....jpg"/>
638  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/76119-44.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/76119-44.jpg"/>
639  <image type="coverart" url="http://www.thetvdb.com/banners/seasons/76119-44-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/seasons/76119-44-2.jpg"/>
640  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/76119-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/76119-1.jpg" width="1920" height="1080"/>
641  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/76119-2.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/76119-2.jpg" width="1920" height="1080"/>
642  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/76119-3.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/76119-3.jpg" width="1280" height="720"/>
643  </images>
644  </item>
645 </metadata>
646 0
647 
648 >>> sys.argv = shlex.split('ttvdb.py -l en -a GB -C 330432')
649 >>> main()
650 <?xml version='1.0' encoding='UTF-8'?>
651 <metadata>
652  <item>
653  <language>en</language>
654  <title>Nine Minute Ninja</title>
655  <network>CBBC</network>
656  <description>Art Ninja spinoff. Ricky makes art in nine minutes.</description>
657  <categories>
658  <category type="genre" name="Children"/>
659  </categories>
660  <studios>
661  <studio name="CBBC"/>
662  </studios>
663  <runtime>10</runtime>
664  <inetref>330432</inetref>
665  <ratingcount>0</ratingcount>
666  <year>2015</year>
667  <releasedate>2015-06-24</releasedate>
668  <lastupdated>...</lastupdated>
669  <status>Continuing</status>
670  <images>
671  <image type="coverart" url="http://www.thetvdb.com/banners/posters/330432-1.jpg" thumb="http://www.thetvdb.com/banners/_cache/posters/330432-1.jpg"/>
672  </images>
673  </item>
674 </metadata>
675 0
676 
677 >>> sys.argv = shlex.split('ttvdb.py -l de -a DE -D 282022 1 1')
678 >>> main()
679 <?xml version='1.0' encoding='UTF-8'?>
680 <metadata>
681  <item>
682  <title>Wilder Westen</title>
683  <subtitle>Gebirge</subtitle>
684  <description>Der Wilde Westen. ...</description>
685  <season>1</season>
686  <episode>1</episode>
687  <categories>
688  <category type="genre" name="Documentary"/>
689  </categories>
690  <studios>
691  <studio name="BBC Four"/>
692  </studios>
693  <runtime>60</runtime>
694  <inetref>282022</inetref>
695  <collectionref>282022</collectionref>
696  <language>de</language>
697  <year>2014</year>
698  <releasedate>2014-05-22</releasedate>
699  <people>
700  <person job="Actor" name="Ray Mears" character="Presenter" url="http://thetvdb.com/banners/actors/330506.jpg" thumb="http://thetvdb.com/banners/actors/330506.jpg"/>
701  <person job="Director" name="Phil Coles"/>
702  </people>
703  <images>
704  <image type="screenshot" url="http://thetvdb.com/banners/episodes/282022/4890147.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/282022/4890147.jpg"/>
705  </images>
706  </item>
707 </metadata>
708 0
709 
710 >>> sys.argv = shlex.split('ttvdb.py -l de -a DE -D 282022')
711 >>> main()
712 Title:Wilder Westen
713 Season:1
714 Episode:1
715 Subtitle:Gebirge
716 Year:2014
717 ReleaseDate:2014-05-22
718 Director:Phil Coles
719 Plot:Der Wilde Westen. Die Vorstellung ...
720 UserRating:
721 Writers:
722 Screenshot:http://thetvdb.com/banners/episodes/282022/4890147.jpg
723 Language:de
724 Absolutenumber:1
725 Airedseasonid:586419
726 Dvddiscid:
727 Id:4890147
728 Imdbid:
729 ...
730 Productioncode:b044jl70
731 Seriesid:282022
732 Showurl:
733 Siterating:0
734 Siteratingcount:0
735 ...
736 Cast:Ray Mears
737 Runtime:60
738 Title:Wilder Westen
739 Season:1
740 Episode:2
741 Subtitle:Great Plains
742 Year:2014
743 ReleaseDate:2014-05-29
744 Director:Phil Coles
745 Plot:Die Great Plains,...
746 UserRating:
747 Writers:
748 Screenshot:http://thetvdb.com/banners/episodes/282022/4890148.jpg
749 Language:de
750 Absolutenumber:2
751 Airedseasonid:586419
752 Dvddiscid:
753 Id:4890148
754 Imdbid:
755 ...
756 Productioncode:b044z1k0
757 Seriesid:282022
758 Showurl:
759 Siterating:0
760 Siteratingcount:0
761 ...
762 Cast:Ray Mears
763 Runtime:60
764 Title:Wilder Westen
765 Season:1
766 Episode:3
767 Subtitle:W...
768 Year:2014
769 ReleaseDate:2014-06-05
770 Director:
771 Plot:
772 UserRating:
773 Writers:
774 Screenshot:http://thetvdb.com/banners/episodes/282022/4890149.jpg
775 Language:de
776 Absolutenumber:3
777 Airedseasonid:586419
778 Dvddiscid:
779 Id:4890149
780 Imdbid:
781 ...
782 Productioncode:b045nz9q
783 Seriesid:282022
784 Showurl:
785 Siterating:0
786 Siteratingcount:0
787 ...
788 Cast:Ray Mears
789 Runtime:60
790 0
791 
792 >>> sys.argv = shlex.split('ttvdb.py -l de -a DE -N 78804 10 1')
793 >>> main()
794 <?xml version='1.0' encoding='UTF-8'?>
795 <metadata>
796  <item>
797  <title>Doctor Who (2005)</title>
798  <subtitle>42</subtitle>
799  <description>Der Doktor und Martha Jones betätigen sich als interplanetare Verkehrswacht - und eilen der Besatzung eines Raumfrachters zu Hilfe, der steuerlos auf einen Stern zutreibt. Den beiden bleiben nur 42 Minuten, bevor das Schiff in der Hitze der Sonne verglüht. Als wäre dieses Zeitlimit noch nicht schlimm genug, bekommen sie es auch mit einer mysteriösen Macht zu tun, die den Crewmitgliedern nach dem Leben trachtet...</description>
800  <season>3</season>
801  <episode>7</episode>
802  <certifications>
803  <certification locale="us" name="TV-PG"/>
804  </certifications>
805  <categories>
806  <category type="genre" name="Adventure"/>
807  <category type="genre" name="Drama"/>
808  <category type="genre" name="Science-Fiction"/>
809  </categories>
810  <studios>
811  <studio name="BBC One"/>
812  </studios>
813  <runtime>45</runtime>
814  <inetref>78804</inetref>
815  <collectionref>78804</collectionref>
816  <imdb>0436992</imdb>
817  <tmsref>EP00750178</tmsref>
818  <language>de</language>
819  <year>2007</year>
820  <releasedate>2007-05-19</releasedate>
821  <people>
822  <person job="Actor" name="Jodie Whitaker" character="Thirteenth Doctor" url="http://thetvdb.com/banners/actors/451936.jpg" thumb="http://thetvdb.com/banners/actors/451936.jpg"/>
823  <person job="Actor" name="Peter Capaldi" character="Twelfth Doctor" url="http://thetvdb.com/banners/actors/333133.jpg" thumb="http://thetvdb.com/banners/actors/333133.jpg"/>
824  <person job="Actor" name="Matt Smith" character="Eleventh Doctor" url="http://thetvdb.com/banners/actors/129621.jpg" thumb="http://thetvdb.com/banners/actors/129621.jpg"/>
825  <person job="Actor" name="David Tennant" character="Tenth Doctor" url="http://thetvdb.com/banners/actors/67922.jpg" thumb="http://thetvdb.com/banners/actors/67922.jpg"/>
826  <person job="Actor" name="Billie Piper" character="Rose Tyler" url="http://thetvdb.com/banners/actors/67923.jpg" thumb="http://thetvdb.com/banners/actors/67923.jpg"/>
827  <person job="Actor" name="Freema Agyeman" character="Martha Jones" url="http://thetvdb.com/banners/actors/67924.jpg" thumb="http://thetvdb.com/banners/actors/67924.jpg"/>
828  <person job="Actor" name="John Barrowman" character="Jack Harkness" url="http://thetvdb.com/banners/actors/67925.jpg" thumb="http://thetvdb.com/banners/actors/67925.jpg"/>
829  <person job="Actor" name="Dan Starkey" character="Strax" url="http://thetvdb.com/banners/actors/343990.jpg" thumb="http://thetvdb.com/banners/actors/343990.jpg"/>
830  <person job="Actor" name="Catrin Stewart" character="Jenny Flint" url="http://thetvdb.com/banners/actors/343989.jpg" thumb="http://thetvdb.com/banners/actors/343989.jpg"/>
831  <person job="Actor" name="Neve McIntosh" character="Madame Vastra" url="http://thetvdb.com/banners/actors/343988.jpg" thumb="http://thetvdb.com/banners/actors/343988.jpg"/>
832  <person job="Actor" name="Christopher Eccleston" character="Ninth Doctor" url="http://thetvdb.com/banners/actors/77724.jpg" thumb="http://thetvdb.com/banners/actors/77724.jpg"/>
833  <person job="Actor" name="Catherine Tate" character="Donna Noble" url="http://thetvdb.com/banners/actors/77751.jpg" thumb="http://thetvdb.com/banners/actors/77751.jpg"/>
834  <person job="Actor" name="John Simm" character="The Master" url="http://thetvdb.com/banners/actors/79737.jpg" thumb="http://thetvdb.com/banners/actors/79737.jpg"/>
835  <person job="Actor" name="Karen Gillan" character="Amy Pond" url="http://thetvdb.com/banners/actors/129611.jpg" thumb="http://thetvdb.com/banners/actors/129611.jpg"/>
836  <person job="Actor" name="Arthur Darvill" character="Rory Williams" url="http://thetvdb.com/banners/actors/200011.jpg" thumb="http://thetvdb.com/banners/actors/200011.jpg"/>
837  <person job="Actor" name="Alex Kingston" character="River Song" url="http://thetvdb.com/banners/actors/286535.jpg" thumb="http://thetvdb.com/banners/actors/286535.jpg"/>
838  <person job="Actor" name="Jenna Coleman" character="Clara Oswald" url="http://thetvdb.com/banners/actors/305854.jpg" thumb="http://thetvdb.com/banners/actors/305854.jpg"/>
839  <person job="Actor" name="Bernard Cribbins" character="Wilfred Mott" url="http://thetvdb.com/banners/actors/308282.jpg" thumb="http://thetvdb.com/banners/actors/308282.jpg"/>
840  <person job="Actor" name="Camille Coduri" character="Jackie Tyler" url="http://thetvdb.com/banners/actors/337609.jpg" thumb="http://thetvdb.com/banners/actors/337609.jpg"/>
841  <person job="Actor" name="Noel Clarke" character="Mickey Smith" url="http://thetvdb.com/banners/actors/337610.jpg" thumb="http://thetvdb.com/banners/actors/337610.jpg"/>
842  <person job="Actor" name="Samuel Anderson" character="Danny Pink" url="http://thetvdb.com/banners/actors/339332.jpg" thumb="http://thetvdb.com/banners/actors/339332.jpg"/>
843  <person job="Actor" name="Michelle Gomez" character="Missy" url="http://thetvdb.com/banners/actors/356014.jpg" thumb="http://thetvdb.com/banners/actors/356014.jpg"/>
844  <person job="Actor" name="Jemma Redgrave" character="Kate Stewart" url="http://thetvdb.com/banners/actors/385486.jpg" thumb="http://thetvdb.com/banners/actors/385486.jpg"/>
845  <person job="Actor" name="Matt Lucas" character="Nardole" url="http://thetvdb.com/banners/actors/418731.jpg" thumb="http://thetvdb.com/banners/actors/418731.jpg"/>
846  <person job="Actor" name="Pearl Mackie" character="Bill Potts" url="http://thetvdb.com/banners/actors/418730.jpg" thumb="http://thetvdb.com/banners/actors/418730.jpg"/>
847  <person job="Guest Star" name="Rebecca Oldfield"/>
848  <person job="Guest Star" name="Gary Powell"/>
849  <person job="Guest Star" name="Vinette Robinson"/>
850  <person job="Guest Star" name="Matthew Chambers"/>
851  <person job="Guest Star" name="William Ash"/>
852  <person job="Guest Star" name="Anthony Flanagan"/>
853  <person job="Guest Star" name="Michelle Collins"/>
854  <person job="Guest Star" name="Adjoa Andoh"/>
855  <person job="Guest Star" name="Elize Du Toit"/>
856  <person job="Director" name="Graeme Harper"/>
857  <person job="Author" name="Chris Chibnall"/>
858  </people>
859  <images>
860  <image type="screenshot" url="http://thetvdb.com/banners/episodes/78804/327376.jpg" thumb="http://thetvdb.com/banners/_cache/episodes/78804/327376.jpg"/>
861  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-54.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-54.jpg" width="1280" height="720"/>
862  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-61.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-61.jpg" width="1280" height="720"/>
863  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-155.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-155.jpg" width="1920" height="1080"/>
864  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-122.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-122.jpg" width="1920" height="1080"/>
865  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-60.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-60.jpg" width="1920" height="1080"/>
866  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-93.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-93.jpg" width="1920" height="1080"/>
867  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-59.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-59.jpg" width="1920" height="1080"/>
868  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-64.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-64.jpg" width="1280" height="720"/>
869  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-148.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-148.jpg" width="1280" height="720"/>
870  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-13.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-13.jpg" width="1920" height="1080"/>
871  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-125.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-125.jpg" width="1920" height="1080"/>
872  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-73.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-73.jpg" width="1920" height="1080"/>
873  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-85.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-85.jpg" width="1920" height="1080"/>
874  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-115.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-115.jpg" width="1920" height="1080"/>
875  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-113.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-113.jpg" width="1280" height="720"/>
876  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-143.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-143.jpg" width="1920" height="1080"/>
877  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-116.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-116.jpg" width="1920" height="1080"/>
878  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-62.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-62.jpg" width="1280" height="720"/>
879  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-114.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-114.jpg" width="1280" height="720"/>
880  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-117.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-117.jpg" width="1920" height="1080"/>
881  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-144.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-144.jpg" width="1920" height="1080"/>
882  <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/78804-63.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/78804-63.jpg" width="1280" height="720"/>
883  </images>
884  </item>
885 </metadata>
886 0
887 
888 """
889 from __future__ import print_function
890 
891 __title__ ="TheTVDB.com";
892 __author__="R.D.Vaughan"
893 __version__="2.0.0"
894 
895 # Version .1 Initial development
896 # Version .2 Add an option to get season and episode numbers from ep name
897 # Version .3 Cleaned up the documentation and added a usage display option
898 # Version .4 Added override formating of the number option (-N)
899 # Version .5 Added a check that tvdb_api.py dependancies are installed
900 # Version .6 Added -M Series List functionality
901 # Version .7 Added -o Series name override file functionality
902 # Version .8 Removed a dependancy, fixed caching for multiple users and
903 # used better method of supporting the -M option with tvdb_api
904 # Version .8.1 Cleaned up documentation errors.
905 # Version .8.2 Added the series name to output of meta data -D
906 # Version .8.3 Patched tv_api so even episode image is fully qualified URL
907 # Version .8.4 Fixed bug in -N when multiple episodes are returned from
908 # a search on episode name.
909 # Version .8.5 Made option -N more flexible in finding a matching episode
910 # Version .8.6 Add option -t for top rated graphics (-P,B,F) for a series
911 # Add option -l to filter graphics on the local language
912 # Add season level granularity to Poster and Banner URLs
913 # Changed the override file location to be specified on the
914 # command line along with the option -o.
915 # Increased the amount of massaging of episode data to improve
916 # compatiblilty with data bases.
917 # Changed the default season episode number format to SxxExx
918 # Add an option (-n) to return the season numbers for a series
919 # Added passing either a thetvdb.com SID (series identifcation
920 # number) or series name for all functions except -M list.
921 # Now ALL available episode meta data is returned. This
922 # includes meta data that MythTv DB does not currently store.
923 # Meta data 'Year' now derived from year in release date.
924 # Version .8.7 Fixed a bug with unicode meta data handling
925 # Version .8.8 Replaced the old configuration file with a true conf file
926 # Version .8.9 Add option -m to better conform to mythvideo standards
927 # Version .9.0 Now when a season level Banner is not found then the
928 # top rated series level graphics is returned instead. This
929 # feature had previously been available for posters only.
930 # Add runtime to episode meta data (-D). It is always the
931 # same for each episode as the information is only available
932 # from the series itself.
933 # Added the TV Series cast members as part of episode
934 # meta data. Option (-D).
935 # Added TV Series series genres as part of episode
936 # meta data. Option (-D).
937 # Resync with tvdb_api getting bug fixes and new features.
938 # Add episode data download to a specific language see
939 # -l 'es' option. If there is no data for a languages episode
940 # then any engish episode data is returned. English is default.
941 # The -M is still only in English. Waiting on tvdb_api fix.
942 # Version .9.1 Bug fix stdio abort when no genre exists for TV series
943 # Version .9.2 Bug fix stdio abort when no cast exists for TV series
944 # Version .9.3 Changed option -N when episodes partially match each
945 # combination of season/episode numbers are returned. This was
946 # added to deal with episodes which have a "(1)" trailing
947 # the episode name. An episode in more than one part.
948 # Version .9.4 Option -M can now list multi-language TV Series
949 # Version .9.5 "Director" metadata field returned as "None" is changed to
950 # "Unknown".
951 # File name parsing was changed to use multi-language capable
952 # regex patterns
953 # Version .9.6 Synced up to the 1.0 release of tvdb_api
954 # Added a tvdb_api version check and abort if not at least v1.0
955 # Changed to new tvdb_api's method of assigning the tvdb api key
956 # Version .9.7 Account for TVDB increasing the number of digits in their
957 # SID number (now greater then 5
958 # e,g, "Defying Gravity" is SID 104581)
959 # Version .9.8 Added a (-S) option for requesting a thetvdb
960 # episode screen shot
961 # Version .9.9 Fixed the -S option when NO episode image exists
962 # Version 1.0. Removed LF and replace with a space for all TVDB metatdata
963 # fields
964 # Version 1.0.1 Return all graphics (series and season) in the order
965 # highest to lowest as rated by users
966 # Version 1.0.2 Added better error messages to config file checking. Updated to
967 # v1.0.2 tvdb_api which contained fixes for concurrent instances
968 # of ttvdb.py generated by MythVideo.
969 # Version 1.0.3 Conform to new -D standards which return all graphics URLs along
970 # with text meta data. Also Posters, Banners and Fan art have one or
971 # comma separated URLs as one continuous string.
972 # Version 1.0.4 Poster Should be Coverart instead.
973 # Version 1.0.5 Added the TVDB URL to the episode metadata
974 # Version 1.0.6 When the language is invalid ignore language and continue processing
975 # Changed all exit codes to be 0 for passed and 1 for failed
976 # Removed duplicates when using the -M option and the -l option
977 # Version 1.0.7 Change over to the installed TTVDB api library
978 # Fixed cast name bad data abort reported in ticket #7957
979 # Fixed ticket #7900 - There is now fall back graphics when a
980 # language is specified and there is no images for that language.
981 # New default location and name of the ttvdb.conf file is '~/.mythtv/ttvdb.conf'. The command
982 # line option "-c" can still override the default location and name.
983 # Version 1.0.8 Removed any stderr messages for non-critical events. They cause dual pop-ups in MythVideo.
984 # Version 1.0.9 Removed another stderr messages when a non-supported language code is passed.
985 # Version 1.1.0 Added support for XML output. See:
986 # http://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
987 # Version 1.1.1 Make XML output the default.
988 # Version 1.1.2 Convert version information to XML
989 # Version 1.1.3 Implement fuzzy matching for episode name lookup
990 # Version 1.1.4 Add test mode (replaces --toprated)
991 # Version 1.1.5 Add the -C (collection option) with corresponding XML output
992 # and add a <collectionref> XML tag to Search and Query XML output
993 # Version 1.1.6 Honor series name overrides during TV series search
994 # Version 2.0.0 Update to API V2
995 
996 usage_txt='''
997 Usage: ttvdb.py usage: ttvdb -hdruviomMPFBDSC [parameters]
998  <series name or 'series and season number' or 'series and season number and episode number'>
999 
1000 For details on using ttvdb with Mythvideo see the ttvdb wiki page at:
1001 http://www.mythtv.org/wiki/Ttvdb.py
1002 
1003 Options:
1004  -h, --help show this help message and exit
1005  -d, --debug Show debugging info
1006  -r, --raw Dump raw data only
1007  -u, --usage Display examples for executing the ttvdb script
1008  -v, --version Display version and author
1009  -t Test mode, to check for installed dependencies
1010  -i, --interactive Interaction mode (allows selection of a specific
1011  Series)
1012  -c FILE, --configure=FILE
1013  Use configuration settings
1014  -a COUNTRY, --area=COUNTRY
1015  Select data that matches the specified country
1016  -l LANGUAGE, --language=LANGUAGE
1017  Select data that matches the specified language fall
1018  back to english if nothing found (e.g. 'es' Español,
1019  'de' Deutsch ... etc)
1020  -n, --num_seasons Return the season numbers for a series
1021  -m, --mythvideo Conform to mythvideo standards when processing -M, -P,
1022  -F and -D
1023  -M, --list Get matching TV Series list
1024  -P, --poster Get Series Poster URL(s)
1025  -F, --fanart Get Series fan art URL(s)
1026  -B, --backdrop Get Series banner/backdrop URL(s)
1027  -S, --screenshot Get Series episode screenshot URL
1028  -D, --data Get Series episode data
1029  -N, --numbers Get Season and Episode numbers
1030  -C, --collection Get A TV Series (collection) series specific information
1031 
1032 Command examples:
1033 (Return the banner graphics for a series)
1034 > ttvdb -B "Sanctuary"
1035 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
1036 
1037 (Return the banner graphics for a Series specific to a season)
1038 > ttvdb -B "SG-1" 1
1039 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
1040 
1041 (Return the screen shot graphic for a Series Episode)
1042 > ttvdb -S "SG-1" 1 10
1043 http://www.thetvdb.com/banners/episodes/72449/85759.jpg
1044 
1045 (Return the banner graphics for a SID (series ID) specific to a season)
1046 (SID "72449" is specific for the series "SG-1")
1047 > ttvdb -B 72449 1
1048 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
1049 
1050 (Return the banner graphics for a file name)
1051 > ttvdb -B "Stargate SG-1 - S08E03 - Lockdown"
1052 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
1053 
1054 (Return the posters, banners and fan art for a series)
1055 > ttvdb -PFB "Sanctuary"
1056 Coverart:http://www.thetvdb.com/banners/posters/80159-2.jpg,http://www.thetvdb.com/banners/posters/80159-1.jpg
1057 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
1058 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
1059 
1060 (Return thetvdb.com's top rated poster, banner and fan art for a TV Series)
1061 (NOTE: If there is no graphic for a type or any graphics at all then those types are not returned)
1062 > ttvdb -tPFB "Stargate SG-1"
1063 Coverart:http://www.thetvdb.com/banners/posters/72449-1.jpg
1064 Fanart:http://www.thetvdb.com/banners/fanart/original/72449-1.jpg
1065 Banner:http://www.thetvdb.com/banners/graphical/185-g3.jpg
1066 > ttvdb -tB "Night Gallery"
1067 http://www.thetvdb.com/banners/blank/70382.jpg
1068 
1069 (Return graphics only matching the local language for a TV series)
1070 (In this case banner 73739-g9.jpg is not included because it does not match the language 'en')
1071 > ttvdb -Bl en "Lost"
1072 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
1073 
1074 (Return a season and episode numbers using the override file to identify the series as the US version)
1075 > ttvdb -N --configure="/home/user/.tvdb/tvdb.conf" "Eleventh Hour" "H2O"
1076 <?xml version='1.0' encoding='UTF-8'?>
1077 <metadata>
1078  <item>
1079  <title>Eleventh Hour (US)</title>
1080  <subtitle>H2O</subtitle>
1081  <language>en</language>
1082  <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>
1083  <season>1</season>
1084  <episode>10</episode>
1085 ...
1086  <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"/>
1087  <image type="banner" url="http://www.thetvdb.com/banners/graphical/83066-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/83066-g.jpg"/>
1088  </images>
1089  </item>
1090 </metadata>
1091 
1092 (Return the season numbers for a series)
1093 > ttvdb --configure="/home/user/.tvdb/tvdb.conf" -n "SG-1"
1094 0,1,2,3,4,5,6,7,8,9,10
1095 
1096 (Return the meta data for a specific series/season/episode)
1097 > ttvdb.py -D 80159 2 2
1098 <?xml version='1.0' encoding='UTF-8'?>
1099 <metadata>
1100  <item>
1101  <title>Sanctuary</title>
1102  <subtitle>End of Nights (2)</subtitle>
1103  <language>en</language>
1104  <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>
1105  <season>2</season>
1106  <episode>2</episode>
1107  <certifications>
1108  <certification locale="us" name="TV-PG"/>
1109  </certifications>
1110  <categories>
1111  <category type="genre" name="Action and Adventure"/>
1112  <category type="genre" name="Science-Fiction"/>
1113  </categories>
1114  <studios>
1115  <studio name="SciFi"/>
1116  </studios>
1117 ...
1118  <image type="banner" url="http://www.thetvdb.com/banners/graphical/80159-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/80159-g.jpg"/>
1119  </images>
1120  </item>
1121 </metadata>
1122 
1123 (Return a list of "thetv.com series id and series name" that contain specific search word(s) )
1124 (!! Be careful with this option as poorly defined search words can result in large lists being returned !!)
1125 > ttvdb.py -M "night a"
1126 <?xml version='1.0' encoding='UTF-8'?>
1127 <metadata>
1128  <item>
1129  <language>en</language>
1130  <title>Love on a Saturday Night</title>
1131  <inetref>74382</inetref>
1132  <releasedate>2004-02-01</releasedate>
1133  </item>
1134  <item>
1135  <language>en</language>
1136  <title>A Night on Mount Edna</title>
1137  <inetref>108281</inetref>
1138  </item>
1139  <item>
1140  <language>en</language>
1141  <title>A Night at the Office</title>
1142  <inetref>118511</inetref>
1143  <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>
1144  <releasedate>2009-01-01</releasedate>
1145  <images>
1146  <image type="banner" url="http://www.thetvdb.com/banners/graphical/118511-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/118511-g.jpg"/>
1147  </images>
1148  </item>
1149  <item>
1150  <language>en</language>
1151  <title>Star For A Night</title>
1152  <inetref>71476</inetref>
1153  <releasedate>1999-01-01</releasedate>
1154  </item>
1155 </metadata>
1156 
1157 (Return TV series collection data of "thetv.com series id" for a specified language)
1158 > ttvdb.py -l de -C 80159
1159 <?xml version='1.0' encoding='UTF-8'?>
1160 <metadata>
1161  <item>
1162  <language>de</language>
1163  <title>Sanctuary</title>
1164  <network>Syfy</network>
1165  <airday>Friday</airday>
1166  <airtime>22:00</airtime>
1167  <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>
1168  <certifications>
1169  <certification locale="us" name="TV-PG"/>
1170  </certifications>
1171  <categories>
1172  <category type="genre" name="Action and Adventure"/>
1173  <category type="genre" name="Science-Fiction"/>
1174  </categories>
1175  <studios>
1176  <studio name="Syfy"/>
1177  </studios>
1178  <runtime>60</runtime>
1179  <inetref>80159</inetref>
1180  <imdb>0965394</imdb>
1181  <tmsref>EP01085421</tmsref>
1182  <userrating>8.0</userrating>
1183  <ratingcount>128</ratingcount>
1184  <year>2007</year>
1185  <releasedate>2007-05-14</releasedate>
1186  <lastupdated>Fri, 17 Feb 2012 16:57:02 GMT</lastupdated>
1187  <status>Continuing</status>
1188  <images>
1189  <image type="coverart" url="http://www.thetvdb.com/banners/posters/80159-4.jpg" thumb="http://www.thetvdb.com/banners/_cache/posters/80159-4.jpg"/>
1190  <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"/>
1191  <image type="banner" url="http://www.thetvdb.com/banners/graphical/80159-g6.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/80159-g6.jpg"/>
1192  </images>
1193  </item>
1194 </metadata>
1195 '''
1196 
1197 # Episode keys that can be used in a episode data/information search.
1198 # All keys are currently being used.
1199 '''
1200 'episodenumber'
1201 'rating'
1202 'overview'
1203 'dvd_episodenumber'
1204 'dvd_discid'
1205 'combined_episodenumber'
1206 'epimgflag'
1207 'id'
1208 'seasonid'
1209 'seasonnumber'
1210 'writer'
1211 'lastupdated'
1212 'filename'
1213 'absolute_number'
1214 'combined_season'
1215 'imdb_id'
1216 'director'
1217 'dvd_chapter'
1218 'dvd_season'
1219 'gueststars'
1220 'seriesid'
1221 'language'
1222 'productioncode'
1223 'firstaired'
1224 'episodename'
1225 '''
1226 
1227 # System modules
1228 import sys, os, re
1229 from optparse import OptionParser
1230 from copy import deepcopy
1231 # shlex for doctest
1232 import shlex
1233 
1234 # import logging
1235 # logger = logging.getLogger()
1236 # ch = logging.StreamHandler()
1237 # fh = logging.FileHandler("ttvdb.log")
1238 # #ch.setLevel(logging.DEBUG)
1239 # fh.setLevel(logging.DEBUG)
1240 # logging.getLogger("dicttoxml").setLevel(logging.WARN)
1241 # logging.getLogger("tvdb_api").setLevel(logging.DEBUG)
1242 # logger.addHandler(ch)
1243 # logger.addHandler(fh)
1244 
1245 IS_PY2 = sys.version_info[0] == 2
1246 if IS_PY2:
1247  import ConfigParser
1248 else:
1249  import configparser as ConfigParser
1250 
1252  # explicit username and account id are not required
1253  # to use the API. API documentation is unclear in this regard
1254  username = ""
1255  account_identifier = ""
1256  apikey = '0BB856A59C51D607'
1257 
1258 # Verify that tvdb_api.py are available
1259 try:
1260  # thetvdb.com specific modules
1261  import MythTV.ttvdb.tvdb_api as tvdb_api
1262  from MythTV.ttvdb.tvdb_api import (tvdb_error, tvdb_shownotfound, tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_episodenotfound, tvdb_attributenotfound, tvdb_userabort)
1263 
1264  # verify version of tvdbapi to make sure it is at least 1.0
1265  if tvdb_api.__version__ < '2.0':
1266  print("\nYour current installed tvdb_api.py version is (%s)\n" % tvdb_api.__version__)
1267  raise
1268 except Exception as e:
1269  print('''
1270 The modules tvdb_api.py (v2.0 or greater).
1271 They should have been installed along with the MythTV python bindings.
1272 Error:(%s)
1273 ''' % e)
1274  sys.exit(1)
1275 finally:
1276  pass
1277 
1278 try:
1279  from MythTV.utility import levenshtein
1280 except Exception as e:
1281  print("""Could not import levenshtein string distance method from MythTV Python Bindings
1282 Error:(%s)
1283 """ % e)
1284  sys.exit(1)
1285 
1286 try:
1287  if IS_PY2:
1288  from StringIO import StringIO
1289  else:
1290  from io import StringIO
1291  from lxml import etree as etree
1292 except Exception as e:
1293  sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
1294  sys.exit(1)
1295 
1296 from MythTV.utility.dicttoxml import dicttoxml
1297 try:
1298  import json
1299  from lxml import etree as eTree
1300 except Exception as e:
1301  sys.stderr.write(u'\n! Error - Importing the "lxml" python library failed on error(%s)\n' % e)
1302  sys.exit(1)
1303 
1304 if IS_PY2:
1305  stdio_type = file
1306 else:
1307  import io
1308  stdio_type = io.TextIOWrapper
1309  unicode = str
1310 
1311 # disable the insecure request warning that we know we are going to get
1312 import urllib3
1313 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
1314 
1315 # Check that the lxml library is current enough
1316 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
1317 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
1318 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
1319 version = ''
1320 for digit in etree.LIBXML_VERSION:
1321  version+=str(digit)+'.'
1322 version = version[:-1]
1323 if version < '2.7.2':
1324  sys.stderr.write(u'''
1325 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
1326  At least "libxml" version 2.7.2 must be installed. Your version is (%s).
1327 ''' % version)
1328  sys.exit(1)
1329 
1330 
1331 # Global variables
1332 http_find="http://www.thetvdb.com"
1333 http_replace="http://www.thetvdb.com" #Keep replace code "just in case"
1334 
1335 name_parse=[
1336  # foo_[s01]_[e01]
1337  re.compile('''^(.+?)[ \._\-]\[[Ss]([0-9]+?)\]_\[[Ee]([0-9]+?)\]?[^\\/]*$'''),
1338  # foo.1x09*
1339  re.compile('''^(.+?)[ \._\-]\[?([0-9]+)x([0-9]+)[^\\/]*$'''),
1340  # foo.s01.e01, foo.s01_e01
1341  re.compile('''^(.+?)[ \._\-][Ss]([0-9]+)[\.\- ]?[Ee]([0-9]+)[^\\/]*$'''),
1342  # foo.103*
1343  re.compile('''^(.+)[ \._\-]([0-9]{1})([0-9]{2})[\._ -][^\\/]*$'''),
1344  # foo.0103*
1345  re.compile('''^(.+)[ \._\-]([0-9]{2})([0-9]{2,3})[\._ -][^\\/]*$'''),
1346 ] # contains regex parsing filename parsing strings used to extract info from video filenames
1347 
1348 # Episode meta data that is massaged
1349 massage={'writer':'|','director':'|', 'overview':'&', 'gueststars':'|' }
1350 # Keys and titles used for episode data (option '-D')
1351 data_keys =['airedSeason','airedEpisodeNumber','episodeName','firstAired','directors','overview','rating','writers','filename','language' ]
1352 data_titles=['Season:','Episode:','Subtitle:','ReleaseDate:','Director:','Plot:','UserRating:','Writers:','Screenshot:','Language:' ]
1353 # High level dictionay keys for select graphics URL(s)
1354 fanart_key='fanart'
1355 banner_key='series'
1356 poster_key='poster'
1357 season_key='season'
1358 # Lower level dictionay keys for select graphics URL(s)
1359 poster_series_key='680x1000'
1360 poster_season_key='season'
1361 fanart_hires_key='1920x1080'
1362 fanart_lowres_key='1280x720'
1363 banner_series_key='graphical'
1364 banner_season_key='seasonwide'
1365 # Type of graphics being requested
1366 poster_type='Poster'
1367 fanart_type='Fanart'
1368 banner_type='Banner'
1369 screenshot_request = False
1370 
1371 # Cache directory name specific to the user. This avoids permission denied error with a common cache dirs
1372 confdir = os.environ.get('MYTHCONFDIR', '')
1373 if (not confdir) or (confdir == '/'):
1374  confdir = os.environ.get('HOME', '')
1375  if (not confdir) or (confdir == '/'):
1376  print("Unable to find MythTV directory for metadata cache.")
1377  sys.exit(1)
1378  confdir = os.path.join(confdir, '.mythtv')
1379 # different cache dirs due to different pickle protocols
1380 # TODO massage pickle so python3 generates python2 compatible pickles
1381 if IS_PY2:
1382  cache_dir=os.path.join(confdir, "cache/tvdb_api/")
1383 else:
1384  cache_dir=os.path.join(confdir, "cache/tvdb_api3/")
1385 if not os.path.exists(cache_dir):
1386  os.makedirs(cache_dir)
1387 
1388 def _can_int(x):
1389  """Takes a string, checks if it is numeric.
1390  >>> _can_int("2")
1391  True
1392  >>> _can_int("A test")
1393  False
1394  """
1395  try:
1396  int(x)
1397  except ValueError:
1398  return False
1399  else:
1400  return True
1401 # end _can_int
1402 
1403 class OutStreamEncoder(object):
1404  """Wraps a stream with an encoder"""
1405  def __init__(self, outstream, encoding=None):
1406  self.out = outstream
1407  if not encoding:
1408  self.encoding = sys.getfilesystemencoding()
1409  else:
1410  self.encoding = encoding
1411 
1412  def write(self, obj):
1413  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
1414  if isinstance(obj, unicode):
1415  obj = obj.encode(self.encoding)
1416  if IS_PY2:
1417  self.out.write(obj)
1418  else:
1419  self.out.buffer.write(obj)
1420 
1421  def __getattr__(self, attr):
1422  """Delegate everything but write to the stream"""
1423  return getattr(self.out, attr)
1424 if isinstance(sys.stdout, stdio_type):
1425  sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
1426  sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
1427 
1428 # modified Show class implementing a fuzzy search
1429 class Show( tvdb_api.Show ):
1430  def fuzzysearch(self, term = None, key = None):
1431  results = []
1432  for cur_season in self.values():
1433  searchresult = cur_season.fuzzysearch(term = term, key = key)
1434  if len(searchresult) != 0:
1435  results.extend(searchresult)
1436  return results
1437 # end Show
1438 
1439 # modified Season class implementing a fuzzy search
1440 class Season( tvdb_api.Season ):
1441  def fuzzysearch(self, term = None, key = None):
1442  results = []
1443  for episode in self.values():
1444  searchresult = episode.fuzzysearch(term = term, key = key)
1445  if searchresult is not None:
1446  results.append(searchresult)
1447  return results
1448 # end Season
1449 
1450 # modified Episode class implementing a fuzzy search
1451 class Episode( tvdb_api.Episode ):
1452  _re_strippart = re.compile('(.*) \([0-9]+\)')
1453  def fuzzysearch(self, term = None, key = None):
1454  if term == None:
1455  raise TypeError("must supply string to search for (contents)")
1456 
1457  term = unicode(term).lower()
1458  for cur_key, cur_value in self.items():
1459  cur_key, cur_value = [unicode(a).lower() for a in [cur_key, cur_value]]
1460  if key is not None and cur_key != key:
1461  continue
1462  distance = levenshtein(cur_value, term)
1463  if distance <= 3:
1464  # handle most matches
1465  self.distance = distance
1466  return self
1467  if distance <= 5:
1468  # handle part numbers, 'subtitle (nn)'
1469  match = self._re_strippart.match(cur_value)
1470  if match:
1471  tmp = match.group(1)
1472  if levenshtein(tmp, term) <= 3:
1473  self.distance = distance
1474  return self
1475  return None
1476 #end Episode
1477 
1478 # modified Tvdb API class using modified show classes
1479 class Tvdb( tvdb_api.Tvdb ):
1480  def series_by_sid(self, sid, language):
1481  "Lookup a series via it's sid"
1482  seriesid = 'sid:' + sid
1483  sid = int(sid)
1484  if not seriesid in self.corrections:
1485  self._getShowData(sid, language=language)
1486  self.corrections[seriesid] = sid
1487  return self.shows[sid]
1488  #end series_by_sid
1489 
1490  # override the existing method, using modified show classes
1491  def _setItem(self, sid, seas, ep, attrib, value):
1492  if sid not in self.shows:
1493  self.shows[sid] = Show()
1494  if seas not in self.shows[sid]:
1495  self.shows[sid][seas] = Season()
1496  if ep not in self.shows[sid][seas]:
1497  self.shows[sid][seas][ep] = Episode()
1498  self.shows[sid][seas][ep][attrib] = value
1499  #end _setItem
1500 
1501  # override the existing method, using modified show class
1502  def _setShowData(self, sid, key, value):
1503  if sid not in self.shows:
1504  self.shows[sid] = Show()
1505  self.shows[sid].data[key] = value
1506  #end _setShowData
1507 #end Tvdb
1508 
1509 # Search for a series by SID or Series name
1510 def search_for_series(tvdb, sid_or_name, language):
1511  "Get series data by sid or series name of the Tv show"
1512  if SID == True:
1513  return tvdb.series_by_sid(sid_or_name, language)
1514  else:
1515  return tvdb[sid_or_name]
1516 # end search_for_series
1517 
1518 # Verify that a Series or Series and Season exists on thetvdb.com
1519 def searchseries(t, opts, series_season_ep):
1520  global SID
1521  series_name=''
1522  key = series_season_ep[0].lower()
1523  if opts.configure != "" and key in override:
1524  if override[key][2]:
1525  series_name=override[key][2] # Override series name
1526  SID = True
1527  else:
1528  series_name=override[key][0] # Override series name
1529  else:
1530  series_name=series_season_ep[0] # Leave the series name alone
1531  try:
1532  # Search for the series or series & season or series & season & episode
1533  series_data = search_for_series(t, series_name, opts.language)
1534  if len(series_season_ep)>1:
1535  if len(series_season_ep)>2: # series & season & episode
1536  seriesfound=series_data[ int(series_season_ep[1]) ][ int(series_season_ep[2]) ]
1537  else:
1538  seriesfound=series_data[ int(series_season_ep[1]) ] # series & season
1539  else:
1540  seriesfound=series_data # Series only
1541  except tvdb_shownotfound:
1542  # No such show found.
1543  # Use the show-name from the files name, and None as the ep name
1544  sys.exit(0)
1545  except (tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound):
1546  # The season, episode or name wasn't found, but the show was.
1547  # Use the corrected show-name, but no episode name.
1548  sys.exit(0)
1549  except tvdb_error as errormsg:
1550  # Error communicating with thetvdb.com
1551  if SID == True: # Maybe the digits were a series name (e.g. 90210)
1552  SID = False
1553  return searchseries(t, opts, series_season_ep)
1554  sys.exit(0)
1555  except tvdb_userabort as errormsg:
1556  # User aborted selection (q or ^c)
1557  print("\n", errormsg)
1558  sys.exit(0)
1559  else:
1560  if opts.raw==True:
1561  print("="*20)
1562  print("Raw Series Data:\n")
1563  if len(series_season_ep)>1:
1564  print(t[ series_name ][ int(series_season_ep[1]) ])
1565  else:
1566  print(t[ series_name ])
1567  print("="*20)
1568  return(seriesfound)
1569 # end searchseries
1570 
1571 # Retrieve Poster or Fan Art or Banner graphics URL(s)
1572 def get_graphics(t, opts, series_season_ep, graphics_type, single_option, language=False):
1573  banners='_banners'
1574  series_name=''
1575  graphics=[]
1576  key = series_season_ep[0].lower()
1577  if opts.configure != "" and key in override:
1578  series_name=override[key][0] # Override series name
1579  else:
1580  series_name=series_season_ep[0] # Leave the series name alone
1581 
1582  if SID == True:
1583  t._parseBanners(series_name)
1584  else:
1585  t._parseBanners(t._nameToSid(series_name))
1586  bid_order = {'fanart': [], 'poster': [], 'series': [], 'season': [], 'seasonwide': []}
1587  URLs = {'fanart': [], 'poster': [], 'series': [], 'season': [], 'seasonwide': []}
1588 
1589  # get the urls in presented order
1590  for key in t.shows.keys():
1591  banner = t.shows[key].data['_banners']
1592  for graphic_type_items in bid_order.keys():
1593  if graphic_type_items in banner:
1594  for graphic_item in banner[graphic_type_items]['raw']:
1595  url = banner[graphic_type_items][graphic_item['resolution']][graphic_item['id']]
1596  url['rating'] = graphic_item['ratingsInfo']['average']
1597  URLs[graphic_type_items].append(url)
1598 
1599  if graphics_type == fanart_type: # Series fanart graphics
1600  if not len(URLs[u'fanart']):
1601  return []
1602  for url in URLs[u'fanart']:
1603  graphics.append(url)
1604  elif len(series_season_ep) == 1:
1605  if not len(URLs[u'series']):
1606  return []
1607  if graphics_type == banner_type: # Series Banners
1608  for url in URLs[u'series']:
1609  graphics.append(url)
1610  else: # Series Posters
1611  for url in URLs[u'poster']:
1612  graphics.append(url)
1613  else:
1614  if not len(URLs[u'season']):
1615  return []
1616  if graphics_type == banner_type: # Season Banners
1617  season_banners=[]
1618  # seasonwide has blank resolution
1619  for url in URLs[u'seasonwide']:
1620  if url[u'resolution'] == u'' and url[u'subKey'] == series_season_ep[1]:
1621  season_banners.append(url)
1622  if not len(season_banners):
1623  return []
1624  graphics = season_banners
1625  else: # Season Posters
1626  # season has blank resolution
1627  season_posters=[]
1628  for url in URLs[u'season']:
1629  if url[u'resolution'] == u'' and url[u'subKey'] == series_season_ep[1]:
1630  season_posters.append(url)
1631  if not len(season_posters):
1632  return []
1633  graphics = season_posters
1634 
1635  graphicsURLs=[]
1636  if single_option==False:
1637  graphicsURLs.append(graphics_type+':')
1638 
1639  count = 0
1640  wasanythingadded = 0
1641  anyotherlanguagegraphics=[]
1642  englishlanguagegraphics=[]
1643  graphics = sorted(graphics, key=lambda k: k['rating'], reverse=True)
1644  for URL in graphics:
1645  if graphics_type == 'filename':
1646  if URL[graphics_type] == None:
1647  continue
1648  if language and 'language' in URL: # Is there a language to filter URLs on?
1649  if language == URL['language']:
1650  graphicsURLs.append((URL['_bannerpath']).replace(http_find, http_replace))
1651  else: # Check for fall back graphics in case there are no selected language graphics
1652  if u'en' == URL['language']:
1653  englishlanguagegraphics.append((URL['_bannerpath']).replace(http_find, http_replace))
1654  else:
1655  anyotherlanguagegraphics.append((URL['_bannerpath']).replace(http_find, http_replace))
1656  else:
1657  graphicsURLs.append((URL['_bannerpath']).replace(http_find, http_replace))
1658  if wasanythingadded == len(graphicsURLs):
1659  continue
1660  wasanythingadded = len(graphicsURLs)
1661 
1662  if not len(graphicsURLs):
1663  if len(englishlanguagegraphics): # Fall back to English graphics
1664  graphicsURLs = englishlanguagegraphics
1665  elif len(anyotherlanguagegraphics): # Fall back-back to any available graphics
1666  graphicsURLs = anyotherlanguagegraphics
1667 
1668  if opts.debug == True:
1669  print(u"\nGraphics:\n", graphicsURLs)
1670 
1671  if len(graphicsURLs) == 1 and graphicsURLs[0] == graphics_type+':':
1672  return [] # Due to the language filter there may not be any URLs
1673  return(graphicsURLs)
1674 # end get_graphics
1675 
1676 # Massage episode name to match those in thetvdb.com for this series
1677 def massageEpisode_name(ep_name, series_season_ep):
1678  for edit in override[series_season_ep[0].lower()][1]:
1679  ep_name=ep_name.replace(edit[0],edit[1]) # Edit episode name for each set of strings
1680  return ep_name
1681 # end massageEpisode_name
1682 
1683 # Remove '|' and replace with commas
1684 def change_to_commas(meta_data):
1685  if not meta_data: return meta_data
1686  meta_data = (u'|'.join([d for d in meta_data.split('| ') if d]))
1687  return (u', '.join([d for d in meta_data.split('|') if d]))
1688 # end change_to_commas
1689 
1690 # Change &amp; values to ascii equivalents
1691 def change_amp(text):
1692  if not text: return text
1693  try:
1694  text = text.replace("&quot;", "'").replace("\r\n", " ")
1695  text = text.replace(r"\'", "'")
1696  except Exception as e:
1697  pass
1698  return text
1699 # end change_amp
1700 
1701 # Prepare for includion into a DB
1702 def make_db_ready(text):
1703  if not text: return text
1704  text = text.replace(u'\u2013', "-")
1705  text = text.replace(u'\u2014', "-")
1706  text = text.replace(u'\u2018', "'")
1707  text = text.replace(u'\u2019', "'")
1708  text = text.replace(u'\u2026', "...")
1709  text = text.replace(u'\u201c', '"')
1710  text = text.replace(u'\u201d', '"')
1711  text = text.encode('latin-1', 'backslashreplace')
1712  return text
1713 # end make_db_ready
1714 
1715 # Get Series Episode data by season
1716 def Getseries_episode_data(t, opts, series_season_ep, language = None):
1717  global screenshot_request, http_find, http_replace
1718 
1719  args = len(series_season_ep)
1720  series_name=''
1721  key = series_season_ep[0].lower()
1722  if opts.configure != "" and key in override:
1723  series_name=override[key][0] # Override series name
1724  else:
1725  series_name=series_season_ep[0] # Leave the series name alone
1726 
1727  # Get Cast members
1728  cast_members=''
1729  tmp_cast = ''
1730  try:
1731  series_data = search_for_series(t, series_name, opts.language)
1732  tmp_cast = series_data['_actors']
1733  tmp_cast = sorted(tmp_cast, key=lambda k: k['sortOrder'])
1734  except:
1735  cast_members=''
1736  if len(tmp_cast):
1737  cast_members=''.encode('utf8')
1738  for cast in tmp_cast:
1739  if cast['name']:
1740  cast_members+=(cast['name']+', ').encode('utf8')
1741  if cast_members != '':
1742  try:
1743  cast_members = cast_members[:-2].encode('utf8')
1744  except (UnicodeDecodeError, AttributeError):
1745  cast_members = unicode(cast_members[:-2],'utf8')
1746  cast_members = change_amp(cast_members)
1747  cast_members = change_to_commas(cast_members)
1748  cast_members=cast_members.replace('\n',' ')
1749 
1750  # Get genre(s)
1751  genres=''
1752  try:
1753  genres_string = series_data[u'genre'].encode('utf-8')
1754  except:
1755  genres_string=''
1756  if genres_string != None and genres_string != '':
1757  genres = change_amp(genres_string)
1758  genres = change_to_commas(genres)
1759 
1760  seasons=sorted(series_data.keys()) # Get the seasons for this series
1761  for season in seasons:
1762  if args > 1: # If a season was specified skip other seasons
1763  if season != int(series_season_ep[1]):
1764  continue
1765  episodes=sorted(series_data[season].keys()) # Get the episodes for this season
1766  for episode in episodes: # If an episode was specified skip other episodes
1767  if args > 2:
1768  if episode != int(series_season_ep[2]):
1769  continue
1770  # get more detailed episode info
1771  t.getDetailedEpisodeInfo(int(series_data['id']), season, episode)
1772  extra_ep_data=[]
1773  available_keys=series_data[season][episode].keys()
1774  if screenshot_request:
1775  if u'filename' in available_keys:
1776  screenshot = series_data[season][episode][u'filename']
1777  if screenshot:
1778  print(screenshot.replace(http_find, http_replace))
1779  return
1780  else:
1781  return
1782  # key ordering is not sorted so that Title is first for existing client
1783  # compatability
1784  key_values=[]
1785  for values in data_keys: # Initialize an array for each possible data element for
1786  key_values.append('') # each episode within a season
1787  for key in sorted(available_keys):
1788  try:
1789  # skip deprecated keys
1790  if key in ['director']:
1791  continue
1792  i = data_keys.index(key) # Include only specific episode data
1793  except ValueError:
1794  if series_data[season][episode][key] != None:
1795  text = series_data[season][episode][key]
1796  if isinstance(text, dict):
1797  # handle language tuple
1798  text = list(text.values())[0]
1799  elif isinstance(text, list):
1800  # handle guest stars lists
1801  text = ', '.join(text)
1802  text = change_amp(unicode(text))
1803  text = change_to_commas(text)
1804  if text == 'None' and key.title() == 'Director':
1805  text = u"Unknown"
1806  try:
1807  extra_ep_data.append(u"%s:%s" % (key.title(), text))
1808  except UnicodeDecodeError:
1809  extra_ep_data.append(u"%s:%s" % (key.title(), unicode(text, "utf8")))
1810  continue
1811  text = series_data[season][episode][key]
1812 
1813  if text == None and key.title() == 'Director':
1814  text = u"Unknown"
1815  if isinstance(text, list):
1816  text = ', '.join(text)
1817  if text == None or text == 'None':
1818  continue
1819  else:
1820  # handle language tuple
1821  if isinstance(text, dict):
1822  # handle language tuple
1823  text = list(text.values())[0]
1824  text = change_amp(unicode(text))
1825  value = change_to_commas(text)
1826  value = value.replace(u'\n', u' ')
1827  key_values[i]=value
1828  index = 0
1829  if SID == False:
1830  print(u"Title:%s" % series_name) # Ouput the full series name
1831  else:
1832  print(u"Title:%s" % series_data[u'seriesname'])
1833 
1834  for key in data_titles:
1835  if key_values[index] != None:
1836  if data_titles[index] == u'ReleaseDate:' and len(key_values[index]) > 4:
1837  print(u'%s%s'% (u'Year:', key_values[index][:4]))
1838  if key_values[index] != 'None':
1839  print(u'%s%s' % (data_titles[index], key_values[index]))
1840  index+=1
1841  cast_print=False
1842  for extra_data in extra_ep_data:
1843  if extra_data[:extra_data.index(':')] == u'Gueststars':
1844  extra_cast = extra_data[extra_data.index(':')+1:]
1845  if not extra_cast:
1846  continue
1847  if (len(extra_cast)>128) and not extra_cast.count(','):
1848  continue
1849  if cast_members:
1850  extra_data=(u"Cast:%s" % cast_members)+', '+extra_cast
1851  else:
1852  extra_data=u"Cast:%s" % extra_cast
1853  cast_print=True
1854  print(extra_data)
1855  if cast_print == False:
1856  print(u"Cast:%s" % cast_members)
1857  if genres != '':
1858  print(u"Genres:%s" % genres)
1859  print(u"Runtime:%s" % series_data[u'runtime'])
1860 
1861  # URL to TVDB web site episode web page for this series
1862  for url_data in [u'seriesid', u'seasonid', u'id']:
1863  if not url_data in available_keys:
1864  break
1865  else:
1866  results = series_data
1867  print(u'URL:http://www.thetvdb.com/?tab=episode&seriesid=%s&seasonid=%s&id=%s' %
1868  (results[season][episode][u'seriesid'],
1869  results[season][episode][u'seasonid'],
1870  results[season][episode][u'id']))
1871 # end Getseries_episode_data
1872 
1873 # Get Series Season and Episode numbers
1874 def Getseries_episode_numbers(t, opts, series_season_ep):
1875  def _episode_sort(episode):
1876  seasonnumber = 0
1877  episodenumber = 0
1878  try: seasonnumber = int(episode['seasonnumber'])
1879  except: pass
1880  try: episodenumber = int(episode['episodenumber'])
1881  except: pass
1882  return (-episode.distance, seasonnumber, episodenumber)
1883 
1884  global xmlFlag
1885  series_name=''
1886  ep_name=''
1887  series_sid = None
1888  key = series_season_ep[0].lower()
1889  if opts.configure != "" and key in override:
1890  if override[key][2]:
1891  series_sid=override[key][2] # Override series name
1892  SID = True
1893  else:
1894  series_name=override[key][0] # Override series name
1895  ep_name=series_season_ep[1]
1896  if len(override[series_season_ep[0].lower()][1]) != 0: # Are there search-replace strings?
1897  ep_name=massageEpisode_name(ep_name, series_season_ep)
1898  else:
1899  series_name=series_season_ep[0] # Leave the series name alone
1900  ep_name=series_season_ep[1] # Leave the episode name alone
1901 
1902  if series_sid:
1903  series = search_for_series(t, series_sid, opts.language)
1904  else:
1905  series = search_for_series(t, series_name, opts.language)
1906  season_ep_num = series.fuzzysearch(ep_name, 'episodename')
1907  if len(season_ep_num) != 0:
1908  for episode in sorted(season_ep_num, key=lambda ep: _episode_sort(ep), reverse=True):
1909 # if episode.distance == 0: # exact match
1910  if xmlFlag:
1911  # get more detailed episode info
1912  t.getDetailedEpisodeInfo(series['id'], episode['airedSeason'], episode)
1913  convert_series_to_xml(t, series_season_ep, season_ep_num)
1914  displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
1915  return 0
1916  print(season_and_episode_num.replace('\\n', '\n') %
1917  (int(episode['seasonnumber']), int(episode['episodenumber'])))
1918 # elif (episode['episodename'].lower()).startswith(ep_name.lower()):
1919 # if len(episode['episodename']) > (len(ep_name)+1):
1920 # if episode['episodename'][len(ep_name):len(ep_name)+2] != ' (':
1921 # continue # Skip episodes the are not part of a set of (1), (2) ... etc
1922 # if xmlFlag:
1923 # displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
1924 # sys.exit(0)
1925 # print(season_and_episode_num.replace('\\n', '\n') % (int(episode['seasonnumber']), int(episode['episodenumber'])))
1926 # end Getseries_episode_numbers
1927 
1928 # Set up a custom interface to get all series matching a partial series name
1929 class returnAllSeriesUI(tvdb_api.BaseUI):
1930  def __init__(self, config, log=None):
1931  self.config = config
1932  self.log = log
1933 
1934  def selectSeries(self, allSeries):
1935  return allSeries
1936 # ends returnAllSeriesUI
1937 
1938 def initialize_override_dictionary(useroptions, language):
1939  """ Change variables through a user supplied configuration file
1940  return False and exit the script if there are issues with the configuration file values
1941  """
1942  if useroptions[0]=='~':
1943  useroptions=os.path.expanduser("~")+useroptions[1:]
1944  if os.path.isfile(useroptions) == False:
1945  sys.stderr.write(
1946  "! The specified user configuration file (%s) is not a file\n" % useroptions
1947  )
1948  sys.exit(1)
1949  massage = {}
1950  overrides = {}
1951  overrides_id = {}
1952  cfg = ConfigParser.SafeConfigParser()
1953  cfg.read(useroptions)
1954 
1955  for section in cfg.sections():
1956  if section == 'regex':
1957  # Change variables per user config file
1958  for option in cfg.options(section):
1959  name_parse.append(re.compile(cfg.get(section, option)))
1960  continue
1961  if section =='ep_name_massage':
1962  for option in cfg.options(section):
1963  tmp =cfg.get(section, option).split(',')
1964  if len(tmp)%2 and len(cfg.get(section, option)) != 0:
1965  sys.stderr.write("! For (%s) 'ep_name_massage' values must be in pairs\n" % option)
1966  sys.exit(1)
1967  tmp_array=[]
1968  i=0
1969  while i != len(tmp):
1970  tmp_array.append([tmp[i].replace('"',''), tmp[i+1].replace('"','')])
1971  i+=2
1972  massage[option]=tmp_array
1973  continue
1974  if section =='series_name_override':
1975  for option in cfg.options(section):
1976  overrides[option] = cfg.get(section, option)
1977  tvdb = Tvdb(banners=False,
1978  debug = False,
1979  interactive = False,
1980  cache = cache_dir,
1981  custom_ui=returnAllSeriesUI,
1982  apikey=tvdb_account.apikey, # thetvdb.com API key requested by MythTV
1983  username=tvdb_account.username,
1984  userkey=tvdb_account.account_identifier)
1985  tvdb.session.verify = False
1986  for key in overrides.keys():
1987  sid = overrides[key]
1988  if len(sid) == 0:
1989  continue
1990  try: # Check that the SID (Series id) is numeric
1991  dummy = int(sid)
1992  except:
1993  sys.stdout.write("! Series (%s) Invalid SID (not numeric) [%s] in config file\n" % (key, sid))
1994  sys.exit(1)
1995  # Make sure that the series name is not empty or all blanks
1996  if len(key.replace(' ','')) == 0:
1997  sys.stdout.write("! Invalid Series name (must have some non-blank characters) [%s] in config file\n" % key)
1998  print(overrides.keys())
1999  sys.exit(1)
2000 
2001  try:
2002  series_name_sid=tvdb.series_by_sid(sid, language)
2003  except:
2004  sys.stdout.write("! Invalid Series (no matches found in thetvdb,com) (%s) sid (%s) in config file\n" % (key, sid))
2005  sys.exit(1)
2006  overrides[key]=unicode(series_name_sid.data[u'seriesName']) #.encode('utf-8')
2007  overrides_id[key] = sid
2008  continue
2009 
2010  for key in overrides.keys():
2011  override[key] = [overrides[key],[],overrides_id[key]]
2012 
2013  for key in massage.keys():
2014  if key in override:
2015  override[key][1]=massage[key]
2016  else:
2017  override[key]=[key, massage[key], None]
2018  return
2019 # END initialize_override_dictionary
2020 
2021 def convert_search_to_xml(t, allSeries):
2022  """
2023  Convert json to xml and set up tvdb_api object as other stuff expects
2024  :param t: tvdb_api object
2025  :param allSeries: json array of series
2026  :return: xml version of allseries
2027  """
2028  # Initialize XML display value to off
2029  t.xml = False
2030  def series_item_func(parent):
2031  if parent == "root":
2032  return "series"
2033  return "alias"
2034  xml = dicttoxml(allSeries, item_func=series_item_func, attr_type=False)
2035  t.searchTree = eTree.XML(xml)
2036  t.seriesInfoTree = None
2037  t.epInfoTree = None
2038  t.actorsInfoTree = None
2039  t.imagesInfoTree = None
2040  t.baseXsltDir = xslt.baseXsltPath
2041 
2042 def convert_series_to_xml(t, series_season_ep, ep_info):
2043  """
2044  Convert json to xml and set up tvdb_api object as other stuff expects
2045  :param t: tvdb_api object
2046  :param ep_info: json array of series
2047  """
2048  # Initialize XML display value to off
2049  t.xml = False
2050  def series_ep_item_func(parent):
2051  if parent == "data":
2052  return "series"
2053  if parent == "_banners_raw":
2054  return "banner"
2055  if parent == "_actors":
2056  return "actor"
2057  return "item"
2058  def series_people_item_func(parent):
2059  if parent == "Actors":
2060  return "Actor"
2061  return "item"
2062  def series_images_item_func(parent):
2063  if parent == "root":
2064  return "images"
2065  return "Banner"
2066  for show_id in t.shows.keys():
2067  break
2068  # sort the cast into sort order
2069  t.shows[show_id].data['_actors'] = sorted(t.shows[show_id].data['_actors'], key=lambda k: k['sortOrder'])
2070  t.searchTree = None
2071  t.seriesInfoTree = None
2072  t.epInfoTree = None
2073  t.actorsInfoTree = None
2074  t.imagesInfoTree = None
2075  sxml = dicttoxml(t.shows[show_id].data, custom_root='series', item_func=series_ep_item_func, attr_type=False)
2076  exml = dicttoxml(t.shows[show_id], custom_root='data', item_func=series_ep_item_func, attr_type=False)
2077  t.seriesInfoTree = eTree.XML(exml)
2078  t.seriesInfoTree.append(eTree.XML(sxml))
2079  t.baseXsltDir = xslt.baseXsltPath
2080 
2081 def initializeXslt(language):
2082  ''' Initalize all data and functions for XSLT stylesheet processing
2083  return nothing
2084  '''
2085  global xslt, tvdbXpath
2086  try:
2087  import MythTV.ttvdb.tvdbXslt as tvdbXslt
2088  except Exception as errmsg:
2089  sys.stderr.write('! Error: Importing tvdbXslt error(%s)\n' % errmsg)
2090  sys.exit(1)
2091 
2092  xslt = tvdbXslt.xpathFunctions()
2093  xslt.language = language
2094  xslt.buildFuncDict()
2095  xslt.baseXsltPath = tvdbXslt.baseXsltDir
2096  tvdbXpath = etree.FunctionNamespace('http://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format')
2097  tvdbXpath.prefix = 'tvdbXpath'
2098  for key in xslt.FuncDict.keys():
2099  tvdbXpath[key] = xslt.FuncDict[key]
2100  return
2101 # end initializeXslt()
2102 
2103 def displaySearchXML(tvdb_api):
2104  '''Using a XSLT style sheet translate TVDB search results into the MythTV Universal Query format
2105  return nothing
2106  '''
2107  global xslt, tvdbXpath
2108 
2109  # Remove duplicates when a non-English language code is specified
2110  if xslt.language != 'en':
2111  compareFilter = etree.XPath('//id[text()=$Id]')
2112  idLangFilter = etree.XPath('//id[text()=$Id]/../language[text()="en"]/..')
2113  tmpTree = deepcopy(tvdb_api.searchTree)
2114  for seriesId in tmpTree.xpath('//Series/id/text()'):
2115  if len(compareFilter(tvdb_api.searchTree, Id=seriesId)) > 1:
2116  tmpList = idLangFilter(tvdb_api.searchTree, Id=seriesId)
2117  if len(tmpList):
2118  tvdb_api.searchTree.remove(tmpList[0])
2119 
2120  tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbQuery.xsl')))
2121  items = tvdbQueryXslt(tvdb_api.searchTree)
2122  if items.getroot() != None:
2123  if len(items.xpath('//item')):
2124  sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
2125  return 0
2126 # end displaySearchXML()
2127 
2128 def displaySeriesXML(tvdb_api, series_season_ep):
2129  '''Using a XSLT style sheet translate TVDB Series data results into the
2130  MythTV Universal Query format
2131  return nothing
2132  '''
2133  global xslt, tvdbXpath
2134  allDataElement = etree.XML(u'<allData></allData>')
2135  requestDetails = etree.XML(u'<requestDetails></requestDetails>')
2136  requestDetails.attrib['lang'] = xslt.language
2137  requestDetails.attrib['series'] = series_season_ep[0]
2138  requestDetails.attrib['season'] = series_season_ep[1]
2139  requestDetails.attrib['episode'] = series_season_ep[2]
2140  allDataElement.append(requestDetails)
2141 
2142  # Combine the various XML inputs into a single XML element and send to the XSLT stylesheet
2143  allDataElement.append(tvdb_api.seriesInfoTree)
2144 
2145  tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbVideo.xsl')))
2146  items = tvdbQueryXslt(allDataElement)
2147  if items.getroot() != None:
2148  if len(items.xpath('//item')):
2149  sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
2150  return 0
2151 # end displaySeriesXML()
2152 
2153 def displayCollectionXML(tvdb_api):
2154  '''Using a XSLT style sheet translate TVDB series results into the MythTV Universal Query format
2155  return nothing
2156  '''
2157  global xslt, tvdbXpath
2158 
2159  # Remove duplicates when non-English language code is specified
2160  if xslt.language != 'en':
2161  compareFilter = etree.XPath('//id[text()=$Id]')
2162  idLangFilter = etree.XPath('//id[text()=$Id]/../language[text()="en"]/..')
2163  tmpTree = deepcopy(tvdb_api.seriesInfoTree)
2164  for seriesId in tmpTree.xpath('//Series/id/text()'):
2165  if len(compareFilter(tvdb_api.seriesInfoTree, Id=seriesId)) > 1:
2166  tmpList = idLangFilter(tvdb_api.seriesInfoTree, Id=seriesId)
2167  if len(tmpList):
2168  tvdb_api.seriesInfoTree.remove(tmpList[0])
2169 
2170  tvdbCollectionXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbCollection.xsl')))
2171  items = tvdbCollectionXslt(tvdb_api.seriesInfoTree)
2172  if items.getroot() != None:
2173  if len(items.xpath('//item')):
2174  sys.stdout.write(etree.tostring(items, encoding='UTF-8', method="xml", xml_declaration=True, pretty_print=True, ))
2175  return 0
2176 # end displayCollectionXML()
2177 
2178 
2179 def doc_test(opts):
2180  import doctest
2181 
2182  if not IS_PY2:
2183  # python 3 doctest capture when teh output is utf8 (bytes)
2184  # convert it back to str
2185  if isinstance(sys.stdout, OutStreamEncoder):
2186  sys.stdout = sys.stdout.out
2187  sys.stderr = sys.stderr.out
2188 
2189  class SpoofDocTestWriter(doctest._SpoofOut):
2190  """Wraps a stream with an decoder"""
2191  def __init__(self, *cwargs, **kwargs):
2192  super(SpoofDocTestWriter, self).__init__(*cwargs, **kwargs)
2193 
2194  def write(self, obj):
2195  """Wraps the output stream, encoding Unicode strings with the specified encoding"""
2196  if isinstance(obj, bytes):
2197  obj = obj.decode('utf-8')
2198  return super(SpoofDocTestWriter, self).write(obj)
2199 
2200  def __getattr__(self, attr):
2201  """Delegate everything but write to the stream"""
2202  return getattr(self.out, attr)
2203 
2204  # replace _SpoofOut with our massager
2205  doctest._SpoofOut = SpoofDocTestWriter
2206  return doctest.testmod(verbose=opts.debug, optionflags=doctest.ELLIPSIS, )
2207 
2208 def main():
2209  global season_and_episode_num, screenshot_request
2210  # reset some globals for doctest mode
2211  screenshot_request = False
2212 
2213  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")
2214 
2215  parser.add_option( "--doctest", action="store_true", default=False, dest="doctest",
2216  help=u"Run doctests")
2217  parser.add_option( "-d", "--debug", action="store_true", default=False, dest="debug",
2218  help=u"Show debugging info")
2219  parser.add_option( "-r", "--raw", action="store_true",default=False, dest="raw",
2220  help=u"Dump raw data only")
2221  parser.add_option( "-u", "--usage", action="store_true", default=False, dest="usage",
2222  help=u"Display examples for executing the ttvdb script")
2223  parser.add_option( "-v", "--version", action="store_true", default=False, dest="version",
2224  help=u"Display version and author")
2225  parser.add_option( "-i", "--interactive", action="store_true", default=False, dest="interactive",
2226  help=u"Interaction mode (allows selection of a specific Series)")
2227  parser.add_option( "-c", "--configure", metavar="FILE", default="", dest="configure",
2228  help=u"Use configuration settings")
2229  parser.add_option( "-l", "--language", metavar="LANGUAGE", default=u'en', dest="language",
2230  help=u"Select data that matches the specified language fall back to english if nothing found (e.g. 'es' Español, 'de' Deutsch ... etc)")
2231  parser.add_option( "-a", "--area", metavar="COUNTRY", default=u'', dest="country",
2232  help=u"Select data that matches the specificed country (e.g. 'de' Germany 'gb' UK). This option is currently not used.")
2233  parser.add_option( "-n", "--num_seasons", action="store_true", default=False, dest="num_seasons",
2234  help=u"Return the season numbers for a series")
2235  parser.add_option( "-t", action="store_true", default=False, dest="test",
2236  help=u"Test for the availability of runtime dependencies")
2237  parser.add_option( "-m", "--mythvideo", action="store_true", default=False, dest="mythvideo",
2238  help=u"Conform to mythvideo standards when processing -M, -P, -F and -D")
2239  parser.add_option( "-M", "--list", action="store_true", default=False, dest="list",
2240  help=u"Get matching TV Series list")
2241  parser.add_option( "-P", "--poster", action="store_true", default=False, dest="poster",
2242  help=u"Get Series Poster URL(s)")
2243  parser.add_option( "-F", "--fanart", action="store_true", default=False, dest="fanart",
2244  help=u"Get Series fan art URL(s)")
2245  parser.add_option( "-B", "--backdrop", action="store_true", default=False, dest="banner",
2246  help=u"Get Series banner/backdrop URL(s)")
2247  parser.add_option( "-S", "--screenshot", action="store_true", default=False, dest="screenshot",
2248  help=u"Get Series episode screenshot URL")
2249  parser.add_option( "-D", "--data", action="store_true", default=False, dest="data",
2250  help=u"Get Series episode data")
2251  parser.add_option( "-N", "--numbers", action="store_true", default=False, dest="numbers",
2252  help=u"Get Season and Episode numbers")
2253  parser.add_option( "-C", "--collection", action="store_true", default=False, dest="collection",
2254  help=u'Get a TV Series (collection) "series" level information')
2255 
2256  opts, series_season_ep = parser.parse_args()
2257 
2258  if opts.doctest:
2259  return doc_test(opts)
2260 
2261  # Test mode, if we've made it here, everything is ok
2262  if opts.test:
2263  print("Everything appears to be in order")
2264  return 0
2265 
2266  # Make everything unicode utf8
2267  if IS_PY2:
2268  for index in range(len(series_season_ep)):
2269  series_season_ep[index] = unicode(series_season_ep[index], 'utf8')
2270 
2271  if opts.debug == True:
2272  print("opts", opts)
2273  print("\nargs", series_season_ep)
2274 
2275  # Process version command line requests
2276  if opts.version == True:
2277  version = etree.XML(u'<grabber></grabber>')
2278  etree.SubElement(version, "name").text = __title__
2279  etree.SubElement(version, "author").text = __author__
2280  etree.SubElement(version, "thumbnail").text = 'ttvdb.png'
2281  etree.SubElement(version, "command").text = 'ttvdb.py'
2282  etree.SubElement(version, "type").text = 'television'
2283  etree.SubElement(version, "description").text = 'Search and metadata downloads for thetvdb.com'
2284  etree.SubElement(version, "version").text = __version__
2285  sys.stdout.write(etree.tostring(version, encoding='UTF-8', pretty_print=True))
2286  return 0
2287 
2288  # Process usage command line requests
2289  if opts.usage == True:
2290  sys.stdout.write(usage_txt)
2291  return 0
2292 
2293  if len(series_season_ep) == 0:
2294  parser.error("! No series or series season episode supplied")
2295  return 1
2296 
2297  # Default output format of season and episode numbers
2298  season_and_episode_num='S%02dE%02d' # Format output example "S04E12"
2299 
2300  if opts.numbers == False:
2301  if len(series_season_ep) > 1:
2302  if not _can_int(series_season_ep[1]):
2303  parser.error("! Season is not numeric")
2304  return 1
2305  if len(series_season_ep) > 2:
2306  if not _can_int(series_season_ep[2]):
2307  parser.error("! Episode is not numeric")
2308  return 1
2309  else:
2310  if len(series_season_ep) < 2:
2311  parser.error("! An Episode name must be included")
2312  return 1
2313  if len(series_season_ep) == 3:
2314  season_and_episode_num = series_season_ep[2] # Override default output format
2315 
2316  if opts.screenshot:
2317  if len(series_season_ep) > 1:
2318  if not _can_int(series_season_ep[1]):
2319  parser.error("! Season is not numeric")
2320  return 1
2321  if len(series_season_ep) > 2:
2322  if not _can_int(series_season_ep[2]):
2323  parser.error("! Episode is not numeric")
2324  return 1
2325  if not len(series_season_ep) > 2:
2326  parser.error("! Option (-S), episode screenshot search requires Season and Episode numbers")
2327  return 1
2328  screenshot_request = True
2329 
2330  if opts.debug == True:
2331  print(series_season_ep)
2332 
2333  if opts.debug == True:
2334  print("#"*20)
2335  print("# series_season_ep array(",series_season_ep,")")
2336 
2337  if opts.debug == True:
2338  print("#"*20)
2339  print("# Starting tvtvb")
2340  print("# Processing (%s) Series" % ( series_season_ep[0] ))
2341 
2342  # List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
2343  # Hard-coded here as it is realtively static, and saves another HTTP request, as
2344  # recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
2345  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"]
2346 
2347  # Validate language as specified by user
2348  if not opts.language in valid_languages:
2349  opts.language = 'en' # Set the default to English when an invalid language was specified
2350 
2351  # Set XML to be the default display mode for -N, -M, -D, -C
2352  opts.xml = True
2353  initializeXslt(opts.language)
2354 
2355  # Access thetvdb.com API with banners (Posters, Fanart, banners, screenshots) data retrieval enabled
2356  if opts.list ==True:
2357  t = Tvdb(banners=False,
2358  debug = opts.debug,
2359  cache = cache_dir,
2360  custom_ui=returnAllSeriesUI,
2361  language = opts.language,
2362  apikey=tvdb_account.apikey, # thetvdb.com API key requested by MythTV
2363  username=tvdb_account.username,
2364  userkey=tvdb_account.account_identifier)
2365  if opts.xml:
2366  t.xml = True
2367  elif opts.interactive == True:
2368  t = Tvdb(banners=True,
2369  debug=opts.debug,
2370  interactive=True,
2371  select_first=False,
2372  cache=cache_dir,
2373  actors = True,
2374  language = opts.language,
2375  apikey=tvdb_account.apikey, # thetvdb.com API key requested by MythTV
2376  username=tvdb_account.username,
2377  userkey=tvdb_account.account_identifier)
2378  if opts.xml:
2379  t.xml = True
2380  else:
2381  t = Tvdb(banners=True,
2382  debug = opts.debug,
2383  cache = cache_dir,
2384  actors = True,
2385  language = opts.language,
2386  apikey=tvdb_account.apikey, # thetvdb.com API key requested by MythTV
2387  username=tvdb_account.username,
2388  userkey=tvdb_account.account_identifier)
2389  if opts.xml:
2390  t.xml = True
2391 
2392  # disable certificate check
2393  t.session.verify = False
2394 
2395  # Determine if there is a SID or a series name to search with
2396  global SID
2397  SID = False
2398  if _can_int(series_season_ep[0]): # if it is numeric then assume it is a series ID number
2399  SID = True
2400  else:
2401  SID = False
2402 
2403  # The -C collections options only supports a SID as input
2404  if opts.collection:
2405  if SID:
2406  pass
2407  else:
2408  parser.error("! Option (-C), collection requires an inetref number")
2409  return 1
2410 
2411  if opts.debug == True:
2412  print("# ..got tvdb mirrors")
2413  print("# Start to process series or series_season_ep")
2414  print("#"*20)
2415 
2416  global override
2417  override={} # Initialize series name override dictionary
2418  # If the user wants Series name overrides and a override file exists then create an overide dictionary
2419  if opts.configure != '': # Did the user want to override the default config file name/location
2420  if opts.configure[0]=='~':
2421  opts.configure=os.path.expanduser("~")+opts.configure[1:]
2422  if os.path.exists(opts.configure) == 1: # Do overrides exist?
2423  initialize_override_dictionary(opts.configure, opts.language)
2424  else:
2425  sys.stderr.write("! The specified override file (%s) does not exist\n" % opts.configure)
2426  return 1
2427  else: # Check if there is a default configuration file
2428  default_config = u"%s/%s" % (os.path.expanduser(u"~"), u".mythtv/ttvdb.conf")
2429  if os.path.isfile(default_config):
2430  opts.configure = default_config
2431  initialize_override_dictionary(opts.configure, opts.language)
2432 
2433  if len(override) == 0:
2434  opts.configure = False # Turn off the override option as there is nothing to override
2435 
2436  # Check if a video name was passed and if so parse it for series name, season and episode numbers
2437  if not opts.collection and len(series_season_ep) == 1:
2438  for r in name_parse:
2439  match = r.match(series_season_ep[0])
2440  if match:
2441  seriesname, seasno, epno = match.groups()
2442  #remove ._- characters from name (- removed only if next to end of line)
2443  seriesname = re.sub("[\._]|\-(?=$)", " ", seriesname).strip()
2444  series_season_ep = [seriesname, seasno, epno]
2445  break # Matched - to the next file!
2446 
2447  # Fetch a list of matching series names
2448  if opts.list ==True:
2449  try:
2450  key = series_season_ep[0].lower()
2451  if opts.configure != "" and key in override:
2452  if override[key][2]:
2453  allSeries = t._getSeries(override[key][2], True)
2454  else:
2455  allSeries = t._getSeries(override[key][0])
2456  else:
2457  allSeries=t._getSeries(series_season_ep[0])
2458  except tvdb_shownotfound:
2459  return 0 # No matching series
2460  except Exception as e:
2461  sys.stderr.write("! Error: %s\n" % (e))
2462  raise
2463  return 1 # Most likely a communications error
2464  if opts.xml:
2465  convert_search_to_xml(t, allSeries)
2466  displaySearchXML(t)
2467  return 0
2468  match_list = []
2469  for series_name_sid in allSeries: # list search results
2470  key_value = u"%s:%s" % (series_name_sid['sid'], series_name_sid['name'])
2471  if not key_value in match_list: # Do not add duplicates
2472  match_list.append(key_value)
2473  print(key_value)
2474  return 0 # The Series list option (-M) is the only option honoured when used
2475 
2476  # Fetch TV series collection information
2477  if opts.collection:
2478  try:
2479  t._getShowData(series_season_ep[0], opts.language)
2480  except tvdb_shownotfound:
2481  return 0 # No matching series
2482  except Exception as e:
2483  sys.stderr.write("! Error: %s\n" % (e))
2484  raise
2485  return 1 # Most likely a communications error
2486  convert_series_to_xml(t, series_season_ep, None)
2488  return 0 # The TV Series collection option (-C) is the only option honoured when used
2489 
2490  # Verify that thetvdb.com has the desired series_season_ep.
2491  # Exit this module if series_season_ep is not found
2492  if opts.numbers == False and opts.num_seasons == False:
2493  seriesfound=searchseries(t, opts, series_season_ep)
2494  x=1
2495  else:
2496  x=[]
2497  x.append(series_season_ep[0]) # Only use series name in check
2498  seriesfound=searchseries(t, opts, x)
2499 
2500  # Return the season numbers for a series
2501  if opts.num_seasons == True:
2502  season_numbers=''
2503  for x in sorted(seriesfound.keys()):
2504  season_numbers+='%d,' % x
2505  print(season_numbers[:-1])
2506  return 0 # Option (-n) is the only option honoured when used
2507 
2508  # Dump information accessible for a Series and ONLY first season of episoded data
2509  if opts.debug == True:
2510  print("#"*20)
2511  print("# Starting Raw keys call")
2512  print("Lvl #1:") # Seasons for series
2513  x = t[series_season_ep[0]].keys()
2514  print(t[series_season_ep[0]].keys())
2515  print("#"*20)
2516  print("Lvl #2:") # Episodes for each season
2517  for y in x:
2518  print(t[series_season_ep[0]][y].keys())
2519  print("#"*20)
2520  print("Lvl #3:") # Keys for each episode within the 1st season
2521  z = t[series_season_ep[0]][1].keys()
2522  for aa in z:
2523  print(t[series_season_ep[0]][1][aa].keys())
2524  print("#"*20)
2525  print("Lvl #4:") # Available data for each episode in 1st season
2526  for aa in z:
2527  codes = t[series_season_ep[0]][1][aa].keys()
2528  print("\n\nStart:")
2529  for c in codes:
2530  print("="*50)
2531  print('Key Name=('+c+'):')
2532  print(t[series_season_ep[0]][1][aa][c])
2533  print("="*50)
2534  print("#"*20)
2535  sys.exit (True)
2536 
2537  if opts.numbers == True: # Fetch and output season and episode numbers
2538  global xmlFlag
2539  if opts.xml:
2540  xmlFlag = True
2541  else:
2542  xmlFlag = False
2543  Getseries_episode_numbers(t, opts, series_season_ep)
2544  return 0 # The Numbers option (-N) is the only option honoured when used
2545 
2546  if opts.data or screenshot_request: # Fetch and output episode data
2547  if opts.mythvideo:
2548  if len(series_season_ep) != 3:
2549  print(u"Season and Episode numbers required.")
2550  else:
2551  if opts.xml:
2552  t.getDetailedEpisodeInfo(seriesfound[u'id'], series_season_ep[1], series_season_ep[2])
2553  convert_series_to_xml(t, series_season_ep, seriesfound)
2554  displaySeriesXML(t, series_season_ep)
2555  return 0
2556  Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
2557  else:
2558  if opts.xml and len(series_season_ep) == 3:
2559  t.getDetailedEpisodeInfo(list(t.shows.values())[0].data['id'], series_season_ep[1], series_season_ep[2])
2560  convert_series_to_xml(t, series_season_ep, seriesfound)
2561  displaySeriesXML(t, series_season_ep)
2562  return 0
2563  Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
2564 
2565  # Fetch the requested graphics URL(s)
2566  if opts.debug == True:
2567  print("#"*20)
2568  print("# Checking if Posters, Fanart or Banners are available")
2569  print("#"*20)
2570 
2571  key = series_season_ep[0].lower()
2572  if opts.configure != "" and key in override:
2573  series_info = search_for_series(t, override[key][0], opts.language)
2574  else:
2575  series_info = search_for_series(t, series_season_ep[0], opts.language)
2576  try:
2577  banners_keys = series_info['_banners'].keys()
2578  except:
2579  banners_keys = None
2580 
2581  banner= False
2582  poster= False
2583  fanart= False
2584 
2585  if banners_keys:
2586  for x in banners_keys: # Determine what type of graphics is available
2587  if x == fanart_key:
2588  fanart=True
2589  elif x == poster_key:
2590  poster=True
2591  elif x == season_key or x == banner_key:
2592  banner=True
2593 
2594  # Make sure that some graphics URL(s) (Posters, FanArt or Banners) are available
2595  if ( fanart!=True and poster!=True and banner!=True ):
2596  return 0
2597 
2598  if opts.debug == True:
2599  print("#"*20)
2600  print("# One or more of Posters, Fanart or Banners are available")
2601  print("#"*20)
2602 
2603  # Determine if graphic URL identification output is required
2604  if opts.data: # Along with episode data get all graphics
2605  opts.poster = True
2606  opts.fanart = True
2607  opts.banner = True
2608  single_option = True
2609  fanart, banner, poster = (True, True, True)
2610  else:
2611  y=0
2612  single_option=True
2613  if opts.poster==True:
2614  y+=1
2615  if opts.fanart==True:
2616  y+=1
2617  if opts.banner==True:
2618  y+=1
2619 
2620  if (poster==True and opts.poster==True and opts.raw!=True): # Get posters and send to stdout
2621  season_poster_found = False
2622  if opts.mythvideo:
2623  if len(series_season_ep) < 2:
2624  print(u"Season and Episode numbers required.")
2625  return 0
2626  all_posters = u'Coverart:'
2627  all_empty = len(all_posters)
2628  for p in get_graphics(t, opts, series_season_ep, poster_type, single_option, opts.language):
2629  all_posters = all_posters+p+u','
2630  season_poster_found = True
2631  if season_poster_found == False: # If there were no season posters get the series top poster
2632  series_name=''
2633  key = series_season_ep[0].lower()
2634  if opts.configure != "" and key in override:
2635  series_name=override[key][0] # Override series name
2636  else:
2637  series_name=series_season_ep[0] # Leave the series name alone
2638  for p in get_graphics(t, opts, [series_name], poster_type, single_option, opts.language):
2639  all_posters = all_posters+p+u','
2640  if len(all_posters) > all_empty:
2641  if all_posters[-1] == u',':
2642  print(all_posters[:-1])
2643  else:
2644  print(all_posters)
2645 
2646  if (fanart==True and opts.fanart==True and opts.raw!=True): # Get Fan Art and send to stdout
2647  all_fanart = u'Fanart:'
2648  all_empty = len(all_fanart)
2649  for f in get_graphics(t, opts, series_season_ep, fanart_type, single_option, opts.language):
2650  all_fanart = all_fanart+f+u','
2651  if len(all_fanart) > all_empty:
2652  if all_fanart[-1] == u',':
2653  print(all_fanart[:-1])
2654  else:
2655  print(all_fanart)
2656 
2657  if (banner==True and opts.banner==True and opts.raw!=True): # Also change to get ALL Series graphics
2658  season_banner_found = False
2659  if opts.mythvideo:
2660  if len(series_season_ep) < 2:
2661  print(u"Season and Episode numbers required.")
2662  return 0
2663  all_banners = u'Banner:'
2664  all_empty = len(all_banners)
2665  for b in get_graphics(t, opts, series_season_ep, banner_type, single_option, opts.language):
2666  all_banners = all_banners+b+u','
2667  season_banner_found = True
2668  if not season_banner_found: # If there were no season banner get the series top banner
2669  series_name=''
2670  key = series_season_ep[0].lower()
2671  if opts.configure != "" and key in override:
2672  series_name=override[key][0] # Override series name
2673  else:
2674  series_name=series_season_ep[0] # Leave the series name alone
2675  for b in get_graphics(t, opts, [series_name], banner_type, single_option, opts.language):
2676  all_banners = all_banners+b+u','
2677  if len(all_banners) > all_empty:
2678  if all_banners[-1] == u',':
2679  print(all_banners[:-1])
2680  else:
2681  print(all_banners)
2682 
2683  if opts.debug == True:
2684  print("#"*20)
2685  print("# Processing complete")
2686  print("#"*20)
2687  return 0
2688 #end main
2689 
2690 if __name__ == "__main__":
2691  sys.exit(main())
def write(text, progress=True)
Definition: mythburn.py:279
def fuzzysearch(self, term=None, key=None)
Definition: ttvdb.py:1441
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
def series_by_sid(self, sid, language)
Definition: ttvdb.py:1480
def selectSeries(self, allSeries)
Definition: ttvdb.py:1934
def displaySeriesXML(tvdb_api, series_season_ep)
Definition: ttvdb.py:2128
def massageEpisode_name(ep_name, series_season_ep)
Definition: ttvdb.py:1677
def searchseries(t, opts, series_season_ep)
Definition: ttvdb.py:1519
def _setItem(self, sid, seas, ep, attrib, value)
Definition: ttvdb.py:1491
def make_db_ready(text)
Definition: ttvdb.py:1702
def convert_series_to_xml(t, series_season_ep, ep_info)
Definition: ttvdb.py:2042
def change_to_commas(meta_data)
Definition: ttvdb.py:1684
def displaySearchXML(tvdb_api)
Definition: ttvdb.py:2103
def write(self, obj)
Definition: ttvdb.py:1412
def _can_int(x)
Definition: ttvdb.py:1388
def fuzzysearch(self, term=None, key=None)
Definition: ttvdb.py:1453
def __init__(self, config, log=None)
Definition: ttvdb.py:1930
def __getattr__(self, attr)
Definition: ttvdb.py:1421
def get_graphics(t, opts, series_season_ep, graphics_type, single_option, language=False)
Definition: ttvdb.py:1572
unicode
Definition: ttvdb.py:1309
def Getseries_episode_numbers(t, opts, series_season_ep)
Definition: ttvdb.py:1874
def search_for_series(tvdb, sid_or_name, language)
Definition: ttvdb.py:1510
def Getseries_episode_data(t, opts, series_season_ep, language=None)
Definition: ttvdb.py:1716
def _setShowData(self, sid, key, value)
Definition: ttvdb.py:1502
def doc_test(opts)
Definition: ttvdb.py:2179
def convert_search_to_xml(t, allSeries)
Definition: ttvdb.py:2021
def __init__(self, outstream, encoding=None)
Definition: ttvdb.py:1405
def change_amp(text)
Definition: ttvdb.py:1691
def initialize_override_dictionary(useroptions, language)
Definition: ttvdb.py:1938
def fuzzysearch(self, term=None, key=None)
Definition: ttvdb.py:1430
def main()
Definition: ttvdb.py:2208
def initializeXslt(language)
Definition: ttvdb.py:2081
def displayCollectionXML(tvdb_api)
Definition: ttvdb.py:2153