From 9bdce0050144cb24f92475f7bdd77180e0e4c26b Mon Sep 17 00:00:00 2001 From: zenyd <30322274+zenyd@users.noreply.github.com> Date: Mon, 12 Feb 2024 03:28:35 +0100 Subject: [PATCH] update speed-transition * use sub-end instead of sub-text. Disadvantage is that ignoring subtitles is broken right now. Advantage: image based subtitles are working. For dvd subtitles (that's what I tested) it can happen that some subtitle lines, get 'eaten'. They don't show even though they should. * implement sub-timeout: when subtitles are displayed without change for some time (5s) -> speedup. (Useful for autogenerated subtitles) * improvements to skip mode: - better behaviour if next subtitle is not in cache - option for non-exact skips. This also implements seek-back since a skip is not guaranteed to end at the next line. It might skip over it. * users can now supply a script configuration file (speed_transition.conf) * remove key binding for sub visibility. Pressing v seems to handle this nicely. * change key binding for skip mode to alt + j * added bunch of debugging code to better debug issues --- README.md | 46 ++- speed-transition.lua | 796 ++++++++++++++++++++++++++++--------------- 2 files changed, 559 insertions(+), 283 deletions(-) diff --git a/README.md b/README.md index 40e351f..410998f 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,69 @@ ## speed-transition + This is a lua script for the mpv media player. The purpose of this script is to speed up the video if no subtitles are visible for a certain amount of (user configurable) time. It is inspired by the [speed-between-subs](https://gist.github.com/bitingsock/47c5ba6466c63c68bcf991dd376f1d18) script. ### How it works + The script looks for the next subtitle and if it is ahead by 5 (default) seconds the video gets sped up and resumes normal playback just before the subtitle becomes visible. This is done to prevent audio glitches when speech starts. ### Usage -For the script to work it is necessary to have an appropriate 'text' subtitle selected and visible. -The script works best in `video-sync=audio` mode (the default in mpv), because it will then be able to minimize frame drops on speed transition from high->normal. Stutter-free playback is the result. +For the script to work it is necessary to have an appropriate subtitle selected. It's possible to hide the subtitles with `v` and still have the benefit of the script. To enable it press `ctrl + j` and to switch modes press `alt + j`. + +It works best in `video-sync=audio` mode (the default in mpv), because it will then be able to minimize frame drops on speed transition from high->normal. Stutter-free playback is the result. Sensible defaults have been set, but if you want to change the `lookahead` value take care to not set it larger than what the buffers can provide. This applies to embedded subtitles and not external. -**skipmode:** -There is an alternative speedup mode called 'skipmode'. In this mode the video doesn't speed up, but it gets skipped till the next subtitle. It can be configured to skip directly to the next subtitle or in steps. To enable skipmode set `skipmode = true` and to enable direct skip set `directskip = true`. +#### operation modes + +The script supports two operation modes *speed mode* (default) and *skip mode*. In *skip mode* the video doesn't speed up, but it gets skipped (with delay) till the next subtitle. It can be configured to skip directly to the next subtitle or in steps. To switch between them press `alt + j`. + +*skip mode* can be further configured to allow non-exact but fast skips. Disadvantage: it is likely that the the next subtitle gets skipped over. Therefore seek-back is implemented so the video gets seeked back just before the skip to aleviate this problem. In general leaving exact skips on (default) is recommended. + +#### Key binds Key Bind|Effect ---------|------ +--------|-------- `ctrl + j`|Toggle script on/off -`ctrl + alt+ j`|Toggle skip mode -`alt + j`|Toggle sub visibility on/off (non-styled subs) +`alt + j`|switch modes (speed up/skip) - also enables script if disabled `alt + '+'`|Increase speedup `alt + '-'`|Decrease speedup `alt + '0'`|Increase leadin (time before the next subtitle to return to normal speed) `alt + '9'`|Decrease leadin -`alt + '8'`|Increase Look ahead (if the next subtitle is closer than this, don't speed up) -`alt + '7'`|Decrease Look ahead +`alt + '8'`|Increase lookahead (if the next subtitle is closer than this, don't speed up) +`alt + '7'`|Decrease lookahead +#### Options + +Option|Description +--------|-------- +lookahead|if the next subtitle appears after this threshold then speedup/skip +speedup|the video speed set during speedup +maxSkip|max. seconds to skip +directskip|directly skip to next subtitle +exact_skip|use slow but accurate skips (default) + +These and more options can be set in a `speed_transition.conf` file in the `script-opts` folder. ## subselect + A lua script for downloading subtitles using a GUI and automatically loading them in mpv. It lets you input the name of the video but mainly tries to guess it based on the video title. Uses subliminal for subtitle download and Python tkinter for GUI. Works both on Windows and Linux (possibly macOS too?). Right now it only lists subtitles from OpenSubtitles, but has the ability to search for the best subtitle which searches all subtitle providers. ### Prerequisits + 1. Install Python 3 2. Make sure Python is in your PATH 3. Linux: Depending on the used distribution installation of `pip` and `tk` may be necessary 3. Install subliminal: `python -m pip install subliminal` should do the trick ### Installation + Copy the `subselect` folder containing `main.lua` and `subselect.py` into your script folder. If you have previous installations of subselect where it was not inside the subselect sub folder, remove them. ### Configuration + Changing the configuration is optional. Options: * *down_dir*: set the download path for the subtitles * *sub_language*: set language for subtitles [default english]; value is a 3-letter ISO-639-3 language code @@ -56,10 +78,12 @@ sub_language=deu ``` ### Usage + First invoke the script using `alt + u`, input a movie name, or use the one provided by the script, search and download subtitles. If you want to change the language for the subtitles append `;[3-letter ISO-639-3 code]`. So if you want to search for e.g. german subtitles append `;deu`. ## delete-file + As the name suggests this is a small script for deleting files played through mpv. You can mark a file to be deleted and can unmark it if desired. Once quitting mpv, the script starts the deletion process. This is platform-agnostic so should work everywhere. Key Bind|Effect @@ -74,10 +98,12 @@ MoveToFolder=yes ``` ## copy-paste-URL + Like its name suggests - copy and paste links into mpv with `ctrl + v` to start playback of a video. This needs an open mpv window like `mpv --idle --force-window` or a window already playing a video. Also the script utilizes powershell, so that needs to be installed as well. ## censor + Skip over parts of videos you don't want (others) to view. ##download and installation @@ -85,7 +111,7 @@ Skip over parts of videos you don't want (others) to view. download only "censor" folder and its contents. put the "censor" folder(along with files in it i.e "names" folder and "main.lua") in the "scripts" folder inside mpv configuration folder. -**Usage:** +**Usage:** imagine you want to skip the intro which begins on timestamp 0:2:30 and lasts until timestamp 0:4:00 your anime is called Attack on Titan - 03.mkv. So you create a text file named Attack on Titan - 03.txt inside the names folder. diff --git a/speed-transition.lua b/speed-transition.lua index e25f577..12212ab 100644 --- a/speed-transition.lua +++ b/speed-transition.lua @@ -1,286 +1,536 @@ -lookahead = 5 --if the next subtitle appears after this threshold then speedup -speedup = 2 --the value that "speed" is set to during speedup -leadin = 1 --seconds to stop short of the next subtitle -skipmode = false --instead of speeding up playback seek to the next known subtitle -maxSkip = 5 --max seek distance (seconds) when skipmode is enabled -minSkip = leadin --this is also configurable but setting it too low can actually make your watch time longer -skipdelay = 1 --in skip mode, this setting delays each skip by x seconds (must be >=0) -directskip = false --seek to next known subtitle (must be in cache) no matter how far away -dropOnAVdesync = true ---Because mpv syncs subtitles to audio it is possible that if audio processing lags behind-- ---video processing then normal playback may not resume in sync with the video. If "avsync" > leadin-- ---then this disables the audio so that we can ensure normal playback resumes on time. -ignorePattern = false --if true, subtitles are matched against "subPattern". A successful match will be treated as if there was no subtitle -subPattern = "^[#♯♩♪♬♫🎵🎶%[%(]+.*[#♯♩♪♬♫🎵🎶%]%)]+$" ----------------User options above this line-- +local opt = require 'mp.options' +local msg = require 'mp.msg' -readahead_secs = mp.get_property_native("demuxer-readahead-secs") -normalspeed=mp.get_property_native("speed") +cfg = { + lookahead = 5, --if the next subtitle appears after this threshold then speedup + speedup = 2, --the value that 'speed' is set to during speedup + leadin = 1, --seconds to stop short of the next subtitle + sub_timeout = 5, --if a subtitle is visible for longer than this value, speedup begins; set to 0 to disable + skipmode = false, --instead of speeding up playback seek to the next known subtitle + maxSkip = 2.5, --max skip distance (seconds) when skipmode is enabled + minSkip = 1, --this is also configurable but setting it too low can actually make your watch time longer + skipdelay = 0.8, --in skip mode, this setting delays each skip by x seconds (must be >=0) + directskip = false, --seek to next known subtitle (must be in cache) no matter how far away + exact_skip = true, --use accurate but slow skips + --Because mpv syncs subtitles to audio it is possible that if audio processing lags behind-- + --video processing then normal playback may not resume in sync with the video. If 'avsync' > leadin-- + --then this disables the audio so that we can ensure normal playback resumes on time. + dropOnAVdesync = true, + ignorePattern = false, --if true, subtitles are matched against 'subPattern'. A successful match will be treated as if there was no subtitle + subPattern = '^[#♯♩♪♬♫🎵🎶%[%(]+.*[#♯♩♪♬♫🎵🎶%]%)]+$' +} -function shouldIgnore(subtext) - if ignorePattern and subtext and subtext~="" then - local st = subtext:match("^%s*(.-)%s*$") -- trim whitespace - if st:find(subPattern) then - return true - end - else - return false - end -end +opt.read_options(cfg) -function set_timeout() - local time_out - if mp.get_property_native("cache-size") ~= nil then - time_out = mp.get_property_native("cache-secs") - else - time_out = mp.get_property_native("demuxer-readahead-secs") - end - return time_out -end - -local aid -function restore_normalspeed() - mp.set_property("speed", normalspeed) - if mp.get_property_native("video-sync") == "desync" then - mp.set_property("video-sync", "audio") - end - if (aid~=nil and aid~=mp.get_property("aid")) then mp.set_property("aid", aid) end -end - -function check_should_speedup() - local subdelay = mp.get_property_native("sub-delay") - mp.command("no-osd set sub-visibility no") - mp.command("no-osd sub-step 1") - local mark = mp.get_property_native("time-pos") - local nextsubdelay = mp.get_property_native("sub-delay") - local nextsub = subdelay - nextsubdelay - if ignorePattern and nextsub > 0 then - local lookNext = true - local ignore = shouldIgnore(mp.get_property("sub-text")) - while ignore and lookNext do - local delay1 = mp.get_property_native("sub-delay") - mp.command("no-osd sub-step 1") - local delay2 = mp.get_property_native("sub-delay") - ignore = shouldIgnore(mp.get_property("sub-text")) - if delay1 == delay2 then - lookNext = false - nextsub = 0 - else - nextsub = subdelay - delay2 - end - end - end - mp.set_property("sub-delay", subdelay) - mp.command("no-osd set sub-visibility yes") - return nextsub, nextsub >= lookahead or nextsub == 0, mark -end - -function check_audio(_,ds) - if state==0 then - return - elseif ds and tonumber(ds)>leadin and mp.get_property("aid")~="no" then - aid = mp.get_property("aid") - mp.set_property("aid", "no") - print("avsync greater than leadin, dropping audio") - end -end - -function check_position(_, position) - if position then - if nextsub ~= 0 and position >= (mark+nextsub-leadin) then - restore_normalspeed() - mp.unobserve_property(check_position) - mp.unobserve_property(check_audio) - elseif nextsub == 0 and position >= (mark+set_timeout()-leadin) then - nextsub, _ , mark = check_should_speedup() - end - end -end - -function skipval() - local skipval = mp.get_property_native("demuxer-cache-duration", 0) - if nextsub > 0 then - if directskip then - skipval = nextsub - leadin - elseif nextsub - skipval - leadin <= 0 then - skipval = clamp(nextsub - leadin, 0, maxSkip) - else - skipval = clamp(skipval, 0, maxSkip) - end - elseif directskip then - skipval = clamp(skipval - leadin, 1, nil) - else - skipval = clamp(skipval - leadin, 1, maxSkip) - end - return skipval -end - -firstskip = true --make the first skip in skip mode not have to wait for skipdelay -delayEnd = true -function speed_transition(_, sub) - if sub~=nil and shouldIgnore(sub) then - sub = "" - end; - if state == 0 then - local subcodec = mp.get_property("current-tracks/sub/codec") -      if sub == "" and subcodec and (subcodec=="subrip" or subcodec=="ass" or subcodec=="webvtt") then - last_speedup_zone_begin = speedup_zone_begin - nextsub, shouldspeedup, speedup_zone_begin = check_should_speedup() - mark = speedup_zone_begin - speedup_zone_end = mark + nextsub - if shouldspeedup or (skipmode and not firstskip) then - local temp_disable_skipmode = false - if last_speedup_zone_begin and mark < last_speedup_zone_begin then - temp_disable_skipmode = true - end - if skipmode and not temp_disable_skipmode and mp.get_property("pause") == "no" then - if firstskip or skipdelay == 0 then - mp.commandv("no-osd", "seek", skipval(), "relative", "exact") - firstskip = false - elseif delayEnd then - delayEnd = false - mp.add_timeout(skipdelay, function() - delayEnd = true - if mp.get_property("pause") == "no" then - nextsub, shouldskip = check_should_speedup() - if shouldskip or nextsub > leadin then - local tSkip = skipval() - currentSub = mp.get_property("sub-text") - if tSkip > minSkip and (currentSub == "" or shouldIgnore(currentSub)) then - mp.commandv("no-osd", "seek", tSkip, "relative", "exact") - else - firstskip = true - end - end - end - end) - end - else - normalspeed = mp.get_property("speed") - if mp.get_property_native("video-sync") == "audio" then - mp.set_property("video-sync", "desync") - end - mp.set_property("speed", speedup) - mp.observe_property("time-pos", "native", check_position) - state = 1 - if dropOnAVdesync then - aid = mp.get_property("aid") - mp.observe_property("avsync", "native", check_audio) - end - end - else - firstskip = true - end - end - elseif state == 1 then - if (sub ~= "" and sub ~= nil) or not mp.get_property_native("sid") then - mp.unobserve_property(check_position) - mp.unobserve_property(check_audio) - restore_normalspeed() - state = 0 - else - local pos = mp.get_property_native("time-pos", 0) - if pos < speedup_zone_begin or pos > speedup_zone_end then - nextsub, _ , mark = check_should_speedup() - end - end - end -end - -toggle2 = false - -function toggle_sub_visibility() - if not toggle2 then - sub_color = mp.get_property("sub-color", "1/1/1/1") - sub_color2 = mp.get_property("sub-border-color", "0/0/0/1") - sub_color3 = mp.get_property("sub-shadow-color", "0/0/0/1") - mp.set_property("sub-color", "0/0/0/0") - mp.set_property("sub-border-color", "0/0/0/0") - mp.set_property("sub-shadow-color", "0/0/0/0") - else - mp.set_property("sub-color", sub_color) - mp.set_property("sub-border-color", sub_color2) - mp.set_property("sub-shadow-color", sub_color3) - end - mp.osd_message("subtitle visibility: "..tostring(toggle2)) - toggle2 = not toggle2 -end - -function toggle_skipmode() - skipmode = not skipmode - if enable then - mp.unobserve_property(speed_transition) - mp.unobserve_property(check_position) - mp.observe_property("sub-text", "native", speed_transition) - state = 0 - end - if skipmode then - mp.osd_message("skip mode") - else - mp.osd_message("speed mode") - end -end - -function clamp(v,l,u) - if l and v < l then - v = l - elseif u and v > u then - v = u - end - return v -end - -function change_speedup(v) - speedup = speedup + v - mp.osd_message("speedup: "..speedup) -end - -function change_leadin(v) - --leadin = clamp(leadin + v, 0, 2) - leadin = clamp(leadin + v, 0, nil) - mp.osd_message("leadin: "..leadin) -end - -function change_lookAhead(v) - lookahead = clamp(lookahead + v , 0, nil) - mp.osd_message("lookahead: "..lookahead) -end +readahead_secs = mp.get_property_native('demuxer-readahead-secs') +normalspeed = mp.get_property_native('speed') enable = false state = 0 +firstskip = true --make the first skip in skip mode not have to wait for skipdelay +aid = nil + +function shouldIgnore(subtext) + if cfg.ignorePattern and subtext and subtext ~= '' then + local st = subtext:match('^%s*(.-)%s*$') -- trim whitespace + if st:find(cfg.subPattern) then + return true + end + else + return false + end +end + +function clamp(v, l, u) + if l and v < l then + v = l + elseif u and v > u then + v = u + end + return v +end + +function formatTime(s) + if not s then + return nil + end + + s = math.abs(s) + local _s = s % 60 + s = s / 60 + local m = math.floor(s % 60) + s = s / 60 + local h = math.floor(s) + return string.format('%02d:%02d:%02f', h, m, _s) +end + +function sleep(s) + local ntime = os.clock() + s + repeat until os.clock() > ntime +end + +function reset_state() + nextsub, shouldspeedup, speedup_zone_begin, speedup_zone_end = nil, false, nil, nil + last_speedup_zone_begin = nil + last_skip_position = nil + last_nextsub_check = nil + firstskip = true + state = 0 +end + +function restore_normalspeed() + if not cfg.skipmode then + mp.set_property('speed', normalspeed) + if video_sync then + mp.set_property('video-sync', video_sync) + end + end + if aid and aid ~= mp.get_property('aid') then + mp.set_property('aid', aid) + end +end + +function speed_up() + normalspeed = mp.get_property('speed') + video_sync = mp.get_property('video-sync') + mp.set_property('video-sync', 'desync') + mp.set_property('speed', cfg.speedup) + if cfg.dropOnAVdesync then + aid = mp.get_property('aid') + mp.observe_property('avsync', 'native', check_audio) + end +end + +function skip(skipval) + if skipval < cfg.minSkip then + msg.warn('skip(): tskip < minSkip; abort!') + return + end + if cfg.exact_skip then + mp.commandv('seek', skipval, 'relative', 'exact') + else + mp.commandv('seek', skipval, 'relative') + end +end + +function delayskip(position, skipdelay) + if not (firstskip or skipdelay == 0) then + sleep(skipdelay) + local tposition = mp.get_property_number('time-pos') + if not tposition then + position = position + skipdelay + else + position = tposition + end + end + + firstskip = false + return position +end + +function skipval(nextsub) + local demuxer_cache_duration = mp.get_property_number('demuxer-cache-duration', 0) + msg.trace('skipval()') + msg.trace(' demuxer_cache_duration:', demuxer_cache_duration) + msg.trace(' nextsub:', nextsub) + local skipval = demuxer_cache_duration * 0.8 + if skipval == 0 or nextsub == 0 then + skipval = cfg.maxSkip + end + + if nextsub > 0 then + if cfg.directskip then + skipval = clamp(nextsub - cfg.leadin, 0, nil) + elseif nextsub - cfg.leadin <= skipval then + skipval = clamp(nextsub - cfg.leadin, 0, skipval) + else + skipval = clamp(skipval, 0, clamp(skipval, 0, clamp(nextsub - cfg.leadin, 0, cfg.maxSkip))) + end + end + + if skipval < cfg.minSkip then + skipval = 0 + elseif skipval > cfg.maxSkip and not cfg.directskip then + skipval = cfg.maxSkip + end + + msg.trace(' skipval:', skipval) + + return skipval +end + +function wait_finish_seeking() + repeat + local seeking = mp.get_property_bool('seeking') + until not seeking +end + +function check_audio(_, ds) + if not ds or cfg.skipmode or state == 0 or cfg.leadin == 0 then + return + elseif (state == 1 or state == 3) and tonumber(ds) > cfg.leadin and mp.get_property('aid') ~= 'no' then + aid = mp.get_property('aid') + mp.set_property('aid', 'no') + msg.warn('avsync greater than leadin, dropping audio') + end +end + +function check_should_speedup(subend) + local subspeed = mp.get_property_number('sub-speed', 1) + local subdelay = mp.get_property_number('sub-delay') + local substart = mp.get_property_number('sub-start') + + subend = subend * subspeed + subdelay + + if substart then + substart = substart * subspeed + subdelay + end + + if cfg.sub_timeout > 0 and substart and substart < subend then + if subend - substart >= cfg.sub_timeout then + subend = substart + cfg.sub_timeout + end + end + + local sub_visibility = mp.get_property_bool('sub-visibility') + if sub_visibility then + mp.set_property_bool('sub-visibility', false) + end + + mp.commandv('sub-step', 1) + + local nextsubstart = mp.get_property_number('sub-start') + if nextsubstart then + nextsubstart = nextsubstart * subspeed + subdelay + end + + if cfg.ignorePattern and nextsubstart and subend < nextsubstart then + repeat + local ignore = shouldIgnore(mp.get_property('sub-text')) + if ignore then + local t_nextsubstart = mp.get_property_number('sub-end') + if t_nextsubstart then + t_nextsubstart = t_nextsubstart * subspeed + subdelay + end + if t_nextsubstart and t_nextsubstart > nextsubstart then + nextsubstart = t_nextsubstart + mp.commandv('sub-step', 1) + else + break + end + end + until not ignore + end + + mp.set_property_number('sub-delay', subdelay) + if sub_visibility then + mp.set_property_bool('sub-visibility', true) + end + + msg.trace('s-start,s-end,ns-start:', formatTime(substart), formatTime(subend), formatTime(nextsubstart)) + + local nextsub + if nextsubstart then + if subend < nextsubstart then + nextsub = nextsubstart - subend + end + end + + if cfg.leadin > cfg.lookahead then + cfg.leadin = 0 + end + + local shouldspeedup = nextsub and nextsub >= cfg.lookahead - cfg.leadin + local speedup_begin = subend + if shouldspeedup then + msg.debug('check_should_speedup()') + msg.debug(' shouldspeedup:', tostring(shouldspeedup)) + msg.debug(' speedup_begin:', formatTime(speedup_begin) or '') + msg.debug(' nextsub:', nextsub or '') + end + + return nextsub, shouldspeedup, speedup_begin +end + +function check_position(_, position) + if position then + if state == 0 and speedup_zone_begin and position >= speedup_zone_begin and shouldspeedup then + if cfg.skipmode then + msg.debug('check_position[0] -> [2]') + msg.debug(' position:', formatTime(position)) + firstskip = true + state = 2 + else + msg.debug('check_position[0] -> [1]') + msg.debug(' position:', formatTime(position)) + speed_up() + state = 1 + end + + msg.debug(' speedup_zone_begin:', formatTime(speedup_zone_begin)) + msg.debug(' speedup_zone_end:', formatTime(speedup_zone_end)) + elseif state == 0 and not nextsub and last_speedup_zone_begin and position - last_speedup_zone_begin > 2 then + msg.debug('check_position[0] -> [3]') + msg.debug(' position:', formatTime(position)) + if not cfg.skipmode then + speed_up() + end + last_speedup_zone_begin = nil + last_nextsub_check = position + speedup_zone_begin = position + speedup_zone_end = nil + firstskip = true + state = 3 + elseif state == 1 and speedup_zone_end and position >= speedup_zone_end then + restore_normalspeed() + reset_state() + msg.debug('check_position[1] -> [0]') + msg.debug(' position:', formatTime(position)) + elseif state == 2 then + -- msg.debug('check_position[2]') + -- msg.debug(' position:', formatTime(position)) + if speedup_zone_end and position >= speedup_zone_end then + msg.debug('check_position[2] -> [0] pos >= end') + msg.debug(' position:', formatTime(position)) + msg.debug(' speedup_zone_end:', formatTime(speedup_zone_end)) + if not cfg.exact_skip and last_skip_position and position > speedup_zone_end then + msg.debug(' ->seek back to:', formatTime(last_skip_position)) + wait_finish_seeking() + mp.set_property_number('time-pos', last_skip_position) + end + reset_state() + elseif speedup_zone_begin <= position and position < speedup_zone_end then + if mp.get_property('pause') == 'no' then + local position_after_skipdelay = position + wait_finish_seeking() + if position + cfg.skipdelay < speedup_zone_end then + position_after_skipdelay = delayskip(position, cfg.skipdelay) + end + local nextsub = speedup_zone_end - position_after_skipdelay + local tSkip = 0 + if nextsub > 0 then + tSkip = skipval(nextsub) + if position_after_skipdelay + tSkip >= speedup_zone_end then + if speedup_zone_end - position_after_skipdelay >= cfg.minSkip then + wait_finish_seeking() + mp.set_property_number('time-pos', speedup_zone_end) + msg.debug('check_position[2]') + msg.debug(' position:', formatTime(position_after_skipdelay)) + msg.debug(' nextsub:', nextsub) + msg.debug(' direct skip to:', formatTime(speedup_zone_end)) + reset_state() + end + elseif tSkip >= cfg.minSkip then + local seeking = mp.get_property_bool('seeking') + if not seeking then + last_skip_position = position_after_skipdelay + skip(tSkip) + msg.debug('check_position[2]') + msg.debug(' position:', formatTime(position_after_skipdelay)) + msg.debug(' nextsub:', nextsub) + msg.debug(' skipval:', tSkip) + end + end + elseif nextsub < 0 and not cfg.exact_skip then + local cursubend = mp.get_property_number('sub-end') + local margin = 0.5 + if cursubend and cursubend > speedup_zone_end + cfg.leadin then + margin = clamp((cursubend - (speedup_zone_end + cfg.leadin)) * 0.35, 0, 1) + end + if position_after_skipdelay > speedup_zone_end + cfg.leadin + margin then + wait_finish_seeking() + mp.set_property_number('time-pos', speedup_zone_end) + msg.debug('check_position[2]') + msg.debug(' position:', formatTime(position_after_skipdelay)) + msg.debug(' nextsub:', nextsub) + msg.debug(' skipval:', tSkip) + msg.debug(' margin:', margin) + msg.debug(' ->seek back to: ' .. formatTime(speedup_zone_end)) + end + reset_state() + else + reset_state() + end + end + end + elseif state == 3 then + if position - last_nextsub_check > 0.5 then + local t_nextsub, t_shouldspeedup, t_speedup_zone_begin = check_should_speedup(position) + if t_nextsub then + msg.debug('check_position[3]') + msg.debug(' position:', formatTime(position)) + msg.debug(' ->found next sub') + if not t_shouldspeedup then + msg.debug(' ->stop speedup') + msg.debug(' [3] -> [0]') + restore_normalspeed() + reset_state() + return + else + nextsub, shouldspeedup = t_nextsub, t_shouldspeedup + speedup_zone_end = t_speedup_zone_begin + nextsub - cfg.leadin + + if cfg.skipmode then + msg.debug('check_position[3] -> [2]') + state = 2 + return + else + msg.debug('check_position[3] -> [1]') + state = 1 + last_nextsub_check = position + return + end + end + end + last_nextsub_check = position + end + + if cfg.skipmode then + local seeking = mp.get_property_bool('seeking') + if mp.get_property('pause') == 'no' and not seeking then + local tlast_skip_position = position + position = delayskip(position, cfg.skipdelay) + local tSkip = skipval(0) + if tSkip >= cfg.minSkip then + last_skip_position = tlast_skip_position + skip(tSkip) + msg.debug('check_position[3]') + msg.debug(' position:', formatTime(position)) + msg.debug(' nextsub: ---') + msg.debug(' skipval:', tSkip) + end + end + end + else + + end + end +end + +function speed_transition(_, subend) + if not subend then + return + end + + msg.debug('speed_transition()') + + if state == 3 or (state == 2 and not cfg.exact_skip) then + msg.debug(' state >= 2: check seek back / reset') + local position = mp.get_property_number('time-pos') + if cfg.skipmode and last_skip_position then + msg.debug(' position:', formatTime(position)) + msg.debug(' ->seek back to:', formatTime(last_skip_position)) + wait_finish_seeking() + mp.set_property_number('time-pos', last_skip_position) + reset_state() + return + end + restore_normalspeed() + reset_state() + end + + local t_nextsub, t_shouldspeedup, t_speedup_zone_begin = check_should_speedup(subend) + if t_shouldspeedup then + if state ~= 0 then + msg.debug(' ->reset: state > 0') + restore_normalspeed() + reset_state() + end + nextsub, shouldspeedup, speedup_zone_begin = t_nextsub, t_shouldspeedup, t_speedup_zone_begin + speedup_zone_end = speedup_zone_begin + nextsub - cfg.leadin + msg.debug(' speedup_zone_end:', formatTime(speedup_zone_end) or '') + else + if state ~= 0 then + msg.debug(' ->reset: state > 0') + restore_normalspeed() + end + reset_state() + end + last_speedup_zone_begin = t_speedup_zone_begin +end function toggle() - if not enable then - normalspeed = mp.get_property("speed") - mp.set_property("demuxer-readahead-secs",lookahead+leadin) - mp.observe_property("sub-text", "native", speed_transition) - mp.osd_message("speed-transition enabled") - else - restore_normalspeed() - mp.set_property("demuxer-readahead-secs",readahead_secs) - mp.unobserve_property(speed_transition) - mp.unobserve_property(check_position) - mp.osd_message("speed-transition disabled") - end - state = 0 - enable = not enable + if not enable then + normalspeed = mp.get_property('speed') + local calculated_readaheadsecs = math.max(5, readahead_secs, cfg.maxSkip + cfg.leadin, + cfg.lookahead + cfg.leadin) + if readahead_secs < calculated_readaheadsecs then + mp.set_property('demuxer-readahead-secs', calculated_readaheadsecs) + end + last_speedup_zone_begin = mp.get_property_number('time-pos') + mp.observe_property('sub-end', 'number', speed_transition) + mp.observe_property('time-pos', 'number', check_position) + mp.osd_message('speed-transition enabled') + msg.info('enabled') + else + restore_normalspeed() + reset_state() + mp.set_property('demuxer-readahead-secs', readahead_secs) + mp.unobserve_property(speed_transition) + mp.unobserve_property(check_position) + mp.unobserve_property(check_audio) + mp.osd_message('speed-transition disabled') + msg.info('disabled') + end + state = 0 + enable = not enable +end + +function switch_mode() + cfg.skipmode = not cfg.skipmode + if not enable then + toggle() + end + if cfg.skipmode then + if state == 1 or state == 3 then + if state == 1 then + state = 2 + end + mp.set_property('speed', normalspeed) + end + mp.osd_message('skip mode') + msg.info('skip mode') + else + if state == 2 or state == 3 then + if state == 2 then + state = 1 + end + speed_up() + end + mp.osd_message('speed mode') + msg.info('speed mode') + end end function reset_on_file_load() - if state == 1 then - mp.unobserve_property(check_position) - restore_normalspeed() - state = 0 - end + restore_normalspeed() + reset_state() end -mp.add_key_binding("ctrl+j", "toggle_speedtrans", toggle) -mp.add_key_binding("alt+j", "toggle_sub_visibility", toggle_sub_visibility) -mp.add_key_binding("ctrl+alt+j", "toggle_skipmode", toggle_skipmode) -mp.add_key_binding("alt++", "increase_speedup", function() change_speedup(0.1) end, {repeatable=true}) -mp.add_key_binding("alt+-", "decrease_speedup", function() change_speedup(-0.1) end, {repeatable=true}) -mp.add_key_binding("alt+0", "increase_leadin", function() change_leadin(0.25) end) -mp.add_key_binding("alt+9", "decrease_leadin", function() change_leadin(-0.25) end) -mp.add_key_binding("alt+8", "increase_lookahead", function() change_lookAhead(0.25) end) -mp.add_key_binding("alt+7", "decrease_lookahead", function() change_lookAhead(-0.25) end) -mp.register_event("file-loaded", reset_on_file_load) +function change_speedup(v) + cfg.speedup = cfg.speedup + v + mp.osd_message('speedup: ' .. cfg.speedup) + msg.info('speedup:', cfg.speedup) +end + +function change_leadin(v) + cfg.leadin = clamp(cfg.leadin + v, 0, 2) + mp.osd_message('leadin: ' .. cfg.leadin) + msg.info('leadin:', cfg.leadin) +end + +function change_lookAhead(v) + cfg.lookahead = clamp(cfg.lookahead + v, 0, nil) + mp.osd_message('lookahead: ' .. cfg.lookahead) + msg.info('lookahead:', cfg.lookahead) +end + +mp.add_key_binding('ctrl+j', 'toggle_speedtrans', toggle) +mp.add_key_binding('alt+j', 'switch_mode', switch_mode) +mp.add_key_binding('alt++', 'increase_speedup', function() change_speedup(0.1) end, { repeatable = true }) +mp.add_key_binding('alt+-', 'decrease_speedup', function() change_speedup(-0.1) end, { repeatable = true }) +mp.add_key_binding('alt+0', 'increase_leadin', function() change_leadin(0.25) end) +mp.add_key_binding('alt+9', 'decrease_leadin', function() change_leadin(-0.25) end) +mp.add_key_binding('alt+8', 'increase_lookahead', function() change_lookAhead(0.25) end) +mp.add_key_binding('alt+7', 'decrease_lookahead', function() change_lookAhead(-0.25) end) +mp.register_event('file-loaded', reset_on_file_load)