MythTV  0.26-pre
mainProcess.py
Go to the documentation of this file.
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()
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends