Merge branch 'beta-version'
This commit is contained in:
commit
7ee68937bc
68 changed files with 2247 additions and 2068 deletions
|
@ -1,5 +1,5 @@
|
|||
[![stable version](https://img.shields.io/badge/stable_version-2.1.1-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-2.2.1-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||
[![beta version](https://img.shields.io/badge/beta_version-2.2.2-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
|
||||
|
||||
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
|
||||
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)
|
||||
|
|
18
addon.xml
18
addon.xml
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.1" provider-name="croneter">
|
||||
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.2.2" provider-name="croneter">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.requests" version="2.9.1" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.4" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.4" />
|
||||
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.5" />
|
||||
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.5" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource" library="default.py">
|
||||
<provides>video audio image</provides>
|
||||
|
@ -67,7 +67,17 @@
|
|||
<summary lang="ru_RU">Нативная интеграция сервера Plex в Kodi</summary>
|
||||
<description lang="ru_RU">Подключите Kodi к своему серверу Plex. Плагин предполагает что вы управляете своими видео с помощью Plex (а не в Kodi). Вы можете потерять текущие базы данных музыки и видео в Kodi (так как плагин напрямую их изменяет). Используйте на свой страх и риск</description>
|
||||
<disclaimer lang="ru_RU">Используйте на свой страх и риск</disclaimer>
|
||||
<news>version 2.2.1 (beta only):
|
||||
<news>version 2.2.2 (beta only):
|
||||
- Fixes to locking mechanisms which resulted in weird behavior in some cases
|
||||
- Switch to Python __future__ unicode_literals and absolute paths
|
||||
- Fix UnboundLocalError for playlists
|
||||
- Check all Kodi database versions before starting PKC
|
||||
- Fix KeyError on non-PKC playback startup
|
||||
- Speed up subtitle download to Kodi
|
||||
- Update translations
|
||||
- PEP-8 stuff
|
||||
|
||||
version 2.2.1 (beta only):
|
||||
- Fix library sync crash due to PMS sending string, not unicode
|
||||
- Fix playback from playlists for add-on paths
|
||||
- Detect playback from a Kodi playlist for add-on paths - because we need some hacks due to Kodi bugs
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
version 2.2.2 (beta only):
|
||||
- Fixes to locking mechanisms which resulted in weird behavior in some cases
|
||||
- Switch to Python __future__ unicode_literals and absolute paths
|
||||
- Fix UnboundLocalError for playlists
|
||||
- Check all Kodi database versions before starting PKC
|
||||
- Fix KeyError on non-PKC playback startup
|
||||
- Speed up subtitle download to Kodi
|
||||
- Update translations
|
||||
- PEP-8 stuff
|
||||
|
||||
version 2.2.1 (beta only):
|
||||
- Fix library sync crash due to PMS sending string, not unicode
|
||||
- Fix playback from playlists for add-on paths
|
||||
|
|
95
default.py
95
default.py
|
@ -1,47 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
###############################################################################
|
||||
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
import logging
|
||||
from os import path as os_path
|
||||
from sys import path as sys_path, argv
|
||||
from sys import argv
|
||||
from urlparse import parse_qsl
|
||||
|
||||
from xbmc import translatePath, sleep, executebuiltin
|
||||
from xbmcaddon import Addon
|
||||
from xbmc import sleep, executebuiltin
|
||||
from xbmcgui import ListItem
|
||||
from xbmcplugin import setResolvedUrl
|
||||
|
||||
_addon = Addon(id='plugin.video.plexkodiconnect')
|
||||
try:
|
||||
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
|
||||
except TypeError:
|
||||
_addon_path = _addon.getAddonInfo('path').decode()
|
||||
try:
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode('utf-8')
|
||||
except TypeError:
|
||||
_base_resource = translatePath(os_path.join(
|
||||
_addon_path,
|
||||
'resources',
|
||||
'lib')).decode()
|
||||
sys_path.append(_base_resource)
|
||||
from resources.lib import entrypoint, utils, pickler, pkc_listitem, \
|
||||
variables as v, loghandler
|
||||
from resources.lib.watchdog.utils import unicode_paths
|
||||
|
||||
###############################################################################
|
||||
|
||||
import entrypoint
|
||||
from utils import window, reset, passwords_xml, language as lang, dialog, \
|
||||
plex_command
|
||||
from pickler import unpickle_me, pickl_window
|
||||
from PKC_listitem import convert_PKC_to_listitem
|
||||
import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
import loghandler
|
||||
|
||||
loghandler.config()
|
||||
log = logging.getLogger('PLEX.default')
|
||||
|
||||
|
@ -54,9 +27,11 @@ class Main():
|
|||
# MAIN ENTRY POINT
|
||||
# @utils.profiling()
|
||||
def __init__(self):
|
||||
log.debug('Full sys.argv received: %s' % argv)
|
||||
log.debug('Full sys.argv received: %s', argv)
|
||||
# Parse parameters
|
||||
params = dict(parse_qsl(argv[2][1:]))
|
||||
path = unicode_paths.decode(argv[0])
|
||||
arguments = unicode_paths.decode(argv[2])
|
||||
params = dict(parse_qsl(arguments[1:]))
|
||||
mode = params.get('mode', '')
|
||||
itemid = params.get('id', '')
|
||||
|
||||
|
@ -104,7 +79,7 @@ class Main():
|
|||
entrypoint.create_new_pms()
|
||||
|
||||
elif mode == 'reset':
|
||||
reset()
|
||||
utils.reset()
|
||||
|
||||
elif mode == 'togglePlexTV':
|
||||
entrypoint.toggle_plex_tv_sign_in()
|
||||
|
@ -113,51 +88,51 @@ class Main():
|
|||
entrypoint.reset_authorization()
|
||||
|
||||
elif mode == 'passwords':
|
||||
passwords_xml()
|
||||
utils.passwords_xml()
|
||||
|
||||
elif mode == 'switchuser':
|
||||
entrypoint.switch_plex_user()
|
||||
|
||||
elif mode in ('manualsync', 'repair'):
|
||||
if window('plex_online') != 'true':
|
||||
if pickler.pickl_window('plex_online') != 'true':
|
||||
# Server is not online, do not run the sync
|
||||
dialog('ok', lang(29999), lang(39205))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39205))
|
||||
log.error('Not connected to a PMS.')
|
||||
else:
|
||||
if mode == 'repair':
|
||||
log.info('Requesting repair lib sync')
|
||||
plex_command('RUN_LIB_SCAN', 'repair')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'repair')
|
||||
elif mode == 'manualsync':
|
||||
log.info('Requesting full library scan')
|
||||
plex_command('RUN_LIB_SCAN', 'full')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'full')
|
||||
|
||||
elif mode == 'texturecache':
|
||||
log.info('Requesting texture caching of all textures')
|
||||
plex_command('RUN_LIB_SCAN', 'textures')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'textures')
|
||||
|
||||
elif mode == 'chooseServer':
|
||||
entrypoint.choose_pms_server()
|
||||
|
||||
elif mode == 'refreshplaylist':
|
||||
log.info('Requesting playlist/nodes refresh')
|
||||
plex_command('RUN_LIB_SCAN', 'views')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'views')
|
||||
|
||||
elif mode == 'deviceid':
|
||||
self.deviceid()
|
||||
|
||||
elif mode == 'fanart':
|
||||
log.info('User requested fanarttv refresh')
|
||||
plex_command('RUN_LIB_SCAN', 'fanart')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'fanart')
|
||||
|
||||
elif '/extrafanart' in argv[0]:
|
||||
plexpath = argv[2][1:]
|
||||
elif '/extrafanart' in path:
|
||||
plexpath = arguments[1:]
|
||||
plexid = itemid
|
||||
entrypoint.extra_fanart(plexid, plexpath)
|
||||
entrypoint.get_video_files(plexid, plexpath)
|
||||
|
||||
# Called by e.g. 3rd party plugin video extras
|
||||
elif ('/Extras' in argv[0] or '/VideoFiles' in argv[0] or
|
||||
'/Extras' in argv[2]):
|
||||
elif ('/Extras' in path or '/VideoFiles' in path or
|
||||
'/Extras' in arguments):
|
||||
plexId = itemid or None
|
||||
entrypoint.get_video_files(plexId, params)
|
||||
|
||||
|
@ -171,40 +146,40 @@ class Main():
|
|||
"""
|
||||
request = '%s&handle=%s' % (argv[2], HANDLE)
|
||||
# Put the request into the 'queue'
|
||||
plex_command('PLAY', request)
|
||||
utils.plex_command('PLAY', request)
|
||||
if HANDLE == -1:
|
||||
# Handle -1 received, not waiting for main thread
|
||||
return
|
||||
# Wait for the result
|
||||
while not pickl_window('plex_result'):
|
||||
while not pickler.pickl_window('plex_result'):
|
||||
sleep(50)
|
||||
result = unpickle_me()
|
||||
result = pickler.unpickle_me()
|
||||
if result is None:
|
||||
log.error('Error encountered, aborting')
|
||||
dialog('notification',
|
||||
heading='{plex}',
|
||||
message=lang(30128),
|
||||
icon='{error}',
|
||||
time=3000)
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=utils.lang(30128),
|
||||
icon='{error}',
|
||||
time=3000)
|
||||
setResolvedUrl(HANDLE, False, ListItem())
|
||||
elif result.listitem:
|
||||
listitem = convert_PKC_to_listitem(result.listitem)
|
||||
listitem = pkc_listitem.convert_pkc_to_listitem(result.listitem)
|
||||
setResolvedUrl(HANDLE, True, listitem)
|
||||
|
||||
@staticmethod
|
||||
def deviceid():
|
||||
deviceId_old = window('plex_client_Id')
|
||||
deviceId_old = pickler.pickl_window('plex_client_Id')
|
||||
from clientinfo import getDeviceId
|
||||
try:
|
||||
deviceId = getDeviceId(reset=True)
|
||||
except Exception as e:
|
||||
log.error('Failed to generate a new device Id: %s' % e)
|
||||
dialog('ok', lang(29999), lang(33032))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(33032))
|
||||
else:
|
||||
log.info('Successfully removed old device ID: %s New deviceId:'
|
||||
'%s' % (deviceId_old, deviceId))
|
||||
# 'Kodi will now restart to apply the changes'
|
||||
dialog('ok', lang(29999), lang(33033))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(33033))
|
||||
executebuiltin('RestartApp')
|
||||
|
||||
|
||||
|
|
|
@ -3,27 +3,24 @@
|
|||
###############################################################################
|
||||
from logging import getLogger
|
||||
from Queue import Queue, Empty
|
||||
from shutil import rmtree
|
||||
from urllib import quote_plus, unquote
|
||||
from threading import Thread
|
||||
from os import makedirs
|
||||
import requests
|
||||
|
||||
import xbmc
|
||||
from xbmcvfs import exists
|
||||
|
||||
from utils import settings, language as lang, kodi_sql, try_encode, try_decode,\
|
||||
thread_methods, dialog, exists_dir
|
||||
import state
|
||||
from . import path_ops
|
||||
from . import utils
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.artwork')
|
||||
|
||||
# Disable annoying requests warnings
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
ARTWORK_QUEUE = Queue()
|
||||
IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC']
|
||||
if not settings('imageSyncDuringPlayback') == 'true':
|
||||
if not utils.settings('imageSyncDuringPlayback') == 'true':
|
||||
IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC')
|
||||
|
||||
###############################################################################
|
||||
|
@ -37,7 +34,7 @@ def double_urldecode(text):
|
|||
return unquote(unquote(text))
|
||||
|
||||
|
||||
@thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS)
|
||||
@utils.thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS)
|
||||
class Image_Cache_Thread(Thread):
|
||||
sleep_between = 50
|
||||
# Potentially issues with limited number of threads
|
||||
|
@ -73,20 +70,20 @@ class Image_Cache_Thread(Thread):
|
|||
'Window.IsVisible(DialogAddonSettings.xml)'):
|
||||
# Avoid saving '0' all the time
|
||||
set_zero = True
|
||||
settings('caching_artwork_count', value='0')
|
||||
utils.settings('caching_artwork_count', value='0')
|
||||
xbmc.sleep(1000)
|
||||
continue
|
||||
set_zero = False
|
||||
if isinstance(url, ArtworkSyncMessage):
|
||||
if state.IMAGE_SYNC_NOTIFICATIONS:
|
||||
dialog('notification',
|
||||
heading=lang(29999),
|
||||
message=url.message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
heading=utils.lang(29999),
|
||||
message=url.message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
queue.task_done()
|
||||
continue
|
||||
url = double_urlencode(try_encode(url))
|
||||
url = double_urlencode(utils.try_encode(url))
|
||||
sleeptime = 0
|
||||
while True:
|
||||
try:
|
||||
|
@ -133,14 +130,15 @@ class Image_Cache_Thread(Thread):
|
|||
if (counter > 20 and not xbmc.getCondVisibility(
|
||||
'Window.IsVisible(DialogAddonSettings.xml)')):
|
||||
counter = 0
|
||||
settings('caching_artwork_count', value=str(queue.qsize()))
|
||||
utils.settings('caching_artwork_count',
|
||||
value=str(queue.qsize()))
|
||||
# Sleep for a bit to reduce CPU strain
|
||||
xbmc.sleep(sleep_between)
|
||||
LOG.info("---===### Stopped Image_Cache_Thread ###===---")
|
||||
|
||||
|
||||
class Artwork():
|
||||
enableTextureCache = settings('enableTextureCache') == "true"
|
||||
enableTextureCache = utils.settings('enableTextureCache') == "true"
|
||||
if enableTextureCache:
|
||||
queue = ARTWORK_QUEUE
|
||||
|
||||
|
@ -156,7 +154,7 @@ class Artwork():
|
|||
artworks = list()
|
||||
# Get all posters and fanart/background for video and music
|
||||
for kind in ('video', 'music'):
|
||||
connection = kodi_sql(kind)
|
||||
connection = utils.kodi_sql(kind)
|
||||
cursor = connection.cursor()
|
||||
for typus in ('poster', 'fanart'):
|
||||
cursor.execute('SELECT url FROM art WHERE type == ?',
|
||||
|
@ -164,7 +162,7 @@ class Artwork():
|
|||
artworks.extend(cursor.fetchall())
|
||||
connection.close()
|
||||
artworks_to_cache = list()
|
||||
connection = kodi_sql('texture')
|
||||
connection = utils.kodi_sql('texture')
|
||||
cursor = connection.cursor()
|
||||
for url in artworks:
|
||||
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
|
||||
|
@ -175,40 +173,40 @@ class Artwork():
|
|||
if not artworks_to_cache:
|
||||
LOG.info('Caching of major images to Kodi texture cache done')
|
||||
# Set to "None"
|
||||
settings('caching_artwork_count', value=lang(30069))
|
||||
utils.settings('caching_artwork_count', value=utils.lang(30069))
|
||||
return
|
||||
length = len(artworks_to_cache)
|
||||
LOG.info('Caching has not been completed - caching %s major images',
|
||||
length)
|
||||
settings('caching_artwork_count', value=str(length))
|
||||
utils.settings('caching_artwork_count', value=str(length))
|
||||
# Caching %s Plex images
|
||||
self.queue.put(ArtworkSyncMessage(lang(30006) % length))
|
||||
self.queue.put(ArtworkSyncMessage(utils.lang(30006) % length))
|
||||
for i, url in enumerate(artworks_to_cache):
|
||||
self.queue.put(url[0])
|
||||
# Plex image caching done
|
||||
self.queue.put(ArtworkSyncMessage(lang(30007)))
|
||||
self.queue.put(ArtworkSyncMessage(utils.lang(30007)))
|
||||
|
||||
def fullTextureCacheSync(self):
|
||||
"""
|
||||
This method will sync all Kodi artwork to textures13.db
|
||||
and cache them locally. This takes diskspace!
|
||||
"""
|
||||
if not dialog('yesno', "Image Texture Cache", lang(39250)):
|
||||
if not utils.dialog('yesno', "Image Texture Cache", utils.lang(39250)):
|
||||
return
|
||||
|
||||
LOG.info("Doing Image Cache Sync")
|
||||
|
||||
# ask to rest all existing or not
|
||||
if dialog('yesno', "Image Texture Cache", lang(39251)):
|
||||
if utils.dialog('yesno', "Image Texture Cache", utils.lang(39251)):
|
||||
LOG.info("Resetting all cache data first")
|
||||
# Remove all existing textures first
|
||||
path = try_decode(xbmc.translatePath("special://thumbnails/"))
|
||||
if exists_dir(path):
|
||||
rmtree(path, ignore_errors=True)
|
||||
path = path_ops.translate_path('special://thumbnails/')
|
||||
if path_ops.exists(path):
|
||||
path_ops.rmtree(path, ignore_errors=True)
|
||||
self.restore_cache_directories()
|
||||
|
||||
# remove all existing data from texture DB
|
||||
connection = kodi_sql('texture')
|
||||
connection = utils.kodi_sql('texture')
|
||||
cursor = connection.cursor()
|
||||
query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
|
||||
cursor.execute(query, ('table', ))
|
||||
|
@ -221,7 +219,7 @@ class Artwork():
|
|||
connection.close()
|
||||
|
||||
# Cache all entries in video DB
|
||||
connection = kodi_sql('video')
|
||||
connection = utils.kodi_sql('video')
|
||||
cursor = connection.cursor()
|
||||
# dont include actors
|
||||
query = "SELECT url FROM art WHERE media_type != ?"
|
||||
|
@ -234,7 +232,7 @@ class Artwork():
|
|||
for url in result:
|
||||
self.cache_texture(url[0])
|
||||
# Cache all entries in music DB
|
||||
connection = kodi_sql('music')
|
||||
connection = utils.kodi_sql('music')
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT url FROM art")
|
||||
result = cursor.fetchall()
|
||||
|
@ -309,7 +307,7 @@ class Artwork():
|
|||
"""
|
||||
Deleted the cached artwork with path url (if it exists)
|
||||
"""
|
||||
connection = kodi_sql('texture')
|
||||
connection = utils.kodi_sql('texture')
|
||||
cursor = connection.cursor()
|
||||
try:
|
||||
cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
|
||||
|
@ -320,10 +318,11 @@ class Artwork():
|
|||
pass
|
||||
else:
|
||||
# Delete thumbnail as well as the entry
|
||||
path = xbmc.translatePath("special://thumbnails/%s" % cachedurl)
|
||||
path = path_ops.translate_path("special://thumbnails/%s"
|
||||
% cachedurl)
|
||||
LOG.debug("Deleting cached thumbnail: %s", path)
|
||||
if exists(path):
|
||||
rmtree(try_decode(path), ignore_errors=True)
|
||||
if path_ops.exists(path):
|
||||
path_ops.rmtree(path, ignore_errors=True)
|
||||
cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
|
||||
connection.commit()
|
||||
finally:
|
||||
|
@ -336,8 +335,8 @@ class Artwork():
|
|||
"a", "b", "c", "d", "e", "f",
|
||||
"Video", "plex")
|
||||
for path in paths:
|
||||
makedirs(try_decode(xbmc.translatePath("special://thumbnails/%s"
|
||||
% path)))
|
||||
new_path = path_ops.translate_path("special://thumbnails/%s" % path)
|
||||
path_ops.makedirs(utils.encode_path(new_path))
|
||||
|
||||
|
||||
class ArtworkSyncMessage(object):
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
###############################################################################
|
||||
from logging import getLogger
|
||||
|
||||
from utils import window, settings
|
||||
import variables as v
|
||||
from . import utils
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
LOG = getLogger('PLEX.clientinfo')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -43,8 +43,8 @@ def getXArgsDeviceInfo(options=None, include_token=True):
|
|||
'X-Plex-Client-Identifier': getDeviceId(),
|
||||
'X-Plex-Provides': 'client,controller,player,pubsub-player',
|
||||
}
|
||||
if include_token and window('pms_token'):
|
||||
xargs['X-Plex-Token'] = window('pms_token')
|
||||
if include_token and utils.window('pms_token'):
|
||||
xargs['X-Plex-Token'] = utils.window('pms_token')
|
||||
if options is not None:
|
||||
xargs.update(options)
|
||||
return xargs
|
||||
|
@ -60,26 +60,26 @@ def getDeviceId(reset=False):
|
|||
"""
|
||||
if reset is True:
|
||||
v.PKC_MACHINE_IDENTIFIER = None
|
||||
window('plex_client_Id', clear=True)
|
||||
settings('plex_client_Id', value="")
|
||||
utils.window('plex_client_Id', clear=True)
|
||||
utils.settings('plex_client_Id', value="")
|
||||
|
||||
client_id = v.PKC_MACHINE_IDENTIFIER
|
||||
if client_id:
|
||||
return client_id
|
||||
|
||||
client_id = settings('plex_client_Id')
|
||||
client_id = utils.settings('plex_client_Id')
|
||||
# Because Kodi appears to cache file settings!!
|
||||
if client_id != "" and reset is False:
|
||||
v.PKC_MACHINE_IDENTIFIER = client_id
|
||||
window('plex_client_Id', value=client_id)
|
||||
log.info("Unique device Id plex_client_Id loaded: %s", client_id)
|
||||
utils.window('plex_client_Id', value=client_id)
|
||||
LOG.info("Unique device Id plex_client_Id loaded: %s", client_id)
|
||||
return client_id
|
||||
|
||||
log.info("Generating a new deviceid.")
|
||||
LOG.info("Generating a new deviceid.")
|
||||
from uuid import uuid4
|
||||
client_id = str(uuid4())
|
||||
settings('plex_client_Id', value=client_id)
|
||||
utils.settings('plex_client_Id', value=client_id)
|
||||
v.PKC_MACHINE_IDENTIFIER = client_id
|
||||
window('plex_client_Id', value=client_id)
|
||||
log.info("Unique device Id plex_client_Id generated: %s", client_id)
|
||||
utils.window('plex_client_Id', value=client_id)
|
||||
LOG.info("Unique device Id plex_client_Id generated: %s", client_id)
|
||||
return client_id
|
||||
|
|
|
@ -2,18 +2,17 @@
|
|||
###############################################################################
|
||||
import logging
|
||||
from threading import Thread
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, thread_methods
|
||||
import state
|
||||
from . import utils
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = logging.getLogger("PLEX." + __name__)
|
||||
LOG = logging.getLogger('PLEX.command_pipeline')
|
||||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods
|
||||
@utils.thread_methods
|
||||
class Monitor_Window(Thread):
|
||||
"""
|
||||
Monitors window('plex_command') for new entries that we need to take care
|
||||
|
@ -26,9 +25,9 @@ class Monitor_Window(Thread):
|
|||
queue = state.COMMAND_PIPELINE_QUEUE
|
||||
LOG.info("----===## Starting Kodi_Play_Client ##===----")
|
||||
while not stopped():
|
||||
if window('plex_command'):
|
||||
value = window('plex_command')
|
||||
window('plex_command', clear=True)
|
||||
if utils.window('plex_command'):
|
||||
value = utils.window('plex_command')
|
||||
utils.window('plex_command', clear=True)
|
||||
if value.startswith('PLAY-'):
|
||||
queue.put(value.replace('PLAY-', ''))
|
||||
elif value == 'SUSPEND_LIBRARY_THREAD-True':
|
||||
|
|
|
@ -2,18 +2,17 @@
|
|||
Processes Plex companion inputs from the plexbmchelper to Kodi commands
|
||||
"""
|
||||
from logging import getLogger
|
||||
|
||||
from xbmc import Player
|
||||
|
||||
from variables import ALEXA_TO_COMPANION
|
||||
import playqueue as PQ
|
||||
from PlexFunctions import GetPlexKeyNumber
|
||||
import json_rpc as js
|
||||
import state
|
||||
from . import playqueue as PQ
|
||||
from . import plex_functions as PF
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.companion')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -25,7 +24,7 @@ def skip_to(params):
|
|||
Does not seem to be implemented yet by Plex!
|
||||
"""
|
||||
playqueue_item_id = params.get('playQueueItemID')
|
||||
_, plex_id = GetPlexKeyNumber(params.get('key'))
|
||||
_, plex_id = PF.GetPlexKeyNumber(params.get('key'))
|
||||
LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
|
||||
playqueue_item_id, plex_id)
|
||||
found = True
|
||||
|
@ -51,8 +50,8 @@ def convert_alexa_to_companion(dictionary):
|
|||
The params passed by Alexa must first be converted to Companion talk
|
||||
"""
|
||||
for key in dictionary:
|
||||
if key in ALEXA_TO_COMPANION:
|
||||
dictionary[ALEXA_TO_COMPANION[key]] = dictionary[key]
|
||||
if key in v.ALEXA_TO_COMPANION:
|
||||
dictionary[v.ALEXA_TO_COMPANION[key]] = dictionary[key]
|
||||
del dictionary[key]
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from os.path import join
|
||||
|
||||
import xbmcgui
|
||||
from xbmcaddon import Addon
|
||||
|
||||
from utils import window
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
ADDON = Addon('plugin.video.plexkodiconnect')
|
||||
LOG = getLogger('PLEX.context')
|
||||
|
||||
ACTION_PARENT_DIR = 9
|
||||
ACTION_PREVIOUS_MENU = 10
|
||||
|
@ -44,8 +42,8 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
|
|||
return self.selected_option
|
||||
|
||||
def onInit(self):
|
||||
if window('PlexUserImage'):
|
||||
self.getControl(USER_IMAGE).setImage(window('PlexUserImage'))
|
||||
if utils.window('PlexUserImage'):
|
||||
self.getControl(USER_IMAGE).setImage(utils.window('PlexUserImage'))
|
||||
height = 479 + (len(self._options) * 55)
|
||||
LOG.debug("options: %s", self._options)
|
||||
self.list_ = self.getControl(LIST)
|
||||
|
@ -66,10 +64,11 @@ class ContextMenu(xbmcgui.WindowXMLDialog):
|
|||
self.close()
|
||||
|
||||
def _add_editcontrol(self, x, y, height, width, password=None):
|
||||
media = join(ADDON.getAddonInfo('path'),
|
||||
'resources', 'skins', 'default', 'media')
|
||||
media = path_ops.path.join(
|
||||
v.ADDON_PATH, 'resources', 'skins', 'default', 'media')
|
||||
filename = utils.try_encode(path_ops.path.join(media, 'white.png'))
|
||||
control = xbmcgui.ControlImage(0, 0, 0, 0,
|
||||
filename=join(media, "white.png"),
|
||||
filename=filename,
|
||||
aspectRatio=0,
|
||||
colorDiffuse="ff111111")
|
||||
control.setPosition(x, y)
|
||||
|
|
|
@ -1,35 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
|
||||
from xbmcaddon import Addon
|
||||
import xbmc
|
||||
import xbmcplugin
|
||||
import xbmcgui
|
||||
import context
|
||||
|
||||
import plexdb_functions as plexdb
|
||||
from utils import window, settings, dialog, language as lang
|
||||
import PlexFunctions as PF
|
||||
from PlexAPI import API
|
||||
import playqueue as PQ
|
||||
import variables as v
|
||||
import state
|
||||
from . import context
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import utils
|
||||
from . import plex_functions as PF
|
||||
from .plex_api import API
|
||||
from . import playqueue as PQ
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.context_entry')
|
||||
|
||||
OPTIONS = {
|
||||
'Refresh': lang(30410),
|
||||
'Delete': lang(30409),
|
||||
'Addon': lang(30408),
|
||||
# 'AddFav': lang(30405),
|
||||
# 'RemoveFav': lang(30406),
|
||||
# 'RateSong': lang(30407),
|
||||
'Transcode': lang(30412),
|
||||
'PMS_Play': lang(30415), # Use PMS to start playback
|
||||
'Extras': lang(30235)
|
||||
'Refresh': utils.lang(30410),
|
||||
'Delete': utils.lang(30409),
|
||||
'Addon': utils.lang(30408),
|
||||
# 'AddFav': utils.lang(30405),
|
||||
# 'RemoveFav': utils.lang(30406),
|
||||
# 'RateSong': utils.lang(30407),
|
||||
'Transcode': utils.lang(30412),
|
||||
'PMS_Play': utils.lang(30415), # Use PMS to start playback
|
||||
'Extras': utils.lang(30235)
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
@ -98,14 +95,14 @@ class ContextMenu(object):
|
|||
options.append(OPTIONS['Transcode'])
|
||||
|
||||
# Delete item, only if the Plex Home main user is logged in
|
||||
if (window('plex_restricteduser') != 'true' and
|
||||
window('plex_allows_mediaDeletion') == 'true'):
|
||||
if (utils.window('plex_restricteduser') != 'true' and
|
||||
utils.window('plex_allows_mediaDeletion') == 'true'):
|
||||
options.append(OPTIONS['Delete'])
|
||||
# Addon settings
|
||||
options.append(OPTIONS['Addon'])
|
||||
context_menu = context.ContextMenu(
|
||||
"script-plex-context.xml",
|
||||
Addon('plugin.video.plexkodiconnect').getAddonInfo('path'),
|
||||
utils.try_encode(v.ADDON_PATH),
|
||||
"default",
|
||||
"1080i")
|
||||
context_menu.set_options(options)
|
||||
|
@ -138,14 +135,14 @@ class ContextMenu(object):
|
|||
Delete item on PMS
|
||||
"""
|
||||
delete = True
|
||||
if settings('skipContextMenu') != "true":
|
||||
if not dialog("yesno", heading="{plex}", line1=lang(33041)):
|
||||
if utils.settings('skipContextMenu') != "true":
|
||||
if not utils.dialog("yesno", heading="{plex}", line1=utils.lang(33041)):
|
||||
LOG.info("User skipped deletion for: %s", self.plex_id)
|
||||
delete = False
|
||||
if delete:
|
||||
LOG.info("Deleting Plex item with id %s", self.plex_id)
|
||||
if PF.delete_item_from_pms(self.plex_id) is False:
|
||||
dialog("ok", heading="{plex}", line1=lang(30414))
|
||||
utils.dialog("ok", heading="{plex}", line1=utils.lang(30414))
|
||||
|
||||
def _PMS_play(self):
|
||||
"""
|
||||
|
|
|
@ -5,10 +5,9 @@ from logging import getLogger
|
|||
import xml.etree.ElementTree as etree
|
||||
import requests
|
||||
|
||||
from utils import window, language as lang, dialog
|
||||
import clientinfo as client
|
||||
|
||||
import state
|
||||
from . import utils
|
||||
from . import clientinfo
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -16,7 +15,7 @@ import state
|
|||
import requests.packages.urllib3
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.downloadutils')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -75,22 +74,22 @@ class DownloadUtils():
|
|||
# Start session
|
||||
self.s = requests.Session()
|
||||
|
||||
self.deviceId = client.getDeviceId()
|
||||
self.deviceId = clientinfo.getDeviceId()
|
||||
# Attach authenticated header to the session
|
||||
self.s.headers = client.getXArgsDeviceInfo()
|
||||
self.s.headers = clientinfo.getXArgsDeviceInfo()
|
||||
self.s.encoding = 'utf-8'
|
||||
# Set SSL settings
|
||||
self.setSSL()
|
||||
|
||||
# Set other stuff
|
||||
self.setServer(window('pms_server'))
|
||||
self.setServer(utils.window('pms_server'))
|
||||
|
||||
# Counters to declare PMS dead or unauthorized
|
||||
# Use window variables because start of movies will be called with a
|
||||
# new plugin instance - it's impossible to share data otherwise
|
||||
if reset is True:
|
||||
window('countUnauthorized', value='0')
|
||||
window('countError', value='0')
|
||||
utils.window('countUnauthorized', value='0')
|
||||
utils.window('countError', value='0')
|
||||
|
||||
# Retry connections to the server
|
||||
self.s.mount("http://", requests.adapters.HTTPAdapter(max_retries=1))
|
||||
|
@ -110,7 +109,7 @@ class DownloadUtils():
|
|||
LOG.info('Request session stopped')
|
||||
|
||||
def getHeader(self, options=None):
|
||||
header = client.getXArgsDeviceInfo()
|
||||
header = clientinfo.getXArgsDeviceInfo()
|
||||
if options is not None:
|
||||
header.update(options)
|
||||
return header
|
||||
|
@ -227,9 +226,9 @@ class DownloadUtils():
|
|||
else:
|
||||
# We COULD contact the PMS, hence it ain't dead
|
||||
if authenticate is True:
|
||||
window('countError', value='0')
|
||||
utils.window('countError', value='0')
|
||||
if r.status_code != 401:
|
||||
window('countUnauthorized', value='0')
|
||||
utils.window('countUnauthorized', value='0')
|
||||
|
||||
if r.status_code == 204:
|
||||
# No body in the response
|
||||
|
@ -247,9 +246,10 @@ class DownloadUtils():
|
|||
LOG.info(r.text)
|
||||
if '401 Unauthorized' in r.text:
|
||||
# Truly unauthorized
|
||||
window('countUnauthorized',
|
||||
value=str(int(window('countUnauthorized')) + 1))
|
||||
if (int(window('countUnauthorized')) >=
|
||||
utils.window(
|
||||
'countUnauthorized',
|
||||
value=str(int(utils.window('countUnauthorized')) + 1))
|
||||
if (int(utils.window('countUnauthorized')) >=
|
||||
self.unauthorizedAttempts):
|
||||
LOG.warn('We seem to be truly unauthorized for PMS'
|
||||
' %s ', url)
|
||||
|
@ -258,11 +258,11 @@ class DownloadUtils():
|
|||
LOG.debug('Setting PMS server status to '
|
||||
'unauthorized')
|
||||
state.PMS_STATUS = '401'
|
||||
window('plex_serverStatus', value="401")
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
lang(30017),
|
||||
icon='{error}')
|
||||
utils.window('plex_serverStatus', value="401")
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30017),
|
||||
icon='{error}')
|
||||
else:
|
||||
# there might be other 401 where e.g. PMS under strain
|
||||
LOG.info('PMS might only be under strain')
|
||||
|
@ -312,12 +312,12 @@ class DownloadUtils():
|
|||
if authenticate is True:
|
||||
# Make the addon aware of status
|
||||
try:
|
||||
window('countError',
|
||||
value=str(int(window('countError')) + 1))
|
||||
if int(window('countError')) >= self.connectionAttempts:
|
||||
utils.window('countError',
|
||||
value=str(int(utils.window('countError')) + 1))
|
||||
if int(utils.window('countError')) >= self.connectionAttempts:
|
||||
LOG.warn('Failed to connect to %s too many times. '
|
||||
'Declare PMS dead', url)
|
||||
window('plex_online', value="false")
|
||||
utils.window('plex_online', value="false")
|
||||
except ValueError:
|
||||
# 'countError' not yet set
|
||||
pass
|
||||
|
|
|
@ -5,32 +5,26 @@
|
|||
#
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from shutil import copyfile
|
||||
from os import walk, makedirs
|
||||
from os.path import basename, join
|
||||
from sys import argv
|
||||
from urllib import urlencode
|
||||
|
||||
import xbmcplugin
|
||||
from xbmc import sleep, executebuiltin, translatePath
|
||||
from xbmc import sleep, executebuiltin
|
||||
from xbmcgui import ListItem
|
||||
|
||||
from utils import window, settings, language as lang, dialog, try_encode, \
|
||||
catch_exceptions, exists_dir, plex_command, try_decode
|
||||
import downloadutils
|
||||
|
||||
from PlexFunctions import GetPlexMetadata, GetPlexSectionResults, \
|
||||
GetMachineIdentifier
|
||||
from PlexAPI import API
|
||||
import json_rpc as js
|
||||
import variables as v
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from .plex_api import API
|
||||
from . import plex_functions as PF
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.entrypoint')
|
||||
|
||||
try:
|
||||
HANDLE = int(argv[1])
|
||||
ARGV_0 = argv[0]
|
||||
ARGV_0 = path_ops.decode_path(argv[0])
|
||||
except IndexError:
|
||||
pass
|
||||
###############################################################################
|
||||
|
@ -47,8 +41,8 @@ def choose_pms_server():
|
|||
server = setup.pick_pms(showDialog=True)
|
||||
if server is None:
|
||||
LOG.error('We did not connect to a new PMS, aborting')
|
||||
plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
utils.plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
return
|
||||
|
||||
LOG.info("User chose server %s", server['name'])
|
||||
|
@ -65,12 +59,12 @@ def choose_pms_server():
|
|||
_log_in()
|
||||
LOG.info("Choosing new PMS complete")
|
||||
# '<PMS> connected'
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
'%s %s' % (server['name'], lang(39220)),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
'%s %s' % (server['name'], utils.lang(39220)),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
|
||||
|
||||
def toggle_plex_tv_sign_in():
|
||||
|
@ -78,38 +72,38 @@ def toggle_plex_tv_sign_in():
|
|||
Signs out of Plex.tv if there was a token saved and thus deletes the token.
|
||||
Or signs in to plex.tv if the user was not logged in before.
|
||||
"""
|
||||
if settings('plexToken'):
|
||||
if utils.settings('plexToken'):
|
||||
LOG.info('Reseting plex.tv credentials in settings')
|
||||
settings('plexLogin', value="")
|
||||
settings('plexToken', value="")
|
||||
settings('plexid', value="")
|
||||
settings('plexHomeSize', value="1")
|
||||
settings('plexAvatar', value="")
|
||||
settings('plex_status', value=lang(39226))
|
||||
utils.settings('plexLogin', value="")
|
||||
utils.settings('plexToken', value="")
|
||||
utils.settings('plexid', value="")
|
||||
utils.settings('plexHomeSize', value="1")
|
||||
utils.settings('plexAvatar', value="")
|
||||
utils.settings('plex_status', value=utils.lang(39226))
|
||||
|
||||
window('plex_token', clear=True)
|
||||
plex_command('PLEX_TOKEN', '')
|
||||
plex_command('PLEX_USERNAME', '')
|
||||
utils.window('plex_token', clear=True)
|
||||
utils.plex_command('PLEX_TOKEN', '')
|
||||
utils.plex_command('PLEX_USERNAME', '')
|
||||
else:
|
||||
LOG.info('Login to plex.tv')
|
||||
import initialsetup
|
||||
initialsetup.InitialSetup().plex_tv_sign_in()
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
lang(39221),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(39221),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
|
||||
|
||||
def reset_authorization():
|
||||
"""
|
||||
User tried login and failed too many times. Reset # of logins
|
||||
"""
|
||||
resp = dialog('yesno', heading="{plex}", line1=lang(39206))
|
||||
resp = utils.dialog('yesno', heading="{plex}", line1=utils.lang(39206))
|
||||
if resp == 1:
|
||||
LOG.info("Reset login attempts.")
|
||||
plex_command('PMS_STATUS', 'Auth')
|
||||
utils.plex_command('PMS_STATUS', 'Auth')
|
||||
else:
|
||||
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
|
||||
|
@ -138,17 +132,17 @@ def show_main_menu(content_type=None):
|
|||
LOG.debug('Do main listing with content_type: %s', content_type)
|
||||
xbmcplugin.setContent(HANDLE, 'files')
|
||||
# Get emby nodes from the window props
|
||||
plexprops = window('Plex.nodes.total')
|
||||
plexprops = utils.window('Plex.nodes.total')
|
||||
if plexprops:
|
||||
totalnodes = int(plexprops)
|
||||
for i in range(totalnodes):
|
||||
path = window('Plex.nodes.%s.index' % i)
|
||||
path = utils.window('Plex.nodes.%s.index' % i)
|
||||
if not path:
|
||||
path = window('Plex.nodes.%s.content' % i)
|
||||
path = utils.window('Plex.nodes.%s.content' % i)
|
||||
if not path:
|
||||
continue
|
||||
label = window('Plex.nodes.%s.title' % i)
|
||||
node_type = window('Plex.nodes.%s.type' % i)
|
||||
label = utils.window('Plex.nodes.%s.title' % i)
|
||||
node_type = utils.window('Plex.nodes.%s.type' % i)
|
||||
# because we do not use seperate entrypoints for each content type,
|
||||
# we need to figure out which items to show in each listing. for
|
||||
# now we just only show picture nodes in the picture library video
|
||||
|
@ -161,21 +155,19 @@ def show_main_menu(content_type=None):
|
|||
|
||||
# Plex Watch later
|
||||
if content_type not in ('image', 'audio'):
|
||||
directory_item(lang(39211),
|
||||
directory_item(utils.lang(39211),
|
||||
"plugin://%s?mode=watchlater" % v.ADDON_ID)
|
||||
# Plex Channels
|
||||
directory_item(lang(30173),
|
||||
"plugin://%s?mode=channels" % v.ADDON_ID)
|
||||
directory_item(utils.lang(30173), "plugin://%s?mode=channels" % v.ADDON_ID)
|
||||
# Plex user switch
|
||||
directory_item('%s%s' % (lang(39200), settings('username')),
|
||||
directory_item('%s%s' % (utils.lang(39200), utils.settings('username')),
|
||||
"plugin://%s?mode=switchuser" % v.ADDON_ID)
|
||||
|
||||
# some extra entries for settings and stuff
|
||||
directory_item(lang(39201),
|
||||
"plugin://%s?mode=settings" % v.ADDON_ID)
|
||||
directory_item(lang(39203),
|
||||
directory_item(utils.lang(39201), "plugin://%s?mode=settings" % v.ADDON_ID)
|
||||
directory_item(utils.lang(39203),
|
||||
"plugin://%s?mode=refreshplaylist" % v.ADDON_ID)
|
||||
directory_item(lang(39204),
|
||||
directory_item(utils.lang(39204),
|
||||
"plugin://%s?mode=manualsync" % v.ADDON_ID)
|
||||
xbmcplugin.endOfDirectory(HANDLE)
|
||||
|
||||
|
@ -188,7 +180,7 @@ def switch_plex_user():
|
|||
# Guess these user avatars are a future feature. Skipping for now
|
||||
# Delete any userimages. Since there's always only 1 user: position = 0
|
||||
# position = 0
|
||||
# window('EmbyAdditionalUserImage.%s' % position, clear=True)
|
||||
# utils.window('EmbyAdditionalUserImage.%s' % position, clear=True)
|
||||
LOG.info("Plex home user switch requested")
|
||||
if not _log_out():
|
||||
return
|
||||
|
@ -258,7 +250,8 @@ def create_listitem(item, append_show_title=False, append_sxxexx=False):
|
|||
listitem.setArt({'icon': 'DefaultTVShows.png'})
|
||||
listitem.setProperty('fanart_image', item['art'].get('tvshow.fanart', ''))
|
||||
try:
|
||||
listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
|
||||
listitem.addContextMenuItems([(utils.lang(30032),
|
||||
'XBMC.Action(Info)',)])
|
||||
except TypeError:
|
||||
# Kodi fuck-up
|
||||
pass
|
||||
|
@ -287,7 +280,7 @@ def next_up_episodes(tagname, limit):
|
|||
'properties': ['title', 'studio', 'mpaa', 'file', 'art']
|
||||
}
|
||||
for item in js.get_tv_shows(params):
|
||||
if settings('ignoreSpecialsNextEpisodes') == "true":
|
||||
if utils.settings('ignoreSpecialsNextEpisodes') == "true":
|
||||
params = {
|
||||
'tvshowid': item['tvshowid'],
|
||||
'sort': {'method': "episode"},
|
||||
|
@ -383,8 +376,8 @@ def recent_episodes(mediatype, tagname, limit):
|
|||
# if the addon is called with recentepisodes parameter,
|
||||
# we return the recentepisodes list of the given tagname
|
||||
xbmcplugin.setContent(HANDLE, 'episodes')
|
||||
append_show_title = settings('RecentTvAppendShow') == 'true'
|
||||
append_sxxexx = settings('RecentTvAppendSeason') == 'true'
|
||||
append_show_title = utils.settings('RecentTvAppendShow') == 'true'
|
||||
append_sxxexx = utils.settings('RecentTvAppendSeason') == 'true'
|
||||
# First we get a list of all the TV shows - filtered by tag
|
||||
show_ids = set()
|
||||
params = {
|
||||
|
@ -401,7 +394,7 @@ def recent_episodes(mediatype, tagname, limit):
|
|||
"dateadded", "lastplayed"],
|
||||
"limits": {"end": limit}
|
||||
}
|
||||
if settings('TVShowWatched') == 'false':
|
||||
if utils.settings('TVShowWatched') == 'false':
|
||||
params['filter'] = {
|
||||
'operator': "lessthan",
|
||||
'field': "playcount",
|
||||
|
@ -444,9 +437,9 @@ def get_video_files(plex_id, params):
|
|||
LOG.info('No Plex ID found, abort getting Extras')
|
||||
return xbmcplugin.endOfDirectory(HANDLE)
|
||||
|
||||
item = GetPlexMetadata(plex_id)
|
||||
item = PF.GetPlexMetadata(plex_id)
|
||||
try:
|
||||
path = item[0][0][0].attrib['file']
|
||||
path = utils.try_decode(item[0][0][0].attrib['file'])
|
||||
except (TypeError, IndexError, AttributeError, KeyError):
|
||||
LOG.error('Could not get file path for item %s', plex_id)
|
||||
return xbmcplugin.endOfDirectory(HANDLE)
|
||||
|
@ -458,18 +451,19 @@ def get_video_files(plex_id, params):
|
|||
elif '\\' in path:
|
||||
path = path.replace('\\', '\\\\')
|
||||
# Directory only, get rid of filename
|
||||
path = path.replace(basename(path), '')
|
||||
if exists_dir(path):
|
||||
for root, dirs, files in walk(path):
|
||||
path = path.replace(path_ops.path.basename(path), '')
|
||||
if path_ops.exists(path):
|
||||
for root, dirs, files in path_ops.walk(path):
|
||||
for directory in dirs:
|
||||
item_path = try_encode(join(root, directory))
|
||||
item_path = utils.try_encode(path_ops.path.join(root,
|
||||
directory))
|
||||
listitem = ListItem(item_path, path=item_path)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=item_path,
|
||||
listitem=listitem,
|
||||
isFolder=True)
|
||||
for file in files:
|
||||
item_path = try_encode(join(root, file))
|
||||
item_path = utils.try_encode(path_ops.path.join(root, file))
|
||||
listitem = ListItem(item_path, path=item_path)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=file,
|
||||
|
@ -480,7 +474,7 @@ def get_video_files(plex_id, params):
|
|||
xbmcplugin.endOfDirectory(HANDLE)
|
||||
|
||||
|
||||
@catch_exceptions(warnuser=False)
|
||||
@utils.catch_exceptions(warnuser=False)
|
||||
def extra_fanart(plex_id, plex_path):
|
||||
"""
|
||||
Get extrafanart for listitem
|
||||
|
@ -497,12 +491,12 @@ def extra_fanart(plex_id, plex_path):
|
|||
|
||||
# We need to store the images locally for this to work
|
||||
# because of the caching system in xbmc
|
||||
fanart_dir = try_decode(translatePath(
|
||||
"special://thumbnails/plex/%s/" % plex_id))
|
||||
if not exists_dir(fanart_dir):
|
||||
fanart_dir = path_ops.translate_path("special://thumbnails/plex/%s/"
|
||||
% plex_id)
|
||||
if not path_ops.exists(fanart_dir):
|
||||
# Download the images to the cache directory
|
||||
makedirs(fanart_dir)
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
path_ops.makedirs(fanart_dir)
|
||||
xml = PF.GetPlexMetadata(plex_id)
|
||||
if xml is None:
|
||||
LOG.error('Could not download metadata for %s', plex_id)
|
||||
return xbmcplugin.endOfDirectory(HANDLE)
|
||||
|
@ -511,19 +505,23 @@ def extra_fanart(plex_id, plex_path):
|
|||
backdrops = api.artwork()['Backdrop']
|
||||
for count, backdrop in enumerate(backdrops):
|
||||
# Same ordering as in artwork
|
||||
art_file = try_encode(join(fanart_dir, "fanart%.3d.jpg" % count))
|
||||
art_file = utils.try_encode(path_ops.path.join(
|
||||
fanart_dir, "fanart%.3d.jpg" % count))
|
||||
listitem = ListItem("%.3d" % count, path=art_file)
|
||||
xbmcplugin.addDirectoryItem(
|
||||
handle=HANDLE,
|
||||
url=art_file,
|
||||
listitem=listitem)
|
||||
copyfile(backdrop, try_decode(art_file))
|
||||
path_ops.copyfile(backdrop, utils.try_decode(art_file))
|
||||
else:
|
||||
LOG.info("Found cached backdrop.")
|
||||
# Use existing cached images
|
||||
for root, _, files in walk(fanart_dir):
|
||||
fanart_dir = utils.try_decode(fanart_dir)
|
||||
for root, _, files in path_ops.walk(fanart_dir):
|
||||
root = utils.decode_path(root)
|
||||
for file in files:
|
||||
art_file = try_encode(join(root, file))
|
||||
file = utils.decode_path(file)
|
||||
art_file = utils.try_encode(path_ops.path.join(root, file))
|
||||
listitem = ListItem(file, path=art_file)
|
||||
xbmcplugin.addDirectoryItem(handle=HANDLE,
|
||||
url=art_file,
|
||||
|
@ -541,13 +539,13 @@ def on_deck_episodes(viewid, tagname, limit):
|
|||
limit: Max. number of items to retrieve, e.g. 50
|
||||
"""
|
||||
xbmcplugin.setContent(HANDLE, 'episodes')
|
||||
append_show_title = settings('OnDeckTvAppendShow') == 'true'
|
||||
append_sxxexx = settings('OnDeckTvAppendSeason') == 'true'
|
||||
if settings('OnDeckTVextended') == 'false':
|
||||
append_show_title = utils.settings('OnDeckTvAppendShow') == 'true'
|
||||
append_sxxexx = utils.settings('OnDeckTvAppendSeason') == 'true'
|
||||
if utils.settings('OnDeckTVextended') == 'false':
|
||||
# Chances are that this view is used on Kodi startup
|
||||
# Wait till we've connected to a PMS. At most 30s
|
||||
counter = 0
|
||||
while window('plex_authenticated') != 'true':
|
||||
while utils.window('plex_authenticated') != 'true':
|
||||
counter += 1
|
||||
if counter == 300:
|
||||
LOG.error('Aborting On Deck view, we were not authenticated '
|
||||
|
@ -555,13 +553,12 @@ def on_deck_episodes(viewid, tagname, limit):
|
|||
xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
return
|
||||
sleep(100)
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'{server}/library/sections/%s/onDeck' % viewid)
|
||||
xml = DU().downloadUrl('{server}/library/sections/%s/onDeck' % viewid)
|
||||
if xml in (None, 401):
|
||||
LOG.error('Could not download PMS xml for view %s', viewid)
|
||||
xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
return
|
||||
direct_paths = settings('useDirectPaths') == '1'
|
||||
direct_paths = utils.settings('useDirectPaths') == '1'
|
||||
counter = 0
|
||||
for item in xml:
|
||||
api = API(item)
|
||||
|
@ -580,7 +577,7 @@ def on_deck_episodes(viewid, tagname, limit):
|
|||
break
|
||||
xbmcplugin.endOfDirectory(
|
||||
handle=HANDLE,
|
||||
cacheToDisc=settings('enableTextureCache') == 'true')
|
||||
cacheToDisc=utils.settings('enableTextureCache') == 'true')
|
||||
return
|
||||
|
||||
# if the addon is called with nextup parameter,
|
||||
|
@ -610,7 +607,7 @@ def on_deck_episodes(viewid, tagname, limit):
|
|||
"dateadded", "lastplayed"
|
||||
],
|
||||
}
|
||||
if settings('ignoreSpecialsNextEpisodes') == "true":
|
||||
if utils.settings('ignoreSpecialsNextEpisodes') == "true":
|
||||
params['filter'] = {
|
||||
'and': [
|
||||
{'operator': "lessthan", 'field': "playcount", 'value': "1"},
|
||||
|
@ -663,37 +660,34 @@ def watchlater():
|
|||
"""
|
||||
Listing for plex.tv Watch Later section (if signed in to plex.tv)
|
||||
"""
|
||||
if window('plex_token') == '':
|
||||
if utils.window('plex_token') == '':
|
||||
LOG.error('No watch later - not signed in to plex.tv')
|
||||
return xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
if window('plex_restricteduser') == 'true':
|
||||
if utils.window('plex_restricteduser') == 'true':
|
||||
LOG.error('No watch later - restricted user')
|
||||
return xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
|
||||
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||
'https://plex.tv/pms/playlists/queue/all',
|
||||
authenticate=False,
|
||||
headerOptions={'X-Plex-Token': window('plex_token')})
|
||||
xml = DU().downloadUrl('https://plex.tv/pms/playlists/queue/all',
|
||||
authenticate=False,
|
||||
headerOptions={'X-Plex-Token': utils.window('plex_token')})
|
||||
if xml in (None, 401):
|
||||
LOG.error('Could not download watch later list from plex.tv')
|
||||
return xbmcplugin.endOfDirectory(HANDLE, False)
|
||||
|
||||
LOG.info('Displaying watch later plex.tv items')
|
||||
xbmcplugin.setContent(HANDLE, 'movies')
|
||||
direct_paths = settings('useDirectPaths') == '1'
|
||||
direct_paths = utils.settings('useDirectPaths') == '1'
|
||||
for item in xml:
|
||||
__build_item(item, direct_paths)
|
||||
|
||||
xbmcplugin.endOfDirectory(
|
||||
handle=HANDLE,
|
||||
cacheToDisc=settings('enableTextureCache') == 'true')
|
||||
cacheToDisc=utils.settings('enableTextureCache') == 'true')
|
||||
|
||||
|
||||
def channels():
|
||||
"""
|
||||
Listing for Plex Channels
|
||||
"""
|
||||
xml = downloadutils.DownloadUtils().downloadUrl('{server}/channels/all')
|
||||
xml = DU().downloadUrl('{server}/channels/all')
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (ValueError, AttributeError, IndexError, TypeError):
|
||||
|
@ -708,7 +702,7 @@ def channels():
|
|||
__build_folder(item)
|
||||
xbmcplugin.endOfDirectory(
|
||||
handle=HANDLE,
|
||||
cacheToDisc=settings('enableTextureCache') == 'true')
|
||||
cacheToDisc=utils.settings('enableTextureCache') == 'true')
|
||||
|
||||
|
||||
def browse_plex(key=None, plex_section_id=None):
|
||||
|
@ -717,9 +711,9 @@ def browse_plex(key=None, plex_section_id=None):
|
|||
be used directly for PMS url {server}<key>) or the plex_section_id
|
||||
"""
|
||||
if key:
|
||||
xml = downloadutils.DownloadUtils().downloadUrl('{server}%s' % key)
|
||||
xml = DU().downloadUrl('{server}%s' % key)
|
||||
else:
|
||||
xml = GetPlexSectionResults(plex_section_id)
|
||||
xml = PF.GetPlexSectionResults(plex_section_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (ValueError, AttributeError, IndexError, TypeError):
|
||||
|
@ -735,7 +729,7 @@ def browse_plex(key=None, plex_section_id=None):
|
|||
artists = False
|
||||
albums = False
|
||||
musicvideos = False
|
||||
direct_paths = settings('useDirectPaths') == '1'
|
||||
direct_paths = utils.settings('useDirectPaths') == '1'
|
||||
for item in xml:
|
||||
if item.tag == 'Directory':
|
||||
__build_folder(item, plex_section_id=plex_section_id)
|
||||
|
@ -802,7 +796,7 @@ def browse_plex(key=None, plex_section_id=None):
|
|||
|
||||
xbmcplugin.endOfDirectory(
|
||||
handle=HANDLE,
|
||||
cacheToDisc=settings('enableTextureCache') == 'true')
|
||||
cacheToDisc=utils.settings('enableTextureCache') == 'true')
|
||||
|
||||
|
||||
def __build_folder(xml_element, plex_section_id=None):
|
||||
|
@ -854,7 +848,7 @@ def extras(plex_id):
|
|||
Lists all extras for plex_id
|
||||
"""
|
||||
xbmcplugin.setContent(HANDLE, 'movies')
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
xml = PF.GetPlexMetadata(plex_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, IndexError, KeyError):
|
||||
|
@ -874,41 +868,47 @@ def create_new_pms():
|
|||
Opens dialogs for the user the plug in the PMS details
|
||||
"""
|
||||
# "Enter your Plex Media Server's IP or URL. Examples are:"
|
||||
dialog('ok', lang(29999), lang(39215), '192.168.1.2', 'plex.myServer.org')
|
||||
address = dialog('input', "Enter PMS IP or URL")
|
||||
utils.dialog('ok',
|
||||
utils.lang(29999),
|
||||
utils.lang(39215),
|
||||
'192.168.1.2',
|
||||
'plex.myServer.org')
|
||||
address = utils.dialog('input', "Enter PMS IP or URL")
|
||||
if address == '':
|
||||
return
|
||||
port = dialog('input', "Enter PMS port", '32400', type='{numeric}')
|
||||
port = utils.dialog('input', "Enter PMS port", '32400', type='{numeric}')
|
||||
if port == '':
|
||||
return
|
||||
url = '%s:%s' % (address, port)
|
||||
# "Does your Plex Media Server support SSL connections?
|
||||
# (https instead of http)"
|
||||
https = dialog('yesno', lang(29999), lang(39217))
|
||||
https = utils.dialog('yesno', utils.lang(29999), utils.lang(39217))
|
||||
if https:
|
||||
url = 'https://%s' % url
|
||||
else:
|
||||
url = 'http://%s' % url
|
||||
https = 'true' if https else 'false'
|
||||
machine_identifier = GetMachineIdentifier(url)
|
||||
machine_identifier = PF.GetMachineIdentifier(url)
|
||||
if machine_identifier is None:
|
||||
# "Error contacting url
|
||||
# Abort (Yes) or save address anyway (No)"
|
||||
if dialog('yesno',
|
||||
lang(29999),
|
||||
'%s %s. %s' % (lang(39218), url, lang(39219))):
|
||||
if utils.dialog('yesno',
|
||||
utils.lang(29999),
|
||||
'%s %s. %s' % (utils.lang(39218),
|
||||
url,
|
||||
utils.lang(39219))):
|
||||
return
|
||||
else:
|
||||
settings('plex_machineIdentifier', '')
|
||||
utils.settings('plex_machineIdentifier', '')
|
||||
else:
|
||||
settings('plex_machineIdentifier', machine_identifier)
|
||||
utils.settings('plex_machineIdentifier', machine_identifier)
|
||||
LOG.info('Set new PMS to https %s, address %s, port %s, machineId %s',
|
||||
https, address, port, machine_identifier)
|
||||
settings('https', value=https)
|
||||
settings('ipaddress', value=address)
|
||||
settings('port', value=port)
|
||||
utils.settings('https', value=https)
|
||||
utils.settings('ipaddress', value=address)
|
||||
utils.settings('port', value=port)
|
||||
# Chances are this is a local PMS, so disable SSL certificate check
|
||||
settings('sslverify', value='false')
|
||||
utils.settings('sslverify', value='false')
|
||||
|
||||
# Sign out to trigger new login
|
||||
if _log_out():
|
||||
|
@ -923,9 +923,9 @@ def _log_in():
|
|||
SUSPEND_LIBRARY_THREAD is set to False in service.py if user was signed
|
||||
out!
|
||||
"""
|
||||
plex_command('RUN_LIB_SCAN', 'full')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'full')
|
||||
# Restart user client
|
||||
plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
utils.plex_command('SUSPEND_USER_CLIENT', 'False')
|
||||
|
||||
|
||||
def _log_out():
|
||||
|
@ -935,22 +935,22 @@ def _log_out():
|
|||
Returns True if successfully signed out, False otherwise
|
||||
"""
|
||||
# Resetting, please wait
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
lang(39207),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(39207),
|
||||
icon='{plex}',
|
||||
time=3000,
|
||||
sound=False)
|
||||
# Pause library sync thread
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'True')
|
||||
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'True')
|
||||
# Wait max for 10 seconds for all lib scans to shutdown
|
||||
counter = 0
|
||||
while window('plex_dbScan') == 'true':
|
||||
while utils.window('plex_dbScan') == 'true':
|
||||
if counter > 200:
|
||||
# Failed to reset PMS and plex.tv connects. Try to restart Kodi.
|
||||
dialog('ok', lang(29999), lang(39208))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39208))
|
||||
# Resuming threads, just in case
|
||||
plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
utils.plex_command('SUSPEND_LIBRARY_THREAD', 'False')
|
||||
LOG.error("Could not stop library sync, aborting")
|
||||
return False
|
||||
counter += 1
|
||||
|
@ -959,17 +959,17 @@ def _log_out():
|
|||
|
||||
counter = 0
|
||||
# Log out currently signed in user:
|
||||
window('plex_serverStatus', value='401')
|
||||
plex_command('PMS_STATUS', '401')
|
||||
utils.window('plex_serverStatus', value='401')
|
||||
utils.plex_command('PMS_STATUS', '401')
|
||||
# Above method needs to have run its course! Hence wait
|
||||
while window('plex_serverStatus') == "401":
|
||||
while utils.window('plex_serverStatus') == "401":
|
||||
if counter > 100:
|
||||
# 'Failed to reset PKC. Try to restart Kodi.'
|
||||
dialog('ok', lang(29999), lang(39208))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39208))
|
||||
LOG.error("Could not sign out user, aborting")
|
||||
return False
|
||||
counter += 1
|
||||
sleep(50)
|
||||
# Suspend the user client during procedure
|
||||
plex_command('SUSPEND_USER_CLIENT', 'True')
|
||||
utils.plex_command('SUSPEND_USER_CLIENT', 'True')
|
||||
return True
|
||||
|
|
|
@ -6,26 +6,29 @@ import xml.etree.ElementTree as etree
|
|||
|
||||
from xbmc import executebuiltin, translatePath
|
||||
|
||||
from utils import settings, window, language as lang, try_decode, dialog, \
|
||||
XmlKodiSetting, reboot_kodi
|
||||
from migration import check_migration
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from userclient import UserClient
|
||||
from clientinfo import getDeviceId
|
||||
import PlexFunctions as PF
|
||||
import plex_tv
|
||||
import json_rpc as js
|
||||
import playqueue as PQ
|
||||
from videonodes import VideoNodes
|
||||
import state
|
||||
import variables as v
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import migration
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import videonodes
|
||||
from . import userclient
|
||||
from . import clientinfo
|
||||
from . import plex_functions as PF
|
||||
from . import plex_tv
|
||||
from . import json_rpc as js
|
||||
from . import playqueue as PQ
|
||||
from . import state
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.initialsetup')
|
||||
|
||||
###############################################################################
|
||||
|
||||
if not path_ops.exists(v.EXTERNAL_SUBTITLE_TEMP_PATH):
|
||||
path_ops.makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
|
||||
|
||||
|
||||
WINDOW_PROPERTIES = (
|
||||
"plex_online", "plex_serverStatus", "plex_shouldStop", "plex_dbScan",
|
||||
|
@ -48,27 +51,28 @@ def reload_pkc():
|
|||
reload(state)
|
||||
# Reset window props
|
||||
for prop in WINDOW_PROPERTIES:
|
||||
window(prop, clear=True)
|
||||
utils.window(prop, clear=True)
|
||||
# Clear video nodes properties
|
||||
VideoNodes().clearProperties()
|
||||
videonodes.VideoNodes().clearProperties()
|
||||
|
||||
# Initializing
|
||||
state.VERIFY_SSL_CERT = settings('sslverify') == 'true'
|
||||
state.SSL_CERT_PATH = settings('sslcert') \
|
||||
if settings('sslcert') != 'None' else None
|
||||
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
|
||||
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
|
||||
state.SYNC_DIALOG = settings('dbSyncIndicator') == 'true'
|
||||
state.ENABLE_MUSIC = settings('enableMusic') == 'true'
|
||||
state.BACKGROUND_SYNC_DISABLED = settings(
|
||||
state.VERIFY_SSL_CERT = utils.settings('sslverify') == 'true'
|
||||
state.SSL_CERT_PATH = utils.settings('sslcert') \
|
||||
if utils.settings('sslcert') != 'None' else None
|
||||
state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
|
||||
state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
|
||||
state.SYNC_DIALOG = utils.settings('dbSyncIndicator') == 'true'
|
||||
state.ENABLE_MUSIC = utils.settings('enableMusic') == 'true'
|
||||
state.BACKGROUND_SYNC_DISABLED = utils.settings(
|
||||
'enableBackgroundSync') == 'false'
|
||||
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
|
||||
settings('backgroundsync_saftyMargin'))
|
||||
state.REPLACE_SMB_PATH = settings('replaceSMB') == 'true'
|
||||
state.REMAP_PATH = settings('remapSMB') == 'true'
|
||||
state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
|
||||
state.FETCH_PMS_ITEM_NUMBER = settings('fetch_pms_item_number')
|
||||
state.FORCE_RELOAD_SKIN = settings('forceReloadSkinOnPlaybackStop') == 'true'
|
||||
utils.settings('backgroundsync_saftyMargin'))
|
||||
state.REPLACE_SMB_PATH = utils.settings('replaceSMB') == 'true'
|
||||
state.REMAP_PATH = utils.settings('remapSMB') == 'true'
|
||||
state.KODI_PLEX_TIME_OFFSET = float(utils.settings('kodiplextimeoffset'))
|
||||
state.FETCH_PMS_ITEM_NUMBER = utils.settings('fetch_pms_item_number')
|
||||
state.FORCE_RELOAD_SKIN = \
|
||||
utils.settings('forceReloadSkinOnPlaybackStop') == 'true'
|
||||
# Init some Queues()
|
||||
state.COMMAND_PIPELINE_QUEUE = Queue()
|
||||
state.COMPANION_QUEUE = Queue(maxsize=100)
|
||||
|
@ -76,9 +80,9 @@ def reload_pkc():
|
|||
set_replace_paths()
|
||||
set_webserver()
|
||||
# To detect Kodi profile switches
|
||||
window('plex_kodiProfile',
|
||||
value=try_decode(translatePath("special://profile")))
|
||||
getDeviceId()
|
||||
utils.window('plex_kodiProfile',
|
||||
value=utils.try_decode(translatePath("special://profile")))
|
||||
clientinfo.getDeviceId()
|
||||
# Initialize the PKC playqueues
|
||||
PQ.init_playqueues()
|
||||
LOG.info('Done (re-)loading PKC settings')
|
||||
|
@ -92,7 +96,7 @@ def set_replace_paths():
|
|||
for typus in v.REMAP_TYPE_FROM_PLEXTYPE.values():
|
||||
for arg in ('Org', 'New'):
|
||||
key = 'remapSMB%s%s' % (typus, arg)
|
||||
value = settings(key)
|
||||
value = utils.settings(key)
|
||||
if '://' in value:
|
||||
protocol = value.split('://', 1)[0]
|
||||
value = value.replace(protocol, protocol.lower())
|
||||
|
@ -129,8 +133,8 @@ def _write_pms_settings(url, token):
|
|||
for entry in xml:
|
||||
if entry.attrib.get('id', '') == 'allowMediaDeletion':
|
||||
value = 'true' if entry.get('value', '1') == '1' else 'false'
|
||||
settings('plex_allows_mediaDeletion', value=value)
|
||||
window('plex_allows_mediaDeletion', value=value)
|
||||
utils.settings('plex_allows_mediaDeletion', value=value)
|
||||
utils.window('plex_allows_mediaDeletion', value=value)
|
||||
|
||||
|
||||
class InitialSetup(object):
|
||||
|
@ -140,8 +144,8 @@ class InitialSetup(object):
|
|||
"""
|
||||
def __init__(self):
|
||||
LOG.debug('Entering initialsetup class')
|
||||
self.server = UserClient().get_server()
|
||||
self.serverid = settings('plex_machineIdentifier')
|
||||
self.server = userclient.UserClient().get_server()
|
||||
self.serverid = utils.settings('plex_machineIdentifier')
|
||||
# Get Plex credentials from settings file, if they exist
|
||||
plexdict = PF.GetPlexLoginFromSettings()
|
||||
self.myplexlogin = plexdict['myplexlogin'] == 'true'
|
||||
|
@ -149,7 +153,7 @@ class InitialSetup(object):
|
|||
self.plex_token = plexdict['plexToken']
|
||||
self.plexid = plexdict['plexid']
|
||||
# Token for the PMS, not plex.tv
|
||||
self.pms_token = settings('accessToken')
|
||||
self.pms_token = utils.settings('accessToken')
|
||||
if self.plex_token:
|
||||
LOG.debug('Found a plex.tv token in the settings')
|
||||
|
||||
|
@ -179,20 +183,20 @@ class InitialSetup(object):
|
|||
# HTTP Error: unauthorized. Token is no longer valid
|
||||
LOG.info('plex.tv connection returned HTTP %s', str(chk))
|
||||
# Delete token in the settings
|
||||
settings('plexToken', value='')
|
||||
settings('plexLogin', value='')
|
||||
utils.settings('plexToken', value='')
|
||||
utils.settings('plexLogin', value='')
|
||||
# Could not login, please try again
|
||||
dialog('ok', lang(29999), lang(39009))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39009))
|
||||
answer = self.plex_tv_sign_in()
|
||||
elif chk is False or chk >= 400:
|
||||
# Problems connecting to plex.tv. Network or internet issue?
|
||||
LOG.info('Problems connecting to plex.tv; connection returned '
|
||||
'HTTP %s', str(chk))
|
||||
dialog('ok', lang(29999), lang(39010))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39010))
|
||||
answer = False
|
||||
else:
|
||||
LOG.info('plex.tv connection with token successful')
|
||||
settings('plex_status', value=lang(39227))
|
||||
utils.settings('plex_status', value=utils.lang(39227))
|
||||
# Refresh the info from Plex.tv
|
||||
xml = DU().downloadUrl('https://plex.tv/users/account',
|
||||
authenticate=False,
|
||||
|
@ -202,11 +206,12 @@ class InitialSetup(object):
|
|||
except (AttributeError, KeyError):
|
||||
LOG.error('Failed to update Plex info from plex.tv')
|
||||
else:
|
||||
settings('plexLogin', value=self.plex_login)
|
||||
utils.settings('plexLogin', value=self.plex_login)
|
||||
home = 'true' if xml.attrib.get('home') == '1' else 'false'
|
||||
settings('plexhome', value=home)
|
||||
settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
|
||||
utils.settings('plexhome', value=home)
|
||||
utils.settings('plexAvatar', value=xml.attrib.get('thumb'))
|
||||
utils.settings('plexHomeSize',
|
||||
value=xml.attrib.get('homeSize', '1'))
|
||||
LOG.info('Updated Plex info from plex.tv')
|
||||
return answer
|
||||
|
||||
|
@ -233,7 +238,7 @@ class InitialSetup(object):
|
|||
LOG.warn('Could not retrieve machineIdentifier')
|
||||
answer = False
|
||||
else:
|
||||
settings('plex_machineIdentifier', value=self.serverid)
|
||||
utils.settings('plex_machineIdentifier', value=self.serverid)
|
||||
elif answer is True:
|
||||
temp_server_id = PF.GetMachineIdentifier(self.server)
|
||||
if temp_server_id != self.serverid:
|
||||
|
@ -325,7 +330,7 @@ class InitialSetup(object):
|
|||
if item.get('machineIdentifier') == self.serverid:
|
||||
server = item
|
||||
if server is None:
|
||||
name = settings('plex_servername')
|
||||
name = utils.settings('plex_servername')
|
||||
LOG.warn('The PMS you have used before with a unique '
|
||||
'machineIdentifier of %s and name %s is '
|
||||
'offline', self.serverid, name)
|
||||
|
@ -356,18 +361,18 @@ class InitialSetup(object):
|
|||
"""
|
||||
https_updated = False
|
||||
# Searching for PMS
|
||||
dialog('notification',
|
||||
heading='{plex}',
|
||||
message=lang(30001),
|
||||
icon='{plex}',
|
||||
time=5000)
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=utils.lang(30001),
|
||||
icon='{plex}',
|
||||
time=5000)
|
||||
while True:
|
||||
if https_updated is False:
|
||||
serverlist = PF.discover_pms(self.plex_token)
|
||||
# Exit if no servers found
|
||||
if not serverlist:
|
||||
LOG.warn('No plex media servers found!')
|
||||
dialog('ok', lang(29999), lang(39011))
|
||||
utils.dialog('ok', utils.lang(29999), utils.lang(39011))
|
||||
return
|
||||
# Get a nicer list
|
||||
dialoglist = []
|
||||
|
@ -375,10 +380,10 @@ class InitialSetup(object):
|
|||
if server['local']:
|
||||
# server is in the same network as client.
|
||||
# Add"local"
|
||||
msg = lang(39022)
|
||||
msg = utils.lang(39022)
|
||||
else:
|
||||
# Add 'remote'
|
||||
msg = lang(39054)
|
||||
msg = utils.lang(39054)
|
||||
if server.get('ownername'):
|
||||
# Display username if its not our PMS
|
||||
dialoglist.append('%s (%s, %s)'
|
||||
|
@ -389,7 +394,7 @@ class InitialSetup(object):
|
|||
dialoglist.append('%s (%s)'
|
||||
% (server['name'], msg))
|
||||
# Let user pick server from a list
|
||||
resp = dialog('select', lang(39012), dialoglist)
|
||||
resp = utils.dialog('select', utils.lang(39012), dialoglist)
|
||||
if resp == -1:
|
||||
# User cancelled
|
||||
return
|
||||
|
@ -406,17 +411,19 @@ class InitialSetup(object):
|
|||
LOG.warn('Not yet authorized for Plex server %s',
|
||||
server['name'])
|
||||
# Please sign in to plex.tv
|
||||
dialog('ok',
|
||||
lang(29999),
|
||||
lang(39013) + server['name'],
|
||||
lang(39014))
|
||||
utils.dialog('ok',
|
||||
utils.lang(29999),
|
||||
utils.lang(39013) + server['name'],
|
||||
utils.lang(39014))
|
||||
if self.plex_tv_sign_in() is False:
|
||||
# Exit while loop if user cancels
|
||||
return
|
||||
# Problems connecting
|
||||
elif chk >= 400 or chk is False:
|
||||
# Problems connecting to server. Pick another server?
|
||||
answ = dialog('yesno', lang(29999), lang(39015))
|
||||
answ = utils.dialog('yesno',
|
||||
utils.lang(29999),
|
||||
utils.lang(39015))
|
||||
# Exit while loop if user chooses No
|
||||
if not answ:
|
||||
return
|
||||
|
@ -429,30 +436,31 @@ class InitialSetup(object):
|
|||
"""
|
||||
Saves server to file settings
|
||||
"""
|
||||
settings('plex_machineIdentifier', server['machineIdentifier'])
|
||||
settings('plex_servername', server['name'])
|
||||
settings('plex_serverowned', 'true' if server['owned'] else 'false')
|
||||
utils.settings('plex_machineIdentifier', server['machineIdentifier'])
|
||||
utils.settings('plex_servername', server['name'])
|
||||
utils.settings('plex_serverowned',
|
||||
'true' if server['owned'] else 'false')
|
||||
# Careful to distinguish local from remote PMS
|
||||
if server['local']:
|
||||
scheme = server['scheme']
|
||||
settings('ipaddress', server['ip'])
|
||||
settings('port', server['port'])
|
||||
utils.settings('ipaddress', server['ip'])
|
||||
utils.settings('port', server['port'])
|
||||
LOG.debug("Setting SSL verify to false, because server is "
|
||||
"local")
|
||||
settings('sslverify', 'false')
|
||||
utils.settings('sslverify', 'false')
|
||||
else:
|
||||
baseURL = server['baseURL'].split(':')
|
||||
scheme = baseURL[0]
|
||||
settings('ipaddress', baseURL[1].replace('//', ''))
|
||||
settings('port', baseURL[2])
|
||||
utils.settings('ipaddress', baseURL[1].replace('//', ''))
|
||||
utils.settings('port', baseURL[2])
|
||||
LOG.debug("Setting SSL verify to true, because server is not "
|
||||
"local")
|
||||
settings('sslverify', 'true')
|
||||
utils.settings('sslverify', 'true')
|
||||
|
||||
if scheme == 'https':
|
||||
settings('https', 'true')
|
||||
utils.settings('https', 'true')
|
||||
else:
|
||||
settings('https', 'false')
|
||||
utils.settings('https', 'false')
|
||||
# And finally do some logging
|
||||
LOG.debug("Writing to Kodi user settings file")
|
||||
LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
|
||||
|
@ -468,9 +476,9 @@ class InitialSetup(object):
|
|||
"""
|
||||
LOG.info("Initial setup called.")
|
||||
try:
|
||||
with XmlKodiSetting('advancedsettings.xml',
|
||||
force_create=True,
|
||||
top_element='advancedsettings') as xml:
|
||||
with utils.XmlKodiSetting('advancedsettings.xml',
|
||||
force_create=True,
|
||||
top_element='advancedsettings') as xml:
|
||||
# Get current Kodi video cache setting
|
||||
cache = xml.get_setting(['cache', 'memorysize'])
|
||||
# Disable foreground "Loading media information from files"
|
||||
|
@ -493,13 +501,13 @@ class InitialSetup(object):
|
|||
# Kodi default cache if no setting is set
|
||||
cache = str(cache.text) if cache is not None else '20971520'
|
||||
LOG.info('Current Kodi video memory cache in bytes: %s', cache)
|
||||
settings('kodi_video_cache', value=cache)
|
||||
utils.settings('kodi_video_cache', value=cache)
|
||||
|
||||
# Hack to make PKC Kodi master lock compatible
|
||||
try:
|
||||
with XmlKodiSetting('sources.xml',
|
||||
force_create=True,
|
||||
top_element='sources') as xml:
|
||||
with utils.XmlKodiSetting('sources.xml',
|
||||
force_create=True,
|
||||
top_element='sources') as xml:
|
||||
root = xml.set_setting(['video'])
|
||||
count = 2
|
||||
for source in root.findall('.//path'):
|
||||
|
@ -526,21 +534,21 @@ class InitialSetup(object):
|
|||
pass
|
||||
|
||||
# Do we need to migrate stuff?
|
||||
check_migration()
|
||||
migration.check_migration()
|
||||
# Reload the server IP cause we might've deleted it during migration
|
||||
self.server = UserClient().get_server()
|
||||
self.server = userclient.UserClient().get_server()
|
||||
|
||||
# Display a warning if Kodi puts ALL movies into the queue, basically
|
||||
# breaking playback reporting for PKC
|
||||
if js.settings_getsettingvalue('videoplayer.autoplaynextitem'):
|
||||
LOG.warn('Kodi setting videoplayer.autoplaynextitem is enabled!')
|
||||
if settings('warned_setting_videoplayer.autoplaynextitem') == 'false':
|
||||
if utils.settings('warned_setting_videoplayer.autoplaynextitem') == 'false':
|
||||
# Only warn once
|
||||
settings('warned_setting_videoplayer.autoplaynextitem',
|
||||
value='true')
|
||||
utils.settings('warned_setting_videoplayer.autoplaynextitem',
|
||||
value='true')
|
||||
# Warning: Kodi setting "Play next video automatically" is
|
||||
# enabled. This could break PKC. Deactivate?
|
||||
if dialog('yesno', lang(29999), lang(30003)):
|
||||
if utils.dialog('yesno', utils.lang(29999), utils.lang(30003)):
|
||||
js.settings_setsettingvalue('videoplayer.autoplaynextitem',
|
||||
False)
|
||||
# Set any video library updates to happen in the background in order to
|
||||
|
@ -556,7 +564,7 @@ class InitialSetup(object):
|
|||
self.server, self.serverid)
|
||||
_write_pms_settings(self.server, self.pms_token)
|
||||
if reboot is True:
|
||||
reboot_kodi()
|
||||
utils.reboot_kodi()
|
||||
return
|
||||
|
||||
# If not already retrieved myplex info, optionally let user sign in
|
||||
|
@ -570,78 +578,91 @@ class InitialSetup(object):
|
|||
self.write_pms_to_settings(server)
|
||||
|
||||
# User already answered the installation questions
|
||||
if settings('InstallQuestionsAnswered') == 'true':
|
||||
if utils.settings('InstallQuestionsAnswered') == 'true':
|
||||
if reboot is True:
|
||||
reboot_kodi()
|
||||
utils.reboot_kodi()
|
||||
return
|
||||
|
||||
# Additional settings where the user needs to choose
|
||||
# Direct paths (\\NAS\mymovie.mkv) or addon (http)?
|
||||
goto_settings = False
|
||||
if dialog('yesno',
|
||||
lang(29999),
|
||||
lang(39027),
|
||||
lang(39028),
|
||||
nolabel="Addon (Default)",
|
||||
yeslabel="Native (Direct Paths)"):
|
||||
if utils.dialog('yesno',
|
||||
utils.lang(29999),
|
||||
utils.lang(39027),
|
||||
utils.lang(39028),
|
||||
nolabel="Addon (Default)",
|
||||
yeslabel="Native (Direct Paths)"):
|
||||
LOG.debug("User opted to use direct paths.")
|
||||
settings('useDirectPaths', value="1")
|
||||
utils.settings('useDirectPaths', value="1")
|
||||
state.DIRECT_PATHS = True
|
||||
# Are you on a system where you would like to replace paths
|
||||
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
|
||||
if dialog('yesno', heading=lang(29999), line1=lang(39033)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39033)):
|
||||
LOG.debug("User chose to replace paths with smb")
|
||||
else:
|
||||
settings('replaceSMB', value="false")
|
||||
utils.settings('replaceSMB', value="false")
|
||||
|
||||
# complete replace all original Plex library paths with custom SMB
|
||||
if dialog('yesno', heading=lang(29999), line1=lang(39043)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39043)):
|
||||
LOG.debug("User chose custom smb paths")
|
||||
settings('remapSMB', value="true")
|
||||
utils.settings('remapSMB', value="true")
|
||||
# Please enter your custom smb paths in the settings under
|
||||
# "Sync Options" and then restart Kodi
|
||||
dialog('ok', heading=lang(29999), line1=lang(39044))
|
||||
utils.dialog('ok',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39044))
|
||||
goto_settings = True
|
||||
|
||||
# Go to network credentials?
|
||||
if dialog('yesno',
|
||||
heading=lang(29999),
|
||||
line1=lang(39029),
|
||||
line2=lang(39030)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39029),
|
||||
line2=utils.lang(39030)):
|
||||
LOG.debug("Presenting network credentials dialog.")
|
||||
from utils import passwords_xml
|
||||
passwords_xml()
|
||||
# Disable Plex music?
|
||||
if dialog('yesno', heading=lang(29999), line1=lang(39016)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39016)):
|
||||
LOG.debug("User opted to disable Plex music library.")
|
||||
settings('enableMusic', value="false")
|
||||
utils.settings('enableMusic', value="false")
|
||||
|
||||
# Download additional art from FanArtTV
|
||||
if dialog('yesno', heading=lang(29999), line1=lang(39061)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39061)):
|
||||
LOG.debug("User opted to use FanArtTV")
|
||||
settings('FanartTV', value="true")
|
||||
utils.settings('FanartTV', value="true")
|
||||
# Do you want to replace your custom user ratings with an indicator of
|
||||
# how many versions of a media item you posses?
|
||||
if dialog('yesno', heading=lang(29999), line1=lang(39718)):
|
||||
if utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39718)):
|
||||
LOG.debug("User opted to replace user ratings with version number")
|
||||
settings('indicate_media_versions', value="true")
|
||||
utils.settings('indicate_media_versions', value="true")
|
||||
|
||||
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and
|
||||
# "Parents Movies", be sure to check https://goo.gl/JFtQV9
|
||||
# dialog.ok(heading=lang(29999), line1=lang(39076))
|
||||
# dialog.ok(heading=utils.lang(29999), line1=utils.lang(39076))
|
||||
|
||||
# Need to tell about our image source for collections: themoviedb.org
|
||||
# dialog.ok(heading=lang(29999), line1=lang(39717))
|
||||
# dialog.ok(heading=utils.lang(29999), line1=utils.lang(39717))
|
||||
# Make sure that we only ask these questions upon first installation
|
||||
settings('InstallQuestionsAnswered', value='true')
|
||||
utils.settings('InstallQuestionsAnswered', value='true')
|
||||
|
||||
if goto_settings is False:
|
||||
# Open Settings page now? You will need to restart!
|
||||
goto_settings = dialog('yesno',
|
||||
heading=lang(29999),
|
||||
line1=lang(39017))
|
||||
goto_settings = utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39017))
|
||||
if goto_settings:
|
||||
state.PMS_STATUS = 'Stop'
|
||||
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
executebuiltin(
|
||||
'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
|
||||
elif reboot is True:
|
||||
reboot_kodi()
|
||||
utils.reboot_kodi()
|
||||
|
|
|
@ -4,18 +4,17 @@ from logging import getLogger
|
|||
from ntpath import dirname
|
||||
from datetime import datetime
|
||||
|
||||
from artwork import Artwork
|
||||
from utils import window, kodi_sql, catch_exceptions
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
|
||||
from PlexAPI import API
|
||||
from PlexFunctions import GetPlexMetadata
|
||||
import variables as v
|
||||
import state
|
||||
from . import artwork
|
||||
from . import utils
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from .plex_api import API
|
||||
from . import plex_functions as PF
|
||||
from . import variables as v
|
||||
from . import state
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.itemtypes')
|
||||
|
||||
# Note: always use same order of URL arguments, NOT urlencode:
|
||||
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play
|
||||
|
@ -32,8 +31,8 @@ class Items(object):
|
|||
kodiType: optional argument; e.g. 'video' or 'music'
|
||||
"""
|
||||
def __init__(self):
|
||||
self.artwork = Artwork()
|
||||
self.server = window('pms_server')
|
||||
self.artwork = artwork.Artwork()
|
||||
self.server = utils.window('pms_server')
|
||||
self.plexconn = None
|
||||
self.plexcursor = None
|
||||
self.kodiconn = None
|
||||
|
@ -45,9 +44,9 @@ class Items(object):
|
|||
"""
|
||||
Open DB connections and cursors
|
||||
"""
|
||||
self.plexconn = kodi_sql('plex')
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
self.plexcursor = self.plexconn.cursor()
|
||||
self.kodiconn = kodi_sql('video')
|
||||
self.kodiconn = utils.kodi_sql('video')
|
||||
self.kodicursor = self.kodiconn.cursor()
|
||||
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
|
||||
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
|
||||
|
@ -63,7 +62,7 @@ class Items(object):
|
|||
self.kodiconn.close()
|
||||
return self
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def getfanart(self, plex_id, refresh=False):
|
||||
"""
|
||||
Tries to get additional fanart for movies (+sets) and TV shows.
|
||||
|
@ -95,7 +94,7 @@ class Items(object):
|
|||
LOG.debug('Already got all fanart for Plex id %s', plex_id)
|
||||
return True
|
||||
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
xml = PF.GetPlexMetadata(plex_id)
|
||||
if xml is None:
|
||||
# Did not receive a valid XML - skip that item for now
|
||||
LOG.error("Could not get metadata for %s. Skipping that item "
|
||||
|
@ -183,7 +182,7 @@ class Movies(Items):
|
|||
"""
|
||||
Used for plex library-type movies
|
||||
"""
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_update(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Process single movie
|
||||
|
@ -513,7 +512,7 @@ class TVShows(Items):
|
|||
"""
|
||||
For Plex library-type TV shows
|
||||
"""
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_update(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Process a single show
|
||||
|
@ -722,7 +721,7 @@ class TVShows(Items):
|
|||
tags.extend(collections)
|
||||
self.kodi_db.modify_tags(showid, v.KODI_TYPE_SHOW, tags)
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_updateSeason(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Process a single season of a certain tv show
|
||||
|
@ -768,7 +767,7 @@ class TVShows(Items):
|
|||
view_id=viewid,
|
||||
checksum=checksum)
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_updateEpisode(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Process single episode
|
||||
|
@ -998,7 +997,7 @@ class TVShows(Items):
|
|||
runtime,
|
||||
playcount,
|
||||
dateplayed,
|
||||
None) # Do send None, we check here
|
||||
None) # Do send None, we check here
|
||||
if not state.DIRECT_PATHS:
|
||||
# need to set a SECOND file entry for a path without plex show id
|
||||
filename = api.file_name(force_first_media=True)
|
||||
|
@ -1014,9 +1013,9 @@ class TVShows(Items):
|
|||
runtime,
|
||||
playcount,
|
||||
dateplayed,
|
||||
None) # Do send None - 2nd entry
|
||||
None) # Do send None - 2nd entry
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def remove(self, plex_id):
|
||||
"""
|
||||
Remove the entire TV shows object (show, season or episode) including
|
||||
|
@ -1139,16 +1138,16 @@ class Music(Items):
|
|||
OVERWRITE this method, because we need to open another DB.
|
||||
Open DB connections and cursors
|
||||
"""
|
||||
self.plexconn = kodi_sql('plex')
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
self.plexcursor = self.plexconn.cursor()
|
||||
# Here it is, not 'video' but 'music'
|
||||
self.kodiconn = kodi_sql('music')
|
||||
self.kodiconn = utils.kodi_sql('music')
|
||||
self.kodicursor = self.kodiconn.cursor()
|
||||
self.plex_db = plexdb.Plex_DB_Functions(self.plexcursor)
|
||||
self.kodi_db = kodidb.KodiDBMethods(self.kodicursor)
|
||||
return self
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_updateArtist(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Adds a single artist
|
||||
|
@ -1236,7 +1235,7 @@ class Music(Items):
|
|||
v.KODI_TYPE_ARTIST,
|
||||
kodicursor)
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_updateAlbum(self, item, viewtag=None, viewid=None, children=None,
|
||||
scan_children=True):
|
||||
"""
|
||||
|
@ -1362,7 +1361,7 @@ class Music(Items):
|
|||
artist_id = plex_db.getItem_byId(parent_id)[0]
|
||||
except TypeError:
|
||||
LOG.info('Artist %s does not yet exist in Plex DB', parent_id)
|
||||
artist = GetPlexMetadata(parent_id)
|
||||
artist = PF.GetPlexMetadata(parent_id)
|
||||
try:
|
||||
artist[0].attrib
|
||||
except (TypeError, IndexError, AttributeError):
|
||||
|
@ -1393,13 +1392,16 @@ class Music(Items):
|
|||
self.genres,
|
||||
v.KODI_TYPE_ALBUM)
|
||||
# Update artwork
|
||||
artwork.modify_artwork(artworks, album_id, v.KODI_TYPE_ALBUM, kodicursor)
|
||||
artwork.modify_artwork(artworks,
|
||||
album_id,
|
||||
v.KODI_TYPE_ALBUM,
|
||||
kodicursor)
|
||||
# Add all children - all tracks
|
||||
if scan_children:
|
||||
for child in children:
|
||||
self.add_updateSong(child, viewtag, viewid)
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def add_updateSong(self, item, viewtag=None, viewid=None):
|
||||
"""
|
||||
Process single song
|
||||
|
@ -1459,7 +1461,7 @@ class Music(Items):
|
|||
if disc == 1:
|
||||
track = tracknumber
|
||||
else:
|
||||
track = disc*2**16 + tracknumber
|
||||
track = disc * 2 ** 16 + tracknumber
|
||||
year = api.year()
|
||||
_, duration = api.resume_runtime()
|
||||
rating = userdata['UserRating']
|
||||
|
@ -1573,7 +1575,7 @@ class Music(Items):
|
|||
# No album found. Let's create it
|
||||
LOG.info("Album database entry missing.")
|
||||
plex_album_id = api.parent_plex_id()
|
||||
album = GetPlexMetadata(plex_album_id)
|
||||
album = PF.GetPlexMetadata(plex_album_id)
|
||||
if album is None or album == 401:
|
||||
LOG.error('Could not download album, abort')
|
||||
return
|
||||
|
@ -1664,7 +1666,8 @@ class Music(Items):
|
|||
idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
'''
|
||||
kodicursor.execute(query, (songid, albumid, track, title, duration))
|
||||
kodicursor.execute(query,
|
||||
(songid, albumid, track, title, duration))
|
||||
# Link song to artists
|
||||
artist_loop = [{
|
||||
'Name': api.grandparent_title(),
|
||||
|
@ -1680,7 +1683,7 @@ class Music(Items):
|
|||
artistid = artist_edb[0]
|
||||
except TypeError:
|
||||
# Artist is missing from plex database, add it.
|
||||
artist_xml = GetPlexMetadata(artist_eid)
|
||||
artist_xml = PF.GetPlexMetadata(artist_eid)
|
||||
if artist_xml is None or artist_xml == 401:
|
||||
LOG.error('Error getting artist, abort')
|
||||
return
|
||||
|
@ -1718,9 +1721,12 @@ class Music(Items):
|
|||
artwork.modify_artwork(artworks, songid, v.KODI_TYPE_SONG, kodicursor)
|
||||
if item.get('parentKey') is None:
|
||||
# Update album artwork
|
||||
artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor)
|
||||
artwork.modify_artwork(artworks,
|
||||
albumid,
|
||||
v.KODI_TYPE_ALBUM,
|
||||
kodicursor)
|
||||
|
||||
@catch_exceptions(warnuser=True)
|
||||
@utils.catch_exceptions(warnuser=True)
|
||||
def remove(self, plex_id):
|
||||
"""
|
||||
Completely remove the item with plex_id from the Kodi and Plex DBs.
|
||||
|
@ -1768,7 +1774,8 @@ class Music(Items):
|
|||
##### IF ARTIST #####
|
||||
elif kodi_type == v.KODI_TYPE_ARTIST:
|
||||
# Delete songs, album, artist
|
||||
albums = self.plex_db.getItem_byParentId(kodi_id, v.KODI_TYPE_ALBUM)
|
||||
albums = self.plex_db.getItem_byParentId(kodi_id,
|
||||
v.KODI_TYPE_ALBUM)
|
||||
for album in albums:
|
||||
songs = self.plex_db.getItem_byParentId(album[1],
|
||||
v.KODI_TYPE_SONG)
|
||||
|
|
|
@ -3,9 +3,10 @@ Collection of functions using the Kodi JSON RPC interface.
|
|||
See http://kodi.wiki/view/JSON-RPC_API
|
||||
"""
|
||||
from json import loads, dumps
|
||||
from utils import millis_to_kodi_time
|
||||
from xbmc import executeJSONRPC
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
class JsonRPC(object):
|
||||
"""
|
||||
|
@ -152,7 +153,7 @@ def seek_to(offset):
|
|||
for playerid in get_player_ids():
|
||||
JsonRPC("Player.Seek").execute(
|
||||
{"playerid": playerid,
|
||||
"value": millis_to_kodi_time(offset)})
|
||||
"value": utils.millis_to_kodi_time(offset)})
|
||||
|
||||
|
||||
def smallforward():
|
||||
|
|
|
@ -7,14 +7,14 @@ from logging import getLogger
|
|||
from ntpath import dirname
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
import artwork
|
||||
from utils import kodi_sql, try_decode, unix_timestamp, unix_date_to_kodi
|
||||
import variables as v
|
||||
import state
|
||||
from . import artwork
|
||||
from . import utils
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.kodidb_functions')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -35,7 +35,7 @@ class GetKodiDB(object):
|
|||
self.db_type = db_type
|
||||
|
||||
def __enter__(self):
|
||||
self.kodiconn = kodi_sql(self.db_type)
|
||||
self.kodiconn = utils.kodi_sql(self.db_type)
|
||||
kodi_db = KodiDBMethods(self.kodiconn.cursor())
|
||||
return kodi_db
|
||||
|
||||
|
@ -118,7 +118,7 @@ class KodiDBMethods(object):
|
|||
if pathid is None:
|
||||
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
|
||||
pathid = self.cursor.fetchone()[0] + 1
|
||||
datetime = unix_date_to_kodi(unix_timestamp())
|
||||
datetime = utils.unix_date_to_kodi(utils.unix_timestamp())
|
||||
query = '''
|
||||
INSERT INTO path(idPath, strPath, dateAdded)
|
||||
VALUES (?, ?, ?)
|
||||
|
@ -209,13 +209,14 @@ class KodiDBMethods(object):
|
|||
INSERT INTO files(idFile, idPath, strFilename, dateAdded)
|
||||
VALUES (?, ?, ?, ?)
|
||||
'''
|
||||
self.cursor.execute(query, (file_id, path_id, filename, date_added))
|
||||
self.cursor.execute(query,
|
||||
(file_id, path_id, filename, date_added))
|
||||
return file_id
|
||||
|
||||
def obsolete_file_ids(self):
|
||||
"""
|
||||
Returns a list of (idFile,) tuples (ints) of all Kodi file ids that do
|
||||
not have a dateAdded set (dateAdded is NULL) and the filename start with
|
||||
not have a dateAdded set (dateAdded NULL) and the filename start with
|
||||
'plugin://plugin.video.plexkodiconnect'
|
||||
These entries should be deleted as they're created falsely by Kodi.
|
||||
"""
|
||||
|
@ -653,7 +654,7 @@ class KodiDBMethods(object):
|
|||
movie_id = self.cursor.fetchone()[0]
|
||||
typus = v.KODI_TYPE_EPISODE
|
||||
except TypeError:
|
||||
LOG.warn('Unexpectantly did not find a match!')
|
||||
LOG.debug('Did not find a video DB match')
|
||||
return
|
||||
return movie_id, typus
|
||||
|
||||
|
@ -1236,7 +1237,7 @@ def kodiid_from_filename(path, kodi_type=None, db_type=None):
|
|||
Returns None, <kodi_type> if not possible
|
||||
"""
|
||||
kodi_id = None
|
||||
path = try_decode(path)
|
||||
path = utils.try_decode(path)
|
||||
try:
|
||||
filename = path.rsplit('/', 1)[1]
|
||||
path = path.rsplit('/', 1)[0] + '/'
|
||||
|
|
|
@ -5,29 +5,25 @@ from logging import getLogger
|
|||
from json import loads
|
||||
from threading import Thread
|
||||
import copy
|
||||
|
||||
import xbmc
|
||||
from xbmcgui import Window
|
||||
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
from utils import window, settings, plex_command, thread_methods, try_encode, \
|
||||
kodi_time_to_millis, unix_date_to_kodi, unix_timestamp
|
||||
from PlexFunctions import scrobble
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from kodidb_functions import kodiid_from_filename
|
||||
from plexbmchelper.subscribers import LOCKER
|
||||
from playback import playback_triage
|
||||
from initialsetup import set_replace_paths
|
||||
import playqueue as PQ
|
||||
import json_rpc as js
|
||||
import playlist_func as PL
|
||||
import state
|
||||
import variables as v
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import utils
|
||||
from . import plex_functions as PF
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import playback
|
||||
from . import initialsetup
|
||||
from . import playqueue as PQ
|
||||
from . import json_rpc as js
|
||||
from . import playlist_func as PL
|
||||
from . import state
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.kodimonitor')
|
||||
|
||||
# settings: window-variable
|
||||
WINDOW_SETTINGS = {
|
||||
|
@ -65,6 +61,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
def __init__(self):
|
||||
self.xbmcplayer = xbmc.Player()
|
||||
self._already_slept = False
|
||||
self.hack_replay = None
|
||||
xbmc.Monitor.__init__(self)
|
||||
for playerid in state.PLAYER_STATES:
|
||||
state.PLAYER_STATES[playerid] = copy.deepcopy(state.PLAYSTATE)
|
||||
|
@ -91,14 +88,14 @@ class KodiMonitor(xbmc.Monitor):
|
|||
changed = False
|
||||
# Reset the window variables from the settings variables
|
||||
for settings_value, window_value in WINDOW_SETTINGS.iteritems():
|
||||
if window(window_value) != settings(settings_value):
|
||||
if utils.window(window_value) != utils.settings(settings_value):
|
||||
changed = True
|
||||
LOG.debug('PKC window settings changed: %s is now %s',
|
||||
settings_value, settings(settings_value))
|
||||
window(window_value, value=settings(settings_value))
|
||||
settings_value, utils.settings(settings_value))
|
||||
utils.window(window_value, value=utils.settings(settings_value))
|
||||
# Reset the state variables in state.py
|
||||
for settings_value, state_name in STATE_SETTINGS.iteritems():
|
||||
new = settings(settings_value)
|
||||
new = utils.settings(settings_value)
|
||||
if new == 'true':
|
||||
new = True
|
||||
elif new == 'false':
|
||||
|
@ -110,19 +107,17 @@ class KodiMonitor(xbmc.Monitor):
|
|||
setattr(state, state_name, new)
|
||||
if state_name == 'FETCH_PMS_ITEM_NUMBER':
|
||||
LOG.info('Requesting playlist/nodes refresh')
|
||||
plex_command('RUN_LIB_SCAN', 'views')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'views')
|
||||
# Special cases, overwrite all internal settings
|
||||
set_replace_paths()
|
||||
state.BACKGROUND_SYNC_DISABLED = settings(
|
||||
initialsetup.set_replace_paths()
|
||||
state.BACKGROUND_SYNC_DISABLED = utils.settings(
|
||||
'enableBackgroundSync') == 'false'
|
||||
state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
|
||||
state.FULL_SYNC_INTERVALL = int(utils.settings('fullSyncInterval')) * 60
|
||||
state.BACKGROUNDSYNC_SAFTYMARGIN = int(
|
||||
settings('backgroundsync_saftyMargin'))
|
||||
state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
|
||||
state.SSL_CERT_PATH = settings('sslcert') \
|
||||
if settings('sslcert') != 'None' else None
|
||||
# Never set through the user
|
||||
# state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
|
||||
utils.settings('backgroundsync_saftyMargin'))
|
||||
state.SYNC_THREAD_NUMBER = int(utils.settings('syncThreadNumber'))
|
||||
state.SSL_CERT_PATH = utils.settings('sslcert') \
|
||||
if utils.settings('sslcert') != 'None' else None
|
||||
if changed is True:
|
||||
# Assume that the user changed the settings so that we can now find
|
||||
# the path to all media files
|
||||
|
@ -137,28 +132,43 @@ class KodiMonitor(xbmc.Monitor):
|
|||
data = loads(data, 'utf-8')
|
||||
LOG.debug("Method: %s Data: %s", method, data)
|
||||
|
||||
# Hack
|
||||
if not method == 'Player.OnStop':
|
||||
self.hack_replay = None
|
||||
|
||||
if method == "Player.OnPlay":
|
||||
state.SUSPEND_SYNC = True
|
||||
self.PlayBackStart(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self.PlayBackStart(data)
|
||||
elif method == "Player.OnStop":
|
||||
# Should refresh our video nodes, e.g. on deck
|
||||
# xbmc.executebuiltin('ReloadSkin()')
|
||||
if data.get('end'):
|
||||
if (self.hack_replay and not data.get('end') and
|
||||
self.hack_replay == data['item']):
|
||||
# Hack for add-on paths
|
||||
self.hack_replay = None
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._hack_addon_paths_replay_video()
|
||||
elif data.get('end'):
|
||||
if state.PKC_CAUSED_STOP is True:
|
||||
state.PKC_CAUSED_STOP = False
|
||||
LOG.debug('PKC caused this playback stop - ignoring')
|
||||
else:
|
||||
_playback_cleanup(ended=True)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
_playback_cleanup(ended=True)
|
||||
else:
|
||||
_playback_cleanup()
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
_playback_cleanup()
|
||||
state.PKC_CAUSED_STOP_DONE = True
|
||||
state.SUSPEND_SYNC = False
|
||||
elif method == 'Playlist.OnAdd':
|
||||
self._playlist_onadd(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._playlist_onadd(data)
|
||||
elif method == 'Playlist.OnRemove':
|
||||
self._playlist_onremove(data)
|
||||
elif method == 'Playlist.OnClear':
|
||||
self._playlist_onclear(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._playlist_onclear(data)
|
||||
elif method == "VideoLibrary.OnUpdate":
|
||||
# Manually marking as watched/unwatched
|
||||
playcount = data.get('playcount')
|
||||
|
@ -182,28 +192,58 @@ class KodiMonitor(xbmc.Monitor):
|
|||
else:
|
||||
# notify the server
|
||||
if playcount > 0:
|
||||
scrobble(itemid, 'watched')
|
||||
PF.scrobble(itemid, 'watched')
|
||||
else:
|
||||
scrobble(itemid, 'unwatched')
|
||||
PF.scrobble(itemid, 'unwatched')
|
||||
elif method == "VideoLibrary.OnRemove":
|
||||
pass
|
||||
elif method == "System.OnSleep":
|
||||
# Connection is going to sleep
|
||||
LOG.info("Marking the server as offline. SystemOnSleep activated.")
|
||||
window('plex_online', value="sleep")
|
||||
utils.window('plex_online', value="sleep")
|
||||
elif method == "System.OnWake":
|
||||
# Allow network to wake up
|
||||
xbmc.sleep(10000)
|
||||
window('plex_online', value="false")
|
||||
utils.window('plex_online', value="false")
|
||||
elif method == "GUI.OnScreensaverDeactivated":
|
||||
if settings('dbSyncScreensaver') == "true":
|
||||
if utils.settings('dbSyncScreensaver') == "true":
|
||||
xbmc.sleep(5000)
|
||||
plex_command('RUN_LIB_SCAN', 'full')
|
||||
utils.plex_command('RUN_LIB_SCAN', 'full')
|
||||
elif method == "System.OnQuit":
|
||||
LOG.info('Kodi OnQuit detected - shutting down')
|
||||
state.STOP_PKC = True
|
||||
|
||||
@LOCKER.lockthis
|
||||
@staticmethod
|
||||
def _hack_addon_paths_replay_video():
|
||||
"""
|
||||
Hack we need for RESUMABLE items because Kodi lost the path of the
|
||||
last played item that is now being replayed (see playback.py's
|
||||
Player().play()) Also see playqueue.py _compare_playqueues()
|
||||
|
||||
Needed if user re-starts the same video from the library using addon
|
||||
paths. (Video is only added to playqueue, then immediately stoppen.
|
||||
There is no playback initialized by Kodi.) Log excerpts:
|
||||
Method: Playlist.OnAdd Data:
|
||||
{u'item': {u'type': u'movie', u'id': 4},
|
||||
u'playlistid': 1,
|
||||
u'position': 0}
|
||||
Now we would hack!
|
||||
Method: Player.OnStop Data:
|
||||
{u'item': {u'type': u'movie', u'id': 4},
|
||||
u'end': False}
|
||||
(within the same micro-second!)
|
||||
"""
|
||||
LOG.info('Detected re-start of playback of last item')
|
||||
old = state.OLD_PLAYER_STATES[1]
|
||||
kwargs = {
|
||||
'plex_id': old['plex_id'],
|
||||
'plex_type': old['plex_type'],
|
||||
'path': old['file'],
|
||||
'resolve': False
|
||||
}
|
||||
thread = Thread(target=playback.playback_triage, kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
def _playlist_onadd(self, data):
|
||||
"""
|
||||
Called if an item is added to a Kodi playlist. Example data dict:
|
||||
|
@ -219,23 +259,12 @@ class KodiMonitor(xbmc.Monitor):
|
|||
if 'id' not in data['item']:
|
||||
return
|
||||
old = state.OLD_PLAYER_STATES[data['playlistid']]
|
||||
if (not state.DIRECT_PATHS and data['position'] == 0 and
|
||||
if (not state.DIRECT_PATHS and
|
||||
data['position'] == 0 and data['playlistid'] == 1 and
|
||||
not PQ.PLAYQUEUES[data['playlistid']].items and
|
||||
data['item']['type'] == old['kodi_type'] and
|
||||
data['item']['id'] == old['kodi_id']):
|
||||
# Hack we need for RESUMABLE items because Kodi lost the path of the
|
||||
# last played item that is now being replayed (see playback.py's
|
||||
# Player().play()) Also see playqueue.py _compare_playqueues()
|
||||
LOG.info('Detected re-start of playback of last item')
|
||||
kwargs = {
|
||||
'plex_id': old['plex_id'],
|
||||
'plex_type': old['plex_type'],
|
||||
'path': old['file'],
|
||||
'resolve': False
|
||||
}
|
||||
thread = Thread(target=playback_triage, kwargs=kwargs)
|
||||
thread.start()
|
||||
return
|
||||
self.hack_replay = data['item']
|
||||
|
||||
def _playlist_onremove(self, data):
|
||||
"""
|
||||
|
@ -247,7 +276,6 @@ class KodiMonitor(xbmc.Monitor):
|
|||
"""
|
||||
pass
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _playlist_onclear(self, data):
|
||||
"""
|
||||
Called if a Kodi playlist is cleared. Example data dict:
|
||||
|
@ -271,7 +299,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
plex_type = None
|
||||
# If using direct paths and starting playback from a widget
|
||||
if not kodi_id and kodi_type and path:
|
||||
kodi_id, _ = kodiid_from_filename(path, kodi_type)
|
||||
kodi_id, _ = kodidb.kodiid_from_filename(path, kodi_type)
|
||||
if kodi_id:
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
|
||||
|
@ -313,13 +341,16 @@ class KodiMonitor(xbmc.Monitor):
|
|||
# element otherwise
|
||||
self._already_slept = True
|
||||
xbmc.sleep(1000)
|
||||
json_item = js.get_item(playerid)
|
||||
try:
|
||||
json_item = js.get_item(playerid)
|
||||
except KeyError:
|
||||
LOG.debug('No playing item returned by Kodi')
|
||||
return None, None, None
|
||||
LOG.debug('Kodi playing item properties: %s', json_item)
|
||||
return (json_item.get('id'),
|
||||
json_item.get('type'),
|
||||
json_item.get('file'))
|
||||
|
||||
@LOCKER.lockthis
|
||||
def PlayBackStart(self, data):
|
||||
"""
|
||||
Called whenever playback is started. Example data:
|
||||
|
@ -426,7 +457,7 @@ class KodiMonitor(xbmc.Monitor):
|
|||
LOG.debug('Set the player state: %s', status)
|
||||
|
||||
|
||||
@thread_methods
|
||||
@utils.thread_methods
|
||||
class SpecialMonitor(Thread):
|
||||
"""
|
||||
Detect the resume dialog for widgets.
|
||||
|
@ -435,8 +466,8 @@ class SpecialMonitor(Thread):
|
|||
def run(self):
|
||||
LOG.info("----====# Starting Special Monitor #====----")
|
||||
# "Start from beginning", "Play from beginning"
|
||||
strings = (try_encode(xbmc.getLocalizedString(12021)),
|
||||
try_encode(xbmc.getLocalizedString(12023)))
|
||||
strings = (utils.try_encode(xbmc.getLocalizedString(12021)),
|
||||
utils.try_encode(xbmc.getLocalizedString(12023)))
|
||||
while not self.stopped():
|
||||
if xbmc.getCondVisibility('Window.IsVisible(DialogContextMenu.xml)'):
|
||||
if xbmc.getInfoLabel('Control.GetLabel(1002)') in strings:
|
||||
|
@ -457,7 +488,6 @@ class SpecialMonitor(Thread):
|
|||
LOG.info("#====---- Special Monitor Stopped ----====#")
|
||||
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _playback_cleanup(ended=False):
|
||||
"""
|
||||
PKC cleanup after playback ends/is stopped. Pass ended=True if Kodi
|
||||
|
@ -501,12 +531,12 @@ def _record_playstate(status, ended):
|
|||
# Item not (yet) in Kodi library
|
||||
LOG.debug('No playstate update due to Plex id not found: %s', status)
|
||||
return
|
||||
totaltime = float(kodi_time_to_millis(status['totaltime'])) / 1000
|
||||
totaltime = float(utils.kodi_time_to_millis(status['totaltime'])) / 1000
|
||||
if ended:
|
||||
progress = 0.99
|
||||
time = v.IGNORE_SECONDS_AT_START + 1
|
||||
else:
|
||||
time = float(kodi_time_to_millis(status['time'])) / 1000
|
||||
time = float(utils.kodi_time_to_millis(status['time'])) / 1000
|
||||
try:
|
||||
progress = time / totaltime
|
||||
except ZeroDivisionError:
|
||||
|
@ -514,7 +544,7 @@ def _record_playstate(status, ended):
|
|||
LOG.debug('Playback progress %s (%s of %s seconds)',
|
||||
progress, time, totaltime)
|
||||
playcount = status['playcount']
|
||||
last_played = unix_date_to_kodi(unix_timestamp())
|
||||
last_played = utils.unix_date_to_kodi(utils.unix_timestamp())
|
||||
if playcount is None:
|
||||
LOG.debug('playcount not found, looking it up in the Kodi DB')
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from Queue import Empty
|
||||
|
||||
import xbmc
|
||||
|
||||
from utils import thread_methods, settings, language as lang, dialog
|
||||
import plexdb_functions as plexdb
|
||||
import itemtypes
|
||||
from artwork import ArtworkSyncMessage
|
||||
import variables as v
|
||||
import state
|
||||
from .. import utils
|
||||
from .. import plexdb_functions as plexdb
|
||||
from .. import itemtypes
|
||||
from .. import artwork
|
||||
from .. import variables as v
|
||||
from .. import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -19,10 +18,10 @@ LOG = getLogger("PLEX." + __name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'DB_SCAN',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'DB_SCAN',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedProcessFanart(Thread):
|
||||
"""
|
||||
Threaded download of additional fanart in the background
|
||||
|
@ -68,17 +67,17 @@ class ThreadedProcessFanart(Thread):
|
|||
'Window.IsVisible(DialogAddonSettings.xml)'):
|
||||
# Avoid saving '0' all the time
|
||||
set_zero = True
|
||||
settings('fanarttv_lookups', value='0')
|
||||
utils.settings('fanarttv_lookups', value='0')
|
||||
xbmc.sleep(200)
|
||||
continue
|
||||
set_zero = False
|
||||
if isinstance(item, ArtworkSyncMessage):
|
||||
if isinstance(item, artwork.ArtworkSyncMessage):
|
||||
if state.IMAGE_SYNC_NOTIFICATIONS:
|
||||
dialog('notification',
|
||||
heading=lang(29999),
|
||||
message=item.message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
heading=utils.lang(29999),
|
||||
message=item.message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
queue.task_done()
|
||||
continue
|
||||
|
||||
|
@ -96,6 +95,6 @@ class ThreadedProcessFanart(Thread):
|
|||
if (counter > 20 and not xbmc.getCondVisibility(
|
||||
'Window.IsVisible(DialogAddonSettings.xml)')):
|
||||
counter = 0
|
||||
settings('fanarttv_lookups', value=str(queue.qsize()))
|
||||
utils.settings('fanarttv_lookups', value=str(queue.qsize()))
|
||||
queue.task_done()
|
||||
LOG.debug("---===### Stopped FanartSync ###===---")
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from Queue import Empty
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import thread_methods, window
|
||||
from PlexFunctions import GetPlexMetadata, GetAllPlexChildren
|
||||
import sync_info
|
||||
from .. import utils
|
||||
from .. import plex_functions as PF
|
||||
from . import sync_info
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -16,9 +15,9 @@ LOG = getLogger("PLEX." + __name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedGetMetadata(Thread):
|
||||
"""
|
||||
Threaded download of Plex XML metadata for a certain library item.
|
||||
|
@ -79,7 +78,7 @@ class ThreadedGetMetadata(Thread):
|
|||
sleep(20)
|
||||
continue
|
||||
# Download Metadata
|
||||
xml = GetPlexMetadata(item['plex_id'])
|
||||
xml = PF.GetPlexMetadata(item['plex_id'])
|
||||
if xml is None:
|
||||
# Did not receive a valid XML - skip that item for now
|
||||
LOG.error("Could not get metadata for %s. Skipping that item "
|
||||
|
@ -93,14 +92,14 @@ class ThreadedGetMetadata(Thread):
|
|||
elif xml == 401:
|
||||
LOG.error('HTTP 401 returned by PMS. Too much strain? '
|
||||
'Cancelling sync for now')
|
||||
window('plex_scancrashed', value='401')
|
||||
utils.window('plex_scancrashed', value='401')
|
||||
# Kill remaining items in queue (for main thread to cont.)
|
||||
queue.task_done()
|
||||
break
|
||||
|
||||
item['xml'] = xml
|
||||
if item.get('get_children') is True:
|
||||
children_xml = GetAllPlexChildren(item['plex_id'])
|
||||
children_xml = PF.GetAllPlexChildren(item['plex_id'])
|
||||
try:
|
||||
children_xml[0].attrib
|
||||
except (TypeError, IndexError, AttributeError):
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from Queue import Empty
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import thread_methods
|
||||
import itemtypes
|
||||
import sync_info
|
||||
from .. import utils
|
||||
from .. import itemtypes
|
||||
from . import sync_info
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
@ -15,9 +14,9 @@ LOG = getLogger("PLEX." + __name__)
|
|||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedProcessMetadata(Thread):
|
||||
"""
|
||||
Not yet implemented for more than 1 thread - if ever. Only to be called by
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from logging import getLogger
|
||||
from threading import Thread, Lock
|
||||
|
||||
from xbmc import sleep
|
||||
from xbmcgui import DialogProgressBG
|
||||
|
||||
from utils import thread_methods, language as lang
|
||||
from .. import utils
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -19,9 +18,9 @@ LOCK = Lock()
|
|||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
|
||||
'STOP_SYNC',
|
||||
'SUSPEND_SYNC'])
|
||||
class ThreadedShowSyncInfo(Thread):
|
||||
"""
|
||||
Threaded class to show the Kodi statusbar of the metadata download.
|
||||
|
@ -44,7 +43,10 @@ class ThreadedShowSyncInfo(Thread):
|
|||
total = self.total
|
||||
dialog = DialogProgressBG('dialoglogProgressBG')
|
||||
dialog.create("%s %s: %s %s"
|
||||
% (lang(39714), self.item_type, str(total), lang(39715)))
|
||||
% (utils.lang(39714),
|
||||
self.item_type,
|
||||
unicode(total),
|
||||
utils.lang(39715)))
|
||||
|
||||
total = 2 * total
|
||||
total_progress = 0
|
||||
|
@ -55,15 +57,15 @@ class ThreadedShowSyncInfo(Thread):
|
|||
view_name = PROCESSING_VIEW_NAME
|
||||
total_progress = get_progress + process_progress
|
||||
try:
|
||||
percentage = int(float(total_progress) / float(total)*100.0)
|
||||
percentage = int(float(total_progress) / float(total) * 100.0)
|
||||
except ZeroDivisionError:
|
||||
percentage = 0
|
||||
dialog.update(percentage,
|
||||
message="%s %s. %s %s: %s"
|
||||
% (get_progress,
|
||||
lang(39712),
|
||||
utils.lang(39712),
|
||||
process_progress,
|
||||
lang(39713),
|
||||
utils.lang(39713),
|
||||
view_name))
|
||||
# Sleep for x milliseconds
|
||||
sleep(200)
|
||||
|
|
|
@ -5,34 +5,27 @@ from threading import Thread
|
|||
import Queue
|
||||
from random import shuffle
|
||||
import copy
|
||||
|
||||
import xbmc
|
||||
from xbmcvfs import exists
|
||||
|
||||
import utils
|
||||
from utils import window, settings, dialog, language as lang, try_decode, \
|
||||
try_encode
|
||||
from downloadutils import DownloadUtils as DU
|
||||
import itemtypes
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import artwork
|
||||
import videonodes
|
||||
import variables as v
|
||||
|
||||
import PlexFunctions as PF
|
||||
import PlexAPI
|
||||
from library_sync.get_metadata import ThreadedGetMetadata
|
||||
from library_sync.process_metadata import ThreadedProcessMetadata
|
||||
import library_sync.sync_info as sync_info
|
||||
from library_sync.fanart import ThreadedProcessFanart
|
||||
import music
|
||||
import playlists
|
||||
import state
|
||||
from . import utils
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import itemtypes
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import artwork
|
||||
from . import videonodes
|
||||
from . import plex_functions as PF
|
||||
from .plex_api import API
|
||||
from .library_sync import get_metadata, process_metadata, fanart, sync_info
|
||||
from . import music
|
||||
from . import playlists
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.librarysync')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -47,10 +40,10 @@ class LibrarySync(Thread):
|
|||
self.views = []
|
||||
self.session_keys = {}
|
||||
self.fanartqueue = Queue.Queue()
|
||||
self.fanartthread = ThreadedProcessFanart(self.fanartqueue)
|
||||
self.fanartthread = fanart.ThreadedProcessFanart(self.fanartqueue)
|
||||
# How long should we wait at least to process new/changed PMS items?
|
||||
self.vnodes = videonodes.VideoNodes()
|
||||
self.install_sync_done = settings('SyncInstallRunDone') == 'true'
|
||||
self.install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
|
||||
# Show sync dialog even if user deactivated?
|
||||
self.force_dialog = True
|
||||
# Need to be set accordingly later
|
||||
|
@ -91,16 +84,16 @@ class LibrarySync(Thread):
|
|||
if state.SYNC_DIALOG is not True and self.force_dialog is not True:
|
||||
return
|
||||
if icon == "plex":
|
||||
dialog('notification',
|
||||
heading='{plex}',
|
||||
message=message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=message,
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
elif icon == "error":
|
||||
dialog('notification',
|
||||
heading='{plex}',
|
||||
message=message,
|
||||
icon='{error}')
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=message,
|
||||
icon='{error}')
|
||||
|
||||
@staticmethod
|
||||
def sync_pms_time():
|
||||
|
@ -200,7 +193,8 @@ class LibrarySync(Thread):
|
|||
|
||||
# Calculate time offset Kodi-PMS
|
||||
state.KODI_PLEX_TIME_OFFSET = float(koditime) - float(plextime)
|
||||
settings('kodiplextimeoffset', value=str(state.KODI_PLEX_TIME_OFFSET))
|
||||
utils.settings('kodiplextimeoffset',
|
||||
value=str(state.KODI_PLEX_TIME_OFFSET))
|
||||
LOG.info("Time offset Koditime - Plextime in seconds: %s",
|
||||
str(state.KODI_PLEX_TIME_OFFSET))
|
||||
return True
|
||||
|
@ -288,15 +282,15 @@ class LibrarySync(Thread):
|
|||
if state.ENABLE_MUSIC:
|
||||
xbmc.executebuiltin('UpdateLibrary(music)')
|
||||
|
||||
if window('plex_scancrashed') == 'true':
|
||||
if utils.window('plex_scancrashed') == 'true':
|
||||
# Show warning if itemtypes.py crashed at some point
|
||||
dialog('ok', heading='{plex}', line1=lang(39408))
|
||||
window('plex_scancrashed', clear=True)
|
||||
elif window('plex_scancrashed') == '401':
|
||||
window('plex_scancrashed', clear=True)
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39408))
|
||||
utils.window('plex_scancrashed', clear=True)
|
||||
elif utils.window('plex_scancrashed') == '401':
|
||||
utils.window('plex_scancrashed', clear=True)
|
||||
if state.PMS_STATUS not in ('401', 'Auth'):
|
||||
# Plex server had too much and returned ERROR
|
||||
dialog('ok', heading='{plex}', line1=lang(39409))
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39409))
|
||||
return True
|
||||
|
||||
def _process_view(self, folder_item, kodi_db, plex_db, totalnodes):
|
||||
|
@ -495,7 +489,7 @@ class LibrarySync(Thread):
|
|||
# totalnodes += 1
|
||||
|
||||
# Save total
|
||||
window('Plex.nodes.total', str(totalnodes))
|
||||
utils.window('Plex.nodes.total', str(totalnodes))
|
||||
|
||||
# Get rid of old items (view has been deleted on Plex side)
|
||||
if self.old_views:
|
||||
|
@ -524,11 +518,11 @@ class LibrarySync(Thread):
|
|||
elif item['kodi_type'] in v.KODI_AUDIOTYPES:
|
||||
delete_music.append(item)
|
||||
|
||||
dialog('notification',
|
||||
heading='{plex}',
|
||||
message=lang(30052),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
utils.dialog('notification',
|
||||
heading='{plex}',
|
||||
message=utils.lang(30052),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
for item in delete_movies:
|
||||
with itemtypes.Movies() as movie_db:
|
||||
movie_db.remove(item['plex_id'])
|
||||
|
@ -638,8 +632,8 @@ class LibrarySync(Thread):
|
|||
|
||||
def process_updatelist(self, item_class):
|
||||
"""
|
||||
Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes them
|
||||
by then calling item_classs.<item_class>()
|
||||
Downloads all XMLs for item_class (e.g. Movies, TV-Shows). Processes
|
||||
them by then calling item_classs.<item_class>()
|
||||
|
||||
Input:
|
||||
item_class: 'Movies', 'TVShows', ...
|
||||
|
@ -665,13 +659,15 @@ class LibrarySync(Thread):
|
|||
# Spawn GetMetadata threads for downloading
|
||||
threads = []
|
||||
for _ in range(min(state.SYNC_THREAD_NUMBER, item_number)):
|
||||
thread = ThreadedGetMetadata(download_queue, process_queue)
|
||||
thread = get_metadata.ThreadedGetMetadata(download_queue,
|
||||
process_queue)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
LOG.debug("%s download threads spawned", len(threads))
|
||||
# Spawn one more thread to process Metadata, once downloaded
|
||||
thread = ThreadedProcessMetadata(process_queue, item_class)
|
||||
thread = process_metadata.ThreadedProcessMetadata(process_queue,
|
||||
item_class)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
|
@ -702,7 +698,7 @@ class LibrarySync(Thread):
|
|||
except:
|
||||
pass
|
||||
LOG.debug("Sync threads finished")
|
||||
if (settings('FanartTV') == 'true' and
|
||||
if (utils.settings('FanartTV') == 'true' and
|
||||
item_class in ('Movies', 'TVShows')):
|
||||
for item in self.updatelist:
|
||||
if item['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
|
||||
|
@ -1029,23 +1025,11 @@ class LibrarySync(Thread):
|
|||
do with "process_" methods
|
||||
"""
|
||||
if message['type'] == 'playing':
|
||||
try:
|
||||
self.process_playing(message['PlaySessionStateNotification'])
|
||||
except KeyError:
|
||||
LOG.error('Received invalid PMS message for playstate: %s',
|
||||
message)
|
||||
self.process_playing(message['PlaySessionStateNotification'])
|
||||
elif message['type'] == 'timeline':
|
||||
try:
|
||||
self.process_timeline(message['TimelineEntry'])
|
||||
except (KeyError, ValueError):
|
||||
LOG.error('Received invalid PMS message for timeline: %s',
|
||||
message)
|
||||
self.process_timeline(message['TimelineEntry'])
|
||||
elif message['type'] == 'activity':
|
||||
try:
|
||||
self.process_activity(message['ActivityNotification'])
|
||||
except KeyError:
|
||||
LOG.error('Received invalid PMS message for activity: %s',
|
||||
message)
|
||||
self.process_activity(message['ActivityNotification'])
|
||||
|
||||
def multi_delete(self, liste, delete_list):
|
||||
"""
|
||||
|
@ -1099,7 +1083,7 @@ class LibrarySync(Thread):
|
|||
continue
|
||||
else:
|
||||
successful = self.process_newitems(item)
|
||||
if successful and settings('FanartTV') == 'true':
|
||||
if successful and utils.settings('FanartTV') == 'true':
|
||||
if item['type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
|
||||
self.fanartqueue.put({
|
||||
'plex_id': item['ratingKey'],
|
||||
|
@ -1200,7 +1184,7 @@ class LibrarySync(Thread):
|
|||
continue
|
||||
playlists.process_websocket(plex_id=str(item['itemID']),
|
||||
updated_at=str(item['updatedAt']),
|
||||
state=status)
|
||||
status=status)
|
||||
elif status == 9:
|
||||
# Immediately and always process deletions (as the PMS will
|
||||
# send additional message with other codes)
|
||||
|
@ -1294,7 +1278,7 @@ class LibrarySync(Thread):
|
|||
if kodi_info is None:
|
||||
# Item not (yet) in Kodi library
|
||||
continue
|
||||
if settings('plex_serverowned') == 'false':
|
||||
if utils.settings('plex_serverowned') == 'false':
|
||||
# Not our PMS, we are not authorized to get the sessions
|
||||
# On the bright side, it must be us playing :-)
|
||||
self.session_keys[session_key] = {}
|
||||
|
@ -1312,7 +1296,7 @@ class LibrarySync(Thread):
|
|||
self.session_keys[session_key]['file_id'] = kodi_info[1]
|
||||
self.session_keys[session_key]['kodi_type'] = kodi_info[4]
|
||||
session = self.session_keys[session_key]
|
||||
if settings('plex_serverowned') != 'false':
|
||||
if utils.settings('plex_serverowned') != 'false':
|
||||
# Identify the user - same one as signed on with PKC? Skip
|
||||
# update if neither session's username nor userid match
|
||||
# (Owner sometime's returns id '1', not always)
|
||||
|
@ -1339,7 +1323,7 @@ class LibrarySync(Thread):
|
|||
LOG.error('Could not get up-to-date xml for item %s',
|
||||
plex_id)
|
||||
continue
|
||||
api = PlexAPI.API(xml[0])
|
||||
api = API(xml[0])
|
||||
userdata = api.userdata()
|
||||
session['duration'] = userdata['Runtime']
|
||||
session['viewCount'] = userdata['PlayCount']
|
||||
|
@ -1378,7 +1362,8 @@ class LibrarySync(Thread):
|
|||
resume,
|
||||
session['duration'],
|
||||
session['file_id'],
|
||||
utils.unix_date_to_kodi(utils.unix_timestamp()),
|
||||
utils.unix_date_to_kodi(
|
||||
utils.unix_timestamp()),
|
||||
plex_type)
|
||||
|
||||
def sync_fanart(self, missing_only=True, refresh=False):
|
||||
|
@ -1389,7 +1374,7 @@ class LibrarySync(Thread):
|
|||
missing_only=True False will start look-up for EVERY item
|
||||
refresh=False True will force refresh all external fanart
|
||||
"""
|
||||
if settings('FanartTV') == 'false':
|
||||
if utils.settings('FanartTV') == 'false':
|
||||
return
|
||||
with plexdb.Get_Plex_DB() as plex_db:
|
||||
if missing_only:
|
||||
|
@ -1407,7 +1392,8 @@ class LibrarySync(Thread):
|
|||
# Shuffle the list to not always start out identically
|
||||
shuffle(items)
|
||||
# Checking FanartTV for %s items
|
||||
self.fanartqueue.put(artwork.ArtworkSyncMessage(lang(30018) % len(items)))
|
||||
self.fanartqueue.put(artwork.ArtworkSyncMessage(
|
||||
utils.lang(30018) % len(items)))
|
||||
for i, item in enumerate(items):
|
||||
self.fanartqueue.put({
|
||||
'plex_id': item['plex_id'],
|
||||
|
@ -1415,7 +1401,7 @@ class LibrarySync(Thread):
|
|||
'refresh': refresh
|
||||
})
|
||||
# FanartTV lookup completed
|
||||
self.fanartqueue.put(artwork.ArtworkSyncMessage(lang(30019)))
|
||||
self.fanartqueue.put(artwork.ArtworkSyncMessage(utils.lang(30019)))
|
||||
|
||||
def triage_lib_scans(self):
|
||||
"""
|
||||
|
@ -1424,27 +1410,27 @@ class LibrarySync(Thread):
|
|||
"""
|
||||
if state.RUN_LIB_SCAN in ("full", "repair"):
|
||||
LOG.info('Full library scan requested, starting')
|
||||
window('plex_dbScan', value="true")
|
||||
utils.window('plex_dbScan', value="true")
|
||||
state.DB_SCAN = True
|
||||
success = self.maintain_views()
|
||||
if success and state.RUN_LIB_SCAN == "full":
|
||||
success = self.full_sync()
|
||||
elif success:
|
||||
success = self.full_sync(repair=True)
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
if success:
|
||||
# Full library sync finished
|
||||
self.show_kodi_note(lang(39407))
|
||||
self.show_kodi_note(utils.lang(39407))
|
||||
elif not self.suspend_item_sync():
|
||||
self.force_dialog = True
|
||||
# ERROR in library sync
|
||||
self.show_kodi_note(lang(39410), icon='error')
|
||||
self.show_kodi_note(utils.lang(39410), icon='error')
|
||||
self.force_dialog = False
|
||||
# Reset views was requested from somewhere else
|
||||
elif state.RUN_LIB_SCAN == "views":
|
||||
LOG.info('Refresh playlist and nodes requested, starting')
|
||||
window('plex_dbScan', value="true")
|
||||
utils.window('plex_dbScan', value="true")
|
||||
state.DB_SCAN = True
|
||||
# First remove playlists
|
||||
utils.delete_playlists()
|
||||
|
@ -1455,28 +1441,28 @@ class LibrarySync(Thread):
|
|||
# Ran successfully
|
||||
LOG.info("Refresh playlists/nodes completed")
|
||||
# "Plex playlists/nodes refreshed"
|
||||
self.show_kodi_note(lang(39405))
|
||||
self.show_kodi_note(utils.lang(39405))
|
||||
else:
|
||||
# Failed
|
||||
LOG.error("Refresh playlists/nodes failed")
|
||||
# "Plex playlists/nodes refresh failed"
|
||||
self.show_kodi_note(lang(39406), icon="error")
|
||||
window('plex_dbScan', clear=True)
|
||||
self.show_kodi_note(utils.lang(39406), icon="error")
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
elif state.RUN_LIB_SCAN == 'fanart':
|
||||
# Only look for missing fanart (No)
|
||||
# or refresh all fanart (Yes)
|
||||
refresh = dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1=lang(39223),
|
||||
nolabel=lang(39224),
|
||||
yeslabel=lang(39225))
|
||||
refresh = utils.dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1=utils.lang(39223),
|
||||
nolabel=utils.lang(39224),
|
||||
yeslabel=utils.lang(39225))
|
||||
self.sync_fanart(missing_only=not refresh, refresh=refresh)
|
||||
elif state.RUN_LIB_SCAN == 'textures':
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
utils.window('plex_dbScan', value="true")
|
||||
artwork.Artwork().fullTextureCacheSync()
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
else:
|
||||
raise NotImplementedError('Library scan not defined: %s'
|
||||
|
@ -1489,12 +1475,12 @@ class LibrarySync(Thread):
|
|||
self._run_internal()
|
||||
except Exception as e:
|
||||
state.DB_SCAN = False
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
LOG.error('LibrarySync thread crashed. Error message: %s', e)
|
||||
import traceback
|
||||
LOG.error("Traceback:\n%s", traceback.format_exc())
|
||||
# Library sync thread has crashed
|
||||
dialog('ok', heading='{plex}', line1=lang(39400))
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39400))
|
||||
raise
|
||||
|
||||
def _run_internal(self):
|
||||
|
@ -1504,18 +1490,21 @@ class LibrarySync(Thread):
|
|||
last_sync = 0
|
||||
last_processing = 0
|
||||
last_time_sync = 0
|
||||
one_day_in_seconds = 60*60*24
|
||||
one_day_in_seconds = 60 * 60 * 24
|
||||
# Link to Websocket queue
|
||||
queue = state.WEBSOCKET_QUEUE
|
||||
|
||||
if not exists(try_encode(v.DB_VIDEO_PATH)):
|
||||
if (not exists(utils.try_encode(v.DB_VIDEO_PATH)) or
|
||||
not exists(utils.try_encode(v.DB_TEXTURE_PATH)) or
|
||||
(state.ENABLE_MUSIC and
|
||||
not exists(utils.try_encode(v.DB_MUSIC_PATH)))):
|
||||
# Database does not exists
|
||||
LOG.error("The current Kodi version is incompatible "
|
||||
"to know which Kodi versions are supported.")
|
||||
LOG.error('Current Kodi version: %s', try_decode(
|
||||
LOG.error('Current Kodi version: %s', utils.try_decode(
|
||||
xbmc.getInfoLabel('System.BuildVersion')))
|
||||
# "Current Kodi version is unsupported, cancel lib sync"
|
||||
dialog('ok', heading='{plex}', line1=lang(39403))
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39403))
|
||||
return
|
||||
|
||||
# Do some initializing
|
||||
|
@ -1523,14 +1512,14 @@ class LibrarySync(Thread):
|
|||
self.initialize_plex_db()
|
||||
# Run start up sync
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
LOG.info("Db version: %s", settings('dbCreatedWithVersion'))
|
||||
utils.window('plex_dbScan', value="true")
|
||||
LOG.info("Db version: %s", utils.settings('dbCreatedWithVersion'))
|
||||
|
||||
LOG.info('Refreshing video nodes and playlists now')
|
||||
# Setup the paths for addon-paths (even when using direct paths)
|
||||
with kodidb.GetKodiDB('video') as kodi_db:
|
||||
kodi_db.setup_path_table()
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
playlist_monitor = None
|
||||
|
||||
|
@ -1546,7 +1535,7 @@ class LibrarySync(Thread):
|
|||
if not self.install_sync_done:
|
||||
# Very first sync upon installation or reset of Kodi DB
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value='true')
|
||||
utils.window('plex_dbScan', value='true')
|
||||
# Initialize time offset Kodi - PMS
|
||||
self.sync_pms_time()
|
||||
last_time_sync = utils.unix_timestamp()
|
||||
|
@ -1559,9 +1548,9 @@ class LibrarySync(Thread):
|
|||
LOG.error('Initial maintain_views not successful')
|
||||
elif self.full_sync():
|
||||
LOG.info('Initial start-up full sync successful')
|
||||
settings('SyncInstallRunDone', value='true')
|
||||
utils.settings('SyncInstallRunDone', value='true')
|
||||
self.install_sync_done = True
|
||||
settings('dbCreatedWithVersion', v.ADDON_VERSION)
|
||||
utils.settings('dbCreatedWithVersion', v.ADDON_VERSION)
|
||||
self.force_dialog = False
|
||||
initial_sync_done = True
|
||||
kodi_db_version_checked = True
|
||||
|
@ -1573,27 +1562,29 @@ class LibrarySync(Thread):
|
|||
else:
|
||||
LOG.error('Initial start-up full sync unsuccessful')
|
||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
|
||||
elif not kodi_db_version_checked:
|
||||
# Install sync was already done, don't force-show dialogs
|
||||
self.force_dialog = False
|
||||
# Verify the validity of the database
|
||||
current_version = settings('dbCreatedWithVersion')
|
||||
if not utils.compare_version(current_version, v.MIN_DB_VERSION):
|
||||
current_version = utils.settings('dbCreatedWithVersion')
|
||||
if not utils.compare_version(current_version,
|
||||
v.MIN_DB_VERSION):
|
||||
LOG.warn("Db version out of date: %s minimum version "
|
||||
"required: %s", current_version, v.MIN_DB_VERSION)
|
||||
# DB out of date. Proceed to recreate?
|
||||
resp = dialog('yesno',
|
||||
heading=lang(29999),
|
||||
line1=lang(39401))
|
||||
resp = utils.dialog('yesno',
|
||||
heading=utils.lang(29999),
|
||||
line1=utils.lang(39401))
|
||||
if not resp:
|
||||
LOG.warn("Db version out of date! USER IGNORED!")
|
||||
# PKC may not work correctly until reset
|
||||
dialog('ok',
|
||||
heading='{plex}',
|
||||
line1=lang(29999) + lang(39402))
|
||||
utils.dialog('ok',
|
||||
heading='{plex}',
|
||||
line1='%s%s' % (utils.lang(29999),
|
||||
utils.lang(39402)))
|
||||
else:
|
||||
utils.reset(ask_user=False)
|
||||
break
|
||||
|
@ -1603,7 +1594,7 @@ class LibrarySync(Thread):
|
|||
# First sync upon PKC restart. Skipped if very first sync upon
|
||||
# PKC installation has been completed
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
utils.window('plex_dbScan', value="true")
|
||||
LOG.info('Doing initial sync on Kodi startup')
|
||||
if state.SUSPEND_SYNC:
|
||||
LOG.warning('Forcing startup sync even if Kodi is playing')
|
||||
|
@ -1624,7 +1615,7 @@ class LibrarySync(Thread):
|
|||
playlist_monitor = playlists.kodi_playlist_monitor()
|
||||
else:
|
||||
LOG.info('Startup sync has not yet been successful')
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
|
||||
# Currently no db scan, so we can start a new scan
|
||||
|
@ -1643,23 +1634,23 @@ class LibrarySync(Thread):
|
|||
not self.suspend_item_sync()):
|
||||
LOG.info('Doing scheduled full library scan')
|
||||
state.DB_SCAN = True
|
||||
window('plex_dbScan', value="true")
|
||||
utils.window('plex_dbScan', value="true")
|
||||
success = self.maintain_views()
|
||||
if success:
|
||||
success = self.full_sync()
|
||||
if not success and not self.suspend_item_sync():
|
||||
LOG.error('Could not finish scheduled full sync')
|
||||
self.force_dialog = True
|
||||
self.show_kodi_note(lang(39410),
|
||||
self.show_kodi_note(utils.lang(39410),
|
||||
icon='error')
|
||||
self.force_dialog = False
|
||||
elif success:
|
||||
last_sync = now
|
||||
# Full library sync finished successfully
|
||||
self.show_kodi_note(lang(39407))
|
||||
self.show_kodi_note(utils.lang(39407))
|
||||
else:
|
||||
LOG.info('Full sync interrupted')
|
||||
window('plex_dbScan', clear=True)
|
||||
utils.window('plex_dbScan', clear=True)
|
||||
state.DB_SCAN = False
|
||||
elif now - last_time_sync > one_day_in_seconds:
|
||||
LOG.info('Starting daily time sync')
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
from logging import getLogger
|
||||
import variables as v
|
||||
from utils import compare_version, settings
|
||||
|
||||
from . import variables as v
|
||||
from . import utils
|
||||
###############################################################################
|
||||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
LOG = getLogger('PLEX.migration')
|
||||
|
||||
|
||||
def check_migration():
|
||||
log.info('Checking whether we need to migrate something')
|
||||
last_migration = settings('last_migrated_PKC_version')
|
||||
LOG.info('Checking whether we need to migrate something')
|
||||
last_migration = utils.settings('last_migrated_PKC_version')
|
||||
if last_migration == v.ADDON_VERSION:
|
||||
log.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
|
||||
LOG.info('Already migrated to PKC version %s' % v.ADDON_VERSION)
|
||||
# Ensure later migration if user downgraded PKC!
|
||||
settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
||||
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
||||
return
|
||||
|
||||
if not compare_version(last_migration, '1.8.2'):
|
||||
log.info('Migrating to version 1.8.1')
|
||||
if not utils.compare_version(last_migration, '1.8.2'):
|
||||
LOG.info('Migrating to version 1.8.1')
|
||||
# Set the new PKC theMovieDB key
|
||||
settings('themoviedbAPIKey', value='19c90103adb9e98f2172c6a6a3d85dc4')
|
||||
utils.settings('themoviedbAPIKey',
|
||||
value='19c90103adb9e98f2172c6a6a3d85dc4')
|
||||
|
||||
if not compare_version(last_migration, '2.0.25'):
|
||||
log.info('Migrating to version 2.0.24')
|
||||
if not utils.compare_version(last_migration, '2.0.25'):
|
||||
LOG.info('Migrating to version 2.0.24')
|
||||
# Need to re-connect with PMS to pick up on plex.direct URIs
|
||||
settings('ipaddress', value='')
|
||||
settings('port', value='')
|
||||
utils.settings('ipaddress', value='')
|
||||
utils.settings('port', value='')
|
||||
|
||||
settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
||||
utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION)
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from logging import getLogger
|
||||
from re import compile as re_compile
|
||||
from xml.etree.ElementTree import ParseError
|
||||
|
||||
from utils import XmlKodiSetting, reboot_kodi, language as lang
|
||||
from PlexAPI import API
|
||||
import variables as v
|
||||
from . import utils
|
||||
from .plex_api import API
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
||||
REGEX_MUSICPATH = re_compile(r'''^\^(.+)\$$''')
|
||||
LOG = getLogger('PLEX.music')
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -37,9 +34,10 @@ def excludefromscan_music_folders(xml):
|
|||
omit_check=True)
|
||||
paths.append(__turn_to_regex(path))
|
||||
try:
|
||||
with XmlKodiSetting('advancedsettings.xml',
|
||||
force_create=True,
|
||||
top_element='advancedsettings') as xml_file:
|
||||
with utils.XmlKodiSetting(
|
||||
'advancedsettings.xml',
|
||||
force_create=True,
|
||||
top_element='advancedsettings') as xml_file:
|
||||
parent = xml_file.set_setting(['audio', 'excludefromscan'])
|
||||
for path in paths:
|
||||
for element in parent:
|
||||
|
@ -71,7 +69,7 @@ def excludefromscan_music_folders(xml):
|
|||
if reboot is True:
|
||||
# 'New Plex music library detected. Sorry, but we need to
|
||||
# restart Kodi now due to the changes made.'
|
||||
reboot_kodi(lang(39711))
|
||||
utils.reboot_kodi(utils.lang(39711))
|
||||
|
||||
|
||||
def __turn_to_regex(path):
|
||||
|
|
192
resources/lib/path_ops.py
Normal file
192
resources/lib/path_ops.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# File and Path operations
|
||||
#
|
||||
# Kodi xbmc*.*() functions usually take utf-8 encoded commands, thus try_encode
|
||||
# works.
|
||||
# Unfortunatly, working with filenames and paths seems to require an encoding in
|
||||
# the OS' getfilesystemencoding - it will NOT always work with unicode paths.
|
||||
# However, sys.getfilesystemencoding might return None.
|
||||
# Feed unicode to all the functions below and you're fine.
|
||||
import shutil
|
||||
import os
|
||||
from os import path # allows to use path_ops.path.join, for example
|
||||
from distutils import dir_util
|
||||
import xbmc
|
||||
import xbmcvfs
|
||||
|
||||
from .watchdog.utils import unicode_paths
|
||||
|
||||
# Kodi seems to encode in utf-8 in ALL cases (unlike e.g. the OS filesystem)
|
||||
KODI_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
def encode_path(path):
|
||||
"""
|
||||
Filenames and paths are not necessarily utf-8 encoded. Use this function
|
||||
instead of try_encode/trydecode if working with filenames and paths!
|
||||
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
|
||||
for Raspberry Pi)
|
||||
"""
|
||||
return unicode_paths.encode(path)
|
||||
|
||||
|
||||
def decode_path(path):
|
||||
"""
|
||||
Filenames and paths are not necessarily utf-8 encoded. Use this function
|
||||
instead of try_encode/trydecode if working with filenames and paths!
|
||||
(os.walk only feeds on encoded paths. sys.getfilesystemencoding returns None
|
||||
for Raspberry Pi)
|
||||
"""
|
||||
return unicode_paths.decode(path)
|
||||
|
||||
|
||||
def translate_path(path):
|
||||
"""
|
||||
Returns the XBMC translated path [unicode]
|
||||
e.g. Converts 'special://masterprofile/script_data'
|
||||
-> '/home/user/XBMC/UserData/script_data' on Linux.
|
||||
"""
|
||||
translated = xbmc.translatePath(path.encode(KODI_ENCODING, 'strict'))
|
||||
return translated.decode(KODI_ENCODING, 'strict')
|
||||
|
||||
|
||||
def exists(path):
|
||||
"""Returns True if the path [unicode] exists"""
|
||||
return xbmcvfs.exists(path.encode(KODI_ENCODING, 'strict'))
|
||||
|
||||
|
||||
def rmtree(path, *args, **kwargs):
|
||||
"""Recursively delete a directory tree.
|
||||
|
||||
If ignore_errors is set, errors are ignored; otherwise, if onerror
|
||||
is set, it is called to handle the error with arguments (func,
|
||||
path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
|
||||
path is the argument to that function that caused it to fail; and
|
||||
exc_info is a tuple returned by sys.exc_info(). If ignore_errors
|
||||
is false and onerror is None, an exception is raised.
|
||||
|
||||
"""
|
||||
return shutil.rmtree(encode_path(path), *args, **kwargs)
|
||||
|
||||
|
||||
def copyfile(src, dst):
|
||||
"""Copy data from src to dst"""
|
||||
return shutil.copyfile(encode_path(src), encode_path(dst))
|
||||
|
||||
|
||||
def makedirs(path, *args, **kwargs):
|
||||
"""makedirs(path [, mode=0777])
|
||||
|
||||
Super-mkdir; create a leaf directory and all intermediate ones. Works like
|
||||
mkdir, except that any intermediate path segment (not just the rightmost)
|
||||
will be created if it does not exist. This is recursive.
|
||||
"""
|
||||
return os.makedirs(encode_path(path), *args, **kwargs)
|
||||
|
||||
|
||||
def remove(path):
|
||||
"""
|
||||
Remove (delete) the file path. If path is a directory, OSError is raised;
|
||||
see rmdir() below to remove a directory. This is identical to the unlink()
|
||||
function documented below. On Windows, attempting to remove a file that is
|
||||
in use causes an exception to be raised; on Unix, the directory entry is
|
||||
removed but the storage allocated to the file is not made available until
|
||||
the original file is no longer in use.
|
||||
"""
|
||||
return os.remove(encode_path(path))
|
||||
|
||||
|
||||
def walk(top, topdown=True, onerror=None, followlinks=False):
|
||||
"""
|
||||
Directory tree generator.
|
||||
|
||||
For each directory in the directory tree rooted at top (including top
|
||||
itself, but excluding '.' and '..'), yields a 3-tuple
|
||||
|
||||
dirpath, dirnames, filenames
|
||||
|
||||
dirpath is a string, the path to the directory. dirnames is a list of
|
||||
the names of the subdirectories in dirpath (excluding '.' and '..').
|
||||
filenames is a list of the names of the non-directory files in dirpath.
|
||||
Note that the names in the lists are just names, with no path components.
|
||||
To get a full path (which begins with top) to a file or directory in
|
||||
dirpath, do os.path.join(dirpath, name).
|
||||
|
||||
If optional arg 'topdown' is true or not specified, the triple for a
|
||||
directory is generated before the triples for any of its subdirectories
|
||||
(directories are generated top down). If topdown is false, the triple
|
||||
for a directory is generated after the triples for all of its
|
||||
subdirectories (directories are generated bottom up).
|
||||
|
||||
When topdown is true, the caller can modify the dirnames list in-place
|
||||
(e.g., via del or slice assignment), and walk will only recurse into the
|
||||
subdirectories whose names remain in dirnames; this can be used to prune the
|
||||
search, or to impose a specific order of visiting. Modifying dirnames when
|
||||
topdown is false is ineffective, since the directories in dirnames have
|
||||
already been generated by the time dirnames itself is generated. No matter
|
||||
the value of topdown, the list of subdirectories is retrieved before the
|
||||
tuples for the directory and its subdirectories are generated.
|
||||
|
||||
By default errors from the os.listdir() call are ignored. If
|
||||
optional arg 'onerror' is specified, it should be a function; it
|
||||
will be called with one argument, an os.error instance. It can
|
||||
report the error to continue with the walk, or raise the exception
|
||||
to abort the walk. Note that the filename is available as the
|
||||
filename attribute of the exception object.
|
||||
|
||||
By default, os.walk does not follow symbolic links to subdirectories on
|
||||
systems that support them. In order to get this functionality, set the
|
||||
optional argument 'followlinks' to true.
|
||||
|
||||
Caution: if you pass a relative pathname for top, don't change the
|
||||
current working directory between resumptions of walk. walk never
|
||||
changes the current directory, and assumes that the client doesn't
|
||||
either.
|
||||
|
||||
Example:
|
||||
|
||||
import os
|
||||
from os.path import join, getsize
|
||||
for root, dirs, files in os.walk('python/Lib/email'):
|
||||
print root, "consumes",
|
||||
print sum([getsize(join(root, name)) for name in files]),
|
||||
print "bytes in", len(files), "non-directory files"
|
||||
if 'CVS' in dirs:
|
||||
dirs.remove('CVS') # don't visit CVS directories
|
||||
|
||||
"""
|
||||
# Get all the results from os.walk and store them in a list
|
||||
walker = list(os.walk(encode_path(top),
|
||||
topdown,
|
||||
onerror,
|
||||
followlinks))
|
||||
for top, dirs, nondirs in walker:
|
||||
yield (decode_path(top),
|
||||
[decode_path(x) for x in dirs],
|
||||
[decode_path(x) for x in nondirs])
|
||||
|
||||
|
||||
def copy_tree(src, dst, *args, **kwargs):
|
||||
"""
|
||||
Copy an entire directory tree 'src' to a new location 'dst'.
|
||||
|
||||
Both 'src' and 'dst' must be directory names. If 'src' is not a
|
||||
directory, raise DistutilsFileError. If 'dst' does not exist, it is
|
||||
created with 'mkpath()'. The end result of the copy is that every
|
||||
file in 'src' is copied to 'dst', and directories under 'src' are
|
||||
recursively copied to 'dst'. Return the list of files that were
|
||||
copied or might have been copied, using their output name. The
|
||||
return value is unaffected by 'update' or 'dry_run': it is simply
|
||||
the list of all files under 'src', with the names changed to be
|
||||
under 'dst'.
|
||||
|
||||
'preserve_mode' and 'preserve_times' are the same as for
|
||||
'copy_file'; note that they only apply to regular files, not to
|
||||
directories. If 'preserve_symlinks' is true, symlinks will be
|
||||
copied as symlinks (on platforms that support them!); otherwise
|
||||
(the default), the destination of the symlink will be copied.
|
||||
'update' and 'verbose' are the same as for 'copy_file'.
|
||||
"""
|
||||
src = encode_path(src)
|
||||
dst = encode_path(dst)
|
||||
return dir_util.copy_tree(src, dst, *args, **kwargs)
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from cPickle import dumps, loads
|
||||
|
||||
from xbmcgui import Window
|
||||
from xbmc import log, LOGDEBUG
|
||||
|
||||
###############################################################################
|
||||
WINDOW = Window(10000)
|
||||
PREFIX = 'PLEX.%s: ' % __name__
|
||||
PREFIX = 'PLEX.pickler: '
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
from xbmcgui import ListItem
|
||||
|
||||
|
||||
def convert_PKC_to_listitem(PKC_listitem):
|
||||
def convert_pkc_to_listitem(pkc_listitem):
|
||||
"""
|
||||
Insert a PKC_listitem and you will receive a valid XBMC listitem
|
||||
Insert a PKCListItem() and you will receive a valid XBMC listitem
|
||||
"""
|
||||
data = PKC_listitem.data
|
||||
data = pkc_listitem.data
|
||||
listitem = ListItem(label=data.get('label'),
|
||||
label2=data.get('label2'),
|
||||
path=data.get('path'))
|
||||
|
@ -26,7 +26,7 @@ def convert_PKC_to_listitem(PKC_listitem):
|
|||
return listitem
|
||||
|
||||
|
||||
class PKC_ListItem(object):
|
||||
class PKCListItem(object):
|
||||
"""
|
||||
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
|
||||
when pickling!
|
|
@ -3,42 +3,36 @@ Used to kick off Kodi playback
|
|||
"""
|
||||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from os.path import join
|
||||
|
||||
from xbmc import Player, sleep
|
||||
|
||||
from PlexAPI import API
|
||||
from PlexFunctions import GetPlexMetadata, init_plex_playqueue
|
||||
from downloadutils import DownloadUtils as DU
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import playlist_func as PL
|
||||
import playqueue as PQ
|
||||
from playutils import PlayUtils
|
||||
from PKC_listitem import PKC_ListItem
|
||||
from pickler import pickle_me, Playback_Successful
|
||||
import json_rpc as js
|
||||
from utils import settings, dialog, language as lang, try_encode
|
||||
from plexbmchelper.subscribers import LOCKER
|
||||
import variables as v
|
||||
import state
|
||||
from .plex_api import API
|
||||
from . import plex_functions as PF
|
||||
from . import utils
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import playlist_func as PL
|
||||
from . import playqueue as PQ
|
||||
from . import json_rpc as js
|
||||
from . import pickler
|
||||
from .playutils import PlayUtils
|
||||
from .pkc_listitem import PKCListItem
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.playback')
|
||||
# Do we need to return ultimately with a setResolvedUrl?
|
||||
RESOLVE = True
|
||||
# We're "failing" playback with a video of 0 length
|
||||
NULL_VIDEO = join(v.ADDON_FOLDER, 'addons', v.ADDON_ID, 'empty_video.mp4')
|
||||
###############################################################################
|
||||
|
||||
|
||||
@LOCKER.lockthis
|
||||
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
||||
"""
|
||||
Hit this function for addon path playback, Plex trailers, etc.
|
||||
Will setup playback first, then on second call complete playback.
|
||||
|
||||
Will set Playback_Successful() with potentially a PKC_ListItem() attached
|
||||
Will set Playback_Successful() with potentially a PKCListItem() attached
|
||||
(to be consumed by setResolvedURL in default.py)
|
||||
|
||||
If trailers or additional (movie-)parts are added, default.py is released
|
||||
|
@ -50,14 +44,14 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
|||
service.py Python instance
|
||||
"""
|
||||
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, '
|
||||
'resolve %s,', plex_id, plex_type, path, resolve)
|
||||
'resolve %s', plex_id, plex_type, path, resolve)
|
||||
global RESOLVE
|
||||
# If started via Kodi context menu, we never resolve
|
||||
RESOLVE = resolve if not state.CONTEXT_MENU_PLAY else False
|
||||
if not state.AUTHENTICATED:
|
||||
LOG.error('Not yet authenticated for PMS, abort starting playback')
|
||||
# "Unauthorized for PMS"
|
||||
dialog('notification', lang(29999), lang(30017))
|
||||
utils.dialog('notification', utils.lang(29999), utils.lang(30017))
|
||||
_ensure_resolve(abort=True)
|
||||
return
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
|
@ -74,7 +68,10 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
|||
except KeyError:
|
||||
LOG.error('Still no position - abort')
|
||||
# "Play error"
|
||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
_ensure_resolve(abort=True)
|
||||
return
|
||||
# HACK to detect playback of playlists for add-on paths
|
||||
|
@ -97,14 +94,21 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
|||
try:
|
||||
item = playqueue.items[pos]
|
||||
except IndexError:
|
||||
LOG.debug('PKC playqueue yet empty, need to initialize playback')
|
||||
initiate = True
|
||||
else:
|
||||
initiate = True if item.plex_id != plex_id else False
|
||||
if initiate:
|
||||
_playback_init(plex_id, plex_type, playqueue, pos)
|
||||
else:
|
||||
# kick off playback on second pass
|
||||
_conclude_playback(playqueue, pos)
|
||||
if item.plex_id != plex_id:
|
||||
LOG.debug('Received new plex_id %s, expected %s. Init playback',
|
||||
plex_id, item.plex_id)
|
||||
initiate = True
|
||||
else:
|
||||
initiate = False
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
if initiate:
|
||||
_playback_init(plex_id, plex_type, playqueue, pos)
|
||||
else:
|
||||
# kick off playback on second pass
|
||||
_conclude_playback(playqueue, pos)
|
||||
|
||||
|
||||
def _playlist_playback(plex_id, plex_type):
|
||||
|
@ -124,13 +128,16 @@ def _playlist_playback(plex_id, plex_type):
|
|||
for the next item in line :-)
|
||||
(by the way: trying to get active Kodi player id will return [])
|
||||
"""
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
xml = PF.GetPlexMetadata(plex_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (IndexError, TypeError, AttributeError):
|
||||
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
|
||||
# "Play error"
|
||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
_ensure_resolve(abort=True)
|
||||
return
|
||||
# Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback
|
||||
|
@ -150,13 +157,16 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
|||
Playback setup if Kodi starts playing an item for the first time.
|
||||
"""
|
||||
LOG.info('Initializing PKC playback')
|
||||
xml = GetPlexMetadata(plex_id)
|
||||
xml = PF.GetPlexMetadata(plex_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (IndexError, TypeError, AttributeError):
|
||||
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
|
||||
# "Play error"
|
||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
_ensure_resolve(abort=True)
|
||||
return
|
||||
if playqueue.kodi_pl.size() > 1:
|
||||
|
@ -179,10 +189,12 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
|||
api = API(xml[0])
|
||||
trailers = False
|
||||
if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and
|
||||
settings('enableCinema') == "true"):
|
||||
if settings('askCinema') == "true":
|
||||
utils.settings('enableCinema') == "true"):
|
||||
if utils.settings('askCinema') == "true":
|
||||
# "Play trailers?"
|
||||
trailers = dialog('yesno', lang(29999), lang(33016))
|
||||
trailers = utils.dialog('yesno',
|
||||
utils.lang(29999),
|
||||
utils.lang(33016))
|
||||
trailers = True if trailers else False
|
||||
else:
|
||||
trailers = True
|
||||
|
@ -198,15 +210,18 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
|
|||
playqueue.clear()
|
||||
if plex_type != v.PLEX_TYPE_CLIP:
|
||||
# Post to the PMS to create a playqueue - in any case due to Companion
|
||||
xml = init_plex_playqueue(plex_id,
|
||||
xml.attrib.get('librarySectionUUID'),
|
||||
mediatype=plex_type,
|
||||
trailers=trailers)
|
||||
xml = PF.init_plex_playqueue(plex_id,
|
||||
xml.attrib.get('librarySectionUUID'),
|
||||
mediatype=plex_type,
|
||||
trailers=trailers)
|
||||
if xml is None:
|
||||
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
|
||||
plex_id, xml.attrib.get('librarySectionUUID'))
|
||||
# "Play error"
|
||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
# Do NOT use _ensure_resolve() because we resolved above already
|
||||
state.CONTEXT_MENU_PLAY = False
|
||||
state.FORCE_TRANSCODE = False
|
||||
|
@ -253,9 +268,13 @@ def _ensure_resolve(abort=False):
|
|||
# Because playback won't start with context menu play
|
||||
state.PKC_CAUSED_STOP = True
|
||||
state.PKC_CAUSED_STOP_DONE = False
|
||||
result = Playback_Successful()
|
||||
result.listitem = PKC_ListItem(path=NULL_VIDEO)
|
||||
pickle_me(result)
|
||||
if not abort:
|
||||
result = pickler.Playback_Successful()
|
||||
result.listitem = PKCListItem(path=v.NULL_VIDEO)
|
||||
pickler.pickle_me(result)
|
||||
else:
|
||||
# Shows PKC error message
|
||||
pickler.pickle_me(None)
|
||||
if abort:
|
||||
# Reset some playback variables
|
||||
state.CONTEXT_MENU_PLAY = False
|
||||
|
@ -308,7 +327,7 @@ def _prep_playlist_stack(xml):
|
|||
# Need to redirect again to PKC to conclude playback
|
||||
path = api.path()
|
||||
listitem = api.create_listitem()
|
||||
listitem.setPath(try_encode(path))
|
||||
listitem.setPath(utils.try_encode(path))
|
||||
else:
|
||||
# Will add directly via the Kodi DB
|
||||
path = None
|
||||
|
@ -373,8 +392,8 @@ def _conclude_playback(playqueue, pos):
|
|||
return PKC listitem attached to result
|
||||
"""
|
||||
LOG.info('Concluding playback for playqueue position %s', pos)
|
||||
result = Playback_Successful()
|
||||
listitem = PKC_ListItem()
|
||||
result = pickler.Playback_Successful()
|
||||
listitem = PKCListItem()
|
||||
item = playqueue.items[pos]
|
||||
if item.xml is not None:
|
||||
# Got a Plex element
|
||||
|
@ -385,7 +404,7 @@ def _conclude_playback(playqueue, pos):
|
|||
playurl = playutils.getPlayUrl()
|
||||
else:
|
||||
playurl = item.file
|
||||
listitem.setPath(try_encode(playurl))
|
||||
listitem.setPath(utils.try_encode(playurl))
|
||||
if item.playmethod == 'DirectStream':
|
||||
listitem.setSubtitles(api.cache_external_subs())
|
||||
elif item.playmethod == 'Transcode':
|
||||
|
@ -405,7 +424,7 @@ def _conclude_playback(playqueue, pos):
|
|||
listitem.setProperty('resumetime', str(item.offset))
|
||||
# Reset the resumable flag
|
||||
result.listitem = listitem
|
||||
pickle_me(result)
|
||||
pickler.pickle_me(result)
|
||||
LOG.info('Done concluding playback')
|
||||
|
||||
|
||||
|
@ -424,7 +443,7 @@ def process_indirect(key, offset, resolve=True):
|
|||
key, offset, resolve)
|
||||
global RESOLVE
|
||||
RESOLVE = resolve
|
||||
result = Playback_Successful()
|
||||
result = pickler.Playback_Successful()
|
||||
if key.startswith('http') or key.startswith('{server}'):
|
||||
xml = DU().downloadUrl(key)
|
||||
elif key.startswith('/system/services'):
|
||||
|
@ -441,7 +460,7 @@ def process_indirect(key, offset, resolve=True):
|
|||
offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset))
|
||||
# Todo: implement offset
|
||||
api = API(xml[0])
|
||||
listitem = PKC_ListItem()
|
||||
listitem = PKCListItem()
|
||||
api.create_listitem(listitem)
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
||||
|
@ -462,14 +481,14 @@ def process_indirect(key, offset, resolve=True):
|
|||
return
|
||||
playurl = xml[0].attrib['key']
|
||||
item.file = playurl
|
||||
listitem.setPath(try_encode(playurl))
|
||||
listitem.setPath(utils.try_encode(playurl))
|
||||
playqueue.items.append(item)
|
||||
if resolve is True:
|
||||
result.listitem = listitem
|
||||
pickle_me(result)
|
||||
pickler.pickle_me(result)
|
||||
else:
|
||||
thread = Thread(target=Player().play,
|
||||
args={'item': try_encode(playurl),
|
||||
args={'item': utils.try_encode(playurl),
|
||||
'listitem': listitem})
|
||||
thread.setDaemon(True)
|
||||
LOG.info('Done initializing PKC playback, starting Kodi player')
|
||||
|
|
|
@ -4,16 +4,16 @@ from logging import getLogger
|
|||
from threading import Thread
|
||||
from urlparse import parse_qsl
|
||||
|
||||
import playback
|
||||
from context_entry import ContextMenu
|
||||
import state
|
||||
import json_rpc as js
|
||||
from pickler import pickle_me, Playback_Successful
|
||||
import kodidb_functions as kodidb
|
||||
from . import playback
|
||||
from . import context_entry
|
||||
from . import json_rpc as js
|
||||
from . import pickler
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.playback_starter')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -29,7 +29,7 @@ class PlaybackStarter(Thread):
|
|||
except ValueError:
|
||||
# E.g. other add-ons scanning for Extras folder
|
||||
LOG.debug('Detected 3rd party add-on call - ignoring')
|
||||
pickle_me(Playback_Successful())
|
||||
pickler.pickle_me(pickler.Playback_Successful())
|
||||
return
|
||||
params = dict(parse_qsl(params))
|
||||
mode = params.get('mode')
|
||||
|
@ -54,10 +54,10 @@ class PlaybackStarter(Thread):
|
|||
else:
|
||||
LOG.error('Could not find tv show id for %s', item)
|
||||
if resolve:
|
||||
pickle_me(Playback_Successful())
|
||||
pickler.pickle_me(pickler.Playback_Successful())
|
||||
elif mode == 'context_menu':
|
||||
ContextMenu(kodi_id=params.get('kodi_id'),
|
||||
kodi_type=params.get('kodi_type'))
|
||||
context_entry.ContextMenu(kodi_id=params.get('kodi_id'),
|
||||
kodi_type=params.get('kodi_type'))
|
||||
|
||||
def run(self):
|
||||
queue = state.COMMAND_PIPELINE_QUEUE
|
||||
|
|
|
@ -3,25 +3,23 @@
|
|||
Collection of functions associated with Kodi and Plex playlists and playqueues
|
||||
"""
|
||||
from logging import getLogger
|
||||
import os
|
||||
import urllib
|
||||
from urlparse import parse_qsl, urlsplit
|
||||
from re import compile as re_compile
|
||||
|
||||
import plexdb_functions as plexdb
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import try_decode, try_encode
|
||||
from PlexAPI import API
|
||||
from PlexFunctions import GetPlexMetadata
|
||||
from kodidb_functions import kodiid_from_filename
|
||||
import json_rpc as js
|
||||
import variables as v
|
||||
from .plex_api import API
|
||||
from . import plex_functions as PF
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.playlist_func')
|
||||
|
||||
REGEX = re_compile(r'''metadata%2F(\d+)''')
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -42,22 +40,23 @@ class PlaylistObjectBaseclase(object):
|
|||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Print the playlist, e.g. to log. Returns utf-8 encoded string
|
||||
Print the playlist, e.g. to log. Returns unicode
|
||||
"""
|
||||
answ = u'{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
|
||||
answ = '{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id)
|
||||
# For some reason, can't use dir directly
|
||||
for key in self.__dict__:
|
||||
if key in ('id', 'kodi_pl'):
|
||||
continue
|
||||
if isinstance(getattr(self, key), str):
|
||||
answ += '\'%s\': \'%s\', ' % (key,
|
||||
try_decode(getattr(self, key)))
|
||||
utils.try_decode(getattr(self,
|
||||
key)))
|
||||
elif isinstance(getattr(self, key), unicode):
|
||||
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
||||
else:
|
||||
# e.g. int
|
||||
answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key)))
|
||||
return try_encode(answ + '}}')
|
||||
return answ + '}}'
|
||||
|
||||
|
||||
class Playlist_Object(PlaylistObjectBaseclase):
|
||||
|
@ -81,7 +80,9 @@ class Playlist_Object(PlaylistObjectBaseclase):
|
|||
|
||||
@kodi_path.setter
|
||||
def kodi_path(self, path):
|
||||
file = os.path.basename(path)
|
||||
if not isinstance(path, unicode):
|
||||
raise RuntimeError('Path is %s, not unicode!' % type(path))
|
||||
file = path_ops.path.basename(path)
|
||||
try:
|
||||
self.kodi_filename, self.kodi_extension = file.split('.', 1)
|
||||
except ValueError:
|
||||
|
@ -219,16 +220,17 @@ class Playlist_Item(object):
|
|||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Print the playlist item, e.g. to log. Returns utf-8 encoded string
|
||||
Print the playlist item, e.g. to log. Returns unicode
|
||||
"""
|
||||
answ = (u'{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
|
||||
answ = ('{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', '
|
||||
% (self.__class__.__name__, self.id, self.plex_id))
|
||||
for key in self.__dict__:
|
||||
if key in ('id', 'plex_id', 'xml'):
|
||||
continue
|
||||
if isinstance(getattr(self, key), str):
|
||||
answ += '\'%s\': \'%s\', ' % (key,
|
||||
try_decode(getattr(self, key)))
|
||||
utils.try_decode(getattr(self,
|
||||
key)))
|
||||
elif isinstance(getattr(self, key), unicode):
|
||||
answ += '\'%s\': \'%s\', ' % (key, getattr(self, key))
|
||||
else:
|
||||
|
@ -238,7 +240,7 @@ class Playlist_Item(object):
|
|||
answ += '\'xml\': None}}'
|
||||
else:
|
||||
answ += '\'xml\': \'%s\'}}' % self.xml.tag
|
||||
return try_encode(answ)
|
||||
return answ
|
||||
|
||||
def plex_stream_index(self, kodi_stream_index, stream_type):
|
||||
"""
|
||||
|
@ -317,7 +319,7 @@ def playlist_item_from_kodi(kodi_item):
|
|||
item.plex_type = query.get('itemType')
|
||||
if item.plex_id is None and item.file is not None:
|
||||
item.uri = ('library://whatever/item/%s'
|
||||
% urllib.quote(try_encode(item.file), safe=''))
|
||||
% urllib.quote(utils.try_encode(item.file), safe=''))
|
||||
else:
|
||||
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
|
||||
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
|
||||
|
@ -343,17 +345,20 @@ def verify_kodi_item(plex_id, kodi_item):
|
|||
# Need more info since we don't have kodi_id nor type. Use file path.
|
||||
if (kodi_item['file'].startswith('plugin') or
|
||||
kodi_item['file'].startswith('http')):
|
||||
raise PlaylistError('kodi_item cannot be used for Plex playback')
|
||||
LOG.error('kodi_item %s cannot be used for Plex playback', kodi_item)
|
||||
raise PlaylistError
|
||||
LOG.debug('Starting research for Kodi id since we didnt get one: %s',
|
||||
kodi_item)
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_MOVIE)
|
||||
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_MOVIE)
|
||||
kodi_item['type'] = v.KODI_TYPE_MOVIE
|
||||
if kodi_id is None:
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_EPISODE)
|
||||
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_EPISODE)
|
||||
kodi_item['type'] = v.KODI_TYPE_EPISODE
|
||||
if kodi_id is None:
|
||||
kodi_id, _ = kodiid_from_filename(kodi_item['file'], v.KODI_TYPE_SONG)
|
||||
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'],
|
||||
v.KODI_TYPE_SONG)
|
||||
kodi_item['type'] = v.KODI_TYPE_SONG
|
||||
kodi_item['id'] = kodi_id
|
||||
kodi_item['type'] = None if kodi_id is None else kodi_item['type']
|
||||
|
@ -519,8 +524,9 @@ def init_plex_playqueue(playlist, plex_id=None, kodi_item=None):
|
|||
# Need to get the details for the playlist item
|
||||
item = playlist_item_from_xml(xml[0])
|
||||
except (KeyError, IndexError, TypeError):
|
||||
raise PlaylistError('Could not init Plex playlist with plex_id %s and '
|
||||
'kodi_item %s' % (plex_id, kodi_item))
|
||||
LOG.error('Could not init Plex playlist: plex_id %s, kodi_item %s',
|
||||
plex_id, kodi_item)
|
||||
raise PlaylistError
|
||||
playlist.items.append(item)
|
||||
LOG.debug('Initialized the playqueue on the Plex side: %s', playlist)
|
||||
return item
|
||||
|
@ -683,7 +689,7 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
|
|||
item = playlist_item_from_kodi(
|
||||
{'id': kodi_id, 'type': kodi_type, 'file': file})
|
||||
if item.plex_id is not None:
|
||||
xml = GetPlexMetadata(item.plex_id)
|
||||
xml = PF.GetPlexMetadata(item.plex_id)
|
||||
item.xml = xml[-1]
|
||||
playlist.items.insert(pos, item)
|
||||
return item
|
||||
|
@ -859,11 +865,12 @@ def get_plextype_from_xml(xml):
|
|||
returns None if unsuccessful
|
||||
"""
|
||||
try:
|
||||
plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0]
|
||||
plex_id = utils.REGEX_PLEX_ID_FROM_URL.findall(
|
||||
xml.attrib['playQueueSourceURI'])[0]
|
||||
except IndexError:
|
||||
LOG.error('Could not get plex_id from xml: %s', xml.attrib)
|
||||
return
|
||||
new_xml = GetPlexMetadata(plex_id)
|
||||
new_xml = PF.GetPlexMetadata(plex_id)
|
||||
try:
|
||||
new_xml[0].attrib
|
||||
except (TypeError, IndexError, AttributeError):
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from logging import getLogger
|
||||
import os
|
||||
import sys
|
||||
from threading import Lock
|
||||
|
||||
from xbmcvfs import exists
|
||||
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
import playlist_func as PL
|
||||
from PlexAPI import API
|
||||
import kodidb_functions as kodidb
|
||||
import plexdb_functions as plexdb
|
||||
import utils
|
||||
import variables as v
|
||||
import state
|
||||
from .watchdog.events import FileSystemEventHandler
|
||||
from .watchdog.observers import Observer
|
||||
from . import playlist_func as PL
|
||||
from .plex_api import API
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
||||
# Necessary to temporarily hold back librarysync/websocket listener when doing
|
||||
# a full sync
|
||||
LOCK = Lock()
|
||||
LOCKER = utils.LockFunction(LOCK)
|
||||
LOG = getLogger('PLEX.playlists')
|
||||
|
||||
# Which playlist formates are supported by PKC?
|
||||
SUPPORTED_FILETYPES = (
|
||||
|
@ -39,12 +30,6 @@ EVENT_TYPE_DELETED = 'deleted'
|
|||
EVENT_TYPE_CREATED = 'created'
|
||||
EVENT_TYPE_MODIFIED = 'modified'
|
||||
|
||||
# m3u files do not have encoding specified
|
||||
if v.PLATFORM == 'Windows':
|
||||
ENCODING = 'mbcs'
|
||||
else:
|
||||
ENCODING = sys.getdefaultencoding()
|
||||
|
||||
|
||||
def create_plex_playlist(playlist):
|
||||
"""
|
||||
|
@ -106,20 +91,21 @@ def create_kodi_playlist(plex_id=None, updated_at=None):
|
|||
playlist.plex_updatedat = updated_at
|
||||
LOG.debug('Creating new Kodi playlist from Plex playlist: %s', playlist)
|
||||
name = utils.valid_filename(playlist.plex_name)
|
||||
path = os.path.join(v.PLAYLIST_PATH, playlist.type, '%s.m3u' % name)
|
||||
while exists(path) or playlist_object_from_db(path=path):
|
||||
path = path_ops.path.join(v.PLAYLIST_PATH, playlist.type, '%s.m3u' % name)
|
||||
while path_ops.exists(path) or playlist_object_from_db(path=path):
|
||||
# In case the Plex playlist names are not unique
|
||||
occurance = utils.REGEX_FILE_NUMBERING.search(path)
|
||||
if not occurance:
|
||||
path = os.path.join(v.PLAYLIST_PATH,
|
||||
playlist.type,
|
||||
'%s_01.m3u' % name[:min(len(name), 248)])
|
||||
path = path_ops.path.join(v.PLAYLIST_PATH,
|
||||
playlist.type,
|
||||
'%s_01.m3u' % name[:min(len(name), 248)])
|
||||
else:
|
||||
occurance = int(occurance.group(1)) + 1
|
||||
path = os.path.join(v.PLAYLIST_PATH,
|
||||
playlist.type,
|
||||
'%s_%02d.m3u' % (name[:min(len(name), 248)],
|
||||
occurance))
|
||||
path = path_ops.path.join(v.PLAYLIST_PATH,
|
||||
playlist.type,
|
||||
'%s_%02d.m3u' % (name[:min(len(name),
|
||||
248)],
|
||||
occurance))
|
||||
LOG.debug('Kodi playlist path: %s', path)
|
||||
playlist.kodi_path = path
|
||||
# Derive filename close to Plex playlist name
|
||||
|
@ -137,7 +123,7 @@ def delete_kodi_playlist(playlist):
|
|||
Returns None or raises PL.PlaylistError
|
||||
"""
|
||||
try:
|
||||
os.remove(playlist.kodi_path)
|
||||
path_ops.remove(playlist.kodi_path)
|
||||
except (OSError, IOError) as err:
|
||||
LOG.error('Could not delete Kodi playlist file %s. Error:\n %s: %s',
|
||||
playlist, err.errno, err.strerror)
|
||||
|
@ -187,10 +173,10 @@ def m3u_to_plex_ids(playlist):
|
|||
Adapter to process *.m3u playlist files. Encoding is not uniform!
|
||||
"""
|
||||
plex_ids = list()
|
||||
with open(playlist.kodi_path, 'rb') as f:
|
||||
with open(path_ops.encode_path(playlist.kodi_path), 'rb') as f:
|
||||
text = f.read()
|
||||
try:
|
||||
text = text.decode(ENCODING)
|
||||
text = text.decode(v.M3U_ENCODING)
|
||||
except UnicodeDecodeError:
|
||||
LOG.warning('Fallback to ISO-8859-1 decoding for %s', playlist)
|
||||
text = text.decode('ISO-8859-1')
|
||||
|
@ -217,15 +203,15 @@ def _write_playlist_to_file(playlist, xml):
|
|||
Feed with playlist [Playlist_Object]. Will write the playlist to a m3u file
|
||||
Returns None or raises PL.PlaylistError
|
||||
"""
|
||||
text = u'#EXTCPlayListM3U::M3U\n'
|
||||
text = '#EXTCPlayListM3U::M3U\n'
|
||||
for element in xml:
|
||||
api = API(element)
|
||||
text += (u'#EXTINF:%s,%s\n%s\n'
|
||||
text += ('#EXTINF:%s,%s\n%s\n'
|
||||
% (api.runtime(), api.title(), api.path()))
|
||||
text += '\n'
|
||||
text = text.encode(ENCODING, 'ignore')
|
||||
text = text.encode(v.M3U_ENCODING, 'strict')
|
||||
try:
|
||||
with open(playlist.kodi_path, 'wb') as f:
|
||||
with open(path_ops.encode_path(playlist.kodi_path), 'wb') as f:
|
||||
f.write(text)
|
||||
except (OSError, IOError) as err:
|
||||
LOG.error('Could not write Kodi playlist file: %s', playlist)
|
||||
|
@ -274,41 +260,49 @@ def _kodi_playlist_identical(xml_element):
|
|||
pass
|
||||
|
||||
|
||||
@LOCKER.lockthis
|
||||
def process_websocket(plex_id, updated_at, state):
|
||||
def process_websocket(plex_id, updated_at, status):
|
||||
"""
|
||||
Hit by librarysync to process websocket messages concerning playlists
|
||||
"""
|
||||
create = False
|
||||
playlist = playlist_object_from_db(plex_id=plex_id)
|
||||
try:
|
||||
if playlist and state == 9:
|
||||
LOG.debug('Plex deletion of playlist detected: %s', playlist)
|
||||
delete_kodi_playlist(playlist)
|
||||
elif playlist and playlist.plex_updatedat == updated_at:
|
||||
LOG.debug('Playlist with id %s already synced: %s',
|
||||
plex_id, playlist)
|
||||
elif playlist:
|
||||
LOG.debug('Change of Plex playlist detected: %s', playlist)
|
||||
delete_kodi_playlist(playlist)
|
||||
create = True
|
||||
elif not playlist and not state == 9:
|
||||
LOG.debug('Creation of new Plex playlist detected: %s', plex_id)
|
||||
create = True
|
||||
# To the actual work
|
||||
if create:
|
||||
create_kodi_playlist(plex_id=plex_id, updated_at=updated_at)
|
||||
except PL.PlaylistError:
|
||||
pass
|
||||
with state.LOCK_PLAYLISTS:
|
||||
playlist = playlist_object_from_db(plex_id=plex_id)
|
||||
try:
|
||||
if playlist and status == 9:
|
||||
LOG.debug('Plex deletion of playlist detected: %s', playlist)
|
||||
delete_kodi_playlist(playlist)
|
||||
elif playlist and playlist.plex_updatedat == updated_at:
|
||||
LOG.debug('Playlist with id %s already synced: %s',
|
||||
plex_id, playlist)
|
||||
elif playlist:
|
||||
LOG.debug('Change of Plex playlist detected: %s', playlist)
|
||||
delete_kodi_playlist(playlist)
|
||||
create = True
|
||||
elif not playlist and not status == 9:
|
||||
LOG.debug('Creation of new Plex playlist detected: %s',
|
||||
plex_id)
|
||||
create = True
|
||||
# To the actual work
|
||||
if create:
|
||||
create_kodi_playlist(plex_id=plex_id, updated_at=updated_at)
|
||||
except PL.PlaylistError:
|
||||
pass
|
||||
|
||||
|
||||
@LOCKER.lockthis
|
||||
def full_sync():
|
||||
"""
|
||||
Full sync of playlists between Kodi and Plex. Returns True is successful,
|
||||
False otherwise
|
||||
"""
|
||||
LOG.info('Starting playlist full sync')
|
||||
with state.LOCK_PLAYLISTS:
|
||||
return _full_sync()
|
||||
|
||||
|
||||
def _full_sync():
|
||||
"""
|
||||
Need to lock because we're messing with playlists
|
||||
"""
|
||||
# Get all Plex playlists
|
||||
xml = PL.get_all_playlists()
|
||||
if xml is None:
|
||||
|
@ -332,7 +326,7 @@ def full_sync():
|
|||
elif playlist.plex_updatedat != api.updated_at():
|
||||
LOG.debug('Detected changed Plex playlist %s: %s',
|
||||
api.plex_id(), api.title())
|
||||
if exists(playlist.kodi_path):
|
||||
if path_ops.exists(playlist.kodi_path):
|
||||
delete_kodi_playlist(playlist)
|
||||
else:
|
||||
update_plex_table(playlist, delete=True)
|
||||
|
@ -345,7 +339,7 @@ def full_sync():
|
|||
pass
|
||||
# Get rid of old Plex playlists that were deleted on the Plex side
|
||||
for plex_id in old_plex_ids:
|
||||
playlist = playlist_object_from_db(plex_id=api.plex_id())
|
||||
playlist = playlist_object_from_db(plex_id=plex_id)
|
||||
if playlist:
|
||||
LOG.debug('Removing outdated Plex playlist %s from %s',
|
||||
playlist.plex_name, playlist.kodi_path)
|
||||
|
@ -360,7 +354,7 @@ def full_sync():
|
|||
if state.ENABLE_MUSIC:
|
||||
master_paths.append(v.PLAYLIST_PATH_MUSIC)
|
||||
for master_path in master_paths:
|
||||
for root, _, files in os.walk(master_path):
|
||||
for root, _, files in path_ops.walk(master_path):
|
||||
for file in files:
|
||||
try:
|
||||
extension = file.rsplit('.', 1)[1]
|
||||
|
@ -368,7 +362,7 @@ def full_sync():
|
|||
continue
|
||||
if extension not in SUPPORTED_FILETYPES:
|
||||
continue
|
||||
path = os.path.join(root, file)
|
||||
path = path_ops.path.join(root, file)
|
||||
kodi_hash = utils.generate_file_md5(path)
|
||||
playlist = playlist_object_from_db(kodi_hash=kodi_hash)
|
||||
playlist_2 = playlist_object_from_db(path=path)
|
||||
|
@ -438,7 +432,7 @@ class PlaylistEventhandler(FileSystemEventHandler):
|
|||
EVENT_TYPE_DELETED: self.on_deleted,
|
||||
}
|
||||
event_type = event.event_type
|
||||
with LOCK:
|
||||
with state.LOCK_PLAYLISTS:
|
||||
_method_map[event_type](event)
|
||||
|
||||
def on_created(self, event):
|
||||
|
|
|
@ -3,25 +3,20 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
|
|||
"""
|
||||
from logging import getLogger
|
||||
from threading import Thread
|
||||
from re import compile as re_compile
|
||||
import xbmc
|
||||
|
||||
from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO, sleep
|
||||
|
||||
from utils import thread_methods
|
||||
import playlist_func as PL
|
||||
from PlexFunctions import GetAllPlexChildren
|
||||
from PlexAPI import API
|
||||
from plexbmchelper.subscribers import LOCK
|
||||
from playback import play_xml
|
||||
import json_rpc as js
|
||||
import variables as v
|
||||
import state
|
||||
from . import utils
|
||||
from . import playlist_func as PL
|
||||
from . import plex_functions as PF
|
||||
from .plex_api import API
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.playqueue')
|
||||
|
||||
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
||||
REGEX = re_compile(r'''plex_id=(\d+)''')
|
||||
|
||||
# Our PKC playqueues (3 instances of Playqueue_Object())
|
||||
PLAYQUEUES = []
|
||||
|
@ -37,7 +32,7 @@ def init_playqueues():
|
|||
LOG.debug('Playqueues have already been initialized')
|
||||
return
|
||||
# Initialize Kodi playqueues
|
||||
with LOCK:
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
for i in (0, 1, 2):
|
||||
# Just in case the Kodi response is not sorted correctly
|
||||
for queue in js.get_playlists():
|
||||
|
@ -48,12 +43,12 @@ def init_playqueues():
|
|||
playqueue.type = queue['type']
|
||||
# Initialize each Kodi playlist
|
||||
if playqueue.type == v.KODI_TYPE_AUDIO:
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||
elif playqueue.type == v.KODI_TYPE_VIDEO:
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
else:
|
||||
# Currently, only video or audio playqueues available
|
||||
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||
# Overwrite 'picture' with 'photo'
|
||||
playqueue.type = v.KODI_TYPE_PHOTO
|
||||
PLAYQUEUES.append(playqueue)
|
||||
|
@ -65,14 +60,13 @@ def get_playqueue_from_type(kodi_playlist_type):
|
|||
Returns the playqueue according to the kodi_playlist_type ('video',
|
||||
'audio', 'picture') passed in
|
||||
"""
|
||||
with LOCK:
|
||||
for playqueue in PLAYQUEUES:
|
||||
if playqueue.type == kodi_playlist_type:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong playlist type passed in: %s',
|
||||
kodi_playlist_type)
|
||||
return playqueue
|
||||
for playqueue in PLAYQUEUES:
|
||||
if playqueue.type == kodi_playlist_type:
|
||||
break
|
||||
else:
|
||||
raise ValueError('Wrong playlist type passed in: %s',
|
||||
kodi_playlist_type)
|
||||
return playqueue
|
||||
|
||||
|
||||
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||
|
@ -81,7 +75,7 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
|||
|
||||
Returns the Playlist_Object
|
||||
"""
|
||||
xml = GetAllPlexChildren(plex_id)
|
||||
xml = PF.GetAllPlexChildren(plex_id)
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (TypeError, IndexError, AttributeError):
|
||||
|
@ -95,41 +89,11 @@ def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
|||
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id())
|
||||
playqueue.plex_transient_token = transient_token
|
||||
LOG.debug('Firing up Kodi player')
|
||||
Player().play(playqueue.kodi_pl, None, False, 0)
|
||||
xbmc.Player().play(playqueue.kodi_pl, None, False, 0)
|
||||
return playqueue
|
||||
|
||||
|
||||
def update_playqueue_from_PMS(playqueue,
|
||||
playqueue_id=None,
|
||||
repeat=None,
|
||||
offset=None,
|
||||
transient_token=None):
|
||||
"""
|
||||
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
||||
in playqueue_id if we need to fetch a new playqueue
|
||||
|
||||
repeat = 0, 1, 2
|
||||
offset = time offset in Plextime (milliseconds)
|
||||
"""
|
||||
LOG.info('New playqueue %s received from Plex companion with offset '
|
||||
'%s, repeat %s', playqueue_id, offset, repeat)
|
||||
# Safe transient token from being deleted
|
||||
if transient_token is None:
|
||||
transient_token = playqueue.plex_transient_token
|
||||
with LOCK:
|
||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||
playqueue.clear()
|
||||
try:
|
||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||
except PL.PlaylistError:
|
||||
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
||||
return
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
playqueue.plex_transient_token = transient_token
|
||||
play_xml(playqueue, xml, offset)
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['PMS_STATUS'])
|
||||
@utils.thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class PlayqueueMonitor(Thread):
|
||||
"""
|
||||
Unfortunately, Kodi does not tell if items within a Kodi playqueue
|
||||
|
@ -167,7 +131,7 @@ class PlayqueueMonitor(Thread):
|
|||
old_item.kodi_type == new_item['type'])
|
||||
else:
|
||||
try:
|
||||
plex_id = REGEX.findall(new_item['file'])[0]
|
||||
plex_id = utils.REGEX_PLEX_ID.findall(new_item['file'])[0]
|
||||
except IndexError:
|
||||
LOG.debug('Comparing paths directly as a fallback')
|
||||
identical = old_item.file == new_item['file']
|
||||
|
@ -222,8 +186,8 @@ class PlayqueueMonitor(Thread):
|
|||
while suspended():
|
||||
if stopped():
|
||||
break
|
||||
sleep(1000)
|
||||
with LOCK:
|
||||
xbmc.sleep(1000)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
for playqueue in PLAYQUEUES:
|
||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||
if playqueue.old_kodi_pl != kodi_pl:
|
||||
|
@ -236,5 +200,5 @@ class PlayqueueMonitor(Thread):
|
|||
# compare old and new playqueue
|
||||
self._compare_playqueues(playqueue, kodi_pl)
|
||||
playqueue.old_kodi_pl = list(kodi_pl)
|
||||
sleep(200)
|
||||
xbmc.sleep(200)
|
||||
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from downloadutils import DownloadUtils as DU
|
||||
|
||||
from utils import window, settings, language as lang, dialog, try_encode
|
||||
import variables as v
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.playutils')
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -46,7 +46,8 @@ class PlayUtils():
|
|||
'maxVideoBitrate': self.get_bitrate(),
|
||||
'videoResolution': self.get_resolution(),
|
||||
'videoQuality': '100',
|
||||
'mediaBufferSize': int(settings('kodi_video_cache'))/1024,
|
||||
'mediaBufferSize': int(
|
||||
utils.settings('kodi_video_cache')) / 1024,
|
||||
})
|
||||
self.item.playmethod = 'Transcode'
|
||||
LOG.info("The playurl is: %s", playurl)
|
||||
|
@ -71,15 +72,15 @@ class PlayUtils():
|
|||
return playurl
|
||||
# set to either 'Direct Stream=1' or 'Transcode=2'
|
||||
# and NOT to 'Direct Play=0'
|
||||
if settings('playType') != "0":
|
||||
if utils.settings('playType') != "0":
|
||||
# User forcing to play via HTTP
|
||||
LOG.info("User chose to not direct play")
|
||||
return
|
||||
if self.mustTranscode():
|
||||
return
|
||||
return self.api.validate_playurl(path,
|
||||
self.api.plex_type(),
|
||||
force_check=True)
|
||||
self.api.plex_type(),
|
||||
force_check=True)
|
||||
|
||||
def mustTranscode(self):
|
||||
"""
|
||||
|
@ -106,7 +107,7 @@ class PlayUtils():
|
|||
# e.g. trailers. Avoids TypeError with "'h265' in codec"
|
||||
LOG.info('No codec from PMS, not transcoding.')
|
||||
return False
|
||||
if ((settings('transcodeHi10P') == 'true' and
|
||||
if ((utils.settings('transcodeHi10P') == 'true' and
|
||||
videoCodec['bitDepth'] == '10') and
|
||||
('h264' in codec)):
|
||||
LOG.info('Option to transcode 10bit h264 video content enabled.')
|
||||
|
@ -139,7 +140,7 @@ class PlayUtils():
|
|||
if self.api.plex_type() == 'track':
|
||||
return True
|
||||
# set to 'Transcode=2'
|
||||
if settings('playType') == "2":
|
||||
if utils.settings('playType') == "2":
|
||||
# User forcing to play via HTTP
|
||||
LOG.info("User chose to transcode")
|
||||
return False
|
||||
|
@ -149,7 +150,7 @@ class PlayUtils():
|
|||
|
||||
def get_max_bitrate(self):
|
||||
# get the addon video quality
|
||||
videoQuality = settings('maxVideoQualities')
|
||||
videoQuality = utils.settings('maxVideoQualities')
|
||||
bitrate = {
|
||||
'0': 320,
|
||||
'1': 720,
|
||||
|
@ -180,13 +181,13 @@ class PlayUtils():
|
|||
'2': 720,
|
||||
'3': 1080
|
||||
}
|
||||
return H265[settings('transcodeH265')]
|
||||
return H265[utils.settings('transcodeH265')]
|
||||
|
||||
def get_bitrate(self):
|
||||
"""
|
||||
Get the desired transcoding bitrate from the settings
|
||||
"""
|
||||
videoQuality = settings('transcoderVideoQualities')
|
||||
videoQuality = utils.settings('transcoderVideoQualities')
|
||||
bitrate = {
|
||||
'0': 320,
|
||||
'1': 720,
|
||||
|
@ -207,7 +208,7 @@ class PlayUtils():
|
|||
"""
|
||||
Get the desired transcoding resolutions from the settings
|
||||
"""
|
||||
chosen = settings('transcoderVideoQualities')
|
||||
chosen = utils.settings('transcoderVideoQualities')
|
||||
res = {
|
||||
'0': '420x420',
|
||||
'1': '576x320',
|
||||
|
@ -244,7 +245,7 @@ class PlayUtils():
|
|||
audio_streams = []
|
||||
subtitle_streams_list = []
|
||||
# No subtitles as an option
|
||||
subtitle_streams = [lang(39706)]
|
||||
subtitle_streams = [utils.lang(39706)]
|
||||
downloadable_streams = []
|
||||
download_subs = []
|
||||
# selectAudioIndex = ""
|
||||
|
@ -264,35 +265,35 @@ class PlayUtils():
|
|||
codec = stream.attrib.get('codec')
|
||||
channellayout = stream.attrib.get('audioChannelLayout', "")
|
||||
try:
|
||||
track = "%s %s - %s %s" % (audio_numb+1,
|
||||
track = "%s %s - %s %s" % (audio_numb + 1,
|
||||
stream.attrib['language'],
|
||||
codec,
|
||||
channellayout)
|
||||
except KeyError:
|
||||
track = "%s %s - %s %s" % (audio_numb+1,
|
||||
lang(39707), # unknown
|
||||
track = "%s %s - %s %s" % (audio_numb + 1,
|
||||
utils.lang(39707), # unknown
|
||||
codec,
|
||||
channellayout)
|
||||
audio_streams_list.append(index)
|
||||
audio_streams.append(try_encode(track))
|
||||
audio_streams.append(utils.try_encode(track))
|
||||
audio_numb += 1
|
||||
|
||||
# Subtitles
|
||||
elif typus == "3":
|
||||
try:
|
||||
track = "%s %s" % (sub_num+1, stream.attrib['language'])
|
||||
track = "%s %s" % (sub_num + 1, stream.attrib['language'])
|
||||
except KeyError:
|
||||
track = "%s %s (%s)" % (sub_num+1,
|
||||
lang(39707), # unknown
|
||||
track = "%s %s (%s)" % (sub_num + 1,
|
||||
utils.lang(39707), # unknown
|
||||
stream.attrib.get('codec'))
|
||||
default = stream.attrib.get('default')
|
||||
forced = stream.attrib.get('forced')
|
||||
downloadable = stream.attrib.get('key')
|
||||
|
||||
if default:
|
||||
track = "%s - %s" % (track, lang(39708)) # Default
|
||||
track = "%s - %s" % (track, utils.lang(39708)) # Default
|
||||
if forced:
|
||||
track = "%s - %s" % (track, lang(39709)) # Forced
|
||||
track = "%s - %s" % (track, utils.lang(39709)) # Forced
|
||||
if downloadable:
|
||||
# We do know the language - temporarily download
|
||||
if 'language' in stream.attrib:
|
||||
|
@ -303,23 +304,23 @@ class PlayUtils():
|
|||
# We don't know the language - no need to download
|
||||
else:
|
||||
path = self.api.attach_plex_token_to_url(
|
||||
"%s%s" % (window('pms_server'),
|
||||
"%s%s" % (utils.window('pms_server'),
|
||||
stream.attrib['key']))
|
||||
downloadable_streams.append(index)
|
||||
download_subs.append(try_encode(path))
|
||||
download_subs.append(utils.try_encode(path))
|
||||
else:
|
||||
track = "%s (%s)" % (track, lang(39710)) # burn-in
|
||||
track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
|
||||
if stream.attrib.get('selected') == '1' and downloadable:
|
||||
# Only show subs without asking user if they can be
|
||||
# turned off
|
||||
default_sub = index
|
||||
|
||||
subtitle_streams_list.append(index)
|
||||
subtitle_streams.append(try_encode(track))
|
||||
subtitle_streams.append(utils.try_encode(track))
|
||||
sub_num += 1
|
||||
|
||||
if audio_numb > 1:
|
||||
resp = dialog('select', lang(33013), audio_streams)
|
||||
resp = utils.dialog('select', utils.lang(33013), audio_streams)
|
||||
if resp > -1:
|
||||
# User selected some audio track
|
||||
args = {
|
||||
|
@ -335,14 +336,14 @@ class PlayUtils():
|
|||
return
|
||||
|
||||
select_subs_index = None
|
||||
if (settings('pickPlexSubtitles') == 'true' and
|
||||
if (utils.settings('pickPlexSubtitles') == 'true' and
|
||||
default_sub is not None):
|
||||
LOG.info('Using default Plex subtitle: %s', default_sub)
|
||||
select_subs_index = default_sub
|
||||
else:
|
||||
resp = dialog('select', lang(33014), subtitle_streams)
|
||||
resp = utils.dialog('select', utils.lang(33014), subtitle_streams)
|
||||
if resp > 0:
|
||||
select_subs_index = subtitle_streams_list[resp-1]
|
||||
select_subs_index = subtitle_streams_list[resp - 1]
|
||||
else:
|
||||
# User selected no subtitles or backed out of dialog
|
||||
select_subs_index = ''
|
||||
|
|
|
@ -30,29 +30,23 @@ http://stackoverflow.com/questions/111945/is-there-any-way-to-do-http-put-in-pyt
|
|||
(and others...)
|
||||
"""
|
||||
from logging import getLogger
|
||||
from re import compile as re_compile, sub
|
||||
from re import sub
|
||||
from urllib import urlencode, unquote
|
||||
from os.path import basename, join
|
||||
from os import makedirs
|
||||
|
||||
from xbmcgui import ListItem
|
||||
from xbmcvfs import exists
|
||||
|
||||
import clientinfo as client
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import window, settings, language as lang, try_decode, try_encode, \
|
||||
unix_date_to_kodi, exists_dir, slugify, dialog, escape_html
|
||||
import PlexFunctions as PF
|
||||
import plexdb_functions as plexdb
|
||||
import kodidb_functions as kodidb
|
||||
import variables as v
|
||||
import state
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import clientinfo
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import plex_functions as PF
|
||||
from . import plexdb_functions as plexdb
|
||||
from . import kodidb_functions as kodidb
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
||||
REGEX_IMDB = re_compile(r'''/(tt\d+)''')
|
||||
REGEX_TVDB = re_compile(r'''thetvdb:\/\/(.+?)\?''')
|
||||
LOG = getLogger('PLEX.plex_api')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -70,7 +64,7 @@ class API(object):
|
|||
# which media part in the XML response shall we look at?
|
||||
self.part = 0
|
||||
self.mediastream = None
|
||||
self.server = window('pms_server')
|
||||
self.server = utils.window('pms_server')
|
||||
|
||||
def set_part_number(self, number=None):
|
||||
"""
|
||||
|
@ -203,7 +197,7 @@ class API(object):
|
|||
ans = None
|
||||
if ans is not None:
|
||||
try:
|
||||
ans = try_decode(unquote(ans))
|
||||
ans = utils.try_decode(unquote(ans))
|
||||
except UnicodeDecodeError:
|
||||
# Sometimes, Plex seems to have encoded in latin1
|
||||
ans = unquote(ans).decode('latin1')
|
||||
|
@ -215,23 +209,23 @@ class API(object):
|
|||
Will always use addon paths, never direct paths
|
||||
"""
|
||||
extension = self.item[0][0].attrib['key'][self.item[0][0].attrib['key'].rfind('.'):].lower()
|
||||
if (window('plex_force_transcode_pix') == 'true' or
|
||||
if (utils.window('plex_force_transcode_pix') == 'true' or
|
||||
extension not in v.KODI_SUPPORTED_IMAGES):
|
||||
# Let Plex transcode
|
||||
# max width/height supported by plex image transcoder is 1920x1080
|
||||
path = self.server + PF.transcode_image_path(
|
||||
self.item[0][0].get('key'),
|
||||
window('pms_token'),
|
||||
utils.window('pms_token'),
|
||||
"%s%s" % (self.server, self.item[0][0].get('key')),
|
||||
1920,
|
||||
1080)
|
||||
else:
|
||||
path = self.attach_plex_token_to_url(
|
||||
'%s%s' % (window('pms_server'),
|
||||
'%s%s' % (utils.window('pms_server'),
|
||||
self.item[0][0].attrib['key']))
|
||||
# Attach Plex id to url to let it be picked up by our playqueue agent
|
||||
# later
|
||||
return try_encode('%s&plex_id=%s' % (path, self.plex_id()))
|
||||
return utils.try_encode('%s&plex_id=%s' % (path, self.plex_id()))
|
||||
|
||||
def tv_show_path(self):
|
||||
"""
|
||||
|
@ -258,7 +252,7 @@ class API(object):
|
|||
"""
|
||||
res = self.item.get('addedAt')
|
||||
if res is not None:
|
||||
res = unix_date_to_kodi(res)
|
||||
res = utils.unix_date_to_kodi(res)
|
||||
else:
|
||||
res = '2000-01-01 10:00:00'
|
||||
return res
|
||||
|
@ -295,7 +289,7 @@ class API(object):
|
|||
played = True if playcount else False
|
||||
|
||||
try:
|
||||
last_played = unix_date_to_kodi(int(item['lastViewedAt']))
|
||||
last_played = utils.unix_date_to_kodi(int(item['lastViewedAt']))
|
||||
except (KeyError, ValueError):
|
||||
last_played = None
|
||||
|
||||
|
@ -423,7 +417,7 @@ class API(object):
|
|||
"""
|
||||
answ = self.item.get('guid')
|
||||
if answ is not None:
|
||||
answ = escape_html(answ)
|
||||
answ = utils.escape_html(answ)
|
||||
return answ
|
||||
|
||||
def provider(self, providername=None):
|
||||
|
@ -438,10 +432,10 @@ class API(object):
|
|||
return None
|
||||
|
||||
if providername == 'imdb':
|
||||
regex = REGEX_IMDB
|
||||
regex = utils.REGEX_IMDB
|
||||
elif providername == 'tvdb':
|
||||
# originally e.g. com.plexapp.agents.thetvdb://276564?lang=en
|
||||
regex = REGEX_TVDB
|
||||
regex = utils.REGEX_TVDB
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -456,7 +450,7 @@ class API(object):
|
|||
"""
|
||||
Returns the title of the element as unicode or 'Missing Title Name'
|
||||
"""
|
||||
return try_decode(self.item.get('title', 'Missing Title Name'))
|
||||
return utils.try_decode(self.item.get('title', 'Missing Title Name'))
|
||||
|
||||
def titles(self):
|
||||
"""
|
||||
|
@ -657,12 +651,12 @@ class API(object):
|
|||
|
||||
url may or may not already contain a '?'
|
||||
"""
|
||||
if window('pms_token') == '':
|
||||
if utils.window('pms_token') == '':
|
||||
return url
|
||||
if '?' not in url:
|
||||
url = "%s?X-Plex-Token=%s" % (url, window('pms_token'))
|
||||
url = "%s?X-Plex-Token=%s" % (url, utils.window('pms_token'))
|
||||
else:
|
||||
url = "%s&X-Plex-Token=%s" % (url, window('pms_token'))
|
||||
url = "%s&X-Plex-Token=%s" % (url, utils.window('pms_token'))
|
||||
return url
|
||||
|
||||
def item_id(self):
|
||||
|
@ -808,12 +802,12 @@ class API(object):
|
|||
track['channels'] = stream.get('channels')
|
||||
# 'unknown' if we cannot get language
|
||||
track['language'] = stream.get(
|
||||
'languageCode', lang(39310)).lower()
|
||||
'languageCode', utils.lang(39310)).lower()
|
||||
audiotracks.append(track)
|
||||
elif media_type == 3: # Subtitle streams
|
||||
# 'unknown' if we cannot get language
|
||||
subtitlelanguages.append(
|
||||
stream.get('languageCode', lang(39310)).lower())
|
||||
stream.get('languageCode', utils.lang(39310)).lower())
|
||||
return {
|
||||
'video': videotracks,
|
||||
'audio': audiotracks,
|
||||
|
@ -966,7 +960,7 @@ class API(object):
|
|||
LOG.info('Start movie set/collection lookup on themoviedb with %s',
|
||||
item.get('title', ''))
|
||||
|
||||
api_key = settings('themoviedbAPIKey')
|
||||
api_key = utils.settings('themoviedbAPIKey')
|
||||
if media_type == v.PLEX_TYPE_SHOW:
|
||||
media_type = 'tv'
|
||||
title = item.get('title', '')
|
||||
|
@ -977,7 +971,7 @@ class API(object):
|
|||
parameters = {
|
||||
'api_key': api_key,
|
||||
'language': v.KODILANGUAGE,
|
||||
'query': try_encode(title)
|
||||
'query': utils.try_encode(title)
|
||||
}
|
||||
data = DU().downloadUrl(url,
|
||||
authenticate=False,
|
||||
|
@ -1103,8 +1097,8 @@ class API(object):
|
|||
try:
|
||||
data.get('poster_path')
|
||||
except AttributeError:
|
||||
LOG.debug('Could not find TheMovieDB poster paths for %s in '
|
||||
'the language %s', title, language)
|
||||
LOG.debug('Could not find TheMovieDB poster paths for %s'
|
||||
' in the language %s', title, language)
|
||||
continue
|
||||
if not poster and data.get('poster_path'):
|
||||
poster = ('https://image.tmdb.org/t/p/original%s' %
|
||||
|
@ -1120,7 +1114,7 @@ class API(object):
|
|||
|
||||
media_id: IMDB id for movies, tvdb id for TV shows
|
||||
"""
|
||||
api_key = settings('FanArtTVAPIKey')
|
||||
api_key = utils.settings('FanArtTVAPIKey')
|
||||
typus = self.plex_type()
|
||||
if typus == v.PLEX_TYPE_SHOW:
|
||||
typus = 'tv'
|
||||
|
@ -1236,17 +1230,17 @@ class API(object):
|
|||
count += 1
|
||||
if (count > 1 and (
|
||||
(self.plex_type() != 'clip' and
|
||||
settings('bestQuality') == 'false')
|
||||
utils.settings('bestQuality') == 'false')
|
||||
or
|
||||
(self.plex_type() == 'clip' and
|
||||
settings('bestTrailer') == 'false'))):
|
||||
utils.settings('bestTrailer') == 'false'))):
|
||||
# Several streams/files available.
|
||||
dialoglist = []
|
||||
for entry in self.item.iterfind('./Media'):
|
||||
# Get additional info (filename / languages)
|
||||
filename = None
|
||||
if 'file' in entry[0].attrib:
|
||||
filename = basename(entry[0].attrib['file'])
|
||||
filename = path_ops.path.basename(entry[0].attrib['file'])
|
||||
# Languages of audio streams
|
||||
languages = []
|
||||
for stream in entry[0]:
|
||||
|
@ -1255,12 +1249,13 @@ class API(object):
|
|||
languages.append(stream.attrib['language'])
|
||||
languages = ', '.join(languages)
|
||||
if filename:
|
||||
option = try_encode(filename)
|
||||
option = utils.try_encode(filename)
|
||||
if languages:
|
||||
if option:
|
||||
option = '%s (%s): ' % (option, try_encode(languages))
|
||||
option = '%s (%s): ' % (option,
|
||||
utils.try_encode(languages))
|
||||
else:
|
||||
option = '%s: ' % try_encode(languages)
|
||||
option = '%s: ' % utils.try_encode(languages)
|
||||
if 'videoResolution' in entry.attrib:
|
||||
option = '%s%sp ' % (option,
|
||||
entry.get('videoResolution'))
|
||||
|
@ -1275,7 +1270,7 @@ class API(object):
|
|||
option = '%s%s ' % (option,
|
||||
entry.get('audioCodec'))
|
||||
dialoglist.append(option)
|
||||
media = dialog('select', 'Select stream', dialoglist)
|
||||
media = utils.dialog('select', 'Select stream', dialoglist)
|
||||
else:
|
||||
media = 0
|
||||
self.mediastream = media
|
||||
|
@ -1306,7 +1301,7 @@ class API(object):
|
|||
self.mediastream_number()
|
||||
if quality is None:
|
||||
quality = {}
|
||||
xargs = client.getXArgsDeviceInfo()
|
||||
xargs = clientinfo.getXArgsDeviceInfo()
|
||||
# For DirectPlay, path/key of PART is needed
|
||||
# trailers are 'clip' with PMS xmls
|
||||
if action == "DirectStream":
|
||||
|
@ -1331,19 +1326,19 @@ class API(object):
|
|||
transcode_path = self.server + \
|
||||
'/video/:/transcode/universal/start.m3u8?'
|
||||
args = {
|
||||
'audioBoost': settings('audioBoost'),
|
||||
'audioBoost': utils.settings('audioBoost'),
|
||||
'autoAdjustQuality': 0,
|
||||
'directPlay': 0,
|
||||
'directStream': 1,
|
||||
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
|
||||
'session': window('plex_client_Id'),
|
||||
'session': utils.window('plex_client_Id'),
|
||||
'fastSeek': 1,
|
||||
'path': path,
|
||||
'mediaIndex': self.mediastream,
|
||||
'partIndex': self.part,
|
||||
'hasMDE': 1,
|
||||
'location': 'lan',
|
||||
'subtitleSize': settings('subtitleSize')
|
||||
'subtitleSize': utils.settings('subtitleSize')
|
||||
}
|
||||
# Look like Android to let the PMS use the transcoding profile
|
||||
xargs.update(headers)
|
||||
|
@ -1400,9 +1395,7 @@ class API(object):
|
|||
|
||||
Returns the path to the downloaded subtitle or None
|
||||
"""
|
||||
if not exists_dir(v.EXTERNAL_SUBTITLE_TEMP_PATH):
|
||||
makedirs(v.EXTERNAL_SUBTITLE_TEMP_PATH)
|
||||
path = join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
|
||||
path = path_ops.path.join(v.EXTERNAL_SUBTITLE_TEMP_PATH, filename)
|
||||
response = DU().downloadUrl(url, return_response=True)
|
||||
try:
|
||||
response.status_code
|
||||
|
@ -1411,14 +1404,8 @@ class API(object):
|
|||
return
|
||||
else:
|
||||
LOG.debug('Writing temp subtitle to %s', path)
|
||||
try:
|
||||
with open(path, 'wb') as filer:
|
||||
filer.write(response.content)
|
||||
except UnicodeEncodeError:
|
||||
LOG.debug('Need to slugify the filename %s', path)
|
||||
path = slugify(path)
|
||||
with open(path, 'wb') as filer:
|
||||
filer.write(response.content)
|
||||
with open(path_ops.encode_path(path), 'wb') as filer:
|
||||
filer.write(response.content)
|
||||
return path
|
||||
|
||||
def kodi_premiere_date(self):
|
||||
|
@ -1560,7 +1547,8 @@ class API(object):
|
|||
listitem.setInfo('video', infoLabels=metadata)
|
||||
try:
|
||||
# Add context menu entry for information screen
|
||||
listitem.addContextMenuItems([(lang(30032), 'XBMC.Action(Info)',)])
|
||||
listitem.addContextMenuItems([(utils.lang(30032),
|
||||
'XBMC.Action(Info)',)])
|
||||
except TypeError:
|
||||
# Kodi fuck-up
|
||||
pass
|
||||
|
@ -1606,20 +1594,21 @@ class API(object):
|
|||
# exist() needs a / or \ at the end to work for directories
|
||||
if folder is False:
|
||||
# files
|
||||
check = exists(try_encode(path))
|
||||
check = exists(utils.try_encode(path))
|
||||
else:
|
||||
# directories
|
||||
if "\\" in path:
|
||||
if not path.endswith('\\'):
|
||||
checkpath = utils.try_encode(path)
|
||||
if b"\\" in checkpath:
|
||||
if not checkpath.endswith('\\'):
|
||||
# Add the missing backslash
|
||||
check = exists_dir(path + "\\")
|
||||
check = utils.exists_dir(checkpath + "\\")
|
||||
else:
|
||||
check = exists_dir(path)
|
||||
check = utils.exists_dir(checkpath)
|
||||
else:
|
||||
if not path.endswith('/'):
|
||||
check = exists_dir(path + "/")
|
||||
if not checkpath.endswith('/'):
|
||||
check = utils.exists_dir(checkpath + "/")
|
||||
else:
|
||||
check = exists_dir(path)
|
||||
check = utils.exists_dir(checkpath)
|
||||
|
||||
if not check:
|
||||
if force_check is False:
|
||||
|
@ -1648,25 +1637,11 @@ class API(object):
|
|||
LOG.warn('Cannot access file: %s', url)
|
||||
# Kodi cannot locate the file #s. Please verify your PKC settings. Stop
|
||||
# syncing?
|
||||
resp = dialog('yesno', heading='{plex}', line1=lang(39031) % url)
|
||||
resp = utils.dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1=utils.lang(39031) % url)
|
||||
return resp
|
||||
|
||||
def set_listitem_artwork(self, listitem):
|
||||
"""
|
||||
Set all artwork to the listitem
|
||||
"""
|
||||
allartwork = self.artwork()
|
||||
listitem.setArt(self.artwork())
|
||||
for arttype in arttypes:
|
||||
art = arttypes[arttype]
|
||||
if art == "Backdrop":
|
||||
# Backdrop is a list, grab the first backdrop
|
||||
self._set_listitem_artprop(listitem,
|
||||
arttype,
|
||||
allartwork[art][0])
|
||||
else:
|
||||
self._set_listitem_artprop(listitem, arttype, allartwork[art])
|
||||
|
||||
@staticmethod
|
||||
def _set_listitem_artprop(listitem, arttype, path):
|
||||
if arttype in (
|
|
@ -6,30 +6,57 @@ from threading import Thread
|
|||
from Queue import Empty
|
||||
from socket import SHUT_RDWR
|
||||
from urllib import urlencode
|
||||
|
||||
from xbmc import sleep, executebuiltin, Player
|
||||
|
||||
from utils import settings, thread_methods, language as lang, dialog
|
||||
from plexbmchelper import listener, plexgdm, subscribers, httppersist
|
||||
from plexbmchelper.subscribers import LOCKER
|
||||
from PlexFunctions import ParseContainerKey, GetPlexMetadata, DownloadChunks
|
||||
from PlexAPI import API
|
||||
from playlist_func import get_pms_playqueue, get_plextype_from_xml, \
|
||||
get_playlist_details_from_xml
|
||||
from playback import playback_triage, play_xml
|
||||
import json_rpc as js
|
||||
import variables as v
|
||||
import state
|
||||
import playqueue as PQ
|
||||
from .plexbmchelper import listener, plexgdm, subscribers, httppersist
|
||||
from .plex_api import API
|
||||
from . import utils
|
||||
from . import plex_functions as PF
|
||||
from . import playlist_func as PL
|
||||
from . import playback
|
||||
from . import json_rpc as js
|
||||
from . import playqueue as PQ
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.plex_companion')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['PMS_STATUS'])
|
||||
def update_playqueue_from_PMS(playqueue,
|
||||
playqueue_id=None,
|
||||
repeat=None,
|
||||
offset=None,
|
||||
transient_token=None):
|
||||
"""
|
||||
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
||||
in playqueue_id if we need to fetch a new playqueue
|
||||
|
||||
repeat = 0, 1, 2
|
||||
offset = time offset in Plextime (milliseconds)
|
||||
"""
|
||||
LOG.info('New playqueue %s received from Plex companion with offset '
|
||||
'%s, repeat %s', playqueue_id, offset, repeat)
|
||||
# Safe transient token from being deleted
|
||||
if transient_token is None:
|
||||
transient_token = playqueue.plex_transient_token
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||
playqueue.clear()
|
||||
try:
|
||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||
except PL.PlaylistError:
|
||||
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
||||
return
|
||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||
playqueue.plex_transient_token = transient_token
|
||||
playback.play_xml(playqueue, xml, offset)
|
||||
|
||||
|
||||
@utils.thread_methods(add_suspends=['PMS_STATUS'])
|
||||
class PlexCompanion(Thread):
|
||||
"""
|
||||
Plex Companion monitoring class. Invoke only once
|
||||
|
@ -47,9 +74,8 @@ class PlexCompanion(Thread):
|
|||
self.subscription_manager = None
|
||||
Thread.__init__(self)
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _process_alexa(self, data):
|
||||
xml = GetPlexMetadata(data['key'])
|
||||
xml = PF.GetPlexMetadata(data['key'])
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (AttributeError, IndexError, TypeError):
|
||||
|
@ -62,28 +88,33 @@ class PlexCompanion(Thread):
|
|||
api.plex_id(),
|
||||
transient_token=data.get('token'))
|
||||
elif data['containerKey'].startswith('/playQueues/'):
|
||||
_, container_key, _ = ParseContainerKey(data['containerKey'])
|
||||
xml = DownloadChunks('{server}/playQueues/%s?' % container_key)
|
||||
_, container_key, _ = PF.ParseContainerKey(data['containerKey'])
|
||||
xml = PF.DownloadChunks('{server}/playQueues/%s?' % container_key)
|
||||
if xml is None:
|
||||
# "Play error"
|
||||
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(30128),
|
||||
icon='{error}')
|
||||
return
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
||||
playqueue.clear()
|
||||
get_playlist_details_from_xml(playqueue, xml)
|
||||
PL.get_playlist_details_from_xml(playqueue, xml)
|
||||
playqueue.plex_transient_token = data.get('token')
|
||||
if data.get('offset') != '0':
|
||||
offset = float(data['offset']) / 1000.0
|
||||
else:
|
||||
offset = None
|
||||
play_xml(playqueue, xml, offset)
|
||||
playback.play_xml(playqueue, xml, offset)
|
||||
else:
|
||||
state.PLEX_TRANSIENT_TOKEN = data.get('token')
|
||||
if data.get('offset') != '0':
|
||||
state.RESUMABLE = True
|
||||
state.RESUME_PLAYBACK = True
|
||||
playback_triage(api.plex_id(), api.plex_type(), resolve=False)
|
||||
playback.playback_triage(api.plex_id(),
|
||||
api.plex_type(),
|
||||
resolve=False)
|
||||
|
||||
@staticmethod
|
||||
def _process_node(data):
|
||||
|
@ -99,17 +130,16 @@ class PlexCompanion(Thread):
|
|||
executebuiltin('RunPlugin(plugin://%s?%s)'
|
||||
% (v.ADDON_ID, urlencode(params)))
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _process_playlist(self, data):
|
||||
# Get the playqueue ID
|
||||
_, container_key, query = ParseContainerKey(data['containerKey'])
|
||||
_, container_key, query = PF.ParseContainerKey(data['containerKey'])
|
||||
try:
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
||||
except KeyError:
|
||||
# E.g. Plex web does not supply the media type
|
||||
# Still need to figure out the type (video vs. music vs. pix)
|
||||
xml = GetPlexMetadata(data['key'])
|
||||
xml = PF.GetPlexMetadata(data['key'])
|
||||
try:
|
||||
xml[0].attrib
|
||||
except (AttributeError, IndexError, TypeError):
|
||||
|
@ -118,14 +148,12 @@ class PlexCompanion(Thread):
|
|||
api = API(xml[0])
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()])
|
||||
PQ.update_playqueue_from_PMS(
|
||||
playqueue,
|
||||
playqueue_id=container_key,
|
||||
repeat=query.get('repeat'),
|
||||
offset=data.get('offset'),
|
||||
transient_token=data.get('token'))
|
||||
update_playqueue_from_PMS(playqueue,
|
||||
playqueue_id=container_key,
|
||||
repeat=query.get('repeat'),
|
||||
offset=data.get('offset'),
|
||||
transient_token=data.get('token'))
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _process_streams(self, data):
|
||||
"""
|
||||
Plex Companion client adjusted audio or subtitle stream
|
||||
|
@ -147,17 +175,16 @@ class PlexCompanion(Thread):
|
|||
else:
|
||||
LOG.error('Unknown setStreams command: %s', data)
|
||||
|
||||
@LOCKER.lockthis
|
||||
def _process_refresh(self, data):
|
||||
"""
|
||||
example data: {'playQueueID': '8475', 'commandID': '11'}
|
||||
"""
|
||||
xml = get_pms_playqueue(data['playQueueID'])
|
||||
xml = PL.get_pms_playqueue(data['playQueueID'])
|
||||
if xml is None:
|
||||
return
|
||||
if len(xml) == 0:
|
||||
LOG.debug('Empty playqueue received - clearing playqueue')
|
||||
plex_type = get_plextype_from_xml(xml)
|
||||
plex_type = PL.get_plextype_from_xml(xml)
|
||||
if plex_type is None:
|
||||
return
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
|
@ -166,7 +193,7 @@ class PlexCompanion(Thread):
|
|||
return
|
||||
playqueue = PQ.get_playqueue_from_type(
|
||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
|
||||
PQ.update_playqueue_from_PMS(playqueue, data['playQueueID'])
|
||||
update_playqueue_from_PMS(playqueue, data['playQueueID'])
|
||||
|
||||
def _process_tasks(self, task):
|
||||
"""
|
||||
|
@ -186,14 +213,17 @@ class PlexCompanion(Thread):
|
|||
LOG.debug('Processing: %s', task)
|
||||
data = task['data']
|
||||
if task['action'] == 'alexa':
|
||||
self._process_alexa(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._process_alexa(data)
|
||||
elif (task['action'] == 'playlist' and
|
||||
data.get('address') == 'node.plexapp.com'):
|
||||
self._process_node(data)
|
||||
elif task['action'] == 'playlist':
|
||||
self._process_playlist(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._process_playlist(data)
|
||||
elif task['action'] == 'refreshPlayQueue':
|
||||
self._process_refresh(data)
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
self._process_refresh(data)
|
||||
elif task['action'] == 'setStreams':
|
||||
try:
|
||||
self._process_streams(data)
|
||||
|
@ -231,7 +261,7 @@ class PlexCompanion(Thread):
|
|||
self.player)
|
||||
self.subscription_manager = subscription_manager
|
||||
|
||||
if settings('plexCompanion') == 'true':
|
||||
if utils.settings('plexCompanion') == 'true':
|
||||
# Start up httpd
|
||||
start_count = 0
|
||||
while True:
|
|
@ -3,24 +3,20 @@ from logging import getLogger
|
|||
from urllib import urlencode, quote_plus
|
||||
from ast import literal_eval
|
||||
from urlparse import urlparse, parse_qsl
|
||||
from re import compile as re_compile
|
||||
from copy import deepcopy
|
||||
from time import time
|
||||
from threading import Thread
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import settings, try_encode, try_decode
|
||||
from variables import PLEX_TO_KODI_TIMEFACTOR
|
||||
import plex_tv
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import plex_tv
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.plex_functions')
|
||||
|
||||
CONTAINERSIZE = int(settings('limitindex'))
|
||||
REGEX_PLEX_KEY = re_compile(r'''/(.+)/(\d+)$''')
|
||||
REGEX_PLEX_DIRECT = re_compile(r'''\.plex\.direct:\d+$''')
|
||||
CONTAINERSIZE = int(utils.settings('limitindex'))
|
||||
|
||||
# For discovery of PMS in the local LAN
|
||||
PLEX_GDM_IP = '239.0.0.250' # multicast to PMS
|
||||
|
@ -36,7 +32,7 @@ def ConvertPlexToKodiTime(plexTime):
|
|||
"""
|
||||
if plexTime is None:
|
||||
return None
|
||||
return int(float(plexTime) * PLEX_TO_KODI_TIMEFACTOR)
|
||||
return int(float(plexTime) * v.PLEX_TO_KODI_TIMEFACTOR)
|
||||
|
||||
|
||||
def GetPlexKeyNumber(plexKey):
|
||||
|
@ -48,7 +44,7 @@ def GetPlexKeyNumber(plexKey):
|
|||
Returns ('','') if nothing is found
|
||||
"""
|
||||
try:
|
||||
result = REGEX_PLEX_KEY.findall(plexKey)[0]
|
||||
result = utils.REGEX_END_DIGITS.findall(plexKey)[0]
|
||||
except IndexError:
|
||||
result = ('', '')
|
||||
return result
|
||||
|
@ -90,13 +86,13 @@ def GetMethodFromPlexType(plexType):
|
|||
def GetPlexLoginFromSettings():
|
||||
"""
|
||||
Returns a dict:
|
||||
'plexLogin': settings('plexLogin'),
|
||||
'plexToken': settings('plexToken'),
|
||||
'plexhome': settings('plexhome'),
|
||||
'plexid': settings('plexid'),
|
||||
'myplexlogin': settings('myplexlogin'),
|
||||
'plexAvatar': settings('plexAvatar'),
|
||||
'plexHomeSize': settings('plexHomeSize')
|
||||
'plexLogin': utils.settings('plexLogin'),
|
||||
'plexToken': utils.settings('plexToken'),
|
||||
'plexhome': utils.settings('plexhome'),
|
||||
'plexid': utils.settings('plexid'),
|
||||
'myplexlogin': utils.settings('myplexlogin'),
|
||||
'plexAvatar': utils.settings('plexAvatar'),
|
||||
'plexHomeSize': utils.settings('plexHomeSize')
|
||||
|
||||
Returns strings or unicode
|
||||
|
||||
|
@ -106,13 +102,13 @@ def GetPlexLoginFromSettings():
|
|||
plexhome is 'true' if plex home is used (the default)
|
||||
"""
|
||||
return {
|
||||
'plexLogin': settings('plexLogin'),
|
||||
'plexToken': settings('plexToken'),
|
||||
'plexhome': settings('plexhome'),
|
||||
'plexid': settings('plexid'),
|
||||
'myplexlogin': settings('myplexlogin'),
|
||||
'plexAvatar': settings('plexAvatar'),
|
||||
'plexHomeSize': settings('plexHomeSize')
|
||||
'plexLogin': utils.settings('plexLogin'),
|
||||
'plexToken': utils.settings('plexToken'),
|
||||
'plexhome': utils.settings('plexhome'),
|
||||
'plexid': utils.settings('plexid'),
|
||||
'myplexlogin': utils.settings('myplexlogin'),
|
||||
'plexAvatar': utils.settings('plexAvatar'),
|
||||
'plexHomeSize': utils.settings('plexHomeSize')
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,7 +136,7 @@ def check_connection(url, token=None, verifySSL=None):
|
|||
if token is not None:
|
||||
header_options = {'X-Plex-Token': token}
|
||||
if verifySSL is True:
|
||||
verifySSL = None if settings('sslverify') == 'true' else False
|
||||
verifySSL = None if utils.settings('sslverify') == 'true' else False
|
||||
if 'plex.tv' in url:
|
||||
url = 'https://plex.tv/api/home/users'
|
||||
LOG.debug("Checking connection to server %s with verifySSL=%s",
|
||||
|
@ -303,11 +299,11 @@ def _plex_gdm():
|
|||
}
|
||||
for line in response['data'].split('\n'):
|
||||
if 'Content-Type:' in line:
|
||||
pms['product'] = try_decode(line.split(':')[1].strip())
|
||||
pms['product'] = utils.try_decode(line.split(':')[1].strip())
|
||||
elif 'Host:' in line:
|
||||
pms['baseURL'] = line.split(':')[1].strip()
|
||||
elif 'Name:' in line:
|
||||
pms['name'] = try_decode(line.split(':')[1].strip())
|
||||
pms['name'] = utils.try_decode(line.split(':')[1].strip())
|
||||
elif 'Port:' in line:
|
||||
pms['port'] = line.split(':')[1].strip()
|
||||
elif 'Resource-Identifier:' in line:
|
||||
|
@ -336,7 +332,7 @@ def _pms_list_from_plex_tv(token):
|
|||
queue = Queue()
|
||||
thread_queue = []
|
||||
|
||||
max_age_in_seconds = 2*60*60*24
|
||||
max_age_in_seconds = 2 * 60 * 60 * 24
|
||||
for device in xml.findall('Device'):
|
||||
if 'server' not in device.get('provides'):
|
||||
# No PMS - skip
|
||||
|
@ -355,7 +351,7 @@ def _pms_list_from_plex_tv(token):
|
|||
'token': device.get('accessToken'),
|
||||
'ownername': device.get('sourceTitle'),
|
||||
'product': device.get('product'), # e.g. 'Plex Media Server'
|
||||
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e...'
|
||||
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e..'
|
||||
'device': device.get('device'), # e.g. 'PC' or 'Windows'
|
||||
'platform': device.get('platform'), # e.g. 'Windows', 'Android'
|
||||
'local': device.get('publicAddressMatches') == '1',
|
||||
|
@ -412,7 +408,7 @@ def _pms_list_from_plex_tv(token):
|
|||
def _poke_pms(pms, queue):
|
||||
data = pms['connections'][0].attrib
|
||||
url = data['uri']
|
||||
if data['local'] == '1' and REGEX_PLEX_DIRECT.findall(url):
|
||||
if data['local'] == '1' and utils.REGEX_PLEX_DIRECT.findall(url):
|
||||
# In case DNS resolve of plex.direct does not work, append a new
|
||||
# connection that will directly access the local IP (e.g. internet down)
|
||||
conn = deepcopy(pms['connections'][0])
|
||||
|
@ -634,7 +630,7 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
|
|||
'repeat': '0'
|
||||
}
|
||||
if trailers is True:
|
||||
args['extrasPrefixCount'] = settings('trailerNumber')
|
||||
args['extrasPrefixCount'] = utils.settings('trailerNumber')
|
||||
xml = DU().downloadUrl(url + '?' + urlencode(args), action_type="POST")
|
||||
try:
|
||||
xml[0].tag
|
||||
|
@ -791,7 +787,7 @@ def GetUserArtworkURL(username):
|
|||
Returns the URL for the user's Avatar. Or False if something went
|
||||
wrong.
|
||||
"""
|
||||
users = plex_tv.list_home_users(settings('plexToken'))
|
||||
users = plex_tv.list_home_users(utils.settings('plexToken'))
|
||||
url = ''
|
||||
# If an error is encountered, set to False
|
||||
if not users:
|
||||
|
@ -818,13 +814,13 @@ def transcode_image_path(key, AuthToken, path, width, height):
|
|||
final path to image file
|
||||
"""
|
||||
# external address - can we get a transcoding request for external images?
|
||||
if key.startswith('http://') or key.startswith('https://'):
|
||||
if key.startswith('http://') or key.startswith('https://'):
|
||||
path = key
|
||||
elif key.startswith('/'): # internal full path.
|
||||
path = 'http://127.0.0.1:32400' + key
|
||||
else: # internal path, add-on
|
||||
path = 'http://127.0.0.1:32400' + path + '/' + key
|
||||
path = try_encode(path)
|
||||
path = utils.try_encode(path)
|
||||
# This is bogus (note the extra path component) but ATV is stupid when it
|
||||
# comes to caching images, it doesn't use querystrings. Fortunately PMS is
|
||||
# lenient...
|
|
@ -1,15 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from logging import getLogger
|
||||
|
||||
from xbmc import sleep, executebuiltin
|
||||
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import dialog, language as lang, settings, try_encode
|
||||
import variables as v
|
||||
import state
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.plex_tx')
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -39,7 +38,7 @@ def choose_home_user(token):
|
|||
username = user['title']
|
||||
userlist.append(username)
|
||||
# To take care of non-ASCII usernames
|
||||
userlist_coded.append(try_encode(username))
|
||||
userlist_coded.append(utils.try_encode(username))
|
||||
usernumber = len(userlist)
|
||||
username = ''
|
||||
usertoken = ''
|
||||
|
@ -47,12 +46,14 @@ def choose_home_user(token):
|
|||
while trials < 3:
|
||||
if usernumber > 1:
|
||||
# Select user
|
||||
user_select = dialog('select', lang(29999) + lang(39306),
|
||||
userlist_coded)
|
||||
user_select = utils.dialog(
|
||||
'select',
|
||||
'%s%s' % (utils.lang(29999), utils.lang(39306)),
|
||||
userlist_coded)
|
||||
if user_select == -1:
|
||||
LOG.info("No user selected.")
|
||||
settings('username', value='')
|
||||
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
||||
utils.settings('username', value='')
|
||||
executebuiltin('Addon.Openutils.settings(%s)' % v.ADDON_ID)
|
||||
return False
|
||||
# Only 1 user received, choose that one
|
||||
else:
|
||||
|
@ -64,11 +65,11 @@ def choose_home_user(token):
|
|||
pin = None
|
||||
if user['protected'] == '1':
|
||||
LOG.debug('Asking for users PIN')
|
||||
pin = dialog('input',
|
||||
lang(39307) + selected_user,
|
||||
'',
|
||||
type='{numeric}',
|
||||
option='{hide}')
|
||||
pin = utils.dialog('input',
|
||||
'%s%s' % (utils.lang(39307), selected_user),
|
||||
'',
|
||||
type='{numeric}',
|
||||
option='{hide}')
|
||||
# User chose to cancel
|
||||
# Plex bug: don't call url for protected user with empty PIN
|
||||
if not pin:
|
||||
|
@ -78,7 +79,7 @@ def choose_home_user(token):
|
|||
result = switch_home_user(user['id'],
|
||||
pin,
|
||||
token,
|
||||
settings('plex_machineIdentifier'))
|
||||
utils.settings('plex_machineIdentifier'))
|
||||
if result:
|
||||
# Successfully retrieved username: break out of while loop
|
||||
username = result['username']
|
||||
|
@ -88,15 +89,16 @@ def choose_home_user(token):
|
|||
else:
|
||||
trials += 1
|
||||
# Could not login user, please try again
|
||||
if not dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1=lang(39308) + selected_user,
|
||||
line2=lang(39309)):
|
||||
if not utils.dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1='%s%s' % (utils.lang(39308),
|
||||
selected_user),
|
||||
line2=utils.lang(39309)):
|
||||
# User chose to cancel
|
||||
break
|
||||
if not username:
|
||||
LOG.error('Failed signing in a user to plex.tv')
|
||||
executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID)
|
||||
executebuiltin('Addon.Openutils.settings(%s)' % v.ADDON_ID)
|
||||
return False
|
||||
return {
|
||||
'username': username,
|
||||
|
@ -123,7 +125,7 @@ def switch_home_user(userid, pin, token, machineIdentifier):
|
|||
for the machineIdentifier that was chosen
|
||||
}
|
||||
|
||||
settings('userid') and settings('username') with new plex token
|
||||
utils.settings('userid') and utils.settings('username') with new plex token
|
||||
"""
|
||||
LOG.info('Switching to user %s', userid)
|
||||
url = 'https://plex.tv/api/home/users/' + userid + '/switch'
|
||||
|
@ -143,12 +145,12 @@ def switch_home_user(userid, pin, token, machineIdentifier):
|
|||
token = answer.attrib.get('authenticationToken', '')
|
||||
|
||||
# Write to settings file
|
||||
settings('username', username)
|
||||
settings('accessToken', token)
|
||||
settings('userid', answer.attrib.get('id', ''))
|
||||
settings('plex_restricteduser',
|
||||
'true' if answer.attrib.get('restricted', '0') == '1'
|
||||
else 'false')
|
||||
utils.settings('username', username)
|
||||
utils.settings('accessToken', token)
|
||||
utils.settings('userid', answer.attrib.get('id', ''))
|
||||
utils.settings('plex_restricteduser',
|
||||
'true' if answer.attrib.get('restricted', '0') == '1'
|
||||
else 'false')
|
||||
state.RESTRICTED_USER = True if \
|
||||
answer.attrib.get('restricted', '0') == '1' else False
|
||||
|
||||
|
@ -239,15 +241,15 @@ def sign_in_with_pin():
|
|||
code, identifier = get_pin()
|
||||
if not code:
|
||||
# Problems trying to contact plex.tv. Try again later
|
||||
dialog('ok', heading='{plex}', line1=lang(39303))
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39303))
|
||||
return False
|
||||
# Go to https://plex.tv/pin and enter the code:
|
||||
# Or press No to cancel the sign in.
|
||||
answer = dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1=lang(39304) + "\n\n",
|
||||
line2=code + "\n\n",
|
||||
line3=lang(39311))
|
||||
answer = utils.dialog('yesno',
|
||||
heading='{plex}',
|
||||
line1='%s%s' % (utils.lang(39304), "\n\n"),
|
||||
line2='%s%s' % (code, "\n\n"),
|
||||
line3=utils.lang(39311))
|
||||
if not answer:
|
||||
return False
|
||||
count = 0
|
||||
|
@ -261,7 +263,7 @@ def sign_in_with_pin():
|
|||
count += 1
|
||||
if xml is False:
|
||||
# Could not sign in to plex.tv Try again later
|
||||
dialog('ok', heading='{plex}', line1=lang(39305))
|
||||
utils.dialog('ok', heading='{plex}', line1=utils.lang(39305))
|
||||
return False
|
||||
# Parse xml
|
||||
userid = xml.attrib.get('id')
|
||||
|
@ -282,15 +284,15 @@ def sign_in_with_pin():
|
|||
'plexid': userid,
|
||||
'homesize': home_size
|
||||
}
|
||||
settings('plexLogin', username)
|
||||
settings('plexToken', token)
|
||||
settings('plexhome', home)
|
||||
settings('plexid', userid)
|
||||
settings('plexAvatar', avatar)
|
||||
settings('plexHomeSize', home_size)
|
||||
utils.settings('plexLogin', username)
|
||||
utils.settings('plexToken', token)
|
||||
utils.settings('plexhome', home)
|
||||
utils.settings('plexid', userid)
|
||||
utils.settings('plexAvatar', avatar)
|
||||
utils.settings('plexHomeSize', home_size)
|
||||
# Let Kodi log into plex.tv on startup from now on
|
||||
settings('myplexlogin', 'true')
|
||||
settings('plex_status', value=lang(39227))
|
||||
utils.settings('myplexlogin', 'true')
|
||||
utils.settings('plex_status', value=utils.lang(39227))
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from socket import error as socket_error
|
|||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.httppersist')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
|
|
@ -6,19 +6,18 @@ from re import sub
|
|||
from SocketServer import ThreadingMixIn
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
from urlparse import urlparse, parse_qs
|
||||
import xbmc
|
||||
|
||||
from xbmc import sleep, Player, Monitor
|
||||
|
||||
from companion import process_command
|
||||
import json_rpc as js
|
||||
from clientinfo import getXArgsDeviceInfo
|
||||
import variables as v
|
||||
from .. import companion
|
||||
from .. import json_rpc as js
|
||||
from .. import clientinfo
|
||||
from .. import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
PLAYER = Player()
|
||||
MONITOR = Monitor()
|
||||
LOG = getLogger('PLEX.listener')
|
||||
PLAYER = xbmc.Player()
|
||||
MONITOR = xbmc.Monitor()
|
||||
|
||||
# Hack we need in order to keep track of the open connections from Plex Web
|
||||
CLIENT_DICT = {}
|
||||
|
@ -122,7 +121,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
RESOURCES_XML.format(
|
||||
title=v.DEVICENAME,
|
||||
machineIdentifier=v.PKC_MACHINE_IDENTIFIER),
|
||||
getXArgsDeviceInfo(include_token=False))
|
||||
clientinfo.getXArgsDeviceInfo(include_token=False))
|
||||
elif request_path == 'player/timeline/poll':
|
||||
# Plex web does polling if connected to PKC via Companion
|
||||
# Only reply if there is indeed something playing
|
||||
|
@ -188,7 +187,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
code=500)
|
||||
elif "/subscribe" in request_path:
|
||||
self.response(v.COMPANION_OK_MESSAGE,
|
||||
getXArgsDeviceInfo(include_token=False))
|
||||
clientinfo.getXArgsDeviceInfo(include_token=False))
|
||||
protocol = params.get('protocol')
|
||||
host = self.client_address[0]
|
||||
port = params.get('port')
|
||||
|
@ -201,14 +200,14 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||
command_id)
|
||||
elif "/unsubscribe" in request_path:
|
||||
self.response(v.COMPANION_OK_MESSAGE,
|
||||
getXArgsDeviceInfo(include_token=False))
|
||||
clientinfo.getXArgsDeviceInfo(include_token=False))
|
||||
uuid = self.headers.get('X-Plex-Client-Identifier') \
|
||||
or self.client_address[0]
|
||||
sub_mgr.remove_subscriber(uuid)
|
||||
else:
|
||||
# Throw it to companion.py
|
||||
process_command(request_path, params)
|
||||
self.response('', getXArgsDeviceInfo(include_token=False))
|
||||
companion.process_command(request_path, params)
|
||||
self.response('', clientinfo.getXArgsDeviceInfo(include_token=False))
|
||||
|
||||
|
||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
|
|
|
@ -25,16 +25,15 @@ import logging
|
|||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
import downloadutils
|
||||
from utils import window, settings, dialog, language
|
||||
import variables as v
|
||||
from ..downloadutils import DownloadUtils as DU
|
||||
from .. import utils
|
||||
from .. import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
log = logging.getLogger('PLEX.plexgdm')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -49,7 +48,7 @@ class plexgdm:
|
|||
self._multicast_address = '239.0.0.250'
|
||||
self.discover_group = (self._multicast_address, 32414)
|
||||
self.client_register_group = (self._multicast_address, 32413)
|
||||
self.client_update_port = int(settings('companionUpdatePort'))
|
||||
self.client_update_port = int(utils.settings('companionUpdatePort'))
|
||||
|
||||
self.server_list = []
|
||||
self.discovery_interval = 120
|
||||
|
@ -58,7 +57,7 @@ class plexgdm:
|
|||
self._registration_is_running = False
|
||||
|
||||
self.client_registered = False
|
||||
self.download = downloadutils.DownloadUtils().downloadUrl
|
||||
self.download = DU().downloadUrl
|
||||
|
||||
def clientDetails(self):
|
||||
self.client_data = (
|
||||
|
@ -120,14 +119,15 @@ class plexgdm:
|
|||
log.error("Unable to bind to port [%s] - Plex Companion will not "
|
||||
"be registered. Change the Plex Companion update port!"
|
||||
% self.client_update_port)
|
||||
if settings('companion_show_gdm_port_warning') == 'true':
|
||||
if dialog('yesno',
|
||||
language(29999),
|
||||
'Port %s' % self.client_update_port,
|
||||
language(39079),
|
||||
yeslabel=language(30013), # Never show again
|
||||
nolabel=language(30012)): # OK
|
||||
settings('companion_show_gdm_port_warning', value='false')
|
||||
if utils.settings('companion_show_gdm_port_warning') == 'true':
|
||||
if utils.dialog('yesno',
|
||||
utils.lang(29999),
|
||||
'Port %s' % self.client_update_port,
|
||||
utils.lang(39079),
|
||||
yeslabel=utils.lang(30013), # Never show again
|
||||
nolabel=utils.lang(30012)): # OK
|
||||
utils.settings('companion_show_gdm_port_warning',
|
||||
value='false')
|
||||
from xbmc import executebuiltin
|
||||
executebuiltin(
|
||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
|
@ -189,7 +189,7 @@ class plexgdm:
|
|||
log.info("Server list is empty. Unable to check")
|
||||
return False
|
||||
for server in self.server_list:
|
||||
if server['uuid'] == window('plex_machineIdentifier'):
|
||||
if server['uuid'] == utils.window('plex_machineIdentifier'):
|
||||
media_server = server['server']
|
||||
media_port = server['port']
|
||||
scheme = server['protocol']
|
||||
|
@ -223,7 +223,7 @@ class plexgdm:
|
|||
return self.server_list
|
||||
|
||||
def discover(self):
|
||||
currServer = window('pms_server')
|
||||
currServer = utils.window('pms_server')
|
||||
if not currServer:
|
||||
return
|
||||
currServerProt, currServerIP, currServerPort = \
|
||||
|
@ -240,9 +240,9 @@ class plexgdm:
|
|||
'owned': '1',
|
||||
'role': 'master',
|
||||
'server': currServerIP,
|
||||
'serverName': window('plex_servername'),
|
||||
'serverName': utils.window('plex_servername'),
|
||||
'updated': int(time.time()),
|
||||
'uuid': window('plex_machineIdentifier'),
|
||||
'uuid': utils.window('plex_machineIdentifier'),
|
||||
'version': 'irrelevant'
|
||||
}]
|
||||
|
||||
|
@ -305,5 +305,5 @@ class plexgdm:
|
|||
|
||||
def start_all(self, daemon=False):
|
||||
self.start_discovery(daemon)
|
||||
if settings('plexCompanion') == 'true':
|
||||
if utils.settings('plexCompanion') == 'true':
|
||||
self.start_registration(daemon)
|
||||
|
|
|
@ -3,22 +3,17 @@ Manages getting playstate from Kodi and sending it to the PMS as well as
|
|||
subscribed Plex Companion clients.
|
||||
"""
|
||||
from logging import getLogger
|
||||
from threading import Thread, RLock
|
||||
from threading import Thread
|
||||
|
||||
from downloadutils import DownloadUtils as DU
|
||||
from utils import window, kodi_time_to_millis, LockFunction
|
||||
import state
|
||||
import variables as v
|
||||
import json_rpc as js
|
||||
import playqueue as PQ
|
||||
from ..downloadutils import DownloadUtils as DU
|
||||
from .. import utils
|
||||
from .. import state
|
||||
from .. import variables as v
|
||||
from .. import json_rpc as js
|
||||
from .. import playqueue as PQ
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
# Need to lock all methods and functions messing with subscribers or state
|
||||
LOCK = RLock()
|
||||
LOCKER = LockFunction(LOCK)
|
||||
|
||||
LOG = getLogger('PLEX.subscribers')
|
||||
###############################################################################
|
||||
|
||||
# What is Companion controllable?
|
||||
|
@ -150,7 +145,6 @@ class SubscriptionMgr(object):
|
|||
position = info['position']
|
||||
return position
|
||||
|
||||
@LOCKER.lockthis
|
||||
def msg(self, players):
|
||||
"""
|
||||
Returns a timeline xml as str
|
||||
|
@ -190,94 +184,98 @@ class SubscriptionMgr(object):
|
|||
return answ
|
||||
|
||||
def _timeline_dict(self, player, ptype):
|
||||
playerid = player['playerid']
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
item = playqueue.items[position]
|
||||
except IndexError:
|
||||
# E.g. for direct path playback for single item
|
||||
return {
|
||||
with state.LOCK_PLAYQUEUES:
|
||||
playerid = player['playerid']
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
playqueue = PQ.PLAYQUEUES[playerid]
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
item = playqueue.items[position]
|
||||
except IndexError:
|
||||
# E.g. for direct path playback for single item
|
||||
return {
|
||||
'controllable': CONTROLLABLE[ptype],
|
||||
'type': ptype,
|
||||
'state': 'stopped'
|
||||
}
|
||||
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO,
|
||||
v.PLEX_PLAYLIST_TYPE_PHOTO):
|
||||
self.location = 'fullScreenVideo'
|
||||
self.stop_sent_to_web = False
|
||||
pbmc_server = utils.window('pms_server')
|
||||
if pbmc_server:
|
||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||
self.server = self.server.replace('/', '')
|
||||
status = 'paused' if int(info['speed']) == 0 else 'playing'
|
||||
duration = utils.kodi_time_to_millis(info['totaltime'])
|
||||
shuffle = '1' if info['shuffled'] else '0'
|
||||
mute = '1' if info['muted'] is True else '0'
|
||||
answ = {
|
||||
'controllable': CONTROLLABLE[ptype],
|
||||
'protocol': self.protocol,
|
||||
'address': self.server,
|
||||
'port': self.port,
|
||||
'machineIdentifier': utils.window('plex_machineIdentifier'),
|
||||
'state': status,
|
||||
'type': ptype,
|
||||
'state': 'stopped'
|
||||
'itemType': ptype,
|
||||
'time': utils.kodi_time_to_millis(info['time']),
|
||||
'duration': duration,
|
||||
'seekRange': '0-%s' % duration,
|
||||
'shuffle': shuffle,
|
||||
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']],
|
||||
'volume': info['volume'],
|
||||
'mute': mute,
|
||||
'mediaIndex': 0, # Still to implement from here
|
||||
'partIndex': 0,
|
||||
'partCount': 1,
|
||||
'providerIdentifier': 'com.plexapp.plugins.library',
|
||||
}
|
||||
if ptype in (v.PLEX_PLAYLIST_TYPE_VIDEO, v.PLEX_PLAYLIST_TYPE_PHOTO):
|
||||
self.location = 'fullScreenVideo'
|
||||
self.stop_sent_to_web = False
|
||||
pbmc_server = window('pms_server')
|
||||
if pbmc_server:
|
||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||
self.server = self.server.replace('/', '')
|
||||
status = 'paused' if int(info['speed']) == 0 else 'playing'
|
||||
duration = kodi_time_to_millis(info['totaltime'])
|
||||
shuffle = '1' if info['shuffled'] else '0'
|
||||
mute = '1' if info['muted'] is True else '0'
|
||||
answ = {
|
||||
'controllable': CONTROLLABLE[ptype],
|
||||
'protocol': self.protocol,
|
||||
'address': self.server,
|
||||
'port': self.port,
|
||||
'machineIdentifier': window('plex_machineIdentifier'),
|
||||
'state': status,
|
||||
'type': ptype,
|
||||
'itemType': ptype,
|
||||
'time': kodi_time_to_millis(info['time']),
|
||||
'duration': duration,
|
||||
'seekRange': '0-%s' % duration,
|
||||
'shuffle': shuffle,
|
||||
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']],
|
||||
'volume': info['volume'],
|
||||
'mute': mute,
|
||||
'mediaIndex': 0, # Still to implement from here
|
||||
'partIndex':0,
|
||||
'partCount': 1,
|
||||
'providerIdentifier': 'com.plexapp.plugins.library',
|
||||
}
|
||||
# Get the plex id from the PKC playqueue not info, as Kodi jumps to next
|
||||
# playqueue element way BEFORE kodi monitor onplayback is called
|
||||
if item.plex_id:
|
||||
answ['key'] = '/library/metadata/%s' % item.plex_id
|
||||
answ['ratingKey'] = item.plex_id
|
||||
# PlayQueue stuff
|
||||
if info['container_key']:
|
||||
answ['containerKey'] = info['container_key']
|
||||
if (info['container_key'] is not None and
|
||||
info['container_key'].startswith('/playQueues')):
|
||||
answ['playQueueID'] = playqueue.id
|
||||
answ['playQueueVersion'] = playqueue.version
|
||||
answ['playQueueItemID'] = item.id
|
||||
if playqueue.items[position].guid:
|
||||
answ['guid'] = item.guid
|
||||
# Temp. token set?
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
elif playqueue.plex_transient_token:
|
||||
answ['token'] = playqueue.plex_transient_token
|
||||
# Process audio and subtitle streams
|
||||
if ptype == v.PLEX_PLAYLIST_TYPE_VIDEO:
|
||||
strm_id = self._plex_stream_index(playerid, 'audio')
|
||||
if strm_id:
|
||||
answ['audioStreamID'] = strm_id
|
||||
else:
|
||||
LOG.error('We could not select a Plex audiostream')
|
||||
strm_id = self._plex_stream_index(playerid, 'video')
|
||||
if strm_id:
|
||||
answ['videoStreamID'] = strm_id
|
||||
else:
|
||||
LOG.error('We could not select a Plex videostream')
|
||||
if info['subtitleenabled']:
|
||||
try:
|
||||
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
||||
except KeyError:
|
||||
# subtitleenabled can be True while currentsubtitle can
|
||||
# still be {}
|
||||
strm_id = None
|
||||
if strm_id is not None:
|
||||
# If None, then the subtitle is only present on Kodi side
|
||||
answ['subtitleStreamID'] = strm_id
|
||||
return answ
|
||||
# Get the plex id from the PKC playqueue not info, as Kodi jumps to
|
||||
# next playqueue element way BEFORE kodi monitor onplayback is
|
||||
# called
|
||||
if item.plex_id:
|
||||
answ['key'] = '/library/metadata/%s' % item.plex_id
|
||||
answ['ratingKey'] = item.plex_id
|
||||
# PlayQueue stuff
|
||||
if info['container_key']:
|
||||
answ['containerKey'] = info['container_key']
|
||||
if (info['container_key'] is not None and
|
||||
info['container_key'].startswith('/playQueues')):
|
||||
answ['playQueueID'] = playqueue.id
|
||||
answ['playQueueVersion'] = playqueue.version
|
||||
answ['playQueueItemID'] = item.id
|
||||
if playqueue.items[position].guid:
|
||||
answ['guid'] = item.guid
|
||||
# Temp. token set?
|
||||
if state.PLEX_TRANSIENT_TOKEN:
|
||||
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
elif playqueue.plex_transient_token:
|
||||
answ['token'] = playqueue.plex_transient_token
|
||||
# Process audio and subtitle streams
|
||||
if ptype == v.PLEX_PLAYLIST_TYPE_VIDEO:
|
||||
strm_id = self._plex_stream_index(playerid, 'audio')
|
||||
if strm_id:
|
||||
answ['audioStreamID'] = strm_id
|
||||
else:
|
||||
LOG.error('We could not select a Plex audiostream')
|
||||
strm_id = self._plex_stream_index(playerid, 'video')
|
||||
if strm_id:
|
||||
answ['videoStreamID'] = strm_id
|
||||
else:
|
||||
LOG.error('We could not select a Plex videostream')
|
||||
if info['subtitleenabled']:
|
||||
try:
|
||||
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
||||
except KeyError:
|
||||
# subtitleenabled can be True while currentsubtitle can
|
||||
# still be {}
|
||||
strm_id = None
|
||||
if strm_id is not None:
|
||||
# If None, then the subtitle is only present on Kodi
|
||||
# side
|
||||
answ['subtitleStreamID'] = strm_id
|
||||
return answ
|
||||
|
||||
def signal_stop(self):
|
||||
"""
|
||||
|
@ -302,14 +300,14 @@ class SubscriptionMgr(object):
|
|||
return playqueue.items[position].plex_stream_index(
|
||||
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
|
||||
|
||||
@LOCKER.lockthis
|
||||
def update_command_id(self, uuid, command_id):
|
||||
"""
|
||||
Updates the Plex Companien client with the machine identifier uuid with
|
||||
command_id
|
||||
"""
|
||||
if command_id and self.subscribers.get(uuid):
|
||||
self.subscribers[uuid].command_id = int(command_id)
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
if command_id and self.subscribers.get(uuid):
|
||||
self.subscribers[uuid].command_id = int(command_id)
|
||||
|
||||
def _playqueue_init_done(self, players):
|
||||
"""
|
||||
|
@ -320,8 +318,6 @@ class SubscriptionMgr(object):
|
|||
for player in players.values():
|
||||
info = state.PLAYER_STATES[player['playerid']]
|
||||
playqueue = PQ.PLAYQUEUES[player['playerid']]
|
||||
LOG.debug('playqueue is: %s', playqueue)
|
||||
LOG.debug('info is: %s', info)
|
||||
position = self._get_correct_position(info, playqueue)
|
||||
try:
|
||||
item = playqueue.items[position]
|
||||
|
@ -334,34 +330,32 @@ class SubscriptionMgr(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
@LOCKER.lockthis
|
||||
def notify(self):
|
||||
"""
|
||||
Causes PKC to tell the PMS and Plex Companion players to receive a
|
||||
notification what's being played.
|
||||
"""
|
||||
self._cleanup()
|
||||
# Get all the active/playing Kodi players (video, audio, pictures)
|
||||
players = js.get_players()
|
||||
# Update the PKC info with what's playing on the Kodi side
|
||||
for player in players.values():
|
||||
update_player_info(player['playerid'])
|
||||
# Check whether we can use the CURRENT info or whether PKC is still
|
||||
# initializing
|
||||
if self._playqueue_init_done(players) is False:
|
||||
LOG.debug('PKC playqueue is still initializing - skipping update')
|
||||
return
|
||||
self._notify_server(players)
|
||||
if self.subscribers:
|
||||
msg = self.msg(players)
|
||||
for subscriber in self.subscribers.values():
|
||||
subscriber.send_update(msg)
|
||||
self.lastplayers = players
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
self._cleanup()
|
||||
# Get all the active/playing Kodi players (video, audio, pictures)
|
||||
players = js.get_players()
|
||||
# Update the PKC info with what's playing on the Kodi side
|
||||
for player in players.values():
|
||||
update_player_info(player['playerid'])
|
||||
# Check whether we can use the CURRENT info or whether PKC is still
|
||||
# initializing
|
||||
if self._playqueue_init_done(players) is False:
|
||||
LOG.debug('PKC playqueue is still initializing - skip update')
|
||||
return
|
||||
self._notify_server(players)
|
||||
if self.subscribers:
|
||||
msg = self.msg(players)
|
||||
for subscriber in self.subscribers.values():
|
||||
subscriber.send_update(msg)
|
||||
self.lastplayers = players
|
||||
|
||||
def _notify_server(self, players):
|
||||
for typus, player in players.iteritems():
|
||||
LOG.debug('player is %s', player)
|
||||
LOG.debug('typus is %s', typus)
|
||||
self._send_pms_notification(
|
||||
player['playerid'], self._get_pms_params(player['playerid']))
|
||||
try:
|
||||
|
@ -386,8 +380,8 @@ class SubscriptionMgr(object):
|
|||
'state': status,
|
||||
'ratingKey': item.plex_id,
|
||||
'key': '/library/metadata/%s' % item.plex_id,
|
||||
'time': kodi_time_to_millis(info['time']),
|
||||
'duration': kodi_time_to_millis(info['totaltime'])
|
||||
'time': utils.kodi_time_to_millis(info['time']),
|
||||
'duration': utils.kodi_time_to_millis(info['totaltime'])
|
||||
}
|
||||
if info['container_key'] is not None:
|
||||
# params['containerKey'] = info['container_key']
|
||||
|
@ -419,7 +413,6 @@ class SubscriptionMgr(object):
|
|||
LOG.debug("Sent server notification with parameters: %s to %s",
|
||||
xargs, url)
|
||||
|
||||
@LOCKER.lockthis
|
||||
def add_subscriber(self, protocol, host, port, uuid, command_id):
|
||||
"""
|
||||
Adds a new Plex Companion subscriber to PKC.
|
||||
|
@ -431,20 +424,21 @@ class SubscriptionMgr(object):
|
|||
command_id,
|
||||
self,
|
||||
self.request_mgr)
|
||||
self.subscribers[subscriber.uuid] = subscriber
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
self.subscribers[subscriber.uuid] = subscriber
|
||||
return subscriber
|
||||
|
||||
@LOCKER.lockthis
|
||||
def remove_subscriber(self, uuid):
|
||||
"""
|
||||
Removes a connected Plex Companion subscriber with machine identifier
|
||||
uuid from PKC notifications.
|
||||
(Calls the cleanup() method of the subscriber)
|
||||
"""
|
||||
for subscriber in self.subscribers.values():
|
||||
if subscriber.uuid == uuid or subscriber.host == uuid:
|
||||
subscriber.cleanup()
|
||||
del self.subscribers[subscriber.uuid]
|
||||
with state.LOCK_SUBSCRIBER:
|
||||
for subscriber in self.subscribers.values():
|
||||
if subscriber.uuid == uuid or subscriber.host == uuid:
|
||||
subscriber.cleanup()
|
||||
del self.subscribers[subscriber.uuid]
|
||||
|
||||
def _cleanup(self):
|
||||
for subscriber in self.subscribers.values():
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
###############################################################################
|
||||
|
||||
from utils import kodi_sql
|
||||
import variables as v
|
||||
from . import utils
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -17,7 +15,7 @@ class Get_Plex_DB():
|
|||
and the db gets closed
|
||||
"""
|
||||
def __enter__(self):
|
||||
self.plexconn = kodi_sql('plex')
|
||||
self.plexconn = utils.kodi_sql('plex')
|
||||
return Plex_DB_Functions(self.plexconn.cursor())
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
|
|
275
resources/lib/service_entry.py
Normal file
275
resources/lib/service_entry.py
Normal file
|
@ -0,0 +1,275 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
import logging
|
||||
import sys
|
||||
import xbmc
|
||||
|
||||
from . import utils
|
||||
from . import userclient
|
||||
from . import initialsetup
|
||||
from . import kodimonitor
|
||||
from . import librarysync
|
||||
from . import websocket_client
|
||||
from . import plex_companion
|
||||
from . import plex_functions as PF
|
||||
from . import command_pipeline
|
||||
from . import playback_starter
|
||||
from . import playqueue
|
||||
from . import artwork
|
||||
from . import variables as v
|
||||
from . import state
|
||||
from . import loghandler
|
||||
|
||||
###############################################################################
|
||||
loghandler.config()
|
||||
LOG = logging.getLogger("PLEX.service_entry")
|
||||
###############################################################################
|
||||
|
||||
|
||||
class Service():
|
||||
|
||||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
user = None
|
||||
ws = None
|
||||
library = None
|
||||
plexcompanion = None
|
||||
|
||||
user_running = False
|
||||
ws_running = False
|
||||
alexa_running = False
|
||||
library_running = False
|
||||
plexcompanion_running = False
|
||||
kodimonitor_running = False
|
||||
playback_starter_running = False
|
||||
image_cache_thread_running = False
|
||||
|
||||
def __init__(self):
|
||||
# Initial logging
|
||||
LOG.info("======== START %s ========", v.ADDON_NAME)
|
||||
LOG.info("Platform: %s", v.PLATFORM)
|
||||
LOG.info("KODI Version: %s", v.KODILONGVERSION)
|
||||
LOG.info("%s Version: %s", v.ADDON_NAME, v.ADDON_VERSION)
|
||||
LOG.info("PKC Direct Paths: %s",
|
||||
utils.settings('useDirectPaths') == '1')
|
||||
LOG.info("Number of sync threads: %s",
|
||||
utils.settings('syncThreadNumber'))
|
||||
LOG.info('Playlist m3u encoding: %s', v.M3U_ENCODING)
|
||||
LOG.info("Full sys.argv received: %s", sys.argv)
|
||||
self.monitor = xbmc.Monitor()
|
||||
# Load/Reset PKC entirely - important for user/Kodi profile switch
|
||||
initialsetup.reload_pkc()
|
||||
|
||||
def _stop_pkc(self):
|
||||
"""
|
||||
Kodi's abortRequested is really unreliable :-(
|
||||
"""
|
||||
return self.monitor.abortRequested() or state.STOP_PKC
|
||||
|
||||
def ServiceEntryPoint(self):
|
||||
# Important: Threads depending on abortRequest will not trigger
|
||||
# if profile switch happens more than once.
|
||||
_stop_pkc = self._stop_pkc
|
||||
monitor = self.monitor
|
||||
|
||||
# Server auto-detect
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
||||
# Detect playback start early on
|
||||
self.command_pipeline = command_pipeline.Monitor_Window()
|
||||
self.command_pipeline.start()
|
||||
|
||||
# Initialize important threads, handing over self for callback purposes
|
||||
self.user = userclient.UserClient()
|
||||
self.ws = websocket_client.PMS_Websocket()
|
||||
self.alexa = websocket_client.Alexa_Websocket()
|
||||
self.library = librarysync.LibrarySync()
|
||||
self.plexcompanion = plex_companion.PlexCompanion()
|
||||
self.specialmonitor = kodimonitor.SpecialMonitor()
|
||||
self.playback_starter = playback_starter.PlaybackStarter()
|
||||
self.playqueue = playqueue.PlayqueueMonitor()
|
||||
if utils.settings('enableTextureCache') == "true":
|
||||
self.image_cache_thread = artwork.Image_Cache_Thread()
|
||||
|
||||
welcome_msg = True
|
||||
counter = 0
|
||||
while not _stop_pkc():
|
||||
|
||||
if utils.window('plex_kodiProfile') != v.KODI_PROFILE:
|
||||
# Profile change happened, terminate this thread and others
|
||||
LOG.info("Kodi profile was: %s and changed to: %s. "
|
||||
"Terminating old PlexKodiConnect thread.",
|
||||
v.KODI_PROFILE, utils.window('plex_kodiProfile'))
|
||||
break
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
# 3. User has access to the server
|
||||
|
||||
if utils.window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (self.user.user is not None) and self.user.has_access:
|
||||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
if welcome_msg is True:
|
||||
# Reset authentication warnings
|
||||
welcome_msg = False
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
"%s %s" % (utils.lang(33000),
|
||||
self.user.user),
|
||||
icon='{plex}',
|
||||
time=2000,
|
||||
sound=False)
|
||||
# Start monitoring kodi events
|
||||
self.kodimonitor_running = kodimonitor.KodiMonitor()
|
||||
self.specialmonitor.start()
|
||||
# Start the Websocket Client
|
||||
if not self.ws_running:
|
||||
self.ws_running = True
|
||||
self.ws.start()
|
||||
# Start the Alexa thread
|
||||
if (not self.alexa_running and
|
||||
utils.settings('enable_alexa') == 'true'):
|
||||
self.alexa_running = True
|
||||
self.alexa.start()
|
||||
# Start the syncing thread
|
||||
if not self.library_running:
|
||||
self.library_running = True
|
||||
self.library.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexcompanion_running:
|
||||
self.plexcompanion_running = True
|
||||
self.plexcompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.start()
|
||||
self.playqueue.start()
|
||||
if (not self.image_cache_thread_running and
|
||||
utils.settings('enableTextureCache') == "true"):
|
||||
self.image_cache_thread_running = True
|
||||
self.image_cache_thread.start()
|
||||
else:
|
||||
if (self.user.user is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future
|
||||
# warning
|
||||
self.warn_auth = False
|
||||
LOG.warn("Not authenticated yet.")
|
||||
|
||||
# User access is restricted.
|
||||
# Keep verifying until access is granted
|
||||
# unless server goes offline or Kodi is shut down.
|
||||
while self.user.has_access is False:
|
||||
# Verify access with an API call
|
||||
self.user.check_access()
|
||||
|
||||
if utils.window('plex_online') != "true":
|
||||
# Server went offline
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
else:
|
||||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not self._stop_pkc():
|
||||
server = self.user.get_server()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
elif PF.check_connection(server, verifySSL=True) is False:
|
||||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
self.server_online = False
|
||||
utils.window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
state.SUSPEND_LIBRARY_THREAD = True
|
||||
LOG.error("Plex Media Server went offline")
|
||||
if utils.settings('show_pms_offline') == 'true':
|
||||
utils.dialog('notification',
|
||||
utils.lang(33001),
|
||||
"%s %s" % (utils.lang(29999),
|
||||
utils.lang(33002)),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
counter += 1
|
||||
# Periodically check if the IP changed, e.g. per minute
|
||||
if counter > 20:
|
||||
counter = 0
|
||||
setup = initialsetup.InitialSetup()
|
||||
tmp = setup.pick_pms()
|
||||
if tmp is not None:
|
||||
setup.write_pms_to_settings(tmp)
|
||||
else:
|
||||
# Server is online
|
||||
counter = 0
|
||||
if not self.server_online:
|
||||
# Server was offline when Kodi started.
|
||||
# Wait for server to be fully established.
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
self.server_online = True
|
||||
# Alert the user that server is online.
|
||||
if (welcome_msg is False and
|
||||
utils.settings('show_pms_offline') == 'true'):
|
||||
utils.dialog('notification',
|
||||
utils.lang(29999),
|
||||
utils.lang(33003),
|
||||
icon='{plex}',
|
||||
time=5000,
|
||||
sound=False)
|
||||
LOG.info("Server %s is online and ready.", server)
|
||||
utils.window('plex_online', value="true")
|
||||
if state.AUTHENTICATED:
|
||||
# Server got offline when we were authenticated.
|
||||
# Hence resume threads
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
|
||||
# Start the userclient thread
|
||||
if not self.user_running:
|
||||
self.user_running = True
|
||||
self.user.start()
|
||||
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(0.05):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
# Terminating PlexKodiConnect
|
||||
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
state.STOP_PKC = True
|
||||
utils.window('plex_service_started', clear=True)
|
||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||
|
||||
|
||||
def start():
|
||||
# Safety net - Kody starts PKC twice upon first installation!
|
||||
if utils.window('plex_service_started') == 'true':
|
||||
EXIT = True
|
||||
else:
|
||||
utils.window('plex_service_started', value='true')
|
||||
EXIT = False
|
||||
|
||||
# Delay option
|
||||
DELAY = int(utils.settings('startupDelay'))
|
||||
|
||||
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
|
||||
if EXIT:
|
||||
LOG.error('PKC service.py already started - exiting this instance')
|
||||
elif DELAY and xbmc.Monitor().waitForAbort(DELAY):
|
||||
# Start the service
|
||||
LOG.info("Abort requested while waiting. PKC not started.")
|
||||
else:
|
||||
Service().ServiceEntryPoint()
|
|
@ -1,5 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# THREAD SAFE
|
||||
from threading import Lock, RLock
|
||||
|
||||
|
||||
# LOCKS
|
||||
####################
|
||||
# Need to lock all methods and functions messing with Plex Companion subscribers
|
||||
LOCK_SUBSCRIBER = RLock()
|
||||
# Need to lock everything messing with Kodi/PKC playqueues
|
||||
LOCK_PLAYQUEUES = RLock()
|
||||
# Necessary to temporarily hold back librarysync/websocket listener when doing
|
||||
# a full sync
|
||||
LOCK_PLAYLISTS = Lock()
|
||||
|
||||
# Quit PKC
|
||||
STOP_PKC = False
|
||||
|
|
|
@ -3,24 +3,24 @@
|
|||
from logging import getLogger
|
||||
from threading import Thread
|
||||
|
||||
from xbmc import sleep, executebuiltin, translatePath
|
||||
import xbmcaddon
|
||||
from xbmcvfs import exists
|
||||
from xbmc import sleep, executebuiltin
|
||||
|
||||
from utils import window, settings, language as lang, thread_methods, dialog
|
||||
from downloadutils import DownloadUtils as DU
|
||||
import plex_tv
|
||||
import PlexFunctions as PF
|
||||
import state
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import plex_tv
|
||||
from . import plex_functions as PF
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.userclient')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
|
||||
@utils.thread_methods(add_suspends=['SUSPEND_USER_CLIENT'])
|
||||
class UserClient(Thread):
|
||||
"""
|
||||
Manage Plex users
|
||||
|
@ -44,7 +44,6 @@ class UserClient(Thread):
|
|||
self.ssl = None
|
||||
self.sslcert = None
|
||||
|
||||
self.addon = xbmcaddon.Addon()
|
||||
self.do_utils = None
|
||||
|
||||
Thread.__init__(self)
|
||||
|
@ -54,11 +53,11 @@ class UserClient(Thread):
|
|||
Get the current PMS' URL
|
||||
"""
|
||||
# Original host
|
||||
self.server_name = settings('plex_servername')
|
||||
https = settings('https') == "true"
|
||||
host = settings('ipaddress')
|
||||
port = settings('port')
|
||||
self.machine_identifier = settings('plex_machineIdentifier')
|
||||
self.server_name = utils.settings('plex_servername')
|
||||
https = utils.settings('https') == "true"
|
||||
host = utils.settings('ipaddress')
|
||||
port = utils.settings('port')
|
||||
self.machine_identifier = utils.settings('plex_machineIdentifier')
|
||||
if not host:
|
||||
LOG.debug("No server information saved.")
|
||||
return False
|
||||
|
@ -74,7 +73,8 @@ class UserClient(Thread):
|
|||
self.machine_identifier = PF.GetMachineIdentifier(server)
|
||||
if not self.machine_identifier:
|
||||
self.machine_identifier = ''
|
||||
settings('plex_machineIdentifier', value=self.machine_identifier)
|
||||
utils.settings('plex_machineIdentifier',
|
||||
value=self.machine_identifier)
|
||||
LOG.debug('Returning active server: %s', server)
|
||||
return server
|
||||
|
||||
|
@ -84,15 +84,15 @@ class UserClient(Thread):
|
|||
Do we need to verify the SSL certificate? Return None if that is the
|
||||
case, else False
|
||||
"""
|
||||
return None if settings('sslverify') == 'true' else False
|
||||
return None if utils.settings('sslverify') == 'true' else False
|
||||
|
||||
@staticmethod
|
||||
def get_ssl_certificate():
|
||||
"""
|
||||
Client side certificate
|
||||
"""
|
||||
return None if settings('sslcert') == 'None' \
|
||||
else settings('sslcert')
|
||||
return None if utils.settings('sslcert') == 'None' \
|
||||
else utils.settings('sslcert')
|
||||
|
||||
def set_user_prefs(self):
|
||||
"""
|
||||
|
@ -103,7 +103,7 @@ class UserClient(Thread):
|
|||
if self.token:
|
||||
url = PF.GetUserArtworkURL(self.user)
|
||||
if url:
|
||||
window('PlexUserImage', value=url)
|
||||
utils.window('PlexUserImage', value=url)
|
||||
|
||||
@staticmethod
|
||||
def check_access():
|
||||
|
@ -141,29 +141,32 @@ class UserClient(Thread):
|
|||
state.PLEX_USER_ID = user_id or None
|
||||
state.PLEX_USERNAME = username
|
||||
# This is the token for the current PMS (might also be '')
|
||||
window('pms_token', value=usertoken)
|
||||
utils.window('pms_token', value=usertoken)
|
||||
state.PMS_TOKEN = usertoken
|
||||
# This is the token for plex.tv for the current user
|
||||
# Is only '' if user is not signed in to plex.tv
|
||||
window('plex_token', value=settings('plexToken'))
|
||||
state.PLEX_TOKEN = settings('plexToken') or None
|
||||
window('plex_restricteduser', value=settings('plex_restricteduser'))
|
||||
utils.window('plex_token', value=utils.settings('plexToken'))
|
||||
state.PLEX_TOKEN = utils.settings('plexToken') or None
|
||||
utils.window('plex_restricteduser',
|
||||
value=utils.settings('plex_restricteduser'))
|
||||
state.RESTRICTED_USER = True \
|
||||
if settings('plex_restricteduser') == 'true' else False
|
||||
window('pms_server', value=self.server)
|
||||
window('plex_machineIdentifier', value=self.machine_identifier)
|
||||
window('plex_servername', value=self.server_name)
|
||||
window('plex_authenticated', value='true')
|
||||
if utils.settings('plex_restricteduser') == 'true' else False
|
||||
utils.window('pms_server', value=self.server)
|
||||
utils.window('plex_machineIdentifier', value=self.machine_identifier)
|
||||
utils.window('plex_servername', value=self.server_name)
|
||||
utils.window('plex_authenticated', value='true')
|
||||
state.AUTHENTICATED = True
|
||||
|
||||
window('useDirectPaths', value='true'
|
||||
if settings('useDirectPaths') == "1" else 'false')
|
||||
state.DIRECT_PATHS = True if settings('useDirectPaths') == "1" \
|
||||
utils.window('useDirectPaths',
|
||||
value='true' if utils.settings('useDirectPaths') == "1"
|
||||
else 'false')
|
||||
state.DIRECT_PATHS = True if utils.settings('useDirectPaths') == "1" \
|
||||
else False
|
||||
state.INDICATE_MEDIA_VERSIONS = True \
|
||||
if settings('indicate_media_versions') == "true" else False
|
||||
window('plex_force_transcode_pix', value='true'
|
||||
if settings('force_transcode_pix') == "1" else 'false')
|
||||
if utils.settings('indicate_media_versions') == "true" else False
|
||||
utils.window('plex_force_transcode_pix',
|
||||
value='true' if utils.settings('force_transcode_pix') == "1"
|
||||
else 'false')
|
||||
|
||||
# Start DownloadUtils session
|
||||
self.do_utils = DU()
|
||||
|
@ -173,9 +176,9 @@ class UserClient(Thread):
|
|||
self.set_user_prefs()
|
||||
|
||||
# Writing values to settings file
|
||||
settings('username', value=username)
|
||||
settings('userid', value=user_id)
|
||||
settings('accessToken', value=usertoken)
|
||||
utils.settings('username', value=username)
|
||||
utils.settings('userid', value=user_id)
|
||||
utils.settings('accessToken', value=usertoken)
|
||||
return True
|
||||
|
||||
def authenticate(self):
|
||||
|
@ -188,16 +191,13 @@ class UserClient(Thread):
|
|||
if self.retry >= 2:
|
||||
LOG.error("Too many retries to login.")
|
||||
state.PMS_STATUS = 'Stop'
|
||||
dialog('ok', lang(33001), lang(39023))
|
||||
utils.dialog('ok', utils.lang(33001), utils.lang(39023))
|
||||
executebuiltin(
|
||||
'Addon.OpenSettings(plugin.video.plexkodiconnect)')
|
||||
'Addon.Openutils.settings(plugin.video.plexkodiconnect)')
|
||||
return False
|
||||
|
||||
# Get /profile/addon_data
|
||||
addondir = translatePath(self.addon.getAddonInfo('profile'))
|
||||
|
||||
# If there's no settings.xml
|
||||
if not exists("%ssettings.xml" % addondir):
|
||||
if not path_ops.exists("%ssettings.xml" % v.ADDON_PROFILE):
|
||||
LOG.error("Error, no settings.xml found.")
|
||||
self.auth = False
|
||||
return False
|
||||
|
@ -209,10 +209,10 @@ class UserClient(Thread):
|
|||
return False
|
||||
|
||||
# If there is a username in the settings, try authenticating
|
||||
username = settings('username')
|
||||
userId = settings('userid')
|
||||
usertoken = settings('accessToken')
|
||||
enforceLogin = settings('enforceUserLogin')
|
||||
username = utils.settings('username')
|
||||
userId = utils.settings('userid')
|
||||
usertoken = utils.settings('accessToken')
|
||||
enforceLogin = utils.settings('enforceUserLogin')
|
||||
# Found a user in the settings, try to authenticate
|
||||
if username and enforceLogin == 'false':
|
||||
LOG.debug('Trying to authenticate with old settings')
|
||||
|
@ -225,15 +225,15 @@ class UserClient(Thread):
|
|||
return True
|
||||
elif answ == 401:
|
||||
LOG.error("User token no longer valid. Sign user out")
|
||||
settings('username', value='')
|
||||
settings('userid', value='')
|
||||
settings('accessToken', value='')
|
||||
utils.settings('username', value='')
|
||||
utils.settings('userid', value='')
|
||||
utils.settings('accessToken', value='')
|
||||
else:
|
||||
LOG.debug("Could not yet authenticate user")
|
||||
return False
|
||||
|
||||
# Could not use settings - try to get Plex user list from plex.tv
|
||||
plextoken = settings('plexToken')
|
||||
plextoken = utils.settings('plexToken')
|
||||
if plextoken:
|
||||
LOG.info("Trying to connect to plex.tv to get a user list")
|
||||
userInfo = plex_tv.choose_home_user(plextoken)
|
||||
|
@ -268,24 +268,24 @@ class UserClient(Thread):
|
|||
self.do_utils.stopSession()
|
||||
except AttributeError:
|
||||
pass
|
||||
window('plex_authenticated', clear=True)
|
||||
utils.window('plex_authenticated', clear=True)
|
||||
state.AUTHENTICATED = False
|
||||
window('pms_token', clear=True)
|
||||
utils.window('pms_token', clear=True)
|
||||
state.PLEX_TOKEN = None
|
||||
state.PLEX_TRANSIENT_TOKEN = None
|
||||
state.PMS_TOKEN = None
|
||||
window('plex_token', clear=True)
|
||||
window('pms_server', clear=True)
|
||||
window('plex_machineIdentifier', clear=True)
|
||||
window('plex_servername', clear=True)
|
||||
utils.window('plex_token', clear=True)
|
||||
utils.window('pms_server', clear=True)
|
||||
utils.window('plex_machineIdentifier', clear=True)
|
||||
utils.window('plex_servername', clear=True)
|
||||
state.PLEX_USER_ID = None
|
||||
state.PLEX_USERNAME = None
|
||||
window('plex_restricteduser', clear=True)
|
||||
utils.window('plex_restricteduser', clear=True)
|
||||
state.RESTRICTED_USER = False
|
||||
|
||||
settings('username', value='')
|
||||
settings('userid', value='')
|
||||
settings('accessToken', value='')
|
||||
utils.settings('username', value='')
|
||||
utils.settings('userid', value='')
|
||||
utils.settings('accessToken', value='')
|
||||
|
||||
self.token = None
|
||||
self.auth = True
|
||||
|
@ -313,7 +313,7 @@ class UserClient(Thread):
|
|||
elif state.PMS_STATUS == "401":
|
||||
# Unauthorized access, revoke token
|
||||
state.PMS_STATUS = 'Auth'
|
||||
window('plex_serverStatus', value='Auth')
|
||||
utils.window('plex_serverStatus', value='Auth')
|
||||
self.reset_client()
|
||||
sleep(3000)
|
||||
|
||||
|
@ -330,7 +330,7 @@ class UserClient(Thread):
|
|||
LOG.info("Current userId: %s", state.PLEX_USER_ID)
|
||||
self.retry = 0
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
window('plex_serverStatus', clear=True)
|
||||
utils.window('plex_serverStatus', clear=True)
|
||||
state.PMS_STATUS = False
|
||||
|
||||
if not self.auth and (self.user is None):
|
||||
|
|
|
@ -4,7 +4,6 @@ Various functions and decorators for PKC
|
|||
"""
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
import os
|
||||
from cProfile import Profile
|
||||
from pstats import Stats
|
||||
from sqlite3 import connect, OperationalError
|
||||
|
@ -14,31 +13,38 @@ from time import localtime, strftime
|
|||
from unicodedata import normalize
|
||||
import xml.etree.ElementTree as etree
|
||||
from functools import wraps, partial
|
||||
from shutil import rmtree
|
||||
from urllib import quote_plus
|
||||
import hashlib
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
import xbmc
|
||||
import xbmcaddon
|
||||
import xbmcgui
|
||||
from xbmcvfs import exists, delete
|
||||
|
||||
import variables as v
|
||||
import state
|
||||
from . import path_ops
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.utils')
|
||||
|
||||
WINDOW = xbmcgui.Window(10000)
|
||||
ADDON = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
|
||||
EPOCH = datetime.utcfromtimestamp(0)
|
||||
|
||||
# Grab Plex id from '...plex_id=XXXX....'
|
||||
REGEX_PLEX_ID = re.compile(r'''plex_id=(\d+)''')
|
||||
# Return the numbers at the end of an url like '.../.../XXXX'
|
||||
REGEX_END_DIGITS = re.compile(r'''/(.+)/(\d+)$''')
|
||||
REGEX_PLEX_DIRECT = re.compile(r'''\.plex\.direct:\d+$''')
|
||||
REGEX_FILE_NUMBERING = re.compile(r'''_(\d+)\.\w+$''')
|
||||
|
||||
# Plex API
|
||||
REGEX_IMDB = re.compile(r'''/(tt\d+)''')
|
||||
REGEX_TVDB = re.compile(r'''thetvdb:\/\/(.+?)\?''')
|
||||
# Plex music
|
||||
REGEX_MUSICPATH = re.compile(r'''^\^(.+)\$$''')
|
||||
# Grab Plex id from an URL-encoded string
|
||||
REGEX_PLEX_ID_FROM_URL = re.compile(r'''metadata%2F(\d+)''')
|
||||
|
||||
###############################################################################
|
||||
# Main methods
|
||||
|
@ -51,7 +57,7 @@ def reboot_kodi(message=None):
|
|||
|
||||
Set optional custom message
|
||||
"""
|
||||
message = message or language(33033)
|
||||
message = message or lang(33033)
|
||||
dialog('ok', heading='{plex}', line1=message)
|
||||
xbmc.executebuiltin('RestartApp')
|
||||
|
||||
|
@ -106,31 +112,7 @@ def settings(setting, value=None):
|
|||
return try_decode(addon.getSetting(setting))
|
||||
|
||||
|
||||
def exists_dir(path):
|
||||
"""
|
||||
Safe way to check whether the directory path exists already (broken in Kodi
|
||||
<17)
|
||||
|
||||
Feed with encoded string or unicode
|
||||
"""
|
||||
if v.KODIVERSION >= 17:
|
||||
answ = exists(try_encode(path))
|
||||
else:
|
||||
dummyfile = os.path.join(try_decode(path), 'dummyfile.txt')
|
||||
try:
|
||||
with open(dummyfile, 'w') as filer:
|
||||
filer.write('text')
|
||||
except IOError:
|
||||
# folder does not exist yet
|
||||
answ = 0
|
||||
else:
|
||||
# Folder exists. Delete file again.
|
||||
delete(try_encode(dummyfile))
|
||||
answ = 1
|
||||
return answ
|
||||
|
||||
|
||||
def language(stringid):
|
||||
def lang(stringid):
|
||||
"""
|
||||
Central string retrieval from strings.po
|
||||
"""
|
||||
|
@ -194,7 +176,7 @@ def dialog(typus, *args, **kwargs):
|
|||
kwargs['option'] = types[kwargs['option']]
|
||||
if 'heading' in kwargs:
|
||||
kwargs['heading'] = kwargs['heading'].replace("{plex}",
|
||||
language(29999))
|
||||
lang(29999))
|
||||
dia = xbmcgui.Dialog()
|
||||
types = {
|
||||
'yesno': dia.yesno,
|
||||
|
@ -313,10 +295,6 @@ def valid_filename(text):
|
|||
else:
|
||||
# Linux
|
||||
text = re.sub(r'/', '', text)
|
||||
if not os.path.supports_unicode_filenames:
|
||||
text = unicodedata.normalize('NFKD', text)
|
||||
text = text.encode('ascii', 'ignore')
|
||||
text = text.decode('ascii')
|
||||
# Ensure that filename length is at most 255 chars (including 3 chars for
|
||||
# filename extension and 1 dot to separate the extension)
|
||||
text = text[:min(len(text), 251)]
|
||||
|
@ -465,15 +443,15 @@ def wipe_database():
|
|||
# Delete all synced playlists
|
||||
for path in playlist_paths:
|
||||
try:
|
||||
os.remove(path)
|
||||
path_ops.remove(path)
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
LOG.info("Resetting all cached artwork.")
|
||||
# Remove all existing textures first
|
||||
path = xbmc.translatePath("special://thumbnails/")
|
||||
if exists(path):
|
||||
rmtree(try_decode(path), ignore_errors=True)
|
||||
path = path_ops.translate_path("special://thumbnails/")
|
||||
if path_ops.exists(path):
|
||||
path_ops.rmtree(path, ignore_errors=True)
|
||||
# remove all existing data from texture DB
|
||||
connection = kodi_sql('texture')
|
||||
cursor = connection.cursor()
|
||||
|
@ -487,8 +465,8 @@ def wipe_database():
|
|||
connection.commit()
|
||||
cursor.close()
|
||||
# Reset the artwork sync status in the PKC settings
|
||||
settings('caching_artwork_count', value=language(39310))
|
||||
settings('fanarttv_lookups', value=language(39310))
|
||||
settings('caching_artwork_count', value=lang(39310))
|
||||
settings('fanarttv_lookups', value=lang(39310))
|
||||
# reset the install run flag
|
||||
settings('SyncInstallRunDone', value="false")
|
||||
|
||||
|
@ -500,8 +478,8 @@ def reset(ask_user=True):
|
|||
"""
|
||||
# Are you sure you want to reset your local Kodi database?
|
||||
if ask_user and not dialog('yesno',
|
||||
heading='{plex} %s ' % language(30132),
|
||||
line1=language(39600)):
|
||||
heading='{plex} %s ' % lang(30132),
|
||||
line1=lang(39600)):
|
||||
return
|
||||
|
||||
# first stop any db sync
|
||||
|
@ -513,8 +491,8 @@ def reset(ask_user=True):
|
|||
if count == 0:
|
||||
# Could not stop the database from running. Please try again later.
|
||||
dialog('ok',
|
||||
heading='{plex} %s' % language(30132),
|
||||
line1=language(39601))
|
||||
heading='{plex} %s' % lang(30132),
|
||||
line1=lang(39601))
|
||||
return
|
||||
xbmc.sleep(1000)
|
||||
|
||||
|
@ -524,13 +502,11 @@ def reset(ask_user=True):
|
|||
# Reset all PlexKodiConnect Addon settings? (this is usually NOT
|
||||
# recommended and unnecessary!)
|
||||
if ask_user and dialog('yesno',
|
||||
heading='{plex} %s ' % language(30132),
|
||||
line1=language(39603)):
|
||||
heading='{plex} %s ' % lang(30132),
|
||||
line1=lang(39603)):
|
||||
# Delete the settings
|
||||
addon = xbmcaddon.Addon()
|
||||
addondir = try_decode(xbmc.translatePath(addon.getAddonInfo('profile')))
|
||||
LOG.info("Deleting: settings.xml")
|
||||
os.remove("%ssettings.xml" % addondir)
|
||||
path_ops.remove("%ssettings.xml" % v.ADDON_PROFILE)
|
||||
reboot_kodi()
|
||||
|
||||
|
||||
|
@ -592,29 +568,6 @@ def compare_version(current, minimum):
|
|||
return curr_patch >= min_patch
|
||||
|
||||
|
||||
def normalize_nodes(text):
|
||||
"""
|
||||
For video nodes
|
||||
"""
|
||||
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.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 = try_encode(normalize('NFKD', unicode(text, 'utf-8')))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def normalize_string(text):
|
||||
"""
|
||||
For theme media, do not modify unless modified in TV Tunes
|
||||
|
@ -636,6 +589,28 @@ def normalize_string(text):
|
|||
return text
|
||||
|
||||
|
||||
def normalize_nodes(text):
|
||||
"""
|
||||
For video nodes. Returns unicode
|
||||
"""
|
||||
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.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 = normalize('NFKD', unicode(text, 'utf-8'))
|
||||
return text
|
||||
|
||||
|
||||
def indent(elem, level=0):
|
||||
"""
|
||||
Prettifies xml trees. Pass the etree root in
|
||||
|
@ -682,9 +657,9 @@ class XmlKodiSetting(object):
|
|||
top_element=None):
|
||||
self.filename = filename
|
||||
if path is None:
|
||||
self.path = os.path.join(v.KODI_PROFILE, filename)
|
||||
self.path = path_ops.path.join(v.KODI_PROFILE, filename)
|
||||
else:
|
||||
self.path = os.path.join(path, filename)
|
||||
self.path = path_ops.path.join(path, filename)
|
||||
self.force_create = force_create
|
||||
self.top_element = top_element
|
||||
self.tree = None
|
||||
|
@ -708,7 +683,7 @@ class XmlKodiSetting(object):
|
|||
LOG.error('Error parsing %s', self.path)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please
|
||||
# visit {1} and correct your file!"
|
||||
dialog('ok', language(29999), language(39716).format(
|
||||
dialog('ok', lang(29999), lang(39716).format(
|
||||
self.filename,
|
||||
'http://kodi.wiki'))
|
||||
self.__exit__(etree.ParseError, None, None)
|
||||
|
@ -849,7 +824,7 @@ def passwords_xml():
|
|||
"""
|
||||
To add network credentials to Kodi's password xml
|
||||
"""
|
||||
path = try_decode(xbmc.translatePath("special://userdata/"))
|
||||
path = path_ops.translate_path('special://userdata/')
|
||||
xmlpath = "%spasswords.xml" % path
|
||||
try:
|
||||
xmlparse = etree.parse(xmlpath)
|
||||
|
@ -861,7 +836,7 @@ def passwords_xml():
|
|||
LOG.error('Error parsing %s', xmlpath)
|
||||
# "Kodi cannot parse {0}. PKC will not function correctly. Please visit
|
||||
# {1} and correct your file!"
|
||||
dialog('ok', language(29999), language(39716).format(
|
||||
dialog('ok', lang(29999), lang(39716).format(
|
||||
'passwords.xml', 'http://forum.kodi.tv/'))
|
||||
return
|
||||
else:
|
||||
|
@ -970,7 +945,7 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
"""
|
||||
Feed with tagname as unicode
|
||||
"""
|
||||
path = try_decode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||
path = path_ops.translate_path("special://profile/playlists/video/")
|
||||
if viewtype == "mixed":
|
||||
plname = "%s - %s" % (tagname, mediatype)
|
||||
xsppath = "%sPlex %s - %s.xsp" % (path, viewid, mediatype)
|
||||
|
@ -979,15 +954,15 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
xsppath = "%sPlex %s.xsp" % (path, viewid)
|
||||
|
||||
# Create the playlist directory
|
||||
if not exists(try_encode(path)):
|
||||
if not path_ops.exists(path):
|
||||
LOG.info("Creating directory: %s", path)
|
||||
os.makedirs(path)
|
||||
path_ops.makedirs(path)
|
||||
|
||||
# Only add the playlist if it doesn't already exists
|
||||
if exists(try_encode(xsppath)):
|
||||
if path_ops.exists(xsppath):
|
||||
LOG.info('Path %s does exist', xsppath)
|
||||
if delete:
|
||||
os.remove(xsppath)
|
||||
path_ops.remove(xsppath)
|
||||
LOG.info("Successfully removed playlist: %s.", tagname)
|
||||
return
|
||||
|
||||
|
@ -999,7 +974,7 @@ def playlist_xsp(mediatype, tagname, viewid, viewtype="", delete=False):
|
|||
'show': 'tvshows'
|
||||
}
|
||||
LOG.info("Writing playlist file to: %s", xsppath)
|
||||
with open(xsppath, 'wb') as filer:
|
||||
with open(path_ops.encode_path(xsppath), 'wb') as filer:
|
||||
filer.write(try_encode(
|
||||
'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n'
|
||||
'<smartplaylist type="%s">\n\t'
|
||||
|
@ -1017,21 +992,22 @@ def delete_playlists():
|
|||
"""
|
||||
Clean up the playlists
|
||||
"""
|
||||
path = try_decode(xbmc.translatePath("special://profile/playlists/video/"))
|
||||
for root, _, files in os.walk(path):
|
||||
path = path_ops.translate_path('special://profile/playlists/video/')
|
||||
for root, _, files in path_ops.walk(path):
|
||||
for file in files:
|
||||
if file.startswith('Plex'):
|
||||
os.remove(os.path.join(root, file))
|
||||
path_ops.remove(path_ops.path.join(root, file))
|
||||
|
||||
|
||||
def delete_nodes():
|
||||
"""
|
||||
Clean up video nodes
|
||||
"""
|
||||
path = try_decode(xbmc.translatePath("special://profile/library/video/"))
|
||||
for root, dirs, _ in os.walk(path):
|
||||
path = path_ops.translate_path("special://profile/library/video/")
|
||||
for root, dirs, _ in path_ops.walk(path):
|
||||
for directory in dirs:
|
||||
if directory.startswith('Plex-'):
|
||||
rmtree(os.path.join(root, directory))
|
||||
path_ops.rmtree(path_ops.path.join(root, directory))
|
||||
break
|
||||
|
||||
|
||||
|
@ -1044,7 +1020,7 @@ def generate_file_md5(path):
|
|||
"""
|
||||
m = hashlib.md5()
|
||||
m.update(path.encode('utf-8'))
|
||||
with open(path, 'rb') as f:
|
||||
with open(path_ops.encode_path(path), 'rb') as f:
|
||||
while True:
|
||||
piece = f.read(32768)
|
||||
if not piece:
|
||||
|
@ -1192,33 +1168,3 @@ def thread_methods(cls=None, add_stops=None, add_suspends=None):
|
|||
|
||||
# Return class to render this a decorator
|
||||
return cls
|
||||
|
||||
|
||||
class LockFunction(object):
|
||||
"""
|
||||
Decorator for class methods and functions to lock them with lock.
|
||||
|
||||
Initialize this class first
|
||||
lockfunction = LockFunction(lock), where lock is a threading.Lock() object
|
||||
|
||||
To then lock a function or method:
|
||||
|
||||
@lockfunction.lockthis
|
||||
def some_function(args, kwargs)
|
||||
"""
|
||||
def __init__(self, lock):
|
||||
self.lock = lock
|
||||
|
||||
def lockthis(self, func):
|
||||
"""
|
||||
Use this method to actually lock a function or method
|
||||
"""
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""
|
||||
Wrapper construct
|
||||
"""
|
||||
with self.lock:
|
||||
result = func(*args, **kwargs)
|
||||
return result
|
||||
return wrapper
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
import sys
|
||||
import xbmc
|
||||
from xbmcaddon import Addon
|
||||
|
||||
|
@ -35,7 +35,9 @@ _ADDON = Addon()
|
|||
ADDON_NAME = 'PlexKodiConnect'
|
||||
ADDON_ID = 'plugin.video.plexkodiconnect'
|
||||
ADDON_VERSION = _ADDON.getAddonInfo('version')
|
||||
ADDON_PATH = try_decode(_ADDON.getAddonInfo('path'))
|
||||
ADDON_FOLDER = try_decode(xbmc.translatePath('special://home'))
|
||||
ADDON_PROFILE = try_decode(_ADDON.getAddonInfo('profile'))
|
||||
|
||||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
|
@ -82,10 +84,6 @@ MIN_DB_VERSION = '2.0.27'
|
|||
|
||||
# Database paths
|
||||
_DB_VIDEO_VERSION = {
|
||||
13: 78, # Gotham
|
||||
14: 90, # Helix
|
||||
15: 93, # Isengard
|
||||
16: 99, # Jarvis
|
||||
17: 107, # Krypton
|
||||
18: 109 # Leia
|
||||
}
|
||||
|
@ -93,10 +91,6 @@ DB_VIDEO_PATH = try_decode(xbmc.translatePath(
|
|||
"special://database/MyVideos%s.db" % _DB_VIDEO_VERSION[KODIVERSION]))
|
||||
|
||||
_DB_MUSIC_VERSION = {
|
||||
13: 46, # Gotham
|
||||
14: 48, # Helix
|
||||
15: 52, # Isengard
|
||||
16: 56, # Jarvis
|
||||
17: 60, # Krypton
|
||||
18: 70 # Leia
|
||||
}
|
||||
|
@ -104,10 +98,6 @@ DB_MUSIC_PATH = try_decode(xbmc.translatePath(
|
|||
"special://database/MyMusic%s.db" % _DB_MUSIC_VERSION[KODIVERSION]))
|
||||
|
||||
_DB_TEXTURE_VERSION = {
|
||||
13: 13, # Gotham
|
||||
14: 13, # Helix
|
||||
15: 13, # Isengard
|
||||
16: 13, # Jarvis
|
||||
17: 13, # Krypton
|
||||
18: 13 # Leia
|
||||
}
|
||||
|
@ -123,6 +113,12 @@ EXTERNAL_SUBTITLE_TEMP_PATH = try_decode(xbmc.translatePath(
|
|||
# Multiply Plex time by this factor to receive Kodi time
|
||||
PLEX_TO_KODI_TIMEFACTOR = 1.0 / 1000.0
|
||||
|
||||
# We're "failing" playback with a video of 0 length
|
||||
NULL_VIDEO = os.path.join(ADDON_FOLDER,
|
||||
'addons',
|
||||
ADDON_ID,
|
||||
'empty_video.mp4')
|
||||
|
||||
# Playlist stuff
|
||||
PLAYLIST_PATH = os.path.join(KODI_PROFILE, 'playlists')
|
||||
PLAYLIST_PATH_MIXED = os.path.join(PLAYLIST_PATH, 'mixed')
|
||||
|
@ -513,3 +509,14 @@ PLEX_STREAM_TYPE_FROM_STREAM_TYPE = {
|
|||
'audio': '2',
|
||||
'subtitle': '3'
|
||||
}
|
||||
|
||||
# Encoding to be used for our m3u playlist files
|
||||
# m3u files do not have encoding specified by definition, unfortunately.
|
||||
if PLATFORM == 'Windows':
|
||||
M3U_ENCODING = 'mbcs'
|
||||
else:
|
||||
M3U_ENCODING = sys.getfilesystemencoding()
|
||||
if (not M3U_ENCODING or
|
||||
M3U_ENCODING == 'ascii' or
|
||||
M3U_ENCODING == 'ANSI_X3.4-1968'):
|
||||
M3U_ENCODING = 'utf-8'
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from distutils import dir_util
|
||||
import xml.etree.ElementTree as etree
|
||||
from os import makedirs
|
||||
|
||||
import xbmc
|
||||
from xbmcvfs import exists
|
||||
|
||||
from utils import window, settings, language as lang, try_encode, indent, \
|
||||
normalize_nodes, exists_dir, try_decode
|
||||
import variables as v
|
||||
import state
|
||||
from . import utils
|
||||
from . import path_ops
|
||||
from . import variables as v
|
||||
from . import state
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = getLogger("PLEX."+__name__)
|
||||
LOG = getLogger('PLEX.videonodes')
|
||||
|
||||
###############################################################################
|
||||
# Paths are strings, NOT unicode!
|
||||
|
||||
|
||||
class VideoNodes(object):
|
||||
|
@ -30,21 +25,26 @@ class VideoNodes(object):
|
|||
root = etree.Element('node', attrib={'order': "%s" % order})
|
||||
elif roottype == 1:
|
||||
# Filter
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "filter"})
|
||||
root = etree.Element('node',
|
||||
attrib={'order': "%s" % order, 'type': "filter"})
|
||||
etree.SubElement(root, 'match').text = "all"
|
||||
# Add tag rule
|
||||
rule = etree.SubElement(root, 'rule', attrib={'field': "tag", 'operator': "is"})
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
attrib={'field': "tag", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = tagname
|
||||
else:
|
||||
# Folder
|
||||
root = etree.Element('node', attrib={'order': "%s" % order, 'type': "folder"})
|
||||
root = etree.Element('node',
|
||||
attrib={'order': "%s" % order, 'type': "folder"})
|
||||
|
||||
etree.SubElement(root, 'label').text = label
|
||||
etree.SubElement(root, 'icon').text = "special://home/addons/plugin.video.plexkodiconnect/icon.png"
|
||||
|
||||
return root
|
||||
|
||||
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False):
|
||||
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid,
|
||||
delete=False):
|
||||
# Plex: reassign mediatype due to Kodi inner workings
|
||||
# How many items do we get at most?
|
||||
limit = state.FETCH_PMS_ITEM_NUMBER
|
||||
|
@ -63,33 +63,30 @@ class VideoNodes(object):
|
|||
dirname = viewid
|
||||
|
||||
# Returns strings
|
||||
path = try_decode(xbmc.translatePath(
|
||||
"special://profile/library/video/"))
|
||||
nodepath = try_decode(xbmc.translatePath(
|
||||
"special://profile/library/video/Plex-%s/" % dirname))
|
||||
path = path_ops.translate_path('special://profile/library/video/')
|
||||
nodepath = path_ops.translate_path(
|
||||
'special://profile/library/video/Plex-%s/' % dirname)
|
||||
|
||||
if delete:
|
||||
if exists_dir(nodepath):
|
||||
from shutil import rmtree
|
||||
rmtree(nodepath)
|
||||
log.info("Sucessfully removed videonode: %s." % tagname)
|
||||
if path_ops.exists(nodepath):
|
||||
path_ops.rmtree(nodepath)
|
||||
LOG.info("Sucessfully removed videonode: %s." % tagname)
|
||||
return
|
||||
|
||||
# Verify the video directory
|
||||
if not exists_dir(path):
|
||||
dir_util.copy_tree(
|
||||
src=try_decode(
|
||||
xbmc.translatePath("special://xbmc/system/library/video")),
|
||||
dst=try_decode(
|
||||
xbmc.translatePath("special://profile/library/video")),
|
||||
if not path_ops.exists(path):
|
||||
path_ops.copy_tree(
|
||||
src=path_ops.translate_path(
|
||||
'special://xbmc/system/library/video'),
|
||||
dst=path_ops.translate_path('special://profile/library/video'),
|
||||
preserve_mode=0) # do not copy permission bits!
|
||||
|
||||
# Create the node directory
|
||||
if mediatype != "photos":
|
||||
if not exists_dir(nodepath):
|
||||
if not path_ops.exists(nodepath):
|
||||
# folder does not exist yet
|
||||
log.debug('Creating folder %s' % nodepath)
|
||||
makedirs(nodepath)
|
||||
LOG.debug('Creating folder %s' % nodepath)
|
||||
path_ops.makedirs(nodepath)
|
||||
|
||||
# Create index entry
|
||||
nodeXML = "%sindex.xml" % nodepath
|
||||
|
@ -97,13 +94,13 @@ class VideoNodes(object):
|
|||
path = "library://video/Plex-%s/" % dirname
|
||||
for i in range(1, indexnumber):
|
||||
# Verify to make sure we don't create duplicates
|
||||
if window('Plex.nodes.%s.index' % i) == path:
|
||||
if utils.window('Plex.nodes.%s.index' % i) == path:
|
||||
return
|
||||
|
||||
if mediatype == "photos":
|
||||
path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % (viewid, viewid)
|
||||
|
||||
window('Plex.nodes.%s.index' % indexnumber, value=path)
|
||||
utils.window('Plex.nodes.%s.index' % indexnumber, value=path)
|
||||
|
||||
# Root
|
||||
if not mediatype == "photos":
|
||||
|
@ -119,7 +116,7 @@ class VideoNodes(object):
|
|||
tagname=tagname,
|
||||
roottype=0)
|
||||
try:
|
||||
indent(root)
|
||||
utils.indent(root)
|
||||
except:
|
||||
pass
|
||||
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
|
||||
|
@ -222,14 +219,15 @@ class VideoNodes(object):
|
|||
# Get label
|
||||
stringid = nodes[node]
|
||||
if node != "1":
|
||||
label = lang(stringid)
|
||||
label = utils.lang(stringid)
|
||||
if not label:
|
||||
label = xbmc.getLocalizedString(stringid)
|
||||
else:
|
||||
label = stringid
|
||||
|
||||
# Set window properties
|
||||
if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all":
|
||||
if ((mediatype == "homevideos" or mediatype == "photos") and
|
||||
nodetype == "all"):
|
||||
# Custom query
|
||||
path = ("plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s"
|
||||
% (viewid, mediatype))
|
||||
|
@ -278,34 +276,39 @@ class VideoNodes(object):
|
|||
templabel = label
|
||||
|
||||
embynode = "Plex.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=templabel)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=mediatype)
|
||||
utils.window('%s.title' % embynode, value=templabel)
|
||||
utils.window('%s.path' % embynode, value=windowpath)
|
||||
utils.window('%s.content' % embynode, value=path)
|
||||
utils.window('%s.type' % embynode, value=mediatype)
|
||||
else:
|
||||
embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype)
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
utils.window('%s.title' % embynode, value=label)
|
||||
utils.window('%s.path' % embynode, value=windowpath)
|
||||
utils.window('%s.content' % embynode, value=path)
|
||||
|
||||
if mediatype == "photos":
|
||||
# For photos, we do not create a node in videos but we do want the window props
|
||||
# to be created.
|
||||
# To do: add our photos nodes to kodi picture sources somehow
|
||||
# For photos, we do not create a node in videos but we do want
|
||||
# the window props to be created. To do: add our photos nodes to
|
||||
# kodi picture sources somehow
|
||||
continue
|
||||
|
||||
if exists(try_encode(nodeXML)):
|
||||
if path_ops.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
continue
|
||||
|
||||
# Create the root
|
||||
if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"):
|
||||
# Folder type with plugin path
|
||||
root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname, roottype=2)
|
||||
root = self.commonRoot(order=sortorder[node],
|
||||
label=label,
|
||||
tagname=tagname,
|
||||
roottype=2)
|
||||
etree.SubElement(root, 'path').text = path
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname)
|
||||
root = self.commonRoot(order=sortorder[node],
|
||||
label=label,
|
||||
tagname=tagname)
|
||||
if nodetype in ('recentepisodes', 'inprogressepisodes'):
|
||||
etree.SubElement(root, 'content').text = "episodes"
|
||||
else:
|
||||
|
@ -313,20 +316,24 @@ class VideoNodes(object):
|
|||
|
||||
# Elements per nodetype
|
||||
if nodetype == "all":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "sorttitle"
|
||||
elif nodetype == "recent":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
if settings('MovieShowWatched') == 'false':
|
||||
if utils.settings('MovieShowWatched') == 'false':
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
{'field': "playcount",
|
||||
'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogress":
|
||||
etree.SubElement(root, 'rule', {'field': "inprogress", 'operator': "true"})
|
||||
etree.SubElement(root,
|
||||
'rule',
|
||||
{'field': "inprogress", 'operator': "true"})
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
etree.SubElement(
|
||||
root,
|
||||
|
@ -335,55 +342,67 @@ class VideoNodes(object):
|
|||
).text = 'lastplayed'
|
||||
|
||||
elif nodetype == "genres":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "genres"
|
||||
|
||||
elif nodetype == "unwatched":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
rule = etree.SubElement(root, "rule", {'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "sorttitle"
|
||||
rule = etree.SubElement(root,
|
||||
"rule",
|
||||
{'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "sets":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root, 'group').text = "tags"
|
||||
|
||||
elif nodetype == "random":
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "random"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "random"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
|
||||
elif nodetype == "recommended":
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "rating"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "descending"}).text = "rating"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
{'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
rule2 = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "rating", 'operator': "greaterthan"})
|
||||
rule2 = etree.SubElement(root,
|
||||
'rule',
|
||||
attrib={'field': "rating", 'operator': "greaterthan"})
|
||||
etree.SubElement(rule2, 'value').text = "7"
|
||||
|
||||
elif nodetype == "recentepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'order', {'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "descending"}).text = "dateadded"
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule', {'field': "playcount", 'operator': "is"})
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
{'field': "playcount", 'operator': "is"})
|
||||
etree.SubElement(rule, 'value').text = "0"
|
||||
|
||||
elif nodetype == "inprogressepisodes":
|
||||
# Kodi Isengard, Jarvis
|
||||
etree.SubElement(root, 'limit').text = limit
|
||||
rule = etree.SubElement(root, 'rule',
|
||||
attrib={'field': "inprogress", 'operator':"true"})
|
||||
|
||||
rule = etree.SubElement(root,
|
||||
'rule',
|
||||
attrib={'field': "inprogress", 'operator':"true"})
|
||||
try:
|
||||
indent(root)
|
||||
utils.indent(root)
|
||||
except:
|
||||
pass
|
||||
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
|
||||
etree.ElementTree(root).write(path_ops.encode_path(nodeXML),
|
||||
encoding="UTF-8")
|
||||
|
||||
def singleNode(self, indexnumber, tagname, mediatype, itemtype):
|
||||
tagname = try_encode(tagname)
|
||||
cleantagname = try_decode(normalize_nodes(tagname))
|
||||
nodepath = try_decode(xbmc.translatePath(
|
||||
"special://profile/library/video/"))
|
||||
cleantagname = utils.normalize_nodes(tagname)
|
||||
nodepath = path_ops.translate_path('special://profile/library/video/')
|
||||
nodeXML = "%splex_%s.xml" % (nodepath, cleantagname)
|
||||
path = "library://video/plex_%s.xml" % cleantagname
|
||||
if v.KODIVERSION >= 17:
|
||||
|
@ -393,13 +412,12 @@ class VideoNodes(object):
|
|||
windowpath = "ActivateWindow(Video,%s,return)" % path
|
||||
|
||||
# Create the video node directory
|
||||
if not exists_dir(nodepath):
|
||||
if not path_ops.exists(nodepath):
|
||||
# We need to copy over the default items
|
||||
dir_util.copy_tree(
|
||||
src=try_decode(
|
||||
xbmc.translatePath("special://xbmc/system/library/video")),
|
||||
dst=try_decode(
|
||||
xbmc.translatePath("special://profile/library/video")),
|
||||
path_ops.copy_tree(
|
||||
src=path_ops.translate_path(
|
||||
'special://xbmc/system/library/video'),
|
||||
dst=path_ops.translate_path('special://profile/library/video'),
|
||||
preserve_mode=0) # do not copy permission bits!
|
||||
|
||||
labels = {
|
||||
|
@ -407,14 +425,14 @@ class VideoNodes(object):
|
|||
'Favorite tvshows': 30181,
|
||||
'channels': 30173
|
||||
}
|
||||
label = lang(labels[tagname])
|
||||
label = utils.lang(labels[tagname])
|
||||
embynode = "Plex.nodes.%s" % indexnumber
|
||||
window('%s.title' % embynode, value=label)
|
||||
window('%s.path' % embynode, value=windowpath)
|
||||
window('%s.content' % embynode, value=path)
|
||||
window('%s.type' % embynode, value=itemtype)
|
||||
utils.window('%s.title' % embynode, value=label)
|
||||
utils.window('%s.path' % embynode, value=windowpath)
|
||||
utils.window('%s.content' % embynode, value=path)
|
||||
utils.window('%s.type' % embynode, value=itemtype)
|
||||
|
||||
if exists(try_encode(nodeXML)):
|
||||
if path_ops.exists(nodeXML):
|
||||
# Don't recreate xml if already exists
|
||||
return
|
||||
|
||||
|
@ -423,23 +441,26 @@ class VideoNodes(object):
|
|||
label=label,
|
||||
tagname=tagname,
|
||||
roottype=2)
|
||||
etree.SubElement(root, 'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
|
||||
etree.SubElement(root,
|
||||
'path').text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels"
|
||||
else:
|
||||
root = self.commonRoot(order=1, label=label, tagname=tagname)
|
||||
etree.SubElement(root, 'order', {'direction': "ascending"}).text = "sorttitle"
|
||||
etree.SubElement(root,
|
||||
'order',
|
||||
{'direction': "ascending"}).text = "sorttitle"
|
||||
|
||||
etree.SubElement(root, 'content').text = mediatype
|
||||
|
||||
try:
|
||||
indent(root)
|
||||
utils.indent(root)
|
||||
except:
|
||||
pass
|
||||
etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
|
||||
|
||||
def clearProperties(self):
|
||||
|
||||
log.info("Clearing nodes properties.")
|
||||
plexprops = window('Plex.nodes.total')
|
||||
LOG.info("Clearing nodes properties.")
|
||||
plexprops = utils.window('Plex.nodes.total')
|
||||
propnames = [
|
||||
"index","path","title","content",
|
||||
"inprogress.content","inprogress.title",
|
||||
|
@ -457,4 +478,5 @@ class VideoNodes(object):
|
|||
totalnodes = int(plexprops)
|
||||
for i in range(totalnodes):
|
||||
for prop in propnames:
|
||||
window('Plex.nodes.%s.%s' % (str(i), prop), clear=True)
|
||||
utils.window('Plex.nodes.%s.%s' % (str(i), prop),
|
||||
clear=True)
|
||||
|
|
|
@ -88,9 +88,9 @@ Event Handler Classes
|
|||
import os.path
|
||||
import logging
|
||||
import re
|
||||
from pathtools.patterns import match_any_paths
|
||||
from watchdog.utils import has_attribute
|
||||
from watchdog.utils import unicode_paths
|
||||
from ..pathtools.patterns import match_any_paths
|
||||
from .utils import has_attribute
|
||||
from .utils import unicode_paths
|
||||
|
||||
|
||||
EVENT_TYPE_MOVED = 'moved'
|
||||
|
|
|
@ -55,8 +55,8 @@ Class Platforms Note
|
|||
"""
|
||||
|
||||
import warnings
|
||||
from watchdog.utils import platform
|
||||
from watchdog.utils import UnsupportedLibc
|
||||
from ..utils import platform
|
||||
from ..utils import UnsupportedLibc
|
||||
|
||||
if platform.is_linux():
|
||||
try:
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
from __future__ import with_statement
|
||||
import threading
|
||||
from watchdog.utils import BaseThread
|
||||
from watchdog.utils.compat import queue
|
||||
from watchdog.utils.bricks import SkipRepeatsQueue
|
||||
from ..utils import BaseThread
|
||||
from ..utils.compat import queue
|
||||
from ..utils.bricks import SkipRepeatsQueue
|
||||
|
||||
DEFAULT_EMITTER_TIMEOUT = 1 # in seconds.
|
||||
DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds.
|
||||
|
|
|
@ -30,7 +30,7 @@ import threading
|
|||
import unicodedata
|
||||
import _watchdog_fsevents as _fsevents
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
FileDeletedEvent,
|
||||
FileModifiedEvent,
|
||||
FileCreatedEvent,
|
||||
|
@ -41,8 +41,8 @@ from watchdog.events import (
|
|||
DirMovedEvent
|
||||
)
|
||||
|
||||
from watchdog.utils.dirsnapshot import DirectorySnapshot
|
||||
from watchdog.observers.api import (
|
||||
from ..utils.dirsnapshot import DirectorySnapshot
|
||||
from ..observers.api import (
|
||||
BaseObserver,
|
||||
EventEmitter,
|
||||
DEFAULT_EMITTER_TIMEOUT,
|
||||
|
|
|
@ -24,9 +24,9 @@ import os
|
|||
import logging
|
||||
import unicodedata
|
||||
from threading import Thread
|
||||
from watchdog.utils.compat import queue
|
||||
from ..utils.compat import queue
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
FileDeletedEvent,
|
||||
FileModifiedEvent,
|
||||
FileCreatedEvent,
|
||||
|
@ -36,7 +36,7 @@ from watchdog.events import (
|
|||
DirCreatedEvent,
|
||||
DirMovedEvent
|
||||
)
|
||||
from watchdog.observers.api import (
|
||||
from ..observers.api import (
|
||||
BaseObserver,
|
||||
EventEmitter,
|
||||
DEFAULT_EMITTER_TIMEOUT,
|
||||
|
|
|
@ -73,14 +73,14 @@ import os
|
|||
import threading
|
||||
from .inotify_buffer import InotifyBuffer
|
||||
|
||||
from watchdog.observers.api import (
|
||||
from ..observers.api import (
|
||||
EventEmitter,
|
||||
BaseObserver,
|
||||
DEFAULT_EMITTER_TIMEOUT,
|
||||
DEFAULT_OBSERVER_TIMEOUT
|
||||
)
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
DirDeletedEvent,
|
||||
DirModifiedEvent,
|
||||
DirMovedEvent,
|
||||
|
@ -92,7 +92,7 @@ from watchdog.events import (
|
|||
generate_sub_moved_events,
|
||||
generate_sub_created_events,
|
||||
)
|
||||
from watchdog.utils import unicode_paths
|
||||
from ..utils import unicode_paths
|
||||
|
||||
|
||||
class InotifyEmitter(EventEmitter):
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
from watchdog.utils import BaseThread
|
||||
from watchdog.utils.delayed_queue import DelayedQueue
|
||||
from watchdog.observers.inotify_c import Inotify
|
||||
from ..utils import BaseThread
|
||||
from ..utils.delayed_queue import DelayedQueue
|
||||
from ..observers.inotify_c import Inotify
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import ctypes
|
|||
import ctypes.util
|
||||
from functools import reduce
|
||||
from ctypes import c_int, c_char_p, c_uint32
|
||||
from watchdog.utils import has_attribute
|
||||
from watchdog.utils import UnsupportedLibc
|
||||
from ..utils import has_attribute
|
||||
from ..utils import UnsupportedLibc
|
||||
|
||||
|
||||
def _load_libc():
|
||||
|
|
|
@ -78,7 +78,7 @@ Collections and Utility Classes
|
|||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
from watchdog.utils import platform
|
||||
from ..utils import platform
|
||||
|
||||
import threading
|
||||
import errno
|
||||
|
@ -94,18 +94,18 @@ if sys.version_info < (2, 7, 0):
|
|||
else:
|
||||
import select
|
||||
|
||||
from pathtools.path import absolute_path
|
||||
from ...pathtools.path import absolute_path
|
||||
|
||||
from watchdog.observers.api import (
|
||||
from ..observers.api import (
|
||||
BaseObserver,
|
||||
EventEmitter,
|
||||
DEFAULT_OBSERVER_TIMEOUT,
|
||||
DEFAULT_EMITTER_TIMEOUT
|
||||
)
|
||||
|
||||
from watchdog.utils.dirsnapshot import DirectorySnapshot
|
||||
from ..utils.dirsnapshot import DirectorySnapshot
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
DirMovedEvent,
|
||||
DirDeletedEvent,
|
||||
DirCreatedEvent,
|
||||
|
|
|
@ -38,16 +38,17 @@ from __future__ import with_statement
|
|||
import os
|
||||
import threading
|
||||
from functools import partial
|
||||
from watchdog.utils import stat as default_stat
|
||||
from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff
|
||||
from watchdog.observers.api import (
|
||||
from ..utils import stat as default_stat
|
||||
from ..utils.dirsnapshot import DirectorySnapshot, \
|
||||
DirectorySnapshotDiff
|
||||
from ..observers.api import (
|
||||
EventEmitter,
|
||||
BaseObserver,
|
||||
DEFAULT_OBSERVER_TIMEOUT,
|
||||
DEFAULT_EMITTER_TIMEOUT
|
||||
)
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
DirMovedEvent,
|
||||
DirDeletedEvent,
|
||||
DirCreatedEvent,
|
||||
|
|
|
@ -24,7 +24,7 @@ import threading
|
|||
import os.path
|
||||
import time
|
||||
|
||||
from watchdog.events import (
|
||||
from ..events import (
|
||||
DirCreatedEvent,
|
||||
DirDeletedEvent,
|
||||
DirMovedEvent,
|
||||
|
@ -37,14 +37,14 @@ from watchdog.events import (
|
|||
generate_sub_created_events,
|
||||
)
|
||||
|
||||
from watchdog.observers.api import (
|
||||
from ..observers.api import (
|
||||
EventEmitter,
|
||||
BaseObserver,
|
||||
DEFAULT_OBSERVER_TIMEOUT,
|
||||
DEFAULT_EMITTER_TIMEOUT
|
||||
)
|
||||
|
||||
from watchdog.observers.winapi import (
|
||||
from ..observers.winapi import (
|
||||
read_events,
|
||||
get_directory_handle,
|
||||
close_directory_handle,
|
||||
|
|
|
@ -22,8 +22,8 @@ import signal
|
|||
import subprocess
|
||||
import time
|
||||
|
||||
from watchdog.utils import echo, has_attribute
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
from ..utils import echo, has_attribute
|
||||
from ..events import PatternMatchingEventHandler
|
||||
|
||||
|
||||
class Trick(PatternMatchingEventHandler):
|
||||
|
|
|
@ -33,9 +33,8 @@ Classes
|
|||
import os
|
||||
import sys
|
||||
import threading
|
||||
import watchdog.utils.platform
|
||||
from watchdog.utils.compat import Event
|
||||
from collections import namedtuple
|
||||
from . import platform
|
||||
from .compat import Event
|
||||
|
||||
|
||||
if sys.version_info[0] == 2 and platform.is_windows():
|
||||
|
|
|
@ -24,6 +24,6 @@ except ImportError:
|
|||
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
from watchdog.utils.event_backport import Event
|
||||
from .event_backport import Event
|
||||
else:
|
||||
from threading import Event
|
|
@ -47,8 +47,7 @@ Classes
|
|||
import errno
|
||||
import os
|
||||
from stat import S_ISDIR
|
||||
from watchdog.utils import platform
|
||||
from watchdog.utils import stat as default_stat
|
||||
from . import stat as default_stat
|
||||
|
||||
|
||||
class DirectorySnapshotDiff(object):
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
from watchdog.utils import platform
|
||||
from . import platform
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
|
|
|
@ -37,8 +37,8 @@ except ImportError:
|
|||
from io import StringIO
|
||||
|
||||
from argh import arg, aliases, ArghParser, expects_obj
|
||||
from watchdog.version import VERSION_STRING
|
||||
from watchdog.utils import load_class
|
||||
from .version import VERSION_STRING
|
||||
from .utils import load_class
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
|
|
@ -48,11 +48,11 @@ import logging
|
|||
import traceback
|
||||
import sys
|
||||
|
||||
import utils
|
||||
from . import utils
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = logging.getLogger("PLEX." + __name__)
|
||||
LOG = logging.getLogger('PLEX.websocket')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
|
|
@ -7,17 +7,16 @@ from json import loads
|
|||
import xml.etree.ElementTree as etree
|
||||
from threading import Thread
|
||||
from ssl import CERT_NONE
|
||||
|
||||
from xbmc import sleep
|
||||
|
||||
from utils import window, settings, thread_methods
|
||||
from companion import process_command
|
||||
import state
|
||||
import variables as v
|
||||
from . import utils
|
||||
from . import companion
|
||||
from . import state
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
LOG = getLogger('PLEX.websocket_client')
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -140,14 +139,14 @@ class WebSocket(Thread):
|
|||
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
|
||||
|
||||
|
||||
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'BACKGROUND_SYNC_DISABLED'])
|
||||
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
|
||||
'BACKGROUND_SYNC_DISABLED'])
|
||||
class PMS_Websocket(WebSocket):
|
||||
"""
|
||||
Websocket connection with the PMS for Plex Companion
|
||||
"""
|
||||
def getUri(self):
|
||||
server = window('pms_server')
|
||||
server = utils.window('pms_server')
|
||||
# Get the appropriate prefix for the websocket
|
||||
if server.startswith('https'):
|
||||
server = "wss%s" % server[5:]
|
||||
|
@ -158,7 +157,7 @@ class PMS_Websocket(WebSocket):
|
|||
if state.PLEX_TOKEN:
|
||||
uri += '?X-Plex-Token=%s' % state.PLEX_TOKEN
|
||||
sslopt = {}
|
||||
if settings('sslverify') == "false":
|
||||
if utils.settings('sslverify') == "false":
|
||||
sslopt["cert_reqs"] = CERT_NONE
|
||||
LOG.debug("%s: Uri: %s, sslopt: %s",
|
||||
self.__class__.__name__, uri, sslopt)
|
||||
|
@ -206,7 +205,7 @@ class Alexa_Websocket(WebSocket):
|
|||
"""
|
||||
Websocket connection to talk to Amazon Alexa.
|
||||
|
||||
Can't use thread_methods!
|
||||
Can't use utils.thread_methods!
|
||||
"""
|
||||
thread_stopped = False
|
||||
thread_suspended = False
|
||||
|
@ -244,9 +243,9 @@ class Alexa_Websocket(WebSocket):
|
|||
LOG.error('%s: Could not parse Alexa message',
|
||||
self.__class__.__name__)
|
||||
return
|
||||
process_command(message.attrib['path'][1:], message.attrib)
|
||||
companion.process_command(message.attrib['path'][1:], message.attrib)
|
||||
|
||||
# Path in thread_methods
|
||||
# Path in utils.thread_methods
|
||||
def stop(self):
|
||||
self.thread_stopped = True
|
||||
|
||||
|
|
297
service.py
297
service.py
|
@ -1,297 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###############################################################################
|
||||
from logging import getLogger
|
||||
from os import path as os_path
|
||||
from sys import path as sys_path, argv
|
||||
|
||||
from xbmc import translatePath, Monitor
|
||||
from xbmcaddon import Addon
|
||||
|
||||
###############################################################################
|
||||
|
||||
_ADDON = Addon(id='plugin.video.plexkodiconnect')
|
||||
try:
|
||||
_ADDON_PATH = _ADDON.getAddonInfo('path').decode('utf-8')
|
||||
except TypeError:
|
||||
_ADDON_PATH = _ADDON.getAddonInfo('path').decode()
|
||||
try:
|
||||
_BASE_RESOURCE = translatePath(os_path.join(
|
||||
_ADDON_PATH,
|
||||
'resources',
|
||||
'lib')).decode('utf-8')
|
||||
except TypeError:
|
||||
_BASE_RESOURCE = translatePath(os_path.join(
|
||||
_ADDON_PATH,
|
||||
'resources',
|
||||
'lib')).decode()
|
||||
sys_path.append(_BASE_RESOURCE)
|
||||
|
||||
###############################################################################
|
||||
|
||||
from utils import settings, window, language as lang, dialog
|
||||
from userclient import UserClient
|
||||
import initialsetup
|
||||
from kodimonitor import KodiMonitor, SpecialMonitor
|
||||
from librarysync import LibrarySync
|
||||
from websocket_client import PMS_Websocket, Alexa_Websocket
|
||||
|
||||
from PlexFunctions import check_connection
|
||||
from PlexCompanion import PlexCompanion
|
||||
from command_pipeline import Monitor_Window
|
||||
from playback_starter import PlaybackStarter
|
||||
from playqueue import PlayqueueMonitor
|
||||
from artwork import Image_Cache_Thread
|
||||
import variables as v
|
||||
import state
|
||||
|
||||
###############################################################################
|
||||
import loghandler
|
||||
|
||||
loghandler.config()
|
||||
LOG = getLogger("PLEX.service")
|
||||
###############################################################################
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from resources.lib import service_entry
|
||||
|
||||
|
||||
class Service():
|
||||
|
||||
server_online = True
|
||||
warn_auth = True
|
||||
|
||||
user = None
|
||||
ws = None
|
||||
library = None
|
||||
plexCompanion = None
|
||||
|
||||
user_running = False
|
||||
ws_running = False
|
||||
alexa_running = False
|
||||
library_running = False
|
||||
plexCompanion_running = False
|
||||
kodimonitor_running = False
|
||||
playback_starter_running = False
|
||||
image_cache_thread_running = False
|
||||
|
||||
def __init__(self):
|
||||
# Initial logging
|
||||
LOG.info("======== START %s ========", v.ADDON_NAME)
|
||||
LOG.info("Platform: %s", v.PLATFORM)
|
||||
LOG.info("KODI Version: %s", v.KODILONGVERSION)
|
||||
LOG.info("%s Version: %s", v.ADDON_NAME, v.ADDON_VERSION)
|
||||
LOG.info("PKC Direct Paths: %s", settings('useDirectPaths') == "true")
|
||||
LOG.info("Number of sync threads: %s", settings('syncThreadNumber'))
|
||||
LOG.info("Full sys.argv received: %s", argv)
|
||||
self.monitor = Monitor()
|
||||
# Load/Reset PKC entirely - important for user/Kodi profile switch
|
||||
initialsetup.reload_pkc()
|
||||
|
||||
def __stop_PKC(self):
|
||||
"""
|
||||
Kodi's abortRequested is really unreliable :-(
|
||||
"""
|
||||
return self.monitor.abortRequested() or state.STOP_PKC
|
||||
|
||||
def ServiceEntryPoint(self):
|
||||
# Important: Threads depending on abortRequest will not trigger
|
||||
# if profile switch happens more than once.
|
||||
__stop_PKC = self.__stop_PKC
|
||||
monitor = self.monitor
|
||||
kodiProfile = v.KODI_PROFILE
|
||||
|
||||
# Server auto-detect
|
||||
initialsetup.InitialSetup().setup()
|
||||
|
||||
# Detect playback start early on
|
||||
self.command_pipeline = Monitor_Window()
|
||||
self.command_pipeline.start()
|
||||
|
||||
# Initialize important threads, handing over self for callback purposes
|
||||
self.user = UserClient()
|
||||
self.ws = PMS_Websocket()
|
||||
self.alexa = Alexa_Websocket()
|
||||
self.library = LibrarySync()
|
||||
self.plexCompanion = PlexCompanion()
|
||||
self.specialMonitor = SpecialMonitor()
|
||||
self.playback_starter = PlaybackStarter()
|
||||
self.playqueue = PlayqueueMonitor()
|
||||
if settings('enableTextureCache') == "true":
|
||||
self.image_cache_thread = Image_Cache_Thread()
|
||||
|
||||
welcome_msg = True
|
||||
counter = 0
|
||||
while not __stop_PKC():
|
||||
|
||||
if window('plex_kodiProfile') != kodiProfile:
|
||||
# Profile change happened, terminate this thread and others
|
||||
LOG.info("Kodi profile was: %s and changed to: %s. "
|
||||
"Terminating old PlexKodiConnect thread.",
|
||||
kodiProfile, window('plex_kodiProfile'))
|
||||
break
|
||||
|
||||
# Before proceeding, need to make sure:
|
||||
# 1. Server is online
|
||||
# 2. User is set
|
||||
# 3. User has access to the server
|
||||
|
||||
if window('plex_online') == "true":
|
||||
# Plex server is online
|
||||
# Verify if user is set and has access to the server
|
||||
if (self.user.user is not None) and self.user.has_access:
|
||||
if not self.kodimonitor_running:
|
||||
# Start up events
|
||||
self.warn_auth = True
|
||||
if welcome_msg is True:
|
||||
# Reset authentication warnings
|
||||
welcome_msg = False
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
"%s %s" % (lang(33000),
|
||||
self.user.user),
|
||||
icon='{plex}',
|
||||
time=2000,
|
||||
sound=False)
|
||||
# Start monitoring kodi events
|
||||
self.kodimonitor_running = KodiMonitor()
|
||||
self.specialMonitor.start()
|
||||
# Start the Websocket Client
|
||||
if not self.ws_running:
|
||||
self.ws_running = True
|
||||
self.ws.start()
|
||||
# Start the Alexa thread
|
||||
if (not self.alexa_running and
|
||||
settings('enable_alexa') == 'true'):
|
||||
self.alexa_running = True
|
||||
self.alexa.start()
|
||||
# Start the syncing thread
|
||||
if not self.library_running:
|
||||
self.library_running = True
|
||||
self.library.start()
|
||||
# Start the Plex Companion thread
|
||||
if not self.plexCompanion_running:
|
||||
self.plexCompanion_running = True
|
||||
self.plexCompanion.start()
|
||||
if not self.playback_starter_running:
|
||||
self.playback_starter_running = True
|
||||
self.playback_starter.start()
|
||||
self.playqueue.start()
|
||||
if (not self.image_cache_thread_running and
|
||||
settings('enableTextureCache') == "true"):
|
||||
self.image_cache_thread_running = True
|
||||
self.image_cache_thread.start()
|
||||
else:
|
||||
if (self.user.user is None) and self.warn_auth:
|
||||
# Alert user is not authenticated and suppress future
|
||||
# warning
|
||||
self.warn_auth = False
|
||||
LOG.warn("Not authenticated yet.")
|
||||
|
||||
# User access is restricted.
|
||||
# Keep verifying until access is granted
|
||||
# unless server goes offline or Kodi is shut down.
|
||||
while self.user.has_access is False:
|
||||
# Verify access with an API call
|
||||
self.user.check_access()
|
||||
|
||||
if window('plex_online') != "true":
|
||||
# Server went offline
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
else:
|
||||
# Wait until Plex server is online
|
||||
# or Kodi is shut down.
|
||||
while not self.__stop_PKC():
|
||||
server = self.user.get_server()
|
||||
if server is False:
|
||||
# No server info set in add-on settings
|
||||
pass
|
||||
elif check_connection(server, verifySSL=True) is False:
|
||||
# Server is offline or cannot be reached
|
||||
# Alert the user and suppress future warning
|
||||
if self.server_online:
|
||||
self.server_online = False
|
||||
window('plex_online', value="false")
|
||||
# Suspend threads
|
||||
state.SUSPEND_LIBRARY_THREAD = True
|
||||
LOG.error("Plex Media Server went offline")
|
||||
if settings('show_pms_offline') == 'true':
|
||||
dialog('notification',
|
||||
lang(33001),
|
||||
"%s %s" % (lang(29999), lang(33002)),
|
||||
icon='{plex}',
|
||||
sound=False)
|
||||
counter += 1
|
||||
# Periodically check if the IP changed, e.g. per minute
|
||||
if counter > 20:
|
||||
counter = 0
|
||||
setup = initialsetup.InitialSetup()
|
||||
tmp = setup.pick_pms()
|
||||
if tmp is not None:
|
||||
setup.write_pms_to_settings(tmp)
|
||||
else:
|
||||
# Server is online
|
||||
counter = 0
|
||||
if not self.server_online:
|
||||
# Server was offline when Kodi started.
|
||||
# Wait for server to be fully established.
|
||||
if monitor.waitForAbort(5):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
self.server_online = True
|
||||
# Alert the user that server is online.
|
||||
if (welcome_msg is False and
|
||||
settings('show_pms_offline') == 'true'):
|
||||
dialog('notification',
|
||||
lang(29999),
|
||||
lang(33003),
|
||||
icon='{plex}',
|
||||
time=5000,
|
||||
sound=False)
|
||||
LOG.info("Server %s is online and ready.", server)
|
||||
window('plex_online', value="true")
|
||||
if state.AUTHENTICATED:
|
||||
# Server got offline when we were authenticated.
|
||||
# Hence resume threads
|
||||
state.SUSPEND_LIBRARY_THREAD = False
|
||||
|
||||
# Start the userclient thread
|
||||
if not self.user_running:
|
||||
self.user_running = True
|
||||
self.user.start()
|
||||
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(3):
|
||||
# Abort was requested while waiting.
|
||||
break
|
||||
|
||||
if monitor.waitForAbort(0.05):
|
||||
# Abort was requested while waiting. We should exit
|
||||
break
|
||||
# Terminating PlexKodiConnect
|
||||
|
||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||
state.STOP_PKC = True
|
||||
window('plex_service_started', clear=True)
|
||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||
|
||||
|
||||
# Safety net - Kody starts PKC twice upon first installation!
|
||||
if window('plex_service_started') == 'true':
|
||||
EXIT = True
|
||||
else:
|
||||
window('plex_service_started', value='true')
|
||||
EXIT = False
|
||||
|
||||
# Delay option
|
||||
DELAY = int(settings('startupDelay'))
|
||||
|
||||
LOG.info("Delaying Plex startup by: %s sec...", DELAY)
|
||||
if EXIT:
|
||||
LOG.error('PKC service.py already started - exiting this instance')
|
||||
elif DELAY and Monitor().waitForAbort(DELAY):
|
||||
# Start the service
|
||||
LOG.info("Abort requested while waiting. PKC not started.")
|
||||
else:
|
||||
Service().ServiceEntryPoint()
|
||||
if __name__ == "__main__":
|
||||
service_entry.start()
|
||||
|
|
Loading…
Reference in a new issue