modern_lisp-machine/.config/mpv/scripts/sub-voracious.lua

269 lines
7.5 KiB
Lua

-- Usage:
-- i - subtitle mode on/off
-- ↑, ↓ (Space) - mode specific shortcuts
-- Note:
-- The script is inspired by voracious.app (https://github.com/rsimmons/voracious).
--
-- - Listening Practice:
-- "Subtitles are initially hidden. At the end of each subtitle, the video will pause automatically.
-- Could you hear what was said? Press ↑ to replay, if necessary.
-- Then press ↓ to reveal the subs, and check if you heard correctly.
-- Then press ↓ to unpause the video."
--
-- - Reading Practice:
-- "At the start of each new subtitle, the video will pause automatically.
-- Try reading the sub. Then press ↓ to unpause the video, and hear it spoken.
-- Did you read it correctly?"
--
----------------------------------
--------- Script Options ---------
srt_file_extensions = {".srt", ".en.srt", ".eng.srt"}
----------------------------------
sub_pad_start = 0.25
sub_pad_end = 0.2
----------------------------------
function srt_time_to_seconds(time)
major, minor = time:match("(%d%d:%d%d:%d%d),(%d%d%d)")
hours, mins, secs = major:match("(%d%d):(%d%d):(%d%d)")
return hours * 3600 + mins * 60 + secs + minor / 1000
end
function seconds_to_srt_time(time)
hours = math.floor(time / 3600)
mins = math.floor(time / 60) % 60
secs = math.floor(time % 60)
milliseconds = (time * 1000) % 1000
return string.format("%02d:%02d:%02d,%03d", hours, mins, secs, milliseconds)
end
function open_subtitles_file()
local video_path = mp.get_property("path")
local srt_filename = video_path:gsub('\\','/'):match("^(.+)/.+$") .. "/" .. mp.get_property("filename/no-ext")
for i, ext in ipairs(srt_file_extensions) do
local f, err = io.open(srt_filename .. ext, "r")
if f then
return f
end
end
return false
end
function read_subtitles()
local f = open_subtitles_file()
if not f then
return false
end
local data = f:read("*all")
data = string.gsub(data, "\r\n", "\n")
f:close()
subs = {}
subs_start = {}
subs_end = {}
for start_time, end_time, text in string.gmatch(data, "(%d%d:%d%d:%d%d,%d%d%d) %-%-> (%d%d:%d%d:%d%d,%d%d%d)\n(.-)\n\n") do
if not filter_subtitles(text) then
table.insert(subs, text)
table.insert(subs_start, srt_time_to_seconds(start_time))
table.insert(subs_end, srt_time_to_seconds(end_time))
end
end
return true
end
function filter_subtitles(text)
if string.match(text, "^%[(.*)%]$") then
return true
end
return false
end
function adjust_subtitles()
sub_id = 1
while sub_id < #subs do
if subs_start[sub_id + 1] - subs_end[sub_id] > sub_pad_end then
subs_end[sub_id] = subs_end[sub_id] + sub_pad_end
elseif subs_start[sub_id + 1] - subs_end[sub_id] > 0 then
subs_end[sub_id] = subs_end[sub_id] + (subs_start[sub_id + 1] - subs_end[sub_id]) / 2
end
if subs_start[sub_id + 1] - subs_end[sub_id] > sub_pad_start then
subs_start[sub_id + 1] = subs_start[sub_id + 1] - sub_pad_start
elseif subs_start[sub_id + 1] - subs_end[sub_id] > 0 then
subs_start[sub_id + 1] = subs_start[sub_id + 1] - (subs_start[sub_id + 1] - subs_end[sub_id]) / 2
end
sub_id = sub_id + 1
end
subs_start[1] = subs_start[1] - sub_pad_start
subs_end[#subs] = subs_end[#subs] + sub_pad_end
end
function update_sub_id()
local pos = mp.get_property_number("time-pos")
if pos == nil then
sub_id = nil
return
end
sub_id = 1
while sub_id < #subs and subs_end[sub_id] < pos do
sub_id = sub_id + 1
end
if subtitle_mode == "Reading Practice" and sub_id < #subs then
sub_id = sub_id + 1
end
end
function replay_sub_without_subtitles()
if sub_id ~= nil then
player_state = "replay"
mp.set_property("sub-visibility", "no")
mp.commandv("seek", subs_start[sub_id], "absolute+exact")
end
end
function on_seek()
if player_state ~= "replay" then
update_sub_id()
end
end
function on_playback_restart()
if player_state == "replay" then
mp.set_property("pause", "no")
player_state = nil
end
end
function on_up_arrow_key()
if subtitle_mode == "Listening Practice" then
replay_sub_without_subtitles()
end
end
function on_down_arrow_key()
if player_state == "pause" then
if mp.get_property("sub-visibility") == "no" then
mp.set_property("sub-visibility", "yes")
else
mp.set_property("sub-visibility", "no")
mp.set_property("pause", "no")
player_state = nil
if sub_id < #subs then
sub_id = sub_id + 1
else
sub_id = nil
end
end
end
end
function on_space_key()
if player_state == "pause" then
on_down_arrow_key()
else
mp.set_property_bool("pause", not mp.get_property_bool("pause"))
end
end
function subtitle_mode_timer()
local pos = mp.get_property_number("time-pos")
if pos ~= nil then
if sub_id ~= nil and mp.get_property("pause") ~= "yes" then
if subtitle_mode == "Listening Practice" and pos >= subs_end[sub_id] then
mp.set_property("pause", "yes")
player_state = "pause"
elseif subtitle_mode == "Reading Practice" then
if (pos + 0.25) >= subs_start[sub_id] then
mp.set_property("sub-visibility", "yes")
end
if pos >= subs_start[sub_id] then
mp.set_property("pause", "yes")
player_state = "pause"
end
end
end
end
end
function init_subtitle_mode()
default_sub_visibility = mp.get_property("sub-visibility")
mp.set_property("sub-visibility", "no")
timer = mp.add_periodic_timer(0.05, subtitle_mode_timer)
update_sub_id()
mp.add_key_binding("up", "up-arrow-key", on_up_arrow_key)
mp.add_key_binding("down", "down-arrow-key", on_down_arrow_key)
mp.add_key_binding("space", "space-arrow-key", on_space_key)
mp.register_event("seek", on_seek)
mp.register_event("playback-restart", on_playback_restart)
end
function release_subtitle_mode()
mp.set_property("sub-visibility", default_sub_visibility)
timer:kill()
mp.remove_key_binding("up-arrow-key")
mp.remove_key_binding("down-arrow-key")
mp.remove_key_binding("space-arrow-key")
mp.unregister_event(on_seek)
mp.unregister_event(on_playback_restart)
end
function toggle_subtitle_mode()
if subtitle_mode == nil then
subtitle_mode = "Listening Practice"
mp.set_property("sub-delay", sub_pad_end + 0.25)
elseif subtitle_mode == "Listening Practice" then
subtitle_mode = "Reading Practice"
mp.set_property("sub-delay", -sub_pad_start - 0.25)
else
mp.set_property("sub-delay", 0)
subtitle_mode = nil
end
if subtitle_mode ~= nil then
mp.osd_message("Subtitle Mode: " .. subtitle_mode)
init_subtitle_mode()
else
mp.osd_message("Subtitle Mode: " .. "Off")
release_subtitle_mode()
end
end
function init()
local ret = read_subtitles()
if ret == false or #subs == 0 then
return
end
adjust_subtitles()
mp.add_key_binding("i", "toggle-interactive-mode", toggle_subtitle_mode)
end
mp.register_event("file-loaded", init)