3.0.0 Revision Krypton

Update playback for Krytpon
Support Multi source
Add Force Transcode
Add a small listener for external players
Update dialog skin (thank you sualfred)
This commit is contained in:
angelblue05 2018-01-07 20:13:11 -06:00 committed by GitHub
parent 6005555b37
commit 9ac37a1c40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 2679 additions and 2378 deletions

View file

@ -7,6 +7,8 @@
import logging
from utils import settings
import artwork
##################################################################################################
log = logging.getLogger("EMBY."+__name__)
@ -19,6 +21,7 @@ class API(object):
def __init__(self, item):
# item is the api response
self.item = item
self.artwork = artwork.Artwork()
def get_userdata(self):
# Default
@ -71,12 +74,8 @@ class API(object):
writer = []
cast = []
try:
people = self.item['People']
except KeyError:
pass
else:
for person in people:
if 'People' in self.item:
for person in self.item['People']:
type_ = person['Type']
name = person['Name']
@ -95,6 +94,26 @@ class API(object):
'Cast': cast
}
def get_actors(self):
cast = []
if 'People' in self.item:
self.artwork.get_people_artwork(self.item['People'])
for person in self.item['People']:
if person['Type'] == "Actor":
cast.append({
'name': person['Name'],
'role': person['Role'],
'order': len(cast) + 1,
'thumbnail': person['imageurl']
})
return cast
def get_media_streams(self):
video_tracks = []

View file

@ -468,7 +468,7 @@ class Artwork(object):
image = ""
person_id = person['Id']
if "PrimaryImageTag" in person:
if 'PrimaryImageTag' in person:
image = (
"%s/emby/Items/%s/Images/Primary?"
"MaxWidth=400&MaxHeight=400&Index=0&Tag=%s"
@ -518,14 +518,14 @@ class Artwork(object):
tag, custom_query))
all_artwork['Backdrop'].append(artwork)
def get_artwork(item_id, type_, tag):
def get_artwork(item_id, type_, tag, override_name=None):
if not tag: return
artwork = ("%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (self.server, item_id, type_, max_width, max_height, tag, custom_query))
all_artwork[type_] = artwork
all_artwork[override_name or type_] = artwork
# Process backdrops
get_backdrops(item_id, backdrops)
@ -554,6 +554,9 @@ class Artwork(object):
get_artwork(item['Parent%sItemId' % parent_artwork], parent_artwork,
item['Parent%sImageTag' % parent_artwork])
if 'SeriesPrimaryImageTag' in item:
get_artwork(item['SeriesId'], "Primary", item['SeriesPrimaryImageTag'], "Series.Primary" if all_artwork['Primary'] else None)
# Parent album works a bit differently
if not all_artwork['Primary']:

View file

@ -78,22 +78,7 @@ class ClientInfo(object):
emby_guid = xbmc.translatePath("special://temp/emby_guid").decode('utf-8')
###$ Begin migration $###
if not xbmcvfs.exists(emby_guid):
addon_path = self.addon.getAddonInfo('path').decode('utf-8')
if os.path.supports_unicode_filenames:
path = os.path.join(addon_path, "machine_guid")
else:
path = os.path.join(addon_path.encode('utf-8'), "machine_guid")
guid_file = xbmc.translatePath(path).decode('utf-8')
if xbmcvfs.exists(guid_file):
xbmcvfs.copy(guid_file, emby_guid)
log.info("guid migration completed")
###$ End migration $###
if reset and xbmcvfs.exists(emby_guid):
# Reset the file
xbmcvfs.delete(emby_guid)
guid = xbmcvfs.File(emby_guid)

View file

@ -4,21 +4,26 @@
import logging
import sys
from datetime import timedelta
import xbmc
import xbmcaddon
import api
import read_embyserver as embyserver
import playbackutils as pbutils
import embydb_functions as embydb
import musicutils as musicutils
from utils import settings, dialog, language as lang
from dialogs import context
from dialogs import context, resume
from database import DatabaseConn
#################################################################################################
log = logging.getLogger("EMBY."+__name__)
addon = xbmcaddon.Addon('plugin.video.emby')
XML_PATH = (addon.getAddonInfo('path'), "default", "1080i")
OPTIONS = {
'Refresh': lang(30410),
@ -38,7 +43,7 @@ class ContextMenu(object):
_selected_option = None
def __init__(self):
def __init__(self, force_transcode=False):
self.emby = embyserver.Read_EmbyServer()
@ -53,7 +58,10 @@ class ContextMenu(object):
self.item = self.emby.getItem(self.item_id)
self.api = api.API(self.item)
if self._select_menu():
if force_transcode:
self._force_transcode()
elif self._select_menu():
self._action_menu()
if self._selected_option in (OPTIONS['Delete'], OPTIONS['AddFav'],
@ -104,10 +112,6 @@ class ContextMenu(object):
userdata = self.api.get_userdata()
options = []
if self.item_type in ("movie", "episode", "song"):
#options.append(OPTIONS['Transcode'])
pass
if userdata['Favorite']:
# Remove from emby favourites
options.append(OPTIONS['RemoveFav'])
@ -126,9 +130,7 @@ class ContextMenu(object):
# Addon settings
options.append(OPTIONS['Addon'])
addon = xbmcaddon.Addon('plugin.video.emby')
context_menu = context.ContextMenu("script-emby-context.xml", addon.getAddonInfo('path'),
"default", "1080i")
context_menu = context.ContextMenu("script-emby-context.xml", *XML_PATH)
context_menu.set_options(options)
context_menu.doModal()
@ -141,10 +143,7 @@ class ContextMenu(object):
selected = self._selected_option.decode('utf-8')
if selected == OPTIONS['Transcode']:
pass
elif selected == OPTIONS['Refresh']:
if selected == OPTIONS['Refresh']:
self.emby.refreshItem(self.item_id)
elif selected == OPTIONS['AddFav']:
@ -198,3 +197,24 @@ class ContextMenu(object):
if delete:
log.info("Deleting request: %s", self.item_id)
self.emby.deleteItem(self.item_id)
def _force_transcode(self):
log.info("Force transcode called.")
seektime = self.api.adjust_resume(self.api.get_userdata()['Resume'])
if seektime:
log.info("Resume dialog called.")
dialog = resume.ResumeDialog("script-emby-resume.xml", *XML_PATH)
dialog.set_resume_point("Resume from %s" % str(timedelta(seconds=seektime)).split(".")[0])
dialog.doModal()
if dialog.is_selected():
if not dialog.get_selected(): # Start from beginning selected.
self.item['UserData']['PlaybackPositionTicks'] = 0
else: # User backed out
log.info("User exited without a selection.")
return
pbutils.PlaybackUtils(self.item).play(self.item['Id'], self.kodi_id, True)

View file

@ -57,7 +57,6 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
for option in self._options:
self.list_.addItem(self._add_listitem(option))
self.background = self._add_editcontrol(730, height, 30, 450)
self.setFocus(self.list_)
def onAction(self, action):

View file

@ -22,6 +22,8 @@ SIGN_IN = 200
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
USER = 204
PASSWORD = 205
ERROR = {
'Invalid': 1,
'Empty': 2
@ -52,21 +54,14 @@ class LoginConnect(xbmcgui.WindowXMLDialog):
def onInit(self):
self.user_field = self._add_editcontrol(725, 385, 40, 500)
self.user_field = self.getControl(USER)
self.setFocus(self.user_field)
self.password_field = self._add_editcontrol(725, 470, 40, 500, password=1)
self.password_field = self.getControl(PASSWORD)
self.signin_button = self.getControl(SIGN_IN)
self.remind_button = self.getControl(CANCEL)
self.error_toggle = self.getControl(ERROR_TOGGLE)
self.error_msg = self.getControl(ERROR_MSG)
self.user_field.controlUp(self.remind_button)
self.user_field.controlDown(self.password_field)
self.password_field.controlUp(self.user_field)
self.password_field.controlDown(self.signin_button)
self.signin_button.controlUp(self.password_field)
self.remind_button.controlDown(self.user_field)
def onClick(self, control):
if control == SIGN_IN:
@ -97,23 +92,6 @@ class LoginConnect(xbmcgui.WindowXMLDialog):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
def _add_editcontrol(self, x, y, height, width, password=0):
media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
control = xbmcgui.ControlEdit(0, 0, 0, 0,
label="User",
font="font10",
textColor="ff525252",
focusTexture=os.path.join(media, "button-focus.png"),
noFocusTexture=os.path.join(media, "button-focus.png"),
isPassword=password)
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
self.addControl(control)
return control
def _login(self, username, password):
result = self.connect_manager.loginToConnect(username, password)

View file

@ -23,6 +23,8 @@ SIGN_IN = 200
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
USER = 204
PASSWORD = 205
ERROR = {
'Invalid': 1,
'Empty': 2
@ -50,7 +52,7 @@ class LoginManual(xbmcgui.WindowXMLDialog):
self.server = server
def set_user(self, user):
self.username = user or {}
self.username = user or None
def get_user(self):
return self._user
@ -61,8 +63,8 @@ class LoginManual(xbmcgui.WindowXMLDialog):
self.cancel_button = self.getControl(CANCEL)
self.error_toggle = self.getControl(ERROR_TOGGLE)
self.error_msg = self.getControl(ERROR_MSG)
self.user_field = self._add_editcontrol(725, 400, 40, 500)
self.password_field = self._add_editcontrol(725, 475, 40, 500, password=1)
self.user_field = self.getControl(USER)
self.password_field = self.getControl(PASSWORD)
if self.username:
self.user_field.setText(self.username)
@ -70,13 +72,6 @@ class LoginManual(xbmcgui.WindowXMLDialog):
else:
self.setFocus(self.user_field)
self.user_field.controlUp(self.cancel_button)
self.user_field.controlDown(self.password_field)
self.password_field.controlUp(self.user_field)
self.password_field.controlDown(self.signin_button)
self.signin_button.controlUp(self.password_field)
self.cancel_button.controlDown(self.user_field)
def onClick(self, control):
if control == SIGN_IN:
@ -106,23 +101,6 @@ class LoginManual(xbmcgui.WindowXMLDialog):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
def _add_editcontrol(self, x, y, height, width, password=0):
media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
control = xbmcgui.ControlEdit(0, 0, 0, 0,
label="User",
font="font10",
textColor="ff525252",
focusTexture=os.path.join(media, "button-focus.png"),
noFocusTexture=os.path.join(media, "button-focus.png"),
isPassword=password)
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
self.addControl(control)
return control
def _login(self, username, password):
try:

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
##################################################################################################
import logging
import xbmc
import xbmcgui
import xbmcaddon
##################################################################################################
log = logging.getLogger("EMBY."+__name__)
addon = xbmcaddon.Addon('plugin.video.emby')
ACTION_PARENT_DIR = 9
ACTION_PREVIOUS_MENU = 10
ACTION_BACK = 92
RESUME = 3010
START_BEGINNING = 3011
##################################################################################################
class ResumeDialog(xbmcgui.WindowXMLDialog):
_resume_point = None
selected_option = None
def __init__(self, *args, **kwargs):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
def set_resume_point(self, time):
self._resume_point = time
def is_selected(self):
return True if self.selected_option is not None else False
def get_selected(self):
return self.selected_option
def onInit(self):
self.getControl(RESUME).setLabel(self._resume_point)
self.getControl(START_BEGINNING).setLabel(xbmc.getLocalizedString(12021))
def onAction(self, action):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
def onClick(self, controlID):
if controlID == RESUME:
self.selected_option = 1
self.close()
if controlID == START_BEGINNING:
self.selected_option = 0
self.close()

View file

@ -21,7 +21,6 @@ ACTION_BACK = 92
ACTION_SELECT_ITEM = 7
ACTION_MOUSE_LEFT_CLICK = 100
USER_IMAGE = 150
USER_NAME = 151
LIST = 155
CANCEL = 201
MESSAGE_BOX = 202
@ -35,7 +34,6 @@ MANUAL_SERVER = 206
class ServerConnect(xbmcgui.WindowXMLDialog):
username = ""
user_image = None
servers = []
@ -49,7 +47,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)
def set_args(self, **kwargs):
# connect_manager, username, user_image, servers, emby_connect
# connect_manager, user_image, servers, emby_connect
for key, value in kwargs.iteritems():
setattr(self, key, value)
@ -77,13 +75,11 @@ class ServerConnect(xbmcgui.WindowXMLDialog):
server_type = "wifi" if server.get('ExchangeToken') else "network"
self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type))
self.getControl(USER_NAME).setLabel("%s %s" % (lang(33000), self.username.decode('utf-8')))
if self.user_image is not None:
self.getControl(USER_IMAGE).setImage(self.user_image)
if not self.emby_connect: # Change connect user
self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+lang(30618)+"[/B][/UPPERCASE]")
self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]%s[/B][/UPPERCASE]" % lang(30618))
if self.servers:
self.setFocus(self.list_)

View file

@ -24,6 +24,8 @@ CONNECT = 200
CANCEL = 201
ERROR_TOGGLE = 202
ERROR_MSG = 203
HOST = 204
PORT = 205
ERROR = {
'Invalid': 1,
'Empty': 2
@ -57,19 +59,12 @@ class ServerManual(xbmcgui.WindowXMLDialog):
self.cancel_button = self.getControl(CANCEL)
self.error_toggle = self.getControl(ERROR_TOGGLE)
self.error_msg = self.getControl(ERROR_MSG)
self.host_field = self._add_editcontrol(725, 400, 40, 500)
self.port_field = self._add_editcontrol(725, 525, 40, 500)
self.host_field = self.getControl(HOST)
self.port_field = self.getControl(PORT)
self.port_field.setText('8096')
self.setFocus(self.host_field)
self.host_field.controlUp(self.cancel_button)
self.host_field.controlDown(self.port_field)
self.port_field.controlUp(self.host_field)
self.port_field.controlDown(self.connect_button)
self.connect_button.controlUp(self.port_field)
self.cancel_button.controlDown(self.host_field)
def onClick(self, control):
if control == CONNECT:
@ -99,22 +94,6 @@ class ServerManual(xbmcgui.WindowXMLDialog):
if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU):
self.close()
def _add_editcontrol(self, x, y, height, width):
media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media')
control = xbmcgui.ControlEdit(0, 0, 0, 0,
label="User",
font="font10",
textColor="ffc2c2c2",
focusTexture=os.path.join(media, "button-focus.png"),
noFocusTexture=os.path.join(media, "button-focus.png"))
control.setPosition(x, y)
control.setHeight(height)
control.setWidth(width)
self.addControl(control)
return control
def _connect_to_server(self, server, port):
server_address = "%s:%s" % (server, port) if port else server

View file

@ -54,7 +54,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
self.list_ = self.getControl(LIST)
for user in self.users:
user_image = ("userflyoutdefault2.png" if 'PrimaryImageTag' not in user
user_image = ("items/logindefault.png" if 'PrimaryImageTag' not in user
else self._get_user_artwork(user['Id'], 'Primary'))
self.list_.addItem(self._add_listitem(user['Name'], user['Id'], user_image))

View file

@ -110,7 +110,7 @@ class DownloadUtils(object):
"SetAudioStreamIndex,SetSubtitleStreamIndex,"
"SetRepeatMode,"
"Mute,Unmute,SetVolume,"
"Play,Playstate,PlayNext"
"Play,Playstate,PlayNext,PlayMediaSource"
)
}

View file

@ -30,12 +30,15 @@ import playbackutils as pbutils
import playutils
import api
from views import Playlist, VideoNodes
from utils import window, settings, dialog, language as lang, plugin_path
from utils import window, settings, dialog, language as lang, urllib_path
#################################################################################################
log = logging.getLogger("EMBY."+__name__)
addon = xbmcaddon.Addon(id='plugin.video.emby')
XML_PATH = (addon.getAddonInfo('path'), "default", "1080i")
#################################################################################################
@ -107,6 +110,12 @@ def doMainListing():
log.info(window('emby_server%s.name' % server))
addDirectoryItem(window('emby_server%s.name' % server), "plugin://plugin.video.emby/?mode=%s" % server)'''
addDirectoryItem("Manual login dialog", "plugin://plugin.video.emby/?mode=manuallogin")
addDirectoryItem("Connect login dialog", "plugin://plugin.video.emby/?mode=connectlogin")
addDirectoryItem("Manual server dialog", "plugin://plugin.video.emby/?mode=manualserver")
addDirectoryItem("Connect servers dialog", "plugin://plugin.video.emby/?mode=connectservers")
addDirectoryItem("Connect users dialog", "plugin://plugin.video.emby/?mode=connectusers")
addDirectoryItem(lang(30517), "plugin://plugin.video.emby/?mode=passwords")
addDirectoryItem(lang(33053), "plugin://plugin.video.emby/?mode=settings")
addDirectoryItem(lang(33054), "plugin://plugin.video.emby/?mode=adduser")
@ -123,6 +132,140 @@ def doMainListing():
xbmcplugin.endOfDirectory(int(sys.argv[1]))
def test_manual_login():
from dialogs import LoginManual
dialog = LoginManual("script-emby-connect-login-manual.xml", *XML_PATH)
dialog.set_server("Test server")
dialog.set_user("Test user")
dialog.doModal()
def test_connect_login():
from dialogs import LoginConnect
dialog = LoginConnect("script-emby-connect-login.xml", *XML_PATH)
dialog.doModal()
def test_manual_server():
from dialogs import ServerManual
dialog = ServerManual("script-emby-connect-server-manual.xml", *XML_PATH)
dialog.doModal()
def test_connect_servers():
from dialogs import ServerConnect
dialog = ServerConnect("script-emby-connect-server.xml", *XML_PATH)
test_servers = [
{
u'LastConnectionMode': 2,
u'Name': u'Server Name',
u'AccessToken': u'Token',
u'RemoteAddress': u'http://remote.address:8096',
u'UserId': u'd4000909883845059aadef13b7110375',
u'ManualAddress': u'http://manual.address:8096',
u'DateLastAccessed': '2018-01-01T02:36:58Z',
u'LocalAddress': u'http://local.address:8096',
u'Id': u'b1ef1940b1964e2188f00b73611d53fd',
u'Users': [
{
u'IsSignedInOffline': True,
u'Id': u'd4000909883845059aadef13b7110375'
}
]
}
]
kwargs = {
'username': "Test user",
'user_image': None,
'servers': test_servers,
'emby_connect': True
}
dialog.set_args(**kwargs)
dialog.doModal()
def test_connect_users():
from dialogs import UsersConnect
test_users = [{
u'Name': u'Guest',
u'HasConfiguredEasyPassword': False,
u'LastActivityDate': u'2018-01-01T04:10:15.4195197Z',
u'HasPassword': False,
u'LastLoginDate': u'2017-12-28T02:53:01.5770625Z',
u'Policy': {
u'EnabledDevices': [
],
u'EnableMediaPlayback': True,
u'EnableRemoteControlOfOtherUsers': False,
u'RemoteClientBitrateLimit': 0,
u'BlockUnratedItems': [
],
u'EnableAllDevices': True,
u'InvalidLoginAttemptCount': 0,
u'EnableUserPreferenceAccess': True,
u'EnableLiveTvManagement': False,
u'EnableLiveTvAccess': False,
u'IsAdministrator': False,
u'EnableContentDeletion': False,
u'EnabledChannels': [
],
u'IsDisabled': False,
u'EnableSyncTranscoding': False,
u'EnableAudioPlaybackTranscoding': True,
u'EnableSharedDeviceControl': False,
u'AccessSchedules': [
],
u'IsHidden': False,
u'EnableContentDeletionFromFolders': [
],
u'EnableContentDownloading': False,
u'EnableVideoPlaybackTranscoding': True,
u'EnabledFolders': [
u'0c41907140d802bb58430fed7e2cd79e',
u'a329cda1727467c08a8f1493195d32d3',
u'f137a2dd21bbc1b99aa5c0f6bf02a805',
u'4514ec850e5ad0c47b58444e17b6346c'
],
u'EnableAllChannels': False,
u'BlockedTags': [
],
u'EnableAllFolders': False,
u'EnablePublicSharing': False,
u'EnablePlaybackRemuxing': True
},
u'ServerId': u'Test',
u'Configuration': {
u'SubtitleMode': u'Default',
u'HidePlayedInLatest': True,
u'GroupedFolders': [
],
u'DisplayCollectionsView': False,
u'OrderedViews': [
],
u'SubtitleLanguagePreference': u'',
u'AudioLanguagePreference': u'',
u'LatestItemsExcludes': [
],
u'EnableLocalPassword': False,
u'RememberAudioSelections': True,
u'RememberSubtitleSelections': True,
u'DisplayMissingEpisodes': False,
u'PlayDefaultAudioTrack': True,
u'EnableNextEpisodeAutoPlay': True
},
u'Id': u'a9d56d37cb6b47a3bfd3453c55138ff1',
u'HasConfiguredPassword': False
}]
dialog = UsersConnect("script-emby-connect-users.xml", *XML_PATH)
dialog.set_server("Test server")
dialog.set_users(test_users)
dialog.doModal()
def emby_connect():
# Login user to emby connect
@ -657,7 +800,7 @@ def BrowseContent(viewname, browse_type="", folderid=""):
'type': browse_type,
'folderid': item['Id']
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=path, listitem=li, isFolder=True)
else: #playable item, set plugin path and mediastreams
xbmcplugin.setContent(int(sys.argv[1]), 'episodes' if folderid else 'files')

View file

@ -4,6 +4,7 @@
import json
import logging
import threading
import xbmc
import xbmcgui
@ -26,11 +27,15 @@ KODI = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
class KodiMonitor(xbmc.Monitor):
retry = True
special_monitor = None
def __init__(self):
xbmc.Monitor.__init__(self)
if settings('useDirectPaths') == "0":
self.special_monitor = SpecialMonitor().start()
self.download = downloadutils.DownloadUtils().downloadUrl
log.info("Kodi monitor started")
@ -60,6 +65,11 @@ class KodiMonitor(xbmc.Monitor):
log.info("New context setting: %s", current_context)
window('emby_context', value=current_context)
current_context = "true" if settings('enableContextTranscode') == "true" else ""
if window('emby_context_transcode') != current_context:
log.info("New context transcode setting: %s", current_context)
window('emby_context_transcode', value=current_context)
@log_error()
def onNotification(self, sender, method, data):
@ -142,7 +152,7 @@ class KodiMonitor(xbmc.Monitor):
else:
window('emby_%s.playmethod' % playurl, value="DirectPlay")
# Set properties for player.py
playback.setProperties(playurl, listitem)
playback.set_properties(playurl, listitem)
def _video_update(self, data):
# Manually marking as watched/unwatched
@ -199,3 +209,58 @@ class KodiMonitor(xbmc.Monitor):
log.info("Could not retrieve item Id")
return item_id
class SpecialMonitor(threading.Thread):
_stop_thread = False
external_count = 0
def run(self):
''' Detect the resume dialog for widgets.
Detect external players.
'''
monitor = xbmc.Monitor()
log.warn("----====# Starting Special Monitor #====----")
while not self._stop_thread:
player = xbmc.Player()
isPlaying = player.isPlaying()
if (not isPlaying and xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and
not xbmc.getCondVisibility('Window.IsVisible(MyVideoNav.xml)') and
xbmc.getInfoLabel('Control.GetLabel(1002)') == xbmc.getLocalizedString(12021)):
control = int(xbmcgui.Window(10106).getFocusId())
if control == 1002: # Start from beginning
log.info("Resume dialog: Start from beginning selected.")
window('emby.resume', value="true")
else:
window('emby.resume', clear=True)
elif isPlaying and not window('emby.external_check'):
time = player.getTime()
if time > 1:
window('emby.external_check', value="true")
self.external_count = 0
elif self.external_count == 15:
log.info("External player detected.")
window('emby.external', value="true")
window('emby.external_check', value="true")
self.external_count = 0
elif time == 0:
self.external_count += 1
if monitor.waitForAbort(0.5):
# Abort was requested while waiting. We should exit
break
log.warn("#====---- Special Monitor Stopped ----====#")
def stop_monitor(self):
self._stop_thread = True

View file

@ -241,7 +241,7 @@ class LibrarySync(threading.Thread):
# use emby and video DBs
with database.DatabaseConn('emby') as cursor_emby:
with database.DatabaseConn('video') as cursor_video:
with database.DatabaseConn('video') as cursor_video:
# content sync: movies, tvshows, musicvideos, music
if manualrun:

View file

@ -63,18 +63,6 @@ class KodiMovies(KodiItems):
return kodi_id
def add_movie(self, *args):
query = (
'''
INSERT INTO movie(
idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07,
c09, c10, c11, c12, c14, c15, c16, c18, c19, c21)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.cursor.execute(query, (args))
def add_movie_17(self, *args):
# Create the movie entry
query = (
'''
@ -88,17 +76,6 @@ class KodiMovies(KodiItems):
self.cursor.execute(query, (args))
def update_movie(self, *args):
query = ' '.join((
"UPDATE movie",
"SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,",
"c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,",
"c16 = ?, c18 = ?, c19 = ?, c21 = ?",
"WHERE idMovie = ?"
))
self.cursor.execute(query, (args))
def update_movie_17(self, *args):
query = ' '.join((
"UPDATE movie",
@ -177,48 +154,16 @@ class KodiMovies(KodiItems):
def add_countries(self, kodi_id, countries):
if self.kodi_version > 14:
for country in countries:
country_id = self._get_country(country)
for country in countries:
country_id = self._get_country(country)
query = (
'''
INSERT OR REPLACE INTO country_link(country_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (country_id, kodi_id, "movie"))
else:
# TODO: Remove Helix code when Krypton is RC
for country in countries:
query = ' '.join((
"SELECT idCountry",
"FROM country",
"WHERE strCountry = ?",
"COLLATE NOCASE"
))
self.cursor.execute(query, (country,))
try:
country_id = self.cursor.fetchone()[0]
except TypeError:
# Create a new entry
self.cursor.execute("select coalesce(max(idCountry),0) from country")
country_id = self.cursor.fetchone()[0] + 1
query = "INSERT INTO country(idCountry, strCountry) values(?, ?)"
self.cursor.execute(query, (country_id, country))
log.debug("Add country to media, processing: %s", country)
query = (
'''
INSERT OR REPLACE INTO countrylinkmovie(idCountry, idMovie)
VALUES (?, ?)
'''
)
self.cursor.execute(query, (country_id, kodi_id))
query = (
'''
INSERT OR REPLACE INTO country_link(country_id, media_id, media_type)
VALUES (?, ?, ?)
'''
)
self.cursor.execute(query, (country_id, kodi_id, "movie"))
def _add_country(self, country):

View file

@ -189,26 +189,6 @@ class KodiMusic(KodiItems):
return album_id
def update_album(self, *args):
query = ' '.join((
"UPDATE album",
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
"iRating = ?, lastScraped = ?, strReleaseType = ?",
"WHERE idAlbum = ?"
))
self.cursor.execute(query, (args))
def update_album_18(self, *args):
query = ' '.join((
"UPDATE album",
"SET strArtistsDisp = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
"iUserrating = ?, lastScraped = ?, strReleaseType = ?",
"WHERE idAlbum = ?"
))
self.cursor.execute(query, (args))
def update_album_17(self, *args):
query = ' '.join((
"UPDATE album",
@ -218,27 +198,6 @@ class KodiMusic(KodiItems):
))
self.cursor.execute(query, (args))
def update_album_15(self, *args):
query = ' '.join((
"UPDATE album",
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
"iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?",
"WHERE idAlbum = ?"
))
self.cursor.execute(query, (args))
def update_album_14(self, *args):
# TODO: Remove Helix code when Krypton is RC
query = ' '.join((
"UPDATE album",
"SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,",
"iRating = ?, lastScraped = ?, dateAdded = ?",
"WHERE idAlbum = ?"
))
self.cursor.execute(query, (args))
def get_album_artist(self, album_id, artists):
query = ' '.join((
@ -356,8 +315,8 @@ class KodiMusic(KodiItems):
"UPDATE song",
"SET idAlbum = ?, strArtistDisp = ?, strGenres = ?, strTitle = ?, iTrack = ?,",
"iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,",
"rating = ?, comment = ?",
"iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,",
"rating = ?, comment = ?",
"WHERE idSong = ?"
))
self.cursor.execute(query, (args))

View file

@ -174,9 +174,8 @@ class KodiTVShows(KodiItems):
except TypeError:
season_id = self._add_season(show_id, number)
if self.kodi_version > 15 and name is not None:
query = "UPDATE seasons SET name = ? WHERE idSeason = ?"
self.cursor.execute(query, (name, season_id))
query = "UPDATE seasons SET name = ? WHERE idSeason = ?"
self.cursor.execute(query, (name, season_id))
return season_id
@ -189,18 +188,6 @@ class KodiTVShows(KodiItems):
return season_id
def add_episode(self, *args):
query = (
'''
INSERT INTO episode(
idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14,
idShow, c15, c16)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
)
self.cursor.execute(query, (args))
def add_episode_16(self, *args):
query = (
'''
INSERT INTO episode(
@ -213,16 +200,6 @@ class KodiTVShows(KodiItems):
self.cursor.execute(query, (args))
def update_episode(self, *args):
query = ' '.join((
"UPDATE episode",
"SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,",
"c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idShow = ?",
"WHERE idEpisode = ?"
))
self.cursor.execute(query, (args))
def update_episode_16(self, *args):
query = ' '.join((
"UPDATE episode",

View file

@ -250,28 +250,18 @@ class Movies(Items):
if update_item:
log.info("UPDATE movie itemid: %s - Title: %s", itemid, title)
# update new ratings Kodi 17
if self.kodi_version >= 17:
ratingid = self.kodi_db.get_ratingid(movieid)
# update ratings
ratingid = self.kodi_db.get_ratingid(movieid)
self.kodi_db.update_ratings(movieid, "movie", "default", rating, votecount, ratingid)
self.kodi_db.update_ratings(movieid, "movie", "default", rating, votecount, ratingid)
# update new uniqueid Kodi 17
if self.kodi_version >= 17:
uniqueid = self.kodi_db.get_uniqueid(movieid)
self.kodi_db.update_uniqueid(movieid, "movie", imdb, "imdb", uniqueid)
# update uniqueid
uniqueid = self.kodi_db.get_uniqueid(movieid)
self.kodi_db.update_uniqueid(movieid, "movie", imdb, "imdb", uniqueid)
# Update the movie entry
if self.kodi_version >= 17:
self.kodi_db.update_movie_17(title, plot, shortplot, tagline, votecount, uniqueid,
writer, year, uniqueid, sorttitle, runtime, mpaa, genre,
director, title, studio, trailer, country, year,
movieid)
else:
self.kodi_db.update_movie(title, plot, shortplot, tagline, votecount, rating,
writer, year, imdb, sorttitle, runtime, mpaa, genre,
director, title, studio, trailer, country, movieid)
self.kodi_db.update_movie(title, plot, shortplot, tagline, votecount, uniqueid,
writer, year, uniqueid, sorttitle, runtime, mpaa, genre,
director, title, studio, trailer, country, year, movieid)
# Update the checksum in emby table
emby_db.updateReference(itemid, checksum)
@ -280,17 +270,13 @@ class Movies(Items):
else:
log.info("ADD movie itemid: %s - Title: %s", itemid, title)
# add new ratings Kodi 17
if self.kodi_version >= 17:
ratingid = self.kodi_db.create_entry_rating()
# Add ratings
ratingid = self.kodi_db.create_entry_rating()
self.kodi_db.add_ratings(ratingid, movieid, "movie", "default", rating, votecount)
self.kodi_db.add_ratings(ratingid, movieid, "movie", "default", rating, votecount)
# add new uniqueid Kodi 17
if self.kodi_version >= 17:
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, movieid, "movie", imdb, "imdb")
# Add uniqueid
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, movieid, "movie", imdb, "imdb")
# Add path
pathid = self.kodi_db.add_path(path)
@ -298,16 +284,10 @@ class Movies(Items):
fileid = self.kodi_db.add_file(filename, pathid)
# Create the movie entry
if self.kodi_version >= 17:
self.kodi_db.add_movie_17(movieid, fileid, title, plot, shortplot, tagline,
votecount, uniqueid, writer, year, uniqueid, sorttitle,
runtime, mpaa, genre, director, title, studio, trailer,
country, year)
else:
self.kodi_db.add_movie(movieid, fileid, title, plot, shortplot, tagline,
votecount, rating, writer, year, imdb, sorttitle,
runtime, mpaa, genre, director, title, studio, trailer,
country)
self.kodi_db.add_movie(movieid, fileid, title, plot, shortplot, tagline,
votecount, uniqueid, writer, year, uniqueid, sorttitle,
runtime, mpaa, genre, director, title, studio, trailer,
country, year)
# Create the reference in emby table
emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None,

View file

@ -303,22 +303,8 @@ class Music(Items):
emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum)
# Process the album info
if self.kodi_version in [17,18]:
# Kodi Krypton/Leia
self.kodi_db.update_album_17(artistname, year, genre, bio, thumb, rating, lastScraped,
"album", albumid)
elif self.kodi_version == 16:
# Kodi Jarvis
self.kodi_db.update_album(artistname, year, genre, bio, thumb, rating, lastScraped,
"album", albumid)
elif self.kodi_version == 15:
# Kodi Isengard
self.kodi_db.update_album_15(artistname, year, genre, bio, thumb, rating, lastScraped,
dateadded, "album", albumid)
else:
# TODO: Remove Helix code when Krypton is RC
self.kodi_db.update_album_14(artistname, year, genre, bio, thumb, rating, lastScraped,
dateadded, albumid)
self.kodi_db.update_album(artistname, year, genre, bio, thumb, rating, lastScraped,
"album", albumid)
# Assign main artists to album
for artist in item['AlbumArtists']:

View file

@ -9,7 +9,7 @@ import api
import embydb_functions as embydb
import _kodi_tvshows
from _common import Items, catch_except
from utils import window, settings, language as lang, plugin_path
from utils import window, settings, language as lang, urllib_path
##################################################################################################
@ -234,11 +234,11 @@ class TVShows(Items):
artwork = self.artwork
API = api.API(item)
# Server api changed or something? RecursiveItemCount always returns 0
"""if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'):
if item.get('Name', None) is not None:
log.info("Skipping empty show: %s", item['Name'])
return"""
# If the show is empty, try to remove it.
if settings('syncEmptyShows') == "false" and not item.get('RecursiveItemCount'):
log.info("Skipping empty show: %s", item.get('Name', item['Id']))
return self.remove(item['Id'])
# If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database
update_item = True
@ -308,12 +308,8 @@ class TVShows(Items):
if self.emby_db.get_view_grouped_series(viewid) and tvdb:
# search kodi db for same provider id
if self.kodi_version > 16:
query = "SELECT idShow FROM tvshow_view WHERE uniqueid_value = ?"
kodicursor.execute(query, (tvdb,))
else:
query = "SELECT idShow FROM tvshow WHERE C12 = ?"
kodicursor.execute(query, (tvdb,))
query = "SELECT idShow FROM tvshow_view WHERE uniqueid_value = ?"
kodicursor.execute(query, (tvdb,))
try:
temps_showid = kodicursor.fetchall()
@ -342,51 +338,22 @@ class TVShows(Items):
force_episodes = True
# Verify series pooling
"""if not update_item and tvdb:
query = "SELECT idShow FROM tvshow WHERE C12 = ?"
kodicursor.execute(query, (tvdb,))
try:
temp_showid = kodicursor.fetchone()[0]
except TypeError:
pass
else:
emby_other = emby_db.getItem_byKodiId(temp_showid, "tvshow")
if emby_other and viewid == emby_other[2]:
log.info("Applying series pooling for %s", title)
emby_other_item = emby_db.getItem_byId(emby_other[0])
showid = emby_other_item[0]
pathid = emby_other_item[2]
log.info("showid: %s pathid: %s", showid, pathid)
# Create the reference in emby table
emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid,
checksum=checksum, mediafolderid=viewid)
update_item = True"""
##### UPDATE THE TVSHOW #####
if update_item:
log.info("UPDATE tvshow itemid: %s - Title: %s", itemid, title)
# update new ratings Kodi 17
if self.kodi_version > 16:
ratingid = self.kodi_db.get_ratingid("tvshow", showid)
# update ratings
ratingid = self.kodi_db.get_ratingid("tvshow", showid)
self.kodi_db.update_ratings(showid, "tvshow", "default", rating, votecount,ratingid)
self.kodi_db.update_ratings(showid, "tvshow", "default", rating, votecount,ratingid)
# update new uniqueid Kodi 17
if self.kodi_version > 16:
uniqueid = self.kodi_db.get_uniqueid("tvshow", showid)
self.kodi_db.update_uniqueid(showid, "tvshow", tvdb, "unknown", uniqueid)
# update uniqueid
uniqueid = self.kodi_db.get_uniqueid("tvshow", showid)
self.kodi_db.update_uniqueid(showid, "tvshow", tvdb, "unknown", uniqueid)
# Update the tvshow entry
if self.kodi_version > 16:
self.kodi_db.update_tvshow(title, plot, uniqueid, premieredate, genre, title,
uniqueid, mpaa, studio, sorttitle, showid)
else:
self.kodi_db.update_tvshow(title, plot, rating, premieredate, genre, title,
tvdb, mpaa, studio, sorttitle, showid)
self.kodi_db.update_tvshow(title, plot, uniqueid, premieredate, genre, title,
uniqueid, mpaa, studio, sorttitle, showid)
# Update the checksum in emby table
emby_db.updateReference(itemid, checksum)
@ -394,17 +361,13 @@ class TVShows(Items):
else:
log.info("ADD tvshow itemid: %s - Title: %s", itemid, title)
# add new ratings Kodi 17
if self.kodi_version > 16:
ratingid = self.kodi_db.create_entry_rating()
# add ratings
ratingid = self.kodi_db.create_entry_rating()
self.kodi_db.add_ratings(ratingid, showid, "tvshow", "default", rating, votecount)
self.kodi_db.add_ratings(ratingid, showid, "tvshow", "default", rating, votecount)
# add new uniqueid Kodi 17
if self.kodi_version > 16:
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, showid, "tvshow", tvdb, "unknown")
# add uniqueid
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, showid, "tvshow", tvdb, "unknown")
# Add top path
toppathid = self.kodi_db.add_path(toplevelpath)
@ -414,12 +377,8 @@ class TVShows(Items):
pathid = self.kodi_db.add_path(path)
# Create the tvshow entry
if self.kodi_version > 16:
self.kodi_db.add_tvshow(showid, title, plot, uniqueid, premieredate, genre,
title, uniqueid, mpaa, studio, sorttitle)
else:
self.kodi_db.add_tvshow(showid, title, plot, rating, premieredate, genre,
title, tvdb, mpaa, studio, sorttitle)
self.kodi_db.add_tvshow(showid, title, plot, uniqueid, premieredate, genre,
title, uniqueid, mpaa, studio, sorttitle)
# Create the reference in emby table
emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid,
@ -629,39 +588,24 @@ class TVShows(Items):
'dbid': episodeid,
'mode': "play"
}
filename = plugin_path(path, params)
filename = urllib_path(path, params)
##### UPDATE THE EPISODE #####
if update_item:
log.info("UPDATE episode itemid: %s - Title: %s", itemid, title)
# update new ratings Kodi 17
if self.kodi_version >= 17:
ratingid = self.kodi_db.get_ratingid("episode", episodeid)
# update ratings
ratingid = self.kodi_db.get_ratingid("episode", episodeid)
self.kodi_db.update_ratings(episodeid, "episode", "default", rating, votecount, ratingid)
self.kodi_db.update_ratings(episodeid, "episode", "default", rating, votecount,ratingid)
# update new uniqueid Kodi 17
if self.kodi_version >= 17:
uniqueid = self.kodi_db.get_uniqueid("episode", episodeid)
self.kodi_db.update_uniqueid(episodeid, "episode", tvdb, "tvdb",uniqueid)
# update uniqueid
uniqueid = self.kodi_db.get_uniqueid("episode", episodeid)
self.kodi_db.update_uniqueid(episodeid, "episode", tvdb, "tvdb", uniqueid)
# Update the episode entry
if self.kodi_version >= 17:
# Kodi Krypton
self.kodi_db.update_episode_16(title, plot, uniqueid, writer, premieredate, runtime,
director, season, episode, title, airsBeforeSeason,
airsBeforeEpisode, seasonid, showid, episodeid)
elif self.kodi_version >= 16 and self.kodi_version < 17:
# Kodi Jarvis
self.kodi_db.update_episode_16(title, plot, rating, writer, premieredate, runtime,
director, season, episode, title, airsBeforeSeason,
airsBeforeEpisode, seasonid, showid, episodeid)
else:
self.kodi_db.update_episode(title, plot, rating, writer, premieredate, runtime,
director, season, episode, title, airsBeforeSeason,
airsBeforeEpisode, showid, episodeid)
self.kodi_db.update_episode(title, plot, uniqueid, writer, premieredate, runtime,
director, season, episode, title, airsBeforeSeason,
airsBeforeEpisode, seasonid, showid, episodeid)
# Update the checksum in emby table
emby_db.updateReference(itemid, checksum)
@ -672,17 +616,13 @@ class TVShows(Items):
else:
log.info("ADD episode itemid: %s - Title: %s", itemid, title)
# add new ratings Kodi 17
if self.kodi_version >= 17:
ratingid = self.kodi_db.create_entry_rating()
# add ratings
ratingid = self.kodi_db.create_entry_rating()
self.kodi_db.add_ratings(ratingid, episodeid, "episode", "default", rating, votecount)
self.kodi_db.add_ratings(ratingid, episodeid, "episode", "default", rating, votecount)
# add new uniqueid Kodi 17
if self.kodi_version >= 17:
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, episodeid, "episode", tvdb, "tvdb")
# add uniqueid
uniqueid = self.kodi_db.create_entry_uniqueid()
self.kodi_db.add_uniqueid(uniqueid, episodeid, "episode", tvdb, "tvdb")
# Add path
pathid = self.kodi_db.add_path(path)
@ -690,20 +630,9 @@ class TVShows(Items):
fileid = self.kodi_db.add_file(filename, pathid)
# Create the episode entry
if self.kodi_version >= 17:
# Kodi Krypton
self.kodi_db.add_episode_16(episodeid, fileid, title, plot, uniqueid, writer,
premieredate, runtime, director, season, episode, title,
showid, airsBeforeSeason, airsBeforeEpisode, seasonid)
elif self.kodi_version >= 16 and self.kodi_version < 17:
# Kodi Jarvis
self.kodi_db.add_episode_16(episodeid, fileid, title, plot, rating, writer,
premieredate, runtime, director, season, episode, title,
showid, airsBeforeSeason, airsBeforeEpisode, seasonid)
else:
self.kodi_db.add_episode(episodeid, fileid, title, plot, rating, writer,
premieredate, runtime, director, season, episode, title,
showid, airsBeforeSeason, airsBeforeEpisode)
self.kodi_db.add_episode(episodeid, fileid, title, plot, uniqueid, writer,
premieredate, runtime, director, season, episode, title,
showid, airsBeforeSeason, airsBeforeEpisode, seasonid)
# Create the reference in emby table
emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid,

View file

@ -21,6 +21,8 @@ import playutils as putils
import playlist
import read_embyserver as embyserver
import shutil
import embydb_functions as embydb
from database import DatabaseConn
from utils import window, settings, language as lang
#################################################################################################
@ -30,329 +32,147 @@ log = logging.getLogger("EMBY."+__name__)
#################################################################################################
class PlaybackUtils():
def __init__(self, item):
class PlaybackUtils(object):
self.item = item
self.API = api.API(self.item)
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.userid = window('emby_currUser')
self.server = window('emby_server%s' % self.userid)
def __init__(self, item=None, item_id=None):
self.artwork = artwork.Artwork()
self.emby = embyserver.Read_EmbyServer()
self.pl = playlist.Playlist()
self.item = item or self.emby.getItem(item_id)
self.API = api.API(self.item)
def play(self, itemid, dbid=None):
self.server = window('emby_server%s' % window('emby_currUser'))
self.stack = []
if self.item['Type'] == "Audio":
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
else:
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
def play(self, item_id, dbid=None, force_transcode=False):
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(self.item)
log.info("Play called.")
playurl = playutils.getPlayUrl()
if not playurl:
log.info("Play called: %s", self.item['Name'])
resume = window('emby.resume')
window('emby.resume', clear=True)
play_url = putils.PlayUtils(self.item, listitem).get_play_url(force_transcode)
if not play_url:
if play_url == False: # User backed-out of menu
self.playlist.clear()
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
if dbid is None:
# Item is not in Kodi database
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
seektime = 0 if resume == "true" else self.API.adjust_resume(self.API.get_userdata()['Resume'])
# TODO: Review once Krypton is RC, no need for workaround.
if force_transcode:
log.info("Clear the playlist.")
self.playlist.clear()
############### ORGANIZE CURRENT PLAYLIST ################
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
startPos = max(playlist.getposition(), 0) # Can return -1
sizePlaylist = playlist.size()
currentPosition = startPos
self.set_playlist(play_url, item_id, listitem, seektime, dbid)
propertiesPlayback = window('emby_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
##### SETUP PLAYBACK
log.debug("Playlist start position: %s" % startPos)
log.debug("Playlist plugin position: %s" % currentPosition)
log.debug("Playlist size: %s" % sizePlaylist)
''' To get everything to work together, play the first item in the stack with setResolvedUrl,
add the rest to the regular playlist.
'''
############### RESUME POINT ################
userdata = self.API.get_userdata()
seektime = self.API.adjust_resume(userdata['Resume'])
index = max(self.playlist.getposition(), 0) + 1 # Can return -1
force_play = False
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
window('emby_playbackProps', value="true")
log.info("Setting up properties in playlist.")
if not homeScreen and not seektime and window('emby_customPlaylist') != "true":
log.debug("Adding dummy file to playlist.")
dummyPlaylist = True
playlist.add(playurl, listitem, index=startPos)
# Remove the original item from playlist
self.pl.remove_from_playlist(startPos+1)
# Readd the original item to playlist - via jsonrpc so we have full metadata
self.pl.insert_to_playlist(currentPosition+1, dbid, self.item['Type'].lower())
currentPosition += 1
############### -- CHECK FOR INTROS ################
if settings('enableCinema') == "true" and not seektime:
# if we have any play them when the movie/show is not being resumed
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
intros = self.doUtils(url)
if intros['TotalRecordCount'] != 0:
getTrailers = True
if settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016))
if not resp:
# User selected to not play trailers
getTrailers = False
log.info("Skip trailers.")
if getTrailers:
for intro in intros['Items']:
# The server randomly returns intros, process them.
introListItem = xbmcgui.ListItem()
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
log.info("Adding Intro: %s" % introPlayurl)
# Set listitem and properties for intros
pbutils = PlaybackUtils(intro)
pbutils.setProperties(introPlayurl, introListItem)
self.pl.insert_to_playlist(currentPosition, url=introPlayurl)
introsPlaylist = True
currentPosition += 1
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
log.info("Adding main item to playlist.")
self.pl.add_to_playlist(dbid, self.item['Type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if self.item.get('PartCount'):
# Only add to the playlist after intros have played
partcount = self.item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
parts = self.doUtils(url)
for part in parts['Items']:
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = putils.PlayUtils(part).getPlayUrl()
log.info("Adding additional part: %s" % partcount)
# Set listitem and properties for each additional parts
pbutils = PlaybackUtils(part)
pbutils.setProperties(additionalPlayurl, additionalListItem)
pbutils.setArtwork(additionalListItem)
playlist.add(additionalPlayurl, additionalListItem, index=currentPosition)
self.pl.verify_playlist()
currentPosition += 1
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
log.info("Processed as a playlist. First item is skipped.")
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
log.debug("Resetting properties playback flag.")
window('emby_playbackProps', clear=True)
#self.pl.verify_playlist()
########## SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if window('emby_%s.playmethod' % playurl) == "Transcode":
# Filter ISO since Emby does not probe anymore
if self.item.get('VideoType') == "Iso":
log.info("Skipping audio/subs prompt, ISO detected.")
else:
playurl = playutils.audioSubsPref(playurl, listitem)
window('emby_%s.playmethod' % playurl, value="Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
if homeScreen and seektime and window('emby_customPlaylist') != "true":
log.info("Play as a widget item.")
self.setListItem(listitem, dbid)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and window('emby_customPlaylist') == "true") or
(homeScreen and not sizePlaylist)):
# Playlist was created just now, play it.
log.info("Play playlist.")
xbmc.Player().play(playlist, startpos=startPos)
else:
log.info("Play as a regular item.")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
itemid = self.item['Id']
itemtype = self.item['Type']
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(self.item.get('RunTimeTicks')))
window('%s.type' % embyitem, value=itemtype)
window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode":
window('%s.refreshid' % embyitem, value=self.item.get('SeriesId'))
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = window('%s.playmethod' % embyitem)
# Only for direct stream
if playmethod in ("DirectStream") and settings('enableExternalSubs') == "true":
# Direct play automatically appends external
subtitles = self.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def externalSubs(self, playurl):
externalsubs = []
mapping = {}
itemid = self.item['Id']
# Stack: [(url, listitem), (url, ...), ...]
self.stack[0][1].setPath(self.stack[0][0])
try:
mediastreams = self.item['MediaSources'][0]['MediaStreams']
except (TypeError, KeyError, IndexError):
return
#if not xbmc.getCondVisibility('Window.IsVisible(MyVideoNav.xml)'): # Causes infinite loop with play from here
if xbmc.getCondVisibility('Window.IsVisible(10000).xml'):
# widgets do not fill artwork correctly
log.info("Detected widget.")
raise IndexError
temp = xbmc.translatePath(
"special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8')
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, self.stack[0][1])
self.stack.pop(0) # remove the first item we just started.
except IndexError:
log.info("Playback activated via the context menu or widgets.")
force_play = True
self.stack[0][1].setProperty('StartOffset', str(seektime))
kodiindex = 0
for stream in mediastreams:
for stack in self.stack:
self.playlist.add(url=stack[0], listitem=stack[1], index=index)
index += 1
index = stream['Index']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
if (stream['Type'] == "Subtitle" and
stream['IsExternal'] and stream['IsTextSubtitleStream']):
if force_play:
xbmc.Player().play(self.playlist)
# Direct stream
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.%s"
% (self.server, itemid, itemid, index, stream['Codec']))
def set_playlist(self, play_url, item_id, listitem, seektime=None, db_id=None):
if "Language" in stream:
filename = "Stream.%s.%s" % (stream['Language'], stream['Codec'])
try:
path = self._download_external_subs(url, temp, filename)
externalsubs.append(path)
except Exception as e:
log.error(e)
externalsubs.append(url)
else:
externalsubs.append(url)
# map external subtitles for mapping
mapping[kodiindex] = index
kodiindex += 1
mapping = json.dumps(mapping)
window('emby_%s.indexMapping' % playurl, value=mapping)
##### CHECK FOR INTROS
return externalsubs
if settings('enableCinema') == "true" and not seektime:
self._set_intros(item_id)
def _download_external_subs(self, src, dst, filename):
##### ADD MAIN ITEM
if not xbmcvfs.exists(dst):
xbmcvfs.mkdir(dst)
self.set_properties(play_url, listitem)
self.set_listitem(listitem, db_id)
self.stack.append([play_url, listitem])
path = os.path.join(dst, filename)
##### ADD ADDITIONAL PARTS
try:
response = requests.get(src, stream=True)
response.raise_for_status()
except Exception as e:
raise
else:
response.encoding = 'utf-8'
with open(path, 'wb') as f:
f.write(response.content)
del response
if self.item.get('PartCount'):
self._set_additional_parts(item_id)
return path
def _set_intros(self, item_id):
# if we have any play them when the movie/show is not being resumed
intros = self.emby.get_intros(item_id)
def setArtwork(self, listItem):
# Set up item and item info
allartwork = self.artwork.get_all_artwork(self.item, parent_info=True)
# Set artwork for listitem
arttypes = {
if intros['Items']:
enabled = True
'poster': "Primary",
'tvshow.poster': "Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb"
}
for arttype in arttypes:
if settings('askCinema') == "true":
art = arttypes[arttype]
if art == "Backdrop":
try: # Backdrop is a list, grab the first backdrop
self.setArtProp(listItem, arttype, allartwork[art][0])
except: pass
else:
self.setArtProp(listItem, arttype, allartwork[art])
resp = xbmcgui.Dialog().yesno("Emby for Kodi", lang(33016))
if not resp:
# User selected to not play trailers
enabled = False
log.info("Skip trailers.")
def setArtProp(self, listItem, arttype, path):
if arttype in (
'thumb', 'fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listItem.setProperty(arttype, path)
else:
listItem.setArt({arttype: path})
if enabled:
for intro in intros['Items']:
def setListItem(self, listItem, dbid=None):
listitem = xbmcgui.ListItem()
url = putils.PlayUtils(intro, listitem).get_play_url()
log.info("Adding Intro: %s" % url)
self.stack.append([url, listitem])
def _set_additional_parts(self, item_id):
parts = self.emby.get_additional_parts(item_id)
for part in parts['Items']:
listitem = xbmcgui.ListItem()
url = putils.PlayUtils(part, listitem).get_play_url()
log.info("Adding additional part: %s" % url)
# Set listitem and properties for each additional parts
pb = PlaybackUtils(part)
pb.set_properties(url, listitem)
pb.setArtwork(listitem)
self.stack.append([url, listitem])
def set_listitem(self, listitem, dbid=None):
people = self.API.get_people()
studios = self.API.get_studios()
mediatype = self.item['Type']
metadata = {
'title': self.item.get('Name', "Missing name"),
'year': self.item.get('ProductionYear'),
'plot': self.API.get_overview(),
@ -360,33 +180,151 @@ class PlaybackUtils():
'writer': people.get('Writer'),
'mpaa': self.API.get_mpaa(),
'genre': " / ".join(self.item['Genres']),
'studio': " / ".join(studios),
'studio': " / ".join(self.API.get_studios()),
'aired': self.API.get_premiere_date(),
'rating': self.item.get('CommunityRating'),
'votes': self.item.get('VoteCount')
}
if "Episode" in self.item['Type']:
if mediatype == "Episode":
# Only for tv shows
# For Kodi Krypton
metadata['mediatype'] = "episode"
metadata['dbid'] = dbid
metadata['TVShowTitle'] = self.item.get('SeriesName', "")
metadata['season'] = self.item.get('ParentIndexNumber', -1)
metadata['episode'] = self.item.get('IndexNumber', -1)
thumbId = self.item.get('SeriesId')
season = self.item.get('ParentIndexNumber', -1)
episode = self.item.get('IndexNumber', -1)
show = self.item.get('SeriesName', "")
metadata['TVShowTitle'] = show
metadata['season'] = season
metadata['episode'] = episode
if "Movie" in self.item['Type']:
# For Kodi Krypton
elif mediatype == "Movie":
metadata['mediatype'] = "movie"
elif mediatype == "MusicVideo":
metadata['mediatype'] = "musicvideo"
elif mediatype == "Audio":
metadata['mediatype'] = "song"
if dbid:
metadata['dbid'] = dbid
listItem.setProperty('IsPlayable', 'true')
listItem.setProperty('IsFolder', 'false')
listItem.setLabel(metadata['title'])
listItem.setInfo('video', infoLabels=metadata)
listitem.setProperty('IsPlayable', 'true')
listitem.setProperty('IsFolder', 'false')
listitem.setLabel(metadata['title'])
listitem.setInfo('Music' if mediatype == "Audio" else 'Video', infoLabels=metadata)
def set_properties(self, url, listitem):
# Set all properties necessary for plugin path playback
item_id = self.item['Id']
item_type = self.item['Type']
play_method = window('emby_%s.playmethod' % url)
window('emby_%s.playmethod' % url, clear=True)
window('emby_%s.json' % url, {
'url': url,
'runtime': str(self.item.get('RunTimeTicks')),
'type': item_type,
'id': item_id,
'refreshid': self.item.get('SeriesId') if item_type == "Episode" else item_id,
'playmethod': play_method
})
self.set_artwork(listitem, item_type)
listitem.setCast(self.API.get_actors())
def set_artwork(self, listitem, item_type):
all_artwork = self.artwork.get_all_artwork(self.item, parent_info=True)
# Set artwork for listitem
if item_type == "Episode":
art = {
'poster': "Series.Primary",
'tvshow.poster': "Series.Primary",
'clearart': "Art",
'tvshow.clearart': "Art",
'clearlogo': "Logo",
'tvshow.clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb",
'tvshow.landscape': "Thumb",
'thumb': "Primary"
}
else:
art = {
'poster': "Primary",
'clearart': "Art",
'clearlogo': "Logo",
'discart': "Disc",
'fanart_image': "Backdrop",
'landscape': "Thumb",
'thumb': "Primary"
}
for k_art, e_art in art.items():
if e_art == "Backdrop" and all_artwork[e_art]:
self._set_art(listitem, k_art, all_artwork[e_art][0])
else:
self._set_art(listitem, k_art, all_artwork.get(e_art))
def _set_art(self, listitem, art, path):
if path:
if art in ('fanart_image', 'small_poster', 'tiny_poster',
'medium_landscape', 'medium_poster', 'small_fanartimage',
'medium_fanartimage', 'fanart_noindicators'):
listitem.setProperty(art, path)
else:
listitem.setArt({art: path})
def play_all(self, item_ids, seektime=None, **kwargs):
self.playlist.clear()
started = False
for item_id in item_ids:
listitem = xbmcgui.ListItem()
db_id = None
item = self.emby.getItem(item_id)
play_url = putils.PlayUtils(item, listitem, **kwargs if item_ids.index(item_id) == 0 else {}).get_play_url()
if not play_url:
log.info("Failed to retrieve playurl")
continue
log.info("Playurl: %s", play_url)
with DatabaseConn('emby') as cursor:
item_db = embydb.Embydb_Functions(cursor).getItem_byId(item_id)
db_id = item_db[0] if item_db else None
pbutils = PlaybackUtils(item)
pbutils.set_playlist(play_url, item_id, listitem, seektime if item_ids.index(item_id) == 1 else None, db_id)
if item_ids.index(item_id) == 1 and seektime:
log.info("Seektime detected: %s", self.API.adjust_resume(seektime))
listitem.setProperty('StartOffset', str(self.API.adjust_resume(seektime)))
index = max(pbutils.playlist.getposition(), 0) + 1 # Can return -1
for stack in pbutils.stack:
pbutils.playlist.add(url=stack[0], listitem=stack[1], index=index)
index += 1
if not started:
started = True
item = window('emby_%s.json' % play_url)
item['forcedaudio'] = kwargs.get('AudioStreamIndex')
item['forcedsubs'] = kwargs.get('SubtitleStreamIndex')
window('emby_%s.json' % play_url, value=item)
player = xbmc.Player()
player.play(pbutils.playlist)
if started:
return True

View file

@ -12,7 +12,7 @@ import xbmcgui
import clientinfo
import downloadutils
import websocket_client as wsc
from utils import window, settings, language as lang
from utils import window, settings, language as lang, JSONRPC
from ga_client import GoogleAnalytics, log_error
#################################################################################################
@ -43,6 +43,39 @@ class Player(xbmc.Player):
log.debug("Starting playback monitor.")
xbmc.Player.__init__(self)
def set_audio_subs(self, audio_index=None, subs_index=None):
''' Only for after playback started
'''
player = xbmc.Player()
log.info("Setting audio: %s subs: %s", audio_index, subs_index)
if audio_index and len(player.getAvailableAudioStreams()) > 1:
player.setAudioStream(audio_index - 1)
if subs_index:
mapping = window('emby_%s.indexMapping.json' % self.current_file)
if subs_index == -1:
player.showSubtitles(False)
elif mapping:
external_index = mapping
# If there's external subtitles added via playbackutils
for index in external_index:
if external_index[index] == subs_index:
player.setSubtitleStream(int(index))
break
else:
# User selected internal subtitles
external = len(external_index)
audio_tracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(external + subs_index - audio_tracks - 1)
else:
# Emby merges audio and subtitle index together
audio_tracks = len(player.getAvailableAudioStreams())
player.setSubtitleStream(subs_index - audio_tracks - 1)
@log_error()
def onPlayBackStarted(self):
# Will be called when xbmc starts playing a file
@ -74,28 +107,31 @@ class Player(xbmc.Player):
self.currentFile = currentFile
# We may need to wait for info to be set in kodi monitor
itemId = window("emby_%s.itemid" % currentFile)
item = window('emby_%s.json' % currentFile)
#itemId = window("emby_%s.itemid" % currentFile)
tryCount = 0
while not itemId:
while not item:
xbmc.sleep(200)
itemId = window("emby_%s.itemid" % currentFile)
item = window('emby_%s.json' % currentFile)
if tryCount == 20: # try 20 times or about 10 seconds
log.info("Could not find itemId, cancelling playback report...")
log.info("Could not find item, cancelling playback report...")
break
else: tryCount += 1
else:
log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId))
item_id = item.get('id')
log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, item_id))
# Only proceed if an itemId was found.
embyitem = "emby_%s" % currentFile
runtime = window("%s.runtime" % embyitem)
refresh_id = window("%s.refreshid" % embyitem)
playMethod = window("%s.playmethod" % embyitem)
itemType = window("%s.type" % embyitem)
window('emby_skipWatched%s' % itemId, value="true")
runtime = item.get('runtime')
refresh_id = item.get('refreshid')
play_method = item.get('playmethod')
item_type = item.get('type')
#self.set_audio_subs(item.get('forcedaudio'), item.get('forcedsubs'))
window('emby_skipWatched%s' % item_id, value="true")
customseek = window('emby_customPlaylist.seektime')
if window('emby_customPlaylist') == "true" and customseek:
# Start at, when using custom playlist (play to Kodi from webclient)
@ -110,18 +146,7 @@ class Player(xbmc.Player):
return
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
result = result.get('result')
volume = result.get('volume')
@ -133,33 +158,26 @@ class Player(xbmc.Player):
'QueueableMediaTypes': "Video",
'CanSeek': True,
'ItemId': itemId,
'MediaSourceId': itemId,
'PlayMethod': playMethod,
'ItemId': item_id,
'MediaSourceId': item_id,
'PlayMethod': play_method,
'VolumeLevel': volume,
'PositionTicks': int(seekTime * 10000000),
'IsMuted': muted
}
# Get the current audio track and subtitles
if playMethod == "Transcode":
if play_method == "Transcode":
# property set in PlayUtils.py
postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile)
postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile)
else:
# Get the current kodi audio and subtitles and convert to Emby equivalent
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
params = {
'playerid': 1,
'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = JSONRPC('Player.GetProperties').execute(params)
tracks_data = None
try:
tracks_data = json.loads(result)
@ -190,7 +208,7 @@ class Player(xbmc.Player):
# Number of audiotracks to help get Emby Index
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
mapping = window("%s.indexMapping" % embyitem)
mapping = window("emby_%s.indexMapping" % currentFile)
if mapping: # Set in playbackutils.py
@ -230,13 +248,13 @@ class Player(xbmc.Player):
data = {
'runtime': runtime,
'item_id': itemId,
'item_id': item_id,
'refresh_id': refresh_id,
'currentfile': currentFile,
'AudioStreamIndex': postdata['AudioStreamIndex'],
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
'playmethod': playMethod,
'Type': itemType,
'playmethod': play_method,
'Type': item_type,
'currentPosition': int(seekTime)
}
@ -244,8 +262,8 @@ class Player(xbmc.Player):
log.info("ADDING_FILE: %s" % self.played_info)
ga = GoogleAnalytics()
ga.sendEventData("PlayAction", itemType, playMethod)
ga.sendScreenView(itemType)
ga.sendEventData("PlayAction", item_type, play_method)
ga.sendScreenView(item_type)
def reportPlayback(self):
@ -267,18 +285,7 @@ class Player(xbmc.Player):
# Get playback volume
volume_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Application.GetProperties",
"params": {
"properties": ["volume", "muted"]
}
}
result = xbmc.executeJSONRPC(json.dumps(volume_query))
result = json.loads(result)
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
result = result.get('result')
volume = result.get('volume')
@ -305,19 +312,11 @@ class Player(xbmc.Player):
else:
# Get current audio and subtitles track
tracks_query = {
"jsonrpc": "2.0",
"id": 1,
"method": "Player.GetProperties",
"params": {
"playerid": 1,
"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
}
result = xbmc.executeJSONRPC(json.dumps(tracks_query))
result = json.loads(result)
params = {
'playerid': 1,
'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"]
}
result = JSONRPC('Player.GetProperties').execute(params)
result = result.get('result')
try: # Audio tracks
@ -413,10 +412,15 @@ class Player(xbmc.Player):
def onPlayBackStopped(self):
# Will be called when user stops xbmc playing a file
log.debug("ONPLAYBACK_STOPPED")
window('emby_customPlaylist', clear=True)
window('emby_customPlaylist.seektime', clear=True)
window('emby_playbackProps', clear=True)
log.info("Clear playlist properties.")
if self.currentFile in self.played_info:
log.info("Clear playlist.")
if self.played_info[self.currentFile]['Type'] == "Audio":
xbmc.PlayList(xbmc.PLAYLIST_MUSIC).clear()
else:
xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear()
self.stopAll()
@log_error()
@ -456,10 +460,16 @@ class Player(xbmc.Player):
if currentPosition and runtime:
try:
if window('emby.external'):
window('emby.external', clear=True)
raise ValueError
percentComplete = (currentPosition * 10000000) / int(runtime)
except ZeroDivisionError:
# Runtime is 0.
percentComplete = 0
except ValueError:
percentComplete = 100
markPlayedAt = float(settings('markPlayed')) / 100
log.info("Percent complete: %s Mark played at: %s"
@ -486,6 +496,8 @@ class Player(xbmc.Player):
else:
log.info("User skipped deletion.")
window('emby.external_check', clear=True)
# Stop transcoding
if playMethod == "Transcode":
log.info("Transcoding for %s terminated." % itemid)

View file

@ -145,7 +145,7 @@ class Playlist(object):
@classmethod
def verify_playlist(cls):
log.debug(JSONRPC('Playlist.GetItems').execute({'playlistid': 1}))
log.info(JSONRPC('Playlist.GetItems').execute({'playlistid': 1}))
@classmethod
def remove_from_playlist(cls, position):
@ -156,3 +156,171 @@ class Playlist(object):
'position': position
}
log.debug(JSONRPC('Playlist.Remove').execute(params))
'''
# -*- coding: utf-8 -*-
#################################################################################################
import logging
import xbmc
import xbmcgui
import playutils
import playbackutils
import embydb_functions as embydb
import read_embyserver as embyserver
from utils import window, JSONRPC
from database import DatabaseConn
#################################################################################################
log = logging.getLogger("EMBY."+__name__)
#################################################################################################
class Playlist(object):
def __init__(self):
self.emby = embyserver.Read_EmbyServer()
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
def play_all(self, item_ids, start_at, **kwargs):
with DatabaseConn('emby') as cursor:
emby_db = embydb.Embydb_Functions(cursor)
player = xbmc.Player()
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playlist.clear()
log.info("---*** PLAY ALL ***---")
log.info("Items: %s and start at: %s", item_ids, start_at)
started = False
window('emby_customplaylist', value="true")
if start_at:
# Seek to the starting position
window('emby_customplaylist.seektime', str(start_at))
for item_id in item_ids:
log.info("Adding %s to playlist", item_id)
item = emby_db.getItem_byId(item_id)
try:
db_id = item[0]
media_type = item[4]
except TypeError:
# Item is not found in our database, add item manually
log.info("Item was not found in the database, manually adding item")
item = self.emby.getItem(item_id)
self.add_to_xbmc_playlist(playlist, item, **kwargs)
else: # Add to playlist
#self.add_to_playlist(db_id, media_type)
item = self.emby.getItem(item_id)
self.add_to_xbmc_playlist(playlist, item, db_id, **kwargs)
if not started:
started = True
player.play(playlist)
self.verify_playlist()
def modify_playlist(self, item_ids):
with DatabaseConn('emby') as cursor:
emby_db = embydb.Embydb_Functions(cursor)
log.info("---*** ADD TO PLAYLIST ***---")
log.info("Items: %s", item_ids)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
for item_id in item_ids:
log.info("Adding %s to playlist", item_id)
item = emby_db.getItem_byId(item_id)
try:
db_id = item[0]
media_type = item[4]
except TypeError:
# Item is not found in our database, add item manually
item = self.emby.getItem(item_id)
self.add_to_xbmc_playlist(playlist, item)
else: # Add to playlist
self.add_to_playlist(db_id, media_type)
self.verify_playlist()
return playlist
@classmethod
def add_to_xbmc_playlist(cls, playlist, item, db_id, **kwargs):
listitem = xbmcgui.ListItem()
play_url = playutils.PlayUtils(item, listitem, **kwargs).get_play_url()
if not play_url:
log.info("Failed to retrieve playurl")
return
log.info("Playurl: %s", play_url)
#playbackutils.PlaybackUtils(item).set_playlist(play_url, item['Id'], listitem, seektime=None, db_id):
#playbackutils.PlaybackUtils(item).set_properties(playurl, listitem)
#playlist.add(playurl, listitem)
@classmethod
def add_to_playlist(cls, db_id, media_type):
params = {
'playlistid': 1,
'item': {
'%sid' % media_type: int(db_id)
}
}
log.info(JSONRPC('Playlist.Add').execute(params))
@classmethod
def insert_to_playlist(cls, position, db_id=None, media_type=None, url=None):
params = {
'playlistid': 1,
'position': position
}
if db_id is not None:
params['item'] = {'%sid' % media_type: int(db_id)}
else:
params['item'] = {'file': url}
log.debug(JSONRPC('Playlist.Insert').execute(params))
@classmethod
def verify_playlist(cls):
log.info(JSONRPC('Playlist.GetItems').execute({'playlistid': 1}))
@classmethod
def remove_from_playlist(cls, position):
params = {
'playlistid': 1,
'position': position
}
log.debug(JSONRPC('Playlist.Remove').execute(params))
'''

File diff suppressed because it is too large Load diff

View file

@ -151,7 +151,7 @@ class Read_EmbyServer():
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
"MediaSources,VoteCount"
"MediaSources,VoteCount,ItemCounts"
)
}
queue.put({'url': url, 'params': params})
@ -182,7 +182,7 @@ class Read_EmbyServer():
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts"
)
}
return self.doUtils.downloadUrl("{server}/emby/Users/{UserId}/Items?format=json", parameters=params)
@ -198,7 +198,7 @@ class Read_EmbyServer():
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts"
)
}
url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json"
@ -219,7 +219,7 @@ class Read_EmbyServer():
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,"
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,ItemCounts"
)
}
url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json"
@ -285,7 +285,7 @@ class Read_EmbyServer():
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
"MediaSources,VoteCount"
"MediaSources,VoteCount,ItemCounts"
)
queue.put({'url': url, 'params': params})
if not self._add_worker_thread(queue, items['Items']):
@ -472,7 +472,7 @@ class Read_EmbyServer():
"Etag,Genres,SortName,Studios,Writer,ProductionYear,"
"CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,"
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview"
"AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview,ItemCounts"
)
}
queue.put({'url': url, 'params': params})
@ -614,7 +614,7 @@ class Read_EmbyServer():
"Metascore,AirTime,DateCreated,MediaStreams,People,Overview,"
"CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations,"
"Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers,"
"MediaSources,VoteCount"
"MediaSources,VoteCount,ItemCounts"
)
})
return params
@ -649,6 +649,39 @@ class Read_EmbyServer():
return library['LibraryOptions']
def get_server_transcoding_settings(self):
return self.doUtils.downloadUrl(self.get_emby_url('System/Configuration/encoding'))
url = self.get_emby_url('/System/Configuration/encoding')
return self.doUtils.downloadUrl(url)
def get_intros(self, item_id):
return self.doUtils.downloadUrl(self.get_emby_url('Users/{UserId}/Items/%s/Intros' % item_id))
def get_additional_parts(self, item_id):
return self.doUtils.downloadUrl(self.get_emby_url('Videos/%s/AdditionalParts' % item_id))
def get_playback_info(self, item_id, profile, offset=0, audio=None, subtitles=None):
url = self.get_emby_url('Items/%s/PlaybackInfo' % item_id)
return self.doUtils.downloadUrl(url, action_type="POST", postBody={
'UserId': self.userId,
'DeviceProfile': profile,
'StartTimeTicks': offset, #TODO
'AudioStreamIndex': audio, #TODO
'SubtitleStreamIndex': subtitles, #TODO
'MediaSourceId': None,
'LiveStreamId': None
})
def get_live_stream(self, item_id, profile, session_id, token, offset=0, audio=None, subtitles=None):
url = self.get_emby_url('/LiveStreams/Open')
return self.doUtils.downloadUrl(url, action_type="POST", postBody={
'UserId': self.userId,
'DeviceProfile': profile,
'ItemId': item_id,
'PlaySessionId': session_id,
'OpenToken': token,
'StartTimeTicks': offset, #TODO
'AudioStreamIndex': audio, #TODO
'SubtitleStreamIndex': subtitles #TODO
})

View file

@ -10,6 +10,7 @@ from datetime import datetime
import platform
import xbmc
import xbmcgui
import userclient
import clientinfo
@ -52,10 +53,11 @@ class Service(object):
self.addon_name = self.client_info.get_addon_name()
log_level = settings('logLevel')
# General settings which are used by other entrypoints
window('emby_logLevel', value=str(log_level))
window('emby_kodiProfile', value=xbmc.translatePath('special://profile'))
context_menu = "true" if settings('enableContext') == "true" else ""
window('emby_context', value=context_menu)
window('emby_context', value="true" if settings('enableContext') == "true" else "")
window('emby_context_transcode', value="true" if settings('enableContextTranscode') == "true" else "")
# Initial logging
log.warn("======== START %s ========", self.addon_name)
@ -72,7 +74,8 @@ class Service(object):
"emby_online", "emby_state.json", "emby_serverStatus", "emby_onWake",
"emby_syncRunning", "emby_dbCheck", "emby_kodiScan",
"emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId",
"emby_initialScan", "emby_customplaylist", "emby_playbackProps"
"emby_initialScan", "emby_customplaylist", "emby_playbackProps",
"emby.external_check", "emby.external", "emby.resume"
]
for prop in properties:
window(prop, clear=True)
@ -319,6 +322,9 @@ class Service(object):
#ga = GoogleAnalytics()
#ga.sendEventData("Application", "Shutdown")
if self.monitor.special_monitor:
self.monitor.special_monitor.stop_monitor()
if self.userclient_running:
self.userclient_thread.stop_client()

View file

@ -313,7 +313,7 @@ class UserClient(threading.Thread):
log.info("Username found: %s", username)
self._auth = True
if monitor.waitForAbort(1):
if monitor.waitForAbort(2):
# Abort was requested while waiting. We should exit
break

View file

@ -78,7 +78,7 @@ def dialog(type_, *args, **kwargs):
}
return types[type_](*args, **kwargs)
def plugin_path(plugin, params):
def urllib_path(plugin, params):
return "%s?%s" % (plugin, urllib.urlencode(params))

View file

@ -14,7 +14,7 @@ import xbmcvfs
import read_embyserver as embyserver
import embydb_functions as embydb
from utils import window, language as lang, indent as xml_indent, plugin_path
from utils import window, language as lang, indent as xml_indent, urllib_path
#################################################################################################
@ -641,7 +641,7 @@ class VideoNodes(object):
'mode': "browsecontent",
'type': mediatype
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
elif (mediatype == "homevideos" or mediatype == "photos"):
params = {
@ -651,7 +651,7 @@ class VideoNodes(object):
'type': mediatype,
'folderid': nodetype
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
elif nodetype == "nextepisodes":
params = {
@ -660,7 +660,7 @@ class VideoNodes(object):
'mode': "nextup",
'limit': 25
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
elif KODI == 14 and nodetype == "recentepisodes":
params = {
@ -669,7 +669,7 @@ class VideoNodes(object):
'mode': "recentepisodes",
'limit': 25
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
elif KODI == 14 and nodetype == "inprogressepisodes":
params = {
@ -678,7 +678,7 @@ class VideoNodes(object):
'mode': "inprogressepisodes",
'limit': 25
}
path = plugin_path("plugin://plugin.video.emby/", params)
path = urllib_path("plugin://plugin.video.emby/", params)
else:
path = "library://video/emby/%s/%s.xml" % (viewid, nodetype)

View file

@ -14,6 +14,7 @@ import downloadutils
import librarysync
import playlist
import userclient
import playbackutils
from utils import window, settings, dialog, language as lang, JSONRPC
from ga_client import log_error
@ -112,19 +113,31 @@ class WebSocketClient(threading.Thread):
@classmethod
def _play(cls, data):
''' {"Id":"e2c106a953bfc0d9c191a49cda6561de",
"ItemIds":["52f0c57e2133e1c11d36c59edcd835fc"],
"PlayCommand":"PlayNow","ControllingUserId":"d40000000000000000000000000000000",
"SubtitleStreamIndex":3,"AudioStreamIndex":1,
"MediaSourceId":"ba83c549ac5c0e4180ae33ebdf813c51"}}
'''
item_ids = data['ItemIds']
kwargs = {
'SubtitleStreamIndex': data.get('SubtitleStreamIndex'),
'AudioStreamIndex': data.get('AudioStreamIndex'),
'MediaSourceId': data.get('MediaSourceId')
}
command = data['PlayCommand']
playlist_ = playlist.Playlist()
if command == 'PlayNow':
startat = data.get('StartPositionTicks', 0)
playlist_.play_all(item_ids, startat)
dialog(type_="notification",
heading="{emby}",
message="%s %s" % (len(item_ids), lang(33004)),
icon="{emby}",
sound=False)
if playbackutils.PlaybackUtils(None, item_ids[0]).play_all(item_ids, data.get('StartPositionTicks', 0), **kwargs):
dialog(type_="notification",
heading="{emby}",
message="%s %s" % (len(item_ids), lang(33004)),
icon="{emby}",
sound=False)
elif command == 'PlayNext':
new_playlist = playlist_.modify_playlist(item_ids)
@ -214,7 +227,7 @@ class WebSocketClient(threading.Thread):
elif command == 'SetSubtitleStreamIndex':
emby_index = int(arguments['Index'])
current_file = player.getPlayingFile()
mapping = window('emby_%s.indexMapping' % current_file)
mapping = window('emby_%s.indexMapping.json' % current_file)
if emby_index == -1:
player.showSubtitles(False)