2024-05-12 19:57:56 +00:00
|
|
|
--[[
|
|
|
|
This file is part of mpv.
|
|
|
|
|
|
|
|
mpv is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
mpv is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
]]
|
|
|
|
|
|
|
|
local utils = require "mp.utils"
|
|
|
|
local input = require "mp.input"
|
|
|
|
|
|
|
|
local function show_error(message)
|
|
|
|
mp.msg.error(message)
|
|
|
|
if mp.get_property_native("vo-configured") then
|
|
|
|
mp.osd_message(message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-playlist", function ()
|
|
|
|
local playlist = {}
|
2024-05-13 18:32:48 +00:00
|
|
|
local default_item
|
2024-06-07 23:28:36 +00:00
|
|
|
local show = mp.get_property_native("osd-playlist-entry")
|
2024-05-12 19:57:56 +00:00
|
|
|
|
|
|
|
for i, entry in ipairs(mp.get_property_native("playlist")) do
|
2024-06-07 23:28:36 +00:00
|
|
|
playlist[i] = entry.title
|
|
|
|
if not playlist[i] or show ~= "title" then
|
|
|
|
playlist[i] = entry.filename
|
|
|
|
if not playlist[i]:find("://") then
|
|
|
|
playlist[i] = select(2, utils.split_path(playlist[i]))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if entry.title and show == "both" then
|
|
|
|
playlist[i] = string.format("%s (%s)", entry.title, playlist[i])
|
|
|
|
end
|
2024-05-12 19:57:56 +00:00
|
|
|
|
|
|
|
if entry.playing then
|
|
|
|
default_item = i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if #playlist == 0 then
|
|
|
|
show_error("The playlist is empty.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select a playlist entry:",
|
|
|
|
items = playlist,
|
|
|
|
default_item = default_item,
|
|
|
|
submit = function (index)
|
|
|
|
mp.commandv("playlist-play-index", index - 1)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
2024-06-27 18:00:31 +00:00
|
|
|
local function format_flags(track)
|
|
|
|
local flags = ""
|
|
|
|
|
|
|
|
for _, flag in ipairs({
|
|
|
|
"default", "forced", "dependent", "visual-impaired", "hearing-impaired",
|
|
|
|
"image", "external"
|
|
|
|
}) do
|
|
|
|
if track[flag] then
|
|
|
|
flags = flags .. flag .. " "
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if flags == "" then
|
|
|
|
return ""
|
|
|
|
end
|
|
|
|
|
|
|
|
return " [" .. flags:sub(1, -2) .. "]"
|
|
|
|
end
|
|
|
|
|
2024-05-12 19:57:56 +00:00
|
|
|
local function format_track(track)
|
|
|
|
return (track.selected and "●" or "○") ..
|
|
|
|
(track.title and " " .. track.title or "") ..
|
|
|
|
" (" .. (
|
|
|
|
(track.lang and track.lang .. " " or "") ..
|
|
|
|
(track.codec and track.codec .. " " or "") ..
|
|
|
|
(track["demux-w"] and track["demux-w"] .. "x" .. track["demux-h"]
|
|
|
|
.. " " or "") ..
|
|
|
|
(track["demux-fps"] and not track.image
|
2024-05-14 18:10:11 +00:00
|
|
|
and string.format("%.4f", track["demux-fps"]):gsub("%.?0*$", "") ..
|
|
|
|
" fps " or "") ..
|
2024-05-12 19:57:56 +00:00
|
|
|
(track["demux-channel-count"] and track["demux-channel-count"] ..
|
2024-06-24 13:56:55 +00:00
|
|
|
"ch " or "") ..
|
2024-05-12 19:57:56 +00:00
|
|
|
(track["codec-profile"] and track.type == "audio"
|
|
|
|
and track["codec-profile"] .. " " or "") ..
|
|
|
|
(track["demux-samplerate"] and track["demux-samplerate"] / 1000 ..
|
2024-06-27 18:00:31 +00:00
|
|
|
" kHz " or "")
|
|
|
|
):sub(1, -2) .. ")" .. format_flags(track)
|
2024-05-12 19:57:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-track", function ()
|
|
|
|
local tracks = {}
|
|
|
|
|
|
|
|
for i, track in ipairs(mp.get_property_native("track-list")) do
|
2024-06-27 14:32:53 +00:00
|
|
|
tracks[i] = (track.image and "Image" or
|
|
|
|
track.type:sub(1, 1):upper() .. track.type:sub(2)) .. ": " ..
|
2024-05-12 19:57:56 +00:00
|
|
|
format_track(track)
|
|
|
|
end
|
|
|
|
|
|
|
|
if #tracks == 0 then
|
|
|
|
show_error("No available tracks.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select a track:",
|
|
|
|
items = tracks,
|
|
|
|
submit = function (id)
|
|
|
|
local track = mp.get_property_native("track-list/" .. id - 1)
|
|
|
|
if track then
|
|
|
|
mp.set_property(track.type, track.selected and "no" or track.id)
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
local function select_track(property, type, prompt, error)
|
|
|
|
local tracks = {}
|
|
|
|
local items = {}
|
2024-05-13 18:32:48 +00:00
|
|
|
local default_item
|
2024-05-12 19:57:56 +00:00
|
|
|
local track_id = mp.get_property_native(property)
|
|
|
|
|
|
|
|
for _, track in ipairs(mp.get_property_native("track-list")) do
|
|
|
|
if track.type == type then
|
|
|
|
tracks[#tracks + 1] = track
|
|
|
|
items[#items + 1] = format_track(track)
|
|
|
|
|
|
|
|
if track.id == track_id then
|
|
|
|
default_item = #items
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if #items == 0 then
|
|
|
|
show_error(error)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = prompt,
|
|
|
|
items = items,
|
|
|
|
default_item = default_item,
|
|
|
|
submit = function (id)
|
|
|
|
mp.set_property(property, tracks[id].selected and "no" or tracks[id].id)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-sid", function ()
|
|
|
|
select_track("sid", "sub", "Select a subtitle:", "No available subtitles.")
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-secondary-sid", function ()
|
|
|
|
select_track("secondary-sid", "sub", "Select a secondary subtitle:",
|
|
|
|
"No available subtitles.")
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-aid", function ()
|
|
|
|
select_track("aid", "audio", "Select an audio track:",
|
|
|
|
"No available audio tracks.")
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-vid", function ()
|
|
|
|
select_track("vid", "video", "Select a video track:",
|
|
|
|
"No available video tracks.")
|
|
|
|
end)
|
|
|
|
|
2024-05-30 07:41:44 +00:00
|
|
|
local function format_time(t, duration)
|
2024-05-12 19:57:56 +00:00
|
|
|
local h = math.floor(t / (60 * 60))
|
|
|
|
t = t - (h * 60 * 60)
|
|
|
|
local m = math.floor(t / 60)
|
|
|
|
local s = t - (m * 60)
|
|
|
|
|
2024-05-30 07:41:44 +00:00
|
|
|
if duration >= 60 * 60 or h > 0 then
|
|
|
|
return string.format("%.2d:%.2d:%.2d", h, m, s)
|
|
|
|
end
|
|
|
|
|
|
|
|
return string.format("%.2d:%.2d", m, s)
|
2024-05-12 19:57:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-chapter", function ()
|
|
|
|
local chapters = {}
|
|
|
|
local default_item = mp.get_property_native("chapter")
|
|
|
|
|
|
|
|
if default_item == nil then
|
|
|
|
show_error("No available chapters.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-05-30 07:41:44 +00:00
|
|
|
local duration = mp.get_property_native("duration", math.huge)
|
|
|
|
|
2024-05-12 19:57:56 +00:00
|
|
|
for i, chapter in ipairs(mp.get_property_native("chapter-list")) do
|
2024-05-30 07:41:44 +00:00
|
|
|
chapters[i] = format_time(chapter.time, duration) .. " " .. chapter.title
|
2024-05-12 19:57:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select a chapter:",
|
|
|
|
items = chapters,
|
|
|
|
default_item = default_item + 1,
|
|
|
|
submit = function (chapter)
|
|
|
|
mp.set_property("chapter", chapter - 1)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-subtitle-line", function ()
|
|
|
|
local sub = mp.get_property_native("current-tracks/sub")
|
|
|
|
|
|
|
|
if sub == nil then
|
|
|
|
show_error("No subtitle is loaded.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2024-05-31 13:52:51 +00:00
|
|
|
if sub.external and sub["external-filename"]:find("^edl://") then
|
|
|
|
sub["external-filename"] = sub["external-filename"]:match('https?://.*')
|
|
|
|
or sub["external-filename"]
|
|
|
|
end
|
|
|
|
|
2024-05-12 19:57:56 +00:00
|
|
|
local r = mp.command_native({
|
|
|
|
name = "subprocess",
|
|
|
|
capture_stdout = true,
|
|
|
|
args = sub.external
|
2024-05-13 13:27:07 +00:00
|
|
|
and {"ffmpeg", "-loglevel", "error", "-i", sub["external-filename"],
|
2024-05-12 19:57:56 +00:00
|
|
|
"-f", "lrc", "-map_metadata", "-1", "-fflags", "+bitexact", "-"}
|
2024-05-13 13:27:07 +00:00
|
|
|
or {"ffmpeg", "-loglevel", "error", "-i", mp.get_property("path"),
|
2024-05-12 19:57:56 +00:00
|
|
|
"-map", "s:" .. sub["id"] - 1, "-f", "lrc", "-map_metadata",
|
|
|
|
"-1", "-fflags", "+bitexact", "-"}
|
|
|
|
})
|
|
|
|
|
2024-05-13 13:27:07 +00:00
|
|
|
if r.error_string == "init" then
|
|
|
|
show_error("Failed to extract subtitles: ffmpeg not found.")
|
2024-05-12 19:57:56 +00:00
|
|
|
return
|
2024-05-13 13:27:07 +00:00
|
|
|
elseif r.status ~= 0 then
|
|
|
|
show_error("Failed to extract subtitles.")
|
2024-05-12 19:57:56 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
local sub_lines = {}
|
2024-05-30 03:21:01 +00:00
|
|
|
local sub_times = {}
|
2024-05-13 18:28:44 +00:00
|
|
|
local default_item
|
2024-06-16 19:45:47 +00:00
|
|
|
local delay = mp.get_property_native("sub-delay")
|
|
|
|
local time_pos = mp.get_property_native("time-pos") - delay
|
2024-05-30 07:41:44 +00:00
|
|
|
local duration = mp.get_property_native("duration", math.huge)
|
2024-05-12 19:57:56 +00:00
|
|
|
|
|
|
|
-- Strip HTML and ASS tags.
|
|
|
|
for line in r.stdout:gsub("<.->", ""):gsub("{\\.-}", ""):gmatch("[^\n]+") do
|
2024-05-30 03:21:01 +00:00
|
|
|
-- ffmpeg outputs LRCs with minutes > 60 instead of adding hours.
|
|
|
|
sub_times[#sub_times + 1] = line:match("%d+") * 60 + line:match(":([%d%.]*)")
|
2024-05-30 07:41:44 +00:00
|
|
|
sub_lines[#sub_lines + 1] = format_time(sub_times[#sub_times], duration) ..
|
|
|
|
" " .. line:gsub(".*]", "", 1)
|
2024-05-12 19:57:56 +00:00
|
|
|
|
2024-06-16 19:10:02 +00:00
|
|
|
if sub_times[#sub_times] <= time_pos then
|
2024-05-30 03:21:01 +00:00
|
|
|
default_item = #sub_times
|
2024-05-12 19:57:56 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select a line to seek to:",
|
|
|
|
items = sub_lines,
|
|
|
|
default_item = default_item,
|
|
|
|
submit = function (index)
|
2024-05-30 03:29:47 +00:00
|
|
|
-- Add an offset to seek to the correct line while paused without a
|
|
|
|
-- video track.
|
2024-06-16 19:45:47 +00:00
|
|
|
if mp.get_property_native("current-tracks/video/image") ~= false then
|
|
|
|
delay = delay + 0.1
|
|
|
|
end
|
|
|
|
|
|
|
|
mp.commandv("seek", sub_times[index] + delay, "absolute")
|
2024-05-12 19:57:56 +00:00
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-audio-device", function ()
|
|
|
|
local devices = mp.get_property_native("audio-device-list")
|
|
|
|
local items = {}
|
|
|
|
-- This is only useful if an --audio-device has been explicitly set,
|
|
|
|
-- otherwise its value is just auto and there is no current-audio-device
|
|
|
|
-- property.
|
|
|
|
local selected_device = mp.get_property("audio-device")
|
2024-05-13 18:32:48 +00:00
|
|
|
local default_item
|
2024-05-12 19:57:56 +00:00
|
|
|
|
|
|
|
if #devices == 0 then
|
|
|
|
show_error("No available audio devices.")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
for i, device in ipairs(devices) do
|
|
|
|
items[i] = device.name .. " (" .. device.description .. ")"
|
|
|
|
|
|
|
|
if device.name == selected_device then
|
|
|
|
default_item = i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select an audio device:",
|
|
|
|
items = items,
|
|
|
|
default_item = default_item,
|
|
|
|
submit = function (id)
|
|
|
|
mp.set_property("audio-device", devices[id].name)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "select-binding", function ()
|
|
|
|
local bindings = {}
|
|
|
|
|
|
|
|
for _, binding in pairs(mp.get_property_native("input-bindings")) do
|
|
|
|
if binding.priority >= 0 and (
|
|
|
|
bindings[binding.key] == nil or
|
|
|
|
(bindings[binding.key].is_weak and not binding.is_weak) or
|
|
|
|
(binding.is_weak == bindings[binding.key].is_weak and
|
|
|
|
binding.priority > bindings[binding.key].priority)
|
|
|
|
) then
|
|
|
|
bindings[binding.key] = binding
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local items = {}
|
|
|
|
for _, binding in pairs(bindings) do
|
|
|
|
if binding.cmd ~= "ignore" then
|
|
|
|
items[#items + 1] = binding.key .. " " .. binding.cmd
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
table.sort(items)
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Select a binding:",
|
|
|
|
items = items,
|
|
|
|
submit = function (i)
|
|
|
|
mp.command(items[i]:gsub("^.- ", ""))
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
local properties = {}
|
|
|
|
|
|
|
|
local function add_property(property, value)
|
|
|
|
value = value or mp.get_property_native(property)
|
|
|
|
|
|
|
|
if type(value) == "table" and next(value) then
|
|
|
|
for key, val in pairs(value) do
|
|
|
|
add_property(property .. "/" .. key, val)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
properties[#properties + 1] = property .. ": " .. utils.to_string(value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
mp.add_forced_key_binding(nil, "show-properties", function ()
|
|
|
|
properties = {}
|
|
|
|
|
|
|
|
-- Don't log errors for renamed and removed properties.
|
|
|
|
local msg_level_backup = mp.get_property("msg-level")
|
|
|
|
mp.set_property("msg-level", msg_level_backup == "" and "cplayer=no"
|
|
|
|
or msg_level_backup .. ",cplayer=no")
|
|
|
|
|
|
|
|
for _, property in pairs(mp.get_property_native("property-list")) do
|
|
|
|
add_property(property)
|
|
|
|
end
|
|
|
|
|
|
|
|
mp.set_property("msg-level", msg_level_backup)
|
|
|
|
|
|
|
|
add_property("current-tracks/audio")
|
|
|
|
add_property("current-tracks/video")
|
|
|
|
add_property("current-tracks/sub")
|
|
|
|
add_property("current-tracks/sub2")
|
|
|
|
|
|
|
|
table.sort(properties)
|
|
|
|
|
|
|
|
input.select({
|
|
|
|
prompt = "Inspect a property:",
|
|
|
|
items = properties,
|
|
|
|
submit = function (i)
|
|
|
|
if mp.get_property_native("vo-configured") then
|
|
|
|
mp.commandv("expand-properties", "show-text",
|
|
|
|
(#properties[i] > 100 and
|
|
|
|
"${osd-ass-cc/0}{\\fs9}${osd-ass-cc/1}" or "") ..
|
|
|
|
"$>" .. properties[i], 20000)
|
|
|
|
else
|
|
|
|
mp.msg.info(properties[i])
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|