Base localization support.

This commit is contained in:
Ian Walton 2020-08-10 03:33:08 -04:00
parent 1a40f62677
commit 0b422624fd
20 changed files with 748 additions and 145 deletions

View File

@ -2,3 +2,4 @@ include jellyfin_mpv_shim/systray.png
recursive-include jellyfin_mpv_shim/webclient_view/webclient *
recursive-include jellyfin_mpv_shim/integration *
recursive-include jellyfin_mpv_shim/default_shader_pack *
recursive-include jellyfin_mpv_shim/messages *.mo

View File

@ -298,6 +298,7 @@ need to.
- Update checks are performed when playing media, once per day.
- `notify_updates` - Display update notification when playing media. Default: `true`
- Notification will only display once until the application is restarted.
- `lang` - Allows overriding system locale. (Enter a language code.) Default: `null`
### MPV Configuration
@ -477,6 +478,25 @@ If you are on Windows there are additional dependencies. Please see the Windows
6. Install any platform-specific dependencies from the respective install tutorials.
7. You should now be able to run the program with `./run.py` or `./run-desktop.py`. Installation is possible with `sudo pip3 install .`.
### Translation
This project uses gettext for translation. The current template language file is `base.pot` in `jellyfin_mpv_shim/messages/`.
To regenerate `base.pot`:
```bash
pygettext --default-domain=base -o jellyfin_mpv_shim/messages/base.pot jellyfin_mpv_shim/*.py jellyfin_mpv_shim/**/*.py
```
To update an existing translation with new strings:
```bash
msgmerge --update jellyfin_mpv_shim/messages/es/LC_MESSAGES/base.po jellyfin_mpv_shim/messages/base.pot
```
To compile all `*.po` files to `*.mo`:
```bash
find -iname '*.po' | while read -r file; do msgfmt "$file" -o "${file%.*}.mo"; done
```
## Linux Installation
You can [install the software from flathub](https://flathub.org/apps/details/com.github.iwalton3.jellyfin-mpv-shim). The pip installation is less integrated but takes up less space if you're not already using flatpak.

View File

@ -8,7 +8,7 @@ rem The desktop version (and corresponding shim shortcut) do work, however.
rem Edge-based build
rem "C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py
rem CEF-based build
"C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
"C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
xcopy /E /Y cef32\cefpython3 dist\run-desktop\
xcopy /E /Y mpv32\mpv-1.dll dist\run-desktop\
del dist\run-desktop\run-desktop.exe.manifest

View File

@ -4,10 +4,10 @@ set PATH=%PATH%;%CD%
rem Edge-based build
rem pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py
rem CEF-based build
pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
xcopy /E /Y cef\cefpython3 dist\run-desktop\
del dist\run-desktop\run-desktop.exe.manifest
copy hidpi.manifest dist\run-desktop\run-desktop.exe.manifest
rd /s /q __pycache__ build
pyinstaller -cF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py
pyinstaller -cF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py
pause

View File

@ -4,10 +4,10 @@ set PATH=%PATH%;%CD%
rem Edge-based build
rem pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py
rem CEF-based build
pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py
xcopy /E /Y cef\cefpython3 dist\run-desktop\
del dist\run-desktop\run-desktop.exe.manifest
copy hidpi.manifest dist\run-desktop\run-desktop.exe.manifest
rd /s /q __pycache__ build
pyinstaller -wF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py
pyinstaller -wF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "Jellyfin MPV Desktop.iss"

View File

@ -1,6 +1,8 @@
from collections import namedtuple
from .utils import get_sub_display_title
import urllib.parse
from .i18n import _
# TODO: Should probably support automatic profiles for languages other than English and Japanese...
import time
import logging
@ -15,7 +17,7 @@ keep_messages = 6
def render_message(message, show_text):
log.info(message)
messages.append(message)
text = "Selecting Tracks..."
text = _("Selecting Tracks...")
for message in messages[-6:]:
text += "\n " + message
show_text(text,2**30,1)
@ -41,9 +43,9 @@ def process_series(mode, player, m_raid=None, m_rsid=None):
audio_list = [Audio(s.get("Index"), s.get("Language"), s.get("Title"),
s.get("DisplayTitle")) for s in media_source["MediaStreams"]
if s.get("Type") == "Audio"]
subtitle_list = [Subtitle(s.get("Index"), s.get("Language"), s.get("Title"), s.get("IsForced"),
get_sub_display_title(s)) for s in media_source["MediaStreams"]
if s.get("Type") == "Subtitle"]
subtitle_list = [Subtitle(s.get("Index"), s.get("Language"), s.get("Title"), s.get("IsForced"),
get_sub_display_title(s)) for s in media_source["MediaStreams"]
if s.get("Type") == "Subtitle"]
part = Part(media_source.get("Id"), audio_list, subtitle_list)
aid = None
@ -63,7 +65,7 @@ def process_series(mode, player, m_raid=None, m_rsid=None):
aid, sid = audio.id, subtitle.id
success_ct += 1
elif audio:
render_message("{0}: No Subtitles".format(name), show_text)
render_message(_("{0}: No Subtitles").format(name), show_text)
aid = audio.id
partial_ct += 1
elif mode == "manual":
@ -86,26 +88,26 @@ def process_series(mode, player, m_raid=None, m_rsid=None):
# This is a horrible hack to change the audio/subtitle settings without playing
# the media. I checked the jelyfin source code and didn't find a better way.
client.jellyfin.session_progress({
"ItemId":part.id,
"ItemId": part.id,
"AudioStreamIndex": aid,
"SubtitleStreamIndex": sid
})
else:
render_message("{0}: Fail".format(name), show_text)
render_message(_("{0}: Fail").format(name), show_text)
if mode == "subbed":
render_message("Set Subbed: {0} ok, {1} fail".format(
render_message(_("Set Subbed: {0} ok, {1} fail").format(
success_ct, count-success_ct), show_text)
elif mode == "dubbed":
render_message("Set Dubbed: {0} ok, {1} audio only, {2} fail".format(
render_message(_("Set Dubbed: {0} ok, {1} audio only, {2} fail").format(
success_ct, partial_ct, count-success_ct-partial_ct), show_text)
elif mode == "manual":
render_message("Manual: {0} ok, {1} fail".format(
render_message(_("Manual: {0} ok, {1} fail").format(
success_ct, count-success_ct), show_text)
time.sleep(3)
if c_aid:
render_message("Setting Current...", show_text)
render_message(_("Setting Current..."), show_text)
player.put_task(player.set_streams, c_aid, c_sid)
player.timeline_handle()
@ -116,7 +118,7 @@ def get_subbed(part):
for audio in part.audio:
lower_title = audio.name.lower() if audio.name is not None else ""
if audio.language_code != "jpn" and not "japan" in lower_title:
if audio.language_code != "jpn" and "japan" not in lower_title:
continue
if "commentary" in lower_title:
continue
@ -127,7 +129,7 @@ def get_subbed(part):
for subtitle in part.subtitle:
lower_title = subtitle.name.lower() if subtitle.name is not None else ""
if subtitle.language_code != "eng" and not "english" in lower_title:
if subtitle.language_code != "eng" and "english" not in lower_title:
continue
if subtitle.is_forced:
continue
@ -148,7 +150,7 @@ def get_dubbed(part):
for audio in part.audio:
lower_title = audio.name.lower() if audio.name is not None else ""
if audio.language_code != "eng" and not "english" in lower_title:
if audio.language_code != "eng" and "english" not in lower_title:
continue
if "commentary" in lower_title:
continue
@ -159,7 +161,7 @@ def get_dubbed(part):
for subtitle in part.subtitle:
lower_title = subtitle.name.lower() if subtitle.name is not None else ""
if subtitle.language_code != "eng" and not "english" in lower_title:
if subtitle.language_code != "eng" and "english" not in lower_title:
continue
if subtitle.is_forced:
sign_subtitles = subtitle

View File

@ -4,6 +4,7 @@ from .conf import settings
from . import conffile
from getpass import getpass
from .constants import CLIENT_VERSION, USER_APP_NAME, USER_AGENT, APP_NAME
from .i18n import _
import sys
import os.path
@ -41,18 +42,18 @@ class ClientManager(object):
add_another = True
while not is_logged_in or add_another:
server = input("Server URL: ")
username = input("Username: ")
password = getpass("Password: ")
server = input(_("Server URL: "))
username = input(_("Username: "))
password = getpass(_("Password: "))
is_logged_in = self.login(server, username, password)
if is_logged_in:
log.info("Successfully added server.")
add_another = input("Add another server? [y/N] ")
log.info(_("Successfully added server."))
add_another = input(_("Add another server? [y/N] "))
add_another = add_another in ("y", "Y", "yes", "Yes")
else:
log.warning("Adding server failed.")
log.warning(_("Adding server failed."))
def client_factory(self):
client = JellyfinClient(allow_multiple_clients=True)

View File

@ -92,6 +92,7 @@ class Settings(object):
"screenshot_menu": True,
"check_updates": True,
"notify_updates": True,
"lang": None,
}
def __getattr__(self, name):

View File

@ -17,9 +17,9 @@ 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.
helpers.on_escape = lambda _x=None: load_idle()
@ -100,8 +100,8 @@ def get_html(server_address=None, item=None):
jinja_vars = {
'random_backdrop': True, # Make the jinja template load some extra JS code for random backdrops
'backdrop_src': helpers.getRandomBackdropUrl(), # Preinitialise it with a random backdrop though
'display_name': "Ready to cast",
'overview': "\n\nSelect your media in Jellyfin and play it here", # FIME: Mention the player_name here
'display_name': _("Ready to cast"),
'overview': "\n\n" + _("Select your media in Jellyfin and play it here"), # FIME: Mention the player_name here
}
jinja_vars.update({

View File

@ -13,6 +13,7 @@ from .conffile import confdir
from .clients import clientManager
from .utils import get_resource
from .log_utils import CustomFormatter
from .i18n import _
log = logging.getLogger('gui_mgr')
@ -132,7 +133,7 @@ class LoggerWindowProcess(Process):
self.tk = tk
root = tk.Tk()
self.root = root
root.title("Application Log")
root.title(_("Application Log"))
text = tk.Text(root)
text.pack(side=tk.LEFT, fill=tk.BOTH, expand = tk.YES)
text.config(wrap=tk.WORD)
@ -170,7 +171,7 @@ class PreferencesWindow(threading.Thread):
else:
self.handle("error")
except Exception:
log.error("Error while adding server.", exc_info=1)
log.error("Error while adding server.", exc_info=True)
self.handle("error")
elif action == "remove":
clientManager.remove_client(param)
@ -206,7 +207,9 @@ class PreferencesWindowProcess(Process):
self.add_button.config(state=self.tk.NORMAL)
self.remove_button.config(state=self.tk.NORMAL)
elif action == "error":
self.messagebox.showerror("Add Server", "Could not add server.\nPlease check your connection infomation.")
self.messagebox.showerror(
_("Add Server"),
_("Could not add server.\nPlease check your connection infomation."))
self.add_button.config(state=self.tk.NORMAL)
elif action == "die":
self.root.destroy()
@ -222,7 +225,7 @@ class PreferencesWindowProcess(Process):
self.serverList.set(["{0} ({1}, {2})".format(
server["Name"],
server["username"],
"Ok" if server["connected"] else "Fail"
_("Ok") if server["connected"] else _("Fail")
) for server in self.servers])
def run(self):
@ -231,7 +234,7 @@ class PreferencesWindowProcess(Process):
self.tk = tk
self.messagebox = messagebox
root = tk.Tk()
root.title("Server Configuration")
root.title(_("Server Configuration"))
self.root = root
self.servers = {}
@ -254,17 +257,17 @@ class PreferencesWindowProcess(Process):
c.grid_columnconfigure(0, weight=1)
c.grid_rowconfigure(4, weight=1)
servername_label = ttk.Label(c, text='Server:')
servername_label = ttk.Label(c, text=_('Server:'))
servername_label.grid(column=1, row=0, sticky=tk.E)
self.servername = tk.StringVar()
servername_box = ttk.Entry(c, textvariable=self.servername)
servername_box.grid(column=2, row=0)
username_label = ttk.Label(c, text='Username:')
username_label = ttk.Label(c, text=_('Username:'))
username_label.grid(column=1, row=1, sticky=tk.E)
self.username = tk.StringVar()
username_box = ttk.Entry(c, textvariable=self.username)
username_box.grid(column=2, row=1)
password_label = ttk.Label(c, text='Password:')
password_label = ttk.Label(c, text=_('Password:'))
password_label.grid(column=1, row=2, sticky=tk.E)
self.password = tk.StringVar()
password_box = ttk.Entry(c, textvariable=self.password, show="*")
@ -285,11 +288,11 @@ class PreferencesWindowProcess(Process):
def close():
self.r_queue.put(("die", None))
self.add_button = ttk.Button(c, text='Add Server', command=add_server)
self.add_button = ttk.Button(c, text=_('Add Server'), command=add_server)
self.add_button.grid(column=2, row=3, pady=5, sticky=tk.E)
self.remove_button = ttk.Button(c, text='Remove Server', command=remove_server)
self.remove_button = ttk.Button(c, text=_('Remove Server'), command=remove_server)
self.remove_button.grid(column=1, row=4, padx=5, pady=10, sticky=(tk.E, tk.S))
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))
serverlist.bind('<<ListboxSelect>>', serverSelect)
@ -393,11 +396,11 @@ class STrayProcess(Process):
self.icon_stop()
menu_items = [
MenuItem("Configure Servers", get_wrapper("show_preferences")),
MenuItem("Show Console", get_wrapper("show_console")),
MenuItem("Application Menu", get_wrapper("open_player_menu")),
MenuItem("Open Config Folder", get_wrapper("open_config_brs")),
MenuItem("Quit", die)
MenuItem(_("Configure Servers"), get_wrapper("show_preferences")),
MenuItem(_("Show Console"), get_wrapper("show_console")),
MenuItem(_("Application Menu"), get_wrapper("open_player_menu")),
MenuItem(_("Open Config Folder"), get_wrapper("open_config_brs")),
MenuItem(_("Quit"), die)
]
icon = Icon(USER_APP_NAME, menu=Menu(*menu_items))

25
jellyfin_mpv_shim/i18n.py Normal file
View File

@ -0,0 +1,25 @@
import gettext
import builtins
from .conf import settings
translation = gettext.NullTranslations()
def configure():
global translation
from .utils import get_resource
messages_dir = get_resource("messages")
if settings.lang is not None:
translation = gettext.translation("base", messages_dir, languages=[settings.lang], fallback=True)
else:
translation = gettext.translation("base", messages_dir, fallback=True)
def get_translation():
return translation
def _(string: str) -> str:
return translation.gettext(string)

View File

@ -8,6 +8,7 @@ from sys import platform
from .conf import settings
from .utils import is_local_domain, get_profile, get_seq
from .i18n import _
log = logging.getLogger('media')
@ -101,7 +102,7 @@ class Video(object):
if year is not None:
title = "%s (%s)" % (title, year)
setattr(self, "_title", title)
return getattr(self, "_title") + (" (Transcode)" if self.is_transcode else "")
return getattr(self, "_title") + (_(" (Transcode)") if self.is_transcode else "")
def set_trs_override(self, video_bitrate, force_transcode):
if force_transcode:
@ -282,9 +283,9 @@ class Media(object):
return self.video
if index < len(self.queue):
return Video(queue[index]["Id"], self)
return Video(self.queue[index]["Id"], self)
log.error("Media::get_video couldn't find video at index %s" % video)
log.error("Media::get_video couldn't find video at index %s" % index)
def insert_items(self, items, append=False):
items = [{ "PlaylistItemId": "playlistItem{0}".format(get_seq()), "Id": id_num } for id_num in items]

View File

@ -4,6 +4,7 @@ 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
@ -24,23 +25,23 @@ TRANSCODE_LEVELS = (
)
COLOR_LIST = (
("White", "#FFFFFFFF"),
("Yellow", "#FFFFEE00"),
("Black", "#FF000000"),
("Cyan", "#FF00FFFF"),
("Blue", "#FF0000FF"),
("Green", "#FF00FF00"),
("Magenta", "#FFEE00EE"),
("Red", "#FFFF0000"),
("Gray", "#FF808080"),
(_("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),
(_("Tiny"), 50),
(_("Small"), 75),
(_("Normal"), 100),
(_("Large"), 125),
(_("Huge"), 200),
)
HEX_TO_COLOR = {v:c for c,v in COLOR_LIST}
@ -101,39 +102,39 @@ class OSDMenu(object):
if hasattr(player, 'osc'):
player.osc = False
self.menu_title = "Main Menu"
self.menu_title = _("Main Menu")
self.menu_selection = 0
if self.playerManager._video and not player.playback_abort:
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),
(_("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),
_("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))
self.menu_list.append((_("Change Video Playback Profile"), self.profile_menu))
if self.playerManager._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))
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))
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))
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.append((_("SVP Settings"), self.svp_menu.menu_action))
self.menu_list.extend([
("Preferences", self.preferences_menu),
("Close Menu", self.hide_menu)
(_("Preferences"), self.preferences_menu),
(_("Close Menu"), self.hide_menu)
])
self.refresh_menu()
@ -211,7 +212,7 @@ class OSDMenu(object):
self.menu_action("back")
def change_audio_menu(self):
self.put_menu("Select Audio Track")
self.put_menu(_("Select Audio Track"))
selected_aid = self.playerManager._video.aid
audio_streams = [s for s in self.playerManager._video.media_source["MediaStreams"]
@ -232,12 +233,12 @@ class OSDMenu(object):
self.menu_action("back")
def change_subtitle_menu(self):
self.put_menu("Select Subtitle Track")
self.put_menu(_("Select Subtitle Track"))
selected_sid = self.playerManager._video.sid
subtitle_streams = [s for s in self.playerManager._video.media_source["MediaStreams"]
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])
for i, subtitle_track in enumerate(subtitle_streams):
sid = subtitle_track.get("Index")
self.menu_list.append([
@ -263,9 +264,9 @@ class OSDMenu(object):
def change_transcode_quality(self):
handle = self.change_transcode_quality_handle
self.put_menu("Select Transcode Quality", [
("No Transcode", handle, "none"),
("Maximum", handle, "max")
self.put_menu(_("Select Transcode Quality"), [
(_("No Transcode"), handle, "none"),
(_("Maximum"), handle, "max")
])
for item in TRANSCODE_LEVELS:
@ -300,10 +301,10 @@ class OSDMenu(object):
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),
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):
@ -330,7 +331,7 @@ class OSDMenu(object):
self.preferences_menu()
def transcode_settings_menu(self):
self.put_menu("Select Default Transcode Profile")
self.put_menu(_("Select Default Transcode Profile"))
handle = self.transcode_settings_handle
for i, item in enumerate(TRANSCODE_LEVELS):
@ -362,41 +363,41 @@ class OSDMenu(object):
self.playerManager.update_subtitle_visuals()
def subtitle_color_menu(self):
self.put_menu("Select Subtitle Color", [
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", [
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"),
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 preferences_menu(self):
self.put_menu("Preferences", [
self.get_settings_toggle("Always Transcode", "always_transcode"),
self.get_settings_toggle("Auto Play", "auto_play"),
("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 H265 to H264", "transcode_h265"),
self.get_settings_toggle("Transcode Hi10p to 8bit", "transcode_hi10p"),
self.get_settings_toggle("Direct Paths", "direct_paths"),
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("Transcode to H265", "transcode_to_h265"),
self.get_settings_toggle("Write Logs to File", "write_logs"),
self.put_menu(_("Preferences"), [
self.get_settings_toggle(_("Always Transcode"), "always_transcode"),
self.get_settings_toggle(_("Auto Play"), "auto_play"),
(_("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 H265 to H264"), "transcode_h265"),
self.get_settings_toggle(_("Transcode Hi10p to 8bit"), "transcode_hi10p"),
self.get_settings_toggle(_("Direct Paths"), "direct_paths"),
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(_("Transcode to H265"), "transcode_to_h265"),
self.get_settings_toggle(_("Write Logs to File"), "write_logs"),
])
def unwatched_menu_handle(self):

View File

@ -0,0 +1,526 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-08-10 02:57+EDT\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: ENCODING\n"
"Generated-By: pygettext.py 1.5\n"
#: jellyfin_mpv_shim/bulk_subtitle.py:20
msgid "Selecting Tracks..."
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:68
msgid "{0}: No Subtitles"
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:97
msgid "{0}: Fail"
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:100
msgid "Set Subbed: {0} ok, {1} fail"
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:103
msgid "Set Dubbed: {0} ok, {1} audio only, {2} fail"
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:106
msgid "Manual: {0} ok, {1} fail"
msgstr ""
#: jellyfin_mpv_shim/bulk_subtitle.py:110
msgid "Setting Current..."
msgstr ""
#: jellyfin_mpv_shim/clients.py:45
msgid "Server URL: "
msgstr ""
#: jellyfin_mpv_shim/clients.py:46
msgid "Username: "
msgstr ""
#: jellyfin_mpv_shim/clients.py:47
msgid "Password: "
msgstr ""
#: jellyfin_mpv_shim/clients.py:52
msgid "Successfully added server."
msgstr ""
#: jellyfin_mpv_shim/clients.py:53
msgid "Add another server? [y/N] "
msgstr ""
#: jellyfin_mpv_shim/clients.py:56
msgid "Adding server failed."
msgstr ""
#: jellyfin_mpv_shim/display_mirror/__init__.py:103
msgid "Ready to cast"
msgstr ""
#: jellyfin_mpv_shim/display_mirror/__init__.py:104
msgid "Select your media in Jellyfin and play it here"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:136
msgid "Application Log"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:211 jellyfin_mpv_shim/gui_mgr.py:291
msgid "Add Server"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:212
msgid ""
"Could not add server.\n"
"Please check your connection infomation."
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:228
msgid "Fail"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:228
msgid "Ok"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:237
msgid "Server Configuration"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:260
msgid "Server:"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:265
msgid "Username:"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:270
msgid "Password:"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:293
msgid "Remove Server"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:295
msgid "Close"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:399
msgid "Configure Servers"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:400
msgid "Show Console"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:401
msgid "Application Menu"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:402
msgid "Open Config Folder"
msgstr ""
#: jellyfin_mpv_shim/gui_mgr.py:403
msgid "Quit"
msgstr ""
#: jellyfin_mpv_shim/media.py:105
msgid " (Transcode)"
msgstr ""
#: jellyfin_mpv_shim/menu.py:28
msgid "White"
msgstr ""
#: jellyfin_mpv_shim/menu.py:29
msgid "Yellow"
msgstr ""
#: jellyfin_mpv_shim/menu.py:30
msgid "Black"
msgstr ""
#: jellyfin_mpv_shim/menu.py:31
msgid "Cyan"
msgstr ""
#: jellyfin_mpv_shim/menu.py:32
msgid "Blue"
msgstr ""
#: jellyfin_mpv_shim/menu.py:33
msgid "Green"
msgstr ""
#: jellyfin_mpv_shim/menu.py:34
msgid "Magenta"
msgstr ""
#: jellyfin_mpv_shim/menu.py:35
msgid "Red"
msgstr ""
#: jellyfin_mpv_shim/menu.py:36
msgid "Gray"
msgstr ""
#: jellyfin_mpv_shim/menu.py:40
msgid "Tiny"
msgstr ""
#: jellyfin_mpv_shim/menu.py:41
msgid "Small"
msgstr ""
#: jellyfin_mpv_shim/menu.py:42
msgid "Normal"
msgstr ""
#: jellyfin_mpv_shim/menu.py:43
msgid "Large"
msgstr ""
#: jellyfin_mpv_shim/menu.py:44
msgid "Huge"
msgstr ""
#: jellyfin_mpv_shim/menu.py:105
msgid "Main Menu"
msgstr ""
#: jellyfin_mpv_shim/menu.py:110
msgid "Change Audio"
msgstr ""
#: jellyfin_mpv_shim/menu.py:111
msgid "Change Subtitles"
msgstr ""
#: jellyfin_mpv_shim/menu.py:112
msgid "Change Video Quality"
msgstr ""
#: jellyfin_mpv_shim/menu.py:113 jellyfin_mpv_shim/syncplay.py:465
msgid "SyncPlay"
msgstr ""
#: jellyfin_mpv_shim/menu.py:117
msgid "MPV Shim v{0} Release Info/Download"
msgstr ""
#: jellyfin_mpv_shim/menu.py:121
msgid "Change Video Playback Profile"
msgstr ""
#: jellyfin_mpv_shim/menu.py:123
msgid "Auto Set Audio/Subtitles (Entire Series)"
msgstr ""
#: jellyfin_mpv_shim/menu.py:124
msgid "Quit and Mark Unwatched"
msgstr ""
#: jellyfin_mpv_shim/menu.py:126
msgid "Screenshot"
msgstr ""
#: jellyfin_mpv_shim/menu.py:130
msgid "Video Playback Profiles"
msgstr ""
#: jellyfin_mpv_shim/menu.py:133
msgid "SVP Settings"
msgstr ""
#: jellyfin_mpv_shim/menu.py:136 jellyfin_mpv_shim/menu.py:385
msgid "Preferences"
msgstr ""
#: jellyfin_mpv_shim/menu.py:137
msgid "Close Menu"
msgstr ""
#: jellyfin_mpv_shim/menu.py:215
msgid "Select Audio Track"
msgstr ""
#: jellyfin_mpv_shim/menu.py:236
msgid "Select Subtitle Track"
msgstr ""
#: jellyfin_mpv_shim/menu.py:241
msgid "None"
msgstr ""
#: jellyfin_mpv_shim/menu.py:267
msgid "Select Transcode Quality"
msgstr ""
#: jellyfin_mpv_shim/menu.py:268
msgid "No Transcode"
msgstr ""
#: jellyfin_mpv_shim/menu.py:269
msgid "Maximum"
msgstr ""
#: jellyfin_mpv_shim/menu.py:304
msgid "Select Audio/Subtitle for Series"
msgstr ""
#: jellyfin_mpv_shim/menu.py:305
msgid "English Audio"
msgstr ""
#: jellyfin_mpv_shim/menu.py:306
msgid "Japanese Audio w/ English Subtitles"
msgstr ""
#: jellyfin_mpv_shim/menu.py:307
msgid "Manual by Track Index (Less Reliable)"
msgstr ""
#: jellyfin_mpv_shim/menu.py:334
msgid "Select Default Transcode Profile"
msgstr ""
#: jellyfin_mpv_shim/menu.py:366
msgid "Select Subtitle Color"
msgstr ""
#: jellyfin_mpv_shim/menu.py:372
msgid "Select Subtitle Size"
msgstr ""
#: jellyfin_mpv_shim/menu.py:378
msgid "Select Subtitle Position"
msgstr ""
#: jellyfin_mpv_shim/menu.py:379
msgid "Bottom"
msgstr ""
#: jellyfin_mpv_shim/menu.py:380
msgid "Top"
msgstr ""
#: jellyfin_mpv_shim/menu.py:381
msgid "Middle"
msgstr ""
#: jellyfin_mpv_shim/menu.py:386
msgid "Always Transcode"
msgstr ""
#: jellyfin_mpv_shim/menu.py:387
msgid "Auto Play"
msgstr ""
#: jellyfin_mpv_shim/menu.py:388
msgid "Remote Transcode Quality: {0:0.1f} Mbps"
msgstr ""
#: jellyfin_mpv_shim/menu.py:389
msgid "Subtitle Size: {0}"
msgstr ""
#: jellyfin_mpv_shim/menu.py:390
msgid "Subtitle Position: {0}"
msgstr ""
#: jellyfin_mpv_shim/menu.py:391
msgid "Subtitle Color: {0}"
msgstr ""
#: jellyfin_mpv_shim/menu.py:392
msgid "Transcode H265 to H264"
msgstr ""
#: jellyfin_mpv_shim/menu.py:393
msgid "Transcode Hi10p to 8bit"
msgstr ""
#: jellyfin_mpv_shim/menu.py:394
msgid "Direct Paths"
msgstr ""
#: jellyfin_mpv_shim/menu.py:395
msgid "Auto Fullscreen"
msgstr ""
#: jellyfin_mpv_shim/menu.py:396
msgid "Media Key Seek"
msgstr ""
#: jellyfin_mpv_shim/menu.py:397
msgid "Use Web Seek Pref"
msgstr ""
#: jellyfin_mpv_shim/menu.py:398
msgid "Display Mirroring"
msgstr ""
#: jellyfin_mpv_shim/menu.py:399
msgid "Transcode to H265"
msgstr ""
#: jellyfin_mpv_shim/menu.py:400
msgid "Write Logs to File"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:36
#: jellyfin_mpv_shim/svp_integration.py:48
msgid "Automatic"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:136
msgid "Disabled"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:144
msgid "Select SVP Profile"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:147
msgid "SVP is Not Active"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:148
msgid "Disable"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:149
msgid "Retry"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:152
msgid "SVP is Disabled"
msgstr ""
#: jellyfin_mpv_shim/svp_integration.py:153
msgid "Enable SVP"
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:16
msgid "The specified SyncPlay group does not exist."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:17
msgid "Creating SyncPlay groups is not allowed."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:18
msgid "SyncPlay group access was denied."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:19
msgid "Access to the SyncPlay library was denied."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:128
msgid "SpeedToSync (x{0})"
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:138
msgid "Sync Disabled (Too Many Attempts)"
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:146
msgid "SkipToSync (x{0})"
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:199
msgid "SyncPlay enabled."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:218
msgid "SyncPlay disabled."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:252
msgid "{0} has joined."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:254
msgid "{0} has left."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:256
msgid "{0} is buffering."
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:453 jellyfin_mpv_shim/video_profile.py:149
msgid "None (Disabled)"
msgstr ""
#: jellyfin_mpv_shim/syncplay.py:457
msgid "New Group"
msgstr ""
#: jellyfin_mpv_shim/update_check.py:47
msgid ""
"MPV Shim v{0} Update Available\n"
"Open menu (press c) for details."
msgstr ""
#: jellyfin_mpv_shim/utils.py:236
msgid "Unkn"
msgstr ""
#: jellyfin_mpv_shim/utils.py:237
msgid " Forced"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:13
msgid "Generic (FSRCNNX)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:14
msgid "Generic High (FSRCNNX x16)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:15
msgid "Anime4K x4 Faithful (For SD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:16
msgid "Anime4K x4 Perceptual (For SD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:17
msgid "Anime4K x4 Perceptual + Deblur (For SD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:18
msgid "Anime4K x2 Faithful (For HD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:19
msgid "Anime4K x2 Perceptual (For HD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:20
msgid "Anime4K x2 Perceptual + Deblur (For HD)"
msgstr ""
#: jellyfin_mpv_shim/video_profile.py:160
msgid "Select Shader Profile"
msgstr ""

View File

@ -7,6 +7,7 @@ import multiprocessing
from threading import Event
from . import conffile
from . import i18n
from .conf import settings
from .clients import clientManager
from .constants import APP_NAME
@ -20,6 +21,7 @@ logging.getLogger('requests').setLevel(logging.CRITICAL)
def main(desktop=False, cef=False):
conf_file = conffile.get(APP_NAME, 'conf.json')
settings.load(conf_file)
i18n.configure()
if settings.sanitize_output:
enable_sanitization()

View File

@ -1,4 +1,6 @@
from .conf import settings
from .i18n import _
import urllib.request
import urllib.error
import logging
@ -12,7 +14,7 @@ def list_request(path):
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:
log.error("Could not reach SVP API server.", exc_info=1)
log.error("Could not reach SVP API server.", exc_info=True)
return None
def simple_request(path):
@ -31,7 +33,7 @@ def get_profiles():
if profile_id == "predef":
continue
if profile_id == "P10000001_1001_1001_1001_100000000001":
profile_name = "Automatic"
profile_name = _("Automatic")
else:
profile_name = simple_request("profiles.{0}.title".format(profile_id))
if simple_request("profiles.{0}.on".format(profile_id)) == "false":
@ -43,7 +45,7 @@ def get_profiles():
def get_name_from_guid(profile_id):
profile_id = "P" + profile_id[1:-1].replace("-", "_")
if profile_id == "P10000001_1001_1001_1001_100000000001":
return "Automatic"
return _("Automatic")
else:
return simple_request("profiles.{0}.title".format(profile_id))
@ -55,7 +57,7 @@ def is_svp_alive():
response = list_request("")
return response is not None
except Exception:
log.error("Could not reach SVP API server.", exc_info=1)
log.error("Could not reach SVP API server.", exc_info=True)
return False
def is_svp_enabled():
@ -131,7 +133,7 @@ class SVPManager:
selected = 0
active_profile = get_last_profile()
profile_option_list = [
("Disabled", self.menu_set_profile, None)
(_("Disabled"), self.menu_set_profile, None)
]
for i, (profile_id, profile_name) in enumerate(get_profiles().items()):
profile_option_list.append(
@ -139,14 +141,14 @@ class SVPManager:
)
if profile_id == active_profile:
selected = i+1
self.menu.put_menu("Select SVP Profile", profile_option_list, selected)
self.menu.put_menu(_("Select SVP Profile"), profile_option_list, selected)
else:
if is_svp_enabled():
self.menu.put_menu("SVP is Not Active", [
("Disable", self.menu_set_profile, None),
("Retry", self.menu_set_enabled)
self.menu.put_menu(_("SVP is Not Active"), [
(_("Disable"), self.menu_set_profile, None),
(_("Retry"), self.menu_set_enabled)
], selected=1)
else:
self.menu.put_menu("SVP is Disabled", [
("Enable SVP", self.menu_set_enabled)
self.menu.put_menu(_("SVP is Disabled"), [
(_("Enable SVP"), self.menu_set_enabled)
])

View File

@ -3,6 +3,7 @@ import threading
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
@ -12,10 +13,10 @@ from .conf import settings
log = logging.getLogger('syncplay')
seconds_in_ticks = 10000000
info_commands = {
"GroupDoesNotExist": "The specified SyncPlay group does not exist.",
"CreateGroupDenied": "Creating SyncPlay groups is not allowed.",
"JoinGroupDenied": "SyncPlay group access was denied.",
"LibraryAccessDenied": "Access to the SyncPlay library was denied."
"GroupDoesNotExist": _("The specified SyncPlay group does not exist."),
"CreateGroupDenied": _("Creating SyncPlay groups is not allowed."),
"JoinGroupDenied": _("SyncPlay group access was denied."),
"LibraryAccessDenied": _("Access to the SyncPlay library was denied.")
}
@ -124,7 +125,7 @@ class SyncPlayManager:
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))
self.player_message(_("SpeedToSync (x{0})").format(speed))
def callback():
self.player.speed = 1
@ -134,7 +135,7 @@ class SyncPlayManager:
if self.attempts > settings.sync_attempts:
self.sync_enabled = False
log.info("SyncPlay Sync Disabled due to too many attempts.")
self.player_message("Sync Disabled (Too Many Attempts)")
self.player_message(_("Sync Disabled (Too Many Attempts)"))
return
# Skip To Sync Method
@ -142,7 +143,7 @@ class SyncPlayManager:
self.sync_enabled = False
self.attempts += 1
log.info("SyncPlay Skip to Sync Activated")
self.player_message("SkipToSync (x{0})".format(self.attempts))
self.player_message(_("SkipToSync (x{0})").format(self.attempts))
def callback():
self.sync_enabled = True
@ -195,7 +196,7 @@ class SyncPlayManager:
log.info("Syncplay enabled.")
if from_server:
self.player_message("SyncPlay enabled.")
self.player_message(_("SyncPlay enabled."))
def disable_sync_play(self, from_server):
self.player.speed = self.playback_rate
@ -214,7 +215,7 @@ class SyncPlayManager:
log.info("Syncplay disabled.")
if from_server:
self.player_message("SyncPlay disabled.")
self.player_message(_("SyncPlay disabled."))
# On Buffer
def on_buffer(self):
@ -248,11 +249,11 @@ class SyncPlayManager:
elif command_type == "GroupLeft" or command_type == "NotInGroup":
self.disable_sync_play(True)
elif command_type == "UserJoined":
self.player_message("{0} has joined.".format(command["Data"]))
self.player_message(_("{0} has joined.").format(command["Data"]))
elif command_type == "UserLeft":
self.player_message("{0} has left.".format(command["Data"]))
self.player_message(_("{0} has left.").format(command["Data"]))
elif command_type == "GroupWait":
self.player_message("{0} is buffering.".format(command["Data"]))
self.player_message(_("{0} is buffering.").format(command["Data"]))
else:
log.error("Unknown SyncPlay command {0} payload {1}.".format(command_type, command))
@ -449,11 +450,11 @@ class SyncPlayManager:
selected = 0
offset = 1
group_option_list = [
("None (Disabled)", self.menu_disable, None),
(_("None (Disabled)"), self.menu_disable, None),
]
if not self.is_enabled():
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)
for i, group in enumerate(groups):
group_option_list.append(
@ -461,4 +462,4 @@ class SyncPlayManager:
)
if group["GroupId"] == self.current_group:
selected = i + offset
self.menu.put_menu("SyncPlay", group_option_list, selected)
self.menu.put_menu(_("SyncPlay"), group_option_list, selected)

View File

@ -5,6 +5,7 @@ import webbrowser
from .constants import CLIENT_VERSION
from .conf import settings
from .i18n import _
log = logging.getLogger("update_check")
release_url = "https://github.com/iwalton3/jellyfin-mpv-shim/releases/"
@ -43,7 +44,7 @@ class UpdateChecker:
self.has_notified = True
log.info("Update Available: {0}".format(self.new_version))
self.playerManager._player.show_text(
"MPV Shim v{0} Update Available\nOpen menu (press c) for details.".format(self.new_version),
_("MPV Shim v{0} Update Available\nOpen menu (press c) for details.").format(self.new_version),
5000, 1
)

View File

@ -11,6 +11,7 @@ from .conf import settings
from datetime import datetime
from functools import wraps
from .constants import USER_APP_NAME
from .i18n import _
log = logging.getLogger('utils')
@ -232,8 +233,8 @@ def get_profile(is_remote=False, video_bitrate=None, force_transcode=False, is_t
def get_sub_display_title(stream):
return "{0}{1} ({2})".format(
stream.get("Language", "Unkn").capitalize(),
" Forced" if stream.get("IsForced") else "",
stream.get("Language", _("Unkn")).capitalize(),
_(" Forced") if stream.get("IsForced") else "",
stream.get("Codec")
)

View File

@ -2,12 +2,24 @@ from .conf import settings
from . import conffile
from .utils import get_resource
from .constants import APP_NAME
from .i18n import _
import logging
import os.path
import shutil
import json
import time
profile_name_translation = {
"Generic (FSRCNNX)": _("Generic (FSRCNNX)"),
"Generic High (FSRCNNX x16)": _("Generic High (FSRCNNX x16)"),
"Anime4K x4 Faithful (For SD)": _("Anime4K x4 Faithful (For SD)"),
"Anime4K x4 Perceptual (For SD)": _("Anime4K x4 Perceptual (For SD)"),
"Anime4K x4 Perceptual + Deblur (For SD)": _("Anime4K x4 Perceptual + Deblur (For SD)"),
"Anime4K x2 Faithful (For HD)": _("Anime4K x2 Faithful (For HD)"),
"Anime4K x2 Perceptual (For HD)": _("Anime4K x2 Perceptual (For HD)"),
"Anime4K x2 Perceptual + Deblur (For HD)": _("Anime4K x2 Perceptual + Deblur (For HD)")
}
log = logging.getLogger('video_profile')
class MPVSettingError(Exception):
@ -54,7 +66,7 @@ class VideoProfileManager:
try:
self.defaults[key] = getattr(self.playerManager._player, key)
except Exception:
log.warning("Your MPV does not support setting {0} used in shader pack.".format(key), exc_info=1)
log.warning("Your MPV does not support setting {0} used in shader pack.".format(key), exc_info=True)
if settings.shader_pack_profile is not None:
self.load_profile(settings.shader_pack_profile, reset=False)
@ -102,15 +114,15 @@ class VideoProfileManager:
self.current_profile = profile_name
return True
except MPVSettingError as ex:
log.error("Could not apply shader profile.", exc_info=1)
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 = []
for setting in self.used_settings:
value = self.defaults[setting]
try:
value = self.defaults[setting]
setattr(self.playerManager._player, setting, value)
except Exception:
log.warning("Default setting {0} value {1} is invalid.".format(setting, value))
@ -134,12 +146,15 @@ class VideoProfileManager:
def menu_action(self):
selected = 0
profile_option_list = [
("None (Disabled)", self.menu_handle, None)
(_("None (Disabled)"), self.menu_handle, None)
]
for i, (profile_name, profile) in enumerate(self.profiles.items()):
name = profile["displayname"]
if name in profile_name_translation:
name = profile_name_translation[name]
profile_option_list.append(
(profile["displayname"], self.menu_handle, profile_name)
(name, self.menu_handle, profile_name)
)
if profile_name == self.current_profile:
selected = i+1
self.menu.put_menu("Select Shader Profile", profile_option_list, selected)
self.menu.put_menu(_("Select Shader Profile"), profile_option_list, selected)