cleaning up .svn files

This commit is contained in:
Erik Kristensen 2010-08-18 21:24:14 -04:00
parent cc5de9acf4
commit 59df11a25a
26 changed files with 0 additions and 7985 deletions

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
K 14
svn:executable
V 1
*
END

View File

@ -1,5 +0,0 @@
K 14
svn:executable
V 1
*
END

View File

@ -1,5 +0,0 @@
K 14
svn:executable
V 1
*
END

View File

@ -1,5 +0,0 @@
K 14
svn:executable
V 1
*
END

View File

@ -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

View File

@ -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 = '[]:[]'

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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__

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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------------------------------------------------------------------------------

View File

@ -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 &amp; with &
- Trailing whitespace
"""
data = data.replace(u"&amp;", 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()

View File

@ -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

View File

@ -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