diff --git a/addon.xml b/addon.xml
index 41fa5f77..7335a2b4 100644
--- a/addon.xml
+++ b/addon.xml
@@ -8,6 +8,7 @@
+
video audio image
diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py
index ea4a4685..1f6fa56a 100644
--- a/resources/lib/json_rpc.py
+++ b/resources/lib/json_rpc.py
@@ -585,6 +585,15 @@ def item_details(kodi_id, kodi_type):
ret = JsonRPC(json).execute({'%sid' % kodi_type: kodi_id,
'properties': fields})
try:
- return ret['result']['%sdetails' % kodi_type]
+ ret = ret['result']['%sdetails' % kodi_type]
except (KeyError, TypeError):
return {}
+ if kodi_type == v.KODI_TYPE_SHOW:
+ # append watched counts to tvshow details
+ ret["extraproperties"] = {
+ "totalseasons": str(ret["season"]),
+ "totalepisodes": str(ret["episode"]),
+ "watchedepisodes": str(ret["watchedepisodes"]),
+ "unwatchedepisodes": str(ret["episode"] - ret["watchedepisodes"])
+ }
+ return ret
diff --git a/resources/lib/kodi_constants.py b/resources/lib/kodi_constants.py
index 61f88cd9..35ff00d9 100644
--- a/resources/lib/kodi_constants.py
+++ b/resources/lib/kodi_constants.py
@@ -5,88 +5,45 @@ script.module.metadatautils
kodi_constants.py
Several common constants for use with Kodi json api
'''
-FIELDS_BASE = ['dateadded', 'file', 'lastplayed', 'plot', 'title', 'art',
- 'playcount']
-FIELDS_FILE = FIELDS_BASE + ['streamdetails', 'director', 'resume', 'runtime']
-FIELDS_MOVIES = FIELDS_FILE + ['plotoutline', 'sorttitle', 'cast', 'votes',
- 'showlink', 'top250', 'trailer', 'year', 'country', 'studio', 'set',
- 'genre', 'mpaa', 'setid', 'rating', 'tag', 'tagline', 'writer',
- 'originaltitle', 'imdbnumber', 'uniqueid']
-FIELDS_TVSHOWS = FIELDS_BASE + ['sorttitle', 'mpaa', 'premiered', 'year',
- 'episode', 'watchedepisodes', 'votes', 'rating', 'studio', 'season',
- 'genre', 'cast', 'episodeguide', 'tag', 'originaltitle', 'imdbnumber']
+FIELDS_BASE = ["dateadded", "file", "lastplayed", "plot", "title", "art", "playcount"]
+FIELDS_FILE = FIELDS_BASE + ["streamdetails", "director", "resume", "runtime"]
+FIELDS_MOVIES = FIELDS_FILE + ["plotoutline", "sorttitle", "cast", "votes", "showlink", "top250", "trailer", "year",
+ "country", "studio", "set", "genre", "mpaa", "setid", "rating", "tag", "tagline",
+ "writer", "originaltitle",
+ "imdbnumber"]
+FIELDS_MOVIES.append("uniqueid")
+FIELDS_TVSHOWS = FIELDS_BASE + ["sorttitle", "mpaa", "premiered", "year", "episode", "watchedepisodes", "votes",
+ "rating", "studio", "season", "genre", "cast", "episodeguide", "tag", "originaltitle",
+ "imdbnumber"]
FIELDS_SEASON = ['art', 'playcount', 'season', 'showtitle', 'episode',
- 'tvshowid', 'watchedepisodes', 'userrating', 'fanart', 'thumbnail']
-FIELDS_EPISODES = FIELDS_FILE + ['cast', 'productioncode', 'rating', 'votes',
- 'episode', 'showtitle', 'tvshowid', 'season', 'firstaired', 'writer',
- 'originaltitle']
-FIELDS_MUSICVIDEOS = FIELDS_FILE + ['genre', 'artist', 'tag', 'album', 'track',
- 'studio', 'year']
-FIELDS_FILES = FIELDS_FILE + ['plotoutline', 'sorttitle', 'cast', 'votes',
- 'trailer', 'year', 'country', 'studio', 'genre', 'mpaa', 'rating',
- 'tagline', 'writer', 'originaltitle', 'imdbnumber', 'premiered', 'episode',
- 'showtitle', 'firstaired', 'watchedepisodes', 'duration', 'season']
-FIELDS_SONGS = ['artist', 'displayartist', 'title', 'rating', 'fanart',
- 'thumbnail', 'duration', 'disc', 'playcount', 'comment', 'file', 'album',
- 'lastplayed', 'genre', 'musicbrainzartistid', 'track', 'dateadded']
-FIELDS_ALBUMS = ['title', 'fanart', 'thumbnail', 'genre', 'displayartist',
- 'artist', 'musicbrainzalbumartistid', 'year', 'rating', 'artistid',
- 'musicbrainzalbumid', 'theme', 'description', 'type', 'style', 'playcount',
- 'albumlabel', 'mood', 'dateadded']
-FIELDS_ARTISTS = ['born', 'formed', 'died', 'style', 'yearsactive', 'mood',
- 'fanart', 'thumbnail', 'musicbrainzartistid', 'disbanded', 'description',
- 'instrument']
-FIELDS_RECORDINGS = ['art', 'channel', 'directory', 'endtime', 'file', 'genre',
- 'icon', 'playcount', 'plot', 'plotoutline', 'resume', 'runtime',
- 'starttime', 'streamurl', 'title']
-FIELDS_CHANNELS = ['broadcastnow', 'channeltype', 'hidden', 'locked',
- 'lastplayed', 'thumbnail', 'channel']
+ 'tvshowid', 'watchedepisodes', 'userrating', 'fanart', 'thumbnail']
+FIELDS_EPISODES = FIELDS_FILE + ["cast", "productioncode", "rating", "votes", "episode", "showtitle", "tvshowid",
+ "season", "firstaired", "writer", "originaltitle"]
+FIELDS_MUSICVIDEOS = FIELDS_FILE + ["genre", "artist", "tag", "album", "track", "studio", "year"]
+FIELDS_FILES = FIELDS_FILE + ["plotoutline", "sorttitle", "cast", "votes", "trailer", "year", "country", "studio",
+ "genre", "mpaa", "rating", "tagline", "writer", "originaltitle", "imdbnumber",
+ "premiered", "episode", "showtitle",
+ "firstaired", "watchedepisodes", "duration", "season"]
+FIELDS_SONGS = ["artist", "displayartist", "title", "rating", "fanart", "thumbnail", "duration", "disc",
+ "playcount", "comment", "file", "album", "lastplayed", "genre", "musicbrainzartistid", "track",
+ "dateadded"]
+FIELDS_ALBUMS = ["title", "fanart", "thumbnail", "genre", "displayartist", "artist",
+ "musicbrainzalbumartistid", "year", "rating", "artistid", "musicbrainzalbumid", "theme", "description",
+ "type", "style", "playcount", "albumlabel", "mood", "dateadded"]
+FIELDS_ARTISTS = ["born", "formed", "died", "style", "yearsactive", "mood", "fanart", "thumbnail",
+ "musicbrainzartistid", "disbanded", "description", "instrument"]
+FIELDS_RECORDINGS = ["art", "channel", "directory", "endtime", "file", "genre", "icon", "playcount", "plot",
+ "plotoutline", "resume", "runtime", "starttime", "streamurl", "title"]
+FIELDS_CHANNELS = ["broadcastnow", "channeltype", "hidden", "locked", "lastplayed", "thumbnail", "channel"]
-FILTER_UNWATCHED = {
- 'operator': 'lessthan',
- 'field': 'playcount',
- 'value': '1'
-}
-FILTER_WATCHED = {
- 'operator': 'isnot',
- 'field': 'playcount',
- 'value': '0'
-}
-FILTER_RATING = {
- 'operator': 'greaterthan',
- 'field': 'rating',
- 'value': '7'
-}
-FILTER_RATING_MUSIC = {
- 'operator': 'greaterthan',
- 'field': 'rating',
- 'value': '3'
-}
-FILTER_INPROGRESS = {
- 'operator': 'true',
- 'field': 'inprogress',
- 'value': ''
-}
-SORT_RATING = {
- 'method': 'rating',
- 'order': 'descending'
-}
-SORT_RANDOM = {
- 'method': 'random',
- 'order': 'descending'
-}
-SORT_TITLE = {
- 'method': 'title',
- 'order': 'ascending'
-}
-SORT_DATEADDED = {
- 'method': 'dateadded',
- 'order': 'descending'
-}
-SORT_LASTPLAYED = {
- 'method': 'lastplayed',
- 'order': 'descending'
-}
-SORT_EPISODE = {
- 'method': 'episode'
-}
+FILTER_UNWATCHED = {"operator": "lessthan", "field": "playcount", "value": "1"}
+FILTER_WATCHED = {"operator": "isnot", "field": "playcount", "value": "0"}
+FILTER_RATING = {"operator": "greaterthan", "field": "rating", "value": "7"}
+FILTER_RATING_MUSIC = {"operator": "greaterthan", "field": "rating", "value": "3"}
+FILTER_INPROGRESS = {"operator": "true", "field": "inprogress", "value": ""}
+SORT_RATING = {"method": "rating", "order": "descending"}
+SORT_RANDOM = {"method": "random", "order": "descending"}
+SORT_TITLE = {"method": "title", "order": "ascending"}
+SORT_DATEADDED = {"method": "dateadded", "order": "descending"}
+SORT_LASTPLAYED = {"method": "lastplayed", "order": "descending"}
+SORT_EPISODE = {"method": "episode"}
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 002e96ca..3f69f303 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -9,6 +9,7 @@ from datetime import datetime
from unicodedata import normalize
from threading import Lock
import urllib
+import arrow
# Originally tried faster cElementTree, but does NOT work reliably with Kodi
# etree parse unsafe; make sure we're always receiving unicode
from . import defused_etree as etree
@@ -616,6 +617,21 @@ def indent(elem, level=0):
LOG.info('Indentation failed with: %s', err)
+def localdate_from_utc_string(timestring):
+ """helper to convert internal utc time (used in pvr) to local timezone"""
+ utc_datetime = arrow.get(timestring)
+ local_datetime = utc_datetime.to('local')
+ return local_datetime.format("YYYY-MM-DD HH:mm:ss")
+
+
+def localized_date_time(timestring):
+ """returns localized version of the timestring (used in pvr)"""
+ date_time = arrow.get(timestring)
+ local_date = date_time.strftime(xbmc.getRegion("dateshort"))
+ local_time = date_time.strftime(xbmc.getRegion("time").replace(":%S", ""))
+ return local_date, local_time
+
+
class XmlKodiSetting(object):
"""
Used to load a Kodi XML settings file from special://profile as an etree
diff --git a/resources/lib/widgets.py b/resources/lib/widgets.py
index 9da16035..5ee37e35 100644
--- a/resources/lib/widgets.py
+++ b/resources/lib/widgets.py
@@ -7,6 +7,7 @@ Loads of different functions called in SEPARATE Python instances through
e.g. plugin://... calls. Hence be careful to only rely on window variables.
"""
from logging import getLogger
+import arrow
import xbmc
import xbmcgui
@@ -157,7 +158,6 @@ def _generate_content(api):
'director': api.directors(), # list of [str]
'duration': api.runtime(),
'episode': api.index(),
- # 'file': '', # e.g. 'videodb://tvshows/titles/20'
'genre': api.genres(),
# 'imdbnumber': '', # e.g.'341663'
'label': api.title(), # e.g. '1x05. Category 55 Emergency Doomsday Crisis'
@@ -246,12 +246,8 @@ def _generate_content(api):
def prepare_listitem(item):
- """
- helper to convert kodi output from json api to compatible format for
- listitems
-
- Code from script.module.metadatautils, kodidb.py
- """
+ """helper to convert kodi output from json api to compatible format for
+ listitems"""
try:
# fix values returned from json to be used as listitem values
properties = item.get("extraproperties", {})
@@ -292,8 +288,8 @@ def prepare_listitem(item):
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
+ if (item["runtime"] // 60) > 300:
+ item["duration"] = item["runtime"] // 60
else:
item["duration"] = item["runtime"]
if "plot" not in item and "comment" in item:
@@ -307,7 +303,7 @@ def prepare_listitem(item):
if "imdbnumber" not in properties and "imdbnumber" in item:
properties["imdbnumber"] = item["imdbnumber"]
if "imdbnumber" not in properties and "uniqueid" in item:
- for value in list(item["uniqueid"].values()):
+ for value in item["uniqueid"].values():
if value.startswith("tt"):
properties["imdbnumber"] = value
@@ -391,6 +387,22 @@ def prepare_listitem(item):
properties["Album_Description"] = item.get('album_description')
# pvr properties
+ if "starttime" in item:
+ # convert utc time to local time
+ item["starttime"] = utils.localdate_from_utc_string(item["starttime"])
+ item["endtime"] = utils.localdate_from_utc_string(item["endtime"])
+ # set localized versions of the time and date as additional props
+ startdate, starttime = utils.localized_date_time(item['starttime'])
+ enddate, endtime = utils.localized_date_time(item['endtime'])
+ properties["StartTime"] = starttime
+ properties["StartDate"] = startdate
+ properties["EndTime"] = endtime
+ properties["EndDate"] = enddate
+ properties["Date"] = "%s %s-%s" % (startdate, starttime, endtime)
+ properties["StartDateTime"] = "%s %s" % (startdate, starttime)
+ properties["EndDateTime"] = "%s %s" % (enddate, endtime)
+ # set date to startdate
+ item["date"] = arrow.get(item["starttime"]).format("DD.MM.YYYY")
if "channellogo" in item:
properties["channellogo"] = item["channellogo"]
properties["channelicon"] = item["channellogo"]
@@ -441,51 +453,47 @@ def prepare_listitem(item):
item["extraproperties"] = properties
- # return the result
+ if "file" not in item or not item['file']:
+ LOG.warn('No filepath for item: %s', item)
+ item["file"] = ""
+
return item
- except Exception:
- utils.ERROR(notify=True)
- LOG.error('item that caused crash: %s', item)
+ except Exception as exc:
+ LOG.error('item: %s', item)
+ LOG.exception('Exception encountered: %s', exc)
-def create_listitem(item, as_tuple=True, offscreen=True,
- listitem=xbmcgui.ListItem):
- """
- 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
- """
+def create_listitem(item, as_tuple=True, offscreen=True):
+ """helper to create a kodi listitem from kodi compatible dict with mediainfo"""
try:
- liz = listitem(
+ liz = xbmcgui.ListItem(
label=item.get("label", ""),
label2=item.get("label2", ""),
path=item['file'],
offscreen=offscreen)
+
# 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"
+ nodetype = "Music"
elif item['type'] == 'photo':
- nodetype = 'pictures'
- else:
- nodetype = 'video'
+ nodetype = 'Pictures'
# extra properties
for key, value in item["extraproperties"].items():
liz.setProperty(key, value)
- if nodetype == 'video':
+ # video infolabels
+ if nodetype == "Video":
infolabels = {
"title": item.get("title"),
+ "path": item.get("file"),
"size": item.get("size"),
"genre": item.get("genre"),
"year": item.get("year"),
@@ -516,8 +524,7 @@ def create_listitem(item, as_tuple=True, offscreen=True,
"album": item.get("album"),
"artist": item.get("artist"),
"votes": item.get("votes"),
- "trailer": item.get("trailer"),
- # "progress": item.get('progresspercentage')
+ "trailer": item.get("trailer")
}
if item["type"] == "episode":
infolabels["season"] = item["season"]
@@ -534,7 +541,8 @@ def create_listitem(item, as_tuple=True, offscreen=True,
if "date" in item:
infolabels["date"] = item["date"]
- elif nodetype == 'music':
+ # music infolabels
+ elif nodetype == 'Music':
infolabels = {
"title": item.get("title"),
"size": item.get("size"),
@@ -560,12 +568,12 @@ def create_listitem(item, as_tuple=True, offscreen=True,
"title": item.get("title"),
'picturepath': item['file']
}
+
# 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", ""]:
+ if item["type"] not in ["recording", "channel", "favourite", "genre", "categorie"]:
infolabels["mediatype"] = item["type"]
# setting the dbid on music items is not supported ?
- if nodetype == "video" and "DBID" in item["extraproperties"]:
+ if nodetype == "Video" and "DBID" in item["extraproperties"]:
infolabels["dbid"] = item["extraproperties"]["DBID"]
if "lastplayed" in item:
@@ -575,9 +583,11 @@ def create_listitem(item, as_tuple=True, offscreen=True,
liz.setInfo(type=nodetype, infoLabels=infolabels)
# artwork
- if "icon" in item:
- item['art']['icon'] = item['icon']
liz.setArt(item.get("art", {}))
+ if "icon" in item:
+ liz.setArt({"icon": item['icon']})
+ if "thumbnail" in item:
+ liz.setArt({"thumb": item['thumbnail']})
# contextmenu
if item["type"] in ["episode", "season"] and "season" in item and "tvshowid" in item:
@@ -585,20 +595,20 @@ def create_listitem(item, as_tuple=True, offscreen=True,
if "contextmenu" not in item:
item["contextmenu"] = []
item["contextmenu"] += [
- (xbmc.getLocalizedString(20364), "ActivateWindow(Video,videodb://tvshows/titles/%s/,return)"
+ (xbmc.getLocalizedString(20364), "ActivateWindow(Videos,videodb://tvshows/titles/%s/,return)"
% (item["tvshowid"])),
- (xbmc.getLocalizedString(20373), "ActivateWindow(Video,videodb://tvshows/titles/%s/%s/,return)"
+ (xbmc.getLocalizedString(20373), "ActivateWindow(Videos,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))
+ 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)
+ except Exception as exc:
+ LOG.error('item: %s', item)
+ LOG.exception('Exception encountered: %s', exc)
def create_main_entry(item):