用于 ffmpeg 无损切割 的AHK GUI

核心只有一个命令
得益于 ffmpeg 的兼容性,绝大部分的媒体格式应该都是支持的

设置输出路径时请注意
由于经常需要来回调整,所以是覆盖目标文件的

手动拖动需要处理的文件到左边的方框
默认输出路径为第一个文件所在目录下的_out目录,可手动输入或拖拽文件夹到此

时间格式 hh:mm:ss.ms
去头,输入起始时间
去尾,输入结束时间
去头去尾,两个都要输入
起始时间 01:00:00:000 表示 抛弃 前1小时

隐藏命令行在处理过程中可显隐 ffmpeg 的界面

由于切割主要受限于IO,所以是单线程处理
状态栏会显示当前进度,完成后可双击打开处理后文件所在的目录

由于 ffmpeg 支持格式众多所以没做限定,所以你也可以拖入不能处理的文件,但具体后果未知

1 个赞

需求AutoHotkey v2.0
更新一下,EXE没更新
可以记住上次保存的路径(需要访问注册表)
当剪贴板包含时间时 (格式 01:23:45 或 01:23:45.678)
可对两种时间的任意一个输入框进行粘贴

1

#Requires AutoHotkey v2.0-a
#SingleInstance force

; ffmpegPath := "D:\OneDrive\Backup\Bin\ffmpeg.exe"
ffmpegPath := "ffmpeg.exe" ; ffmpeg存在于环境变量中

MyGui := Gui()
MyGui.SetFont(, "Segoe UI")
LV := MyGui.Add("ListView", " r17 w400 xm", ["名称", "格式", "路径"])

FolderSave := RegRead("HKCU\SOFTWARE\AHK", "SaveFolder", "")
FolderSaveBool := FolderSave ~= "."
SaveFolder := MyGui.Add("CheckBox", "Section ym Checked" FolderSaveBool, "记住上次路径")
Dir := MyGui.Add("Edit", "voutDir w220", FolderSaveBool ? FolderSave : "输出路径")
DirBool := ()=> (!SaveFolder.Value || !FileExist(SaveFolder.Value))

MyGui.Add("Radio", "vMyRadio Checked", "去头")
MyGui.Add("Radio", "yp", "去尾")
MyGui.Add("Radio", "yp", "去头去尾")

MyGui.Add("Text","xs", "起始时间 : ")
s1 := GuiEdit("Sh")
s2 := GuiEdit("Sm")
s3 := GuiEdit("Ss", 0)
MyGui.Add("Text","yp", ".")
s4 := GuiEdit("Sms",0, 3, "000")

MyGui.Add("Text","xs", "结束时间 : ")
e1 := GuiEdit("Eh")
e2 := GuiEdit("Em")
e3 := GuiEdit("Es", 0)
MyGui.Add("Text","yp", ".")
e4 := GuiEdit("Ems",0, 3, "000")

showCmd := MyGui.Add("CheckBox", "Checked xs", "隐藏命令行")
showCmd.OnEvent("Click", ShowHideCMD)
MyGui.Add("CheckBox", "vaddTime xs", "输出文件名称附加时间")
MyGui.Add("Button", "w220", "清空列表").OnEvent("Click", Clear)
MyGui.Add("Button", "w220 h90", "开始处理").OnEvent("Click", do)
SB := MyGui.Add("StatusBar")
MyGui.OnEvent("DropFiles", DropFiles)
myGui.OnEvent("Close", Close)
MyGui.OnEvent("Escape", Close)
MyGui.Show

if A_Args.Length
    LVAdd(A_Args)

DropFiles(thisGui, Ctrl, FileArray, *)
{
    MouseGetPos(, , , &control)
    if control = LV.ClassNN
    {
        LV.Delete
        LVAdd(FileArray)
        if LV.GetCount()
            SB.SetText("总共 " LV.GetCount() " 个文件")
    }
    else if control = Dir.ClassNN && FileGetSize(FileArray[1]) = 0 && DirBool()
        Dir.Value := FileArray[1]
}

LVAdd(arr)
{
    for i in arr
        if FileGetSize(i) != 0
            loop files i
                LV.Add(, A_LoopFileName, A_LoopFileExt, A_LoopFileFullPath)
    if LV.GetCount() && DirBool()
        LV.ModifyCol(), Dir.Value := StrReplace(LV.GetText(1,3), LV.GetText(1)) "_out\"
}

GuiEdit(var, fh := 1, Limits := 2, default := "00")
{
    _v := MyGui.Add("Edit", "v" var " Number yp Limit" Limits, default)
    _v.OnEvent("Focus", (*) => SendMessage(177, 0, -1,MyGui.FocusedCtrl))
    _v.OnEvent("Change", (Ctrl, Info) => StrLen(Ctrl.Value) = Limits ? Send("{Tab}") : "")
    if fh
        MyGui.Add("Text","yp", ":")
    return _v
}

Clear(*)
{
    if LV.GetCount()
    {
        LV.Delete
        if DirBool()
            Dir.Value := "输出路径"
    }
}

do(*)
{
    global pid
    if !LV.GetCount()
        return

    _v := MyGui.Submit(0)

    if !FileExist(_outDir := _v.outDir ~= "\\$" ? _v.outDir : _v.outDir "\")
        DirCreate(_outDir)

    _startTime := (_v.MyRadio = 1 || _v.MyRadio = 3) ? " -ss " _v.Sh ":" _v.Sm ":" _v.Ss "." _v.Sms " " : " "
    _endTime := (_v.MyRadio = 2 || _v.MyRadio = 3) ? " -to " _v.Eh ":" _v.Em ":" _v.Es "." _v.Ems " " : " "

    SB.SetText("处理中...")
    Loop LV.GetCount()
    {
        _name := LV.GetText(A_Index)
        _path := LV.GetText(A_Index, 3)
        _ext := LV.GetText(A_Index, 2)
        lastPath := ffmpeg(_path, _name, "." _ext)
        SB.SetText("进度 " A_Index "\" LV.GetCount() "...")
        _Count := A_Index
    }
    SB.SetText("处理完成,共处理 " _Count "\" LV.GetCount() " 项, 双击打开所在文件夹")
    SB.OnEvent("DoubleClick", (*) => Run(_outDir))

    ffmpeg(path := "", name := "", ext := "")
    {
        global pid := ""
        _path := " `"" path "`" "
        _name := _v.addTime ? Addtime() : name
        _opath := " `"" _outDir _name "`" "
        RunWait(ffmpegPath " -i" _path "-c copy -y" _startTime _endTime _opath, ,showCmd.Value ? "Hide" : "", &pid)
        pid := ""
        return _opath
        Addtime()
        {
            _AddStartTime := (_v.Sh ? w2(_v.Sh) "h" : "") w2(_v.Sm) "m" w2(_v.Ss) "s"
            _AddEndTime := (_v.Eh || _v.Em || _v.Es || _v.Ems) ? (_v.Sh ? w2(_v.Sh) "h" : "") w2(_v.Sm) "m" w2(_v.Ss) "s" : GetLength(path)
            return StrReplace(name, ext, "(" _AddStartTime "-" _AddEndTime ")" ext)
        }
    }
}

ShowHideCMD(Ctrl, Info)
{
    if IsSet(pid) && pid
        (Ctrl.Value ? WinHide : WinShow)("ahk_pid " pid)
}

GetLength(path)
{
    SplitPath(path, &name, &dir)
    _s := ""
    try _s := Ceil(ComObject("Shell.Application").NameSpace(dir).ParseName(name).ExtendedProperty("System.Media.Duration") / 10000000)
    if !IsNumber(_s)
        return
    _h := w2(_s // 3600)
    _s := _h ? _s - 3600*_h : _s
    _m := w2(_s // 60)
    _s := w2(_m ? _s - 60*_m : _s)
    Return (_h ? _h "h" : "") _m "m" _s "s"
}

w2(str)
{
    return StrLen(str) = 2 ? str : "0" str
}

Close(thisGui)
{
    if SaveFolder.Value && FileExist(Dir.Value)
        RegWrite(Dir.Value, "REG_SZ", "HKCU\SOFTWARE\AHK", "SaveFolder")
    else if FolderSave
        RegWrite("", "REG_SZ", "HKCU\SOFTWARE\AHK", "SaveFolder")
    ExitApp()
}

#HotIf WinActive("ahk_id " MyGui.Hwnd) && A_Clipboard ~= "\d\d:\d\d:\d\d"
^v::
{
    RegExMatch(A_Clipboard,"(\d\d):(\d\d):(\d\d)(.([\d]{2,3}))?", &t)
    id := ControlGetFocus("a")
    if id = s1.Hwnd || id = s2.Hwnd || id = s3.Hwnd || id = s4.Hwnd
        s1.Value := t.1, s2.Value := t.2, s3.Value := t.3, s4.Value := t.5 ? t.5 : "000"
    else if id = e1.Hwnd || id = e2.Hwnd || id = e3.Hwnd || id = e4.Hwnd
        e1.Value := t.1, e2.Value := t.2, e3.Value := t.3, e4.Value := t.5 ? t.5 : "000"
}

附带一个mpv复制时间的脚本,快捷键CTRL+C

require 'mp'

local function set_clipboard(text)
    mp.commandv("run", "powershell", "set-clipboard", text);
end

local function copyTime()
    local time_pos = mp.get_property_number("time-pos")
    local time_in_seconds = time_pos
    local time_seg = time_pos % 60
    time_pos = time_pos - time_seg
    local time_hours = math.floor(time_pos / 3600)
    time_pos = time_pos - (time_hours * 3600)
    local time_minutes = time_pos/60
    time_seg,time_ms=string.format("%.03f", time_seg):match"([^.]*).(.*)"
    time = string.format("%02d:%02d:%02d.%s", time_hours, time_minutes, time_seg, time_ms)
    mp.osd_message(string.format("Copied to Clipboard: %s", time))
    set_clipboard(time)    
end

mp.add_key_binding("ctrl+c", "copyTime", copyTime)

链接: 百度网盘 请输入提取码 提取码: pnqc 复制这段内容后打开百度网盘手机App,操作更方便哦

exe 需要 ffmpeg 存在于环境变量中
测试方法
开始-运行-CMD
输入 ffmpeg
如图所示就代表可行

会有关键帧的问题吧,以前这帖子谈到过。

我以前用 Boilsoft Video Splitter也有这问题
不过就个人的用途只是用来去除一些宣传或广告
精度也不用要求那么细。

Boilsoft Video Splitter 需要找破解还要装code还有格式限制
ffmpeg 几乎兼容我所有的媒体格式,mp3,m4a,mp4,webm,mkv…
命令行也简单
除了速度比不上,简直完美
速度的问题像上图, 不会立即处理,会卡在 frame = 0这个阶段一会
开始处理后就只受限于IO速度了。

以前意识到这问题之前,剪某个视频,落不到想要的点上,给我急死。所以真到需要用的时候才觉得重要,其它时候无所谓了。

1 个赞