[批處理]視頻進(jìn)度條異常修復(fù)工具

1. 背景

使用迅雷影音的“截取視頻” 功能得到的視頻片段在某些播放器下無(wú)法拖拽進(jìn)度條,甚至無(wú)法播放,撰寫(xiě)此批處理實(shí)現(xiàn)視頻無(wú)損修復(fù)(調(diào)用 ffmpeg )。

2. 功能

  1. 列出批處理所在文件夾內(nèi)的所有視頻
  2. 檢查視頻存在的問(wèn)題
  3. 依次修復(fù)(需要用戶確認(rèn),“y” 執(zhí)行“n”跳過(guò))
  4. 使用快速修復(fù)模式
  5. 快速修復(fù)失敗會(huì)使用重編碼方式修復(fù)

3. 使用方法

  1. 下載 ffmpeg.exe 和 ffprobe.exe 并放到視頻所在的文件夾
  2. 將本文下面提供的批處理源碼拷貝并存到視頻所在的文件夾
  3. 修改文本名稱為:“修復(fù)視頻.bat”
  4. 雙擊運(yùn)行
  5. 根據(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容