Skip to content

Commit 2089586

Browse files
committed
select.lua: use sub-lines instead of forking to ffmpeg
This: - Removes the dependency of ffmpeg in PATH - useful on Windows, macOS (?), Flatpak. - Will support subrandr. - Avoids redownloading network subtitles. - Avoids slow demuxing of large mkvs. - But no longer returns future lines of embedded subtitles. - No longer returns embedded lines of regions you seeked beyond without playing them. - Changing sub track drops cached subtitles.
1 parent 66aa647 commit 2089586

3 files changed

Lines changed: 12 additions & 105 deletions

File tree

DOCS/man/mpv.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,7 @@ g-e
336336
Select an MKV edition or DVD/Blu-ray title.
337337

338338
g-l
339-
Select a subtitle line to seek to. This currently requires ``ffmpeg`` in
340-
``PATH``, or in the same folder as mpv on Windows.
339+
Select a subtitle line to seek to.
341340

342341
g-d
343342
Select an audio device.

DOCS/man/select.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,6 @@ Available script bindings are:
102102
``select-subtitle-line``
103103
Select a subtitle line to seek to. This doesn't work with image subtitles.
104104

105-
This currently requires ``ffmpeg`` in ``PATH``, or in the same folder as mpv
106-
on Windows.
107-
108105
``select-audio-device``
109106
Select an audio device.
110107

player/lua/select.lua

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -285,128 +285,39 @@ mp.add_key_binding(nil, "select-edition", function ()
285285
end)
286286

287287
mp.add_key_binding(nil, "select-subtitle-line", function ()
288-
local sub = mp.get_property_native("current-tracks/sub")
288+
local lines = mp.get_property_native("sub-lines")
289289

290-
if sub == nil then
291-
show_warning("No subtitle is loaded.")
290+
if not lines then
291+
show_warning("Subtitle lines could not be retrieved.")
292292
return
293293
end
294294

295-
if sub.external and sub["external-filename"]:find("^edl://") then
296-
sub["external-filename"] = sub["external-filename"]:match('https?://.*')
297-
or sub["external-filename"]
298-
end
299-
300-
local r = mp.command_native({
301-
_name = "subprocess",
302-
capture_stdout = true,
303-
args = sub.external
304-
and {"ffmpeg", "-loglevel", "error", "-i", sub["external-filename"],
305-
"-f", "lrc", "-map_metadata", "-1", "-fflags", "+bitexact", "-"}
306-
or {"ffmpeg", "-loglevel", "error", "-i", mp.get_property("path"),
307-
"-map", "s:" .. sub["id"] - 1, "-f", "lrc", "-map_metadata",
308-
"-1", "-fflags", "+bitexact", "-"}
309-
})
310-
311-
if r.error_string == "init" then
312-
show_error("Failed to extract subtitles: ffmpeg not found.")
313-
return
314-
elseif r.status ~= 0 then
315-
show_error("Failed to extract subtitles.")
316-
return
317-
end
318-
319-
local sub_lines = {}
320-
local sub_times = {}
295+
local items = {}
321296
local default_item
322297
local delay = mp.get_property_native("sub-delay")
323298
local time_pos = mp.get_property_native("time-pos") - delay
324299
local duration = mp.get_property_native("duration", math.huge)
325-
local sub_content = {}
326-
327-
-- Strip HTML and ASS tags and process subtitles
328-
for line in r.stdout:gmatch("[^\n]+") do
329-
-- Clean up tags
330-
local sub_line = line:gsub("<.->", "") -- Strip HTML tags
331-
:gsub("\\h+", " ") -- Replace '\h' tag
332-
:gsub("{[\\=].-}", "") -- Remove ASS formatting
333-
:gsub(".-]", "", 1) -- Remove time info prefix
334-
:gsub("^%s*(.-)%s*$", "%1") -- Strip whitespace
335-
:gsub("^m%s[mbl%s%-%d%.]+$", "") -- Remove graphics code
336-
337-
if sub.codec == "text" or (sub_line ~= "" and sub_line:match("^%s+$") == nil) then
338-
local sub_time = line:match("%d+") * 60 + line:match(":([%d%.]*)")
339-
local time_seconds = math.floor(sub_time)
340-
sub_content[time_seconds] = sub_content[time_seconds] or {}
341-
sub_content[time_seconds][sub_line] = true
342-
end
343-
end
344-
345-
-- Process all timestamps and content into selectable subtitle list
346-
for time_seconds, contents in pairs(sub_content) do
347-
for sub_line in pairs(contents) do
348-
sub_times[#sub_times + 1] = time_seconds
349-
sub_lines[#sub_lines + 1] = format_time(time_seconds, duration) .. " " .. sub_line
350-
end
351-
end
352-
353-
-- Generate time -> subtitle mapping
354-
local time_to_lines = {}
355-
for i = 1, #sub_times do
356-
local time = sub_times[i]
357-
local line = sub_lines[i]
358300

359-
if not time_to_lines[time] then
360-
time_to_lines[time] = {}
361-
end
362-
table.insert(time_to_lines[time], line)
363-
end
364-
365-
-- Sort by timestamp
366-
local sorted_sub_times = {}
367-
for i = 1, #sub_times do
368-
sorted_sub_times[i] = sub_times[i]
369-
end
370-
table.sort(sorted_sub_times)
371-
372-
-- Use a helper table to avoid duplicates
373-
local added_times = {}
374-
375-
-- Rebuild sub_lines and sub_times based on the sorted timestamps
376-
local sorted_sub_lines = {}
377-
for _, sub_time in ipairs(sorted_sub_times) do
378-
-- Iterate over all subtitle content for this timestamp
379-
if not added_times[sub_time] then
380-
added_times[sub_time] = true
381-
for _, line in ipairs(time_to_lines[sub_time]) do
382-
table.insert(sorted_sub_lines, line)
383-
end
384-
end
385-
end
386-
387-
-- Use the sorted subtitle list
388-
sub_lines = sorted_sub_lines
389-
sub_times = sorted_sub_times
301+
for i, line in ipairs(lines) do
302+
items[i] = format_time(line.start, duration) .. " " .. line.text
390303

391-
-- Get the default item (last subtitle before current time position)
392-
for i, sub_time in ipairs(sub_times) do
393-
if sub_time <= time_pos then
394-
default_item = i
304+
if line.start <= time_pos then
305+
default_item = #items
395306
end
396307
end
397308

398309
input.select({
399310
prompt = "Select a line to seek to:",
400-
items = sub_lines,
311+
items = items,
401312
default_item = default_item,
402-
submit = function (index)
313+
submit = function (i)
403314
-- Add an offset to seek to the correct line while paused without a
404315
-- video track.
405316
if mp.get_property_native("current-tracks/video/image") ~= false then
406317
delay = delay + 0.1
407318
end
408319

409-
mp.commandv("seek", sub_times[index] + delay, "absolute")
320+
mp.commandv("seek", lines[i].start + delay, "absolute")
410321
end,
411322
})
412323
end)

0 commit comments

Comments
 (0)