MythTV  0.26-pre
rev3_api.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # -*- coding: UTF-8 -*-
00003 # ----------------------
00004 # Name: rev3_api - Simple-to-use Python interface to the Revision3 RSS feeds (http://revision3.com/)
00005 # Python Script
00006 # Author:   R.D. Vaughan
00007 # Purpose:  This python script is intended to perform a variety of utility functions to search and access text
00008 #           metadata, video and image URLs from rev3. These routines are based on the api. Specifications
00009 #           for this api are published at http://developer.rev3nservices.com/docs
00010 #
00011 # License:Creative Commons GNU GPL v2
00012 # (http://creativecommons.org/licenses/GPL/2.0/)
00013 #-------------------------------------
00014 __title__ ="rev3_api - Simple-to-use Python interface to the Revision3 RSS feeds (http://revision3.com/)"
00015 __author__="R.D. Vaughan"
00016 __purpose__='''
00017 This python script is intended to perform a variety of utility functions to search and access text
00018 meta data, video and image URLs from rev3. These routines process RSS feeds provided by Revision3
00019 (http://revision3.com/). The specific Revision3 RSS feeds that are processed are controled through
00020 a user XML preference file usually found at "~/.mythtv/MythNetvision/userGrabberPrefs/rev3.xml"
00021 '''
00022 
00023 __version__="v0.1.4"
00024 # 0.1.0 Initial development
00025 # 0.1.1 Changed the search functionality to be "Videos" only rather than the site search.
00026 #       Added support for Revision3's Personal RSS feed
00027 #       Changed the logger to only output to stderr rather than a file
00028 # 0.1.2 Fixed an abort when no RSS feed data was returned
00029 # 0.1.3 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path
00030 # 0.1.4 Fixed missing shows from the creation of the user default preference due to Web site changes
00031 #       Fixed two incorrect variable names in debug messages
00032 
00033 import os, struct, sys, re, time, datetime, urllib, re
00034 import logging
00035 from socket import gethostname, gethostbyname
00036 from threading import Thread
00037 from copy import deepcopy
00038 
00039 from rev3_exceptions import (Rev3UrlError, Rev3HttpError, Rev3RssError, Rev3VideoNotFound, Rev3ConfigFileError, Rev3UrlDownloadError)
00040 
00041 class OutStreamEncoder(object):
00042     """Wraps a stream with an encoder"""
00043     def __init__(self, outstream, encoding=None):
00044         self.out = outstream
00045         if not encoding:
00046             self.encoding = sys.getfilesystemencoding()
00047         else:
00048             self.encoding = encoding
00049 
00050     def write(self, obj):
00051         """Wraps the output stream, encoding Unicode strings with the specified encoding"""
00052         if isinstance(obj, unicode):
00053             try:
00054                 self.out.write(obj.encode(self.encoding))
00055             except IOError:
00056                 pass
00057         else:
00058             try:
00059                 self.out.write(obj)
00060             except IOError:
00061                 pass
00062 
00063     def __getattr__(self, attr):
00064         """Delegate everything but write to the stream"""
00065         return getattr(self.out, attr)
00066 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
00067 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')
00068 
00069 
00070 try:
00071     from StringIO import StringIO
00072     from lxml import etree
00073 except Exception, e:
00074     sys.stderr.write(u'\n! Error - Importing the "lxml" and "StringIO" python libraries failed on error(%s)\n' % e)
00075     sys.exit(1)
00076 
00077 # Check that the lxml library is current enough
00078 # From the lxml documents it states: (http://codespeak.net/lxml/installation.html)
00079 # "If you want to use XPath, do not use libxml2 2.6.27. We recommend libxml2 2.7.2 or later"
00080 # Testing was performed with the Ubuntu 9.10 "python-lxml" version "2.1.5-1ubuntu2" repository package
00081 version = ''
00082 for digit in etree.LIBXML_VERSION:
00083     version+=str(digit)+'.'
00084 version = version[:-1]
00085 if version < '2.7.2':
00086     sys.stderr.write(u'''
00087 ! Error - The installed version of the "lxml" python library "libxml" version is too old.
00088           At least "libxml" version 2.7.2 must be installed. Your version is (%s).
00089 ''' % version)
00090     sys.exit(1)
00091 
00092 
00093 class Videos(object):
00094     """Main interface to http://www.rev3.com/
00095     This is done to support a common naming framework for all python Netvision plugins no matter their site
00096     target.
00097 
00098     Supports search methods
00099     The apikey is a not required to access http://www.rev3.com/
00100     """
00101     def __init__(self,
00102                 apikey,
00103                 mythtv = True,
00104                 interactive = False,
00105                 select_first = False,
00106                 debug = False,
00107                 custom_ui = None,
00108                 language = None,
00109                 search_all_languages = False,
00110                 ):
00111         """apikey (str/unicode):
00112             Specify the target site API key. Applications need their own key in some cases
00113 
00114         mythtv (True/False):
00115             When True, the returned meta data is being returned has the key and values massaged to match MythTV
00116             When False, the returned meta data  is being returned matches what target site returned
00117 
00118         interactive (True/False): (This option is not supported by all target site apis)
00119             When True, uses built-in console UI is used to select the correct show.
00120             When False, the first search result is used.
00121 
00122         select_first (True/False): (This option is not supported currently implemented in any grabbers)
00123             Automatically selects the first series search result (rather
00124             than showing the user a list of more than one series).
00125             Is overridden by interactive = False, or specifying a custom_ui
00126 
00127         debug (True/False):
00128              shows verbose debugging information
00129 
00130         custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers)
00131             A callable subclass of interactive class (overrides interactive option)
00132 
00133         language (2 character language abbreviation): (This option is not supported by all target site apis)
00134             The language of the returned data. Is also the language search
00135             uses. Default is "en" (English). For full list, run..
00136 
00137         search_all_languages (True/False): (This option is not supported by all target site apis)
00138             By default, a Netvision grabber will only search in the language specified using
00139             the language option. When this is True, it will search for the
00140             show in any language
00141 
00142         """
00143         self.config = {}
00144 
00145         if apikey is not None:
00146             self.config['apikey'] = apikey
00147         else:
00148             pass    # Rev3 does not require an apikey
00149 
00150         self.config['debug_enabled'] = debug # show debugging messages
00151         self.common = common
00152         self.common.debug = debug   # Set the common function debug level
00153 
00154         self.log_name = u'Rev3_Grabber'
00155         self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name)
00156         self.logger = self.common.logger # Setups the logger (self.log.debug() etc)
00157 
00158         self.config['custom_ui'] = custom_ui
00159 
00160         self.config['interactive'] = interactive
00161 
00162         self.config['select_first'] = select_first
00163 
00164         self.config['search_all_languages'] = search_all_languages
00165 
00166         self.error_messages = {'Rev3UrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'Rev3HttpError': u"! Error: An HTTP communications error with Rev3 was raised (%s)\n", 'Rev3RssError': u"! Error: Invalid RSS meta data\nwas received from Rev3 error (%s). Skipping item.\n", 'Rev3VideoNotFound': u"! Error: Video search with Rev3 did not return any results (%s)\n", 'Rev3ConfigFileError': u"! Error: rev3_config.xml file missing\nit should be located in and named as (%s).\n", 'Rev3UrlDownloadError': u"! Error: Downloading a RSS feed or Web page (%s).\n", }
00167 
00168         # Channel details and search results
00169         self.channel = {'channel_title': u'Revision3', 'channel_link': u'http://revision3.com/', 'channel_description': u"Revision3 is the leading television network for the internet generation.", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0}
00170 
00171         # Season and/or Episode detection regex patterns
00172         self.s_e_Patterns = [
00173             # Season 3, Episode 8
00174             re.compile(u'''^.+?Season\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE),
00175             # "Episode 1" anywhere in text
00176             re.compile(u'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
00177             # "Episode 1" at the start of the text
00178             re.compile(u'''Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE),
00179             # "--0027--" when the episode is in the URl link
00180             re.compile(u'''^.+?--(?P<seasno>[0-9]+)--.*$''', re.UNICODE),
00181             ]
00182 
00183         self.FullScreen = u'http://revision3.com/show/popupPlayer?video_id=%s&quality=high&offset=0'
00184         self.FullScreenParser = self.common.parsers['html'].copy()
00185         self.FullScreenVidIDxPath = etree.XPath('//object', namespaces=self.common.namespaces )
00186 
00187         self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/rev3.png'
00188     # end __init__()
00189 
00190 ###########################################################################################################
00191 #
00192 # Start - Utility functions
00193 #
00194 ###########################################################################################################
00195 
00196     def getRev3Config(self):
00197         ''' Read the MNV Revision3 grabber "rev3_config.xml" configuration file
00198         return nothing
00199         '''
00200         # Read the grabber rev3_config.xml configuration file
00201         url = u'file://%s/nv_python_libs/configs/XML/rev3_config.xml' % (baseProcessingDir, )
00202         if not os.path.isfile(url[7:]):
00203             raise Rev3ConfigFileError(self.error_messages['Rev3ConfigFileError'] % (url[7:], ))
00204 
00205         if self.config['debug_enabled']:
00206             print url
00207             print
00208         try:
00209             self.rev3_config = etree.parse(url)
00210         except Exception, e:
00211             raise Rev3UrlError(self.error_messages['Rev3UrlError'] % (url, errormsg))
00212         return
00213     # end getRev3Config()
00214 
00215 
00216     def getUserPreferences(self):
00217         '''Read the rev3_config.xml and user preference rev3.xml file.
00218         If the rev3.xml file does not exist then create it.
00219         If the rev3.xml file is too old then update it.
00220         return nothing
00221         '''
00222         # Get rev3_config.xml
00223         self.getRev3Config()
00224 
00225         # Check if the rev3.xml file exists
00226         userPreferenceFile = self.rev3_config.find('userPreferenceFile').text
00227         if userPreferenceFile[0] == '~':
00228              self.rev3_config.find('userPreferenceFile').text = u"%s%s" % (os.path.expanduser(u"~"), userPreferenceFile[1:])
00229         if os.path.isfile(self.rev3_config.find('userPreferenceFile').text):
00230             # Read the grabber rev3_config.xml configuration file
00231             url = u'file://%s' % (self.rev3_config.find('userPreferenceFile').text, )
00232             if self.config['debug_enabled']:
00233                 print url
00234                 print
00235             try:
00236                 self.userPrefs = etree.parse(url)
00237             except Exception, e:
00238                 raise Rev3UrlError(self.error_messages['Rev3UrlError'] % (url, errormsg))
00239             # Check if the rev3.xml file is too old
00240             nextUpdateSecs = int(self.userPrefs.find('updateDuration').text)*86400 # seconds in a day
00241             nextUpdate = time.localtime(os.path.getmtime(self.rev3_config.find('userPreferenceFile').text)+nextUpdateSecs)
00242             now = time.localtime()
00243             if nextUpdate > now:
00244                 return
00245             create = False
00246         else:
00247             create = True
00248 
00249         # If required create/update the rev3.xml file
00250         self.updateRev3(create)
00251         return
00252     # end getUserPreferences()
00253 
00254     def updateRev3(self, create=False):
00255         ''' Create or update the rev3.xml user preferences file
00256         return nothing
00257         '''
00258         userRev3 = etree.XML(u'''
00259 <userRev3>
00260 <!--
00261     The shows are split into three top level directories which represent how Rev3 categories
00262     their videos. Each top level directory has one or more shows. Each show has one or more
00263     MP4 formats that can be downloaded. The formats represent various video quality levels.
00264     Initially only three shows are enabled. You must change a show's "mp4Format" element's
00265     "enabled" attribute to 'true'. When you change the attribute to 'true' that show's RSS feed
00266     will be added to the Rev3 tree view. You could activate more than one MP4 format but that
00267     would result in duplicates. With the exception of "Tekzilla" which is a show that has
00268     both a weekly and daily video RSS feed within the same show element.
00269     When the Rev3 Tree view is created it will have links to the video's web page but also a
00270     link to the MP4 file that you can download through the MythNetvision interface.
00271     If a show moves from one top level directory to another your show sections will be
00272     preserved as long as that format is available in the new top level directory.
00273     Updates to the "rev3.xml" file is made every X number of days as determined by the value of
00274     the "updateDuration" element in this file. The default is every 3 days.
00275 -->
00276 <!-- Number of days between updates to the config file -->
00277 <updateDuration>3</updateDuration>
00278 
00279 <!--
00280     Personal RSS feed.
00281     "globalmax" (optional) Is a way to limit the number of items processed per RSS feed for all
00282                 treeview URLs. A value of zero (0) means there are no limitions.
00283     "max" (optional) Is a way to limit the number of items processed for an individual RSS feed.
00284           This value will override any "globalmax" setting. A value of zero (0) means
00285           there are no limitions and would be the same if the attribute was no included at all.
00286     "enabled" If you want to remove a RSS feed then change the "enabled" attribute to "false".
00287 
00288     See details: http://revision3.com/blog/2010/03/11/pick-your-favorite-shows-create-a-personalized-feed/
00289     Once you sign up and create your personal RSS feed replace the url in the example below with the
00290     Revision3 "Your RSS Feed Address" URL and change the "enabled" element attribute to "true".
00291 -->
00292 <treeviewURLS globalmax="0">
00293     <url enabled="false">http://revision3.com/feed/user/EXAMPLE</url>
00294 </treeviewURLS>
00295 </userRev3>
00296 ''')
00297 
00298         # Get the current show links from the Rev3 web site
00299         linksTree = self.common.getUrlData(self.rev3_config.find('treeviewUrls'))
00300 
00301         if self.config['debug_enabled']:
00302             print "create(%s)" % create
00303             print "linksTree:"
00304             sys.stdout.write(etree.tostring(linksTree, encoding='UTF-8', pretty_print=True))
00305             print
00306 
00307         # Extract the show name and Web page links
00308         showData = etree.XML(u'<xml></xml>')
00309         complexFilter = u"//div[@class='subscribe_rss']//div//p[.='MP4']/..//a"
00310         for result in linksTree.xpath('//results'):
00311             tmpDirectory = etree.XML(u'<directory></directory>')
00312             dirName = result.find('name').text
00313             tmpDirectory.attrib['name'] = dirName
00314 
00315             if self.config['debug_enabled']:
00316                 print "Results: #Items(%s) for (%s)" % (len(result.xpath('.//a')), dirName)
00317                 print
00318 
00319             for anchor in result.xpath('.//a'):
00320                 showURL = None
00321                 if dirName == 'Shows':
00322                     showURL = anchor.attrib.get('href')
00323                     showFilter = complexFilter
00324                     tmpName = anchor.text
00325                 elif dirName == 'Revision3 Beta':
00326                     tmpName = etree.tostring(anchor, method="text", encoding=unicode)
00327                     showURL = u'http://revision3beta.com%sfeed/' % anchor.attrib.get('href')
00328                     showFilter = None
00329                 elif dirName == 'Archived Shows':
00330                     showURL = u'http://revision3.com%s' % anchor.attrib.get('href')
00331                     showFilter = complexFilter
00332                     tmpName = anchor.text
00333                 if tmpName == u'Revision3 Beta':
00334                     continue
00335                 if showURL != None:
00336                     url = etree.SubElement(tmpDirectory, "url")
00337                     etree.SubElement(url, "name").text = tmpName
00338                     etree.SubElement(url, "href").text = showURL
00339                     etree.SubElement(url, "filter").text = showFilter
00340                     etree.SubElement(url, "parserType").text = u'html'
00341             if tmpDirectory.find('url') != None:
00342                 showData.append(tmpDirectory)
00343 
00344         if self.config['debug_enabled']:
00345             print "showData:"
00346             sys.stdout.write(etree.tostring(showData, encoding='UTF-8', pretty_print=True))
00347             print
00348 
00349         # Assemble the feeds and formats
00350         for directory in showData.findall('directory'):
00351             if create:
00352                 firstEnabled = True
00353             else:
00354                 firstEnabled = False
00355             tmpDirectory = etree.XML(u'<directory></directory>')
00356             tmpDirectory.attrib['name'] = directory.attrib['name']
00357             if directory.attrib['name'] == u'Revision3 Beta':
00358                 for show in directory.findall('url'):
00359                     tmpShow = etree.XML(u'<show></show>')
00360                     tmpShow.attrib['name'] = show.find('name').text
00361                     mp4Format = etree.SubElement(tmpShow, u"mp4Format")
00362                     if firstEnabled:
00363                         mp4Format.attrib['enabled'] = u'true'
00364                         firstEnabled = False
00365                     else:
00366                         mp4Format.attrib['enabled'] = u'false'
00367                     mp4Format.attrib['name'] = u'Web Only'
00368                     mp4Format.attrib['rss'] = show.find('href').text
00369                     tmpDirectory.append(tmpShow)
00370             else:
00371                 showResults = self.common.getUrlData(directory)
00372                 for show in showResults.xpath('//results'):
00373                     tmpShow = etree.XML(u'<show></show>')
00374                     tmpShow.attrib['name'] = show.find('name').text
00375 
00376                     if self.config['debug_enabled']:
00377                         print "Results: #Items(%s) for (%s)" % (len(show.xpath('.//a')), tmpShow.attrib['name'])
00378                         print
00379 
00380                     for format in show.xpath('.//a'):
00381                         link = u'http://revision3.com%s' % format.attrib['href']
00382                         # If this is a "tekzilla" link without extra parameters that skip show
00383                         # This forces the Tekzilla weekly show to be seperate from the daily show
00384                         if link.find('/tekzilla/') != -1 and link.find('?subshow=false') == -1:
00385                             continue
00386                         mp4Format = etree.SubElement(tmpShow, "mp4Format")
00387                         if firstEnabled:
00388                             mp4Format.attrib['enabled'] = u'true'
00389                             firstEnabled = False
00390                         else:
00391                             mp4Format.attrib['enabled'] = u'false'
00392                         mp4Format.attrib['name'] = format.text
00393                         mp4Format.attrib['rss'] = link
00394                     if tmpShow.find('mp4Format') != None:
00395                         tmpDirectory.append(tmpShow)
00396 
00397             # If there is any data then add to new rev3.xml element tree
00398             if tmpDirectory.find('show') != None:
00399                 userRev3.append(tmpDirectory)
00400 
00401         if self.config['debug_enabled']:
00402             print "Before any merging userRev3:"
00403             sys.stdout.write(etree.tostring(userRev3, encoding='UTF-8', pretty_print=True))
00404             print
00405 
00406         # If there was an existing rev3.xml file then add any relevant user settings to this new rev3.xml
00407         if not create:
00408             userRev3.find('updateDuration').text = self.userPrefs.find('updateDuration').text
00409             for mp4Format in self.userPrefs.xpath("//mp4Format[@enabled='true']"):
00410                 showName = mp4Format.getparent().attrib['name']
00411                 mp4name = mp4Format.attrib['name']
00412                 elements = userRev3.xpath("//show[@name=$showName]/mp4Format[@name=$mp4name]", showName=showName, mp4name=mp4name)
00413                 if len(elements):
00414                     elements[0].attrib['enabled'] = u'true'
00415 
00416         if self.config['debug_enabled']:
00417             print "After any merging userRev3:"
00418             sys.stdout.write(etree.tostring(userRev3, encoding='UTF-8', pretty_print=True))
00419             print
00420 
00421         # Save the rev3.xml file
00422         prefDir = self.rev3_config.find('userPreferenceFile').text.replace(u'/rev3.xml', u'')
00423         if not os.path.isdir(prefDir):
00424             os.makedirs(prefDir)
00425         fd = open(self.rev3_config.find('userPreferenceFile').text, 'w')
00426         fd.write(u'<userRev3>\n'+u''.join(etree.tostring(element, encoding='UTF-8', pretty_print=True) for element in userRev3)+u'</userRev3>')
00427         fd.close()
00428 
00429         # Read the refreshed user config file
00430         try:
00431             self.userPrefs = etree.parse(self.rev3_config.find('userPreferenceFile').text)
00432         except Exception, e:
00433             raise Rev3UrlError(self.error_messages['Rev3UrlError'] % (url, errormsg))
00434         return
00435     # end updateRev3()
00436 
00437     def getSeasonEpisode(self, title, link=None):
00438         ''' Check is there is any season or episode number information in an item's title
00439         return array of season and/or episode numbers
00440         return array with None values
00441         '''
00442         s_e = [None, None]
00443         for index in range(len(self.s_e_Patterns)):
00444             match = self.s_e_Patterns[index].match(title)
00445             if not match:
00446                 if link:
00447                     match = self.s_e_Patterns[index].match(link)
00448                     if not match:
00449                         continue
00450                 else:
00451                     continue
00452             if index < 2:
00453                 s_e[0], s_e[1] = match.groups()
00454                 break
00455             else:
00456                 s_e[1] = u'%s' % int(match.groups()[0])
00457                 break
00458         return s_e
00459     # end getSeasonEpisode()
00460 
00461     def getVideoID(self, link):
00462         ''' Read the Web page to find the video ID number used for fullscreen autoplay
00463         return the video ID number
00464         return None if the number cannot be found
00465         '''
00466         videoID = None
00467         try:
00468             eTree = etree.parse(link, self.FullScreenParser)
00469         except Exception, errormsg:
00470             sys.stderr.write(u"! Error: The URL (%s) cause the exception error (%s)\n" % (link, errormsg))
00471             return videoID
00472 
00473         if self.config['debug_enabled']:
00474             print "Raw unfiltered URL imput:"
00475             sys.stdout.write(etree.tostring(eTree, encoding='UTF-8', pretty_print=True))
00476             print
00477 
00478         if not eTree:
00479             return videoID
00480 
00481         # Filter out the video id
00482         try:
00483            tmpVideoID = self.FullScreenVidIDxPath(eTree)
00484         except AssertionError, e:
00485             sys.stderr.write("No filter results for VideoID from url(%s)\n" % link)
00486             sys.stderr.write("! Error:(%s)\n" % e)
00487             return videoID
00488 
00489         if len(tmpVideoID):
00490             if tmpVideoID[0].get('id'):
00491                 videoID = tmpVideoID[0].attrib['id'].strip().replace(u'player-', u'')
00492 
00493         return videoID
00494 
00495 ###########################################################################################################
00496 #
00497 # End of Utility functions
00498 #
00499 ###########################################################################################################
00500 
00501 
00502     def searchTitle(self, title, pagenumber, pagelen):
00503         '''Key word video search of the Rev3 web site
00504         return an array of matching item elements
00505         return
00506         '''
00507         try:
00508             searchVar = u'?q=%s' % (urllib.quote(title.encode("utf-8")).replace(u' ', u'+'))
00509         except UnicodeDecodeError:
00510             searchVar = u'?q=%s' % (urllib.quote(title).replace(u' ', u'+'))
00511         url = self.rev3_config.find('searchURLS').xpath(".//href")[0].text+searchVar
00512 
00513         if self.config['debug_enabled']:
00514             print url
00515             print
00516 
00517         self.rev3_config.find('searchURLS').xpath(".//href")[0].text = url
00518 
00519         # Perform a search
00520         try:
00521             resultTree = self.common.getUrlData(self.rev3_config.find('searchURLS'), pageFilter=self.rev3_config.find('searchURLS').xpath(".//pageFilter")[0].text)
00522         except Exception, errormsg:
00523             raise Rev3UrlDownloadError(self.error_messages['Rev3UrlDownloadError'] % (errormsg))
00524 
00525         if resultTree is None:
00526             raise Rev3VideoNotFound(u"No Rev3 Video matches found for search value (%s)" % title)
00527 
00528         searchResults = resultTree.xpath('//result//li[@class="video"]')
00529         if not len(searchResults):
00530             raise Rev3VideoNotFound(u"No Rev3 Video matches found for search value (%s)" % title)
00531 
00532         # Set the number of search results returned
00533         self.channel['channel_numresults'] = len(searchResults)
00534 
00535         # Rev3 search results fo not have a pubDate so use the current data time
00536         # e.g. "Sun, 06 Jan 2008 21:44:36 GMT"
00537         pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat)
00538 
00539         # Translate the search results into MNV RSS item format
00540         thumbnailFilter = etree.XPath('.//a[@class="thumbnail"]/img/@src')
00541         titleFilter = etree.XPath('.//a[@class="title"]')
00542         descFilter = etree.XPath('.//div[@class="description"]')
00543         itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces)
00544         itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces)
00545         itemDict = {}
00546         for result in searchResults:
00547             if len(titleFilter(result)):   # Make sure that this result actually has a video
00548                 rev3Item = etree.XML(self.common.mnvItem)
00549                 # Extract and massage data
00550                 thumbNail = self.common.ampReplace(thumbnailFilter(result)[0])
00551                 title = self.common.massageText(titleFilter(result)[0].text.strip())
00552                 tmpDesc = etree.tostring(descFilter(result)[0], method="text", encoding=unicode).strip()
00553                 index = tmpDesc.find(u'–')
00554                 if index != -1:
00555                     tmpDesc = tmpDesc[index+1:].strip()
00556                 description = self.common.massageText(tmpDesc)
00557                 link = self.common.ampReplace(titleFilter(result)[0].attrib['href'])
00558                 author = u'Revision3'
00559                 # Insert data into a new item element
00560                 rev3Item.find('title').text = title
00561                 rev3Item.find('author').text = author
00562                 rev3Item.find('pubDate').text = pubDate
00563                 rev3Item.find('description').text = description
00564                 rev3Item.find('link').text = link
00565                 itemThumbNail(rev3Item)[0].attrib['url'] = thumbNail
00566                 itemDwnLink(rev3Item)[0].attrib['url'] = link
00567                 s_e = self.getSeasonEpisode(title, None)
00568                 if s_e[0]:
00569                     etree.SubElement(rev3Item, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
00570                 if s_e[1]:
00571                     etree.SubElement(rev3Item, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
00572                 itemDict[title.lower()] = rev3Item
00573 
00574         if not len(itemDict.keys()):
00575             raise Rev3VideoNotFound(u"No Rev3 Video matches found for search value (%s)" % title)
00576 
00577         return [itemDict, resultTree.xpath('//pageInfo')[0].text]
00578         # end searchTitle()
00579 
00580 
00581     def searchForVideos(self, title, pagenumber):
00582         """Common name for a video search. Used to interface with MythTV plugin NetVision
00583         """
00584         # Get rev3_config.xml
00585         self.getRev3Config()
00586 
00587         if self.config['debug_enabled']:
00588             print "self.rev3_config:"
00589             sys.stdout.write(etree.tostring(self.rev3_config, encoding='UTF-8', pretty_print=True))
00590             print
00591 
00592         # Easier for debugging
00593 #        print self.searchTitle(title, pagenumber, self.page_limit)
00594 #        print
00595 #        sys.exit()
00596 
00597         try:
00598             data = self.searchTitle(title, pagenumber, self.page_limit)
00599         except Rev3VideoNotFound, msg:
00600             sys.stderr.write(u"%s\n" % msg)
00601             sys.exit(0)
00602         except Rev3UrlError, msg:
00603             sys.stderr.write(u'%s\n' % msg)
00604             sys.exit(1)
00605         except Rev3HttpError, msg:
00606             sys.stderr.write(self.error_messages['Rev3HttpError'] % msg)
00607             sys.exit(1)
00608         except Rev3RssError, msg:
00609             sys.stderr.write(self.error_messages['Rev3RssError'] % msg)
00610             sys.exit(1)
00611         except Exception, e:
00612             sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e))
00613             sys.exit(1)
00614 
00615         # Create RSS element tree
00616         rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
00617 
00618         # Set the paging values
00619         itemCount = len(data[0].keys())
00620         if data[1] == 'true':
00621             self.channel['channel_returned'] = itemCount
00622             self.channel['channel_startindex'] = itemCount
00623             self.channel['channel_numresults'] = itemCount+(self.page_limit*(int(pagenumber)-1)+1)
00624         else:
00625             self.channel['channel_returned'] = itemCount+(self.page_limit*(int(pagenumber)-1))
00626             self.channel['channel_startindex'] = self.channel['channel_returned']
00627             self.channel['channel_numresults'] = self.channel['channel_returned']
00628 
00629         # Add the Channel element tree
00630         channelTree = self.common.mnvChannelElement(self.channel)
00631         rssTree.append(channelTree)
00632 
00633         lastKey = None
00634         for key in sorted(data[0].keys()):
00635             if lastKey != key:
00636                 channelTree.append(data[0][key])
00637                 lastKey = key
00638 
00639         # Output the MNV search results
00640         sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
00641         sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
00642         sys.exit(0)
00643     # end searchForVideos()
00644 
00645     def displayTreeView(self):
00646         '''Gather the Revision3 feeds then get a max page of videos meta data in each of them
00647         Display the results and exit
00648         '''
00649         personalFeed = u"Personal Feed" # A label used to identify processing of a personal RSS feed
00650 
00651         # Get the user preferences that specify which shows and formats they want to be in the treeview
00652         try:
00653             self.getUserPreferences()
00654         except Exception, e:
00655             sys.stderr.write(u'%s' % e)
00656             sys.exit(1)
00657 
00658         if self.config['debug_enabled']:
00659             print "self.userPrefs:"
00660             sys.stdout.write(etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True))
00661             print
00662 
00663         # Verify that there is at least one RSS feed that user wants to download
00664         rssFeeds = self.userPrefs.xpath("//mp4Format[@enabled='true']")
00665         personalFeeds = self.userPrefs.xpath("//treeviewURLS//url[@enabled='true']")
00666         if not len(rssFeeds) and not len(personalFeeds):
00667             sys.stderr.write(u'There are no mp4Format or personal RSS feed elements "enabled" in your "rev3.xml" user preferences\nfile (%s)\n' % self.rev3_config.find('userPreferenceFile').text)
00668             sys.exit(1)
00669 
00670         # Create a structure of feeds that can be concurrently downloaded
00671         showData = etree.XML(u'<xml></xml>')
00672         for feed in personalFeeds:
00673             rssFeeds.append(feed)
00674         count = 0
00675         for rssFeed in rssFeeds:
00676             if rssFeed.getparent().tag == 'treeviewURLS':
00677                 uniqueName = u'%s:%s' % (personalFeed, count)
00678                 count+=1
00679             else:
00680                 uniqueName = u'%s:%s:%s' % (rssFeed.getparent().getparent().attrib['name'], rssFeed.getparent().attrib['name'], rssFeed.attrib['name'])
00681             url = etree.XML(u'<url></url>')
00682             etree.SubElement(url, "name").text = uniqueName
00683             if uniqueName.startswith(personalFeed):
00684                 etree.SubElement(url, "href").text = rssFeed.text
00685             else:
00686                 etree.SubElement(url, "href").text = rssFeed.attrib['rss']
00687             etree.SubElement(url, "filter").text = u"//channel"
00688             etree.SubElement(url, "parserType").text = u'xml'
00689             showData.append(url)
00690 
00691         if self.config['debug_enabled']:
00692             print "showData:"
00693             sys.stdout.write(etree.tostring(showData, encoding='UTF-8', pretty_print=True))
00694             print
00695 
00696         # Get the RSS Feed data
00697         try:
00698             resultTree = self.common.getUrlData(showData)
00699         except Exception, errormsg:
00700             raise Rev3UrlDownloadError(self.error_messages['Rev3UrlDownloadError'] % (errormsg))
00701 
00702         if resultTree is None:
00703             sys.exit(0)
00704 
00705         if self.config['debug_enabled']:
00706             print "resultTree:"
00707             sys.stdout.write(etree.tostring(resultTree, encoding='UTF-8', pretty_print=True))
00708             print
00709 
00710         # Create RSS element tree
00711         rssTree = etree.XML(self.common.mnvRSS+u'</rss>')
00712 
00713         # Add the Channel element tree
00714         channelTree = self.common.mnvChannelElement(self.channel)
00715         rssTree.append(channelTree)
00716 
00717         # Process each directory of the user preferences that have an enabled rss feed
00718         itemFilter = etree.XPath('.//item')
00719         channelFilter = etree.XPath('./result/channel')
00720         imageFilter = etree.XPath('.//image/url')
00721         itemDwnLink = './/media:content'
00722         itemThumbNail = './/media:thumbnail'
00723         itemDuration = './/media:content'
00724         itemLanguage = './/media:content'
00725         categoryDir = None
00726         showDir = None
00727         categoryElement = etree.XML(u'<directory></directory>')
00728         itemAuthor = u'Revision3'
00729         for result in resultTree.findall('results'):
00730             names = result.find('name').text.split(':')
00731             for index in range(len(names)):
00732                 names[index] = self.common.massageText(names[index])
00733             channel = channelFilter(result)[0]
00734             if channel.find('image') != None:
00735                 channelThumbnail = self.common.ampReplace(imageFilter(channel)[0].text)
00736             else:
00737                 channelThumbnail = self.common.ampReplace(channel.find('link').text.replace(u'/watch/', u'/images/')+u'100.jpg')
00738             channelLanguage = u'en'
00739             if channel.find('language') != None:
00740                 channelLanguage = channel.find('language').text[:2]
00741             # Create a new directory and/or subdirectory if required
00742             if names[0] != categoryDir:
00743                 if categoryDir != None:
00744                     channelTree.append(categoryElement)
00745                 categoryElement = etree.XML(u'<directory></directory>')
00746                 if names[0] == personalFeed:
00747                     categoryElement.attrib['name'] = channel.find('title').text
00748                 else:
00749                     categoryElement.attrib['name'] = names[0]
00750                 categoryElement.attrib['thumbnail'] = self.channel_icon
00751                 categoryDir = names[0]
00752             if names[1] != showDir:
00753                 if names[0] == personalFeed:
00754                     showElement = categoryElement
00755                 else:
00756                     showElement = etree.SubElement(categoryElement, u"directory")
00757                     if names[2] == 'Web Only':
00758                         showElement.attrib['name'] = u'%s' % (names[1])
00759                     else:
00760                         showElement.attrib['name'] = u'%s: %s' % (names[1], names[2])
00761                     showElement.attrib['thumbnail'] = channelThumbnail
00762                 showDir = names[1]
00763 
00764             if self.config['debug_enabled']:
00765                 print "Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names)
00766                 print
00767 
00768             # Convert each RSS item into a MNV item
00769             for itemData in itemFilter(result):
00770                 rev3Item = etree.XML(self.common.mnvItem)
00771                 # Extract and massage data also insert data into a new item element
00772                 rev3Item.find('title').text = self.common.massageText(itemData.find('title').text.strip())
00773                 rev3Item.find('author').text = itemAuthor
00774                 rev3Item.find('pubDate').text = self.common.massageText(itemData.find('pubDate').text)
00775                 rev3Item.find('description').text = self.common.massageText(itemData.find('description').text.strip())
00776                 link = self.common.ampReplace(itemData.find('link').text)
00777                 downLoadLink = self.common.ampReplace(itemData.xpath(itemDwnLink, namespaces=self.common.namespaces)[0].attrib['url'])
00778 
00779                 # If this is one of the main shows or from the personal RSS feed
00780                 # then get a full screen video id
00781                 if names[0] == 'Shows' or names[0] == personalFeed:
00782                     fullScreenVideoID = self.getVideoID(itemData.find('link').text)
00783                     if fullScreenVideoID:
00784                         if link == downLoadLink:
00785                             downLoadLink = self.common.ampReplace(self.FullScreen % fullScreenVideoID)
00786                         link = self.common.ampReplace(self.FullScreen % fullScreenVideoID)
00787 
00788                 rev3Item.find('link').text = self.common.ampReplace(link)
00789                 rev3Item.xpath(itemDwnLink, namespaces=self.common.namespaces)[0].attrib['url'] = downLoadLink
00790                 try:
00791                     rev3Item.xpath(itemThumbNail, namespaces=self.common.namespaces)[0].attrib['url'] = self.common.ampReplace(itemData.xpath(itemThumbNail, namespaces=self.common.namespaces)[0].attrib['url'].replace(u'--mini', u'--medium'))
00792                 except IndexError:
00793                     pass
00794                 try:
00795                     rev3Item.xpath(itemDuration, namespaces=self.common.namespaces)[0].attrib['duration'] = itemData.xpath(itemDuration, namespaces=self.common.namespaces)[0].attrib['duration']
00796                 except KeyError:
00797                     pass
00798                 rev3Item.xpath(itemLanguage, namespaces=self.common.namespaces)[0].attrib['lang'] = channelLanguage
00799                 if rev3Item.xpath(itemThumbNail, namespaces=self.common.namespaces)[0].get('url'):
00800                     s_e = self.getSeasonEpisode(rev3Item.find('title').text, rev3Item.xpath(itemThumbNail, namespaces=self.common.namespaces)[0].attrib['url'])
00801                 else:
00802                     s_e = self.getSeasonEpisode(rev3Item.find('title').text, rev3Item.xpath(itemDwnLink, namespaces=self.common.namespaces)[0].attrib['url'])
00803                 if s_e[0]:
00804                     etree.SubElement(rev3Item, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season").text = s_e[0]
00805                 if s_e[1]:
00806                     etree.SubElement(rev3Item, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode").text = s_e[1]
00807                 if s_e[0] and s_e[1]:
00808                     rev3Item.find('title').text = u'S%02dE%02d: %s' % (int(s_e[0]), int (s_e[1]), rev3Item.find('title').text)
00809                 elif s_e[0]:
00810                     rev3Item.find('title').text = u'S%02d: %s' % (int(s_e[0]), rev3Item.find('title').text)
00811                 elif s_e[1]:
00812                     rev3Item.find('title').text = u'Ep%03d: %s' % (int(s_e[1]), rev3Item.find('title').text)
00813                 showElement.append(rev3Item)
00814 
00815         # Add the last directory processed
00816         if categoryElement.xpath('.//item') != None:
00817             channelTree.append(categoryElement)
00818 
00819         # Check that there was at least some items
00820         if len(rssTree.xpath('//item')):
00821             # Output the MNV search results
00822             sys.stdout.write(u'<?xml version="1.0" encoding="UTF-8"?>\n')
00823             sys.stdout.write(etree.tostring(rssTree, encoding='UTF-8', pretty_print=True))
00824 
00825         sys.exit(0)
00826     # end displayTreeView()
00827 # end Videos() class
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends