mirror of
https://github.com/jellyfin/jellyfin-mpv-shim.git
synced 2024-11-23 14:09:57 +00:00
573 lines
20 KiB
Python
573 lines
20 KiB
Python
from queue import LifoQueue
|
|
from .bulk_subtitle import process_series
|
|
from .conf import settings
|
|
from .utils import mpv_color_to_plex, get_sub_display_title
|
|
from .video_profile import VideoProfileManager
|
|
from .svp_integration import SVPManager
|
|
from .i18n import _
|
|
|
|
import time
|
|
import logging
|
|
|
|
log = logging.getLogger("menu")
|
|
|
|
TRANSCODE_LEVELS = (
|
|
("1080p 20 Mbps", 20000),
|
|
("1080p 12 Mbps", 12000),
|
|
("1080p 10 Mbps", 10000),
|
|
("720p 4 Mbps", 4000),
|
|
("720p 3 Mbps", 3000),
|
|
("720p 2.5 Mbps", 2500),
|
|
("540p 1.5 Mbps", 1500),
|
|
("540p 0.9 Mbps", 950),
|
|
("480p 0.4 Mbps", 400),
|
|
("320p 0.3 Mbps", 320),
|
|
)
|
|
|
|
COLOR_LIST = (
|
|
(_("White"), "#FFFFFFFF"),
|
|
(_("Yellow"), "#FFFFEE00"),
|
|
(_("Black"), "#FF000000"),
|
|
(_("Cyan"), "#FF00FFFF"),
|
|
(_("Blue"), "#FF0000FF"),
|
|
(_("Green"), "#FF00FF00"),
|
|
(_("Magenta"), "#FFEE00EE"),
|
|
(_("Red"), "#FFFF0000"),
|
|
(_("Gray"), "#FF808080"),
|
|
)
|
|
|
|
SIZE_LIST = (
|
|
(_("Tiny"), 50),
|
|
(_("Small"), 75),
|
|
(_("Normal"), 100),
|
|
(_("Large"), 125),
|
|
(_("Huge"), 200),
|
|
)
|
|
|
|
HEX_TO_COLOR = {v: c for c, v in COLOR_LIST}
|
|
|
|
lang_filter = set(settings.lang_filter.split(","))
|
|
if "und" in lang_filter:
|
|
lang_filter.add(None)
|
|
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
if TYPE_CHECKING:
|
|
from .player import playerManager as playerManager_type
|
|
|
|
|
|
class OSDMenu(object):
|
|
def __init__(self, player_manager: "playerManager_type", player):
|
|
self.playerManager = player_manager
|
|
|
|
self.is_menu_shown = False
|
|
self.menu_title = ""
|
|
self.menu_stack = LifoQueue()
|
|
self.menu_list = []
|
|
self.menu_selection = 0
|
|
self.menu_tmp = None
|
|
self.mouse_back = False
|
|
(
|
|
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, 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, player_manager)
|
|
except Exception:
|
|
log.error("Could not load SVP integration.", exc_info=True)
|
|
|
|
# The menu is a bit of a hack...
|
|
# It works using multiline OSD.
|
|
# We also have to force the window to open.
|
|
|
|
def refresh_menu(self):
|
|
if not self.is_menu_shown:
|
|
return
|
|
|
|
items = self.menu_list
|
|
selected_item = self.menu_selection
|
|
|
|
if self.mouse_back:
|
|
menu_text = "(<--) {0}".format(self.menu_title)
|
|
else:
|
|
menu_text = self.menu_title
|
|
for i, item in enumerate(items):
|
|
fmt = "\n {0}"
|
|
if i == selected_item and not self.mouse_back:
|
|
fmt = "\n **{0}**"
|
|
menu_text += fmt.format(item[0])
|
|
|
|
self.playerManager.show_text(menu_text, 2**30, 1)
|
|
|
|
def mouse_select(self, idx: int):
|
|
if idx < 0 or idx > len(self.menu_list):
|
|
return
|
|
if idx == 0:
|
|
self.mouse_back = True
|
|
else:
|
|
self.mouse_back = False
|
|
self.menu_selection = idx - 1
|
|
self.refresh_menu()
|
|
|
|
def show_menu(self):
|
|
self.is_menu_shown = True
|
|
self.playerManager.set_osd_settings("#CC333333", 40)
|
|
|
|
self.playerManager.enable_osc(False)
|
|
self.playerManager.triggered_menu(True)
|
|
|
|
self.menu_title = _("Main Menu")
|
|
self.menu_selection = 0
|
|
self.mouse_back = False
|
|
|
|
if self.playerManager.is_playing():
|
|
self.menu_list = [
|
|
(_("Change Audio"), self.change_audio_menu),
|
|
(_("Change Subtitles"), self.change_subtitle_menu),
|
|
(_("Change Video Quality"), self.change_transcode_quality),
|
|
(_("SyncPlay"), self.playerManager.syncplay.menu_action),
|
|
]
|
|
if self.playerManager.update_check.new_version is not None:
|
|
self.menu_list.insert(
|
|
0,
|
|
(
|
|
_("MPV Shim v{0} Release Info/Download").format(
|
|
self.playerManager.update_check.new_version
|
|
),
|
|
self.playerManager.update_check.open,
|
|
),
|
|
)
|
|
if self.profile_menu is not None:
|
|
self.menu_list.append(
|
|
(_("Change Video Playback Profile"), self.profile_menu)
|
|
)
|
|
if self.playerManager.get_video().parent.is_tv:
|
|
self.menu_list.append(
|
|
(
|
|
_("Auto Set Audio/Subtitles (Entire Series)"),
|
|
self.change_tracks_menu,
|
|
)
|
|
)
|
|
self.menu_list.append(
|
|
(_("Quit and Mark Unwatched"), self.unwatched_menu_handle)
|
|
)
|
|
if settings.screenshot_menu:
|
|
self.menu_list.append((_("Screenshot"), self.screenshot))
|
|
else:
|
|
self.menu_list = []
|
|
if self.profile_menu is not None:
|
|
self.menu_list.append((_("Video Playback Profiles"), self.profile_menu))
|
|
|
|
if self.svp_menu is not None and self.svp_menu.is_available():
|
|
self.menu_list.append((_("SVP Settings"), self.svp_menu.menu_action))
|
|
|
|
self.menu_list.extend(
|
|
[
|
|
(_("Video Preferences"), self.video_preferences_menu),
|
|
(_("Player Preferences"), self.player_preferences_menu),
|
|
(_("Close Menu"), self.hide_menu),
|
|
]
|
|
)
|
|
|
|
self.refresh_menu()
|
|
|
|
# Wait until the menu renders to pause.
|
|
time.sleep(0.2)
|
|
|
|
if self.playerManager.playback_is_aborted():
|
|
self.playerManager.force_window(True)
|
|
else:
|
|
if not self.playerManager.syncplay.is_enabled():
|
|
self.playerManager.set_paused(True)
|
|
|
|
def hide_menu(self):
|
|
if self.is_menu_shown:
|
|
self.playerManager.set_osd_settings(
|
|
self.original_osd_color, self.original_osd_size
|
|
)
|
|
self.playerManager.show_text("", 0, 0)
|
|
|
|
self.playerManager.enable_osc(settings.enable_osc)
|
|
self.playerManager.triggered_menu(False)
|
|
self.playerManager.force_window(False)
|
|
|
|
if not self.playerManager.playback_is_aborted():
|
|
if not self.playerManager.syncplay.is_enabled():
|
|
self.playerManager.set_paused(False)
|
|
|
|
self.is_menu_shown = False
|
|
|
|
def screenshot(self):
|
|
self.playerManager.show_text("", 0, 0)
|
|
time.sleep(0.5)
|
|
self.playerManager.screenshot()
|
|
self.hide_menu()
|
|
|
|
def put_menu(self, title: str, entries: Optional[list] = None, selected: int = 0):
|
|
if entries is None:
|
|
entries = []
|
|
|
|
self.menu_stack.put((self.menu_title, self.menu_list, self.menu_selection))
|
|
self.menu_title = title
|
|
self.menu_list = entries
|
|
self.menu_selection = selected
|
|
|
|
def menu_action(self, action: str):
|
|
if not self.is_menu_shown and action in ("home", "ok"):
|
|
self.show_menu()
|
|
else:
|
|
if action == "up":
|
|
self.menu_selection = (self.menu_selection - 1) % len(self.menu_list)
|
|
elif action == "down":
|
|
self.menu_selection = (self.menu_selection + 1) % len(self.menu_list)
|
|
elif action == "back":
|
|
if self.menu_stack.empty():
|
|
self.hide_menu()
|
|
else:
|
|
(
|
|
self.menu_title,
|
|
self.menu_list,
|
|
self.menu_selection,
|
|
) = self.menu_stack.get_nowait()
|
|
elif action == "ok":
|
|
if self.mouse_back:
|
|
self.menu_action("back")
|
|
else:
|
|
self.menu_list[self.menu_selection][1]()
|
|
elif action == "home":
|
|
self.show_menu()
|
|
self.mouse_back = False
|
|
self.refresh_menu()
|
|
|
|
def change_audio_menu_handle(self):
|
|
self.playerManager.put_task(
|
|
self.playerManager.set_streams, self.menu_list[self.menu_selection][2], None
|
|
)
|
|
self.playerManager.timeline_handle()
|
|
self.menu_action("back")
|
|
|
|
def change_audio_menu(self):
|
|
self.put_menu(_("Select Audio Track"))
|
|
|
|
selected_aid = self.playerManager.get_video().aid
|
|
audio_streams = [
|
|
s
|
|
for s in self.playerManager.get_video().media_source["MediaStreams"]
|
|
if s.get("Type") == "Audio"
|
|
]
|
|
for i, audio_track in enumerate(audio_streams):
|
|
aid = audio_track.get("Index")
|
|
if (
|
|
settings.lang_filter_audio
|
|
and aid != selected_aid
|
|
and audio_track.get("Language") not in lang_filter
|
|
):
|
|
continue
|
|
|
|
self.menu_list.append(
|
|
[
|
|
"{0} ({1})".format(
|
|
audio_track.get("DisplayTitle"), audio_track.get("Title")
|
|
),
|
|
self.change_audio_menu_handle,
|
|
aid,
|
|
]
|
|
)
|
|
if aid == selected_aid:
|
|
self.menu_selection = i
|
|
|
|
def change_subtitle_menu_handle(self):
|
|
self.playerManager.put_task(
|
|
self.playerManager.set_streams, None, self.menu_list[self.menu_selection][2]
|
|
)
|
|
self.playerManager.timeline_handle()
|
|
self.menu_action("back")
|
|
|
|
def change_subtitle_menu(self):
|
|
self.put_menu(_("Select Subtitle Track"))
|
|
|
|
selected_sid = self.playerManager.get_video().sid
|
|
subtitle_streams = [
|
|
s
|
|
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])
|
|
for i, subtitle_track in enumerate(subtitle_streams):
|
|
sid = subtitle_track.get("Index")
|
|
if (
|
|
settings.lang_filter_sub
|
|
and sid != selected_sid
|
|
and subtitle_track.get("Language") not in lang_filter
|
|
):
|
|
continue
|
|
|
|
self.menu_list.append(
|
|
[
|
|
"{0} ({1})".format(
|
|
get_sub_display_title(subtitle_track),
|
|
subtitle_track.get("Title"),
|
|
),
|
|
self.change_subtitle_menu_handle,
|
|
sid,
|
|
]
|
|
)
|
|
if sid == selected_sid:
|
|
self.menu_selection = i + 1
|
|
|
|
def change_transcode_quality_handle(self):
|
|
bitrate = self.menu_list[self.menu_selection][2]
|
|
if bitrate == "none":
|
|
self.playerManager.get_video().set_trs_override(None, False)
|
|
elif bitrate == "max":
|
|
self.playerManager.get_video().set_trs_override(None, True)
|
|
else:
|
|
self.playerManager.get_video().set_trs_override(bitrate, True)
|
|
|
|
self.menu_action("back")
|
|
self.playerManager.put_task(self.playerManager.restart_playback)
|
|
self.playerManager.timeline_handle()
|
|
|
|
def change_transcode_quality(self):
|
|
handle = self.change_transcode_quality_handle
|
|
self.put_menu(
|
|
_("Select Transcode Quality"),
|
|
[(_("No Transcode"), handle, "none"), (_("Maximum"), handle, "max")],
|
|
)
|
|
|
|
for item in TRANSCODE_LEVELS:
|
|
self.menu_list.append((item[0], handle, item[1]))
|
|
|
|
self.menu_selection = 7
|
|
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
|
|
|
|
def change_tracks_handle(self):
|
|
mode = self.menu_list[self.menu_selection][2]
|
|
process_series(mode, self.playerManager)
|
|
|
|
def change_tracks_manual_s1(self):
|
|
self.change_audio_menu()
|
|
for item in self.menu_list:
|
|
item[1] = self.change_tracks_manual_s2
|
|
|
|
def change_tracks_manual_s2(self):
|
|
self.menu_tmp = self.menu_selection
|
|
self.change_subtitle_menu()
|
|
for item in self.menu_list:
|
|
item[1] = self.change_tracks_manual_s3
|
|
|
|
def change_tracks_manual_s3(self):
|
|
aid, sid = self.menu_tmp, self.menu_selection - 1
|
|
# Pop 3 menu items.
|
|
for i in range(3):
|
|
self.menu_action("back")
|
|
process_series("manual", self.playerManager, aid, sid)
|
|
|
|
def change_tracks_menu(self):
|
|
self.put_menu(
|
|
_("Select Audio/Subtitle for Series"),
|
|
[
|
|
(_("English Audio"), self.change_tracks_handle, "dubbed"),
|
|
(
|
|
_("Japanese Audio w/ English Subtitles"),
|
|
self.change_tracks_handle,
|
|
"subbed",
|
|
),
|
|
(
|
|
_("Manual by Track Index (Less Reliable)"),
|
|
self.change_tracks_manual_s1,
|
|
),
|
|
],
|
|
)
|
|
|
|
def settings_toggle_bool(self):
|
|
_x, _x, key, name = self.menu_list[self.menu_selection]
|
|
setattr(settings, key, not getattr(settings, key))
|
|
settings.save()
|
|
self.menu_list[self.menu_selection] = self.get_settings_toggle(name, key)
|
|
|
|
def get_settings_toggle(self, name: str, setting: str):
|
|
return (
|
|
"{0}: {1}".format(name, getattr(settings, setting)),
|
|
self.settings_toggle_bool,
|
|
setting,
|
|
name,
|
|
)
|
|
|
|
def transcode_settings_handle(self):
|
|
settings.remote_kbps = self.menu_list[self.menu_selection][2]
|
|
settings.save()
|
|
|
|
# Need to re-render preferences menu.
|
|
for i in range(2):
|
|
self.menu_action("back")
|
|
self.video_preferences_menu()
|
|
|
|
def transcode_settings_menu(self):
|
|
self.put_menu(_("Select Default Transcode Profile"))
|
|
handle = self.transcode_settings_handle
|
|
self.menu_list.append((_("No Transcode") + " 2 Gbps", handle, 2147483))
|
|
|
|
for i, item in enumerate(TRANSCODE_LEVELS):
|
|
self.menu_list.append((item[0], handle, item[1]))
|
|
if settings.remote_kbps == item[1]:
|
|
self.menu_selection = i
|
|
|
|
@staticmethod
|
|
def get_subtitle_color(color: str):
|
|
if color in HEX_TO_COLOR:
|
|
return HEX_TO_COLOR[color]
|
|
else:
|
|
return mpv_color_to_plex(color)
|
|
|
|
def sub_settings_handle(self):
|
|
setting_name = self.menu_list[self.menu_selection][2]
|
|
value = self.menu_list[self.menu_selection][3]
|
|
setattr(settings, setting_name, value)
|
|
settings.save()
|
|
|
|
# Need to re-render preferences menu.
|
|
for i in range(2):
|
|
self.menu_action("back")
|
|
self.video_preferences_menu()
|
|
|
|
if self.playerManager.get_video().is_transcode:
|
|
if setting_name == "subtitle_size":
|
|
self.playerManager.put_task(self.playerManager.update_subtitle_visuals)
|
|
else:
|
|
self.playerManager.update_subtitle_visuals()
|
|
|
|
def subtitle_color_menu(self):
|
|
self.put_menu(
|
|
_("Select Subtitle Color"),
|
|
[
|
|
(name, self.sub_settings_handle, "subtitle_color", color)
|
|
for name, color in COLOR_LIST
|
|
],
|
|
)
|
|
|
|
def subtitle_size_menu(self):
|
|
self.put_menu(
|
|
_("Select Subtitle Size"),
|
|
[
|
|
(name, self.sub_settings_handle, "subtitle_size", size)
|
|
for name, size in SIZE_LIST
|
|
],
|
|
selected=2,
|
|
)
|
|
|
|
def subtitle_position_menu(self):
|
|
self.put_menu(
|
|
_("Select Subtitle Position"),
|
|
[
|
|
(_("Bottom"), self.sub_settings_handle, "subtitle_position", "bottom"),
|
|
(_("Top"), self.sub_settings_handle, "subtitle_position", "top"),
|
|
(_("Middle"), self.sub_settings_handle, "subtitle_position", "middle"),
|
|
],
|
|
)
|
|
|
|
def video_preferences_menu(self):
|
|
options = [
|
|
(
|
|
_("Remote Transcode Quality: {0:0.1f} Mbps").format(
|
|
settings.remote_kbps / 1000
|
|
),
|
|
self.transcode_settings_menu,
|
|
),
|
|
(
|
|
_("Subtitle Size: {0}").format(settings.subtitle_size),
|
|
self.subtitle_size_menu,
|
|
),
|
|
(
|
|
_("Subtitle Position: {0}").format(settings.subtitle_position),
|
|
self.subtitle_position_menu,
|
|
),
|
|
(
|
|
_("Subtitle Color: {0}").format(
|
|
self.get_subtitle_color(settings.subtitle_color)
|
|
),
|
|
self.subtitle_color_menu,
|
|
),
|
|
self.get_settings_toggle(_("Transcode Hi10p to 8bit"), "transcode_hi10p"),
|
|
self.get_settings_toggle(_("Transcode HDR"), "transcode_hdr"),
|
|
self.get_settings_toggle(_("Direct Paths"), "direct_paths"),
|
|
self.get_settings_toggle(_("Disable Direct Play"), "always_transcode"),
|
|
]
|
|
|
|
if self.profile_manager and len(self.profile_manager.profile_subtypes):
|
|
options.append(
|
|
(
|
|
_("Video Profile Subtype: {0}").format(
|
|
settings.shader_pack_subtype
|
|
),
|
|
self.shader_pack_subtype_menu,
|
|
),
|
|
)
|
|
|
|
self.put_menu(_("Video Preferences"), options)
|
|
|
|
def shader_pack_subtype_menu(self):
|
|
self.put_menu(
|
|
_("Video Profile Subtype"),
|
|
[
|
|
(option, self.shader_pack_subtype_handle)
|
|
for option in self.profile_manager.profile_subtypes
|
|
],
|
|
)
|
|
|
|
def shader_pack_subtype_handle(self):
|
|
option_value = self.menu_list[self.menu_selection][0]
|
|
settings.shader_pack_subtype = option_value
|
|
settings.save()
|
|
|
|
# Need to re-render preferences menu.
|
|
for i in range(2):
|
|
self.menu_action("back")
|
|
self.video_preferences_menu()
|
|
|
|
def player_preferences_menu(self):
|
|
self.put_menu(
|
|
_("Player Preferences"),
|
|
[
|
|
self.get_settings_toggle(_("Auto Play"), "auto_play"),
|
|
self.get_settings_toggle(_("Auto Fullscreen"), "fullscreen"),
|
|
self.get_settings_toggle(_("Media Key Seek"), "media_key_seek"),
|
|
self.get_settings_toggle(_("Use Web Seek Pref"), "use_web_seek"),
|
|
self.get_settings_toggle(_("Display Mirroring"), "display_mirroring"),
|
|
self.get_settings_toggle(_("Write Logs to File"), "write_logs"),
|
|
self.get_settings_toggle(_("Check for Updates"), "check_updates"),
|
|
self.get_settings_toggle(
|
|
_("Discord Rich Presence"), "discord_presence"
|
|
),
|
|
self.get_settings_toggle(_("Always Skip Intros"), "skip_intro_always"),
|
|
self.get_settings_toggle(_("Ask to Skip Intros"), "skip_intro_prompt"),
|
|
self.get_settings_toggle(
|
|
_("Always Skip Credits"), "skip_credits_always"
|
|
),
|
|
self.get_settings_toggle(
|
|
_("Ask to Skip Credits"), "skip_credits_prompt"
|
|
),
|
|
self.get_settings_toggle(
|
|
_("Enable thumbnail previews"), "thumbnail_enable"
|
|
),
|
|
],
|
|
)
|
|
|
|
def unwatched_menu_handle(self):
|
|
self.playerManager.put_task(self.playerManager.unwatched_quit)
|
|
self.playerManager.timeline_handle()
|