diff --git a/MANIFEST.in b/MANIFEST.in index 913d9f5..48bab37 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,4 +3,5 @@ recursive-include jellyfin_mpv_shim/integration * recursive-include jellyfin_mpv_shim/default_shader_pack * recursive-include jellyfin_mpv_shim/messages *.mo include jellyfin_mpv_shim/mouse.lua -include jellyfin_mpv_shim/trickplay.lua +include jellyfin_mpv_shim/trickplay-osc.lua +include jellyfin_mpv_shim/thumbfast.lua diff --git a/README.md b/README.md index 3e549ba..0716cab 100644 --- a/README.md +++ b/README.md @@ -313,12 +313,12 @@ MPV will automatically display thumbnail previews. By default it uses the Jellyf but it can also use JellyScrub as the source. Please note that this feature will download and uncompress all of the chapter images before it becomes available for a video. For a 4 hour movie this causes disk usage of about 250 MB, but for the average TV episode it is around 40 MB. It also requires -overriding the default MPV OSC, which may conflict with some custom user script. The `trickplay.lua` -file contains the feature if you want to make a modified version. +overriding the default MPV OSC, which may conflict with some custom user script. Trickplay is compatible +with any OSC that uses [thumbfast](https://github.com/po5/thumbfast), as I have added a [compatibility layer](https://github.com/jellyfin/jellyfin-mpv-shim/blob/master/jellyfin_mpv_shim/thumbfast.lua). - `thumbnail_enable` - Enable thumbnail feature. (Default: `true`) - `thumbnail_jellyscrub` - Use JellyScrub as the thumbnail source instead of chapter images. (Default: `false`) - - `thumbnail_custom_script` - If enabled, it disables the default `trickplay.lua` so you can provide a modified version. (Default: `null`) + - `thumbnail_osc_builtin` - Disable this setting if you want to use your own custom osc but leave trickplay enabled. (Default: `true`) - `thumbnail_preferred_size` - The ideal size for thumbnails. (Default: `320`) ### SVP Integration diff --git a/build-win-32.bat b/build-win-32.bat index 0b2c143..6d97636 100644 --- a/build-win-32.bat +++ b/build-win-32.bat @@ -1,7 +1,7 @@ @echo off rd /s /q __pycache__ dist build set PATH=%PATH%;%CD% -pyinstaller -w --add-binary "mpv-2.dll;." --add-data "jellyfin_mpv_shim\mouse.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\trickplay.lua;jellyfin_mpv_shim" --hidden-import pystray._win32 --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\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.py +pyinstaller -w --add-binary "mpv-2.dll;." --add-data "jellyfin_mpv_shim\mouse.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\trickplay-osc.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\thumbfast.lua;jellyfin_mpv_shim" --hidden-import pystray._win32 --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\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.py if %errorlevel% neq 0 exit /b %errorlevel% del dist\run\run.exe.manifest copy hidpi.manifest dist\run\run.exe.manifest diff --git a/build-win.bat b/build-win.bat index f678ffd..3958a7d 100644 --- a/build-win.bat +++ b/build-win.bat @@ -1,7 +1,7 @@ @echo off rd /s /q __pycache__ dist build set PATH=%PATH%;%CD% -pyinstaller -w --add-binary "mpv-2.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --hidden-import pystray._win32 --add-data "jellyfin_mpv_shim\mouse.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\trickplay.lua;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;." --icon jellyfin.ico run.py +pyinstaller -w --add-binary "mpv-2.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --hidden-import pystray._win32 --add-data "jellyfin_mpv_shim\mouse.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\trickplay-osc.lua;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\thumbfast.lua;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;." --icon jellyfin.ico run.py if %errorlevel% neq 0 exit /b %errorlevel% del dist\run\run.exe.manifest copy hidpi.manifest dist\run\run.exe.manifest diff --git a/jellyfin_mpv_shim/conf.py b/jellyfin_mpv_shim/conf.py index f91bd02..b21b652 100644 --- a/jellyfin_mpv_shim/conf.py +++ b/jellyfin_mpv_shim/conf.py @@ -128,7 +128,7 @@ class Settings(SettingsBase): skip_intro_prompt: bool = False thumbnail_enable: bool = True thumbnail_jellyscrub: bool = False - thumbnail_custom_script: Optional[str] = None + thumbnail_osc_builtin: bool = True thumbnail_preferred_size: int = 320 def __get_file(self, path: str, mode: str = "r", create: bool = True): diff --git a/jellyfin_mpv_shim/player.py b/jellyfin_mpv_shim/player.py index 9a99117..16e8559 100644 --- a/jellyfin_mpv_shim/player.py +++ b/jellyfin_mpv_shim/player.py @@ -176,10 +176,10 @@ class PlayerManager(object): self.trickplay = TrickPlay(self) self.trickplay.start() - if settings.thumbnail_custom_script: - scripts.append(settings.thumbnail_custom_script) - else: - scripts.append(get_resource("trickplay.lua")) + scripts.append(get_resource("thumbfast.lua")) + if settings.thumbnail_osc_builtin: + scripts.append(get_resource("trickplay-osc.lua")) + except Exception: log.error("Could not enable trickplay.", exc_info=True) diff --git a/jellyfin_mpv_shim/thumbfast.lua b/jellyfin_mpv_shim/thumbfast.lua new file mode 100644 index 0000000..29ca312 --- /dev/null +++ b/jellyfin_mpv_shim/thumbfast.lua @@ -0,0 +1,111 @@ +local utils = require 'mp.utils' + +img_count = 0 +img_multiplier = 0 +img_width = 0 +img_height = 0 +img_file = "" +img_last_frame = -1 +img_is_shown = false +img_enabled = false +img_is_bif = false +img_chapters = {} +img_overlay_id = 46 + +function send_thumbfast_message() + local json, err = utils.format_json({ + width = img_width, + height = img_height, + disabled = not img_enabled, + available = img_enabled, + overlay_id = img_overlay_id + }) + if err ~= nil + then + mp.log("error", "Failed to format JSON: " .. err) + else + mp.commandv("script-message", "thumbfast-info", json) + end +end + +function client_message_handler(event) + local event_name = event["args"][1] + if event_name == "shim-trickplay-clear" + then + mp.log("info", "Clearing trickplay.") + img_enabled = false + if img_is_shown + then + mp.commandv("overlay-remove", 46) + img_is_shown = false + end + send_thumbfast_message() + elseif event_name == "shim-trickplay-bif" + then + mp.log("info", "Received BIF data.") + img_count = tonumber(event["args"][2]) + img_multiplier = tonumber(event["args"][3]) + img_width = tonumber(event["args"][4]) + img_height = tonumber(event["args"][5]) + img_file = event["args"][6] + img_last_frame = -1 + img_enabled = true + img_is_bif = true + send_thumbfast_message() + elseif event_name == "shim-trickplay-chapters" + then + mp.log("info", "Received chapter metadata.") + img_width = tonumber(event["args"][2]) + img_height = tonumber(event["args"][3]) + img_file = event["args"][4] + + img_chapters = {} + for timestamp in string.gmatch(event["args"][5], '([^,]+)') do + table.insert(img_chapters, tonumber(timestamp)) + end + + img_last_frame = -1 + img_enabled = true + img_is_bif = false + send_thumbfast_message() + elseif event_name == "thumb" + then + local offset_seconds = tonumber(event["args"][2]) + local x = tonumber(event["args"][3]) + local y = tonumber(event["args"][4]) + if offset_seconds == nil or x == nil or y == nil then + return + end + + if img_enabled then + local frame = 0; + if img_is_bif then + frame = math.floor(offset_seconds / (img_multiplier / 1000)) + else + for i = #img_chapters, 1, -1 do + if img_chapters[i] <= offset_seconds then + frame = i - 1 + break + end + end + end + should_render_preview = true + if frame ~= img_last_frame then + if img_is_bif and frame >= img_count then + frame = img_count -1 + end + local offset = frame * img_width * img_height * 4 + img_is_shown = true + mp.commandv("overlay-add", img_overlay_id, x, y, img_file, offset, "bgra", img_width, img_height, img_width * 4) + end + end + elseif event_name == "clear" + then + if img_is_shown + then + mp.commandv("overlay-remove", img_overlay_id) + img_is_shown = false + end + end +end +mp.register_event("client-message", client_message_handler) diff --git a/jellyfin_mpv_shim/trickplay.lua b/jellyfin_mpv_shim/trickplay-osc.lua similarity index 96% rename from jellyfin_mpv_shim/trickplay.lua rename to jellyfin_mpv_shim/trickplay-osc.lua index 04bcd50..8ac045f 100644 --- a/jellyfin_mpv_shim/trickplay.lua +++ b/jellyfin_mpv_shim/trickplay-osc.lua @@ -2,65 +2,29 @@ -- Sadly there is no way to do this without forking the entire OSC script. -- All of my changes are marked with -- BEGIN patch and -- END patch. --- BEGIN patch add thumbnails -img_count = 0 -img_multiplier = 0 -img_width = 0 -img_height = 132 -img_file = "" -img_last_frame = -1 -img_is_shown = false -img_enabled = false -img_is_bif = false -img_chapters = {} - -function client_message_handler(event) - local event_name = event["args"][1] - if event_name == "shim-trickplay-clear" - then - mp.log("info", "Clearing trickplay.") - img_enabled = false - if img_is_shown - then - mp.commandv("overlay-remove", 46) - img_is_shown = false - end - elseif event_name == "shim-trickplay-bif" - then - mp.log("info", "Received BIF data.") - img_count = tonumber(event["args"][2]) - img_multiplier = tonumber(event["args"][3]) - img_width = tonumber(event["args"][4]) - img_height = tonumber(event["args"][5]) - img_file = event["args"][6] - img_last_frame = -1 - img_enabled = true - img_is_bif = true - elseif event_name == "shim-trickplay-chapters" - then - mp.log("info", "Received chapter metadata.") - img_width = tonumber(event["args"][2]) - img_height = tonumber(event["args"][3]) - img_file = event["args"][4] - - img_chapters = {} - for timestamp in string.gmatch(event["args"][5], '([^,]+)') do - table.insert(img_chapters, tonumber(timestamp)) - end - - img_last_frame = -1 - img_enabled = true - img_is_bif = false - end -end -mp.register_event("client-message", client_message_handler) --- END patch add thumbnails - local assdraw = require 'mp.assdraw' local msg = require 'mp.msg' local opt = require 'mp.options' local utils = require 'mp.utils' +-- BEGIN patch add thumbnails +local thumbfast = { + width = 0, + height = 0, + disabled = true, + available = false +} + +mp.register_script_message("thumbfast-info", function(json) + local data = utils.parse_json(json) + if type(data) ~= "table" or not data.width or not data.height then + msg.error("thumbfast-info: received json didn't produce a table with thumbnail information") + else + thumbfast = data + end +end) +-- END patch add thumbnails + -- -- Parameters -- @@ -896,39 +860,23 @@ function render_elements(master_ass) -- BEGIN patch add thumbnails local duration = mp.get_property_number("duration", nil) - if img_enabled and not ((duration == nil) or (sliderpos == nil)) then - local frame = 0; - if img_is_bif then - frame = math.floor(duration * (sliderpos / 100) / (img_multiplier / 1000)) - else - local current_time = duration * (sliderpos / 100) - for i = #img_chapters, 1, -1 do - if img_chapters[i] <= current_time then - frame = i - 1 - break - end - end - end + if thumbfast.available and not ((duration == nil) or (sliderpos == nil)) then should_render_preview = true - if frame ~= img_last_frame then - if img_is_bif and frame >= img_count then - frame = img_count -1 - end - local offset = frame * img_width * img_height * 4 - -- 46 is a random number, but hopefully it won't conflict with other extensions - local sx, sy = get_virt_scale_factor() - local mX, mY = get_virt_mouse_pos() - img_is_shown = true - mp.commandv("overlay-add", 46, math.floor(mX / sx - img_width / 2), math.floor((element.hitbox.y1 - 24) / sy - img_height), img_file, offset, "bgra", img_width, img_height, img_width * 4) - end + local sx, sy = get_virt_scale_factor() + local mX, mY = get_virt_mouse_pos() + mp.commandv("script-message-to", "thumbfast", "thumb", + duration * (sliderpos / 100), + math.floor(mX / sx - thumbfast.width / 2), + math.floor((element.hitbox.y1 - 24) / sy - thumbfast.height) + ) end -- END patch add thumbnails end end -- BEGIN patch add thumbnails - if img_is_shown and not should_render_preview then - mp.commandv("overlay-remove", 46) + if thumbfast.available and not should_render_preview then + mp.commandv("script-message-to", "thumbfast", "clear") end -- END patch add thumbnails @@ -2562,8 +2510,8 @@ function render() render_elements(ass) else -- BEGIN patch add thumbnails - if img_is_shown then - mp.commandv("overlay-remove", 46) + if thumbfast.available then + mp.commandv("script-message-to", "thumbfast", "clear") end -- END patch add thumbnails end