cleaning up .svn files
This commit is contained in:
parent
cc5de9acf4
commit
59df11a25a
|
@ -1,35 +0,0 @@
|
|||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 69
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV
|
||||
END
|
||||
MythStatic.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 83
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythStatic.py
|
||||
END
|
||||
MythData.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 81
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythData.py
|
||||
END
|
||||
MythBase.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 81
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythBase.py
|
||||
END
|
||||
MythFunc.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 81
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/MythFunc.py
|
||||
END
|
||||
__init__.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 81
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/__init__.py
|
||||
END
|
|
@ -1,204 +0,0 @@
|
|||
10
|
||||
|
||||
dir
|
||||
25726
|
||||
http://svn.mythtv.org/svn/tags/release-0-23-1/mythtv/bindings/python/MythTV
|
||||
http://svn.mythtv.org/svn
|
||||
|
||||
|
||||
|
||||
2010-07-22T02:21:00.112292Z
|
||||
25396
|
||||
gigem
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7dbf422c-18fa-0310-86e9-fd20926502f2
|
||||
|
||||
MythStatic.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
2645324c83c882ef4d85662fe2c3c9d7
|
||||
2010-07-22T02:21:00.112292Z
|
||||
25396
|
||||
gigem
|
||||
has-props
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
261
|
||||
|
||||
MythData.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
f973584ab4647d8e5f7a0eb16d51bf16
|
||||
2010-06-07T00:03:02.104550Z
|
||||
25014
|
||||
wagnerrp
|
||||
has-props
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
57441
|
||||
|
||||
ttvdb
|
||||
dir
|
||||
|
||||
MythBase.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
4e2f2010af14b04d6ebb3ead0606faa1
|
||||
2010-05-05T00:45:58.150174Z
|
||||
24420
|
||||
wagnerrp
|
||||
has-props
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
69983
|
||||
|
||||
MythFunc.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
3f6f4d43c4dc47e8c6ab2ab747d8042f
|
||||
2010-05-23T19:52:40.992320Z
|
||||
24818
|
||||
wagnerrp
|
||||
has-props
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
41550
|
||||
|
||||
tmdb
|
||||
dir
|
||||
|
||||
__init__.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
fc69a94c478228940c6be7eee98a8903
|
||||
2010-06-22T03:35:41.632640Z
|
||||
25154
|
||||
wagnerrp
|
||||
has-props
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1532
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
K 14
|
||||
svn:executable
|
||||
V 1
|
||||
*
|
||||
END
|
|
@ -1,5 +0,0 @@
|
|||
K 14
|
||||
svn:executable
|
||||
V 1
|
||||
*
|
||||
END
|
|
@ -1,5 +0,0 @@
|
|||
K 14
|
||||
svn:executable
|
||||
V 1
|
||||
*
|
||||
END
|
|
@ -1,5 +0,0 @@
|
|||
K 14
|
||||
svn:executable
|
||||
V 1
|
||||
*
|
||||
END
|
|
@ -1,17 +0,0 @@
|
|||
K 13
|
||||
svn:eol-style
|
||||
V 6
|
||||
native
|
||||
K 14
|
||||
svn:executable
|
||||
V 1
|
||||
*
|
||||
K 12
|
||||
svn:keywords
|
||||
V 31
|
||||
Id Date Revision Author HeadURL
|
||||
K 13
|
||||
svn:mime-type
|
||||
V 13
|
||||
text/x-python
|
||||
END
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Contains any static and global variables for MythTV Python Bindings
|
||||
"""
|
||||
|
||||
OWN_VERSION = (0,23,1,0)
|
||||
SCHEMA_VERSION = 1254
|
||||
MVSCHEMA_VERSION = 1032
|
||||
NVSCHEMA_VERSION = 1004
|
||||
PROTO_VERSION = 23056
|
||||
PROGRAM_FIELDS = 47
|
||||
BACKEND_SEP = '[]:[]'
|
|
@ -1,54 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
__all__ = ['MythStatic', \
|
||||
\
|
||||
'DictData', 'DBData', 'DBDataWrite', 'DBDataCRef', 'MythDBConn', \
|
||||
'MythBEConn', 'MythXMLConn', 'MythLog', 'MythError', \
|
||||
'StorageGroup', 'Grabber', \
|
||||
\
|
||||
'ftopen', 'FileTransfer', 'FreeSpace', 'Program', 'Record', \
|
||||
'Recorded', 'RecordedProgram', 'OldRecorded', 'Job', 'Channel', \
|
||||
'Guide', 'Video', 'VideoGrabber', 'NetVisionRSSItem', \
|
||||
'NetVisionTreeItem', 'NetVisionSite', 'NetVisionGrabber', \
|
||||
\
|
||||
'MythBE', 'Frontend', 'MythDB', 'MythVideo', 'MythXML']
|
||||
|
||||
import26 = """
|
||||
import warnings
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('ignore')
|
||||
from MythStatic import *
|
||||
from MythBase import *
|
||||
from MythData import *
|
||||
from MythFunc import *
|
||||
"""
|
||||
|
||||
import25 = """
|
||||
from MythStatic import *
|
||||
from MythBase import *
|
||||
from MythData import *
|
||||
from MythFunc import *
|
||||
"""
|
||||
|
||||
from sys import version_info
|
||||
if version_info >= (2, 6): # 2.6 or newer
|
||||
exec(import26)
|
||||
else:
|
||||
exec(import25)
|
||||
|
||||
__version__ = OWN_VERSION
|
||||
MythStatic.mysqldb = MySQLdb.__version__
|
||||
|
||||
if __name__ == '__main__':
|
||||
banner = 'MythTV Python interactive shell.'
|
||||
import code
|
||||
try:
|
||||
import readline, rlcompleter
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
readline.parse_and_bind("tab: complete")
|
||||
banner += ' TAB completion available.'
|
||||
namespace = globals().copy()
|
||||
namespace.update(locals())
|
||||
code.InteractiveConsole(namespace).interact(banner)
|
|
@ -1,29 +0,0 @@
|
|||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 74
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb
|
||||
END
|
||||
__init__.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb/__init__.py
|
||||
END
|
||||
tmdb_api.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb/tmdb_api.py
|
||||
END
|
||||
tmdb_ui.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 85
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb/tmdb_ui.py
|
||||
END
|
||||
tmdb_exceptions.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 93
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb/tmdb_exceptions.py
|
||||
END
|
|
@ -1,164 +0,0 @@
|
|||
10
|
||||
|
||||
dir
|
||||
25726
|
||||
http://svn.mythtv.org/svn/tags/release-0-23-1/mythtv/bindings/python/MythTV/tmdb
|
||||
http://svn.mythtv.org/svn
|
||||
|
||||
|
||||
|
||||
2010-04-29T22:38:50.878564Z
|
||||
24305
|
||||
robertm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7dbf422c-18fa-0310-86e9-fd20926502f2
|
||||
|
||||
__init__.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
d41d8cd98f00b204e9800998ecf8427e
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
0
|
||||
|
||||
tmdb_api.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
80afb5dbadea4de7d5ed5d0e1fa956bf
|
||||
2010-04-29T22:38:50.878564Z
|
||||
24305
|
||||
robertm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
43403
|
||||
|
||||
tmdb_ui.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
89285d26d830a7a0fc71f92e4f60f815
|
||||
2010-04-12T22:36:01.726283Z
|
||||
24097
|
||||
robertm
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
10747
|
||||
|
||||
tmdb_exceptions.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
d6d57ccfa5baa9fd79eb04eb3b6e7aff
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1384
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,45 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
# ----------------------
|
||||
# Name: tmdb_exceptions - Custom exceptions used or raised by tmdb_api
|
||||
# Python Script
|
||||
# Author: dbr/Ben modified by R.D. Vaughan
|
||||
# Purpose: Custom exceptions used or raised by tmdb_api
|
||||
#
|
||||
# License:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
#-------------------------------------
|
||||
__title__ ="tmdb_exceptions - Custom exceptions used or raised by tmdb_api";
|
||||
__author__="dbr/Ben modified by R.D. Vaughan"
|
||||
__version__="v0.1.4"
|
||||
# 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
|
||||
|
||||
__all__ = ["TmdBaseError", "TmdHttpError", "TmdXmlError", "TmdbUiAbort", "TmdbMovieOrPersonNotFound", ]
|
||||
|
||||
# Start of code used to access themoviedb.org api
|
||||
class TmdBaseError(Exception):
|
||||
pass
|
||||
|
||||
class TmdHttpError(TmdBaseError):
|
||||
def __repr__(self): # Display the type of error
|
||||
return None
|
||||
# end __repr__
|
||||
|
||||
class TmdXmlError(TmdBaseError):
|
||||
def __repr__(self): # Display the type of error
|
||||
return None
|
||||
# end __repr__
|
||||
|
||||
class TmdbMovieOrPersonNotFound(TmdBaseError):
|
||||
def __repr__(self):
|
||||
return None
|
||||
# end __repr__
|
||||
|
||||
class TmdbUiAbort(TmdBaseError):
|
||||
def __repr__(self):
|
||||
return None
|
||||
# end __repr__
|
|
@ -1,266 +0,0 @@
|
|||
#!/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()
|
|
@ -1,41 +0,0 @@
|
|||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 75
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb
|
||||
END
|
||||
tvdb_api.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 87
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/tvdb_api.py
|
||||
END
|
||||
ttvdb-example.conf
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 94
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/ttvdb-example.conf
|
||||
END
|
||||
tvdb_ui.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 86
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/tvdb_ui.py
|
||||
END
|
||||
__init__.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 87
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/__init__.py
|
||||
END
|
||||
tvdb_exceptions.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 94
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/tvdb_exceptions.py
|
||||
END
|
||||
cache.py
|
||||
K 25
|
||||
svn:wc:ra_dav:version-url
|
||||
V 84
|
||||
/svn/!svn/ver/25397/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb/cache.py
|
||||
END
|
|
@ -1,232 +0,0 @@
|
|||
10
|
||||
|
||||
dir
|
||||
25726
|
||||
http://svn.mythtv.org/svn/tags/release-0-23-1/mythtv/bindings/python/MythTV/ttvdb
|
||||
http://svn.mythtv.org/svn
|
||||
|
||||
|
||||
|
||||
2010-02-01T05:23:09.925099Z
|
||||
23416
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7dbf422c-18fa-0310-86e9-fd20926502f2
|
||||
|
||||
tvdb_api.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
554f84950a1f7cc2bab09477c3798f18
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
29233
|
||||
|
||||
ttvdb-example.conf
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
8a9a6d831a1892828eb38318b114f0b0
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5590
|
||||
|
||||
tvdb_ui.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
305a22b10a8be8dfef8ff90c399d9a38
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
4387
|
||||
|
||||
__init__.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
d41d8cd98f00b204e9800998ecf8427e
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
0
|
||||
|
||||
tvdb_exceptions.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
90ce82c602de1cc41d603c01563d3bfb
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1168
|
||||
|
||||
cache.py
|
||||
file
|
||||
|
||||
|
||||
|
||||
|
||||
2010-08-18T01:49:43.000000Z
|
||||
e904998a5a3c1e088dc5b00a57947127
|
||||
2010-01-29T01:39:37.380922Z
|
||||
23354
|
||||
wagnerrp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7345
|
||||
|
|
@ -1,232 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#encoding:utf-8
|
||||
#author:dbr/Ben
|
||||
#project:tvdb_api
|
||||
#repository:http://github.com/dbr/tvdb_api
|
||||
#license:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
|
||||
"""
|
||||
urllib2 caching handler
|
||||
Modified from http://code.activestate.com/recipes/491261/
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
|
||||
__author__ = "dbr/Ben"
|
||||
__version__ = "1.2.1"
|
||||
|
||||
import os
|
||||
import time
|
||||
import errno
|
||||
import httplib
|
||||
import urllib2
|
||||
import StringIO
|
||||
from hashlib import md5
|
||||
from threading import RLock
|
||||
|
||||
cache_lock = RLock()
|
||||
|
||||
def locked_function(origfunc):
|
||||
"""Decorator to execute function under lock"""
|
||||
def wrapped(*args, **kwargs):
|
||||
cache_lock.acquire()
|
||||
try:
|
||||
return origfunc(*args, **kwargs)
|
||||
finally:
|
||||
cache_lock.release()
|
||||
return wrapped
|
||||
|
||||
def calculate_cache_path(cache_location, url):
|
||||
"""Checks if [cache_location]/[hash_of_url].headers and .body exist
|
||||
"""
|
||||
thumb = md5(url).hexdigest()
|
||||
header = os.path.join(cache_location, thumb + ".headers")
|
||||
body = os.path.join(cache_location, thumb + ".body")
|
||||
return header, body
|
||||
|
||||
def check_cache_time(path, max_age):
|
||||
"""Checks if a file has been created/modified in the [last max_age] seconds.
|
||||
False means the file is too old (or doesn't exist), True means it is
|
||||
up-to-date and valid"""
|
||||
if not os.path.isfile(path):
|
||||
return False
|
||||
cache_modified_time = os.stat(path).st_mtime
|
||||
time_now = time.time()
|
||||
if cache_modified_time < time_now - max_age:
|
||||
# Cache is old
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@locked_function
|
||||
def exists_in_cache(cache_location, url, max_age):
|
||||
"""Returns if header AND body cache file exist (and are up-to-date)"""
|
||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
||||
if os.path.exists(hpath) and os.path.exists(bpath):
|
||||
return(
|
||||
check_cache_time(hpath, max_age)
|
||||
and check_cache_time(bpath, max_age)
|
||||
)
|
||||
else:
|
||||
# File does not exist
|
||||
return False
|
||||
|
||||
@locked_function
|
||||
def store_in_cache(cache_location, url, response):
|
||||
"""Tries to store response in cache."""
|
||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
||||
try:
|
||||
outf = open(hpath, "w")
|
||||
headers = str(response.info())
|
||||
outf.write(headers)
|
||||
outf.close()
|
||||
|
||||
outf = open(bpath, "w")
|
||||
outf.write(response.read())
|
||||
outf.close()
|
||||
except IOError:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
class CacheHandler(urllib2.BaseHandler):
|
||||
"""Stores responses in a persistant on-disk cache.
|
||||
|
||||
If a subsequent GET request is made for the same URL, the stored
|
||||
response is returned, saving time, resources and bandwidth
|
||||
"""
|
||||
@locked_function
|
||||
def __init__(self, cache_location, max_age = 21600):
|
||||
"""The location of the cache directory"""
|
||||
self.max_age = max_age
|
||||
self.cache_location = cache_location
|
||||
if not os.path.exists(self.cache_location):
|
||||
try:
|
||||
os.mkdir(self.cache_location)
|
||||
except OSError, e:
|
||||
if e.errno == errno.EEXIST and os.path.isdir(self.cache_location):
|
||||
# File exists, and it's a directory,
|
||||
# another process beat us to creating this dir, that's OK.
|
||||
pass
|
||||
else:
|
||||
# Our target dir is already a file, or different error,
|
||||
# relay the error!
|
||||
raise OSError(e)
|
||||
|
||||
def default_open(self, request):
|
||||
"""Handles GET requests, if the response is cached it returns it
|
||||
"""
|
||||
if request.get_method() is not "GET":
|
||||
return None # let the next handler try to handle the request
|
||||
|
||||
if exists_in_cache(
|
||||
self.cache_location, request.get_full_url(), self.max_age
|
||||
):
|
||||
return CachedResponse(
|
||||
self.cache_location,
|
||||
request.get_full_url(),
|
||||
set_cache_header = True
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
def http_response(self, request, response):
|
||||
"""Gets a HTTP response, if it was a GET request and the status code
|
||||
starts with 2 (200 OK etc) it caches it and returns a CachedResponse
|
||||
"""
|
||||
if (request.get_method() == "GET"
|
||||
and str(response.code).startswith("2")
|
||||
):
|
||||
if 'x-local-cache' not in response.info():
|
||||
# Response is not cached
|
||||
set_cache_header = store_in_cache(
|
||||
self.cache_location,
|
||||
request.get_full_url(),
|
||||
response
|
||||
)
|
||||
else:
|
||||
set_cache_header = True
|
||||
#end if x-cache in response
|
||||
|
||||
return CachedResponse(
|
||||
self.cache_location,
|
||||
request.get_full_url(),
|
||||
set_cache_header = set_cache_header
|
||||
)
|
||||
else:
|
||||
return response
|
||||
|
||||
class CachedResponse(StringIO.StringIO):
|
||||
"""An urllib2.response-like object for cached responses.
|
||||
|
||||
To determine if a response is cached or coming directly from
|
||||
the network, check the x-local-cache header rather than the object type.
|
||||
"""
|
||||
|
||||
@locked_function
|
||||
def __init__(self, cache_location, url, set_cache_header=True):
|
||||
self.cache_location = cache_location
|
||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
||||
|
||||
StringIO.StringIO.__init__(self, file(bpath).read())
|
||||
|
||||
self.url = url
|
||||
self.code = 200
|
||||
self.msg = "OK"
|
||||
headerbuf = file(hpath).read()
|
||||
if set_cache_header:
|
||||
headerbuf += "x-local-cache: %s\r\n" % (bpath)
|
||||
self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf))
|
||||
|
||||
def info(self):
|
||||
"""Returns headers
|
||||
"""
|
||||
return self.headers
|
||||
|
||||
def geturl(self):
|
||||
"""Returns original URL
|
||||
"""
|
||||
return self.url
|
||||
|
||||
@locked_function
|
||||
def recache(self):
|
||||
new_request = urllib2.urlopen(self.url)
|
||||
set_cache_header = store_in_cache(
|
||||
self.cache_location,
|
||||
new_request.url,
|
||||
new_request
|
||||
)
|
||||
CachedResponse.__init__(self, self.cache_location, self.url, True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
"""Quick test/example of CacheHandler"""
|
||||
opener = urllib2.build_opener(CacheHandler("/tmp/"))
|
||||
response = opener.open("http://google.com")
|
||||
print response.headers
|
||||
print "Response:", response.read()
|
||||
|
||||
response.recache()
|
||||
print response.headers
|
||||
print "After recache:", response.read()
|
||||
|
||||
# Test usage in threads
|
||||
from threading import Thread
|
||||
class CacheThreadTest(Thread):
|
||||
lastdata = None
|
||||
def run(self):
|
||||
req = opener.open("http://google.com")
|
||||
newdata = req.read()
|
||||
if self.lastdata is None:
|
||||
self.lastdata = newdata
|
||||
assert self.lastdata == newdata, "Data was not consistent, uhoh"
|
||||
req.recache()
|
||||
threads = [CacheThreadTest() for x in range(50)]
|
||||
print "Starting threads"
|
||||
[t.start() for t in threads]
|
||||
print "..done"
|
||||
print "Joining threads"
|
||||
[t.join() for t in threads]
|
||||
print "..done"
|
||||
main()
|
|
@ -1,109 +0,0 @@
|
|||
[File ttvdb-example.conf]
|
||||
#-------------------------------------
|
||||
# Name: ttvdb-example.conf
|
||||
# Project: ttvdb
|
||||
# Configuration file
|
||||
# Author: R.D. Vaughan
|
||||
# Version: 0.1.0 - Initial alpha release
|
||||
# Version: 0.8.9 - version changed to match the current ttvdb release number
|
||||
# Version: 0.9.5 - Changed regex pattern strings to support multi-langiage file names
|
||||
#
|
||||
# License:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
#-------------------------------------
|
||||
#
|
||||
# START season name overrides section --------------------------------------------------------------
|
||||
#####
|
||||
# PURPOSE: TV program sources such as Schedule Direct series names do not always match the series
|
||||
# names on thetvdb.com. This section allow you to override series names to reduce the
|
||||
# need for manual interaction and more accurate matching with thetvdb.com TV series wiki.
|
||||
#
|
||||
# FORMAT:
|
||||
# Any line starting with "#" is treated as a comment
|
||||
# Any blank line is ignored
|
||||
# All other lines must have two fields in this specific order:
|
||||
# 1st) The series name or the %TITLE% field as passed by MythTV and a trailing ':' character
|
||||
# 2nd) The thetvdb.com series id (SID) as specified for the series on thetvdb.com wiki. This will
|
||||
# override the series name to eliminate the need for manual interaction.
|
||||
# NOTE: Included here. but disabled are examples of current (2009) TV shows that Schedule Direct
|
||||
# has different TV series names then what is on thetvdb.com wiki. If you are searching for
|
||||
# series based on the Schedule Direct names as MythTV does then the wrong TV series will be
|
||||
# identified. You can pick the correct series by using interactive mode but this does not
|
||||
# help with unattended processing. This section allows you to specify the correct TV series
|
||||
# on thetvbd.com despite the name in Schedule Direct. Luckily there are not many TV series
|
||||
# that require overrides.
|
||||
#####
|
||||
[series_name_override]
|
||||
# Specify recorded "Life On Mars" shows as the US version
|
||||
#Life on Mars:82289
|
||||
# For overnight episode updates when a filename is used
|
||||
#Life on Mars (US):82289
|
||||
|
||||
# Specify recorded "Eleventh Hour" shows as the US version
|
||||
#Eleventh Hour:83066
|
||||
# For overnight episode updates when a filename is used
|
||||
#Eleventh Hour (US):83066
|
||||
|
||||
# Specify recorded "Frontline" or "Frontline/World" shows as the "Frontline PBS" version
|
||||
#Frontline/World:80646
|
||||
#Frontline:80646
|
||||
|
||||
# Specify recorded "The Beast" shows are the 2009 version
|
||||
#The Beast:83805
|
||||
# For overnight episode updates when a filename is used
|
||||
#The Beast (2009):83805
|
||||
|
||||
# Specify recorded "Castle" shows are the 2009 version
|
||||
#Castle:83462
|
||||
# For overnight episode updates when a filename is used
|
||||
#Castle (2009):83462
|
||||
|
||||
# Specify recorded "Battlestar Galactica" shows are the 2003 version
|
||||
#Battlestar Galactica:73545
|
||||
# For overnight episode updates when a filename is used
|
||||
#Battlestar Galactica (2003):73545
|
||||
# END season name overrides section --------------------------------------------------------------
|
||||
|
||||
# START episode name massaging section --------------------------------------------------------------
|
||||
#####
|
||||
# PURPOSE: TV program sources such as Schedule Direct episode names do not always match the episode
|
||||
# names on thetvdb.com. This section allow you to massage episode names to reduce the
|
||||
# need for manual interaction and more accurate matching with thetvdb.com TV series wiki.
|
||||
# Alter the episode names for a series to reduce the need for manual interaction
|
||||
# and more accurate matching with thetvdb.com TV series wiki. See example below.
|
||||
#
|
||||
# FORMAT:
|
||||
# All lines must in the following format:
|
||||
# 1st) The series name or the %TITLE% field as passed by MythTV and a trailing ':' character
|
||||
# 2nd) Pairs of values separated by commas. The first value is the search text to match to text within
|
||||
# the episode name such as the %SUBTITLE% field passed by MythTV and the text to replace the
|
||||
# matched searched text. If the replacement text contains a space then surround that text with
|
||||
# the '"' double quotation characters.
|
||||
# E.g. "24": PM," PM", AM, " AM" will turn the episode name
|
||||
# "Day 7: 11:00AM to 12:00PM" into "Day 7: 11:00 AM to 12:00 PM"
|
||||
#
|
||||
#####
|
||||
[ep_name_massage]
|
||||
#24: PM," PM", AM, " AM",M-,"M - "
|
||||
# END episode name massaging section --------------------------------------------------------------
|
||||
|
||||
# START regex section------------------------------------------------------------------------------
|
||||
#####
|
||||
# NOTE: If you do not understand regex expressions DO NOT USE this section.
|
||||
# PURPOSE: This section adds regex strings used to parse video file names when extracting
|
||||
# the following: series name, season number, episode number. Essential when downloading
|
||||
# metadata from mythtvfrontend->mythvideo when using ttvdb. You only need to add a regex
|
||||
# string if ttvdb cannot extract the required information from your video file names.
|
||||
# NOTE: ANY % percent sign in the expression must be doubled (e.g. a % must be changed to %% )
|
||||
# NOTE: The key value (e.g. "regex##") must be unique for each value.
|
||||
# NOTE: DO NOT surround the regex pattern string with the traditional ''' three single quotes
|
||||
#
|
||||
# "regex01" is an example video file name "foo_S01_12" where:
|
||||
# series name is "foo", season number starts at "S" and episode number starts after '_'
|
||||
# foo_[s01]_[e01]
|
||||
#####
|
||||
[regex]
|
||||
# foo_S01_12
|
||||
regex01: ^(.+?)[ \._\-][Ss]([0-9]+)_([0-9]+)[^\\/]*$
|
||||
# END regex section------------------------------------------------------------------------------
|
||||
|
|
@ -1,805 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#encoding:utf-8
|
||||
#author:dbr/Ben
|
||||
#project:tvdb_api
|
||||
#repository:http://github.com/dbr/tvdb_api
|
||||
#license:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
|
||||
"""Simple-to-use Python interface to The TVDB's API (www.thetvdb.com)
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> from tvdb_api import Tvdb
|
||||
>>> t = Tvdb()
|
||||
>>> t['Lost'][4][11]['episodename']
|
||||
u'Cabin Fever'
|
||||
"""
|
||||
__author__ = "dbr/Ben"
|
||||
__version__ = "1.2.1"
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urllib
|
||||
import urllib2
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
try:
|
||||
import xml.etree.cElementTree as ElementTree
|
||||
except ImportError:
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
|
||||
from cache import CacheHandler
|
||||
|
||||
from tvdb_ui import BaseUI, ConsoleUI
|
||||
from tvdb_exceptions import (tvdb_error, tvdb_userabort, tvdb_shownotfound,
|
||||
tvdb_seasonnotfound, tvdb_episodenotfound, tvdb_attributenotfound)
|
||||
|
||||
class ShowContainer(dict):
|
||||
"""Simple dict that holds a series of Show instances
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Show(dict):
|
||||
"""Holds a dict of seasons, and show data.
|
||||
"""
|
||||
def __init__(self):
|
||||
dict.__init__(self)
|
||||
self.data = {}
|
||||
|
||||
def __repr__(self):
|
||||
return "<Show %s (containing %s seasons)>" % (
|
||||
self.data.get(u'seriesname', 'instance'),
|
||||
len(self)
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
# Key is an episode, return it
|
||||
return dict.__getitem__(self, key)
|
||||
|
||||
if key in self.data:
|
||||
# Non-numeric request is for show-data
|
||||
return dict.__getitem__(self.data, key)
|
||||
|
||||
# Data wasn't found, raise appropriate error
|
||||
if isinstance(key, int) or key.isdigit():
|
||||
# Episode number x was not found
|
||||
raise tvdb_seasonnotfound("Could not find season %s" % (repr(key)))
|
||||
else:
|
||||
# If it's not numeric, it must be an attribute name, which
|
||||
# doesn't exist, so attribute error.
|
||||
raise tvdb_attributenotfound("Cannot find attribute %s" % (repr(key)))
|
||||
|
||||
def search(self, term = None, key = None):
|
||||
"""
|
||||
Search all episodes in show. Can search all data, or a specific key (for
|
||||
example, episodename)
|
||||
|
||||
Always returns an array (can be empty). First index contains the first
|
||||
match, and so on.
|
||||
|
||||
Each array index is an Episode() instance, so doing
|
||||
search_results[0]['episodename'] will retrieve the episode name of the
|
||||
first match.
|
||||
|
||||
Search terms are converted to lower case (unicode) strings.
|
||||
|
||||
# Examples
|
||||
|
||||
These examples assume t is an instance of Tvdb():
|
||||
|
||||
>>> t = Tvdb()
|
||||
>>>
|
||||
|
||||
To search for all episodes of Scrubs with a bit of data
|
||||
containing "my first day":
|
||||
|
||||
>>> t['Scrubs'].search("my first day")
|
||||
[<Episode 01x01 - My First Day>]
|
||||
>>>
|
||||
|
||||
Search for "My Name Is Earl" episode named "Faked His Own Death":
|
||||
|
||||
>>> t['My Name Is Earl'].search('Faked His Own Death', key = 'episodename')
|
||||
[<Episode 01x04 - Faked His Own Death>]
|
||||
>>>
|
||||
|
||||
To search Scrubs for all episodes with "mentor" in the episode name:
|
||||
|
||||
>>> t['scrubs'].search('mentor', key = 'episodename')
|
||||
[<Episode 01x02 - My Mentor>, <Episode 03x15 - My Tormented Mentor>]
|
||||
>>>
|
||||
|
||||
# Using search results
|
||||
|
||||
>>> results = t['Scrubs'].search("my first")
|
||||
>>> print results[0]['episodename']
|
||||
My First Day
|
||||
>>> for x in results: print x['episodename']
|
||||
My First Day
|
||||
My First Step
|
||||
My First Kill
|
||||
>>>
|
||||
"""
|
||||
results = []
|
||||
for cur_season in self.values():
|
||||
searchresult = cur_season.search(term = term, key = key)
|
||||
if len(searchresult) != 0:
|
||||
results.extend(searchresult)
|
||||
#end for cur_season
|
||||
return results
|
||||
|
||||
|
||||
class Season(dict):
|
||||
def __repr__(self):
|
||||
return "<Season instance (containing %s episodes)>" % (
|
||||
len(self.keys())
|
||||
)
|
||||
|
||||
def __getitem__(self, episode_number):
|
||||
if episode_number not in self:
|
||||
raise tvdb_episodenotfound("Could not find episode %s" % (repr(episode_number)))
|
||||
else:
|
||||
return dict.__getitem__(self, episode_number)
|
||||
|
||||
def search(self, term = None, key = None):
|
||||
"""Search all episodes in season, returns a list of matching Episode
|
||||
instances.
|
||||
|
||||
>>> t = Tvdb()
|
||||
>>> t['scrubs'][1].search('first day')
|
||||
[<Episode 01x01 - My First Day>]
|
||||
>>>
|
||||
|
||||
See Show.search documentation for further information on search
|
||||
"""
|
||||
results = []
|
||||
for ep in self.values():
|
||||
searchresult = ep.search(term = term, key = key)
|
||||
if searchresult is not None:
|
||||
results.append(
|
||||
searchresult
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
class Episode(dict):
|
||||
def __repr__(self):
|
||||
seasno = int(self.get(u'seasonnumber', 0))
|
||||
epno = int(self.get(u'episodenumber', 0))
|
||||
epname = self.get(u'episodename')
|
||||
if epname is not None:
|
||||
return "<Episode %02dx%02d - %s>" % (seasno, epno, epname)
|
||||
else:
|
||||
return "<Episode %02dx%02d>" % (seasno, epno)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
raise tvdb_attributenotfound("Cannot find attribute %s" % (repr(key)))
|
||||
|
||||
def search(self, term = None, key = None):
|
||||
"""Search episode data for term, if it matches, return the Episode (self).
|
||||
The key parameter can be used to limit the search to a specific element,
|
||||
for example, episodename.
|
||||
|
||||
This primarily for use use by Show.search and Season.search. See
|
||||
Show.search for further information on search
|
||||
|
||||
Simple example:
|
||||
|
||||
>>> e = Episode()
|
||||
>>> e['episodename'] = "An Example"
|
||||
>>> e.search("examp")
|
||||
<Episode 00x00 - An Example>
|
||||
>>>
|
||||
|
||||
Limiting by key:
|
||||
|
||||
>>> e.search("examp", key = "episodename")
|
||||
<Episode 00x00 - An Example>
|
||||
>>>
|
||||
"""
|
||||
if term == None:
|
||||
raise TypeError("must supply string to search for (contents)")
|
||||
|
||||
term = unicode(term).lower()
|
||||
for cur_key, cur_value in self.items():
|
||||
cur_key, cur_value = unicode(cur_key).lower(), unicode(cur_value).lower()
|
||||
if key is not None and cur_key != key:
|
||||
# Do not search this key
|
||||
continue
|
||||
if cur_value.find( unicode(term).lower() ) > -1:
|
||||
return self
|
||||
#end if cur_value.find()
|
||||
#end for cur_key, cur_value
|
||||
|
||||
|
||||
class Actors(list):
|
||||
"""Holds all Actor instances for a show
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Actor(dict):
|
||||
"""Represents a single actor. Should contain..
|
||||
|
||||
id,
|
||||
image,
|
||||
name,
|
||||
role,
|
||||
sortorder
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "<Actor \"%s\">" % (self.get("name"))
|
||||
|
||||
|
||||
class Tvdb:
|
||||
"""Create easy-to-use interface to name of season/episode name
|
||||
>>> t = Tvdb()
|
||||
>>> t['Scrubs'][1][24]['episodename']
|
||||
u'My Last Day'
|
||||
"""
|
||||
def __init__(self,
|
||||
interactive = False,
|
||||
select_first = False,
|
||||
debug = False,
|
||||
cache = True,
|
||||
banners = False,
|
||||
actors = False,
|
||||
custom_ui = None,
|
||||
language = None,
|
||||
search_all_languages = False,
|
||||
apikey = None):
|
||||
"""interactive (True/False):
|
||||
When True, uses built-in console UI is used to select the correct show.
|
||||
When False, the first search result is used.
|
||||
|
||||
select_first (True/False):
|
||||
Automatically selects the first series search result (rather
|
||||
than showing the user a list of more than one series).
|
||||
Is overridden by interactive = False, or specifying a custom_ui
|
||||
|
||||
debug (True/False):
|
||||
shows verbose debugging information
|
||||
|
||||
cache (True/False/str/unicode):
|
||||
Retrieved XML are persisted to to disc. If true, stores in tvdb_api
|
||||
folder under your systems TEMP_DIR, if set to str/unicode instance it
|
||||
will use this as the cache location. If False, disables caching.
|
||||
|
||||
banners (True/False):
|
||||
Retrieves the banners for a show. These are accessed
|
||||
via the _banners key of a Show(), for example:
|
||||
|
||||
>>> Tvdb(banners=True)['scrubs']['_banners'].keys()
|
||||
['fanart', 'poster', 'series', 'season']
|
||||
|
||||
actors (True/False):
|
||||
Retrieves a list of the actors for a show. These are accessed
|
||||
via the _actors key of a Show(), for example:
|
||||
|
||||
>>> t = Tvdb(actors=True)
|
||||
>>> t['scrubs']['_actors'][0]['name']
|
||||
u'Zach Braff'
|
||||
|
||||
custom_ui (tvdb_ui.BaseUI subclass):
|
||||
A callable subclass of tvdb_ui.BaseUI (overrides interactive option)
|
||||
|
||||
language (2 character language abbreviation):
|
||||
The language of the returned data. Is also the language search
|
||||
uses. Default is "en" (English). For full list, run..
|
||||
|
||||
>>> Tvdb().config['valid_languages'] #doctest: +ELLIPSIS
|
||||
['da', 'fi', 'nl', ...]
|
||||
|
||||
search_all_languages (True/False):
|
||||
By default, Tvdb will only search in the language specified using
|
||||
the language option. When this is True, it will search for the
|
||||
show in and language
|
||||
|
||||
apikey (str/unicode):
|
||||
Override the default thetvdb.com API key. By default it will use
|
||||
tvdb_api's own key (fine for small scripts), but you can use your
|
||||
own key if desired - this is recommended if you are embedding
|
||||
tvdb_api in a larger application)
|
||||
See http://thetvdb.com/?tab=apiregister to get your own key
|
||||
"""
|
||||
self.shows = ShowContainer() # Holds all Show classes
|
||||
self.corrections = {} # Holds show-name to show_id mapping
|
||||
|
||||
self.config = {}
|
||||
|
||||
if apikey is not None:
|
||||
self.config['apikey'] = apikey
|
||||
else:
|
||||
self.config['apikey'] = "0629B785CE550C8D" # tvdb_api's API key
|
||||
|
||||
self.config['debug_enabled'] = debug # show debugging messages
|
||||
|
||||
self.config['custom_ui'] = custom_ui
|
||||
|
||||
self.config['interactive'] = interactive # prompt for correct series?
|
||||
|
||||
self.config['select_first'] = select_first
|
||||
|
||||
self.config['search_all_languages'] = search_all_languages
|
||||
|
||||
if cache is True:
|
||||
self.config['cache_enabled'] = True
|
||||
self.config['cache_location'] = self._getTempDir()
|
||||
elif isinstance(cache, basestring):
|
||||
self.config['cache_enabled'] = True
|
||||
self.config['cache_location'] = cache
|
||||
else:
|
||||
self.config['cache_enabled'] = False
|
||||
|
||||
if self.config['cache_enabled']:
|
||||
self.urlopener = urllib2.build_opener(
|
||||
CacheHandler(self.config['cache_location'])
|
||||
)
|
||||
else:
|
||||
self.urlopener = urllib2.build_opener()
|
||||
|
||||
self.config['banners_enabled'] = banners
|
||||
self.config['actors_enabled'] = actors
|
||||
|
||||
self.log = self._initLogger() # Setups the logger (self.log.debug() etc)
|
||||
|
||||
# List of language from http://www.thetvdb.com/api/0629B785CE550C8D/languages.xml
|
||||
# Hard-coded here as it is realtively static, and saves another HTTP request, as
|
||||
# recommended on http://thetvdb.com/wiki/index.php/API:languages.xml
|
||||
self.config['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"
|
||||
]
|
||||
|
||||
if language is None:
|
||||
self.config['language'] = "en"
|
||||
elif language not in self.config['valid_languages']:
|
||||
raise ValueError("Invalid language %s, options are: %s" % (
|
||||
language, self.config['valid_languages']
|
||||
))
|
||||
else:
|
||||
self.config['language'] = language
|
||||
|
||||
# The following url_ configs are based of the
|
||||
# http://thetvdb.com/wiki/index.php/Programmers_API
|
||||
self.config['base_url'] = "http://www.thetvdb.com"
|
||||
|
||||
if self.config['search_all_languages']:
|
||||
self.config['url_getSeries'] = "%(base_url)s/api/GetSeries.php?seriesname=%%s&language=all" % self.config
|
||||
else:
|
||||
self.config['url_getSeries'] = "%(base_url)s/api/GetSeries.php?seriesname=%%s&language=%(language)s" % self.config
|
||||
|
||||
self.config['url_epInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/all/%(language)s.xml" % self.config
|
||||
|
||||
self.config['url_seriesInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/%(language)s.xml" % self.config
|
||||
self.config['url_actorsInfo'] = "%(base_url)s/api/%(apikey)s/series/%%s/actors.xml" % self.config
|
||||
|
||||
self.config['url_seriesBanner'] = "%(base_url)s/api/%(apikey)s/series/%%s/banners.xml" % self.config
|
||||
self.config['url_artworkPrefix'] = "%(base_url)s/banners/%%s" % self.config
|
||||
|
||||
#end __init__
|
||||
|
||||
def _initLogger(self):
|
||||
"""Setups a logger using the logging module, returns a log object
|
||||
"""
|
||||
logger = logging.getLogger("tvdb")
|
||||
formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')
|
||||
|
||||
hdlr = logging.StreamHandler(sys.stdout)
|
||||
|
||||
hdlr.setFormatter(formatter)
|
||||
logger.addHandler(hdlr)
|
||||
|
||||
if self.config['debug_enabled']:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
return logger
|
||||
#end initLogger
|
||||
|
||||
def _getTempDir(self):
|
||||
"""Returns the [system temp dir]/tvdb_api
|
||||
"""
|
||||
return os.path.join(tempfile.gettempdir(), "tvdb_api")
|
||||
|
||||
def _loadUrl(self, url, recache = False):
|
||||
try:
|
||||
self.log.debug("Retrieving URL %s" % url)
|
||||
resp = self.urlopener.open(url)
|
||||
if 'x-local-cache' in resp.headers:
|
||||
self.log.debug("URL %s was cached in %s" % (
|
||||
url,
|
||||
resp.headers['x-local-cache'])
|
||||
)
|
||||
if recache:
|
||||
self.log.debug("Attempting to recache %s" % url)
|
||||
resp.recache()
|
||||
except urllib2.URLError, errormsg:
|
||||
raise tvdb_error("Could not connect to server: %s" % (errormsg))
|
||||
#end try
|
||||
|
||||
return resp.read()
|
||||
|
||||
def _getetsrc(self, url):
|
||||
"""Loads a URL sing caching, returns an ElementTree of the source
|
||||
"""
|
||||
src = self._loadUrl(url)
|
||||
try:
|
||||
return ElementTree.fromstring(src)
|
||||
except SyntaxError:
|
||||
src = self._loadUrl(url, recache=True)
|
||||
try:
|
||||
return ElementTree.fromstring(src)
|
||||
except SyntaxError, exceptionmsg:
|
||||
errormsg = "There was an error with the XML retrieved from thetvdb.com:\n%s" % (
|
||||
exceptionmsg
|
||||
)
|
||||
|
||||
if self.config['cache_enabled']:
|
||||
errormsg += "\nFirst try emptying the cache folder at..\n%s" % (
|
||||
self.config['cache_location']
|
||||
)
|
||||
|
||||
errormsg += "\nIf this does not resolve the issue, please try again later. If the error persists, report a bug on"
|
||||
errormsg += "\nhttp://dbr.lighthouseapp.com/projects/13342-tvdb_api/overview\n"
|
||||
raise tvdb_error(errormsg)
|
||||
#end _getetsrc
|
||||
|
||||
def _setItem(self, sid, seas, ep, attrib, value):
|
||||
"""Creates a new episode, creating Show(), Season() and
|
||||
Episode()s as required. Called by _getShowData to populute
|
||||
|
||||
Since the nice-to-use tvdb[1][24]['name] interface
|
||||
makes it impossible to do tvdb[1][24]['name] = "name"
|
||||
and still be capable of checking if an episode exists
|
||||
so we can raise tvdb_shownotfound, we have a slightly
|
||||
less pretty method of setting items.. but since the API
|
||||
is supposed to be read-only, this is the best way to
|
||||
do it!
|
||||
The problem is that calling tvdb[1][24]['episodename'] = "name"
|
||||
calls __getitem__ on tvdb[1], there is no way to check if
|
||||
tvdb.__dict__ should have a key "1" before we auto-create it
|
||||
"""
|
||||
if sid not in self.shows:
|
||||
self.shows[sid] = Show()
|
||||
if seas not in self.shows[sid]:
|
||||
self.shows[sid][seas] = Season()
|
||||
if ep not in self.shows[sid][seas]:
|
||||
self.shows[sid][seas][ep] = Episode()
|
||||
self.shows[sid][seas][ep][attrib] = value
|
||||
#end _set_item
|
||||
|
||||
def _setShowData(self, sid, key, value):
|
||||
"""Sets self.shows[sid] to a new Show instance, or sets the data
|
||||
"""
|
||||
if sid not in self.shows:
|
||||
self.shows[sid] = Show()
|
||||
self.shows[sid].data[key] = value
|
||||
|
||||
def _cleanData(self, data):
|
||||
"""Cleans up strings returned by TheTVDB.com
|
||||
|
||||
Issues corrected:
|
||||
- Replaces & with &
|
||||
- Trailing whitespace
|
||||
"""
|
||||
data = data.replace(u"&", u"&")
|
||||
data = data.strip()
|
||||
return data
|
||||
#end _cleanData
|
||||
|
||||
def _getSeries(self, series):
|
||||
"""This searches TheTVDB.com for the series name,
|
||||
If a custom_ui UI is configured, it uses this to select the correct
|
||||
series. If not, and interactive == True, ConsoleUI is used, if not
|
||||
BaseUI is used to select the first result.
|
||||
"""
|
||||
series = urllib.quote(series.encode("utf-8"))
|
||||
self.log.debug("Searching for show %s" % series)
|
||||
seriesEt = self._getetsrc(self.config['url_getSeries'] % (series))
|
||||
allSeries = []
|
||||
for series in seriesEt:
|
||||
sn = series.find('SeriesName')
|
||||
value = self._cleanData(sn.text)
|
||||
cur_sid = series.find('id').text
|
||||
self.log.debug('Found series %s (id: %s)' % (value, cur_sid))
|
||||
allSeries.append( {'sid':cur_sid, 'name':value} )
|
||||
#end for series
|
||||
|
||||
if len(allSeries) == 0:
|
||||
self.log.debug('Series result returned zero')
|
||||
raise tvdb_shownotfound("Show-name search returned zero results (cannot find show on TVDB)")
|
||||
|
||||
if self.config['custom_ui'] is not None:
|
||||
self.log.debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
|
||||
ui = self.config['custom_ui'](config = self.config, log = self.log)
|
||||
else:
|
||||
if not self.config['interactive']:
|
||||
self.log.debug('Auto-selecting first search result using BaseUI')
|
||||
ui = BaseUI(config = self.config, log = self.log)
|
||||
else:
|
||||
self.log.debug('Interactivily selecting show using ConsoleUI')
|
||||
ui = ConsoleUI(config = self.config, log = self.log)
|
||||
#end if config['interactive]
|
||||
#end if custom_ui != None
|
||||
|
||||
return ui.selectSeries(allSeries)
|
||||
|
||||
#end _getSeries
|
||||
|
||||
def _parseBanners(self, sid):
|
||||
"""Parses banners XML, from
|
||||
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml
|
||||
|
||||
Banners are retrieved using t['show name]['_banners'], for example:
|
||||
|
||||
>>> t = Tvdb(banners = True)
|
||||
>>> t['scrubs']['_banners'].keys()
|
||||
['fanart', 'poster', 'series', 'season']
|
||||
>>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath']
|
||||
'http://www.thetvdb.com/banners/posters/76156-2.jpg'
|
||||
>>>
|
||||
|
||||
Any key starting with an underscore has been processed (not the raw
|
||||
data from the XML)
|
||||
|
||||
This interface will be improved in future versions.
|
||||
"""
|
||||
self.log.debug('Getting season banners for %s' % (sid))
|
||||
bannersEt = self._getetsrc( self.config['url_seriesBanner'] % (sid) )
|
||||
banners = {}
|
||||
for cur_banner in bannersEt.findall('Banner'):
|
||||
bid = cur_banner.find('id').text
|
||||
btype = cur_banner.find('BannerType')
|
||||
btype2 = cur_banner.find('BannerType2')
|
||||
if btype is None or btype2 is None:
|
||||
continue
|
||||
btype, btype2 = btype.text, btype2.text
|
||||
if not btype in banners:
|
||||
banners[btype] = {}
|
||||
if not btype2 in banners[btype]:
|
||||
banners[btype][btype2] = {}
|
||||
if not bid in banners[btype][btype2]:
|
||||
banners[btype][btype2][bid] = {}
|
||||
|
||||
self.log.debug("Banner: %s", bid)
|
||||
for cur_element in cur_banner.getchildren():
|
||||
tag = cur_element.tag.lower()
|
||||
value = cur_element.text
|
||||
if tag is None or value is None:
|
||||
continue
|
||||
tag, value = tag.lower(), value.lower()
|
||||
self.log.debug("Banner info: %s = %s" % (tag, value))
|
||||
banners[btype][btype2][bid][tag] = value
|
||||
|
||||
for k, v in banners[btype][btype2][bid].items():
|
||||
if k.endswith("path"):
|
||||
new_key = "_%s" % (k)
|
||||
self.log.debug("Transforming %s to %s" % (k, new_key))
|
||||
new_url = self.config['url_artworkPrefix'] % (v)
|
||||
self.log.debug("New banner URL: %s" % (new_url))
|
||||
banners[btype][btype2][bid][new_key] = new_url
|
||||
|
||||
self._setShowData(sid, "_banners", banners)
|
||||
|
||||
|
||||
# Alternate tvdb_api's method for retrieving graphics URLs but returned as a list that preserves
|
||||
# the user rating order highest rated to lowest rated
|
||||
def ttvdb_parseBanners(self, sid):
|
||||
"""Parses banners XML, from
|
||||
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/banners.xml
|
||||
|
||||
Banners are retrieved using t['show name]['_banners'], for example:
|
||||
|
||||
>>> t = Tvdb(banners = True)
|
||||
>>> t['scrubs']['_banners'].keys()
|
||||
['fanart', 'poster', 'series', 'season']
|
||||
>>> t['scrubs']['_banners']['poster']['680x1000']['35308']['_bannerpath']
|
||||
'http://www.thetvdb.com/banners/posters/76156-2.jpg'
|
||||
>>>
|
||||
|
||||
Any key starting with an underscore has been processed (not the raw
|
||||
data from the XML)
|
||||
|
||||
This interface will be improved in future versions.
|
||||
Changed in this interface is that a list or URLs is created to preserve the user rating order from
|
||||
top rated to lowest rated.
|
||||
"""
|
||||
|
||||
self.log.debug('Getting season banners for %s' % (sid))
|
||||
bannersEt = self._getetsrc( self.config['url_seriesBanner'] % (sid) )
|
||||
banners = {}
|
||||
bid_order = {'fanart': [], 'poster': [], 'series': [], 'season': []}
|
||||
for cur_banner in bannersEt.findall('Banner'):
|
||||
bid = cur_banner.find('id').text
|
||||
btype = cur_banner.find('BannerType')
|
||||
btype2 = cur_banner.find('BannerType2')
|
||||
if btype is None or btype2 is None:
|
||||
continue
|
||||
btype, btype2 = btype.text, btype2.text
|
||||
if not btype in banners:
|
||||
banners[btype] = {}
|
||||
if not btype2 in banners[btype]:
|
||||
banners[btype][btype2] = {}
|
||||
if not bid in banners[btype][btype2]:
|
||||
banners[btype][btype2][bid] = {}
|
||||
if btype in bid_order.keys():
|
||||
if btype2 != u'blank':
|
||||
bid_order[btype].append([bid, btype2])
|
||||
|
||||
self.log.debug("Banner: %s", bid)
|
||||
for cur_element in cur_banner.getchildren():
|
||||
tag = cur_element.tag.lower()
|
||||
value = cur_element.text
|
||||
if tag is None or value is None:
|
||||
continue
|
||||
tag, value = tag.lower(), value.lower()
|
||||
self.log.debug("Banner info: %s = %s" % (tag, value))
|
||||
banners[btype][btype2][bid][tag] = value
|
||||
|
||||
for k, v in banners[btype][btype2][bid].items():
|
||||
if k.endswith("path"):
|
||||
new_key = "_%s" % (k)
|
||||
self.log.debug("Transforming %s to %s" % (k, new_key))
|
||||
new_url = self.config['url_artworkPrefix'] % (v)
|
||||
self.log.debug("New banner URL: %s" % (new_url))
|
||||
banners[btype][btype2][bid][new_key] = new_url
|
||||
|
||||
graphics_in_order = {'fanart': [], 'poster': [], 'series': [], 'season': []}
|
||||
for key in bid_order.keys():
|
||||
for bid in bid_order[key]:
|
||||
graphics_in_order[key].append(banners[key][bid[1]][bid[0]])
|
||||
return graphics_in_order
|
||||
# end ttvdb_parseBanners()
|
||||
|
||||
|
||||
def _parseActors(self, sid):
|
||||
"""Parsers actors XML, from
|
||||
http://www.thetvdb.com/api/[APIKEY]/series/[SERIES ID]/actors.xml
|
||||
|
||||
Actors are retrieved using t['show name]['_actors'], for example:
|
||||
|
||||
>>> t = Tvdb(actors = True)
|
||||
>>> actors = t['scrubs']['_actors']
|
||||
>>> type(actors)
|
||||
<class 'tvdb_api.Actors'>
|
||||
>>> type(actors[0])
|
||||
<class 'tvdb_api.Actor'>
|
||||
>>> actors[0]
|
||||
<Actor "Zach Braff">
|
||||
>>> sorted(actors[0].keys())
|
||||
['id', 'image', 'name', 'role', 'sortorder']
|
||||
>>> actors[0]['name']
|
||||
u'Zach Braff'
|
||||
>>> actors[0]['image']
|
||||
'http://www.thetvdb.com/banners/actors/43640.jpg'
|
||||
|
||||
Any key starting with an underscore has been processed (not the raw
|
||||
data from the XML)
|
||||
"""
|
||||
self.log.debug("Getting actors for %s" % (sid))
|
||||
actorsEt = self._getetsrc(self.config['url_actorsInfo'] % (sid))
|
||||
|
||||
cur_actors = Actors()
|
||||
for curActorItem in actorsEt.findall("Actor"):
|
||||
curActor = Actor()
|
||||
for curInfo in curActorItem:
|
||||
tag = curInfo.tag.lower()
|
||||
value = curInfo.text
|
||||
if value is not None:
|
||||
if tag == "image":
|
||||
value = self.config['url_artworkPrefix'] % (value)
|
||||
else:
|
||||
value = self._cleanData(value)
|
||||
curActor[tag] = value
|
||||
cur_actors.append(curActor)
|
||||
self._setShowData(sid, '_actors', cur_actors)
|
||||
|
||||
def _getShowData(self, sid):
|
||||
"""Takes a series ID, gets the epInfo URL and parses the TVDB
|
||||
XML file into the shows dict in layout:
|
||||
shows[series_id][season_number][episode_number]
|
||||
"""
|
||||
|
||||
# Parse show information
|
||||
self.log.debug('Getting all series data for %s' % (sid))
|
||||
seriesInfoEt = self._getetsrc(self.config['url_seriesInfo'] % (sid))
|
||||
for curInfo in seriesInfoEt.findall("Series")[0]:
|
||||
tag = curInfo.tag.lower()
|
||||
value = curInfo.text
|
||||
|
||||
if value is not None:
|
||||
if tag in ['banner', 'fanart', 'poster']:
|
||||
value = self.config['url_artworkPrefix'] % (value)
|
||||
else:
|
||||
value = self._cleanData(value)
|
||||
|
||||
self._setShowData(sid, tag, value)
|
||||
self.log.debug("Got info: %s = %s" % (tag, value))
|
||||
#end for series
|
||||
|
||||
# Parse banners
|
||||
if self.config['banners_enabled']:
|
||||
self._parseBanners(sid)
|
||||
|
||||
# Parse actors
|
||||
if self.config['actors_enabled']:
|
||||
self._parseActors(sid)
|
||||
|
||||
# Parse episode data
|
||||
self.log.debug('Getting all episodes of %s' % (sid))
|
||||
epsEt = self._getetsrc( self.config['url_epInfo'] % (sid) )
|
||||
|
||||
for cur_ep in epsEt.findall("Episode"):
|
||||
seas_no = int(cur_ep.find('SeasonNumber').text)
|
||||
ep_no = int(cur_ep.find('EpisodeNumber').text)
|
||||
for cur_item in cur_ep.getchildren():
|
||||
tag = cur_item.tag.lower()
|
||||
value = cur_item.text
|
||||
if value is not None:
|
||||
if tag == 'filename':
|
||||
value = self.config['url_artworkPrefix'] % (value)
|
||||
else:
|
||||
value = self._cleanData(value)
|
||||
self._setItem(sid, seas_no, ep_no, tag, value)
|
||||
#end for cur_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 name in self.corrections:
|
||||
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.corrections[name] = sid
|
||||
self._getShowData(sid)
|
||||
#end if name in self.corrections
|
||||
return sid
|
||||
#end _nameToSid
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Handles tvdb_instance['seriesname'] calls.
|
||||
The dict index should be the show id
|
||||
"""
|
||||
if isinstance(key, (int, long)):
|
||||
# Item is integer, treat as show id
|
||||
if key not in self.shows:
|
||||
self._getShowData(key)
|
||||
return self.shows[key]
|
||||
|
||||
key = key.lower() # make key lower case
|
||||
sid = self._nameToSid(key)
|
||||
self.log.debug('Got series id %s' % (sid))
|
||||
return self.shows[sid]
|
||||
#end __getitem__
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.shows)
|
||||
#end __repr__
|
||||
#end Tvdb
|
||||
|
||||
def main():
|
||||
"""Simple example of using tvdb_api - it just
|
||||
grabs an episode name interactively.
|
||||
"""
|
||||
tvdb_instance = Tvdb(interactive=True, debug=True, cache=False)
|
||||
print tvdb_instance['Lost']['seriesname']
|
||||
print tvdb_instance['Lost'][1][4]['episodename']
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,48 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#encoding:utf-8
|
||||
#author:dbr/Ben
|
||||
#project:tvdb_api
|
||||
#repository:http://github.com/dbr/tvdb_api
|
||||
#license:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
|
||||
"""Custom exceptions used or raised by tvdb_api
|
||||
"""
|
||||
|
||||
__author__ = "dbr/Ben"
|
||||
__version__ = "1.2.1"
|
||||
|
||||
__all__ = ["tvdb_error", "tvdb_userabort", "tvdb_shownotfound",
|
||||
"tvdb_seasonnotfound", "tvdb_episodenotfound", "tvdb_attributenotfound"]
|
||||
|
||||
class tvdb_error(Exception):
|
||||
"""An error with www.thetvdb.com (Cannot connect, for example)
|
||||
"""
|
||||
pass
|
||||
|
||||
class tvdb_userabort(Exception):
|
||||
"""User aborted the interactive selection (via
|
||||
the q command, ^c etc)
|
||||
"""
|
||||
pass
|
||||
|
||||
class tvdb_shownotfound(Exception):
|
||||
"""Show cannot be found on www.thetvdb.com (non-existant show)
|
||||
"""
|
||||
pass
|
||||
|
||||
class tvdb_seasonnotfound(Exception):
|
||||
"""Season cannot be found on www.thetvdb.com
|
||||
"""
|
||||
pass
|
||||
|
||||
class tvdb_episodenotfound(Exception):
|
||||
"""Episode cannot be found on www.thetvdb.com
|
||||
"""
|
||||
pass
|
||||
|
||||
class tvdb_attributenotfound(Exception):
|
||||
"""Raised if an episode does not have the requested
|
||||
attribute (such as a episode name)
|
||||
"""
|
||||
pass
|
|
@ -1,124 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#encoding:utf-8
|
||||
#author:dbr/Ben
|
||||
#project:tvdb_api
|
||||
#repository:http://github.com/dbr/tvdb_api
|
||||
#license:Creative Commons GNU GPL v2
|
||||
# (http://creativecommons.org/licenses/GPL/2.0/)
|
||||
|
||||
"""Contains included user interfaces for Tvdb show selection.
|
||||
|
||||
A UI is a callback. A class, it's __init__ function takes two arguments:
|
||||
|
||||
- config, which is the Tvdb config dict, setup in tvdb_api.py
|
||||
- log, which is Tvdb's logger instance (which uses the logging module). You can
|
||||
call log.info() log.warning() etc
|
||||
|
||||
It must have a method "selectSeries", this is passed a list of dicts, each dict
|
||||
contains the the keys "name" (human readable show name), and "sid" (the shows
|
||||
ID as on thetvdb.com). For example:
|
||||
|
||||
[{'name': u'Lost', 'sid': u'73739'},
|
||||
{'name': u'Lost Universe', 'sid': u'73181'}]
|
||||
|
||||
The "selectSeries" method must return the appropriate dict, or it can raise
|
||||
tvdb_userabort (if the selection is aborted), tvdb_shownotfound (if the show
|
||||
cannot be found).
|
||||
|
||||
A simple example callback, which returns a random series:
|
||||
|
||||
>>> import random
|
||||
>>> from tvdb_ui import BaseUI
|
||||
>>> class RandomUI(BaseUI):
|
||||
... def selectSeries(self, allSeries):
|
||||
... import random
|
||||
... return random.choice(allSeries)
|
||||
|
||||
Then to use it..
|
||||
|
||||
>>> from tvdb_api import Tvdb
|
||||
>>> t = Tvdb(custom_ui = RandomUI)
|
||||
>>> random_matching_series = t['Lost']
|
||||
>>> type(random_matching_series)
|
||||
<class 'tvdb_api.Show'>
|
||||
"""
|
||||
|
||||
__author__ = "dbr/Ben"
|
||||
__version__ = "1.2.1"
|
||||
|
||||
from tvdb_exceptions import tvdb_userabort
|
||||
|
||||
class BaseUI:
|
||||
"""Default non-interactive UI, which auto-selects first results
|
||||
"""
|
||||
def __init__(self, config, log):
|
||||
self.config = config
|
||||
self.log = log
|
||||
|
||||
def selectSeries(self, allSeries):
|
||||
return allSeries[0]
|
||||
|
||||
|
||||
class ConsoleUI(BaseUI):
|
||||
"""Interactively allows the user to select a show from a console based UI
|
||||
"""
|
||||
|
||||
def _displaySeries(self, allSeries):
|
||||
"""Helper function, lists series with corresponding ID
|
||||
"""
|
||||
print "TVDB Search Results:"
|
||||
for i in range(len(allSeries[:6])): # list first 6 search results
|
||||
i_show = i + 1 # Start at more human readable number 1 (not 0)
|
||||
self.log.debug('Showing allSeries[%s] = %s)' % (i_show, allSeries[i]))
|
||||
print "%s -> %s # http://thetvdb.com/?tab=series&id=%s" % (
|
||||
i_show,
|
||||
allSeries[i]['name'].encode("UTF-8","ignore"),
|
||||
allSeries[i]['sid'].encode("UTF-8","ignore")
|
||||
)
|
||||
|
||||
def selectSeries(self, allSeries):
|
||||
self._displaySeries(allSeries)
|
||||
|
||||
if len(allSeries) == 1:
|
||||
# Single result, return it!
|
||||
print "Automatically selecting only result"
|
||||
return allSeries[0]
|
||||
|
||||
if self.config['select_first'] is True:
|
||||
print "Automatically returning first search result"
|
||||
return allSeries[0]
|
||||
|
||||
while True: # return breaks this loop
|
||||
try:
|
||||
print "Enter choice (first number, ? for help):"
|
||||
ans = raw_input()
|
||||
except KeyboardInterrupt:
|
||||
raise tvdb_userabort("User aborted (^c keyboard interupt)")
|
||||
except EOFError:
|
||||
raise tvdb_userabort("User aborted (EOF received)")
|
||||
|
||||
self.log.debug('Got choice of: %s' % (ans))
|
||||
try:
|
||||
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('Got quit command (q)')
|
||||
raise tvdb_userabort("User aborted ('q' quit command)")
|
||||
elif ans == "?":
|
||||
print "## Help"
|
||||
print "# Enter the number that corresponds to the correct show."
|
||||
print "# ? - this help"
|
||||
print "# q - abort tvnamer"
|
||||
else:
|
||||
self.log.debug('Unknown keypress %s' % (ans))
|
||||
else:
|
||||
self.log.debug('Trying to return ID: %d' % (selected_id))
|
||||
try:
|
||||
return allSeries[ selected_id ]
|
||||
except IndexError:
|
||||
self.log.debug('Invalid show number entered!')
|
||||
print "Invalid number (%s) selected!"
|
||||
self._displaySeries(allSeries)
|
||||
#end try
|
||||
#end while not valid_input
|
||||
|
Loading…
Reference in New Issue