Search code examples
windowsbatch-filecmdcommand-linebatch-processing

Changes to Batch script resulted in failure of ffprobe Bitrate reading


Okay here is a script that uses ffprobe to read the bitrate used in a video file. It takes a video as input, reads the video bitrate, and then proceeds to encode a (cropped) copy of the video using the same bitrate and x265 codec (as far as I can tell recoding is the only way to remove black boarders from videos from showing up in Icaros thumbnailer).

The script is executed from the windows explorer by right-clicking a video and choosing Transcode (command: cmd /k d:\utils\transcode.cmd "%1"). Everything else it does automatically.

@echo off
cd /d "%~dp0"
set "manifest=%~dpn0.txt"
set "temporary=%temp%\%~n0.tmp"
:: input must exist and be no duplicate
if exist "%~1" findstr /c:"%~1" "%manifest%" >nul || echo "%~1">>"%manifest%"
:: exit all but first instance
tasklist /fi "imagename eq handbrakecli.exe" | find /i "handbrakecli" && exit

:manifest
for /f "delims=" %%f in (%manifest%) do (
    set "in=%%~ff"
    set "out=%%~dpnf-2.mkv"
    call :transcode )
for %%a in ("%manifest%") do if not %%~za lss 4 goto :manifest
exit /b

:transcode
if not exist "%in%" goto :cleanup
if exist "%out%" goto :cleanup
    
ffprobe "%in%" -v 0 -select_streams v:0 -show_entries stream=bit_rate -print_format compact=p=0:nokey=1 >"%temporary%"
set /p bitrate=<%temporary%
if not defined bitrate echo failed to fetch bitrate & goto :cleanup
:: reduce to full kilobytes
set "bitrate=%bitrate:~0,-3%"
if %bitrate% gtr 7000 set bitrate=7000

HandBrakeCLI -i "%in%" -o "%out%" --encoder x265_10bit --vb %bitrate% --two-pass --turbo --audio 1-9 --aencoder copy --audio-copy-mask aac,ac3,mp2,mp3,opus --audio-fallback opus --ab 160 --drc 2.0

:cleanup
findstr /v /c:"%in%" "%manifest%">"%temporary%"
move /y "%temporary%" "%manifest%">nul

The command is solid, but it constantly keeps getting broken according to me working on the rest of the script. Please note that the ffprobe command itself, the variables etc has been constantly the same, but it still breaks every now and then. I suspect it has to do with " quotes or something similar somewhere else in the script. Currently, instead of a number representing the video track bitrate I get a N/A. And yes this has been tested on videos that have already been proven to work well with earlier script version, passing the bitrate info via ffprobe.

The %~n0.txt file looks like this

"D:\VIDEOS\SCOPITONE\Dario - Big Bang.mkv"
"D:\VIDEOS\SCOPITONE\Rossi - BB (edit).mp4"

See, instead of every launch of the script creating a separate encoding instance, a file is queued in this file. Only the first instance of the script is allowed to continue, and files added later will be processed from the end of the queue.

The file %temporary% is supposed to include the bitrate 2534533 and gets reduced to kilos by dropping the last three digits so 2534 would mean the new video will be coded with 2534k/s


Solution

  • Processing lots of video files can take hours. It could happen that a shutdown of the PC or a restart of Windows must be done while the processing of one or more video files according to one or more existing list files is currently in progress. The list files with file or folder names not completely processed remain in the directory for temporary files on processing is interrupted and stopped for whatever reason. It should be possible to force a restart of the processing of the remaining video files according to the still existing list files later on processor time and power is available again for this video files processing task.

    Below is the batch file from my initial answer with some enhancements.

    @echo off
    setlocal EnableExtensions DisableDelayedExpansion
    set "OutputNameExt=_2.mkv"
    set "ExeFFProbe="
    set "ExeHandBrake="
    
    if not "%~1" == "" goto GetFFProbe
    echo(
    echo ERROR: "%~nx0" must be executed with at least one video file name!
    echo(
    if not defined NoPausePrompt set /P "=Press any key to exit ... " 0<nul & pause 1>nul
    exit /B 2
    
    :GetFFProbe
    if defined ExeFFProbe if exist "%ExeFFProbe%" goto GetHandBrakeCLI
    if exist "%~dp0ffprobe.exe" set "ExeFFProbe=%~dp0ffprobe.exe" & goto GetHandBrakeCLI
    if exist ffprobe.exe for %%I in (ffrobe.exe) do set "ExeFFProbe=%%~fI" & goto GetHandBrakeCLI
    for %%I in (ffprobe.exe) do set "ExeFFProbe=%%~$PATH:I"
    if defined ExeFFProbe goto GetHandBrakeCLI
    set "ExeMissing=ffprobe"
    goto MissingEXE
    
    :GetHandBrakeCLI
    if defined ExeHandBrake if exist "%ExeHandBrake%" goto AddListFile
    if exist "%~dp0HandBrakeCLI.exe" set "ExeHandBrake=%~dp0HandBrakeCLI.exe" & goto AddListFile
    if exist HandBrakeCLI.exe for %%I in (HandBrakeCLI.exe) do set "ExeHandBrake=%%~fI" & goto AddListFile
    for %%I in (HandBrakeCLI.exe) do set "ExeHandBrake=%%~$PATH:I"
    if defined ExeHandBrake goto AddListFile
    set "ExeMissing=HandBrakeCLI"
    
    :MissingEXE
    echo(
    echo ERROR: "%~nx0" could not find the executable %ExeMissing%.exe!
    echo(
    echo Please make sure the program %ExeMissing%.exe is in the current directory or
    echo can be found using the environment variable PATH or is in the directory:
    echo(
    echo "%~dp0"
    echo(
    if not defined NoPausePrompt set /P "=Press any key to exit ... " 0<nul & pause 1>nul
    exit /B 3
    
    :AddListFile
    set "RetryCount=0"
    set "ListFileName=%~n0"
    set "ListFileName=%ListFileName:_= %"
    set "ListFileName=%ListFileName:.= %_"
    set "ListFullName=%TEMP%\%ListFileName%"
    if /I "%~1" == "/ForceRestart" echo INFO: Force a restart of video files processing.& goto ProcessFiles
    :GetFileNumber
    set "FileNumber=0"
    setlocal EnableDelayedExpansion
    for /F "eol=| tokens=2 delims=_." %%I in ('dir "!ListFullName!*.tmp" /A-D-H-L /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "_[0123456789][0123456789]*\.tmp$"') do if %%I GTR !FileNumber! set "FileNumber=%%I"
    endlocal & set "FileNumber=%FileNumber%"
    set /A FileNumber+=1
    ((for %%I in (%*) do echo("%~f1") 1>"%ListFullName%%FileNumber%.tmp") 2>nul
    if not errorlevel 1 goto QueueTask
    set /A RetryCount+=1
    if not %RetryCount% == 50 goto GetFileNumber
    echo(
    echo ERROR: "%~nx0" could not create the temporary list file:
    echo(
    echo "%ListFullName%%FileNumber%.tmp"
    echo(
    if not defined NoPausePrompt set /P "=Press any key to exit ... " 0<nul & pause 1>nul
    exit /B 1
    
    :QueueTask
    if not %FileNumber% == 1 exit /B
    set "RetryCount=0"
    
    :ProcessFiles
    set "OneMoreList="
    for /F "eol=| delims=" %%G in ('dir "%ListFullName%*.tmp" /A-D-H-L /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "_[0123456789][0123456789]*\.tmp$"') do (
        set "OneMoreList=1"
        set "LinesProcessed="
        (for /F "usebackq eol=| delims=" %%H in ("%TEMP%\%%G") do (
            if exist "%%~H\" (
                echo Processing *.mkv and *.mp4 in: %%H
                for /F "eol=| delims=" %%I in ('dir "%%~H\*.mkv" "%%~H\*.mp4" /A-D-L /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /E /I /L /V /C:"%OutputNameExt%"') do set "VideoFile=%%~H\%%I" & call :ProcessVideo
            ) else if exist "%%~H" set "VideoFile=%%~H" & call :ProcessVideo
            set "LinesProcessed=1"
        )) 2>nul
        if defined LinesProcessed (del "%TEMP%\%%G") else set /A RetryCount+=1
    )
    if not defined OneMoreList exit /B 0
    if %RetryCount% LSS 50 goto ProcessFiles
    echo(
    echo ERROR: "%~nx0" could not process all the temporary list files:
    echo(
    echo "%ListFullName%*.tmp"
    echo(
    if not defined NoPausePrompt set /P "=Press any key to exit ... " 0<nul & pause 1>nul
    exit /B 1
    
    :ProcessVideo
    for %%J in ("%VideoFile%") do (
        if exist "%%~dpnJ%OutputNameExt%" echo INFO: Skipping file: "%VideoFile%"& goto :EOF
        set "OutputFile=%%~nJ%OutputNameExt%"
        set "TempFile=%%~dpnJ.tmp"
    )
    echo Processing video: "%VideoFile%"
    set "BitRate="
    for /F %%J in ('^""%ExeFFProbe%" "%VideoFile%" -v 0 -select_streams v:0 -show_entries stream^=bit_rate -print_format compact^=p^=0:nokey^=1 2^>nul^"') do set "BitRate=%%J"
    if not defined BitRate echo ERROR: "%VideoFile%" is not a video file!& goto :EOF
    set /A BitRate/=1000
    if %BitRate% GTR 7000 set "BitRate=7000"
    "%ExeHandBrake%" -i "%VideoFile%" -o "%TempFile%" --encoder x265_10bit --vb %BitRate% --two-pass --turbo --audio 1-9 --aencoder copy --audio-copy-mask aac,ac3,mp2,mp3,opus --audio-fallback opus --ab 160 --drc 2.0
    if errorlevel 1 del "%TempFile%" 2>nul & goto :EOF
    ren "%TempFile%" "%OutputFile%"
    

    1. Customizable output file name extension

    The third command line in the batch file is now:

    set "OutputNameExt=_2.mkv"
    

    This line defines the string which is appended to the source video file name without file extension to the output video file name. That makes it easily possible changing the output file names to *_x265.mkv or whatever seems to be best without doing a search and replace on entire batch file.

    The string value of the environment variable OutputNameExt is referenced three times in the entire batch file.

    2. Batch file option /ForceRestart

    There is added in the command block below the label :AddListFile the command line:

    if /I "%~1" == "/ForceRestart" echo INFO: Force a restart of video files processing.& goto ProcessFiles
    

    That extension makes it possible running the batch file with the case-insensitive interpreted command line option /ForceRestart for restarting the processing of the video files according to the still existing list files in the directory for temporary files after a restart of Windows.

    Running the batch file with the option /ForceRestart should be done only if there is no command process running which is already processing this batch file.

    3. Information about processing videos in a directory

    The following command line is added to the batch file for informing the user about the processing of videos in a directory.

    echo INFO: Processing *.mkv and *.mp4 in: %%H
    

    4. Ignoring videos in a directory having a file name with output name extension

    The batch file could be started with one or more directory names which contain already several output videos created some time before. That can especially occur on stopping the video processing due to a necessary shutdown or restart of Windows and later force a restart of the video processing and one or more existing list files contain one or more directory names instead of just video file names.

    It is also possible that a directory has multiple videos processed already before and later are added additional videos into the same directory which need to be processed too. This extension makes it possible to run the batch file with just the directory name to get processed now only all added video files and skipping the output videos created some time before in same directory.

    This enhancement was done by extending the command line

    for /F "eol=| delims=" %%I in ('dir "%%~H\*.mkv" "%%~H\*.mp4" /A-D-L /B 2^>nul') do set "VideoFile=%%~H\%%I" & call :ProcessVideo
    

    to the command line

    for /F "eol=| delims=" %%I in ('dir "%%~H\*.mkv" "%%~H\*.mp4" /A-D-L /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /E /I /L /V /C:"%OutputNameExt%"') do set "VideoFile=%%~H\%%I" & call :ProcessVideo
    

    FINDSTR is used now for getting the list of video file names in the directory to process without the video file names ending case-insensitive with the output name extension as defined in the third line in the batch file.

    It is nevertheless possible to process a video of which name ends with the string assigned to the environment variable OutputNameExt. But it is necessary to run the batch file with the name of such a video file as argument instead of the name of the directory containing such a video file.

    5. Ignoring videos for which an appropriate output video already exists

    There is added below the label :ProcessVideo the following command block:

    for %%J in ("%VideoFile%") do (
        if exist "%%~dpnJ%OutputNameExt%" echo INFO: Skipping file: "%VideoFile%"& goto :EOF
        set "OutputFile=%%~nJ%OutputNameExt%"
        set "TempFile=%%~dpnJ.tmp"
    )
    

    This command block replaces the former last but one command line:

    for %%J in ("%VideoFile%") do set "OutputFile=%%~dpnJ_2.mkv"
    

    For each video file to process is checked now first if there is in the same directory already a video file with the output name extension as defined in the third command line. There is just output an information message about skipping this video file if there is already a matching output video file for the file to process next.

    This enhancement is necessary as addition to the fourth enhancement to avoid processing of videos in a directory for which a matching output video already exists. It is also necessary for the use case of an interrupted and later restarted video processing on which multiple file names are stored in a list file and some of them have been processed already before interrupting the video files processing.

    An already existing output video file must be explicitly deleted if a source video file has been modified somehow and there should be created the output video file once again using this batch file.

    6. Using first a temporary file name as output file name

    The fourth argument string passed to the program HandBrakeCLI is now a temporary file name.
    There are appended also two more command lines at the bottom of the batch file.

    if errorlevel 1 del "%TempFile%" 2>nul & goto :EOF
    ren "%TempFile%" "%OutputFile%"
    

    This enhancement deletes the temporary file created by HandBrakeCLI on HandBrakeCLI exited with an exit code greater or equal 1 indicating an error and the subroutine ProcessFile is exited without running the last command. That modified behavior is also useful if an error occurs on processing the source video file.

    The last command line renames the created output file with the temporary file name to the correct output video file name as defined by the source file name and the string value assigned to the environment variable OutputNameExt.

    That enhancement makes it possible to interrupt the video processing even while HandBrakeCLI is currently running resulting in a just partly done video processing task of the currently processed source video file. The just partly processed source video file is processed most likely first on restarting later the video processing by running the batch file with its option /ForceRestart.