mirror of
https://github.com/jellyfin/jellyfin-mpv-shim.git
synced 2024-11-27 00:00:37 +00:00
Linter warnings.
This commit is contained in:
parent
1e104a8c86
commit
21ca9d1087
@ -1,4 +1,3 @@
|
|||||||
import logging
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .player import playerManager
|
from .player import playerManager
|
||||||
@ -18,7 +17,7 @@ class ActionThread(threading.Thread):
|
|||||||
def run(self):
|
def run(self):
|
||||||
force_next = False
|
force_next = False
|
||||||
while not self.halt:
|
while not self.halt:
|
||||||
if (playerManager._player and playerManager._video) or force_next:
|
if playerManager.is_active() or force_next:
|
||||||
playerManager.update()
|
playerManager.update()
|
||||||
|
|
||||||
force_next = False
|
force_next = False
|
||||||
|
@ -29,11 +29,11 @@ def render_message(message, show_text):
|
|||||||
|
|
||||||
def process_series(mode, player, m_raid=None, m_rsid=None):
|
def process_series(mode, player, m_raid=None, m_rsid=None):
|
||||||
messages.clear()
|
messages.clear()
|
||||||
media = player._video
|
media = player.get_video()
|
||||||
client = media.client
|
client = media.client
|
||||||
show_text = player._player.show_text
|
show_text = player.show_text
|
||||||
c_aid, c_sid = None, None
|
c_aid, c_sid = None, None
|
||||||
c_pid = player._video.media_source.get("Id")
|
c_pid = media.media_source.get("Id")
|
||||||
|
|
||||||
success_ct = 0
|
success_ct = 0
|
||||||
partial_ct = 0
|
partial_ct = 0
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import time
|
|
||||||
from .clients import clientManager
|
from .clients import clientManager
|
||||||
|
|
||||||
|
|
||||||
@ -7,7 +6,8 @@ class UserInterface(object):
|
|||||||
self.open_player_menu = lambda: None
|
self.open_player_menu = lambda: None
|
||||||
self.stop = lambda: None
|
self.stop = lambda: None
|
||||||
|
|
||||||
def login_servers(self):
|
@staticmethod
|
||||||
|
def login_servers():
|
||||||
clientManager.cli_connect()
|
clientManager.cli_connect()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@ -17,4 +17,4 @@ class UserInterface(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
userInterface = UserInterface()
|
user_interface = UserInterface()
|
||||||
|
@ -57,7 +57,8 @@ class ClientManager(object):
|
|||||||
else:
|
else:
|
||||||
log.warning(_("Adding server failed."))
|
log.warning(_("Adding server failed."))
|
||||||
|
|
||||||
def client_factory(self):
|
@staticmethod
|
||||||
|
def client_factory():
|
||||||
client = JellyfinClient(allow_multiple_clients=True)
|
client = JellyfinClient(allow_multiple_clients=True)
|
||||||
client.config.data["app.default"] = True
|
client.config.data["app.default"] = True
|
||||||
client.config.app(
|
client.config.app(
|
||||||
@ -208,7 +209,7 @@ class ClientManager(object):
|
|||||||
if uuid is None and server is not None:
|
if uuid is None and server is not None:
|
||||||
uuid = server["uuid"]
|
uuid = server["uuid"]
|
||||||
|
|
||||||
if not uuid in self.clients:
|
if uuid not in self.clients:
|
||||||
return
|
return
|
||||||
|
|
||||||
if server is not None:
|
if server is not None:
|
||||||
|
@ -4,7 +4,7 @@ import sys
|
|||||||
import getpass
|
import getpass
|
||||||
|
|
||||||
# If no platform is matched, use the current directory.
|
# If no platform is matched, use the current directory.
|
||||||
_confdir = lambda app: ""
|
_confdir = None
|
||||||
username = getpass.getuser()
|
username = getpass.getuser()
|
||||||
|
|
||||||
|
|
||||||
@ -47,8 +47,10 @@ for i, arg in enumerate(sys.argv):
|
|||||||
def confdir(app):
|
def confdir(app):
|
||||||
if custom_config is not None:
|
if custom_config is not None:
|
||||||
return custom_config
|
return custom_config
|
||||||
else:
|
elif _confdir is not None:
|
||||||
return _confdir(app)
|
return _confdir(app)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def get(app, conf_file, create=False):
|
def get(app, conf_file, create=False):
|
||||||
|
@ -15,13 +15,13 @@ import jinja2 # python3-jinja2 in Debian, Jinja2 in pypi
|
|||||||
# 2.3 has a webview_ready() function that blocks until webview is ready (or timeout is passed)
|
# 2.3 has a webview_ready() function that blocks until webview is ready (or timeout is passed)
|
||||||
import webview # Python3-webview in Debian, pywebview in pypi
|
import webview # Python3-webview in Debian, pywebview in pypi
|
||||||
|
|
||||||
from ..clients import clientManager
|
|
||||||
from ..utils import get_text
|
from ..utils import get_text
|
||||||
from ..i18n import _
|
from ..i18n import _
|
||||||
from . import helpers
|
from . import helpers
|
||||||
|
|
||||||
# This makes me rather uncomfortable, but there's no easy way around this other than importing display_mirror in helpers.
|
# This makes me rather uncomfortable, but there's no easy way around this other than
|
||||||
# Lambda needed because the 2.3 version of the JS api adds an argument even when not used.
|
# importing display_mirror in helpers. Lambda needed because the 2.3 version of the JS
|
||||||
|
# api adds an argument even when not used.
|
||||||
helpers.on_escape = lambda _x=None: load_idle()
|
helpers.on_escape = lambda _x=None: load_idle()
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +35,7 @@ class DisplayMirror(object):
|
|||||||
def get_webview(self):
|
def get_webview(self):
|
||||||
return self.webview
|
return self.webview
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
def run(self):
|
def run(self):
|
||||||
# Webview needs to be run in the MainThread.
|
# Webview needs to be run in the MainThread.
|
||||||
|
|
||||||
@ -56,15 +57,19 @@ class DisplayMirror(object):
|
|||||||
self.webview = window
|
self.webview = window
|
||||||
|
|
||||||
# 3.2's .loaded event runs every time a new DOM is loaded as well, so not suitable for this purpose
|
# 3.2's .loaded event runs every time a new DOM is loaded as well, so not suitable for this purpose
|
||||||
# However, 3.2's load_html waits for the DOM to be ready, so we can completely skip waiting for that ourselves.
|
# However, 3.2's load_html waits for the DOM to be ready, so we can completely skip
|
||||||
|
# waiting for that ourselves.
|
||||||
threading.Thread(target=load_idle).start()
|
threading.Thread(target=load_idle).start()
|
||||||
|
|
||||||
webview.start()
|
webview.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
webview.destroy_window()
|
if hasattr(webview, "destroy_window"):
|
||||||
|
getattr(webview, "destroy_window")()
|
||||||
|
else:
|
||||||
|
self.webview.destroy()
|
||||||
|
|
||||||
def DisplayContent(self, client, arguments):
|
def display_content(self, client, arguments):
|
||||||
item = client.jellyfin.get_item(arguments["Arguments"]["ItemId"])
|
item = client.jellyfin.get_item(arguments["Arguments"]["ItemId"])
|
||||||
html = get_html(server_address=client.config.data["auth.server"], item=item)
|
html = get_html(server_address=client.config.data["auth.server"], item=item)
|
||||||
self.display_window.load_html(html)
|
self.display_window.load_html(html)
|
||||||
@ -110,9 +115,7 @@ def get_html(server_address=None, item=None):
|
|||||||
), # FIME: Mention the player_name here
|
), # FIME: Mention the player_name here
|
||||||
}
|
}
|
||||||
|
|
||||||
jinja_vars.update(
|
jinja_vars.update({"jellyfin_css": get_text("display_mirror", "jellyfin.css")})
|
||||||
{"jellyfin_css": get_text("display_mirror", "jellyfin.css"),}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tpl = jinja2.Template(get_text("display_mirror", "index.html"))
|
tpl = jinja2.Template(get_text("display_mirror", "index.html"))
|
||||||
|
@ -5,9 +5,11 @@ import math
|
|||||||
from ..clients import clientManager
|
from ..clients import clientManager
|
||||||
|
|
||||||
# This started as a copy of some useful functions from jellyfin-chromecast's helpers.js and translated them to Python.
|
# This started as a copy of some useful functions from jellyfin-chromecast's helpers.js and translated them to Python.
|
||||||
# Only reason their not put straight into __init__.py is to keep the same logical separation that jellyfin-chromecast has.
|
# Only reason their not put straight into __init__.py is to keep the same logical separation that
|
||||||
|
# jellyfin-chromecast has.
|
||||||
#
|
#
|
||||||
# I've since added some extra functions, and completely reworked some of the old ones such that it's not directly compatible.
|
# I've since added some extra functions, and completely reworked some of the old ones such that it's
|
||||||
|
# not directly compatible.
|
||||||
#
|
#
|
||||||
# Should this stuff be in jellyfin_apiclient_python instead?
|
# Should this stuff be in jellyfin_apiclient_python instead?
|
||||||
# Is this stuff in there already?
|
# Is this stuff in there already?
|
||||||
@ -15,6 +17,7 @@ from ..clients import clientManager
|
|||||||
# FIXME: A lot of this could be done so much better with format-strings
|
# FIXME: A lot of this could be done so much better with format-strings
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming,PyPep8Naming
|
||||||
def getUrl(serverAddress, name):
|
def getUrl(serverAddress, name):
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
@ -27,6 +30,7 @@ def getUrl(serverAddress, name):
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming,PyPep8Naming
|
||||||
def getBackdropUrl(item, serverAddress):
|
def getBackdropUrl(item, serverAddress):
|
||||||
if item.get("BackdropImageTags"):
|
if item.get("BackdropImageTags"):
|
||||||
return getUrl(
|
return getUrl(
|
||||||
@ -48,6 +52,7 @@ def getBackdropUrl(item, serverAddress):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming,PyPep8Naming
|
||||||
def getLogoUrl(item, serverAddress):
|
def getLogoUrl(item, serverAddress):
|
||||||
if item.get("ImageTags", {}).get("Logo", None):
|
if item.get("ImageTags", {}).get("Logo", None):
|
||||||
return getUrl(
|
return getUrl(
|
||||||
@ -66,6 +71,7 @@ def getLogoUrl(item, serverAddress):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming,PyPep8Naming
|
||||||
def getPrimaryImageUrl(item, serverAddress):
|
def getPrimaryImageUrl(item, serverAddress):
|
||||||
if item.get("AlbumPrimaryImageTag"):
|
if item.get("AlbumPrimaryImageTag"):
|
||||||
return getUrl(
|
return getUrl(
|
||||||
@ -92,6 +98,7 @@ def getPrimaryImageUrl(item, serverAddress):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
def getDisplayName(item):
|
def getDisplayName(item):
|
||||||
name = item.get("EpisodeTitle", item.get("Name"))
|
name = item.get("EpisodeTitle", item.get("Name"))
|
||||||
|
|
||||||
@ -114,6 +121,7 @@ def getDisplayName(item):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
def getRatingHtml(item):
|
def getRatingHtml(item):
|
||||||
html = ""
|
html = ""
|
||||||
|
|
||||||
@ -156,6 +164,7 @@ def __convert_jf_str_datetime(jf_string):
|
|||||||
return datetime.datetime.strptime(jf_string.partition(".")[0], "%Y-%m-%dT%H:%M:%S")
|
return datetime.datetime.strptime(jf_string.partition(".")[0], "%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming,PyPep8Naming,PyPep8Naming
|
||||||
def getMiscInfoHtml(item):
|
def getMiscInfoHtml(item):
|
||||||
# FIXME: Flake8 is complaining this function is too complex.
|
# FIXME: Flake8 is complaining this function is too complex.
|
||||||
# I agree, this needs to be cleaned up, a lot.
|
# I agree, this needs to be cleaned up, a lot.
|
||||||
@ -172,7 +181,7 @@ def getMiscInfoHtml(item):
|
|||||||
if item.get("StartDate"):
|
if item.get("StartDate"):
|
||||||
date = __convert_jf_str_datetime(item["StartDate"])
|
date = __convert_jf_str_datetime(item["StartDate"])
|
||||||
text = date.strftime("%x")
|
text = date.strftime("%x")
|
||||||
miscInfo.push(text)
|
miscInfo.append(text)
|
||||||
|
|
||||||
if item["Type"] != "Recording":
|
if item["Type"] != "Recording":
|
||||||
pass
|
pass
|
||||||
@ -201,7 +210,7 @@ def getMiscInfoHtml(item):
|
|||||||
if item["Type"] == "Audio":
|
if item["Type"] == "Audio":
|
||||||
# FIXME
|
# FIXME
|
||||||
raise Exception("Haven't translated this to Python yet")
|
raise Exception("Haven't translated this to Python yet")
|
||||||
miscInfo.append(datetime.getDisplayRunningTime(item["RunTimeTicks"]))
|
# miscInfo.append(datetime.getDisplayRunningTime(item["RunTimeTicks"]))
|
||||||
else:
|
else:
|
||||||
# Using math.ceil instead of round because I want the minutes rounded *up* specifically,
|
# Using math.ceil instead of round because I want the minutes rounded *up* specifically,
|
||||||
# mostly because '1min' makes more sense than '0min' for a 1-59sec clip
|
# mostly because '1min' makes more sense than '0min' for a 1-59sec clip
|
||||||
@ -222,9 +231,11 @@ def getMiscInfoHtml(item):
|
|||||||
return " ".join(miscInfo)
|
return " ".join(miscInfo)
|
||||||
|
|
||||||
|
|
||||||
# For some reason the webview 2.3 js api will send a positional argument of None when there are no arguments being passed in.
|
# For some reason the webview 2.3 js api will send a positional argument of None when there are no
|
||||||
# This really long argument name is here to catch that and hopefully not eat other intentional arguments.
|
# arguments being passed in. This really long argument name is here to catch that and hopefully not
|
||||||
def getRandomBackdropUrl(positional_arg_that_is_never_used=None, **params):
|
# eat other intentional arguments.
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
def getRandomBackdropUrl(_positional_arg_that_is_never_used=None, **params):
|
||||||
# This function is to get 1 random item, so ignore those arguments
|
# This function is to get 1 random item, so ignore those arguments
|
||||||
params["SortBy"] = "Random"
|
params["SortBy"] = "Random"
|
||||||
params["Limit"] = 1
|
params["Limit"] = 1
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .utils import plex_color_to_mpv
|
|
||||||
from .conf import settings
|
from .conf import settings
|
||||||
from .media import Media
|
from .media import Media
|
||||||
from .player import playerManager
|
from .player import playerManager
|
||||||
@ -41,9 +40,9 @@ class EventHandler(object):
|
|||||||
log.debug("Unhandled Event {0}: {1}".format(event_name, arguments))
|
log.debug("Unhandled Event {0}: {1}".format(event_name, arguments))
|
||||||
|
|
||||||
@bind("Play")
|
@bind("Play")
|
||||||
def play_media(self, client, event_name, arguments):
|
def play_media(self, client, _event_name, arguments):
|
||||||
play_command = arguments.get("PlayCommand")
|
play_command = arguments.get("PlayCommand")
|
||||||
if not playerManager._video:
|
if not playerManager.has_video():
|
||||||
play_command = "PlayNow"
|
play_command = "PlayNow"
|
||||||
|
|
||||||
if play_command == "PlayNow":
|
if play_command == "PlayNow":
|
||||||
@ -70,22 +69,22 @@ class EventHandler(object):
|
|||||||
if settings.pre_media_cmd:
|
if settings.pre_media_cmd:
|
||||||
os.system(settings.pre_media_cmd)
|
os.system(settings.pre_media_cmd)
|
||||||
playerManager.play(video, offset)
|
playerManager.play(video, offset)
|
||||||
timelineManager.SendTimeline()
|
timelineManager.send_timeline()
|
||||||
if arguments.get("SyncPlayGroup") is not None:
|
if arguments.get("SyncPlayGroup") is not None:
|
||||||
playerManager.syncplay.join_group(arguments["SyncPlayGroup"])
|
playerManager.syncplay.join_group(arguments["SyncPlayGroup"])
|
||||||
elif play_command == "PlayLast":
|
elif play_command == "PlayLast":
|
||||||
playerManager._video.parent.insert_items(
|
playerManager.get_video().parent.insert_items(
|
||||||
arguments.get("ItemIds"), append=True
|
arguments.get("ItemIds"), append=True
|
||||||
)
|
)
|
||||||
playerManager.upd_player_hide()
|
playerManager.upd_player_hide()
|
||||||
elif play_command == "PlayNext":
|
elif play_command == "PlayNext":
|
||||||
playerManager._video.parent.insert_items(
|
playerManager.get_video().parent.insert_items(
|
||||||
arguments.get("ItemIds"), append=False
|
arguments.get("ItemIds"), append=False
|
||||||
)
|
)
|
||||||
playerManager.upd_player_hide()
|
playerManager.upd_player_hide()
|
||||||
|
|
||||||
@bind("GeneralCommand")
|
@bind("GeneralCommand")
|
||||||
def general_command(self, client, event_name, arguments):
|
def general_command(self, client, _event_name, arguments):
|
||||||
command = arguments.get("Name")
|
command = arguments.get("Name")
|
||||||
if command == "SetVolume":
|
if command == "SetVolume":
|
||||||
# There is currently a bug that causes this to be spammed, so we
|
# There is currently a bug that causes this to be spammed, so we
|
||||||
@ -100,7 +99,7 @@ class EventHandler(object):
|
|||||||
# If you have an idle command set, this will delay it.
|
# If you have an idle command set, this will delay it.
|
||||||
timelineManager.delay_idle()
|
timelineManager.delay_idle()
|
||||||
if self.mirror:
|
if self.mirror:
|
||||||
self.mirror.DisplayContent(client, arguments)
|
self.mirror.display_content(client, arguments)
|
||||||
elif command in (
|
elif command in (
|
||||||
"Back",
|
"Back",
|
||||||
"Select",
|
"Select",
|
||||||
@ -121,7 +120,7 @@ class EventHandler(object):
|
|||||||
playerManager.toggle_fullscreen()
|
playerManager.toggle_fullscreen()
|
||||||
|
|
||||||
@bind("Playstate")
|
@bind("Playstate")
|
||||||
def play_state(self, client, event_name, arguments):
|
def play_state(self, _client, _event_name, arguments):
|
||||||
command = arguments.get("Command")
|
command = arguments.get("Command")
|
||||||
if command == "PlayPause":
|
if command == "PlayPause":
|
||||||
playerManager.toggle_pause()
|
playerManager.toggle_pause()
|
||||||
@ -141,17 +140,17 @@ class EventHandler(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@bind("PlayPause")
|
@bind("PlayPause")
|
||||||
def pausePlay(self, client, event_name, arguments):
|
def pause_play(self, _client, _event_name, _arguments):
|
||||||
playerManager.toggle_pause()
|
playerManager.toggle_pause()
|
||||||
timelineManager.SendTimeline()
|
timelineManager.send_timeline()
|
||||||
|
|
||||||
@bind("SyncPlayGroupUpdate")
|
@bind("SyncPlayGroupUpdate")
|
||||||
def sync_play_group_update(self, client, event_name, arguments):
|
def sync_play_group_update(self, client, _event_name, arguments):
|
||||||
playerManager.syncplay.client = client
|
playerManager.syncplay.client = client
|
||||||
playerManager.syncplay.process_group_update(arguments)
|
playerManager.syncplay.process_group_update(arguments)
|
||||||
|
|
||||||
@bind("SyncPlayCommand")
|
@bind("SyncPlayCommand")
|
||||||
def sync_play_command(self, client, event_name, arguments):
|
def sync_play_command(self, client, _event_name, arguments):
|
||||||
playerManager.syncplay.client = client
|
playerManager.syncplay.client = client
|
||||||
playerManager.syncplay.process_command(arguments)
|
playerManager.syncplay.process_command(arguments)
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import threading
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import queue
|
import queue
|
||||||
import os.path
|
|
||||||
|
|
||||||
from .constants import USER_APP_NAME, APP_NAME
|
from .constants import USER_APP_NAME, APP_NAME
|
||||||
from .conffile import confdir
|
from .conffile import confdir
|
||||||
@ -19,6 +18,8 @@ log = logging.getLogger("gui_mgr")
|
|||||||
|
|
||||||
# From https://stackoverflow.com/questions/6631299/
|
# From https://stackoverflow.com/questions/6631299/
|
||||||
# This is for opening the config directory.
|
# This is for opening the config directory.
|
||||||
|
|
||||||
|
|
||||||
def _show_file_darwin(path):
|
def _show_file_darwin(path):
|
||||||
subprocess.Popen(["open", path])
|
subprocess.Popen(["open", path])
|
||||||
|
|
||||||
@ -82,6 +83,9 @@ root_logger.addHandler(guiHandler)
|
|||||||
class LoggerWindow(threading.Thread):
|
class LoggerWindow(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.dead = False
|
self.dead = False
|
||||||
|
self.queue = None
|
||||||
|
self.r_queue = None
|
||||||
|
self.process = None
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -104,7 +108,7 @@ class LoggerWindow(threading.Thread):
|
|||||||
def handle(self, action, params=None):
|
def handle(self, action, params=None):
|
||||||
self.queue.put((action, params))
|
self.queue.put((action, params))
|
||||||
|
|
||||||
def stop(self, is_source=False):
|
def stop(self):
|
||||||
self.r_queue.put(("die", None))
|
self.r_queue.put(("die", None))
|
||||||
|
|
||||||
def _die(self):
|
def _die(self):
|
||||||
@ -118,6 +122,9 @@ class LoggerWindowProcess(Process):
|
|||||||
def __init__(self, queue, r_queue):
|
def __init__(self, queue, r_queue):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.r_queue = r_queue
|
self.r_queue = r_queue
|
||||||
|
self.tk = None
|
||||||
|
self.root = None
|
||||||
|
self.text = None
|
||||||
Process.__init__(self)
|
Process.__init__(self)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
@ -141,7 +148,6 @@ class LoggerWindowProcess(Process):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox
|
|
||||||
|
|
||||||
self.tk = tk
|
self.tk = tk
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
@ -164,6 +170,9 @@ class PreferencesWindow(threading.Thread):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.dead = False
|
self.dead = False
|
||||||
self.dead_trigger = threading.Event()
|
self.dead_trigger = threading.Event()
|
||||||
|
self.queue = None
|
||||||
|
self.r_queue = None
|
||||||
|
self.process = None
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -194,7 +203,7 @@ class PreferencesWindow(threading.Thread):
|
|||||||
def handle(self, action, params=None):
|
def handle(self, action, params=None):
|
||||||
self.queue.put((action, params))
|
self.queue.put((action, params))
|
||||||
|
|
||||||
def stop(self, is_source=False):
|
def stop(self):
|
||||||
self.r_queue.put(("die", None))
|
self.r_queue.put(("die", None))
|
||||||
|
|
||||||
def block_until_close(self):
|
def block_until_close(self):
|
||||||
@ -211,6 +220,18 @@ class PreferencesWindowProcess(Process):
|
|||||||
def __init__(self, queue, r_queue):
|
def __init__(self, queue, r_queue):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.r_queue = r_queue
|
self.r_queue = r_queue
|
||||||
|
self.servers = None
|
||||||
|
self.server_ids = None
|
||||||
|
self.tk = None
|
||||||
|
self.messagebox = None
|
||||||
|
self.root = None
|
||||||
|
self.serverList = None
|
||||||
|
self.current_uuid = None
|
||||||
|
self.servername = None
|
||||||
|
self.username = None
|
||||||
|
self.password = None
|
||||||
|
self.add_button = None
|
||||||
|
self.remove_button = None
|
||||||
Process.__init__(self)
|
Process.__init__(self)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
@ -266,7 +287,7 @@ class PreferencesWindowProcess(Process):
|
|||||||
self.serverList = tk.StringVar(value=[])
|
self.serverList = tk.StringVar(value=[])
|
||||||
self.current_uuid = None
|
self.current_uuid = None
|
||||||
|
|
||||||
def serverSelect(_x):
|
def server_select(_x):
|
||||||
idxs = serverlist.curselection()
|
idxs = serverlist.curselection()
|
||||||
if len(idxs) == 1:
|
if len(idxs) == 1:
|
||||||
self.current_uuid = self.server_ids[idxs[0]]
|
self.current_uuid = self.server_ids[idxs[0]]
|
||||||
@ -322,7 +343,7 @@ class PreferencesWindowProcess(Process):
|
|||||||
close_button = ttk.Button(c, text=_("Close"), command=close)
|
close_button = ttk.Button(c, text=_("Close"), command=close)
|
||||||
close_button.grid(column=2, row=4, pady=10, sticky=(tk.E, tk.S))
|
close_button.grid(column=2, row=4, pady=10, sticky=(tk.E, tk.S))
|
||||||
|
|
||||||
serverlist.bind("<<ListboxSelect>>", serverSelect)
|
serverlist.bind("<<ListboxSelect>>", server_select)
|
||||||
self.update()
|
self.update()
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
self.r_queue.put(("die", None))
|
self.r_queue.put(("die", None))
|
||||||
@ -348,6 +369,8 @@ class UserInterface(threading.Thread):
|
|||||||
self.preferences_window = None
|
self.preferences_window = None
|
||||||
self.stop_callback = None
|
self.stop_callback = None
|
||||||
self.gui_ready = None
|
self.gui_ready = None
|
||||||
|
self.r_queue = None
|
||||||
|
self.process = None
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
@ -398,7 +421,8 @@ class UserInterface(threading.Thread):
|
|||||||
if self.gui_ready:
|
if self.gui_ready:
|
||||||
self.gui_ready.set()
|
self.gui_ready.set()
|
||||||
|
|
||||||
def open_config_brs(self):
|
@staticmethod
|
||||||
|
def open_config_brs():
|
||||||
if open_config:
|
if open_config:
|
||||||
open_config()
|
open_config()
|
||||||
else:
|
else:
|
||||||
@ -408,6 +432,7 @@ class UserInterface(threading.Thread):
|
|||||||
class STrayProcess(Process):
|
class STrayProcess(Process):
|
||||||
def __init__(self, r_queue):
|
def __init__(self, r_queue):
|
||||||
self.r_queue = r_queue
|
self.r_queue = r_queue
|
||||||
|
self.icon_stop = None
|
||||||
Process.__init__(self)
|
Process.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@ -447,4 +472,4 @@ class STrayProcess(Process):
|
|||||||
self.r_queue.put(("die", None))
|
self.r_queue.put(("die", None))
|
||||||
|
|
||||||
|
|
||||||
userInterface = UserInterface()
|
user_interface = UserInterface()
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import gettext
|
import gettext
|
||||||
import builtins
|
|
||||||
import locale
|
import locale
|
||||||
|
|
||||||
from .conf import settings
|
from .conf import settings
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
@ -55,7 +54,7 @@ class Video(object):
|
|||||||
self.audio_uid[index] = stream["Index"]
|
self.audio_uid[index] = stream["Index"]
|
||||||
self.audio_seq[stream["Index"]] = index
|
self.audio_seq[stream["Index"]] = index
|
||||||
|
|
||||||
if stream.get("IsExternal") == False:
|
if not stream.get("IsExternal"):
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
index = 1
|
index = 1
|
||||||
@ -74,7 +73,7 @@ class Video(object):
|
|||||||
elif sub.get("DeliveryMethod") == "Encode":
|
elif sub.get("DeliveryMethod") == "Encode":
|
||||||
self.subtitle_enc.add(sub["Index"])
|
self.subtitle_enc.add(sub["Index"])
|
||||||
|
|
||||||
if sub.get("IsExternal") == False:
|
if not sub.get("IsExternal"):
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
user_aid = self.media_source.get("DefaultAudioStreamIndex")
|
user_aid = self.media_source.get("DefaultAudioStreamIndex")
|
||||||
@ -140,7 +139,7 @@ class Video(object):
|
|||||||
self.client.config.data["app.device_id"]
|
self.client.config.data["app.device_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_url_from_source(self, source):
|
def _get_url_from_source(self):
|
||||||
# Only use Direct Paths if:
|
# Only use Direct Paths if:
|
||||||
# - The media source supports direct paths.
|
# - The media source supports direct paths.
|
||||||
# - Direct paths are enabled in the config.
|
# - Direct paths are enabled in the config.
|
||||||
@ -216,11 +215,9 @@ class Video(object):
|
|||||||
log.warning("Preferred media source is unplayable.")
|
log.warning("Preferred media source is unplayable.")
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
def get_playback_url(
|
def get_playback_url(self, video_bitrate=None, force_transcode=False):
|
||||||
self, offset=0, video_bitrate=None, force_transcode=False, force_bitrate=False
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Returns the URL to use for the trancoded file.
|
Returns the URL to use for the transcoded file.
|
||||||
"""
|
"""
|
||||||
self.terminate_transcode()
|
self.terminate_transcode()
|
||||||
|
|
||||||
@ -239,7 +236,7 @@ class Video(object):
|
|||||||
|
|
||||||
self.media_source = self.get_best_media_source(self.srcid)
|
self.media_source = self.get_best_media_source(self.srcid)
|
||||||
self.map_streams()
|
self.map_streams()
|
||||||
url = self._get_url_from_source(self.media_source)
|
url = self._get_url_from_source()
|
||||||
|
|
||||||
# If there are more media sources and the default one fails, try all of them.
|
# If there are more media sources and the default one fails, try all of them.
|
||||||
if url is None and len(self.playback_info["MediaSources"]) > 1:
|
if url is None and len(self.playback_info["MediaSources"]) > 1:
|
||||||
@ -248,7 +245,7 @@ class Video(object):
|
|||||||
if media_source["Id"] != self.srcid:
|
if media_source["Id"] != self.srcid:
|
||||||
self.media_source = media_source
|
self.media_source = media_source
|
||||||
self.map_streams()
|
self.map_streams()
|
||||||
url = self._get_url_from_source(self.media_source)
|
url = self._get_url_from_source()
|
||||||
if url is not None:
|
if url is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ if "und" in lang_filter:
|
|||||||
|
|
||||||
|
|
||||||
class OSDMenu(object):
|
class OSDMenu(object):
|
||||||
def __init__(self, playerManager):
|
def __init__(self, player_manager, player):
|
||||||
self.playerManager = playerManager
|
self.playerManager = player_manager
|
||||||
|
|
||||||
self.is_menu_shown = False
|
self.is_menu_shown = False
|
||||||
self.menu_title = ""
|
self.menu_title = ""
|
||||||
@ -62,21 +62,23 @@ class OSDMenu(object):
|
|||||||
self.menu_selection = 0
|
self.menu_selection = 0
|
||||||
self.menu_tmp = None
|
self.menu_tmp = None
|
||||||
self.mouse_back = False
|
self.mouse_back = False
|
||||||
self.original_osd_color = playerManager._player.osd_back_color
|
(
|
||||||
self.original_osd_size = playerManager._player.osd_font_size
|
self.original_osd_color,
|
||||||
|
self.original_osd_size,
|
||||||
|
) = player_manager.get_osd_settings()
|
||||||
|
|
||||||
self.profile_menu = None
|
self.profile_menu = None
|
||||||
self.profile_manager = None
|
self.profile_manager = None
|
||||||
if settings.shader_pack_enable:
|
if settings.shader_pack_enable:
|
||||||
try:
|
try:
|
||||||
self.profile_manager = VideoProfileManager(self, playerManager)
|
self.profile_manager = VideoProfileManager(self, player_manager, player)
|
||||||
self.profile_menu = self.profile_manager.menu_action
|
self.profile_menu = self.profile_manager.menu_action
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error("Could not load profile manager.", exc_info=True)
|
log.error("Could not load profile manager.", exc_info=True)
|
||||||
|
|
||||||
self.svp_menu = None
|
self.svp_menu = None
|
||||||
try:
|
try:
|
||||||
self.svp_menu = SVPManager(self, playerManager)
|
self.svp_menu = SVPManager(self, player_manager)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.error("Could not load SVP integration.", exc_info=True)
|
log.error("Could not load SVP integration.", exc_info=True)
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ class OSDMenu(object):
|
|||||||
fmt = "\n **{0}**"
|
fmt = "\n **{0}**"
|
||||||
menu_text += fmt.format(item[0])
|
menu_text += fmt.format(item[0])
|
||||||
|
|
||||||
self.playerManager._player.show_text(menu_text, 2 ** 30, 1)
|
self.playerManager.show_text(menu_text, 2 ** 30, 1)
|
||||||
|
|
||||||
def mouse_select(self, idx):
|
def mouse_select(self, idx):
|
||||||
if idx < 0 or idx > len(self.menu_list):
|
if idx < 0 or idx > len(self.menu_list):
|
||||||
@ -115,20 +117,16 @@ class OSDMenu(object):
|
|||||||
|
|
||||||
def show_menu(self):
|
def show_menu(self):
|
||||||
self.is_menu_shown = True
|
self.is_menu_shown = True
|
||||||
player = self.playerManager._player
|
self.playerManager.set_osd_settings("#CC333333", 40)
|
||||||
player.osd_back_color = "#CC333333"
|
|
||||||
player.osd_font_size = 40
|
|
||||||
|
|
||||||
if hasattr(player, "osc"):
|
self.playerManager.enable_osc(False)
|
||||||
player.osc = False
|
self.playerManager.capture_mouse(True)
|
||||||
|
|
||||||
player.command("script-message", "shim-menu-enable", "True")
|
|
||||||
|
|
||||||
self.menu_title = _("Main Menu")
|
self.menu_title = _("Main Menu")
|
||||||
self.menu_selection = 0
|
self.menu_selection = 0
|
||||||
self.mouse_back = False
|
self.mouse_back = False
|
||||||
|
|
||||||
if self.playerManager._video and not player.playback_abort:
|
if self.playerManager.is_playing():
|
||||||
self.menu_list = [
|
self.menu_list = [
|
||||||
(_("Change Audio"), self.change_audio_menu),
|
(_("Change Audio"), self.change_audio_menu),
|
||||||
(_("Change Subtitles"), self.change_subtitle_menu),
|
(_("Change Subtitles"), self.change_subtitle_menu),
|
||||||
@ -149,7 +147,7 @@ class OSDMenu(object):
|
|||||||
self.menu_list.append(
|
self.menu_list.append(
|
||||||
(_("Change Video Playback Profile"), self.profile_menu)
|
(_("Change Video Playback Profile"), self.profile_menu)
|
||||||
)
|
)
|
||||||
if self.playerManager._video.parent.is_tv:
|
if self.playerManager.get_video().parent.is_tv:
|
||||||
self.menu_list.append(
|
self.menu_list.append(
|
||||||
(
|
(
|
||||||
_("Auto Set Audio/Subtitles (Entire Series)"),
|
_("Auto Set Audio/Subtitles (Entire Series)"),
|
||||||
@ -182,40 +180,31 @@ class OSDMenu(object):
|
|||||||
# Wait until the menu renders to pause.
|
# Wait until the menu renders to pause.
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
if player.playback_abort:
|
if self.playerManager.playback_is_aborted():
|
||||||
player.force_window = True
|
self.playerManager.force_window(True)
|
||||||
player.keep_open = True
|
|
||||||
player.play("")
|
|
||||||
if settings.fullscreen:
|
|
||||||
player.fs = True
|
|
||||||
else:
|
else:
|
||||||
if not self.playerManager.syncplay.is_enabled():
|
if not self.playerManager.syncplay.is_enabled():
|
||||||
player.pause = True
|
self.playerManager.set_paused(True)
|
||||||
|
|
||||||
def hide_menu(self):
|
def hide_menu(self):
|
||||||
player = self.playerManager._player
|
|
||||||
if self.is_menu_shown:
|
if self.is_menu_shown:
|
||||||
player.osd_back_color = self.original_osd_color
|
self.playerManager.set_osd_settings(
|
||||||
player.osd_font_size = self.original_osd_size
|
self.original_osd_color, self.original_osd_size
|
||||||
player.show_text("", 0, 0)
|
)
|
||||||
player.force_window = False
|
self.playerManager.show_text("", 0, 0)
|
||||||
player.keep_open = False
|
|
||||||
|
|
||||||
if hasattr(player, "osc"):
|
self.playerManager.enable_osc(True)
|
||||||
player.osc = settings.enable_osc
|
self.playerManager.capture_mouse(False)
|
||||||
|
self.playerManager.force_window(False)
|
||||||
|
|
||||||
player.command("script-message", "shim-menu-enable", "False")
|
if not self.playerManager.playback_is_aborted():
|
||||||
|
|
||||||
if player.playback_abort:
|
|
||||||
player.play("")
|
|
||||||
else:
|
|
||||||
if not self.playerManager.syncplay.is_enabled():
|
if not self.playerManager.syncplay.is_enabled():
|
||||||
player.pause = False
|
self.playerManager.set_paused(False)
|
||||||
|
|
||||||
self.is_menu_shown = False
|
self.is_menu_shown = False
|
||||||
|
|
||||||
def screenshot(self):
|
def screenshot(self):
|
||||||
self.playerManager._player.show_text("", 0, 0)
|
self.playerManager.show_text("", 0, 0)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
self.playerManager.screenshot()
|
self.playerManager.screenshot()
|
||||||
self.hide_menu()
|
self.hide_menu()
|
||||||
@ -266,10 +255,10 @@ class OSDMenu(object):
|
|||||||
def change_audio_menu(self):
|
def change_audio_menu(self):
|
||||||
self.put_menu(_("Select Audio Track"))
|
self.put_menu(_("Select Audio Track"))
|
||||||
|
|
||||||
selected_aid = self.playerManager._video.aid
|
selected_aid = self.playerManager.get_video().aid
|
||||||
audio_streams = [
|
audio_streams = [
|
||||||
s
|
s
|
||||||
for s in self.playerManager._video.media_source["MediaStreams"]
|
for s in self.playerManager.get_video().media_source["MediaStreams"]
|
||||||
if s.get("Type") == "Audio"
|
if s.get("Type") == "Audio"
|
||||||
]
|
]
|
||||||
for i, audio_track in enumerate(audio_streams):
|
for i, audio_track in enumerate(audio_streams):
|
||||||
@ -303,10 +292,10 @@ class OSDMenu(object):
|
|||||||
def change_subtitle_menu(self):
|
def change_subtitle_menu(self):
|
||||||
self.put_menu(_("Select Subtitle Track"))
|
self.put_menu(_("Select Subtitle Track"))
|
||||||
|
|
||||||
selected_sid = self.playerManager._video.sid
|
selected_sid = self.playerManager.get_video().sid
|
||||||
subtitle_streams = [
|
subtitle_streams = [
|
||||||
s
|
s
|
||||||
for s in self.playerManager._video.media_source["MediaStreams"]
|
for s in self.playerManager.get_video().media_source["MediaStreams"]
|
||||||
if s.get("Type") == "Subtitle"
|
if s.get("Type") == "Subtitle"
|
||||||
]
|
]
|
||||||
self.menu_list.append([_("None"), self.change_subtitle_menu_handle, -1])
|
self.menu_list.append([_("None"), self.change_subtitle_menu_handle, -1])
|
||||||
@ -335,11 +324,11 @@ class OSDMenu(object):
|
|||||||
def change_transcode_quality_handle(self):
|
def change_transcode_quality_handle(self):
|
||||||
bitrate = self.menu_list[self.menu_selection][2]
|
bitrate = self.menu_list[self.menu_selection][2]
|
||||||
if bitrate == "none":
|
if bitrate == "none":
|
||||||
self.playerManager._video.set_trs_override(None, False)
|
self.playerManager.get_video().set_trs_override(None, False)
|
||||||
elif bitrate == "max":
|
elif bitrate == "max":
|
||||||
self.playerManager._video.set_trs_override(None, True)
|
self.playerManager.get_video().set_trs_override(None, True)
|
||||||
else:
|
else:
|
||||||
self.playerManager._video.set_trs_override(bitrate, True)
|
self.playerManager.get_video().set_trs_override(bitrate, True)
|
||||||
|
|
||||||
self.menu_action("back")
|
self.menu_action("back")
|
||||||
self.playerManager.put_task(self.playerManager.restart_playback)
|
self.playerManager.put_task(self.playerManager.restart_playback)
|
||||||
@ -356,7 +345,7 @@ class OSDMenu(object):
|
|||||||
self.menu_list.append((item[0], handle, item[1]))
|
self.menu_list.append((item[0], handle, item[1]))
|
||||||
|
|
||||||
self.menu_selection = 7
|
self.menu_selection = 7
|
||||||
cur_bitrate = self.playerManager._video.get_transcode_bitrate()
|
cur_bitrate = self.playerManager.get_video().get_transcode_bitrate()
|
||||||
for i, option in enumerate(self.menu_list):
|
for i, option in enumerate(self.menu_list):
|
||||||
if cur_bitrate == option[2]:
|
if cur_bitrate == option[2]:
|
||||||
self.menu_selection = i
|
self.menu_selection = i
|
||||||
@ -433,7 +422,8 @@ class OSDMenu(object):
|
|||||||
if settings.remote_kbps == item[1]:
|
if settings.remote_kbps == item[1]:
|
||||||
self.menu_selection = i
|
self.menu_selection = i
|
||||||
|
|
||||||
def get_subtitle_color(self, color):
|
@staticmethod
|
||||||
|
def get_subtitle_color(color):
|
||||||
if color in HEX_TO_COLOR:
|
if color in HEX_TO_COLOR:
|
||||||
return HEX_TO_COLOR[color]
|
return HEX_TO_COLOR[color]
|
||||||
else:
|
else:
|
||||||
@ -450,7 +440,7 @@ class OSDMenu(object):
|
|||||||
self.menu_action("back")
|
self.menu_action("back")
|
||||||
self.video_preferences_menu()
|
self.video_preferences_menu()
|
||||||
|
|
||||||
if self.playerManager._video.is_transcode:
|
if self.playerManager.get_video().is_transcode:
|
||||||
if setting_name == "subtitle_size":
|
if setting_name == "subtitle_size":
|
||||||
self.playerManager.put_task(self.playerManager.update_subtitle_visuals)
|
self.playerManager.put_task(self.playerManager.update_subtitle_visuals)
|
||||||
else:
|
else:
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
@ -33,23 +32,24 @@ def main(desktop=False, cef=False):
|
|||||||
if sys.platform.startswith("darwin"):
|
if sys.platform.startswith("darwin"):
|
||||||
multiprocessing.set_start_method("forkserver")
|
multiprocessing.set_start_method("forkserver")
|
||||||
|
|
||||||
userInterface = None
|
user_interface = None
|
||||||
mirror = None
|
mirror = None
|
||||||
use_gui = False
|
use_gui = False
|
||||||
|
gui_ready = None
|
||||||
use_webview = desktop or settings.enable_desktop
|
use_webview = desktop or settings.enable_desktop
|
||||||
get_webview = lambda: None
|
get_webview = lambda: None
|
||||||
if use_webview:
|
if use_webview:
|
||||||
from .webclient_view import WebviewClient
|
from .webclient_view import WebviewClient
|
||||||
|
|
||||||
userInterface = WebviewClient(cef=cef)
|
user_interface = WebviewClient(cef=cef)
|
||||||
get_webview = userInterface.get_webview
|
get_webview = user_interface.get_webview
|
||||||
elif settings.enable_gui:
|
elif settings.enable_gui:
|
||||||
try:
|
try:
|
||||||
from .gui_mgr import userInterface
|
from .gui_mgr import user_interface
|
||||||
|
|
||||||
use_gui = True
|
use_gui = True
|
||||||
gui_ready = Event()
|
gui_ready = Event()
|
||||||
userInterface.gui_ready = gui_ready
|
user_interface.gui_ready = gui_ready
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning(
|
log.warning(
|
||||||
"Cannot load GUI. Falling back to command line interface.",
|
"Cannot load GUI. Falling back to command line interface.",
|
||||||
@ -62,10 +62,11 @@ def main(desktop=False, cef=False):
|
|||||||
|
|
||||||
get_webview = mirror.get_webview
|
get_webview = mirror.get_webview
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
mirror = None
|
||||||
log.warning("Cannot load display mirror.", exc_info=True)
|
log.warning("Cannot load display mirror.", exc_info=True)
|
||||||
|
|
||||||
if not userInterface:
|
if not user_interface:
|
||||||
from .cli_mgr import userInterface
|
from .cli_mgr import user_interface
|
||||||
|
|
||||||
from .player import playerManager
|
from .player import playerManager
|
||||||
from .action_thread import actionThread
|
from .action_thread import actionThread
|
||||||
@ -78,23 +79,23 @@ def main(desktop=False, cef=False):
|
|||||||
actionThread.start()
|
actionThread.start()
|
||||||
playerManager.action_trigger = actionThread.trigger
|
playerManager.action_trigger = actionThread.trigger
|
||||||
playerManager.get_webview = get_webview
|
playerManager.get_webview = get_webview
|
||||||
userInterface.open_player_menu = playerManager.menu.show_menu
|
user_interface.open_player_menu = playerManager.menu.show_menu
|
||||||
eventHandler.mirror = mirror
|
eventHandler.mirror = mirror
|
||||||
userInterface.start()
|
user_interface.start()
|
||||||
userInterface.login_servers()
|
user_interface.login_servers()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if use_webview:
|
if use_webview:
|
||||||
userInterface.run()
|
user_interface.run()
|
||||||
elif mirror:
|
elif mirror:
|
||||||
userInterface.stop_callback = mirror.stop
|
user_interface.stop_callback = mirror.stop
|
||||||
# If the webview runs before the systray icon, it fails.
|
# If the webview runs before the systray icon, it fails.
|
||||||
if use_gui:
|
if use_gui:
|
||||||
gui_ready.wait()
|
gui_ready.wait()
|
||||||
mirror.run()
|
mirror.run()
|
||||||
else:
|
else:
|
||||||
halt = Event()
|
halt = Event()
|
||||||
userInterface.stop_callback = halt.set
|
user_interface.stop_callback = halt.set
|
||||||
try:
|
try:
|
||||||
halt.wait()
|
halt.wait()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@ -105,7 +106,7 @@ def main(desktop=False, cef=False):
|
|||||||
timelineManager.stop()
|
timelineManager.stop()
|
||||||
actionThread.stop()
|
actionThread.stop()
|
||||||
clientManager.stop()
|
clientManager.stop()
|
||||||
userInterface.stop()
|
user_interface.stop()
|
||||||
|
|
||||||
|
|
||||||
def main_desktop(cef=False):
|
def main_desktop(cef=False):
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from threading import RLock, Lock, Event
|
from threading import RLock, Lock, Event
|
||||||
@ -34,6 +33,7 @@ python_mpv_available = True
|
|||||||
is_using_ext_mpv = False
|
is_using_ext_mpv = False
|
||||||
if not settings.mpv_ext:
|
if not settings.mpv_ext:
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyPackageRequirements
|
||||||
import mpv
|
import mpv
|
||||||
|
|
||||||
log.info("Using libmpv1 playback backend.")
|
log.info("Using libmpv1 playback backend.")
|
||||||
@ -106,6 +106,7 @@ if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
|
|||||||
# the event thread, which would cause deadlock if they run there.
|
# the event thread, which would cause deadlock if they run there.
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
class PlayerManager(object):
|
class PlayerManager(object):
|
||||||
"""
|
"""
|
||||||
The underlying player is thread safe, however, locks are used in this
|
The underlying player is thread safe, however, locks are used in this
|
||||||
@ -161,7 +162,7 @@ class PlayerManager(object):
|
|||||||
loglevel=settings.mpv_log_level,
|
loglevel=settings.mpv_log_level,
|
||||||
**mpv_options
|
**mpv_options
|
||||||
)
|
)
|
||||||
self.menu = OSDMenu(self)
|
self.menu = OSDMenu(self, self._player)
|
||||||
self.syncplay = SyncPlayManager(self)
|
self.syncplay = SyncPlayManager(self)
|
||||||
|
|
||||||
if hasattr(self._player, "osc"):
|
if hasattr(self._player, "osc"):
|
||||||
@ -307,13 +308,13 @@ class PlayerManager(object):
|
|||||||
|
|
||||||
# Fires at the end.
|
# Fires at the end.
|
||||||
@self._player.property_observer("playback-abort")
|
@self._player.property_observer("playback-abort")
|
||||||
def handle_end_idle(name, value):
|
def handle_end_idle(_name, value):
|
||||||
if self._video and value:
|
if self._video and value:
|
||||||
has_lock = self._finished_lock.acquire(False)
|
has_lock = self._finished_lock.acquire(False)
|
||||||
self.put_task(self.finished_callback, has_lock)
|
self.put_task(self.finished_callback, has_lock)
|
||||||
|
|
||||||
@self._player.property_observer("seeking")
|
@self._player.property_observer("seeking")
|
||||||
def handle_seeking(name, value):
|
def handle_seeking(_name, value):
|
||||||
if self.syncplay.is_enabled():
|
if self.syncplay.is_enabled():
|
||||||
play_time = self._player.playback_time
|
play_time = self._player.playback_time
|
||||||
if (
|
if (
|
||||||
@ -404,6 +405,7 @@ class PlayerManager(object):
|
|||||||
if self.get_webview() is not None and (
|
if self.get_webview() is not None and (
|
||||||
settings.display_mirroring or settings.desktop_fullscreen
|
settings.display_mirroring or settings.desktop_fullscreen
|
||||||
):
|
):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
self.get_webview().hide()
|
self.get_webview().hide()
|
||||||
|
|
||||||
self._player.play(self.url)
|
self._player.play(self.url)
|
||||||
@ -457,7 +459,8 @@ class PlayerManager(object):
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def exec_stop_cmd(self):
|
@staticmethod
|
||||||
|
def exec_stop_cmd():
|
||||||
if settings.stop_cmd:
|
if settings.stop_cmd:
|
||||||
os.system(settings.stop_cmd)
|
os.system(settings.stop_cmd)
|
||||||
|
|
||||||
@ -678,6 +681,11 @@ class PlayerManager(object):
|
|||||||
self._player.fs = not self._player.fs
|
self._player.fs = not self._player.fs
|
||||||
self.fullscreen_disable = not self._player.fs
|
self.fullscreen_disable = not self._player.fs
|
||||||
|
|
||||||
|
@synchronous("_lock")
|
||||||
|
def set_fullscreen(self, enabled):
|
||||||
|
self._player.fs = enabled
|
||||||
|
self.fullscreen_disable = not enabled
|
||||||
|
|
||||||
@synchronous("_lock")
|
@synchronous("_lock")
|
||||||
def set_mute(self, mute):
|
def set_mute(self, mute):
|
||||||
self._player.mute = mute
|
self._player.mute = mute
|
||||||
@ -825,5 +833,68 @@ class PlayerManager(object):
|
|||||||
seek_right = custom_prefs.get("skipForwardLength") or 30000
|
seek_right = custom_prefs.get("skipForwardLength") or 30000
|
||||||
return -int(seek_left) / 1000, int(seek_right) / 1000
|
return -int(seek_left) / 1000, int(seek_right) / 1000
|
||||||
|
|
||||||
|
# Wrappers to avoid private access
|
||||||
|
def is_active(self):
|
||||||
|
return bool(self._player and self._video)
|
||||||
|
|
||||||
|
def is_playing(self):
|
||||||
|
return bool(self._video and not self._player.playback_abort)
|
||||||
|
|
||||||
|
def has_video(self):
|
||||||
|
return self._video is not None
|
||||||
|
|
||||||
|
def get_video(self):
|
||||||
|
return self._video
|
||||||
|
|
||||||
|
def show_text(self, text, duration, level=1):
|
||||||
|
self._player.show_text(text, duration, level)
|
||||||
|
|
||||||
|
def get_osd_settings(self):
|
||||||
|
return self._player.osd_back_color, self._player.osd_font_size
|
||||||
|
|
||||||
|
def set_osd_settings(self, back_color, font_size):
|
||||||
|
self._player.osd_back_color = back_color
|
||||||
|
self._player.osd_font_size = font_size
|
||||||
|
|
||||||
|
def enable_osc(self, enabled):
|
||||||
|
if hasattr(self._player, "osc"):
|
||||||
|
self._player.osc = enabled
|
||||||
|
|
||||||
|
def capture_mouse(self, enabled):
|
||||||
|
self._player.command(
|
||||||
|
"script-message", "shim-menu-enable", "True" if enabled else "False"
|
||||||
|
)
|
||||||
|
|
||||||
|
def playback_is_aborted(self):
|
||||||
|
return self._player.playback_abort
|
||||||
|
|
||||||
|
def force_window(self, enabled):
|
||||||
|
if enabled:
|
||||||
|
self._player.force_window = True
|
||||||
|
self._player.keep_open = True
|
||||||
|
self._player.play("")
|
||||||
|
if settings.fullscreen:
|
||||||
|
self._player.fs = True
|
||||||
|
else:
|
||||||
|
self._player.force_window = False
|
||||||
|
self._player.keep_open = False
|
||||||
|
if self._player.playback_abort:
|
||||||
|
self._player.play("")
|
||||||
|
|
||||||
|
def add_ipc(self, ipc_name):
|
||||||
|
self._player.input_ipc_server = ipc_name
|
||||||
|
|
||||||
|
def get_current_client(self):
|
||||||
|
return self._video.client
|
||||||
|
|
||||||
|
def get_time(self):
|
||||||
|
return self._player.playback_time
|
||||||
|
|
||||||
|
def get_speed(self):
|
||||||
|
return self._player.speed
|
||||||
|
|
||||||
|
def set_speed(self, speed):
|
||||||
|
self._player.speed = speed
|
||||||
|
|
||||||
|
|
||||||
playerManager = PlayerManager()
|
playerManager = PlayerManager()
|
||||||
|
@ -14,7 +14,7 @@ def list_request(path):
|
|||||||
try:
|
try:
|
||||||
response = urllib.request.urlopen(settings.svp_url + "?" + path)
|
response = urllib.request.urlopen(settings.svp_url + "?" + path)
|
||||||
return response.read().decode("utf-8").replace("\r\n", "\n").split("\n")
|
return response.read().decode("utf-8").replace("\r\n", "\n").split("\n")
|
||||||
except urllib.error.URLError as ex:
|
except urllib.error.URLError:
|
||||||
log.error("Could not reach SVP API server.", exc_info=True)
|
log.error("Could not reach SVP API server.", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ def set_disabled(disabled):
|
|||||||
|
|
||||||
|
|
||||||
class SVPManager:
|
class SVPManager:
|
||||||
def __init__(self, menu, playerManager):
|
def __init__(self, menu, player_manager):
|
||||||
self.menu = menu
|
self.menu = menu
|
||||||
|
|
||||||
if settings.svp_enable:
|
if settings.svp_enable:
|
||||||
@ -113,14 +113,15 @@ class SVPManager:
|
|||||||
socket = "/tmp/mpvsocket"
|
socket = "/tmp/mpvsocket"
|
||||||
|
|
||||||
# This actually *adds* another ipc server.
|
# This actually *adds* another ipc server.
|
||||||
playerManager._player.input_ipc_server = socket
|
player_manager.add_ipc(socket)
|
||||||
|
|
||||||
if settings.svp_enable and not is_svp_alive():
|
if settings.svp_enable and not is_svp_alive():
|
||||||
log.error(
|
log.error(
|
||||||
"SVP is not reachable. Please make sure you have the API enabled."
|
"SVP is not reachable. Please make sure you have the API enabled."
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_available(self):
|
@staticmethod
|
||||||
|
def is_available():
|
||||||
if not settings.svp_enable:
|
if not settings.svp_enable:
|
||||||
return False
|
return False
|
||||||
if not is_svp_alive():
|
if not is_svp_alive():
|
||||||
|
@ -4,7 +4,6 @@ import os
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from .media import Media
|
from .media import Media
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
# This is based on: https://github.com/jellyfin/jellyfin-web/blob/master/src/components/syncPlay/syncPlayManager.js
|
# This is based on: https://github.com/jellyfin/jellyfin-web/blob/master/src/components/syncPlay/syncPlayManager.js
|
||||||
|
|
||||||
@ -53,7 +52,6 @@ def set_timeout(ms: float, callback, *args):
|
|||||||
class SyncPlayManager:
|
class SyncPlayManager:
|
||||||
def __init__(self, manager):
|
def __init__(self, manager):
|
||||||
self.playerManager = manager
|
self.playerManager = manager
|
||||||
self.player = manager._player
|
|
||||||
self.menu = manager.menu
|
self.menu = manager.menu
|
||||||
|
|
||||||
self.sync_enabled = False
|
self.sync_enabled = False
|
||||||
@ -106,7 +104,7 @@ class SyncPlayManager:
|
|||||||
self.last_sync_time = current_time
|
self.last_sync_time = current_time
|
||||||
play_at_time = self.last_command["When"]
|
play_at_time = self.last_command["When"]
|
||||||
|
|
||||||
current_position_ticks = int(self.player.playback_time * seconds_in_ticks)
|
current_position_ticks = int(self.playerManager.get_time() * seconds_in_ticks)
|
||||||
server_position_ticks = (
|
server_position_ticks = (
|
||||||
self.last_command["PositionTicks"]
|
self.last_command["PositionTicks"]
|
||||||
+ ((current_time - play_at_time) + self.time_offset).total_seconds()
|
+ ((current_time - play_at_time) + self.time_offset).total_seconds()
|
||||||
@ -132,14 +130,14 @@ class SyncPlayManager:
|
|||||||
# Speed To Sync Method
|
# Speed To Sync Method
|
||||||
speed = 1 + diff_ms / settings.sync_speed_time
|
speed = 1 + diff_ms / settings.sync_speed_time
|
||||||
|
|
||||||
self.player.speed = speed
|
self.playerManager.set_speed(speed)
|
||||||
self.sync_enabled = False
|
self.sync_enabled = False
|
||||||
self.attempts += 1
|
self.attempts += 1
|
||||||
log.info("SyncPlay Speed to Sync rate: {0}".format(speed))
|
log.info("SyncPlay Speed to Sync rate: {0}".format(speed))
|
||||||
self.player_message(_("SpeedToSync (x{0})").format(speed))
|
self.player_message(_("SpeedToSync (x{0})").format(speed))
|
||||||
|
|
||||||
def callback():
|
def callback():
|
||||||
self.player.speed = 1
|
self.playerManager.set_speed(1)
|
||||||
self.sync_enabled = True
|
self.sync_enabled = True
|
||||||
|
|
||||||
set_timeout(settings.sync_speed_time, callback)
|
set_timeout(settings.sync_speed_time, callback)
|
||||||
@ -187,7 +185,7 @@ class SyncPlayManager:
|
|||||||
log.error("Syncplay ping reporting failed.", exc_info=True)
|
log.error("Syncplay ping reporting failed.", exc_info=True)
|
||||||
|
|
||||||
def enable_sync_play(self, start_time, from_server):
|
def enable_sync_play(self, start_time, from_server):
|
||||||
self.playback_rate = self.player.speed
|
self.playback_rate = self.playerManager.get_speed()
|
||||||
self.enabled_at = start_time
|
self.enabled_at = start_time
|
||||||
self.enable_speed_sync = True
|
self.enable_speed_sync = True
|
||||||
|
|
||||||
@ -200,7 +198,7 @@ class SyncPlayManager:
|
|||||||
self.ready = False
|
self.ready = False
|
||||||
self.notify_sync_ready = True
|
self.notify_sync_ready = True
|
||||||
|
|
||||||
self.client = self.playerManager._video.client
|
self.client = self.playerManager.get_current_client()
|
||||||
timesync = self.client.timesync
|
timesync = self.client.timesync
|
||||||
if self.timesync is not None and timesync is not self.timesync:
|
if self.timesync is not None and timesync is not self.timesync:
|
||||||
self.timesync.remove_subscriber(self.on_timesync_update)
|
self.timesync.remove_subscriber(self.on_timesync_update)
|
||||||
@ -215,7 +213,7 @@ class SyncPlayManager:
|
|||||||
self.player_message(_("SyncPlay enabled."))
|
self.player_message(_("SyncPlay enabled."))
|
||||||
|
|
||||||
def disable_sync_play(self, from_server):
|
def disable_sync_play(self, from_server):
|
||||||
self.player.speed = self.playback_rate
|
self.playerManager.set_speed(self.playback_rate)
|
||||||
|
|
||||||
self.enabled_at = None
|
self.enabled_at = None
|
||||||
self.ready = False
|
self.ready = False
|
||||||
@ -332,7 +330,7 @@ class SyncPlayManager:
|
|||||||
|
|
||||||
def prepare_session(self, group_id, session_data):
|
def prepare_session(self, group_id, session_data):
|
||||||
play_command = session_data.get("PlayCommand")
|
play_command = session_data.get("PlayCommand")
|
||||||
if not self.playerManager._video:
|
if not self.playerManager.has_video():
|
||||||
play_command = "PlayNow"
|
play_command = "PlayNow"
|
||||||
|
|
||||||
seq = session_data.get("StartIndex")
|
seq = session_data.get("StartIndex")
|
||||||
@ -349,12 +347,12 @@ class SyncPlayManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.playerManager._video
|
self.playerManager.has_video()
|
||||||
and self.playerManager._video.item_id == session_data["ItemIds"][0]
|
and self.playerManager.get_video().item_id == session_data["ItemIds"][0]
|
||||||
and play_command == "PlayNow"
|
and play_command == "PlayNow"
|
||||||
):
|
):
|
||||||
# We assume the video is already available.
|
# We assume the video is already available.
|
||||||
self.playerManager._video.parent = media
|
self.playerManager.get_video().parent = media
|
||||||
log.info("Syncplay Session Prepare: {0} {1}".format(group_id, session_data))
|
log.info("Syncplay Session Prepare: {0} {1}".format(group_id, session_data))
|
||||||
self.local_seek(
|
self.local_seek(
|
||||||
(session_data.get("PositionTicks", 0) or 0) / seconds_in_ticks
|
(session_data.get("PositionTicks", 0) or 0) / seconds_in_ticks
|
||||||
@ -378,12 +376,12 @@ class SyncPlayManager:
|
|||||||
self.join_group(group_id)
|
self.join_group(group_id)
|
||||||
self.playerManager.timeline_handle()
|
self.playerManager.timeline_handle()
|
||||||
elif play_command == "PlayLast":
|
elif play_command == "PlayLast":
|
||||||
self.playerManager._video.parent.insert_items(
|
self.playerManager.get_video().parent.insert_items(
|
||||||
session_data.get("ItemIds"), append=True
|
session_data.get("ItemIds"), append=True
|
||||||
)
|
)
|
||||||
self.playerManager.upd_player_hide()
|
self.playerManager.upd_player_hide()
|
||||||
elif play_command == "PlayNext":
|
elif play_command == "PlayNext":
|
||||||
self.playerManager._video.parent.insert_items(
|
self.playerManager.get_video().parent.insert_items(
|
||||||
session_data.get("ItemIds"), append=False
|
session_data.get("ItemIds"), append=False
|
||||||
)
|
)
|
||||||
self.playerManager.upd_player_hide()
|
self.playerManager.upd_player_hide()
|
||||||
@ -392,7 +390,7 @@ class SyncPlayManager:
|
|||||||
# Messages overwrite menu, so they are ignored.
|
# Messages overwrite menu, so they are ignored.
|
||||||
if not self.menu.is_menu_shown:
|
if not self.menu.is_menu_shown:
|
||||||
if settings.sync_osd_message:
|
if settings.sync_osd_message:
|
||||||
self.player.show_text(message)
|
self.playerManager.show_text(message)
|
||||||
else:
|
else:
|
||||||
log.info("SyncPlay Message: {0}".format(message))
|
log.info("SyncPlay Message: {0}".format(message))
|
||||||
else:
|
else:
|
||||||
@ -466,7 +464,7 @@ class SyncPlayManager:
|
|||||||
self.sync_timeout()
|
self.sync_timeout()
|
||||||
|
|
||||||
self.sync_enabled = False
|
self.sync_enabled = False
|
||||||
self.player.speed = 1
|
self.playerManager.set_speed(1)
|
||||||
|
|
||||||
def play_request(self):
|
def play_request(self):
|
||||||
self.client.jellyfin.play_sync_play()
|
self.client.jellyfin.play_sync_play()
|
||||||
@ -506,7 +504,7 @@ class SyncPlayManager:
|
|||||||
self.client.jellyfin.new_sync_play()
|
self.client.jellyfin.new_sync_play()
|
||||||
|
|
||||||
def menu_action(self):
|
def menu_action(self):
|
||||||
self.client = self.playerManager._video.client
|
self.client = self.playerManager.get_current_client()
|
||||||
|
|
||||||
selected = 0
|
selected = 0
|
||||||
offset = 1
|
offset = 1
|
||||||
@ -516,7 +514,9 @@ class SyncPlayManager:
|
|||||||
if not self.is_enabled():
|
if not self.is_enabled():
|
||||||
offset = 2
|
offset = 2
|
||||||
group_option_list.append((_("New Group"), self.menu_create_group, None))
|
group_option_list.append((_("New Group"), self.menu_create_group, None))
|
||||||
groups = self.client.jellyfin.get_sync_play(self.playerManager._video.item_id)
|
groups = self.client.jellyfin.get_sync_play(
|
||||||
|
self.playerManager.get_video().item_id
|
||||||
|
)
|
||||||
for i, group in enumerate(groups):
|
for i, group in enumerate(groups):
|
||||||
group_option_list.append(
|
group_option_list.append(
|
||||||
(group["PlayingItemName"], self.menu_join_group, group)
|
(group["PlayingItemName"], self.menu_join_group, group)
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import jellyfin_apiclient_python.exceptions
|
import jellyfin_apiclient_python.exceptions
|
||||||
|
|
||||||
from .conf import settings
|
from .conf import settings
|
||||||
from .player import playerManager
|
from .player import playerManager
|
||||||
from .utils import Timer, mpv_color_to_plex
|
from .utils import Timer
|
||||||
|
|
||||||
log = logging.getLogger("timeline")
|
log = logging.getLogger("timeline")
|
||||||
|
|
||||||
@ -27,19 +26,16 @@ class TimelineManager(threading.Thread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while not self.halt:
|
while not self.halt:
|
||||||
if (
|
if playerManager.is_active() and (
|
||||||
playerManager._player
|
not settings.idle_when_paused or not playerManager.is_paused()
|
||||||
and playerManager._video
|
|
||||||
and (not settings.idle_when_paused or not playerManager.is_paused())
|
|
||||||
):
|
):
|
||||||
self.SendTimeline()
|
self.send_timeline()
|
||||||
self.delay_idle()
|
self.delay_idle()
|
||||||
force_next = False
|
|
||||||
if self.idleTimer.elapsed() > settings.idle_cmd_delay and not self.is_idle:
|
if self.idleTimer.elapsed() > settings.idle_cmd_delay and not self.is_idle:
|
||||||
if (
|
if (
|
||||||
settings.idle_when_paused
|
settings.idle_when_paused
|
||||||
and settings.stop_idle
|
and settings.stop_idle
|
||||||
and playerManager._video
|
and playerManager.has_video()
|
||||||
):
|
):
|
||||||
playerManager.stop()
|
playerManager.stop()
|
||||||
if settings.idle_cmd:
|
if settings.idle_cmd:
|
||||||
@ -52,7 +48,8 @@ class TimelineManager(threading.Thread):
|
|||||||
self.idleTimer.restart()
|
self.idleTimer.restart()
|
||||||
self.is_idle = False
|
self.is_idle = False
|
||||||
|
|
||||||
def SendTimeline(self):
|
@staticmethod
|
||||||
|
def send_timeline():
|
||||||
try:
|
try:
|
||||||
# Send_timeline sometimes (once every couple hours) gets a 404 response from Jellyfin.
|
# Send_timeline sometimes (once every couple hours) gets a 404 response from Jellyfin.
|
||||||
# Without this try/except that would cause this entire thread to crash keeping it from self-healing.
|
# Without this try/except that would cause this entire thread to crash keeping it from self-healing.
|
||||||
|
@ -14,8 +14,8 @@ one_day = 86400
|
|||||||
|
|
||||||
|
|
||||||
class UpdateChecker:
|
class UpdateChecker:
|
||||||
def __init__(self, playerManager):
|
def __init__(self, player_manager):
|
||||||
self.playerManager = playerManager
|
self.playerManager = player_manager
|
||||||
self.has_notified = False
|
self.has_notified = False
|
||||||
self.new_version = None
|
self.new_version = None
|
||||||
self.last_check = None
|
self.last_check = None
|
||||||
@ -49,7 +49,7 @@ class UpdateChecker:
|
|||||||
if not self.has_notified and settings.notify_updates:
|
if not self.has_notified and settings.notify_updates:
|
||||||
self.has_notified = True
|
self.has_notified = True
|
||||||
log.info("Update Available: {0}".format(self.new_version))
|
log.info("Update Available: {0}".format(self.new_version))
|
||||||
self.playerManager._player.show_text(
|
self.playerManager.show_text(
|
||||||
_(
|
_(
|
||||||
"MPV Shim v{0} Update Available\nOpen menu (press c) for details."
|
"MPV Shim v{0} Update Available\nOpen menu (press c) for details."
|
||||||
).format(self.new_version),
|
).format(self.new_version),
|
||||||
@ -58,7 +58,7 @@ class UpdateChecker:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
self.playerManager._player.command("set", "fullscreen", "no")
|
self.playerManager.set_fullscreen(False)
|
||||||
try:
|
try:
|
||||||
webbrowser.open(release_url + "latest")
|
webbrowser.open(release_url + "latest")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -21,12 +21,12 @@ seq_num_lock = Lock()
|
|||||||
|
|
||||||
class Timer(object):
|
class Timer(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.restart()
|
self.started = datetime.now()
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
self.started = datetime.now()
|
self.started = datetime.now()
|
||||||
|
|
||||||
def elapsedMs(self):
|
def elapsed_ms(self):
|
||||||
return self.elapsed() * 1e3
|
return self.elapsed() * 1e3
|
||||||
|
|
||||||
def elapsed(self):
|
def elapsed(self):
|
||||||
@ -222,7 +222,7 @@ def get_resource(*path):
|
|||||||
# Detect if bundled via pyinstaller.
|
# Detect if bundled via pyinstaller.
|
||||||
# From: https://stackoverflow.com/questions/404744/
|
# From: https://stackoverflow.com/questions/404744/
|
||||||
if getattr(sys, "_MEIPASS", False):
|
if getattr(sys, "_MEIPASS", False):
|
||||||
application_path = os.path.join(sys._MEIPASS, "jellyfin_mpv_shim")
|
application_path = os.path.join(getattr(sys, "_MEIPASS"), "jellyfin_mpv_shim")
|
||||||
else:
|
else:
|
||||||
application_path = os.path.dirname(os.path.abspath(__file__))
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import logging
|
|||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
|
|
||||||
profile_name_translation = {
|
profile_name_translation = {
|
||||||
"Generic (FSRCNNX)": _("Generic (FSRCNNX)"),
|
"Generic (FSRCNNX)": _("Generic (FSRCNNX)"),
|
||||||
@ -34,17 +33,16 @@ class MPVSettingError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class VideoProfileManager:
|
class VideoProfileManager:
|
||||||
def __init__(self, menu, playerManager):
|
def __init__(self, menu, player_manager, player):
|
||||||
self.menu = menu
|
self.menu = menu
|
||||||
self.playerManager = playerManager
|
self.playerManager = player_manager
|
||||||
self.used_settings = set()
|
self.used_settings = set()
|
||||||
self.current_profile = None
|
self.current_profile = None
|
||||||
|
self.player = player
|
||||||
|
|
||||||
self.load_shader_pack()
|
|
||||||
|
|
||||||
def load_shader_pack(self):
|
|
||||||
shader_pack_builtin = get_resource("default_shader_pack")
|
shader_pack_builtin = get_resource("default_shader_pack")
|
||||||
|
|
||||||
|
# Load shader pack
|
||||||
self.shader_pack = shader_pack_builtin
|
self.shader_pack = shader_pack_builtin
|
||||||
if settings.shader_pack_custom:
|
if settings.shader_pack_custom:
|
||||||
self.shader_pack = conffile.get(APP_NAME, "shader_pack")
|
self.shader_pack = conffile.get(APP_NAME, "shader_pack")
|
||||||
@ -71,7 +69,7 @@ class VideoProfileManager:
|
|||||||
if key in self.defaults or key in self.revert_ignore:
|
if key in self.defaults or key in self.revert_ignore:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self.defaults[key] = getattr(self.playerManager._player, key)
|
self.defaults[key] = getattr(self.player, key)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning(
|
log.warning(
|
||||||
"Your MPV does not support setting {0} used in shader pack.".format(
|
"Your MPV does not support setting {0} used in shader pack.".format(
|
||||||
@ -123,25 +121,25 @@ class VideoProfileManager:
|
|||||||
if (key, value) in already_set:
|
if (key, value) in already_set:
|
||||||
continue
|
continue
|
||||||
log.debug("Set MPV setting {0} to {1}".format(key, value))
|
log.debug("Set MPV setting {0} to {1}".format(key, value))
|
||||||
setattr(self.playerManager._player, key, value)
|
setattr(self.player, key, value)
|
||||||
already_set.add((key, value))
|
already_set.add((key, value))
|
||||||
|
|
||||||
# Apply Shaders
|
# Apply Shaders
|
||||||
log.debug("Set shaders: {0}".format(shaders_to_apply))
|
log.debug("Set shaders: {0}".format(shaders_to_apply))
|
||||||
self.playerManager._player.glsl_shaders = shaders_to_apply
|
self.player.glsl_shaders = shaders_to_apply
|
||||||
self.current_profile = profile_name
|
self.current_profile = profile_name
|
||||||
return True
|
return True
|
||||||
except MPVSettingError as ex:
|
except MPVSettingError:
|
||||||
log.error("Could not apply shader profile.", exc_info=True)
|
log.error("Could not apply shader profile.", exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def unload_profile(self):
|
def unload_profile(self):
|
||||||
log.info("Unloading shader profile.")
|
log.info("Unloading shader profile.")
|
||||||
self.playerManager._player.glsl_shaders = []
|
self.player.glsl_shaders = []
|
||||||
for setting in self.used_settings:
|
for setting in self.used_settings:
|
||||||
value = self.defaults[setting]
|
value = self.defaults[setting]
|
||||||
try:
|
try:
|
||||||
setattr(self.playerManager._player, setting, value)
|
setattr(self.player, setting, value)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.warning(
|
log.warning(
|
||||||
"Default setting {0} value {1} is invalid.".format(setting, value)
|
"Default setting {0} value {1} is invalid.".format(setting, value)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import threading
|
import threading
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from datetime import date
|
|
||||||
from werkzeug.serving import make_server
|
from werkzeug.serving import make_server
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@ -45,6 +44,7 @@ def do_not_cache(response):
|
|||||||
class Server(threading.Thread):
|
class Server(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.srv = None
|
self.srv = None
|
||||||
|
self.ctx = None
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
@ -134,8 +134,9 @@ class Server(threading.Thread):
|
|||||||
self.srv.serve_forever()
|
self.srv.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
# This makes me rather uncomfortable, but there's no easy way around this other than importing display_mirror in helpers.
|
# This makes me rather uncomfortable, but there's no easy way around this other than
|
||||||
# Lambda needed because the 2.3 version of the JS api adds an argument even when not used.
|
# importing display_mirror in helpers. Lambda needed because the 2.3 version of the JS
|
||||||
|
# api adds an argument even when not used.
|
||||||
class WebviewClient(object):
|
class WebviewClient(object):
|
||||||
def __init__(self, cef=False):
|
def __init__(self, cef=False):
|
||||||
self.open_player_menu = lambda: None
|
self.open_player_menu = lambda: None
|
||||||
@ -146,7 +147,8 @@ class WebviewClient(object):
|
|||||||
def start(self):
|
def start(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def login_servers(self):
|
@staticmethod
|
||||||
|
def login_servers():
|
||||||
success = clientManager.try_connect()
|
success = clientManager.try_connect()
|
||||||
if success:
|
if success:
|
||||||
loaded.set()
|
loaded.set()
|
||||||
@ -204,7 +206,7 @@ class WebviewClient(object):
|
|||||||
try:
|
try:
|
||||||
from webview.platforms import cocoa
|
from webview.platforms import cocoa
|
||||||
|
|
||||||
def override_cocoa(self, webview, nav):
|
def override_cocoa(_self, webview, _nav):
|
||||||
# Add the webview to the window if it's not yet the contentView
|
# Add the webview to the window if it's not yet the contentView
|
||||||
i = cocoa.BrowserView.get_instance("webkit", webview)
|
i = cocoa.BrowserView.get_instance("webkit", webview)
|
||||||
|
|
||||||
@ -221,7 +223,8 @@ class WebviewClient(object):
|
|||||||
try:
|
try:
|
||||||
from webview.platforms import gtk
|
from webview.platforms import gtk
|
||||||
|
|
||||||
def override_gtk(self, webview, status):
|
# noinspection PyUnresolvedReferences
|
||||||
|
def override_gtk(_self, webview, _status):
|
||||||
if not webview.props.opacity:
|
if not webview.props.opacity:
|
||||||
gtk.glib.idle_add(webview.set_opacity, 1.0)
|
gtk.glib.idle_add(webview.set_opacity, 1.0)
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||||
import win32gui
|
import win32gui
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger("win_utils")
|
log = logging.getLogger("win_utils")
|
||||||
|
|
||||||
|
|
||||||
def windowEnumerationHandler(hwnd, top_windows):
|
def window_enumeration_handler(hwnd, top_windows):
|
||||||
top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
|
top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ def raise_mpv():
|
|||||||
try:
|
try:
|
||||||
top_windows = []
|
top_windows = []
|
||||||
fg_win = win32gui.GetForegroundWindow()
|
fg_win = win32gui.GetForegroundWindow()
|
||||||
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
|
win32gui.EnumWindows(window_enumeration_handler, top_windows)
|
||||||
for i in top_windows:
|
for i in top_windows:
|
||||||
if " - mpv" in i[1].lower():
|
if " - mpv" in i[1].lower():
|
||||||
if i[0] != fg_win:
|
if i[0] != fg_win:
|
||||||
@ -30,7 +31,7 @@ def raise_mpv():
|
|||||||
def mirror_act(state, name="Jellyfin MPV Shim Mirror"):
|
def mirror_act(state, name="Jellyfin MPV Shim Mirror"):
|
||||||
try:
|
try:
|
||||||
top_windows = []
|
top_windows = []
|
||||||
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
|
win32gui.EnumWindows(window_enumeration_handler, top_windows)
|
||||||
for i in top_windows:
|
for i in top_windows:
|
||||||
if name in i[1]:
|
if name in i[1]:
|
||||||
print(i)
|
print(i)
|
||||||
|
@ -9,7 +9,7 @@ if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
|
|||||||
# Detect if bundled via pyinstaller.
|
# Detect if bundled via pyinstaller.
|
||||||
# From: https://stackoverflow.com/questions/404744/
|
# From: https://stackoverflow.com/questions/404744/
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
application_path = sys._MEIPASS
|
application_path = getattr(sys, "_MEIPASS")
|
||||||
else:
|
else:
|
||||||
application_path = os.path.dirname(os.path.abspath(__file__))
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
||||||
|
@ -9,7 +9,7 @@ if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
|
|||||||
# Detect if bundled via pyinstaller.
|
# Detect if bundled via pyinstaller.
|
||||||
# From: https://stackoverflow.com/questions/404744/
|
# From: https://stackoverflow.com/questions/404744/
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
application_path = sys._MEIPASS
|
application_path = getattr(sys, "_MEIPASS")
|
||||||
else:
|
else:
|
||||||
application_path = os.path.dirname(os.path.abspath(__file__))
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
||||||
|
2
run.py
2
run.py
@ -9,7 +9,7 @@ if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
|
|||||||
# Detect if bundled via pyinstaller.
|
# Detect if bundled via pyinstaller.
|
||||||
# From: https://stackoverflow.com/questions/404744/
|
# From: https://stackoverflow.com/questions/404744/
|
||||||
if getattr(sys, "frozen", False):
|
if getattr(sys, "frozen", False):
|
||||||
application_path = sys._MEIPASS
|
application_path = getattr(sys, "_MEIPASS")
|
||||||
else:
|
else:
|
||||||
application_path = os.path.dirname(os.path.abspath(__file__))
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]
|
||||||
|
2
setup.py
2
setup.py
@ -34,7 +34,7 @@ setup(
|
|||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
"gui": ["pystray"],
|
"gui": ["pystray", "PIL"],
|
||||||
"mirror": ["Jinja2", "pywebview>=3.3.1"],
|
"mirror": ["Jinja2", "pywebview>=3.3.1"],
|
||||||
"desktop": ["Flask", "pywebview>=3.3.1", "Werkzeug"],
|
"desktop": ["Flask", "pywebview>=3.3.1", "Werkzeug"],
|
||||||
"discord": ["pypresence"],
|
"discord": ["pypresence"],
|
||||||
|
Loading…
Reference in New Issue
Block a user