Updated kodi settings on Lenovo

This commit is contained in:
2026-03-22 22:28:43 +01:00
parent 725dfa7157
commit 32b5a81da6
10925 changed files with 575678 additions and 5511 deletions

View File

@@ -0,0 +1,153 @@
#!/usr/bin/python
# coding: utf-8
########################
from resources.lib.helper import *
from resources.lib.json_map import *
########################
class Database(object):
def __init__(self,dbid=None,dbtype=None,append=''):
self.dbid = dbid
self.append = append
self.data = {}
if dbtype:
if dbtype in ['movie', 'tvshow', 'season', 'episode', 'musicvideo']:
library = 'Video'
self.data['nfo'] = True
elif dbtype in ['set']:
library = 'Video'
self.data['nfo'] = False
else:
library = 'Audio'
self.data['nfo'] = False
self.set_details = '%sLibrary.Set%sDetails' % (library, dbtype.replace('set', 'movieset'))
self.param = '%sid' % dbtype
def result(self):
return self.data
def write(self,key,value):
if not isinstance(key, list):
key = [key]
value = [value]
for k in key:
json_call(self.set_details,
params={'%s' % k: value[key.index(k)], self.param: int(self.dbid)},
debug=LOG_JSON
)
def movies(self):
self._items('video', 'movie')
def movie(self):
self._item('video', 'movie')
def sets(self):
self._items('video', 'set')
def set(self):
self._item('video', 'set')
def tvshows(self):
self._items('video', 'tvshow')
def tvshow(self):
self._item('video', 'tvshow')
if self.data['tvshow'] and 'episodes' in self.append:
tvshowid = self.data['tvshow'][0].get('tvshowid')
self._items('video', 'episode', {'tvshowid': int(tvshowid)})
def episode(self):
self._item('video', 'episode')
def episodes(self):
self._items('video', 'episode')
def musicvideo(self):
self._item('video', 'musicvideo')
def musicvideos(self):
self._items('video', 'musicvideo')
def artist(self):
self._item('audio', 'artist')
def artists(self):
self._items('audio', 'artist')
def album(self):
self._item('audio', 'album')
def albums(self):
self._items('audio', 'album')
def song(self):
self._item('audio', 'song')
def songs(self):
self._items('audio', 'song')
def genre(self):
movie = []
tvshow = []
musicvideo = []
music = []
video = []
audio = []
# video db
for i in ['movie', 'tvshow', 'musicvideo']:
genres = json_call('VideoLibrary.GetGenres',
properties=['title'],
params={'type': i}
)
genres = genres.get('result', {}).get('genres', [])
for genre in genres:
eval(i).append(genre.get('label'))
# audio db
genres = json_call('AudioLibrary.GetGenres',
properties=['title']
)
genres = genres.get('result', {}).get('genres', [])
for genre in genres:
music.append(genre.get('label'))
self.data['moviegenres'] = movie
self.data['tvshowgenres'] = tvshow
self.data['musicvideogenres'] = musicvideo
self.data['musicgenres'] = music
self.data['videogenres'] = list(set(movie + tvshow + musicvideo))
self.data['audiogenres'] = list(set(music + musicvideo))
def tags(self):
tags = json_call('VideoLibrary.GetTags',
properties=['title']
)
self.data['tags'] = tags.get('result', {}).get('tags', [])
def _item(self,library,dbtype):
item = json_call('%sLibrary.Get%sDetails' % (library, dbtype.replace('set', 'movieset')),
properties=JSON_MAP.get('%s_properties' % dbtype),
params={'%sid' % dbtype: int(self.dbid)}
)
self.data[dbtype] = [item.get('result', {}).get('%sdetails' % dbtype)]
def _items(self,library,dbtype,params=None,query_filter=None):
items = json_call('%sLibrary.Get%ss' % (library, dbtype.replace('set', 'movieset')),
properties=JSON_MAP.get('%ss_properties' % dbtype),
params=params,
query_filter=query_filter
)
self.data[dbtype] = items.get('result', {}).get('%ss' % dbtype, [])

View File

@@ -0,0 +1,338 @@
#!/usr/bin/python
# coding: utf-8
########################
from resources.lib.helper import *
from resources.lib.json_map import *
from resources.lib.functions import *
from resources.lib.database import *
from resources.lib.nfo_updater import *
########################
class EditDialog(object):
def __init__(self,dbid,dbtype):
winprop('SelectDialogPreselect', clear=True)
self.dbid = dbid
self.dbtype = dbtype
self.db = Database(dbid=self.dbid, dbtype=self.dbtype)
self.nfo_support = self.db.result().get('nfo')
self.status = None
self.get_details()
def get_details(self):
getattr(self.db, self.dbtype)()
self.details = self.db.result().get(self.dbtype)[0]
self.file = self.details.get('file') if self.nfo_support else False
def editor(self):
self.modeselect = []
self.keylist = []
self.presetlist = []
self.typelist = []
self.optionlist = []
self.generate_list()
self.dialog()
def set(self,key,type):
preset = self.details.get(key)
if isinstance(preset, list):
preset = get_joined_items(preset)
elif isinstance(preset, float):
preset = str(get_rounded_value(preset))
elif isinstance(preset, int):
preset = str(preset)
self._handle_dbitem(value_type=type,
key=key,
preset=preset
)
self.get_details()
self.quit()
def quit(self):
if self.file:
if self.status:
self.details['status'] = self.status
update_nfo(file=self.file,
dbtype=self.dbtype,
dbid=self.dbid,
details=self.details
)
reload_widgets()
def dialog(self):
preselect = winprop('SelectDialogPreselect')
if not preselect:
preselect = -1
if self.details.get('title'):
headline = ADDON.getLocalizedString(32040) + ' "' + self.details.get('title') + '"'
elif self.details.get('artist'):
headline = ADDON.getLocalizedString(32040) + ' "' + self.details.get('artist') + '"'
else:
headline = ADDON.getLocalizedString(32000)
self.editdialog = DIALOG.select(headline, self.modeselect, preselect=int(preselect), useDetails=True)
# Dialog closed -> write changes to nfo and exit
if self.editdialog == -1:
winprop('SelectDialogPreselect', clear=True)
self.quit()
else:
# Edit value based on the type
winprop('SelectDialogPreselect', str(self.editdialog))
self._handle_dbitem(value_type=self.typelist[self.editdialog],
key=self.keylist[self.editdialog],
preset=self.presetlist[self.editdialog],
option=self.optionlist[self.editdialog]
)
# Refetch updated data and return to entry_point to populate the changes in the dialog
self.get_details()
self.editor()
def generate_list(self):
details = self.details
uniqueid = details.get('uniqueid', {})
ratings = details.get('ratings')
votes = details.get('votes', 0)
if not votes or votes == -1:
votes = 0
# Fallback rule. Create own ratings dict if it's missing in the database.
if not ratings:
ratings = {'default': {'default': True,
'rating': details.get('rating', 0.0),
'votes': details.get('votes', 0)}
}
ratings_default = None
for item in ratings:
if ratings[item].get('default'):
ratings_value = str(get_rounded_value(ratings[item].get('rating', 0.0)))
votes_value = str(ratings[item].get('votes', '0'))
ratings_default = ratings_value + ' / ' + votes_value + ' (' + xbmc.getLocalizedString(21870) + ': ' + item + ')'
break
if not ratings_default and len(ratings) > 0:
ratings_default = ADDON.getLocalizedString(32047)
if self.dbtype == 'movie':
self._create_list(xbmc.getLocalizedString(369), 'title', value=details.get('title'), type='string')
self._create_list(xbmc.getLocalizedString(20376), 'originaltitle', value=details.get('originaltitle'), type='string')
self._create_list(xbmc.getLocalizedString(171), 'sorttitle', value=details.get('sorttitle'), type='string')
self._create_list(xbmc.getLocalizedString(345) + ' / ' + xbmc.getLocalizedString(172), 'premiered', value=details.get('premiered'), type='date')
self._create_list(xbmc.getLocalizedString(515), 'genre', value=get_joined_items(details.get('genre')), type='array')
self._create_list(xbmc.getLocalizedString(202), 'tagline', value=details.get('tagline'), type='string')
self._create_list(xbmc.getLocalizedString(207), 'plot', value=details.get('plot'), type='string')
self._create_list(xbmc.getLocalizedString(203), 'plotoutline', value=details.get('plotoutline'), type='string')
self._create_list(xbmc.getLocalizedString(20457), 'set', value=details.get('set'), type='movieset')
self._create_list(xbmc.getLocalizedString(563) + ' / ' + xbmc.getLocalizedString(205), 'ratings', value=ratings_default, type='ratings', option=ratings)
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
self._create_list(xbmc.getLocalizedString(20074), 'mpaa', value=details.get('mpaa'), type='string')
self._create_list(xbmc.getLocalizedString(20339), 'director', value=get_joined_items(details.get('director')), type='array')
self._create_list(xbmc.getLocalizedString(20417), 'writer', value=get_joined_items(details.get('writer')), type='array')
self._create_list(xbmc.getLocalizedString(21875), 'country', value=get_joined_items(details.get('country')), type='array')
self._create_list(xbmc.getLocalizedString(572), 'studio', value=get_joined_items(details.get('studio')), type='array')
self._create_list(xbmc.getLocalizedString(20459), 'tag', value=get_joined_items(details.get('tag')), type='array')
self._create_list(xbmc.getLocalizedString(20410), 'trailer', value=details.get('trailer'), type='string')
self._create_list('IMDb ID', 'uniqueid', value=uniqueid.get('imdb'), type='uniqueid', option={'type': 'imdb', 'uniqueids': uniqueid})
self._create_list('TMDb ID', 'uniqueid', value=uniqueid.get('tmdb'), type='uniqueid', option={'type': 'tmdb', 'uniqueids': uniqueid})
self._create_list(xbmc.getLocalizedString(13409), 'top250', value=str(details.get('top250')), type='integer')
self._create_list(xbmc.getLocalizedString(570), 'dateadded', value=details.get('dateadded'), type='datetime')
self._create_list(xbmc.getLocalizedString(568), 'lastplayed', value=details.get('lastplayed'), type='datetime')
self._create_list(xbmc.getLocalizedString(567), 'playcount', value=str(details.get('playcount', 0)), type='integer')
elif self.dbtype == 'tvshow':
if KODI_VERSION < 19:
status = ADDON.getLocalizedString(32022)
else:
status = details.get('status', '')
self._create_list(xbmc.getLocalizedString(369), 'title', value=details.get('title'), type='string')
self._create_list(xbmc.getLocalizedString(20376),'originaltitle', value=details.get('originaltitle'), type='string')
self._create_list(xbmc.getLocalizedString(171), 'sorttitle', value=details.get('sorttitle'), type='string')
self._create_list(xbmc.getLocalizedString(345) + ' / ' + xbmc.getLocalizedString(172), 'premiered', value=details.get('premiered'), type='date')
self._create_list(xbmc.getLocalizedString(515), 'genre', value=get_joined_items(details.get('genre')), type='array')
self._create_list(xbmc.getLocalizedString(207), 'plot', value=details.get('plot'), type='string')
self._create_list(xbmc.getLocalizedString(563) + ' / ' + xbmc.getLocalizedString(205), 'ratings', value=ratings_default, type='ratings', option=ratings)
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
self._create_list(xbmc.getLocalizedString(20074), 'mpaa', value=details.get('mpaa'), type='string')
self._create_list(xbmc.getLocalizedString(572), 'studio', value=get_joined_items(details.get('studio')), type='array')
self._create_list(xbmc.getLocalizedString(20459), 'tag', value=get_joined_items(details.get('tag')), type='array')
self._create_list(xbmc.getLocalizedString(126), 'status', value=status, type='status')
self._create_list('IMDb ID', 'uniqueid', value=uniqueid.get('imdb'), type='uniqueid', option={'type': 'imdb', 'uniqueids': uniqueid, 'episodeguide': details.get('episodeguide')})
self._create_list('TMDb ID', 'uniqueid', value=uniqueid.get('tmdb'), type='uniqueid', option={'type': 'tmdb', 'uniqueids': uniqueid, 'episodeguide': details.get('episodeguide')})
self._create_list('TVDb ID', 'uniqueid', value=uniqueid.get('tvdb'), type='uniqueid', option={'type': 'tvdb', 'uniqueids': uniqueid, 'episodeguide': details.get('episodeguide')})
self._create_list('aniDB ID', 'uniqueid', value=uniqueid.get('anidb'), type='uniqueid', option={'type': 'anidb', 'uniqueids': uniqueid, 'episodeguide': details.get('episodeguide')})
elif self.dbtype == 'episode':
self._create_list(xbmc.getLocalizedString(369), 'title', value=details.get('title'), type='string')
self._create_list(xbmc.getLocalizedString(20376), 'originaltitle', value=details.get('originaltitle'), type='string')
self._create_list(xbmc.getLocalizedString(20416), 'firstaired', value=details.get('firstaired'), type='date')
self._create_list(xbmc.getLocalizedString(207), 'plot', value=details.get('plot'), type='string')
self._create_list(xbmc.getLocalizedString(563) + ' / ' + xbmc.getLocalizedString(205), 'ratings', value=ratings_default, type='ratings', option=ratings)
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
self._create_list(xbmc.getLocalizedString(20339), 'director', value=get_joined_items(details.get('director')), type='array')
self._create_list(xbmc.getLocalizedString(20417), 'writer', value=get_joined_items(details.get('writer')), type='array')
self._create_list('IMDb ID', 'uniqueid', value=uniqueid.get('imdb'), type='uniqueid', option={'type': 'imdb', 'uniqueids': uniqueid})
self._create_list('TMDb ID', 'uniqueid', value=uniqueid.get('tmdb'), type='uniqueid', option={'type': 'tmdb', 'uniqueids': uniqueid})
self._create_list('TVDb ID', 'uniqueid', value=uniqueid.get('tvdb'), type='uniqueid', option={'type': 'tvdb', 'uniqueids': uniqueid})
self._create_list('aniDB ID', 'uniqueid', value=uniqueid.get('anidb'), type='uniqueid', option={'type': 'anidb', 'uniqueids': uniqueid})
self._create_list(xbmc.getLocalizedString(570), 'dateadded', value=details.get('dateadded'), type='datetime')
self._create_list(xbmc.getLocalizedString(568), 'lastplayed', value=details.get('lastplayed'), type='datetime')
self._create_list(xbmc.getLocalizedString(567), 'playcount', value=str(details.get('playcount', 0)), type='integer')
elif self.dbtype == 'set':
self._create_list(xbmc.getLocalizedString(369), 'title', value=details.get('title'), type='string')
self._create_list(xbmc.getLocalizedString(207), 'plot', value=details.get('plot'), type='string')
elif self.dbtype == 'musicvideo':
self._create_list(xbmc.getLocalizedString(369), 'title', value=details.get('title'), type='string')
self._create_list(xbmc.getLocalizedString(557), 'artist', value=get_joined_items(details.get('artist')), type='array')
self._create_list(xbmc.getLocalizedString(558), 'album', value=details.get('album'), type='string')
self._create_list(xbmc.getLocalizedString(345) + ' / ' + xbmc.getLocalizedString(172), 'premiered', value=details.get('premiered'), type='date')
self._create_list(xbmc.getLocalizedString(554), 'track', value=str(details.get('track')), type='integer')
self._create_list(xbmc.getLocalizedString(207), 'plot', value=details.get('plot'), type='string')
self._create_list(xbmc.getLocalizedString(515), 'genre', value=get_joined_items(details.get('genre')), type='array')
self._create_list(xbmc.getLocalizedString(20339), 'director', value=get_joined_items(details.get('director')), type='array')
self._create_list(xbmc.getLocalizedString(572), 'studio', value=get_joined_items(details.get('studio')), type='array')
# self._create_list(xbmc.getLocalizedString(563), 'rating', value=str(get_rounded_value(details.get('rating'))), type='integer') broken in kodi? cannot be set
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
self._create_list(xbmc.getLocalizedString(20459), 'tag', value=get_joined_items(details.get('tag')), type='array')
self._create_list(xbmc.getLocalizedString(570), 'dateadded', value=details.get('dateadded'), type='datetime')
self._create_list(xbmc.getLocalizedString(568), 'lastplayed', value=details.get('lastplayed'), type='datetime')
self._create_list(xbmc.getLocalizedString(567), 'playcount', value=str(details.get('playcount', 0)), type='integer')
elif self.dbtype == 'artist':
self._create_list(xbmc.getLocalizedString(515), 'genre', value=get_joined_items(details.get('genre')), type='array')
self._create_list(xbmc.getLocalizedString(21821), 'description', value=details.get('description'), type='string')
self._create_list(xbmc.getLocalizedString(39026), 'disambiguation', value=details.get('disambiguation'), type='string')
self._create_list(xbmc.getLocalizedString(736), 'style', value=get_joined_items(details.get('style')), type='array')
self._create_list(xbmc.getLocalizedString(175), 'mood', value=get_joined_items(details.get('mood')), type='array')
self._create_list(xbmc.getLocalizedString(21892), 'instrument', value=get_joined_items(details.get('instrument')), type='array')
self._create_list(xbmc.getLocalizedString(21893), 'born', value=details.get('born'), type='string')
self._create_list(xbmc.getLocalizedString(21897), 'died', value=details.get('died'), type='string')
self._create_list(xbmc.getLocalizedString(21894), 'formed', value=details.get('formed'), type='string')
self._create_list(xbmc.getLocalizedString(21896), 'disbanded', value=details.get('disbanded'), type='string')
self._create_list(xbmc.getLocalizedString(21898), 'yearsactive', value=get_joined_items(details.get('yearsactive')), type='array')
elif self.dbtype == 'album':
self._create_list(ADDON.getLocalizedString(32023), 'albumlabel', value=details.get('albumlabel'), type='string')
self._create_list(xbmc.getLocalizedString(21821), 'description', value=details.get('description'), type='string')
self._create_list(xbmc.getLocalizedString(345), 'year', value=str(details.get('year')), type='integer')
self._create_list(xbmc.getLocalizedString(467), 'type', value=details.get('type'), type='string')
self._create_list(xbmc.getLocalizedString(515), 'genre', value=get_joined_items(details.get('genre')), type='array')
self._create_list(xbmc.getLocalizedString(15111), 'theme', value=get_joined_items(details.get('theme')), type='array')
self._create_list(xbmc.getLocalizedString(175), 'mood', value=get_joined_items(details.get('mood')), type='array')
self._create_list(xbmc.getLocalizedString(736), 'style', value=get_joined_items(details.get('style')), type='array')
self._create_list(xbmc.getLocalizedString(563), 'rating', value=str(get_rounded_value(details.get('rating'))), type='float')
self._create_list(xbmc.getLocalizedString(205), 'votes', value=str(votes), type='integer')
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
elif self.dbtype == 'song':
self._create_list(xbmc.getLocalizedString(563), 'rating', value=str(get_rounded_value(details.get('rating'))), type='float')
#self._create_list(xbmc.getLocalizedString(205), 'votes', value=str(details.get('votes')), type='integer') not available in methods.json? DaveBlake will fix it.
self._create_list(ADDON.getLocalizedString(32001), 'userrating', value=str(details.get('userrating')), type='userrating')
self._create_list(xbmc.getLocalizedString(568), 'lastplayed', value=details.get('lastplayed'), type='datetime')
self._create_list(xbmc.getLocalizedString(567), 'playcount', value=str(details.get('playcount', 0)), type='integer')
def _create_list(self,label,key,type,value,option=None):
if type in ['uniqueid', 'status', 'movieset']:
icon = 'string'
elif type == ('userrating'):
icon = 'integer'
elif type.startswith('date'):
icon = 'date'
elif type.startswith('rating'):
icon = 'float'
else:
icon = type
li_item = xbmcgui.ListItem(label=label, label2='n/a' if not value else value)
li_item.setArt({'icon': 'special://home/addons/script.metadata.editor/resources/media/icon_%s.png' % icon})
self.modeselect.append(li_item)
self.keylist.append(key)
self.typelist.append(type)
self.optionlist.append(option)
self.presetlist.append('' if not value else value)
def _handle_dbitem(self,key,value_type,preset=None,option=None):
if preset:
preset = preset.replace('n/a','')
if value_type == 'array':
value = set_array(self.dbtype, key, preset)
elif value_type == 'select':
value = modify_array(self.dbtype, key, preset)
elif value_type == 'string':
value = set_string(preset)
elif value_type == 'integer':
value = set_integer(preset)
elif value_type == 'float':
value = set_float(preset)
elif value_type == 'date':
value = set_date(preset)
elif value_type == 'datetime':
preset = preset.split(' ') if preset else ['', '']
date = set_date(preset[0])
time = set_time(preset[1][:-3])
value = date + ' ' + time + ':00'
elif value_type == 'userrating':
value = set_integer_range(preset, 11)
elif value_type == 'ratings':
value = set_ratings(option)
elif value_type == 'status':
value = set_status(preset)
self.status = value
elif value_type == 'watchlist':
value = toggle_tag(preset)
elif value_type == 'movieset':
value = set_movieset(preset)
elif value_type == ('uniqueid'):
returned_value = set_string(preset)
returned_value_json = returned_value if returned_value else None
returned_value_str = returned_value if returned_value else ''
uniqueid_key = option.get('type')
uniqueids = option.get('uniqueids')
value = {uniqueid_key: returned_value_json}
# build nfo info
updated_dict = {}
for item in uniqueids:
if item == uniqueid_key:
updated_dict[item] = returned_value_json
else:
updated_dict[item] = uniqueids.get(item)
if uniqueid_key not in updated_dict:
updated_dict[uniqueid_key] = returned_value_str
nfo_value = [updated_dict, option.get('episodeguide')]
self.db.write(key=key, value=value)

View File

@@ -0,0 +1,321 @@
#!/usr/bin/python
# coding: utf-8
########################
from resources.lib.helper import *
from resources.lib.database import *
########################
def set_ratings(ratings):
providerlist = []
for item in ratings:
providerlist.append(str(item))
preselect = -1
for item in providerlist:
if ratings[item].get('default'):
preselect = providerlist.index(item)
menu = DIALOG.select(xbmc.getLocalizedString(424), [ADDON.getLocalizedString(32015), ADDON.getLocalizedString(32016), ADDON.getLocalizedString(32017)])
if menu == 0: # set default provider
providerdefault = DIALOG.select(ADDON.getLocalizedString(32014), providerlist, preselect=preselect)
if providerdefault >= 0:
name = providerlist[providerdefault]
for item in ratings:
default = True if item == name else False
ratings[item] = {'default': default,
'rating': ratings[item].get('rating'),
'votes': ratings[item].get('votes')}
elif menu == 1: # edit votes/rating
providerratings = DIALOG.select(ADDON.getLocalizedString(32012), providerlist, preselect=preselect)
if providerratings >= 0:
name = providerlist[providerratings]
cur_rating = round(ratings[name].get('rating', 0.0), 1)
cur_votes = ratings[name].get('votes', 0)
rating = set_float(cur_rating)
votes = set_integer(cur_votes)
if not rating:
rating = 0.0
if not votes:
votes = 0
ratings[name] = {'default': ratings[name].get('default'),
'rating': rating,
'votes': votes}
elif menu == 2: # add new rating provider
supportedlist = ['imdb', 'themoviedb', 'tomatometerallcritics', 'tomatometeravgcritics', 'tomatometerallaudience', 'tomatometeravgaudience', 'metacritic']
for item in supportedlist:
if item in providerlist:
supportedlist.remove(item)
newprovider = DIALOG.select(ADDON.getLocalizedString(32013), supportedlist)
if newprovider >= 0:
name = supportedlist[newprovider]
rating = set_float(heading='Enter rating (floating number - min 0.1 / max 10.0)')
if not rating or float(rating) > 10:
DIALOG.ok(xbmc.getLocalizedString(257), ADDON.getLocalizedString(32018))
else:
votes = set_integer()
if not votes:
votes = 0
if not DIALOG.yesno(ADDON.getLocalizedString(32019), ADDON.getLocalizedString(32020)):
default = False
else:
default = True
for item in ratings:
ratings[item] = {'default': False,
'rating': ratings[item].get('rating'),
'votes': ratings[item].get('votes')}
ratings[name] = {'default': default,
'rating': rating,
'votes': votes}
return ratings
def set_movieset(preset):
db = Database()
db.sets()
sets = db.result().get('set', [])
selectlist = []
for item in sets:
selectlist.append(item.get('title'))
selectlist.sort()
selectlist.insert(0, xbmc.getLocalizedString(231))
selectlist.insert(1, ADDON.getLocalizedString(32005))
preselect = selectlist.index(preset) if preset in selectlist else -1
selectdialog = DIALOG.select(xbmc.getLocalizedString(20466), selectlist, preselect=preselect)
if selectdialog == 0:
return ''
elif selectdialog == 1:
value = set_string()
return value
elif selectdialog > 1:
return selectlist[selectdialog]
return preset
def set_array(dbtype,key,preset):
actionlist = [ADDON.getLocalizedString(32005), ADDON.getLocalizedString(32007), ADDON.getLocalizedString(32006)]
array_action = DIALOG.select(xbmc.getLocalizedString(14241), actionlist)
array_list = get_list_items(preset)
if array_action == 0:
keyboard = xbmc.Keyboard()
keyboard.doModal()
if keyboard.isConfirmed():
new_item = keyboard.getText()
if new_item not in array_list:
array_list.append(new_item)
return remove_empty(array_list)
elif array_action == 1:
array = modify_array(dbtype, key, array_list)
return array
elif array_action == 2:
keyboard = xbmc.Keyboard(preset)
keyboard.doModal()
if keyboard.isConfirmed():
array = keyboard.getText()
else:
array = preset
return get_list_items(array)
else:
return array_list
def modify_array(dbtype,key,values):
modified = []
all_values = []
if not isinstance(values, list):
values = get_list_items(values)
if key in ['genre', 'tags']:
db = Database()
getattr(db, key)()
result = db.result()
if key == 'genre':
if dbtype in ['musicvideo', 'artist', 'album']:
for genre in result.get('audiogenres'):
all_values.append(genre)
else:
for genre in result.get('videogenres'):
all_values.append(genre)
elif key == 'tags':
for tag in result.get('tags'):
if tag not in values:
all_values.append(tag)
all_values = list(set(values + all_values))
all_values.sort()
values.sort()
# open common array dialog if all_values are empty
if not all_values:
notification(ADDON.getLocalizedString(32000), ADDON.getLocalizedString(32048))
value = set_array(dbtype, key, '')
return value
preselectlist = []
for item in values:
preselectlist.append(all_values.index(item))
selectdialog = DIALOG.multiselect(ADDON.getLocalizedString(32002), all_values, preselect=preselectlist)
if selectdialog == -1 or selectdialog is None:
return values
for index in selectdialog:
modified.append(all_values[index])
return modified
def set_integer(preset=''):
preset = str(preset)
if preset == '0':
preset = ''
value = xbmcgui.Dialog().numeric(0, xbmc.getLocalizedString(16028), preset)
if not value:
return None
return int(value)
def set_float(preset='',heading=ADDON.getLocalizedString(32011)):
try:
preset = float(preset)
preset = round(preset,1)
except Exception:
preset = ''
keyboard = xbmc.Keyboard(str(preset))
keyboard.setHeading(heading)
keyboard.doModal()
if keyboard.isConfirmed():
try:
value = float(keyboard.getText())
value = round(value,1)
return value
except Exception:
set_float(preset)
return preset
def set_date(preset):
try:
conv = time.strptime(preset,'%Y-%m-%d')
conv = time.strftime('%d/%m/%Y', conv)
except Exception:
conv = '01/01/1900'
value = xbmcgui.Dialog().numeric(1, xbmc.getLocalizedString(16028), conv)
if value:
value = value.replace(' ','0')
value = time.strptime(value,'%d/%m/%Y')
value = time.strftime('%Y-%m-%d',value)
return value
return preset
def set_time(preset):
value = xbmcgui.Dialog().numeric(2, xbmc.getLocalizedString(16028), preset)
if value:
return value
return preset
def set_string(preset=''):
value = preset.replace('\n', '[CR]')
keyboard = xbmc.Keyboard(value)
keyboard.doModal()
if keyboard.isConfirmed():
value = keyboard.getText()
return value.replace('[CR]', '\n')
def set_integer_range(preset, maximum):
preset = int(preset) if preset else 0
rangelist = []
for i in range(0, maximum):
rangelist.append(str(i))
value = DIALOG.select(xbmc.getLocalizedString(424), rangelist, preselect=preset)
if value >= 0:
return value
return preset
def set_status(preset):
statuslist = ['Returning series', 'In production', 'Planned', 'Cancelled', 'Ended']
if preset == ADDON.getLocalizedString(32022):
preset = ''
value = DIALOG.select(xbmc.getLocalizedString(126), statuslist)
if value >= 0:
return statuslist[value]
return preset
def toggle_tag(preset):
tag = 'Watchlist'
tags = get_list_items(preset)
if tag in tags:
tags.remove(tag)
else:
tags.append(tag)
return tags

View File

@@ -0,0 +1,250 @@
#!/usr/bin/python
# coding: utf-8
########################
import sys
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
import xbmcplugin
import json
import time
import datetime
import os
import hashlib
import xml.etree.ElementTree as ET
import requests
import urllib.request as urllib
from urllib.parse import urlencode
from contextlib import contextmanager
########################
ADDON = xbmcaddon.Addon()
ADDON_ID = ADDON.getAddonInfo('id')
ADDON_DATA_PATH = os.path.join(xbmcvfs.translatePath("special://profile/addon_data/%s" % ADDON_ID))
NOTICE = xbmc.LOGINFO
WARNING = xbmc.LOGWARNING
DEBUG = xbmc.LOGDEBUG
ERROR = xbmc.LOGERROR
LOG_JSON = ADDON.getSettingBool('json_log')
KODI_VERSION = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
DIALOG = xbmcgui.Dialog()
########################
def log(txt,loglevel=DEBUG,json=False,force=False):
if loglevel in [DEBUG, WARNING, ERROR] or force:
if force:
loglevel = NOTICE
if json:
txt = json_prettyprint(txt)
message = u'[ %s ] %s' % (ADDON_ID,txt)
xbmc.log(msg=message, level=loglevel)
def unicode_string(string):
string = u'%s' % string
return string
def remove_quotes(label):
if not label:
return ''
if label.startswith("'") and label.endswith("'") and len(label) > 2:
label = label[1:-1]
if label.startswith('"') and label.endswith('"') and len(label) > 2:
label = label[1:-1]
elif label.startswith('&quot;') and label.endswith('&quot;'):
label = label[6:-6]
return label
def get_joined_items(item):
if len(item) > 0 and item is not None:
item = '; '.join(item)
item = item + ';'
else:
item = ''
return item
def get_list_items(string):
return remove_empty(string.replace('; ',';').split(';'))
def get_key_item(items,key):
try:
return items.get(key)
except Exception:
return
def get_rounded_value(value):
try:
if not isinstance(value, str) and not isinstance(value, float):
value = str(value)
if not isinstance(value, float):
value = float(value)
return round(value,1)
except Exception:
return
def remove_empty(array):
cleaned_array = []
for item in array:
if not item or item in ['', ';']:
continue
cleaned_array.append(item)
return cleaned_array
def execute(cmd):
xbmc.executebuiltin(cmd)
def condition(condition):
return xbmc.getCondVisibility(condition)
def winprop(key, value=None, clear=False, window_id=10000):
window = xbmcgui.Window(window_id)
if clear:
window.clearProperty(key.replace('.json', '').replace('.bool', '').replace('.str', ''))
elif value is not None:
if key.endswith('.json'):
key = key.replace('.json', '')
value = json.dumps(value)
elif key.endswith('.bool'):
key = key.replace('.bool', '')
value = 'true' if value else 'false'
elif key.endswith('.str'):
key = key.replace('.str', '')
value = str(value)
window.setProperty(key, value)
else:
result = window.getProperty(key.replace('.json', '').replace('.bool', '').replace('.str', ''))
if result:
if key.endswith('.json'):
result = json.loads(result)
elif key.endswith('.bool'):
result = result in ('true', '1')
elif key.endswith('.str'):
result = eval(result)
return result
def json_call(method,properties=None,sort=None,query_filter=None,limit=None,params=None,item=None,options=None,limits=None,debug=False):
json_string = {'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': {}}
if properties is not None:
json_string['params']['properties'] = properties
if limit is not None:
json_string['params']['limits'] = {'start': 0, 'end': int(limit)}
if sort is not None:
json_string['params']['sort'] = sort
if query_filter is not None:
json_string['params']['filter'] = query_filter
if options is not None:
json_string['params']['options'] = options
if limits is not None:
json_string['params']['limits'] = limits
if item is not None:
json_string['params']['item'] = item
if params is not None:
json_string['params'].update(params)
jsonrpc_call = json.dumps(json_string)
result = xbmc.executeJSONRPC(jsonrpc_call)
result = json.loads(result)
if debug:
log('--> JSON CALL: ' + json_prettyprint(json_string), force=True)
log('--> JSON RESULT: ' + json_prettyprint(result), force=True)
return result
def json_prettyprint(string):
return json.dumps(string, sort_keys=True, indent=4, separators=(',', ': '))
def xml_prettyprint(root,level=0):
i = '\n' + level * ' '
if len(root):
if not root.text or not root.text.strip():
root.text = i + ' '
if not root.tail or not root.tail.strip():
root.tail = i
for root in root:
xml_prettyprint(root, level+1)
if not root.tail or not root.tail.strip():
root.tail = i
else:
if level and (not root.tail or not root.tail.strip()):
root.tail = i
def notification(header=ADDON.getLocalizedString(32000),message=''):
DIALOG.notification(header, message, icon='special://home/addons/script.metadata.editor/resources/icon.png')
def reload_widgets():
# Notifies script.embuary.helper to reload widgets
execute('NotifyAll(%s,Finished)' % ADDON_ID)
@contextmanager
def busy_dialog(force=False):
if force:
execute('ActivateWindow(busydialognocancel)')
elif not winprop('UpdatingRatings.bool'):
# NFO writing usually only takes < 1s. Just show BusyDialog if it takes longer for whatever reason.
execute('AlarmClock(BusyAlarmDelay,ActivateWindow(busydialognocancel),00:02,silent)')
try:
yield
finally:
execute('CancelAlarm(BusyAlarmDelay,silent)')
execute('Dialog.Close(busydialognocancel)')

View File

@@ -0,0 +1,267 @@
#!/usr/bin/python
JSON_MAP = {
'movie_properties': [
'title',
'originaltitle',
'sorttitle',
'votes',
'playcount',
'year',
'genre',
'studio',
'country',
'tagline',
'tag',
'plot',
'runtime',
'premiered',
'file',
'plotoutline',
'lastplayed',
'trailer',
'rating',
'ratings',
'userrating',
'resume',
'art',
'mpaa',
'director',
'writer',
'cast',
'set',
'setid',
'top250',
'dateadded',
'imdbnumber',
'uniqueid'
],
'movies_properties': [
'title',
'year'
],
'set_properties': [
'title',
'plot'
],
'sets_properties': [
'title'
],
'episode_properties': [
'title',
'playcount',
'season',
'episode',
'showtitle',
'originaltitle',
'plot',
'votes',
'file',
'rating',
'ratings',
'userrating',
'resume',
'tvshowid',
'firstaired',
'art',
'runtime',
'director',
'writer',
'cast',
'dateadded',
'lastplayed',
'uniqueid'
],
'episodes_properties': [
'title',
'showtitle'
],
'season_properties': [
'season',
'episode',
'art',
'userrating',
'watchedepisodes',
'showtitle',
'playcount',
'tvshowid'
],
'seasons_properties': [
'season',
'showtitle',
'tvshowid'
],
'tvshow_properties': [
'title',
'studio',
'year',
'plot',
'cast',
'rating',
'ratings',
'userrating',
'votes',
'file',
'genre',
'episode',
'season',
'runtime',
'mpaa',
'premiered',
'playcount',
'lastplayed',
'sorttitle',
'originaltitle',
'episodeguide',
'art',
'tag',
'dateadded',
'watchedepisodes',
'imdbnumber',
'uniqueid'
],
'tvshows_properties': [
'title',
'year'
],
'musicvideo_properties': [
'title',
'playcount',
'runtime',
'director',
'studio',
'year',
'plot',
'album',
'artist',
'genre',
'track',
'lastplayed',
'fanart',
'thumbnail',
'file',
'resume',
'dateadded',
'tag',
'art',
'rating',
'userrating',
'premiered'
],
'musicvideos_properties': [
'title',
'year'
],
'artist_properties': [
'instrument',
'style',
'mood',
'born',
'formed',
'description',
'genre',
'died',
'disbanded',
'yearsactive',
'musicbrainzartistid',
'fanart',
'thumbnail',
'compilationartist',
'dateadded',
'roles',
'songgenres',
'isalbumartist',
'disambiguation'
],
'artists_properties': [
'dateadded'
],
'album_properties': [
'title',
'description',
'artist',
'genre',
'theme',
'mood',
'style',
'type',
'albumlabel',
'rating',
'votes',
'userrating',
'year',
'musicbrainzalbumid',
'musicbrainzalbumartistid',
'fanart',
'thumbnail',
'playcount',
'artistid',
'displayartist',
'compilation',
'releasetype',
'dateadded'
],
'albums_properties': [
'title',
'year'
],
'song_properties': [
'title',
'artist',
'albumartist',
'genre',
'year',
'rating',
'album',
'track',
'duration',
'comment',
'lyrics',
'musicbrainztrackid',
'musicbrainzartistid',
'musicbrainzalbumid',
'musicbrainzalbumartistid',
'playcount',
'fanart',
'thumbnail',
'file',
'albumid',
'lastplayed',
'disc',
'genreid',
'artistid',
'displayartist',
'albumartistid',
'albumreleasetype',
'dateadded',
'votes',
'userrating',
'mood',
'contributors',
'displaycomposer',
'displayconductor',
'displayorchestra',
'displaylyricist'
],
'songs_properties': [
'title',
'artist'
]
}

View File

@@ -0,0 +1,337 @@
#!/usr/bin/python
# coding: utf-8
########################
from resources.lib.helper import *
from resources.lib.database import *
########################
def update_nfo(dbtype,dbid,details=None,file=None, forced=False):
if not forced and not ADDON.getSettingBool('nfo_updating'):
winprop('updatenfo', clear=True)
return
if not details:
db = Database(dbid, dbtype)
getattr(db, dbtype)()
details = db.result().get(dbtype)[0]
if not details:
log('NFO updater: No item details found or provided --> ID: %s Type: %s' % (dbid, dbtype), ERROR)
return
if not file:
file = details.get('file')
if not file:
log('NFO updater: No item path available --> ID: %s Type: %s' % (dbid, dbtype), ERROR)
return
if dbtype == 'tvshow':
path = os.path.join(file,'tvshow.nfo')
else:
path = file.replace(os.path.splitext(file)[1], '.nfo')
UpdateNFO(file=path,
dbtype=dbtype,
dbid=dbid,
details=details)
# support for additional movie.nfo
if dbtype == 'movie':
path = file.replace(os.path.basename(file), 'movie.nfo')
if xbmcvfs.exists(path):
UpdateNFO(file=path,
dbtype=dbtype,
dbid=dbid,
details=details)
class UpdateNFO():
def __init__(self,file,dbtype,dbid,details):
self.targetfile = file
self.dbtype = dbtype
self.dbid = dbid
self.details = details
self.root = None
self.sortlist = []
self.run()
def run(self):
with busy_dialog():
try:
if xbmcvfs.exists(self.targetfile):
self.root = self.read_file()
self.existing_nfo = True
elif ADDON.getSettingBool('create_nfo'):
self.root = ET.Element(self.dbtype.replace('episode', 'episodedetails'))
self.existing_nfo = False
else:
raise Exception('File not found')
self.handle_details()
self.write_file()
success = True
except Exception as error:
log('Cannot process .nfo file: %s --> %s' % (self.targetfile, error), ERROR)
success = False
if winprop('updatenfo.bool'):
msg = xbmc.getLocalizedString(20177) if success else xbmc.getLocalizedString(257)
notification(ADDON.getLocalizedString(32046), msg)
winprop('updatenfo', clear=True)
def read_file(self):
file = xbmcvfs.File(self.targetfile)
content = file.read()
file.close()
if content:
tree = ET.ElementTree(ET.fromstring(content))
root = tree.getroot()
return root
def write_file(self):
# sort nfo
if self.existing_nfo:
index = 0
for key in self.sortlist:
for elem in self.root.findall(key):
self.root.remove(elem)
self.root.insert(index, elem)
index += 1
xml_prettyprint(self.root)
content = ET.tostring(self.root, encoding='UTF8', method='xml').decode()
with xbmcvfs.File(self.targetfile, 'w') as f:
result = f.write(content)
def handle_details(self):
li = [{'key': 'title', 'value': self.details.get('title')},
{'key': 'originaltitle', 'value': self.details.get('originaltitle')},
{'key': 'showtitle', 'value': self.details.get('showtitle')},
{'key': 'sorttitle', 'value': self.details.get('sorttitle')},
{'key': 'userrating', 'value': self.details.get('userrating')},
{'key': 'outline', 'value': self.details.get('plotoutline')},
{'key': 'plot', 'value': self.details.get('plot')},
{'key': 'tagline', 'value': self.details.get('tagline')},
{'key': 'mpaa', 'value': self.details.get('mpaa')},
{'key': 'premiered', 'value': self.details.get('premiered')},
{'key': 'releasedate', 'value': self.details.get('releasedate')}, #emby
{'key': 'year', 'value': self.details.get('premiered', '')[:4]}, #emby
{'key': 'country', 'value': self.details.get('country')},
{'key': 'studio', 'value': self.details.get('studio')},
{'key': 'director', 'value': self.details.get('director')},
{'key': 'credits', 'value': self.details.get('writer')},
{'key': 'writer', 'value': self.details.get('writer')}, #emby
{'key': 'tag', 'value': self.details.get('tag')},
{'key': 'isuserfavorite', 'value': 'true' if 'Favorite movies' in self.details.get('tag', []) or 'Favorite tvshows' in self.details.get('tag', []) else 'false'}, #emby
{'key': 'genre', 'value': self.details.get('genre')},
{'key': 'top250', 'value': self.details.get('top250')},
{'key': 'ratings', 'value': self.details.get('ratings')},
{'key': 'uniqueid', 'value': self.details.get('uniqueid')},
{'key': 'status', 'value': self.details.get('status')},
{'key': 'aired', 'value': self.details.get('firstaired')},
{'key': 'dateadded', 'value': self.details.get('dateadded')}
]
if ADDON.getSettingBool('write_watched_stated'):
li.append({'key': 'playcount', 'value': self.details.get('playcount')})
li.append({'key': 'watched', 'value': 'true' if self.details.get('playcount', 0) > 0 else 'false'}) #emby
li.append({'key': 'lastplayed', 'value': self.details.get('lastplayed')})
for item in li:
key = item.get('key')
value = item.get('value')
self.sortlist.append(key)
if key == 'ratings':
self.handle_ratings(value)
elif key == 'uniqueid':
self.handle_uniqueid(value, self.details.get('episodeguide', ''))
else:
self.handle_elem(key, value)
def handle_elem(self,key,value):
if key != 'status' or (key == 'status' and value): # Keep status value. Required for leia because status isn't returned thru json.
for elem in self.root.findall(key):
self.root.remove(elem)
if isinstance(value, list):
for i in value:
if i:
elem = ET.SubElement(self.root, key)
elem.text = unicode_string(i)
elif value:
elem = ET.SubElement(self.root, key)
elem.text = unicode_string(value)
def handle_ratings(self,value):
for elem in self.root.findall('ratings'):
self.root.remove(elem)
elem = ET.SubElement(self.root, 'ratings')
for item in value:
rating = float(value[item].get('rating', 0.0))
rating = str(round(rating, 1))
votes = str(value[item].get('votes', 0))
subelem = ET.SubElement(elem, 'rating')
subelem.set('name', item)
subelem.set('max', '10')
if value[item].get('default'):
subelem.set('default', 'true')
# Emby <votes>, <rating>
for key in ['rating', 'votes']:
for defaultelem in self.root.findall(key):
self.root.remove(defaultelem)
defaultelem = ET.SubElement(self.root, key)
defaultelem.text = eval(key)
self.sortlist.append(key)
else:
subelem.set('default', 'false')
rating_elem = ET.SubElement(subelem, 'value')
rating_elem.text = rating
votes_elem = ET.SubElement(subelem, 'votes')
votes_elem.text = votes
# Emby <criticrating> Rotten ratings
if item == 'tomatometerallcritics':
normalized_rating = int(float(rating) * 10)
if normalized_rating > 100:
normalized_rating = ''
for emby_elem in self.root.findall('criticrating'):
self.root.remove(emby_elem)
emby_rotten = ET.SubElement(self.root, 'criticrating')
emby_rotten.text = str(normalized_rating)
self.sortlist.append('criticrating')
def handle_uniqueid(self,uniqueids,episodeguide):
# find default uniqueid
default = ''
if 'tvdb' in episodeguide:
default = 'tvdb'
elif 'tmdb' in episodeguide:
default = 'tmdb'
else:
for elem in self.root.findall('uniqueid'):
if elem.get('default'):
default = elem.get('type')
break
# set fallback default uniqueid
if not default:
if self.dbtype == 'movie':
if uniqueids.get('tmdb'):
default = 'tmdb'
elif uniqueids.get('imdb'):
default = 'imdb'
elif self.dbtype == 'tvshow':
scraper_default = ADDON.getSetting('tv_scraper_base')
if (scraper_default == 'TVDb' and uniqueids.get('tvdb')):
default = 'tvdb'
elif scraper_default == 'TMDb' and uniqueids.get('tmdb'):
default = 'tmdb'
# <uniqueid> fields
for elem in self.root.findall('uniqueid'):
self.root.remove(elem)
for item in uniqueids:
value = uniqueids.get(item, '')
if value:
elem = ET.SubElement(self.root, 'uniqueid')
elem.set('type', item)
elem.text = value
if default == item:
elem.set('default', 'true')
if self.dbtype == 'tvshow':
self._set_episodeguide(item, value)
self.sortlist.append(item)
# Emby <imdbid>, <tmdbid>, etc.
emby_uniqueids = {}
for item in uniqueids:
if item == 'imdb':
emby_uniqueids['imdbid'] = uniqueids.get(item)
emby_uniqueids['imdb_id'] = uniqueids.get(item) # emby is using a underscore for tvshows Oo
elif item == 'tmdbcollection':
emby_uniqueids['collectionnumber'] = uniqueids.get(item)
elif item.lower() in ['zap2it', 'tvrage', 'tvdb', 'tmdb']:
emby_uniqueids['%sid' % item] = uniqueids.get(item)
for item in emby_uniqueids:
for elem in self.root.findall(item):
self.root.remove(elem)
value = emby_uniqueids.get(item)
if value:
elem = ET.SubElement(self.root, item)
elem.text = value
self.sortlist.append(item)
def _set_episodeguide(self,type,value):
post = False
cache = ''
if type == 'tvdb':
post = 'yes'
cache = 'auth.json'
url = 'https://api.thetvdb.com/login?{"apikey":"439DFEBA9D3059C6","id":%s}|Content-Type=application/json' % str(value)
json_value = '<episodeguide><url post="%s" cache="%s"><url>%s</url></episodeguide>' % (post, cache, url)
elif type == 'tmdb':
language = ADDON.getSetting('tmdb_language')
cache = 'tmdb-%s-%s.json' % (str(value), language)
url = 'http://api.themoviedb.org/3/tv/%s?api_key=6a5be4999abf74eba1f9a8311294c267&amp;language=%s' % (str(value), language)
json_value = '<episodeguide><url cache="%s"><url>%s</url></episodeguide>' % (cache, url)
else:
url = ''
json_value = '<episodeguide><url cache=""><url></url></episodeguide>'
for elem in self.root.findall('episodeguide'):
self.root.remove(elem)
episodeguide_elem = ET.SubElement(self.root, 'episodeguide')
url_elem = ET.SubElement(episodeguide_elem, 'url')
if post:
url_elem.set('post', post)
url_elem.set('cache', cache)
url_elem.text = url
self.sortlist.append('episodeguide')
json_call('VideoLibrary.SetTVShowDetails',
params={'episodeguide': json_value, 'tvshowid': int(self.dbid)},
debug=LOG_JSON
)

View File

@@ -0,0 +1,601 @@
#!/usr/bin/python
# coding: utf-8
########################
from __future__ import division
from resources.lib.helper import *
from resources.lib.functions import *
from resources.lib.database import *
from resources.lib.nfo_updater import *
########################
RUN_IN_BACKGROUND = ADDON.getSettingBool('update_background')
BUSYDIALOG = False if RUN_IN_BACKGROUND else True
OMDB_FALLBACK = ADDON.getSettingBool('omdb_fallback_search')
OMDB_API = ADDON.getSetting('omdb_api_key')
COUNTRY_CODE = ADDON.getSetting('country_code')
SKIP_MPAA = ADDON.getSettingBool('mpaa_skip')
SKIP_NOT_RATED = ADDON.getSettingBool('mpaa_skip_nr')
MPAA_FALLBACK = ADDON.getSettingBool('mpaa_fallback')
TMDB_LANGUAGE = ADDON.getSetting('tmdb_language')
RATING_DEBUG = ADDON.getSetting('debug_rating_updater')
########################
def update_ratings(dbid=None,dbtype=None,content=None):
# no omdb API key message
if not OMDB_API:
if not DIALOG.yesno(xbmc.getLocalizedString(14117), ADDON.getLocalizedString(32035)):
return
winprop('UpdatingRatings.bool', True)
msg_text = xbmc.getLocalizedString(19256)
# get database ids
if isinstance(dbtype, str):
dbtype = dbtype.split('+')
with busy_dialog(force=BUSYDIALOG):
db = Database(dbid=dbid, append=['episodes'])
for i in dbtype:
getattr(db, i)()
result = db.result()
# calc total items to process
total_items = 0
for i in result:
if result.get(i):
total_items = total_items + len(result[i])
if total_items > 1:
# show progress if 1< will be processed
progressdialog = ProgressDialog(total_items)
for i in result:
if i == 'movie':
cat = xbmc.getLocalizedString(20338)
elif i == 'tvshow':
cat = xbmc.getLocalizedString(20364)
elif i == 'episode':
cat = xbmc.getLocalizedString(20359)
for item in result[i]:
if progressdialog.canceled():
break
if item.get('showtitle') and item.get('label'):
label = item.get('showtitle') + ' - ' + item.get('label')
else:
label = item.get('title')
if item.get('year'):
label = label + ' (' + str(item.get('year')) + ')'
progressdialog.update(cat, label)
UpdateRating({'dbid': item.get('%sid' % i),
'type': i})
#xbmc.sleep(50)
if progressdialog.canceled():
msg_text = ADDON.getLocalizedString(32042)
break
progressdialog.close()
elif total_items == 1:
# process single item
for i in result:
UpdateRating({'dbid': result[i][0].get('%sid' % i),
'type': i})
else:
# error message
msg_text = ADDON.getLocalizedString(32048)
winprop('UpdatingRatings', clear=True)
notification(ADDON.getLocalizedString(32030), msg_text)
class ProgressDialog(object):
def __init__(self,total_items):
if RUN_IN_BACKGROUND:
self.progressdialog = xbmcgui.DialogProgressBG()
else:
self.progressdialog = xbmcgui.DialogProgress()
self.progressdialog.create('Updating', '')
self.total_items = total_items
self.processed_items = 0
self.progress = 0
def canceled(self):
if RUN_IN_BACKGROUND:
return True if winprop('CancelRatingUpdater.bool') else False
else:
return True if self.progressdialog.iscanceled() or winprop('CancelRatingUpdater.bool') else False
def update(self,cat,label):
self.processed_items += 1
progress = int(100 / self.total_items * self.processed_items)
processed = str(self.processed_items) + ' / ' + str(self.total_items)
if RUN_IN_BACKGROUND:
self.progressdialog.update(progress, processed, cat + ':' + label)
else:
self.progressdialog.update(progress, cat + ':[CR]' + label + '[CR]' + processed)
def close(self):
self.progressdialog.close()
self.progressdialog = None
winprop('CancelRatingUpdater', clear=True)
class UpdateRating(object):
def __init__(self,params):
self.dbid = params.get('dbid')
self.dbtype = params.get('type')
self.tmdb_type = 'movie' if self.dbtype == 'movie' else 'tv'
self.tmdb_tv_status = None
self.tmdb_mpaa = None
self.tmdb_mpaa_fallback = None
self.tmdb_rating = None
self.imdb_rating = None
self.omdb_limit = False
self.update_uniqueid = False
self.episodeguide = None
# collect db data
self.db = Database(dbid=self.dbid, dbtype=self.dbtype)
self.get_details()
self.uniqueid = self.details.get('uniqueid', {})
self.ratings = self.details.get('ratings', {})
self.file = self.details.get('file')
self.year = self.details.get('year')
self.premiered = self.details.get('premiered') or self.details.get('firstaired')
self.title = self.details.get('title')
self.original_title = self.details.get('originaltitle') or self.title
self.tags = self.details.get('tag')
if any(string in self.details.get('episodeguide', '') for string in ['tvdb', 'tmdb']):
self.episodeguide = self.details.get('episodeguide')
else:
self.episodeguide = None
if self.uniqueid:
self.run()
def get_details(self):
getattr(self.db, self.dbtype)()
self.details = self.db.result().get(self.dbtype)[0]
def run(self):
log('Run rating updater - %s: %s - ID: %s' % (self.dbtype, self.title, str(self.dbid)), force=RATING_DEBUG)
self.imdb = self.uniqueid.get('imdb')
self.tmdb = self.uniqueid.get('tmdb')
self.tvdb = self.uniqueid.get('tvdb')
# don't proceed for episodes if no IMDb is available
if self.dbtype == 'episode' and not self.imdb:
log('Episode with no IMDb. Skip.', force=RATING_DEBUG)
return
# get the default used rating
self.default_rating = None
for rating in self.ratings:
if self.ratings[rating].get('default'):
self.default_rating = rating
break
if self.dbtype != 'episode':
# get TMDb ID (if not available) by using the ID of IMDb or TVDb
if not self.tmdb and self.imdb:
log('No TMDb. Try to get by IMDb ID %s' % self.imdb, force=RATING_DEBUG)
self.get_tmdb_externalid(self.imdb)
elif not self.tmdb and self.tvdb:
log('No TMDb. Try to get by TVDb ID %s' % str(self.tvdb), force=RATING_DEBUG)
self.get_tmdb_externalid(self.tvdb)
# get TMDb rating and IMDb number if not available
if self.tmdb:
log('Fetch data by TMDb ID %s' % str(self.tmdb), force=RATING_DEBUG)
self.get_tmdb()
# get Rotten, Metacritic and IMDb ratings of OMDb
if not self.omdb_limit:
log('Fetch OMDb data', force=RATING_DEBUG)
self.get_omdb()
# if no TMDb ID was known before but OMDb return the IMDb ID -> try to get TMDb data again
if self.dbtype != 'episode' and not self.tmdb and self.imdb:
log('Try to get TMDb ID by returned IMDb ID %s of OMDb' % self.imdb, force=RATING_DEBUG)
self.get_tmdb_externalid(self.imdb)
if self.tmdb:
log('Fetch data by TMDb ID %s' % str(self.tmdb), force=RATING_DEBUG)
self.get_tmdb()
# emby <ratings> and <votes>
if 'default' in self.ratings:
self.emby_ratings()
# update db + nfo
log('Updating info', force=RATING_DEBUG)
self.update_info()
def emby_ratings(self):
# Emby For Kodi is storing the rating as 'default'
if self.imdb_rating:
self._update_ratings_dict(key='default',
rating=float(self.imdb_rating),
votes=int(self.imdb_votes)
)
elif self.tmdb_rating:
self._update_ratings_dict(key='default',
rating=float(self.tmdb_rating),
votes=int(self.tmdb_votes)
)
def get_tmdb(self):
result = self._tmdb(action=self.tmdb_type,
call=str(self.tmdb),
params={'append_to_response': 'release_dates,content_ratings,external_ids'}
)
if not result:
return
self.tmdb_rating = result.get('vote_average')
self.tmdb_votes = result.get('vote_count')
if not self.original_title:
self.original_title = result.get('original_title') or resultresult.get('original_name')
# update original title if missing
if self.original_title:
self._set_value('originaltitle', self.original_title)
if self.tmdb_type == 'tv':
premiered = result.get('first_air_date')
self.tmdb_tv_status = result.get('status')
# update TV status as well
if self.tmdb_tv_status:
self._set_value('status', self.tmdb_tv_status)
else:
premiered = result.get('release_date')
# update the year if not correct
if premiered and self.premiered != premiered:
self.year = premiered[:4]
if ADDON.getSettingBool('update_premiered') or not self.premiered:
self._set_value('premiered', premiered)
if self.tmdb_rating:
self._update_ratings_dict(key='themoviedb',
rating=self.tmdb_rating,
votes=self.tmdb_votes
)
# set MPAA based on setting
if not SKIP_MPAA:
if self.tmdb_type == 'movie':
release_dates = result['release_dates']['results']
for country in release_dates:
if country.get('iso_3166_1') == COUNTRY_CODE:
for item in country['release_dates']:
if item.get('certification'):
self.tmdb_mpaa = item.get('certification')
break
break
elif country.get('iso_3166_1') == 'US':
for item in country['release_dates']:
if item.get('certification'):
self.tmdb_mpaa_fallback = item.get('certification')
break
if self.tmdb_type == 'tv':
content_ratings = result['content_ratings']['results']
for country in content_ratings:
if country.get('iso_3166_1') == COUNTRY_CODE:
self.tmdb_mpaa = country.get('rating')
break
elif country.get('iso_3166_1') == 'US':
self.tmdb_mpaa_fallback = country.get('rating')
if SKIP_NOT_RATED:
if self.tmdb_mpaa == 'NR':
self.tmdb_mpaa = None
if self.tmdb_mpaa_fallback == 'NR':
self.tmdb_mpaa_fallback = None
if self.tmdb_mpaa:
if COUNTRY_CODE == 'DE':
self.tmdb_mpaa = 'FSK ' + self.tmdb_mpaa
self._set_value('mpaa', self.tmdb_mpaa)
elif self.tmdb_mpaa_fallback and MPAA_FALLBACK:
self._set_value('mpaa', self.tmdb_mpaa_fallback)
else:
self._set_value('mpaa', '')
# set IMDb ID if not available in the library
if not self.imdb:
if self.tmdb_type == 'movie':
self.imdb = result.get('imdb_id')
elif self.tmdb_type == 'tv':
self.imdb = result['external_ids'].get('imdb_id')
if self.imdb:
self._update_uniqueid_dict('imdb', self.imdb)
# add TVDb ID to uniqueid if missing
if not self.tvdb and self.tmdb_type == 'tv':
self.tvdb = result['external_ids'].get('tvdb_id')
if self.tvdb:
self._update_uniqueid_dict('tvdb', self.tvdb)
def get_tmdb_externalid(self,external_id):
result = self._tmdb(action='find',
call=str(external_id),
params={'external_source': 'imdb_id' if external_id.startswith('tt') else 'tvdb_id'}
)
if self.dbtype == 'movie' and result.get('movie_results'):
self.tmdb = result['movie_results'][0].get('id')
elif self.dbtype == 'tvshow' and result.get('tv_results'):
self.tmdb = result['tv_results'][0].get('id')
if self.tmdb:
self._update_uniqueid_dict('tmdb', self.tmdb)
def get_omdb(self):
omdb = self._omdb()
if not omdb:
return
tree = ET.ElementTree(ET.fromstring(omdb))
root = tree.getroot()
for child in root:
# imdb ratings
self.imdb_rating = child.get('imdbRating', '').replace('N/A', '')
self.imdb_votes = child.get('imdbVotes', '0').replace('N/A', '0').replace(',', '')
if self.imdb_rating:
self._update_ratings_dict(key='imdb',
rating=float(self.imdb_rating),
votes=int(self.imdb_votes)
)
# regular rotten rating
tomatometerallcritics = child.get('tomatoMeter', '').replace('N/A', '')
tomatometerallcritics_avg = child.get('tomatoRating', '').replace('N/A', '')
tomatometerallcritics_votes = child.get('tomatoReviews', '0').replace('N/A', '0').replace(',', '')
if tomatometerallcritics:
self._update_ratings_dict(key='tomatometerallcritics',
rating=int(tomatometerallcritics) / 10,
votes=int(tomatometerallcritics_votes))
if tomatometerallcritics_avg:
self._update_ratings_dict(key='tomatometeravgcritics',
rating=float(tomatometerallcritics_avg),
votes=int(tomatometerallcritics_votes))
# user rotten rating
tomatometerallaudience = child.get('tomatoUserMeter', '').replace('N/A', '')
tomatometerallaudience_avg = child.get('tomatoUserRating', '').replace('N/A', '')
tomatometerallaudience_votes = child.get('tomatoUserReviews', '0').replace('N/A', '0').replace(',', '')
if tomatometerallaudience:
self._update_ratings_dict(key='tomatometerallaudience',
rating=int(tomatometerallaudience) / 10,
votes=int(tomatometerallaudience_votes))
if tomatometerallaudience_avg:
self._update_ratings_dict(key='tomatometeravgaudience',
rating=float(tomatometerallaudience_avg),
votes=int(tomatometerallaudience_votes))
# metacritic
metacritic = child.get('metascore', '').replace('N/A', '')
if metacritic:
metacritic = int(metacritic) / 10
self._update_ratings_dict(key='metacritic',
rating=metacritic,
votes=0)
# set imdb if not set before
if not self.imdb and child.get('imdbID') and child.get('imdbID') != 'N/A':
self.imdb = child.get('imdbID')
self._update_uniqueid_dict('imdb', child.get('imdbID'))
break
def update_info(self):
# set at least one default rating if none is set in the library
if not self.default_rating and self.ratings:
for item in ['imdb', 'themoviedb', 'tomatometerallcritics', 'tomatometeravgcritics', 'metacritic']:
if item in self.ratings:
self.default_rating = item
break
# unkown rating source is stored -> use the first one
if not self.default_rating:
for item in self.ratings:
self.default_rating = item
break
# update to library
self._set_value(key='ratings', value=self.ratings)
if self.update_uniqueid:
self._set_value(key='uniqueid', value=self.uniqueid)
# episode guide verification
if self.episodeguide:
if 'thetvdb' in self.episodeguide and 'tvdb' not in self.uniqueid:
self.episodeguide = None
elif 'themoviedb' in self.episodeguide and 'tmdb' not in self.uniqueid:
self.episodeguide = None
if self.dbtype == 'tvshow' and not self.episodeguide:
if 'tvdb' in self.uniqueid:
value = self.uniqueid.get('tvdb')
url = 'https://api.thetvdb.com/login?{"apikey":"439DFEBA9D3059C6","id":%s}|Content-Type=application/json' % str(value)
json_value = '<episodeguide><url post="yes" cache="auth.json"><url>%s</url></episodeguide>' % url
elif 'tmdb' in self.uniqueid:
value = self.uniqueid.get('tmdb')
cache = 'tmdb-%s-%s.json' % (str(value), TMDB_LANGUAGE)
url = 'http://api.themoviedb.org/3/tv/%s?api_key=6a5be4999abf74eba1f9a8311294c267&amp;language=%s' % (str(value), TMDB_LANGUAGE)
json_value = '<episodeguide><url cache="%s"><url>%s</url></episodeguide>' % (cache, url)
else:
json_value = '<episodeguide><url cache=""><url></url></episodeguide>'
self.episodeguide = json_value
self._set_value('episodeguide', json_value)
# nfo updating
if self.file:
# get updated data
self.get_details()
# TV status cannot be fetched in Leia
if self.tmdb_tv_status and not self.details.get('status'):
self.details['status'] = self.tmdb_tv_status
update_nfo(file=self.file,
dbtype=self.dbtype,
dbid=self.dbid,
details=self.details
)
def _update_ratings_dict(self,key,rating,votes):
self.ratings[key] = {'default': True if key == self.default_rating else False,
'rating': rating,
'votes': votes}
def _update_uniqueid_dict(self,key,value):
self.uniqueid[key] = str(value)
self.update_uniqueid = True
def _set_value(self,key,value):
self.db.write(key=key, value=value)
def _omdb(self):
if not OMDB_API:
log('No OMDb API key configured. Skip.', force=RATING_DEBUG)
return
if self.imdb:
url = 'http://www.omdbapi.com/?apikey=%s&i=%s&plot=short&r=xml&tomatoes=true' % (OMDB_API, self.imdb)
elif OMDB_FALLBACK and self.dbtype != 'episode' and self.original_title and self.year:
# urllib has issues with some asian letters
try:
title = urllib.quote(self.original_title)
except KeyError:
return
url = 'http://www.omdbapi.com/?apikey=%s&t=%s&year=%s&plot=short&r=xml&tomatoes=true' % (OMDB_API, title, self.year)
else:
return
error_msg = 'OMDb error for "%s (%s)" IMDBd "%s". Error --> ' % (self.original_title, self.year, self.imdb)
for i in range(1,4): # loop if heavy server load
log('OMDb call try %s/3' % str(i), force=RATING_DEBUG)
try:
request = requests.get(url, timeout=5)
if not str(request.status_code).startswith('5'):
break
elif i == 1:
notification('OMDb', ADDON.getLocalizedString(32024))
except Exception:
if i < 3:
xbmc.sleep(500)
else:
log(error_msg + '408', WARNING)
return
if request.status_code == 401:
log('OMDb error --> API limit reached', WARNING)
if DIALOG.yesno(xbmc.getLocalizedString(257), ADDON.getLocalizedString(32033)):
log('OMDb limit reached and disabled for next calls', force=RATING_DEBUG)
self.omdb_limit = True
else:
log('OMDb limit reached and rating updater canceled', force=RATING_DEBUG)
winprop('CancelRatingUpdater.bool', True)
return
elif not request.ok:
log(error_msg + str(request.status_code), WARNING)
return
result = request.text
if not result or '<root response="False">' in result:
log(error_msg + 'Result = ' + str(result), WARNING)
return
return result
def _tmdb(self,action,call=None,get=None,params=None):
result = {}
args = {}
args['api_key'] = 'fc168650632c6597038cf7072a7c20da'
if params:
args.update(params)
call = '/' + str(call) if call else ''
get = '/' + get if get else ''
url = 'https://api.themoviedb.org/3/' + action + call + get
url = '{0}?{1}'.format(url, urlencode(args))
for i in range(1,4): # loop if heavy server load
log('TMDb call try %s/3' % str(i), force=RATING_DEBUG)
try:
request = requests.get(url, timeout=5)
if not str(request.status_code).startswith('5'):
break
elif i == 1:
notification('TMDb', ADDON.getLocalizedString(32024))
except Exception:
if i < 3:
xbmc.sleep(500)
else:
log('TMDb connection error', force=RATING_DEBUG)
return result
if request.ok:
result = request.json()
else:
log('TMDb returned nothing', force=RATING_DEBUG)
return result