00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 __title__ ="Netvision Common Query Processing";
00023 __author__="R.D.Vaughan"
00024 __version__="v0.2.5"
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036 import sys, os
00037 from optparse import OptionParser
00038 import re
00039 from string import capitalize
00040
00041
00042 class OutStreamEncoder(object):
00043 """Wraps a stream with an encoder"""
00044 def __init__(self, outstream, encoding=None):
00045 self.out = outstream
00046 if not encoding:
00047 self.encoding = sys.getfilesystemencoding()
00048 else:
00049 self.encoding = encoding
00050
00051 def write(self, obj):
00052 """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00053 if isinstance(obj, unicode):
00054 try:
00055 self.out.write(obj.encode(self.encoding))
00056 except IOError:
00057 pass
00058 else:
00059 try:
00060 self.out.write(obj)
00061 except IOError:
00062 pass
00063
00064 def __getattr__(self, attr):
00065 """Delegate everything but write to the stream"""
00066 return getattr(self.out, attr)
00067 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00068 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00069
00070
00071 try:
00072 from StringIO import StringIO
00073 from lxml import etree
00074 except Exception, e:
00075 sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
00076 sys.exit(1)
00077
00078
00079
00080
00081
00082 version = ''
00083 for digit in etree.LIBXML_VERSION:
00084 version+=str(digit)+'.'
00085 version = version[:-1]
00086 if version < '2.7.2':
00087 sys.stderr.write(u'''
00088 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
00089 At least "libxml" version 2.7.2 must be installed. Your version is (%s).
00090 ''' % version)
00091 sys.exit(1)
00092
00093
00094 class siteQueries():
00095 '''Methods that quering video Web sites for metadata and outputs the results to stdout any errors are output
00096 to stderr.
00097 '''
00098 def __init__(self,
00099 apikey,
00100 target,
00101 mythtv = False,
00102 interactive = False,
00103 select_first = False,
00104 debug = False,
00105 custom_ui = None,
00106 language = None,
00107 search_all_languages = False,
00108 ):
00109 """apikey (str/unicode):
00110 Specify the themoviedb.org API key. Applications need their own key.
00111 See http://api.themoviedb.org/2.1/ to get your own API key
00112
00113 mythtv (True/False):
00114 When True, the movie metadata is being returned has the key and values massaged to match MythTV
00115 When False, the movie metadata is being returned matches what TMDB returned
00116
00117 interactive (True/False):
00118 When True, uses built-in console UI is used to select the correct show.
00119 When False, the first search result is used.
00120
00121 select_first (True/False):
00122 Automatically selects the first series search result (rather
00123 than showing the user a list of more than one series).
00124 Is overridden by interactive = False, or specifying a custom_ui
00125
00126 debug (True/False):
00127 shows verbose debugging information
00128
00129 custom_ui (tvdb_ui.BaseUI subclass):
00130 A callable subclass of tvdb_ui.BaseUI (overrides interactive option)
00131
00132 language (2 character language abbreviation):
00133 The language of the returned data. Is also the language search
00134 uses. Default is "en" (English). For full list, run..
00135
00136 >>> MovieDb().config['valid_languages'] #doctest: +ELLIPSIS
00137 ['da', 'fi', 'nl', ...]
00138
00139 search_all_languages (True/False):
00140 By default, TMDB will only search in the language specified using
00141 the language option. When this is True, it will search for the
00142 show in any language
00143
00144 """
00145 self.config = {}
00146
00147 self.config['apikey'] = apikey
00148 self.config['target'] = target.Videos(apikey, mythtv = mythtv,
00149 interactive = interactive,
00150 select_first = select_first,
00151 debug = debug,
00152 custom_ui = custom_ui,
00153 language = language,
00154 search_all_languages = search_all_languages,)
00155 self.searchKeys = [u'title', u'releasedate', u'overview', u'url', u'thumbnail', u'video', u'runtime', u'viewcount']
00156
00157 self.searchXML = {'header': """<?xml version="1.0" encoding="UTF-8"?>""", 'rss': """
00158 <rss version="2.0"
00159 xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
00160 xmlns:content="http://purl.org/rss/1.0/modules/content/"
00161 xmlns:cnettv="http://cnettv.com/mrss/"
00162 xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"
00163 xmlns:media="http://search.yahoo.com/mrss/"
00164 xmlns:atom="http://www.w3.org/2005/Atom"
00165 xmlns:amp="http://www.adobe.com/amp/1.0"
00166 xmlns:dc="http://purl.org/dc/elements/1.1/"
00167 xmlns:mythtv="http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format">""", 'channel': """
00168 <channel>
00169 <title>%(channel_title)s</title>
00170 <link>%(channel_link)s</link>
00171 <description>%(channel_description)s</description>
00172 <numresults>%(channel_numresults)d</numresults>
00173 <returned>%(channel_returned)d</returned>
00174 <startindex>%(channel_startindex)d</startindex>""", 'item': """
00175 <item>
00176 <title>%(item_title)s</title>
00177 <author>%(item_author)s</author>
00178 <pubDate>%(item_pubdate)s</pubDate>
00179 <description>%(item_description)s</description>
00180 <link>%(item_link)s</link>
00181 <media:group>
00182 <media:thumbnail url='%(item_thumbnail)s'/>
00183 <media:content url='%(item_url)s' duration='%(item_duration)s' width='%(item_width)s' height='%(item_height)s' lang='%(item_lang)s'/>
00184 </media:group>
00185 <rating>%(item_rating)s</rating>
00186 </item>""", 'end_channel': """
00187 </channel>""", 'end_rss': """
00188 </rss>
00189 """, }
00190
00191 self.treeViewXML = {'header': """<?xml version="1.0" encoding="UTF-8"?>""", 'rss': """
00192 <rss version="2.0"
00193 xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
00194 xmlns:content="http://purl.org/rss/1.0/modules/content/"
00195 xmlns:cnettv="http://cnettv.com/mrss/"
00196 xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"
00197 xmlns:media="http://search.yahoo.com/mrss/"
00198 xmlns:atom="http://www.w3.org/2005/Atom"
00199 xmlns:amp="http://www.adobe.com/amp/1.0"
00200 xmlns:dc="http://purl.org/dc/elements/1.1/"
00201 xmlns:mythtv="http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format">""", 'start_channel': """
00202 <channel>
00203 <title>%(channel_title)s</title>
00204 <link>%(channel_link)s</link>
00205 <description>%(channel_description)s</description>
00206 <numresults>%(channel_numresults)d</numresults>
00207 <returned>%(channel_returned)d</returned>
00208 <startindex>%(channel_startindex)d</startindex>""", "start_directory": """
00209 <directory name="%s" thumbnail="%s">""", 'item': """
00210 <item>
00211 <title>%(item_title)s</title>
00212 <author>%(item_author)s</author>
00213 <pubDate>%(item_pubdate)s</pubDate>
00214 <description>%(item_description)s</description>
00215 <link>%(item_link)s</link>
00216 <media:group>
00217 <media:thumbnail url='%(item_thumbnail)s'/>
00218 <media:content url='%(item_url)s' duration='%(item_duration)s' width='%(item_width)s' height='%(item_height)s' lang='%(item_lang)s'/>
00219 </media:group>
00220 <rating>%(item_rating)s</rating>
00221 </item>""", "end_directory": """
00222 </directory>""", 'end_channel': """
00223 </channel>""", 'end_rss': """
00224 </rss>
00225 """, }
00226
00227
00228
00229
00230 def searchForVideos(self, search_text, pagenumber):
00231 '''Search for vidoes that match the search text and output the results a XML to stdout
00232 '''
00233 self.firstVideo = True
00234 self.config['target'].page_limit = self.page_limit
00235 self.config['target'].grabber_title = self.grabber_title
00236 self.config['target'].mashup_title = self.mashup_title
00237
00238 data_sets = self.config['target'].searchForVideos(search_text, pagenumber)
00239 if data_sets == None:
00240 return
00241 if not len(data_sets):
00242 return
00243
00244 for data_set in data_sets:
00245 if self.firstVideo:
00246 sys.stdout.write(self.searchXML['header'])
00247 sys.stdout.write(self.searchXML['rss'])
00248 self.firstVideo = False
00249 sys.stdout.write(self.searchXML['channel'] % data_set[0])
00250 for item in data_set[1]:
00251 sys.stdout.write(self.searchXML['item'] % item)
00252 sys.stdout.write(self.searchXML['end_channel'])
00253 sys.stdout.write(self.searchXML['end_rss'])
00254
00255
00256 def displayTreeView(self):
00257 '''Create a Tree View specific to a target site and output the results a XML to stdout. Nested
00258 directories are permissable.
00259 '''
00260 self.firstVideo = True
00261 self.config['target'].page_limit = self.page_limit
00262 self.config['target'].grabber_title = self.grabber_title
00263 self.config['target'].mashup_title = self.mashup_title
00264
00265 data_sets = self.config['target'].displayTreeView()
00266 if data_sets == None:
00267 return
00268 if not len(data_sets):
00269 return
00270
00271 for data_set in data_sets:
00272 if self.firstVideo:
00273 sys.stdout.write(self.treeViewXML['header'])
00274 sys.stdout.write(self.treeViewXML['rss'])
00275 self.firstVideo = False
00276 sys.stdout.write(self.treeViewXML['start_channel'] % data_set[0])
00277 for directory in data_set[1]:
00278 if isinstance(directory, list):
00279 if directory[0] == '':
00280 sys.stdout.write(self.treeViewXML['end_directory'])
00281 continue
00282 sys.stdout.write(self.treeViewXML['start_directory'] % (directory[0], directory[1]))
00283 continue
00284 sys.stdout.write(self.treeViewXML['item'] % directory)
00285 sys.stdout.write(self.treeViewXML['end_channel'])
00286 sys.stdout.write(self.treeViewXML['end_rss'])
00287
00288
00289 def displayHTML(self, videocode):
00290 '''Request a custom Web page from the grabber and display on stdout
00291 '''
00292 self.firstVideo = True
00293 self.config['target'].page_limit = self.page_limit
00294 self.config['target'].grabber_title = self.grabber_title
00295 self.config['target'].mashup_title = self.mashup_title
00296 self.config['target'].HTMLvideocode = videocode
00297
00298 sys.stdout.write(self.config['target'].displayCustomHTML())
00299 return
00300
00301
00302
00303
00304 class mainProcess:
00305 '''Common processing for all Netvision python grabbers.
00306 '''
00307 def __init__(self, target, apikey, ):
00308 '''
00309 '''
00310 self.target = target
00311 self.apikey = apikey
00312
00313
00314
00315 def main(self):
00316 """Gets video details a search term
00317 """
00318 parser = OptionParser()
00319
00320 parser.add_option( "-d", "--debug", action="store_true", default=False, dest="debug",
00321 help=u"Show debugging info (URLs, raw XML ... etc, info varies per grabber)")
00322 parser.add_option( "-u", "--usage", action="store_true", default=False, dest="usage",
00323 help=u"Display examples for executing the script")
00324 parser.add_option( "-v", "--version", action="store_true", default=False, dest="version",
00325 help=u"Display grabber name and supported options")
00326 parser.add_option( "-l", "--language", metavar="LANGUAGE", default=u'', dest="language",
00327 help=u"Select data that matches the specified language fall back to English if nothing found (e.g. 'es' EspaƱol, 'de' Deutsch ... etc).\nNot all sites or grabbers support this option.")
00328 parser.add_option( "-p", "--pagenumber", metavar="PAGE NUMBER", default=1, dest="pagenumber",
00329 help=u"Display specific page of the search results. Default is page 1.\nPage number is ignored with the Tree View option (-T).")
00330 functionality = u''
00331 if self.grabberInfo['search']:
00332 parser.add_option( "-S", "--search", action="store_true", default=False, dest="search",
00333 help=u"Search for videos")
00334 functionality+='S'
00335
00336 if self.grabberInfo['tree']:
00337 parser.add_option( "-T", "--treeview", action="store_true", default=False, dest="treeview",
00338 help=u"Display a Tree View of a sites videos")
00339 functionality+='T'
00340
00341 if self.grabberInfo['html']:
00342 parser.add_option( "-H", "--customhtml", action="store_true", default=False,
00343 dest="customhtml", help=u"Return a custom HTML Web page")
00344 functionality+='H'
00345
00346 parser.usage=u"./%%prog -hduvl%s [parameters] <search text>\nVersion: %s Author: %s\n\nFor details on the MythTV Netvision plugin see the wiki page at:\nhttp://www.mythtv.org/wiki/MythNetvision" % (functionality, self.grabberInfo['version'], self.grabberInfo['author'])
00347
00348 opts, args = parser.parse_args()
00349
00350
00351 for index in range(len(args)):
00352 args[index] = unicode(args[index], 'utf8')
00353
00354 if opts.debug:
00355 sys.stdout.write("\nopts: %s\n" % opts)
00356 sys.stdout.write("\nargs: %s\n\n" % args)
00357
00358
00359 if opts.version:
00360 version = etree.XML(u'<grabber></grabber>')
00361 etree.SubElement(version, "name").text = self.grabberInfo['title']
00362 etree.SubElement(version, "author").text = self.grabberInfo['author']
00363 etree.SubElement(version, "thumbnail").text = self.grabberInfo['thumbnail']
00364 etree.SubElement(version, "command").text = self.grabberInfo['command']
00365 for t in self.grabberInfo['type']:
00366 etree.SubElement(version, "type").text = t
00367 etree.SubElement(version, "description").text = self.grabberInfo['desc']
00368 etree.SubElement(version, "version").text = self.grabberInfo['version']
00369 if self.grabberInfo['search']:
00370 etree.SubElement(version, "search").text = 'true'
00371 if self.grabberInfo['tree']:
00372 etree.SubElement(version, "tree").text = 'true'
00373 sys.stdout.write(etree.tostring(version, encoding='UTF-8', pretty_print=True))
00374 sys.exit(0)
00375
00376
00377 if opts.usage:
00378 sys.stdout.write(self.grabberInfo['usage'])
00379 sys.exit(0)
00380
00381 if self.grabberInfo['search']:
00382 if opts.search and not len(args) == 1:
00383 sys.stderr.write("! Error: There must be one value for the search option. Your options are (%s)\n" % (args))
00384 sys.exit(1)
00385 if opts.search and args[0] == u'':
00386 sys.stderr.write("! Error: There must be a non-empty search argument, yours is empty.\n")
00387 sys.exit(1)
00388
00389 if self.grabberInfo['html']:
00390 if opts.customhtml and not len(args) == 1:
00391 sys.stderr.write("! Error: There must be one value for the search option. Your options are (%s)\n" % (args))
00392 sys.exit(1)
00393 if opts.customhtml and args[0] == u'':
00394 sys.stderr.write("! Error: There must be a non-empty Videocode argument, yours is empty.\n")
00395 sys.exit(1)
00396
00397 if not self.grabberInfo['search'] and not self.grabberInfo['tree'] and not self.grabberInfo['html']:
00398 sys.stderr.write("! Error: You have not selected a valid option.\n")
00399 sys.exit(1)
00400
00401 try:
00402 x = int(opts.pagenumber)
00403 except:
00404 sys.stderr.write("! Error: When specified the page number must be numeric. Yours was (%s)\n" % opts.pagenumber)
00405 sys.exit(1)
00406
00407 Queries = siteQueries(self.apikey, self.target,
00408 mythtv = True,
00409 interactive = False,
00410 select_first = False,
00411 debug = opts.debug,
00412 custom_ui = None,
00413 language = opts.language,
00414 search_all_languages = False,)
00415
00416
00417 if self.grabberInfo['search']:
00418 if opts.search:
00419 if not 'SmaxPage' in self.grabberInfo.keys():
00420 Queries.page_limit = 20
00421 else:
00422 Queries.page_limit = self.grabberInfo['SmaxPage']
00423
00424
00425 if self.grabberInfo['tree']:
00426 if opts.treeview:
00427 if not 'TmaxPage' in self.grabberInfo.keys():
00428 Queries.page_limit = 20
00429 else:
00430 Queries.page_limit = self.grabberInfo['TmaxPage']
00431
00432
00433 Queries.grabber_title = self.grabberInfo['title']
00434
00435
00436 if not 'mashup_title' in self.grabberInfo.keys():
00437 Queries.mashup_title = Queries.grabber_title
00438 else:
00439 if self.grabberInfo['search']:
00440 if opts.search:
00441 Queries.mashup_title = self.grabberInfo['mashup_title'] + "search"
00442 if self.grabberInfo['tree']:
00443 if opts.treeview:
00444 Queries.mashup_title = self.grabberInfo['mashup_title'] + "treeview"
00445 if self.grabberInfo['html']:
00446 if opts.customhtml:
00447 Queries.mashup_title = self.grabberInfo['mashup_title'] + "customhtml"
00448
00449
00450 if self.grabberInfo['search']:
00451 if opts.search:
00452 self.page_limit = Queries.page_limit
00453 Queries.searchForVideos(args[0], opts.pagenumber)
00454
00455 if self.grabberInfo['tree']:
00456 if opts.treeview:
00457 self.page_limit = Queries.page_limit
00458 Queries.displayTreeView()
00459
00460 if self.grabberInfo['html']:
00461 if opts.customhtml:
00462 Queries.displayHTML(args[0])
00463
00464 sys.exit(0)
00465