|
MythTV
0.26-pre
|
00001 #!/usr/bin/env python 00002 # -*- coding: UTF-8 -*- 00003 # ---------------------- 00004 # Name: bliptv_api - Simple-to-use Python interface to the bliptv API (http://blip.tv/) 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 # meta data, video and image URLs from blip.tv. 00009 # These routines are based on the v2.0 api. Specifications 00010 # for this api are published at http://blip.tv/about/api/ 00011 # 00012 # License:Creative Commons GNU GPL v2 00013 # (http://creativecommons.org/licenses/GPL/2.0/) 00014 #------------------------------------- 00015 __title__ ="bliptv_api - Simple-to-use Python interface to the bliptv API (http://blip.tv/about/api/)" 00016 __author__="R.D. Vaughan" 00017 __purpose__=''' 00018 This python script is intended to perform a variety of utility functions to search and access text 00019 meta data, video and image URLs from blip.tv. These routines are based on the v2.0 api. Specifications 00020 for this api are published at http://blip.tv/about/api/ 00021 ''' 00022 00023 __version__="v0.2.5" 00024 # 0.1.0 Initial development 00025 # 0.1.1 Changed to use bliptv's rss data rather than JSON as JSON ad a number of error 00026 # 0.1.2 Changes Search to parse XML and added Tree view 00027 # 0.1.3 Added directory image logic 00028 # 0.1.4 Documentation updates 00029 # 0.2.0 Public release 00030 # 0.2.1 New python bindings conversion 00031 # Better exception error reporting 00032 # Better handling of invalid unicode data from source 00033 # 0.2.2 Completed exception message improvements 00034 # Removed the unused import of the feedparser library 00035 # 0.2.3 Fixed an exception message output code error in two places 00036 # 0.2.4 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path 00037 # 0.2.5 Changed link URL to full screen when a "blip:embedUrl" exists for an item 00038 # Removed a subdirectory level as the "Featured" RSS feed has been discontinued 00039 00040 import os, struct, sys, re, time 00041 import urllib, urllib2 00042 import logging 00043 from MythTV import MythXML 00044 00045 try: 00046 import xml.etree.cElementTree as ElementTree 00047 except ImportError: 00048 import xml.etree.ElementTree as ElementTree 00049 00050 from bliptv_exceptions import (BliptvUrlError, BliptvHttpError, BliptvRssError, BliptvVideoNotFound, BliptvXmlError) 00051 00052 class OutStreamEncoder(object): 00053 """Wraps a stream with an encoder""" 00054 def __init__(self, outstream, encoding=None): 00055 self.out = outstream 00056 if not encoding: 00057 self.encoding = sys.getfilesystemencoding() 00058 else: 00059 self.encoding = encoding 00060 00061 def write(self, obj): 00062 """Wraps the output stream, encoding Unicode strings with the specified encoding""" 00063 if isinstance(obj, unicode): 00064 try: 00065 self.out.write(obj.encode(self.encoding)) 00066 except IOError: 00067 pass 00068 else: 00069 try: 00070 self.out.write(obj) 00071 except IOError: 00072 pass 00073 00074 def __getattr__(self, attr): 00075 """Delegate everything but write to the stream""" 00076 return getattr(self.out, attr) 00077 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8') 00078 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8') 00079 00080 00081 class XmlHandler: 00082 """Deals with retrieval of XML files from API 00083 """ 00084 def __init__(self, url): 00085 self.url = url 00086 00087 def _grabUrl(self, url): 00088 try: 00089 urlhandle = urllib.urlopen(url) 00090 except IOError, errormsg: 00091 raise BliptvHttpError(errormsg) 00092 return urlhandle.read() 00093 00094 def getEt(self): 00095 xml = self._grabUrl(self.url) 00096 try: 00097 et = ElementTree.fromstring(xml) 00098 except SyntaxError, errormsg: 00099 raise BliptvXmlError(errormsg) 00100 return et 00101 00102 00103 class Videos(object): 00104 """Main interface to http://blip.tv/ 00105 This is done to support a common naming framework for all python Netvision plugins no matter their site 00106 target. 00107 00108 Supports search and tree view methods 00109 The apikey is a not required to access http://blip.tv/ 00110 """ 00111 def __init__(self, 00112 apikey, 00113 mythtv = True, 00114 interactive = False, 00115 select_first = False, 00116 debug = False, 00117 custom_ui = None, 00118 language = None, 00119 search_all_languages = False, 00120 ): 00121 """apikey (str/unicode): 00122 Specify the target site API key. Applications need their own key in some cases 00123 00124 mythtv (True/False): 00125 When True, the returned meta data is being returned has the key and values massaged to match MythTV 00126 When False, the returned meta data is being returned matches what target site returned 00127 00128 interactive (True/False): (This option is not supported by all target site apis) 00129 When True, uses built-in console UI is used to select the correct show. 00130 When False, the first search result is used. 00131 00132 select_first (True/False): (This option is not supported currently implemented in any grabbers) 00133 Automatically selects the first series search result (rather 00134 than showing the user a list of more than one series). 00135 Is overridden by interactive = False, or specifying a custom_ui 00136 00137 debug (True/False): 00138 shows verbose debugging information 00139 00140 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) 00141 A callable subclass of interactive class (overrides interactive option) 00142 00143 language (2 character language abbreviation): (This option is not supported by all target site apis) 00144 The language of the returned data. Is also the language search 00145 uses. Default is "en" (English). For full list, run.. 00146 00147 search_all_languages (True/False): (This option is not supported by all target site apis) 00148 By default, a Netvision grabber will only search in the language specified using 00149 the language option. When this is True, it will search for the 00150 show in any language 00151 00152 """ 00153 self.config = {} 00154 self.mythxml = MythXML() 00155 00156 if apikey is not None: 00157 self.config['apikey'] = apikey 00158 else: 00159 pass # blip.tv does not require an apikey 00160 00161 self.config['debug_enabled'] = debug # show debugging messages 00162 00163 self.log_name = "Bliptv" 00164 self.log = self._initLogger() # Setups the logger (self.log.debug() etc) 00165 00166 self.config['custom_ui'] = custom_ui 00167 00168 self.config['interactive'] = interactive # prompt for correct series? 00169 00170 self.config['select_first'] = select_first 00171 00172 self.config['search_all_languages'] = search_all_languages 00173 00174 # Defaulting to ENGISH but the blip.tv apis do not support specifying a language 00175 self.config['language'] = "en" 00176 00177 self.error_messages = {'BliptvUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'BliptvHttpError': u"! Error: An HTTP communicating error with blip.tv was raised (%s)\n", 'BliptvRssError': u"! Error: Invalid RSS meta data\nwas received from blip.tv error (%s). Skipping item.\n", 'BliptvVideoNotFound': u"! Error: Video search with blip.tv did not return any results (%s)\n", } 00178 00179 # This is an example that must be customized for each target site 00180 self.key_translation = [{'channel_title': 'channel_title', 'channel_link': 'channel_link', 'channel_description': 'channel_description', 'channel_numresults': 'channel_numresults', 'channel_returned': 'channel_returned', 'channel_startindex': 'channel_startindex'}, {'title': 'item_title', 'blip_safeusername': 'item_author', 'updated': 'item_pubdate', 'blip_puredescription': 'item_description', 'link': 'item_link', 'blip_picture': 'item_thumbnail', 'video': 'item_url', 'blip_runtime': 'item_duration', 'blip_rating': 'item_rating', 'width': 'item_width', 'height': 'item_height', 'language': 'item_lang'}] 00181 00182 # The following url_ configs are based of the 00183 # http://blip.tv/about/api/ 00184 self.config['base_url'] = "http://www.blip.tv%s" 00185 self.config['thumb_url'] = u"http://a.images.blip.tv%s" 00186 00187 self.config[u'urls'] = {} 00188 00189 # v2 api calls - An example that must be customized for each target site 00190 self.config[u'urls'][u'video.search'] = "http://www.blip.tv/?search=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss" 00191 self.config[u'urls'][u'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api" 00192 00193 self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions 00194 00195 # Functions that parse video data from RSS data 00196 self.config['item_parser'] = {} 00197 self.config['item_parser']['main'] = self.getVideosForURL 00198 00199 # Tree view url and the function that parses that urls meta data 00200 self.config[u'urls'][u'tree.view'] = { 00201 'P_R_R_F': { 00202 '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'], 00203 }, 00204 'categories': { 00205 '__all__': ['http://www.blip.tv/rss/', 'main'], 00206 }, 00207 } 00208 00209 # Tree view categories are disabled until their results can be made more meaningful 00210 #self.tree_order = ['P_R_R_F', 'categories', ] 00211 self.tree_order = ['P_R_R_F'] 00212 00213 self.tree_org = { 00214 # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]], 00215 'P_R_R_F': [['', ['popular', 'recent', 'random', 'featured',]], 00216 ], 00217 # categories are dynamically filled in from a list retrieved from the blip.tv site 00218 'categories': [ 00219 ['Categories', u''], 00220 ], 00221 } 00222 00223 self.tree_customize = { 00224 'P_R_R_F': { 00225 '__default__': { }, 00226 #'cat name': {}, 00227 }, 00228 'categories': { 00229 '__default__': {'categories_id': u'', 'sort': u'', }, 00230 #'cat name': {}, 00231 }, 00232 } 00233 00234 self.feed_names = { 00235 'P_R_R_F': {'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', 00236 }, 00237 'categories': {'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', 00238 }, 00239 } 00240 00241 self.feed_icons = { 00242 'P_R_R_F': {'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', 00243 }, 00244 'categories': {'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', 00245 }, 00246 } 00247 00248 # Initialize the tree view flag so that the item parsing code can be used for multiple purposes 00249 self.categories = False 00250 self.treeview = False 00251 self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bliptv.png' 00252 # end __init__() 00253 00254 00255 ########################################################################################################### 00256 # 00257 # Start - Utility functions 00258 # 00259 ########################################################################################################### 00260 00261 def detectUserLocationByIP(self): 00262 '''Get longitude and latitiude to find videos relative to your location. Up to three different 00263 servers will be tried before giving up. 00264 return a dictionary e.g. 00265 {'Latitude': '43.6667', 'Country': 'Canada', 'Longitude': '-79.4167', 'City': 'Toronto'} 00266 return an empty dictionary if there were any errors 00267 Code found at: http://blog.suinova.com/2009/04/from-ip-to-geolocation-country-city.html 00268 ''' 00269 def getExternalIP(): 00270 '''Find the external IP address of this computer. 00271 ''' 00272 url = urllib.URLopener() 00273 try: 00274 resp = url.open('http://www.whatismyip.com/automation/n09230945.asp') 00275 return resp.read() 00276 except: 00277 return None 00278 # end getExternalIP() 00279 00280 ip = getExternalIP() 00281 00282 if ip == None: 00283 return {} 00284 00285 try: 00286 gs = urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml' % ip) 00287 txt = gs.read() 00288 except: 00289 try: 00290 gs = urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip) 00291 txt = gs.read() 00292 except: 00293 try: 00294 gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip) 00295 txt = gs.read() 00296 except: 00297 logging.error('GeoIP servers not available') 00298 return {} 00299 try: 00300 if txt.find('<Response>') > 0: 00301 countrys = re.findall(r'<CountryName>([\w ]+)<',txt)[0] 00302 citys = re.findall(r'<City>([\w ]+)<',txt)[0] 00303 lats,lons = re.findall(r'<Latitude>([\d\-\.]+)</Latitude>\s*<Longitude>([\d\-\.]+)<',txt)[0] 00304 elif txt.find('GLatLng') > 0: 00305 citys,countrys = re.findall('<br />\s*([^<]+)<br />\s*([^<]+)<',txt)[0] 00306 lats,lons = re.findall('LatLng\(([-\d\.]+),([-\d\.]+)',txt)[0] 00307 elif txt.find('<gml:coordinates>') > 0: 00308 citys = re.findall('<Hostip>\s*<gml:name>(\w+)</gml:name>',txt)[0] 00309 countrys = re.findall('<countryName>([\w ,\.]+)</countryName>',txt)[0] 00310 lats,lons = re.findall('gml:coordinates>([-\d\.]+),([-\d\.]+)<',txt)[0] 00311 else: 00312 logging.error('error parsing IP result %s'%txt) 00313 return {} 00314 return {'Country':countrys,'City':citys,'Latitude':lats,'Longitude':lons} 00315 except: 00316 logging.error('Error parsing IP result %s'%txt) 00317 return {} 00318 # end detectUserLocationByIP() 00319 00320 def massageDescription(self, text): 00321 '''Removes HTML markup from a text string. 00322 @param text The HTML source. 00323 @return The plain text. If the HTML source contains non-ASCII 00324 entities or character references, this is a Unicode string. 00325 ''' 00326 def fixup(m): 00327 text = m.group(0) 00328 if text[:1] == "<": 00329 return "" # ignore tags 00330 if text[:2] == "&#": 00331 try: 00332 if text[:3] == "&#x": 00333 return unichr(int(text[3:-1], 16)) 00334 else: 00335 return unichr(int(text[2:-1])) 00336 except ValueError: 00337 pass 00338 elif text[:1] == "&": 00339 import htmlentitydefs 00340 entity = htmlentitydefs.entitydefs.get(text[1:-1]) 00341 if entity: 00342 if entity[:2] == "&#": 00343 try: 00344 return unichr(int(entity[2:-1])) 00345 except ValueError: 00346 pass 00347 else: 00348 return unicode(entity, "iso-8859-1") 00349 return text # leave as is 00350 return self.ampReplace(re.sub(u"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace(u'\n',u' ') 00351 # end massageDescription() 00352 00353 00354 def _initLogger(self): 00355 """Setups a logger using the logging module, returns a log object 00356 """ 00357 logger = logging.getLogger(self.log_name) 00358 formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s') 00359 00360 hdlr = logging.StreamHandler(sys.stdout) 00361 00362 hdlr.setFormatter(formatter) 00363 logger.addHandler(hdlr) 00364 00365 if self.config['debug_enabled']: 00366 logger.setLevel(logging.DEBUG) 00367 else: 00368 logger.setLevel(logging.WARNING) 00369 return logger 00370 #end initLogger 00371 00372 00373 def textUtf8(self, text): 00374 if text == None: 00375 return text 00376 try: 00377 return unicode(text, 'utf8') 00378 except UnicodeDecodeError: 00379 return u'' 00380 except (UnicodeEncodeError, TypeError): 00381 return text 00382 # end textUtf8() 00383 00384 00385 def ampReplace(self, text): 00386 '''Replace all "&" characters with "&" 00387 ''' 00388 text = self.textUtf8(text) 00389 return text.replace(u'&',u'~~~~~').replace(u'&',u'&').replace(u'~~~~~', u'&') 00390 # end ampReplace() 00391 00392 def setTreeViewIcon(self, dir_icon=None): 00393 '''Check if there is a specific generic tree view icon. If not default to the channel icon. 00394 return self.tree_dir_icon 00395 ''' 00396 self.tree_dir_icon = self.channel_icon 00397 if not dir_icon: 00398 if not self.feed_icons.has_key(self.tree_key): 00399 return self.tree_dir_icon 00400 if not self.feed_icons[self.tree_key].has_key(self.feed): 00401 return self.tree_dir_icon 00402 dir_icon = self.feed_icons[self.tree_key][self.feed] 00403 if not dir_icon: 00404 return self.tree_dir_icon 00405 self.tree_dir_icon = u'%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, ) 00406 return self.tree_dir_icon 00407 # end setTreeViewIcon() 00408 00409 ########################################################################################################### 00410 # 00411 # End of Utility functions 00412 # 00413 ########################################################################################################### 00414 00415 def processVideoUrl(self, url): 00416 playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bliptv.html", \ 00417 url.replace(u'http://blip.tv/play/', '')) 00418 return self.ampReplace(playerUrl) 00419 00420 def searchTitle(self, title, pagenumber, pagelen): 00421 '''Key word video search of the blip.tv web site 00422 return an array of matching item dictionaries 00423 return 00424 ''' 00425 url = self.config[u'urls'][u'video.search'] % (urllib.quote_plus(title.encode("utf-8")), pagenumber, pagelen, self.config['language']) 00426 00427 if self.config['debug_enabled']: 00428 print "Search URL:" 00429 print url 00430 print 00431 00432 try: 00433 etree = XmlHandler(url).getEt() 00434 except Exception, errormsg: 00435 raise BliptvUrlError(self.error_messages['BliptvUrlError'] % (url, errormsg)) 00436 00437 if etree is None: 00438 raise BliptvVideoNotFound(u"1-No blip.tv Video matches found for search value (%s)" % title) 00439 00440 # Massage each field and eliminate any item without a URL 00441 elements_final = [] 00442 dictionary_first = False 00443 directory_image = u'' 00444 self.next_page = False 00445 language = self.config['language'] 00446 for elements in etree.find('channel'): 00447 if elements.tag == 'language': 00448 if elements.text: 00449 language = elements.text[:2] 00450 continue 00451 if not elements.tag == 'item': 00452 continue 00453 item = {} 00454 item['language'] = language 00455 embedURL = u'' 00456 for elem in elements: 00457 if elem.tag == 'title': 00458 if elem.text: 00459 item['title'] = self.massageDescription(elem.text.strip()) 00460 continue 00461 if elem.tag.endswith('safeusername'): 00462 if elem.text: 00463 item['blip_safeusername'] = self.massageDescription(elem.text.strip()) 00464 continue 00465 if elem.tag.endswith('pubDate'): 00466 if elem.text: 00467 item['updated'] = self.massageDescription(elem.text.strip()) 00468 continue 00469 if elem.tag.endswith('puredescription'): 00470 if elem.text: 00471 item['blip_puredescription'] = self.massageDescription(elem.text.strip()) 00472 continue 00473 if elem.tag.endswith('link'): 00474 if elem.text: 00475 item['link'] = self.ampReplace(elem.text.strip()) 00476 continue 00477 if elem.tag.endswith('embedUrl'): 00478 if elem.text: 00479 embedURL = self.ampReplace(elem.text.strip()) 00480 continue 00481 if elem.tag.endswith('thumbnail'): 00482 if elem.get('url'): 00483 item['blip_picture'] = self.ampReplace(elem.get('url').strip()) 00484 continue 00485 if elem.tag.endswith('group'): 00486 file_size = 0 00487 for e in elem: 00488 if e.tag.endswith('content'): 00489 if e.get('fileSize'): 00490 try: 00491 if int(e.get('fileSize')) > file_size: 00492 item['video'] = self.ampReplace(e.get('url').strip()) 00493 file_size = int(e.get('fileSize')) 00494 except: 00495 pass 00496 continue 00497 continue 00498 if elem.tag.endswith('runtime'): 00499 if elem.text: 00500 item['blip_runtime'] = self.massageDescription(elem.text.strip()) 00501 continue 00502 if elem.tag.endswith('rating'): 00503 if elem.text: 00504 item['blip_rating'] = self.massageDescription(elem.text.strip()) 00505 continue 00506 if not item.has_key('video') and not item.has_key('link') and not embedURL: 00507 continue 00508 if embedURL: 00509 item['link'] = self.processVideoUrl(embedURL) 00510 if item.has_key('link') and not item.has_key('video'): 00511 continue 00512 if item.has_key('video') and not item.has_key('link'): 00513 item['link'] = item['video'] 00514 elements_final.append(item) 00515 00516 if not len(elements_final): 00517 raise BliptvVideoNotFound(u"2-No blip.tv Video matches found for search value (%s)" % title) 00518 00519 return elements_final 00520 # end searchTitle() 00521 00522 00523 def searchForVideos(self, title, pagenumber): 00524 """Common name for a video search. Used to interface with MythTV plugin NetVision 00525 """ 00526 try: 00527 data = self.searchTitle(title, pagenumber, self.page_limit) 00528 except BliptvVideoNotFound, msg: 00529 sys.stderr.write(u"%s\n" % msg) 00530 return None 00531 except BliptvUrlError, msg: 00532 sys.stderr.write(u'%s' % msg) 00533 sys.exit(1) 00534 except BliptvHttpError, msg: 00535 sys.stderr.write(self.error_messages['BliptvHttpError'] % msg) 00536 sys.exit(1) 00537 except BliptvRssError, msg: 00538 sys.stderr.write(self.error_messages['BliptvRssError'] % msg) 00539 sys.exit(1) 00540 except Exception, e: 00541 sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) 00542 sys.exit(1) 00543 00544 if data == None: 00545 return None 00546 if not len(data): 00547 return None 00548 00549 items = [] 00550 for match in data: 00551 item_data = {} 00552 for key in self.key_translation[1].keys(): 00553 if key in match.keys(): 00554 item_data[self.key_translation[1][key]] = match[key] 00555 else: 00556 item_data[self.key_translation[1][key]] = u'' 00557 items.append(item_data) 00558 00559 # Channel details and search results 00560 channel = {'channel_title': u'blip.tv', 'channel_link': u'http://blip.tv', 'channel_description': u"We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0} 00561 00562 if len(items) == self.page_limit: 00563 channel['channel_numresults'] = self.page_limit * int(pagenumber) + 1 00564 else: 00565 channel['channel_numresults'] = self.page_limit * int(pagenumber) 00566 channel['channel_startindex'] = self.page_limit * int(pagenumber) 00567 channel['channel_returned'] = len(items) 00568 00569 if len(items): 00570 return [[channel, items]] 00571 return None 00572 # end searchForVideos() 00573 00574 00575 def getCategories(self): 00576 '''Get the list of valid category ids and their name and update the proper dictionaries 00577 return nothing 00578 ''' 00579 url = self.config[u'urls'][u'categories'] 00580 if self.config['debug_enabled']: 00581 print "Category list URL:" 00582 print url 00583 print 00584 00585 try: 00586 etree = XmlHandler(url).getEt() 00587 except Exception, errormsg: 00588 sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg)) 00589 self.tree_order.remove('categories') 00590 return 00591 00592 if etree is None: 00593 sys.stderr.write(u'1-No Categories found at (%s)\n' % url) 00594 self.tree_order.remove('categories') 00595 return 00596 00597 if not etree.find('payload'): 00598 sys.stderr.write(u'2-No Categories found at (%s)\n' % url) 00599 self.tree_order.remove('categories') 00600 return 00601 00602 category = False 00603 for element in etree.find('payload'): 00604 if element.tag == 'category': 00605 tmp_name = u'' 00606 tmp_id = u'' 00607 for e in element: 00608 if e.tag == 'id': 00609 if e.text == '-1': 00610 break 00611 if e.text: 00612 tmp_id = self.massageDescription(e.text.strip()) 00613 if e.tag == 'name': 00614 if e.text: 00615 tmp_name = self.massageDescription(e.text.strip()) 00616 if tmp_id and tmp_name: 00617 category = True 00618 self.tree_org['categories'].append([tmp_name, ['popular', 'recent', 'random', 'featured',]]) 00619 self.feed_names['categories'][tmp_name] = tmp_id 00620 00621 if not category: 00622 sys.stderr.write(u'3-No Categories found at (%s)\n' % url) 00623 self.tree_order.remove('categories') 00624 return 00625 00626 self.tree_org['categories'].append([u'', u'']) # Adds a end of the Categories directory indicator 00627 00628 return 00629 # end getCategories() 00630 00631 def displayTreeView(self): 00632 '''Gather the categories/feeds/...etc then retrieve a max page of videos meta data in each of them 00633 return array of directories and their video meta data 00634 ''' 00635 # Channel details and search results 00636 self.channel = {'channel_title': u'blip.tv', 'channel_link': u'http://blip.tv', 'channel_description': u"We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0} 00637 00638 if self.config['debug_enabled']: 00639 print self.config[u'urls'] 00640 print 00641 00642 # Get category ids 00643 self.getCategories() 00644 00645 # Process the various video feeds/categories/... etc 00646 self.treeview = True 00647 dictionaries = [] 00648 for key in self.tree_order: 00649 if key == 'categories': 00650 self.categories = True 00651 else: 00652 self.categories = False 00653 self.tree_key = key 00654 dictionaries = self.getVideos(self.tree_org[key], dictionaries) 00655 00656 return [[self.channel, dictionaries]] 00657 # end displayTreeView() 00658 00659 def makeURL(self, URL): 00660 '''Form a URL to search for videos 00661 return a URL 00662 ''' 00663 additions = dict(self.tree_customize[self.tree_key]['__default__']) # Set defaults 00664 00665 # Add customizations 00666 if self.feed in self.tree_customize[self.tree_key].keys(): 00667 for element in self.tree_customize[self.tree_key][self.feed].keys(): 00668 additions[element] = self.tree_customize[self.tree_key][self.feed][element] 00669 00670 # Make the search extension string that is added to the URL 00671 addition = u'' 00672 for ky in additions.keys(): 00673 if ky.startswith('add_'): 00674 addition+=u'/%s' % additions[ky] 00675 else: 00676 addition+=u'?%s=%s' % (ky, additions[ky]) 00677 index = URL.find('%') 00678 if index == -1: 00679 return (URL+addition) 00680 else: 00681 return (URL+addition) % self.feed 00682 # end makeURL() 00683 00684 00685 def getVideos(self, dir_dict, dictionaries): 00686 '''Parse a list made of categories and retrieve video meta data 00687 return a dictionary of directory names and categories video meta data 00688 ''' 00689 for sets in dir_dict: 00690 if not isinstance(sets[1], list): 00691 if sets[0] != '': # Add the nested dictionaries display name 00692 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon]) 00693 else: 00694 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00695 continue 00696 temp_dictionary = [] 00697 for self.feed in sets[1]: 00698 if self.categories: 00699 self.tree_customize[self.tree_key]['__default__']['categories_id'] = self.feed_names['categories'][sets[0]] 00700 self.tree_customize[self.tree_key]['__default__']['sort'] = self.feed 00701 if self.config[u'urls'][u'tree.view'][self.tree_key].has_key('__all__'): 00702 URL = self.config[u'urls'][u'tree.view'][self.tree_key]['__all__'] 00703 else: 00704 URL = self.config[u'urls'][u'tree.view'][self.tree_key][self.feed] 00705 temp_dictionary = self.config['item_parser'][URL[1]](self.makeURL(URL[0]), temp_dictionary) 00706 if len(temp_dictionary): 00707 if len(sets[0]): # Add the nested dictionaries display name 00708 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon]) 00709 for element in temp_dictionary: 00710 dictionaries.append(element) 00711 if len(sets[0]): 00712 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00713 return dictionaries 00714 # end getVideos() 00715 00716 def getVideosForURL(self, url, dictionaries): 00717 '''Get the video meta data for url search 00718 return the video dictionary of directories and their video mata data 00719 ''' 00720 initial_length = len(dictionaries) 00721 00722 if self.config['debug_enabled']: 00723 print "Video URL:" 00724 print url 00725 print 00726 00727 try: 00728 etree = XmlHandler(url).getEt() 00729 except Exception, errormsg: 00730 sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg)) 00731 return dictionaries 00732 00733 if etree is None: 00734 sys.stderr.write(u'1-No Videos for (%s)\n' % self.feed) 00735 return dictionaries 00736 00737 dictionary_first = False 00738 self.next_page = False 00739 language = self.config['language'] 00740 for elements in etree.find('channel'): 00741 if elements.tag.endswith(u'language'): 00742 if elements.text: 00743 language = elements.text[:2] 00744 continue 00745 00746 if not elements.tag.endswith(u'item'): 00747 continue 00748 00749 item = {} 00750 item['language'] = language 00751 embedURL = u'' 00752 for elem in elements: 00753 if elem.tag == 'title': 00754 if elem.text: 00755 item['title'] = self.massageDescription(elem.text.strip()) 00756 continue 00757 if elem.tag.endswith('safeusername'): 00758 if elem.text: 00759 item['blip_safeusername'] = self.massageDescription(elem.text.strip()) 00760 continue 00761 if elem.tag.endswith('pubDate'): 00762 if elem.text: 00763 item['updated'] = self.massageDescription(elem.text.strip()) 00764 continue 00765 if elem.tag.endswith('puredescription'): 00766 if elem.text: 00767 item['blip_puredescription'] = self.massageDescription(elem.text.strip()) 00768 continue 00769 if elem.tag == 'link': 00770 if elem.text: 00771 item['link'] = self.ampReplace(elem.text.strip()) 00772 continue 00773 if elem.tag.endswith('embedUrl'): 00774 if elem.text: 00775 embedURL = self.ampReplace(elem.text.strip()) 00776 continue 00777 if elem.tag.endswith('thumbnail'): 00778 if elem.get('url'): 00779 item['blip_picture'] = self.ampReplace(elem.get('url').strip()) 00780 continue 00781 if elem.tag.endswith('group'): 00782 file_size = 0 00783 for e in elem: 00784 if e.tag.endswith('content'): 00785 for key in e.keys(): 00786 if key.endswith('vcodec'): 00787 break 00788 else: 00789 continue 00790 if e.get('fileSize'): 00791 try: 00792 if int(e.get('fileSize')) > file_size: 00793 item['video'] = self.ampReplace(e.get('url').strip()) 00794 file_size = int(e.get('fileSize')) 00795 except: 00796 pass 00797 if e.get('height'): 00798 item['height'] = e.get('height').strip() 00799 if e.get('width'): 00800 item['width'] = e.get('width').strip() 00801 continue 00802 continue 00803 if elem.tag.endswith('runtime'): 00804 if elem.text: 00805 item['blip_runtime'] = self.massageDescription(elem.text.strip()) 00806 continue 00807 if elem.tag.endswith('rating'): 00808 if elem.text: 00809 item['blip_rating'] = self.massageDescription(elem.text.strip()) 00810 continue 00811 if not item.has_key('video') and not item.has_key('link'): 00812 continue 00813 if embedURL: 00814 item['link'] = embedURL 00815 if item.has_key('link') and not item.has_key('video'): 00816 continue 00817 if item.has_key('video') and not item.has_key('link'): 00818 item['link'] = item['video'] 00819 00820 if self.treeview: 00821 if not dictionary_first: # Add the dictionaries display name 00822 dictionaries.append([self.massageDescription(self.feed_names[self.tree_key][self.feed]), self.setTreeViewIcon()]) 00823 dictionary_first = True 00824 00825 final_item = {} 00826 for key in self.key_translation[1].keys(): 00827 if not item.has_key(key): 00828 final_item[self.key_translation[1][key]] = u'' 00829 else: 00830 final_item[self.key_translation[1][key]] = item[key] 00831 dictionaries.append(final_item) 00832 00833 if self.treeview: 00834 if initial_length < len(dictionaries): # Need to check if there was any items for this Category 00835 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00836 return dictionaries 00837 # end getVideosForURL() 00838 # end Videos() class
1.7.6.1