用 FFmpeg + BAT 批处理打造视频工具箱:踩坑全记录

moonjerx
2026-04-29 / 0 评论 / 5 阅读 / 正在检测是否收录...
记录一次从零开始编写 Windows 批处理视频工具箱的完整过程,重点整理调试途中踩到的经典 CMD 坑。工具最终实现了视频掐头去尾、多种合并模式、旋转、分辨率预处理、参数对比统一等功能。

最终脚本

@echo off
chcp 936 >nul
setlocal enabledelayedexpansion

:: 配置项: 是否显示ffmpeg编码输出 (1=显示, 0=隐藏)
set "SHOW_LOG=1"

title 视频工具箱

:MENU
cls
echo ==========================================
echo        视频工具箱 (FFmpeg)
echo ==========================================
echo.
echo   1. 移除视频开头 (掐头)
echo   2. 移除视频结尾 (去尾)
echo   3. 合并多个视频 (直接合并)
echo   4. 合并多个视频 (自动统一分辨率)
echo   5. 旋转视频 - 快速 (仅修改标记)
echo   6. 旋转视频 - 真实 (重新编码)
echo   7. 预处理视频分辨率 (填充黑边)
echo   8. 预处理视频参数 (对比并统一)
echo   9. 退出
echo.
echo ==========================================
set /p choice="请输入选项 (1-9): "
if "%choice%"=="1" goto MODE_CUT_START
if "%choice%"=="2" goto MODE_CUT_END
if "%choice%"=="3" goto MODE_MERGE
if "%choice%"=="4" goto MODE_MERGE_PAD
if "%choice%"=="5" goto MODE_ROTATE_FAST
if "%choice%"=="6" goto MODE_ROTATE_REAL
if "%choice%"=="7" goto MODE_PAD
if "%choice%"=="8" goto MODE_PREPROCESS
if "%choice%"=="9" exit /b
goto MENU

:: ==========================================
:: 模式 1: 移除开头
:: ==========================================
:MODE_CUT_START
cls
echo [模式: 移除开头]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set "tn=%%i"
    echo !tn! | findstr /i "_out _rotated _padded _handle" >nul
    if errorlevel 1 (
        set /a index+=1
        set "file_!index!=%%i"
        set /a file_count+=1
        echo !index!.    %%i
    )
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU
set /p sec="移除开头秒数: "
if "%sec%"=="" goto MENU
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "vf=%%file_%%n%%"
    echo 处理: !vf!
    if "%SHOW_LOG%"=="1" (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -ss %sec% -i "!vf!" -map 0 -c:v copy -c:a copy -map_metadata 0 "%%~nf_out%%~xf" -y
    ) else (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -ss %sec% -i "!vf!" -map 0 -c:v copy -c:a copy -map_metadata 0 "%%~nf_out%%~xf" -y -loglevel error
    )
    if !errorlevel! equ 0 (echo   成功) else (echo   失败)
)
pause & goto MENU

:: ==========================================
:: 模式 2: 移除结尾
:: ==========================================
:MODE_CUT_END
cls
echo [模式: 移除结尾]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set "tn=%%i"
    echo !tn! | findstr /i "_out _rotated _padded _handle" >nul
    if errorlevel 1 (
        set /a index+=1
        set "file_!index!=%%i"
        set /a file_count+=1
        echo !index!.    %%i
    )
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU
set /p rmsec="移除结尾秒数: "
if "%rmsec%"=="" goto MENU
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "vf=%%file_%%n%%"
    echo 处理: !vf!
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "!vf!" > temp_dur.txt 2>nul
    set /p dur=<temp_dur.txt
    del temp_dur.txt 2>nul
    for /f %%b in ('powershell -command "[math]::Round([double]'!dur!' - %rmsec%, 3)"') do set keep=%%b
    echo   保留: !keep! 秒
    if "%SHOW_LOG%"=="1" (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -i "!vf!" -t !keep! -map 0 -c:v copy -c:a copy -map_metadata 0 "%%~nf_out%%~xf" -y
    ) else (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -i "!vf!" -t !keep! -map 0 -c:v copy -c:a copy -map_metadata 0 "%%~nf_out%%~xf" -y -loglevel error
    )
    if !errorlevel! equ 0 (echo   成功) else (echo   失败)
)
pause & goto MENU

:: ==========================================
:: 模式 3: 直接合并
:: ==========================================
:MODE_MERGE
cls
echo [模式: 合并视频 (直接合并)]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set /a index+=1
    set "file_!index!=%%i"
    set /a file_count+=1
    echo !index!.    %%i
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU

del temp_list.txt 2>nul
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "tmpf=%%file_%%n%%"
    echo file '!tmpf!' >> temp_list.txt
)

echo 合并中...
if "%SHOW_LOG%"=="1" (
    ffmpeg -f concat -safe 0 -i temp_list.txt -c copy -y merged_out.mp4
) else (
    ffmpeg -f concat -safe 0 -i temp_list.txt -c copy -y merged_out.mp4 -loglevel error -stats
)
if exist merged_out.mp4 (
    echo 成功: merged_out.mp4
    del temp_list.txt 2>nul
) else (
    echo 失败
)
pause & goto MENU

:: ==========================================
:: 模式 4: 合并视频 (全自动 - 统一所有参数后合并)
:: ==========================================
:MODE_MERGE_PAD
cls
echo [模式: 合并视频 - 全自动统一参数并合并]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set /a index+=1
    set "file_!index!=%%i"
    set /a file_count+=1
    echo !index!.    %%i
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU

del temp_sel.txt temp_list.txt temp_merge_*.mp4 2>nul
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "tmpf=%%file_%%n%%"
    echo !tmpf! >> temp_sel.txt
)

:: 读取所有文件信息
set /a maxw=0
set /a maxh=0
set /a sel_count=0
for /f "delims=" %%f in (temp_sel.txt) do (
    set /a sel_count+=1
    echo %%f > temp_cur.txt
    set /p cur_file=<temp_cur.txt
    del temp_cur.txt 2>nul
    set "sel_file_!sel_count!=!cur_file!"

    ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "!cur_file!" > temp_w.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "!cur_file!" > temp_h.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of csv=s=x:p=0 "!cur_file!" > temp_br.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of csv=s=x:p=0 "!cur_file!" > temp_tb.txt 2>nul
    set /p tw=<temp_w.txt
    set /p th=<temp_h.txt
    set /p tbr=<temp_br.txt
    set /p ttb=<temp_tb.txt
    del temp_w.txt temp_h.txt temp_br.txt temp_tb.txt 2>nul
    set /a tw=!tw!+0
    set /a th=!th!+0
    set "sel_w_!sel_count!=!tw!"
    set "sel_h_!sel_count!=!th!"
    set "sel_br_!sel_count!=!tbr!"
    set "sel_tb_!sel_count!=!ttb!"
    if !tw! gtr !maxw! set /a maxw=!tw!
    if !th! gtr !maxh! set /a maxh=!th!
)
del temp_sel.txt 2>nul

:: 展示对比信息
echo.
echo ==========================================
for /L %%i in (1,1,%sel_count%) do (
    echo [%%i] !sel_file_%%i!
    echo     分辨率: !sel_w_%%i! x !sel_h_%%i!  time_base: !sel_tb_%%i!
)
echo.
echo 目标分辨率: !maxw! x !maxh!  目标time_base: 1/90000
echo ==========================================
echo.

:: 逐个处理并生成临时文件
for /L %%i in (1,1,%sel_count%) do (
    echo %%i > temp_idx.txt
    echo !sel_file_%%i! > temp_cf.txt
    echo !sel_w_%%i! > temp_vw.txt
    echo !sel_h_%%i! > temp_vh.txt
    echo !sel_tb_%%i! > temp_vtb.txt
    echo !sel_br_%%i! > temp_vbr.txt

    set /p cur_file=<temp_cf.txt
    set /p vw=<temp_vw.txt
    set /p vh=<temp_vh.txt
    set /p vtb=<temp_vtb.txt
    set /p vbr=<temp_vbr.txt
    del temp_cf.txt temp_vw.txt temp_vh.txt temp_vtb.txt temp_vbr.txt temp_idx.txt 2>nul
    set /a vw=!vw!+0
    set /a vh=!vh!+0
    set /a vbr=!vbr!+0

    echo [%%i] !cur_file!

    set "need_pad=1"
    if "!vw!"=="!maxw!" if "!vh!"=="!maxh!" set "need_pad=0"

    set "need_tb=1"
    echo !vtb! > temp_check.txt
    findstr /x "1/90000" temp_check.txt >nul 2>nul
    if not errorlevel 1 set "need_tb=0"
    del temp_check.txt 2>nul

    if "!need_pad!"=="1" (
        echo   填充分辨率 + 统一time_base
        set /a px=maxw - vw
        set /a px=px / 2
        set /a py=maxh - vh
        set /a py=py / 2
        ffmpeg -noautorotate -i "!cur_file!" -vf "pad=!maxw!:!maxh!:!px!:!py!:black" -c:v libx264 -preset fast -b:v !vbr! -video_track_timescale 90000 -c:a copy "temp_merge_%%i.mp4" -y -loglevel error
        if exist "temp_merge_%%i.mp4" (
            echo   完成
            echo file 'temp_merge_%%i.mp4' >> temp_list.txt
        ) else (
            echo   失败
        )
    )

    if "!need_pad!"=="0" if "!need_tb!"=="1" (
        echo   统一time_base
        ffmpeg -i "!cur_file!" -c copy -video_track_timescale 90000 "temp_merge_%%i.mp4" -y -loglevel error
        if exist "temp_merge_%%i.mp4" (
            echo   完成
            echo file 'temp_merge_%%i.mp4' >> temp_list.txt
        ) else (
            echo   失败
        )
    )

    if "!need_pad!"=="0" if "!need_tb!"=="0" (
        echo   参数一致,直接使用
        echo file '!cur_file!' >> temp_list.txt
    )
    echo.
)

echo 合并中...
ffmpeg -f concat -safe 0 -i temp_list.txt -c copy -y merged_out.mp4 -loglevel error -stats
if exist merged_out.mp4 (
    echo.
    echo 成功: merged_out.mp4
    del temp_list.txt temp_merge_*.mp4 2>nul
) else (
    echo 失败
)
pause & goto MENU

:: ==========================================
:: 模式 5: 旋转视频 - 快速
:: ==========================================
:MODE_ROTATE_FAST
cls
echo [模式: 旋转视频 - 快速]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set "tn=%%i"
    echo !tn! | findstr /i "_rotated" >nul
    if errorlevel 1 (
        set /a index+=1
        set "file_!index!=%%i"
        set /a file_count+=1
        echo !index!.    %%i
    )
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU
echo.
echo   1. 左转90度
echo   2. 右转90度
echo   3. 旋转180度
set /p rc="请选择: "
if "%rc%"=="1" set "rv=270"
if "%rc%"=="2" set "rv=90"
if "%rc%"=="3" set "rv=180"
if not defined rv goto MODE_ROTATE_FAST
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "vf=%%file_%%n%%"
    echo 处理: !vf!
    for /f "delims=" %%f in ("!vf!") do ffmpeg -i "!vf!" -map 0 -c copy -metadata:s:v:0 rotate=%rv% "%%~nf_rotated%%~xf" -y -loglevel error
    if !errorlevel! equ 0 (echo   成功) else (echo   失败)
)
pause & goto MENU

:: ==========================================
:: 模式 6: 旋转视频 - 真实
:: ==========================================
:MODE_ROTATE_REAL
cls
echo [模式: 旋转视频 - 真实重编码]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set "tn=%%i"
    echo !tn! | findstr /i "_rotated" >nul
    if errorlevel 1 (
        set /a index+=1
        set "file_!index!=%%i"
        set /a file_count+=1
        echo !index!.    %%i
    )
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号(逗号分隔): "
if "%sel%"=="" goto MENU
echo.
echo   1. 左转90度
echo   2. 右转90度
echo   3. 旋转180度
set /p rc="请选择: "
if "%rc%"=="1" set "tv=2"
if "%rc%"=="2" set "tv=1"
if "%rc%"=="3" set "tv=2,transpose=2"
if not defined tv goto MODE_ROTATE_REAL
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "vf=%%file_%%n%%"
    echo 处理: !vf!
    if "%SHOW_LOG%"=="1" (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -i "!vf!" -vf "transpose=%tv%" -c:v libx264 -preset fast -crf 18 -c:a copy "%%~nf_rotated%%~xf" -y
    ) else (
        for /f "delims=" %%f in ("!vf!") do ffmpeg -i "!vf!" -vf "transpose=%tv%" -c:v libx264 -preset fast -crf 18 -c:a copy "%%~nf_rotated%%~xf" -y -loglevel error -stats
    )
    if !errorlevel! equ 0 (echo   成功) else (echo   失败)
)
pause & goto MENU

:: ==========================================
:: 模式 7: 预处理视频分辨率
:: ==========================================
:MODE_PAD
cls
echo [模式: 预处理视频分辨率]
echo.
set /a file_count=0
set /a index=0
echo 序号  分辨率(宽x高)    文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set /a index+=1
    set "file_!index!=%%i"
    set /a file_count+=1
    ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "%%i" > temp_w.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "%%i" > temp_h.txt 2>nul
    set /p fw=<temp_w.txt
    set /p fh=<temp_h.txt
    del temp_w.txt temp_h.txt 2>nul
    echo !index!.  !fw! x !fh!    %%i
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入序号: "
if "%sel%"=="" goto MENU
call set "vf=%%file_%sel%%%"
echo.
echo 选中: !vf!
echo.
echo 请输入目标分辨率,格式: 宽,高
echo 例如当前视频是 720x400,想填充到 720x404,输入: 720,404
echo.
set /p res="目标分辨率(宽,高): "
if "%res%"=="" goto MENU

for /f "tokens=1,2 delims=," %%a in ("%res%") do (
    set /a target_w=%%a
    set /a target_h=%%b
)

echo 目标分辨率: !target_w!(宽) x !target_h!(高)

ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "!vf!" > temp_w.txt 2>nul
ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "!vf!" > temp_h.txt 2>nul
ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of csv=s=x:p=0 "!vf!" > temp_br.txt 2>nul
set /p vw=<temp_w.txt
set /p vh=<temp_h.txt
set /p vbr=<temp_br.txt
del temp_w.txt temp_h.txt temp_br.txt 2>nul
set /a vw=!vw!+0
set /a vh=!vh!+0
set /a vbr=!vbr!+0

if "!vw!"=="!target_w!" if "!vh!"=="!target_h!" (
    echo 当前分辨率已与目标一致,无需处理
    pause & goto MENU
)

set /a px=target_w - vw
set /a px=px / 2
set /a py=target_h - vh
set /a py=py / 2

echo 当前: !vw!(宽) x !vh!(高)  填充: 左右各!px!px 上下各!py!px
echo 处理中...

for /f "delims=" %%f in ("!vf!") do (
    if "%SHOW_LOG%"=="1" (
        ffmpeg -noautorotate -i "!vf!" -vf "pad=!target_w!:!target_h!:!px!:!py!:black" -c:v libx264 -preset fast -b:v !vbr! -video_track_timescale 90000 -c:a copy "%%~nf_padded%%~xf" -y
    ) else (
        ffmpeg -noautorotate -i "!vf!" -vf "pad=!target_w!:!target_h!:!px!:!py!:black" -c:v libx264 -preset fast -b:v !vbr! -video_track_timescale 90000 -c:a copy "%%~nf_padded%%~xf" -y -loglevel error
    )
)
if !errorlevel! equ 0 (
    echo 成功
) else (
    echo 失败
)
pause & goto MENU

:: ==========================================
:: 模式 8: 预处理视频参数 (对比并统一)
:: ==========================================
:MODE_PREPROCESS
cls
echo [模式: 预处理视频参数 - 对比并统一]
echo.
set /a file_count=0
set /a index=0
echo 序号  文件名
echo ----------------------------------------
for /f "delims=" %%i in ('dir /b *.mp4 *.mov *.m4v 2^>nul') do (
    set "tn=%%i"
    echo !tn! | findstr /i "_handle" >nul
    if errorlevel 1 (
        set /a index+=1
        set "file_!index!=%%i"
        set /a file_count+=1
        echo !index!.    %%i
    )
)
if !file_count! equ 0 (echo 未找到视频文件 & pause & goto MENU)
echo.
set /p sel="输入要对比的文件序号(逗号分隔,至少2个): "
if "%sel%"=="" goto MENU

del temp_sel.txt 2>nul
set "sel=%sel: =%"
set "sel=%sel:,= %"
for %%n in (%sel%) do (
    call set "tmpf=%%file_%%n%%"
    echo !tmpf! >> temp_sel.txt
)

:: 读取文件信息并存入变量
set /a maxw=0
set /a maxh=0
set /a sel_count=0
for /f "delims=" %%f in (temp_sel.txt) do (
    set /a sel_count+=1
    echo %%f > temp_cur.txt
    set /p cur_file=<temp_cur.txt
    del temp_cur.txt 2>nul
    set "sel_file_!sel_count!=!cur_file!"

    ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 "!cur_file!" > temp_w.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "!cur_file!" > temp_h.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of csv=s=x:p=0 "!cur_file!" > temp_br.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of csv=s=x:p=0 "!cur_file!" > temp_tb.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of csv=s=x:p=0 "!cur_file!" > temp_fps.txt 2>nul
    ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=s=x:p=0 "!cur_file!" > temp_codec.txt 2>nul
    ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "!cur_file!" > temp_dur.txt 2>nul

    set /p fw=<temp_w.txt
    set /p fh=<temp_h.txt
    set /p fbr=<temp_br.txt
    set /p ftb=<temp_tb.txt
    set /p ffps=<temp_fps.txt
    set /p fcodec=<temp_codec.txt
    set /p fdur=<temp_dur.txt
    del temp_w.txt temp_h.txt temp_br.txt temp_tb.txt temp_fps.txt temp_codec.txt temp_dur.txt 2>nul

    set /a fw=!fw!+0
    set /a fh=!fh!+0
    set "sel_w_!sel_count!=!fw!"
    set "sel_h_!sel_count!=!fh!"
    set "sel_br_!sel_count!=!fbr!"
    set "sel_tb_!sel_count!=!ftb!"
    set "sel_fps_!sel_count!=!ffps!"
    set "sel_codec_!sel_count!=!fcodec!"
    set "sel_dur_!sel_count!=!fdur!"

    if !fw! gtr !maxw! set /a maxw=!fw!
    if !fh! gtr !maxh! set /a maxh=!fh!
)
del temp_sel.txt 2>nul

:: 展示对比结果
echo.
echo ==========================================
echo 对比结果
echo ==========================================
for /L %%i in (1,1,%sel_count%) do (
    echo.
    echo [%%i] !sel_file_%%i!
    echo     分辨率  : !sel_w_%%i! x !sel_h_%%i!
    echo     time_base: !sel_tb_%%i!
    echo     帧率    : !sel_fps_%%i!
    echo     编码    : !sel_codec_%%i!
    echo     码率    : !sel_br_%%i! bps
    echo     时长    : !sel_dur_%%i! 秒
)
echo.
echo ==========================================
echo 统一参数
echo ==========================================
echo   目标分辨率  : !maxw! x !maxh!
echo   目标time_base: 1/90000
echo.
echo 将对每个文件生成 _handle 文件
echo ==========================================
echo.
set /p confirm="确认处理? (y/n): "
if /i not "%confirm%"=="y" goto MENU

echo.
for /L %%i in (1,1,%sel_count%) do (
    echo !sel_file_%%i! > temp_cf.txt
    echo !sel_w_%%i! > temp_vw.txt
    echo !sel_h_%%i! > temp_vh.txt
    echo !sel_tb_%%i! > temp_vtb.txt
    echo !sel_br_%%i! > temp_vbr.txt

    set /p cur_file=<temp_cf.txt
    set /p vw=<temp_vw.txt
    set /p vh=<temp_vh.txt
    set /p vtb=<temp_vtb.txt
    set /p vbr=<temp_vbr.txt
    del temp_cf.txt temp_vw.txt temp_vh.txt temp_vtb.txt temp_vbr.txt 2>nul
    set /a vw=!vw!+0
    set /a vh=!vh!+0
    set /a vbr=!vbr!+0

    echo [%%i] 处理: !cur_file!

    set "need_pad=1"
    if "!vw!"=="!maxw!" if "!vh!"=="!maxh!" set "need_pad=0"

    set "need_tb=1"
    echo !vtb! > temp_check.txt
    findstr /x "1/90000" temp_check.txt >nul 2>nul
    if not errorlevel 1 set "need_tb=0"
    del temp_check.txt 2>nul

    if "!need_pad!"=="1" (
        echo   填充分辨率 + 统一time_base
        set /a px=maxw - vw
        set /a px=px / 2
        set /a py=maxh - vh
        set /a py=py / 2
        for /f "delims=" %%f in ("!cur_file!") do (
            ffmpeg -noautorotate -i "!cur_file!" -vf "pad=!maxw!:!maxh!:!px!:!py!:black" -c:v libx264 -preset fast -b:v !vbr! -video_track_timescale 90000 -c:a copy "%%~nf_handle%%~xf" -y -loglevel error
        )
    )

    if "!need_pad!"=="0" if "!need_tb!"=="1" (
        echo   统一time_base
        for /f "delims=" %%f in ("!cur_file!") do (
            ffmpeg -i "!cur_file!" -c copy -video_track_timescale 90000 "%%~nf_handle%%~xf" -y -loglevel error
        )
    )

    if "!need_pad!"=="0" if "!need_tb!"=="0" (
        echo   无需处理,参数已一致
        for /f "delims=" %%f in ("!cur_file!") do (
            copy "!cur_file!" "%%~nf_handle%%~xf" >nul
        )
    )

    for /f "delims=" %%f in ("!cur_file!") do (
        if exist "%%~nf_handle%%~xf" (
            echo   成功: %%~nf_handle%%~xf
        ) else (
            echo   失败
        )
    )
    echo.
)

echo ==========================================
echo 全部处理完成
echo 请使用模式3直接合并所有 _handle 文件
echo ==========================================
pause & goto MENU

背景

手头有一批视频文件需要批量处理:掐头去尾、合并分集、统一分辨率。想做一个放在视频目录下就能双击运行的 .bat 工具箱,依赖 FFmpeg 和 ffprobe,无需安装额外环境。

最终实现的功能菜单:

1. 移除视频开头 (掐头)
2. 移除视频结尾 (去尾)
3. 合并多个视频 (直接合并)
4. 合并多个视频 (全自动统一参数后合并)
5. 旋转视频 - 快速 (仅修改标记)
6. 旋转视频 - 真实 (重新编码)
7. 预处理视频分辨率 (填充黑边)
8. 预处理视频参数 (对比并统一)
9. 退出

经典错误一览

❌ 错误1:-metadata:s:v rotate=-90 修改旋转无效

现象: 执行后视频没有任何变化。

原因: -metadata:s:v rotate=-90 只是修改元数据标记,很多播放器不识别,且参数值应为 270 而非 -90

正确做法:

:: 快速旋转(仅改标记,秒级完成)
ffmpeg -i input.mp4 -map 0 -c copy -metadata:s:v:0 rotate=270 output.mp4

:: 真实旋转(重新编码,100%兼容)
ffmpeg -i input.mp4 -vf "transpose=2" -c:v libx264 output.mp4

transpose 参数对照:

  • 1 = 顺时针90°
  • 2 = 逆时针90°
  • 2,transpose=2 = 旋转180°

❌ 错误2:-ss 参数位置错误导致截取不准

现象: 指定去掉开头8秒,实际从第6秒开始截取;输出文件比原文件还大。

原因: -ss 放在 -i 之后是输出侧裁剪,需要解码所有帧,精度低且文件结构复杂。

:: ❌ 错误写法(输出侧裁剪)
ffmpeg -i input.mp4 -ss 8 -c copy output.mp4

:: ✅ 正确写法(输入侧裁剪,快速且精确)
ffmpeg -ss 8 -i input.mp4 -c copy output.mp4

❌ 错误3:ffprobe 获取分辨率时 %%h 变量失效

现象: 脚本显示 宽=500 高=%h,高度变成了字面字符串。

原因:for 循环中使用 tokens=1,2 delims=x 时,%%h 作为第二个 token 变量,在循环外或特定上下文中会失效。

修复: 将宽和高分两次独立获取,并通过临时文件中转:

ffprobe -v error -select_streams v:0 -show_entries stream=width  -of csv=s=x:p=0 "input.mp4" > temp_w.txt
ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 "input.mp4" > temp_h.txt
set /p vw=<temp_w.txt
set /p vh=<temp_h.txt
del temp_w.txt temp_h.txt

❌ 错误4:文件名含 [ ] 导致 for /f 解析失败

现象: 文件名如 [001]-video.mp4,在 for /f ... in ('dir /b') 循环中调用 ffprobe 时,分辨率始终为空。

原因: CMD 的 for 循环会将 [ ] 当作通配符处理。

修复: 先将文件名写入临时文件,再用 set /p 读取:

echo %%f > temp_cur.txt
set /p cur_file=<temp_cur.txt
del temp_cur.txt
ffprobe ... "!cur_file!" > temp_w.txt

❌ 错误5:set /a 浮点数截断导致1KB空文件

现象: 去除结尾操作后,输出文件只有1KB。

原因: 视频时长是浮点数(如 125.47 秒),set /a 只能处理整数,直接赋值会截断为 125,导致 -t 参数计算错误。

修复: 使用 PowerShell 计算浮点数:

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "input.mp4" > temp_dur.txt
set /p dur=<temp_dur.txt
for /f %%b in ('powershell -command "[math]::Round([double]'!dur!' - %rmsec%, 3)"') do set keep=%%b
ffmpeg -i "input.mp4" -t !keep! -c copy output.mp4

❌ 错误6:if !var!==!var! 触发 CMD 解析崩溃

现象: 整个 for 循环块进不去,连第一行 pause 也无法执行,直接闪退。

原因: if !vw!==!maxw! 展开后形如 if 608==608,CMD 解析时将右侧紧贴 ==! 误识别为三个等号,导致整个代码块预解析失败。

:: ❌ 危险写法
if !vw!==!maxw! ...

:: ✅ 正确写法(变量两边加引号)
if "!vw!"=="!maxw!" ...
CMD 铁律:if 比较变量时必须加引号。

❌ 错误7:for /L 循环内 if...else 嵌套导致预解析崩溃

现象: 同样是进不去循环,直接闪退。

原因: CMD 对整个括号块进行预解析,循环内嵌套 if "%SHOW_LOG%"=="1" (...) else (...) 会导致括号层次解析失败。

:: ❌ 危险写法
for /L %%i in (1,1,%count%) do (
    if "%SHOW_LOG%"=="1" (
        ffmpeg ...
    ) else (
        ffmpeg ... -loglevel error
    )
)

:: ✅ 修复写法(去掉 else,拆成两个独立 if)
for /L %%i in (1,1,%count%) do (
    if "%SHOW_LOG%"=="1" ffmpeg ...
    if "%SHOW_LOG%"=="0" ffmpeg ... -loglevel error
)

❌ 错误8:set /a px=(!maxw! - !vw!) / 2 导致 for 块崩溃(最隐蔽)

现象: 经历了错误6、7的修复后,循环依然闪退,连循环体第一行都进不去。

根本原因(由 GPT 最终定位):

for (...) do ( ... ) 本身是代码块,内部再出现 set /a px=(...) 的算术括号,叠加延迟变量 !var!,CMD 预解析阶段会将括号结构解析错,导致整个 for 块语法崩溃。

这是 CMD 解析期(parse-time)崩溃,而非运行期错误,因此任何运行期的修复都无效。

:: ❌ 致命写法(for块内的算术括号)
set /a px=(!maxw! - !vw!) / 2
set /a py=(!maxh! - !vh!) / 2

:: ✅ 正确写法(去掉括号,set /a 内可直接用变量名)
set /a px=maxw - vw
set /a px=px / 2
set /a py=maxh - vh
set /a py=py / 2
set /a 中可以直接使用变量名,不需要 !变量!,且不能在 for 块内使用括号表达式。

❌ 错误9:合并视频后 time_base 不一致导致时间轴异常

现象: 两个视频合并后,播放到衔接点卡住;用 ffprobe 检查合并文件时长,发现远超两段视频之和(如应为7000秒,实际显示41000秒)。

诊断:

ffprobe -v error -show_entries stream=codec_name,start_time,time_base -of default=noprint_wrappers=1 "video1.mp4"
ffprobe -v error -show_entries stream=codec_name,start_time,time_base -of default=noprint_wrappers=1 "video2.mp4"

输出对比:

video1: time_base=1/15360
video2: time_base=1/90000

原因: time_base 不一致,流复制拼接时时间戳计算完全错乱。

修复: 合并前统一 time_base 为标准值 1/90000(无需重新编码,仅修改容器信息):

ffmpeg -i "video1.mp4" -c copy -video_track_timescale 90000 "video1_fixed.mp4"

❌ 错误10:填充黑边后 time_base 被重置

现象:-c:v libx264 重编码填充黑边后,生成文件的 time_base 变为非标准值,再与其他文件合并时又出现时间轴问题。

修复: 填充黑边时同步指定 time_base

ffmpeg -noautorotate -i "input.mp4" ^
    -vf "pad=1920:1080:8:0:black" ^
    -c:v libx264 -preset fast -b:v 1200000 ^
    -video_track_timescale 90000 ^
    -c:a copy "output.mp4"

CMD 批处理避坑总结

问题错误写法正确写法
变量比较if !a!==!b!if "!a!"=="!b!"
for块内if分支if (...) else (...)拆成两个独立 if
for块内算术set /a x=(!a! - !b!) / 2set /a x=a-b / set /a x=x/2
含特殊字符文件名直接在for循环中使用先写临时文件再 set /p 读取
浮点数计算set /a 直接赋值通过 PowerShell 计算
编码问题chcp 65001 (UTF-8)chcp 936 (ANSI/GBK)
CMD 的 for 代码块会整体预解析,任何括号结构错误都会导致整块失效,表现为直接闪退。这是 parse-time 崩溃,与运行期错误性质不同,运行期的修复对其无效。

FFmpeg 常用参数备忘

:: 查看视频信息
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate,bit_rate,time_base -of default=noprint_wrappers=1 "input.mp4"

:: 流复制截取(快速,输入侧 -ss)
ffmpeg -ss 10 -i "input.mp4" -c copy "output.mp4"

:: 填充黑边
ffmpeg -i "input.mp4" -vf "pad=宽:高:x偏移:y偏移:black" -c:v libx264 "output.mp4"

:: 统一 time_base(不重编码)
ffmpeg -i "input.mp4" -c copy -video_track_timescale 90000 "output.mp4"

:: concat 合并
ffmpeg -f concat -safe 0 -i filelist.txt -c copy "output.mp4"

工具依赖:FFmpeg(含 ffprobe),需加入系统 PATH。脚本放于视频目录下双击运行。

0

评论 (0)

取消

您的IP: