diff options
| author | navewindre <boneyaard@gmail.com> | 2025-04-05 03:00:29 +0200 |
|---|---|---|
| committer | navewindre <boneyaard@gmail.com> | 2025-04-05 03:00:29 +0200 |
| commit | d6c4365b8de32b621ac46074a9b69908b95686c0 (patch) | |
| tree | 495cb5b1aa7e68ab6ec07fa5fb09904a8c7e47e7 /config/mpv/scripts/subs2srsa/subtitles | |
| parent | b24463f3d045783b8f4e72926054d53b908e150f (diff) | |
a
Diffstat (limited to 'config/mpv/scripts/subs2srsa/subtitles')
| -rw-r--r-- | config/mpv/scripts/subs2srsa/subtitles/observer.lua | 344 | ||||
| -rw-r--r-- | config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua | 196 | ||||
| -rw-r--r-- | config/mpv/scripts/subs2srsa/subtitles/sub_list.lua | 74 | ||||
| -rw-r--r-- | config/mpv/scripts/subs2srsa/subtitles/subtitle.lua | 56 |
4 files changed, 0 insertions, 670 deletions
diff --git a/config/mpv/scripts/subs2srsa/subtitles/observer.lua b/config/mpv/scripts/subs2srsa/subtitles/observer.lua deleted file mode 100644 index d9284c5..0000000 --- a/config/mpv/scripts/subs2srsa/subtitles/observer.lua +++ /dev/null @@ -1,344 +0,0 @@ ---[[ -Copyright: Ren Tatsumoto and contributors -License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html - -Observer waits for subtitles to appear on the screen and adds them to a list. -]] - -local h = require('helpers') -local timings = require('utils.timings') -local sub_list = require('subtitles.sub_list') -local Subtitle = require('subtitles.subtitle') -local mp = require('mp') -local platform = require('platform.init') -local switch = require('utils.switch') - -local self = {} - -local dialogs = sub_list.new() -local secondary_dialogs = sub_list.new() -local all_dialogs = sub_list.new() -local all_secondary_dialogs = sub_list.new() -local user_timings = timings.new() - -local append_dialogue = false -local autoclip_enabled = false -local autoclip_method = {} - - ------------------------------------------------------------- --- private - -local function copy_primary_sub() - if autoclip_enabled then - autoclip_method.call() - end -end - -local function append_primary_sub() - local current_sub = Subtitle:now() - all_dialogs.insert(current_sub) - if append_dialogue and dialogs.insert(current_sub) then - self.menu:update() - end -end - -local function append_secondary_sub() - local current_secondary = Subtitle:now('secondary') - all_secondary_dialogs.insert(current_secondary) - if append_dialogue and secondary_dialogs.insert(Subtitle:now('secondary')) then - self.menu:update() - end -end - -local function start_appending() - append_dialogue = true - append_primary_sub() - append_secondary_sub() -end - -local function handle_primary_sub() - append_primary_sub() - copy_primary_sub() -end - -local function handle_secondary_sub() - append_secondary_sub() -end - -local function on_external_finish(success, result, error) - if success ~= true or error ~= nil then - h.notify("Command failed: " .. table.concat(result)) - end -end - -local function external_command_args(cur_lines) - local args = {} - for arg in string.gmatch(self.config.autoclip_custom_args, "%S+") do - if arg == '%MPV_PRIMARY%' then - arg = cur_lines.primary - elseif arg == '%MPV_SECONDARY%' then - arg = cur_lines.secondary - end - table.insert(args, arg) - end - return args -end - -local function call_external_command(cur_lines) - if not h.is_empty(self.config.autoclip_custom_args) then - h.subprocess(external_command_args(cur_lines), on_external_finish) - end -end - -local function current_subtitle_lines() - local primary = dialogs.get_text() - - if h.is_empty(primary) then - primary = mp.get_property("sub-text") - end - - if h.is_empty(primary) then - return nil - end - - local secondary = secondary_dialogs.get_text() - - if h.is_empty(secondary) then - secondary = mp.get_property("secondary-sub-text") or "" - end - - return { primary = self.clipboard_prepare(primary), secondary = secondary } -end - -local function ensure_goldendict_running() - --- Ensure that goldendict is running and is disowned by mpv. - --- Avoid goldendict getting killed when mpv exits. - if autoclip_enabled and self.autocopy_current_method() == "goldendict" then - os.execute("setsid -f goldendict") - end -end - ------------------------------------------------------------- --- autoclip methods - -autoclip_method = (function() - local methods = { 'clipboard', 'goldendict', 'custom_command', } - local current_method = switch.new(methods) - - local function call() - local cur_lines = current_subtitle_lines() - if h.is_empty(cur_lines) then - return - end - - if current_method.get() == 'clipboard' then - self.copy_to_clipboard("autocopy action", cur_lines.primary) - elseif current_method.get() == 'goldendict' then - h.subprocess_detached({ 'goldendict', cur_lines.primary }, on_external_finish) - elseif current_method.get() == 'custom_command' then - call_external_command(cur_lines) - end - end - - return { - call = call, - get = current_method.get, - bump = current_method.bump, - set = current_method.set, - } -end)() - -local function copy_subtitle(subtitle_id) - self.copy_to_clipboard("copy-on-demand", mp.get_property(subtitle_id)) -end - ------------------------------------------------------------- --- public - -self.copy_to_clipboard = function(_, text) - if platform.healthy == false then - h.notify(platform.clip_util .. " is not installed.", "error", 5) - end - if not h.is_empty(text) then - platform.copy_to_clipboard(self.clipboard_prepare(text)) - end -end - -self.clipboard_prepare = function(text) - text = self.config.clipboard_trim_enabled and h.trim(text) or h.remove_newlines(text) - text = self.maybe_remove_all_spaces(text) - return text -end - -self.maybe_remove_all_spaces = function(str) - if self.config.nuke_spaces == true and h.contains_non_latin_letters(str) then - return h.remove_all_spaces(str) - else - return str - end -end - -self.copy_current_primary_to_clipboard = function() - copy_subtitle("sub-text") -end - -self.copy_current_secondary_to_clipboard = function() - copy_subtitle("secondary-sub-text") -end - -self.user_altered = function() - --- Return true if the user manually set at least start or end. - return user_timings.is_set('start') or user_timings.is_set('end') -end - -self.get_timing = function(position) - if user_timings.is_set(position) then - return user_timings.get(position) - elseif not dialogs.is_empty() then - return dialogs.get_time(position) - end - return -1 -end - -self.collect_from_all_dialogues = function(n_lines) - local current_sub = Subtitle:now() - local current_secondary_sub = Subtitle:now('secondary') - all_dialogs.insert(current_sub) - all_secondary_dialogs.insert(current_secondary_sub) - if current_sub == nil then - return Subtitle:new() -- return a default empty new Subtitle to let consumer handle - end - local text, end_sub = all_dialogs.get_n_text(current_sub, n_lines) - local secondary_text, _ - if current_secondary_sub == nil then - secondary_text = '' - else - secondary_text, _ = all_secondary_dialogs.get_n_text(current_secondary_sub, n_lines) -- we'll use main sub's timing - end - return Subtitle:new { - ['text'] = text, - ['secondary'] = secondary_text, - ['start'] = current_sub['start'], - ['end'] = end_sub['end'], - } -end - -self.collect_from_current = function() - --- Return all recorded subtitle lines as one subtitle object. - --- The caller has to call subs_observer.clear() afterwards. - if dialogs.is_empty() then - dialogs.insert(Subtitle:now()) - end - if secondary_dialogs.is_empty() then - secondary_dialogs.insert(Subtitle:now('secondary')) - end - return Subtitle:new { - ['text'] = dialogs.get_text(), - ['secondary'] = secondary_dialogs.get_text(), - ['start'] = self.get_timing('start'), - ['end'] = self.get_timing('end'), - } -end - -self.set_manual_timing = function(position) - user_timings.set(position, mp.get_property_number('time-pos') - mp.get_property("audio-delay")) - h.notify(h.capitalize_first_letter(position) .. " time has been set.") - start_appending() -end - -self.set_manual_timing_to_sub = function(position) - local sub = Subtitle:now() - if sub then - user_timings.set(position, sub[position] - mp.get_property("audio-delay")) - h.notify(h.capitalize_first_letter(position) .. " time has been set.") - start_appending() - else - h.notify("There's no visible subtitle.", "info", 2) - end -end - -self.set_to_current_sub = function() - self.clear() - if Subtitle:now() then - start_appending() - h.notify("Timings have been set to the current sub.", "info", 2) - else - h.notify("There's no visible subtitle.", "info", 2) - end -end - -self.clear = function() - append_dialogue = false - dialogs = sub_list.new() - secondary_dialogs = sub_list.new() - user_timings = timings.new() -end - -self.clear_all_dialogs = function() - all_dialogs = sub_list.new() - all_secondary_dialogs = sub_list.new() -end - -self.clear_and_notify = function() - --- Clear then notify the user. - --- Called by the OSD menu when the user presses a button to drop recorded subtitles. - self.clear() - h.notify("Timings have been reset.", "info", 2) -end - -self.is_appending = function() - return append_dialogue -end - -self.recorded_subs = function() - return dialogs.get_subs_list() -end - -self.recorded_secondary_subs = function() - return secondary_dialogs.get_subs_list() -end - -self.autocopy_status_str = function() - return string.format( - "%s (%s)", - (autoclip_enabled and 'enabled' or 'disabled'), - autoclip_method.get():gsub('_', ' ') - ) -end - -self.autocopy_current_method = function() - return autoclip_method.get() -end - -local function notify_autocopy() - if autoclip_enabled then - copy_primary_sub() - end - h.notify(string.format("Clipboard autocopy has been %s.", self.autocopy_status_str())) -end - -self.toggle_autocopy = function() - autoclip_enabled = not autoclip_enabled - notify_autocopy() -end - -self.next_autoclip_method = function() - autoclip_method.bump() - notify_autocopy() -end - -self.init = function(menu, config) - self.menu = menu - self.config = config - - -- The autoclip state is copied as a local value - -- to prevent it from being reset when the user reloads the config file. - autoclip_enabled = self.config.autoclip - autoclip_method.set(self.config.autoclip_method) - - mp.observe_property("sub-text", "string", handle_primary_sub) - mp.observe_property("secondary-sub-text", "string", handle_secondary_sub) -end - -return self diff --git a/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua b/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua deleted file mode 100644 index 21576e8..0000000 --- a/config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua +++ /dev/null @@ -1,196 +0,0 @@ ---[[ -Copyright: Ren Tatsumoto and contributors -License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html - -This module automatically finds and sets secondary sid if it's not already set. -Secondary sid will be shown when mouse is moved to the top part of the mpv window. -]] - -local mp = require('mp') -local h = require('helpers') - -local self = { - visibility = 'auto', - visibility_states = { auto = true, never = true, always = true, }, -} - -local function is_accepted_language(sub_lang) - -- for missing keys compares nil to true - return self.accepted_languages[sub_lang] == true -end - -local function is_selected_language(track, active_track) - return track.id == mp.get_property_native('sid') or (active_track and active_track.lang == track.lang) -end - -local function is_full(track) - return h.str_contains(track.title, 'full') -end - -local function is_garbage(track) - for _, keyword in pairs({ 'song', 'sign', 'caption', 'commentary' }) do - if h.str_contains(track.title, keyword) then - return true - end - end - return false -end - -local function prioritize_full_subs(tracks_list) - return table.sort(tracks_list, function(first, second) - return (is_full(first) and not is_full(second)) or (is_garbage(second) and not is_garbage(first)) - end) -end - -local function find_best_secondary_sid() - local active_track = h.get_active_track('sub') - local sub_tracks = h.get_loaded_tracks('sub') - prioritize_full_subs(sub_tracks) - for _, track in ipairs(sub_tracks) do - if is_accepted_language(track.lang) and not is_selected_language(track, active_track) then - return track.id - end - end - return nil -end - -local function window_height() - return mp.get_property_native('osd-dimensions/h') -end - -local function get_accepted_sub_langs() - local languages = {} - for lang in self.config.secondary_sub_lang:gmatch('[a-zA-Z-]+') do - languages[lang] = true - end - return languages -end - -local function on_mouse_move(_, state) - -- state = {x=int,y=int, hover=true|false, } - if mp.get_property_native('secondary-sid') and self.visibility == 'auto' and state ~= nil then - mp.set_property_bool( - 'secondary-sub-visibility', - state.hover and (state.y / window_height()) < self.config.secondary_sub_area - ) - end -end - -local function on_file_loaded() - -- If secondary sid is not already set, try to find and set it. - local secondary_sid = mp.get_property_native('secondary-sid') - if secondary_sid == false and self.config.secondary_sub_auto_load == true then - secondary_sid = find_best_secondary_sid() - if secondary_sid ~= nil then - mp.set_property_native('secondary-sid', secondary_sid) - end - end -end - -local function update_visibility() - mp.set_property_bool('secondary-sub-visibility', self.visibility == 'always') -end - -local function init(config) - self.config = config - self.visibility = config.secondary_sub_visibility - self.accepted_languages = get_accepted_sub_langs() - mp.register_event('file-loaded', on_file_loaded) - if config.secondary_sub_area > 0 then - mp.observe_property('mouse-pos', 'native', on_mouse_move) - end - update_visibility() -end - -local function change_visibility() - while true do - self.visibility = next(self.visibility_states, self.visibility) - if self.visibility ~= nil then - break - end - end - update_visibility() - h.notify("Secondary sid visibility: " .. self.visibility) -end - -local function compare_by_preference_then_id(track1, track2) - if is_accepted_language(track1.lang) and not is_accepted_language(track2.lang) then - return true - elseif not is_accepted_language(track1.lang) and is_accepted_language(track2.lang) then - return false - else - return (track1.id < track2.id) - end -end - -local function split_before_after(previous_tracks, next_tracks, all_tracks, current_track_id) - -- works like take_while() and drop_while() combined - local prev = true - for _, track in ipairs(all_tracks) do - if prev == true and track.id == current_track_id then - prev = false - end - if track.id ~= current_track_id then - if prev then - table.insert(previous_tracks, track) - else - table.insert(next_tracks, track) - end - end - end -end - -local function not_primary_sid(track) - return mp.get_property_native('sid') ~= track.id -end - -local function find_new_secondary_sub(direction) - local subtitle_tracks = h.filter(h.get_loaded_tracks('sub'), not_primary_sid) - table.sort(subtitle_tracks, compare_by_preference_then_id) - - local secondary_sid = mp.get_property_native('secondary-sid') - local new_secondary_sub = { id = false, title = "removed" } - - if #subtitle_tracks > 0 then - if not secondary_sid then - new_secondary_sub = (direction == 'prev') and subtitle_tracks[#subtitle_tracks] or subtitle_tracks[1] - else - local previous_tracks = {} - local next_tracks = {} - split_before_after(previous_tracks, next_tracks, subtitle_tracks, secondary_sid) - if direction == 'prev' and #previous_tracks > 0 then - new_secondary_sub = previous_tracks[#previous_tracks] - elseif direction == 'next' and #next_tracks > 0 then - new_secondary_sub = next_tracks[1] - end - end - end - return new_secondary_sub -end - -local function switch_secondary_sid(direction) - local new_secondary_sub = find_new_secondary_sub(direction) - - mp.set_property_native('secondary-sid', new_secondary_sub.id) - if new_secondary_sub.id == false then - h.notify("Removed secondary sid.") - else - h.notify(string.format( - "Secondary #%d: %s (%s)", - new_secondary_sub.id, - new_secondary_sub.title or "No title", - new_secondary_sub.lang or "Unknown" - )) - end -end - -return { - init = init, - change_visibility = change_visibility, - select_previous = function() - switch_secondary_sid('prev') - end, - select_next = function() - switch_secondary_sid('next') - end, -} diff --git a/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua b/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua deleted file mode 100644 index de7e1c2..0000000 --- a/config/mpv/scripts/subs2srsa/subtitles/sub_list.lua +++ /dev/null @@ -1,74 +0,0 @@ ---[[ -Copyright: Ren Tatsumoto and contributors -License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html - -Subtitle list remembers selected subtitle lines. -]] - -local h = require('helpers') - -local new_sub_list = function() - local subs_list = {} - - local find_i = function(sub) - for i, v in ipairs(subs_list) do - if sub < v then - return i - end - end - return #subs_list + 1 - end - local get_time = function(position) - local i = position == 'start' and 1 or #subs_list - return subs_list[i][position] - end - local get_text = function() - local speech = {} - for _, sub in ipairs(subs_list) do - table.insert(speech, sub['text']) - end - return table.concat(speech, ' ') - end - local get_n_text = function(sub, n_lines) - local speech = {} - local end_sub = sub - for _, v in ipairs(subs_list) do - if v['start'] - end_sub['end'] >= 20 then - break - end - if v >= sub and #speech < n_lines then - table.insert(speech, v['text']) - end_sub = v - end - end - return table.concat(speech, ' '), end_sub - end - local insert = function(sub) - if sub ~= nil and not h.contains(subs_list, sub) then - table.insert(subs_list, find_i(sub), sub) - return true - end - return false - end - local get_subs_list = function() - local copy = {} - for key, value in pairs(subs_list) do - copy[key] = value - end - return copy - end - return { - get_subs_list = get_subs_list, - get_time = get_time, - get_text = get_text, - get_n_text = get_n_text, - insert = insert, - is_empty = function() - return h.is_empty(subs_list) - end, - } -end - -return { - new = new_sub_list, -} diff --git a/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua b/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua deleted file mode 100644 index 4e8df41..0000000 --- a/config/mpv/scripts/subs2srsa/subtitles/subtitle.lua +++ /dev/null @@ -1,56 +0,0 @@ ---[[ -Copyright: Ren Tatsumoto and contributors -License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html - -Subtitle class provides methods for storing and comparing subtitle lines. -]] - -local mp = require('mp') - -local Subtitle = { - ['text'] = '', - ['secondary'] = '', - ['start'] = -1, - ['end'] = -1, -} - -function Subtitle:new(o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o -end - -function Subtitle:now(secondary) - local prefix = secondary and "secondary-" or "" - local this = self:new { - ['text'] = mp.get_property(prefix .. "sub-text"), - ['start'] = mp.get_property_number(prefix .. "sub-start"), - ['end'] = mp.get_property_number(prefix .. "sub-end"), - } - if this:is_valid() then - return this:delay(mp.get_property_native("sub-delay") - mp.get_property_native("audio-delay")) - else - return nil - end -end - -function Subtitle:delay(delay) - self['start'] = self['start'] + delay - self['end'] = self['end'] + delay - return self -end - -function Subtitle:is_valid() - return self['start'] and self['end'] and self['start'] >= 0 and self['end'] > self['start'] -end - -Subtitle.__eq = function(lhs, rhs) - return lhs['text'] == rhs['text'] -end - -Subtitle.__lt = function(lhs, rhs) - return lhs['start'] < rhs['start'] -end - -return Subtitle |
