diff options
Diffstat (limited to 'config/mpv/scripts/subs2srs/cfg_mgr.lua')
| -rw-r--r-- | config/mpv/scripts/subs2srs/cfg_mgr.lua | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/config/mpv/scripts/subs2srs/cfg_mgr.lua b/config/mpv/scripts/subs2srs/cfg_mgr.lua new file mode 100644 index 0000000..076c0d9 --- /dev/null +++ b/config/mpv/scripts/subs2srs/cfg_mgr.lua @@ -0,0 +1,240 @@ +--[[ +Copyright: Ren Tatsumoto and contributors +License: GNU GPL, version 3 or later; http://www.gnu.org/licenses/gpl.html + +Config management, validation, loading. +]] + +local mp = require('mp') +local mpopt = require('mp.options') +local msg = require('mp.msg') +local h = require('helpers') +local utils = require('mp.utils') + +local min_side_px = 42 +local max_side_px = 640 +local default_height_px = 200 + +-- This constant should be used in place of width and/or height in the config file. +-- It tells the encoder to preserve aspect ratio when downscaling snapshots. +-- The user almost always wants to set either width or height to this value. +-- Note: If set to -1, encoding will fail with the "height/width not divisible by 2" error. +local preserve_aspect_ratio = -2 + +local self = { + config = nil, + profiles = nil, + initial_config = {} +} + +local default_profile_filename = 'subs2srs' +local profiles_filename = 'subs2srs_profiles' + +local function set_file_extension_for_opus() + -- Default to OGG, then change if an extension is supported. + -- https://en.wikipedia.org/wiki/Core_Audio_Format + self.config.audio_extension = '.ogg' + for _, extension in ipairs({ 'opus', 'm4a', 'webm', 'caf' }) do + if extension == self.config.opus_container then + self.config.audio_extension = '.' .. self.config.opus_container + break + end + end +end + +local function set_audio_format() + if self.config.audio_format == 'opus' then + -- https://opus-codec.org/ + self.config.audio_codec = 'libopus' + set_file_extension_for_opus() + else + self.config.audio_codec = 'libmp3lame' + self.config.audio_extension = '.mp3' + end +end + +local function set_video_format() + if self.config.snapshot_format == 'avif' then + self.config.snapshot_extension = '.avif' + self.config.snapshot_codec = 'libaom-av1' + elseif self.config.snapshot_format == 'webp' then + self.config.snapshot_extension = '.webp' + self.config.snapshot_codec = 'libwebp' + else + self.config.snapshot_extension = '.jpg' + self.config.snapshot_codec = 'mjpeg' + end + + -- Animated webp images can only have .webp extension. + -- The user has no choice on this. Same logic for avif. + if self.config.animated_snapshot_format == 'avif' then + self.config.animated_snapshot_extension = '.avif' + self.config.animated_snapshot_codec = 'libaom-av1' + else + self.config.animated_snapshot_extension = '.webp' + self.config.animated_snapshot_codec = 'libwebp' + end +end + +local function ensure_in_range(dimension) + self.config[dimension] = self.config[dimension] < min_side_px and preserve_aspect_ratio or self.config[dimension] + self.config[dimension] = self.config[dimension] > max_side_px and max_side_px or self.config[dimension] +end + +local function conditionally_set_defaults(width, height, quality) + if self.config[width] < 1 and self.config[height] < 1 then + self.config[width] = preserve_aspect_ratio + self.config[height] = default_height_px + end + if self.config[quality] < 0 or self.config[quality] > 100 then + self.config[quality] = 15 + end +end + +local function check_image_settings() + ensure_in_range('snapshot_width') + ensure_in_range('snapshot_height') + conditionally_set_defaults('snapshot_width', 'snapshot_height', 'snapshot_quality') +end + +local function ensure_correct_fps() + if self.config.animated_snapshot_fps == nil or self.config.animated_snapshot_fps <= 0 or self.config.animated_snapshot_fps > 30 then + self.config.animated_snapshot_fps = 10 + end +end + +local function check_animated_snapshot_settings() + ensure_in_range('animated_snapshot_width') + ensure_in_range('animated_snapshot_height') + conditionally_set_defaults('animated_snapshot_width', 'animated_snapshot_height', 'animated_snapshot_quality') + ensure_correct_fps() +end + +local function validate_config() + set_audio_format() + set_video_format() + check_image_settings() + check_animated_snapshot_settings() +end + +local function remember_initial_config() + if h.is_empty(self.initial_config) then + for key, value in pairs(self.config) do + self.initial_config[key] = value + end + else + msg.fatal("Ignoring. Initial config has been read already.") + end +end + +local function restore_initial_config() + for key, value in pairs(self.initial_config) do + self.config[key] = value + end +end + +local function read_profile_list() + mpopt.read_options(self.profiles, profiles_filename) + msg.info("Read profile list. Defined profiles: " .. self.profiles.profiles) +end + +local function read_profile(profile_name) + mpopt.read_options(self.config, profile_name) + msg.info("Read config file: " .. profile_name) +end + +local function read_default_config() + read_profile(default_profile_filename) +end + +local function reload_from_disk() + --- Loads default config file (subs2srs.conf), then overwrites it with current profile. + if not h.is_empty(self.config) and not h.is_empty(self.profiles) then + restore_initial_config() + read_default_config() + if self.profiles.active ~= default_profile_filename then + read_profile(self.profiles.active) + end + validate_config() + else + msg.fatal("Attempt to load config when init hasn't been done.") + end +end + +local function next_profile() + local first, next, new + for profile in string.gmatch(self.profiles.profiles, '[^,]+') do + if not first then + first = profile + end + if profile == self.profiles.active then + next = true + elseif next then + next = false + new = profile + end + end + if next == true or not new then + new = first + end + self.profiles.active = new + reload_from_disk() +end + +local function create_config_file() + local name = default_profile_filename + -- ~/.config/mpv/scripts/ and the mpvacious dir + local parent, child = utils.split_path(mp.get_script_directory()) + -- ~/.config/mpv/ and "scripts" + parent, child = utils.split_path(parent:gsub("/$", "")) + -- ~/.config/mpv/script-opts/subs2srs.conf + local config_filepath = utils.join_path(utils.join_path(parent, "script-opts"), string.format('%s.conf', name)) + local example_config_filepath = utils.join_path(mp.get_script_directory(), ".github/RELEASE/subs2srs.conf") + + local file_info = utils.file_info(config_filepath) + if file_info and file_info.is_file then + print("config already exists") + return + end + + local handle = io.open(example_config_filepath, 'r') + if handle == nil then + return + end + + local content = handle:read("*a") + handle:close() + + handle = io.open(config_filepath, 'w') + if handle == nil then + h.notify(string.format("Couldn't open %s.", config_filepath), "error", 4) + return + end + + handle:write(string.format("# Written by %s on %s.\n", name, os.date())) + handle:write(content) + handle:close() + h.notify("Settings saved.", "info", 2) +end + +local function init(config_table, profiles_table) + create_config_file() + self.config, self.profiles = config_table, profiles_table + -- 'subs2srs' is the main profile, it is always loaded. 'active profile' overrides it afterwards. + -- initial state is saved to another table to maintain consistency when cycling through incomplete profiles. + read_profile_list() + read_default_config() + remember_initial_config() + if self.profiles.active ~= default_profile_filename then + read_profile(self.profiles.active) + end + validate_config() +end + +return { + reload_from_disk = reload_from_disk, + init = init, + next_profile = next_profile, + default_height_px = default_height_px, + preserve_aspect_ratio = preserve_aspect_ratio, +} |
