-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
tvrage.py
341 lines (232 loc) · 13.3 KB
/
tvrage.py
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of Sick Beard.
#
# Sick Beard is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Sick Beard is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
import urllib, urllib2
import datetime
import traceback
import sickbeard
from sickbeard import logger
from sickbeard.common import UNAIRED
from sickbeard import db
from sickbeard import exceptions, helpers
from sickbeard.exceptions import ex
from lib.tvdb_api import tvdb_api, tvdb_exceptions
class TVRage:
def __init__(self, show):
self.show = show
self.lastEpInfo = None
self.nextEpInfo = None
self._tvrid = 0
self._tvrname = None
if self.show.tvrid == 0:
# if it's the right show then use the tvrage ID that the last lookup found (cached in self._trvid)
show_is_right = self.confirmShow() or self.checkSync()
if not show_is_right:
raise exceptions.TVRageException("Shows aren't the same, aborting")
if self._tvrid == 0 or self._tvrname == None:
raise exceptions.TVRageException("We confirmed sync but got invalid data (no ID/name)")
if show_is_right:
logger.log(u"Setting TVRage ID for "+show.name+" to "+str(self._tvrid))
self.show.tvrid = self._tvrid
self.show.saveToDB()
if not self.show.tvrname:
if self._tvrname == None:
self._getTVRageInfo()
logger.log(u"Setting TVRage Show Name for "+show.name+" to "+self._tvrname)
self.show.tvrname = self._tvrname
self.show.saveToDB()
def confirmShow(self, force=False):
if self.show.tvrid != 0 and not force:
logger.log(u"We already have a TVRage ID, skipping confirmation", logger.DEBUG)
return True
logger.log(u"Checking the first episode of each season to see if the air dates match between TVDB and TVRage")
tvdb_lang = self.show.lang
try:
try:
# There's gotta be a better way of doing this but we don't wanna
# change the language value elsewhere
ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy()
if tvdb_lang and not tvdb_lang == 'en':
ltvdb_api_parms['language'] = tvdb_lang
t = tvdb_api.Tvdb(**ltvdb_api_parms)
except tvdb_exceptions.tvdb_exception, e:
logger.log(u"Currently this doesn't work with TVDB down but with some DB magic it can be added", logger.DEBUG)
return None
# check the first episode of every season
for curSeason in t[self.show.tvdbid]:
logger.log(u"Checking TVDB and TVRage sync for season "+str(curSeason), logger.DEBUG)
airdate = None
try:
# don't do specials and don't do seasons with no episode 1
if curSeason == 0 or 1 not in t[self.show.tvdbid]:
continue
# get the episode info from TVDB
ep = t[self.show.tvdbid][curSeason][1]
# make sure we have a date to compare with
if ep["firstaired"] == "" or ep["firstaired"] == None:
continue
# get a datetime object
rawAirdate = [int(x) for x in ep["firstaired"].split("-")]
airdate = datetime.date(rawAirdate[0], rawAirdate[1], rawAirdate[2])
# get the episode info from TVRage
info = self._getTVRageInfo(curSeason, 1)
# make sure we have enough info
if info == None or not info.has_key('Episode Info'):
logger.log(u"TVRage doesn't have the episode info, skipping it", logger.DEBUG)
continue
# parse the episode info
curEpInfo = self._getEpInfo(info['Episode Info'])
# make sure we got some info back
if curEpInfo == None:
continue
# if we couldn't compare with TVDB try comparing it with the local database
except tvdb_exceptions.tvdb_exception, e:
logger.log(u"Unable to check TVRage info against TVDB: "+ex(e))
logger.log(u"Trying against DB instead", logger.DEBUG)
myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? AND season = ? and episode = ?", [self.show.tvdbid, self.lastEpInfo['season'], self.lastEpInfo['episode']])
if len(sqlResults) == 0:
raise exceptions.EpisodeNotFoundException("Unable to find episode in DB")
else:
airdate = datetime.date.fromordinal(int(sqlResults[0]["airdate"]))
# check if TVRage and TVDB have the same airdate for this episode
if curEpInfo['airdate'] == airdate:
logger.log(u"Successful match for TVRage and TVDB data for episode "+str(curSeason)+"x1)", logger.DEBUG)
return True
logger.log(u"Date from TVDB for episode " + str(curSeason) + "x1: " + str(airdate), logger.DEBUG)
logger.log(u"Date from TVRage for episode " + str(curSeason) + "x1: " + str(curEpInfo['airdate']), logger.DEBUG)
except Exception, e:
logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + ex(e), logger.WARNING)
logger.log(traceback.format_exc(), logger.DEBUG)
return False
def checkSync(self, info=None):
logger.log(u"Checking the last aired episode to see if the dates match between TVDB and TVRage")
if self.lastEpInfo == None or self.nextEpInfo == None:
self._saveLatestInfo(info)
if self.nextEpInfo['season'] == 0 or self.nextEpInfo['episode'] == 0:
return None
try:
airdate = None
tvdb_lang = self.show.lang
# There's gotta be a better way of doing this but we don't wanna
# change the language value elsewhere
ltvdb_api_parms = sickbeard.TVDB_API_PARMS.copy()
if tvdb_lang and not tvdb_lang == 'en':
ltvdb_api_parms['language'] = tvdb_lang
# make sure the last TVDB episode matches our last episode
try:
t = tvdb_api.Tvdb(**ltvdb_api_parms)
ep = t[self.show.tvdbid][self.lastEpInfo['season']][self.lastEpInfo['episode']]
if ep["firstaired"] == "" or ep["firstaired"] == None:
return None
rawAirdate = [int(x) for x in ep["firstaired"].split("-")]
airdate = datetime.date(rawAirdate[0], rawAirdate[1], rawAirdate[2])
except tvdb_exceptions.tvdb_exception, e:
logger.log(u"Unable to check TVRage info against TVDB: "+ex(e))
logger.log(u"Trying against DB instead", logger.DEBUG)
myDB = db.DBConnection()
sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? AND season = ? and episode = ?", [self.show.tvdbid, self.lastEpInfo['season'], self.lastEpInfo['episode']])
if len(sqlResults) == 0:
raise exceptions.EpisodeNotFoundException("Unable to find episode in DB")
else:
airdate = datetime.date.fromordinal(int(sqlResults[0]["airdate"]))
logger.log(u"Date from TVDB for episode " + str(self.lastEpInfo['season']) + "x" + str(self.lastEpInfo['episode']) + ": " + str(airdate), logger.DEBUG)
logger.log(u"Date from TVRage for episode " + str(self.lastEpInfo['season']) + "x" + str(self.lastEpInfo['episode']) + ": " + str(self.lastEpInfo['airdate']), logger.DEBUG)
if self.lastEpInfo['airdate'] == airdate:
return True
except Exception, e:
logger.log(u"Error encountered while checking TVRage<->TVDB sync: " + ex(e), logger.WARNING)
logger.log(traceback.format_exc(), logger.DEBUG)
return False
def _getTVRageInfo(self, season=None, episode=None, full=False):
url = "http://services.tvrage.com/tools/quickinfo.php?"
# if we need full info OR if we don't have a tvrage id, use show name
if full == True or self.show.tvrid == 0:
if self.show.tvrname != "" and self.show.tvrname != None:
showName = self.show.tvrname
else:
showName = self.show.name
urlData = {'show': showName.encode('utf-8')}
# if we don't need full info and we have a tvrage id, use it
else:
urlData = {'sid': self.show.tvrid}
if season != None and episode != None:
urlData['ep'] = str(season)+'x'+str(episode)
# build the URL
url += urllib.urlencode(urlData)
logger.log(u"Loading TVRage info from URL: " + url, logger.DEBUG)
try:
result = helpers.getURL(url).decode('utf-8')
except (urllib2.HTTPError, IOError), e:
logger.log(u"Unable to load TVRage info: " + ex(e))
raise exceptions.TVRageException("urlopen call to " + url + " failed")
urlData = result.splitlines()
info = {}
for x in urlData:
key, value = x.split("@")
key = key.replace('<pre>','')
info[key] = value.strip()
# save it for later in case somebody is curious
if info.has_key('Show ID'):
self._tvrid = info['Show ID']
if info.has_key('Show Name'):
self._tvrname = info['Show Name']
return info
def _saveLatestInfo(self, info=None):
if info == None:
info = self._getTVRageInfo()
if not info.has_key('Next Episode') or not info.has_key('Latest Episode'):
raise exceptions.TVRageException("TVRage doesn't have all the required info for this show")
self.lastEpInfo = self._getEpInfo(info['Latest Episode'])
self.nextEpInfo = self._getEpInfo(info['Next Episode'])
if self.lastEpInfo == None or self.nextEpInfo == None:
raise exceptions.TVRageException("TVRage has malformed data, unable to update the show")
def _getEpInfo(self, epString):
logger.log(u"Parsing info from TVRage: " + epString, logger.DEBUG)
epInfo = epString.split('^')
numInfo = [int(x) for x in epInfo[0].split('x')]
try:
date = datetime.datetime.strptime(epInfo[2], "%b/%d/%Y").date()
except ValueError:
try:
date = datetime.datetime.strptime(epInfo[2], "%d/%b/%Y").date()
except ValueError:
logger.log(u"Unable to figure out the time from the TVRage data "+epInfo[2])
return None
toReturn = {'season': int(numInfo[0]), 'episode': numInfo[1], 'name': epInfo[1], 'airdate': date}
logger.log(u"Result of parse: " + str(toReturn), logger.DEBUG)
return toReturn
def findLatestEp(self):
# will use tvrage name if it got set in the constructor, or tvdb name if not
info = self._getTVRageInfo(full=True)
if not self.checkSync(info):
raise exceptions.TVRageException("TVRage info isn't in sync with TVDB, not using data")
myDB = db.DBConnection()
# double check that it's not already in there
sqlResults = myDB.select("SELECT * FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?", [self.show.tvdbid, self.nextEpInfo['season'], self.nextEpInfo['episode']])
if len(sqlResults) > 0:
raise exceptions.TVRageException("Show is already in database, not adding the TVRage info")
# insert it
myDB.action("INSERT INTO tv_episodes (showid, tvdbid, name, season, episode, description, airdate, hasnfo, hastbn, status, location) VALUES (?,?,?,?,?,?,?,?,?,?,?)", \
[self.show.tvdbid, -1, self.nextEpInfo['name'], self.nextEpInfo['season'], self.nextEpInfo['episode'], '', self.nextEpInfo['airdate'].toordinal(), 0, 0, UNAIRED, ''])
# once it's in the DB make an object and return it
ep = None
try:
ep = self.show.getEpisode(self.nextEpInfo['season'], self.nextEpInfo['episode'])
except exceptions.SickBeardException, e:
logger.log(u"Unable to create episode from tvrage (could be for a variety of reasons): " + ex(e))
return ep