0

I have been able to parse the output of ffmpeg cropdetect with a batch file in Windows 7 to get crop=640:480:0:0 but the process goes too far and processes the last mp4 or mkv file twice. I run the first for loop to get a list of mkv or mp4 files in the folder and run a process :gin. The second for loop to run ffmpeg skipping ahead 30 seconds and run cropdetect on only one second of video with the long file of 60 plus entrys of [Parsed_cropdetect_0 @ 0000000002ef8f00] x1:0 x2:1279 y1:0 y2:719 w:1280 h:720 x:0 y:0 pts:1081 t:1.081000 crop=1280:720:0:0 going to tmp.txt Tail gives me the last line of tmp.txt and outputs to a new text file tmp1.txt The last for loop looks at tmp1.txt and returns the 14th. token of crop=1280:720:0:0

This is a Zeranoe Windows static build and I have tails for windows installed.

I have tried... different for loops

for %g in (*.mp4, *.mkv) do set this=%g
for /f "delims=*" %g in ('dir /b /o:n *.mp4, *.mkv') set this=%g

I have also tried to have tail output overwrite the input with tail -1 tmp.txt > tmp.txt

this all works but is not as elegant. what I have so far,

for /f "delims=*" %%g in ('dir /b /o:-n *.m??') do set cdet=%%g&& call :gin

:gin

ffmpeg -hide_banner -ss 00:0:30.000 -i "%cdet%" -t 1 -vf cropdetect -f null -2>&1 | findstr /c:"crop=" > tmp.txt 

tail -1 tmp.txt  >tmp1.txt

for /f "usebackq tokens=14" %%a in ("tmp1.txt") do set line=%%a

del tmp*.txt

echo %line%

I would like to see if there is a better way to do this without creating temp files and overwriting already processed files.

for those interested the updated script is:

for /F "eol=| delims=" %%I in ('dir /a-d /b /o:-n *.mkv 2^>nul') do set "cdet=%%I" && call :gin

goto :end
:gin
ffmpeg -hide_banner -ss 00:0:30.000 -i "%cdet%" -t 1 -vf cropdetect -f null - 2>&1 | findstr /c:"crop=" >tmp1.txt
for /f "usebackq tokens=14" %%a in ("tmp1.txt") do set line=%%a
del tmp*.txt
echo %line%
pause
:end
exit /b
Greg
  • 31
  • 5

1 Answers1

2

The last file is processed twice because of goto :EOF or exit /B is missing after first for loop to avoid a fall through to the command lines of the subroutine after first for finished. See also: Where does GOTO :EOF return to?

The batch file can be optimized most likely with avoiding the subroutine completely according to provided data with using this code:

setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir *.m?? /A-D-H /B /O-N 2^>nul') do (
    for /F "tokens=2 delims==" %%J in ('"ffmpeg.exe -hide_banner -ss 00:0:30.000 -i "%%I" -t 1 -vf cropdetect -f null - 2>&1 | %SystemRoot%\System32\findstr.exe /c:"crop=""') do set "CropData=%%J"
    call echo crop=%%CropData%%
)
endlocal

Command FOR with option /F and a command line specified between ' results in starting one more command process in background with %ComSpec% /c and the specified command line. So executed by FOR is with Windows being installed into C:\Windows:

C:\Windows\System32\cmd.exe /c dir *.m?? /A-D-H /B /O-N 2>nul

DIR searches

  • in current directory
  • just for non-hidden files because of option /A-D-H (attribute not directory and not hidden)
  • matching the wildcard pattern *.m??
  • and outputs in bare format because of option /B just the names of the files without file path
  • ordered reverse by file name because of option /O-N (for whatever reason).

The file names are output to handle STDOUT (standard output) of background command process. This output is captured by FOR respectively the command process running the batch file.

It is possible that no directory entry matches the specified search criteria resulting in printing an error message by DIR to handle STDERR (standard error) which is redirected by FOR to STDERR of command process running the batch file. This error message can be suppressed by redirecting it to device NUL by started cmd.exe running in background.

Read the Microsoft documentation about Using Command Redirection Operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line in a separate command process started in background.

FOR with option /F processes the captured standard output of started command process line by line after started cmd.exe terminated itself as follows:

  1. Empty lines are always ignored by FOR, but empty lines do not occur here.
  2. A line is split up by default into substrings using normal space and horizontal tab as delimiters and just first space/tab separated string is assigned to specified loop variable I. This line splitting behavior is not wanted in this case as the file names could contain spaces and the entire file name should be assigned to loop variable I and not just the file name part up to first space. For that reason delims= defines an empty list of delimiters to turn off line splitting completely.
  3. Next FOR checks if first substring, i.e. entire file name in this case, starts with default end of line character ; which is a valid character for first character of a file name. For that reason eol=| redefines end of line character to vertical bar which no file name can contain according to Microsoft documentation Naming Files, Paths, and Namespaces.

For each file the outer FOR executes the inner FOR which runs again in background a command process for example with the command line:

C:\Windows\System32\cmd.exe /c "ffmpeg.exe -hide_banner -ss 00:0:30.000 -i "C:\Temp\My vido.mp4" -t 1 -vf cropdetect -f - null 2>&1 | C:\Windows\System32\findstr.exe /c:"crop=""

The started Windows command processor instanced running in background removes in this case first and last " before executing the remaining command line:

ffmpeg.exe -hide_banner -ss 00:0:30.000 -i "C:\Temp\My vido.mp4" -t 1 -vf cropdetect -f - null 2>&1 | C:\Windows\System32\findstr.exe /c:"crop="

ffmpeg.exe outputs information like this one as far as I know to handle STDERR (standard error) instead of STDOUT (standard output). For that reason 2>&1 is needed to redirect output written to handle STDERR of background command process by ffmpeg.exe to handle STDOUT of background command process which is redirected next to STDIN of FINDSTR searching case sensitive for literal string crop= anywhere in a line and outputs the entire line containing this string to STDOUT of started background command process.

Those lines are captured by FOR and processed one after the other as described above. But this time delims== modifies the list of string delimiters to just equal sign character which results in splitting up a line like

[Parsed_cropdetect_0 @ 0000000002ef8f00] x1:0 x2:1279 y1:0 y2:719 w:1280 h:720 x:0 y:0 pts:1081 t:1.081000 crop=1280:720:0:0

into just two substrings:

  1. [Parsed_cropdetect_0 @ 0000000002ef8f00] x1:0 x2:1279 y1:0 y2:719 w:1280 h:720 x:0 y:0 pts:1081 t:1.081000 crop
  2. 1280:720:0:0

Just the second substring is of interest which is the reason for using tokens=2 to get assigned just 1280:720:0:0 to specified loop variable J which is assigned next to environment variable CropData. It is also possible to use the default string delimiters normal space and horizontal tab and assign fourteenth space/tab separated string to specified loop variable J using "tokens=14" which in this case includes crop= in string assigned finally to environment variable CropData.

There are multiple lines output by FINDSTR with crop= and so multiple lines are processed by inner FOR resulting in assigning multiple times crop data to environment variable CropData. That's okay because of wanted are just the last crop data.

The second command executed by outer FOR on each file outputs just the last crop data with the string crop=. The command CALL is used to force Windows command processor to parse the command line echo crop %CropData% as it is already after parsing the entire command block starting with ( and ending with matching ) at end before executing outer FOR a second time to real output the current value of environment variable CropData. See also: How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Another solution would be using delayed expansion as shown below.

setlocal EnableExtensions EnableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir *.m?? /A-D-H /B /O-N 2^>nul') do (
    for /F "tokens=2 delims==" %%J in ('"ffmpeg.exe -hide_banner -ss 00:0:30.000 -i "%%I" -t 1 -vf cropdetect -f null - 2>&1 | %SystemRoot%\System32\findstr.exe /c:"crop=""') do set "CropData=%%J"
    echo crop=!CropData!
)
endlocal

But this solution can be used only if no file name contains one or even more ! as otherwise the exclamation mark(s) in file name would be interpreted as begin/end of an environment variable referenced and so the file name would not be correct passed to ffmpeg.exe.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • cmd /? ... explains how double quotes in string(s) after option /C or /K are interpreted by Windows command processor and when a file name (or any other argument string) must be enclosed in " on containing a space or one of these characters &()[]{}^=;!'+,`~<|>.
  • echo /?
  • endlocal /?
  • findstr /?
  • for /?
  • set /?
  • setlocal /?
Mofi
  • 38,783
  • 14
  • 62
  • 115