mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-01-24 08:56:10 +00:00
Remove old code
This commit is contained in:
parent
e4e5881ffc
commit
ce47644868
10 changed files with 6 additions and 1761 deletions
|
@ -28,7 +28,7 @@ LOG = logging.getLogger("EMBY.context")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
LOG.info("--->[ context ]")
|
LOG.debug("--->[ context ]")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Context()
|
Context()
|
||||||
|
|
|
@ -28,7 +28,7 @@ LOG = logging.getLogger("EMBY.context")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
LOG.info("--->[ context ]")
|
LOG.debug("--->[ context ]")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Context(True)
|
Context(True)
|
||||||
|
|
196
default.py
196
default.py
|
@ -28,7 +28,7 @@ LOG = logging.getLogger("EMBY.default")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
LOG.info("--->[ default ]")
|
LOG.debug("--->[ default ]")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Events()
|
Events()
|
||||||
|
@ -36,197 +36,3 @@ if __name__ == "__main__":
|
||||||
LOG.exception(error)
|
LOG.exception(error)
|
||||||
|
|
||||||
LOG.info("---<[ default ]")
|
LOG.info("---<[ default ]")
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urlparse
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcplugin
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
_ADDON = xbmcaddon.Addon(id='plugin.video.emby')
|
|
||||||
_CWD = _ADDON.getAddonInfo('path').decode('utf-8')
|
|
||||||
_BASE_LIB = xbmc.translatePath(os.path.join(_CWD, 'resources', 'lib')).decode('utf-8')
|
|
||||||
sys.path.append(_BASE_LIB)
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import entrypoint
|
|
||||||
import loghandler
|
|
||||||
from utils import window, dialog, language as lang
|
|
||||||
from ga_client import GoogleAnalytics
|
|
||||||
import database
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
loghandler.config()
|
|
||||||
log = logging.getLogger("EMBY.default")
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
class Main(object):
|
|
||||||
|
|
||||||
# MAIN ENTRY POINT
|
|
||||||
#@utils.profiling()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
# Parse parameters
|
|
||||||
base_url = sys.argv[0]
|
|
||||||
path = sys.argv[2]
|
|
||||||
params = urlparse.parse_qs(path[1:])
|
|
||||||
log.warn("Parameter string: %s params: %s", path, params)
|
|
||||||
try:
|
|
||||||
mode = params['mode'][0]
|
|
||||||
except (IndexError, KeyError):
|
|
||||||
mode = ""
|
|
||||||
|
|
||||||
if "/extrafanart" in base_url:
|
|
||||||
|
|
||||||
emby_path = path[1:]
|
|
||||||
emby_id = params.get('id', [""])[0]
|
|
||||||
entrypoint.getExtraFanArt(emby_id, emby_path)
|
|
||||||
|
|
||||||
elif "/Extras" in base_url or "/VideoFiles" in base_url:
|
|
||||||
|
|
||||||
emby_path = path[1:]
|
|
||||||
emby_id = params.get('id', [""])[0]
|
|
||||||
entrypoint.getVideoFiles(emby_id, emby_path)
|
|
||||||
|
|
||||||
elif not self._modes(mode, params):
|
|
||||||
# Other functions
|
|
||||||
if mode == 'settings':
|
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)')
|
|
||||||
|
|
||||||
elif mode in ('manualsync', 'fastsync', 'repair', 'refreshboxsets'):
|
|
||||||
self._library_sync(mode)
|
|
||||||
|
|
||||||
elif mode == 'texturecache':
|
|
||||||
import artwork
|
|
||||||
artwork.Artwork().texture_cache_sync()
|
|
||||||
else:
|
|
||||||
entrypoint.doMainListing()
|
|
||||||
|
|
||||||
if sys.argv:
|
|
||||||
xbmcplugin.endOfDirectory(int(sys.argv[1]))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _modes(cls, mode, params):
|
|
||||||
import utils
|
|
||||||
modes = {
|
|
||||||
|
|
||||||
'reset': database.db_reset,
|
|
||||||
'resetauth': entrypoint.resetAuth,
|
|
||||||
'play': entrypoint.doPlayback,
|
|
||||||
'passwords': utils.passwordsXML,
|
|
||||||
'adduser': entrypoint.addUser,
|
|
||||||
'thememedia': entrypoint.getThemeMedia,
|
|
||||||
'channels': entrypoint.BrowseChannels,
|
|
||||||
'channelsfolder': entrypoint.BrowseChannels,
|
|
||||||
'browsecontent': entrypoint.BrowseContent,
|
|
||||||
'getsubfolders': entrypoint.GetSubFolders,
|
|
||||||
'nextup': entrypoint.getNextUpEpisodes,
|
|
||||||
'inprogressepisodes': entrypoint.getInProgressEpisodes,
|
|
||||||
'recentepisodes': entrypoint.getRecentEpisodes,
|
|
||||||
'refreshplaylist': entrypoint.refreshPlaylist,
|
|
||||||
'deviceid': entrypoint.resetDeviceId,
|
|
||||||
'delete': entrypoint.deleteItem,
|
|
||||||
'connect': entrypoint.emby_connect,
|
|
||||||
'backup': entrypoint.emby_backup,
|
|
||||||
|
|
||||||
'manuallogin': entrypoint.test_manual_login,
|
|
||||||
'connectlogin': entrypoint.test_connect_login,
|
|
||||||
'manualserver': entrypoint.test_manual_server,
|
|
||||||
'connectservers': entrypoint.test_connect_servers,
|
|
||||||
'connectusers': entrypoint.test_connect_users
|
|
||||||
}
|
|
||||||
if mode in modes:
|
|
||||||
# Simple functions
|
|
||||||
action = modes[mode]
|
|
||||||
item_id = params.get('id')
|
|
||||||
if item_id:
|
|
||||||
item_id = item_id[0]
|
|
||||||
|
|
||||||
if mode == 'play':
|
|
||||||
database_id = params.get('dbid')
|
|
||||||
action(item_id, database_id)
|
|
||||||
|
|
||||||
elif mode == 'recentepisodes':
|
|
||||||
limit = int(params['limit'][0])
|
|
||||||
action(item_id, limit, params.get('filters', [""])[0])
|
|
||||||
|
|
||||||
elif mode in ('nextup', 'inprogressepisodes'):
|
|
||||||
limit = int(params['limit'][0])
|
|
||||||
action(item_id, limit)
|
|
||||||
|
|
||||||
elif mode in ('channels', 'getsubfolders'):
|
|
||||||
action(item_id)
|
|
||||||
|
|
||||||
elif mode == 'browsecontent':
|
|
||||||
action(item_id, params.get('type', [""])[0], params.get('folderid', [""])[0])
|
|
||||||
|
|
||||||
elif mode == 'channelsfolder':
|
|
||||||
folderid = params['folderid'][0]
|
|
||||||
action(item_id, folderid)
|
|
||||||
else:
|
|
||||||
action()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _library_sync(cls, mode):
|
|
||||||
|
|
||||||
if window('emby_online') != "true":
|
|
||||||
# Server is not online, do not run the sync
|
|
||||||
dialog(type_="ok",
|
|
||||||
heading="{emby}",
|
|
||||||
line1=lang(33034))
|
|
||||||
log.warn("Not connected to the emby server")
|
|
||||||
|
|
||||||
elif window('emby_dbScan') != "true":
|
|
||||||
import librarysync
|
|
||||||
library_sync = librarysync.LibrarySync()
|
|
||||||
|
|
||||||
if mode == 'manualsync':
|
|
||||||
librarysync.ManualSync().sync()
|
|
||||||
elif mode == 'fastsync':
|
|
||||||
library_sync.startSync()
|
|
||||||
elif mode == 'refreshboxsets':
|
|
||||||
librarysync.ManualSync().sync('boxsets')
|
|
||||||
else:
|
|
||||||
library_sync.fullSync(repair=True)
|
|
||||||
else:
|
|
||||||
log.warn("Database scan is already running")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
log.info("plugin.video.emby started")
|
|
||||||
|
|
||||||
try:
|
|
||||||
Main()
|
|
||||||
except Exception as error:
|
|
||||||
if not (hasattr(error, 'quiet') and error.quiet):
|
|
||||||
ga = GoogleAnalytics()
|
|
||||||
errStrings = ga.formatException()
|
|
||||||
ga.sendEventData("Exception", errStrings[0], errStrings[1])
|
|
||||||
log.exception(error)
|
|
||||||
raise
|
|
||||||
|
|
||||||
log.info("plugin.video.emby stopped")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
|
@ -315,488 +315,3 @@ class FullSync(object):
|
||||||
obj.boxsets_reset()
|
obj.boxsets_reset()
|
||||||
|
|
||||||
self.boxsets()
|
self.boxsets()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
##################################################################################################
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sqlite3
|
|
||||||
import threading
|
|
||||||
from datetime import datetime, timedelta, time
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
import api
|
|
||||||
import utils
|
|
||||||
import clientinfo
|
|
||||||
import database
|
|
||||||
import downloadutils
|
|
||||||
import itemtypes
|
|
||||||
import emby_api as mb
|
|
||||||
import embydb_functions as embydb
|
|
||||||
import read_embyserver as embyserver
|
|
||||||
import userclient
|
|
||||||
import views
|
|
||||||
from objects import Movies, MusicVideos, TVShows, Music
|
|
||||||
from utils import window, settings, language as lang, should_stop
|
|
||||||
from ga_client import GoogleAnalytics
|
|
||||||
|
|
||||||
##################################################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("EMBY."+__name__)
|
|
||||||
|
|
||||||
##################################################################################################
|
|
||||||
|
|
||||||
class LibrarySync(threading.Thread):
|
|
||||||
|
|
||||||
_shared_state = {}
|
|
||||||
|
|
||||||
isFastSync = False
|
|
||||||
|
|
||||||
stop_thread = False
|
|
||||||
suspend_thread = False
|
|
||||||
|
|
||||||
# Track websocketclient updates
|
|
||||||
addedItems = []
|
|
||||||
updateItems = []
|
|
||||||
userdataItems = []
|
|
||||||
removeItems = []
|
|
||||||
forceLibraryUpdate = False
|
|
||||||
incremental_count = 0
|
|
||||||
refresh_views = False
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
|
||||||
self.monitor = xbmc.Monitor()
|
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
|
||||||
self.user = userclient.UserClient()
|
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
|
||||||
self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2])
|
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
|
|
||||||
|
|
||||||
def progressDialog(self, title):
|
|
||||||
|
|
||||||
dialog = None
|
|
||||||
|
|
||||||
dialog = xbmcgui.DialogProgressBG()
|
|
||||||
dialog.create("Emby for Kodi", title)
|
|
||||||
log.debug("Show progress dialog: %s" % title)
|
|
||||||
|
|
||||||
return dialog
|
|
||||||
|
|
||||||
def saveLastSync(self):
|
|
||||||
|
|
||||||
# Save last sync time
|
|
||||||
overlap = 2
|
|
||||||
|
|
||||||
try: # datetime fails when used more than once, TypeError
|
|
||||||
if self.isFastSync:
|
|
||||||
result = self.doUtils("{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json")
|
|
||||||
server_time = result['ServerDateTime']
|
|
||||||
server_time = utils.convertDate(server_time)
|
|
||||||
else:
|
|
||||||
raise Exception("Fast sync server plugin is not enabled.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# If the server plugin is not installed or an error happened.
|
|
||||||
log.debug("An exception occurred: %s" % e)
|
|
||||||
time_now = datetime.utcnow()-timedelta(minutes=overlap)
|
|
||||||
lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
log.info("New sync time: client time -%s min: %s" % (overlap, lastSync))
|
|
||||||
|
|
||||||
else:
|
|
||||||
lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
log.info("New sync time: server time -%s min: %s" % (overlap, lastSync))
|
|
||||||
|
|
||||||
finally:
|
|
||||||
settings('LastIncrementalSync', value=lastSync)
|
|
||||||
|
|
||||||
|
|
||||||
def fullSync(self, manualrun=False, repair=False):
|
|
||||||
# Only run once when first setting up. Can be run manually.
|
|
||||||
music_enabled = settings('enableMusic') == "true"
|
|
||||||
|
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(true)')
|
|
||||||
screensaver = utils.getScreensaver()
|
|
||||||
utils.setScreensaver(value="")
|
|
||||||
window('emby_dbScan', value="true")
|
|
||||||
# Add sources
|
|
||||||
utils.sourcesXML()
|
|
||||||
|
|
||||||
# use emby and video DBs
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn('video') as cursor_video:
|
|
||||||
# content sync: movies, tvshows, musicvideos, music
|
|
||||||
|
|
||||||
if manualrun:
|
|
||||||
message = "Manual sync"
|
|
||||||
elif repair:
|
|
||||||
message = "Repair sync"
|
|
||||||
repair_list = []
|
|
||||||
choices = ['all', 'movies', 'musicvideos', 'tvshows']
|
|
||||||
if music_enabled:
|
|
||||||
choices.append('music')
|
|
||||||
|
|
||||||
if self.kodi_version > 15:
|
|
||||||
# Jarvis or higher
|
|
||||||
types = xbmcgui.Dialog().multiselect(lang(33094), choices)
|
|
||||||
if types is None:
|
|
||||||
pass
|
|
||||||
elif 0 in types: # all
|
|
||||||
choices.pop(0)
|
|
||||||
repair_list.extend(choices)
|
|
||||||
else:
|
|
||||||
for index in types:
|
|
||||||
repair_list.append(choices[index])
|
|
||||||
else:
|
|
||||||
resp = xbmcgui.Dialog().select(lang(33094), choices)
|
|
||||||
if resp == 0: # all
|
|
||||||
choices.pop(resp)
|
|
||||||
repair_list.extend(choices)
|
|
||||||
else:
|
|
||||||
repair_list.append(choices[resp])
|
|
||||||
|
|
||||||
log.info("Repair queued for: %s", repair_list)
|
|
||||||
else:
|
|
||||||
message = "Initial sync"
|
|
||||||
window('emby_initialScan', value="true")
|
|
||||||
|
|
||||||
pDialog = self.progressDialog("%s" % message)
|
|
||||||
starttotal = datetime.now()
|
|
||||||
|
|
||||||
# Set views
|
|
||||||
views.Views(cursor_emby, cursor_video).maintain()
|
|
||||||
cursor_emby.connection.commit()
|
|
||||||
#self.maintainViews(cursor_emby, cursor_video)
|
|
||||||
|
|
||||||
# Sync video library
|
|
||||||
process = {
|
|
||||||
|
|
||||||
'movies': self.movies,
|
|
||||||
'boxsets': self.boxsets,
|
|
||||||
'musicvideos': self.musicvideos,
|
|
||||||
'tvshows': self.tvshows
|
|
||||||
}
|
|
||||||
for itemtype in process:
|
|
||||||
|
|
||||||
if repair and itemtype not in repair_list:
|
|
||||||
continue
|
|
||||||
|
|
||||||
startTime = datetime.now()
|
|
||||||
completed = process[itemtype](cursor_emby, cursor_video, pDialog)
|
|
||||||
if not completed:
|
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
|
||||||
utils.setScreensaver(value=screensaver)
|
|
||||||
window('emby_dbScan', clear=True)
|
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
elapsedTime = datetime.now() - startTime
|
|
||||||
log.info("SyncDatabase (finished %s in: %s)"
|
|
||||||
% (itemtype, str(elapsedTime).split('.')[0]))
|
|
||||||
|
|
||||||
|
|
||||||
# sync music
|
|
||||||
# use emby and music
|
|
||||||
if music_enabled:
|
|
||||||
if repair and 'music' not in repair_list:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn('music') as cursor_music:
|
|
||||||
startTime = datetime.now()
|
|
||||||
completed = self.music(cursor_emby, cursor_music, pDialog)
|
|
||||||
if not completed:
|
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
|
||||||
utils.setScreensaver(value=screensaver)
|
|
||||||
window('emby_dbScan', clear=True)
|
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
elapsedTime = datetime.now() - startTime
|
|
||||||
log.info("SyncDatabase (finished music in: %s)"
|
|
||||||
% (str(elapsedTime).split('.')[0]))
|
|
||||||
|
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
emby_db = embydb.Embydb_Functions(cursor_emby)
|
|
||||||
current_version = emby_db.get_version(self.clientInfo.get_version())
|
|
||||||
|
|
||||||
window('emby_version', current_version)
|
|
||||||
|
|
||||||
settings('SyncInstallRunDone', value="true")
|
|
||||||
|
|
||||||
self.saveLastSync()
|
|
||||||
xbmc.executebuiltin('UpdateLibrary(video)')
|
|
||||||
elapsedtotal = datetime.now() - starttotal
|
|
||||||
|
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
|
||||||
utils.setScreensaver(value=screensaver)
|
|
||||||
window('emby_dbScan', clear=True)
|
|
||||||
window('emby_initialScan', clear=True)
|
|
||||||
|
|
||||||
xbmcgui.Dialog().notification(
|
|
||||||
heading=lang(29999),
|
|
||||||
message="%s %s %s" %
|
|
||||||
(message, lang(33025), str(elapsedtotal).split('.')[0]),
|
|
||||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
|
||||||
sound=False)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def refreshViews(self):
|
|
||||||
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn() as cursor_video:
|
|
||||||
# Compare views, assign correct tags to items
|
|
||||||
views.Views(cursor_emby, cursor_video).maintain()
|
|
||||||
|
|
||||||
def offline_mode_views(self):
|
|
||||||
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn() as cursor_video:
|
|
||||||
views.Views(cursor_emby, cursor_video).offline_mode()
|
|
||||||
|
|
||||||
|
|
||||||
def compareDBVersion(self, current, minimum):
|
|
||||||
# It returns True is database is up to date. False otherwise.
|
|
||||||
log.info("current: %s minimum: %s" % (current, minimum))
|
|
||||||
|
|
||||||
try:
|
|
||||||
currMajor, currMinor, currPatch = current.split(".")
|
|
||||||
minMajor, minMinor, minPatch = minimum.split(".")
|
|
||||||
except ValueError as error:
|
|
||||||
raise ValueError("Unable to compare versions: %s, %s" % (current, minimum))
|
|
||||||
|
|
||||||
if currMajor > minMajor:
|
|
||||||
return True
|
|
||||||
elif currMajor == minMajor and (currMinor > minMinor or
|
|
||||||
(currMinor == minMinor and currPatch >= minPatch)):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
# Database out of date.
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.run_internal()
|
|
||||||
except Warning as e:
|
|
||||||
if "restricted" in e:
|
|
||||||
pass
|
|
||||||
elif "401" in e:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
ga = GoogleAnalytics()
|
|
||||||
errStrings = ga.formatException()
|
|
||||||
if not (hasattr(e, 'quiet') and e.quiet):
|
|
||||||
ga.sendEventData("Exception", errStrings[0], errStrings[1])
|
|
||||||
window('emby_dbScan', clear=True)
|
|
||||||
log.exception(e)
|
|
||||||
xbmcgui.Dialog().ok(
|
|
||||||
heading=lang(29999),
|
|
||||||
line1=(
|
|
||||||
"Library sync thread has exited! "
|
|
||||||
"You should restart Kodi now. "
|
|
||||||
"Please report this on the forum."),
|
|
||||||
line2=(errStrings[0] + " (" + errStrings[1] + ")"))
|
|
||||||
|
|
||||||
def run_internal(self):
|
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
|
|
||||||
startupComplete = False
|
|
||||||
|
|
||||||
log.warn("---===### Starting LibrarySync ###===---")
|
|
||||||
if utils.verify_advancedsettings():
|
|
||||||
# Advancedsettings was modified, Kodi needs to restart
|
|
||||||
log.warn("###===--- LibrarySync Aborted ---===###")
|
|
||||||
return
|
|
||||||
|
|
||||||
while not self.monitor.abortRequested():
|
|
||||||
|
|
||||||
# In the event the server goes offline
|
|
||||||
while self.suspend_thread:
|
|
||||||
# Set in service.py
|
|
||||||
if self.monitor.waitForAbort(5):
|
|
||||||
# Abort was requested while waiting. We should exit
|
|
||||||
break
|
|
||||||
|
|
||||||
if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"):
|
|
||||||
# Verify the validity of the database
|
|
||||||
log.info("Doing DB Version Check")
|
|
||||||
with database.DatabaseConn('emby') as cursor:
|
|
||||||
emby_db = embydb.Embydb_Functions(cursor)
|
|
||||||
currentVersion = emby_db.get_version()
|
|
||||||
###$ Begin migration $###
|
|
||||||
if not currentVersion:
|
|
||||||
currentVersion = emby_db.get_version(settings('dbCreatedWithVersion') or self.clientInfo.get_version())
|
|
||||||
log.info("Migration of database version completed")
|
|
||||||
###$ End migration $###
|
|
||||||
|
|
||||||
window('emby_version', value=currentVersion)
|
|
||||||
|
|
||||||
minVersion = window('emby_minDBVersion')
|
|
||||||
uptoDate = self.compareDBVersion(currentVersion, minVersion)
|
|
||||||
|
|
||||||
if not uptoDate:
|
|
||||||
log.warn("Database version out of date: %s minimum version required: %s"
|
|
||||||
% (currentVersion, minVersion))
|
|
||||||
|
|
||||||
resp = dialog.yesno(lang(29999), lang(33022))
|
|
||||||
if not resp:
|
|
||||||
log.warn("Database version is out of date! USER IGNORED!")
|
|
||||||
dialog.ok(lang(29999), lang(33023))
|
|
||||||
else:
|
|
||||||
database.db_reset()
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
window('emby_dbCheck', value="true")
|
|
||||||
|
|
||||||
|
|
||||||
if not startupComplete:
|
|
||||||
# Verify the video database can be found
|
|
||||||
videoDb = database.video_database()
|
|
||||||
if not xbmcvfs.exists(videoDb):
|
|
||||||
# Database does not exists
|
|
||||||
log.error(
|
|
||||||
"The current Kodi version is incompatible "
|
|
||||||
"with the Emby for Kodi add-on. Please visit "
|
|
||||||
"https://github.com/MediaBrowser/Emby.Kodi/wiki "
|
|
||||||
"to know which Kodi versions are supported.")
|
|
||||||
|
|
||||||
dialog.ok(
|
|
||||||
heading=lang(29999),
|
|
||||||
line1=lang(33024))
|
|
||||||
break
|
|
||||||
|
|
||||||
# Run start up sync
|
|
||||||
log.warn("Database version: %s", window('emby_version'))
|
|
||||||
log.info("SyncDatabase (started)")
|
|
||||||
startTime = datetime.now()
|
|
||||||
librarySync = self.startSync()
|
|
||||||
elapsedTime = datetime.now() - startTime
|
|
||||||
log.info("SyncDatabase (finished in: %s) %s"
|
|
||||||
% (str(elapsedTime).split('.')[0], librarySync))
|
|
||||||
|
|
||||||
# Add other servers at this point
|
|
||||||
# TODO: re-add once plugin listing is created
|
|
||||||
# self.user.load_connect_servers()
|
|
||||||
|
|
||||||
# Only try the initial sync once per kodi session regardless
|
|
||||||
# This will prevent an infinite loop in case something goes wrong.
|
|
||||||
startupComplete = True
|
|
||||||
|
|
||||||
# Process updates
|
|
||||||
if self.incremental_count > 5:
|
|
||||||
self.incremental_count = 0
|
|
||||||
window('emby_kodiScan', clear=True)
|
|
||||||
|
|
||||||
if ((not xbmc.Player().isPlayingVideo() or xbmc.getCondVisibility('VideoPlayer.Content(livetv)')) and
|
|
||||||
window('emby_dbScan') != "true" and window('emby_shouldStop') != "true"):
|
|
||||||
|
|
||||||
self.incrementalSync()
|
|
||||||
|
|
||||||
if window('emby_onWake') == "true" and window('emby_online') == "true":
|
|
||||||
# Kodi is waking up
|
|
||||||
# Set in kodimonitor.py
|
|
||||||
window('emby_onWake', clear=True)
|
|
||||||
if window('emby_syncRunning') != "true":
|
|
||||||
log.info("SyncDatabase onWake (started)")
|
|
||||||
librarySync = self.startSync()
|
|
||||||
log.info("SyncDatabase onWake (finished) %s" % librarySync)
|
|
||||||
|
|
||||||
if self.stop_thread:
|
|
||||||
# Set in service.py
|
|
||||||
log.debug("Service terminated thread.")
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.monitor.waitForAbort(1):
|
|
||||||
# Abort was requested while waiting. We should exit
|
|
||||||
break
|
|
||||||
|
|
||||||
log.warn("###===--- LibrarySync Stopped ---===###")
|
|
||||||
|
|
||||||
def stopThread(self):
|
|
||||||
self.stop_thread = True
|
|
||||||
log.debug("Ending thread...")
|
|
||||||
|
|
||||||
def suspendThread(self):
|
|
||||||
self.suspend_thread = True
|
|
||||||
log.debug("Pausing thread...")
|
|
||||||
|
|
||||||
def resumeThread(self):
|
|
||||||
self.suspend_thread = False
|
|
||||||
log.debug("Resuming thread...")
|
|
||||||
|
|
||||||
class ManualSync(LibrarySync):
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
LibrarySync.__init__(self)
|
|
||||||
|
|
||||||
def sync(self, mediatype=None):
|
|
||||||
|
|
||||||
if mediatype in ('movies', 'boxsets', 'musicvideos', 'tvshows'):
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn('video') as cursor_video:
|
|
||||||
pDialog = self.progressDialog("Manual Sync: %s" % mediatype)
|
|
||||||
if mediatype == 'movies':
|
|
||||||
self.movies(cursor_emby, cursor_video, pDialog)
|
|
||||||
elif mediatype == "boxsets":
|
|
||||||
self.boxsets(cursor_emby, cursor_video, pDialog)
|
|
||||||
elif mediatype =='musicvideos':
|
|
||||||
self.musicvideos(cursor_emby, cursor_video, pDialog)
|
|
||||||
elif mediatype == 'tvshows':
|
|
||||||
self.tvshows(cursor_emby, cursor_video, pDialog)
|
|
||||||
|
|
||||||
pDialog.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
elif mediatype == 'music':
|
|
||||||
with database.DatabaseConn('emby') as cursor_emby:
|
|
||||||
with database.DatabaseConn('music') as cursor_music:
|
|
||||||
pDialog = self.progressDialog("Manual Sync: %s" % mediatype)
|
|
||||||
self.music(cursor_emby, cursor_music, pDialog)
|
|
||||||
pDialog.close()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
return self.fullSync(manualrun=True)
|
|
||||||
|
|
||||||
def movies(self, embycursor, kodicursor, pdialog):
|
|
||||||
return Movies(embycursor, kodicursor, pdialog).compare_all()
|
|
||||||
|
|
||||||
def boxsets(self, embycursor, kodicursor, pdialog):
|
|
||||||
return Movies(embycursor, kodicursor, pdialog).force_refresh_boxsets()
|
|
||||||
|
|
||||||
def musicvideos(self, embycursor, kodicursor, pdialog):
|
|
||||||
return MusicVideos(embycursor, kodicursor, pdialog).compare_all()
|
|
||||||
|
|
||||||
def tvshows(self, embycursor, kodicursor, pdialog):
|
|
||||||
return TVShows(embycursor, kodicursor, pdialog).compare_all()
|
|
||||||
|
|
||||||
def music(self, embycursor, kodicursor, pdialog):
|
|
||||||
return Music(embycursor, kodicursor).compare_all()
|
|
||||||
"""
|
|
||||||
|
|
|
@ -33,80 +33,6 @@ class API(object):
|
||||||
'''
|
'''
|
||||||
return (playcount or 1) if played else None
|
return (playcount or 1) if played else None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_userdata(self):
|
|
||||||
# Default
|
|
||||||
favorite = False
|
|
||||||
likes = None
|
|
||||||
playcount = None
|
|
||||||
played = False
|
|
||||||
last_played = None
|
|
||||||
resume = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
userdata = self.item['UserData']
|
|
||||||
except KeyError: # No userdata found.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
favorite = userdata['IsFavorite']
|
|
||||||
likes = userdata.get('Likes')
|
|
||||||
|
|
||||||
last_played = userdata.get('LastPlayedDate')
|
|
||||||
if last_played:
|
|
||||||
last_played = last_played.split('.')[0].replace('T', " ")
|
|
||||||
|
|
||||||
if userdata['Played']:
|
|
||||||
# Playcount is tied to the watch status
|
|
||||||
played = True
|
|
||||||
playcount = userdata['PlayCount']
|
|
||||||
if playcount == 0:
|
|
||||||
playcount = 1
|
|
||||||
|
|
||||||
if last_played is None:
|
|
||||||
last_played = self.get_date_created()
|
|
||||||
|
|
||||||
playback_position = userdata.get('PlaybackPositionTicks')
|
|
||||||
if playback_position:
|
|
||||||
resume = playback_position / 10000000.0
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
'Favorite': favorite,
|
|
||||||
'Likes': likes,
|
|
||||||
'PlayCount': playcount,
|
|
||||||
'Played': played,
|
|
||||||
'LastPlayedDate': last_played,
|
|
||||||
'Resume': resume
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_people(self):
|
|
||||||
# Process People
|
|
||||||
director = []
|
|
||||||
writer = []
|
|
||||||
cast = []
|
|
||||||
|
|
||||||
if 'People' in self.item:
|
|
||||||
for person in self.item['People']:
|
|
||||||
|
|
||||||
type_ = person['Type']
|
|
||||||
name = person['Name']
|
|
||||||
|
|
||||||
if type_ == 'Director':
|
|
||||||
director.append(name)
|
|
||||||
elif type_ == 'Actor':
|
|
||||||
cast.append(name)
|
|
||||||
elif type_ in ('Writing', 'Writer'):
|
|
||||||
writer.append(name)
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
'Director': director,
|
|
||||||
'Writer': writer,
|
|
||||||
'Cast': cast
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_actors(self):
|
def get_actors(self):
|
||||||
cast = []
|
cast = []
|
||||||
|
|
||||||
|
@ -125,40 +51,6 @@ class API(object):
|
||||||
|
|
||||||
return cast
|
return cast
|
||||||
|
|
||||||
def get_media_streams(self):
|
|
||||||
|
|
||||||
video_tracks = []
|
|
||||||
audio_tracks = []
|
|
||||||
subtitle_languages = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
media_streams = self.item['MediaSources'][0]['MediaStreams']
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
if not self.item.get("MediaStreams"):
|
|
||||||
return None
|
|
||||||
media_streams = self.item['MediaStreams']
|
|
||||||
|
|
||||||
for media_stream in media_streams:
|
|
||||||
# Sort through Video, Audio, Subtitle
|
|
||||||
stream_type = media_stream['Type']
|
|
||||||
|
|
||||||
if stream_type == "Video":
|
|
||||||
self._video_stream(video_tracks, media_stream)
|
|
||||||
|
|
||||||
elif stream_type == "Audio":
|
|
||||||
self._audio_stream(audio_tracks, media_stream)
|
|
||||||
|
|
||||||
elif stream_type == "Subtitle":
|
|
||||||
subtitle_languages.append(media_stream.get('Language', "Unknown"))
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
'video': video_tracks,
|
|
||||||
'audio': audio_tracks,
|
|
||||||
'subtitle': subtitle_languages
|
|
||||||
}
|
|
||||||
|
|
||||||
def media_streams(self, video, audio, subtitles):
|
def media_streams(self, video, audio, subtitles):
|
||||||
return {
|
return {
|
||||||
'video': video or [],
|
'video': video or [],
|
||||||
|
@ -247,21 +139,6 @@ class API(object):
|
||||||
|
|
||||||
return resume
|
return resume
|
||||||
|
|
||||||
def get_studios(self):
|
|
||||||
# Process Studios
|
|
||||||
studios = []
|
|
||||||
try:
|
|
||||||
studio = self.item['SeriesStudio']
|
|
||||||
studios.append(self.validate_studio(studio))
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
for studio in self.item['Studios']:
|
|
||||||
|
|
||||||
name = studio['Name']
|
|
||||||
studios.append(self.validate_studio(name))
|
|
||||||
|
|
||||||
return studios
|
|
||||||
|
|
||||||
def validate_studio(self, studio_name):
|
def validate_studio(self, studio_name):
|
||||||
# Convert studio for Kodi to properly detect them
|
# Convert studio for Kodi to properly detect them
|
||||||
studios = {
|
studios = {
|
||||||
|
@ -277,36 +154,6 @@ class API(object):
|
||||||
}
|
}
|
||||||
return studios.get(studio_name.lower(), studio_name)
|
return studios.get(studio_name.lower(), studio_name)
|
||||||
|
|
||||||
def get_genres(self):
|
|
||||||
|
|
||||||
all_genres = ""
|
|
||||||
genres = self.item.get('Genres', self.item.get('SeriesGenres'))
|
|
||||||
|
|
||||||
if genres:
|
|
||||||
all_genres = " / ".join(genres)
|
|
||||||
|
|
||||||
return all_genres
|
|
||||||
|
|
||||||
def get_date_created(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
date_added = self.item['DateCreated']
|
|
||||||
date_added = date_added.split('.')[0].replace('T', " ")
|
|
||||||
except KeyError:
|
|
||||||
date_added = None
|
|
||||||
|
|
||||||
return date_added
|
|
||||||
|
|
||||||
def get_premiere_date(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
premiere = self.item['PremiereDate']
|
|
||||||
premiere = premiere.split('.')[0].replace('T', " ")
|
|
||||||
except KeyError:
|
|
||||||
premiere = None
|
|
||||||
|
|
||||||
return premiere
|
|
||||||
|
|
||||||
def get_overview(self, overview=None):
|
def get_overview(self, overview=None):
|
||||||
|
|
||||||
overview = overview or self.item.get('Overview')
|
overview = overview or self.item.get('Overview')
|
||||||
|
@ -321,24 +168,6 @@ class API(object):
|
||||||
|
|
||||||
return overview
|
return overview
|
||||||
|
|
||||||
def get_tagline(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
tagline = self.item['Taglines'][0]
|
|
||||||
except IndexError:
|
|
||||||
tagline = None
|
|
||||||
|
|
||||||
return tagline
|
|
||||||
|
|
||||||
def get_provider(self, name):
|
|
||||||
|
|
||||||
try:
|
|
||||||
provider = self.item['ProviderIds'][name]
|
|
||||||
except KeyError:
|
|
||||||
provider = None
|
|
||||||
|
|
||||||
return provider
|
|
||||||
|
|
||||||
def get_mpaa(self):
|
def get_mpaa(self):
|
||||||
# Convert more complex cases
|
# Convert more complex cases
|
||||||
mpaa = self.item.get('OfficialRating', "")
|
mpaa = self.item.get('OfficialRating', "")
|
||||||
|
@ -352,15 +181,6 @@ class API(object):
|
||||||
|
|
||||||
return mpaa
|
return mpaa
|
||||||
|
|
||||||
def get_country(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
country = self.item['ProductionLocations'][0]
|
|
||||||
except (IndexError, KeyError):
|
|
||||||
country = None
|
|
||||||
|
|
||||||
return country
|
|
||||||
|
|
||||||
def get_file_path(self, path=None):
|
def get_file_path(self, path=None):
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
|
|
|
@ -273,117 +273,3 @@ def normalize_string(text):
|
||||||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# Utility methods
|
|
||||||
|
|
||||||
def getScreensaver():
|
|
||||||
# Get the current screensaver value
|
|
||||||
result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"})
|
|
||||||
try:
|
|
||||||
return result['result']['value']
|
|
||||||
except KeyError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def setScreensaver(value):
|
|
||||||
# Toggle the screensaver
|
|
||||||
params = {
|
|
||||||
'setting': "screensaver.mode",
|
|
||||||
'value': value
|
|
||||||
}
|
|
||||||
result = JSONRPC('Settings.setSettingValue').execute(params)
|
|
||||||
log.info("Toggling screensaver: %s %s" % (value, result))
|
|
||||||
|
|
||||||
def convertDate(date):
|
|
||||||
try:
|
|
||||||
date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
except (ImportError, TypeError):
|
|
||||||
# TypeError: attribute of type 'NoneType' is not callable
|
|
||||||
# Known Kodi/python error
|
|
||||||
date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
|
|
||||||
|
|
||||||
return date
|
|
||||||
|
|
||||||
|
|
||||||
def indent(elem, level=0):
|
|
||||||
# Prettify xml trees
|
|
||||||
i = "\n" + level*" "
|
|
||||||
if len(elem):
|
|
||||||
if not elem.text or not elem.text.strip():
|
|
||||||
elem.text = i + " "
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
for elem in elem:
|
|
||||||
indent(elem, level+1)
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
else:
|
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
|
||||||
elem.tail = i
|
|
||||||
|
|
||||||
def profiling(sortby="cumulative"):
|
|
||||||
# Will print results to Kodi log
|
|
||||||
def decorator(func):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
import cProfile
|
|
||||||
import pstats
|
|
||||||
|
|
||||||
pr = cProfile.Profile()
|
|
||||||
|
|
||||||
pr.enable()
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
pr.disable()
|
|
||||||
|
|
||||||
s = StringIO.StringIO()
|
|
||||||
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
|
||||||
ps.print_stats()
|
|
||||||
log.info(s.getvalue())
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# Addon utilities
|
|
||||||
|
|
||||||
|
|
||||||
def verify_advancedsettings():
|
|
||||||
# Track the existance of <cleanonupdate>true</cleanonupdate>
|
|
||||||
# incompatible with plugin paths
|
|
||||||
log.info("verifying advanced settings")
|
|
||||||
if settings('useDirectPaths') != "0": return
|
|
||||||
|
|
||||||
path = xbmc.translatePath("special://userdata/").decode('utf-8')
|
|
||||||
xmlpath = "%sadvancedsettings.xml" % path
|
|
||||||
|
|
||||||
try:
|
|
||||||
xmlparse = etree.parse(xmlpath)
|
|
||||||
except: # Document is blank or missing
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
root = xmlparse.getroot()
|
|
||||||
|
|
||||||
video = root.find('videolibrary')
|
|
||||||
if video is not None:
|
|
||||||
cleanonupdate = video.find('cleanonupdate')
|
|
||||||
if cleanonupdate is not None and cleanonupdate.text == "true":
|
|
||||||
log.warn("cleanonupdate disabled")
|
|
||||||
video.remove(cleanonupdate)
|
|
||||||
|
|
||||||
try:
|
|
||||||
indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(xmlpath)
|
|
||||||
|
|
||||||
xbmcgui.Dialog().ok(heading=language(29999), line1=language(33097))
|
|
||||||
xbmc.executebuiltin('RestartApp')
|
|
||||||
return True
|
|
||||||
return
|
|
||||||
"""
|
|
||||||
|
|
|
@ -80,272 +80,7 @@ def tvtunes_nfo(path, urls):
|
||||||
indent(xml)
|
indent(xml)
|
||||||
write_xml(etree.tostring(xml, 'UTF-8'), path)
|
write_xml(etree.tostring(xml, 'UTF-8'), path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sqlite3
|
|
||||||
import StringIO
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib
|
|
||||||
import unicodedata
|
|
||||||
import xml.etree.ElementTree as etree
|
|
||||||
from datetime import datetime
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcaddon
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcplugin
|
|
||||||
import xbmcvfs
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("EMBY."+__name__)
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# Utility methods
|
|
||||||
|
|
||||||
def getScreensaver():
|
|
||||||
# Get the current screensaver value
|
|
||||||
result = JSONRPC('Settings.getSettingValue').execute({'setting': "screensaver.mode"})
|
|
||||||
try:
|
|
||||||
return result['result']['value']
|
|
||||||
except KeyError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def setScreensaver(value):
|
|
||||||
# Toggle the screensaver
|
|
||||||
params = {
|
|
||||||
'setting': "screensaver.mode",
|
|
||||||
'value': value
|
|
||||||
}
|
|
||||||
result = JSONRPC('Settings.setSettingValue').execute(params)
|
|
||||||
log.info("Toggling screensaver: %s %s" % (value, result))
|
|
||||||
|
|
||||||
def convertDate(date):
|
|
||||||
try:
|
|
||||||
date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
except (ImportError, TypeError):
|
|
||||||
# TypeError: attribute of type 'NoneType' is not callable
|
|
||||||
# Known Kodi/python error
|
|
||||||
date = datetime(*(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")[0:6]))
|
|
||||||
|
|
||||||
return date
|
|
||||||
|
|
||||||
def normalize_string(text):
|
|
||||||
# For theme media, do not modify unless
|
|
||||||
# modified in TV Tunes
|
|
||||||
text = text.replace(":", "")
|
|
||||||
text = text.replace("/", "-")
|
|
||||||
text = text.replace("\\", "-")
|
|
||||||
text = text.replace("<", "")
|
|
||||||
text = text.replace(">", "")
|
|
||||||
text = text.replace("*", "")
|
|
||||||
text = text.replace("?", "")
|
|
||||||
text = text.replace('|', "")
|
|
||||||
text = text.strip()
|
|
||||||
# Remove dots from the last character as windows can not have directories
|
|
||||||
# with dots at the end
|
|
||||||
text = text.rstrip('.')
|
|
||||||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def indent(elem, level=0):
|
|
||||||
# Prettify xml trees
|
|
||||||
i = "\n" + level*" "
|
|
||||||
if len(elem):
|
|
||||||
if not elem.text or not elem.text.strip():
|
|
||||||
elem.text = i + " "
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
for elem in elem:
|
|
||||||
indent(elem, level+1)
|
|
||||||
if not elem.tail or not elem.tail.strip():
|
|
||||||
elem.tail = i
|
|
||||||
else:
|
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
|
||||||
elem.tail = i
|
|
||||||
|
|
||||||
def profiling(sortby="cumulative"):
|
|
||||||
# Will print results to Kodi log
|
|
||||||
def decorator(func):
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
import cProfile
|
|
||||||
import pstats
|
|
||||||
|
|
||||||
pr = cProfile.Profile()
|
|
||||||
|
|
||||||
pr.enable()
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
pr.disable()
|
|
||||||
|
|
||||||
s = StringIO.StringIO()
|
|
||||||
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
|
||||||
ps.print_stats()
|
|
||||||
log.info(s.getvalue())
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
# Addon utilities
|
|
||||||
|
|
||||||
def sourcesXML():
|
|
||||||
# To make Master lock compatible
|
|
||||||
path = xbmc.translatePath("special://profile/").decode('utf-8')
|
|
||||||
xmlpath = "%ssources.xml" % path
|
|
||||||
|
|
||||||
try:
|
|
||||||
xmlparse = etree.parse(xmlpath)
|
|
||||||
except: # Document is blank or missing
|
|
||||||
root = etree.Element('sources')
|
|
||||||
else:
|
|
||||||
root = xmlparse.getroot()
|
|
||||||
|
|
||||||
|
|
||||||
video = root.find('video')
|
|
||||||
if video is None:
|
|
||||||
video = etree.SubElement(root, 'video')
|
|
||||||
etree.SubElement(video, 'default', attrib={'pathversion': "1"})
|
|
||||||
|
|
||||||
# Add elements
|
|
||||||
count = 2
|
|
||||||
for source in root.findall('.//path'):
|
|
||||||
if source.text == "smb://":
|
|
||||||
count -= 1
|
|
||||||
|
|
||||||
if count == 0:
|
|
||||||
# sources already set
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Missing smb:// occurences, re-add.
|
|
||||||
for i in range(0, count):
|
|
||||||
source = etree.SubElement(video, 'source')
|
|
||||||
etree.SubElement(source, 'name').text = "Emby"
|
|
||||||
etree.SubElement(source, 'path', attrib={'pathversion': "1"}).text = "smb://"
|
|
||||||
etree.SubElement(source, 'allowsharing').text = "true"
|
|
||||||
# Prettify and write to file
|
|
||||||
try:
|
|
||||||
indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(xmlpath)
|
|
||||||
|
|
||||||
def passwordsXML():
|
|
||||||
|
|
||||||
# To add network credentials
|
|
||||||
path = xbmc.translatePath("special://userdata/").decode('utf-8')
|
|
||||||
xmlpath = "%spasswords.xml" % path
|
|
||||||
|
|
||||||
try:
|
|
||||||
xmlparse = etree.parse(xmlpath)
|
|
||||||
except: # Document is blank or missing
|
|
||||||
root = etree.Element('passwords')
|
|
||||||
else:
|
|
||||||
root = xmlparse.getroot()
|
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
credentials = settings('networkCreds')
|
|
||||||
if credentials:
|
|
||||||
# Present user with options
|
|
||||||
option = dialog.select(language(33075), [language(33076), language(33077)])
|
|
||||||
|
|
||||||
if option < 0:
|
|
||||||
# User cancelled dialog
|
|
||||||
return
|
|
||||||
|
|
||||||
elif option == 1:
|
|
||||||
# User selected remove
|
|
||||||
for paths in root.getiterator('passwords'):
|
|
||||||
for path in paths:
|
|
||||||
if path.find('.//from').text == "smb://%s/" % credentials:
|
|
||||||
paths.remove(path)
|
|
||||||
log.info("Successfully removed credentials for: %s" % credentials)
|
|
||||||
etree.ElementTree(root).write(xmlpath)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
log.info("Failed to find saved server: %s in passwords.xml" % credentials)
|
|
||||||
|
|
||||||
settings('networkCreds', value="")
|
|
||||||
xbmcgui.Dialog().notification(
|
|
||||||
heading=language(29999),
|
|
||||||
message="%s %s" % (language(33078), credentials),
|
|
||||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
|
||||||
time=1000,
|
|
||||||
sound=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
elif option == 0:
|
|
||||||
# User selected to modify
|
|
||||||
server = dialog.input(language(33083), credentials)
|
|
||||||
if not server:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# No credentials added
|
|
||||||
dialog.ok(heading=language(29999), line1=language(33082))
|
|
||||||
server = dialog.input(language(33084))
|
|
||||||
if not server:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Network username
|
|
||||||
user = dialog.input(language(33079))
|
|
||||||
if not user:
|
|
||||||
return
|
|
||||||
# Network password
|
|
||||||
password = dialog.input(heading=language(33080), option=xbmcgui.ALPHANUM_HIDE_INPUT)
|
|
||||||
if not password:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add elements
|
|
||||||
for path in root.findall('.//path'):
|
|
||||||
if path.find('.//from').text.lower() == "smb://%s/" % server.lower():
|
|
||||||
# Found the server, rewrite credentials
|
|
||||||
topath = "smb://%s:%s@%s/" % (user, password, server)
|
|
||||||
path.find('.//to').text = topath.replace("\\", ";")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# Server not found, add it.
|
|
||||||
path = etree.SubElement(root, 'path')
|
|
||||||
etree.SubElement(path, 'from', attrib={'pathversion': "1"}).text = "smb://%s/" % server
|
|
||||||
topath = "smb://%s:%s@%s/" % (user, password, server)
|
|
||||||
etree.SubElement(path, 'to', attrib={'pathversion': "1"}).text = topath.replace("\\", ";")
|
|
||||||
# Force Kodi to see the credentials without restarting
|
|
||||||
xbmcvfs.exists(topath)
|
|
||||||
|
|
||||||
# Add credentials
|
|
||||||
settings('networkCreds', value=server)
|
|
||||||
log.info("Added server: %s to passwords.xml" % server)
|
|
||||||
# Prettify and write to file
|
|
||||||
try:
|
|
||||||
indent(root)
|
|
||||||
except: pass
|
|
||||||
etree.ElementTree(root).write(xmlpath)
|
|
||||||
|
|
||||||
dialog.notification(
|
|
||||||
heading=language(29999),
|
|
||||||
message="%s %s" % (language(33081), server),
|
|
||||||
icon="special://home/addons/plugin.video.emby/icon.png",
|
|
||||||
time=1000,
|
|
||||||
sound=False)
|
|
||||||
|
|
||||||
def verify_advancedsettings():
|
def verify_advancedsettings():
|
||||||
# Track the existance of <cleanonupdate>true</cleanonupdate>
|
# Track the existance of <cleanonupdate>true</cleanonupdate>
|
||||||
# incompatible with plugin paths
|
# incompatible with plugin paths
|
||||||
|
|
|
@ -364,519 +364,3 @@ class Player(xbmc.Player):
|
||||||
window('emby.external_check', clear=True)
|
window('emby.external_check', clear=True)
|
||||||
|
|
||||||
self.played.clear()
|
self.played.clear()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
import xbmcvfs
|
|
||||||
import xbmcgui
|
|
||||||
|
|
||||||
import clientinfo
|
|
||||||
import downloadutils
|
|
||||||
import read_embyserver as embyserver
|
|
||||||
import websocket_client as wsc
|
|
||||||
from utils import window, settings, language as lang, JSONRPC
|
|
||||||
from ga_client import GoogleAnalytics, log_error
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("EMBY."+__name__)
|
|
||||||
|
|
||||||
#################################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
class Player(xbmc.Player):
|
|
||||||
|
|
||||||
# Borg - multiple instances, shared state
|
|
||||||
_shared_state = {}
|
|
||||||
|
|
||||||
played_info = {}
|
|
||||||
currentFile = None
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
|
||||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
|
||||||
self.ws = wsc.WebSocketClient()
|
|
||||||
self.xbmcplayer = 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
|
|
||||||
self.stopAll()
|
|
||||||
|
|
||||||
# Get current file
|
|
||||||
try:
|
|
||||||
currentFile = self.xbmcplayer.getPlayingFile()
|
|
||||||
xbmc.sleep(300)
|
|
||||||
except:
|
|
||||||
currentFile = ""
|
|
||||||
count = 0
|
|
||||||
while not currentFile:
|
|
||||||
xbmc.sleep(100)
|
|
||||||
try:
|
|
||||||
currentFile = self.xbmcplayer.getPlayingFile()
|
|
||||||
except: pass
|
|
||||||
|
|
||||||
if count == 5: # try 5 times
|
|
||||||
log.info("Cancelling playback report...")
|
|
||||||
break
|
|
||||||
else: count += 1
|
|
||||||
|
|
||||||
# if we did not get the current file return
|
|
||||||
if currentFile == "":
|
|
||||||
return
|
|
||||||
|
|
||||||
# process the playing file
|
|
||||||
self.currentFile = currentFile
|
|
||||||
|
|
||||||
# We may need to wait for info to be set in kodi monitor
|
|
||||||
item = window('emby_%s.json' % currentFile)
|
|
||||||
#itemId = window("emby_%s.itemid" % currentFile)
|
|
||||||
tryCount = 0
|
|
||||||
while not item:
|
|
||||||
|
|
||||||
xbmc.sleep(200)
|
|
||||||
item = window('emby_%s.json' % currentFile)
|
|
||||||
if tryCount == 20: # try 20 times or about 10 seconds
|
|
||||||
log.info("Could not find item, cancelling playback report...")
|
|
||||||
break
|
|
||||||
else: tryCount += 1
|
|
||||||
|
|
||||||
else:
|
|
||||||
item_id = item.get('id')
|
|
||||||
log.info("ONPLAYBACK_STARTED: %s itemid: %s", currentFile.decode('utf-8'), item_id)
|
|
||||||
|
|
||||||
# Only proceed if an itemId was found.
|
|
||||||
runtime = item.get('runtime')
|
|
||||||
refresh_id = item.get('refreshid')
|
|
||||||
play_method = item.get('playmethod')
|
|
||||||
item_type = item.get('type')
|
|
||||||
playsession_id = item.get('playsession_id')
|
|
||||||
mediasource_id = item.get('mediasource_id')
|
|
||||||
|
|
||||||
|
|
||||||
#self.set_audio_subs(item.get('forcedaudio'), item.get('forcedsubs'))
|
|
||||||
|
|
||||||
# Prevent manually mark as watched in Kodi monitor
|
|
||||||
window('emby.skip.%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)
|
|
||||||
log.info("Seeking to: %s", customseek)
|
|
||||||
self.xbmcplayer.seekTime(int(customseek)/10000000.0)
|
|
||||||
window('emby_customPlaylist.seektime', clear=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
seekTime = self.xbmcplayer.getTime()
|
|
||||||
except:
|
|
||||||
# at this point we should be playing and if not then bail out
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get playback volume
|
|
||||||
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
|
||||||
result = result.get('result')
|
|
||||||
|
|
||||||
volume = result.get('volume')
|
|
||||||
muted = result.get('muted')
|
|
||||||
|
|
||||||
# Postdata structure to send to Emby server
|
|
||||||
url = "{server}/emby/Sessions/Playing"
|
|
||||||
postdata = {
|
|
||||||
|
|
||||||
'QueueableMediaTypes': "Video,Audio",
|
|
||||||
'CanSeek': True,
|
|
||||||
'ItemId': item_id,
|
|
||||||
'MediaSourceId': mediasource_id or item_id,
|
|
||||||
'PlayMethod': play_method,
|
|
||||||
'VolumeLevel': volume,
|
|
||||||
'PositionTicks': int(seekTime * 10000000),
|
|
||||||
'IsMuted': muted,
|
|
||||||
'PlaySessionId': playsession_id
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get the current audio track and subtitles
|
|
||||||
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
|
|
||||||
params = {
|
|
||||||
'playerid': 1,
|
|
||||||
'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"]
|
|
||||||
}
|
|
||||||
result = JSONRPC('Player.GetProperties').execute(params)
|
|
||||||
tracks_data = None
|
|
||||||
try:
|
|
||||||
tracks_data = json.loads(result)
|
|
||||||
tracks_data = tracks_data.get('result')
|
|
||||||
except:
|
|
||||||
tracks_data = None
|
|
||||||
|
|
||||||
try: # Audio tracks
|
|
||||||
indexAudio = tracks_data['currentaudiostream']['index']
|
|
||||||
except:
|
|
||||||
indexAudio = 0
|
|
||||||
|
|
||||||
try: # Subtitles tracks
|
|
||||||
indexSubs = tracks_data['currentsubtitle']['index']
|
|
||||||
except:
|
|
||||||
indexSubs = 0
|
|
||||||
|
|
||||||
try: # If subtitles are enabled
|
|
||||||
subsEnabled = tracks_data['subtitleenabled']
|
|
||||||
except:
|
|
||||||
subsEnabled = ""
|
|
||||||
|
|
||||||
# Postdata for the audio
|
|
||||||
postdata['AudioStreamIndex'] = indexAudio + 1
|
|
||||||
|
|
||||||
# Postdata for the subtitles
|
|
||||||
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
|
|
||||||
|
|
||||||
# Number of audiotracks to help get Emby Index
|
|
||||||
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
|
|
||||||
mapping = window("emby_%s.indexMapping" % currentFile)
|
|
||||||
|
|
||||||
if mapping: # Set in playbackutils.py
|
|
||||||
|
|
||||||
log.debug("Mapping for external subtitles index: %s", mapping)
|
|
||||||
externalIndex = json.loads(mapping)
|
|
||||||
|
|
||||||
if externalIndex.get(str(indexSubs)):
|
|
||||||
# If the current subtitle is in the mapping
|
|
||||||
postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)]
|
|
||||||
else:
|
|
||||||
# Internal subtitle currently selected
|
|
||||||
subindex = indexSubs - len(externalIndex) + audioTracks + 1
|
|
||||||
postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
|
|
||||||
else: # Direct paths enabled scenario or no external subtitles set
|
|
||||||
postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1
|
|
||||||
else:
|
|
||||||
postdata['SubtitleStreamIndex'] = ""
|
|
||||||
|
|
||||||
|
|
||||||
# Post playback to server
|
|
||||||
log.debug("Sending POST play started: %s.", postdata)
|
|
||||||
self.doUtils(url, postBody=postdata, action_type="POST")
|
|
||||||
|
|
||||||
# Ensure we do have a runtime
|
|
||||||
try:
|
|
||||||
runtime = int(runtime)
|
|
||||||
except ValueError:
|
|
||||||
try:
|
|
||||||
runtime = int(self.xbmcplayer.getTotalTime())
|
|
||||||
log.info("Runtime is missing, Kodi runtime: %s" % runtime)
|
|
||||||
except:
|
|
||||||
runtime = 0
|
|
||||||
log.info("Runtime is missing, Using Zero")
|
|
||||||
|
|
||||||
# Save data map for updates and position calls
|
|
||||||
data = {
|
|
||||||
|
|
||||||
'runtime': runtime,
|
|
||||||
'item_id': item_id,
|
|
||||||
'mediasource_id': mediasource_id,
|
|
||||||
'refresh_id': refresh_id,
|
|
||||||
'currentfile': currentFile,
|
|
||||||
'AudioStreamIndex': postdata['AudioStreamIndex'],
|
|
||||||
'SubtitleStreamIndex': postdata['SubtitleStreamIndex'],
|
|
||||||
'playmethod': play_method,
|
|
||||||
'Type': item_type,
|
|
||||||
'currentPosition': int(seekTime),
|
|
||||||
'playsession_id': playsession_id
|
|
||||||
}
|
|
||||||
|
|
||||||
self.played_info[currentFile] = data
|
|
||||||
log.info("ADDING_FILE: %s", self.played_info)
|
|
||||||
|
|
||||||
ga = GoogleAnalytics()
|
|
||||||
ga.sendEventData("PlayAction", item_type, play_method)
|
|
||||||
ga.sendScreenView(item_type)
|
|
||||||
|
|
||||||
def reportPlayback(self):
|
|
||||||
|
|
||||||
log.debug("reportPlayback Called")
|
|
||||||
|
|
||||||
# Get current file
|
|
||||||
currentFile = self.currentFile
|
|
||||||
data = self.played_info.get(currentFile)
|
|
||||||
|
|
||||||
# only report playback if emby has initiated the playback (item_id has value)
|
|
||||||
if data:
|
|
||||||
# Get playback information
|
|
||||||
itemId = data['item_id']
|
|
||||||
audioindex = data['AudioStreamIndex']
|
|
||||||
subtitleindex = data['SubtitleStreamIndex']
|
|
||||||
playTime = data['currentPosition']
|
|
||||||
playMethod = data['playmethod']
|
|
||||||
paused = data.get('paused', False)
|
|
||||||
playsession_id = data.get('playsession_id')
|
|
||||||
mediasource_id = data.get('mediasource_id')
|
|
||||||
|
|
||||||
|
|
||||||
# Get playback volume
|
|
||||||
result = JSONRPC('Application.GetProperties').execute({'properties': ["volume", "muted"]})
|
|
||||||
result = result.get('result')
|
|
||||||
|
|
||||||
volume = result.get('volume')
|
|
||||||
muted = result.get('muted')
|
|
||||||
|
|
||||||
# Postdata for the websocketclient report
|
|
||||||
postdata = {
|
|
||||||
|
|
||||||
'QueueableMediaTypes': "Video",
|
|
||||||
'CanSeek': True,
|
|
||||||
'ItemId': itemId,
|
|
||||||
'MediaSourceId': mediasource_id or itemId,
|
|
||||||
'PlayMethod': playMethod,
|
|
||||||
'PositionTicks': int(playTime * 10000000),
|
|
||||||
'IsPaused': paused,
|
|
||||||
'VolumeLevel': volume,
|
|
||||||
'IsMuted': muted,
|
|
||||||
'PlaySessionId': playsession_id
|
|
||||||
}
|
|
||||||
|
|
||||||
if playMethod == "Transcode":
|
|
||||||
# Track can't be changed, keep reporting the same index
|
|
||||||
postdata['AudioStreamIndex'] = audioindex
|
|
||||||
postdata['AudioStreamIndex'] = subtitleindex
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Get current audio and subtitles track
|
|
||||||
params = {
|
|
||||||
'playerid': 1,
|
|
||||||
'properties': ["currentsubtitle","currentaudiostream","subtitleenabled"]
|
|
||||||
}
|
|
||||||
result = JSONRPC('Player.GetProperties').execute(params)
|
|
||||||
result = result.get('result')
|
|
||||||
|
|
||||||
try: # Audio tracks
|
|
||||||
indexAudio = result['currentaudiostream']['index']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
indexAudio = 0
|
|
||||||
|
|
||||||
try: # Subtitles tracks
|
|
||||||
indexSubs = result['currentsubtitle']['index']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
indexSubs = 0
|
|
||||||
|
|
||||||
try: # If subtitles are enabled
|
|
||||||
subsEnabled = result['subtitleenabled']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
subsEnabled = ""
|
|
||||||
|
|
||||||
# Postdata for the audio
|
|
||||||
data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2
|
|
||||||
|
|
||||||
# Postdata for the subtitles
|
|
||||||
if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0:
|
|
||||||
|
|
||||||
# Number of audiotracks to help get Emby Index
|
|
||||||
audioTracks = len(xbmc.Player().getAvailableAudioStreams())
|
|
||||||
mapping = window("emby_%s.indexMapping" % currentFile)
|
|
||||||
|
|
||||||
if mapping: # Set in PlaybackUtils.py
|
|
||||||
|
|
||||||
log.debug("Mapping for external subtitles index: %s" % mapping)
|
|
||||||
externalIndex = json.loads(mapping)
|
|
||||||
|
|
||||||
if externalIndex.get(str(indexSubs)):
|
|
||||||
# If the current subtitle is in the mapping
|
|
||||||
subindex = [externalIndex[str(indexSubs)]] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
else:
|
|
||||||
# Internal subtitle currently selected
|
|
||||||
subindex = [indexSubs - len(externalIndex) + audioTracks + 1] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
|
|
||||||
else: # Direct paths enabled scenario or no external subtitles set
|
|
||||||
subindex = [indexSubs + audioTracks + 1] * 2
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = subindex
|
|
||||||
else:
|
|
||||||
data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2
|
|
||||||
|
|
||||||
# Report progress via websocketclient
|
|
||||||
log.debug("Report: %s", postdata)
|
|
||||||
self.emby.progress_report(postdata)
|
|
||||||
|
|
||||||
|
|
||||||
@log_error()
|
|
||||||
def onPlayBackSeek(self, time, seekOffset):
|
|
||||||
# Make position when seeking a bit more accurate
|
|
||||||
currentFile = self.currentFile
|
|
||||||
log.debug("PLAYBACK_SEEK: %s" % currentFile)
|
|
||||||
|
|
||||||
if self.played_info.get(currentFile):
|
|
||||||
position = None
|
|
||||||
try:
|
|
||||||
position = self.xbmcplayer.getTime()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if position is not None:
|
|
||||||
self.played_info[currentFile]['currentPosition'] = position
|
|
||||||
self.reportPlayback()
|
|
||||||
|
|
||||||
def stopAll(self):
|
|
||||||
|
|
||||||
if not self.played_info:
|
|
||||||
return
|
|
||||||
|
|
||||||
log.info("Played_information: %s" % self.played_info)
|
|
||||||
# Process each items
|
|
||||||
for item in self.played_info:
|
|
||||||
|
|
||||||
data = self.played_info.get(item)
|
|
||||||
if data:
|
|
||||||
|
|
||||||
log.debug("Item path: %s" % item)
|
|
||||||
log.debug("Item data: %s" % data)
|
|
||||||
|
|
||||||
runtime = data['runtime']
|
|
||||||
currentPosition = data['currentPosition']
|
|
||||||
itemid = data['item_id']
|
|
||||||
refresh_id = data['refresh_id']
|
|
||||||
currentFile = data['currentfile']
|
|
||||||
media_type = data['Type']
|
|
||||||
playMethod = data['playmethod']
|
|
||||||
|
|
||||||
self.stop_playback(data)
|
|
||||||
|
|
||||||
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"
|
|
||||||
% (percentComplete, markPlayedAt))
|
|
||||||
|
|
||||||
# Send the delete action to the server.
|
|
||||||
offerDelete = False
|
|
||||||
|
|
||||||
if media_type == "Episode" and settings('deleteTV') == "true":
|
|
||||||
offerDelete = True
|
|
||||||
elif media_type == "Movie" and settings('deleteMovies') == "true":
|
|
||||||
offerDelete = True
|
|
||||||
|
|
||||||
if settings('offerDelete') != "true":
|
|
||||||
# Delete could be disabled, even if the subsetting is enabled.
|
|
||||||
offerDelete = False
|
|
||||||
|
|
||||||
if percentComplete >= markPlayedAt and offerDelete:
|
|
||||||
resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000)
|
|
||||||
if resp:
|
|
||||||
url = "{server}/emby/Items/%s?format=json" % itemid
|
|
||||||
log.info("Deleting request: %s" % itemid)
|
|
||||||
self.doUtils(url, action_type="DELETE")
|
|
||||||
else:
|
|
||||||
log.info("User skipped deletion.")
|
|
||||||
|
|
||||||
window('emby.external_check', clear=True)
|
|
||||||
|
|
||||||
##### Track end of playlist
|
|
||||||
if media_type == "Audio":
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
|
||||||
else:
|
|
||||||
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
|
|
||||||
if int(playlist.getposition()) < 0:
|
|
||||||
''' If playback is stopped within the first 30 seconds,
|
|
||||||
Kodi doesn't consider this watched and playlist should not be cleared.
|
|
||||||
When the end is reached, position is -1.
|
|
||||||
'''
|
|
||||||
log.info("Clear playlist, end detected.")
|
|
||||||
playlist.clear()
|
|
||||||
|
|
||||||
path = xbmc.translatePath(
|
|
||||||
"special://profile/addon_data/plugin.video.emby/temp/").decode('utf-8')
|
|
||||||
|
|
||||||
dirs, files = xbmcvfs.listdir(path)
|
|
||||||
for file in files:
|
|
||||||
xbmcvfs.delete("%s%s" % (path, file))
|
|
||||||
|
|
||||||
self.played_info.clear()
|
|
||||||
|
|
||||||
ga = GoogleAnalytics()
|
|
||||||
ga.sendEventData("PlayAction", "Stopped")
|
|
||||||
|
|
||||||
def stop_playback(self, data):
|
|
||||||
|
|
||||||
log.info("stop playback called.")
|
|
||||||
|
|
||||||
position_ticks = int(data['currentPosition'] * 10000000)
|
|
||||||
position = data['runtime'] if position_ticks and window('emby.external') else position_ticks
|
|
||||||
|
|
||||||
self.emby.stop_playback(data['item_id'], position, data['playsession_id'], data.get('mediasource_id'))
|
|
||||||
|
|
||||||
# Stop transcode
|
|
||||||
if data['playmethod'] == "Transcode":
|
|
||||||
log.info("Transcoding for %s terminated." % data['item_id'])
|
|
||||||
url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % self.clientInfo.get_device_id()
|
|
||||||
self.doUtils(url, action_type="DELETE")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
|
@ -121,8 +121,7 @@ class Views(object):
|
||||||
|
|
||||||
def get_views(self):
|
def get_views(self):
|
||||||
|
|
||||||
''' Get all views and the media folders that make up the views.
|
''' Get the media folders. Add or remove them.
|
||||||
Add custom views that are not media folders but should still be added
|
|
||||||
'''
|
'''
|
||||||
media = {
|
media = {
|
||||||
'movies': "Movie",
|
'movies': "Movie",
|
||||||
|
|
|
@ -31,7 +31,7 @@ DELAY = int(settings('startupDelay') or 0)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
LOG.info("--->[ service ]")
|
LOG.warn("--->[ service ]")
|
||||||
LOG.warn("Delay startup by %s seconds.", DELAY)
|
LOG.warn("Delay startup by %s seconds.", DELAY)
|
||||||
|
|
||||||
session = Service()
|
session = Service()
|
||||||
|
@ -46,4 +46,4 @@ if __name__ == "__main__":
|
||||||
LOG.exception(error)
|
LOG.exception(error)
|
||||||
session.shutdown()
|
session.shutdown()
|
||||||
|
|
||||||
LOG.info("---<[ service ]")
|
LOG.warn("---<[ service ]")
|
||||||
|
|
Loading…
Reference in a new issue