mirror of
https://github.com/jellyfin/jellyfin-mpv-shim.git
synced 2024-11-23 05:59:43 +00:00
Config data validation and warnings with pydantic.
This commit is contained in:
parent
03e2e3fd64
commit
1e104a8c86
@ -527,7 +527,7 @@ If you'd like to run the application without installing it, run `./run.py`.
|
||||
The project is written entirely in Python 3. There are no closed-source
|
||||
components in this project. It is fully hackable.
|
||||
|
||||
The project is dependent on `python-mpv`, `python-mpv-jsonipc`, and `jellyfin-apiclient-python`. If you are
|
||||
The project is dependent on `python-mpv`, `python-mpv-jsonipc`, `pydantic`, and `jellyfin-apiclient-python`. If you are
|
||||
using Windows and would like mpv to be maximize properly, `pywin32` is also needed. The GUI
|
||||
component uses `pystray` and `tkinter`, but there is a fallback cli mode. The mirroring dependencies
|
||||
are `Jinja2` and `pywebview`, along with platform-specific dependencies. (See the installation and building
|
||||
@ -550,7 +550,7 @@ The shaders included in the shader pack are also available under verious open so
|
||||
|
||||
If you are on Windows there are additional dependencies. Please see the Windows Build Instructions.
|
||||
|
||||
1. Install the dependencies: `sudo pip3 install --upgrade python-mpv jellyfin-apiclient-python pystray Jinja2 pywebview python-mpv-jsonipc Flask Werkzeug pypresence`.
|
||||
1. Install the dependencies: `sudo pip3 install --upgrade python-mpv jellyfin-apiclient-python pystray Jinja2 pywebview python-mpv-jsonipc Flask Werkzeug pypresence pydantic`.
|
||||
- If you run `./gen_pkg.sh --install`, it will also fetch these for you.
|
||||
2. Clone this repository: `git clone https://github.com/iwalton3/jellyfin-mpv-shim`
|
||||
- You can also download a zip build.
|
||||
@ -661,7 +661,7 @@ You may also need to edit the batch file for 32 bit builds to point to the right
|
||||
1. Install Git for Windows. Open Git Bash and run `git clone https://github.com/iwalton3/jellyfin-mpv-shim; cd jellyfin-mpv-shim`.
|
||||
- You can update the project later with `git pull`.
|
||||
2. Install [Python3](https://www.python.org/downloads/) with PATH enabled. Install [7zip](https://ninite.com/7zip/).
|
||||
3. After installing python3, open `cmd` as admin and run `pip install --upgrade pyinstaller python-mpv jellyfin-apiclient-python pywin32 pystray Jinja2 pywebview[cef] python-mpv-jsonipc Flask Werkzeug pypresence`.
|
||||
3. After installing python3, open `cmd` as admin and run `pip install --upgrade pyinstaller python-mpv jellyfin-apiclient-python pywin32 pystray Jinja2 pywebview[cef] python-mpv-jsonipc Flask Werkzeug pypresence pydantic`.
|
||||
4. Download [libmpv](https://sourceforge.net/projects/mpv-player-windows/files/libmpv/).
|
||||
5. Extract the `mpv-1.dll` from the file and move it to the `jellyfin-mpv-shim` folder.
|
||||
6. Open a regular `cmd` prompt. Navigate to the `jellyfin-mpv-shim` folder.
|
||||
|
@ -1,180 +1,160 @@
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import pickle as pickle
|
||||
import socket
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
log = logging.getLogger("conf")
|
||||
config_path = None
|
||||
|
||||
|
||||
class Settings(object):
|
||||
_listeners = []
|
||||
|
||||
_path = None
|
||||
_data = {
|
||||
"player_name": socket.gethostname(),
|
||||
"audio_output": "hdmi",
|
||||
"client_uuid": str(uuid.uuid4()),
|
||||
"media_ended_cmd": None,
|
||||
"pre_media_cmd": None,
|
||||
"stop_cmd": None,
|
||||
"auto_play": True,
|
||||
"idle_cmd": None,
|
||||
"idle_cmd_delay": 60,
|
||||
"direct_paths": False,
|
||||
"remote_direct_paths": False,
|
||||
"always_transcode": False,
|
||||
"transcode_h265": False,
|
||||
"transcode_hi10p": False,
|
||||
"remote_kbps": 10000,
|
||||
"local_kbps": 2147483,
|
||||
"subtitle_size": 100,
|
||||
"subtitle_color": "#FFFFFFFF",
|
||||
"subtitle_position": "bottom",
|
||||
"fullscreen": True,
|
||||
"enable_gui": True,
|
||||
"media_key_seek": False,
|
||||
"mpv_ext": sys.platform.startswith("darwin"),
|
||||
"mpv_ext_path": None,
|
||||
"mpv_ext_ipc": None,
|
||||
"mpv_ext_start": True,
|
||||
"mpv_ext_no_ovr": False,
|
||||
"enable_osc": True,
|
||||
"use_web_seek": False,
|
||||
"display_mirroring": False,
|
||||
"log_decisions": False,
|
||||
"mpv_log_level": "info",
|
||||
"enable_desktop": False,
|
||||
"desktop_fullscreen": False,
|
||||
"desktop_keep_pos": False,
|
||||
"desktop_keep_size": True,
|
||||
"idle_when_paused": False,
|
||||
"stop_idle": False,
|
||||
"transcode_to_h265": False,
|
||||
"kb_stop": "q",
|
||||
"kb_prev": "<",
|
||||
"kb_next": ">",
|
||||
"kb_watched": "w",
|
||||
"kb_unwatched": "u",
|
||||
"kb_menu": "c",
|
||||
"kb_menu_esc": "esc",
|
||||
"kb_menu_ok": "enter",
|
||||
"kb_menu_left": "left",
|
||||
"kb_menu_right": "right",
|
||||
"kb_menu_up": "up",
|
||||
"kb_menu_down": "down",
|
||||
"kb_pause": "space",
|
||||
"kb_fullscreen": "f",
|
||||
"kb_debug": "~",
|
||||
"kb_kill_shader": "k",
|
||||
"seek_up": 60,
|
||||
"seek_down": -60,
|
||||
"seek_right": 5,
|
||||
"seek_left": -5,
|
||||
"shader_pack_enable": True,
|
||||
"shader_pack_custom": False,
|
||||
"shader_pack_remember": True,
|
||||
"shader_pack_profile": None,
|
||||
"svp_enable": False,
|
||||
"svp_url": "http://127.0.0.1:9901/",
|
||||
"svp_socket": None,
|
||||
"sanitize_output": True,
|
||||
"write_logs": False,
|
||||
"playback_timeout": 30,
|
||||
"sync_max_delay_speed": 50,
|
||||
"sync_max_delay_skip": 300,
|
||||
"sync_method_thresh": 2000,
|
||||
"sync_speed_time": 1000,
|
||||
"sync_speed_attempts": 3,
|
||||
"sync_attempts": 5,
|
||||
"sync_revert_seek": True,
|
||||
"sync_osd_message": True,
|
||||
"screenshot_menu": True,
|
||||
"check_updates": True,
|
||||
"notify_updates": True,
|
||||
"lang": None,
|
||||
"desktop_scale": 1.0,
|
||||
"discord_presence": False,
|
||||
"ignore_ssl_cert": False,
|
||||
"menu_mouse": True,
|
||||
"media_keys": True,
|
||||
"connect_retry_mins": 0,
|
||||
"transcode_warning": True,
|
||||
"lang_filter": "und,eng,jpn,mis,mul,zxx",
|
||||
"lang_filter_sub": False,
|
||||
"lang_filter_audio": False,
|
||||
}
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._data[name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self._data:
|
||||
self._data[name] = value
|
||||
self.save()
|
||||
|
||||
for callback in self._listeners:
|
||||
try:
|
||||
callback(name, value)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
super(Settings, self).__setattr__(name, value)
|
||||
class Settings(BaseModel):
|
||||
player_name: str = socket.gethostname()
|
||||
audio_output: str = "hdmi"
|
||||
client_uuid: str = str(uuid.uuid4())
|
||||
media_ended_cmd: Optional[str] = None
|
||||
pre_media_cmd: Optional[str] = None
|
||||
stop_cmd: Optional[str] = None
|
||||
auto_play: bool = True
|
||||
idle_cmd: Optional[str] = None
|
||||
idle_cmd_delay: int = 60
|
||||
direct_paths: bool = False
|
||||
remote_direct_paths: bool = False
|
||||
always_transcode: bool = False
|
||||
transcode_h265: bool = False
|
||||
transcode_hi10p: bool = False
|
||||
remote_kbps: int = 10000
|
||||
local_kbps: int = 2147483
|
||||
subtitle_size: int = 100
|
||||
subtitle_color: str = "#FFFFFFFF"
|
||||
subtitle_position: str = "bottom"
|
||||
fullscreen: bool = True
|
||||
enable_gui: bool = True
|
||||
media_key_seek: bool = False
|
||||
mpv_ext: bool = sys.platform.startswith("darwin")
|
||||
mpv_ext_path: Optional[str] = None
|
||||
mpv_ext_ipc: Optional[str] = None
|
||||
mpv_ext_start: bool = True
|
||||
mpv_ext_no_ovr: bool = False
|
||||
enable_osc: bool = True
|
||||
use_web_seek: bool = False
|
||||
display_mirroring: bool = False
|
||||
log_decisions: bool = False
|
||||
mpv_log_level: str = "info"
|
||||
enable_desktop: bool = False
|
||||
desktop_fullscreen: bool = False
|
||||
desktop_keep_pos: bool = False
|
||||
desktop_keep_size: bool = True
|
||||
idle_when_paused: bool = False
|
||||
stop_idle: bool = False
|
||||
transcode_to_h265: bool = False
|
||||
kb_stop: str = "q"
|
||||
kb_prev: str = "<"
|
||||
kb_next: str = ">"
|
||||
kb_watched: str = "w"
|
||||
kb_unwatched: str = "u"
|
||||
kb_menu: str = "c"
|
||||
kb_menu_esc: str = "esc"
|
||||
kb_menu_ok: str = "enter"
|
||||
kb_menu_left: str = "left"
|
||||
kb_menu_right: str = "right"
|
||||
kb_menu_up: str = "up"
|
||||
kb_menu_down: str = "down"
|
||||
kb_pause: str = "space"
|
||||
kb_fullscreen: str = "f"
|
||||
kb_debug: str = "~"
|
||||
kb_kill_shader: str = "k"
|
||||
seek_up: int = 60
|
||||
seek_down: int = -60
|
||||
seek_right: int = 5
|
||||
seek_left: int = -5
|
||||
shader_pack_enable: bool = True
|
||||
shader_pack_custom: bool = False
|
||||
shader_pack_remember: bool = True
|
||||
shader_pack_profile: Optional[str] = None
|
||||
svp_enable: bool = False
|
||||
svp_url: str = "http://127.0.0.1:9901/"
|
||||
svp_socket: Optional[str] = None
|
||||
sanitize_output: bool = True
|
||||
write_logs: bool = False
|
||||
playback_timeout: int = 30
|
||||
sync_max_delay_speed: int = 50
|
||||
sync_max_delay_skip: int = 300
|
||||
sync_method_thresh: int = 2000
|
||||
sync_speed_time: int = 1000
|
||||
sync_speed_attempts: int = 3
|
||||
sync_attempts: int = 5
|
||||
sync_revert_seek: bool = True
|
||||
sync_osd_message: bool = True
|
||||
screenshot_menu: bool = True
|
||||
check_updates: bool = True
|
||||
notify_updates: bool = True
|
||||
lang: Optional[str] = None
|
||||
desktop_scale: float = 1.0
|
||||
discord_presence: bool = False
|
||||
ignore_ssl_cert: bool = False
|
||||
menu_mouse: bool = True
|
||||
media_keys: bool = True
|
||||
connect_retry_mins: int = 0
|
||||
transcode_warning: bool = True
|
||||
lang_filter: str = "und,eng,jpn,mis,mul,zxx"
|
||||
lang_filter_sub: bool = False
|
||||
lang_filter_audio: bool = False
|
||||
|
||||
def __get_file(self, path, mode="r", create=True):
|
||||
created = False
|
||||
|
||||
if not os.path.exists(path):
|
||||
try:
|
||||
fh = open(path, mode)
|
||||
_fh = open(path, mode)
|
||||
except IOError as e:
|
||||
if e.errno == 2 and create:
|
||||
fh = open(path, "w")
|
||||
json.dump(self._data, fh, indent=4, sort_keys=True)
|
||||
json.dump(self.dict(), fh, indent=4, sort_keys=True)
|
||||
fh.close()
|
||||
created = True
|
||||
else:
|
||||
raise e
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
log.error("Error opening settings from path: %s" % path)
|
||||
return None
|
||||
|
||||
# This should work now
|
||||
return open(path, mode), created
|
||||
|
||||
def migrate_config(self, old_path, new_path):
|
||||
fh, created = self.__get_file(old_path, "rb+", False)
|
||||
if not created:
|
||||
try:
|
||||
data = pickle.load(fh)
|
||||
self._data.update(data)
|
||||
except Exception as e:
|
||||
log.error("Error loading settings from pickle: %s" % e)
|
||||
fh.close()
|
||||
return False
|
||||
|
||||
os.remove(old_path)
|
||||
self._path = new_path
|
||||
fh.close()
|
||||
self.save()
|
||||
return True
|
||||
|
||||
def load(self, path, create=True):
|
||||
global config_path # Don't want in model.
|
||||
fh, created = self.__get_file(path, "r", create)
|
||||
self._path = path
|
||||
config_path = path
|
||||
if not created:
|
||||
try:
|
||||
data = json.load(fh)
|
||||
safe_data = self.parse_obj(data)
|
||||
|
||||
# Copy and count items
|
||||
input_params = 0
|
||||
for key in safe_data.__fields_set__:
|
||||
setattr(self, key, getattr(safe_data, key))
|
||||
input_params += 1
|
||||
|
||||
# Print warnings
|
||||
for key, value in data.items():
|
||||
if key in self._data:
|
||||
input_params += 1
|
||||
self._data[key] = value
|
||||
if key not in safe_data.__fields_set__:
|
||||
log.warning("Config item {0} was ignored.".format(key))
|
||||
elif value != getattr(safe_data, key):
|
||||
log.warning(
|
||||
"Config item {0} was was coerced from {1} to {2}.".format(
|
||||
key, repr(value), repr(getattr(safe_data, key))
|
||||
)
|
||||
)
|
||||
|
||||
log.info("Loaded settings from json: %s" % path)
|
||||
if input_params < len(self._data):
|
||||
if input_params < len(self.__fields__):
|
||||
log.info("Saving back due to schema change.")
|
||||
self.save()
|
||||
except Exception as e:
|
||||
log.error("Error loading settings from json: %s" % e)
|
||||
@ -185,10 +165,10 @@ class Settings(object):
|
||||
return True
|
||||
|
||||
def save(self):
|
||||
fh, created = self.__get_file(self._path, "w", True)
|
||||
fh, created = self.__get_file(config_path, "w", True)
|
||||
|
||||
try:
|
||||
json.dump(self._data, fh, indent=4, sort_keys=True)
|
||||
json.dump(self.dict(), fh, indent=4, sort_keys=True)
|
||||
fh.flush()
|
||||
fh.close()
|
||||
except Exception as e:
|
||||
@ -197,17 +177,5 @@ class Settings(object):
|
||||
|
||||
return True
|
||||
|
||||
def add_listener(self, callback):
|
||||
"""
|
||||
Register a callback to be called anytime a setting value changes.
|
||||
An example callback function:
|
||||
|
||||
def my_callback(key, value):
|
||||
# Do something with the new setting ``value``...
|
||||
|
||||
"""
|
||||
if callback not in self._listeners:
|
||||
self._listeners.append(callback)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
Loading…
Reference in New Issue
Block a user