2019-01-09 04:00:54 +11:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
Code from script.module.metadatautils, kodidb.py
|
|
|
|
|
|
|
|
Loads of different functions called in SEPARATE Python instances through
|
|
|
|
e.g. plugin://... calls. Hence be careful to only rely on window variables.
|
|
|
|
"""
|
|
|
|
from __future__ import absolute_import, division, unicode_literals
|
2020-12-19 03:10:20 +11:00
|
|
|
from builtins import str
|
2019-01-09 04:00:54 +11:00
|
|
|
from logging import getLogger
|
|
|
|
|
|
|
|
import xbmc
|
|
|
|
import xbmcgui
|
|
|
|
import xbmcvfs
|
|
|
|
|
|
|
|
from . import json_rpc as js, utils, variables as v
|
|
|
|
|
|
|
|
LOG = getLogger('PLEX.widget')
|
|
|
|
|
|
|
|
# To easily use threadpool, we can only pass one argument
|
|
|
|
PLEX_TYPE = None
|
|
|
|
SECTION_ID = None
|
|
|
|
APPEND_SHOW_TITLE = None
|
|
|
|
APPEND_SXXEXX = None
|
|
|
|
SYNCHED = True
|
|
|
|
# Need to chain the PMS keys
|
|
|
|
KEY = None
|
|
|
|
|
|
|
|
|
|
|
|
def get_clean_image(image):
|
2019-03-26 03:47:42 +11:00
|
|
|
'''
|
|
|
|
helper to strip all kodi tags/formatting of an image path/url
|
|
|
|
Pass in either unicode or str; returns unicode
|
|
|
|
'''
|
2019-01-09 04:00:54 +11:00
|
|
|
if not image:
|
|
|
|
return ""
|
2019-03-26 03:47:42 +11:00
|
|
|
if not isinstance(image, str):
|
|
|
|
image = image.encode('utf-8')
|
|
|
|
if b"music@" in image:
|
2019-01-09 04:00:54 +11:00
|
|
|
# fix for embedded images
|
2019-03-26 03:47:42 +11:00
|
|
|
thumbcache = xbmc.getCacheThumbName(image)
|
|
|
|
thumbcache = thumbcache.replace(b".tbn", b".jpg")
|
|
|
|
thumbcache = b"special://thumbnails/%s/%s" % (thumbcache[0], thumbcache)
|
2019-01-09 04:00:54 +11:00
|
|
|
if not xbmcvfs.exists(thumbcache):
|
|
|
|
xbmcvfs.copy(image, thumbcache)
|
|
|
|
image = thumbcache
|
2019-03-26 03:47:42 +11:00
|
|
|
if image and b"image://" in image:
|
|
|
|
image = image.replace(b"image://", b"")
|
2019-03-30 20:32:56 +11:00
|
|
|
image = utils.unquote(image)
|
|
|
|
if image.endswith("/"):
|
2019-01-09 04:00:54 +11:00
|
|
|
image = image[:-1]
|
2019-03-30 20:32:56 +11:00
|
|
|
return image
|
|
|
|
else:
|
|
|
|
return image.decode('utf-8')
|
2019-01-09 04:00:54 +11:00
|
|
|
|
|
|
|
|
2019-06-11 05:29:42 +10:00
|
|
|
def generate_item(api):
|
2019-01-09 04:00:54 +11:00
|
|
|
"""
|
|
|
|
Meant to be consumed by metadatautils.kodidb.prepare_listitem(), and then
|
|
|
|
subsequently by metadatautils.kodidb.create_listitem()
|
|
|
|
|
|
|
|
Do NOT set resumetime - otherwise Kodi always resumes at that time
|
|
|
|
even if the user chose to start element from the beginning
|
|
|
|
listitem.setProperty('resumetime', str(userdata['Resume']))
|
|
|
|
|
|
|
|
The key 'file' needs to be set later with the item's path
|
|
|
|
"""
|
|
|
|
try:
|
2019-06-11 05:29:42 +10:00
|
|
|
if api.tag in ('Directory', 'Playlist', 'Hub'):
|
|
|
|
return _generate_folder(api)
|
2019-01-09 04:00:54 +11:00
|
|
|
else:
|
2019-06-11 05:29:42 +10:00
|
|
|
return _generate_content(api)
|
2019-01-09 04:00:54 +11:00
|
|
|
except Exception:
|
|
|
|
# Usefull to catch everything here since we're using threadpool
|
|
|
|
LOG.error('xml that caused the crash: "%s": %s',
|
2019-06-11 05:29:42 +10:00
|
|
|
api.tag, api.attrib)
|
2019-01-09 04:00:54 +11:00
|
|
|
utils.ERROR(notify=True)
|
|
|
|
|
|
|
|
|
2019-06-11 05:29:42 +10:00
|
|
|
def _generate_folder(api):
|
2019-01-09 04:00:54 +11:00
|
|
|
'''Generates "folder"/"directory" items that user can further navigate'''
|
2019-07-07 05:20:23 +10:00
|
|
|
typus = ''
|
|
|
|
if api.plex_type == v.PLEX_TYPE_GENRE:
|
|
|
|
# Unfortunately, 'genre' is not yet supported by Kodi
|
|
|
|
# typus = v.KODI_TYPE_GENRE
|
|
|
|
pass
|
|
|
|
elif api.plex_type == v.PLEX_TYPE_SHOW:
|
|
|
|
typus = v.KODI_TYPE_SHOW
|
|
|
|
elif api.plex_type == v.PLEX_TYPE_SEASON:
|
|
|
|
typus = v.KODI_TYPE_SEASON
|
|
|
|
elif api.plex_type == v.PLEX_TYPE_ARTIST:
|
|
|
|
typus = v.KODI_TYPE_ARTIST
|
|
|
|
elif api.plex_type == v.PLEX_TYPE_ALBUM:
|
|
|
|
typus = v.KODI_TYPE_ALBUM
|
|
|
|
elif api.fast_key and '?collection=' in api.fast_key:
|
|
|
|
typus = v.KODI_TYPE_SET
|
2019-07-13 22:47:43 +10:00
|
|
|
if typus and typus != v.KODI_TYPE_SET:
|
|
|
|
content = _generate_content(api)
|
|
|
|
content['type'] = typus
|
|
|
|
content['file'] = api.directory_path(section_id=SECTION_ID,
|
|
|
|
plex_type=PLEX_TYPE,
|
|
|
|
old_key=KEY)
|
|
|
|
content['isFolder'] = True
|
|
|
|
content['IsPlayable'] = 'false'
|
|
|
|
return content
|
|
|
|
else:
|
|
|
|
art = api.artwork()
|
2019-10-31 22:51:21 +11:00
|
|
|
title = api.title() if api.plex_type != v.PLEX_TYPE_TAG else api.tag_label()
|
2019-07-13 22:47:43 +10:00
|
|
|
return {
|
2019-10-31 22:51:21 +11:00
|
|
|
'title': title,
|
|
|
|
'label': title,
|
2019-07-13 22:47:43 +10:00
|
|
|
'file': api.directory_path(section_id=SECTION_ID,
|
|
|
|
plex_type=PLEX_TYPE,
|
|
|
|
old_key=KEY),
|
|
|
|
'icon': 'DefaultFolder.png',
|
|
|
|
'art': {
|
|
|
|
'thumb': art['thumb'] if 'thumb' in art else
|
|
|
|
(art['poster'] if 'poster' in art else
|
|
|
|
'special://home/addons/%s/icon.png' % v.ADDON_ID),
|
|
|
|
'fanart': art['fanart'] if 'fanart' in art else
|
|
|
|
'special://home/addons/%s/fanart.jpg' % v.ADDON_ID},
|
|
|
|
'isFolder': True,
|
|
|
|
'type': typus,
|
|
|
|
'IsPlayable': 'false',
|
|
|
|
}
|
2019-01-09 04:00:54 +11:00
|
|
|
|
|
|
|
|
2019-06-11 05:29:42 +10:00
|
|
|
def _generate_content(api):
|
|
|
|
plex_type = api.plex_type
|
|
|
|
if api.kodi_id:
|
2019-01-09 04:00:54 +11:00
|
|
|
# Item is synched to the Kodi db - let's use that info
|
|
|
|
# (will thus e.g. include additional artwork or metadata)
|
2019-06-11 05:29:42 +10:00
|
|
|
item = js.item_details(api.kodi_id, api.kodi_type)
|
2019-01-09 04:00:54 +11:00
|
|
|
else:
|
|
|
|
cast = [{
|
|
|
|
'name': x[0],
|
|
|
|
'thumbnail': x[1],
|
|
|
|
'role': x[2],
|
|
|
|
'order': x[3],
|
2019-06-11 05:29:42 +10:00
|
|
|
} for x in api.people()['actor']]
|
2019-01-09 04:00:54 +11:00
|
|
|
item = {
|
|
|
|
'cast': cast,
|
2019-06-11 05:29:42 +10:00
|
|
|
'country': api.countries(),
|
2019-01-09 04:00:54 +11:00
|
|
|
'dateadded': api.date_created(), # e.g '2019-01-03 19:40:59'
|
2019-06-11 05:29:42 +10:00
|
|
|
'director': api.directors(), # list of [str]
|
|
|
|
'duration': api.runtime(),
|
|
|
|
'episode': api.index(),
|
2019-01-09 04:00:54 +11:00
|
|
|
# 'file': '', # e.g. 'videodb://tvshows/titles/20'
|
2019-06-11 05:29:42 +10:00
|
|
|
'genre': api.genres(),
|
2019-01-09 04:00:54 +11:00
|
|
|
# 'imdbnumber': '', # e.g.'341663'
|
|
|
|
'label': api.title(), # e.g. '1x05. Category 55 Emergency Doomsday Crisis'
|
2019-06-11 05:29:42 +10:00
|
|
|
'lastplayed': api.lastplayed(), # e.g. '2019-01-04 16:05:03'
|
2019-01-09 04:00:54 +11:00
|
|
|
'mpaa': api.content_rating(), # e.g. 'TV-MA'
|
|
|
|
'originaltitle': '', # e.g. 'Titans (2018)'
|
2019-06-11 05:29:42 +10:00
|
|
|
'playcount': api.viewcount(), # [int]
|
2019-01-09 04:00:54 +11:00
|
|
|
'plot': api.plot(), # [str]
|
|
|
|
'plotoutline': api.tagline(),
|
|
|
|
'premiered': api.premiere_date(), # '2018-10-12'
|
2019-06-11 05:29:42 +10:00
|
|
|
'rating': api.rating(), # [float]
|
|
|
|
'season': api.season_number(),
|
2019-01-09 04:00:54 +11:00
|
|
|
'sorttitle': api.sorttitle(), # 'Titans (2018)'
|
2019-06-11 05:29:42 +10:00
|
|
|
'studio': api.studios(),
|
2019-01-09 04:00:54 +11:00
|
|
|
'tag': [], # List of tags this item belongs to
|
|
|
|
'tagline': api.tagline(),
|
|
|
|
'thumbnail': '', # e.g. 'image://https%3a%2f%2fassets.tv'
|
|
|
|
'title': api.title(), # 'Titans (2018)'
|
2019-06-11 05:29:42 +10:00
|
|
|
'type': api.kodi_type,
|
2019-01-09 04:00:54 +11:00
|
|
|
'trailer': api.trailer(),
|
2019-06-11 05:29:42 +10:00
|
|
|
'tvshowtitle': api.show_title(),
|
2019-01-09 04:00:54 +11:00
|
|
|
'uniqueid': {
|
2020-11-06 01:47:04 +11:00
|
|
|
'imdbnumber': api.guids.get('imdb') or '',
|
|
|
|
'tvdb_id': api.guids.get('tvdb') or '',
|
|
|
|
'tmdb_id': api.guids.get('tmdb') or ''
|
2019-01-09 04:00:54 +11:00
|
|
|
},
|
|
|
|
'votes': '0', # [str]!
|
2019-06-11 05:29:42 +10:00
|
|
|
'writer': api.writers(), # list of [str]
|
2019-01-09 04:00:54 +11:00
|
|
|
'year': api.year(), # [int]
|
|
|
|
}
|
|
|
|
|
|
|
|
if plex_type in (v.PLEX_TYPE_EPISODE, v.PLEX_TYPE_SEASON, v.PLEX_TYPE_SHOW):
|
|
|
|
leaves = api.leave_count()
|
|
|
|
if leaves:
|
|
|
|
item['extraproperties'] = leaves
|
|
|
|
# Add all the artwork we can
|
|
|
|
item['art'] = api.artwork(full_artwork=True)
|
|
|
|
# Add all info for e.g. video and audio streams
|
|
|
|
item['streamdetails'] = api.mediastreams()
|
|
|
|
# Cleanup required due to the way metadatautils works
|
|
|
|
if not item['lastplayed']:
|
|
|
|
del item['lastplayed']
|
|
|
|
for stream in item['streamdetails']['video']:
|
|
|
|
stream['height'] = utils.cast(int, stream['height'])
|
|
|
|
stream['width'] = utils.cast(int, stream['width'])
|
|
|
|
stream['aspect'] = utils.cast(float, stream['aspect'])
|
|
|
|
item['streamdetails']['subtitle'] = [{'language': x} for x in item['streamdetails']['subtitle']]
|
|
|
|
# Resume point
|
|
|
|
resume = api.resume_point()
|
|
|
|
if resume:
|
|
|
|
item['resume'] = {
|
|
|
|
'position': resume,
|
2019-06-11 05:29:42 +10:00
|
|
|
'total': api.runtime()
|
2019-01-09 04:00:54 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
item['icon'] = v.ICON_FROM_PLEXTYPE[plex_type]
|
|
|
|
# Some customization
|
|
|
|
if plex_type == v.PLEX_TYPE_EPISODE:
|
|
|
|
# Prefix to the episode's title/label
|
2019-06-11 05:29:42 +10:00
|
|
|
if api.season_number() is not None and api.index() is not None:
|
2019-01-09 04:00:54 +11:00
|
|
|
if APPEND_SXXEXX is True:
|
2019-06-11 05:29:42 +10:00
|
|
|
item['title'] = "S%.2dE%.2d - %s" % (api.season_number(), api.index(), item['title'])
|
2019-01-09 04:00:54 +11:00
|
|
|
if APPEND_SHOW_TITLE is True:
|
2019-06-11 05:29:42 +10:00
|
|
|
item['title'] = "%s - %s " % (api.show_title(), item['title'])
|
2019-03-22 06:06:52 +11:00
|
|
|
item['label'] = item['title']
|
2019-01-09 04:00:54 +11:00
|
|
|
|
|
|
|
# Determine the path for this item
|
|
|
|
key = api.path_and_plex_id()
|
|
|
|
if key.startswith('/system/services') or key.startswith('http'):
|
|
|
|
params = {
|
|
|
|
'mode': 'plex_node',
|
|
|
|
'key': key,
|
2019-06-11 05:29:42 +10:00
|
|
|
'offset': api.resume_point_plex()
|
2019-01-09 04:00:54 +11:00
|
|
|
}
|
2019-03-30 20:32:56 +11:00
|
|
|
url = utils.extend_url('plugin://%s' % v.ADDON_ID, params)
|
2019-01-09 04:00:54 +11:00
|
|
|
elif plex_type == v.PLEX_TYPE_PHOTO:
|
|
|
|
url = api.get_picture_path()
|
|
|
|
else:
|
2020-02-27 04:10:15 +11:00
|
|
|
url = api.fullpath(force_first_media=True)[0]
|
2019-06-11 05:29:42 +10:00
|
|
|
if not api.kodi_id and plex_type == v.PLEX_TYPE_EPISODE:
|
2019-01-09 04:00:54 +11:00
|
|
|
# Hack - Item is not synched to the Kodi database
|
|
|
|
# We CANNOT use paths that show up in the Kodi paths table!
|
|
|
|
url = url.replace('plugin.video.plexkodiconnect.tvshows',
|
|
|
|
'plugin.video.plexkodiconnect')
|
|
|
|
item['file'] = url
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
|
|
def prepare_listitem(item):
|
|
|
|
"""
|
|
|
|
helper to convert kodi output from json api to compatible format for
|
|
|
|
listitems
|
|
|
|
|
|
|
|
Code from script.module.metadatautils, kodidb.py
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
# fix values returned from json to be used as listitem values
|
|
|
|
properties = item.get("extraproperties", {})
|
|
|
|
|
|
|
|
# set type
|
|
|
|
for idvar in [
|
|
|
|
('episode', 'DefaultTVShows.png'),
|
|
|
|
('tvshow', 'DefaultTVShows.png'),
|
|
|
|
('movie', 'DefaultMovies.png'),
|
|
|
|
('song', 'DefaultAudio.png'),
|
|
|
|
('album', 'DefaultAudio.png'),
|
|
|
|
('artist', 'DefaultArtist.png'),
|
|
|
|
('musicvideo', 'DefaultMusicVideos.png'),
|
|
|
|
('recording', 'DefaultTVShows.png'),
|
|
|
|
('channel', 'DefaultAddonPVRClient.png')]:
|
|
|
|
dbid = item.get(idvar[0] + "id")
|
|
|
|
if dbid:
|
|
|
|
properties["DBID"] = str(dbid)
|
|
|
|
if not item.get("type"):
|
|
|
|
item["type"] = idvar[0]
|
|
|
|
if not item.get("icon"):
|
|
|
|
item["icon"] = idvar[1]
|
|
|
|
break
|
|
|
|
|
|
|
|
# general properties
|
|
|
|
if "genre" in item and isinstance(item['genre'], list):
|
|
|
|
item["genre"] = " / ".join(item['genre'])
|
|
|
|
if "studio" in item and isinstance(item['studio'], list):
|
|
|
|
item["studio"] = " / ".join(item['studio'])
|
|
|
|
if "writer" in item and isinstance(item['writer'], list):
|
|
|
|
item["writer"] = " / ".join(item['writer'])
|
|
|
|
if 'director' in item and isinstance(item['director'], list):
|
|
|
|
item["director"] = " / ".join(item['director'])
|
|
|
|
if 'artist' in item and not isinstance(item['artist'], list):
|
|
|
|
item["artist"] = [item['artist']]
|
|
|
|
if 'artist' not in item:
|
|
|
|
item["artist"] = []
|
|
|
|
if item['type'] == "album" and 'album' not in item and 'label' in item:
|
|
|
|
item['album'] = item['label']
|
|
|
|
if "duration" not in item and "runtime" in item:
|
|
|
|
if (item["runtime"] / 60) > 300:
|
|
|
|
item["duration"] = item["runtime"] / 60
|
|
|
|
else:
|
|
|
|
item["duration"] = item["runtime"]
|
|
|
|
if "plot" not in item and "comment" in item:
|
|
|
|
item["plot"] = item["comment"]
|
|
|
|
if "tvshowtitle" not in item and "showtitle" in item:
|
|
|
|
item["tvshowtitle"] = item["showtitle"]
|
|
|
|
if "premiered" not in item and "firstaired" in item:
|
|
|
|
item["premiered"] = item["firstaired"]
|
|
|
|
if "firstaired" in item and "aired" not in item:
|
|
|
|
item["aired"] = item["firstaired"]
|
|
|
|
if "imdbnumber" not in properties and "imdbnumber" in item:
|
|
|
|
properties["imdbnumber"] = item["imdbnumber"]
|
|
|
|
if "imdbnumber" not in properties and "uniqueid" in item:
|
2020-12-19 03:10:20 +11:00
|
|
|
for value in list(item["uniqueid"].values()):
|
2019-01-09 04:00:54 +11:00
|
|
|
if value.startswith("tt"):
|
|
|
|
properties["imdbnumber"] = value
|
|
|
|
|
|
|
|
properties["dbtype"] = item["type"]
|
|
|
|
properties["DBTYPE"] = item["type"]
|
|
|
|
properties["type"] = item["type"]
|
|
|
|
properties["path"] = item.get("file")
|
|
|
|
|
|
|
|
# cast
|
|
|
|
list_cast = []
|
|
|
|
list_castandrole = []
|
|
|
|
item["cast_org"] = item.get("cast", [])
|
|
|
|
if "cast" in item and isinstance(item["cast"], list):
|
|
|
|
for castmember in item["cast"]:
|
|
|
|
if isinstance(castmember, dict):
|
|
|
|
list_cast.append(castmember.get("name", ""))
|
|
|
|
list_castandrole.append((castmember["name"], castmember["role"]))
|
|
|
|
else:
|
|
|
|
list_cast.append(castmember)
|
|
|
|
list_castandrole.append((castmember, ""))
|
|
|
|
|
|
|
|
item["cast"] = list_cast
|
|
|
|
item["castandrole"] = list_castandrole
|
|
|
|
|
|
|
|
if "season" in item and "episode" in item:
|
|
|
|
properties["episodeno"] = "s%se%s" % (item.get("season"), item.get("episode"))
|
|
|
|
if "resume" in item:
|
|
|
|
properties["resumetime"] = str(item['resume']['position'])
|
|
|
|
properties["totaltime"] = str(item['resume']['total'])
|
|
|
|
properties['StartOffset'] = str(item['resume']['position'])
|
|
|
|
|
|
|
|
# streamdetails
|
|
|
|
if "streamdetails" in item:
|
|
|
|
streamdetails = item["streamdetails"]
|
|
|
|
audiostreams = streamdetails.get('audio', [])
|
|
|
|
videostreams = streamdetails.get('video', [])
|
|
|
|
subtitles = streamdetails.get('subtitle', [])
|
|
|
|
if len(videostreams) > 0:
|
|
|
|
stream = videostreams[0]
|
|
|
|
height = stream.get("height", "")
|
|
|
|
width = stream.get("width", "")
|
|
|
|
if height and width:
|
|
|
|
resolution = ""
|
|
|
|
if width <= 720 and height <= 480:
|
|
|
|
resolution = "480"
|
|
|
|
elif width <= 768 and height <= 576:
|
|
|
|
resolution = "576"
|
|
|
|
elif width <= 960 and height <= 544:
|
|
|
|
resolution = "540"
|
|
|
|
elif width <= 1280 and height <= 720:
|
|
|
|
resolution = "720"
|
|
|
|
elif width <= 1920 and height <= 1080:
|
|
|
|
resolution = "1080"
|
|
|
|
elif width * height >= 6000000:
|
|
|
|
resolution = "4K"
|
|
|
|
properties["VideoResolution"] = resolution
|
|
|
|
if stream.get("codec", ""):
|
|
|
|
properties["VideoCodec"] = str(stream["codec"])
|
|
|
|
if stream.get("aspect", ""):
|
|
|
|
properties["VideoAspect"] = str(round(stream["aspect"], 2))
|
|
|
|
item["streamdetails"]["video"] = stream
|
|
|
|
|
|
|
|
# grab details of first audio stream
|
|
|
|
if len(audiostreams) > 0:
|
|
|
|
stream = audiostreams[0]
|
|
|
|
properties["AudioCodec"] = stream.get('codec', '')
|
|
|
|
properties["AudioChannels"] = str(stream.get('channels', ''))
|
|
|
|
properties["AudioLanguage"] = stream.get('language', '')
|
|
|
|
item["streamdetails"]["audio"] = stream
|
|
|
|
|
|
|
|
# grab details of first subtitle
|
|
|
|
if len(subtitles) > 0:
|
|
|
|
properties["SubtitleLanguage"] = subtitles[0].get('language', '')
|
|
|
|
item["streamdetails"]["subtitle"] = subtitles[0]
|
|
|
|
else:
|
|
|
|
item["streamdetails"] = {}
|
|
|
|
item["streamdetails"]["video"] = {'duration': item.get('duration', 0)}
|
|
|
|
|
|
|
|
# additional music properties
|
|
|
|
if 'album_description' in item:
|
|
|
|
properties["Album_Description"] = item.get('album_description')
|
|
|
|
|
|
|
|
# pvr properties
|
|
|
|
if "channellogo" in item:
|
|
|
|
properties["channellogo"] = item["channellogo"]
|
|
|
|
properties["channelicon"] = item["channellogo"]
|
|
|
|
if "episodename" in item:
|
|
|
|
properties["episodename"] = item["episodename"]
|
|
|
|
if "channel" in item:
|
|
|
|
properties["channel"] = item["channel"]
|
|
|
|
properties["channelname"] = item["channel"]
|
|
|
|
item["label2"] = item["title"]
|
|
|
|
|
|
|
|
# artwork
|
|
|
|
art = item.get("art", {})
|
|
|
|
if item["type"] in ["episode", "season"]:
|
|
|
|
if not art.get("fanart") and art.get("season.fanart"):
|
|
|
|
art["fanart"] = art["season.fanart"]
|
|
|
|
if not art.get("poster") and art.get("season.poster"):
|
|
|
|
art["poster"] = art["season.poster"]
|
|
|
|
if not art.get("landscape") and art.get("season.landscape"):
|
|
|
|
art["poster"] = art["season.landscape"]
|
|
|
|
if not art.get("fanart") and art.get("tvshow.fanart"):
|
|
|
|
art["fanart"] = art.get("tvshow.fanart")
|
|
|
|
if not art.get("poster") and art.get("tvshow.poster"):
|
|
|
|
art["poster"] = art.get("tvshow.poster")
|
|
|
|
if not art.get("clearlogo") and art.get("tvshow.clearlogo"):
|
|
|
|
art["clearlogo"] = art.get("tvshow.clearlogo")
|
|
|
|
if not art.get("banner") and art.get("tvshow.banner"):
|
|
|
|
art["banner"] = art.get("tvshow.banner")
|
|
|
|
if not art.get("landscape") and art.get("tvshow.landscape"):
|
|
|
|
art["landscape"] = art.get("tvshow.landscape")
|
|
|
|
if not art.get("fanart") and item.get('fanart'):
|
|
|
|
art["fanart"] = item.get('fanart')
|
|
|
|
if not art.get("thumb") and item.get('thumbnail'):
|
|
|
|
art["thumb"] = get_clean_image(item.get('thumbnail'))
|
|
|
|
if not art.get("thumb") and art.get('poster'):
|
|
|
|
art["thumb"] = get_clean_image(art.get('poster'))
|
|
|
|
if not art.get("thumb") and item.get('icon'):
|
|
|
|
art["thumb"] = get_clean_image(item.get('icon'))
|
|
|
|
if not item.get("thumbnail") and art.get('thumb'):
|
|
|
|
item["thumbnail"] = art["thumb"]
|
|
|
|
|
|
|
|
# clean art
|
2020-12-19 03:10:20 +11:00
|
|
|
for key, value in art.items():
|
|
|
|
if not isinstance(value, str):
|
2019-01-09 04:00:54 +11:00
|
|
|
art[key] = ""
|
|
|
|
elif value:
|
|
|
|
art[key] = get_clean_image(value)
|
|
|
|
item["art"] = art
|
|
|
|
|
|
|
|
item["extraproperties"] = properties
|
|
|
|
|
|
|
|
# return the result
|
|
|
|
return item
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
utils.ERROR(notify=True)
|
|
|
|
LOG.error('item that caused crash: %s', item)
|
|
|
|
|
|
|
|
|
2019-06-11 05:29:42 +10:00
|
|
|
def create_listitem(item, as_tuple=True, offscreen=True,
|
|
|
|
listitem=xbmcgui.ListItem):
|
2019-01-09 04:00:54 +11:00
|
|
|
"""
|
|
|
|
helper to create a kodi listitem from kodi compatible dict with mediainfo
|
|
|
|
|
|
|
|
WARNING: paths, so item['file'] for items NOT synched to the Kodi DB
|
|
|
|
shall NOT occur in the Kodi paths table!
|
|
|
|
Kodi information screen does not work otherwise
|
|
|
|
|
|
|
|
Code from script.module.metadatautils, kodidb.py
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
if v.KODIVERSION > 17:
|
2019-06-11 05:29:42 +10:00
|
|
|
liz = listitem(
|
2019-01-09 04:00:54 +11:00
|
|
|
label=item.get("label", ""),
|
|
|
|
label2=item.get("label2", ""),
|
|
|
|
path=item['file'],
|
|
|
|
offscreen=offscreen)
|
|
|
|
else:
|
2019-06-11 05:29:42 +10:00
|
|
|
liz = listitem(
|
2019-01-09 04:00:54 +11:00
|
|
|
label=item.get("label", ""),
|
|
|
|
label2=item.get("label2", ""),
|
|
|
|
path=item['file'])
|
|
|
|
|
|
|
|
# only set isPlayable prop if really needed
|
|
|
|
if item.get("isFolder", False):
|
|
|
|
liz.setProperty('IsPlayable', 'false')
|
|
|
|
elif "plugin://script.skin.helper" not in item['file']:
|
|
|
|
liz.setProperty('IsPlayable', 'true')
|
|
|
|
|
|
|
|
nodetype = "Video"
|
|
|
|
if item["type"] in ["song", "album", "artist"]:
|
|
|
|
nodetype = "Music"
|
|
|
|
|
|
|
|
# extra properties
|
2020-12-19 03:10:20 +11:00
|
|
|
for key, value in item["extraproperties"].items():
|
2019-01-09 04:00:54 +11:00
|
|
|
liz.setProperty(key, value)
|
|
|
|
|
|
|
|
# video infolabels
|
|
|
|
if nodetype == "Video":
|
|
|
|
infolabels = {
|
|
|
|
"title": item.get("title"),
|
|
|
|
"size": item.get("size"),
|
|
|
|
"genre": item.get("genre"),
|
|
|
|
"year": item.get("year"),
|
|
|
|
"top250": item.get("top250"),
|
|
|
|
"tracknumber": item.get("tracknumber"),
|
|
|
|
"rating": item.get("rating"),
|
|
|
|
"playcount": item.get("playcount"),
|
|
|
|
"overlay": item.get("overlay"),
|
|
|
|
"cast": item.get("cast"),
|
|
|
|
"castandrole": item.get("castandrole"),
|
|
|
|
"director": item.get("director"),
|
|
|
|
"mpaa": item.get("mpaa"),
|
|
|
|
"plot": item.get("plot"),
|
|
|
|
"plotoutline": item.get("plotoutline"),
|
|
|
|
"originaltitle": item.get("originaltitle"),
|
|
|
|
"sorttitle": item.get("sorttitle"),
|
|
|
|
"duration": item.get("duration"),
|
|
|
|
"studio": item.get("studio"),
|
|
|
|
"tagline": item.get("tagline"),
|
|
|
|
"writer": item.get("writer"),
|
|
|
|
"tvshowtitle": item.get("tvshowtitle"),
|
|
|
|
"premiered": item.get("premiered"),
|
|
|
|
"status": item.get("status"),
|
|
|
|
"code": item.get("imdbnumber"),
|
|
|
|
"imdbnumber": item.get("imdbnumber"),
|
|
|
|
"aired": item.get("aired"),
|
|
|
|
"credits": item.get("credits"),
|
|
|
|
"album": item.get("album"),
|
|
|
|
"artist": item.get("artist"),
|
|
|
|
"votes": item.get("votes"),
|
|
|
|
"trailer": item.get("trailer"),
|
|
|
|
# "progress": item.get('progresspercentage')
|
|
|
|
}
|
|
|
|
if item["type"] == "episode":
|
|
|
|
infolabels["season"] = item["season"]
|
|
|
|
infolabels["episode"] = item["episode"]
|
|
|
|
|
|
|
|
# streamdetails
|
|
|
|
if item.get("streamdetails"):
|
|
|
|
liz.addStreamInfo("video", item["streamdetails"].get("video", {}))
|
|
|
|
liz.addStreamInfo("audio", item["streamdetails"].get("audio", {}))
|
|
|
|
liz.addStreamInfo("subtitle", item["streamdetails"].get("subtitle", {}))
|
|
|
|
|
|
|
|
if "dateadded" in item:
|
|
|
|
infolabels["dateadded"] = item["dateadded"]
|
|
|
|
if "date" in item:
|
|
|
|
infolabels["date"] = item["date"]
|
|
|
|
|
|
|
|
# music infolabels
|
|
|
|
else:
|
|
|
|
infolabels = {
|
|
|
|
"title": item.get("title"),
|
|
|
|
"size": item.get("size"),
|
|
|
|
"genre": item.get("genre"),
|
|
|
|
"year": item.get("year"),
|
|
|
|
"tracknumber": item.get("track"),
|
|
|
|
"album": item.get("album"),
|
|
|
|
"artist": " / ".join(item.get('artist')),
|
|
|
|
"rating": str(item.get("rating", 0)),
|
|
|
|
"lyrics": item.get("lyrics"),
|
|
|
|
"playcount": item.get("playcount")
|
|
|
|
}
|
|
|
|
if "date" in item:
|
|
|
|
infolabels["date"] = item["date"]
|
|
|
|
if "duration" in item:
|
|
|
|
infolabels["duration"] = item["duration"]
|
|
|
|
if "lastplayed" in item:
|
|
|
|
infolabels["lastplayed"] = item["lastplayed"]
|
|
|
|
|
|
|
|
# setting the dbtype and dbid is supported from kodi krypton and up
|
|
|
|
# PKC hack: ignore empty type
|
|
|
|
if item["type"] not in ["recording", "channel", "favourite", ""]:
|
|
|
|
infolabels["mediatype"] = item["type"]
|
|
|
|
# setting the dbid on music items is not supported ?
|
|
|
|
if nodetype == "Video" and "DBID" in item["extraproperties"]:
|
|
|
|
infolabels["dbid"] = item["extraproperties"]["DBID"]
|
|
|
|
|
|
|
|
if "lastplayed" in item:
|
|
|
|
infolabels["lastplayed"] = item["lastplayed"]
|
|
|
|
|
|
|
|
# assign the infolabels
|
|
|
|
liz.setInfo(type=nodetype, infoLabels=infolabels)
|
|
|
|
|
|
|
|
# artwork
|
|
|
|
if "icon" in item:
|
2019-06-11 05:29:42 +10:00
|
|
|
item['art']['icon'] = item['icon']
|
|
|
|
liz.setArt(item.get("art", {}))
|
2019-01-09 04:00:54 +11:00
|
|
|
|
|
|
|
# contextmenu
|
|
|
|
if item["type"] in ["episode", "season"] and "season" in item and "tvshowid" in item:
|
|
|
|
# add series and season level to widgets
|
|
|
|
if "contextmenu" not in item:
|
|
|
|
item["contextmenu"] = []
|
|
|
|
item["contextmenu"] += [
|
|
|
|
(xbmc.getLocalizedString(20364), "ActivateWindow(Video,videodb://tvshows/titles/%s/,return)"
|
|
|
|
% (item["tvshowid"])),
|
|
|
|
(xbmc.getLocalizedString(20373), "ActivateWindow(Video,videodb://tvshows/titles/%s/%s/,return)"
|
|
|
|
% (item["tvshowid"], item["season"]))]
|
|
|
|
if "contextmenu" in item:
|
|
|
|
liz.addContextMenuItems(item["contextmenu"])
|
|
|
|
|
|
|
|
if as_tuple:
|
|
|
|
return (item["file"], liz, item.get("isFolder", False))
|
|
|
|
else:
|
|
|
|
return liz
|
|
|
|
except Exception:
|
|
|
|
utils.ERROR(notify=True)
|
|
|
|
LOG.error('item that should have been turned into a listitem: %s', item)
|
|
|
|
|
|
|
|
|
|
|
|
def create_main_entry(item):
|
|
|
|
'''helper to create a simple (directory) listitem'''
|
|
|
|
return {
|
|
|
|
'title': item[0],
|
|
|
|
'label': item[0],
|
|
|
|
'file': item[1],
|
|
|
|
'icon': item[2],
|
|
|
|
'art': {
|
|
|
|
'thumb': 'special://home/addons/%s/icon.png' % v.ADDON_ID,
|
|
|
|
'fanart': 'special://home/addons/%s/fanart.jpg' % v.ADDON_ID},
|
|
|
|
'isFolder': True,
|
|
|
|
'type': '',
|
|
|
|
'IsPlayable': 'false'
|
|
|
|
}
|