jellyfin-mpv-shim/jellyfin_mpv_shim/menu.py
2024-06-05 18:45:52 -04:00

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