找了一圈常用播放器(vlc, potplayer, mpv等),传统的 A-B Repeat 永远只能设一组 \[A, B\]。如果一段视频里有三四处想反复精读的地方,每次都要重新设点,打断思绪。 想要一个能像播放列表一样管理“多个片段区间”,点播放后只在这些区间里循环跳跃播放的本地播放器。求大家指路!
这个需求确实很有个性,所以也许不太容易找到符合你需求的现成app,可以拿AI花半小时搓一个。
确实,现在的 AI 写代码是快。你这么一说我倒真想试试用 mpv 的 lua 脚本糊一个。
不过好奇问下,如果是你来做,UI 这块打算怎么处理?单条进度条上怎么优雅地展示并操作多段选区?如果只是快捷键打点盲跳感觉有点容易乱,真想听听大家的交互思路。
mpv写一个脚本可以的, 因为有个edl格式支持截取时间, 我用我旧的标记选段脚本找ai升级了一下内联edl播放成功了, 不过由于脚本标记模式的信息有点多, 把画面都挡完了不适合观看,我只是测试了一下可行度
-- ============================================================
-- video_cut_mini.lua - 极简版:F2 标记模式 / N 标记 / Ctrl+p EDL 播放
-- ============================================================
-- script-opts/video_cut_mini.conf 可配置:
-- keybind_toggle=f2 (进入/退出标记模式)
-- keybind_mark=n (标记 A/B 点)
-- keybind_play_marked=Ctrl+p(EDL 播放/退出)
-- ============================================================
local options = require 'mp.options'
local o = {
keybind_toggle = "f2",
keybind_mark = "n",
keybind_play_marked = "Ctrl+p",
}
options.read_options(o, 'video_cut_mini')
-- ====================== 状态 ======================
local marking_mode = false
local segments = {}
local current_segment_start = nil
local edl_active = false
local edl_saved_path = nil
local edl_saved_pos = 0
-- ====================== 提示(2 秒自动消失) ======================
local function notify(msg, duration)
mp.osd_message(msg, duration or 2)
end
-- ====================== 路径 → file:// URL(用于 EDL 片段) ======================
local function path_to_file_url(p)
p = p:gsub("\\", "/")
if p:match("^%a:") then p = "/" .. p end
if not p:match("^/") then p = "/" .. p end
-- 百分号编码:保留 unreserved 与 / :,其余转义(兼容中文路径、空格、, ; =)
p = p:gsub("[^%w%-%._~:/]", function(c)
return string.format("%%%02X", string.byte(c))
end)
return "file://" .. p
end
-- ====================== 有效片段 ======================
local function get_valid_segments()
local valid = {}
for _, s in ipairs(segments) do
if s.start and s.end_ and s.start < s.end_ then
table.insert(valid, s)
end
end
return valid
end
-- ====================== 标记 A/B ======================
local function mark_current_time()
if not marking_mode then return end
local pos = mp.get_property_number("time-pos")
if not pos then
notify("无法获取当前位置", 2)
return
end
if not current_segment_start then
current_segment_start = pos
table.insert(segments, { start = pos })
notify(string.format("✓ 开始点: %.3f", pos), 2)
else
local n = #segments
segments[n].end_ = pos
current_segment_start = nil
notify(string.format("✓ 结束点: %.3f (片段 %d)", pos, n), 2)
end
end
-- ====================== EDL 播放 / 退出 ======================
local function stop_edl()
if not edl_active then return end
if edl_saved_path then
mp.commandv("loadfile", edl_saved_path, "replace")
if edl_saved_pos and edl_saved_pos > 0 then
mp.commandv("seek", string.format("%.3f", edl_saved_pos), "absolute")
end
end
edl_active = false
edl_saved_path = nil
edl_saved_pos = 0
end
local function play_marked_segments()
-- 已在 EDL:退出并回原文件
if edl_active then
stop_edl()
notify("已退出 EDL 播放模式", 2)
return
end
if not marking_mode then
notify("请先按 " .. o.keybind_toggle .. " 进入标记模式", 2)
return
end
local valid = get_valid_segments()
if #valid == 0 then
notify("没有有效片段,无法播放", 2)
return
end
local path = mp.get_property("path")
if not path or path == "" then
notify("无法获取文件路径", 2)
return
end
edl_saved_path = path
edl_saved_pos = mp.get_property_number("time-pos") or 0
-- 正确的 EDL 片段格式:<file_url>,<start>,<length>;...
local file_url = path_to_file_url(path)
local parts = {}
for _, seg in ipairs(valid) do
local length = seg.end_ - seg.start
table.insert(parts, string.format("%s,%.6f,%.6f", file_url, seg.start, length))
end
local edl = "edl://" .. table.concat(parts, ";")
mp.msg.info("EDL URL: " .. edl)
local ok = pcall(function()
mp.command_native({
name = "loadfile",
url = edl,
flags = "replace",
options = "external-file=" .. path
})
end)
if not ok then
mp.set_property("external-file", path)
mp.commandv("loadfile", edl, "replace")
end
edl_active = true
notify(string.format("▶ EDL 播放:%d 个片段(%s 退出)", #valid, o.keybind_play_marked), 2)
end
-- ====================== 模式切换 ======================
local function toggle_marking_mode()
if not marking_mode then
marking_mode = true
segments = {}
current_segment_start = nil
mp.add_forced_key_binding(o.keybind_mark, "mini_mark", mark_current_time)
mp.add_forced_key_binding(o.keybind_play_marked, "mini_play", play_marked_segments)
mp.add_forced_key_binding("Esc", "mini_exit", function()
if marking_mode then toggle_marking_mode() end
end)
notify("已进入标记模式(" .. o.keybind_mark .. " 标记 A/B," ..
o.keybind_play_marked .. " EDL 播放,Esc 退出)", 2)
else
-- 退出:先结束 EDL(如有)
if edl_active then stop_edl() end
marking_mode = false
segments = {}
current_segment_start = nil
mp.remove_key_binding("mini_mark")
mp.remove_key_binding("mini_play")
mp.remove_key_binding("mini_exit")
notify("已退出标记模式", 2)
end
end
-- ====================== 全局绑定 ======================
mp.add_key_binding(o.keybind_toggle, "mini_toggle", toggle_marking_mode)
使用流程
F2→ 提示“已进入标记模式”,2 秒后消失- 播放到 A 点按
n→ “✓ 开始点” - 播放到 B 点按
n→ “✓ 结束点” - 重复 2-3 标记多个片段
Ctrl+p→ 顺序播放所有片段(再按一次回原文件)Esc或F2→ 退出模式
这个是我那个脚本的精简版, 把其他主体功能都砍完了, 只保留了进模式 标记 和播放, 可以拿去给ai看看思路
时间轴上面加一个段落轨道,通过鼠标或快捷键快速导航。
用 AI 辅助不难实现
另外这个需求也可以用视频剪辑软件(反复预览是视频剪辑的常规操作)