用 FFmpeg + BAT 批处理打造视频工具箱：踩坑全记录
记录一次从零开始编写 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.mp4transpose 参数对照：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 / 2set /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 (...)拆成两个独立 iffor块内算术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。脚本放于视频目录下双击运行。
在 Windows 11 专业版中，你可以通过以下步骤来释放休眠文件（Hibernate File），从而腾出磁盘空间。休眠文件是系统休眠（Hibernate）功能所需要的文件，它保存了系统的当前状态，以便在休眠状态下恢复。如果你不使用休眠功能，或者因为 C 盘空间不足，可以考虑释放这个文件。打开命令提示符或 PowerShell：点击任务栏上的“搜索”按钮，然后键入“cmd”或“PowerShell”。在搜索结果中，右键点击“命令提示符”或“Windows PowerShell”并选择“以管理员身份运行”。执行命令：在命令提示符或 PowerShell 窗口中，输入以下命令并按下 Enter：powercfg -h off这个命令会关闭休眠功能，并删除休眠文件。释放磁盘空间：休眠文件被删除后，系统会释放之前被占用的磁盘空间。重新启用休眠功能（可选）：如果以后你想要重新启用休眠功能，可以执行以下命令：powercfg -h on这个命令会重新启用休眠功能，并重新创建休眠文件。请注意：禁用休眠功能会使你无法使用系统的休眠模式。如果你经常使用休眠功能来保存系统状态并快速恢复，最好不要禁用休眠功能。另外，释放休眠文件可能会影响系统恢复速度。休眠功能的主要作用包括：快速恢复：休眠功能使计算机能够在保持状态的同时降低功耗，从而可以快速恢复到之前的工作状态。节省电能：休眠状态下，计算机的功耗会显著降低，有助于节省电能。临时关闭计算机：休眠功能类似于关机，但可以在保持工作状态的同时关闭计算机。避免数据丢失：休眠功能可以帮助你避免数据丢失，即使在电源中断的情况下也可以恢复到之前的状态。
如何在Windows系统中实现网络监控及自动关机与邮件通知功能
如何在Windows系统中实现网络监控及自动关机与邮件通知功能本文将指导您如何在Windows系统中实现以下功能：每分钟检测当前网络环境是否断网。当断网超过30分钟时自动关机。当网络恢复时发送通知邮件到指定邮箱。准备工作确保您的系统已安装Microsoft Office（特别是Outlook），以便发送邮件。确保您有权限执行shutdown命令。您需要知道SMTP服务器的信息，包括地址、端口、用户名和密码。步骤一：编写检测网络状态的批处理脚本首先，我们需要创建一个批处理文件来检测网络状态。创建一个名为network_check.bat的文件，并将以下内容复制进去：@echo off setlocal enabledelayedexpansion rem 开始批处理脚本 rem @echo off 禁止显示命令 rem setlocal enabledelayedexpansion 允许使用延迟变量扩展 rem 检查网络状态 rem ping -n 1 www.google.com >nul 发送一个 ICMP 请求包到 www.google.com rem 如果返回错误级别为 1，则表示网络不通 if %errorlevel% EQU 1 ( rem 断网 rem 如果 disconnection_time 变量未定义，则记录当前时间 if not defined disconnection_time ( set "disconnection_time=%time%" echo Disconnected at !disconnection_time! ) else ( rem 如果已经记录了断网时间，则计算断网时间 rem 获取当前时间 set "current_time=%time%" rem 分离小时和分钟 set "disconnection=!disconnection_time:~0,2! !disconnection_time:~3,2!" set "current=!current_time:~0,2! !current_time:~3,2!" rem 计算断网时间（分钟） set /a "disconnection_minutes=!disconnection! * 60 + !disconnection:~3! - !current! * 60 - !current:~3!" rem 如果断网时间超过 30 分钟，则执行关机命令 if !disconnection_minutes! GTR 30 ( echo No internet for more than 30 minutes. Shutting down... shutdown /s /t 0 ) ) ) else ( rem 网络连接正常 rem 如果之前记录了断网时间，则清除该记录，并发送邮件通知 if defined disconnection_time ( echo Reconnected at %time% echo Resetting disconnection time and sending email notification. set "disconnection_time=" rem 调用 send_email.vbs 脚本来发送邮件 cscript //nologo send_email.vbs ) ) rem 结束批处理脚本 endlocal步骤二：创建发送邮件的VBScript脚本接下来，创建一个名为send_email.vbs的VBScript文件，用于发送邮件通知。将以下内容复制进去：Option Explicit ' 邮件服务器设置 ' SMTP_SERVER 是 SMTP 服务器的地址 ' SMTP_PORT 是 SMTP 服务器使用的端口 ' USER_NAME 是用于登录 SMTP 服务器的用户名 ' PASSWORD 是用于登录 SMTP 服务器的密码 Const SMTP_SERVER = "smtp.example.com" Const SMTP_PORT = 587 Const USER_NAME = "your_username@example.com" Const PASSWORD = "your_password" ' 收件人信息 ' TO_ADDRESS 是接收邮件的邮箱地址 ' FROM_ADDRESS 是发送邮件的邮箱地址 Const TO_ADDRESS = "recipient@example.com" Const FROM_ADDRESS = "sender@example.com" ' 创建 Outlook 对象 ' 使用 CreateObject 创建 Outlook.Application 对象 ' 使用 GetNamespace 获取命名空间对象 ' 使用 CreateItem 创建邮件对象 Dim objMail, objOutlook, objNS Set objOutlook = CreateObject("Outlook.Application") Set objNS = objOutlook.GetNamespace("MAPI") Set objMail = objOutlook.CreateItem(0) ' 配置邮件 ' 使用 With...End With 语句来配置邮件 With objMail ' 设置收件人地址 .To = TO_ADDRESS ' 设置邮件主题 .Subject = "Internet Connection Restored" ' 设置邮件正文 .Body = "The internet connection has been restored." ' 设置发件人账户 .SendUsingAccount = objNS.Accounts.Item(FROM_ADDRESS) ' 设置不显示邮件窗口直接发送 .Display False ' 发送邮件 .Send End With ' 退出脚本 WScript.Quit请确保替换 SMTP_SERVER, SMTP_PORT, USER_NAME, PASSWORD, TO_ADDRESS, 和 FROM_ADDRESS 为实际的值。步骤三：设置计划任务打开“控制面板” -> “系统和安全” -> “管理工具” -> “任务计划程序”。在右侧点击“创建基本任务...”。输入任务名称和描述，然后点击“下一步”。选择“重复”，然后点击“下一步”。选择“每分钟”，点击“下一步”。选择“启动程序”，点击“下一步”。浏览并选择你刚刚创建的批处理文件 network_check.bat，点击“下一步”。完成设置，点击“完成”。这样，你就设置了一个每分钟运行一次的脚本来检查网络连接，并且如果断网超过30分钟就会自动关机。当网络恢复时，还会发送一封邮件通知到指定的邮箱地址。注意事项请确保您的邮箱服务支持通过脚本发送邮件，有些服务可能需要特殊设置或启用第三方应用访问。如果您的邮箱提供商使用 SSL/TLS 加密，请使用相应的端口（如 Gmail 使用的是 465 或 587 端口）。如果你在企业环境中，可能还需要考虑防火墙或代理服务器的设置。请确保以管理员身份运行批处理文件，因为 shutdown 命令需要管理员权限。现在，您已经成功设置了网络监控系统，它会在网络断开超过30分钟后自动关机，并在网络恢复时发送邮件通知。
解决双系统时间不一致的问题解决双系统（如Windows和Linux）时间不一致的问题主要有两种方法：让Linux使用本地时间这个方法涉及到让Linux系统将硬件时钟当作本地时间，与Windows的行为一致。步骤包括：打开Linux系统；打开终端；执行命令 timedatectl set-local-rtc 1 --adjust-system-clock 来让硬件时钟假定为本地时间而非UTC；然后重启电脑。这个方法比较简单，但可能会影响Linux中某些时间相关的功能，尤其是那些依赖于UTC时间的功能。让Windows使用UTC时间另一种方法是让Windows将硬件时钟视为UTC时间。这需要编辑Windows的注册表。步骤如下：在Windows中，打开注册表编辑器（按下 Windows + R，输入 regedit，然后按回车）；导航到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation；在这个位置，右键点击空白区域，选择“新建” -> “DWORD (32位) 值”，将新建的值命名为 RealTimeIsUniversal；双击这个新值，将数据设置为 1；重启电脑。这种方法更符合标准，因为UTC时间是推荐的硬件时钟设置。但请注意，对注册表的编辑需要谨慎进行，错误的操作可能会影响系统稳定性。总结选择哪种方法取决于用户的使用偏好和系统的主要用途。如果Linux系统是用户主要使用的系统，那么让Linux使用本地时间可能更为合适。反之，如果Windows是主要系统，那么让Windows使用UTC时间可能更为合适。
如何在Windows 11中禁用休眠文件以释放更多磁盘空间
对于众多Windows 11用户而言，管理硬盘空间是一项日常且重要的任务。随着应用程序、文件和数据的不断积累，有效的空间管理策略变得尤为关键。休眠文件（Hiberfil.sys）作为支持休眠模式的一个系统文件，会占用相当一部分硬盘空间。在本文中，我们将详细指导您如何在Windows 11中禁用休眠文件以释放系统空间，从而为您的电脑“瘦身”。工具与原料：系统版本： Windows 11品牌型号： 所有支持Windows 11的电脑品牌和型号软件版本： 不适用一、休眠文件简介休眠模式允许电脑将当前状态（包括打开的文档和应用程序）保存到硬盘中的休眠文件，然后关闭电源。当您再次启动电脑时，可以迅速恢复到之前的状态，而无需重新启动。尽管休眠模式为用户带来便利，但其休眠文件可能占用大量硬盘空间，特别是在拥有较小SSD的电脑上更是如此。二、检查休眠模式状态在禁用休眠文件之前，首先需要检查休眠模式是否已经启用。打开“命令提示符”（以管理员身份），输入powercfg /availablesleepstates，回车。如果看到“休眠”被列出，则说明休眠模式已启用。三、禁用休眠文件要禁用休眠文件，需要使用命令提示符执行特定命令。以管理员身份打开命令提示符，输入powercfg.exe /hibernate off，然后回车。这条命令将禁用休眠模式并删除休眠文件，从而释放占用的硬盘空间。四、检验空间释放效果执行完上述操作后，可以通过查看硬盘属性来检验空间是否已成功释放。休眠文件的删除应该会立即释放与之前休眠文件大小相当的硬盘空间。总结：通过禁用Windows 11中的休眠文件，用户可以有效地释放出一部分硬盘空间，尤其对于SSD用户来说，这可能是一项有益的系统优化策略。然而，值得注意的是，禁用休眠模式意味着您将无法使用休眠功能。因此，在执行此操作之前，请确保您不常使用休眠模式，或者硬盘空间的需求超过了对休眠功能的需求。总之，合理管理系统文件和功能，可以让您的电脑运行更加高效，同时也保证了足够的存储空间供日常使用。
Office2016出现“你可能是盗版软件的受害者”提示
Windows设置Windows Update服务本地组策略编辑器任务计划程序注册表编辑器注意：Win10自动更新关闭后，系统长期不更新当您再次尝试将系统更新到新版本系统，可能会导致系统出错。若您担心系统出错，可以提前对系统备份。方法1. Windows设置按 Windows + I 键，打开Windows设置，再单击 更新和安全然后，在Windows更新处，单击“高级选项”。在高级选项中，您可以将“更新选项”中项目全部关闭，或者选择“暂停更新”，但此暂停更新至多只能暂停35天，达到暂停限制后需先获取新的更新，然后才能再次暂停更新。方法2. 关闭Windows Update服务按“Windows + R”键，打开运行对话框，并输入“services.msc”，然后再单击“确定”。在弹出的服务窗口中，找到“Windows Update”选项并双击打开它。在弹出的“Windows Update的属性”对话框中，将“启动类型”设置为“禁用”。然后再单击“恢复”，将图中框选的部分都设置为“无操作”，再单击“应用”和“确定”。方法3. 本地组策略编辑器另一种Win10关闭自动更新方法就是通过更改本地组策略编辑器中的“配置自动更新”和“删除使用所有Windows更新功能的访问权限”选项来关闭Win10自动更新。按“Windows + R”键，打开运行对话框，并输入“gpedit.msc”，然后再单击“确定”。在弹出的本地组策略编辑中，依次单击“计算机配置” > “管理模板” > “Windows组件” > “Windows更新”，然后找到“配置自动更新”并双击打开它。在弹出的“配置自动更新”窗口中，选择“已禁用”，再单击“确定”。然后在“Windows更新”页面找到“删除使用所有Windows更新功能的访问权限”，并双击打开它。在弹出的窗口中，再勾选“已启用”，然后单击“确定”。方法4. 任务计划程序您也可以通过任务计划程序，将“Schedule Start”禁用，以实现Win10自动更新的关闭。按“Windows + R”键，打开运行对话框，并输入“taskschd.msc”，然后再单击“确定”。在任务计划程序窗口中，跳转到该路径“任务计划程序库/Microsoft/Windows/WindowsUpdate”。然后，选中“Schedule Start”，并在“所选项”中单击“禁用”。方法5. 注册表编辑器还有一种Win10关闭自动更新方法就是通过注册表编辑器，来彻底关闭Win10自动更新。具体操作步骤如下：按“Windows + R”键，打开运行对话框，并输入“regedit.exe”，然后再单击“确定”，在弹出的窗口中再点击“是”。进入注册表编辑器后，跳转到以下路径，并在右侧找到“Start”键并双击打开它。路径：HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\UsoSvc在弹出的窗口中将“数值数据”改为“4”，并单击“确定”。然后再找到“FailureActions”键并双击进行编辑，将“00000010”和“00000018”行中的第五个数值，由原来的“01”改为“00”，再单击“确定”。
开启服务此电脑右键-管理-服务-WebClient 设为自动，停止服务修改注册表修改注册表使得WIN同时支持 http 和 https ，默认只支持http快速打开系统注册表,键盘组合键按下 win + R 然后输入以下命令按回车打开注册表regedit按照以下顺序依次打开到注册表的 BasicAuthLevel 文件： HKEY_LOCAL_MACHINE SYSTEM CurrentControlSet Services WebClient Parameters BasicAuthLevel 把 BasicAuthLevel 值改成 2
echo off color 1f :start cls echo, echo Modify right menu mode:1.Win10 mode 2.Win11 mode 0.Exist echo, choice /c:120 /n /m:"Please choose mode!" if %errorlevel%==0 exit if %errorlevel%==2 goto cmd2 if %errorlevel%==1 goto cmd1 exit :cmd1 reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve shutdown -l exit :cmd2 reg.exe delete "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" /f shutdown -l exit
1.打开系统环境变量配置2.添加 JAVA_HOMED:\Program Files\Java\jdk1.8.0_253.修改 Path%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;4.添加 CLASSPATH%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;5.验证java -versionjavajavac