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