fd
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
Some checks failed
smoke-mm / Install & smoke test mm --help (push) Has been cancelled
This commit is contained in:
204
MPV/LUA/trim.lua
Normal file
204
MPV/LUA/trim.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
-- Trim file directly in MPV using FFmpeg
|
||||
-- This script handles the actual video trimming with FFmpeg subprocess
|
||||
-- Then passes the trimmed file to Python for upload/metadata handling
|
||||
|
||||
local mp = require "mp"
|
||||
local utils = require "mp.utils"
|
||||
|
||||
local trim = {}
|
||||
|
||||
-- Configuration for trim presets
|
||||
trim.config = {
|
||||
output_dir = os.getenv('TEMP') or os.getenv('TMP') or '/tmp', -- use temp dir by default
|
||||
video_codec = "copy", -- lossless by default
|
||||
audio_codec = "copy",
|
||||
container = "auto",
|
||||
audio_bitrate = "",
|
||||
osd_duration = 2000,
|
||||
}
|
||||
|
||||
-- Quality presets for video trimming
|
||||
trim.presets = {
|
||||
copy = { video_codec="copy", audio_codec="copy" },
|
||||
high = { video_codec="libx264", crf="18", preset="slower", audio_codec="aac", audio_bitrate="192k" },
|
||||
medium = { video_codec="libx264", crf="20", preset="medium", audio_codec="aac", audio_bitrate="128k" },
|
||||
fast = { video_codec="libx264", crf="23", preset="fast", audio_codec="aac", audio_bitrate="96k" },
|
||||
tiny = { video_codec="libx264", crf="28", preset="ultrafast", audio_codec="aac", audio_bitrate="64k" },
|
||||
}
|
||||
|
||||
trim.current_quality = "copy"
|
||||
|
||||
-- Get active preset with current quality
|
||||
local function _get_active_preset()
|
||||
local preset = trim.presets[trim.current_quality] or {}
|
||||
local merged = {}
|
||||
for k, v in pairs(preset) do
|
||||
merged[k] = v
|
||||
end
|
||||
for k, v in pairs(trim.config) do
|
||||
if merged[k] == nil then
|
||||
merged[k] = v
|
||||
end
|
||||
end
|
||||
return merged
|
||||
end
|
||||
|
||||
-- Extract title from file path, handling special URL formats
|
||||
local function _parse_file_title(filepath)
|
||||
-- For torrent URLs, try to extract meaningful filename
|
||||
if filepath:match("torrentio%.strem%.fun") then
|
||||
-- Format: https://torrentio.strem.fun/resolve/alldebrid/.../filename/0/filename
|
||||
local filename = filepath:match("([^/]+)/0/[^/]+$")
|
||||
if filename then
|
||||
filename = filename:gsub("%.mkv$", ""):gsub("%.mp4$", ""):gsub("%.avi$", "")
|
||||
return filename
|
||||
end
|
||||
end
|
||||
|
||||
-- Standard file path
|
||||
local dir, name = utils.split_path(filepath)
|
||||
return name:gsub("%..+$", "")
|
||||
end
|
||||
|
||||
-- Format time duration as "1h3m-1h3m15s"
|
||||
local function _format_time_range(start_sec, end_sec)
|
||||
local function _sec_to_str(sec)
|
||||
local h = math.floor(sec / 3600)
|
||||
local m = math.floor((sec % 3600) / 60)
|
||||
local s = math.floor(sec % 60)
|
||||
|
||||
local parts = {}
|
||||
if h > 0 then table.insert(parts, h .. "h") end
|
||||
if m > 0 then table.insert(parts, m .. "m") end
|
||||
if s > 0 or #parts == 0 then table.insert(parts, s .. "s") end
|
||||
|
||||
return table.concat(parts)
|
||||
end
|
||||
|
||||
return _sec_to_str(start_sec) .. "-" .. _sec_to_str(end_sec)
|
||||
end
|
||||
|
||||
-- Trim file using FFmpeg with range in format "1h3m-1h3m15s"
|
||||
-- Returns: (success, output_path, error_msg)
|
||||
function trim.trim_file(input_file, range_str, temp_dir)
|
||||
if not input_file or input_file == "" then
|
||||
return false, nil, "No input file specified"
|
||||
end
|
||||
|
||||
if not range_str or range_str == "" then
|
||||
return false, nil, "No range specified (format: 1h3m-1h3m15s)"
|
||||
end
|
||||
|
||||
-- Use provided temp_dir or fall back to config
|
||||
if not temp_dir or temp_dir == "" then
|
||||
temp_dir = trim.config.output_dir
|
||||
end
|
||||
|
||||
-- Parse range string "1h3m-1h3m15s"
|
||||
local start_str, end_str = range_str:match("^([^-]+)-(.+)$")
|
||||
if not start_str or not end_str then
|
||||
return false, nil, "Invalid range format. Use: 1h3m-1h3m15s"
|
||||
end
|
||||
|
||||
-- Convert time string to seconds
|
||||
local function _parse_time(time_str)
|
||||
local sec = 0
|
||||
local h = time_str:match("(%d+)h")
|
||||
local m = time_str:match("(%d+)m")
|
||||
local s = time_str:match("(%d+)s")
|
||||
|
||||
if h then sec = sec + tonumber(h) * 3600 end
|
||||
if m then sec = sec + tonumber(m) * 60 end
|
||||
if s then sec = sec + tonumber(s) end
|
||||
|
||||
return sec
|
||||
end
|
||||
|
||||
local start_time = _parse_time(start_str)
|
||||
local end_time = _parse_time(end_str)
|
||||
local duration = end_time - start_time
|
||||
|
||||
if duration <= 0 then
|
||||
return false, nil, "Invalid range: end time must be after start time"
|
||||
end
|
||||
|
||||
-- Prepare output path
|
||||
local dir, name = utils.split_path(input_file)
|
||||
|
||||
-- If input is a URL, extract filename from URL path
|
||||
if input_file:match("^https?://") then
|
||||
-- For URLs, try to extract the meaningful filename
|
||||
name = input_file:match("([^/]+)$") or "stream"
|
||||
dir = trim.config.output_dir
|
||||
end
|
||||
|
||||
local out_dir = trim.config.output_dir
|
||||
local ext = (trim.config.container == "auto") and input_file:match("^.+(%..+)$") or ("." .. trim.config.container)
|
||||
local base_name = name:gsub("%..+$", "")
|
||||
local out_path = utils.join_path(out_dir, base_name .. "_" .. range_str .. ext)
|
||||
|
||||
-- Normalize path to use consistent backslashes on Windows
|
||||
out_path = out_path:gsub("/", "\\")
|
||||
|
||||
-- Build FFmpeg command
|
||||
local p = _get_active_preset()
|
||||
local args = { "ffmpeg", "-y", "-ss", tostring(start_time), "-i", input_file, "-t", tostring(duration) }
|
||||
|
||||
-- Video codec
|
||||
if p.video_codec == "copy" then
|
||||
table.insert(args, "-c:v")
|
||||
table.insert(args, "copy")
|
||||
else
|
||||
table.insert(args, "-c:v")
|
||||
table.insert(args, p.video_codec)
|
||||
if p.crf and p.crf ~= "" then
|
||||
table.insert(args, "-crf")
|
||||
table.insert(args, p.crf)
|
||||
end
|
||||
if p.preset and p.preset ~= "" then
|
||||
table.insert(args, "-preset")
|
||||
table.insert(args, p.preset)
|
||||
end
|
||||
end
|
||||
|
||||
-- Audio codec
|
||||
if p.audio_codec == "copy" then
|
||||
table.insert(args, "-c:a")
|
||||
table.insert(args, "copy")
|
||||
else
|
||||
table.insert(args, "-c:a")
|
||||
table.insert(args, p.audio_codec)
|
||||
if p.audio_bitrate and p.audio_bitrate ~= "" then
|
||||
table.insert(args, "-b:a")
|
||||
table.insert(args, p.audio_bitrate)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(args, out_path)
|
||||
|
||||
-- Execute FFmpeg synchronously
|
||||
local result = mp.command_native({ name = "subprocess", args = args, capture_stdout = true, capture_stderr = true })
|
||||
|
||||
if not result or result.status ~= 0 then
|
||||
local error_msg = result and result.stderr or "Unknown FFmpeg error"
|
||||
return false, nil, "FFmpeg failed: " .. error_msg
|
||||
end
|
||||
|
||||
return true, out_path, nil
|
||||
end
|
||||
|
||||
-- Cycle to next quality preset
|
||||
function trim.cycle_quality()
|
||||
local presets = { "copy", "high", "medium", "fast", "tiny" }
|
||||
local idx = 1
|
||||
for i, v in ipairs(presets) do
|
||||
if v == trim.current_quality then
|
||||
idx = i
|
||||
break
|
||||
end
|
||||
end
|
||||
trim.current_quality = presets[(idx % #presets) + 1]
|
||||
return trim.current_quality
|
||||
end
|
||||
|
||||
return trim
|
||||
Reference in New Issue
Block a user