267 lines
10 KiB
Python
267 lines
10 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: UTF-8 -*-
|
||
|
# ----------------------
|
||
|
# Name: tmdb_ui.py Is a simple console user interface for the tmdb_api. The interface is used selection
|
||
|
# of a movie from themoviesdb.org.
|
||
|
# Python Script
|
||
|
# Author: dbr/Ben modified by R.D. Vaughan
|
||
|
# Purpose: Console interface for selecting a movie from themoviedb.org. This interface would be invoked when
|
||
|
# an exact match is not found and the invoking script has specified the "interactive = True" when
|
||
|
# creating an instance of MovieDb().
|
||
|
#
|
||
|
# License:Creative Commons GNU GPL v2
|
||
|
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||
|
#-------------------------------------
|
||
|
__title__ ="tmdb_ui - Is a simple console user interface for the tmdb_api. The interface is used selection of a movie oe person from themoviesdb.org.";
|
||
|
__author__="dbr/Ben modified by R.D. Vaughan"
|
||
|
__purpose__='''Console interface for selecting a movie from themoviedb.org. This interface would be invoked when an exact match is not found and the invoking script has specified the "interactive = True" when creating an instance of MovieDb().
|
||
|
'''
|
||
|
__version__="v0.1.5"
|
||
|
# 0.1.0 Initial development
|
||
|
# 0.1.1 Alpha Release
|
||
|
# 0.1.2 Release bump - no changes to this code
|
||
|
# 0.1.3 Release bump - no changes to this code
|
||
|
# 0.1.4 Release bump - no changes to this code
|
||
|
# 0.1.5 Modified automated selection when there was only one search result. It was causing
|
||
|
# too many incorrect selections.
|
||
|
# Also removed a duplicate initialization of the version number.
|
||
|
|
||
|
|
||
|
"""Contains included user interfaces for tmdb movie/person selection.
|
||
|
|
||
|
A UI is a callback. A class, it's __init__ function takes two arguments:
|
||
|
|
||
|
- config, which is the tmdb config dict, setup in tmdb_api.py
|
||
|
- log, which is tmdb's logger instance (which uses the logging module). You can
|
||
|
call log.info() log.warning() etc
|
||
|
|
||
|
It pass a dictionary "allElements", this is passed a list of dicts, each dict
|
||
|
contains at least the keys "name" (human readable movie name), and "id" (the movies/person)
|
||
|
ID as on themoviedb.org). for movies only if the key 'released' is included the year will be added
|
||
|
to the movie title like 'Avatar (2009)' all other keys will be ignored.
|
||
|
For example:
|
||
|
|
||
|
[{'name': u'Avatar', 'id': u'19995', 'released': '2009-12-25'},
|
||
|
{'name': u'Avatar - Sequel', 'id': u'73181'}]
|
||
|
OR
|
||
|
[{'name': u'Tom Cruise', 'id': u'500'},
|
||
|
{'name': u'Cruise Moylan', 'id': u'77716'}]
|
||
|
|
||
|
|
||
|
The "selectMovieOrPerson" method must return the appropriate dict, or it can raise
|
||
|
TmdbUiAbort (if the selection is aborted), TmdbMovieOrPersonNotFound (if the movie
|
||
|
or person cannot be found).
|
||
|
|
||
|
A simple example callback, which returns a random movie:
|
||
|
|
||
|
>>> import random
|
||
|
>>> from tmdb_api_ui import BaseUI
|
||
|
>>> class RandomUI(BaseUI):
|
||
|
... def selectMovieOrPerson(self, allElements):
|
||
|
... import random
|
||
|
... return random.choice(allElements)
|
||
|
|
||
|
Then to use it..
|
||
|
|
||
|
>>> from tmdb_api import MovieDb
|
||
|
>>> t = MovieDb(custom_ui = RandomUI)
|
||
|
>>> random_matching_movie = t.searchTitle('Avatar')
|
||
|
>>> type(random_matching_movie)
|
||
|
[{"Avatar",'19995'}]
|
||
|
"""
|
||
|
|
||
|
import os, struct, sys, string
|
||
|
from tmdb_exceptions import TmdbUiAbort
|
||
|
|
||
|
class OutStreamEncoder(object):
|
||
|
"""Wraps a stream with an encoder"""
|
||
|
def __init__(self, outstream, encoding=None):
|
||
|
self.out = outstream
|
||
|
if not encoding:
|
||
|
self.encoding = sys.getfilesystemencoding()
|
||
|
else:
|
||
|
self.encoding = encoding
|
||
|
|
||
|
def write(self, obj):
|
||
|
"""Wraps the output stream, encoding Unicode strings with the specified encoding"""
|
||
|
if isinstance(obj, unicode):
|
||
|
try:
|
||
|
self.out.write(obj.encode(self.encoding))
|
||
|
except IOError:
|
||
|
pass
|
||
|
else:
|
||
|
try:
|
||
|
self.out.write(obj)
|
||
|
except IOError:
|
||
|
pass
|
||
|
|
||
|
def __getattr__(self, attr):
|
||
|
"""Delegate everything but write to the stream"""
|
||
|
return getattr(self.out, attr)
|
||
|
sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
|
||
|
sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
|
||
|
|
||
|
# Two routines used for movie title search and matching
|
||
|
def is_punct_char(char):
|
||
|
'''check if char is punctuation char
|
||
|
return True if char is punctuation
|
||
|
return False if char is not punctuation
|
||
|
'''
|
||
|
return char in string.punctuation
|
||
|
|
||
|
def is_not_punct_char(char):
|
||
|
'''check if char is not punctuation char
|
||
|
return True if char is not punctuation
|
||
|
return False if chaar is punctuation
|
||
|
'''
|
||
|
return not is_punct_char(char)
|
||
|
|
||
|
|
||
|
def makeDict(movieORperson):
|
||
|
'''Make a dictionary out of the chosen movie data
|
||
|
return a dictionary of the movie and ancillary data
|
||
|
'''
|
||
|
selection = {}
|
||
|
for key in movieORperson.keys():
|
||
|
selection[key] = movieORperson[key]
|
||
|
return selection
|
||
|
# end makeDict()
|
||
|
|
||
|
|
||
|
class BaseUI:
|
||
|
"""Default non-interactive UI, which auto-selects first results
|
||
|
"""
|
||
|
def __init__(self, config, log, searchTerm=None):
|
||
|
self.config = config
|
||
|
self.log = log
|
||
|
self.searchTerm = searchTerm
|
||
|
|
||
|
def selectMovieOrPerson(self, allElements):
|
||
|
return makeDict([allElements[0]])
|
||
|
|
||
|
|
||
|
class ConsoleUI(BaseUI):
|
||
|
"""Interactively allows the user to select a movie or person from a console based UI
|
||
|
"""
|
||
|
|
||
|
def removeCommonWords(self, title):
|
||
|
'''Remove common words from a title
|
||
|
return title striped of common words
|
||
|
'''
|
||
|
if not title:
|
||
|
return u' '
|
||
|
wordList = [u'the ', u'a ', u' '] # common word list. Leave double space as the last value.
|
||
|
title = title.lower()
|
||
|
for word in wordList:
|
||
|
title = title.replace(word, u'')
|
||
|
if not title:
|
||
|
return u' '
|
||
|
title = title.strip()
|
||
|
return filter(is_not_punct_char, title)
|
||
|
# end removeCommonWords()
|
||
|
|
||
|
|
||
|
def _displayMovie(self, allElements):
|
||
|
"""Helper function, lists movies or people with corresponding ID
|
||
|
"""
|
||
|
print u"themoviedb.org Search Results:"
|
||
|
for i in range(len(allElements[:15])): # list first 15 search results
|
||
|
i_show = i + 1 # Start at more human readable number 1 (not 0)
|
||
|
self.log.debug('Showing allElements[%s] = %s)' % (i_show, allElements[i]))
|
||
|
if allElements[i]['name'] == u'User choses to ignore video':
|
||
|
print u"% 2s -> %s # %s" % (
|
||
|
i_show,
|
||
|
'99999999', "Set this video to be ignored by Jamu with a reference number of '99999999'"
|
||
|
)
|
||
|
continue
|
||
|
if not allElements[i].has_key('released'):
|
||
|
title = allElements[i]['name']
|
||
|
elif len(allElements[i]['released']) > 3:
|
||
|
title = u"%s (%s)" % (allElements[i]['name'], allElements[i]['released'][:4])
|
||
|
else:
|
||
|
title = allElements[i]['name']
|
||
|
if allElements[i]['url'].find('/person/') > -1:
|
||
|
format = u"%2s -> %-30s # http://www.themoviedb.org/person/%s"
|
||
|
else:
|
||
|
format = u"%2s -> %-50s # http://www.themoviedb.org/movie/%s"
|
||
|
print format % (
|
||
|
i_show,
|
||
|
title,
|
||
|
allElements[i]['id']
|
||
|
)
|
||
|
print u"Direct search of themoviedb.org # http://themoviedb.org/"
|
||
|
# end _displayMovie()
|
||
|
|
||
|
def selectMovieOrPerson(self, allElements):
|
||
|
if allElements[0]['url'].find('/movie/') > -1:
|
||
|
morp = u'movie'
|
||
|
else:
|
||
|
morp = u'person'
|
||
|
# Add the ability to select the skip inetref of '99999999' for movies only
|
||
|
if morp == u'movie':
|
||
|
allElements.append( {'id': '99999999', 'name': u'User choses to ignore video'} )
|
||
|
|
||
|
self._displayMovie(allElements)
|
||
|
|
||
|
refsize = 5 # The number of digits required in a TMDB number is directly entered
|
||
|
if len(allElements) == 2 and morp == u'movie':
|
||
|
data = makeDict(allElements[0])
|
||
|
if self.removeCommonWords(data['name']) == self.removeCommonWords(self.searchTerm) and data.has_key('released'):
|
||
|
# Single result, return it!
|
||
|
print u"Automatically selecting only result"
|
||
|
return [data]
|
||
|
|
||
|
if len(allElements) == 1 and morp == u'person':
|
||
|
data = makeDict(allElements[0])
|
||
|
if self.removeCommonWords(data['name']) == self.removeCommonWords(self.searchTerm):
|
||
|
# Single result, return it!
|
||
|
print u"Automatically selecting only result"
|
||
|
return [data]
|
||
|
|
||
|
if self.config['select_first'] is True:
|
||
|
print u"Automatically returning first search result"
|
||
|
return [makeDict(allElements[0])]
|
||
|
|
||
|
while True: # return breaks this loop
|
||
|
try:
|
||
|
print u'Enter choice:\n("Enter" key equals first selection (1)) or input a zero padded 5 digit %s TMDB id number, ? for help):' % morp
|
||
|
ans = raw_input()
|
||
|
except KeyboardInterrupt:
|
||
|
raise TmdbUiAbort("User aborted (^c keyboard interupt)")
|
||
|
except EOFError:
|
||
|
raise TmdbUiAbort("User aborted (EOF received)")
|
||
|
|
||
|
self.log.debug(u'Got choice of: %s' % (ans))
|
||
|
try:
|
||
|
if ans == '': # Enter pressed which equates to the first selection
|
||
|
selected_id = 0
|
||
|
else:
|
||
|
if int(ans) == 0:
|
||
|
raise ValueError
|
||
|
selected_id = int(ans) - 1 # The human entered 1 as first result, not zero
|
||
|
except ValueError: # Input was not number
|
||
|
if ans == "q":
|
||
|
self.log.debug(u'Got quit command (q)')
|
||
|
raise TmdbUiAbort("User aborted ('q' quit command)")
|
||
|
elif ans == "?":
|
||
|
print u"## Help"
|
||
|
print u"# Enter the number that corresponds to the correct movie."
|
||
|
print u"# Paste a TMDB %s ID number (pad with leading zeros to make 5 digits) from themoviedb.org and hit 'Enter'" % morp
|
||
|
print u"# ? - this help"
|
||
|
print u"# q - abort/skip movie selection"
|
||
|
else:
|
||
|
print '! Unknown/Invalid keypress "%s"\n' % (ans)
|
||
|
self.log.debug('Unknown keypress %s' % (ans))
|
||
|
else:
|
||
|
self.log.debug('Trying to return ID: %d' % (selected_id))
|
||
|
try:
|
||
|
data = makeDict(allElements[selected_id])
|
||
|
data['userResponse'] = u'User selected'
|
||
|
return [data]
|
||
|
except IndexError:
|
||
|
if len(ans) == refsize:
|
||
|
return [{'userResponse': u'User input', 'id': u'%d' % int(ans)}]
|
||
|
#end try
|
||
|
#end while not valid_input
|
||
|
# end selectMovieOrPerson()
|