New hybrid method

This commit is contained in:
angelblue05 2018-09-06 03:36:32 -05:00
parent 7f5084c62e
commit ace50b34dc
279 changed files with 39526 additions and 19994 deletions

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#################################################################################################
import logging
import sys
from helper import loghandler
from emby import Emby
#################################################################################################
Emby.set_loghandler(loghandler.LogHandler, logging.DEBUG)
loghandler.config()
LOG = logging.getLogger('EMBY.entrypoint')
#################################################################################################
try:
sys.path.insert(0, xbmc.translatePath('special://temp/emby/').decode('utf-8'))
except Exception as error:
LOG.debug('No objects not found, using default.')
from default import Events
from service import Service
from context import Context

View file

@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import logging
import sys
import xbmc
import xbmcaddon
import database
from dialogs import context
from helper import _, settings, dialog
from downloader import TheVoid
from objects import Actions
#################################################################################################
LOG = logging.getLogger("EMBY."+__name__)
XML_PATH = (xbmcaddon.Addon('plugin.video.emby').getAddonInfo('path'), "default", "1080i")
OPTIONS = {
'Refresh': _(30410),
'Delete': _(30409),
'Addon': _(30408),
'AddFav': _(30405),
'RemoveFav': _(30406),
'Transcode': _(30412)
}
#################################################################################################
class Context(object):
_selected_option = None
def __init__(self, transcode=False):
self.kodi_id = sys.listitem.getVideoInfoTag().getDbId() or None
self.media = self.get_media_type()
self.server = sys.listitem.getProperty('embyserver') or None
item_id = sys.listitem.getProperty('embyid')
if self.server or item_id:
self.item = TheVoid('GetItem', {'ServerId': self.server, 'Id': item_id}).get()
else:
self.item = self.get_item_id()
if self.item:
if transcode:
self.transcode()
elif self.select_menu():
self.action_menu()
if self._selected_option.decode('utf-8') in (OPTIONS['Delete'], OPTIONS['AddFav'], OPTIONS['RemoveFav']):
xbmc.sleep(500)
xbmc.executebuiltin('Container.Refresh')
def get_media_type(self):
''' Get media type based on sys.listitem. If unfilled, base on visible window.
'''
media = sys.listitem.getVideoInfoTag().getMediaType()
if not media:
if xbmc.getCondVisibility('Container.Content(albums)'):
media = "album"
elif xbmc.getCondVisibility('Container.Content(artists)'):
media = "artist"
elif xbmc.getCondVisibility('Container.Content(songs)'):
media = "song"
elif xbmc.getCondVisibility('Container.Content(pictures)'):
media = "picture"
else:
LOG.info("media is unknown")
return media.decode('utf-8')
def get_item_id(self):
''' Get synced item from embydb.
'''
item = database.get_item(self.kodi_id, self.media)
if not item:
return
return {
'Id': item[0],
'UserData': json.loads(item[4]) if item[4] else {},
'Type': item[3]
}
def select_menu(self):
''' Display the select dialog.
Favorites, Refresh, Delete (opt), Settings.
'''
options = []
if self.item['Type'] not in ('Season'):
if self.item['UserData'].get('IsFavorite'):
options.append(OPTIONS['RemoveFav'])
else:
options.append(OPTIONS['AddFav'])
options.append(OPTIONS['Refresh'])
if settings('enableContextDelete.bool'):
options.append(OPTIONS['Delete'])
options.append(OPTIONS['Addon'])
context_menu = context.ContextMenu("script-emby-context.xml", *XML_PATH)
context_menu.set_options(options)
context_menu.doModal()
if context_menu.is_selected():
self._selected_option = context_menu.get_selected()
return self._selected_option
def action_menu(self):
selected = self._selected_option.decode('utf-8')
if selected == OPTIONS['Refresh']:
TheVoid('RefreshItem', {'ServerId': self.server, 'Id': self.item['Id']})
elif selected == OPTIONS['AddFav']:
TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': True})
elif selected == OPTIONS['RemoveFav']:
TheVoid('FavoriteItem', {'ServerId': self.server, 'Id': self.item['Id'], 'Favorite': False})
elif selected == OPTIONS['Addon']:
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
elif selected == OPTIONS['Delete']:
delete = True
if not settings('skipContextMenu.bool'):
if not dialog("yesno", heading="{emby}", line1=_(33015)):
delete = False
if delete:
TheVoid('DeleteItem', {'ServerId': self.server, 'Id': self.item['Id']})
def transcode(self):
item = TheVoid('GetItem', {'Id': self.item['Id'], 'ServerId': self.server}).get()
Actions(self.server).play(item, self.kodi_id, True)

View file

@ -0,0 +1,667 @@
# -*- coding: utf-8 -*-
#################################################################################################
import json
import logging
import sys
import urlparse
import urllib
import os
import sys
import xbmc
import xbmcvfs
import xbmcgui
import xbmcplugin
import xbmcaddon
import client
from database import reset, get_sync, Database, emby_db, get_credentials
from objects import Objects, Actions
from downloader import TheVoid
from helper import _, event, settings, window, dialog, api, JSONRPC
from emby import Emby
#################################################################################################
LOG = logging.getLogger("EMBY."+__name__)
#################################################################################################
class Events(object):
def __init__(self):
''' Parse the parameters. Reroute to our service.py
where user is fully identified already.
'''
base_url = sys.argv[0]
path = sys.argv[2]
try:
params = dict(urlparse.parse_qsl(path[1:]))
except Exception:
params = {}
mode = params.get('mode')
server = params.get('server')
if server == 'None':
server = None
LOG.warn("path: %s params: %s", path, json.dumps(params, indent=4))
if '/extrafanart' in base_url:
emby_path = path[1:]
emby_id = params.get('id')
get_fanart(emby_id, emby_path, server)
elif '/Extras' in base_url or '/VideoFiles' in base_url:
emby_path = path[1:]
emby_id = params.get('id')
get_video_extras(emby_id, emby_path, server)
elif mode =='play':
item = TheVoid('GetItem', {'Id': params['id'], 'ServerId': server}).get()
Actions(params.get('server')).play(item, params.get('dbid'))
elif mode == 'playlist':
event('PlayPlaylist', {'Id': params['id'], 'ServerId': server})
elif mode == 'deviceid':
client.reset_device_id()
elif mode == 'reset':
reset()
elif mode == 'refreshboxsets':
event('SyncLibrary', {'Id': "Boxsets:Refresh"})
elif mode == 'nextepisodes':
get_next_episodes(params['id'], params['limit'])
elif mode == 'browse':
browse(params.get('type'), params.get('id'), params.get('folder'), server)
elif mode == 'synclib':
event('SyncLibrary', {'Id': params.get('id')})
elif mode == 'repairlib':
event('RepairLibrary', {'Id': params.get('id')})
elif mode == 'removelib':
event('RemoveLibrary', {'Id': params.get('id')})
elif mode == 'repairlibs':
event('RepairLibrarySelection')
elif mode == 'updatelibs':
event('SyncLibrarySelection')
elif mode == 'connect':
event('EmbyConnect')
elif mode == 'addserver':
event('AddServer')
elif mode == 'login':
event('ServerConnect', {'Id': server})
elif mode == 'removeserver':
event('RemoveServer', {'Id': server})
elif mode == 'settings':
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
elif mode == 'adduser':
add_user()
elif mode == 'updateserver':
event('UpdateServer')
elif mode == 'thememedia':
get_themes()
else:
listing()
def listing():
''' Display all emby nodes and dynamic entries when appropriate.
'''
total = int(window('Emby.nodes.total') or 0)
sync = get_sync()
servers = get_credentials()['Servers'][1:]
for i in range(total):
window_prop = "Emby.nodes.%s" % i
path = window('%s.index' % window_prop)
if not path:
path = window('%s.content' % window_prop) or window('%s.path' % window_prop)
label = window('%s.title' % window_prop)
node = window('%s.type' % window_prop)
artwork = window('%s.artwork' % window_prop)
view_id = window('%s.id' % window_prop)
context = []
if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id not in sync['Whitelist']:
context.append((_(33123), "RunPlugin(plugin://plugin.video.emby?mode=synclib&id=%s)" % view_id))
if view_id and node in ('movies', 'tvshows', 'musicvideos', 'music') and view_id in sync['Whitelist']:
context.append((_(33136), "RunPlugin(plugin://plugin.video.emby?mode=synclib&id=%s)" % view_id))
context.append((_(33132), "RunPlugin(plugin://plugin.video.emby?mode=repairlib&id=%s)" % view_id))
context.append((_(33133), "RunPlugin(plugin://plugin.video.emby?mode=removelib&id=%s)" % view_id))
LOG.debug("--[ listing/%s/%s ] %s", node, label, path)
if path:
if xbmc.getCondVisibility('Window.IsActive(Pictures)') and node in ('photos', 'homevideos'):
directory(label, path, artwork=artwork)
elif xbmc.getCondVisibility('Window.IsActive(Videos)') and node not in ('photos', 'homevideos', 'music'):
directory(label, path, artwork=artwork, context=context)
elif xbmc.getCondVisibility('Window.IsActive(Music)') and node == 'music':
directory(label, path, artwork=artwork, context=context)
elif not xbmc.getCondVisibility('Window.IsActive(Videos) | Window.IsActive(Pictures) | Window.IsActive(Music)'):
directory(label, path, artwork=artwork)
for server in servers:
context = []
if server.get('ManualAddress'):
context.append((_(33141), "RunPlugin(plugin://plugin.video.emby/?mode=removeserver&server=%s)"))
if 'AccessToken' not in server:
directory("%s (%s)" % (server['Name'], _(30539)), "plugin://plugin.video.emby/?mode=login&server=%s" % server['Id'], False, context=context)
else:
directory(server['Name'], "plugin://plugin.video.emby/?mode=browse&server=%s" % server['Id'], context=context)
directory(_(33134), "plugin://plugin.video.emby/?mode=addserver", False)
directory(_(5), "plugin://plugin.video.emby/?mode=settings", False)
directory(_(33054), "plugin://plugin.video.emby/?mode=adduser", False)
directory(_(33098), "plugin://plugin.video.emby/?mode=refreshboxsets", False)
directory(_(33139), "plugin://plugin.video.emby/?mode=updatelibs", False)
directory(_(33140), "plugin://plugin.video.emby/?mode=repairlibs", False)
directory(_(33060), "plugin://plugin.video.emby/?mode=thememedia", False)
directory(_(33058), "plugin://plugin.video.emby/?mode=reset", False)
xbmcplugin.setContent(int(sys.argv[1]), 'files')
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def directory(label, path, folder=True, artwork=None, fanart=None, context=None):
''' Add directory listitem. context should be a list of tuples [(label, action)*]
'''
li = dir_listitem(label, path, artwork, fanart)
if context:
li.addContextMenuItems(context)
xbmcplugin.addDirectoryItem(int(sys.argv[1]), path, li, folder)
return li
def dir_listitem(label, path, artwork=None, fanart=None):
li = xbmcgui.ListItem(label, path=path)
li.setThumbnailImage(artwork or "special://home/addons/plugin.video.emby/icon.png")
li.setArt({"fanart": fanart or "special://home/addons/plugin.video.emby/fanart.jpg"})
li.setArt({"landscape": artwork or fanart or "special://home/addons/plugin.video.emby/fanart.jpg"})
return li
def browse(media, view_id=None, folder=None, server_id=None):
''' Browse content dynamically.
'''
LOG.info("--[ v:%s/%s ] %s", view_id, media, folder)
if view_id:
view = TheVoid('GetItem', {'ServerId': server_id, 'Id': view_id}).get()
xbmcplugin.setPluginCategory(int(sys.argv[1]), view['Name'])
content_type = "files"
if media in ('tvshows', 'seasons', 'episodes', 'movies', 'musicvideos'):
content_type = media
elif media in ('homevideos', 'photos'):
content_type = "images"
if folder == 'FavEpisodes':
listing = TheVoid('Browse', {'Media': "Episode", 'ServerId': server_id, 'Limit': 25, 'Filters': "IsFavorite"}).get()
elif media == 'homevideos':
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Video,Folder,PhotoAlbum,Photo", 'ServerId': server_id, 'Recursive': False}).get()
elif media == 'movies':
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Movie,BoxSet", 'ServerId': server_id, 'Recursive': True}).get()
elif media == 'episodes':
listing = TheVoid('Browse', {'Id': folder or view_id, 'Media': "Episode", 'ServerId': server_id, 'Recursive': True}).get()
elif media == 'library':
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': True}).get()
else:
listing = TheVoid('Browse', {'Id': folder or view_id, 'ServerId': server_id, 'Recursive': False}).get()
if listing and listing.get('Items'):
actions = Actions(server_id)
list_li = []
for item in listing['Items']:
li = xbmcgui.ListItem()
li.setProperty('embyid', item['Id'])
li.setProperty('embyserver', server_id)
actions.set_listitem(item, li)
if item.get('IsFolder'):
params = {
'id': view_id or item['Id'],
'mode': "browse",
'type': get_folder_type(item) or media,
'folder': item['Id'],
'server': server_id
}
path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params))
context = []
if item['Type'] in ('Series', 'Season', 'Playlist'):
context.append(("Play", "RunPlugin(plugin://plugin.video.emby?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id)))
if item['UserData']['Played']:
context.append((_(16104), "RunPlugin(plugin://plugin.video.emby?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id)))
else:
context.append((_(16103), "RunPlugin(plugin://plugin.video.emby?mode=watched&id=%s&server=%s)" % (item['Id'], server_id)))
li.addContextMenuItems(context)
list_li.append((path, li, True))
else:
if item['Type'] not in ('Photo', 'PhotoAlbum'):
params = {
'id': item['Id'],
'mode': "play",
'server': server_id
}
path = "%s?%s" % ("plugin://plugin.video.emby", urllib.urlencode(params))
li.setProperty('path', path)
context = [(_(13412), "RunPlugin(plugin://plugin.video.emby?mode=playlist&id=%s&server=%s)" % (item['Id'], server_id))]
if item['UserData']['Played']:
context.append((_(16104), "RunPlugin(plugin://plugin.video.emby?mode=unwatched&id=%s&server=%s)" % (item['Id'], server_id)))
else:
context.append((_(16103), "RunPlugin(plugin://plugin.video.emby?mode=watched&id=%s&server=%s)" % (item['Id'], server_id)))
li.addContextMenuItems(context)
list_li.append((li.getProperty('path'), li, False))
xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li))
if content_type == 'images':
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE)
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RATING)
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
xbmcplugin.setContent(int(sys.argv[1]), content_type)
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def get_folder_type(item):
media = item['Type']
if media == 'Series':
return "seasons"
elif media == 'Season':
return "episodes"
elif media == 'BoxSet':
return "movies"
elif media == 'MusicArtist':
return "albums"
elif media == 'MusicAlbum':
return "songs"
elif media == 'CollectionFolder':
return item.get('CollectionType', 'library')
def get_fanart(item_id, path, server_id=None):
''' Get extra fanart for listitems. This is called by skinhelper.
Images are stored locally, due to the Kodi caching system.
'''
if not item_id and 'plugin.video.emby' in path:
item_id = path.split('/')[-2]
if not item_id:
return
LOG.info("[ extra fanart ] %s", item_id)
objects = Objects()
list_li = []
API = api.API(item, TheVoid('GetServerAddress', {'ServerId': server_id}).get())
directory = xbmc.translatePath("special://thumbnails/emby/%s/" % item_id).decode('utf-8')
if not xbmcvfs.exists(directory):
xbmcvfs.mkdirs(directory)
item = TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get()
obj = objects.map(item, 'Artwork')
backdrops = API.get_all_artwork(obj)
tags = obj['BackdropTags']
for index, backdrop in enumerate(backdrops):
tag = tags[index]
fanart = os.path.join(directory, "fanart%s.jpg" % tag)
li = xbmcgui.ListItem(tag, path=fanart)
xbmcvfs.copy(backdrop, fanart)
list_li.append((fanart, li, False))
else:
LOG.debug("cached backdrop found")
dirs, files = xbmcvfs.listdir(directory)
for file in files:
fanart = os.path.join(directory, file.decode('utf-8'))
li = xbmcgui.ListItem(file, path=fanart)
list_li.append((fanart, li, False))
xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li))
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def get_video_extras(item_id, path, server_id=None):
''' Returns the video files for the item as plugin listing, can be used
to browse actual files or video extras, etc.
'''
if not item_id and 'plugin.video.emby' in path:
item_id = path.split('/')[-2]
if not item_id:
return
item = TheVoid('GetItem', {'ServerId': server_id, 'Id': item_id}).get()
# TODO
"""
def getVideoFiles(embyId,embyPath):
#returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc.
emby = embyserver.Read_EmbyServer()
if not embyId:
if "plugin.video.emby" in embyPath:
embyId = embyPath.split("/")[-2]
if embyId:
item = emby.getItem(embyId)
putils = playutils.PlayUtils(item)
if putils.isDirectPlay():
#only proceed if we can access the files directly. TODO: copy local on the fly if accessed outside
filelocation = putils.directPlay()
if not filelocation.endswith("/"):
filelocation = filelocation.rpartition("/")[0]
dirs, files = xbmcvfs.listdir(filelocation)
for file in files:
file = filelocation + file
li = xbmcgui.ListItem(file, path=file)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=file, listitem=li)
for dir in dirs:
dir = filelocation + dir
li = xbmcgui.ListItem(dir, path=dir)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=dir, listitem=li, isFolder=True)
#xbmcplugin.endOfDirectory(int(sys.argv[1]))
"""
def get_next_episodes(item_id, limit):
''' Only for synced content.
'''
with Database('emby') as embydb:
db = emby_db.EmbyDatabase(embydb.cursor)
library = db.get_view_name(item_id)
if not library:
return
result = JSONRPC('VideoLibrary.GetTVShows').execute({
'sort': {'order': "descending", 'method': "lastplayed"},
'filter': {
'and': [
{'operator': "true", 'field': "inprogress", 'value': ""},
{'operator': "is", 'field': "tag", 'value': "%s" % library}
]},
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
})
try:
items = result['result']['tvshows']
except (KeyError, TypeError):
return
list_li = []
for item in items:
if settings('ignoreSpecialsNextEpisodes.bool'):
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {
'and': [
{'operator': "lessthan", 'field': "playcount", 'value': "1"},
{'operator': "greaterthan", 'field': "season", 'value': "0"}
]},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
else:
params = {
'tvshowid': item['tvshowid'],
'sort': {'method': "episode"},
'filter': {'operator': "lessthan", 'field': "playcount", 'value': "1"},
'properties': [
"title", "playcount", "season", "episode", "showtitle",
"plot", "file", "rating", "resume", "tvshowid", "art",
"streamdetails", "firstaired", "runtime", "writer",
"dateadded", "lastplayed"
],
'limits': {"end": 1}
}
result = JSONRPC('VideoLibrary.GetEpisodes').execute(params)
try:
episodes = result['result']['episodes']
except (KeyError, TypeError):
pass
else:
for episode in episodes:
li = create_listitem(episode)
list_li.append((episode['file'], li))
if len(list_li) == limit:
break
xbmcplugin.addDirectoryItems(int(sys.argv[1]), list_li, len(list_li))
xbmcplugin.setContent(int(sys.argv[1]), 'episodes')
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def create_listitem(item):
''' Listitem based on jsonrpc items.
'''
title = item['title']
label2 = ""
li = xbmcgui.ListItem(title)
li.setProperty('IsPlayable', "true")
metadata = {
'Title': title,
'duration': str(item['runtime']/60),
'Plot': item['plot'],
'Playcount': item['playcount']
}
if "showtitle" in item:
metadata['TVshowTitle'] = item['showtitle']
label2 = item['showtitle']
if "episodeid" in item:
# Listitem of episode
metadata['mediatype'] = "episode"
metadata['dbid'] = item['episodeid']
# TODO: Review once Krypton is RC - probably no longer needed if there's dbid
if "episode" in item:
episode = item['episode']
metadata['Episode'] = episode
if "season" in item:
season = item['season']
metadata['Season'] = season
if season and episode:
episodeno = "s%.2de%.2d" % (season, episode)
li.setProperty('episodeno', episodeno)
label2 = "%s - %s" % (label2, episodeno) if label2 else episodeno
if "firstaired" in item:
metadata['Premiered'] = item['firstaired']
if "rating" in item:
metadata['Rating'] = str(round(float(item['rating']),1))
if "director" in item:
metadata['Director'] = " / ".join(item['director'])
if "writer" in item:
metadata['Writer'] = " / ".join(item['writer'])
if "cast" in item:
cast = []
castandrole = []
for person in item['cast']:
name = person['name']
cast.append(name)
castandrole.append((name, person['role']))
metadata['Cast'] = cast
metadata['CastAndRole'] = castandrole
li.setLabel2(label2)
li.setInfo(type="Video", infoLabels=metadata)
li.setProperty('resumetime', str(item['resume']['position']))
li.setProperty('totaltime', str(item['resume']['total']))
li.setArt(item['art'])
li.setThumbnailImage(item['art'].get('thumb',''))
li.setIconImage('DefaultTVShows.png')
li.setProperty('dbid', str(item['episodeid']))
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
for key, value in item['streamdetails'].iteritems():
for stream in value:
li.addStreamInfo(key, stream)
return li
def add_user():
''' Add or remove users from the default server session.
'''
if not window('emby_online.bool'):
return
session = TheVoid('GetSession', {}).get()
users = TheVoid('GetUsers', {'IsDisabled': False, 'IsHidden': False}).get()
current = session[0]['AdditionalUsers']
result = dialog("select", _(33061), [_(33062), _(33063)] if current else [_(33062)])
if result < 0:
return
if not result: # Add user
eligible = [x for x in users if x['Id'] not in [current_user['UserId'] for current_user in current]]
resp = dialog("select", _(33064), [x['Name'] for x in eligible])
if resp < 0:
return
user = eligible[resp]
event('AddUser', {'Id': user['Id'], 'Add': True})
else: # Remove user
resp = dialog("select", _(33064), [x['UserName'] for x in current])
if resp < 0:
return
user = current[resp]
event('AddUser', {'Id': user['UserId'], 'Add': False})
def get_themes():
''' Add theme media locally, via strm. This is only for tv tunes.
If another script is used, adjust this code.
'''
from helper.utils import normalize_string
from helper.playutils import PlayUtils
from helper.xmls import tvtunes_nfo
library = xbmc.translatePath("special://profile/addon_data/plugin.video.emby/library/").decode('utf-8')
play = settings('useDirectPaths') == "1"
if not xbmcvfs.exists(library):
xbmcvfs.mkdir(library)
if xbmc.getCondVisibility('System.HasAddon(script.tvtunes)'):
tvtunes = xbmcaddon.Addon(id="script.tvtunes")
tvtunes.setSetting('custom_path_enable', "true")
tvtunes.setSetting('custom_path', library)
LOG.info("TV Tunes custom path is enabled and set.")
else:
dialog("ok", heading="{emby}", line1=_(33152))
return
with Database('emby') as embydb:
all_views = emby_db.EmbyDatabase(embydb.cursor).get_views()
views = [x[0] for x in all_views if x[2] in ('movies', 'tvshows', 'mixed')]
items = {}
server = TheVoid('GetServerAddress', {'ServerId': None}).get()
token = TheVoid('GetToken', {'ServerId': None}).get()
for view in views:
result = TheVoid('GetThemes', {'Type': "Video", 'Id': view}).get()
for item in result['Items']:
folder = normalize_string(item['Name'].encode('utf-8'))
items[item['Id']] = folder
result = TheVoid('GetThemes', {'Type': "Song", 'Id': view}).get()
for item in result['Items']:
folder = normalize_string(item['Name'].encode('utf-8'))
items[item['Id']] = folder
for item in items:
nfo_path = os.path.join(library, items[item])
nfo_file = os.path.join(nfo_path, "tvtunes.nfo")
if not xbmcvfs.exists(nfo_path):
xbmcvfs.mkdir(nfo_path)
themes = TheVoid('GetTheme', {'Id': item}).get()
paths = []
for theme in themes['ThemeVideosResult']['Items'] + themes['ThemeSongsResult']['Items']:
putils = PlayUtils(theme, False, None, server, token)
if play:
paths.append(putils.direct_play(theme['MediaSources'][0]).encode('utf-8'))
else:
paths.append(putils.direct_url(theme['MediaSources'][0]).encode('utf-8'))
tvtunes_nfo(nfo_file, paths)
dialog("notification", heading="{emby}", message=_(33153), icon="{emby}", time=1000, sound=False)

View file

@ -0,0 +1,461 @@
# -*- coding: utf-8 -*-
#################################################################################################
import _strptime # Workaround for threads using datetime: _striptime is locked
import json
import logging
import sys
from datetime import datetime
import xbmc
import xbmcgui
import connect
import client
import library
import setup
import monitor
import objects.utils
from libraries import requests
from views import Views, verify_kodi_defaults
from helper import _, window, settings, event, dialog, find
from objects import version
from downloader import get_objects
from emby import Emby
#################################################################################################
LOG = logging.getLogger("EMBY."+__name__)
#################################################################################################
class Service(xbmc.Monitor):
running = True
library_thread = None
monitor = None
play_event = None
warn = True
settings = {'last_progress': datetime.today()}
def __init__(self):
self.settings['profile'] = xbmc.translatePath('special://profile')
self.settings['mode'] = settings('useDirectPaths')
self.settings['log_level'] = settings('logLevel') or "1"
self.settings['enable_context'] = settings('enableContext.bool')
self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
self.settings['kodi_companion'] = settings('kodiCompanion.bool')
window('emby_logLevel', value=str(self.settings['log_level']))
window('emby_kodiProfile', value=self.settings['profile'])
if self.settings['enable_context']:
window('emby_context', value="true")
if self.settings['enable_context_transcode']:
window('emby_context_transcode', value="true")
LOG.warn("--->>>[ %s ]", client.get_addon_name())
LOG.warn("Version: %s", client.get_version())
LOG.warn("KODI Version: %s", xbmc.getInfoLabel('System.BuildVersion'))
LOG.warn("Platform: %s", client.get_platform())
LOG.warn("Python Version: %s", sys.version)
LOG.warn("Using dynamic paths: %s", settings('useDirectPaths') == "0")
LOG.warn("Log Level: %s", self.settings['log_level'])
verify_kodi_defaults()
Views().get_nodes()
window('emby.connected.bool', True)
xbmc.Monitor.__init__(self)
self.check_update()
settings('groupedSets.bool', objects.utils.get_grouped_set())
def service(self):
''' Keeps the service monitor going.
Exit on Kodi shutdown or profile switch.
if profile switch happens more than once,
Threads depending on abortRequest will not trigger.
'''
self.monitor = monitor.Monitor()
self.connect = connect.Connect()
self.start_default()
self.settings['mode'] = settings('useDirectPaths')
while self.running:
if window('emby_online.bool'):
if self.settings['profile'] != window('emby_kodiProfile'):
LOG.info("[ profile switch ] %s", self.settings['profile'])
break
if self.monitor.player.isPlaying():
difference = datetime.today() - self.settings['last_progress']
if difference.seconds > 270:
event("ReportProgressRequested", None)
self.settings['last_progress'] = datetime.today()
if self.waitForAbort(1):
break
self.shutdown()
def start_default(self):
try:
self.connect.register()
setup.Setup()
except Exception as error:
LOG.error(error)
def stop_default(self):
window('emby_online', clear=True)
Emby().close()
if self.library_thread is not None:
self.library_thread.stop_client()
self.library_thread = None
def check_update(self):
''' Check for objects build version and compare.
This pulls a dict that contains all the information for the build needed.
'''
LOG.info("--[ check updates ]")
kodi = xbmc.getInfoLabel('System.BuildVersion')
url = "https://sheets.googleapis.com/v4/spreadsheets/1cKWQCVL0lVONulO2KyGzBilzhGvsyuSjFvrqe8g6nJw/values/A2:B?key=AIzaSyAP-1mcBglk9zIofJlqGpvKXkff3GRMhdI"
try:
self.versions = {k.lower(): v for k, v in dict(requests.get(url).json()['values']).items()}
build = find(self.versions, kodi)
if not build:
raise Exception("build %s incompatible?!", kodi)
label, zipfile = build.split('-', 1)
if label == version:
LOG.info("--[ objects/%s ]", version)
return
get_objects(zipfile, label + '.zip')
except Exception as error:
LOG.info(error)
self.shutdown()
return
dialog("ok", heading="{emby}", line1=_(33135))
xbmc.executebuiltin('RestartApp')
def onNotification(self, sender, method, data):
''' All notifications are sent via NotifyAll built-in or Kodi.
Central hub.
'''
if sender.lower() not in ('plugin.video.emby', 'xbmc'):
return
if sender == 'plugin.video.emby':
method = method.split('.')[1]
if method not in ('ServerUnreachable', 'ServerShuttingDown', 'UserDataChanged', 'ServerConnect',
'LibraryChanged', 'ServerOnline', 'SyncLibrary', 'RepairLibrary', 'RemoveLibrary',
'EmbyConnect', 'SyncLibrarySelection', 'RepairLibrarySelection', 'AddServer',
'Unauthorized', 'UpdateServer', 'UserConfigurationUpdated', 'ServerRestarting'):
return
data = json.loads(data)[0]
else:
if method not in ('System.OnQuit', 'System.OnSleep', 'System.OnWake'):
return
data = json.loads(data)
LOG.debug("[ %s: %s ] %s", sender, method, json.dumps(data, indent=4))
if method == 'ServerOnline':
if data['ServerId'] is None:
window('emby_online.bool', True)
self.warn = True
if self.library_thread is None:
self.library_thread = library.Library(self)
self.library_thread.start()
elif method in ('ServerUnreachable', 'ServerShuttingDown'):
if self.warn or data.get('ServerId'):
self.warn = data.get('ServerId') is not None
dialog("notification", heading="{emby}", message=_(33146) if data.get('ServerId') is None else _(33149), icon=xbmcgui.NOTIFICATION_ERROR)
if data.get('ServerId') is None:
self.stop_default()
if self.waitForAbort(20):
return
self.start_default()
elif method == 'Unauthorized':
dialog("notification", heading="{emby}", message=_(33147) if data['ServerId'] is None else _(33148), icon=xbmcgui.NOTIFICATION_ERROR)
elif method == 'ServerRestarting':
if data.get('ServerId'):
return
if settings('restartMsg.bool'):
dialog("notification", heading="{emby}", message=_(33006), icon="{emby}")
self.stop_default()
if self.waitForAbort(10):
return
self.start_default()
elif method == 'ServerConnect':
self.connect.register(data['Id'])
xbmc.executebuiltin("Container.Refresh")
elif method == 'EmbyConnect':
self.connect.setup_login_connect()
elif method == 'AddServer':
self.connect.setup_manual_server()
xbmc.executebuiltin("Container.Refresh")
elif method == 'RemoveServer':
self.connect.remove_server(data['Id'])
xbmc.executebuiltin("Container.Refresh")
elif method == 'UpdateServer':
dialog("ok", heading="{emby}", line1=_(33151))
self.connect.setup_manual_server()
elif method == 'UserDataChanged' and self.library_thread:
if data.get('ServerId'):
return
self.library_thread.userdata(data['UserDataList'])
elif method == 'LibraryChanged' and self.library_thread:
if data.get('ServerId'):
return
self.library_thread.updated(data['ItemsUpdated'] + data['ItemsAdded'])
self.library_thread.removed(data['ItemsRemoved'])
elif method == 'System.OnQuit':
window('emby_should_stop.bool', True)
self.running = False
elif method in ('SyncLibrarySelection', 'RepairLibrarySelection'):
self.library_thread.select_libraries('SyncLibrary' if method == 'SyncLibrarySelection' else 'RepairLibrary')
elif method == 'SyncLibrary':
libraries = data['Id'].split(',')
for lib in libraries:
self.library_thread.add_library(lib)
xbmc.executebuiltin("Container.Refresh")
elif method == 'RepairLibrary':
libraries = data['Id'].split(',')
for lib in libraries:
self.library_thread.remove_library(lib)
self.library_thread.add_library(lib)
xbmc.executebuiltin("Container.Refresh")
elif method == 'RemoveLibrary':
libraries = data['Id'].split(',')
for lib in libraries:
self.library_thread.remove_library(lib)
xbmc.executebuiltin("Container.Refresh")
elif method == 'System.OnSleep':
LOG.info("-->[ sleep ]")
if self.library_thread is not None:
self.library_thread.stop_client()
self.library_thread = None
Emby.close_all()
elif method == 'System.OnWake':
LOG.info("--<[ sleep ]")
xbmc.sleep(10000)# Allow network to wake up
try:
self.connect.register()
except Exception as error:
LOG.error(error)
elif method == 'GUI.OnScreensaverDeactivated':
LOG.info("--<[ screensaver ]")
xbmc.sleep(5000)
if self.library_thread is not None:
self.library_thread.fast_sync()
elif method == 'UserConfigurationUpdated':
if data.get('ServerId') is None:
Views().get_views()
def onSettingsChanged(self):
''' React to setting changes that impact window values.
'''
if window('emby_should_stop.bool'):
return
if settings('logLevel') != self.settings['log_level']:
log_level = settings('logLevel')
window('emby_logLevel', str(log_level))
self.settings['logLevel'] = log_level
LOG.warn("New log level: %s", log_level)
if settings('enableContext.bool') != self.settings['enable_context']:
window('emby_context', settings('enableContext'))
self.settings['enable_context'] = settings('enableContext.bool')
LOG.warn("New context setting: %s", self.settings['enable_context'])
if settings('enableContextTranscode.bool') != self.settings['enable_context_transcode']:
window('emby_context_transcode', settings('enableContextTranscode'))
self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool')
LOG.warn("New context transcode setting: %s", self.settings['enable_context_transcode'])
if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started:
self.settings['mode'] = settings('useDirectPaths')
LOG.warn("New playback mode setting: %s", self.settings['mode'])
if not self.settings.get('mode_warn'):
self.settings['mode_warn'] = True
dialog("yesno", heading="{emby}", line1=_(33118))
if settings('kodiCompanion.bool') != self.settings['kodi_companion']:
self.settings['kodi_companion'] = settings('kodiCompanion.bool')
if not self.settings['kodi_companion']:
dialog("ok", heading="{emby}", line1=_(33138))
def shutdown(self):
LOG.warn("---<[ EXITING ]")
properties = [ # TODO: review
"emby_state", "emby_serverStatus",
"emby_syncRunning", "emby_dbCheck",
"emby_currUser", "emby_dbScan",
"emby_initialScan", "emby_playbackProps",
"emby_play", "emby_online", "emby.connected", "emby_should_stop", "emby.resume",
"emby.external", "emby.external_check"
]
for prop in properties:
window(prop, clear=True)
Emby.close_all()
if self.library_thread is not None:
self.library_thread.stop_client()
if self.monitor is not None:
self.monitor.listener.stop()
LOG.warn("---<<<[ %s ]", client.get_addon_name())
"""
if window('emby_online') == "true":
# Emby server is online
# Verify if user is set and has access to the server
if user_client.get_user() is not None and user_client.get_access():
if self.kodi_player.isPlaying():
self._report_progress()
# If an item is playing
if not self.startup:
self.startup = self._startup()
if not self.websocket_running:
# Start the Websocket Client
self.websocket_running = True
self.websocket_thread.start()
if not self.library_running:
# Start the syncing thread
self.library_running = True
self.library_thread.start()
if not self.capabitilities and user_client.post_capabilities():
self.capabitilities = True
if self.monitor.waitForAbort(15):
# Abort was requested while waiting. We should exit
break
else:
if (user_client.get_user() is None) and self.warn_auth:
# Alert user is not authenticated and suppress future warning
self.warn_auth = False
log.info("Not authenticated yet.")
# User access is restricted.
# Keep verifying until access is granted
# unless server goes offline or Kodi is shut down.
self._access_check()
else:
# Wait until Emby server is online
# or Kodi is shut down.
self._server_online_check()
if self.monitor.waitForAbort(1):
# Abort was requested while waiting. We should exit
break
"""