pkkid / python-plexapi

Python bindings for the Plex API.
BSD 3-Clause "New" or "Revised" License
1.15k stars 199 forks source link

Getting Episodes of a Season or Show returns Episodes with missing attributes #1007

Closed imranoftherings closed 2 years ago

imranoftherings commented 2 years ago

Describe the Bug

It seems that if you fetch episodes as part of a show or season, the Episode objects don't have all the relevant information (for e.g. Guids). When I debugged this issue, I found this issue is because of the XML returned by the Plex Media Server.

It boils down to the API call /library/metadata/<show or season ratingkey>/allLeaves. This API call returns partial Episode details and hence the object itself doesn't have many key relevant information missing

Code Snippets

Use the following function call to return episodes of a season or show
library.section(plex_tv_lib).get(show.title).episodes()

Expected Behavior

Expect to see all of the Episode attributes returned

    Video._loadData(self, data)
    Playable._loadData(self, data)
    self._seasonNumber = None  # cached season number
    self.audienceRating = utils.cast(float, data.attrib.get('audienceRating'))
    self.audienceRatingImage = data.attrib.get('audienceRatingImage')
    self.chapters = self.findItems(data, media.Chapter)
    self.chapterSource = data.attrib.get('chapterSource')
    self.collections = self.findItems(data, media.Collection)
    self.contentRating = data.attrib.get('contentRating')
    self.directors = self.findItems(data, media.Director)
    self.duration = utils.cast(int, data.attrib.get('duration'))
    self.grandparentArt = data.attrib.get('grandparentArt')
    self.grandparentGuid = data.attrib.get('grandparentGuid')
    self.grandparentKey = data.attrib.get('grandparentKey')
    self.grandparentRatingKey = utils.cast(int, data.attrib.get('grandparentRatingKey'))
    self.grandparentTheme = data.attrib.get('grandparentTheme')
    self.grandparentThumb = data.attrib.get('grandparentThumb')
    self.grandparentTitle = data.attrib.get('grandparentTitle')
    self.guids = self.findItems(data, media.Guid)
    self.index = utils.cast(int, data.attrib.get('index'))
    self.labels = self.findItems(data, media.Label)
    self.markers = self.findItems(data, media.Marker)
    self.media = self.findItems(data, media.Media)
    self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
    self.parentGuid = data.attrib.get('parentGuid')
    self.parentIndex = utils.cast(int, data.attrib.get('parentIndex'))
    self.parentKey = data.attrib.get('parentKey')
    self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey'))
    self.parentThumb = data.attrib.get('parentThumb')
    self.parentTitle = data.attrib.get('parentTitle')
    self.parentYear = utils.cast(int, data.attrib.get('parentYear'))
    self.producers = self.findItems(data, media.Producer)
    self.rating = utils.cast(float, data.attrib.get('rating'))
    self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
    self.roles = self.findItems(data, media.Role)
    self.skipParent = utils.cast(bool, data.attrib.get('skipParent', '0'))
    self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
    self.writers = self.findItems(data, media.Writer)
    self.year = utils.cast(int, data.attrib.get('year'))

Additional Context

I modified the episodes method of Show and Season object to the following, to individually query metdata of each episode and copy it over. This obviously is quite expensive depending on the total number of episodes returned by the original query, but I am not familiar enough with the plex rest api to know whether there is an allLeaves alternate which will return well hydrated Episode information

    def episodes(self, **kwargs):
        """ Returns a list of :class:`~plexapi.video.Episode` objects in the show. """
        key = f'{self.key}/allLeaves'
        print("key:", key)
        episodes = self.fetchItems(key, Episode, **kwargs)
        for e in episodes:
          episode = self.fetchItem(e.ratingKey, Episode)
          e.guids = episode.guids
          ...
          ...
        return episodes

Episode XML as part of the allLeaves query

<Video ratingKey="50856" key="/library/metadata/50856" parentRatingKey="50855" grandparentRatingKey="50490" guid="plex://episode/5d9c12b902391c001f5fa3fb" studio="Warner Bros. Television" type="episode" title="Pilot" grandparentKey="/library/metadata/50490" parentKey="/library/metadata/50855" grandparentTitle="The 100" parentTitle="Season 1" contentRating="TV-14" summary="97 years after a nuclear war, human kind is living in space. 100 juvenile delinquents are sent down to Earth to see if the planet is habitable." index="1" parentIndex="1" audienceRating="7.4" viewCount="1" lastViewedAt="1474750793" parentYear="2014" thumb="/library/metadata/50856/thumb/1632507116" art="/library/metadata/50490/art/1469648223" parentThumb="/library/metadata/50855/thumb/1632507114" grandparentThumb="/library/metadata/50490/thumb/1469648223" grandparentArt="/library/metadata/50490/art/1469648223" grandparentTheme="/library/metadata/50490/theme/1469648223" duration="2476478" originallyAvailableAt="2014-03-19" addedAt="1469648223" updatedAt="1632507116" audienceRatingImage="thetvdb://image.rating" chapterSource="media">
<Media id="71309" duration="2476478" bitrate="11360" width="1920" height="1080" aspectRatio="1.78" audioChannels="6" audioCodec="dca" videoCodec="h264" videoResolution="1080" container="mkv" videoFrameRate="24p" audioProfile="dts" videoProfile="high">
<Part id="216855" key="/library/parts/216855/1469506462/file.mkv" duration="2476478" file="..." size="3517310304" audioProfile="dts" container="mkv" videoProfile="high"/>
</Media>
<Director tag="Bharat Nalluri"/>
<Writer tag="Jason Rothenberg"/>
<Role tag="Terry Chen"/>
<Role tag="Kelly Hu"/>
<Role tag="Aaron Miko"/>
</Video>

Episode XML as a direct Episode metdata query

<Video ratingKey="50856" key="/library/metadata/50856" parentRatingKey="50855" grandparentRatingKey="50490" guid="plex://episode/5d9c12b902391c001f5fa3fb" parentGuid="plex://season/602e6816fdd281002cdf6852" grandparentGuid="plex://show/5d9c086d705e7a001e6dc723" type="episode" title="Pilot" grandparentKey="/library/metadata/50490" parentKey="/library/metadata/50855" librarySectionTitle="TV Shows" librarySectionID="2" librarySectionKey="/library/sections/2" grandparentTitle="The 100" parentTitle="Season 1" contentRating="TV-14" summary="97 years after a nuclear war, human kind is living in space. 100 juvenile delinquents are sent down to Earth to see if the planet is habitable." index="1" parentIndex="1" audienceRating="7.4" viewCount="1" lastViewedAt="1474750793" parentYear="2014" thumb="/library/metadata/50856/thumb/1632507116" art="/library/metadata/50490/art/1632507112" parentThumb="/library/metadata/50855/thumb/1632507114" grandparentThumb="/library/metadata/50490/thumb/1632507112" grandparentArt="/library/metadata/50490/art/1632507112" grandparentTheme="/library/metadata/50490/theme/1632507112" duration="2476478" originallyAvailableAt="2014-03-19" addedAt="1469648223" updatedAt="1632507116" audienceRatingImage="thetvdb://image.rating" chapterSource="media">
<Media id="71309" duration="2476478" bitrate="11360" width="1920" height="1080" aspectRatio="1.78" audioChannels="6" audioCodec="dca" videoCodec="h264" videoResolution="1080" container="mkv" videoFrameRate="24p" audioProfile="dts" videoProfile="high">
<Part accessible="1" exists="1" id="216855" key="/library/parts/216855/1469506462/file.mkv" duration="2476478" file="..." size="3517310304" audioProfile="dts" container="mkv" deepAnalysisVersion="6" requiredBandwidths="20597,17390,15016,13626,12661,12575,12575,12575" videoProfile="high">
<Stream id="495581" streamType="1" default="1" codec="h264" index="0" bitrate="9851" language="English" languageTag="en" languageCode="eng" bitDepth="8" chromaLocation="left" chromaSubsampling="4:2:0" codedHeight="1088" codedWidth="1920" frameRate="23.976" hasScalingMatrix="0" height="1080" level="41" profile="high" refFrames="4" requiredBandwidths="19088,15881,13506,12116,11152,11081,11081,11081" scanType="progressive" width="1920" displayTitle="1080p (H.264)" extendedDisplayTitle="1080p (H.264)"> </Stream>
<Stream id="495582" streamType="2" selected="1" default="1" codec="dca" index="1" channels="6" bitrate="1509" language="English" languageTag="en" languageCode="eng" audioChannelLayout="5.1(side)" bitDepth="24" profile="dts" requiredBandwidths="1507,1507,1507,1507,1507,1507,1507,1507" samplingRate="48000" displayTitle="English (DTS 5.1)" extendedDisplayTitle="English (DTS 5.1)"> </Stream>
</Part>
</Media>
<Director id="110593" filter="director=110593" tag="Bharat Nalluri"/>
<Writer id="12167" filter="writer=12167" tag="Jason Rothenberg"/>
<Producer id="110597" filter="producer=110597" tag="Jae Marchant"/>
<Guid id="imdb://tt2912494"/>
<Guid id="tmdb://972730"/>
<Guid id="tvdb://4543295"/>
<Role id="207969" filter="actor=207969" tag="Terry Chen" role="Commander Shumway" thumb="https://metadata-static.plex.tv/people/5d7768254de0ee001fcc84fe.jpg"/>
<Role id="218383" filter="actor=218383" tag="Kelly Hu" role="Callie 'Cece' Cartwig" thumb="https://metadata-static.plex.tv/9/people/9aea42eb970f07be06971b27c81ec1cf.jpg"/>
<Role id="110579" filter="actor=110579" tag="Aaron Miko" role="John Mbege" thumb="https://metadata-static.plex.tv/a/people/a949280aabc2f82c34d75eaed02c08d2.jpg"/>
<Role id="110580" filter="actor=110580" tag="Austin J.M. Ross" role="Tim Barlet (uncredited)"/>
<Role id="110581" filter="actor=110581" tag="Conrad Whitaker" role="Tranq Gun Guard"/>
<Role id="110582" filter="actor=110582" tag="Jojo Ahenkorah" role="Costa (uncredited)" thumb="https://metadata-static.plex.tv/people/5d7768c696b655001fdc16b2.jpg"/>
<Role id="110583" filter="actor=110583" tag="Genevieve Buechner" role="Fox" thumb="https://metadata-static.plex.tv/people/5d77683f103a2d001f56a3eb.jpg"/>
<Role id="110584" filter="actor=110584" tag="Reese Alexander" role="Guard #1" thumb="https://metadata-static.plex.tv/people/5d77682d8718ba001e312f89.jpg"/>
<Role id="230531" filter="actor=230531" tag="Kett Turton" role="Apprentice" thumb="https://image.tmdb.org/t/p/original/dsmW4TgzKUcLpWyE3hhZ8POmjS2.jpg"/>
<Role id="110585" filter="actor=110585" tag="Shaw Madson" role="Angry Parent #2" thumb="https://metadata-static.plex.tv/people/5d7768385af944001f1faa8d.jpg"/>
<Role id="110586" filter="actor=110586" tag="Tatiana Turner" role="Angry Parent #1" thumb="https://metadata-static.plex.tv/7/people/79d47bb72e42c995fc43322bf581506d.jpg"/>
<Role id="110587" filter="actor=110587" tag="Fred Cruz" role="Louis Montgomery (uncredited)"/>
<Role id="12198" filter="actor=12198" tag="Richard Harmon" role="John Murphy" thumb="https://metadata-static.plex.tv/0/people/05151900908cd11eb29e4d76ec974cef.jpg"/>
<Role id="213265" filter="actor=213265" tag="Eli Goree" role="Wells Jaha" thumb="https://metadata-static.plex.tv/a/people/a9229d416883c57799d5e1f4c9976cfd.jpg"/>
<Role id="110588" filter="actor=110588" tag="James Forrester" role="Juvenile Delinquent (uncredited)"/>
<Role id="247576" filter="actor=247576" tag="Alessandro Juliani" role="Jacapo Sinclair" thumb="https://metadata-static.plex.tv/people/5d776833103a2d001f567561.jpg"/>
<Role id="110589" filter="actor=110589" tag="Sachin Sahel" role="Dr. Eric Jackson" thumb="https://metadata-static.plex.tv/people/5d776903f617c9002015bf85.jpg"/>
<Chapter id="67" filter="thumb=67" index="1" startTimeOffset="0" endTimeOffset="162662" thumb="/library/media/71309/chapterImages/1"/>
<Chapter id="67" filter="thumb=67" index="2" startTimeOffset="162662" endTimeOffset="609108" thumb="/library/media/71309/chapterImages/2"/>
<Chapter id="67" filter="thumb=67" index="3" startTimeOffset="609108" endTimeOffset="1067316" thumb="/library/media/71309/chapterImages/3"/>
<Chapter id="67" filter="thumb=67" index="4" startTimeOffset="1067316" endTimeOffset="1473180" thumb="/library/media/71309/chapterImages/4"/>
<Chapter id="67" filter="thumb=67" index="5" startTimeOffset="1473180" endTimeOffset="1812477" thumb="/library/media/71309/chapterImages/5"/>
<Chapter id="67" filter="thumb=67" index="6" startTimeOffset="1812477" endTimeOffset="2134090" thumb="/library/media/71309/chapterImages/6"/>
<Chapter id="67" filter="thumb=67" index="7" startTimeOffset="2134090" endTimeOffset="2447070" thumb="/library/media/71309/chapterImages/7"/>
<Chapter id="67" filter="thumb=67" index="8" startTimeOffset="2447070" endTimeOffset="2476480" thumb="/library/media/71309/chapterImages/8"/>
<Extras size="0"> </Extras>
<Related> </Related>
</Video>

Operating System and Version

Windows 10

Plex Media Server Version

1.51.1.3185-700af1eb

Python Version

3.10

PlexAPI Version

4.12.1

JonnyWong16 commented 2 years ago

This is intentional.

Lots of methods in PlexAPI return a PlexPartialObject. This is done to ensure the API calls remain speedy and attributes are only loaded when needed. Accessing a missing attribute should automatically reload the object with all attributes. Or you can explicitly call the reload() method yourself.

>> episodes = library.section(plex_tv_lib).get(show.title).episodes()

>> episodes[0].isPartialObject()
True
>> episodes[0].guids  # This should automatically reload the object when accessing a missing attribute
>> episodes[0].isPartialObject()
False

>> episodes[1].isPartialObject()
True
>> episodes[1].reload()  # Explicitly reload the object yourself
>> episodes[1].isPartialObject()
False
imranoftherings commented 2 years ago

Thank you, appreciate the response. This is perfect.