Skip to content

Commit

Permalink
Add glsl shader pack and SVP integration.
Browse files Browse the repository at this point in the history
  • Loading branch information
iwalton3 committed Aug 2, 2020
1 parent 8ea63c1 commit e3da03c
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __pycache__
dist
build
plex_mpv_shim.egg-info
plex_mpv_shim/default_shader_pack/*
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The menu enables you to:
- Change subtitles or audio, while knowing the track names.
- Change subtitles or audio for an entire series at once.
- Mark the media as unwatched and quit.
- Configure shader packs and SVP profiles.

On your computer, use the arrow keys, enter, and escape to navigate. On your phone, use
the arrow buttons, ok, back, and home to navigate. (The option for remote controls is
Expand All @@ -40,6 +41,26 @@ Please also note that the on-screen controller for MPV (if available) cannot cha
audio and subtitle track configurations for transcoded media. It also cannot load external
subtitles. You must either use the menu or the application you casted from.

### Shader Packs

Shader packs are a recent feature addition that allows you to easily use advanced video
shaders and video quality settings. These usually require a lot of configuration to use,
but MPV Shim's default shader pack comes with [FSRCNNX](https://github.com/igv/FSRCNN-TensorFlow)
and [Anime4K](https://github.com/bloc97/Anime4K) preconfigured. Try experimenting with video
profiles! It may greatly improve your experience.

Shader Packs are ready to use as of the most recent MPV Shim version. To use, simply
navigate to the **Video Playback Profiles** option and select a profile.

For details on the shader settings, please see [default-shader-pack](https://github.com/iwalton3/default-shader-pack).
If you would like to customize the shader pack, there are details in the configuration section.

### SVP Integration

SVP integration allows you to easily configure SVP support, change profiles, and enable/disable
SVP without having to exit the player. It is not enabled by default, please see the configuration
instructions for instructions on how to enable it.

### Keyboard Shortcuts

This program supports most of the [keyboard shortcuts from MPV](https://mpv.io/manual/stable/#interactive-control). The custom keyboard shortcuts are:
Expand Down Expand Up @@ -151,6 +172,36 @@ You can reconfigure the custom keyboard shortcuts. You can also set them to `nul
- `seek_right` - Time to seek for "right" key. (Default: `5`)
- `seek_left` - Time to seek for "left" key. (Default: `-5`)

### Shader Packs

Shader packs allow you to import MPV config and shader presets into MPV Shim and easily switch
between them at runtime through the built-in menu. This enables easy usage and switching of
advanced MPV video playback options, such as video upscaling, while being easy to use.

If you select one of the presets from the shader pack, it will override some MPV configurations
and any shaders manually specified in `mpv.conf`. If you would like to customize the shader pack,
use `shader_pack_custom`.

- `shader_pack_enable` - Enable shader pack. (Default: `true`)
- `shader_pack_custom` - Enable to use a custom shader pack. (Default: `false`)
- If you enable this, it will copy the default shader pack to the `shader_pack` config folder.
- This initial copy will only happen if the `shader_pack` folder didn't exist.
- This shader pack will then be used instead of the built-in one from then on.
- `shader_pack_remember` - Automatically remember the last used shader profile. (Default: `true`)
- `shader_pack_profile` - The default profile to use. (Default: `null`)
- If you use `shader_pack_remember`, this will be updated when you set a profile through the UI.

### SVP Integration

To enable SVP integration, set `svp_enable` to `true` and enable "External control via HTTP" within SVP
under Settings > Control options. Adjust the `svp_url` and `svp_socket` settings if needed.

- `svp_enable` - Enable SVP integration. (Default: `false`)
- `svp_url` - URL for SVP web API. (Default: `http://127.0.0.1:9901/`)
- `svp_socket` - Custom MPV socket to use for SVP.
- Default on Windows: `mpvpipe`
- Default on other platforms: `/tmp/mpvsocket`

### Other Configuration Options

- `player_name` - The name of the player that appears in the cast menu. Initially set from your hostname.
Expand Down Expand Up @@ -323,7 +374,9 @@ discovery protocol](https://support.plex.tv/articles/201543147-what-network-port
This project is based on https://github.com/wnielson/omplex, which
is available under the terms of the MIT License. The project was ported
to python3, modified to use mpv as the player, and updated to allow all
features of the remote control api for video playback.
features of the remote control api for video playback. The shaders included
in the shader pack are also available under verious open source licenses,
[which you can read about here](https://github.com/iwalton3/default-shader-pack/blob/master/LICENSE.md).

## Linux Installation

Expand Down Expand Up @@ -379,4 +432,5 @@ and libmpv libraries are either 64 or 32 bit. (Don't mismatch them.)
3. Download [libmpv](https://sourceforge.net/projects/mpv-player-windows/files/libmpv/).
4. Extract the `mpv-1.dll` from the file and move it to the `plex-mpv-shim` folder.
5. Open a regular `cmd` prompt. Navigate to the `plex-mpv-shim` folder.
6. Run `pyinstaller -wF --add-binary "mpv-1.dll;." --add-binary "plex_mpv_shim\systray.png;." --icon media.ico run.py`.
6. If you would like the shader pack included, [download it](https://github.com/iwalton3/default-shader-pack) and put the contents into `plex_mpv_shim\default_shader_pack`.
7. Run `pyinstaller -wF --add-binary "mpv-1.dll;." --add-binary "plex_mpv_shim\systray.png;." --icon media.ico run.py`.
7 changes: 7 additions & 0 deletions plex_mpv_shim/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class Settings(object):
"seek_left": -5,
"skip_intro_always": False,
"skip_intro_prompt": True,
"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,
}

def __getattr__(self, name):
Expand Down
26 changes: 26 additions & 0 deletions plex_mpv_shim/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from .bulk_subtitle import process_series
from .conf import settings
from .utils import mpv_color_to_plex
from .video_profile import VideoProfileManager
from .svp_integration import SVPManager
import time
import logging

log = logging.getLogger('menu')

TRANSCODE_LEVELS = (
("1080p 20 Mbps", 20000),
Expand Down Expand Up @@ -52,6 +57,20 @@ def __init__(self, playerManager):
self.original_osd_color = playerManager._player.osd_back_color
self.original_osd_size = playerManager._player.osd_font_size

self.profile_menu = None
if settings.shader_pack_enable:
try:
profile_manager = VideoProfileManager(self, playerManager)
self.profile_menu = profile_manager.menu_action
except Exception:
log.error("Could not load profile manager.", exc_info=True)

self.svp_menu = None
try:
self.svp_menu = SVPManager(self, playerManager)
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.
Expand Down Expand Up @@ -90,11 +109,18 @@ def show_menu(self):
("Change Subtitles", self.change_subtitle_menu),
("Change Video Quality", self.change_transcode_quality),
]
if self.profile_menu is not None:
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))
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([
("Preferences", self.preferences_menu),
Expand Down
145 changes: 145 additions & 0 deletions plex_mpv_shim/svp_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from .conf import settings
import urllib.request
import urllib.error
import logging
import sys

log = logging.getLogger('svp_integration')

def list_request(path):
try:
response = urllib.request.urlopen(settings.svp_url + "?" + path)
return response.read().decode('utf-8').replace('\r\n', '\n').split('\n')
except urllib.error.URLError as ex:
log.error("Could not reach SVP API server.", exc_info=1)
return None

def simple_request(path):
response_list = list_request(path)
if response_list is None:
return None
if len(response_list) != 1 or " = " not in response_list[0]:
return None
return response_list[0].split(" = ")[1]

def get_profiles():
profile_ids = list_request("list=profiles")
profiles = {}
for profile_id in profile_ids:
profile_id = profile_id.replace("profiles.", "")
if profile_id == "P10000001_1001_1001_1001_100000000001":
profile_name = "Automatic"
else:
profile_name = simple_request("profiles.{0}.title".format(profile_id))
profile_guid = "{" + profile_id[1:].replace("_", "-") + "}"
profiles[profile_guid] = profile_name
return 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"
else:
return simple_request("profiles.{0}.title".format(profile_id))

def get_last_profile():
return simple_request("rt.playback.last_profile")

def is_svp_alive():
try:
response = list_request("")
return response is not None
except Exception:
log.error("Could not reach SVP API server.", exc_info=1)
return False

def is_svp_enabled():
return simple_request("rt.disabled") == "false"

def is_svp_active():
response = simple_request("rt.playback.active")
if response is None:
return False
return response != ""

def set_active_profile(profile_id):
# As far as I know, there is no way to directly set the profile.
if not is_svp_active():
return False
if profile_id == get_last_profile():
return True
for i in range(len(list_request("list=profiles"))):
list_request("!profile_next")
if get_last_profile() == profile_id:
return True
return False

def set_disabled(disabled):
return simple_request("rt.disabled={0}".format("true" if disabled else "false")) == "true"

class SVPManager:
def __init__(self, menu, playerManager):
self.menu = menu

if settings.svp_enable:
socket = settings.svp_socket
if socket is None:
if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
socket = "mpvpipe"
else:
socket = "/tmp/mpvsocket"

# This actually *adds* another ipc server.
playerManager._player.input_ipc_server = socket

if not is_svp_alive():
log.error("SVP is not reachable. Please make sure you have the API enabled.")

def is_available(self):
if not settings.svp_enable:
return False
if not is_svp_alive():
return False
return True

def menu_set_profile(self):
profile_id = self.menu.menu_list[self.menu.menu_selection][2]
if profile_id is None:
set_disabled(True)
else:
set_active_profile(profile_id)
# Need to re-render menu.
self.menu.menu_action("back")
self.menu_action()

def menu_set_enabled(self):
set_disabled(False)

# Need to re-render menu.
self.menu.menu_action("back")
self.menu_action()

def menu_action(self):
if is_svp_active():
selected = 0
active_profile = get_last_profile()
profile_option_list = [
("Disabled", self.menu_set_profile, None)
]
for i, (profile_id, profile_name) in enumerate(get_profiles().items()):
profile_option_list.append(
(profile_name, self.menu_set_profile, profile_id)
)
if profile_id == active_profile:
selected = i+1
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)
], selected=1)
else:
self.menu.put_menu("SVP is Disabled", [
("Enable SVP", self.menu_set_enabled)
])
11 changes: 11 additions & 0 deletions plex_mpv_shim/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ipaddress
import uuid
import re
import sys

from .conf import settings
from datetime import datetime
Expand Down Expand Up @@ -148,3 +149,13 @@ def mpv_color_to_plex(color):

def plex_color_to_mpv(color):
return '#FF'+color.upper()[1:]

def get_resource(*path):
# Detect if bundled via pyinstaller.
# From: https://stackoverflow.com/questions/404744/
if getattr(sys, '_MEIPASS', False):
application_path = os.path.join(sys._MEIPASS, "plex_mpv_shim")
else:
application_path = os.path.dirname(os.path.abspath(__file__))

return os.path.join(application_path, *path)
Loading

0 comments on commit e3da03c

Please sign in to comment.