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
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 with using 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:
Empty lines are always ignored by FOR, but empty lines do not occur here.
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.
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:
[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
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 /?
help please.
I am on Windows command line and I want ffmpeg error returned as variable.
Tried many things, still not working.
#echo off&setlocal disabledelayedexpansion
SET S="path\to\ffmpeg.exe" -hide_banner -i InputFile.FLV -vframes 1 -an -s 400x222 -ss 30 OutputFile.jpg
!S!
for /f "usebackq delims=" %%A in ('!S!') do set var=%%A
echo !var!
The result is that the statement !S! produces the correct output on the console (InputFile.FLV: No such file or directory) but does not put that into the variable. The output of echo !var! statement simply shows as "path\to\ffmpeg.exe" -hide_banner -i InputFile.FLV -vframes 1 -an -s 400x222 -ss 30 OutputFile.jpg
How do I get the output of !S! into !var!?
The command setlocal disabledelayedexpansion explicitly disables delayed expansion and so !S! is not expanded delayed at all. For that reason the command line with just !S! results in an error message on execution of the batch file and definitely not in output InputFile.FLV: No such file or directory. And last line just outputs the string !var!.
The usage of for option usebackq results in interpreting the string between '...' as string and not as command line to execute with %ComSpec% /c in a background command process. For that reason the string !S! is assigned to variable var.
Therefore it can be expected that batch file was executed with setlocal EnableDelayedExpansion in real to get the outputs as written in question.
This not tested batch file should work for this task.
#echo off
set "var="
for /F delims^=^ eol^= %%I in ('""path\to\ffmpeg.exe" -hide_banner -i InputFile.FLV -vframes 1 -an -s 400x222 -ss 30 OutputFile.jpg 2>&1"') do set "var=%%I"
if defined var set var
Command FOR with option /F without using option usebackq and with a command line defined between the two ' executes in background %ComSpec% /c and the string between the two ' appended. So executed with Windows being installed in C:\Windows is:
C:\Windows\System32\cmd.exe /c ""path\to\ffmpeg.exe" -hide_banner -i InputFile.FLV -vframes 1 -an -s 400x222 -ss 30 OutputFile.jpg 2>&1"
The started Windows command processor instanced running in background removes in this case first and last " before executing the remaining command line:
"path\to\ffmpeg.exe" -hide_banner -i InputFile.FLV -vframes 1 -an -s 400x222 -ss 30 OutputFile.jpg 2>&1
ffmpeg.exe not installed by me at all outputs information like this one as far as I know to handle STDERR (standard error) instead of STDOUT (standard output). But FOR captures just output written to handle STDOUT of started command process. 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 for being also captured by FOR of command process which is processing the batch file.
With FOR options argument string delims^=^ eol^= an empty list of string delimiters and no end of line character is defined to really get always the entire line as captured by FOR assigned to specified loop variable I. The two options are specified here by way of an exception not enclosed in " as otherwise it is not possible to define an empty list of delimiters and no end of line character. The two equal signs and the space must be escaped with caret character ^ to be interpreted as literal characters and not as argument string separators because of not being enclosed in a double quoted argument string.
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.
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 &()[]{}^=;!'+,`~<|>.
for /?
if /?
set /?
See also the Microsoft article about Using command redirection operators.
I'm trying to get a string from an url using a batch file.
String example:
e-e --ser u.g --p 3 --f 0 x,ss
I am using the command below to CURL output directly to a variable:
FOR /F %%I IN ('curl.exe -s -S %URL%') DO (SET W=%%I)
The problem is, when I echo the variable [W] after the command runs, most of the string is missing...
e-e
What is the best method to get around this issue?
By default, the FOR /F command delimits the output based on a space and tab. That is stated in the help file. To keep that from happening use the DELIMS option to tell the FOR command to not use any delimiters.
FOR /F "delims=" %%I IN ('curl.exe -s -S %URL%') DO (SET W=%%I)
I'm trying to create a script to run a program that receives as an argument the name of a file. The problem I'm having is that the program doesn't work if the filename has special characters in it.
I found this post which helped me a bit. With a few modifications on that script I'm able to use the program with filenames containing [ and ].
However, there are still some other special characters that cause the program to not run. Is it possible to create a batch for all special characters? If not, I would at least need to be able to parse not only [ and ], but ' as well.
This is what I have working at the moment. How can I at least add ' to this?
if not exist "mp4\" mkdir mp4
setlocal disableDelayedExpansion
for %%f in (*.mkv) do (
set _a=%%~nf
set _c=%%f
setlocal enableDelayedExpansion
set _b=!_a:[=\[!
set _name=!_b:]=\]!
set _d=!_c:[=\[!
set _fullname=!_d:]=\]!
"SOMEPATH\ffmpeg\bin\ffmpeg.exe" -i "%%f" -vf subtitles="!_name!.mkv:si=1" -c:v h264_qsv -c:a copy -map 0:v:0 -map 0:a:1 -q:v 6 -look_ahead 0 "mp4/%%~nf.mp4"
endlocal
)
endlocal
pause
If you could explain the reasoning behind stuff that would be cool as well. I don't understand much about scripting, but I would like to understand what's going on in this batch...
Oh, and here is the documentation for escaping characters in this program.
This question already has answers here:
Set output of a command as a variable (with pipes) [duplicate]
(6 answers)
Closed 7 years ago.
I need to run a simple find command and redirect the output to a variable in a Windows Batch File.
I have tried this:
set file=ls|find ".txt"
echo %file%
But it does not work.
If I run this command it works without problems:
set file=test.txt
echo %file%
So obviously my command output is not being set to my variable. Can anyone help? Thanks
I just find out how to use commands with pipes in it, here's my command (that extracts the head revision of an svn repo) :
SET SVN_INFO_CMD=svn info http://mySvnRepo/MyProjects
FOR /f "tokens=1 delims=" %%i IN ('%SVN_INFO_CMD% ^| find "Revision"') DO echo %%i
First of all, what you seem to expect from your question isn't even possible in UNIX shells. How should the shell know that ls|find foo is a command and test.txt is not? What to execute here? That's why UNIX shells have the backtick for such things. Anyway, I digress.
You can't set environment variables to multi-line strings from the shell. So we now have a problem because the output of ls wouldn't quite fit.
What you really want here, though, is a list of all text files, right? Depending on what you need it's very easy to do. The main part in all of these examples is the for loop, iterating over a set of files.
If you just need to do an action for every text file:
for %%i in (*.txt) do echo Doing something with "%%i"
This even works for file names with spaces and it won't erroneously catch files that just have a .txt in the middle of their name, such as foo.txt.bar. Just to point out that your approach isn't as pretty as you'd like it to be.
Anyway, if you want a list of files you can use a little trick to create arrays, or something like that:
setlocal enabledelayedexpansion
set N=0
for %%i in (*.txt) do (
set Files[!N!]=%%i
set /a N+=1
)
After this you will have a number of environment variables, named Files[0], Files[1], etc. each one containing a single file name. You can loop over that with
for /l %%x in (1,1,%N%) do echo.!Files[%%x]!
(Note that we output a superfluous new line here, we could remove that but takes one more line of code :-))
Then you can build a really long line of file names, if you wish. You might recognize the pattern:
setlocal enabledelayedexpansion
set Files=
for %%i in (*.txt) do set Files=!Files! "%%i"
Now we have a really long line with file names. Use it for whatever you wish. This is sometimes handy for passing a bunch of files to another program.
Keep in mind though, that the maximum line length for batch files is around 8190 characters. So that puts a limit on the number of things you can have in a single line. And yes, enumerating a whole bunch of files in a single line might overflow here.
Back to the original point, that batch files have no way of capturing a command output. Others have noted it before. You can use for /f for this purpose:
for /f %%i in ('dir /b') do ...
This will iterate over the lines returned by the command, tokenizing them along the way. Not quite as handy maybe as backticks but close enough and sufficient for most puposes.
By default the tokens are broken up at whitespace, so if you got a file name "Foo bar" then suddenly you would have only "Foo" in %%i and "bar" in %%j. It can be confusing and such things are the main reason why you don't ever want to use for /f just to get a file listing.
You can also use backticks instead of apostrophes if that clashes with some program arguments:
for /f "usebackq" %%i in (`echo I can write 'apostrophes'`) do ...
Note that this also tokenizes. There are some more options you can give. They are detailed in the help for command.
set command has /p option that tells it to read a value from standard input. Unfortunately, it does not support piping into it, but it supports reading a value from a first line of existing file.
So, to set your variable to the name of a first *.txt file, you could do the following:
dir /b *.txt > filename.tmp
set /p file=< filename.tmp
del /q filename.tmp
It is important not to add a space before or even after =.
P. S. No fors, no tokens.
Here's a batch file which will return the last item output by find:
#echo off
ls | find ".txt" > %temp%\temp.txt
for /f %%i in (%temp%\temp.txt) do set file=%%i
del %temp%\temp.txt
echo %file%
for has a syntax for parsing command output, for /f "usebackq", but it cannot handle pipes in the command, so I've redirected output to a temporary location.
I strongly recommend, given that you have access to ls, that you consider using a better batch language, such as bash or even an scripting language like python or ruby. Even bash would be a 20x improvement over cmd scripting.
The short answer is: Don't!
A windows shell env var can hold a max of 32 Kb and it isn't safe to save output from programs in them.
That's why you can't. In batch script you must adopt another programming style. If you need all of the output
from the program then save it to file. If you only need to check for certain properties then pipe the output into
a program that does the checking and use the errorlevel mechanism:
#echo off
type somefile.txt | find "somestring" >nul
if %errorlevel% EQU 1 echo Sorry, not found!
REM Alternatively:
if errorlevel 1 echo Sorry, not found!
However, it's more elegant to use the logical operators Perl style:
#echo off
(type somefile.txt | find "somestring" >nul) || echo Sorry, not found!
It's not available in DOS, but in the Windows console, there is the for command. Just type 'help for' at a command prompt to see all of the options. To set a single variable you can use this:
for /f %%i in ('find .txt') do set file=%%i
Note this will only work for the first line returned from 'find .txt' because windows only expands variable once by default. You'll have to enable delayed expansion as shown here.
what you are essentially doing is listing out .txt files. With that, you can use a for loop to over dir cmd
eg
for /f "tokens=*" %%i in ('dir /b *.txt') do set file=%%i
or if you prefer using your ls, there's no need to pipe to find.
for /f "tokens=*" %%i in ('ls *.txt') do set file=%%i
Example of setting a variable from command output:
FOR /F "usebackq" %%Z IN ( `C:\cygwin\bin\cygpath "C:\scripts\sample.sh"` ) DO SET BASH_SCRIPT=%%Z
c:\cygwin\bin\bash -c '. ~/.bashrc ; %BASH_SCRIPT%'
Also, note that if you want to test out the FOR command in a DOS shell, then you need only use %Z instead of %%Z, otherwise it will complain with the following error:
%%Z was unexpected at this time.