get value from cropdetect - batch-file

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 /?

Related

Parse ffmpeg output into batch variable

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.

How to remove digits from beginning of filename?

I need to rename a filename like this 7612372 filename 50x50.jpg into this filename 50x50.jpg, removing all digits at the beginning of the filename.
The number of digit can be variable.
I need to integrate this into an existing batch file run from the Windows command prompt.
If the format of the filename would be the same for all the files in the folder, then you can try:
#echo off
for /F "delims=" %%A IN ('dir /b /A-D') do (
for /F "tokens=2-3" %%B IN ("%%A") do (
ren "%%~fA" "%%B %%C"
)
)
This is the shortest way, but not the most accurate one. It is unsecure, because if the filename contains spaces, the file will be rename incorrectly. I suggest the following code for the task:
#echo off
setlocal EnableDelayedExpansion
for /F "delims=" %%A IN ('dir /b /A-D') do (
set filename=%%A
for /F "tokens=1" %%B IN ("%%A") do (
ren "%%~fA" "!filename:%%B =!"
)
)
which is more accurate and renames all files correctly only if they have the format mentioned in the beginning.
#echo off turns command-echoing off.
setlocal EnableDelayedExpansion enables delayed expansion. We use it only here, as we have to access variables inside a for loop which is a code block. You must use delayed expansion always inside these code blocks.
Now we make a for loop to parse the output (/F) of the dir /b /A-D command which lists all items in current working directory (%cd%), excluding directories (/A-D).
We need to set a variable here with the filename. We could use the variable name of the loop (%%A), but variables have an advantage: %var:search=replace%, or even !var:search=replace! which we need here.
Now we make another for loop to parse a string (/F): the filename (%%A). We need to access the first token to substract it later. We don't really need to specify it here, but it is good to make it clearer.
We rename files now: %%~fA is the full path where filename currently processed is and !filename:%%B =! means to take filename environment variable, search for string "%%B " (first part of filename [digits] and a space) and replace it with an empty string; actually nothing!
An easier solution is to use
all digits and space as delims and
tokens=*
:: Q:\Test\2019\01\06\SO_54054587.cmd
for /F "delims=" %%A in (
'dir "* *" /A-D-H /B 2^>nul'
) do for /F "tokens=* delims=0123456789 " %%B in (
"%%A"
) do ren "%%A" "%%B"
this will remove all leading delimiters while not splitting the remainder of the file name.
Like the other answers this will not account for the shorted file name already being present.
Your question is not specific enough for us to provide a solution, you really need to provide the section of code into which you wish this to be integrated.
This one expects only one file, as in your question, and that file must be named in the format you've indicated, i.e. the required part is separated from the non-required part by a space:
#Set "name=7612372 filename 50x50.jpg"
#Ren "%name%" "%name:* =%"
[Edit /]
I have noted from your comments that you were indeed looking to parse several files and those files did not match the naming scheme you provided in your question.
Here therefore is an updated potential solution based on those changed parameters.
#For %%A In (*.*) Do #For /F "Tokens=* Delims=0123456789 " %%B In ("%%A") Do #Ren "%%~A" "%%B"
Apologies to LotPings, who I've noticed has posted a very similar method/solution
It's very simple with the basic DOS command rename.
7612372 filename 50x50.jpg
If this is your sample file in the folder, it contains 7 digits and 1 blank space. Totally 8 characters.
We can do this by simply running this command on the particular folder
rename "*.mp3" "////////*.mp3"
each / represents a character you want to remove. That's it.
I suggest following batch code for this task:
#echo off
for /F "delims=" %%A in ('dir "* *" /A-D-H /B 2^>nul') do for /F "tokens=1*" %%B in ("%%A") do ren "%%A" "%%C"
pause
The command FOR runs with cmd.exe /C (more precise %ComSpec% /C) in a separate command process in background the command line:
dir "* *" /A-D-H /B 2>nul
DIR outputs to handle STDOUT of this background command process
just the names of all non-hidden files because of option /A-D-H (attribute not directory and not hidden)
in bare format because of option /B without file path
matching the wildcard pattern * * which matches any file name with at least one space inside
in current directory which can but must not be the directory of the batch file.
DIR would output an error message to handle STDERR if it can't find any directory entry matching these criteria. This error message is redirected to device NUL to suppress it.
Read the Microsoft article 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 captures all lines output to handle STDOUT of started command process and processes those lines after started cmd.exe terminated itself. It is very important for this file renaming task that FOR runs on a list of file names captured before doing the file renames as otherwise the directory entries would change while FOR is accessing them. For that reason for can't be used directly in this case because of for would process the list of * * directory entries while this list changes on each successful file rename. The result would be files not renamed or renamed multiple times or even an endless running loop depending on file system (NTFS or a FAT file system like FAT32 or ExFAT).
FOR with option /F ignores empty lines which do not occur here. FOR ignores also lines starting with a semicolon because of end of line option eol=; is the default. But all lines output by DIR should start with a number and for that reason the default end of line definition can be kept for this task.
FOR with option /F splits up a line by default to substrings using normal space and horizontal tab as delimiters and assigns just first space/tab separated string to specified loop variable. This line splitting behavior is not wanted here in outer FOR loop because loop variable A should hold complete file name with all spaces. Therefore delims= is used to define an empty list of delimiters to disable the line splitting behavior. Safer would be "delims= eol=" which defines also no end of line character.
The file name assigned to loop variable A is referenced with %%A as string in inner FOR loop which splits up the file name into two substrings (tokens). The first substring is the number assigned to specified loop variable B. The second substring after first sequence of spaces (tabs not possible in a file name) is assigned without any further splitting to next loop variable C according to ASCII table. In other words on file name 7612372 filename 50x50.jpg loop variable B holds 7612372 and filename 50x50.jpg is assigned to loop variable C.
The command REN renames the file by referencing complete file name as assigned to loop variable A to the part after first sequence of spaces as assigned to loop variable C.
The command PAUSE at end is added to see the error message output by command REN if renaming a file failed. There is nothing output except the prompt by PAUSE on all files could be renamed successfully.
The batch code can be enhanced further by using FINDSTR as filter to make sure that a file to rename starts really with one or more digits up to first space by using this code:
#echo off
for /F "delims=" %%A in ('dir "* *" /A-D-H /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /R /C:"^[0123456789][0123456789]* "') do for /F "tokens=1*" %%B in ("%%A") do ren "%%A" "%%C"
pause
One more variant for renaming a file with name 03T30 NAME T ALL 40X40X2 - Copy.JPG to T30 NAME T ALL 40X40X2 - Copy.JPG:
#echo off
for /F "delims=" %%A in ('dir /A-D-H /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /R "^[0123456789][0123456789]*"') do for /F "tokens=* delims=0123456789 " %%B in ("%%A") do ren "%%A" "%%B"
pause
DIR outputs the names of all non-hidden files in current directory. This output is redirected as input for FINDSTR which checks if the file name starts with one or more digits. Only those file names are output to STDOUT of background command process to be processed next by FOR.
The inner FOR interprets all digits and space character as string delimiters because of delims=0123456789  and assigns everything after first sequence of digits or spaces to loop variable B because of tokens=*. So loop variable B holds filename 50x50.jpg with 7612372 filename 50x50.jpg assigned to A and T30 NAME T ALL 40X40X2 - Copy.JPG for file name 03T30 NAME T ALL 40X40X2 - Copy.JPG.
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.
dir /?
echo /?
findstr /?
for /?
pause /?
ren /?
PS: I recommend the usage of the shareware file manager Total Commander which has a built-in multi-rename tool for renaming files and folders for people with no coding experience. Download, install and start Total Commander, navigate to the folder containing all these files, press Ctrl+A to select the files, press Ctrl+M to open multi-rename tool window and the rest is self-explaining. If you need nevertheless help, press key F1 to open the help page for multi-rename tool.

How to delete the oldest file in a folder?

I am writing a batch script to backup files and want to include logic that deletes the oldest file in the folder if the number of backups ever exceeds a certain number set by a variable. Pretty new to batch and my current code deletes all files once it exceeds the number.
Any ideas how to do this?
SET Count=0
FOR %%A IN ("%SNAPSHOTNAME%*.*") DO SET /A Count += 1
IF %Count% gtr %NumberOfBackups% FOR %%A IN ("%SNAPSHOTNAME%_PBCS_Test_*.*") DO del "%%A"
I suggest the following single command line for this task:
for /F "skip=%NumberOfBackups% eol=| delims=" %%I in ('dir "%SNAPSHOTNAME%_PBCS_Test_*.*" /A-D-H /B /O-D 2^>nul') do del "%%I"
FOR executes in a separate command process started with cmd.exe /C in background the command line:
dir "%SNAPSHOTNAME%_PBCS_Test_*.*" /A-D-H /B /O-D 2>nul
DIR outputs to handle STDOUT line by line
just non-hidden files because of /A-D-H (not attribute directory or hidden)
in bare format because of /B which means file name only
ordered by last modification (write) time with newest file first and oldest file last.
An error message output by DIR to handle STDERR is suppressed by redirecting it to device NUL with 2>nul.
Read also the Microsoft article 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 captures this output and processes it line by line according to the options specified in the double quoted string with ignoring empty lines which do not exist in this output of DIR.
The first %NumberOfBackups% lines are skipped by FOR which means the newest %NumberOfBackups% are always kept in current directory.
FOR by default ignores also lines starting with a semicolon because of ; is the default for end of line character. For that reason eol=| is used to change end of line character to vertical bar which is not possible in a file or folder name and so it is impossible that a file name output by DIR starts with |.
FOR by default splits a line into substrings using normal space and horizontal tab character as string delimiters and assigns just first space/tab separated string to loop variable I. This string splitting behavior can be disabled by specifying an empty list of delimiters with delims= at end of the options string to get the entire line (= file name) assigned to the loop variable I.
So DIR outputs the names all non-hidden files matching pattern "%SNAPSHOTNAME%_PBCS_Test_*.*" line by line with newest file first and oldest file last and FOR ignores the first/newest %NumberOfBackups% files and deletes all other older files.
FOR does nothing if DIR outputs less or exactly %NumberOfBackups% file names.
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.
del /?
dir /?
for /?
Note: This is nearly the same answer as written by Squashman, but with full explanation how it works.
If you change to using a FOR /F command with the SKIP option, you can get rid of the IF comparison.
set "NumberOfBackups=6"
FOR /F "SKIP=%NumberOfBackups% DELIMS=" %%G IN ('DIR /A-D /B /O-D "%SNAPSHOTNAME%_PBCS_Test_*.*"') DO DEL "%%G"
Your second for loop has no sort and no break condition so as mentioned by you it will delete all files.
A dir command can sort the files so instead of taking the for over all files take a for over the sorted output of an dir command. Option /OD will sort for file timestamp.
To let for loop handle the output use for /f. The break condition is to only delete the first (in this case the oldest) file and than jump out the for loop.
IF %Count% gtr %NumberOfBackups% for /f "delims=" %%f in ('dir /B /OD "%SNAPSHOTNAME%_PBCS_Test_*.*"') do del "%%f" & goto :gotIt
:gotIt

set variable and call result during FOR loop; batch script windows

I have read several posts here on Stackoverflow about binding a variable during a FOR loop. While I figure most of the help provided here has been for Linux/Unix, I'm reaching out for help with batch scripting in Windows. My main goal is to extract the "date created" from a mp4-file and "overlay the date on my video" using ffmpeg (and or ffprobe).
I have experimented a lot, but my latest attempt has been trying to bind the result from ffprobe onto a variable, and use the variable later. My latest and simplest attempt looks like this:
SETLOCAL ENABLEDELAYEDEXPANSION
for %%a in ("*.mp4") do (
for /F "tokens=*" %%G in ('ffprobe -v quiet %%a -print_format compact -show_entries format_tags=creation_time') do (
set DateC=%%G
echo !DateC!)
)
I was hoping to be able to print the tag result from ffprobe using that code, but apparently not. So helping me bind that variable, and how to call it again later inside the following code snippet in Windows, would be deeply appreciated:
ffmpeg -i %%a -filter_complex "drawtext=fontfile=/Windows/Fonts/Arial.ttf:x=28:y=650:fontsize=45:fontcolor=white:box=1:boxcolor=black#0.4:text='!DateC!'" -c:a copy output.mp4
I must also mention I've seen the following code on StackOverflow:
ffmpeg -i %%a -filter_complex "drawtext=fontfile=/Windows/Fonts/Arial.ttf:x=28:y=650:fontsize=45:fontcolor=white:box=1:boxcolor=black#0.4:text='%{metadata\:creation_time}'" -c:a copy output.mp4
But I have the same problem making Windows recognize and print the metadata.
I am certain the file in question contains this metadata.
I suggest this not tested code for the batch file:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for %%a in ("*.mp4") do (
for /F "delims=" %%G in ('ffprobe.exe -v quiet "%%a" -print_format compact -show_entries format_tags^=creation_time 2^>^&1') do (
set "DateC=%%G"
echo !DateC!
)
)
endlocal
The inner FOR runs in a separate command process started with cmd /C the command line:
ffprobe.exe -v quiet "%%a" -print_format compact -show_entries format_tags=creation_time 2>&1
"%%a" is already replaced by name of current *.mp4 file. The double quotes are necessary in case of current *.mp4 file name contains a space or one of these characters &()[]{}^=;!'+,`~.
It is necessary to escape the equal sign with ^ in arguments list to get the command line correct passed to cmd.exe started by FOR in background.
2>&1 results in redirecting output written to handle STDERR to handle STDOUT because of FOR captures only everything written to STDOUT of the started command process.
The redirection operators > and & must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded ffprobe.exe command line in the separate command process started in background.
I don't have ffprobe.exe and ffmpeg.exe installed, but I think those console applications write information about files to handle STDERR (standard error) instead of STDOUT (standard output) which is the reason for using 2>&1 to get the wanted information captured by FOR and assigned to an environment variable.
"tokens=*" is the same as "delims=". Both result in getting the entire line captured by FOR assigned to loop variable G without splitting it up into substrings (tokens) using space/tab as delimiters, except the line starts with a semicolon in which case FOR would ignore that line completely for processing because of internal default eol=;.

Batch file log function for printing command output to both log file and screen

I found there are some topics similar to this problem but not exactly what I want, so I raise this topic.
I want to create a log function for printing message to both formatted log file and console output. The function is as below:
:LOGDEBUG
#echo DEBUG: %~1
if NOT EXIST %LOG_FILE% exit /b 1
#echo [%date - %time%] DEBUG: %~1 >> %LOG_FILE% 2>&1
exit /b 0
And I try to use it for printing the command execution output and if the output contains special character like "<" and ">", this function doesn't work well and prompt "The system cannot find the file specified". My code for executing a command is below:
for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
CALL :LOGDEBUG "%%a"
)
However, when I use "echo" command directly instead of the log function, the output can be printed correctly on the console. Like the following code:
for /f "usebackq delims=" %%a in (`dir c:\temp`) do (
echo %%a
)
May I know what is the problem, and how can I print the output correctly by using the log function? Thanks
You have answered your question by own: when I use "echo" command directly...
#ECHO OFF
SETLOCAL EnableExtensions
set "LOG_FILE=D:\tempx\program.log" my testing value
rem type NUL>"%LOG_FILE%"
for /f "usebackq delims=" %%a in (`dir d:\temp 2^>NUL`) do (
CALL :LOGDEBUG "%%a"
)
rem type "%LOG_FILE%"
ENDLOCAL
exit /b
:LOGDEBUG
FOR %%A in ("%~1") do (
#echo DEBUG: %%~A
if NOT EXIST "%LOG_FILE%" exit /b 1
#echo [%date% - %time%] DEBUG: %%~A >> "%LOG_FILE%" 2>&1
)
exit /b 0
Resources (required reading):
(command reference) An A-Z Index of the Windows CMD command line
(additional particularities) Windows CMD Shell Command Line Syntax
(%~A etc. special page) Command Line arguments (Parameters)
(special page) EnableDelayedExpansion
Here is the batch code which should work.
#echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do call :LOGDEBUG "%%a"
endlocal
goto :EOF
:LOGDEBUG
set "StringToOutput=%~1"
echo DEBUG: !StringToOutput!
echo [%DATE% - %TIME%] DEBUG: !StringToOutput!>>"%LOG_FILE%"
goto :EOF
First delayed environment variable expansion is enabled and a copy of existing environment table is made. It is explained below why this is done.
Next the name of the log file with full path is assigned to an environment variable in local variable table. This path can be with or without 1 or more spaces in path. The log file is deleted in case of existing already from a previous run. This code can be removed if you want to append new lines to already existing file. But you should add in this case code to avoid that the log file permanently increases until no free storage space anymore.
The FOR command executes the command DIR and processes each line of the output of DIR written to stdout. Blank lines are skipped. The default delimiters are space, tab and newline characters. As wanted here are the entire lines of DIR, the default delimiter list is replaced by nothing which means only newline characters remain and loop variable %a gets assigned always an entire non blank line.
The output of command DIR contains < and > which are interpreted as redirection operators if found by command processor within a line not quoted. Therefore the line for DIR output is passed quoted to subroutine LOGDEBUG. Which characters must be usually quoted are listed on last help page printed into a command prompt window when executing cmd /? in a command prompt window.
When the loop has finished, the local environment table is deleted which means LOG_FILE and StringToOutput are also removed, and previous environment is restored which usually means the delayed expansion is turned off again before batch execution exits with a jump to predefined label to end of file.
The subroutine LOGDEBUG first assigns the passed string to an environment variable without surrounding quotes just needed because of special characters in line like < and >.
Next the line is written to console window without quotes using delayed expansion as otherwise < and > would be interpreted as redirecting operators and not literally.
The same line is written also to the log file with the difference of date and time inserted at beginning of line. You missed the percent sign after date in your code. Again delayed expansion is used to get the line with the characters < and > written to file without being interpreted as redirection operators.
Important is also that there is no space before >> as otherwise each line in log file would have a trailing space. 2>&1 is useless here as command echo does not write something to stderr.
The subroutine is exited with a jump to end of file resulting in command FOR processes next line.
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 /?
del /?
dir /?
for /?
goto /?
set /?
It would be of course possible to do all the output directly in body of command FOR without using a subroutine.
#echo off
setlocal EnableDelayedExpansion
set "LOG_FILE=C:\program.log"
del "%LOG_FILE%" 2>nul
for /F "delims=" %%a in ('dir "C:\temp\*" 2^>nul') do (
echo DEBUG: %%a
echo [!DATE! - !TIME!] DEBUG: %%a>>"%LOG_FILE%"
)
endlocal
Delayed variable expansion is nevertheless required here as otherwise %DATE% and %TIME% would be expanded by command processor like %LOG_FILE% already on parsing entire block defined by ( and ) before command FOR is executed at all which would result in same date and time written for all lines to the log file.

Resources