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__":
|
||||
|
||||
LOG.info("--->[ context ]")
|
||||
LOG.debug("--->[ context ]")
|
||||
|
||||
try:
|
||||
Context()
|
||||
|
|
|
@ -28,7 +28,7 @@ LOG = logging.getLogger("EMBY.context")
|
|||
|
||||
if __name__ == "__main__":
|
||||
|
||||
LOG.info("--->[ context ]")
|
||||
LOG.debug("--->[ context ]")
|
||||
|
||||
try:
|
||||
Context(True)
|
||||
|
|
196
default.py
196
default.py
|
@ -28,7 +28,7 @@ LOG = logging.getLogger("EMBY.default")
|
|||
|
||||
if __name__ == "__main__":
|
||||
|
||||
LOG.info("--->[ default ]")
|
||||
LOG.debug("--->[ default ]")
|
||||
|
||||
try:
|
||||
Events()
|
||||
|
@ -36,197 +36,3 @@ if __name__ == "__main__":
|
|||
LOG.exception(error)
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
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):
|
||||
cast = []
|
||||
|
||||
|
@ -125,40 +51,6 @@ class API(object):
|
|||
|
||||
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):
|
||||
return {
|
||||
'video': video or [],
|
||||
|
@ -247,21 +139,6 @@ class API(object):
|
|||
|
||||
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):
|
||||
# Convert studio for Kodi to properly detect them
|
||||
studios = {
|
||||
|
@ -277,36 +154,6 @@ class API(object):
|
|||
}
|
||||
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):
|
||||
|
||||
overview = overview or self.item.get('Overview')
|
||||
|
@ -321,24 +168,6 @@ class API(object):
|
|||
|
||||
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):
|
||||
# Convert more complex cases
|
||||
mpaa = self.item.get('OfficialRating', "")
|
||||
|
@ -352,15 +181,6 @@ class API(object):
|
|||
|
||||
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):
|
||||
|
||||
if path is None:
|
||||
|
|
|
@ -273,117 +273,3 @@ def normalize_string(text):
|
|||
text = unicodedata.normalize('NFKD', unicode(text, 'utf-8')).encode('ascii', 'ignore')
|
||||
|
||||
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)
|
||||
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():
|
||||
# Track the existance of <cleanonupdate>true</cleanonupdate>
|
||||
# incompatible with plugin paths
|
||||
|
|
|
@ -364,519 +364,3 @@ class Player(xbmc.Player):
|
|||
window('emby.external_check', clear=True)
|
||||
|
||||
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):
|
||||
|
||||
''' Get all views and the media folders that make up the views.
|
||||
Add custom views that are not media folders but should still be added
|
||||
''' Get the media folders. Add or remove them.
|
||||
'''
|
||||
media = {
|
||||
'movies': "Movie",
|
||||
|
|
|
@ -31,7 +31,7 @@ DELAY = int(settings('startupDelay') or 0)
|
|||
|
||||
if __name__ == "__main__":
|
||||
|
||||
LOG.info("--->[ service ]")
|
||||
LOG.warn("--->[ service ]")
|
||||
LOG.warn("Delay startup by %s seconds.", DELAY)
|
||||
|
||||
session = Service()
|
||||
|
@ -46,4 +46,4 @@ if __name__ == "__main__":
|
|||
LOG.exception(error)
|
||||
session.shutdown()
|
||||
|
||||
LOG.info("---<[ service ]")
|
||||
LOG.warn("---<[ service ]")
|
||||
|
|
Loading…
Reference in a new issue