1. 背景
使用迅雷影音的“截取視頻” 功能得到的視頻片段在某些播放器下無(wú)法拖拽進(jìn)度條,甚至無(wú)法播放,撰寫(xiě)此批處理實(shí)現(xiàn)視頻無(wú)損修復(fù)(調(diào)用 ffmpeg )。
2. 功能
- 列出批處理所在文件夾內(nèi)的所有視頻
- 檢查視頻存在的問(wèn)題
- 依次修復(fù)(需要用戶確認(rèn),“y” 執(zhí)行“n”跳過(guò))
- 使用快速修復(fù)模式
- 快速修復(fù)失敗會(huì)使用重編碼方式修復(fù)
3. 使用方法
- 下載 ffmpeg.exe 和 ffprobe.exe 并放到視頻所在的文件夾
- 將本文下面提供的批處理源碼拷貝并存到視頻所在的文件夾
- 修改文本名稱為:“修復(fù)視頻.bat”
- 雙擊運(yùn)行
- 根據(jù)控制臺(tái)提示操作,你只需要3個(gè)動(dòng)作:敲回車(chē),敲"y" 或者 “n”
4. 批處理源碼
@echo off
setlocal enabledelayedexpansion
chcp 65001 >nul
title MP4視頻智能掃描修復(fù)工具
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 智能掃描修復(fù)工具
echo 狀態(tài):正常 / 異常 / 已處理 檢測(cè):ffprobe 高速 修復(fù):IBP幀/時(shí)間戳/進(jìn)度條
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(相對(duì)路徑 + 大?。?:: ==============================================
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! 個(gè)
echo.
pause
:: ==============================================
:: 步驟2:狀態(tài)分析
:: ==============================================
echo.
echo 【步驟2/4】視頻狀態(tài)分析
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 統(tǒng)計(jì):正常=!CNT_NORMAL! 已處理=!CNT_FIXED! 異常=!CNT_ERROR!
echo ------------------------------------------------------------------------------------------------------------------------------
echo.
if !CNT_ERROR! equ 0 (
echo ? 無(wú)異常視頻,程序結(jié)束
pause>nul
exit /b
)
pause
:: ==============================================
:: 步驟3:逐條確認(rèn)
:: ==============================================
echo.
echo 【步驟3/4】異常視頻逐條確認(rèn)(Y=修復(fù) N=跳過(guò))
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
:: ==============================================
:: 分析單個(gè)文件
:: ==============================================
:ANALYZE_FILE
set "FILE=%~1"
set "NAME=%~n1"
set "REASON="
set "STATE="
set "RELATIVE=%~1"
set "RELATIVE=!RELATIVE:%cd%\=!"
call :GET_MB "%~1" MB
:: 跳過(guò)修復(fù)文件
if /i "!NAME:~-6!"=="_fixed" (
set "STATE=已處理(修復(fù)文件)"
set /a CNT_FIXED+=1
goto ANALYZE_SHOW
)
:: 已存在修復(fù)版
if exist "%~dp1%~n1_fixed.mp4" (
set "STATE=已處理(已修復(fù))"
set /a CNT_FIXED+=1
goto ANALYZE_SHOW
)
:: 基于 flat 輸出做結(jié)構(gòu)化檢測(cè)
"%~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=文件無(wú)法讀取"
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=時(shí)間戳錯(cuò)亂"
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 結(jié)果
:: ==============================================
: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|時(shí)長(zhǎng)異常'} elseif($hasSt -and $st -lt 0){'BAD|起始時(shí)間為負(fù)'} elseif($hasSt -and $st -gt 30 -and $st -gt ($du * 2)){'BAD|起始時(shí)間遠(yuǎn)大于時(shí)長(zhǎng)'} 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|首幀時(shí)間戳過(guò)大'} 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
:: ==============================================
:: 逐條確認(rèn)修復(fù)
:: ==============================================
:CONFIRM_FIX
set "RELATIVE=%~1"
set "RELATIVE=!RELATIVE:%cd%\=!"
echo 第 !CUR!/!CNT_ERROR! 個(gè):!RELATIVE!
:CHOOSE_LOOP
choice /c YN /n /m " 是否修復(fù)?[Y/N]:"
if errorlevel 2 (
echo 已跳過(guò)
) else if errorlevel 1 (
call :FIX "%~1"
) else (
goto CHOOSE_LOOP
)
echo.
goto :eof
:: ==============================================
:: 獲取文件大?。∕B)
:: ==============================================
: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
:: ==============================================
:: 修復(fù)函數(shù)
:: ==============================================
:FIX
set "OUT=%~dpn1_fixed.mp4"
del /q "!OUT!" 2>nul
echo 正在修復(fù)...
echo 修復(fù)模式:無(wú)損重封裝(時(shí)間戳重建 + 起始時(shí)間歸零 + 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 ? 修復(fù)成功(模式:無(wú)損重封裝)
goto :eof
)
del /q "!OUT!" 2>nul
echo 無(wú)損重封裝失敗,切換到:重編碼修復(fù)
echo 修復(fù)模式:重編碼修復(fù)(H.264 + AAC,兼容性優(yōu)先)
"%~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 ? 修復(fù)成功(模式:重編碼修復(fù))
) else (
del /q "!OUT!" 2>nul
echo ? 修復(fù)失敗
echo 可查看日志:%FIX_LOG%
)
goto :eof