|
MythTV
0.26-pre
|
00001 #!/usr/bin/env python 00002 # -*- coding: UTF-8 -*- 00003 # ---------------------- 00004 # Name: mainProcess.py 00005 # Python Script 00006 # Author: R.D. Vaughan 00007 # Purpose: 00008 # This python script is the common process for all python MythTV Netvision plugin grabber processing. 00009 # It follows the MythTV standards set for Netvision grabbers. 00010 # Command example: 00011 # See help (-u and -h) options 00012 # 00013 # Design: 00014 # 1) Verify the command line options (display help or version and exit) 00015 # 2) Call the target site with an information request according to the selected options 00016 # 3) Prepare and display XML results from the information request 00017 # 00018 # 00019 # License:Creative Commons GNU GPL v2 00020 # (http://creativecommons.org/licenses/GPL/2.0/) 00021 #------------------------------------- 00022 __title__ ="Netvision Common Query Processing"; 00023 __author__="R.D.Vaughan" 00024 __version__="v0.2.5" 00025 # 0.1.0 Initial development 00026 # 0.1.1 Refining the code like the additional of a grabber specifing the maximum number of items to return 00027 # 0.1.2 Added the Tree view option 00028 # 0.1.3 Updated deocumentation 00029 # 0.2.0 Public release 00030 # 0.2.1 Added the ability to have a mashup name independant of the mashup title 00031 # 0.2.2 Added support of the -H option 00032 # 0.2.3 Added support of the XML version information 00033 # 0.2.4 Added the "command" tag to the XML version information 00034 # 0.2.5 Fixed a bug in the setting up of a search mashup page item maximum 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 # Check that the lxml library is current enough 00079 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html) 00080 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later" 00081 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package 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, ###CHANGE - Needs to be added 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 # end __init__() 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 # end searchForVideos() 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 # end displayTreeView() 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 # end displayHTML() 00301 00302 # end Class siteQueries() 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 # end __init__() 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 # Make alls command line arguments unicode utf8 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 # Process version command line requests 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 # Process usage command line requests 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 # Set the maximum number of items to display per Mythtvnetvision search page 00417 if self.grabberInfo['search']: 00418 if opts.search: 00419 if not 'SmaxPage' in self.grabberInfo.keys(): 00420 Queries.page_limit = 20 # Default items per page 00421 else: 00422 Queries.page_limit = self.grabberInfo['SmaxPage'] 00423 00424 # Set the maximum number of items to display per Mythtvnetvision tree view page 00425 if self.grabberInfo['tree']: 00426 if opts.treeview: 00427 if not 'TmaxPage' in self.grabberInfo.keys(): 00428 Queries.page_limit = 20 # Default items per page 00429 else: 00430 Queries.page_limit = self.grabberInfo['TmaxPage'] 00431 00432 # Set the grabber title 00433 Queries.grabber_title = self.grabberInfo['title'] 00434 00435 # Set the mashup title 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 # Process requested option 00450 if self.grabberInfo['search']: 00451 if opts.search: # Video search -S 00452 self.page_limit = Queries.page_limit 00453 Queries.searchForVideos(args[0], opts.pagenumber) 00454 00455 if self.grabberInfo['tree']: 00456 if opts.treeview: # Video treeview -T 00457 self.page_limit = Queries.page_limit 00458 Queries.displayTreeView() 00459 00460 if self.grabberInfo['html']: 00461 if opts.customhtml: # Video treeview -H 00462 Queries.displayHTML(args[0]) 00463 00464 sys.exit(0) 00465 # end main()
1.7.6.1