def toFileItem(plexServer, plexItem, mediaType=None, plexLibType=None): # determine the matching Plex library type if possible checkMediaType = mediaType is not None if checkMediaType and not plexLibType: mappedMediaType = Api.getPlexMediaType(mediaType) if not mappedMediaType: log( 'cannot import unsupported media type "{}"'.format( mediaType), xbmc.LOGERROR) return None plexLibType = mappedMediaType['libtype'] # make sure the item matches the media type if plexLibType is not None and not Api.validatePlexLibraryItemType( plexItem, plexLibType): log( 'cannot import {} item from invalid Plex library item: {}'. format(mediaType, plexItem), xbmc.LOGERROR) return None # determine the Kodi media type based on the Plex library type if not checkMediaType: plexLibType = plexItem.type mappedMediaTypes = Api.getKodiMediaTypesFromPlexLibraryTpe( plexLibType) if not mappedMediaTypes: log( 'cannot import unsupported Plex library type "{}"'.format( plexLibType), xbmc.LOGERROR) return None if len(mappedMediaTypes) > 1: log( '{} supported media type for Plex library type "{}"'. format(len(mappedMediaTypes), plexLibType), xbmc.LOGDEBUG) mediaType = mappedMediaTypes[0]['kodi'] itemId = plexItem.ratingKey if not itemId: log('cannot import {} item without identifier'.format(mediaType), xbmc.LOGERROR) return None item = ListItem(label=plexItem.title) # fill video details Api.fillVideoInfos(plexServer, itemId, plexItem, mediaType, item) if not item.getPath(): log( 'failed to retrieve a path for {} item "{}"'.format( mediaType, item.getLabel()), xbmc.LOGWARNING) return None return item
def downloads(): if kodiutils.get_setting_as_bool("folder") and kodiutils.get_setting("downloadPath") and xbmcvfs.exists(kodiutils.get_setting("downloadPath")): dirs, files = xbmcvfs.listdir(kodiutils.get_setting("downloadPath")) if files: items = [] for file_ in files: cm = [] liz = ListItem(file_.split(".")[0]) liz.setPath(os.path.join(kodiutils.get_setting("downloadPath"),file_)) liz.setProperty('IsPlayable', 'true') cm.append((kodiutils.get_string(32055), 'XBMC.RunPlugin(plugin://%s/delete_file/%s)' % (ADDON.getAddonInfo("id"),urllib.quote(os.path.join(kodiutils.get_setting("downloadPath"),file_), safe='')) )) liz.addContextMenuItems(cm, replaceItems=False) items.append((liz.getPath(), liz, False)) if items: addDirectoryItems(plugin.handle, items, totalItems=len(items)) endOfDirectory(plugin.handle)
def fillVideoInfos( plexServer: plexapi.server.PlexServer, itemId: int, plexItem: video.Video, mediaType: str, item: ListItem ): """ Populate the provided ListItem object with existing data from plexItem and additional detail pulled from the provided plexServer :param plexServer: Plex server to gather additional details from :type plexServer: plexapi.server.PlexServer :param itemId: Unique ID of the plex Video object item :type itemId: int :param plexItem: Plex object populated with information about the item :type plexItem: video.Video :param mediaType: Kodi Media type object :type mediaType: str :param item: Instantiated Kodi ListItem to populate with additional details :type item: :class:`ListItem` """ info = { 'mediatype': mediaType, 'path': '', 'filenameandpath': '', 'title': item.getLabel() or '', 'sorttitle': '', 'originaltitle': '', 'plot': plexItem.summary or '', 'dateadded': Api.convertDateTimeToDbDateTime(plexItem.addedAt), 'year': 0, 'set': '', 'rating': 0.0, 'userrating': 0.0, 'mpaa': '', 'duration': 0, 'playcount': 0, 'lastplayed': '', 'director': [], 'writer': [], 'genre': [], 'country': [], 'tag': [] } date = None isFolder = False resumePoint = { 'totaltime': 0, 'resumetime': 0 } artwork = {} collections = [] media = [] locations = [] roles = [] if isinstance(plexItem, video.Video): info.update({ 'sorttitle': plexItem.titleSort, 'playcount': plexItem.viewCount, 'lastplayed': Api.convertDateTimeToDbDateTime(plexItem.lastViewedAt), }) info['tag'].append(plexItem.librarySectionTitle) if isinstance(plexItem, video.Movie): info.update({ 'mpaa': plexItem.contentRating or '', 'duration': Api.MillisecondsToSeconds(plexItem.duration), 'originaltitle': plexItem.originalTitle or '', 'premiered': Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt), 'rating': plexItem.rating or 0.0, 'studio': Api.ListFromString(plexItem.studio), 'tagline': plexItem.tagline or '', 'userrating': plexItem.userRating or 0.0, 'year': plexItem.year or 0, 'country': Api.ListFromMediaTags(plexItem.countries), 'director': Api.ListFromMediaTags(plexItem.directors), 'genre': Api.ListFromMediaTags(plexItem.genres), 'writer': Api.ListFromMediaTags(plexItem.writers), }) date = info['premiered'] resumePoint['resumetime'] = Api.MillisecondsToSeconds(plexItem.viewOffset) collections = plexItem.collections media = plexItem.media roles = plexItem.roles elif isinstance(plexItem, library.Collections): isFolder = True elif isinstance(plexItem, video.Show): info.update({ 'mpaa': plexItem.contentRating or '', 'duration': Api.MillisecondsToSeconds(plexItem.duration), 'premiered': Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt), 'rating': plexItem.rating or 0.0, 'studio': Api.ListFromString(plexItem.studio), 'year': plexItem.year or 0, 'genre': Api.ListFromMediaTags(plexItem.genres), }) date = info['premiered'] isFolder = True locations = plexItem.locations collections = plexItem.collections roles = plexItem.roles banner = plexItem.banner if banner: artwork['banner'] = plexServer.url(banner, includeToken=True) elif isinstance(plexItem, video.Season): info.update({ 'tvshowtitle': plexItem.parentTitle or '', 'season': plexItem.index, }) isFolder = True elif isinstance(plexItem, video.Episode): info.update({ 'tvshowtitle': plexItem.grandparentTitle or '', 'season': plexItem.parentIndex, 'episode': plexItem.index, 'mpaa': plexItem.contentRating or '', 'duration': Api.MillisecondsToSeconds(plexItem.duration), 'aired': Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt), 'rating': plexItem.rating or 0.0, 'year': plexItem.year or 0, 'director': Api.ListFromMediaTags(plexItem.directors), 'writer': Api.ListFromMediaTags(plexItem.writers), }) date = info['aired'] resumePoint['resumetime'] = Api.MillisecondsToSeconds(plexItem.viewOffset) media = plexItem.media # handle collections / sets collections = Api.ListFromMediaTags(collections) if collections: # Kodi can only store one set per media item info['set'] = collections[0] # set the item's datetime if available if date: item.setDateTime(date) # specify whether the item is a folder or not item.setIsFolder(isFolder) # add the item's ID as a unique ID belonging to Plex item.getVideoInfoTag().setUniqueIDs({ PLEX_PROTOCOL: itemId }, PLEX_PROTOCOL) # handle actors / cast cast = [] for index, role in enumerate(roles): cast.append({ 'name': role.tag.strip(), 'role': role.role.strip(), 'order': index }) if cast: item.setCast(cast) # handle resume point if resumePoint['resumetime'] > 0 and info['duration'] > 0: resumePoint['totaltime'] = info['duration'] item.setProperties(resumePoint) # handle stream details mediaPart = None for mediaStream in media: for part in mediaStream.parts: # pick the first MediaPart with a valid file and stream URL if mediaPart is None and part.file is not None and part.key is not None: mediaPart = part for videoStream in part.videoStreams(): item.addStreamInfo('video', { 'codec': videoStream.codec, 'language': videoStream.language, 'width': videoStream.width, 'height': videoStream.height, 'duration': info['duration'] }) for audioStream in part.audioStreams(): item.addStreamInfo( 'audio', { 'codec': audioStream.codec, 'language': audioStream.language, 'channels': audioStream.channels } ) for subtitleStream in part.subtitleStreams(): item.addStreamInfo( 'subtitle', { 'language': subtitleStream.language } ) if mediaPart: # extract the absolute / actual path and the stream URL from the selected MediaPart info['path'] = mediaPart.file item.setPath(plexServer.url(mediaPart.key, includeToken=True)) elif isFolder: # for folders use locations for the path if locations: info['path'] = locations[0] item.setPath(plexServer.url(plexItem.key, includeToken=True)) info['filenameandpath'] = item.getPath() # set all the video infos item.setInfo('video', info) # handle artwork poster = None fanart = None if isinstance(plexItem, video.Video): poster = plexItem.thumbUrl fanart = plexItem.artUrl elif isinstance(plexItem, library.Collections) and plexItem.thumb: poster = plexServer.url(plexItem.thumb, includeToken=True) if poster: artwork['poster'] = poster if fanart: artwork['fanart'] = fanart if artwork: item.setArt(artwork)
def toFileItem( plexServer: plexapi.server.PlexServer, plexItem: video.Video, mediaType: str = "", plexLibType: str = "" ) -> ListItem: """Validate, populate, and convert the provided plexItem into a Kodi GUI ListItem object :param plexServer: Plex server to gather additional details from :type plexServer: plexapi.server.PlexServer :param plexItem: Plex object populated with information about the item :type plexItem: video.Video :param mediaType: Kodi Media type object, defaults to '' :type mediaType: str, optional :param plexLibType: Type of plex library (movie, show, season, episode, collection), defaults to '' :type plexLibType: str, optional :return: ListItem object populated with the retreived plex item details :rtype: ListItem """ # determine the matching Plex library type if possible checkMediaType = mediaType is not None if checkMediaType and not plexLibType: mappedMediaType = Api.getPlexMediaType(mediaType) if not mappedMediaType: log(f"cannot import unsupported media type '{mediaType}'", xbmc.LOGERROR) return None plexLibType = mappedMediaType['libtype'] # make sure the item matches the media type if plexLibType is not None and not Api.validatePlexLibraryItemType(plexItem, plexLibType): log(f"cannot import {mediaType} item from invalid Plex library item: {plexItem}", xbmc.LOGERROR) return None # determine the Kodi media type based on the Plex library type if not checkMediaType: plexLibType = plexItem.type mappedMediaTypes = Api.getKodiMediaTypesFromPlexLibraryType(plexLibType) if not mappedMediaTypes: log(f"cannot import unsupported Plex library type '{plexLibType}'", xbmc.LOGERROR) return None if len(mappedMediaTypes) > 1: log( f"{len(mappedMediaTypes)} supported media type for Plex library type '{plexLibType}'", xbmc.LOGDEBUG ) mediaType = mappedMediaTypes[0]['kodi'] itemId = plexItem.ratingKey if not itemId: log(f"cannot import {mediaType} item without identifier", xbmc.LOGERROR) return None item = ListItem(label=plexItem.title) # fill video details Api.fillVideoInfos(plexServer, itemId, plexItem, mediaType, item) if not item.getPath(): log(f"failed to retrieve a path for {mediaType} item '{item.getLabel()}'", xbmc.LOGWARNING) return None return item
def createVideoInfoItemWithVideoSetters(embyServer, itemId, itemPath, itemObj, mediaType, libraryView='', allowDirectPlay=True): item = ListItem(path=itemPath, label=itemObj.get(constants.PROPERTY_ITEM_NAME, ''), offscreen=True) item.setIsFolder(itemObj.get(constants.PROPERTY_ITEM_IS_FOLDER, False)) # handle date premiereDate = itemObj.get(constants.PROPERTY_ITEM_PREMIERE_DATE) if premiereDate: item.setDateTime(premiereDate) videoInfoTag = item.getVideoInfoTag() userdata = {} if constants.PROPERTY_ITEM_USER_DATA in itemObj: userdata = itemObj[constants.PROPERTY_ITEM_USER_DATA] duration = int( Api.ticksToSeconds( itemObj.get(constants.PROPERTY_ITEM_RUN_TIME_TICKS, 0))) videoInfoTag.setMediaType(mediaType) videoInfoTag.setPath(itemObj.get(constants.PROPERTY_ITEM_PATH, '')) videoInfoTag.setFilenameAndPath(item.getPath()) videoInfoTag.setTitle(item.getLabel() or '') videoInfoTag.setSortTitle( itemObj.get(constants.PROPERTY_ITEM_SORT_NAME, '')) videoInfoTag.setOriginalTitle( itemObj.get(constants.PROPERTY_ITEM_ORIGINAL_TITLE, '')) videoInfoTag.setPlot( Api._mapOverview(itemObj.get(constants.PROPERTY_ITEM_OVERVIEW, ''))) videoInfoTag.setPlotOutline( itemObj.get(constants.PROPERTY_ITEM_SHORT_OVERVIEW, '')) videoInfoTag.setDateAdded( Api.convertDateTimeToDbDateTime( itemObj.get(constants.PROPERTY_ITEM_DATE_CREATED, ''))) videoInfoTag.setYear( itemObj.get(constants.PROPERTY_ITEM_PRODUCTION_YEAR, 0)) videoInfoTag.setMpaa( Api._mapMpaa( itemObj.get(constants.PROPERTY_ITEM_OFFICIAL_RATING, ''))) videoInfoTag.setDuration(duration) videoInfoTag.setPlaycount( userdata.get(constants.PROPERTY_ITEM_USER_DATA_PLAY_COUNT, 0 ) if userdata. get(constants.PROPERTY_ITEM_USER_DATA_PLAYED, False) else 0) videoInfoTag.setLastPlayed( Api.convertDateTimeToDbDateTime( userdata.get( constants.PROPERTY_ITEM_USER_DATA_LAST_PLAYED_DATE, ''))) videoInfoTag.setArtists( itemObj.get(constants.PROPERTY_ITEM_ARTISTS, [])) videoInfoTag.setAlbum(itemObj.get(constants.PROPERTY_ITEM_ALBUM, '')) videoInfoTag.setGenres(itemObj.get(constants.PROPERTY_ITEM_GENRES, [])) videoInfoTag.setCountries( itemObj.get(constants.PROPERTY_ITEM_PRODUCTION_LOCATIONS, [])) # process ratings if constants.PROPERTY_ITEM_COMMUNITY_RATING in itemObj: defaultRating = itemObj.get( constants.PROPERTY_ITEM_COMMUNITY_RATING) videoInfoTag.setRating(defaultRating, isDefault=True) # handle critic rating as rotten tomato rating if constants.PROPERTY_ITEM_CRITIC_RATING in itemObj: criticRating = float( itemObj.get(constants.PROPERTY_ITEM_CRITIC_RATING)) / 10.0 videoInfoTag.setRating(criticRating, type='tomatometerallcritics') # handle unique / provider IDs uniqueIds = \ {key.lower(): value for key, value in iteritems(itemObj.get(constants.PROPERTY_ITEM_PROVIDER_IDS, {}))} defaultUniqueId = Api._mapDefaultUniqueId(uniqueIds, mediaType) # add the item's ID as a unique ID belonging to Emby uniqueIds[constants.EMBY_PROTOCOL] = itemId videoInfoTag.setUniqueIDs(uniqueIds, defaultUniqueId) # process tags tags = [] if constants.PROPERTY_ITEM_TAG_ITEMS in itemObj: tags = [ tag.get(constants.PROPERTY_ITEM_TAG_ITEMS_NAME) for tag in itemObj.get(constants.PROPERTY_ITEM_TAG_ITEMS) if constants.PROPERTY_ITEM_TAG_ITEMS_NAME in tag ] # add the library view as a tag if libraryView: tags.append(libraryView) videoInfoTag.setTags(tags) # handle aired / premiered if premiereDate: pos = premiereDate.find('T') if pos >= 0: premiereDate = premiereDate[:pos] if mediaType == xbmcmediaimport.MediaTypeEpisode: videoInfoTag.setFirstAired(premiereDate) else: videoInfoTag.setPremiered(premiereDate) # handle trailers trailerUrl = Api.getTrailer(embyServer, itemId, itemObj, allowDirectPlay=allowDirectPlay) if trailerUrl: videoInfoTag.setTrailer(trailerUrl) # handle taglines embyTaglines = itemObj.get(constants.PROPERTY_ITEM_TAGLINES, []) if embyTaglines: videoInfoTag.setTagLine(embyTaglines[0]) # handle studios studios = [] for studio in itemObj.get(constants.PROPERTY_ITEM_STUDIOS, []): studios.append(Api._mapStudio(studio['Name'])) videoInfoTag.setStudios(studios) # handle tvshow, season and episode specific properties if mediaType == xbmcmediaimport.MediaTypeTvShow: videoInfoTag.setTvShowTitle(videoInfoTag.getTitle()) videoInfoTag.setTvShowStatus( itemObj.get(constants.PROPERTY_ITEM_STATUS, '')) elif mediaType in (xbmcmediaimport.MediaTypeSeason, xbmcmediaimport.MediaTypeEpisode): videoInfoTag.setTvShowTitle( itemObj.get(constants.PROPERTY_ITEM_SERIES_NAME, '')) index = itemObj.get(constants.PROPERTY_ITEM_INDEX_NUMBER, 0) if mediaType == xbmcmediaimport.MediaTypeSeason: videoInfoTag.setSeason(index) # ATTENTION # something is wrong with the SortName property for seasons which interfers with Kodi # abusing sorttitle for custom season titles videoInfoTag.setSortTitle('') else: videoInfoTag.setSeason( itemObj.get(constants.PROPERTY_ITEM_PARENT_INDEX_NUMBER, 0)) videoInfoTag.setEpisode(index) # handle resume point videoInfoTag.setResumePoint( Api.ticksToSeconds( userdata.get( constants.PROPERTY_ITEM_USER_DATA_PLAYBACK_POSITION_TICKS, 0)), duration) # handle actors / cast cast = [] writers = [] directors = [] for index, person in enumerate( itemObj.get(constants.PROPERTY_ITEM_PEOPLE, [])): name = person.get(constants.PROPERTY_ITEM_PEOPLE_NAME, '') castType = person.get(constants.PROPERTY_ITEM_PEOPLE_TYPE, '') if castType == constants.PROPERTY_ITEM_PEOPLE_TYPE_ACTOR: role = person.get(constants.PROPERTY_ITEM_PEOPLE_ROLE, '') # determine the thumbnail (if available) thumbnail = '' personId = person.get(constants.PROPERTY_ITEM_PEOPLE_ID, None) primaryImageTag = person.get( constants.PROPERTY_ITEM_PEOPLE_PRIMARY_IMAGE_TAG, '') if personId and primaryImageTag: thumbnail = \ embyServer.BuildImageUrl(personId, constants.PROPERTY_ITEM_IMAGE_TAGS_PRIMARY, primaryImageTag) cast.append(xbmc.Actor(name, role, index, thumbnail)) elif castType == constants.PROPERTY_ITEM_PEOPLE_TYPE_WRITER: writers.append(name) elif castType == constants.PROPERTY_ITEM_PEOPLE_TYPE_DIRECTOR: directors.append(name) videoInfoTag.setCast(cast) videoInfoTag.setWriters(writers) videoInfoTag.setDirectors(directors) # stream details for stream in itemObj.get(constants.PROPERTY_ITEM_MEDIA_STREAMS, []): streamType = stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_TYPE, '') if streamType == 'video': details = Api._mapVideoStream({ 'codec': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_CODEC, ''), 'profile': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_PROFILE, ''), 'language': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_LANGUAGE, ''), 'width': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_WIDTH, 0), 'height': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_HEIGHT, 0), 'aspect': stream.get( constants.PROPERTY_ITEM_MEDIA_STREAM_ASPECT_RATIO, '0'), 'stereomode': stream.get( constants.PROPERTY_ITEM_MEDIA_STREAM_VIDEO_3D_FORMAT, 'mono'), 'duration': duration }) videoInfoTag.addVideoStream( xbmc.VideoStreamDetail( width=details['width'], height=details['height'], aspect=details['aspect'], duration=details['duration'], codec=details['codec'], stereoMode=details['stereomode'], language=details['language'], )) elif streamType == 'audio': details = Api._mapAudioStream({ 'codec': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_CODEC, ''), 'profile': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_PROFILE, ''), 'language': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_LANGUAGE, ''), 'channels': stream.get(constants.PROPERTY_ITEM_MEDIA_STREAM_CHANNELS, 2) }) videoInfoTag.addAudioStream( xbmc.AudioStreamDetail( channels=details['channels'], codec=details['codec'], language=details['language'], )) elif streamType == 'subtitle': videoInfoTag.addSubtitleStream( xbmc.SubtitleStreamDetail(language=stream.get( constants.PROPERTY_ITEM_MEDIA_STREAM_LANGUAGE, ''), )) return item
def fillVideoInfos(plexServer: server.PlexServer, itemId: int, plexItem: video.Video, mediaType: str, item: ListItem, allowDirectPlay: bool = False): """ Populate the provided ListItem object with existing data from plexItem and additional detail pulled from the provided plexServer :param plexServer: Plex server to gather additional details from :type plexServer: server.PlexServer :param itemId: Unique ID of the plex Video object item :type itemId: int :param plexItem: Plex object populated with information about the item :type plexItem: video.Video :param mediaType: Kodi Media type object :type mediaType: str :param item: Instantiated Kodi ListItem to populate with additional details :type item: :class:`ListItem` :param allowDirectPlay: Settings definition on provider if directPlay is allowed :type allowDirectPlay: bool, optional """ videoInfoTag = item.getVideoInfoTag() videoInfoTag.setMediaType(mediaType) videoInfoTag.setTitle(item.getLabel() or '') date = None isFolder = False resumeTime = 0.0 duration = 0.0 artwork = {} collections = [] media = [] locations = [] roles = [] if isinstance(plexItem, video.Video): videoInfoTag.setSortTitle(plexItem.titleSort or '') videoInfoTag.setPlot(plexItem.summary or '') videoInfoTag.setDateAdded( Api.convertDateTimeToDbDateTime(plexItem.addedAt)) videoInfoTag.setPlaycount(plexItem.viewCount or 0) videoInfoTag.setLastPlayed( Api.convertDateTimeToDbDateTime(plexItem.lastViewedAt)) videoInfoTag.setTags([plexItem.librarySectionTitle]) if isinstance(plexItem, video.Movie): date = Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt) duration = Api.MillisecondsToSeconds(plexItem.duration) resumeTime = Api.MillisecondsToSeconds(plexItem.viewOffset) collections = plexItem.collections or [] media = plexItem.media or [] roles = plexItem.roles or [] videoInfoTag.setMpaa(plexItem.contentRating or '') videoInfoTag.setDuration(int(duration)) videoInfoTag.setOriginalTitle(plexItem.originalTitle or '') videoInfoTag.setPremiered(date) videoInfoTag.setRating(plexItem.rating or 0.0) videoInfoTag.setTagLine(plexItem.tagline or '') videoInfoTag.setUserRating(int(plexItem.userRating or 0)) videoInfoTag.setYear(plexItem.year or 0) videoInfoTag.setStudios(Api.ListFromString(plexItem.studio)) videoInfoTag.setCountries(Api.ListFromMediaTags( plexItem.countries)) videoInfoTag.setGenres(Api.ListFromMediaTags(plexItem.genres)) videoInfoTag.setDirectors(Api.ListFromMediaTags( plexItem.directors)) videoInfoTag.setWriters(Api.ListFromMediaTags(plexItem.writers)) elif isinstance(plexItem, collection.Collection): # ignore empty collections if plexItem.childCount <= 0: return isFolder = True videoInfoTag.setPlot(plexItem.summary or '') videoInfoTag.setDateAdded( Api.convertDateTimeToDbDateTime(plexItem.addedAt)) elif isinstance(plexItem, video.Show): isFolder = True date = Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt) duration = Api.MillisecondsToSeconds(plexItem.duration) locations = plexItem.locations or [] collections = plexItem.collections or [] roles = plexItem.roles or [] banner = plexItem.banner if banner: artwork['banner'] = plexServer.url(banner, includeToken=True) videoInfoTag.setMpaa(plexItem.contentRating or '') videoInfoTag.setDuration(int(duration)) videoInfoTag.setOriginalTitle(plexItem.originalTitle or '') videoInfoTag.setPremiered(date) videoInfoTag.setRating(plexItem.rating or 0.0) videoInfoTag.setTagLine(plexItem.tagline or '') videoInfoTag.setYear(plexItem.year or 0) videoInfoTag.setStudios(Api.ListFromString(plexItem.studio)) videoInfoTag.setGenres(Api.ListFromMediaTags(plexItem.genres)) elif isinstance(plexItem, video.Season): isFolder = True videoInfoTag.setTvShowTitle(plexItem.parentTitle or '') videoInfoTag.setSeason(plexItem.index) elif isinstance(plexItem, video.Episode): date = Api.convertDateTimeToDbDate(plexItem.originallyAvailableAt) resumeTime = Api.MillisecondsToSeconds(plexItem.viewOffset) duration = Api.MillisecondsToSeconds(plexItem.duration) media = plexItem.media or [] videoInfoTag.setTvShowTitle(plexItem.grandparentTitle or '') videoInfoTag.setSeason(int(plexItem.parentIndex)) videoInfoTag.setEpisode(plexItem.index) videoInfoTag.setMpaa(plexItem.contentRating or '') videoInfoTag.setDuration(int(duration)) videoInfoTag.setFirstAired(date) videoInfoTag.setRating(plexItem.rating or 0.0) videoInfoTag.setYear(plexItem.year or 0) videoInfoTag.setDirectors(Api.ListFromMediaTags( plexItem.directors)) videoInfoTag.setWriters(Api.ListFromMediaTags(plexItem.writers)) # handle collections / sets collections = Api.ListFromMediaTags(collections) if collections: # Kodi can only store one set per media item videoInfoTag.setSet(collections[0]) # set the item's datetime if available if date: item.setDateTime(date) # specify whether the item is a folder or not item.setIsFolder(isFolder) # add the item's ID as a unique ID belonging to Plex uniqueIDs = {PLEX_PROTOCOL: str(itemId)} # retrieve and map GUIDS from Plex if isinstance(plexItem, (video.Movie, video.Show, video.Season, video.Episode)): guids = Api._mapGuids(plexItem.guids) if guids: uniqueIDs = {**guids, **uniqueIDs} videoInfoTag.setUniqueIDs(uniqueIDs, PLEX_PROTOCOL) # handle actors / cast cast = [] for index, role in enumerate(roles): actor = xbmc.Actor(role.tag.strip(), (role.role or '').strip(), index, role.thumb) cast.append(actor) if cast: videoInfoTag.setCast(cast) # handle resume point if resumeTime > 0 and duration > 0.0: videoInfoTag.setResumePoint(resumeTime, duration) # handle stream details path = None for mediaStream in media: for part in mediaStream.parts: # pick the first MediaPart with a valid file and stream URL if not path and part.file and part.key: path = part.file for videoStream in part.videoStreams(): videoInfoTag.addVideoStream( xbmc.VideoStreamDetail(width=videoStream.width or 0, height=videoStream.height or 0, codec=videoStream.codec or '', duration=int(duration), language=videoStream.language or '')) for audioStream in part.audioStreams(): videoInfoTag.addAudioStream( xbmc.AudioStreamDetail(channels=audioStream.channels or 2, codec=audioStream.codec or '', language=audioStream.language or '')) for index, subtitleStream in enumerate(part.subtitleStreams()): videoInfoTag.addSubtitleStream( xbmc.SubtitleStreamDetail( language=subtitleStream.language or f"[{index}]")) if isFolder: # for folders use locations for the path if locations: path = locations[0] item.setPath(plexServer.url(plexItem.key, includeToken=True)) else: # determine if directPlay is enabled and possible if allowDirectPlay: directPlayUrl = Api.getDirectPlayUrlFromPlexItem(plexItem) if directPlayUrl: item.setPath(directPlayUrl) # otherwise determine the stream URL if not item.getPath(): item.setPath(Api.getStreamUrlFromPlexItem( plexItem, plexServer)) if path: videoInfoTag.setPath(path) videoInfoTag.setFilenameAndPath(item.getPath()) # handle artwork poster = None fanart = None if isinstance(plexItem, video.Video): poster = plexItem.thumbUrl fanart = plexItem.artUrl elif isinstance(plexItem, collection.Collection) and plexItem.thumb: poster = plexServer.url(plexItem.thumb, includeToken=True) if poster: artwork['poster'] = poster if fanart: artwork['fanart'] = fanart if artwork: item.setArt(artwork)