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)
Example #14
0
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 "&amp;"
        '''
        text = self.textUtf8(text)
        return text.replace(u'&amp;',u'~~~~~').replace(u'&',u'&amp;').replace(u'~~~~~', u'&amp;')
    # 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)
Example #16
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)
Example #19
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': 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'
Example #20
0
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&regionCode=%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
Example #21
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    # 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'
Example #22
0
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 "&amp;"
        '''
        text = self.textUtf8(text)
        return text.replace('&amp;', '~~~~~').replace('&', '&amp;').replace(
            '~~~~~', '&amp;')

    # 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
Example #23
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.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
Example #25
0
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)