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
from .player import playerManager
@ -18,7 +17,7 @@ class ActionThread(threading.Thread):
def run(self):
force_next = False
while not self.halt:
if (playerManager._player and playerManager._video) or force_next:
if playerManager.is_active() or force_next:
playerManager.update()
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):
messages.clear()
media = player._video
media = player.get_video()
client = media.client
show_text = player._player.show_text
show_text = player.show_text
c_aid, c_sid = None, None
c_pid = player._video.media_source.get("Id")
c_pid = media.media_source.get("Id")
success_ct = 0
partial_ct = 0

View File

@ -1,4 +1,3 @@
import time
from .clients import clientManager
@ -7,7 +6,8 @@ class UserInterface(object):
self.open_player_menu = lambda: None
self.stop = lambda: None
def login_servers(self):
@staticmethod
def login_servers():
clientManager.cli_connect()
def start(self):
@ -17,4 +17,4 @@ class UserInterface(object):
pass
userInterface = UserInterface()
user_interface = UserInterface()

View File

@ -57,7 +57,8 @@ class ClientManager(object):
else:
log.warning(_("Adding server failed."))
def client_factory(self):
@staticmethod
def client_factory():
client = JellyfinClient(allow_multiple_clients=True)
client.config.data["app.default"] = True
client.config.app(
@ -208,7 +209,7 @@ class ClientManager(object):
if uuid is None and server is not None:
uuid = server["uuid"]
if not uuid in self.clients:
if uuid not in self.clients:
return
if server is not None:

View File

@ -4,7 +4,7 @@ import sys
import getpass
# If no platform is matched, use the current directory.
_confdir = lambda app: ""
_confdir = None
username = getpass.getuser()
@ -47,8 +47,10 @@ for i, arg in enumerate(sys.argv):
def confdir(app):
if custom_config is not None:
return custom_config
else:
elif _confdir is not None:
return _confdir(app)
else:
return ""
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)
import webview # Python3-webview in Debian, pywebview in pypi
from ..clients import clientManager
from ..utils import get_text
from ..i18n import _
from . import helpers
# This makes me rather uncomfortable, but there's no easy way around this other than importing display_mirror in helpers.
# Lambda needed because the 2.3 version of the JS api adds an argument even when not used.
# This makes me rather uncomfortable, but there's no easy way around this other than
# 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()
@ -35,6 +35,7 @@ class DisplayMirror(object):
def get_webview(self):
return self.webview
# noinspection PyUnresolvedReferences
def run(self):
# Webview needs to be run in the MainThread.
@ -56,15 +57,19 @@ class DisplayMirror(object):
self.webview = window
# 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()
webview.start()
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"])
html = get_html(server_address=client.config.data["auth.server"], item=item)
self.display_window.load_html(html)
@ -110,9 +115,7 @@ def get_html(server_address=None, item=None):
), # FIME: Mention the player_name here
}
jinja_vars.update(
{"jellyfin_css": get_text("display_mirror", "jellyfin.css"),}
)
jinja_vars.update({"jellyfin_css": get_text("display_mirror", "jellyfin.css")})
try:
tpl = jinja2.Template(get_text("display_mirror", "index.html"))

View File

@ -5,9 +5,11 @@ import math
from ..clients import clientManager
# 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?
# 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
# noinspection PyPep8Naming,PyPep8Naming
def getUrl(serverAddress, name):
if not name:
@ -27,6 +30,7 @@ def getUrl(serverAddress, name):
return url
# noinspection PyPep8Naming,PyPep8Naming
def getBackdropUrl(item, serverAddress):
if item.get("BackdropImageTags"):
return getUrl(
@ -48,6 +52,7 @@ def getBackdropUrl(item, serverAddress):
return None
# noinspection PyPep8Naming,PyPep8Naming
def getLogoUrl(item, serverAddress):
if item.get("ImageTags", {}).get("Logo", None):
return getUrl(
@ -66,6 +71,7 @@ def getLogoUrl(item, serverAddress):
return None
# noinspection PyPep8Naming,PyPep8Naming
def getPrimaryImageUrl(item, serverAddress):
if item.get("AlbumPrimaryImageTag"):
return getUrl(
@ -92,6 +98,7 @@ def getPrimaryImageUrl(item, serverAddress):
return None
# noinspection PyPep8Naming
def getDisplayName(item):
name = item.get("EpisodeTitle", item.get("Name"))
@ -114,6 +121,7 @@ def getDisplayName(item):
return name
# noinspection PyPep8Naming
def getRatingHtml(item):
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")
# noinspection PyPep8Naming,PyPep8Naming,PyPep8Naming
def getMiscInfoHtml(item):
# FIXME: Flake8 is complaining this function is too complex.
# I agree, this needs to be cleaned up, a lot.
@ -172,7 +181,7 @@ def getMiscInfoHtml(item):
if item.get("StartDate"):
date = __convert_jf_str_datetime(item["StartDate"])
text = date.strftime("%x")
miscInfo.push(text)
miscInfo.append(text)
if item["Type"] != "Recording":
pass
@ -201,7 +210,7 @@ def getMiscInfoHtml(item):
if item["Type"] == "Audio":
# FIXME
raise Exception("Haven't translated this to Python yet")
miscInfo.append(datetime.getDisplayRunningTime(item["RunTimeTicks"]))
# miscInfo.append(datetime.getDisplayRunningTime(item["RunTimeTicks"]))
else:
# 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
@ -222,9 +231,11 @@ def getMiscInfoHtml(item):
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.
# This really long argument name is here to catch that and hopefully not eat other intentional arguments.
def getRandomBackdropUrl(positional_arg_that_is_never_used=None, **params):
# For some reason the webview 2.3 js api will send a positional argument of None when there are no
# arguments being passed in. This really long argument name is here to catch that and hopefully not
# 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
params["SortBy"] = "Random"
params["Limit"] = 1

View File

@ -1,7 +1,6 @@
import logging
import os
from .utils import plex_color_to_mpv
from .conf import settings
from .media import Media
from .player import playerManager
@ -41,9 +40,9 @@ class EventHandler(object):
log.debug("Unhandled Event {0}: {1}".format(event_name, arguments))
@bind("Play")
def play_media(self, client, event_name, arguments):
def play_media(self, client, _event_name, arguments):
play_command = arguments.get("PlayCommand")
if not playerManager._video:
if not playerManager.has_video():
play_command = "PlayNow"
if play_command == "PlayNow":
@ -70,22 +69,22 @@ class EventHandler(object):
if settings.pre_media_cmd:
os.system(settings.pre_media_cmd)
playerManager.play(video, offset)
timelineManager.SendTimeline()
timelineManager.send_timeline()
if arguments.get("SyncPlayGroup") is not None:
playerManager.syncplay.join_group(arguments["SyncPlayGroup"])
elif play_command == "PlayLast":
playerManager._video.parent.insert_items(
playerManager.get_video().parent.insert_items(
arguments.get("ItemIds"), append=True
)
playerManager.upd_player_hide()
elif play_command == "PlayNext":
playerManager._video.parent.insert_items(
playerManager.get_video().parent.insert_items(
arguments.get("ItemIds"), append=False
)
playerManager.upd_player_hide()
@bind("GeneralCommand")
def general_command(self, client, event_name, arguments):
def general_command(self, client, _event_name, arguments):
command = arguments.get("Name")
if command == "SetVolume":
# 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.
timelineManager.delay_idle()
if self.mirror:
self.mirror.DisplayContent(client, arguments)
self.mirror.display_content(client, arguments)
elif command in (
"Back",
"Select",
@ -121,7 +120,7 @@ class EventHandler(object):
playerManager.toggle_fullscreen()
@bind("Playstate")
def play_state(self, client, event_name, arguments):
def play_state(self, _client, _event_name, arguments):
command = arguments.get("Command")
if command == "PlayPause":
playerManager.toggle_pause()
@ -141,17 +140,17 @@ class EventHandler(object):
)
@bind("PlayPause")
def pausePlay(self, client, event_name, arguments):
def pause_play(self, _client, _event_name, _arguments):
playerManager.toggle_pause()
timelineManager.SendTimeline()
timelineManager.send_timeline()
@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.process_group_update(arguments)
@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.process_command(arguments)

View File

@ -6,7 +6,6 @@ import threading
import sys
import logging
import queue
import os.path
from .constants import USER_APP_NAME, APP_NAME
from .conffile import confdir
@ -19,6 +18,8 @@ log = logging.getLogger("gui_mgr")
# From https://stackoverflow.com/questions/6631299/
# This is for opening the config directory.
def _show_file_darwin(path):
subprocess.Popen(["open", path])
@ -82,6 +83,9 @@ root_logger.addHandler(guiHandler)
class LoggerWindow(threading.Thread):
def __init__(self):
self.dead = False
self.queue = None
self.r_queue = None
self.process = None
threading.Thread.__init__(self)
def run(self):
@ -104,7 +108,7 @@ class LoggerWindow(threading.Thread):
def handle(self, action, params=None):
self.queue.put((action, params))
def stop(self, is_source=False):
def stop(self):
self.r_queue.put(("die", None))
def _die(self):
@ -118,6 +122,9 @@ class LoggerWindowProcess(Process):
def __init__(self, queue, r_queue):
self.queue = queue
self.r_queue = r_queue
self.tk = None
self.root = None
self.text = None
Process.__init__(self)
def update(self):
@ -141,7 +148,6 @@ class LoggerWindowProcess(Process):
def run(self):
import tkinter as tk
from tkinter import ttk, messagebox
self.tk = tk
root = tk.Tk()
@ -164,6 +170,9 @@ class PreferencesWindow(threading.Thread):
def __init__(self):
self.dead = False
self.dead_trigger = threading.Event()
self.queue = None
self.r_queue = None
self.process = None
threading.Thread.__init__(self)
def run(self):
@ -194,7 +203,7 @@ class PreferencesWindow(threading.Thread):
def handle(self, action, params=None):
self.queue.put((action, params))
def stop(self, is_source=False):
def stop(self):
self.r_queue.put(("die", None))
def block_until_close(self):
@ -211,6 +220,18 @@ class PreferencesWindowProcess(Process):
def __init__(self, queue, r_queue):
self.queue = 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)
def update(self):
@ -266,7 +287,7 @@ class PreferencesWindowProcess(Process):
self.serverList = tk.StringVar(value=[])
self.current_uuid = None
def serverSelect(_x):
def server_select(_x):
idxs = serverlist.curselection()
if len(idxs) == 1:
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.grid(column=2, row=4, pady=10, sticky=(tk.E, tk.S))
serverlist.bind("<<ListboxSelect>>", serverSelect)
serverlist.bind("<<ListboxSelect>>", server_select)
self.update()
root.mainloop()
self.r_queue.put(("die", None))
@ -348,6 +369,8 @@ class UserInterface(threading.Thread):
self.preferences_window = None
self.stop_callback = None
self.gui_ready = None
self.r_queue = None
self.process = None
threading.Thread.__init__(self)
@ -398,7 +421,8 @@ class UserInterface(threading.Thread):
if self.gui_ready:
self.gui_ready.set()
def open_config_brs(self):
@staticmethod
def open_config_brs():
if open_config:
open_config()
else:
@ -408,6 +432,7 @@ class UserInterface(threading.Thread):
class STrayProcess(Process):
def __init__(self, r_queue):
self.r_queue = r_queue
self.icon_stop = None
Process.__init__(self)
def run(self):
@ -447,4 +472,4 @@ class STrayProcess(Process):
self.r_queue.put(("die", None))
userInterface = UserInterface()
user_interface = UserInterface()

View File

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

View File

@ -1,5 +1,4 @@
import logging
import uuid
import urllib.parse
import os.path
import re
@ -55,7 +54,7 @@ class Video(object):
self.audio_uid[index] = stream["Index"]
self.audio_seq[stream["Index"]] = index
if stream.get("IsExternal") == False:
if not stream.get("IsExternal"):
index += 1
index = 1
@ -74,7 +73,7 @@ class Video(object):
elif sub.get("DeliveryMethod") == "Encode":
self.subtitle_enc.add(sub["Index"])
if sub.get("IsExternal") == False:
if not sub.get("IsExternal"):
index += 1
user_aid = self.media_source.get("DefaultAudioStreamIndex")
@ -140,7 +139,7 @@ class Video(object):
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:
# - The media source supports direct paths.
# - Direct paths are enabled in the config.
@ -216,11 +215,9 @@ class Video(object):
log.warning("Preferred media source is unplayable.")
return selected
def get_playback_url(
self, offset=0, video_bitrate=None, force_transcode=False, force_bitrate=False
):
def get_playback_url(self, video_bitrate=None, force_transcode=False):
"""
Returns the URL to use for the trancoded file.
Returns the URL to use for the transcoded file.
"""
self.terminate_transcode()
@ -239,7 +236,7 @@ class Video(object):
self.media_source = self.get_best_media_source(self.srcid)
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 url is None and len(self.playback_info["MediaSources"]) > 1:
@ -248,7 +245,7 @@ class Video(object):
if media_source["Id"] != self.srcid:
self.media_source = media_source
self.map_streams()
url = self._get_url_from_source(self.media_source)
url = self._get_url_from_source()
if url is not None:
break

View File

@ -52,8 +52,8 @@ if "und" in lang_filter:
class OSDMenu(object):
def __init__(self, playerManager):
self.playerManager = playerManager
def __init__(self, player_manager, player):
self.playerManager = player_manager
self.is_menu_shown = False
self.menu_title = ""
@ -62,21 +62,23 @@ class OSDMenu(object):
self.menu_selection = 0
self.menu_tmp = None
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_manager = None
if settings.shader_pack_enable:
try:
self.profile_manager = VideoProfileManager(self, playerManager)
self.profile_manager = VideoProfileManager(self, player_manager, player)
self.profile_menu = self.profile_manager.menu_action
except Exception:
log.error("Could not load profile manager.", exc_info=True)
self.svp_menu = None
try:
self.svp_menu = SVPManager(self, playerManager)
self.svp_menu = SVPManager(self, player_manager)
except Exception:
log.error("Could not load SVP integration.", exc_info=True)
@ -101,7 +103,7 @@ class OSDMenu(object):
fmt = "\n **{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):
if idx < 0 or idx > len(self.menu_list):
@ -115,20 +117,16 @@ class OSDMenu(object):
def show_menu(self):
self.is_menu_shown = True
player = self.playerManager._player
player.osd_back_color = "#CC333333"
player.osd_font_size = 40
self.playerManager.set_osd_settings("#CC333333", 40)
if hasattr(player, "osc"):
player.osc = False
player.command("script-message", "shim-menu-enable", "True")
self.playerManager.enable_osc(False)
self.playerManager.capture_mouse(True)
self.menu_title = _("Main Menu")
self.menu_selection = 0
self.mouse_back = False
if self.playerManager._video and not player.playback_abort:
if self.playerManager.is_playing():
self.menu_list = [
(_("Change Audio"), self.change_audio_menu),
(_("Change Subtitles"), self.change_subtitle_menu),
@ -149,7 +147,7 @@ class OSDMenu(object):
self.menu_list.append(
(_("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(
(
_("Auto Set Audio/Subtitles (Entire Series)"),
@ -182,40 +180,31 @@ class OSDMenu(object):
# Wait until the menu renders to pause.
time.sleep(0.2)
if player.playback_abort:
player.force_window = True
player.keep_open = True
player.play("")
if settings.fullscreen:
player.fs = True
if self.playerManager.playback_is_aborted():
self.playerManager.force_window(True)
else:
if not self.playerManager.syncplay.is_enabled():
player.pause = True
self.playerManager.set_paused(True)
def hide_menu(self):
player = self.playerManager._player
if self.is_menu_shown:
player.osd_back_color = self.original_osd_color
player.osd_font_size = self.original_osd_size
player.show_text("", 0, 0)
player.force_window = False
player.keep_open = False
self.playerManager.set_osd_settings(
self.original_osd_color, self.original_osd_size
)
self.playerManager.show_text("", 0, 0)
if hasattr(player, "osc"):
player.osc = settings.enable_osc
self.playerManager.enable_osc(True)
self.playerManager.capture_mouse(False)
self.playerManager.force_window(False)
player.command("script-message", "shim-menu-enable", "False")
if player.playback_abort:
player.play("")
else:
if not self.playerManager.playback_is_aborted():
if not self.playerManager.syncplay.is_enabled():
player.pause = False
self.playerManager.set_paused(False)
self.is_menu_shown = False
def screenshot(self):
self.playerManager._player.show_text("", 0, 0)
self.playerManager.show_text("", 0, 0)
time.sleep(0.5)
self.playerManager.screenshot()
self.hide_menu()
@ -266,10 +255,10 @@ class OSDMenu(object):
def change_audio_menu(self):
self.put_menu(_("Select Audio Track"))
selected_aid = self.playerManager._video.aid
selected_aid = self.playerManager.get_video().aid
audio_streams = [
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"
]
for i, audio_track in enumerate(audio_streams):
@ -303,10 +292,10 @@ class OSDMenu(object):
def change_subtitle_menu(self):
self.put_menu(_("Select Subtitle Track"))
selected_sid = self.playerManager._video.sid
selected_sid = self.playerManager.get_video().sid
subtitle_streams = [
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"
]
self.menu_list.append([_("None"), self.change_subtitle_menu_handle, -1])
@ -335,11 +324,11 @@ class OSDMenu(object):
def change_transcode_quality_handle(self):
bitrate = self.menu_list[self.menu_selection][2]
if bitrate == "none":
self.playerManager._video.set_trs_override(None, False)
self.playerManager.get_video().set_trs_override(None, False)
elif bitrate == "max":
self.playerManager._video.set_trs_override(None, True)
self.playerManager.get_video().set_trs_override(None, True)
else:
self.playerManager._video.set_trs_override(bitrate, True)
self.playerManager.get_video().set_trs_override(bitrate, True)
self.menu_action("back")
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_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):
if cur_bitrate == option[2]:
self.menu_selection = i
@ -433,7 +422,8 @@ class OSDMenu(object):
if settings.remote_kbps == item[1]:
self.menu_selection = i
def get_subtitle_color(self, color):
@staticmethod
def get_subtitle_color(color):
if color in HEX_TO_COLOR:
return HEX_TO_COLOR[color]
else:
@ -450,7 +440,7 @@ class OSDMenu(object):
self.menu_action("back")
self.video_preferences_menu()
if self.playerManager._video.is_transcode:
if self.playerManager.get_video().is_transcode:
if setting_name == "subtitle_size":
self.playerManager.put_task(self.playerManager.update_subtitle_visuals)
else:

View File

@ -2,7 +2,6 @@
import logging
import sys
import time
import multiprocessing
from threading import Event
@ -33,23 +32,24 @@ def main(desktop=False, cef=False):
if sys.platform.startswith("darwin"):
multiprocessing.set_start_method("forkserver")
userInterface = None
user_interface = None
mirror = None
use_gui = False
gui_ready = None
use_webview = desktop or settings.enable_desktop
get_webview = lambda: None
if use_webview:
from .webclient_view import WebviewClient
userInterface = WebviewClient(cef=cef)
get_webview = userInterface.get_webview
user_interface = WebviewClient(cef=cef)
get_webview = user_interface.get_webview
elif settings.enable_gui:
try:
from .gui_mgr import userInterface
from .gui_mgr import user_interface
use_gui = True
gui_ready = Event()
userInterface.gui_ready = gui_ready
user_interface.gui_ready = gui_ready
except Exception:
log.warning(
"Cannot load GUI. Falling back to command line interface.",
@ -62,10 +62,11 @@ def main(desktop=False, cef=False):
get_webview = mirror.get_webview
except ImportError:
mirror = None
log.warning("Cannot load display mirror.", exc_info=True)
if not userInterface:
from .cli_mgr import userInterface
if not user_interface:
from .cli_mgr import user_interface
from .player import playerManager
from .action_thread import actionThread
@ -78,23 +79,23 @@ def main(desktop=False, cef=False):
actionThread.start()
playerManager.action_trigger = actionThread.trigger
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
userInterface.start()
userInterface.login_servers()
user_interface.start()
user_interface.login_servers()
try:
if use_webview:
userInterface.run()
user_interface.run()
elif mirror:
userInterface.stop_callback = mirror.stop
user_interface.stop_callback = mirror.stop
# If the webview runs before the systray icon, it fails.
if use_gui:
gui_ready.wait()
mirror.run()
else:
halt = Event()
userInterface.stop_callback = halt.set
user_interface.stop_callback = halt.set
try:
halt.wait()
except KeyboardInterrupt:
@ -105,7 +106,7 @@ def main(desktop=False, cef=False):
timelineManager.stop()
actionThread.stop()
clientManager.stop()
userInterface.stop()
user_interface.stop()
def main_desktop(cef=False):

View File

@ -1,7 +1,6 @@
import logging
import os
import sys
import urllib.parse
import time
from threading import RLock, Lock, Event
@ -34,6 +33,7 @@ python_mpv_available = True
is_using_ext_mpv = False
if not settings.mpv_ext:
try:
# noinspection PyPackageRequirements
import mpv
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.
# noinspection PyUnresolvedReferences
class PlayerManager(object):
"""
The underlying player is thread safe, however, locks are used in this
@ -161,7 +162,7 @@ class PlayerManager(object):
loglevel=settings.mpv_log_level,
**mpv_options
)
self.menu = OSDMenu(self)
self.menu = OSDMenu(self, self._player)
self.syncplay = SyncPlayManager(self)
if hasattr(self._player, "osc"):
@ -307,13 +308,13 @@ class PlayerManager(object):
# Fires at the end.
@self._player.property_observer("playback-abort")
def handle_end_idle(name, value):
def handle_end_idle(_name, value):
if self._video and value:
has_lock = self._finished_lock.acquire(False)
self.put_task(self.finished_callback, has_lock)
@self._player.property_observer("seeking")
def handle_seeking(name, value):
def handle_seeking(_name, value):
if self.syncplay.is_enabled():
play_time = self._player.playback_time
if (
@ -404,6 +405,7 @@ class PlayerManager(object):
if self.get_webview() is not None and (
settings.display_mirroring or settings.desktop_fullscreen
):
# noinspection PyUnresolvedReferences
self.get_webview().hide()
self._player.play(self.url)
@ -457,7 +459,8 @@ class PlayerManager(object):
1,
)
def exec_stop_cmd(self):
@staticmethod
def exec_stop_cmd():
if settings.stop_cmd:
os.system(settings.stop_cmd)
@ -678,6 +681,11 @@ class PlayerManager(object):
self._player.fs = 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")
def set_mute(self, mute):
self._player.mute = mute
@ -825,5 +833,68 @@ class PlayerManager(object):
seek_right = custom_prefs.get("skipForwardLength") or 30000
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()

View File

@ -14,7 +14,7 @@ def list_request(path):
try:
response = urllib.request.urlopen(settings.svp_url + "?" + path)
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)
return None
@ -99,7 +99,7 @@ def set_disabled(disabled):
class SVPManager:
def __init__(self, menu, playerManager):
def __init__(self, menu, player_manager):
self.menu = menu
if settings.svp_enable:
@ -113,14 +113,15 @@ class SVPManager:
socket = "/tmp/mpvsocket"
# 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():
log.error(
"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:
return False
if not is_svp_alive():

View File

@ -4,7 +4,6 @@ import os
from datetime import datetime, timedelta
from .media import Media
from .i18n import _
from time import sleep
# 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:
def __init__(self, manager):
self.playerManager = manager
self.player = manager._player
self.menu = manager.menu
self.sync_enabled = False
@ -106,7 +104,7 @@ class SyncPlayManager:
self.last_sync_time = current_time
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 = (
self.last_command["PositionTicks"]
+ ((current_time - play_at_time) + self.time_offset).total_seconds()
@ -132,14 +130,14 @@ class SyncPlayManager:
# Speed To Sync Method
speed = 1 + diff_ms / settings.sync_speed_time
self.player.speed = speed
self.playerManager.set_speed(speed)
self.sync_enabled = False
self.attempts += 1
log.info("SyncPlay Speed to Sync rate: {0}".format(speed))
self.player_message(_("SpeedToSync (x{0})").format(speed))
def callback():
self.player.speed = 1
self.playerManager.set_speed(1)
self.sync_enabled = True
set_timeout(settings.sync_speed_time, callback)
@ -187,7 +185,7 @@ class SyncPlayManager:
log.error("Syncplay ping reporting failed.", exc_info=True)
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.enable_speed_sync = True
@ -200,7 +198,7 @@ class SyncPlayManager:
self.ready = False
self.notify_sync_ready = True
self.client = self.playerManager._video.client
self.client = self.playerManager.get_current_client()
timesync = self.client.timesync
if self.timesync is not None and timesync is not self.timesync:
self.timesync.remove_subscriber(self.on_timesync_update)
@ -215,7 +213,7 @@ class SyncPlayManager:
self.player_message(_("SyncPlay enabled."))
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.ready = False
@ -332,7 +330,7 @@ class SyncPlayManager:
def prepare_session(self, group_id, session_data):
play_command = session_data.get("PlayCommand")
if not self.playerManager._video:
if not self.playerManager.has_video():
play_command = "PlayNow"
seq = session_data.get("StartIndex")
@ -349,12 +347,12 @@ class SyncPlayManager:
)
if (
self.playerManager._video
and self.playerManager._video.item_id == session_data["ItemIds"][0]
self.playerManager.has_video()
and self.playerManager.get_video().item_id == session_data["ItemIds"][0]
and play_command == "PlayNow"
):
# 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))
self.local_seek(
(session_data.get("PositionTicks", 0) or 0) / seconds_in_ticks
@ -378,12 +376,12 @@ class SyncPlayManager:
self.join_group(group_id)
self.playerManager.timeline_handle()
elif play_command == "PlayLast":
self.playerManager._video.parent.insert_items(
self.playerManager.get_video().parent.insert_items(
session_data.get("ItemIds"), append=True
)
self.playerManager.upd_player_hide()
elif play_command == "PlayNext":
self.playerManager._video.parent.insert_items(
self.playerManager.get_video().parent.insert_items(
session_data.get("ItemIds"), append=False
)
self.playerManager.upd_player_hide()
@ -392,7 +390,7 @@ class SyncPlayManager:
# Messages overwrite menu, so they are ignored.
if not self.menu.is_menu_shown:
if settings.sync_osd_message:
self.player.show_text(message)
self.playerManager.show_text(message)
else:
log.info("SyncPlay Message: {0}".format(message))
else:
@ -466,7 +464,7 @@ class SyncPlayManager:
self.sync_timeout()
self.sync_enabled = False
self.player.speed = 1
self.playerManager.set_speed(1)
def play_request(self):
self.client.jellyfin.play_sync_play()
@ -506,7 +504,7 @@ class SyncPlayManager:
self.client.jellyfin.new_sync_play()
def menu_action(self):
self.client = self.playerManager._video.client
self.client = self.playerManager.get_current_client()
selected = 0
offset = 1
@ -516,7 +514,9 @@ class SyncPlayManager:
if not self.is_enabled():
offset = 2
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):
group_option_list.append(
(group["PlayingItemName"], self.menu_join_group, group)

View File

@ -1,13 +1,12 @@
import logging
import threading
import time
import os
import jellyfin_apiclient_python.exceptions
from .conf import settings
from .player import playerManager
from .utils import Timer, mpv_color_to_plex
from .utils import Timer
log = logging.getLogger("timeline")
@ -27,19 +26,16 @@ class TimelineManager(threading.Thread):
def run(self):
while not self.halt:
if (
playerManager._player
and playerManager._video
and (not settings.idle_when_paused or not playerManager.is_paused())
if playerManager.is_active() and (
not settings.idle_when_paused or not playerManager.is_paused()
):
self.SendTimeline()
self.send_timeline()
self.delay_idle()
force_next = False
if self.idleTimer.elapsed() > settings.idle_cmd_delay and not self.is_idle:
if (
settings.idle_when_paused
and settings.stop_idle
and playerManager._video
and playerManager.has_video()
):
playerManager.stop()
if settings.idle_cmd:
@ -52,7 +48,8 @@ class TimelineManager(threading.Thread):
self.idleTimer.restart()
self.is_idle = False
def SendTimeline(self):
@staticmethod
def send_timeline():
try:
# 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.

View File

@ -14,8 +14,8 @@ one_day = 86400
class UpdateChecker:
def __init__(self, playerManager):
self.playerManager = playerManager
def __init__(self, player_manager):
self.playerManager = player_manager
self.has_notified = False
self.new_version = None
self.last_check = None
@ -49,7 +49,7 @@ class UpdateChecker:
if not self.has_notified and settings.notify_updates:
self.has_notified = True
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."
).format(self.new_version),
@ -58,7 +58,7 @@ class UpdateChecker:
)
def open(self):
self.playerManager._player.command("set", "fullscreen", "no")
self.playerManager.set_fullscreen(False)
try:
webbrowser.open(release_url + "latest")
except Exception:

View File

@ -21,12 +21,12 @@ seq_num_lock = Lock()
class Timer(object):
def __init__(self):
self.restart()
self.started = datetime.now()
def restart(self):
self.started = datetime.now()
def elapsedMs(self):
def elapsed_ms(self):
return self.elapsed() * 1e3
def elapsed(self):
@ -222,7 +222,7 @@ def get_resource(*path):
# Detect if bundled via pyinstaller.
# From: https://stackoverflow.com/questions/404744/
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:
application_path = os.path.dirname(os.path.abspath(__file__))

View File

@ -7,7 +7,6 @@ import logging
import os.path
import shutil
import json
import time
profile_name_translation = {
"Generic (FSRCNNX)": _("Generic (FSRCNNX)"),
@ -34,17 +33,16 @@ class MPVSettingError(Exception):
class VideoProfileManager:
def __init__(self, menu, playerManager):
def __init__(self, menu, player_manager, player):
self.menu = menu
self.playerManager = playerManager
self.playerManager = player_manager
self.used_settings = set()
self.current_profile = None
self.player = player
self.load_shader_pack()
def load_shader_pack(self):
shader_pack_builtin = get_resource("default_shader_pack")
# Load shader pack
self.shader_pack = shader_pack_builtin
if settings.shader_pack_custom:
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:
continue
try:
self.defaults[key] = getattr(self.playerManager._player, key)
self.defaults[key] = getattr(self.player, key)
except Exception:
log.warning(
"Your MPV does not support setting {0} used in shader pack.".format(
@ -123,25 +121,25 @@ class VideoProfileManager:
if (key, value) in already_set:
continue
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))
# Apply Shaders
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
return True
except MPVSettingError as ex:
except MPVSettingError:
log.error("Could not apply shader profile.", exc_info=True)
return False
def unload_profile(self):
log.info("Unloading shader profile.")
self.playerManager._player.glsl_shaders = []
self.player.glsl_shaders = []
for setting in self.used_settings:
value = self.defaults[setting]
try:
setattr(self.playerManager._player, setting, value)
setattr(self.player, setting, value)
except Exception:
log.warning(
"Default setting {0} value {1} is invalid.".format(setting, value)

View File

@ -1,6 +1,5 @@
import threading
import urllib.request
from datetime import date
from werkzeug.serving import make_server
from flask import Flask, request, jsonify
from time import sleep
@ -45,6 +44,7 @@ def do_not_cache(response):
class Server(threading.Thread):
def __init__(self):
self.srv = None
self.ctx = None
threading.Thread.__init__(self)
@ -134,8 +134,9 @@ class Server(threading.Thread):
self.srv.serve_forever()
# This makes me rather uncomfortable, but there's no easy way around this other than importing display_mirror in helpers.
# Lambda needed because the 2.3 version of the JS api adds an argument even when not used.
# This makes me rather uncomfortable, but there's no easy way around this other than
# 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):
def __init__(self, cef=False):
self.open_player_menu = lambda: None
@ -146,7 +147,8 @@ class WebviewClient(object):
def start(self):
pass
def login_servers(self):
@staticmethod
def login_servers():
success = clientManager.try_connect()
if success:
loaded.set()
@ -204,7 +206,7 @@ class WebviewClient(object):
try:
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
i = cocoa.BrowserView.get_instance("webkit", webview)
@ -221,7 +223,8 @@ class WebviewClient(object):
try:
from webview.platforms import gtk
def override_gtk(self, webview, status):
# noinspection PyUnresolvedReferences
def override_gtk(_self, webview, _status):
if not webview.props.opacity:
gtk.glib.idle_add(webview.set_opacity, 1.0)

View File

@ -1,10 +1,11 @@
# noinspection PyUnresolvedReferences,PyPackageRequirements
import win32gui
import logging
log = logging.getLogger("win_utils")
def windowEnumerationHandler(hwnd, top_windows):
def window_enumeration_handler(hwnd, top_windows):
top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
@ -15,7 +16,7 @@ def raise_mpv():
try:
top_windows = []
fg_win = win32gui.GetForegroundWindow()
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
win32gui.EnumWindows(window_enumeration_handler, top_windows)
for i in top_windows:
if " - mpv" in i[1].lower():
if i[0] != fg_win:
@ -30,7 +31,7 @@ def raise_mpv():
def mirror_act(state, name="Jellyfin MPV Shim Mirror"):
try:
top_windows = []
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
win32gui.EnumWindows(window_enumeration_handler, top_windows)
for i in top_windows:
if name in i[1]:
print(i)

View File

@ -9,7 +9,7 @@ if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
# Detect if bundled via pyinstaller.
# From: https://stackoverflow.com/questions/404744/
if getattr(sys, "frozen", False):
application_path = sys._MEIPASS
application_path = getattr(sys, "_MEIPASS")
else:
application_path = os.path.dirname(os.path.abspath(__file__))
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.
# From: https://stackoverflow.com/questions/404744/
if getattr(sys, "frozen", False):
application_path = sys._MEIPASS
application_path = getattr(sys, "_MEIPASS")
else:
application_path = os.path.dirname(os.path.abspath(__file__))
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.
# From: https://stackoverflow.com/questions/404744/
if getattr(sys, "frozen", False):
application_path = sys._MEIPASS
application_path = getattr(sys, "_MEIPASS")
else:
application_path = os.path.dirname(os.path.abspath(__file__))
os.environ["PATH"] = application_path + os.pathsep + os.environ["PATH"]

View File

@ -34,7 +34,7 @@ setup(
"Operating System :: OS Independent",
],
extras_require={
"gui": ["pystray"],
"gui": ["pystray", "PIL"],
"mirror": ["Jinja2", "pywebview>=3.3.1"],
"desktop": ["Flask", "pywebview>=3.3.1", "Werkzeug"],
"discord": ["pypresence"],