3
from flexget.manager import Session
4
from flexget.plugin import register_plugin, PluginError, get_plugin_by_name, priority, register_parser_option, register_feed_phase
5
from flexget.manager import Base
6
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Unicode
7
from flexget.utils.imdb import extract_id, ImdbSearch, ImdbParser, log as imdb_log
8
from flexget.utils.tools import str_to_boolean
9
from flexget.utils import qualities
11
log = logging.getLogger('imdb_queue')
14
class QueueError(Exception):
15
"""Exception raised if there is an error with a queue operation"""
17
def __init__(self, message, errno=0):
18
self.message = message
24
__tablename__ = 'imdb_queue'
26
id = Column(Integer, primary_key=True)
27
imdb_id = Column(String)
28
quality = Column(String)
29
title = Column(Unicode)
30
immortal = Column(Boolean)
31
added = Column(DateTime)
33
def __init__(self, imdb_id, quality, immortal):
34
self.imdb_id = imdb_id
35
self.quality = quality
36
self.immortal = immortal
37
self.added = datetime.datetime.now()
40
return '<ImdbQueue(imdb_id=%s,quality=%s,force=%s)>' % (self.imdb_id, self.quality, self.immortal)
43
class FilterImdbQueue(object):
45
Allows a queue of upcoming movies that will be forcibly allowed if the given quality matches
52
# Dict of entries accepted by this plugin {imdb_id: entry} format
56
from flexget import validator
57
return validator.factory('boolean')
60
def on_feed_filter(self, feed):
61
# Doing this so that I register as a filter plugin. Just need to filter
62
# after urlrewrite happens, just before download.
63
# Also have to accept anything with an IMDB url that matches, even if
66
for entry in feed.entries:
67
# make sure the entry has IMDB fields filled
69
get_plugin_by_name('imdb_lookup').instance.lookup(feed, entry)
71
# no IMDB data, can't do anything
75
if 'imdb_url' in entry:
77
if 'imdb_id' in entry and entry['imdb_id'] is not None:
78
imdb_id = entry['imdb_id']
80
imdb_id = extract_id(entry['imdb_url'])
83
log.warning("No imdb id could be determined for %s" % entry['title'])
86
item = feed.session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
89
entry['immortal'] = item.immortal
90
log.debug("Pre-accepting %s from queue" % (entry['title']))
91
feed.accept(entry, 'imdb-queue pre-accept')
92
# Keep track of entries we accepted, so they can be removed from queue on feed_exit if successful
93
self.accepted_entries[imdb_id] = entry
95
log.debugall("%s not in queue, skipping" % entry['title'])
97
def on_feed_imdbqueue(self, feed):
99
for entry in feed.entries:
100
if entry['url'] == '':
101
feed.reject(entry, 'imdb-queue - no URL in entry: '
102
'%s' % entry['title'])
103
rejected.append(entry['title'])
105
# make sure the entry has IMDB fields filled
107
get_plugin_by_name('imdb_lookup').instance.lookup(feed, entry)
109
# no IMDB data, can't do anything
113
if 'imdb_url' in entry:
115
if 'imdb_id' in entry and entry['imdb_id'] is not None:
116
imdb_id = entry['imdb_id']
118
imdb_id = extract_id(entry['imdb_url'])
121
log.warning("No imdb id could be determined for %s" % entry['title'])
124
if not 'quality' in entry:
125
log.warning('No quality found for %s, assigning unknown.' % entry['title'])
126
entry['quality'] = 'unknown'
128
item = feed.session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
131
# This will return UNKNOWN quality if 'ANY' quality
132
minquality = qualities.parse_quality(item.quality)
133
if 'quality' in entry:
134
entry_quality = qualities.parse_quality(entry['quality'])
135
if entry_quality >= minquality:
136
entry['immortal'] = item.immortal
137
log.debug("found %s quality for %s. Need minimum %s" %
138
(entry['title'], entry['quality'],
140
log.info("Accepting %s from queue with quality %s. Force: %s" % (entry['title'], entry['quality'], entry['immortal']))
141
feed.accept(entry, 'imdb-queue - force: %s' % entry['immortal'])
142
# Keep track of entries we accepted, so they can be removed from queue on feed_exit if successful
143
self.accepted_entries[imdb_id] = entry
145
log.debug("imdb-queue rejecting - found "
146
"%s quality for %s. Need minimum %s" %
147
(entry['title'], entry['quality'],
149
# Rejecting, as imdb-queue overrides anything. Don't
150
# want to accidentally grab lower quality than desired.
151
entry['immortal'] = False
152
feed.reject(entry, 'imdb-queue quality '
153
'%s below minimum %s for %s' %
154
(entry_quality.name, minquality.name,
157
log.debugall("%s not in queue with wanted quality, skipping" % entry['title'])
159
log.info("Rejected due to no URL in entry (URLRewrite probably failed):"
160
" %s" % ', '.join(rejected))
162
def on_feed_exit(self, feed):
164
Removes any entries that have not been rejected by another plugin or failed from the queue.
166
for imdb_id, entry in self.accepted_entries.iteritems():
167
if entry in feed.accepted and entry not in feed.failed:
168
# If entry was not rejected or failed, remove from database
169
item = feed.session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
170
feed.session.delete(item)
171
log.debug('%s was successful, removing from imdb-queue' % entry['title'])
174
class ImdbQueueManager(object):
176
Handle IMDb queue management; add, delete and list
179
valid_actions = ['add', 'del', 'list']
184
def optik_imdb_queue(option, opt, value, parser):
187
--imdb-queue (add|del|list) [IMDB_URL|NAME] [quality]
190
print 'Usage: --imdb-queue (add|del|list) [IMDB_URL|NAME] [QUALITY] [FORCE]'
191
# set some usage option so that feeds will be disabled later
192
ImdbQueueManager.options['usage'] = True
195
ImdbQueueManager.options['action'] = parser.rargs[0].lower()
197
if len(parser.rargs) == 1:
199
# 2 args is the minimum allowed (operation + item)
200
if len(parser.rargs) >= 2:
201
ImdbQueueManager.options['what'] = parser.rargs[1]
204
if len(parser.rargs) >= 3:
205
ImdbQueueManager.options['quality'] = parser.rargs[2]
207
ImdbQueueManager.options['quality'] = 'ANY' # TODO: Get default from config somehow?
210
if len(parser.rargs) >= 4:
211
ImdbQueueManager.options['force'] = str_to_boolean(parser.rargs[3])
213
ImdbQueueManager.options['force'] = True
215
def parse_what(self, what):
216
"""Given an imdb id or movie title, looks up from imdb and returns a dict with imdb_id and title keys"""
217
imdb_id = extract_id(what)
221
# Given an imdb id, find title
222
parser = ImdbParser()
224
parser.parse('http://www.imdb.com/title/%s' % imdb_id)
226
raise QueueError('Error parsing info from imdb for %s' % imdb_id)
230
# Given a title, try to do imdb search for id
231
print 'Searching imdb for %s' % what
232
search = ImdbSearch()
233
result = search.smart_match(what)
235
raise QueueError('ERROR: Unable to find any such movie from imdb, use imdb url instead.')
236
imdb_id = extract_id(result['url'])
237
title = result['name']
239
self.options['imdb_id'] = imdb_id
240
self.options['title'] = title
241
return {'title': title, 'imdb_id': imdb_id}
243
def on_process_start(self, feed):
245
Handle IMDb queue management
251
feed.manager.disable_feeds()
253
if 'usage' in self.options:
256
action = self.options['action']
257
if action not in self.valid_actions:
258
self.error('Invalid action, valid actions are: ' + ', '.join(self.valid_actions))
261
# all actions except list require imdb_url to work
263
if not self.options.get('what'):
264
self.error('No URL or NAME given')
267
# Generate imdb_id and movie title from movie name, or imdb_url
269
what = self.parse_what(self.options['what'])
270
except QueueError, e:
273
self.options.update(what)
275
if not self.options.get('title') or not self.options.get('imdb_id'):
276
print 'could not determine movie to add' # TODO: Rethink errors
279
from sqlalchemy.exceptions import OperationalError
283
added = self.queue_add(title=self.options['title'], imdb_id=self.options['imdb_id'],
284
quality=self.options['quality'], force=self.options['force'])
285
except QueueError, e:
288
# This is an invalid quality error, display some more info
289
print 'Recognized qualities are %s' % ', '.join([qual.name for qual in qualities.all()])
290
print 'ANY is the default and can also be used explicitly to specify that quality should be ignored.'
292
print 'Added %s to queue with quality %s' % (added['title'], added['quality'])
293
elif action == 'del':
295
title = self.queue_del(self.options['imdb_id'])
296
except QueueError, e:
299
print '%s removed from queue.' % title
300
elif action == 'list':
302
except OperationalError:
303
log.critical('OperationalError')
305
def error(self, msg):
306
print 'IMDb Queue error: %s' % msg
308
def validate_quality(self, quality):
309
# Check that the quality is valid
310
# Make sure quality is in the format we expect
311
if quality.upper() == 'ANY':
313
elif qualities.get(quality, False):
314
return qualities.common_name(quality)
316
raise QueueError('ERROR! Unknown quality `%s`' % quality, errno=1)
318
def queue_add(self, title=None, imdb_id=None, quality='ANY', force=True):
319
"""Add an item to the queue with the specified quality"""
321
if not title or not imdb_id:
322
# We don't have all the info we need to add movie, do a lookup for more info
323
result = self.parse_what(imdb_id or title)
324
title = result['title']
325
imdb_id = result['imdb_id']
326
quality = self.validate_quality(quality)
330
# check if the item is already queued
331
item = session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
333
item = ImdbQueue(imdb_id, quality, force)
337
return {'title': title, 'imdb_id': imdb_id, 'quality': quality, 'force': force}
339
raise QueueError('ERROR: %s is already in the queue' % title)
341
def queue_del(self, imdb_id):
342
"""Delete the given item from the queue"""
345
# check if the item is queued
346
item = session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
353
raise QueueError('%s is not in the queue' % imdb_id)
355
def queue_edit(self, imdb_id, quality):
356
"""Change the required quality for a movie in the queue"""
357
self.validate_quality(quality)
359
# check if the item is queued
360
item = session.query(ImdbQueue).filter(ImdbQueue.imdb_id == imdb_id).first()
362
item.quality = quality
366
raise QueueError('%s is not in the queue' % imdb_id)
368
def queue_list(self):
369
"""List IMDb queue"""
371
items = self.queue_get()
373
print '%-10s %-45s %-8s %s' % ('IMDB id', 'Title', 'Quality', 'Force')
376
print '%-10s %-45s %-8s %s' % (item.imdb_id, item.title, item.quality, item.immortal)
379
print 'IMDB queue is empty'
384
"""Get the current IMDb queue.
388
List of ImdbQueue objects (detached from session)
392
items = session.query(ImdbQueue).all()
395
# old database does not have title / title not retrieved
397
item.title = self.parse_what(item.imdb_id)['title']
404
register_plugin(FilterImdbQueue, 'imdb_queue')
405
register_plugin(ImdbQueueManager, 'imdb_queue_manager', builtin=True)
406
# Handle if a urlrewrite happens, need to get accurate quality.
407
register_feed_phase(FilterImdbQueue, 'imdbqueue', after='urlrewrite')
409
register_parser_option('--imdb-queue', action='callback', callback=ImdbQueueManager.optik_imdb_queue,
410
help='(add|del|list) [IMDB_URL|NAME] [QUALITY]')