1. 背景
使用迅雷影音的“截取视频” 功能得到的视频片段在某些播放器下无法拖拽进度条,甚至无法播放,撰写此批处理实现视频无损修复(调用 ffmpeg )。
2. 功能
- 列出批处理所在文件夹内的所有视频
- 检查视频存在的问题
- 依次修复(需要用户确认,“y” 执行“n”跳过)
- 使用快速修复模式
- 快速修复失败会使用重编码方式修复
3. 使用方法
- 下载 ffmpeg.exe 和 ffprobe.exe 并放到视频所在的文件夹
- 将本文下面提供的批处理源码拷贝并存到视频所在的文件夹
- 修改文本名称为:“修复视频.bat”
- 双击运行
- 根据控制台提示操作,你只需要3个动作:敲回车,敲"y" 或者 “n”
4. 批处理源码
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul
title MP4视频智能扫描修复工具
mode con: cols=130 lines=40
set "LIST_ALL=%temp%\mp4_all_list.tmp"
set "LIST_BAD=%temp%\mp4_bad_list.tmp"
set "LIST_BAD_WORK=%temp%\mp4_bad_valid_list.tmp"
set "FFP_LOG=%temp%\ffp.log"
set "FFP_ERR=%temp%\ffp.err"
set "PS_OUT=%temp%\mp4_check_result.tmp"
set "FIX_LOG=%temp%\mp4_fix.log"
del /q "%LIST_ALL%" 2>nul
del /q "%LIST_BAD%" 2>nul
del /q "%LIST_BAD_WORK%" 2>nul
del /q "%FFP_LOG%" 2>nul
del /q "%FFP_ERR%" 2>nul
del /q "%PS_OUT%" 2>nul
del /q "%FIX_LOG%" 2>nul
echo.
echo ==============================================================================================================================
echo MP4 智能扫描修复工具
echo 状态:正常 / 异常 / 已处理 检测:ffprobe 高速 修复:IBP帧/时间戳/进度条
echo ==============================================================================================================================
echo.
if not exist "%~dp0ffprobe.exe" (echo 缺少 ffprobe.exe & pause>nul & exit)
if not exist "%~dp0ffmpeg.exe" (echo 缺少 ffmpeg.exe & pause>nul & exit)
:: ==============================================
:: 步骤1:采集所有MP4(相对路径 + 大小)
:: ==============================================
echo 【步骤1/4】采集所有 MP4 文件(递归子目录)
echo ------------------------------------------------------------------------------------------------------------------------------
set "TOTAL=0"
for /r %%f in (*.mp4) do (
set "RELATIVE=%%~f"
set "RELATIVE=!RELATIVE:%cd%\=!"
call :GET_MB "%%~f" MB
echo !RELATIVE! [!MB! MB]
>> "%LIST_ALL%" echo %%~f
set /a TOTAL+=1
)
echo ------------------------------------------------------------------------------------------------------------------------------
echo 采集完成!共找到 MP4:!TOTAL! 个
echo.
pause
:: ==============================================
:: 步骤2:状态分析
:: ==============================================
echo.
echo 【步骤2/4】视频状态分析
echo ------------------------------------------------------------------------------------------------------------------------------
set "CNT_NORMAL=0"
set "CNT_FIXED=0"
set "CNT_ERROR=0"
for /f "usebackq delims=" %%f in ("%LIST_ALL%") do (
call :ANALYZE_FILE "%%f"
)
call :PREPARE_BAD_LIST
echo ------------------------------------------------------------------------------------------------------------------------------
echo 统计:正常=!CNT_NORMAL! 已处理=!CNT_FIXED! 异常=!CNT_ERROR!
echo ------------------------------------------------------------------------------------------------------------------------------
echo.
if !CNT_ERROR! equ 0 (
echo ✅ 无异常视频,程序结束
pause>nul
exit /b
)
pause
:: ==============================================
:: 步骤3:逐条确认
:: ==============================================
echo.
echo 【步骤3/4】异常视频逐条确认(Y=修复 N=跳过)
echo ------------------------------------------------------------------------------------------------------------------------------
set "CUR=0"
for /f "usebackq delims=" %%f in ("%LIST_BAD_WORK%") do (
set /a CUR+=1
call :CONFIRM_FIX "%%f"
)
:: ==============================================
:: 步骤4:完成
:: ==============================================
echo.
echo ==============================================================================================================================
echo ✅ 全部处理完成
echo ==============================================================================================================================
echo.
pause>nul
exit /b
:: ==============================================
:: 分析单个文件
:: ==============================================
:ANALYZE_FILE
set "FILE=%~1"
set "NAME=%~n1"
set "REASON="
set "STATE="
set "RELATIVE=%~1"
set "RELATIVE=!RELATIVE:%cd%\=!"
call :GET_MB "%~1" MB
:: 跳过修复文件
if /i "!NAME:~-6!"=="_fixed" (
set "STATE=已处理(修复文件)"
set /a CNT_FIXED+=1
goto ANALYZE_SHOW
)
:: 已存在修复版
if exist "%~dp1%~n1_fixed.mp4" (
set "STATE=已处理(已修复)"
set /a CNT_FIXED+=1
goto ANALYZE_SHOW
)
:: 基于 flat 输出做结构化检测
"%~dp0ffprobe.exe" -v error -select_streams v:0 -show_entries format=start_time,duration:stream=start_pts,time_base,start_time,avg_frame_rate -of flat "%~1" 1> "%FFP_LOG%" 2> "%FFP_ERR%"
set "code=!errorlevel!"
if !code! neq 0 (
set "STATE=异常"
set "REASON=文件无法读取"
goto ANALYZE_BAD
)
call :CHECK_FFPROBE_RESULT
if /i "!CHECK_RESULT!"=="BAD" (
set "STATE=异常"
set "REASON=!CHECK_REASON!"
goto ANALYZE_BAD
)
if exist "%FFP_ERR%" (
findstr /i /c:"non monotonically increasing dts" /c:"non monotonically increasing pts" /c:"invalid timestamps" /c:"application provided invalid" "%FFP_ERR%" >nul && (
set "STATE=异常"
set "REASON=时间戳错乱"
goto ANALYZE_BAD
)
)
set "STATE=正常"
set /a CNT_NORMAL+=1
goto ANALYZE_SHOW
:ANALYZE_BAD
set /a CNT_ERROR+=1
>> "%LIST_BAD%" echo %~1
:ANALYZE_SHOW
echo !STATE! ^| !RELATIVE! [!MB! MB]
if not "!REASON!"=="" echo 原因:!REASON!
echo.
goto :eof
:: ==============================================
:: 检查 ffprobe 结果
:: ==============================================
:CHECK_FFPROBE_RESULT
set "CHECK_RESULT=OK"
set "CHECK_REASON="
set "FORMAT_START="
set "VIDEO_START="
set "CHECK_START="
set "DURATION="
set "START_PTS="
set "TIME_BASE="
set "TB_NUM="
set "TB_DEN="
for /f "usebackq tokens=1,* delims==" %%a in ("%FFP_LOG%") do (
set "KEY=%%a"
set "VAL=%%b"
set "VAL=!VAL:\"=!"
if /i "!KEY!"=="format.start_time" set "FORMAT_START=!VAL!"
if /i "!KEY!"=="format.duration" set "DURATION=!VAL!"
if /i "!KEY!"=="streams.stream.0.start_time" set "VIDEO_START=!VAL!"
if /i "!KEY!"=="streams.stream.0.start_pts" set "START_PTS=!VAL!"
if /i "!KEY!"=="streams.stream.0.time_base" set "TIME_BASE=!VAL!"
)
set "CHECK_START=!FORMAT_START!"
if not defined CHECK_START set "CHECK_START=!VIDEO_START!"
if /i "!CHECK_START!"=="N/A" set "CHECK_START=!VIDEO_START!"
if not defined CHECK_START set "CHECK_START=0"
call :PS_COMPARE_START "!DURATION!" "!CHECK_START!"
if /i "!CHECK_RESULT!"=="BAD" goto :eof
if defined TIME_BASE (
for /f "tokens=1,2 delims=/" %%i in ("!TIME_BASE!") do (
set "TB_NUM=%%i"
set "TB_DEN=%%j"
)
)
if defined START_PTS if defined TB_NUM if defined TB_DEN (
call :PS_COMPARE_PTS "!DURATION!" "!START_PTS!" "!TB_NUM!" "!TB_DEN!"
)
goto :eof
:: ==============================================
:: PowerShell 比较:start_time / duration
:: ==============================================
:PS_COMPARE_START
set "CHECK_RESULT=OK"
set "CHECK_REASON="
del /q "%PS_OUT%" 2>nul
powershell -NoProfile -Command "$du=0.0; $st=0.0; $hasDu=[double]::TryParse('%~1',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$du); $hasSt=[double]::TryParse('%~2',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$st); if((-not $hasDu) -or $du -le 0){'BAD|时长异常'} elseif($hasSt -and $st -lt 0){'BAD|起始时间为负'} elseif($hasSt -and $st -gt 30 -and $st -gt ($du * 2)){'BAD|起始时间远大于时长'} else {'OK|'}" > "%PS_OUT%"
for /f "usebackq tokens=1,* delims=|" %%a in ("%PS_OUT%") do (
set "CHECK_RESULT=%%a"
set "CHECK_REASON=%%b"
)
goto :eof
:: ==============================================
:: PowerShell 比较:start_pts / time_base
:: ==============================================
:PS_COMPARE_PTS
set "CHECK_RESULT=OK"
set "CHECK_REASON="
del /q "%PS_OUT%" 2>nul
powershell -NoProfile -Command "$du=0.0; $pts=0.0; $num=0.0; $den=0.0; $hasDu=[double]::TryParse('%~1',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$du); $hasPts=[double]::TryParse('%~2',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$pts); $hasNum=[double]::TryParse('%~3',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$num); $hasDen=[double]::TryParse('%~4',[System.Globalization.NumberStyles]::Float,[System.Globalization.CultureInfo]::InvariantCulture,[ref]$den); if($hasDu -and $hasPts -and $hasNum -and $hasDen -and $den -gt 0){ $s=$pts*($num/$den); if($s -gt 30 -and $s -gt ($du * 2)){'BAD|首帧时间戳过大'} else {'OK|'} } else {'OK|'}" > "%PS_OUT%"
for /f "usebackq tokens=1,* delims=|" %%a in ("%PS_OUT%") do (
set "CHECK_RESULT=%%a"
set "CHECK_REASON=%%b"
)
goto :eof
:: ==============================================
:: 重建有效异常列表
:: ==============================================
:PREPARE_BAD_LIST
set "CNT_ERROR=0"
del /q "%LIST_BAD_WORK%" 2>nul
if not exist "%LIST_BAD%" goto :eof
for /f "usebackq delims=" %%f in ("%LIST_BAD%") do (
if exist "%%f" (
>> "%LIST_BAD_WORK%" echo %%f
set /a CNT_ERROR+=1
)
)
goto :eof
:: ==============================================
:: 逐条确认修复
:: ==============================================
:CONFIRM_FIX
set "RELATIVE=%~1"
set "RELATIVE=!RELATIVE:%cd%\=!"
echo 第 !CUR!/!CNT_ERROR! 个:!RELATIVE!
:CHOOSE_LOOP
choice /c YN /n /m " 是否修复?[Y/N]:"
if errorlevel 2 (
echo 已跳过
) else if errorlevel 1 (
call :FIX "%~1"
) else (
goto CHOOSE_LOOP
)
echo.
goto :eof
:: ==============================================
:: 获取文件大小(MB)
:: ==============================================
:GET_MB
set "%~2="
for /f "usebackq delims=" %%a in (`powershell -NoProfile -Command "([double](%~z1) / 1MB).ToString('0')"`) do set "%~2=%%a"
if not defined %~2 set "%~2=0"
goto :eof
:: ==============================================
:: 修复函数
:: ==============================================
:FIX
set "OUT=%~dpn1_fixed.mp4"
del /q "!OUT!" 2>nul
echo 正在修复...
echo 修复模式:无损重封装(时间戳重建 + 起始时间归零 + faststart)
"%~dp0ffmpeg.exe" -y -hide_banner -stats -fflags +genpts -i "%~1" -map 0 -c copy -avoid_negative_ts make_zero -movflags +faststart "!OUT!"
if !errorlevel! equ 0 (
echo ✅ 修复成功(模式:无损重封装)
goto :eof
)
del /q "!OUT!" 2>nul
echo 无损重封装失败,切换到:重编码修复
echo 修复模式:重编码修复(H.264 + AAC,兼容性优先)
"%~dp0ffmpeg.exe" -y -hide_banner -stats -i "%~1" ^
-fflags +genpts ^
-reset_timestamps 1 ^
-vsync cfr ^
-c:v libx264 -preset fast -crf 22 ^
-c:a aac -b:a 192k ^
"!OUT!" 1> "%FIX_LOG%" 2>&1
if !errorlevel! equ 0 (
echo ✅ 修复成功(模式:重编码修复)
) else (
del /q "!OUT!" 2>nul
echo ❌ 修复失败
echo 可查看日志:%FIX_LOG%
)
goto :eof