-
-
Notifications
You must be signed in to change notification settings - Fork 50
/
cfg_mgr.lua
238 lines (206 loc) · 7.59 KB
/
cfg_mgr.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
--[[
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
local parent, child = utils.split_path(mp.get_script_directory())
parent, child = utils.split_path(parent:gsub("/$", ""))
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,
}