def test_Methodheap_MythXML_002_01(self): """Test MythXML.getHosts() with logging.""" with add_log_flags(): m_instance = MythXML() hosts = m_instance.getHosts() bename = self.testenv['BACKENDNAME'] self.assertTrue(bename in hosts)
def test_Methodheap_MythXML_001_01(self): """Test MythXML.getHosts().""" m_instance = MythXML() hosts = m_instance.getHosts() #print(hosts) bename = self.testenv['BACKENDNAME'] self.assertTrue(bename in hosts)
def test_Methodheap_MythXML_002_02(self): """Test MythXML.getKeys() with logging.""" a = False with add_log_flags(): m_instance = MythXML() keys_list = m_instance.getKeys() self.assertTrue(u'MenuTheme' in keys_list) a = (len(tailandgrep('/tmp/my_logfile', 3, r'/Myth/GetKeys')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_001_06(self): """Test MythXML.getChannelIcon() """ m_instance = MythXML() icon = m_instance.getChannelIcon(self.testenv['RECCHANID']) with open('/tmp/icon', 'wb') as f: f.write(icon) os.system('file /tmp/icon > /tmp/my_logfile') a = (len(tailandgrep('/tmp/my_logfile', 2, 'JPEG|PNG')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_002_03(self): """Test MythXML.getSetting() with logging.""" a = False with add_log_flags(): m_instance = MythXML() port = m_instance.getSetting('BackendServerPort', default='1111') self.assertTrue(int(port), 6543) a = (len(tailandgrep('/tmp/my_logfile', 3, r'BackendServerPort')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_001_09(self): """Test MythXML.getPreviewImage().""" m_instance = MythXML() rec_chanid = self.testenv['DOWNCHANID'] rec_starttime = self.testenv['DOWNSTARTTIME'] preview = m_instance.getPreviewImage(str(rec_chanid), rec_starttime) with open('/tmp/preview', 'wb') as f: f.write(preview) os.system('file /tmp/preview > /tmp/my_logfile') a = (len(tailandgrep('/tmp/my_logfile', 2, 'JPEG|PNG')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_002_08(self): """Test MythXML.getExpiring() with logging.""" a = False with add_log_flags(): m_instance = MythXML() rec_list = m_instance.getExpiring() rec = next(rec_list) self.assertTrue(len(rec.title) > 0) a = (len(tailandgrep('/tmp/my_logfile', 3, r'GetExpiringList')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_001_04(self): """Test MythXML.getProgramGuide() """ now_0 = pdtime.now() now_4 = now_0 + pddelta(hours=4) m_instance = MythXML() guide_list = m_instance.getProgramGuide(now_0.isoformat(), now_4.isoformat(), self.testenv['RECCHANID'], numchan=None) prog = next(guide_list) self.assertTrue(len(prog.title) > 0)
def test_Methodheap_MythXML_002_06(self): """Test MythXML.getChannelIcon() with logging.""" a = False b = False with add_log_flags(): m_instance = MythXML() icon = m_instance.getChannelIcon(self.testenv['RECCHANID']) with open('/tmp/icon', 'wb') as f: f.write(icon) os.system('file /tmp/icon >> /tmp/my_logfile') a = (len(tailandgrep('/tmp/my_logfile', 2, 'JPEG|PNG')) > 0) self.assertTrue(a) b = (len(tailandgrep('/tmp/my_logfile', 3, r'GetChannelIcon')) > 0) self.assertTrue(b)
def test_Methodheap_MythXML_001_05(self): """Test MythXML.getProgramDetails(). """ now_0 = pdtime.now() now_4 = now_0 + pddelta(hours=4) m_instance = MythXML() guide_list = m_instance.getProgramGuide(now_0.isoformat(), now_4.isoformat(), self.testenv['RECCHANID'], numchan=None) prog = next(guide_list) self.assertTrue(len(prog.title) > 0) p_details = m_instance.getProgramDetails(prog.chanid, prog.starttime) #print(repr(p_details)) self.assertTrue(len(p_details.title) > 0)
def test_Methodheap_MythXML_002_04(self): """Test MythXML.getProgramGuide() with logging.""" a = False with add_log_flags(): now_0 = pdtime.now() now_4 = now_0 + pddelta(hours=4) m_instance = MythXML() guide_list = m_instance.getProgramGuide(now_0.isoformat(), now_4.isoformat(), self.testenv['RECCHANID'], numchan=None) prog = next(guide_list) self.assertTrue(len(prog.title) > 0) a = (len(tailandgrep('/tmp/my_logfile', 20, r'GetProgramGuide')) > 0) self.assertTrue(a)
def test_Methodheap_MythXML_001_010(self): """Test MythXML.getRecorded() during standard time and daylight saving time. """ preview_cet_is_pic = False preview_cest_is_pic = False m_instance = MythXML() progs = m_instance.getRecorded() try: found_cet = False found_cest = False while True: p = next(progs) if not found_cet: if (p.starttime > self.t1_cet and p.starttime < self.t2_cet): pcet = p found_cet = True if not found_cest: if (p.starttime > self.t1_cest and p.starttime < self.t2_cest): pcest = p found_cest = True if (found_cet and found_cest): break except StopIteration: raise preview_cet = m_instance.getPreviewImage(str(pcet.chanid), pcet.recstartts) with open('/tmp/preview_cet', 'wb') as f: f.write(preview_cet) s_cet = System(path='file') out_cet = s_cet('/tmp/preview_cet') preview_cet_is_pic = ((b'PNG' in out_cet) or (b'JPEG' in out_cet)) preview_cest = m_instance.getPreviewImage(str(pcest.chanid), pcest.recstartts) with open('/tmp/preview_cest', 'wb') as f: f.write(preview_cest) s_cest = System(path='file') out_cest = s_cest('/tmp/preview_cest') preview_cest_is_pic = ((b'PNG' in out_cest) or (b'JPEG' in out_cest)) self.assertTrue(preview_cet_is_pic) self.assertTrue(preview_cest_is_pic)
def test_Methodheap_MythXML_002_010(self): """Test MythXML.getRecorded() during standard time and daylight saving time. """ preview_cet_is_pic = False preview_cest_is_pic = False with add_log_flags(): m_instance = MythXML() progs = m_instance.getRecorded() try: found_cet = False found_cest = False while True: p = next(progs) if not found_cet: if (p.starttime > self.t1_cet and p.starttime < self.t2_cet): pcet = p found_cet = True if not found_cest: if (p.starttime > self.t1_cest and p.starttime < self.t2_cest): pcest = p found_cest = True if (found_cet and found_cest): break except StopIteration: raise preview_cet = m_instance.getPreviewImage(str(pcet.chanid), pcet.recstartts) with open('/tmp/preview_cet', 'wb') as f: f.write(preview_cet) out_cet = System.system('file /tmp/preview_cet') preview_cet_is_pic = (len( tailandgrep('/tmp/my_logfile', 2, 'JPEG|PNG')) > 0) preview_cest = m_instance.getPreviewImage(str(pcest.chanid), pcest.recstartts) with open('/tmp/preview_cest', 'wb') as f: f.write(preview_cest) out_cest = System.system('file /tmp/preview_cest') preview_cest_is_pic = (len( tailandgrep('/tmp/my_logfile', 2, 'JPEG|PNG')) > 0) self.assertTrue(preview_cet_is_pic) self.assertTrue(preview_cest_is_pic)
class Videos(object): """Main interface to http://blip.tv/ This is done to support a common naming framework for all python Netvision plugins no matter their site target. Supports search and tree view methods The apikey is a not required to access http://blip.tv/ """ def __init__(self, apikey, mythtv = True, interactive = False, select_first = False, debug = False, custom_ui = None, language = None, search_all_languages = False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # blip.tv does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.log_name = "Bliptv" self.log = self._initLogger() # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages # Defaulting to ENGISH but the blip.tv apis do not support specifying a language self.config['language'] = "en" 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", } # This is an example that must be customized for each target site 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': '******', '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'}] # The following url_ configs are based of the # http://blip.tv/about/api/ self.config['base_url'] = "http://www.blip.tv%s" self.config['thumb_url'] = u"http://a.images.blip.tv%s" self.config[u'urls'] = {} # v2 api calls - An example that must be customized for each target site self.config[u'urls'][u'video.search'] = "http://www.blip.tv/search?q=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss" self.config[u'urls'][u'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api" self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions # Functions that parse video data from RSS data self.config['item_parser'] = {} self.config['item_parser']['main'] = self.getVideosForURL # Tree view url and the function that parses that urls meta data self.config[u'urls'][u'tree.view'] = { 'P_R_R_F': { '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'], }, 'categories': { '__all__': ['http://www.blip.tv/rss/', 'main'], }, } # Tree view categories are disabled until their results can be made more meaningful #self.tree_order = ['P_R_R_F', 'categories', ] self.tree_order = ['P_R_R_F'] self.tree_org = { # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]], 'P_R_R_F': [['', ['popular', 'recent', 'random', 'featured',]], ], # categories are dynamically filled in from a list retrieved from the blip.tv site 'categories': [ ['Categories', u''], ], } self.tree_customize = { 'P_R_R_F': { '__default__': { }, #'cat name': {}, }, 'categories': { '__default__': {'categories_id': u'', 'sort': u'', }, #'cat name': {}, }, } self.feed_names = { 'P_R_R_F': {'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, 'categories': {'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, } self.feed_icons = { 'P_R_R_F': {'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, 'categories': {'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, } # Initialize the tree view flag so that the item parsing code can be used for multiple purposes self.categories = False self.treeview = False self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bliptv.png' # end __init__() ########################################################################################################### # # Start - Utility functions # ########################################################################################################### def detectUserLocationByIP(self): '''Get longitude and latitiude to find videos relative to your location. Up to three different servers will be tried before giving up. return a dictionary e.g. {'Latitude': '43.6667', 'Country': 'Canada', 'Longitude': '-79.4167', 'City': 'Toronto'} return an empty dictionary if there were any errors Code found at: http://blog.suinova.com/2009/04/from-ip-to-geolocation-country-city.html ''' def getExternalIP(): '''Find the external IP address of this computer. ''' url = urllib.URLopener() try: resp = url.open('http://www.whatismyip.com/automation/n09230945.asp') return resp.read() except: return None # end getExternalIP() ip = getExternalIP() if ip == None: return {} try: gs = urllib.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml' % ip) txt = gs.read() except: try: gs = urllib.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip) txt = gs.read() except: try: gs = urllib.urlopen('http://api.hostip.info/?ip=%s' % ip) txt = gs.read() except: logging.error('GeoIP servers not available') return {} try: if txt.find('<Response>') > 0: countrys = re.findall(r'<CountryName>([\w ]+)<',txt)[0] citys = re.findall(r'<City>([\w ]+)<',txt)[0] lats,lons = re.findall(r'<Latitude>([\d\-\.]+)</Latitude>\s*<Longitude>([\d\-\.]+)<',txt)[0] elif txt.find('GLatLng') > 0: citys,countrys = re.findall('<br />\s*([^<]+)<br />\s*([^<]+)<',txt)[0] lats,lons = re.findall('LatLng\(([-\d\.]+),([-\d\.]+)',txt)[0] elif txt.find('<gml:coordinates>') > 0: citys = re.findall('<Hostip>\s*<gml:name>(\w+)</gml:name>',txt)[0] countrys = re.findall('<countryName>([\w ,\.]+)</countryName>',txt)[0] lats,lons = re.findall('gml:coordinates>([-\d\.]+),([-\d\.]+)<',txt)[0] else: logging.error('error parsing IP result %s'%txt) return {} return {'Country':countrys,'City':citys,'Latitude':lats,'Longitude':lons} except: logging.error('Error parsing IP result %s'%txt) return {} # end detectUserLocationByIP() def massageDescription(self, text): '''Removes HTML markup from a text string. @param text The HTML source. @return The plain text. If the HTML source contains non-ASCII entities or character references, this is a Unicode string. ''' def fixup(m): text = m.group(0) if text[:1] == "<": return "" # ignore tags if text[:2] == "&#": try: if text[:3] == "&#x": return unichr(int(text[3:-1], 16)) else: return unichr(int(text[2:-1])) except ValueError: pass elif text[:1] == "&": import htmlentitydefs entity = htmlentitydefs.entitydefs.get(text[1:-1]) if entity: if entity[:2] == "&#": try: return unichr(int(entity[2:-1])) except ValueError: pass else: return unicode(entity, "iso-8859-1") return text # leave as is return self.ampReplace(re.sub(u"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace(u'\n',u' ') # end massageDescription() def _initLogger(self): """Setups a logger using the logging module, returns a log object """ logger = logging.getLogger(self.log_name) formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s') hdlr = logging.StreamHandler(sys.stdout) hdlr.setFormatter(formatter) logger.addHandler(hdlr) if self.config['debug_enabled']: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) return logger #end initLogger def textUtf8(self, text): if text == None: return text try: return unicode(text, 'utf8') except UnicodeDecodeError: return u'' except (UnicodeEncodeError, TypeError): return text # end textUtf8() def ampReplace(self, text): '''Replace all "&" characters with "&" ''' text = self.textUtf8(text) return text.replace(u'&',u'~~~~~').replace(u'&',u'&').replace(u'~~~~~', u'&') # end ampReplace() def setTreeViewIcon(self, dir_icon=None): '''Check if there is a specific generic tree view icon. If not default to the channel icon. return self.tree_dir_icon ''' self.tree_dir_icon = self.channel_icon if not dir_icon: if not self.feed_icons.has_key(self.tree_key): return self.tree_dir_icon if not self.feed_icons[self.tree_key].has_key(self.feed): return self.tree_dir_icon dir_icon = self.feed_icons[self.tree_key][self.feed] if not dir_icon: return self.tree_dir_icon self.tree_dir_icon = u'%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, ) return self.tree_dir_icon # end setTreeViewIcon() ########################################################################################################### # # End of Utility functions # ########################################################################################################### def processVideoUrl(self, url): playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bliptv.html", \ url.replace(u'http://blip.tv/play/', '')) return self.ampReplace(playerUrl) def searchTitle(self, title, pagenumber, pagelen): '''Key word video search of the blip.tv web site return an array of matching item dictionaries return ''' url = self.config[u'urls'][u'video.search'] % (urllib.quote_plus(title.encode("utf-8")), pagenumber, pagelen, self.config['language']) if self.config['debug_enabled']: print "Search URL:" print url print try: etree = XmlHandler(url).getEt() except Exception, errormsg: raise BliptvUrlError(self.error_messages['BliptvUrlError'] % (url, errormsg)) if etree is None: raise BliptvVideoNotFound(u"1-No blip.tv Video matches found for search value (%s)" % title) # Massage each field and eliminate any item without a URL elements_final = [] dictionary_first = False directory_image = u'' self.next_page = False language = self.config['language'] for elements in etree.find('channel'): if elements.tag == 'language': if elements.text: language = elements.text[:2] continue if not elements.tag == 'item': continue item = {} item['language'] = language embedURL = u'' for elem in elements: if elem.tag == 'title': if elem.text: item['title'] = self.massageDescription(elem.text.strip()) continue if elem.tag.endswith('safeusername'): if elem.text: item['blip_safeusername'] = self.massageDescription(elem.text.strip()) continue if elem.tag.endswith('pubDate'): if elem.text: item['updated'] = self.massageDescription(elem.text.strip()) continue if elem.tag.endswith('puredescription'): if elem.text: item['blip_puredescription'] = self.massageDescription(elem.text.strip()) continue if elem.tag.endswith('link'): if elem.text: item['link'] = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('embedUrl'): if elem.text: embedURL = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('thumbnail'): if elem.get('url'): item['blip_picture'] = self.ampReplace(elem.get('url').strip()) continue if elem.tag.endswith('group'): file_size = 0 for e in elem: if e.tag.endswith('content'): if e.get('fileSize'): try: if int(e.get('fileSize')) > file_size: item['video'] = self.ampReplace(e.get('url').strip()) file_size = int(e.get('fileSize')) except: pass continue continue if elem.tag.endswith('runtime'): if elem.text: item['blip_runtime'] = self.massageDescription(elem.text.strip()) continue if elem.tag.endswith('rating'): if elem.text: item['blip_rating'] = self.massageDescription(elem.text.strip()) continue if not item.has_key('video') and not item.has_key('link') and not embedURL: continue if embedURL: item['link'] = self.processVideoUrl(embedURL) if item.has_key('link') and not item.has_key('video'): continue if item.has_key('video') and not item.has_key('link'): item['link'] = item['video'] elements_final.append(item) if not len(elements_final): raise BliptvVideoNotFound(u"2-No blip.tv Video matches found for search value (%s)" % title) return elements_final
def test_Methodheap_MythXML_001_08(self): """Test MythXML.getExpiring().""" m_instance = MythXML() rec_list = m_instance.getExpiring() rec = next(rec_list) self.assertTrue(len(rec.title) > 0)
def __init__( self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # blip.tv does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.log_name = "Bliptv" self.log = self._initLogger( ) # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages # Defaulting to ENGISH but the blip.tv apis do not support specifying a language self.config['language'] = "en" self.error_messages = { 'BliptvUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'BliptvHttpError': "! Error: An HTTP communicating error with blip.tv was raised (%s)\n", 'BliptvRssError': "! Error: Invalid RSS meta data\nwas received from blip.tv error (%s). Skipping item.\n", 'BliptvVideoNotFound': "! Error: Video search with blip.tv did not return any results (%s)\n", } # This is an example that must be customized for each target site 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': '******', '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' }] # The following url_ configs are based of the # http://blip.tv/about/api/ self.config['base_url'] = "http://www.blip.tv%s" self.config['thumb_url'] = "http://a.images.blip.tv%s" self.config['urls'] = {} # v2 api calls - An example that must be customized for each target site self.config['urls'][ 'video.search'] = "http://www.blip.tv/?search=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss" self.config['urls'][ 'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api" self.config['image_extentions'] = ["png", "jpg", "bmp" ] # Acceptable image extentions # Functions that parse video data from RSS data self.config['item_parser'] = {} self.config['item_parser']['main'] = self.getVideosForURL # Tree view url and the function that parses that urls meta data self.config['urls']['tree.view'] = { 'P_R_R_F': { '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'], }, 'categories': { '__all__': ['http://www.blip.tv/rss/', 'main'], }, } # Tree view categories are disabled until their results can be made more meaningful #self.tree_order = ['P_R_R_F', 'categories', ] self.tree_order = ['P_R_R_F'] self.tree_org = { # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]], 'P_R_R_F': [ ['', [ 'popular', 'recent', 'random', 'featured', ]], ], # categories are dynamically filled in from a list retrieved from the blip.tv site 'categories': [ ['Categories', ''], ], } self.tree_customize = { 'P_R_R_F': { '__default__': {}, #'cat name': {}, }, 'categories': { '__default__': { 'categories_id': '', 'sort': '', }, #'cat name': {}, }, } self.feed_names = { 'P_R_R_F': { 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, 'categories': { 'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, } self.feed_icons = { 'P_R_R_F': { 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, 'categories': { 'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, } # Initialize the tree view flag so that the item parsing code can be used for multiple purposes self.categories = False self.treeview = False self.channel_icon = '%SHAREDIR%/mythnetvision/icons/bliptv.png'
def test_Methodheap_MythXML_001_02(self): """Test MythXML.getKeys().""" m_instance = MythXML() keys_list = m_instance.getKeys() self.assertTrue(u'MenuTheme' in keys_list)
def test_Methodheap_MythXML_001_03(self): """Test MythXML.getSetting().""" m_instance = MythXML() port = m_instance.getSetting('BackendServerPort', default='1111') self.assertTrue(int(port), 6543)
def __init__(self, apikey, mythtv = True, interactive = False, select_first = False, debug = False, custom_ui = None, language = None, search_all_languages = False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # blip.tv does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.log_name = "Bliptv" self.log = self._initLogger() # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages # Defaulting to ENGISH but the blip.tv apis do not support specifying a language self.config['language'] = "en" 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", } # This is an example that must be customized for each target site 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': '******', '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'}] # The following url_ configs are based of the # http://blip.tv/about/api/ self.config['base_url'] = "http://www.blip.tv%s" self.config['thumb_url'] = u"http://a.images.blip.tv%s" self.config[u'urls'] = {} # v2 api calls - An example that must be customized for each target site self.config[u'urls'][u'video.search'] = "http://www.blip.tv/search?q=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss" self.config[u'urls'][u'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api" self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions # Functions that parse video data from RSS data self.config['item_parser'] = {} self.config['item_parser']['main'] = self.getVideosForURL # Tree view url and the function that parses that urls meta data self.config[u'urls'][u'tree.view'] = { 'P_R_R_F': { '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'], }, 'categories': { '__all__': ['http://www.blip.tv/rss/', 'main'], }, } # Tree view categories are disabled until their results can be made more meaningful #self.tree_order = ['P_R_R_F', 'categories', ] self.tree_order = ['P_R_R_F'] self.tree_org = { # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]], 'P_R_R_F': [['', ['popular', 'recent', 'random', 'featured',]], ], # categories are dynamically filled in from a list retrieved from the blip.tv site 'categories': [ ['Categories', u''], ], } self.tree_customize = { 'P_R_R_F': { '__default__': { }, #'cat name': {}, }, 'categories': { '__default__': {'categories_id': u'', 'sort': u'', }, #'cat name': {}, }, } self.feed_names = { 'P_R_R_F': {'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, 'categories': {'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, } self.feed_icons = { 'P_R_R_F': {'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, 'categories': {'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, } # Initialize the tree view flag so that the item parsing code can be used for multiple purposes self.categories = False self.treeview = False self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bliptv.png'
class Videos(object): """Main interface to http://www.youtube.com/ This is done to support a common naming framework for all python Netvision plugins no matter their site target. Supports search methods """ def __init__(self, apikey, mythtv = True, interactive = False, select_first = False, debug = False, custom_ui = None, language = None, search_all_languages = False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.common = common_api.Common() self.mythxml = MythXML() self.config['debug_enabled'] = debug # show debugging messages self.log_name = "youtube" self.log = self._initLogger() # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages self.error_messages = \ {'YouTubeUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'YouTubeHttpError': "! Error: An HTTP communications error with YouTube was raised (%s)\n", 'YouTubeRssError': "! Error: Invalid RSS meta data\nwas received from YouTube error (%s). Skipping item.\n", 'YouTubeVideoNotFound': "! Error: Video search with YouTube did not return any results (%s)\n", 'YouTubeVideoDetailError': "! Error: Invalid Video meta data detail\nwas received from YouTube error (%s). Skipping item.\n", } # This is an example that must be customized for each target site 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', 'author': 'item_author', 'published_parsed': 'item_pubdate', 'media_description': 'item_description', 'video': 'item_link', 'thumbnail': 'item_thumbnail', 'link': 'item_url', 'duration': 'item_duration', 'rating': 'item_rating', 'item_width': 'item_width', 'item_height': 'item_height', 'language': 'item_lang'}] # Defaulting to no language specified. The YouTube apis does support specifying a language if language: self.config['language'] = language else: self.config['language'] = '' self.getUserPreferences("~/.mythtv/MythNetvision/userGrabberPrefs/youtube.xml") # Read region code from user preferences, used by tree view region = self.userPrefs.find("region") if region is not None and region.text: self.config['region'] = region.text else: self.config['region'] = 'us' self.apikey = getData().update(getData().a) apikey = self.userPrefs.find("apikey") if apikey is not None and apikey.text: self.apikey = apikey.text self.feed_icons = { 'Film & Animation': 'directories/topics/movies', 'Movies': 'directories/topics/movies', 'Trailers': 'directories/topics/movies', 'Sports': 'directories/topics/sports', 'News & Politics': 'directories/topics/news', 'Science & Technology': 'directories/topics/technology', 'Education': 'directories/topics/education', 'Howto & Style': 'directories/topics/howto', 'Music': 'directories/topics/music', 'Gaming': 'directories/topics/games', 'Entertainment': 'directories/topics/entertainment', 'Autos & Vehicles': 'directories/topics/automotive', 'Pets & Animals': 'directories/topics/animals', 'Travel & Events': 'directories/topics/travel', 'People & Blogs': 'directories/topics/people', } self.treeview = False self.channel_icon = '%SHAREDIR%/mythnetvision/icons/youtube.png' # end __init__() def getUserPreferences(self, userPreferenceFilePath): userPreferenceFilePath = os.path.expanduser(userPreferenceFilePath) # If the user config file does not exists then copy one the default if not os.path.isfile(userPreferenceFilePath): # Make the necessary directories if they do not already exist prefDir = os.path.dirname(userPreferenceFilePath) if not os.path.isdir(prefDir): os.makedirs(prefDir) fileName = os.path.basename(userPreferenceFilePath) defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/%s' \ % (baseProcessingDir, fileName) shutil.copy2(defaultConfig, userPreferenceFilePath) # Read the grabber hulu_config.xml configuration file url = 'file://%s' % userPreferenceFilePath if self.config['debug_enabled']: print(url) print() try: self.userPrefs = self.common.etree.parse(url) except Exception as e: raise Exception(url, e) ########################################################################################################### # # Start - Utility functions # ########################################################################################################### def detectUserLocationByIP(self): '''Get longitude and latitiude to find videos relative to your location. Up to three different servers will be tried before giving up. return a dictionary e.g. {'Latitude': '43.6667', 'Country': 'Canada', 'Longitude': '-79.4167', 'City': 'Toronto'} return an empty dictionary if there were any errors Code found at: http://blog.suinova.com/2009/04/from-ip-to-geolocation-country-city.html ''' def getExternalIP(): '''Find the external IP address of this computer. ''' url = urllib.request.URLopener() try: resp = url.open('http://www.whatismyip.com/automation/n09230945.asp') return resp.read() except: return None # end getExternalIP() ip = getExternalIP() if ip is None: return {} try: gs = urllib.request.urlopen('http://blogama.org/ip_query.php?ip=%s&output=xml' % ip) txt = gs.read() except: try: gs = urllib.request.urlopen('http://www.seomoz.org/ip2location/look.php?ip=%s' % ip) txt = gs.read() except: try: gs = urllib.request.urlopen('http://api.hostip.info/?ip=%s' % ip) txt = gs.read() except: logging.error('GeoIP servers not available') return {} try: if txt.find('<Response>') > 0: countrys = re.findall(r'<CountryName>([\w ]+)<',txt)[0] citys = re.findall(r'<City>([\w ]+)<',txt)[0] lats,lons = re.findall(r'<Latitude>([\d\-\.]+)</Latitude>\s*<Longitude>([\d\-\.]+)<',txt)[0] elif txt.find('GLatLng') > 0: citys,countrys = re.findall(r'<br />\s*([^<]+)<br />\s*([^<]+)<',txt)[0] lats,lons = re.findall(r'LatLng\(([-\d\.]+),([-\d\.]+)',txt)[0] elif txt.find('<gml:coordinates>') > 0: citys = re.findall(r'<Hostip>\s*<gml:name>(\w+)</gml:name>',txt)[0] countrys = re.findall(r'<countryName>([\w ,\.]+)</countryName>',txt)[0] lats,lons = re.findall(r'gml:coordinates>([-\d\.]+),([-\d\.]+)<',txt)[0] else: logging.error('error parsing IP result %s'%txt) return {} return {'Country':countrys,'City':citys,'Latitude':lats,'Longitude':lons} except: logging.error('Error parsing IP result %s'%txt) return {} # end detectUserLocationByIP() def massageDescription(self, text): '''Removes HTML markup from a text string. @param text The HTML source. @return The plain text. If the HTML source contains non-ASCII entities or character references, this is a Unicode string. ''' def fixup(m): text = m.group(0) if text[:1] == "<": return "" # ignore tags if text[:2] == "&#": try: if text[:3] == "&#x": return chr(int(text[3:-1], 16)) else: return chr(int(text[2:-1])) except ValueError: pass elif text[:1] == "&": import html.entities entity = html.entities.entitydefs.get(text[1:-1]) if entity: if entity[:2] == "&#": try: return chr(int(entity[2:-1])) except ValueError: pass else: return str(entity, "iso-8859-1") return text # leave as is return self.common.ampReplace(re.sub(r"(?s)<[^>]*>|&#?\w+;", fixup, self.common.textUtf8(text))) # end massageDescription() def _initLogger(self): """Setups a logger using the logging module, returns a log object """ logger = logging.getLogger(self.log_name) formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s') hdlr = logging.StreamHandler(sys.stdout) hdlr.setFormatter(formatter) logger.addHandler(hdlr) if self.config['debug_enabled']: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) return logger #end initLogger def setTreeViewIcon(self, dir_icon=None): '''Check if there is a specific generic tree view icon. If not default to the channel icon. return self.tree_dir_icon ''' self.tree_dir_icon = self.channel_icon if not dir_icon: if self.tree_key not in self.feed_icons: return self.tree_dir_icon dir_icon = self.feed_icons[self.tree_key] if not dir_icon: return self.tree_dir_icon self.tree_dir_icon = '%%SHAREDIR%%/mythnetvision/icons/%s.png' % (dir_icon, ) return self.tree_dir_icon # end setTreeViewIcon() ########################################################################################################### # # End of Utility functions # ########################################################################################################### def searchTitle(self, title, pagenumber, pagelen): '''Key word video search of the YouTube web site return an array of matching item dictionaries return ''' # Special case where the grabber has been executed without any page # argument if 1 == pagenumber: pagenumber = "" result = self.getSearchResults(title, pagenumber, pagelen) if not result: raise YouTubeVideoNotFound("No YouTube Video matches found for search value (%s)" % title) self.channel['channel_numresults'] = int(result['pageInfo']['totalResults']) if 'nextPageToken' in result: self.channel['nextpagetoken'] = result['nextPageToken'] if 'prevPageToken' in result: self.channel['prevpagetoken'] = result['prevPageToken'] ids = [entry['id']['videoId'] for entry in result['items']] result = self.getVideoDetails(ids) data = [self.parseDetails(entry) for entry in result['items']] if not len(data): raise YouTubeVideoNotFound("No YouTube Video matches found for search value (%s)" % title) return data # end searchTitle() def getSearchResults(self, title, pagenumber, pagelen): url = ('https://www.googleapis.com/youtube/v3/search?part=snippet&' + \ 'type=video&q=%s&maxResults=%s&order=relevance&' + \ 'videoEmbeddable=true&key=%s&pageToken=%s') % \ (urllib.parse.quote_plus(title.encode("utf-8")), pagelen, self.apikey, pagenumber) if self.config['debug_enabled']: print(url) print() try: return JsonHandler(url).getJson() except Exception as errormsg: raise YouTubeUrlError(self.error_messages['YouTubeUrlError'] % (url, errormsg)) def getVideoDetails(self, ids): url = 'https://www.googleapis.com/youtube/v3/videos?part=id,snippet,' + \ 'contentDetails&key=%s&id=%s' % (self.apikey, ",".join(ids)) try: return JsonHandler(url).getJson() except Exception as errormsg: raise YouTubeUrlError(self.error_messages['YouTubeUrlError'] % (url, errormsg)) def parseDetails(self, entry): item = {} try: item['id'] = entry['id'] item['video'] = \ self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/youtube.html", \ item['id']) item['link'] = item['video'] snippet = entry['snippet'] item['title'] = snippet['title'] item['media_description'] = snippet['description'] item['thumbnail'] = snippet['thumbnails']['high']['url'] item['author'] = snippet['channelTitle'] item['published_parsed'] = snippet['publishedAt'] try: duration = aniso8601.parse_duration(entry['contentDetails']['duration']) item['duration'] = duration.days * 24 * 3600 + duration.seconds except Exception: pass for key in list(item.keys()): # Make sure there are no item elements that are None if item[key] is None: item[key] = '' elif key == 'published_parsed': # 2010-01-23T08:38:39.000Z if item[key]: pub_time = time.strptime(item[key].strip(), "%Y-%m-%dT%H:%M:%SZ") item[key] = time.strftime('%a, %d %b %Y %H:%M:%S GMT', pub_time) elif key == 'media_description' or key == 'title': # Strip the HTML tags if item[key]: item[key] = self.massageDescription(item[key].strip()) item[key] = item[key].replace('|', '-') elif type(item[key]) == type(''): if item[key]: item[key] = self.common.ampReplace(item[key].replace('"\n',' ').strip()) except KeyError: pass return item def searchForVideos(self, title, pagenumber): """Common name for a video search. Used to interface with MythTV plugin NetVision """ # Channel details and search results self.channel = { 'channel_title': 'YouTube', 'channel_link': 'http://www.youtube.com/', 'channel_description': "Share your videos with friends, family, and the world.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0} # Easier for debugging # print self.searchTitle(title, pagenumber, self.page_limit) # print # sys.exit() try: data = self.searchTitle(title, pagenumber, self.page_limit) except YouTubeVideoNotFound as msg: sys.stderr.write("%s\n" % msg) return None except YouTubeUrlError as msg: sys.stderr.write('%s\n' % msg) sys.exit(1) except YouTubeHttpError as msg: sys.stderr.write(self.error_messages['YouTubeHttpError'] % msg) sys.exit(1) except YouTubeRssError as msg: sys.stderr.write(self.error_messages['YouTubeRssError'] % msg) sys.exit(1) except Exception as e: sys.stderr.write("! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) sys.exit(1) if data is None: return None if not len(data): return None items = [self.translateItem(match) for match in data] self.channel['channel_returned'] = len(items) if len(items): return [[self.channel, items]] return None # end searchForVideos() def translateItem(self, item): item_data = {} for key in list(self.key_translation[1].keys()): if key in list(item.keys()): item_data[self.key_translation[1][key]] = item[key] else: item_data[self.key_translation[1][key]] = '' return item_data def displayTreeView(self): '''Gather the Youtube categories/feeds/...etc then get a max page of videos meta data in each of them return array of directories and their video metadata ''' # Channel details and search results self.channel = { 'channel_title': 'YouTube', 'channel_link': 'http://www.youtube.com/', 'channel_description': "Share your videos with friends, family, and the world.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0} etree = self.getVideoCategories() if etree is None: raise YouTubeCategoryNotFound("No YouTube Categories found for Tree view") feed_names = {} for category in etree['items']: snippet = category['snippet'] feed_names[snippet['title']] = self.common.ampReplace(category['id']) # Get videos within each category dictionaries = [] # Process the various video feeds/categories/... etc for category in feed_names: self.tree_key = category dictionaries = self.getVideosForCategory(feed_names[category], dictionaries) return [[self.channel, dictionaries]] # end displayTreeView() def getVideoCategories(self): try: url = 'https://www.googleapis.com/youtube/v3/videoCategories?' + \ 'part=snippet®ionCode=%s&key=%s' % \ (self.config['region'], self.apikey) return JsonHandler(url).getJson() except Exception as errormsg: raise YouTubeUrlError(self.error_messages['YouTubeUrlError'] % (url, errormsg)) def getVideosForCategory(self, categoryId, dictionaries): '''Parse a list made of category lists and retrieve video meta data return a dictionary of directory names and categories video metadata ''' url = 'https://www.googleapis.com/youtube/v3/videos?part=snippet&' + \ 'chart=mostPopular&videoCategoryId=%s&maxResults=%s&key=%s' % \ (categoryId, self.page_limit, self.apikey) temp_dictionary = [] temp_dictionary = self.getVideosForURL(url, temp_dictionary) for element in temp_dictionary: dictionaries.append(element) return dictionaries # end getVideosForCategory() def getVideosForURL(self, url, dictionaries): '''Get the video metadata for url search return the video dictionary of directories and their video mata data ''' initial_length = len(dictionaries) if self.config['debug_enabled']: print("Category URL:") print(url) print() try: result = JsonHandler(url).getJson() except Exception as errormsg: sys.stderr.write(self.error_messages['YouTubeUrlError'] % (url, errormsg)) return dictionaries if result is None: sys.stderr.write('1-No Videos for (%s)\n' % self.feed) return dictionaries if 'pageInfo' not in result or 'items' not in result: return dictionaries dictionary_first = False self.channel['channel_numresults'] += int(result['pageInfo']['totalResults']) self.channel['channel_startindex'] = self.page_limit self.channel['channel_returned'] = len(result['items']) for entry in result['items']: item = self.parseDetails(entry) if not dictionary_first: # Add the dictionaries display name dictionaries.append([self.massageDescription(self.tree_key), self.setTreeViewIcon()]) dictionary_first = True dictionaries.append(self.translateItem(item)) if initial_length < len(dictionaries): # Need to check if there was any items for this Category dictionaries.append(['', '']) # Add the nested dictionary indicator return dictionaries
def __init__(self, apikey, mythtv = True, interactive = False, select_first = False, debug = False, custom_ui = None, language = None, search_all_languages = False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # YouTube does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.log_name = "youtube" self.log = self._initLogger() # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages self.error_messages = {'YouTubeUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'YouTubeHttpError': u"! Error: An HTTP communications error with YouTube was raised (%s)\n", 'YouTubeRssError': u"! Error: Invalid RSS meta data\nwas received from YouTube error (%s). Skipping item.\n", 'YouTubeVideoNotFound': u"! Error: Video search with YouTube did not return any results (%s)\n", 'YouTubeVideoDetailError': u"! Error: Invalid Video meta data detail\nwas received from YouTube error (%s). Skipping item.\n", } # This is an example that must be customized for each target site 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', 'author': 'item_author', 'published_parsed': 'item_pubdate', 'media_description': 'item_description', 'video': 'item_link', 'thumbnail': 'item_thumbnail', 'link': 'item_url', 'duration': 'item_duration', 'rating': 'item_rating', 'item_width': 'item_width', 'item_height': 'item_height', 'language': 'item_lang'}] # Defaulting to no language specified. The YouTube apis does support specifying a language if language: self.config['language'] = language else: self.config['language'] = u'' self.config[u'urls'] = {} # v2 api calls - An example that must be customized for each target site self.config[u'urls'][u'video.search'] = 'http://gdata.youtube.com/feeds/api/videos?vq=%s&max-results=%s&start-index=%s&orderby=relevance&Ir=%s' # Functions that parse video data from RSS data self.config['item_parser'] = {} self.config['item_parser']['main'] = self.getVideosForURL # Tree view url and the function that parses that urls meta data self.config[u'urls'][u'tree.view'] = { 'standard_feeds': { '__all__': ['http://gdata.youtube.com/feeds/api/standardfeeds/%s?v=2', 'main'], }, 'category': { '__all__': ['http://gdata.youtube.com/feeds/api/videos?category=%s&v=2', 'main'], }, 'local_feeds': { '__all__': ['http://gdata.youtube.com/feeds/api/standardfeeds/%s?v=2', 'main'], }, 'location_feeds': { '__all__': ['http://gdata.youtube.com/feeds/api/videos?v=2&q=%s', 'main'], }, } self.config[u'urls'][u'categories_list'] = 'http://gdata.youtube.com/schemas/2007/categories.cat' self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions self.tree_order = ['standard_feeds', 'location_feeds', 'local_feeds', 'category'] self.tree_org = { 'category': [ ['', ['Film']], ['', ['Sports']], ['Information', ['News', 'Tech', 'Education', 'Howto', ]], ['Entertainment', ['Comedy', 'Music', 'Games', 'Entertainment', ]], ['Other', ['Autos', 'Animals', 'Travel', 'People', 'Nonprofit']] ], 'standard_feeds': [['Feeds', ['top_rated', 'top_favourites', 'most_viewed', 'most_popular', 'most_recent', 'most_discussed', 'most_responded', 'recently_featured', '']], ], 'local_feeds': [['Feeds', ['top_rated', 'top_favourites', 'most_viewed', 'most_popular', 'most_recent', 'most_discussed', 'most_responded', 'recently_featured', '']], ], 'location_feeds': [['', ['location']], ] } self.tree_customize = { 'category': { '__default__': {'order': 'rating', 'max-results': '20', 'start-index': '1', 'Ir': self.config['language']}, #'cat name': {'order: '', 'max-results': , 'start-index': , 'restriction: '', 'time': '', 'Ir': ''}, 'Film': {'max-results': '40', 'time': 'this_month',}, 'Music': {'max-results': '40', 'time': 'this_month',}, 'Sports': {'max-results': '40', 'time': 'this_month',}, }, 'standard_feeds': { '__default__': {'order': 'rating', 'max-results': '20', 'start-index': '1', 'Ir': self.config['language'], 'time': 'this_month'}, #'feed name": {'order: '', 'max-results': , 'start-index': , 'restriction: '', 'time': '', 'Ir': ''} }, 'local_feeds': { '__default__': {'order': 'rating', 'max-results': '20', 'start-index': '1', 'Ir': self.config['language'], 'location': '', 'location-radius':'500km'}, #'feed name": {'order: '', 'max-results': , 'start-index': , 'restriction: '', 'time': '', 'Ir': ''} }, 'location_feeds': { '__default__': {'order': 'rating', 'max-results': '20', 'start-index': '1', 'Ir': self.config['language'], }, #'feed name": {'order: '', 'max-results': , 'start-index': , 'restriction: '', 'time': '', 'Ir': ''} }, } self.feed_names = { 'standard_feeds': {'top_rated': 'Highest Rated', 'top_favourites': 'Most Subscribed', 'most_viewed': 'Most Viewed', 'most_popular': 'Most Popular', 'most_recent': 'Most Recent', 'most_discussed': 'Most Comments', 'most_responded': 'Most Responses', 'recently_featured': 'Featured'} } self.feed_icons = { 'standard_feeds': {'top_rated': 'directories/topics/rated', 'top_favourites': 'directories/topics/most_subscribed', 'most_viewed': 'directories/topics/most_viewed', 'most_popular': None, 'most_recent': 'directories/topics/most_recent', 'most_discussed': 'directories/topics/most_comments', 'most_responded': None, 'recently_featured': 'directories/topics/featured' }, 'local_feeds': {'top_rated': 'directories/topics/rated', 'top_favourites': 'directories/topics/most_subscribed', 'most_viewed': 'directories/topics/most_viewed', 'most_popular': None, 'most_recent': 'directories/topics/most_recent', 'most_discussed': 'directories/topics/most_comments', 'most_responded': None, 'recently_featured': 'directories/topics/featured' }, 'category': { 'Film': 'directories/topics/movies', 'Comedy': 'directories/film_genres/comedy', 'Sports': 'directories/topics/sports', 'News': 'directories/topics/news', 'Tech': 'directories/topics/technology', 'Education': 'directories/topics/education', 'Howto': 'directories/topics/howto', 'Music': 'directories/topics/music', 'Games': 'directories/topics/games', 'Entertainment': 'directories/topics/entertainment', 'Autos': 'directories/topics/automotive', 'Animals': 'directories/topics/animals', 'Travel': 'directories/topics/travel', 'People': 'directories/topics/people', 'Nonprofit': 'directories/topics/nonprofit', }, } self.treeview = False self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/youtube.png'
class Videos(object): """Main interface to http://blip.tv/ This is done to support a common naming framework for all python Netvision plugins no matter their site target. Supports search and tree view methods The apikey is a not required to access http://blip.tv/ """ def __init__( self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # blip.tv does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.log_name = "Bliptv" self.log = self._initLogger( ) # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages # Defaulting to ENGISH but the blip.tv apis do not support specifying a language self.config['language'] = "en" self.error_messages = { 'BliptvUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'BliptvHttpError': "! Error: An HTTP communicating error with blip.tv was raised (%s)\n", 'BliptvRssError': "! Error: Invalid RSS meta data\nwas received from blip.tv error (%s). Skipping item.\n", 'BliptvVideoNotFound': "! Error: Video search with blip.tv did not return any results (%s)\n", } # This is an example that must be customized for each target site 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': '******', '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' }] # The following url_ configs are based of the # http://blip.tv/about/api/ self.config['base_url'] = "http://www.blip.tv%s" self.config['thumb_url'] = "http://a.images.blip.tv%s" self.config['urls'] = {} # v2 api calls - An example that must be customized for each target site self.config['urls'][ 'video.search'] = "http://www.blip.tv/?search=%s;&page=%s;&pagelen=%s;&language_code=%s;&skin=rss" self.config['urls'][ 'categories'] = "http://www.blip.tv/?section=categories&cmd=view&skin=api" self.config['image_extentions'] = ["png", "jpg", "bmp" ] # Acceptable image extentions # Functions that parse video data from RSS data self.config['item_parser'] = {} self.config['item_parser']['main'] = self.getVideosForURL # Tree view url and the function that parses that urls meta data self.config['urls']['tree.view'] = { 'P_R_R_F': { '__all__': ['http://www.blip.tv/%s/?skin=rss', 'main'], }, 'categories': { '__all__': ['http://www.blip.tv/rss/', 'main'], }, } # Tree view categories are disabled until their results can be made more meaningful #self.tree_order = ['P_R_R_F', 'categories', ] self.tree_order = ['P_R_R_F'] self.tree_org = { # 'P_R_R_F': [['Popular/Recent/Features/Random ...', ['popular', 'recent', 'random', 'featured',]], 'P_R_R_F': [ ['', [ 'popular', 'recent', 'random', 'featured', ]], ], # categories are dynamically filled in from a list retrieved from the blip.tv site 'categories': [ ['Categories', ''], ], } self.tree_customize = { 'P_R_R_F': { '__default__': {}, #'cat name': {}, }, 'categories': { '__default__': { 'categories_id': '', 'sort': '', }, #'cat name': {}, }, } self.feed_names = { 'P_R_R_F': { 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, 'categories': { 'featured': 'Featured Videos', 'popular': 'Most Comments', 'recent': 'Most Recent', 'random': 'Random selection', }, } self.feed_icons = { 'P_R_R_F': { 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, 'categories': { 'featured': 'directories/topics/featured', 'popular': 'directories/topics/most_comments', 'recent': 'directories/topics/most_recent', 'random': 'directories/topics/random', }, } # Initialize the tree view flag so that the item parsing code can be used for multiple purposes self.categories = False self.treeview = False self.channel_icon = '%SHAREDIR%/mythnetvision/icons/bliptv.png' # end __init__() ########################################################################################################### # # Start - Utility functions # ########################################################################################################### def detectUserLocationByIP(self): '''Get longitude and latitiude to find videos relative to your location. Up to three different servers will be tried before giving up. return a dictionary e.g. {'Latitude': '43.6667', 'Country': 'Canada', 'Longitude': '-79.4167', 'City': 'Toronto'} return an empty dictionary if there were any errors Code found at: http://blog.suinova.com/2009/04/from-ip-to-geolocation-country-city.html ''' def getExternalIP(): '''Find the external IP address of this computer. ''' url = urllib.request.URLopener() try: resp = url.open( 'http://www.whatismyip.com/automation/n09230945.asp') return resp.read() except: return None # end getExternalIP() ip = getExternalIP() if ip is None: return {} try: gs = urllib.request.urlopen( 'http://blogama.org/ip_query.php?ip=%s&output=xml' % ip) txt = gs.read() except: try: gs = urllib.request.urlopen( 'http://www.seomoz.org/ip2location/look.php?ip=%s' % ip) txt = gs.read() except: try: gs = urllib.request.urlopen( 'http://api.hostip.info/?ip=%s' % ip) txt = gs.read() except: logging.error('GeoIP servers not available') return {} try: if txt.find('<Response>') > 0: countrys = re.findall(r'<CountryName>([\w ]+)<', txt)[0] citys = re.findall(r'<City>([\w ]+)<', txt)[0] lats, lons = re.findall( r'<Latitude>([\d\-\.]+)</Latitude>\s*<Longitude>([\d\-\.]+)<', txt)[0] elif txt.find('GLatLng') > 0: citys, countrys = re.findall( r'<br />\s*([^<]+)<br />\s*([^<]+)<', txt)[0] lats, lons = re.findall(r'LatLng\(([-\d\.]+),([-\d\.]+)', txt)[0] elif txt.find('<gml:coordinates>') > 0: citys = re.findall(r'<Hostip>\s*<gml:name>(\w+)</gml:name>', txt)[0] countrys = re.findall( r'<countryName>([\w ,\.]+)</countryName>', txt)[0] lats, lons = re.findall( r'gml:coordinates>([-\d\.]+),([-\d\.]+)<', txt)[0] else: logging.error('error parsing IP result %s' % txt) return {} return { 'Country': countrys, 'City': citys, 'Latitude': lats, 'Longitude': lons } except: logging.error('Error parsing IP result %s' % txt) return {} # end detectUserLocationByIP() def massageDescription(self, text): '''Removes HTML markup from a text string. @param text The HTML source. @return The plain text. If the HTML source contains non-ASCII entities or character references, this is a Unicode string. ''' def fixup(m): text = m.group(0) if text[:1] == "<": return "" # ignore tags if text[:2] == "&#": try: if text[:3] == "&#x": return chr(int(text[3:-1], 16)) else: return chr(int(text[2:-1])) except ValueError: pass elif text[:1] == "&": import html.entities entity = html.entities.entitydefs.get(text[1:-1]) if entity: if entity[:2] == "&#": try: return chr(int(entity[2:-1])) except ValueError: pass else: return str(entity, "iso-8859-1") return text # leave as is return self.ampReplace( re.sub(r"(?s)<[^>]*>|&#?\w+;", fixup, self.textUtf8(text))).replace('\n', ' ') # end massageDescription() def _initLogger(self): """Setups a logger using the logging module, returns a log object """ logger = logging.getLogger(self.log_name) formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s') hdlr = logging.StreamHandler(sys.stdout) hdlr.setFormatter(formatter) logger.addHandler(hdlr) if self.config['debug_enabled']: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) return logger #end initLogger def textUtf8(self, text): if text is None: return text try: return str(text, 'utf8') except UnicodeDecodeError: return '' except (UnicodeEncodeError, TypeError): return text # end textUtf8() def ampReplace(self, text): '''Replace all "&" characters with "&" ''' text = self.textUtf8(text) return text.replace('&', '~~~~~').replace('&', '&').replace( '~~~~~', '&') # end ampReplace() def setTreeViewIcon(self, dir_icon=None): '''Check if there is a specific generic tree view icon. If not default to the channel icon. return self.tree_dir_icon ''' self.tree_dir_icon = self.channel_icon if not dir_icon: if self.tree_key not in self.feed_icons: return self.tree_dir_icon if self.feed not in self.feed_icons[self.tree_key]: return self.tree_dir_icon dir_icon = self.feed_icons[self.tree_key][self.feed] if not dir_icon: return self.tree_dir_icon self.tree_dir_icon = '%%SHAREDIR%%/mythnetvision/icons/%s.png' % ( dir_icon, ) return self.tree_dir_icon # end setTreeViewIcon() ########################################################################################################### # # End of Utility functions # ########################################################################################################### def processVideoUrl(self, url): playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bliptv.html", \ url.replace('http://blip.tv/play/', '')) return self.ampReplace(playerUrl) def searchTitle(self, title, pagenumber, pagelen): '''Key word video search of the blip.tv web site return an array of matching item dictionaries return ''' url = self.config['urls']['video.search'] % (urllib.parse.quote_plus( title.encode("utf-8")), pagenumber, pagelen, self.config['language']) if self.config['debug_enabled']: print("Search URL:") print(url) print() try: etree = XmlHandler(url).getEt() except Exception as errormsg: raise BliptvUrlError(self.error_messages['BliptvUrlError'] % (url, errormsg)) if etree is None: raise BliptvVideoNotFound( "1-No blip.tv Video matches found for search value (%s)" % title) # Massage each field and eliminate any item without a URL elements_final = [] dictionary_first = False directory_image = '' self.next_page = False language = self.config['language'] for elements in etree.find('channel'): if elements.tag == 'language': if elements.text: language = elements.text[:2] continue if not elements.tag == 'item': continue item = {} item['language'] = language embedURL = '' for elem in elements: if elem.tag == 'title': if elem.text: item['title'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('safeusername'): if elem.text: item['blip_safeusername'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('pubDate'): if elem.text: item['updated'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('puredescription'): if elem.text: item['blip_puredescription'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('link'): if elem.text: item['link'] = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('embedUrl'): if elem.text: embedURL = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('thumbnail'): if elem.get('url'): item['blip_picture'] = self.ampReplace( elem.get('url').strip()) continue if elem.tag.endswith('group'): file_size = 0 for e in elem: if e.tag.endswith('content'): if e.get('fileSize'): try: if int(e.get('fileSize')) > file_size: item['video'] = self.ampReplace( e.get('url').strip()) file_size = int(e.get('fileSize')) except: pass continue continue if elem.tag.endswith('runtime'): if elem.text: item['blip_runtime'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('rating'): if elem.text: item['blip_rating'] = self.massageDescription( elem.text.strip()) continue if 'video' not in item and 'link' not in item and not embedURL: continue if embedURL: item['link'] = self.processVideoUrl(embedURL) if 'link' in item and 'video' not in item: continue if 'video' in item and 'link' not in item: item['link'] = item['video'] elements_final.append(item) if not len(elements_final): raise BliptvVideoNotFound( "2-No blip.tv Video matches found for search value (%s)" % title) return elements_final # end searchTitle() def searchForVideos(self, title, pagenumber): """Common name for a video search. Used to interface with MythTV plugin NetVision """ try: data = self.searchTitle(title, pagenumber, self.page_limit) except BliptvVideoNotFound as msg: sys.stderr.write("%s\n" % msg) return None except BliptvUrlError as msg: sys.stderr.write('%s' % msg) sys.exit(1) except BliptvHttpError as msg: sys.stderr.write(self.error_messages['BliptvHttpError'] % msg) sys.exit(1) except BliptvRssError as msg: sys.stderr.write(self.error_messages['BliptvRssError'] % msg) sys.exit(1) except Exception as e: sys.stderr.write( "! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) sys.exit(1) if data is None: return None if not len(data): return None items = [] for match in data: item_data = {} for key in list(self.key_translation[1].keys()): if key in list(match.keys()): item_data[self.key_translation[1][key]] = match[key] else: item_data[self.key_translation[1][key]] = '' items.append(item_data) # Channel details and search results channel = { 'channel_title': 'blip.tv', 'channel_link': 'http://blip.tv', 'channel_description': "We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0 } if len(items) == self.page_limit: channel[ 'channel_numresults'] = self.page_limit * int(pagenumber) + 1 else: channel['channel_numresults'] = self.page_limit * int(pagenumber) channel['channel_startindex'] = self.page_limit * int(pagenumber) channel['channel_returned'] = len(items) if len(items): return [[channel, items]] return None # end searchForVideos() def getCategories(self): '''Get the list of valid category ids and their name and update the proper dictionaries return nothing ''' url = self.config['urls']['categories'] if self.config['debug_enabled']: print("Category list URL:") print(url) print() try: etree = XmlHandler(url).getEt() except Exception as errormsg: sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg)) self.tree_order.remove('categories') return if etree is None: sys.stderr.write('1-No Categories found at (%s)\n' % url) self.tree_order.remove('categories') return if not etree.find('payload'): sys.stderr.write('2-No Categories found at (%s)\n' % url) self.tree_order.remove('categories') return category = False for element in etree.find('payload'): if element.tag == 'category': tmp_name = '' tmp_id = '' for e in element: if e.tag == 'id': if e.text == '-1': break if e.text: tmp_id = self.massageDescription(e.text.strip()) if e.tag == 'name': if e.text: tmp_name = self.massageDescription(e.text.strip()) if tmp_id and tmp_name: category = True self.tree_org['categories'].append([ tmp_name, [ 'popular', 'recent', 'random', 'featured', ] ]) self.feed_names['categories'][tmp_name] = tmp_id if not category: sys.stderr.write('3-No Categories found at (%s)\n' % url) self.tree_order.remove('categories') return self.tree_org['categories'].append( ['', '']) # Adds a end of the Categories directory indicator return # end getCategories() def displayTreeView(self): '''Gather the categories/feeds/...etc then retrieve a max page of videos meta data in each of them return array of directories and their video meta data ''' # Channel details and search results self.channel = { 'channel_title': 'blip.tv', 'channel_link': 'http://blip.tv', 'channel_description': "We're the next generation television network", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0 } if self.config['debug_enabled']: print(self.config['urls']) print() # Get category ids self.getCategories() # Process the various video feeds/categories/... etc self.treeview = True dictionaries = [] for key in self.tree_order: if key == 'categories': self.categories = True else: self.categories = False self.tree_key = key dictionaries = self.getVideos(self.tree_org[key], dictionaries) return [[self.channel, dictionaries]] # end displayTreeView() def makeURL(self, URL): '''Form a URL to search for videos return a URL ''' additions = dict( self.tree_customize[self.tree_key]['__default__']) # Set defaults # Add customizations if self.feed in list(self.tree_customize[self.tree_key].keys()): for element in list( self.tree_customize[self.tree_key][self.feed].keys()): additions[element] = self.tree_customize[self.tree_key][ self.feed][element] # Make the search extension string that is added to the URL addition = '' for ky in list(additions.keys()): if ky.startswith('add_'): addition += '/%s' % additions[ky] else: addition += '?%s=%s' % (ky, additions[ky]) index = URL.find('%') if index == -1: return (URL + addition) else: return (URL + addition) % self.feed # end makeURL() def getVideos(self, dir_dict, dictionaries): '''Parse a list made of categories and retrieve video meta data return a dictionary of directory names and categories video meta data ''' for sets in dir_dict: if not isinstance(sets[1], list): if sets[0] != '': # Add the nested dictionaries display name dictionaries.append( [self.massageDescription(sets[0]), self.channel_icon]) else: dictionaries.append( ['', '']) # Add the nested dictionary indicator continue temp_dictionary = [] for self.feed in sets[1]: if self.categories: self.tree_customize[self.tree_key]['__default__'][ 'categories_id'] = self.feed_names['categories'][ sets[0]] self.tree_customize[ self.tree_key]['__default__']['sort'] = self.feed if '__all__' in self.config['urls']['tree.view'][ self.tree_key]: URL = self.config['urls']['tree.view'][ self.tree_key]['__all__'] else: URL = self.config['urls']['tree.view'][self.tree_key][ self.feed] temp_dictionary = self.config['item_parser'][URL[1]]( self.makeURL(URL[0]), temp_dictionary) if len(temp_dictionary): if len(sets[0]): # Add the nested dictionaries display name dictionaries.append( [self.massageDescription(sets[0]), self.channel_icon]) for element in temp_dictionary: dictionaries.append(element) if len(sets[0]): dictionaries.append( ['', '']) # Add the nested dictionary indicator return dictionaries # end getVideos() def getVideosForURL(self, url, dictionaries): '''Get the video meta data for url search return the video dictionary of directories and their video mata data ''' initial_length = len(dictionaries) if self.config['debug_enabled']: print("Video URL:") print(url) print() try: etree = XmlHandler(url).getEt() except Exception as errormsg: sys.stderr.write(self.error_messages['BliptvUrlError'] % (url, errormsg)) return dictionaries if etree is None: sys.stderr.write('1-No Videos for (%s)\n' % self.feed) return dictionaries dictionary_first = False self.next_page = False language = self.config['language'] for elements in etree.find('channel'): if elements.tag.endswith('language'): if elements.text: language = elements.text[:2] continue if not elements.tag.endswith('item'): continue item = {} item['language'] = language embedURL = '' for elem in elements: if elem.tag == 'title': if elem.text: item['title'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('safeusername'): if elem.text: item['blip_safeusername'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('pubDate'): if elem.text: item['updated'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('puredescription'): if elem.text: item['blip_puredescription'] = self.massageDescription( elem.text.strip()) continue if elem.tag == 'link': if elem.text: item['link'] = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('embedUrl'): if elem.text: embedURL = self.ampReplace(elem.text.strip()) continue if elem.tag.endswith('thumbnail'): if elem.get('url'): item['blip_picture'] = self.ampReplace( elem.get('url').strip()) continue if elem.tag.endswith('group'): file_size = 0 for e in elem: if e.tag.endswith('content'): for key in list(e.keys()): if key.endswith('vcodec'): break else: continue if e.get('fileSize'): try: if int(e.get('fileSize')) > file_size: item['video'] = self.ampReplace( e.get('url').strip()) file_size = int(e.get('fileSize')) except: pass if e.get('height'): item['height'] = e.get('height').strip() if e.get('width'): item['width'] = e.get('width').strip() continue continue if elem.tag.endswith('runtime'): if elem.text: item['blip_runtime'] = self.massageDescription( elem.text.strip()) continue if elem.tag.endswith('rating'): if elem.text: item['blip_rating'] = self.massageDescription( elem.text.strip()) continue if 'video' not in item and 'link' not in item: continue if embedURL: item['link'] = embedURL if 'link' in item and 'video' not in item: continue if 'video' in item and 'link' not in item: item['link'] = item['video'] if self.treeview: if not dictionary_first: # Add the dictionaries display name dictionaries.append([ self.massageDescription( self.feed_names[self.tree_key][self.feed]), self.setTreeViewIcon() ]) dictionary_first = True final_item = {} for key in list(self.key_translation[1].keys()): if key not in item: final_item[self.key_translation[1][key]] = '' else: final_item[self.key_translation[1][key]] = item[key] dictionaries.append(final_item) if self.treeview: if initial_length < len( dictionaries ): # Need to check if there was any items for this Category dictionaries.append(['', '' ]) # Add the nested dictionary indicator return dictionaries
def __init__( self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.common = common_api.Common() self.mythxml = MythXML() self.config['debug_enabled'] = debug # show debugging messages self.log_name = "youtube" self.log = self._initLogger( ) # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive # prompt for correct series? self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages self.error_messages = \ {'YouTubeUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'YouTubeHttpError': u"! Error: An HTTP communications error with YouTube was raised (%s)\n", 'YouTubeRssError': u"! Error: Invalid RSS meta data\nwas received from YouTube error (%s). Skipping item.\n", 'YouTubeVideoNotFound': u"! Error: Video search with YouTube did not return any results (%s)\n", 'YouTubeVideoDetailError': u"! Error: Invalid Video meta data detail\nwas received from YouTube error (%s). Skipping item.\n", } # This is an example that must be customized for each target site 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', 'author': 'item_author', 'published_parsed': 'item_pubdate', 'media_description': 'item_description', 'video': 'item_link', 'thumbnail': 'item_thumbnail', 'link': 'item_url', 'duration': 'item_duration', 'rating': 'item_rating', 'item_width': 'item_width', 'item_height': 'item_height', 'language': 'item_lang'}] # Defaulting to no language specified. The YouTube apis does support specifying a language if language: self.config['language'] = language else: self.config['language'] = u'' self.getUserPreferences( "~/.mythtv/MythNetvision/userGrabberPrefs/youtube.xml") # Read region code from user preferences, used by tree view region = self.userPrefs.find("region") if region is not None and region.text: self.config['region'] = region.text else: self.config['region'] = u'us' self.apikey = getData().update(getData().a) apikey = self.userPrefs.find("apikey") if apikey is not None and apikey.text: self.apikey = apikey.text self.feed_icons = { 'Film & Animation': 'directories/topics/movies', 'Movies': 'directories/topics/movies', 'Trailers': 'directories/topics/movies', 'Sports': 'directories/topics/sports', 'News & Politics': 'directories/topics/news', 'Science & Technology': 'directories/topics/technology', 'Education': 'directories/topics/education', 'Howto & Style': 'directories/topics/howto', 'Music': 'directories/topics/music', 'Gaming': 'directories/topics/games', 'Entertainment': 'directories/topics/entertainment', 'Autos & Vehicles': 'directories/topics/automotive', 'Pets & Animals': 'directories/topics/animals', 'Travel & Events': 'directories/topics/travel', 'People & Blogs': 'directories/topics/people', } self.treeview = False self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/youtube.png'
def __init__( self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # BBC does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.common = common self.common.debug = debug # Set the common function debug level self.log_name = u'BBCiPlayer_Grabber' self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name) self.logger = self.common.logger # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages self.error_messages = { 'BBCUrlError': u"! Error: The URL (%s) cause the exception error (%s)\n", 'BBCHttpError': u"! Error: An HTTP communications error with the BBC was raised (%s)\n", 'BBCRssError': u"! Error: Invalid RSS meta data\nwas received from the BBC error (%s). Skipping item.\n", 'BBCVideoNotFound': u"! Error: Video search with the BBC did not return any results (%s)\n", 'BBCConfigFileError': u"! Error: bbc_config.xml file missing\nit should be located in and named as (%s).\n", 'BBCUrlDownloadError': u"! Error: Downloading a RSS feed or Web page (%s).\n", } # Channel details and search results self.channel = { 'channel_title': u'BBC iPlayer', 'channel_link': u'http://www.bbc.co.uk', 'channel_description': u"BBC iPlayer is our service that lets you catch up with radio and television programmes from the past week.", 'channel_numresults': 0, 'channel_returned': 1, u'channel_startindex': 0 } # XPath parsers used to detect a video type of item self.countryCodeParsers = [ etree.XPath('.//a[@class="episode-title title-link cta-video"]', namespaces=self.common.namespaces), etree.XPath('.//div[@class="feature video"]', namespaces=self.common.namespaces), etree.XPath('.//atm:category[@term="TV"]', namespaces=self.common.namespaces), ] # Season and Episode detection regex patterns self.s_e_Patterns = [ # "Series 7 - Episode 4" or "Series 7 - Episode 4" or "Series 7: On Holiday: Episode 10" re.compile( u'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 5 - 1 re.compile( u'''^.+?Series\\ (?P<seasno>[0-9]+)\\ \\-\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 1 - Warriors of Kudlak - Part 2 re.compile( u'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Part\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 3: Programme 3 re.compile( u'''^.+?Series\\ (?P<seasno>[0-9]+)\\:\\ Programme\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 3: re.compile(u'''^.+?Series\\ (?P<seasno>[0-9]+).*$''', re.UNICODE), # Episode 1 re.compile(u'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE), ] self.channel_icon = u'%SHAREDIR%/mythnetvision/icons/bbciplayer.jpg' self.config[u'image_extentions'] = ["png", "jpg", "bmp" ] # Acceptable image extentions
class Videos(object): """Main interface to http://www.bbciplayer.com/ This is done to support a common naming framework for all python Netvision plugins no matter their site target. Supports search methods The apikey is a not required to access http://www.bbciplayer.com/ """ def __init__( self, apikey, mythtv=True, interactive=False, select_first=False, debug=False, custom_ui=None, language=None, search_all_languages=False, ): """apikey (str/unicode): Specify the target site API key. Applications need their own key in some cases mythtv (True/False): When True, the returned meta data is being returned has the key and values massaged to match MythTV When False, the returned meta data is being returned matches what target site returned interactive (True/False): (This option is not supported by all target site apis) When True, uses built-in console UI is used to select the correct show. When False, the first search result is used. select_first (True/False): (This option is not supported currently implemented in any grabbers) Automatically selects the first series search result (rather than showing the user a list of more than one series). Is overridden by interactive = False, or specifying a custom_ui debug (True/False): shows verbose debugging information custom_ui (xx_ui.BaseUI subclass): (This option is not supported currently implemented in any grabbers) A callable subclass of interactive class (overrides interactive option) language (2 character language abbreviation): (This option is not supported by all target site apis) The language of the returned data. Is also the language search uses. Default is "en" (English). For full list, run.. search_all_languages (True/False): (This option is not supported by all target site apis) By default, a Netvision grabber will only search in the language specified using the language option. When this is True, it will search for the show in any language """ self.config = {} self.mythxml = MythXML() if apikey is not None: self.config['apikey'] = apikey else: pass # BBC does not require an apikey self.config['debug_enabled'] = debug # show debugging messages self.common = common self.common.debug = debug # Set the common function debug level self.log_name = 'BBCiPlayer_Grabber' self.common.logger = self.common.initLogger(path=sys.stderr, log_name=self.log_name) self.logger = self.common.logger # Setups the logger (self.log.debug() etc) self.config['custom_ui'] = custom_ui self.config['interactive'] = interactive self.config['select_first'] = select_first self.config['search_all_languages'] = search_all_languages self.error_messages = { 'BBCUrlError': "! Error: The URL (%s) cause the exception error (%s)\n", 'BBCHttpError': "! Error: An HTTP communications error with the BBC was raised (%s)\n", 'BBCRssError': "! Error: Invalid RSS meta data\nwas received from the BBC error (%s). Skipping item.\n", 'BBCVideoNotFound': "! Error: Video search with the BBC did not return any results (%s)\n", 'BBCConfigFileError': "! Error: bbc_config.xml file missing\nit should be located in and named as (%s).\n", 'BBCUrlDownloadError': "! Error: Downloading a RSS feed or Web page (%s).\n", } # Channel details and search results self.channel = { 'channel_title': 'BBC iPlayer', 'channel_link': 'http://www.bbc.co.uk', 'channel_description': "BBC iPlayer is our service that lets you catch up with radio and television programmes from the past week.", 'channel_numresults': 0, 'channel_returned': 1, 'channel_startindex': 0 } # XPath parsers used to detect a video type of item self.countryCodeParsers = [ etree.XPath('.//a[@class="episode-title title-link cta-video"]', namespaces=self.common.namespaces), etree.XPath('.//div[@class="feature video"]', namespaces=self.common.namespaces), etree.XPath('.//atm:category[@term="TV"]', namespaces=self.common.namespaces), ] # Season and Episode detection regex patterns self.s_e_Patterns = [ # "Series 7 - Episode 4" or "Series 7 - Episode 4" or "Series 7: On Holiday: Episode 10" re.compile( r'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Episode\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 5 - 1 re.compile( r'''^.+?Series\\ (?P<seasno>[0-9]+)\\ \\-\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 1 - Warriors of Kudlak - Part 2 re.compile( r'''^.+?Series\\ (?P<seasno>[0-9]+).*.+?Part\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 3: Programme 3 re.compile( r'''^.+?Series\\ (?P<seasno>[0-9]+)\\:\\ Programme\\ (?P<epno>[0-9]+).*$''', re.UNICODE), # Series 3: re.compile(r'''^.+?Series\\ (?P<seasno>[0-9]+).*$''', re.UNICODE), # Episode 1 re.compile(r'''^.+?Episode\\ (?P<seasno>[0-9]+).*$''', re.UNICODE), ] self.channel_icon = '%SHAREDIR%/mythnetvision/icons/bbciplayer.jpg' self.config['image_extentions'] = ["png", "jpg", "bmp" ] # Acceptable image extentions # end __init__() ########################################################################################################### # # Start - Utility functions # ########################################################################################################### def getBBCConfig(self): ''' Read the MNV BBC iPlayer grabber "bbc_config.xml" configuration file return nothing ''' # Read the grabber bbciplayer_config.xml configuration file url = 'file://%s/nv_python_libs/configs/XML/bbc_config.xml' % ( baseProcessingDir, ) if not os.path.isfile(url[7:]): raise BBCConfigFileError( self.error_messages['BBCConfigFileError'] % (url[7:], )) if self.config['debug_enabled']: print(url) print() try: self.bbciplayer_config = etree.parse(url) except Exception as e: raise BBCUrlError(self.error_messages['BBCUrlError'] % (url, errormsg)) return # end getBBCConfig() def getUserPreferences(self): '''Read the bbciplayer_config.xml and user preference bbciplayer.xml file. If the bbciplayer.xml file does not exist then copy the default. return nothing ''' # Get bbciplayer_config.xml self.getBBCConfig() # Check if the bbciplayer.xml file exists userPreferenceFile = self.bbciplayer_config.find( 'userPreferenceFile').text if userPreferenceFile[0] == '~': self.bbciplayer_config.find('userPreferenceFile').text = "%s%s" % ( os.path.expanduser("~"), userPreferenceFile[1:]) # If the user config file does not exists then copy one from the default if not os.path.isfile( self.bbciplayer_config.find('userPreferenceFile').text): # Make the necessary directories if they do not already exist prefDir = self.bbciplayer_config.find( 'userPreferenceFile').text.replace('/bbciplayer.xml', '') if not os.path.isdir(prefDir): os.makedirs(prefDir) defaultConfig = '%s/nv_python_libs/configs/XML/defaultUserPrefs/bbciplayer.xml' % ( baseProcessingDir, ) shutil.copy2( defaultConfig, self.bbciplayer_config.find('userPreferenceFile').text) # Read the grabber bbciplayer_config.xml configuration file url = 'file://%s' % ( self.bbciplayer_config.find('userPreferenceFile').text, ) if self.config['debug_enabled']: print(url) print() try: self.userPrefs = etree.parse(url) except Exception as e: raise BBCUrlError(self.error_messages['BBCUrlError'] % (url, errormsg)) return # end getUserPreferences() def setCountry(self, item): '''Parse the item information (HTML or RSS/XML) to identify if the content is a video or audio file. Set the contry code if a video is detected as it can only be played in the "UK" return "uk" if a video type was detected. return None if a video type was NOT detected. ''' countryCode = None for xpathP in self.countryCodeParsers: if len(xpathP(item)): countryCode = 'uk' break return countryCode # end setCountry() def getSeasonEpisode(self, title): ''' Check is there is any season or episode number information in an item's title return array of season and/or episode numbers return array with None values ''' s_e = [None, None] for index in range(len(self.s_e_Patterns)): match = self.s_e_Patterns[index].match(title) if not match: continue if index < 4: s_e[0], s_e[1] = match.groups() break elif index == 4: s_e[0] = match.groups()[0] break elif index == 5: s_e[1] = match.groups()[0] break return s_e # end getSeasonEpisode() ########################################################################################################### # # End of Utility functions # ########################################################################################################### def processVideoUrl(self, url): playerUrl = self.mythxml.getInternetContentUrl("nv_python_libs/configs/HTML/bbciplayer.html", \ url) return playerUrl def searchTitle(self, title, pagenumber, pagelen): '''Key word video search of the BBC iPlayer web site return an array of matching item elements return ''' # Save the origninal URL orgUrl = self.bbciplayer_config.find('searchURLS').xpath( ".//href")[0].text try: searchVar = '/?q=%s&page=%s' % (urllib.parse.quote( title.encode("utf-8")), pagenumber) except UnicodeDecodeError: searchVar = '/?q=%s&page=%s' % (urllib.parse.quote(title), pagenumber) url = self.bbciplayer_config.find('searchURLS').xpath( ".//href")[0].text + searchVar if self.config['debug_enabled']: print(url) print() self.bbciplayer_config.find('searchURLS').xpath( ".//href")[0].text = url # Perform a search try: resultTree = self.common.getUrlData( self.bbciplayer_config.find('searchURLS'), pageFilter=self.bbciplayer_config.find('searchURLS').xpath( ".//pageFilter")[0].text) except Exception as errormsg: # Restore the origninal URL self.bbciplayer_config.find('searchURLS').xpath( ".//href")[0].text = orgUrl raise BBCUrlDownloadError( self.error_messages['BBCUrlDownloadError'] % (errormsg)) # Restore the origninal URL self.bbciplayer_config.find('searchURLS').xpath( ".//href")[0].text = orgUrl if resultTree is None: raise BBCVideoNotFound( "No BBC Video matches found for search value (%s)" % title) searchResults = resultTree.xpath('//result//li') if not len(searchResults): raise BBCVideoNotFound( "No BBC Video matches found for search value (%s)" % title) # BBC iPlayer search results fo not have a pubDate so use the current data time # e.g. "Sun, 06 Jan 2008 21:44:36 GMT" pubDate = datetime.datetime.now().strftime(self.common.pubDateFormat) # Set the display type for the link (Fullscreen, Web page, Game Console) if self.userPrefs.find('displayURL') is not None: urlType = self.userPrefs.find('displayURL').text else: urlType = 'fullscreen' # Translate the search results into MNV RSS item format audioFilter = etree.XPath( 'contains(./@class,"audio") or contains(./../../@class,"audio")') linkFilter = etree.XPath(".//div[@class='episode-info ']//a") titleFilter = etree.XPath(".//div[@class='episode-info ']//a") descFilter = etree.XPath( ".//div[@class='episode-info ']//p[@class='episode-synopsis']") thumbnailFilter = etree.XPath( ".//span[@class='episode-image cta-play']//img") itemDict = {} for result in searchResults: tmpLink = linkFilter(result) if not len(tmpLink ): # Make sure that this result actually has a video continue bbciplayerItem = etree.XML(self.common.mnvItem) # Is this an Audio or Video item (true/false) audioTF = audioFilter(result) # Extract and massage data link = tmpLink[0].attrib['href'] if urlType == 'bigscreen': link = 'http://www.bbc.co.uk/iplayer/bigscreen%s' % link.replace( '/iplayer', '') elif urlType == 'bbcweb': link = 'http://www.bbc.co.uk' + link else: if not audioTF: link = link.replace('/iplayer/episode/', '') index = link.find('/') link = link[:index] link = self.processVideoUrl(link) etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml" ).text = 'true' else: # Audio media will not play in the embedded HTML page link = 'http://www.bbc.co.uk' + link link = self.common.ampReplace(link) title = self.common.massageText( titleFilter(result)[0].attrib['title'].strip()) description = self.common.massageText( etree.tostring(descFilter(result)[0], method="text", encoding=str).strip()) # Insert data into a new item element bbciplayerItem.find('title').text = title bbciplayerItem.find('author').text = 'BBC' bbciplayerItem.find('pubDate').text = pubDate bbciplayerItem.find('description').text = description bbciplayerItem.find('link').text = link bbciplayerItem.xpath('.//media:thumbnail', namespaces=self.common.namespaces )[0].attrib['url'] = self.common.ampReplace( thumbnailFilter(result)[0].attrib['src']) bbciplayerItem.xpath( './/media:content', namespaces=self.common.namespaces)[0].attrib['url'] = link # Videos are only viewable in the UK so add a country indicator if this is a video if audioTF: countCode = None else: countCode = 'uk' if countCode: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country" ).text = countCode s_e = self.getSeasonEpisode(title) if s_e[0]: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season" ).text = s_e[0] if s_e[1]: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode" ).text = s_e[1] itemDict[title.lower()] = bbciplayerItem if not len(list(itemDict.keys())): raise BBCVideoNotFound( "No BBC Video matches found for search value (%s)" % title) # Set the number of search results returned self.channel['channel_numresults'] = len(itemDict) return [itemDict, resultTree.xpath('//pageInfo')[0].text] # end searchTitle() def searchForVideos(self, title, pagenumber): """Common name for a video search. Used to interface with MythTV plugin NetVision """ # Get the user preferences try: self.getUserPreferences() except Exception as e: sys.stderr.write('%s' % e) sys.exit(1) if self.config['debug_enabled']: print("self.userPrefs:") sys.stdout.write( etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True)) print() # Easier for debugging # print self.searchTitle(title, pagenumber, self.page_limit) # print # sys.exit() try: data = self.searchTitle(title, pagenumber, self.page_limit) except BBCVideoNotFound as msg: sys.stderr.write("%s\n" % msg) sys.exit(0) except BBCUrlError as msg: sys.stderr.write('%s\n' % msg) sys.exit(1) except BBCHttpError as msg: sys.stderr.write(self.error_messages['BBCHttpError'] % msg) sys.exit(1) except BBCRssError as msg: sys.stderr.write(self.error_messages['BBCRssError'] % msg) sys.exit(1) except Exception as e: sys.stderr.write( "! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) sys.exit(1) # Create RSS element tree rssTree = etree.XML(self.common.mnvRSS + '</rss>') # Set the paging values itemCount = len(list(data[0].keys())) if data[1] == 'true': self.channel['channel_returned'] = itemCount self.channel['channel_startindex'] = itemCount self.channel['channel_numresults'] = itemCount + ( self.page_limit * (int(pagenumber) - 1) + 1) else: self.channel['channel_returned'] = itemCount + ( self.page_limit * (int(pagenumber) - 1)) self.channel['channel_startindex'] = self.channel[ 'channel_returned'] self.channel['channel_numresults'] = self.channel[ 'channel_returned'] # Add the Channel element tree channelTree = self.common.mnvChannelElement(self.channel) rssTree.append(channelTree) lastKey = None for key in sorted(data[0].keys()): if lastKey != key: channelTree.append(data[0][key]) lastKey = key # Output the MNV search results sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n') sys.stdout.write( etree.tostring(rssTree, encoding='UTF-8', pretty_print=True)) sys.exit(0) # end searchForVideos() def displayTreeView(self): '''Gather the BBC iPlayer feeds then get a max page of videos meta data in each of them Display the results and exit ''' # Get the user preferences try: self.getUserPreferences() except Exception as e: sys.stderr.write('%s' % e) sys.exit(1) if self.config['debug_enabled']: print("self.userPrefs:") sys.stdout.write( etree.tostring(self.userPrefs, encoding='UTF-8', pretty_print=True)) print() # Massage channel icon self.channel_icon = self.common.ampReplace(self.channel_icon) # Create RSS element tree rssTree = etree.XML(self.common.mnvRSS + '</rss>') # Add the Channel element tree channelTree = self.common.mnvChannelElement(self.channel) rssTree.append(channelTree) # Process any user specified searches searchResultTree = [] searchFilter = etree.XPath("//item") userSearchStrings = 'userSearchStrings' if self.userPrefs.find(userSearchStrings) is not None: userSearch = self.userPrefs.find(userSearchStrings).xpath( './userSearch') if len(userSearch): for searchDetails in userSearch: try: data = self.searchTitle( searchDetails.find('searchTerm').text, 1, self.page_limit) except BBCVideoNotFound as msg: sys.stderr.write("%s\n" % msg) continue except BBCUrlError as msg: sys.stderr.write('%s\n' % msg) continue except BBCHttpError as msg: sys.stderr.write(self.error_messages['BBCHttpError'] % msg) continue except BBCRssError as msg: sys.stderr.write(self.error_messages['BBCRssError'] % msg) continue except Exception as e: sys.stderr.write( "! Error: Unknown error during a Video search (%s)\nError(%s)\n" % (title, e)) continue dirElement = etree.XML('<directory></directory>') dirElement.attrib['name'] = self.common.massageText( searchDetails.find('dirName').text) dirElement.attrib['thumbnail'] = self.channel_icon lastKey = None for key in sorted(data[0].keys()): if lastKey != key: dirElement.append(data[0][key]) lastKey = key channelTree.append(dirElement) continue # Create a structure of feeds that can be concurrently downloaded rssData = etree.XML('<xml></xml>') for feedType in ['treeviewURLS', 'userFeeds']: if self.userPrefs.find(feedType) is None: continue if not len(self.userPrefs.find(feedType).xpath('./url')): continue for rssFeed in self.userPrefs.find(feedType).xpath('./url'): urlEnabled = rssFeed.attrib.get('enabled') if urlEnabled == 'false': continue urlName = rssFeed.attrib.get('name') if urlName: uniqueName = '%s;%s' % (urlName, rssFeed.text) else: uniqueName = 'RSS;%s' % (rssFeed.text) url = etree.XML('<url></url>') etree.SubElement(url, "name").text = uniqueName etree.SubElement(url, "href").text = rssFeed.text etree.SubElement(url, "filter").text = "atm:title" etree.SubElement(url, "filter").text = "//atm:entry" etree.SubElement(url, "parserType").text = 'xml' rssData.append(url) if self.config['debug_enabled']: print("rssData:") sys.stdout.write( etree.tostring(rssData, encoding='UTF-8', pretty_print=True)) print() # Get the RSS Feed data if rssData.find('url') is not None: try: resultTree = self.common.getUrlData(rssData) except Exception as errormsg: raise BBCUrlDownloadError( self.error_messages['BBCUrlDownloadError'] % (errormsg)) if self.config['debug_enabled']: print("resultTree:") sys.stdout.write( etree.tostring(resultTree, encoding='UTF-8', pretty_print=True)) print() # Set the display type for the link (Fullscreen, Web page, Game Console) if self.userPrefs.find('displayURL') is not None: urlType = self.userPrefs.find('displayURL').text else: urlType = 'fullscreen' # Process each directory of the user preferences that have an enabled rss feed feedFilter = etree.XPath('//url[text()=$url]') itemFilter = etree.XPath('.//atm:entry', namespaces=self.common.namespaces) titleFilter = etree.XPath('.//atm:title', namespaces=self.common.namespaces) mediaFilter = etree.XPath('.//atm:category[@term="TV"]', namespaces=self.common.namespaces) linkFilter = etree.XPath('.//atm:link', namespaces=self.common.namespaces) descFilter1 = etree.XPath('.//atm:content', namespaces=self.common.namespaces) descFilter2 = etree.XPath('.//p') itemThumbNail = etree.XPath('.//media:thumbnail', namespaces=self.common.namespaces) creationDate = etree.XPath('.//atm:updated', namespaces=self.common.namespaces) itemDwnLink = etree.XPath('.//media:content', namespaces=self.common.namespaces) itemLanguage = etree.XPath('.//media:content', namespaces=self.common.namespaces) rssName = etree.XPath('atm:title', namespaces=self.common.namespaces) categoryDir = None categoryElement = None itemAuthor = 'BBC' for result in resultTree.findall('results'): names = result.find('name').text.split(';') names[0] = self.common.massageText(names[0]) if names[0] == 'RSS': names[0] = self.common.massageText( rssName(result.find('result'))[0].text.replace( 'BBC iPlayer - ', '')) count = 0 urlMax = None url = feedFilter(self.userPrefs, url=names[1]) if len(url): if url[0].attrib.get('max'): try: urlMax = int(url[0].attrib.get('max')) except: pass elif url[0].getparent().attrib.get('globalmax'): try: urlMax = int( url[0].getparent().attrib.get('globalmax')) except: pass if urlMax == 0: urlMax = None channelThumbnail = self.channel_icon channelLanguage = 'en' # Create a new directory and/or subdirectory if required if names[0] != categoryDir: if categoryDir is not None: channelTree.append(categoryElement) categoryElement = etree.XML('<directory></directory>') categoryElement.attrib['name'] = names[0] categoryElement.attrib['thumbnail'] = self.channel_icon categoryDir = names[0] if self.config['debug_enabled']: print("Results: #Items(%s) for (%s)" % (len(itemFilter(result)), names)) print() # Create a list of item elements in pubDate order to that the items are processed in # date and time order itemDict = [(pd.text, pd.getparent()) for pd in creationDate(result)] itemList = sorted(itemDict, key=itemgetter(0), reverse=True) # Convert each RSS item into a MNV item for tupleDate in itemList: itemData = tupleDate[1] bbciplayerItem = etree.XML(self.common.mnvItem) tmpLink = linkFilter(itemData) if len( tmpLink ): # Make sure that this result actually has a video link = tmpLink[0].attrib['href'] if urlType == 'bigscreen': link = link.replace('/iplayer/', '/iplayer/bigscreen/') elif urlType == 'bbcweb': pass # Link does not need adjustments else: if len(mediaFilter(itemData)): link = link.replace( 'http://www.bbc.co.uk/iplayer/episode/', '') index = link.find('/') link = link[:index] link = self.processVideoUrl(link) etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}customhtml" ).text = 'true' else: pass # Radio media cannot be played within the embedded weg page else: continue # Convert the pubDate "2010-03-22T07:56:21Z" to a MNV pubdate format pubdate = creationDate(itemData) if len(pubdate): pubdate = pubdate[0].text pubdate = time.strptime(pubdate, '%Y-%m-%dT%H:%M:%SZ') pubdate = time.strftime(self.common.pubDateFormat, pubdate) else: pubdate = datetime.datetime.now().strftime( self.common.pubDateFormat) # Extract and massage data also insert data into a new item element bbciplayerItem.find( 'title').text = self.common.massageText( titleFilter(itemData)[0].text.strip()) bbciplayerItem.find('author').text = itemAuthor bbciplayerItem.find('pubDate').text = pubdate description = etree.HTML( etree.tostring(descFilter1(itemData)[0], method="text", encoding=str).strip()) description = etree.tostring(descFilter2(description)[1], method="text", encoding=str).strip() bbciplayerItem.find( 'description').text = self.common.massageText( description) bbciplayerItem.find('link').text = link itemDwnLink(bbciplayerItem)[0].attrib['url'] = link try: itemThumbNail(bbciplayerItem)[0].attrib[ 'url'] = self.common.ampReplace( itemThumbNail(itemData)[0].attrib['url']) except IndexError: pass itemLanguage( bbciplayerItem)[0].attrib['lang'] = channelLanguage # Videos are only viewable in the UK so add a country indicator if this is a video countCode = self.setCountry(itemData) if countCode: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}country" ).text = countCode s_e = self.getSeasonEpisode( bbciplayerItem.find('title').text) if s_e[0]: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}season" ).text = s_e[0] if s_e[1]: etree.SubElement( bbciplayerItem, "{http://www.mythtv.org/wiki/MythNetvision_Grabber_Script_Format}episode" ).text = s_e[1] categoryElement.append(bbciplayerItem) if urlMax: # Check of the maximum items to processes has been met count += 1 if count > urlMax: break # Add the last directory processed if categoryElement is not None: if categoryElement.xpath('.//item') is not None: channelTree.append(categoryElement) # Check that there was at least some items if len(rssTree.xpath('//item')): # Output the MNV search results sys.stdout.write('<?xml version="1.0" encoding="UTF-8"?>\n') sys.stdout.write( etree.tostring(rssTree, encoding='UTF-8', pretty_print=True)) sys.exit(0)