mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-26 10:46:11 +00:00
553 lines
No EOL
21 KiB
Python
553 lines
No EOL
21 KiB
Python
# -- coding: utf-8 --
|
|
# API.py
|
|
# This class helps translate more complex cases from the MediaBrowser API to the XBMC API
|
|
|
|
from datetime import datetime
|
|
from random import randrange
|
|
import xbmc
|
|
import xbmcgui
|
|
|
|
import Utils as utils
|
|
|
|
class API():
|
|
|
|
def getPeople(self, item):
|
|
# Process People
|
|
director = []
|
|
writer = []
|
|
cast = []
|
|
|
|
try:
|
|
people = item['People']
|
|
except: pass
|
|
else:
|
|
|
|
for person in people:
|
|
|
|
type = person['Type']
|
|
Name = person['Name']
|
|
|
|
if "Director" in type:
|
|
director.append(Name)
|
|
elif "Writing" in type:
|
|
writer.append(Name)
|
|
elif "Writer" in type:
|
|
writer.append(Name)
|
|
elif "Actor" in type:
|
|
cast.append(Name)
|
|
|
|
return {
|
|
|
|
'Director': director,
|
|
'Writer': writer,
|
|
'Cast': cast
|
|
}
|
|
|
|
def getTimeInfo(self, item):
|
|
# Runtime and Resume point
|
|
tempRuntime = 0
|
|
runtime = 0
|
|
resume = 0
|
|
|
|
try: # Get resume point
|
|
userdata = item['UserData']
|
|
playbackPosition = userdata['PlaybackPositionTicks']
|
|
resume = playbackPosition / 10000000.0
|
|
except: pass
|
|
|
|
try: # Get total runtime
|
|
tempRuntime = item['RunTimeTicks']
|
|
|
|
except:
|
|
try: tempRuntime = item['CumulativeRunTimeTicks']
|
|
except: pass
|
|
|
|
finally:
|
|
runtime = tempRuntime / 10000000.0
|
|
|
|
|
|
return {
|
|
|
|
'ResumeTime': resume,
|
|
'TotalTime': runtime
|
|
}
|
|
|
|
def getStudios(self, item):
|
|
# Process Studio
|
|
studios = []
|
|
|
|
try:
|
|
studio = item['SeriesStudio']
|
|
studios.append(self.getStudio(studio))
|
|
except:
|
|
try:
|
|
studioArray = item['Studios']
|
|
for studio in studioArray:
|
|
studios.append(self.getStudio(studio['Name']))
|
|
except: pass
|
|
|
|
return studios
|
|
|
|
def getStudio(self, studioName):
|
|
# Convert studio for Kodi to properly detect them
|
|
studios = {
|
|
|
|
'abc (us)': "ABC",
|
|
'fox (us)': "FOX",
|
|
'mtv (us)': "MTV",
|
|
'showcase (ca)': "Showcase",
|
|
'wgn america': "WGN"
|
|
}
|
|
|
|
return studios.get(studioName.lower(), studioName)
|
|
|
|
def getGenre(self,item):
|
|
genre = ""
|
|
genres = item.get("Genres")
|
|
if genres != None and genres != []:
|
|
for genre_string in genres:
|
|
if genre == "": #Just take the first genre
|
|
genre = genre_string
|
|
else:
|
|
genre = genre + " / " + genre_string
|
|
elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '':
|
|
genres = item.get("SeriesGenres")
|
|
if genres != None and genres != []:
|
|
for genre_string in genres:
|
|
if genre == "": #Just take the first genre
|
|
genre = genre_string
|
|
else:
|
|
genre = genre + " / " + genre_string
|
|
return genre
|
|
|
|
def getMediaStreams(self, item, mediaSources = False):
|
|
|
|
videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D
|
|
audiotracks = [] # Codec, Channels, language
|
|
subtitlelanguages = [] # Language
|
|
|
|
if mediaSources:
|
|
try:
|
|
MediaStreams = item['MediaSources'][0]['MediaStreams']
|
|
except:
|
|
MediaStreams = None
|
|
else:
|
|
MediaStreams = item.get('MediaStreams')
|
|
|
|
if MediaStreams:
|
|
# Sort through the Video, Audio, Subtitle tracks
|
|
for mediaStream in MediaStreams:
|
|
|
|
type = mediaStream.get('Type', "")
|
|
profile = mediaStream.get('Profile', "").lower()
|
|
codec = mediaStream.get('Codec', "").lower()
|
|
|
|
if "Video" in type:
|
|
videotrack = {}
|
|
videotrack['videocodec'] = codec
|
|
container = item['MediaSources'][0].get('Container', "").lower()
|
|
if "msmpeg4" in videotrack['videocodec']:
|
|
videotrack['videocodec'] = "divx"
|
|
elif "mpeg4" in videotrack['videocodec']:
|
|
if "simple profile" in profile or profile == "":
|
|
videotrack['videocodec'] = "xvid"
|
|
elif "h264" in videotrack['videocodec']:
|
|
if container in ("mp4", "mov", "m4v"):
|
|
videotrack['videocodec'] = "avc1"
|
|
videotrack['height'] = mediaStream.get('Height')
|
|
videotrack['width'] = mediaStream.get('Width')
|
|
videotrack['Video3DFormat'] = item.get('Video3DFormat')
|
|
if item.get('AspectRatio'):
|
|
# Metadata aspect ratio
|
|
videotrack['aspectratio'] = item['AspectRatio']
|
|
else: # File aspect ratio
|
|
videotrack['aspectratio'] = mediaStream.get('AspectRatio', "0")
|
|
if len(videotrack['aspectratio']) >= 3:
|
|
try:
|
|
aspectwidth, aspectheight = videotrack['aspectratio'].split(':')
|
|
videotrack['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6)
|
|
except:
|
|
videotrack['aspectratio'] = 1.85
|
|
else:
|
|
try:
|
|
videotrack['aspectratio'] = round(float(videotrack['width'] / videotrack['height']), 6)
|
|
except: # In the event the aspect ratio is missing and the width and height are missing as well.
|
|
videotrack['aspectratio'] = 1.85
|
|
videotracks.append(videotrack)
|
|
|
|
elif "Audio" in type:
|
|
audiotrack = {}
|
|
audiotrack['audiocodec'] = codec
|
|
if "dca" in audiotrack['audiocodec'] and "dts-hd ma" in profile:
|
|
audiotrack['audiocodec'] = "dtshd_ma"
|
|
audiotrack['channels'] = mediaStream.get('Channels')
|
|
audiotrack['audiolanguage'] = mediaStream.get('Language')
|
|
audiotracks.append(audiotrack)
|
|
|
|
elif "Subtitle" in type:
|
|
try:
|
|
subtitlelanguages.append(mediaStream['Language'])
|
|
except:
|
|
subtitlelanguages.append("Unknown")
|
|
|
|
return {
|
|
|
|
'videocodec' : videotracks,
|
|
'audiocodec' : audiotracks,
|
|
'subtitlelanguage' : subtitlelanguages
|
|
}
|
|
|
|
|
|
def getChecksum(self, item):
|
|
# use the etags checksum for this if available
|
|
# AND the userdata
|
|
checksum = ""
|
|
|
|
if item.get("Etag") != None:
|
|
checksum = item.get("Etag")
|
|
userData = item.get("UserData")
|
|
if(userData != None):
|
|
checksum += str(userData.get("Played"))
|
|
checksum += str(userData.get("IsFavorite"))
|
|
if userData.get('UnplayedItemCount') != None:
|
|
checksum += str(userData.get("UnplayedItemCount"))
|
|
if userData.get('LastPlayedDate') != None:
|
|
checksum += str(userData.get("LastPlayedDate"))
|
|
if userData.get('PlaybackPositionTicks') != None:
|
|
checksum += str(userData.get("PlaybackPositionTicks"))
|
|
|
|
return checksum
|
|
|
|
def getUserData(self, item):
|
|
# Default
|
|
favorite = False
|
|
playcount = None
|
|
lastPlayedDate = None
|
|
userKey = ""
|
|
|
|
try:
|
|
userdata = item['UserData']
|
|
|
|
except: # No userdata found.
|
|
pass
|
|
|
|
else:
|
|
favorite = userdata['IsFavorite']
|
|
userKey = userdata.get('Key', "")
|
|
|
|
watched = userdata['Played']
|
|
if watched:
|
|
# Playcount is tied to the watch status
|
|
playcount = userdata['PlayCount']
|
|
if playcount == 0:
|
|
playcount = 1
|
|
else:
|
|
playcount = None
|
|
|
|
lastPlayedDate = userdata.get('LastPlayedDate', None)
|
|
if lastPlayedDate:
|
|
lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ")
|
|
|
|
return {
|
|
|
|
'Favorite': favorite,
|
|
'PlayCount': playcount,
|
|
'LastPlayedDate': lastPlayedDate,
|
|
'Key': userKey
|
|
}
|
|
|
|
|
|
def getRecursiveItemCount(self, item):
|
|
if item.get("RecursiveItemCount") != None:
|
|
return str(item.get("RecursiveItemCount"))
|
|
else:
|
|
return "0"
|
|
|
|
def getOverview(self, item):
|
|
|
|
overview = ""
|
|
|
|
try:
|
|
overview = item['Overview']
|
|
overview = overview.replace("\"", "\'")
|
|
overview = overview.replace("\n", " ")
|
|
overview = overview.replace("\r", " ")
|
|
except: pass
|
|
|
|
return overview
|
|
|
|
def getTVInfo(self, item, userData):
|
|
TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount")
|
|
TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount")
|
|
WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount"))
|
|
UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount"))
|
|
NumEpisodes = TotalEpisodes
|
|
tempEpisode = ""
|
|
if (item.get("IndexNumber") != None):
|
|
episodeNum = item.get("IndexNumber")
|
|
if episodeNum < 10:
|
|
tempEpisode = "0" + str(episodeNum)
|
|
else:
|
|
tempEpisode = str(episodeNum)
|
|
|
|
tempSeason = ""
|
|
if (str(item.get("ParentIndexNumber")) != None):
|
|
tempSeason = str(item.get("ParentIndexNumber"))
|
|
if item.get("ParentIndexNumber") < 10:
|
|
tempSeason = "0" + tempSeason
|
|
if item.get("SeriesName") != None:
|
|
temp=item.get("SeriesName")
|
|
SeriesName=temp.encode('utf-8')
|
|
else:
|
|
SeriesName=''
|
|
return {'TotalSeasons' : str(TotalSeasons),
|
|
'TotalEpisodes' : str(TotalEpisodes),
|
|
'WatchedEpisodes' : str(WatchedEpisodes),
|
|
'UnWatchedEpisodes': str(UnWatchedEpisodes),
|
|
'NumEpisodes' : str(NumEpisodes),
|
|
'Season' : tempSeason,
|
|
'Episode' : tempEpisode,
|
|
'SeriesName' : SeriesName
|
|
}
|
|
|
|
def getDateCreated(self, item):
|
|
|
|
try:
|
|
dateadded = item['DateCreated']
|
|
dateadded = dateadded.split('.')[0].replace('T', " ")
|
|
except:
|
|
dateadded = None
|
|
|
|
return dateadded
|
|
|
|
def getPremiereDate(self, item):
|
|
|
|
try:
|
|
premiere = item['PremiereDate']
|
|
premiere = premiere.split('.')[0].replace('T', " ")
|
|
except:
|
|
premiere = None
|
|
|
|
return premiere
|
|
|
|
def getTagline(self, item):
|
|
|
|
try:
|
|
tagline = item['Taglines'][0]
|
|
except:
|
|
tagline = None
|
|
|
|
return tagline
|
|
|
|
def getProvider(self, item, providername):
|
|
# Provider Name: imdb or tvdb
|
|
try:
|
|
if "imdb" in providername:
|
|
provider = item['ProviderIds']['Imdb']
|
|
elif "tvdb" in providername:
|
|
provider = item['ProviderIds']['Tvdb']
|
|
elif "musicBrainzArtist" in providername:
|
|
provider = item['ProviderIds']['MusicBrainzArtist']
|
|
elif "musicBrainzAlbum" in providername:
|
|
provider = item['ProviderIds']['MusicBrainzAlbum']
|
|
elif "musicBrainzTrackId" in providername:
|
|
provider = item['ProviderIds']['MusicBrainzTrackId']
|
|
except:
|
|
provider = None
|
|
|
|
return provider
|
|
|
|
def getCountry(self, item):
|
|
|
|
try:
|
|
country = item['ProductionLocations'][0]
|
|
except:
|
|
country = None
|
|
|
|
return country
|
|
|
|
def getMpaa(self, item):
|
|
# Convert more complex cases
|
|
mpaa = item.get('OfficialRating', "")
|
|
if mpaa in ("NR", "UR"):
|
|
# Kodi seems to not like NR, but will accept Rated Not Rated
|
|
mpaa = "Rated Not Rated"
|
|
|
|
return mpaa
|
|
|
|
def getArtworks(self, data, type, mediaType = "", index = "0", getAll = False):
|
|
|
|
"""
|
|
Get all artwork, it will return an empty string
|
|
for the artwork type not found.
|
|
|
|
Index only matters when getAll is False.
|
|
|
|
mediaType: movie, boxset, tvshow, episode, season
|
|
|
|
Artwork type: Primary, Banner, Logo, Art, Thumb,
|
|
Disc Backdrop
|
|
"""
|
|
id = data['Id']
|
|
|
|
maxHeight = 10000
|
|
maxWidth = 10000
|
|
imageTag = "e3ab56fe27d389446754d0fb04910a34" # Place holder tag
|
|
|
|
|
|
if getAll:
|
|
|
|
allartworks = {
|
|
|
|
'Primary': "",
|
|
'Banner': "",
|
|
'Logo': "",
|
|
'Art': "",
|
|
'Thumb': "",
|
|
'Disc': "",
|
|
'Backdrop': ""
|
|
}
|
|
|
|
for keytype in allartworks:
|
|
type = keytype
|
|
url = ""
|
|
|
|
allartworks[keytype] = url
|
|
|
|
|
|
return allartworks
|
|
|
|
else: pass
|
|
|
|
def getArtwork(self, data, type, mediaType = "", index = "0", userParentInfo = False):
|
|
|
|
id = data.get("Id")
|
|
getSeriesData = False
|
|
userData = data.get("UserData")
|
|
|
|
if type == "tvshow.poster": # Change the Id to the series to get the overall series poster
|
|
if data.get("Type") == "Season" or data.get("Type")== "Episode":
|
|
id = data.get("SeriesId")
|
|
getSeriesData = True
|
|
elif type == "poster" and data.get("Type") == "Episode" and utils.settings('useSeasonPoster')=='true': # Change the Id to the Season to get the season poster
|
|
id = data.get("SeasonId")
|
|
if type == "poster" or type == "tvshow.poster": # Now that the Ids are right, change type to MB3 name
|
|
type="Primary"
|
|
if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art
|
|
if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4" and type != "Thumb" and type != "Banner" and type!="Thumb3" and type!="Backdrop":
|
|
id = data.get("SeriesId")
|
|
getSeriesData = True
|
|
if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art.
|
|
if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4":
|
|
id = data.get("SeriesId")
|
|
getSeriesData = True
|
|
if type =="Primary2" or type=="Primary3" or type=="Primary4":
|
|
id = data.get("SeasonId")
|
|
getSeriesData = True
|
|
if data.get("SeasonUserData") != None:
|
|
userData = data.get("SeasonUserData")
|
|
if id == None:
|
|
id=data.get("Id")
|
|
|
|
imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format
|
|
originalType = type
|
|
if type == "Primary2" or type == "Primary3" or type == "Primary4" or type=="SeriesPrimary":
|
|
type = "Primary"
|
|
if type == "Backdrop2" or type=="Backdrop3" or type=="BackdropNoIndicators":
|
|
type = "Backdrop"
|
|
if type == "Thumb2" or type=="Thumb3":
|
|
type = "Thumb"
|
|
if(data.get("ImageTags") != None and data.get("ImageTags").get(type) != None):
|
|
imageTag = data.get("ImageTags").get(type)
|
|
|
|
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Logo":
|
|
imageTag = data.get("ParentLogoImageTag")
|
|
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Art":
|
|
imageTag = data.get("ParentArtImageTag")
|
|
if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Backdrop":
|
|
if data.get("BackdropImageTags"):
|
|
imageTag = data['BackdropImageTags'][0]
|
|
if (data.get("Type") == "Episode" and originalType=="Thumb3"):
|
|
imageTag = data.get("SeriesThumbImageTag")
|
|
if (data.get("Type") == "Season" and originalType=="Thumb3" and imageTag=="e3ab56fe27d389446754d0fb04910a34"):
|
|
imageTag = data.get("ParentThumbImageTag")
|
|
id = data.get("SeriesId")
|
|
|
|
# for music we return the parent art if no image exists
|
|
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Backdrop" and not data.get("BackdropImageTags"):
|
|
data["BackdropImageTags"] = data.get("ParentBackdropImageTags")
|
|
id = data.get("ParentBackdropItemId")
|
|
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Logo" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"):
|
|
imageTag = data.get("ParentLogoImageTag")
|
|
id = data.get("ParentLogoItemId")
|
|
if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Art" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"):
|
|
imageTag = data.get("ParentArtImageTag")
|
|
id = data.get("ParentArtItemId")
|
|
|
|
query = ""
|
|
maxHeight = "10000"
|
|
maxWidth = "10000"
|
|
height = ""
|
|
width = ""
|
|
played = "0"
|
|
totalbackdrops = 0
|
|
|
|
if utils.settings('coverArtratio') == "true":
|
|
if mediaType in ("movie","boxset","tvshow"):
|
|
if "Primary" in type:
|
|
# Only force ratio for cover art for main covers
|
|
aspectratio = data.get("PrimaryImageAspectRatio")
|
|
width = "&Width=1000"
|
|
height = "&Height=1480"
|
|
|
|
if originalType =="BackdropNoIndicators" and index == "0" and data.get("BackdropImageTags") != None:
|
|
totalbackdrops = len(data.get("BackdropImageTags"))
|
|
if totalbackdrops != 0:
|
|
index = str(randrange(0,totalbackdrops))
|
|
# use the local image proxy server that is made available by this addons service
|
|
# Load user information set by UserClient
|
|
WINDOW = xbmcgui.Window(10000)
|
|
username = WINDOW.getProperty('currUser')
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
|
|
if utils.settings('compressArt')=='true':
|
|
query = query + "&Quality=90"
|
|
|
|
if imageTag == None:
|
|
imageTag = "e3ab56fe27d389446754d0fb04910a34"
|
|
|
|
artwork = "%s/mediabrowser/Items/%s/Images/%s/%s?MaxWidth=%s&MaxHeight=%s%s%s&Format=original&Tag=%s%s" % (server, id, type, index, maxWidth, maxHeight, height, width, imageTag, query)
|
|
#artwork = "%s/mediabrowser/Items/%s/Images/%s/%s/%s/original/%s/%s/%s?%s" % (server, id, type, index, imageTag, width, height, played, query) <- broken
|
|
if utils.settings('disableCoverArt')=='true':
|
|
artwork = artwork + "&EnableImageEnhancers=false"
|
|
|
|
# do not return non-existing images
|
|
if ( (type!="Backdrop" and imageTag=="e3ab56fe27d389446754d0fb04910a34") | #Remember, this is the placeholder tag, meaning we didn't find a valid tag
|
|
(type=="Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) |
|
|
(type=="Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0)
|
|
):
|
|
if type != "Backdrop" or (type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None) or (type=="Backdrop" and getSeriesData!=True):
|
|
artwork=''
|
|
|
|
return artwork
|
|
|
|
def imageUrl(self, id, type, index, width, height):
|
|
|
|
WINDOW = xbmcgui.Window(10000)
|
|
username = WINDOW.getProperty('currUser')
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
# For people image - actors, directors, writers
|
|
return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (server, id, type, width, height, index)
|
|
|
|
def getUserArtwork(self, data, type, index = "0"):
|
|
|
|
# Load user information set by UserClient
|
|
WINDOW = xbmcgui.Window(10000)
|
|
username = WINDOW.getProperty('currUser')
|
|
server = WINDOW.getProperty('server%s' % username)
|
|
id = data.get("Id")
|
|
|
|
artwork = "%s/mediabrowser/Users/%s/Images/%s?Format=original" % (server, id, type)
|
|
|
|
return artwork
|
|
|