summaryrefslogtreecommitdiffstats
path: root/player/lua/select.lua
diff options
context:
space:
mode:
Diffstat (limited to 'player/lua/select.lua')
-rw-r--r--player/lua/select.lua386
1 files changed, 386 insertions, 0 deletions
diff --git a/player/lua/select.lua b/player/lua/select.lua
new file mode 100644
index 0000000000..504f578f47
--- /dev/null
+++ b/player/lua/select.lua
@@ -0,0 +1,386 @@
+--[[
+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 = {}
+ local default_item
+ local show = mp.get_property_native("osd-playlist-entry")
+
+ for i, entry in ipairs(mp.get_property_native("playlist")) do
+ 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
+
+ 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)
+
+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
+ and string.format("%.4f", track["demux-fps"]):gsub("%.?0*$", "") ..
+ " fps " or "") ..
+ (track["demux-channel-count"] and track["demux-channel-count"] ..
+ " ch " or "") ..
+ (track["codec-profile"] and track.type == "audio"
+ and track["codec-profile"] .. " " or "") ..
+ (track["demux-samplerate"] and track["demux-samplerate"] / 1000 ..
+ " kHz " or "") ..
+ (track.external and "external " or "")
+ ):sub(1, -2) .. ")"
+end
+
+mp.add_forced_key_binding(nil, "select-track", function ()
+ local tracks = {}
+
+ for i, track in ipairs(mp.get_property_native("track-list")) do
+ tracks[i] = track.type:sub(1, 1):upper() .. track.type:sub(2) .. ": " ..
+ 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 = {}
+ local default_item
+ 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)
+
+local function format_time(t, duration)
+ local h = math.floor(t / (60 * 60))
+ t = t - (h * 60 * 60)
+ local m = math.floor(t / 60)
+ local s = t - (m * 60)
+
+ 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)
+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
+
+ local duration = mp.get_property_native("duration", math.huge)
+
+ for i, chapter in ipairs(mp.get_property_native("chapter-list")) do
+ chapters[i] = format_time(chapter.time, duration) .. " " .. chapter.title
+ 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
+
+ if sub.external and sub["external-filename"]:find("^edl://") then
+ sub["external-filename"] = sub["external-filename"]:match('https?://.*')
+ or sub["external-filename"]
+ end
+
+ local r = mp.command_native({
+ name = "subprocess",
+ capture_stdout = true,
+ args = sub.external
+ and {"ffmpeg", "-loglevel", "error", "-i", sub["external-filename"],
+ "-f", "lrc", "-map_metadata", "-1", "-fflags", "+bitexact", "-"}
+ or {"ffmpeg", "-loglevel", "error", "-i", mp.get_property("path"),
+ "-map", "s:" .. sub["id"] - 1, "-f", "lrc", "-map_metadata",
+ "-1", "-fflags", "+bitexact", "-"}
+ })
+
+ if r.error_string == "init" then
+ show_error("Failed to extract subtitles: ffmpeg not found.")
+ return
+ elseif r.status ~= 0 then
+ show_error("Failed to extract subtitles.")
+ return
+ end
+
+ local sub_lines = {}
+ local sub_times = {}
+ local default_item
+ local sub_start = mp.get_property_native("sub-start",
+ mp.get_property_native("time-pos"))
+ local duration = mp.get_property_native("duration", math.huge)
+
+ -- Strip HTML and ASS tags.
+ for line in r.stdout:gsub("<.->", ""):gsub("{\\.-}", ""):gmatch("[^\n]+") do
+ -- ffmpeg outputs LRCs with minutes > 60 instead of adding hours.
+ sub_times[#sub_times + 1] = line:match("%d+") * 60 + line:match(":([%d%.]*)")
+ sub_lines[#sub_lines + 1] = format_time(sub_times[#sub_times], duration) ..
+ " " .. line:gsub(".*]", "", 1)
+
+ if sub_times[#sub_times] <= sub_start then
+ default_item = #sub_times
+ end
+ end
+
+ -- Handle sub-start of embedded subs being slightly earlier than
+ -- ffmpeg's timestamps.
+ sub_start = mp.get_property_native("sub-start")
+ if sub_start and default_item and sub_times[default_item] < sub_start and
+ sub_lines[default_item + 1] then
+ default_item = default_item + 1
+ end
+
+ input.select({
+ prompt = "Select a line to seek to:",
+ items = sub_lines,
+ default_item = default_item,
+ submit = function (index)
+ -- Add an offset to seek to the correct line while paused without a
+ -- video track.
+ local offset = mp.get_property_native("current-tracks/video/image") == false
+ and 0 or .09
+ mp.commandv("seek", sub_times[index] + offset, "absolute")
+ 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")
+ local default_item
+
+ 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)