Context
I'm trying to develop a script that unifies my currently working scripts to parse some video files. The first one is very simple, just makes sure all video files are on mkv containers. The next few each call mkvmerge -i
to check the file for subtitles, tags, attachments and other undesired extras to strip (which is done sometimes using find
, sometimes using findstr
, for the RegEx).
This is what the output of mkvmerge -i
looks like:
File 'test.mkv': container: Matroska
Track ID 0: video (AVC/H.264/MPEG-4p10)
Track ID 1: audio (Opus)
Track ID 2: subtitles (SubRip/SRT)
Attachment ID 1: type 'image/jpeg', size 30184 bytes, file name 'test.jpg'
Attachment ID 2: type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'
Attachment ID 3: type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'
Chapters: 9 entries
Currently, I just pipe it to find
or findstr
and search for words such as "subtitles", "bytes" or : [0-9]
.
My goal is, for each processed file, to have mkvmerge -i
called just once per file instead of once per script it goes through. This is an old project I'm picking up again and my previous try can be seen in this question.
The question
Following this answer, I managed to append the output of mkvmerge -i
to a variable (%mkvmergeinfo%
) and now all I have to do is to pipe this variable a few times and have the rest of the code act accordingly. This is what it looks like currently:
for /f "usebackq delims= eol=$" %%c in (`echo !mkvmergeinfo! ^| find /c /i "subtitles"`) do (
if [%%c]==[0] (
...
If I change echo !mkvmergeinfo!
to mkvmerge -i
, the rest of the code works correctly, but as now I'm trying to pass a multiline command, when I add echo %%c
after do
, it just displays the first line of echo !mkvmergeinfo!
(which I already checked and it does contain all the lines).
I tried to solve this replacing the whole command inside the parenthesis to a call to a label, which would do the echo and pipe, but that didn't work.
Is there another way around the issue without just writing the output to a file and instead parsing that file?
CodePudding user response:
A for /F
command with a set to process the output of a command line results in starting in background one more Windows command processor with %SystemRoot%\System32\cmd.exe /c
and the command line appended as additional arguments. Therefore delayed variable expansion is not enabled for this command process running in background as it would be necessary for echo !mkvmergeinfo!
. It would be necessary to run the background cmd.exe
with option /V:ON
left to option /c
and the command line to execute. But that is not possible, except running two additional cmd.exe
, the first one with /c
and a command line which starts with %ComSpec% /D /V:ON /C ...
a second cmd.exe
as suggested by aschipfl in his comment above.
I suggest to run mkvmerge.exe -i
once for each *.mkv
file and process the output using just internal commands of cmd.exe
like FOR and IF without using find.exe
and findstr.exe
at all.
Example:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%G in (*.mkv) do (
echo/
echo Processing file "%%G" ...
for /F "delims=" %%H in ('mkvmerge.exe -i "%%G"') do (
echo/
echo Processing info line: %%H
for /F "tokens=1,3,4* delims=:( " %%I in ("%%H") do (
if "%%I" == "Track" (
for /F "delims=)" %%M in ("%%L") do (
if "%%K" == "video" (
echo ... Track %%J is a video track with video codec %%M
) else if "%%K" == "audio" (
echo ... Track %%J is an audio track with audio codec %%M
) else if "%%K" == "subtitles" (
echo ... Track %%J is a subtitles track with subtitles codec %%M
)
)
) else if "%%I" == "Attachment" (
echo ... Attachment %%J is %%K %%L
)
)
)
)
endlocal
How each line output by mkvmerge.exe -i "%%G"
is processed further is up to you. This is just a demonstration which results in following output for the posted information of file test.mkv
.
Processing file "test.mkv" ...
Processing info line: File 'test.mkv': container: Matroska
Processing info line: Track ID 0: video (AVC/H.264/MPEG-4p10)
... Track 0 is a video track with video codec AVC/H.264/MPEG-4p10
Processing info line: Track ID 1: audio (Opus)
... Track 1 is an audio track with audio codec Opus
Processing info line: Track ID 2: subtitles (SubRip/SRT)
... Track 2 is a subtitles track with subtitles codec SubRip/SRT
Processing info line: Attachment ID 1: type 'image/jpeg', size 30184 bytes, file name 'test.jpg'
... Attachment 1 is type 'image/jpeg', size 30184 bytes, file name 'test.jpg'
Processing info line: Attachment ID 2: type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'
... Attachment 2 is type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'
Processing info line: Attachment ID 3: type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'
... Attachment 3 is type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'
Processing info line: Chapters: 9 entries
So the first and the last line of information output are not really processed while there is a further processing on the lines with first substring (token) being case-sensitive Track
or Attachment
.
The second FOR command line results in starting in background with Windows installed into C:\Windows
one more Windows command processor with the following command line on current file being test.mkv
:
C:\Windows\System32\cmd.exe /c mkvmerge.exe -i "test.mkv"
The Windows command processor has to search first for the file mkvmerge.exe
by using the environment variables PATHEXT
and PATH
. So if the batch file has to process 100 .mkv
files, there must be executed most likely several thousand of file system accesses in total to find this executable again and again. The usage of the fully qualified file name, i.e. mkvmerge.exe
with its full path, would help to reduce the number of file system accesses to exactly 100 for 100 .mkv
files for 100 executions of mkvmerge.exe
.
Once mkvmerge.exe
is found, the second cmd.exe
started in background calls this executable with its full path and passes the two arguments to it. mkvmerge.exe
outputs the data to handle STDOUT of the background command process.
The output to handle STDOUT of the background command process is captured by cmd.exe
processing the batch file and FOR processes the lines one after the other after started cmd.exe
closed itself after mkvmerge.exe
finished.
FOR with option /F
ignores always empty lines. That is no problem here.
Non-empty lines would be split up by default into substrings (tokens) using normal space and horizontal tab as string delimiters, then the first substring would be checked on beginning with a semicolon which is the default end of line character in which case the line would be ignored also for further processing and finally the first space/tab delimited substring is assigned to the specified loop variable H
. But that default line processing behavior is not wanted in this case. For that reason delims=
is used to defined an empty list of delimiters which results in getting the entire captured line assigned to the loop variable H
, except the line would start with ;
which can be excluded here because of mkvmerge.exe
does not output a line with a semicolon at the beginning.
The entire line is output by the demonstration code to the console window so that it can be seen how the line looks like which is processed further.
The second for /F
loop makes now the real job of processing the data of the line. The line assigned to loop variable H
is specified in double quotes inside the round brackets resulting in FOR interpreting the line as string to process like a line captured from a command process executed in background or a line read from a text file.
This time the string delimiters are defined with a colon, an opening round bracket and a normal space using delims=:(
. There is additionally specified with tokens=1,3,4*
that of interest is not only the first substring, but the first, the third, the fourth and the rest of the line not further split up into substrings using the delimiters.
For example, let us look on what happens now with the following line:
Track ID 0: video (AVC/H.264/MPEG-4p10)
This line is split up to:
Track
being assigned to the specified loop variableI
.ID
which is not further processed at all.0
which is assigned to next but one loop variableJ
according to the ASCII table.
This is the reason for loop variables being case-sensitive.video
which is assigned to loop variableK
according to the ASCII table.AVC/H.264/MPEG-4p10)
which is the rest of the line being assigned to the loop variableL
.
The strings assigned to the loop variables I
to L
are processed further with the IF conditions whereby the round bracket at end of the codec string of a line beginning with Track
is removed using one more for /F
command with delims=)
.
It would be also possible to directly split up the captured lines into substrings as demonstrated with the following code which additionally ignores all lines beginning with Attachment
on having before processed lines beginning with Track
.
@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%G in (*.mkv) do (
set "Tracks="
echo/
echo Processing file "%%G" ...
for /F "tokens=1,3,4* delims=:( " %%H in ('mkvmerge.exe -i "%%G"') do (
if "%%H" == "Track" (
set "Tracks=1"
for /F "delims=)" %%L in ("%%K") do (
if "%%J" == "video" (
echo ... Track %%I is a video track with video codec %%L
) else if "%%J" == "audio" (
echo ... Track %%I is an audio track with audio codec %%L
) else if "%%J" == "subtitles" (
echo ... Track %%I is a subtitles track with subtitles codec %%L
)
)
) else if not defined Tracks (
if "%%H" == "Attachment" echo ... Attachment %%I is %%J %%K
)
)
)
endlocal
The output of this demonstration code for file test.mkv
is:
Processing file "test.mkv" ...
... Track 0 is a video track with video codec AVC/H.264/MPEG-4p10
... Track 1 is an audio track with audio codec Opus
... Track 2 is a subtitles track with subtitles codec SubRip/SRT
There is undefined first with set "Tracks="
the environment variable Tracks
for each file before processing the lines output by mkvmerge.exe
. If there is a line beginning with Track
, the environment variable Tracks
is defined with value 1
whereby the value does not matter.
On all other lines there is checked first if the environment variable Tracks
is still not defined. If this condition is not true because of at least one line beginning with Track
was processed before, the line is completely ignored. Otherwise there were no track information processed before and for that reason lines beginning with Attachment
are processed next with printing information about the attachment. The first line with File
and the last line with Chapters
are still completely ignored in any case.
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.
echo /?
endlocal /?
for /?
if /?
setlocal /?
See also my answer on Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files to get knowledge why it makes sense to enclose both strings to compare in "
while it does not make sense to enclose them in [
and ]
because of the square brackets have no special meaning for the Windows command processor.