summaryrefslogtreecommitdiff
path: root/config/mpv/scripts/subs2srsa/subtitles
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-04-05 03:00:29 +0200
committernavewindre <boneyaard@gmail.com>2025-04-05 03:00:29 +0200
commitd6c4365b8de32b621ac46074a9b69908b95686c0 (patch)
tree495cb5b1aa7e68ab6ec07fa5fb09904a8c7e47e7 /config/mpv/scripts/subs2srsa/subtitles
parentb24463f3d045783b8f4e72926054d53b908e150f (diff)
a
Diffstat (limited to 'config/mpv/scripts/subs2srsa/subtitles')
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/observer.lua344
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/secondary_sid.lua196
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/sub_list.lua74
-rw-r--r--config/mpv/scripts/subs2srsa/subtitles/subtitle.lua56
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