diff --git a/lib/plexapi/audio.py b/lib/plexapi/audio.py index b70aa93c..61890a23 100644 --- a/lib/plexapi/audio.py +++ b/lib/plexapi/audio.py @@ -36,6 +36,8 @@ class Audio(PlexPartialObject): self.key = data.attrib.get('key') self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt')) self.librarySectionID = data.attrib.get('librarySectionID') + self.librarySectionKey = data.attrib.get('librarySectionKey') + self.librarySectionTitle = data.attrib.get('librarySectionTitle') self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.summary = data.attrib.get('summary') self.thumb = data.attrib.get('thumb') @@ -120,17 +122,26 @@ class Artist(Audio): TAG = 'Directory' TYPE = 'artist' + _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' + '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' + '&includeMarkers=1&includeConcerts=1&includePreferences=1' + '&indcludeBandwidths=1&includeLoudnessRamps=1') + def _loadData(self, data): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) + self._details_key = self.key + self._include self.art = data.attrib.get('art') self.guid = data.attrib.get('guid') self.key = self.key.replace('/children', '') # FIX_BUG_50 self.locations = self.listAttrs(data, 'path', etag='Location') self.countries = self.findItems(data, media.Country) + self.fields = self.findItems(data, media.Field) self.genres = self.findItems(data, media.Genre) self.similar = self.findItems(data, media.Similar) self.collections = self.findItems(data, media.Collection) + self.moods = self.findItems(data, media.Mood) + self.styles = self.findItems(data, media.Style) def __iter__(self): for album in self.albums(): @@ -217,17 +228,26 @@ class Album(Audio): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) self.art = data.attrib.get('art') + self.guid = data.attrib.get('guid') + self.leafCount = utils.cast(int, data.attrib.get('leafCount')) + self.loudnessAnalysisVersion = utils.cast(int, data.attrib.get('loudnessAnalysisVersion')) self.key = self.key.replace('/children', '') # fixes bug #50 self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d') + self.parentGuid = data.attrib.get('parentGuid') self.parentKey = data.attrib.get('parentKey') self.parentRatingKey = data.attrib.get('parentRatingKey') self.parentThumb = data.attrib.get('parentThumb') self.parentTitle = data.attrib.get('parentTitle') + self.rating = utils.cast(float, data.attrib.get('rating')) self.studio = data.attrib.get('studio') + self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) self.year = utils.cast(int, data.attrib.get('year')) - self.genres = self.findItems(data, media.Genre) self.collections = self.findItems(data, media.Collection) + self.fields = self.findItems(data, media.Field) + self.genres = self.findItems(data, media.Genre) self.labels = self.findItems(data, media.Label) + self.moods = self.findItems(data, media.Mood) + self.styles = self.findItems(data, media.Style) def track(self, title): """ Returns the :class:`~plexapi.audio.Track` that matches the specified title. @@ -312,20 +332,28 @@ class Track(Audio, Playable): TAG = 'Track' TYPE = 'track' + _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' + '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' + '&includeMarkers=1&includeConcerts=1&includePreferences=1' + '&indcludeBandwidths=1&includeLoudnessRamps=1') + def _loadData(self, data): """ Load attribute values from Plex XML response. """ Audio._loadData(self, data) Playable._loadData(self, data) + self._details_key = self.key + self._include self.art = data.attrib.get('art') self.chapterSource = data.attrib.get('chapterSource') 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 = data.attrib.get('grandparentRatingKey') self.grandparentThumb = data.attrib.get('grandparentThumb') self.grandparentTitle = data.attrib.get('grandparentTitle') self.guid = data.attrib.get('guid') self.originalTitle = data.attrib.get('originalTitle') + self.parentGuid = data.attrib.get('parentGuid') self.parentIndex = data.attrib.get('parentIndex') self.parentKey = data.attrib.get('parentKey') self.parentRatingKey = data.attrib.get('parentRatingKey') @@ -351,6 +379,13 @@ class Track(Audio, Playable): """ Return this track's :class:`~plexapi.audio.Artist`. """ return self.fetchItem(self.grandparentKey) + @property + def locations(self): + """ This does not exist in plex xml response but is added to have a common + interface to get the location of the Artist + """ + return [part.file for part in self.iterParts() if part] + def _defaultSyncTitle(self): """ Returns str, default title for a new syncItem. """ return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title) diff --git a/lib/plexapi/library.py b/lib/plexapi/library.py index e7798459..335534f7 100644 --- a/lib/plexapi/library.py +++ b/lib/plexapi/library.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from plexapi import X_PLEX_CONTAINER_SIZE, log, utils +from plexapi import X_PLEX_CONTAINER_SIZE, log, utils, media from plexapi.base import PlexObject from plexapi.compat import quote, quote_plus, unquote, urlencode from plexapi.exceptions import BadRequest, NotFound @@ -1092,9 +1092,15 @@ class Collections(PlexObject): def _loadData(self, data): self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self._details_key = "/library/metadata/%s%s" % (self.ratingKey, self._include) + self.contentRating = data.attrib.get('contentRating') + self.guid = data.attrib.get('guid') self.key = data.attrib.get('key') + self.librarySectionID = data.attrib.get('librarySectionID') + self.librarySectionKey = data.attrib.get('librarySectionKey') + self.librarySectionTitle = data.attrib.get('librarySectionTitle') self.type = data.attrib.get('type') self.title = data.attrib.get('title') + self.titleSort = data.attrib.get('titleSort') self.subtype = data.attrib.get('subtype') self.summary = data.attrib.get('summary') self.index = utils.cast(int, data.attrib.get('index')) @@ -1106,6 +1112,7 @@ class Collections(PlexObject): self.maxYear = utils.cast(int, data.attrib.get('maxYear')) self.collectionMode = data.attrib.get('collectionMode') self.collectionSort = data.attrib.get('collectionSort') + self.fields = self.findItems(data, media.Field) @property def children(self): diff --git a/lib/plexapi/media.py b/lib/plexapi/media.py index 50252e4f..17a3f274 100644 --- a/lib/plexapi/media.py +++ b/lib/plexapi/media.py @@ -5,7 +5,7 @@ import xml from plexapi import compat, log, settings, utils from plexapi.base import PlexObject from plexapi.exceptions import BadRequest -from plexapi.utils import cast +from plexapi.utils import cast, SEARCHTYPES @utils.registerPlexObject @@ -45,6 +45,7 @@ class Media(PlexObject): self.aspectRatio = cast(float, data.attrib.get('aspectRatio')) self.audioChannels = cast(int, data.attrib.get('audioChannels')) self.audioCodec = data.attrib.get('audioCodec') + self.audioProfile = data.attrib.get('videoProfile') self.bitrate = cast(int, data.attrib.get('bitrate')) self.container = data.attrib.get('container') self.duration = cast(int, data.attrib.get('duration')) @@ -60,6 +61,16 @@ class Media(PlexObject): self.videoResolution = data.attrib.get('videoResolution') self.width = cast(int, data.attrib.get('width')) self.parts = self.findItems(data, MediaPart) + self.proxyType = cast(int, data.attrib.get('proxyType')) + self.optimizedVersion = self.proxyType == SEARCHTYPES['optimizedVersion'] + + # For Photo only + self.aperture = data.attrib.get('aperture') + self.exposure = data.attrib.get('exposure') + self.iso = cast(int, data.attrib.get('iso')) + self.lens = data.attrib.get('lens') + self.make = data.attrib.get('make') + self.model = data.attrib.get('model') def delete(self): part = self._initpath + '/media/%s' % self.id @@ -96,26 +107,34 @@ class MediaPart(PlexObject): def _loadData(self, data): """ Load attribute values from Plex XML response. """ self._data = data + self.audioProfile = data.attrib.get('audioProfile') self.container = data.attrib.get('container') + self.deepAnalysisVersion = cast(int, data.attrib.get('deepAnalysisVersion')) self.duration = cast(int, data.attrib.get('duration')) self.file = data.attrib.get('file') + self.has64bitOffsets = cast(bool, data.attrib.get('has64bitOffsets')) + self.hasThumbnail = cast(bool, data.attrib.get('hasThumbnail')) self.id = cast(int, data.attrib.get('id')) self.indexes = data.attrib.get('indexes') self.key = data.attrib.get('key') self.size = cast(int, data.attrib.get('size')) self.decision = data.attrib.get('decision') self.optimizedForStreaming = cast(bool, data.attrib.get('optimizedForStreaming')) + self.requiredBandwidths = data.attrib.get('requiredBandwidths') self.syncItemId = cast(int, data.attrib.get('syncItemId')) self.syncState = data.attrib.get('syncState') self.videoProfile = data.attrib.get('videoProfile') self.streams = self._buildStreams(data) self.exists = cast(bool, data.attrib.get('exists')) self.accessible = cast(bool, data.attrib.get('accessible')) + + # For Photo only + self.orientation = cast(int, data.attrib.get('orientation')) def _buildStreams(self, data): streams = [] for elem in data: - for cls in (VideoStream, AudioStream, SubtitleStream): + for cls in (VideoStream, AudioStream, SubtitleStream, LyricStream): if elem.attrib.get('streamType') == str(cls.STREAMTYPE): streams.append(cls(self._server, elem, self._initpath)) return streams @@ -132,6 +151,10 @@ class MediaPart(PlexObject): """ Returns a list of :class:`~plexapi.media.SubtitleStream` objects in this MediaPart. """ return [stream for stream in self.streams if stream.streamType == SubtitleStream.STREAMTYPE] + def lyricStreams(self): + """ Returns a list of :class:`~plexapi.media.LyricStream` objects in this MediaPart. """ + return [stream for stream in self.streams if stream.streamType == LyricStream.STREAMTYPE] + def setDefaultAudioStream(self, stream): """ Set the default :class:`~plexapi.media.AudioStream` for this MediaPart. @@ -177,7 +200,8 @@ class MediaPartStream(PlexObject): languageCode (str): Ascii code for language (ex: eng, tha). selected (bool): True if this stream is selected. streamType (int): Stream type (1=:class:`~plexapi.media.VideoStream`, - 2=:class:`~plexapi.media.AudioStream`, 3=:class:`~plexapi.media.SubtitleStream`). + 2=:class:`~plexapi.media.AudioStream`, 3=:class:`~plexapi.media.SubtitleStream`, + 4=:class:`~plexapi.media.LyricStream`). type (int): Alias for streamType. """ @@ -186,18 +210,22 @@ class MediaPartStream(PlexObject): self._data = data self.codec = data.attrib.get('codec') self.codecID = data.attrib.get('codecID') + self.default = cast(bool, data.attrib.get('selected', '0')) + self.displayTitle = data.attrib.get('displayTitle') + self.extendedDisplayTitle = data.attrib.get('extendedDisplayTitle') self.id = cast(int, data.attrib.get('id')) self.index = cast(int, data.attrib.get('index', '-1')) self.language = data.attrib.get('language') self.languageCode = data.attrib.get('languageCode') self.selected = cast(bool, data.attrib.get('selected', '0')) self.streamType = cast(int, data.attrib.get('streamType')) + self.title = data.attrib.get('title') self.type = cast(int, data.attrib.get('streamType')) @staticmethod def parse(server, data, initpath): # pragma: no cover seems to be dead code. """ Factory method returns a new MediaPartStream from xml data. """ - STREAMCLS = {1: VideoStream, 2: AudioStream, 3: SubtitleStream} + STREAMCLS = {1: VideoStream, 2: AudioStream, 3: SubtitleStream, 4: LyricStream} stype = cast(int, data.attrib.get('streamType')) cls = STREAMCLS.get(stype, MediaPartStream) return cls(server, data, initpath) @@ -236,18 +264,25 @@ class VideoStream(MediaPartStream): self.bitDepth = cast(int, data.attrib.get('bitDepth')) self.bitrate = cast(int, data.attrib.get('bitrate')) self.cabac = cast(int, data.attrib.get('cabac')) + self.chromaLocation = data.attrib.get('chromaLocation') self.chromaSubsampling = data.attrib.get('chromaSubsampling') + self.codedHeight = data.attrib.get('codedHeight') + self.codedWidth = data.attrib.get('codedWidth') + self.colorPrimaries = data.attrib.get('colorPrimaries') + self.colorRange = data.attrib.get('colorRange') self.colorSpace = data.attrib.get('colorSpace') + self.colorTrc = data.attrib.get('colorTrc') self.duration = cast(int, data.attrib.get('duration')) self.frameRate = cast(float, data.attrib.get('frameRate')) self.frameRateMode = data.attrib.get('frameRateMode') - self.hasScallingMatrix = cast(bool, data.attrib.get('hasScallingMatrix')) + self.hasScalingMatrix = cast(bool, data.attrib.get('hasScalingMatrix')) self.height = cast(int, data.attrib.get('height')) self.level = cast(int, data.attrib.get('level')) self.profile = data.attrib.get('profile') self.refFrames = cast(int, data.attrib.get('refFrames')) + self.requiredBandwidths = data.attrib.get('requiredBandwidths') self.scanType = data.attrib.get('scanType') - self.title = data.attrib.get('title') + self.streamIdentifier = cast(int, data.attrib.get('streamIdentifier')) self.width = cast(int, data.attrib.get('width')) @@ -281,8 +316,20 @@ class AudioStream(MediaPartStream): self.channels = cast(int, data.attrib.get('channels')) self.dialogNorm = cast(int, data.attrib.get('dialogNorm')) self.duration = cast(int, data.attrib.get('duration')) + self.profile = data.attrib.get('profile') + self.requiredBandwidths = data.attrib.get('requiredBandwidths') self.samplingRate = cast(int, data.attrib.get('samplingRate')) - self.title = data.attrib.get('title') + + # For Track only + self.albumGain = cast(float, data.attrib.get('albumGain')) + self.albumPeak = cast(float, data.attrib.get('albumPeak')) + self.albumRange = cast(float, data.attrib.get('albumRange')) + self.endRamp = data.attrib.get('endRamp') + self.gain = cast(float, data.attrib.get('gain')) + self.loudness = cast(float, data.attrib.get('loudness')) + self.lra = cast(float, data.attrib.get('lra')) + self.peak = cast(float, data.attrib.get('peak')) + self.startRamp = data.attrib.get('startRamp') @utils.registerPlexObject @@ -303,10 +350,35 @@ class SubtitleStream(MediaPartStream): def _loadData(self, data): """ Load attribute values from Plex XML response. """ super(SubtitleStream, self)._loadData(data) + self.container = data.attrib.get('container') self.forced = cast(bool, data.attrib.get('forced', '0')) self.format = data.attrib.get('format') self.key = data.attrib.get('key') - self.title = data.attrib.get('title') + self.requiredBandwidths = data.attrib.get('requiredBandwidths') + + +@utils.registerPlexObject +class LyricStream(MediaPartStream): + """ Respresents a lyric stream within a :class:`~plexapi.media.MediaPart`. + + Attributes: + TAG (str): 'Stream' + STREAMTYPE (int): 4 + format (str): Lyric format (ex: lrc). + key (str): Key of this subtitle stream (ex: /library/streams/212284). + title (str): Title of this lyric stream. + """ + TAG = 'Stream' + STREAMTYPE = 4 + + def _loadData(self, data): + """ Load attribute values from Plex XML response. """ + super(LyricStream, self)._loadData(data) + self.format = data.attrib.get('format') + self.key = data.attrib.get('key') + self.minLines = cast(int, data.attrib.get('minLines')) + self.provider = data.attrib.get('provider') + self.timed = cast(bool, data.attrib.get('timed', '0')) @utils.registerPlexObject @@ -601,6 +673,18 @@ class Mood(MediaTag): FILTER = 'mood' +@utils.registerPlexObject +class Style(MediaTag): + """ Represents a single Style media tag. + + Attributes: + TAG (str): 'Style' + FILTER (str): 'style' + """ + TAG = 'Style' + FILTER = 'style' + + @utils.registerPlexObject class Poster(PlexObject): """ Represents a Poster. @@ -689,6 +773,7 @@ class Chapter(PlexObject): self.filter = data.attrib.get('filter') # I couldn't filter on it anyways self.tag = data.attrib.get('tag') self.title = self.tag + self.thumb = data.attrib.get('thumb') self.index = cast(int, data.attrib.get('index')) self.start = cast(int, data.attrib.get('startTimeOffset')) self.end = cast(int, data.attrib.get('endTimeOffset')) diff --git a/lib/plexapi/photo.py b/lib/plexapi/photo.py index 66c6d561..d1ac4f47 100644 --- a/lib/plexapi/photo.py +++ b/lib/plexapi/photo.py @@ -40,6 +40,8 @@ class Photoalbum(PlexPartialObject): self.index = utils.cast(int, data.attrib.get('index')) self.key = data.attrib.get('key') self.librarySectionID = data.attrib.get('librarySectionID') + self.librarySectionKey = data.attrib.get('librarySectionKey') + self.librarySectionTitle = data.attrib.get('librarySectionTitle') self.ratingKey = data.attrib.get('ratingKey') self.summary = data.attrib.get('summary') self.thumb = data.attrib.get('thumb') @@ -99,16 +101,32 @@ class Photo(PlexPartialObject): TYPE = 'photo' METADATA_TYPE = 'photo' + _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' + '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' + '&includeMarkers=1&includeConcerts=1&includePreferences=1' + '&indcludeBandwidths=1&includeLoudnessRamps=1') + def _loadData(self, data): """ Load attribute values from Plex XML response. """ + self.key = data.attrib.get('key') + self._details_key = self.key + self._include self.listType = 'photo' self.addedAt = utils.toDatetime(data.attrib.get('addedAt')) + self.createdAtAccuracy = data.attrib.get('createdAtAccuracy') + self.createdAtTZOffset = utils.cast(int, data.attrib.get('createdAtTZOffset')) + self.guid = data.attrib.get('guid') self.index = utils.cast(int, data.attrib.get('index')) - self.key = data.attrib.get('key') + self.librarySectionID = data.attrib.get('librarySectionID') + self.librarySectionKey = data.attrib.get('librarySectionKey') + self.librarySectionTitle = data.attrib.get('librarySectionTitle') 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 = data.attrib.get('parentRatingKey') + self.parentThumb = data.attrib.get('parentThumb') + self.parentTitle = data.attrib.get('parentTitle') self.ratingKey = data.attrib.get('ratingKey') self.summary = data.attrib.get('summary') self.thumb = data.attrib.get('thumb') diff --git a/lib/plexapi/utils.py b/lib/plexapi/utils.py index a23719d5..344622b9 100644 --- a/lib/plexapi/utils.py +++ b/lib/plexapi/utils.py @@ -23,7 +23,8 @@ log = logging.getLogger('plexapi') # Library Types - Populated at runtime SEARCHTYPES = {'movie': 1, 'show': 2, 'season': 3, 'episode': 4, 'trailer': 5, 'comic': 6, 'person': 7, 'artist': 8, 'album': 9, 'track': 10, 'picture': 11, 'clip': 12, 'photo': 13, 'photoalbum': 14, - 'playlist': 15, 'playlistFolder': 16, 'collection': 18, 'userPlaylistItem': 1001} + 'playlist': 15, 'playlistFolder': 16, 'collection': 18, + 'optimizedVersion': 42, 'userPlaylistItem': 1001} PLEXOBJECTS = {} diff --git a/lib/plexapi/video.py b/lib/plexapi/video.py index 2dcc73a7..c78dc793 100644 --- a/lib/plexapi/video.py +++ b/lib/plexapi/video.py @@ -35,6 +35,8 @@ class Video(PlexPartialObject): self.key = data.attrib.get('key', '') self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt')) self.librarySectionID = data.attrib.get('librarySectionID') + self.librarySectionKey = data.attrib.get('librarySectionKey') + self.librarySectionTitle = data.attrib.get('librarySectionTitle') self.ratingKey = utils.cast(int, data.attrib.get('ratingKey')) self.summary = data.attrib.get('summary') self.thumb = data.attrib.get('thumb') @@ -276,7 +278,8 @@ class Movie(Playable, Video): METADATA_TYPE = 'movie' _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeConcerts=1&includePreferences=1') + '&includeConcerts=1&includePreferences=1' + '&indcludeBandwidths=1') def _loadData(self, data): """ Load attribute values from Plex XML response. """ @@ -415,6 +418,7 @@ class Show(Video): self.theme = data.attrib.get('theme') self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) self.year = utils.cast(int, data.attrib.get('year')) + self.fields = self.findItems(data, media.Field) self.genres = self.findItems(data, media.Genre) self.roles = self.findItems(data, media.Role) self.labels = self.findItems(data, media.Label) @@ -527,12 +531,19 @@ class Season(Video): Video._loadData(self, data) # fix key if loaded from search self.key = self.key.replace('/children', '') + self.art = data.attrib.get('art') + self.guid = data.attrib.get('guid') self.leafCount = utils.cast(int, data.attrib.get('leafCount')) self.index = utils.cast(int, data.attrib.get('index')) + self.parentGuid = data.attrib.get('parentGuid') + self.parentIndex = data.attrib.get('parentIndex') self.parentKey = data.attrib.get('parentKey') self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey')) + self.parentTheme = data.attrib.get('parentTheme') + self.parentThumb = data.attrib.get('parentThumb') self.parentTitle = data.attrib.get('parentTitle') self.viewedLeafCount = utils.cast(int, data.attrib.get('viewedLeafCount')) + self.fields = self.findItems(data, media.Field) def __repr__(self): return '<%s>' % ':'.join([p for p in [ @@ -644,7 +655,8 @@ class Episode(Playable, Video): _include = ('?checkFiles=1&includeExtras=1&includeRelated=1' '&includeOnDeck=1&includeChapters=1&includePopularLeaves=1' - '&includeConcerts=1&includePreferences=1') + '&includeMarkers=1&includeConcerts=1&includePreferences=1' + '&indcludeBandwidths=1') def _loadData(self, data): """ Load attribute values from Plex XML response. """ @@ -657,6 +669,7 @@ class Episode(Playable, Video): self.contentRating = data.attrib.get('contentRating') 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') @@ -665,6 +678,7 @@ class Episode(Playable, Video): self.guid = data.attrib.get('guid') self.index = utils.cast(int, data.attrib.get('index')) self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d') + self.parentGuid = data.attrib.get('parentGuid') self.parentIndex = data.attrib.get('parentIndex') self.parentKey = data.attrib.get('parentKey') self.parentRatingKey = utils.cast(int, data.attrib.get('parentRatingKey')) @@ -675,6 +689,7 @@ class Episode(Playable, Video): self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0)) self.year = utils.cast(int, data.attrib.get('year')) self.directors = self.findItems(data, media.Director) + self.fields = self.findItems(data, media.Field) self.media = self.findItems(data, media.Media) self.writers = self.findItems(data, media.Writer) self.labels = self.findItems(data, media.Label)