|
MythTV
0.26-pre
|
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
1.7.6.1