Ticket #11264: ttvdb_py_v025_and_higher_multiple_episodes.patch

File ttvdb_py_v025_and_higher_multiple_episodes.patch, 21.2 KB (added by Raymond Wagner, 8 years ago)
  • mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbVideo.xsl

    From d606669339fe6e2f4557b5f9d491c7fadfa53061 Mon Sep 17 00:00:00 2001
    From: "R.D. Vaughan" <r.d.vaughan@rogers.com>
    Date: Thu, 29 Nov 2012 13:32:29 -0500
    Subject: [PATCH 42/42] Log: Changed ttvdb.py to return each episode's
     metadata for a TV series which matches the passed
     episode name when the "-N" option is used.
    
    ---
     .../python/MythTV/ttvdb/XSLT/tvdbVideo.xsl         |   46 +++----
     mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py    |   31 +++--
     .../programs/scripts/metadata/Television/ttvdb.py  |  133 ++++++++++++++------
     3 files changed, 141 insertions(+), 69 deletions(-)
    
    diff --git a/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbVideo.xsl b/mythtv/bindings/python/MythTV/ttvdb/XSLT/tvdbVideo.xsl
    index 3eb3c8e..4f2ab79 100755
    a b  
    1818    <xsl:template match="/">
    1919        <xsl:if test="//Data/Series">
    2020            <metadata>
    21                 <xsl:call-template name='tvdbVideoData'/>
     21                <xsl:call-template name="tvdbEpisodes"/>
    2222            </metadata>
    2323        </xsl:if>
    2424    </xsl:template>
    2525
    26     <xsl:template name="tvdbVideoData">
    27         <xsl:for-each select="//Data/Series">
     26    <xsl:template name="tvdbEpisodes">
     27        <xsl:for-each select="//requestDetails">
     28            <xsl:variable name="episode_cnt" select="tvdbXpath:episode_counter()" />
    2829            <item>
    29                 <title><xsl:value-of select="normalize-space(SeriesName)"/></title>
     30                <title><xsl:value-of select="normalize-space(//Data/Series/SeriesName)"/></title>
    3031                <xsl:if test="tvdbXpath:getValue(//requestDetails, //Data, 'subtitle') != ''">
    3132                    <subtitle><xsl:value-of select="normalize-space(tvdbXpath:getResult())"/></subtitle>
    3233                </xsl:if>
    33                 <language><xsl:value-of select="normalize-space(Language)"/></language>
     34                <language><xsl:value-of select="normalize-space(//Data/Series/Language)"/></language>
    3435                <xsl:if test="tvdbXpath:getValue(//requestDetails, //Data, 'description') != ''">
    3536                    <description><xsl:value-of select="normalize-space(tvdbXpath:htmlToString(tvdbXpath:getResult()))"/></description>
    3637                </xsl:if>
    37                 <season><xsl:value-of select="normalize-space(//requestDetails/@season)"/></season>
    38                 <episode><xsl:value-of select="normalize-space(//requestDetails/@episode)"/></episode>
    39                 <xsl:if test="./ContentRating/text() != ''">
     38                <season><xsl:value-of select="normalize-space(./@season)"/></season>
     39                <episode><xsl:value-of select="normalize-space(./@episode)"/></episode>
     40                <xsl:if test="//Data/Series/ContentRating/text() != ''">
    4041                    <certifications>
    41                         <xsl:for-each select=".//ContentRating">
     42                        <xsl:for-each select="//Data/Series//ContentRating">
    4243                            <xsl:element name="certification">
    4344                                <xsl:attribute name="locale">us</xsl:attribute>
    4445                                <xsl:attribute name="name"><xsl:value-of select="normalize-space(.)"/></xsl:attribute>
     
    4647                        </xsl:for-each>
    4748                    </certifications>
    4849                </xsl:if>
    49                 <xsl:if test="./Genre/text() != ''">
     50                <xsl:if test="//Data/Series/Genre/text() != ''">
    5051                    <categories>
    51                         <xsl:for-each select="tvdbXpath:stringToList(string(./Genre))">
     52                        <xsl:for-each select="tvdbXpath:stringToList(string(//Data/Series/Genre))">
    5253                            <xsl:element name="category">
    5354                                <xsl:attribute name="type">genre</xsl:attribute>
    5455                                <xsl:attribute name="name"><xsl:value-of select="normalize-space(.)"/></xsl:attribute>
     
    5657                        </xsl:for-each>
    5758                    </categories>
    5859                </xsl:if>
    59                 <xsl:if test="./Network/text() != ''">
     60                <xsl:if test="//Data/Series/Network/text() != ''">
    6061                    <studios>
    61                         <xsl:for-each select="./Network">
     62                        <xsl:for-each select="//Data/Series/Network">
    6263                            <xsl:element name="studio">
    6364                                <xsl:attribute name="name"><xsl:value-of select="normalize-space(.)"/></xsl:attribute>
    6465                            </xsl:element>
    6566                        </xsl:for-each>
    6667                    </studios>
    6768                </xsl:if>
    68                 <xsl:if test="./Runtime/text() != ''">
    69                     <runtime><xsl:value-of select="normalize-space(Runtime)"/></runtime>
     69                <xsl:if test="//Data/Series/Runtime/text() != ''">
     70                    <runtime><xsl:value-of select="normalize-space(//Data/Series/Runtime)"/></runtime>
    7071                </xsl:if>
    71                 <inetref><xsl:value-of select="normalize-space(id)"/></inetref>
    72                 <collectionref><xsl:value-of select="normalize-space(id)"/></collectionref>
    73                 <xsl:if test="./IMDB_ID/text() != '' and tvdbXpath:getValue(//requestDetails, //Data, 'IMDB') = ''">
    74                     <imdb><xsl:value-of select="normalize-space(substring-after(string(IMDB_ID), 'tt'))"/></imdb>
     72                <inetref><xsl:value-of select="normalize-space(//Data/Series/id)"/></inetref>
     73                <collectionref><xsl:value-of select="normalize-space(//Data/Series/id)"/></collectionref>
     74                <xsl:if test="//Data/Series/IMDB_ID/text() != '' and tvdbXpath:getValue(//requestDetails, //Data, 'IMDB') = ''">
     75                    <imdb><xsl:value-of select="normalize-space(substring-after(string(//Data/Series/IMDB_ID), 'tt'))"/></imdb>
    7576                </xsl:if>
    76                 <xsl:if test="./zap2it_id/text() != ''">
    77                     <tmsref><xsl:value-of select="normalize-space(zap2it_id)"/></tmsref>
     77                <xsl:if test="//Data/Series/zap2it_id/text() != ''">
     78                    <tmsref><xsl:value-of select="normalize-space(//Data/Series/zap2it_id)"/></tmsref>
    7879                </xsl:if>
    7980                <xsl:for-each select="tvdbXpath:getValue(//requestDetails, //Data, 'allEpisodes', 'allresults')">
    8081                    <xsl:if test="./IMDB_ID/text() != ''">
    81                         <imdb><xsl:value-of select="normalize-space(substring-after(string(IMDB_ID), 'tt'))"/></imdb>
     82                        <imdb><xsl:value-of select="normalize-space(substring-after(string(./IMDB_ID), 'tt'))"/></imdb>
    8283                    </xsl:if>
    8384                    <xsl:if test="./Rating/text() != ''">
    8485                        <userrating><xsl:value-of select="normalize-space(./Rating)"/></userrating>
     
    186187            </item>
    187188        </xsl:for-each>
    188189    </xsl:template>
     190
    189191</xsl:stylesheet>
  • mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py

    diff --git a/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py b/mythtv/bindings/python/MythTV/ttvdb/tvdbXslt.py
    index ff5dc68..670d9bf 100755
    a b See this link for the specifications: 
    2121http://www.mythtv.org/wiki/MythTV_Universal_Metadata_Format
    2222'''
    2323
    24 __version__="v0.1.2"
     24__version__="v0.1.3"
    2525# 0.1.0 Initial development
    2626# 0.1.1 Converted categories, genre, ... etc text characters to be XML compliant
    2727# 0.1.2 Performance improvements by removing complex data searches from the XLST stylesheet
     28# 0.1.3 Change to handle multiple episodes with matching names
    2829
    2930
    3031# Specify the class names that have XPath extention functions
    class xpathFunctions(object): 
    9394    """Functions specific extending XPath
    9495    """
    9596    def __init__(self):
     97        #
     98        self.episode_cnt = None
     99        #
    96100        self.filters = {
    97101            'fanart': [u'//Banner[BannerType/text()="%(type)s" and Language/text()="%(language)s"]', u'//Banner[BannerType/text()="%(type)s" and Language/text()="en"]', u'//Banner[BannerType/text()="%(type)s"]'],
    98102            'poster': [u'//Banner[BannerType/text()="season" and Language/text()="%(language)s" and Season/text()="%(season)s" and BannerType2/text()="season"]', u'//Banner[BannerType/text()="%(type)s" and Language/text()="%(language)s"]', u'//Banner[BannerType/text()="season" and Language/text()="en" and Season/text()="%(season)s" and BannerType2/text()="season"]', u'//Banner[BannerType/text()="season" and Season/text()="%(season)s" and BannerType2/text()="season"]', u'//Banner[BannerType/text()="%(type)s" and Language/text()="en"]', u'//Banner[BannerType/text()="%(type)s"]'],
    class xpathFunctions(object): 
    124128            'imageElements': self.imageElements,
    125129            'getValue': self.getValue,
    126130            'getResult': self.getResult,
     131            'episode_counter': self.episode_counter,
    127132            }
    128133        return
    129134    # end buildFuncDict()
    class xpathFunctions(object): 
    179184        if not len(args[0]):
    180185            return []
    181186        elementList = []
    182 
    183187        parmDict = {
    184188            'type': args[1],
    185             'language': args[2][0].attrib['lang'],
    186             'season': args[2][0].attrib['season'],
    187             'episode': args[2][0].attrib['episode'],
     189            'language': args[2][self.episode_cnt].attrib['lang'],
     190            'season': args[2][self.episode_cnt].attrib['season'],
     191            'episode': args[2][self.episode_cnt].attrib['episode'],
    188192            }
    189193        filters = []
    190194        for index in range(len(self.filters[args[1]])):
    class xpathFunctions(object): 
    276280        else:
    277281            allValues = True
    278282        parmDict = {
    279             'season': args[0][0].attrib['season'],
    280             'episode': args[0][0].attrib['episode'],
     283            'season': args[0][self.episode_cnt].attrib['season'],
     284            'episode': args[0][self.episode_cnt].attrib['episode'],
    281285            }
    282286        xpathFilter = etree.XPath(self.dataFilters[args[2]] % parmDict)
    283287        results = xpathFilter(args[1][0])
    284 
    285288        # Sometimes all the results are required
    286289        if allValues == True:
    287290            self.persistentResult = results
    class xpathFunctions(object): 
    300303        return self.persistentResult
    301304    # end getResult()
    302305
     306    def episode_counter(self, *args):
     307        ''' Add one or set the index value for the episode array
     308        return current index value
     309        '''
     310        if self.episode_cnt == None:
     311            self.episode_cnt = 0
     312        else:
     313            self.episode_cnt += 1
     314        #
     315        return self.episode_cnt
     316    # end episode_counter()
     317
    303318######################################################################################################
    304319#
    305320# End of XPath extension functions
  • mythtv/programs/scripts/metadata/Television/ttvdb.py

    diff --git a/mythtv/programs/scripts/metadata/Television/ttvdb.py b/mythtv/programs/scripts/metadata/Television/ttvdb.py
    index 77517c0..3da5a15 100755
    a b  
    3737#-------------------------------------
    3838__title__ ="TheTVDB.com";
    3939__author__="R.D.Vaughan"
    40 __version__="1.1.5"
     40__version__="1.1.6"
    4141# Version .1    Initial development
    4242# Version .2    Add an option to get season and episode numbers from ep name
    4343# Version .3    Cleaned up the documentation and added a usage display option
    __version__="1.1.5" 
    136136# Version 1.1.4 Add test mode (replaces --toprated)
    137137# Version 1.1.5 Add the -C (collection option) with corresponding XML output
    138138#               and add a <collectionref> XML tag to Search and Query XML output
     139# Version 1.1.6 Fixed -N season and episode returned values when there is a
     140#               multi-part episode with the same name e.g. "Haven" "Magic Hour".
     141#               Now both the episodes details are returned. MythTV can use the
     142#               original air date to match with the releasedate to determine
     143#               which episode is the correct one to select.
     144#
    139145
    140146usage_txt='''
    141147Usage: ttvdb.py usage: ttvdb -hdruviomMPFBDSC [parameters]
    http://www.thetvdb.com/banners/blank/70382.jpg 
    213219> ttvdb -Bl en "Lost"
    214220Banner:http://www.thetvdb.com/banners/graphical/73739-g4.jpg,http://www.thetvdb.com/banners/graphical/73739-g.jpg,http://www.thetvdb.com/banners/graphical/73739-g6.jpg,http://www.thetvdb.com/banners/graphical/73739-g8.jpg,http://www.thetvdb.com/banners/graphical/73739-g3.jpg,http://www.thetvdb.com/banners/graphical/73739-g7.jpg,http://www.thetvdb.com/banners/graphical/73739-g5.jpg,http://www.thetvdb.com/banners/graphical/24313-g2.jpg,http://www.thetvdb.com/banners/graphical/24313-g.jpg,http://www.thetvdb.com/banners/graphical/73739-g10.jpg,http://www.thetvdb.com/banners/graphical/73739-g2.jpg
    215221
    216 (Return a season and episode numbers using the override file to identify the series as the US version)
    217 > ttvdb -N --configure="/home/user/.tvdb/tvdb.conf" "Eleventh Hour" "H2O"
     222(Return multiple episodes when the episode name returns multiple values)
     223> ttvdb.py -N "Haven" "Magic Hour"
    218224<?xml version='1.0' encoding='UTF-8'?>
    219225<metadata>
    220226  <item>
    221     <title>Eleventh Hour (US)</title>
    222     <subtitle>H2O</subtitle>
     227    <title>Haven</title>
     228    <subtitle>Magic Hour (2)</subtitle>
    223229    <language>en</language>
    224     <description>An epidemic of sudden, violent outbursts by law-abiding citizens draws Dr. Jacob Hood to a quiet Texas community to investigate - but he soon succumbs to the same erratic behavior.</description>
    225     <season>1</season>
    226     <episode>10</episode>
    227 ...
    228       <image type="fanart" url="http://www.thetvdb.com/banners/fanart/original/83066-4.jpg" thumb="http://www.thetvdb.com/banners/_cache/fanart/original/83066-4.jpg" width="1280" height="720"/>
    229       <image type="banner" url="http://www.thetvdb.com/banners/graphical/83066-g.jpg" thumb="http://www.thetvdb.com/banners/_cache/graphical/83066-g.jpg"/>
    230     </images>
     230    <description>Audrey and Duke return from Colorado; Audrey, Duke and Tommy tries to find the woman with the resurrection touch.</description>
     231    <season>3</season>
     232    <episode>8</episode>
     233    <certifications>
     234      <certification locale="us" name="TV-PG"/>
     235    </certifications>
     236    <categories>
     237      <category type="genre" name="Drama"/>
     238      <category type="genre" name="Science-Fiction"/>
     239    </categories>
     240    <studios>
     241      <studio name="Syfy"/>
     242    </studios>
     243    <runtime>60</runtime>
     244    <inetref>158661</inetref>
     245    <collectionref>158661</collectionref>
     246    <imdb>1519931</imdb>
     247    <tmsref>SH01281487</tmsref>
     248    <userrating>7.9</userrating>
     249    <year>2012</year>
     250    <releasedate>2012-11-09</releasedate>
     251    ...
     252  </item>
     253  <item>
     254    <title>Haven</title>
     255    <subtitle>Magic Hour (1)</subtitle>
     256    <language>en</language>
     257    <description>Audrey and Duke follow a lead on the Colorado Kid; Nathan and Tommy investigate a series of resurrections.</description>
     258    <season>3</season>
     259    <episode>7</episode>
     260    <certifications>
     261      <certification locale="us" name="TV-PG"/>
     262    </certifications>
     263    <categories>
     264      <category type="genre" name="Drama"/>
     265      <category type="genre" name="Science-Fiction"/>
     266    </categories>
     267    <studios>
     268      <studio name="Syfy"/>
     269    </studios>
     270    <runtime>60</runtime>
     271    <inetref>158661</inetref>
     272    <collectionref>158661</collectionref>
     273    <imdb>1519931</imdb>
     274    <tmsref>SH01281487</tmsref>
     275    <userrating>7.9</userrating>
     276    <year>2012</year>
     277    <releasedate>2012-11-02</releasedate>
     278    <lastupdated>Thu, 15 Nov 2012 10:59:08 GMT</lastupdated>
     279    <homepage>http://thetvdb.com/?tab=episode&amp;seriesid=158661&amp;seasonid=491933&amp;id=4361567</homepage>
     280    ...
    231281  </item>
    232282</metadata>
    233283
    def Getseries_episode_numbers(t, opts, series_season_ep): 
    926976        ep_name=series_season_ep[1] # Leave the episode name alone
    927977
    928978    season_ep_num=search_for_series(t, series_name).fuzzysearch(ep_name, 'episodename')
    929     if len(season_ep_num) != 0:
    930         for episode in sorted(season_ep_num, key=lambda ep: _episode_sort(ep), reverse=True):
    931 #            if episode.distance == 0: # exact match
    932                 if xmlFlag:
    933                     displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
    934                     sys.exit(0)
    935                 print season_and_episode_num.replace('\\n', '\n') % (int(episode['seasonnumber']), int(episode['episodenumber']))
    936 #            elif (episode['episodename'].lower()).startswith(ep_name.lower()):
    937 #                if len(episode['episodename']) > (len(ep_name)+1):
    938 #                    if episode['episodename'][len(ep_name):len(ep_name)+2] != ' (':
    939 #                        continue # Skip episodes the are not part of a set of (1), (2) ... etc
    940 #                    if xmlFlag:
    941 #                        displaySeriesXML(t, [series_name, episode['seasonnumber'], episode['episodenumber']])
    942 #                        sys.exit(0)
    943 #                    print season_and_episode_num.replace('\\n', '\n') % (int(episode['seasonnumber']), int(episode['episodenumber']))
     979    all_matching_episodes = sorted(season_ep_num, key=lambda ep: _episode_sort(ep), reverse=True)
     980    all_matching_episodes_cnt = len(all_matching_episodes)
     981    #
     982    all_matching_episodes_array = []
     983    for episode in all_matching_episodes:
     984        all_matching_episodes_array.append([series_name,
     985                                            episode['seasonnumber'],
     986                                            episode['episodenumber']])
     987    if all_matching_episodes_array:
     988        if xmlFlag:
     989            displaySeriesXML(t, all_matching_episodes_array)
     990        else:
     991            for episode in all_matching_episodes:
     992                print season_and_episode_num.replace('\\n', '\n') % (
     993                                        int(episode['seasonnumber']),
     994                                        int(episode['episodenumber']))
     995    #
     996    sys.exit(0)
    944997# end Getseries_episode_numbers
    945998
    946999# Set up a custom interface to get all series matching a partial series name
    def displaySearchXML(tvdb_api): 
    10721125    sys.exit(0)
    10731126# end displaySearchXML()
    10741127
    1075 def displaySeriesXML(tvdb_api, series_season_ep):
     1128def displaySeriesXML(tvdb_api, series_season_ep_array):
    10761129    '''Using a XSLT style sheet translate TVDB Series data results into the
    10771130    MythTV Universal Query format
    10781131    return nothing
    def displaySeriesXML(tvdb_api, series_season_ep): 
    10801133    global xslt, tvdbXpath
    10811134    allDataElement = etree.XML(u'<allData></allData>')
    10821135    requestDetails = etree.XML(u'<requestDetails></requestDetails>')
    1083     requestDetails.attrib['lang'] = xslt.language
    1084     requestDetails.attrib['series'] = series_season_ep[0]
    1085     requestDetails.attrib['season'] = series_season_ep[1]
    1086     requestDetails.attrib['episode'] = series_season_ep[2]
    1087     allDataElement.append(requestDetails)
    1088 
     1136    for series_season_ep in series_season_ep_array:
     1137        requestDetails_copy = deepcopy(requestDetails)
     1138        requestDetails_copy.attrib['lang'] = xslt.language
     1139        requestDetails_copy.attrib['series'] = series_season_ep[0]
     1140        requestDetails_copy.attrib['season'] = series_season_ep[1]
     1141        requestDetails_copy.attrib['episode'] = series_season_ep[2]
     1142        allDataElement.append(requestDetails_copy)
     1143    #
    10891144    # Combine the various XML inputs into a single XML element and send to the XSLT stylesheet
    10901145    if tvdb_api.epInfoTree != None:
    10911146        allDataElement.append(tvdb_api.epInfoTree)
    def displaySeriesXML(tvdb_api, series_season_ep): 
    10991154        allDataElement.append(tvdb_api.imagesInfoTree)
    11001155    else:
    11011156        allDataElement.append(etree.XML(u'<Banners></Banners>'))
    1102 
     1157    #
    11031158    tvdbQueryXslt = etree.XSLT(etree.parse(u'%s%s' % (tvdb_api.baseXsltDir, u'tvdbVideo.xsl')))
    11041159    items = tvdbQueryXslt(allDataElement)
    11051160    if items.getroot() != None:
    def main(): 
    12281283        if len(series_season_ep) < 2:
    12291284            parser.error("! An Episode name must be included")
    12301285            sys.exit(1)
    1231         if len(series_season_ep) == 3:
     1286        elif len(series_season_ep) == 3 and not opts.numbers:
    12321287            season_and_episode_num = series_season_ep[2] # Override default output format
    1233 
     1288    #
    12341289    if opts.screenshot:
    12351290        if len(series_season_ep) > 1:
    12361291            if not _can_int(series_season_ep[1]):
    def main(): 
    14301485                print u"Season and Episode numbers required."
    14311486            else:
    14321487                if opts.xml:
    1433                     displaySeriesXML(t, series_season_ep)
     1488                    displaySeriesXML(t, [series_season_ep])
    14341489                    sys.exit(0)
    14351490                Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
    14361491        else:
    14371492            if opts.xml and len(series_season_ep) == 3:
    1438                 displaySeriesXML(t, series_season_ep)
     1493                displaySeriesXML(t, [series_season_ep])
    14391494                sys.exit(0)
    14401495            Getseries_episode_data(t, opts, series_season_ep, language=opts.language)
    14411496