|
MythTV
0.26-pre
|
00001 #!/usr/bin/env python 00002 # -*- coding: UTF-8 -*- 00003 # ---------------------- 00004 # Name: mtv_api - Simple-to-use Python interface to the MTV API (http://www.mtv.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 MTV. These routines are based on the api. Specifications 00009 # for this api are published at http://developer.mtvnservices.com/docs 00010 # 00011 # License:Creative Commons GNU GPL v2 00012 # (http://creativecommons.org/licenses/GPL/2.0/) 00013 #------------------------------------- 00014 __title__ ="mtv_api - Simple-to-use Python interface to the MTV API (http://developer.mtvnservices.com/docs)" 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 metadata, video and image URLs from MTV. These routines are based on the api. Specifications 00019 for this api are published at http://developer.mtvnservices.com/docs 00020 ''' 00021 00022 __version__="v0.2.5" 00023 # 0.1.0 Initial development 00024 # 0.1.1 Added Tree View Processing 00025 # 0.1.2 Modified Reee view code and structure to be standandized across all grabbers 00026 # 0.1.3 Added directory image access and display 00027 # 0.1.4 Documentation review 00028 # 0.2.0 Public release 00029 # 0.2.1 New python bindings conversion 00030 # Better exception error reporting 00031 # Better handling of invalid unicode data from source 00032 # 0.2.2 Complete abort error message display improvements 00033 # Removed the import and use of the feedparser library 00034 # 0.2.3 Fixed an exception message output code error in two places 00035 # 0.2.4 Removed the need for python MythTV bindings and added "%SHAREDIR%" to icon directory path 00036 # 0.2.5 Use MTV web page as API not returning valid URLs 00037 00038 import os, struct, sys, re, time 00039 from datetime import datetime, timedelta 00040 import urllib, urllib2 00041 import logging 00042 00043 try: 00044 import xml.etree.cElementTree as ElementTree 00045 except ImportError: 00046 import xml.etree.ElementTree as ElementTree 00047 00048 from mtv_exceptions import (MtvUrlError, MtvHttpError, MtvRssError, MtvVideoNotFound, MtvInvalidSearchType, MtvXmlError, MtvVideoDetailError) 00049 00050 class OutStreamEncoder(object): 00051 """Wraps a stream with an encoder""" 00052 def __init__(self, outstream, encoding=None): 00053 self.out = outstream 00054 if not encoding: 00055 self.encoding = sys.getfilesystemencoding() 00056 else: 00057 self.encoding = encoding 00058 00059 def write(self, obj): 00060 """Wraps the output stream, encoding Unicode strings with the specified encoding""" 00061 if isinstance(obj, unicode): 00062 try: 00063 self.out.write(obj.encode(self.encoding)) 00064 except IOError: 00065 pass 00066 else: 00067 try: 00068 self.out.write(obj) 00069 except IOError: 00070 pass 00071 00072 def __getattr__(self, attr): 00073 """Delegate everything but write to the stream""" 00074 return getattr(self.out, attr) 00075 sys.stdout = OutStreamEncoder(sys.stdout, 'utf8') 00076 sys.stderr = OutStreamEncoder(sys.stderr, 'utf8') 00077 00078 00079 class XmlHandler: 00080 """Deals with retrieval of XML files from API 00081 """ 00082 def __init__(self, url): 00083 self.url = url 00084 00085 def _grabUrl(self, url): 00086 try: 00087 urlhandle = urllib.urlopen(url) 00088 except IOError, errormsg: 00089 raise MtvHttpError(errormsg) 00090 return urlhandle.read() 00091 00092 def getEt(self): 00093 xml = self._grabUrl(self.url) 00094 try: 00095 et = ElementTree.fromstring(xml) 00096 except SyntaxError, errormsg: 00097 raise MtvXmlError(errormsg) 00098 return et 00099 00100 00101 class Videos(object): 00102 """Main interface to http://www.mtv.com/ 00103 This is done to support a common naming framework for all python Netvision plugins no matter their site 00104 target. 00105 00106 Supports search methods 00107 The apikey is a not required to access http://www.mtv.com/ 00108 """ 00109 def __init__(self, 00110 apikey, 00111 mythtv = True, 00112 interactive = False, 00113 select_first = False, 00114 debug = False, 00115 custom_ui = None, 00116 language = None, 00117 search_all_languages = False, 00118 ): 00119 """apikey (str/unicode): 00120 Specify the target site API key. Applications need their own key in some cases 00121 00122 mythtv (True/False): 00123 When True, the returned meta data is being returned has the key and values massaged to match MythTV 00124 When False, the returned meta data is being returned matches what target site returned 00125 00126 interactive (True/False): (This option is not supported by all target site apis) 00127 When True, uses built-in console UI is used to select the correct show. 00128 When False, the first search result is used. 00129 00130 select_first (True/False): (This option is not supported currently implemented in any grabbers) 00131 Automatically selects the first series search result (rather 00132 than showing the user a list of more than one series). 00133 Is overridden by interactive = False, or specifying a custom_ui 00134 00135 debug (True/False): 00136 shows verbose debugging information 00137 00138 custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) 00139 A callable subclass of interactive class (overrides interactive option) 00140 00141 language (2 character language abbreviation): (This option is not supported by all target site apis) 00142 The language of the returned data. Is also the language search 00143 uses. Default is "en" (English). For full list, run.. 00144 00145 search_all_languages (True/False): (This option is not supported by all target site apis) 00146 By default, a Netvision grabber will only search in the language specified using 00147 the language option. When this is True, it will search for the 00148 show in any language 00149 00150 """ 00151 self.config = {} 00152 00153 if apikey is not None: 00154 self.config['apikey'] = apikey 00155 else: 00156 pass # MTV does not require an apikey 00157 00158 self.config['debug_enabled'] = debug # show debugging messages 00159 00160 self.log_name = "MTV" 00161 self.log = self._initLogger() # Setups the logger (self.log.debug() etc) 00162 00163 self.config['custom_ui'] = custom_ui 00164 00165 self.config['interactive'] = interactive # prompt for correct series? 00166 00167 self.config['select_first'] = select_first 00168 00169 self.config['search_all_languages'] = search_all_languages 00170 00171 # Defaulting to ENGISH but the MTV apis do not support specifying a language 00172 self.config['language'] = "en" 00173 00174 self.error_messages = {'MtvUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'MtvHttpError': u"! Error: An HTTP communications error with MTV was raised (%s)\n", 'MtvRssError': u"! Error: Invalid RSS metadata\nwas received from MTV error (%s). Skipping item.\n", 'MtvVideoNotFound': u"! Error: Video search with MTV did not return any results (%s)\n", 'MtvVideoDetailError': u"! Error: Invalid Video metadata detail\nwas received from MTV error (%s). Skipping item.\n", } 00175 00176 # This is an example that must be customized for each target site 00177 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', 'media_credit': 'item_author', 'published_parsed': 'item_pubdate', 'media_description': 'item_description', 'video': 'item_link', 'thumbnail': 'item_thumbnail', 'link': 'item_url', 'duration': 'item_duration', 'item_rating': 'item_rating', 'item_width': 'item_width', 'item_height': 'item_height', 'language': 'item_lang'}] 00178 00179 self.config[u'urls'] = {} 00180 00181 self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions 00182 self.config[u'urls'] = {} 00183 00184 # Functions that parse video data from RSS data 00185 self.config['item_parser'] = {} 00186 self.config['item_parser']['main'] = self.getVideosForURL 00187 00188 # v2 api calls - An example that must be customized for each target site 00189 self.config[u'urls'][u'tree.view'] = { 00190 'new_genres': { 00191 '__all__': ['http://api.mtvnservices.com/1/genre/%s/videos/?', 'main'], 00192 }, 00193 'genres': { 00194 '__all__': ['http://api.mtvnservices.com/1/genre/%s/videos/?', 'main'], 00195 }, 00196 } 00197 00198 self.tree_order = ['new_genres', 'genres', ] 00199 self.tree_org = { 00200 'new_genres': [['New over the last 3 months', ['pop', 'rock', 'metal', 'randb', 'jazz', 'blues_folk', 'country', 'latin', 'hip_hop', 'world_reggae', 'electronic_dance', 'easy_listening', 'classical', 'soundtracks_musicals', 'alternative', 'environmental', ]], 00201 ], 00202 'genres': [['All Genres', ['pop', 'rock', 'metal', 'randb', 'jazz', 'blues_folk', 'country', 'latin', 'hip_hop', 'world_reggae', 'electronic_dance', 'easy_listening', 'classical', 'soundtracks_musicals', 'alternative', 'environmental', ]], 00203 ], 00204 } 00205 00206 # Time periods of videos e.g, &date=01011980-12311989 or MMDDYYYY-MMDDYYYY 00207 d1 = datetime.now() 00208 yr = d1 - timedelta(weeks=52) 00209 mts = d1 - timedelta(days=93) 00210 last_3_months = u'%s-%s' % (mts.strftime('%m%d%Y'), d1.strftime('%m%d%Y')) 00211 last_year = u'%s-%s' % (yr.strftime('%m%d%Y'), d1.strftime('%m%d%Y')) 00212 00213 # http://api.mtvnservices.com/1/genre/rock/videos/?&max-results=20&start-index=1 00214 self.tree_customize = { 00215 'new_genres': { # ?term=%s&start-index=%s&max-results=%s 00216 '__default__': {'max-results': '20', 'start-index': '1', 'date': last_3_months, 'sort': 'date_descending'}, 00217 #'cat name': {'max-results': '', 'start-index': '', 'date': '', 'sort': ''}, 00218 }, 00219 'genres': { # ?term=%s&start-index=%s&max-results=%s 00220 '__default__': {'max-results': '20', 'start-index': '1', 'sort': 'date_descending'}, 00221 #'cat name': {'max-results': '', 'start-index': '', 'date': '', 'sort': ''}, 00222 'rock': {'date': last_year, }, 00223 'R&B': {'date': last_year, }, 00224 'country': {'date': last_year, }, 00225 'hip_hop': {'date': last_year, }, 00226 }, 00227 } 00228 00229 self.feed_names = { 00230 'new_genres': {'world_reggae': 'World/Reggae', 'pop': 'Pop', 'metal': 'Metal', 'environmental': 'Environmental', 'latin': 'Latin', 'randb': 'R&B', 'rock': 'Rock', 'easy_listening': 'Easy Listening', 'jazz': 'Jazz', 'country': 'Country', 'hip_hop': 'Hip-Hop', 'classical': 'Classical', 'electronic_dance': 'Electro / Dance', 'blues_folk': 'Blues / Folk', 'alternative': 'Alternative', 'soundtracks_musicals': 'Soundtracks / Musicals', 'New over the last 3 months': 'directories/topics/month' 00231 }, 00232 'genres': {'world_reggae': 'World/Reggae', 'pop': 'Pop', 'metal': 'Metal', 'environmental': 'Environmental', 'latin': 'Latin', 'randb': 'R&B', 'rock': 'Rock', 'easy_listening': 'Easy Listening', 'jazz': 'Jazz', 'country': 'Country', 'hip_hop': 'Hip-Hop', 'classical': 'Classical', 'electronic_dance': 'Electro / Dance', 'blues_folk': 'Blues / Folk', 'alternative': 'Alternative', 'soundtracks_musicals': 'Soundtracks / Musicals', 00233 }, 00234 } 00235 00236 self.feed_icons = { 00237 'new_genres': {'New over the last 3 months': 'directories/topics/recent', 'world_reggae': 'directories/music_genres/world_reggae', 'pop': 'directories/music_genres/pop', 'metal': 'directories/music_genres/metal', 'environmental': 'directories/music_genres/environmental', 'latin': 'directories/music_genres/latino', 'randb': 'directories/music_genres/rnb', 'rock': 'directories/music_genres/rock', 'easy_listening': 'directories/music_genres/easy_listening', 'jazz': 'directories/music_genres/jazz', 'country': 'directories/music_genres/country', 'hip_hop': 'directories/music_genres/hiphop', 'classical': 'directories/music_genres/classical', 'electronic_dance': 'directories/music_genres/electronic_dance', 'blues_folk': 'directories/music_genres/blues_folk', 'alternative': 'directories/music_genres/alternative', 'soundtracks_musicals': 'directories/music_genres/soundtracks_musicals', 00238 }, 00239 'genres': {'Genres': 'directories/topics/music','world_reggae': 'directories/music_genres/world_reggae', 'pop': 'directories/music_genres/pop', 'metal': 'directories/music_genres/metal', 'environmental': 'directories/music_genres/environmental', 'latin': 'directories/music_genres/latino', 'randb': 'directories/music_genres/rnb', 'rock': 'directories/music_genres/rock', 'easy_listening': 'directories/music_genres/easy_listening', 'jazz': 'directories/music_genres/jazz', 'country': 'directories/music_genres/country', 'hip_hop': 'directories/music_genres/hiphop', 'classical': 'directories/music_genres/classical', 'electronic_dance': 'directories/music_genres/electronic_dance', 'blues_folk': 'directories/music_genres/blues_folk', 'alternative': 'directories/music_genres/alternative', 'soundtracks_musicals': 'directories/music_genres/soundtracks_musicals', 00240 }, 00241 } 00242 # Get the absolute path to the mtv.html file 00243 self.mtvHtmlPath = u'file://'+os.path.dirname( os.path.realpath( __file__ )).replace(u'/nv_python_libs/mtv', u'/nv_python_libs/configs/HTML/mtv.html?title=%s&videocode=%s') 00244 00245 # Initialize the tree view flag so that the item parsing code can be used for multiple purposes 00246 self.treeview = False 00247 self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/mtv.png' 00248 # end __init__() 00249 00250 ########################################################################################################### 00251 # 00252 # Start - Utility functions 00253 # 00254 ########################################################################################################### 00255 00256 def massageDescription(self, text): 00257 '''Removes HTML markup from a text string. 00258 @param text The HTML source. 00259 @return The plain text. If the HTML source contains non-ASCII 00260 entities or character references, this is a Unicode string. 00261 ''' 00262 def fixup(m): 00263 text = m.group(0) 00264 if text[:1] == "<": 00265 return "" # ignore tags 00266 if text[:2] == "&#": 00267 try: 00268 if text[:3] == "&#x": 00269 return unichr(int(text[3:-1], 16)) 00270 else: 00271 return unichr(int(text[2:-1])) 00272 except ValueError: 00273 pass 00274 elif text[:1] == "&": 00275 import htmlentitydefs 00276 entity = htmlentitydefs.entitydefs.get(text[1:-1]) 00277 if entity: 00278 if entity[:2] == "&#": 00279 try: 00280 return unichr(int(entity[2:-1])) 00281 except ValueError: 00282 pass 00283 else: 00284 return unicode(entity, "iso-8859-1") 00285 return text # leave as is 00286 return self.ampReplace(re.sub(u"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace(u'\n',u' ') 00287 # end massageDescription() 00288 00289 00290 def _initLogger(self): 00291 """Setups a logger using the logging module, returns a log object 00292 """ 00293 logger = logging.getLogger(self.log_name) 00294 formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s') 00295 00296 hdlr = logging.StreamHandler(sys.stdout) 00297 00298 hdlr.setFormatter(formatter) 00299 logger.addHandler(hdlr) 00300 00301 if self.config['debug_enabled']: 00302 logger.setLevel(logging.DEBUG) 00303 else: 00304 logger.setLevel(logging.WARNING) 00305 return logger 00306 #end initLogger 00307 00308 00309 def textUtf8(self, text): 00310 if text == None: 00311 return text 00312 try: 00313 return unicode(text, 'utf8') 00314 except UnicodeDecodeError: 00315 return u'' 00316 except (UnicodeEncodeError, TypeError): 00317 return text 00318 # end textUtf8() 00319 00320 00321 def ampReplace(self, text): 00322 '''Replace all "&" characters with "&" 00323 ''' 00324 text = self.textUtf8(text) 00325 return text.replace(u'&',u'~~~~~').replace(u'&',u'&').replace(u'~~~~~', u'&') 00326 # end ampReplace() 00327 00328 00329 def setTreeViewIcon(self, dir_icon=None): 00330 '''Check if there is a specific generic tree view icon. If not default to the channel icon. 00331 return self.tree_dir_icon 00332 ''' 00333 self.tree_dir_icon = self.channel_icon 00334 if not dir_icon: 00335 if not self.feed_icons.has_key(self.tree_key): 00336 return self.tree_dir_icon 00337 if not self.feed_icons[self.tree_key].has_key(self.feed): 00338 return self.tree_dir_icon 00339 dir_icon = self.feed_icons[self.tree_key][self.feed] 00340 if not dir_icon: 00341 return self.tree_dir_icon 00342 self.tree_dir_icon = u'%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, ) 00343 return self.tree_dir_icon 00344 # end setTreeViewIcon() 00345 00346 ########################################################################################################### 00347 # 00348 # End of Utility functions 00349 # 00350 ########################################################################################################### 00351 00352 00353 def searchTitle(self, title, pagenumber, pagelen): 00354 '''Key word video search of the MTV web site 00355 return an array of matching item dictionaries 00356 return 00357 ''' 00358 url = self.config[u'urls'][u'video.search'] % (urllib.quote_plus(title.encode("utf-8")), pagenumber , pagelen,) 00359 if self.config['debug_enabled']: 00360 print url 00361 print 00362 00363 try: 00364 etree = XmlHandler(url).getEt() 00365 except Exception, errormsg: 00366 raise MtvUrlError(self.error_messages['MtvUrlError'] % (url, errormsg)) 00367 00368 if etree is None: 00369 raise MtvVideoNotFound(u"No MTV Video matches found for search value (%s)" % title) 00370 00371 data = [] 00372 for entry in etree: 00373 if not entry.tag.endswith('entry'): 00374 continue 00375 item = {} 00376 for parts in entry: 00377 if parts.tag.endswith('id'): 00378 item['id'] = parts.text 00379 if parts.tag.endswith('title'): 00380 item['title'] = parts.text 00381 if parts.tag.endswith('author'): 00382 for e in parts: 00383 if e.tag.endswith('name'): 00384 item['media_credit'] = e.text 00385 break 00386 if parts.tag.endswith('published'): 00387 item['published_parsed'] = parts.text 00388 if parts.tag.endswith('description'): 00389 item['media_description'] = parts.text 00390 data.append(item) 00391 00392 # Make sure there are no item elements that are None 00393 for item in data: 00394 for key in item.keys(): 00395 if item[key] == None: 00396 item[key] = u'' 00397 00398 # Massage each field and eliminate any item without a URL 00399 elements_final = [] 00400 for item in data: 00401 if not 'id' in item.keys(): 00402 continue 00403 00404 video_details = None 00405 try: 00406 video_details = self.videoDetails(item['id'], urllib.quote(item['title'].encode("utf-8"))) 00407 except MtvUrlError, msg: 00408 sys.stderr.write(self.error_messages['MtvUrlError'] % msg) 00409 except MtvVideoDetailError, msg: 00410 sys.stderr.write(self.error_messages['MtvVideoDetailError'] % msg) 00411 except Exception, e: 00412 sys.stderr.write(u"! Error: Unknown error while retrieving a Video's meta data. Skipping video.' (%s)\nError(%s)\n" % (title, e)) 00413 00414 if video_details: 00415 for key in video_details.keys(): 00416 item[key] = video_details[key] 00417 00418 item['language'] = u'' 00419 for key in item.keys(): 00420 if key == 'content': 00421 if len(item[key]): 00422 if item[key][0].has_key('language'): 00423 if item[key][0]['language'] != None: 00424 item['language'] = item[key][0]['language'] 00425 if key == 'published_parsed': # '2009-12-21T00:00:00Z' 00426 if item[key]: 00427 pub_time = time.strptime(item[key].strip(), "%Y-%m-%dT%H:%M:%SZ") 00428 item[key] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time) 00429 continue 00430 if key == 'media_description' or key == 'title': 00431 # Strip the HTML tags 00432 if item[key]: 00433 item[key] = self.massageDescription(item[key].strip()) 00434 item[key] = item[key].replace(u'|', u'-') 00435 continue 00436 if type(item[key]) == type(u''): 00437 if item[key]: 00438 item[key] = item[key].replace('"\n',' ').strip() 00439 elements_final.append(item) 00440 00441 if not len(elements_final): 00442 raise MtvVideoNotFound(u"No MTV Video matches found for search value (%s)" % title) 00443 00444 return elements_final 00445 # end searchTitle() 00446 00447 00448 def videoDetails(self, url, title=u''): 00449 '''Using the passed URL retrieve the video meta data details 00450 return a dictionary of video metadata details 00451 return 00452 ''' 00453 if self.config['debug_enabled']: 00454 print url 00455 print 00456 00457 try: 00458 etree = XmlHandler(url).getEt() 00459 except Exception, errormsg: 00460 raise MtvUrlError(self.error_messages['MtvUrlError'] % (url, errormsg)) 00461 00462 if etree is None: 00463 raise MtvVideoDetailError(u'1-No Video meta data for (%s)' % url) 00464 00465 metadata = {} 00466 cur_size = True 00467 for e in etree: 00468 if e.tag.endswith(u'content') and e.text == None: 00469 index = e.get('url').rindex(u':') 00470 metadata['video'] = self.mtvHtmlPath % (title, e.get('url')[index+1:]) 00471 # !! This tag will need to be added at a later date 00472 # metadata['customhtml'] = u'true' 00473 metadata['duration'] = e.get('duration') 00474 if e.tag.endswith(u'player'): 00475 metadata['link'] = e.get('url') 00476 if e.tag.endswith(u'thumbnail'): 00477 if cur_size == False: 00478 continue 00479 height = e.get('height') 00480 width = e.get('width') 00481 if int(width) > cur_size: 00482 metadata['thumbnail'] = e.get('url') 00483 cur_size = int(width) 00484 if int(width) >= 200: 00485 cur_size = False 00486 break 00487 00488 if not len(metadata): 00489 raise MtvVideoDetailError(u'2-No Video meta data for (%s)' % url) 00490 00491 if not metadata.has_key('video'): 00492 metadata['video'] = metadata['link'] 00493 metadata['duration'] = u'' 00494 else: 00495 metadata['link'] = metadata['video'] 00496 00497 return metadata 00498 # end videoDetails() 00499 00500 00501 def searchForVideos(self, title, pagenumber): 00502 """Common name for a video search. Used to interface with MythTV plugin NetVision 00503 """ 00504 # v2 api calls - An example that must be customized for each target site 00505 if self.grabber_title == 'MTV': 00506 self.config[u'urls'][u'video.search'] = "http://api.mtvnservices.com/1/video/search/?term=%s&start-index=%s&max-results=%s" 00507 elif self.grabber_title == 'MTV Artists': # This search type is not currently implemented 00508 self.config[u'urls'][u'video.search'] = "http://api.mtvnservices.com/1/artist/search/?term=%s&start-index=%s&max-results=%s" 00509 else: 00510 sys.stderr.write(u"! Error: MtvInvalidSearchType - The grabber name (%s) is invalid \n" % self.grabber_title) 00511 sys.exit(1) 00512 00513 00514 # Easier for debugging 00515 # print self.searchTitle(title, pagenumber, self.page_limit) 00516 # print 00517 # sys.exit() 00518 00519 00520 startindex = (int(pagenumber) -1) * self.page_limit + 1 00521 try: 00522 data = self.searchTitle(title, startindex, self.page_limit) 00523 except MtvVideoNotFound, msg: 00524 sys.stderr.write(u"%s\n" % msg) 00525 return None 00526 except MtvUrlError, msg: 00527 sys.stderr.write(u'%s\n' % msg) 00528 sys.exit(1) 00529 except MtvHttpError, msg: 00530 sys.stderr.write(self.error_messages['MtvHttpError'] % msg) 00531 sys.exit(1) 00532 except MtvRssError, msg: 00533 sys.stderr.write(self.error_messages['MtvRssError'] % msg) 00534 sys.exit(1) 00535 except Exception, e: 00536 sys.stderr.write(u"! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) 00537 sys.exit(1) 00538 00539 if data == None: 00540 return None 00541 if not len(data): 00542 return None 00543 00544 items = [] 00545 for match in data: 00546 item_data = {} 00547 for key in self.key_translation[1].keys(): 00548 if key in match.keys(): 00549 item_data[self.key_translation[1][key]] = match[key] 00550 else: 00551 item_data[self.key_translation[1][key]] = u'' 00552 items.append(item_data) 00553 00554 # Channel details and search results 00555 channel = {'channel_title': u'MTV', 'channel_link': u'http://www.mtv.com', 'channel_description': u"Visit MTV (Music Television) for TV shows, music videos, celebrity photos, news.", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0} 00556 00557 if len(items) == self.page_limit: 00558 channel['channel_numresults'] = self.page_limit * int(pagenumber) + 1 00559 elif len(items) < self.page_limit: 00560 channel['channel_numresults'] = self.page_limit * (int(pagenumber)-1) + len(items) 00561 else: 00562 channel['channel_numresults'] = self.page_limit * int(pagenumber) 00563 channel['channel_startindex'] = self.page_limit * int(pagenumber) 00564 channel['channel_returned'] = len(items) 00565 00566 if len(items): 00567 return [[channel, items]] 00568 return None 00569 # end searchForVideos() 00570 00571 00572 def displayTreeView(self): 00573 '''Gather the MTV Genres/Artists/...etc then get a max page of videos meta data in each of them 00574 return array of directories and their video metadata 00575 ''' 00576 # Channel details and search results 00577 self.channel = {'channel_title': u'MTV', 'channel_link': u'http://www.mtv.com', 'channel_description': u"Visit MTV (Music Television) for TV shows, music videos, celebrity photos, news.", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0} 00578 00579 if self.config['debug_enabled']: 00580 print self.config[u'urls'] 00581 print 00582 00583 # Set the default videos per page limit for all feeds/categories/... etc 00584 for key in self.tree_customize.keys(): 00585 if '__default__' in self.tree_customize[key].keys(): 00586 if 'max-results' in self.tree_customize[key]['__default__'].keys(): 00587 self.tree_customize[key]['__default__']['max-results'] = unicode(self.page_limit) 00588 00589 # Get videos within each category 00590 dictionaries = [] 00591 00592 # Process the various video feeds/categories/... etc 00593 for key in self.tree_order: 00594 self.tree_key = key 00595 dictionaries = self.getVideos(self.tree_org[key], dictionaries) 00596 00597 return [[self.channel, dictionaries]] 00598 # end displayTreeView() 00599 00600 def makeURL(self, URL): 00601 '''Form a URL to search for videos 00602 return a URL 00603 ''' 00604 additions = dict(self.tree_customize[self.tree_key]['__default__']) # Set defaults 00605 00606 # Add customizations 00607 if self.feed in self.tree_customize[self.tree_key].keys(): 00608 for element in self.tree_customize[self.tree_key][self.feed].keys(): 00609 additions[element] = self.tree_customize[self.tree_key][self.feed][element] 00610 00611 # Make the search extension string that is added to the URL 00612 addition = u'' 00613 for ky in additions.keys(): 00614 if ky.startswith('add_'): 00615 addition+=u'/%s' % additions[ky] 00616 else: 00617 addition+=u'&%s=%s' % (ky, additions[ky]) 00618 index = URL.find('%') 00619 if index == -1: 00620 return (URL+addition) 00621 else: 00622 return (URL+addition) % self.feed 00623 # end makeURL() 00624 00625 00626 def getVideos(self, dir_dict, dictionaries): 00627 '''Parse a list made of genres/artists ... etc lists and retrieve video meta data 00628 return a dictionary of directory names and categories video metadata 00629 ''' 00630 for sets in dir_dict: 00631 if not isinstance(sets[1], list): 00632 if sets[0] != '': # Add the nested dictionaries display name 00633 try: 00634 dictionaries.append([self.massageDescription(sets[0]), self.setTreeViewIcon(self.feed_icons[self.tree_key][sets[0]])]) 00635 except KeyError: 00636 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon]) 00637 else: 00638 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00639 continue 00640 temp_dictionary = [] 00641 for self.feed in sets[1]: 00642 if self.config[u'urls'][u'tree.view'][self.tree_key].has_key('__all__'): 00643 URL = self.config[u'urls'][u'tree.view'][self.tree_key]['__all__'] 00644 else: 00645 URL = self.config[u'urls'][u'tree.view'][self.tree_key][self.feed] 00646 temp_dictionary = self.config['item_parser'][URL[1]](self.makeURL(URL[0]), temp_dictionary) 00647 if len(temp_dictionary): 00648 if len(sets[0]): # Add the nested dictionaries display name 00649 try: 00650 dictionaries.append([self.massageDescription(sets[0]), self.setTreeViewIcon(self.feed_icons[self.tree_key][sets[0]])]) 00651 except KeyError: 00652 dictionaries.append([self.massageDescription(sets[0]), self.channel_icon]) 00653 for element in temp_dictionary: 00654 dictionaries.append(element) 00655 if len(sets[0]): 00656 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00657 return dictionaries 00658 # end getVideos() 00659 00660 00661 def getVideosForURL(self, url, dictionaries): 00662 '''Get the video metadata for url search 00663 return the video dictionary of directories and their video mata data 00664 ''' 00665 initial_length = len(dictionaries) 00666 00667 if self.config['debug_enabled']: 00668 print "Category URL:" 00669 print url 00670 print 00671 00672 try: 00673 etree = XmlHandler(url).getEt() 00674 except Exception, errormsg: 00675 sys.stderr.write(self.error_messages['MtvUrlError'] % (url, errormsg)) 00676 return dictionaries 00677 00678 if etree is None: 00679 sys.stderr.write(u'1-No Videos for (%s)\n' % self.feed) 00680 return dictionaries 00681 00682 dictionary_first = False 00683 for elements in etree: 00684 if elements.tag.endswith(u'totalResults'): 00685 self.channel['channel_numresults'] += int(elements.text) 00686 self.channel['channel_startindex'] = self.page_limit 00687 self.channel['channel_returned'] = self.page_limit # False value CHANGE later 00688 continue 00689 00690 if not elements.tag.endswith(u'entry'): 00691 continue 00692 00693 metadata = {} 00694 cur_size = True 00695 flash = False 00696 metadata['language'] = self.config['language'] 00697 for e in elements: 00698 if e.tag.endswith(u'title'): 00699 if e.text != None: 00700 metadata['title'] = self.massageDescription(e.text.strip()) 00701 else: 00702 metadata['title'] = u'' 00703 continue 00704 if e.tag == u'content': 00705 if e.text != None: 00706 metadata['media_description'] = self.massageDescription(e.text.strip()) 00707 else: 00708 metadata['media_description'] = u'' 00709 continue 00710 if e.tag.endswith(u'published'): # '2007-03-06T00:00:00Z' 00711 if e.text != None: 00712 pub_time = time.strptime(e.text.strip(), "%Y-%m-%dT%H:%M:%SZ") 00713 metadata['published_parsed'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time) 00714 else: 00715 metadata['published_parsed'] = u'' 00716 continue 00717 if e.tag.endswith(u'content') and e.text == None: 00718 metadata['video'] = self.ampReplace(e.get('url')) 00719 metadata['duration'] = e.get('duration') 00720 continue 00721 if e.tag.endswith(u'player'): 00722 metadata['link'] = self.ampReplace(e.get('url')) 00723 continue 00724 if e.tag.endswith(u'thumbnail'): 00725 if cur_size == False: 00726 continue 00727 height = e.get('height') 00728 width = e.get('width') 00729 if int(width) > cur_size: 00730 metadata['thumbnail'] = self.ampReplace(e.get('url')) 00731 cur_size = int(width) 00732 if int(width) >= 200: 00733 cur_size = False 00734 continue 00735 if e.tag.endswith(u'author'): 00736 for a in e: 00737 if a.tag.endswith(u'name'): 00738 if a.text: 00739 metadata['media_credit'] = self.massageDescription(a.text.strip()) 00740 else: 00741 metadata['media_credit'] = u'' 00742 break 00743 continue 00744 00745 if not len(metadata): 00746 raise MtvVideoDetailError(u'2-No Video meta data for (%s)' % url) 00747 00748 if not metadata.has_key('video') and not metadata.has_key('link'): 00749 continue 00750 00751 if not metadata.has_key('video'): 00752 metadata['video'] = metadata['link'] 00753 else: 00754 index = metadata['video'].rindex(u':') 00755 metadata['video'] = self.mtvHtmlPath % (urllib.quote(metadata['title'].encode("utf-8")), metadata['video'][index+1:]) 00756 metadata['link'] = metadata['video'] 00757 # !! This tag will need to be added at a later date 00758 # metadata['customhtml'] = u'true' 00759 00760 if not dictionary_first: # Add the dictionaries display name 00761 dictionaries.append([self.massageDescription(self.feed_names[self.tree_key][self.feed]), self.setTreeViewIcon()]) 00762 dictionary_first = True 00763 00764 final_item = {} 00765 for key in self.key_translation[1].keys(): 00766 if not metadata.has_key(key): 00767 final_item[self.key_translation[1][key]] = u'' 00768 else: 00769 final_item[self.key_translation[1][key]] = metadata[key] 00770 dictionaries.append(final_item) 00771 00772 if initial_length < len(dictionaries): # Need to check if there was any items for this Category 00773 dictionaries.append(['', u'']) # Add the nested dictionary indicator 00774 return dictionaries 00775 # end getVideosForURL() 00776 # end Videos() class
1.7.6.1