Skip to content

Commit 4b75fd3

Browse files
committed
Use multiple workers instead of a single server
Simply copy the _server.lua once or twice in the scripts directory. They will register themselves with the "client" automatically. Props to anon for reminding me. This improves performance a bunch, at the cost of, well, peformance. Your CPU will max out. It'd be cool to automatically generate the workers from the main script, but you can't tell mpv to load up a script, as far as I know.
1 parent 5c09b04 commit 4b75fd3

3 files changed

Lines changed: 126 additions & 59 deletions

File tree

cat_server.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"<version>",
66
"lib/helpers.lua",
77
"src/options.lua",
8-
"src/thumbnailer_shared.lua",
98
"src/thumbnailer_server.lua"
109
],
1110

src/thumbnailer_server.lua

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -82,35 +82,17 @@ function check_output(ret, output_path)
8282
end
8383

8484

85-
function generate_thumbnails(from_keypress)
86-
msg.debug("Thumbnailer state:")
87-
msg.debug(utils.to_string(Thumbnailer.state))
88-
89-
if not Thumbnailer.state.available then
90-
if from_keypress then
91-
mp.osd_message("Nothing to thumbnail", 2)
92-
end
93-
if Thumbnailer.state.is_remote then
94-
msg.warn("Not thumbnailing remote file")
95-
end
85+
function do_worker_job(state_json_string, frames_json_string)
86+
local thumb_state, err = utils.parse_json(state_json_string)
87+
if err then
88+
msg.error("Failed to parse state JSON")
9689
return
9790
end
9891

99-
local thumbnail_count = Thumbnailer.state.thumbnail_count
100-
local thumbnail_delta = Thumbnailer.state.thumbnail_delta
101-
local thumbnail_size = Thumbnailer.state.thumbnail_size
102-
local file_template = Thumbnailer.state.thumbnail_template
103-
local file_duration = mp.get_property_native("duration")
104-
local file_path = mp.get_property_native("path")
105-
106-
msg.info(("Generating %d thumbnails @ %dx%d"):format(thumbnail_count, thumbnail_size.w, thumbnail_size.h))
107-
108-
-- Create directory for the thumbnails
109-
local thumbnail_directory = split_path(file_template)
110-
local l, err = utils.readdir(thumbnail_directory)
92+
local thumbnail_indexes, err = utils.parse_json(frames_json_string)
11193
if err then
112-
msg.info("Creating", thumbnail_directory)
113-
create_directories(thumbnail_directory)
94+
msg.error("Failed to parse thumbnail frame indexes")
95+
return
11496
end
11597

11698
local thumbnail_func = create_thumbnail_mpv
@@ -122,7 +104,10 @@ function generate_thumbnails(from_keypress)
122104
end
123105
end
124106

125-
if Thumbnailer.state.is_remote then
107+
local file_duration = mp.get_property_native("duration")
108+
local file_path = mp.get_property_native("path")
109+
110+
if thumb_state.is_remote then
126111
thumbnail_func = create_thumbnail_mpv
127112
if thumbnailer_options.remote_direct_stream then
128113
-- Use the direct stream (possibly) provided by ytdl
@@ -132,14 +117,16 @@ function generate_thumbnails(from_keypress)
132117
end
133118
end
134119

135-
mp.commandv("script-message", "mpv_thumbnail_script-enabled")
120+
msg.debug(("Generating %d thumbnails @ %dx%d"):format(#thumbnail_indexes, thumb_state.thumbnail_size.w, thumb_state.thumbnail_size.h))
136121

137122
local generate_thumbnail_for_index = function(thumbnail_index)
123+
local thumbnail_path = thumb_state.thumbnail_template:format(thumbnail_index)
124+
local timestamp = math.min(file_duration, thumbnail_index * thumb_state.thumbnail_delta)
138125

139126
mp.commandv("script-message", "mpv_thumbnail_script-progress", tostring(thumbnail_index))
140127

141128
-- The expected size (raw BGRA image)
142-
local thumbnail_raw_size = (thumbnail_size.w * thumbnail_size.h * 4)
129+
local thumbnail_raw_size = (thumb_state.thumbnail_size.w * thumb_state.thumbnail_size.h * 4)
143130

144131
local need_thumbnail_generation = false
145132

@@ -158,7 +145,7 @@ function generate_thumbnails(from_keypress)
158145
end
159146

160147
if need_thumbnail_generation then
161-
local ret = thumbnail_func(file_path, timestamp, thumbnail_size, thumbnail_path)
148+
local ret = thumbnail_func(file_path, timestamp, thumb_state.thumbnail_size, thumbnail_path)
162149
local success = check_output(ret, thumbnail_path)
163150

164151
if success == nil then
@@ -200,33 +187,35 @@ function generate_thumbnails(from_keypress)
200187
mp.commandv("script-message", "mpv_thumbnail_script-ready", tostring(thumbnail_index), thumbnail_path)
201188
end
202189

203-
-- Keep track of which thumbnails we've checked during the passes (instead of proper math for no-overlap)
204-
local generated_thumbnails = {}
205-
206-
-- Do several passes over the thumbnails with increasing frequency
207-
for res = 6, 0, -1 do
208-
local nth = (2^res)
209-
210-
for thumbnail_index = 0, thumbnail_count-1, nth do
211-
if not generated_thumbnails[thumbnail_index] then
212-
local bail = generate_thumbnail_for_index(thumbnail_index)
213-
if bail then return end
214-
generated_thumbnails[thumbnail_index] = true
215-
end
216-
end
190+
for i, thumbnail_index in ipairs(thumbnail_indexes) do
191+
local bail = generate_thumbnail_for_index(thumbnail_index)
192+
if bail then return end
217193
end
218194
end
219195

196+
-- Set up listeners and keybinds
220197

221-
function on_script_keypress()
222-
mp.osd_message("Starting thumbnail generation", 2)
223-
generate_thumbnails(true)
224-
mp.osd_message("All thumbnails generated", 2)
225-
end
198+
-- Job listener
199+
mp.register_script_message("mpv_thumbnail_script-job", do_worker_job)
226200

227-
-- Set up listeners and keybinds
228201

229-
mp.register_script_message("mpv_thumbnail_script-generate", generate_thumbnails)
202+
-- Register this worker with the master script
203+
local register_timer = nil
204+
local register_timeout = mp.get_time() + 3
205+
206+
local register_function = function()
207+
if mp.get_time() > register_timeout and register_timer then
208+
msg.error("Thumbnail worker registering timed out")
209+
register_timer:stop()
210+
else
211+
msg.debug("Announcing self to master...")
212+
mp.commandv("script-message", "mpv_thumbnail_script-worker", mp.get_script_name())
213+
end
214+
end
215+
216+
register_timer = mp.add_periodic_timer(0.1, register_function)
230217

231-
local thumb_script_key = not thumbnailer_options.disable_keybinds and "T" or nil
232-
mp.add_key_binding(thumb_script_key, "generate-thumbnails", on_script_keypress)
218+
mp.register_script_message("mpv_thumbnail_script-slaved", function()
219+
msg.debug("Successfully registered with master")
220+
register_timer:stop()
221+
end)

src/thumbnailer_shared.lua

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ local Thumbnailer = {
1515

1616
finished_thumbnails = 0,
1717
thumbnails = {}
18-
}
18+
},
19+
workers = {}
1920
}
2021

2122
function Thumbnailer:clear_state()
@@ -197,27 +198,105 @@ function Thumbnailer:get_thumbnail_path(time_position)
197198
end
198199

199200
function Thumbnailer:register_client()
200-
-- Wait for server to tell us we're live
201-
mp.register_script_message("mpv_thumbnail_script-enabled", function() self.state.enabled = true end)
202201
mp.register_script_message("mpv_thumbnail_script-ready", function(index, path)
203202
self:on_thumb_ready(tonumber(index), path)
204203
end)
205204
mp.register_script_message("mpv_thumbnail_script-progress", function(index, path)
206205
self:on_thumb_progress(tonumber(index), path)
207206
end)
208207

209-
-- Notify server to generate thumbnails when video loads/changes
208+
mp.register_script_message("mpv_thumbnail_script-worker", function(worker_name)
209+
if not self.workers[worker_name] then
210+
msg.debug("Registered worker", worker_name)
211+
self.workers[worker_name] = true
212+
mp.commandv("script-message-to", worker_name, "mpv_thumbnail_script-slaved")
213+
end
214+
end)
215+
216+
-- Notify workers to generate thumbnails when video loads/changes
217+
-- This will be executed after the on_video_change (because it's registered after it)
210218
mp.observe_property("video-dec-params", "native", function()
211219
local duration = mp.get_property_native("duration")
212220
local max_duration = thumbnailer_options.autogenerate_max_duration
213221

214-
if duration and thumbnailer_options.autogenerate then
222+
if self.state.available and thumbnailer_options.autogenerate then
215223
-- Notify if autogenerate is on and video is not too long
216224
if duration < max_duration or max_duration == 0 then
217-
mp.commandv("script-message", "mpv_thumbnail_script-generate")
225+
self:start_worker_jobs()
218226
end
219227
end
220228
end)
229+
230+
local thumb_script_key = not thumbnailer_options.disable_keybinds and "T" or nil
231+
mp.add_key_binding(thumb_script_key, "generate-thumbnails", function()
232+
if self.state.available then
233+
self:start_worker_jobs()
234+
mp.osd_message("Started thumbnailer jobs")
235+
else
236+
mp.osd_message("Thumbnailing unavailabe")
237+
end
238+
end)
239+
end
240+
241+
function Thumbnailer:_create_thumbnail_job_order()
242+
local used_frames = {}
243+
local work_frames = {}
244+
245+
for x = 6, 0, -1 do
246+
local nth = (2^x)
247+
248+
for thi = 0, self.state.thumbnail_count-1, nth do
249+
if not used_frames[thi] then
250+
table.insert(work_frames, thi)
251+
used_frames[thi] = true
252+
end
253+
end
254+
end
255+
return work_frames
256+
end
257+
258+
function Thumbnailer:start_worker_jobs()
259+
self.state.enabled = true
260+
261+
-- Create directory for the thumbnails, if needed
262+
local thumbnail_directory = split_path(self.state.thumbnail_template)
263+
local l, err = utils.readdir(thumbnail_directory)
264+
if err then
265+
msg.info("Creating", thumbnail_directory)
266+
create_directories(thumbnail_directory)
267+
end
268+
269+
local worker_list = {}
270+
for worker_name in pairs(self.workers) do table.insert(worker_list, worker_name) end
271+
272+
local worker_count = #worker_list
273+
274+
if worker_count == 0 then
275+
local err = "No thumbnail workers found. Make sure you are not missing a script!"
276+
msg.error(err)
277+
mp.osd_message(err, 3)
278+
279+
else
280+
local frame_job_order = self:_create_thumbnail_job_order()
281+
local worker_jobs = {}
282+
for i = 1, worker_count do worker_jobs[worker_list[i]] = {} end
283+
284+
-- Split frames amongs the workers
285+
for i, thumbnail_index in ipairs(frame_job_order) do
286+
local worker_id = worker_list[ ((i-1) % worker_count) + 1 ]
287+
table.insert(worker_jobs[worker_id], thumbnail_index)
288+
end
289+
290+
local state_json_string = utils.format_json(self.state)
291+
292+
for worker_name, worker_frames in pairs(worker_jobs) do
293+
if #worker_frames > 0 then
294+
local frames_json_string = utils.format_json(worker_frames)
295+
msg.debug("Giving job to", worker_name, frames_json_string)
296+
mp.commandv("script-message-to", worker_name, "mpv_thumbnail_script-job", state_json_string, frames_json_string)
297+
end
298+
end
299+
end
221300
end
222301

223302
mp.observe_property("video-dec-params", "native", function(name, params) Thumbnailer:on_video_change(params) end)

0 commit comments

Comments
 (0)