GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Description: Simple to use TVDB (thetvdb.com) API in Python, and automatic TV episode namer
Homepage: http://dbr.lighthouseapp.com/projects/13342-tvdb_api/tickets
Clone URL: git://github.com/dbr/tvdb_api.git
commit  6dc0826a24e09861627cd952abbe17e19c23fe52
tree    762f6a5d39d7c6e36955b1d3a0884102f7821f71
parent  29fdfbb9bba85e5ebc165becc51c0f590c1ca97b
tvdb_api / tvdb_api.py
100644 261 lines (227 sloc) 9.443 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
__author__ = "dbr/Ben"
__version = "0.1"
 
class _Ddict(dict):
    """Lazy-dict, automatically creates multidimensional dicts
by having __getitem__ create sub-dict automatically"""
    def __init__(self, default=None):
        self.default = default
    #end __init__
 
    def __getitem__(self, key):
        if not self.has_key(key):
            self[key] = self.__class__(self.default) # Create sub-instance
        return dict.__getitem__(self, key)
    #end __getitem__
#end _Ddict
 
class Cache:
    import os
    import tempfile
    import urllib
    try:
        import sha1 as hasher
    except ImportError:
        import md5 as hasher
    
    def __init__(self,prefix="tvdb_api"):
        self.prefix = prefix
        tmp = self.tempfile.gettempdir()
        tmppath = self.os.path.join(tmp, prefix)
        if not self.os.path.isdir(tmppath):
            self.os.mkdir(tmppath)
        self.tmp = tmppath
    #end __init__
    
    def getCachePath(self,url):
        cache_name = self.hasher.new(url).hexdigest()
        cache_path = self.os.path.join(self.tmp, cache_name)
        return cache_path
    #end getUrl
    
    def checkCache(self,url):
        path = self.getCachePath(url)
        if self.os.path.isfile(path):
            return path
        else:
            return False
    #end checkCache
 
    def loadUrl(self,url):
        cacheExists = self.checkCache(url)
        if cacheExists:
            f=open(cacheExists)
            dat = f.read()
            f.close()
            return dat
        else:
            path = self.getCachePath(url)
            dat = self.urllib.urlopen(url).read()
            f=open(path,"w+")
            f.write(dat)
            f.close()
            return dat
        #end if cacheExists
    #end getUrl
#end Cache
 
# Custom exceptions
class tvdb_error(Exception):pass
class tvdb_shownotfound(Exception):pass
class tvdb_userabort(Exception):pass
 
class tvdb:
    """
Create easy-to-use interface to name of season/episode name
>>> i = tvdb()
>>> i['showname']['1']['24']['name']
'Last Episode'
"""
    from BeautifulSoup import BeautifulStoneSoup
    
    def __init__(self,interactive=False,debug=False):
        self.config={}
        self.config['apikey'] = "0629B785CE550C8D"
        # The following url_ configs are based of the http://www.thetvdb.com API documentation
        self.config['url_mirror'] = "http://www.thetvdb.com/api/%s/mirrors.xml" % (self.config['apikey'])
        self.config['url_getSeries'] = "http://www.thetvdb.com/api/GetSeries.php?seriesname=%s"
        self.config['url_epInfo'] = "http://www.thetvdb.com/api/%s/series/%%s/all/" % (self.config['apikey'])
        
        self.config['interactive'] = interactive # prompt for correct series if needed
        
        self.config['debug_enabled'] = debug # show debugging messages
        self.config['debug_tofile'] = False
        self.config['debug_filename'] = "tvdb.log"
        self.config['debug_path'] = '.'
        
        self.cache = Cache("tvdb_api") # Caches retreived URLs in tmp dir
        self.log = self.initLogger() # Setups the logger (self.log.debug() etc)
        self.shows = {} # Holds all show data in shows[show_id] = dict of ep data
        self.corrections = {} # Holds show-name to show_id mapping
        
        # Config setup. Grab TVDB mirrors
        self.mirrors = self._getMirrors() # TODO: Apply random mirror urls (Minor: Currently 1 mirror)
    #end __init__
    
    def initLogger(self):
        import os,logging,sys
        logdir = os.path.expanduser( self.config['debug_path'] )
        logpath = os.path.join(logdir,self.config['debug_filename'])
        
        logger = logging.getLogger("tvdb")
        formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')
        
        if self.config['debug_tofile']:
            hdlr = logging.FileHandler(logpath)
        else:
            hdlr = logging.StreamHandler(sys.stdout)
        #end if debug_tofile
        
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)
        
        if self.config['debug_enabled']:
            logger.setLevel(logging.DEBUG)
        else:
            logger.setLevel(logging.INFO)
        return logger
    #end initLogger
    
    def _getsoupsrc(self,url):
        self.log.debug('Retriving URL %s' % (url.replace(" ","+")))
        
        url=url.replace(" ","+")
        try:
            src=self.cache.loadUrl(url)
        except IOError,errormsg:
            raise tvdb_error("Could not connect to server: %s\n" % (errormsg))
        #end try
        soup=self.BeautifulStoneSoup(src)
        return soup
    #end _getsoupsrc
    
    def _getMirrors(self):
        mirrorSoup=self._getsoupsrc( self.config['url_mirror'] )
        mirrors=[]
        for mirror in mirrorSoup.findAll('mirror'):
            self.log.debug('Found mirror %s' % (mirror))
            
            mirrors.append(
                mirror.find('mirrorpath').contents[0]
            )
        #end for mirror
        self.log.debug('Found total of %s mirrors' % (len(mirrors)))
        return mirrors
    #end _getMirrors
 
    def _cleanName(self,name):
        name = name.replace("&","and")
        return name
    #end _cleanName
    
    def _getSeries(self,series):
        seriesSoup = self._getsoupsrc( self.config['url_getSeries'] % (series) )
        allSeries=[]
        for series in seriesSoup.findAll('series'):
            cur_name = series.find('seriesname').contents[0]
            cur_name = self._cleanName(cur_name)
            cur_sid = series.find('id').contents[0]
            self.log.debug('Found series %s (id: %s)' % (cur_name,cur_sid))
            allSeries.append( {'sid':cur_sid, 'name':cur_name} )
        #end for series
        
        if len(allSeries) == 0:
            self.log.debug('Series result returned zero')
            raise tvdb_shownotfound("Show-name search returned zero results")
        
        if self.config['interactive']:
            self.log.debug('Interactivily selecting show')
            for i in range(len(allSeries[:6])):
                i_show = i + 1 # Start at more human readable number 1 (not zero)
                self.log.debug( 'Showing allSeries[%s] = %s)' % (i_show,allSeries[i]) )
                print "%s -> %s (tvdb id: %s)" % (i_show,allSeries[i]['name'].encode("UTF-8","ignore"),allSeries[i]['sid'].encode("UTF-8","ignore"))
            print "Enter choice (first number):"
            ans=raw_input()
            self.log.debug( 'Got choice of: %s' % (ans))
            try:
                selected_id = int(ans) - 1 # The human entered 1 as first result, not zero
                self.log.debug( 'Trying to return ID: %d' % (selected_id))
                return allSeries[ selected_id ]
            except ValueError: # Input was not number
                if ans == "q":
                    self.log.debug('Got quit command (q)')
                    raise tvdb_userabort("User aborted")
                else:
                    self.log.debug('Unknown keypress %s' % (ans))
                    raise tvdb_userabort("Invalid keypress") # TODO: Better UI
            #end for k,v
        else:
            self.log.debug('Auto-selecting first search result')
            return allSeries[0]
    #end _getSeries
 
    def _getEps(self,sid):
        self.log.debug('Getting all episodes of %s' % (sid))
        epsSoup=self._getsoupsrc( self.config['url_epInfo']% (sid) )
        for ep in epsSoup.findAll('episode'):
            ep_no = int( ep.find('episodenumber').contents[0] )
            seas_no = int( ep.find('seasonnumber').contents[0] )
            ep_name = str( ep.find('episodename').contents[0] )
            
            self.shows[sid][seas_no][ep_no] = {'name':ep_name}
        #end for ep
    #end _geEps
    
    def _nameToSid(self,name):
        """
Takes show name, returns the correct series ID (if the show has
already been grabbed), or grabs all episodes and returns
the correct SID.
"""
        if self.corrections.has_key(name):
            self.log.debug('Correcting %s to %s' % (name,self.corrections[name]) )
            sid = self.corrections[name]
        else:
            self.log.debug('Getting show %s' % (name))
            selected_series = self._getSeries( name )
            sname, sid = selected_series['name'], selected_series['sid']
            self.log.debug( "Got %s, sid %s" % (sname,sid) )
            self.shows[sid] = _Ddict(dict)
            self.shows[sid]['showname'] = sname
            self.corrections[name] = sid
            self._getEps( sid )
        #end if self.corrections.has_key
        return sid
    #end _nameToSid
 
    def __getitem__(self,key):
        """
Handles tvdb_instance['showname'] calls.
The dict index should be the show name
"""
        key=key.lower() # make key lower case
        sid = self._nameToSid(key)
        self.log.debug('Got series id %s' % (sid))
        return dict.__getitem__(self.shows, sid)
    #end __getitem__
    
    def __setitem__(self,key,value):
        self.log.debug('Setting %s = %s' % (key,value))
        self.shows[key] = value
    #end __getitem__
    def __str__(self):
        return str(self.shows) #TODO: Improve this
    #end __str__
#end tvdb
 
if __name__ == '__main__':
    x=tvdb(interactive=True,debug=True)
    print x['lost'][1][4]
    print x['Lost'][1][4]