Last active
March 4, 2026 04:18
-
-
Save donal56/27435f73c2e038f61a839b44f7f041ea to your computer and use it in GitHub Desktop.
Wizard to compress videos with ffmpeg and ffprobe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| :: Wizard para comprimir videos usando ffmpeg y ffprobe. | |
| :: El script está seccionado como se describe y todos los comentarios y salidas a consola son en inglés. | |
| :: Descripción del proceso del programa: | |
| :: | |
| :: 1.- El programa se llama cleanVideo y toma como argumento un video. | |
| :: Opcionalmente puede tomar -s o --silent para saltarse todas las opciones del wizard. | |
| :: Siempre aplica: | |
| :: - Codec de video: libx265 (H.265) | |
| :: - Contenedor: MP4 | |
| :: - Nombre de salida: nombre original + " new" (si existe "new", intenta "new2", "new3", etc.) | |
| :: - CRF: 27 (Fijo) | |
| :: | |
| :: 2.- Se realizan validaciones básicas. | |
| :: 2.1.- Verificar que los argumentos sean válidos. | |
| :: 2.2.- Verificar que el video sea un archivo existente. | |
| :: 2.3.- Verificar que el video tenga una extensión válida (.mp4, .mov, .avi, .mkv, .webm, .flv, .ts). | |
| :: 2.4.- Verificar que ffmpeg y ffprobe estén instalados. | |
| :: | |
| :: 3.- Recupera y muestra información metadata del video. Si algún dato no puede ser recuperado entonces el programa fallará. Datos mostrados: | |
| :: - Nombre del archivo incluyendo extensión | |
| :: - Tamaño del archivo | |
| :: - Duración del video | |
| :: - Codec de video | |
| :: - Resolución (ancho x alto) | |
| :: - Aspecto ratio (detecta automáticamente: 16:9, 3:2, 4:3, 2:3, 9:16, 3:4, 1:1 o muestra como custom) | |
| :: - FPS (frames por segundo) | |
| :: - Bitrate de video | |
| :: - Bitrate de audio | |
| :: - Bitrate total | |
| :: | |
| :: 4.- Ofrece opciones de downscale según el aspecto ratio detectado. | |
| :: Si es una proporción estándar (16:9, 3:2, 4:3, 2:3, 9:16, 3:4, 1:1): | |
| :: - Muestra 4 opciones predefinidas con resoluciones apropiadas para el aspecto ratio | |
| :: - El usuario selecciona [1-4] o ingresa cualquier otra tecla para saltar | |
| :: Ejemplo para 16:9: [1] 2560x1440, [2] 1920x1080, [3] 1280x720, [4] 800x450 | |
| :: | |
| :: Si el aspecto ratio es custom (no detectado como estándar): | |
| :: - Verifica automáticamente si está cercano a una proporción estándar usando tolerancia | |
| :: - Si está cercano, ofrece automáticamente cropear a esa proporción (y/n) | |
| :: - Si el usuario rechaza el crop o no está cercano a ninguno, pregunta por el ancho final deseado (el alto se calcula automáticamente manteniendo el aspecto ratio original) | |
| :: | |
| :: 5.- Si el video tiene más de 30 fps, pregunta si desea cambiar los fps (e.g., 24, 30). Si ingresa un número lo usa como nuevo fps, cualquier otra entrada salta este paso. | |
| :: | |
| :: 6.- Pregunta si desea modificar el bitrate de video. Si ingresa un número lo usa como kbps, cualquier otra entrada salta este paso. | |
| :: | |
| :: 7.- Ofrece rotar el video 90 grados hacia la izquierda o derecha. El usuario puede seleccionar [1] Right, [2] Left o cualquier otra tecla para saltar. | |
| :: | |
| :: 8.- Ofrece cortar el video. Solicita timestamp de inicio (mm:ss) y fin (mm:ss). Si no se ingresa nada se usan los valores por defecto (inicio del video y final). Si no se especifica ni inicio ni fin, se omite el trimming. | |
| :: | |
| :: 9.- Pregunta si desea remover el audio (solo si el video tiene pista de audio). | |
| :: | |
| :: 10.- Ejecuta el comando ffmpeg con los parámetros seleccionados (resolución, fps, rotación, recorte, bitrate, trimming, etc.). | |
| :: Después de la codificación muestra: | |
| :: - Tamaño del archivo original y nuevo | |
| :: - Reducción/aumento de tamaño en bytes y porcentaje | |
| :: - Ruta del archivo de salida | |
| @echo off | |
| setlocal enabledelayedexpansion | |
| :: Wizard to compress videos with ffmpeg and ffprobe | |
| :: By ChatGPT - cleanVideo.bat | |
| :: Supported aspect ratio values for auto-crop | |
| set "RATIO_16_9_NUM=16" | |
| set "RATIO_16_9_DEN=9" | |
| set "RATIO_4_3_NUM=4" | |
| set "RATIO_4_3_DEN=3" | |
| set "RATIO_9_16_NUM=9" | |
| set "RATIO_9_16_DEN=16" | |
| set "RATIO_3_4_NUM=3" | |
| set "RATIO_3_4_DEN=4" | |
| set "RATIO_3_2_NUM=3" | |
| set "RATIO_3_2_DEN=2" | |
| set "RATIO_2_3_NUM=2" | |
| set "RATIO_2_3_DEN=3" | |
| set "RATIO_1_1_NUM=1" | |
| set "RATIO_1_1_DEN=1" | |
| :: Tolerance for detecting close aspect ratios (in pixels of difference) | |
| set "RATIO_TOLERANCE=50" | |
| :: ============================= | |
| :: 1. Parse arguments | |
| :: ============================= | |
| set "VIDEO=%~1" | |
| set "SILENT=0" | |
| if "%~2"=="-s" set SILENT=1 | |
| if "%~2"=="--silent" set SILENT=1 | |
| if not "%~3"=="" ( | |
| echo Error: Invalid argument. Usage: cleanVideo ^<video_file^> [-s^|--silent] | |
| exit /b 1 | |
| ) | |
| if not "%~2"=="" ( | |
| if not "%~2"=="-s" ( | |
| if not "%~2"=="--silent" ( | |
| echo Error: Invalid flag. Only -s or --silent are allowed. | |
| exit /b 1 | |
| ) | |
| ) | |
| ) | |
| :: ============================= | |
| :: 2. Basic validations | |
| :: ============================= | |
| if "%VIDEO%"=="" ( | |
| echo Error: No video file provided. | |
| exit /b 1 | |
| ) | |
| if not exist "%VIDEO%" ( | |
| echo Error: File does not exist: %VIDEO% | |
| exit /b 1 | |
| ) | |
| set "EXT=%~x1" | |
| set "VALID_EXT=.mp4 .mov .avi .mkv .webm .flv .ts" | |
| set "FOUND=0" | |
| for %%e in (%VALID_EXT%) do ( | |
| if /I "%%e"=="%EXT%" set FOUND=1 | |
| ) | |
| if %FOUND%==0 ( | |
| echo Error: Invalid video file extension: %EXT% | |
| exit /b 1 | |
| ) | |
| where ffmpeg >nul 2>nul || (echo Error: ffmpeg not found & exit /b 1) | |
| where ffprobe >nul 2>nul || (echo Error: ffprobe not found & exit /b 1) | |
| :: ============================= | |
| :: 3. Extract and display metadata | |
| :: ============================= | |
| for /f "tokens=1,* delims==" %%a in ('"ffprobe -v error -select_streams v:0 -show_entries stream=width,height,codec_name,bit_rate,display_aspect_ratio,r_frame_rate -of default=noprint_wrappers=1 "%VIDEO%""') do set "%%a=%%b" | |
| for /f "tokens=1,* delims==" %%a in ('"ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1 "%VIDEO%""') do set "AUDIO_%%a=%%b" | |
| for /f "tokens=1,* delims==" %%a in ('"ffprobe -v error -show_entries format=duration,bit_rate -of default=noprint_wrappers=1 "%VIDEO%""') do set "%%a=%%b" | |
| set "filename=%~nx1" | |
| if "!filename!"=="" echo Error: Could not retrieve filename & exit /b 1 | |
| if "!duration!"=="" echo Error: Could not retrieve duration & exit /b 1 | |
| if "!codec_name!"=="" echo Error: Could not retrieve codec & exit /b 1 | |
| if "!width!"=="" echo Error: Could not retrieve resolution & exit /b 1 | |
| if "!bit_rate!"=="" echo Error: Could not retrieve video bitrate & exit /b 1 | |
| REM Parse fps from r_frame_rate (e.g., "30/1" or "24000/1001") | |
| if defined r_frame_rate ( | |
| for /f "tokens=1 delims=/" %%a in ("!r_frame_rate!") do set "fps=%%a" | |
| for /f "tokens=2 delims=/" %%a in ("!r_frame_rate!") do set "fps_den=%%a" | |
| if defined fps_den ( | |
| if !fps_den! GTR 1 set /a fps=!fps! / !fps_den! | |
| ) | |
| ) else ( | |
| set "fps=N/A" | |
| ) | |
| :: Convert duration to MM:SS format if >= 60 seconds | |
| REM Trim spaces and extract integer part from duration | |
| for /f "tokens=* delims= " %%a in ("!duration!") do set "duration=%%a" | |
| for /f "tokens=1 delims=." %%a in ("!duration!") do set "duration_int=%%a" | |
| if !duration_int! GEQ 60 ( | |
| set /a minutes=!duration_int! / 60 | |
| set /a seconds_remainder=!duration_int! %% 60 | |
| set "duration=!minutes!m !seconds_remainder!s" | |
| ) else ( | |
| set "duration=!duration_int!s" | |
| ) | |
| set /a TOTAL_bit_rate=!bit_rate! | |
| if "!AUDIO_bit_rate!"=="" ( | |
| set "AUDIO_bit_rate=N/A" | |
| set "HAS_AUDIO=0" | |
| ) else ( | |
| set "HAS_AUDIO=1" | |
| set /a AUDIO_bit_rate=!AUDIO_bit_rate! / 1024 | |
| set /a bit_rate= !TOTAL_bit_rate! - !AUDIO_bit_rate! | |
| ) | |
| set /a bit_rate= !bit_rate! / 1024 | |
| set /a TOTAL_bit_rate= !TOTAL_bit_rate! / 1024 | |
| if !height! GTR 0 ( | |
| set /a ratio=!width!*100/!height! | |
| ) else ( | |
| set "ratio=0" | |
| ) | |
| if !ratio! GEQ 175 if !ratio! LEQ 178 set "ASPECT=16_9" | |
| if !ratio! GEQ 148 if !ratio! LEQ 152 set "ASPECT=3_2" | |
| if !ratio! GEQ 132 if !ratio! LEQ 135 set "ASPECT=4_3" | |
| if !ratio! GEQ 65 if !ratio! LEQ 68 set "ASPECT=2_3" | |
| if !ratio! GEQ 55 if !ratio! LEQ 58 set "ASPECT=9_16" | |
| if !ratio! GEQ 74 if !ratio! LEQ 77 set "ASPECT=3_4" | |
| if "!ratio!"=="100" set "ASPECT=1_1" | |
| if not defined ASPECT set "ASPECT=Custom" | |
| :: Calculate file size | |
| set /a FILE_SIZE_BYTES=%~z1 | |
| set /a FILE_SIZE_MB=!FILE_SIZE_BYTES! / 1048576 | |
| set /a FILE_SIZE_REMAINDER=!FILE_SIZE_BYTES! %% 1048576 | |
| set /a FILE_SIZE_KB=!FILE_SIZE_REMAINDER! / 1024 | |
| if !FILE_SIZE_MB! GTR 0 ( | |
| if !FILE_SIZE_KB! LSS 10 ( | |
| set "FILE_SIZE=!FILE_SIZE_MB!.0!FILE_SIZE_KB! MB" | |
| ) else ( | |
| set "FILE_SIZE=!FILE_SIZE_MB!.!FILE_SIZE_KB! MB" | |
| ) | |
| ) else ( | |
| set /a FILE_SIZE_KB=!FILE_SIZE_BYTES! / 1024 | |
| if !FILE_SIZE_KB! EQU 0 set "FILE_SIZE=!FILE_SIZE_BYTES! bytes" | |
| if !FILE_SIZE_KB! GTR 0 set "FILE_SIZE=!FILE_SIZE_KB! KB" | |
| ) | |
| :: If silent mode is enabled, skip wizard and go directly to encoding | |
| if %SILENT% equ 1 goto encode | |
| echo ===== VIDEO METADATA ===== | |
| echo File: !filename! | |
| echo File size: !FILE_SIZE! | |
| echo Duration: !duration! | |
| echo Codec: !codec_name! | |
| echo Resolution: !width!x!height! | |
| echo Aspect ratio: !ASPECT:_=:! | |
| echo FPS: !fps! | |
| echo Video bitrate: !bit_rate! kbps | |
| echo Audio bitrate: !AUDIO_bit_rate! kbps | |
| echo Total bitrate: !TOTAL_bit_rate! kbps | |
| echo ============================= | |
| :: ============================= | |
| :: 4. Resolution wizard | |
| :: ============================= | |
| set "DOWNSCALE=" | |
| set "ORIENT=" | |
| if "!ASPECT!"=="16_9" set "ORIENT=H169" | |
| if "!ASPECT!"=="3_2" set "ORIENT=H32" | |
| if "!ASPECT!"=="4_3" set "ORIENT=H43" | |
| if "!ASPECT!"=="2_3" set "ORIENT=V23" | |
| if "!ASPECT!"=="9_16" set "ORIENT=V916" | |
| if "!ASPECT!"=="3_4" set "ORIENT=V43" | |
| if "!ASPECT!"=="1_1" set "ORIENT=SQUARE" | |
| if defined ORIENT ( | |
| echo Would you like to downscale the video? | |
| if "!ORIENT!"=="H169" ( | |
| echo [1] 2560x1440 | |
| echo [2] 1920x1080 | |
| echo [3] 1280x720 | |
| echo [4] 800x450 | |
| ) else if "!ORIENT!"=="H32" ( | |
| echo [1] 1920x1280 | |
| echo [2] 1440x960 | |
| echo [3] 960x640 | |
| echo [4] 640x426 | |
| ) else if "!ORIENT!"=="H43" ( | |
| echo [1] 2048x1536 | |
| echo [2] 1024x768 | |
| echo [3] 800x600 | |
| echo [4] 512x384 | |
| ) else if "!ORIENT!"=="V23" ( | |
| echo [1] 1280x1920 | |
| echo [2] 960x1440 | |
| echo [3] 640x960 | |
| echo [4] 426x640 | |
| ) else if "!ORIENT!"=="V916" ( | |
| echo [1] 1440x2560 | |
| echo [2] 1080x1920 | |
| echo [3] 720x1280 | |
| echo [4] 450x800 | |
| ) else if "!ORIENT!"=="V43" ( | |
| echo [1] 1536x2048 | |
| echo [2] 768x1024 | |
| echo [3] 600x800 | |
| echo [4] 384x512 | |
| ) else if "!ORIENT!"=="SQUARE" ( | |
| echo [1] 1200x1200 | |
| echo [2] 1000x1000 | |
| echo [3] 800x800 | |
| echo [4] 600x600 | |
| ) | |
| set /p OPTION=Choose option [1-4] or any other key to skip: | |
| REM Trim spaces from OPTION | |
| for /f "tokens=* delims= " %%a in ("!OPTION!") do set "OPTION=%%a" | |
| set "DOWNSCALE_CODE=!ORIENT!_!OPTION!" | |
| REM Map downscale codes to actual dimensions (OUTSIDE nested parentheses to avoid colon issues) | |
| if "!DOWNSCALE_CODE!"=="H169_1" set "DOWNSCALE=2560x1440" | |
| if "!DOWNSCALE_CODE!"=="H169_2" set "DOWNSCALE=1920x1080" | |
| if "!DOWNSCALE_CODE!"=="H169_3" set "DOWNSCALE=1280x720" | |
| if "!DOWNSCALE_CODE!"=="H169_4" set "DOWNSCALE=800x450" | |
| if "!DOWNSCALE_CODE!"=="H32_1" set "DOWNSCALE=1920x1280" | |
| if "!DOWNSCALE_CODE!"=="H32_2" set "DOWNSCALE=1440x960" | |
| if "!DOWNSCALE_CODE!"=="H32_3" set "DOWNSCALE=960x640" | |
| if "!DOWNSCALE_CODE!"=="H32_4" set "DOWNSCALE=640x426" | |
| if "!DOWNSCALE_CODE!"=="H43_1" set "DOWNSCALE=2048x1536" | |
| if "!DOWNSCALE_CODE!"=="H43_2" set "DOWNSCALE=1024x768" | |
| if "!DOWNSCALE_CODE!"=="H43_3" set "DOWNSCALE=800x600" | |
| if "!DOWNSCALE_CODE!"=="H43_4" set "DOWNSCALE=512x384" | |
| if "!DOWNSCALE_CODE!"=="V23_1" set "DOWNSCALE=1280x1920" | |
| if "!DOWNSCALE_CODE!"=="V23_2" set "DOWNSCALE=960x1440" | |
| if "!DOWNSCALE_CODE!"=="V23_3" set "DOWNSCALE=640x960" | |
| if "!DOWNSCALE_CODE!"=="V23_4" set "DOWNSCALE=426x640" | |
| if "!DOWNSCALE_CODE!"=="V916_1" set "DOWNSCALE=1440x2560" | |
| if "!DOWNSCALE_CODE!"=="V916_2" set "DOWNSCALE=1080x1920" | |
| if "!DOWNSCALE_CODE!"=="V916_3" set "DOWNSCALE=720x1280" | |
| if "!DOWNSCALE_CODE!"=="V916_4" set "DOWNSCALE=450x800" | |
| if "!DOWNSCALE_CODE!"=="V43_1" set "DOWNSCALE=1536x2048" | |
| if "!DOWNSCALE_CODE!"=="V43_2" set "DOWNSCALE=768x1024" | |
| if "!DOWNSCALE_CODE!"=="V43_3" set "DOWNSCALE=600x800" | |
| if "!DOWNSCALE_CODE!"=="V43_4" set "DOWNSCALE=384x512" | |
| if "!DOWNSCALE_CODE!"=="SQUARE_1" set "DOWNSCALE=1200x1200" | |
| if "!DOWNSCALE_CODE!"=="SQUARE_2" set "DOWNSCALE=1000x1000" | |
| if "!DOWNSCALE_CODE!"=="SQUARE_3" set "DOWNSCALE=800x800" | |
| if "!DOWNSCALE_CODE!"=="SQUARE_4" set "DOWNSCALE=600x600" | |
| ) else ( | |
| REM For custom aspect ratio, check if it's close to standard ratios | |
| set "CROP=" | |
| set "CLOSE_RATIO=" | |
| REM Check 16:9 | |
| set /a diff_16_9=!width!*!RATIO_16_9_DEN! - !height!*!RATIO_16_9_NUM! | |
| if !diff_16_9! LSS 0 set /a diff_16_9=!diff_16_9!*-1 | |
| if !diff_16_9! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=16_9" | |
| ) | |
| REM Check 4:3 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_4_3=!width!*!RATIO_4_3_DEN! - !height!*!RATIO_4_3_NUM! | |
| if !diff_4_3! LSS 0 set /a diff_4_3=!diff_4_3!*-1 | |
| if !diff_4_3! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=4_3" | |
| ) | |
| ) | |
| REM Check 3:2 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_3_2=!width!*!RATIO_3_2_DEN! - !height!*!RATIO_3_2_NUM! | |
| if !diff_3_2! LSS 0 set /a diff_3_2=!diff_3_2!*-1 | |
| if !diff_3_2! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=3_2" | |
| ) | |
| ) | |
| REM Check 2:3 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_2_3=!width!*!RATIO_2_3_DEN! - !height!*!RATIO_2_3_NUM! | |
| if !diff_2_3! LSS 0 set /a diff_2_3=!diff_2_3!*-1 | |
| if !diff_2_3! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=2_3" | |
| ) | |
| ) | |
| REM Check 9:16 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_9_16=!width!*!RATIO_9_16_DEN! - !height!*!RATIO_9_16_NUM! | |
| if !diff_9_16! LSS 0 set /a diff_9_16=!diff_9_16!*-1 | |
| if !diff_9_16! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=9_16" | |
| ) | |
| ) | |
| REM Check 3:4 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_3_4=!width!*!RATIO_3_4_DEN! - !height!*!RATIO_3_4_NUM! | |
| if !diff_3_4! LSS 0 set /a diff_3_4=!diff_3_4!*-1 | |
| if !diff_3_4! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=3_4" | |
| ) | |
| ) | |
| REM Check 1:1 | |
| if not defined CLOSE_RATIO ( | |
| set /a diff_1_1=!width! - !height! | |
| if !diff_1_1! LSS 0 set /a diff_1_1=!diff_1_1!*-1 | |
| if !diff_1_1! LSS !RATIO_TOLERANCE! ( | |
| set "CLOSE_RATIO=1_1" | |
| ) | |
| ) | |
| REM Map internal ratio codes to display format (need to be OUTSIDE all parentheses) | |
| REM Create mapping without direct colon assignment issues | |
| if "!CLOSE_RATIO!"=="16_9" set "DISPLAY_RATIO_19=16" | |
| if "!CLOSE_RATIO!"=="16_9" set "DISPLAY_RATIO_9=9" | |
| if "!CLOSE_RATIO!"=="4_3" set "DISPLAY_RATIO_19=4" | |
| if "!CLOSE_RATIO!"=="4_3" set "DISPLAY_RATIO_9=3" | |
| if "!CLOSE_RATIO!"=="9_16" set "DISPLAY_RATIO_19=9" | |
| if "!CLOSE_RATIO!"=="9_16" set "DISPLAY_RATIO_9=16" | |
| if "!CLOSE_RATIO!"=="3_4" set "DISPLAY_RATIO_19=3" | |
| if "!CLOSE_RATIO!"=="3_4" set "DISPLAY_RATIO_9=4" | |
| if "!CLOSE_RATIO!"=="3_2" set "DISPLAY_RATIO_19=3" | |
| if "!CLOSE_RATIO!"=="3_2" set "DISPLAY_RATIO_9=2" | |
| if "!CLOSE_RATIO!"=="2_3" set "DISPLAY_RATIO_19=2" | |
| if "!CLOSE_RATIO!"=="2_3" set "DISPLAY_RATIO_9=3" | |
| if "!CLOSE_RATIO!"=="1_1" set "DISPLAY_RATIO_19=1" | |
| if "!CLOSE_RATIO!"=="1_1" set "DISPLAY_RATIO_9=1" | |
| REM Now construct DISPLAY_RATIO with x instead of colon for now | |
| if defined DISPLAY_RATIO_19 ( | |
| set "DISPLAY_RATIO=!DISPLAY_RATIO_19!x!DISPLAY_RATIO_9!" | |
| ) | |
| REM Convert x to : for display (MUST BE OUTSIDE parentheses) | |
| if defined DISPLAY_RATIO ( | |
| set "DISPLAY_RATIO_SHOW=!DISPLAY_RATIO:x=:!" | |
| ) | |
| REM If close ratio found, ask for crop (set /p OUTSIDE of if blocks) | |
| set "CROP_OPTION=" | |
| if defined CLOSE_RATIO ( | |
| set /p CROP_OPTION=The video is very close to the !DISPLAY_RATIO_SHOW! ratio, auto-crop to match it? [y/n]: | |
| ) | |
| REM Process crop if user said yes | |
| if /I "!CROP_OPTION!"=="y" ( | |
| REM Calculate crop dimensions based on closest ratio | |
| REM Use separate sequential if statements instead of else if to avoid batch syntax errors | |
| REM 16:9 ratio | |
| if "!CLOSE_RATIO!"=="16_9" ( | |
| if !width! LSS !height! ( | |
| set /a crop_w=!height! * 16 / 9 | |
| if !crop_w! GTR !width! set /a crop_w=!width! | |
| set /a crop_h=!crop_w! * 9 / 16 | |
| ) else ( | |
| set /a crop_h=!width! * 9 / 16 | |
| set /a crop_w=!crop_h! * 16 / 9 | |
| if !crop_w! GTR !width! ( | |
| set /a crop_w=!width! | |
| set /a crop_h=!crop_w! * 9 / 16 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 4:3 ratio | |
| if "!CLOSE_RATIO!"=="4_3" ( | |
| if !width! LSS !height! ( | |
| set /a crop_w=!height! * 4 / 3 | |
| if !crop_w! GTR !width! set /a crop_w=!width! | |
| set /a crop_h=!crop_w! * 3 / 4 | |
| ) else ( | |
| set /a crop_h=!width! * 3 / 4 | |
| set /a crop_w=!crop_h! * 4 / 3 | |
| if !crop_w! GTR !width! ( | |
| set /a crop_w=!width! | |
| set /a crop_h=!crop_w! * 3 / 4 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 9:16 ratio | |
| if "!CLOSE_RATIO!"=="9_16" ( | |
| if !width! GTR !height! ( | |
| set /a crop_h=!width! * 16 / 9 | |
| if !crop_h! GTR !height! set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 9 / 16 | |
| ) else ( | |
| set /a crop_w=!height! * 9 / 16 | |
| set /a crop_h=!crop_w! * 16 / 9 | |
| if !crop_h! GTR !height! ( | |
| set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 9 / 16 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 3:4 ratio | |
| if "!CLOSE_RATIO!"=="3_4" ( | |
| if !width! GTR !height! ( | |
| set /a crop_h=!width! * 4 / 3 | |
| if !crop_h! GTR !height! set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 3 / 4 | |
| ) else ( | |
| set /a crop_w=!height! * 3 / 4 | |
| set /a crop_h=!crop_w! * 4 / 3 | |
| if !crop_h! GTR !height! ( | |
| set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 3 / 4 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 3:2 ratio | |
| if "!CLOSE_RATIO!"=="3_2" ( | |
| if !width! GTR !height! ( | |
| set /a crop_h=!width! * 2 / 3 | |
| if !crop_h! GTR !height! set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 3 / 2 | |
| ) else ( | |
| set /a crop_w=!height! * 3 / 2 | |
| set /a crop_h=!crop_w! * 2 / 3 | |
| if !crop_h! GTR !height! ( | |
| set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 3 / 2 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 2:3 ratio | |
| if "!CLOSE_RATIO!"=="2_3" ( | |
| if !width! GTR !height! ( | |
| set /a crop_h=!width! * 3 / 2 | |
| if !crop_h! GTR !height! set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 2 / 3 | |
| ) else ( | |
| set /a crop_w=!height! * 2 / 3 | |
| set /a crop_h=!crop_w! * 3 / 2 | |
| if !crop_h! GTR !height! ( | |
| set /a crop_h=!height! | |
| set /a crop_w=!crop_h! * 2 / 3 | |
| ) | |
| ) | |
| set /a crop_x=^(!width! - !crop_w!^) / 2 | |
| set /a crop_y=^(!height! - !crop_h!^) / 2 | |
| set "CROP=!crop_w!x!crop_h!x!crop_x!x!crop_y!" | |
| ) | |
| REM 1:1 ratio | |
| if "!CLOSE_RATIO!"=="1_1" ( | |
| set /a min_dim=!width! | |
| if !height! LSS !min_dim! set /a min_dim=!height! | |
| set /a crop_x=^(!width! - !min_dim!^) / 2 | |
| set /a crop_y=^(!height! - !min_dim!^) / 2 | |
| set "CROP=!min_dim!x!min_dim!x!crop_x!x!crop_y!" | |
| ) | |
| ) else ( | |
| REM If neither crop nor downscale was selected, ask for final width | |
| set /p FINAL_WIDTH=Enter desired final width [pixels]: | |
| REM Validate that FINAL_WIDTH is a valid number and greater than 0 | |
| if not "!FINAL_WIDTH!"=="" ( | |
| REM Try to use it in arithmetic - if it fails, errorlevel will be non-zero | |
| set /a FINAL_WIDTH_TEST=!FINAL_WIDTH! | |
| if !FINAL_WIDTH! GTR 0 ( | |
| set /a FINAL_HEIGHT=!FINAL_WIDTH! * !height! / !width! | |
| set "DOWNSCALE=!FINAL_WIDTH!x!FINAL_HEIGHT!" | |
| ) | |
| ) | |
| ) | |
| ) | |
| :: ============================= | |
| :: 5. FPS change | |
| :: ============================= | |
| set "NEW_FPS=" | |
| if not "!fps!"=="N/A" if !fps! GTR 30 set /p NEW_FPS=Enter desired FPS (press enter to skip): | |
| :: ============================= | |
| :: 6. Bitrate change | |
| :: ============================= | |
| set /p NEWBR=Enter new video bitrate [kbps] or press enter to skip: | |
| :: ============================= | |
| :: 7. Rotation | |
| :: ============================= | |
| set "ROTATION=" | |
| set /p ROTATE_OPTION=Rotate video? Press [1] to rotate to the right, [2] for left, or enter to skip: | |
| if "!ROTATE_OPTION!"=="1" set "ROTATION=transpose=1" | |
| if "!ROTATE_OPTION!"=="2" set "ROTATION=transpose=2" | |
| :: ============================= | |
| :: 8. Trimming | |
| :: ============================= | |
| set /p START=Enter start timestamp [mm:ss] or press enter to skip: | |
| set /p END=Enter end timestamp [mm:ss] or press enter to skip: | |
| :: ============================= | |
| :: 9. Remove audio | |
| :: ============================= | |
| if !HAS_AUDIO! EQU 1 set /p REMOVE_AUDIO=Remove audio? [y/n]: | |
| :: ============================= | |
| :: 10. Encode | |
| :: ============================= | |
| :encode | |
| set "OUTFILE=%~dpn1 new.mp4" | |
| set /a COUNT=2 | |
| :checkexist | |
| if exist "%OUTFILE%" ( | |
| set "OUTFILE=%~dpn1 new!COUNT!.mp4" | |
| set /a COUNT+=1 | |
| goto checkexist | |
| ) | |
| set "CMD=ffmpeg -loglevel quiet -stats" | |
| set CMD=!CMD! -i "!VIDEO!" | |
| :: ss before input may be faster but can result in length mismatches | |
| if defined START set "CMD=!CMD! -ss !START!" | |
| if defined END set "CMD=!CMD! -to !END!" | |
| REM Build video filters (crop and/or scale and/or rotation) | |
| set "FILTERS=" | |
| if defined CROP ( | |
| REM Convert x back to : for ffmpeg crop filter | |
| set "CROP_FIXED=!CROP:x=:!" | |
| set "FILTERS=crop=!CROP_FIXED!" | |
| ) | |
| if defined DOWNSCALE ( | |
| REM Convert x back to : for ffmpeg scale filter | |
| set "DOWNSCALE_FIXED=!DOWNSCALE:x=:!" | |
| if defined FILTERS ( | |
| set "FILTERS=!FILTERS!,scale=!DOWNSCALE_FIXED!" | |
| ) else ( | |
| set "FILTERS=scale=!DOWNSCALE_FIXED!" | |
| ) | |
| ) | |
| if defined ROTATION ( | |
| if defined FILTERS ( | |
| set "FILTERS=!FILTERS!,!ROTATION!" | |
| ) else ( | |
| set "FILTERS=!ROTATION!" | |
| ) | |
| ) | |
| if defined FILTERS ( | |
| set CMD=!CMD! -vf "!FILTERS!" | |
| ) | |
| if defined NEW_FPS set "CMD=!CMD! -r !NEW_FPS!" | |
| if /I "!REMOVE_AUDIO!"=="y" set "CMD=!CMD! -an" | |
| if not defined REMOVE_AUDIO set CMD=!CMD! -map 0 -c^:s copy | |
| set CMD=!CMD! -c^:v libx265 | |
| if defined NEWBR ( | |
| set CMD=!CMD! -b^:v !NEWBR!k | |
| ) else ( | |
| set CMD=!CMD! -crf 27 | |
| ) | |
| set CMD=!CMD! -y "!OUTFILE!" | |
| if %SILENT% equ 0 ( | |
| echo Running: | |
| echo !CMD! | |
| ) | |
| !CMD! | |
| for %%A in ("!OUTFILE!") do set "OUT_SIZE_BYTES=%%~zA" | |
| REM Calculate size in KB first for accuracy | |
| set /a OUT_SIZE_KB_TOTAL=!OUT_SIZE_BYTES! / 1024 | |
| if !OUT_SIZE_KB_TOTAL! LSS 1024 ( | |
| set "OUT_SIZE=!OUT_SIZE_KB_TOTAL! KB" | |
| ) else ( | |
| REM Calculate MB with decimal precision | |
| set /a OUT_SIZE_MB_TIMES100=!OUT_SIZE_BYTES! * 100 / 1048576 | |
| set /a OUT_SIZE_MB=!OUT_SIZE_MB_TIMES100! / 100 | |
| set /a OUT_SIZE_DECIMAL=!OUT_SIZE_MB_TIMES100! %% 100 | |
| if !OUT_SIZE_DECIMAL! LSS 10 ( | |
| set "OUT_SIZE=!OUT_SIZE_MB!.0!OUT_SIZE_DECIMAL! MB" | |
| ) else ( | |
| set "OUT_SIZE=!OUT_SIZE_MB!.!OUT_SIZE_DECIMAL! MB" | |
| ) | |
| ) | |
| set /a SIZE_DIFF=!FILE_SIZE_BYTES! - !OUT_SIZE_BYTES! | |
| set /a PERCENT_CHANGE=^(!SIZE_DIFF! * 100^) / !FILE_SIZE_BYTES! | |
| if %SILENT% equ 0 ( | |
| echo. | |
| echo Finished successfully. Output saved to: !OUTFILE! | |
| echo Size: !FILE_SIZE! -^> !OUT_SIZE! | |
| if !SIZE_DIFF! GEQ 0 ( | |
| echo Reduced by !SIZE_DIFF! bytes ^(!PERCENT_CHANGE!%%^) | |
| ) else ( | |
| set /a SIZE_DIFF_ABS=!OUT_SIZE_BYTES! - !FILE_SIZE_BYTES! | |
| set /a PERCENT_INCREASE=^(!SIZE_DIFF_ABS! * 100^) / !FILE_SIZE_BYTES! | |
| echo Increased by !SIZE_DIFF_ABS! bytes ^(!PERCENT_INCREASE!%%^) | |
| ) | |
| ) else ( | |
| echo File "!filename!" compressed from !FILE_SIZE! to !OUT_SIZE! | |
| ) | |
| endlocal | |
| exit /b 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment