Linter warnings.

This commit is contained in:
Ian Walton 2020-08-22 15:00:24 -04:00
parent 1e104a8c86
commit 21ca9d1087
26 changed files with 290 additions and 192 deletions

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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):

View File

@ -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"))

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -1,5 +1,4 @@
import gettext import gettext
import builtins
import locale import locale
from .conf import settings from .conf import settings

View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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()

View File

@ -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():

View File

@ -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)

View File

@ -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.

View File

@ -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:

View File

@ -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__))

View 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)

View File

@ -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)

View File

@ -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)

View File

@ -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"]

View File

@ -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
View File

@ -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"]

View File

@ -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"],