递归运行ffprobe以获取编解码器类型

时间:2018-11-18 03:57:27

标签: batch-file ffmpeg ffprobe

我从@rojo修改了here代码,以查找h264 / AC3并通过所有子文件夹递归运行。我唯一的问题是,它总是说视频具有h264和AC3,但是当我手动运行ffprobe命令时,状态却有所不同。我想念什么吗?

@if (@CodeSection == @Batch) @then
@echo off & setlocal

for /R %%f in (*.mkv, *.mp4) do (
    echo Testing %%f

    set ffprobe=C:\ffmpeg-4.0.2-win64-static\bin\ffprobe -v quiet -show_entries "stream=codec_name,height" -of json "%%f"

    for /f "delims=" %%I in ('%ffprobe% ^| cscript /nologo /e:JScript "%~f0"') do set "%%~I"

    set "pre=-hide_banner -fflags +genpts+discardcorrupt+fastseek -analyzeduration 100M"
    set "pre=%pre% -probesize 50M -hwaccel dxva2 -y -threads 3 -v error -stats"
    set "global="
    set "video=-c:v h264_nvenc"
    set "audio=-c:a ac3"

    if defined h264 if defined ac3 (
        echo %%~nf already in x264 + AC3 format.
    )

    if not defined h264 if not defined ac3 (

        if not defined ac3 (
            echo Already has AC3 audio.  Re-encoding video only.
            set "audio=-c:a copy"
        ) 

        if not defined h264 (
            echo Already has h264 video.  Re-encoding audio only.
            set "video=-c:v copy"
        )

        echo output "%%~df%%~pf%%~nf.new.mkv"
        echo C:\ffmpeg-4.0.2-win64-static\bin\ffmpeg %pre% -i "%%f" %global% %video% %audio% "%%~df%%~pf%%~nf.new.mkv"

        pause

        echo del "%%f" /f /q
        echo ren "%%~df%%~pf%%~nf.new.mkv" "%%f"
    )

)
@end // end Batch / begin JScript

var stdin = WSH.CreateObject('Scripting.FileSystemObject').GetStandardStream(0),
    htmlfile = WSH.CreateObject('htmlfile'),
    JSON;

htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9" />');
htmlfile.close(JSON = htmlfile.parentWindow.JSON);

var obj = JSON.parse(stdin.ReadAll());

for (var i = obj.streams.length; i--;) {
    if (/h264/i.test(obj.streams[i].codec_name)) WSH.Echo('h264=true');
    if (/ac3/i.test(obj.streams[i].codec_name)) WSH.Echo('ac3=true');
}

我花了一秒钟的时间,然后由于某种原因停止了工作。

@if (@CodeSection == @Batch) @then
@echo off & setlocal & goto run

:run
for /R %%f in (*.mkv, *.mp4) do (
    echo Testing %%f

    set "file=%%f"
    set "drive=%%~df"
    set "dir=%%~pf"
    set "name=%%~nf"
    set "ext=%%~xf"

    for /f "delims=" %%I in ('C:\ffmpeg-4.0.2-win64-static\bin\ffprobe.exe -v quiet -show_entries "stream=codec_name,height" -of json "%%f" ^| cscript /nologo /e:JScript "%~f0"') do (set "%%~I")

    set "pre=-hide_banner -fflags +genpts+discardcorrupt+fastseek -analyzeduration 100M"
    set "pre=%pre% -probesize 50M -hwaccel dxva2 -y -threads 3 -v error -stats"
    set "global="
    set "video=-c:v h264_nvenc"
    set "audio=-c:a ac3"

    if defined ac3 if defined h264 call :both
    if not defined ac3 call :either
    if not defined h264 call :either
)

:both
echo %name% already in x264 + AC3 format.
goto :EOF

:either
if not defined h264 (
    echo Already has AC3 audio.  Re-encoding video only.
    set "audio=-c:a copy"
) 

if not defined ac3 (
    echo Already has h264 video.  Re-encoding audio only.
    set "video=-c:v copy"
)

echo "C:\ffmpeg-4.0.2-win64-static\bin\ffmpeg %pre% -i "%file%" %global% %video% %audio% "%drive%%dir%%name%.new.mkv""
echo del "%file%" /f /q
echo ren "%drive%%dir%%name%.new.mkv" "%name%%ext%"
goto :EOF

@end // end Batch / begin JScript

var stdin = WSH.CreateObject('Scripting.FileSystemObject').GetStandardStream(0),
    htmlfile = WSH.CreateObject('htmlfile'),
    JSON;

htmlfile.write('<meta http-equiv="x-ua-compatible" content="IE=9" />');
htmlfile.close(JSON = htmlfile.parentWindow.JSON);

var obj = JSON.parse(stdin.ReadAll());

for (var i = obj.streams.length; i--;) {
    if (/h264/i.test(obj.streams[i].codec_name)) WSH.Echo('h264=true');
    if (/ac3/i.test(obj.streams[i].codec_name)) WSH.Echo('ac3=true');
}

h264的ffprobe输出

{
    "programs": [

    ],
    "streams": [
        {
            "codec_name": "h264",
            "height": 528
        },
        {
            "codec_name": "aac"
        }
    ]
}

ac3的输出

{
    "programs": [

    ],
    "streams": [
        {
            "codec_name": "h265",
            "height": 528
        },
        {
            "codec_name": "ac3"
        }
    ]
}

ac3 / h264的输出

{
    "programs": [

    ],
    "streams": [
        {
            "codec_name": "h264",
            "height": 528
        },
        {
            "codec_name": "ac3"
        }
    ]
}

1 个答案:

答案 0 :(得分:1)

似乎batch file / JScript hybrid script编写的rojo并非旨在对目录树中的所有* .mkv和* .mp4文件进行递归执行。因此,我完全重写了批处理文件,并省略了JScript脚本部分。

由于ffprobe选项"stream=codec_name,height"似乎不需要"stream=codec_name"输出的视频高度信息,因为每个视频都应独立于其高度进行处理。因此,ProbeOptions定义行上的ffprobe应该足以使此任务将ffprobe的输出减少一行。

在此用例中,也可以直接使用 FOR 循环将,的JSON输出作为定界符使用逗号:,冒号[,左方括号]水平选项卡 TAB ,右方括号{,左}和右括号{和< strong>正常空间 空格。在处理JSON格式的输出时,可以完全忽略以codec_name开头的行。区分大小写的字符串比较用于确定行中是否包含@echo off setlocal EnableExtensions DisableDelayedExpansion set "ProgramFolder=C:\ffmpeg-4.0.2-win64-static\bin" set "ProbeOptions=-v quiet -show_entries "stream^^=codec_name" -of json" set "MpegOptions=-hide_banner -fflags +genpts+discardcorrupt+fastseek -analyzeduration 100M -probesize 50M -hwaccel dxva2 -y -threads 3 -v error -stats" set "FilesFound=0" set "FilesEncoded=0" for /F "delims=" %%I in ('dir *.mkv *.mp4 /A-D-H /B /S 2^>nul') do ( set "FullFileName=%%I" set "TempFileName=%%~dpnI_new%%~xI" set "AudioCodec=" set "AudioOption=ac3" set "VideoCodec=" set "VideoOption=h264_nvenc" set /A FilesFound+=1 for /F "eol={ tokens=1,2 delims=,:[ ]{} " %%B in ('""%ProgramFolder%\ffprobe.exe" %ProbeOptions% "%%I""') do ( if "%%~B" == "codec_name" ( if not defined VideoCodec ( set "VideoCodec=%%~C" if "%%~C" == "h264" set "VideoOption=copy" ) else ( set "AudioCodec=%%~C" if "%%~C" == "ac3" set "AudioOption=copy" ) ) ) setlocal EnableDelayedExpansion echo( echo File: !FullFileName! echo Video codec: !VideoCodec! echo Audio codec: !AudioCodec! if not "!VideoOption!" == "!AudioOption!" ( "%ProgramFolder%\ffmpeg.exe" %MpegOptions% -i "!FullFileName!" -c:v !VideoOption! -c:a !AudioOption! "!TempFileName!" if not errorlevel 1 ( move /Y "!TempFileName!" "!FullFileName!" if not errorlevel 1 set /A FilesEncoded+=1 ) if exist "!TempFileName!" del "!TempFileName!" ) endlocal ) if %FilesFound% == 1 (set "PluralS=") else set "PluralS=s" echo( echo Re-encoded %FilesEncoded% of %FilesFound% video file%PluralS%. endlocal pause 值,并将第一个编码器/解码器值解释为视频编解码器,将第二个编码器/解码器值解释为音频编解码器。

[

注意]ProbeOptions之间的空格必须是批处理文件中的制表符!

该批处理文件首先根据此批处理文件的需要设置具有启用的命令扩展名的本地环境,并禁用延迟的环境变量扩展,以便能够处理在文件名或文件路径中带有一个或多个感叹号的文件。 / p>

接下来,在脚本中稍后定义一些环境变量以供使用。特殊之处在于变量"stream=codec_name"的定义,因为参数字符串^必须随后传递给以 FOR 开始的单独命令过程,要求用两个{ {1}}最终将=传递给ffprobe.exe

外部 FOR 在以cmd.exe /C开头的单独命令过程中在后台命令行中执行一次:

dir *.mkv *.mp4 /A-D-H /B /S 2>nul

DIR 输出以处理此命令过程的 STDOUT

  • 仅使用/B中的文件名
  • 由于/A-D-H而导致的非隐藏文件(属性不是目录,也不是隐藏的)
  • 匹配通配符模式*.mkv*.mp4
  • 由于/S在当前目录及其所有子目录中
  • 由于/S而具有完整路径。

可能找不到匹配的文件名,导致 DIR 输出错误消息以处理 STDERR 。通过将其重定向到设备 NUL ,可以抑制此错误消息。

阅读有关Using Command Redirection Operators的Microsoft文章,以获取2>nul的解释。当Windows命令解释器在执行命令之前处理此命令行时,重定向操作符>必须在 FOR 命令行上使用脱字符号^进行转义,才能被解释为文字字符。 FOR ,它将在后台启动的单独命令进程中执行嵌入式dir命令行。

FOR 捕获所有输出到后台命令进程的 STDOUT 的行,并在启动cmd.exe终止后对其进行处理。因此, FOR 正在处理运行循环时未更改的标准文件名列表。

在具有NTFS的驱动器上,使用起来也很安全:

for /R %%I in (*.mkv *.mp4) do (

这还会导致处理当前目录和所有子目录中的所有非隐藏* .mkv和* .mp4文件。 NTFS返回按字母顺序排序的文件列表。但是这种方法在FAT32和ExFAT驱动器上存在问题,因为在循环的每次迭代中执行的代码都可能导致文件分配表的更新。 FAT32和ExFAT返回符合特定条件的文件名,就像当前存储在文件分配表中一样,目录中最后修改的文件始终位于目录表的底部。这意味着当循环在由FAT32和ExFAT文件系统返回的第一,第二,第三,...文件名上运行时,文件名列表可能会更改。这可能会导致一个视频文件多次处理,并跳过其他文件。因此最好处理在循环迭代开始之前已完全加载到内存中的文件名列表。

带有选项/F

FOR 在这种情况下默认情况下会跳过 DIR 未输出的空行以及以分号开头的行,这在这里也是不可能的,因为每行以驱动器号C开头。但是 FOR 会将使用正常空格和水平制表符作为字符串定界符的每条捕获的行分割为子字符串(令牌),并且仅将第一个空格/制表符分隔的字符串分配给指定的循环变量I。这里不需要这种行为,因为即使包含一个或多个空格,也始终需要完整的合格文件名。因此,delims=用于定义一个空的字符串定界符列表,从而完全关闭字符串拆分行为,并始终将循环变量I分配给找到的* .mkv或*的文件名。 .mp4文件,具有路径,名称和扩展名。

以下每次循环迭代都会发生:

  1. 当前* .mkv或* .mp4文件的完整限定文件名已分配给环境变量FullFileName
  2. 在文件扩展名左边插入_new的当前* .mkv或* .mp4文件的完整合格文件名将分配给环境变量TempFileName
  3. 如果循环的上一迭代中存在环境变量AudioCodec,则会将其删除。
  4. 环境变量AudioOption定义为字符串值ac3作为所需的音频编解码器。
  5. 如果循环的上一迭代中存在环境变量VideoCodec,则会将其删除。
  6. 环境变量VideoOption定义为字符串值h264_nvenc作为所需的视频编解码器。
  7. 环境变量FilesFound用一个简单的算术表达式加1,该表达式由命令 SET 计算。

然后再使用一个 FOR 用于在后台运行ffprobe的{​​{1}}命令行。在这种特殊情况下,必须使用参数字符串cmd.exe /C将整个命令行括在双引号中,以使整个命令行正确传递给 FOR 开始的其他命令过程。

内部的 FOR 捕获"stream=codec_name"以JSON格式编写的输出,以处理已启动命令过程的 STDOUT 并逐行处理该输出。仅包含ffprobe的行才有意义。因此,选项"codec_name"用于完全忽略以eol={开头的所有行。选项{导致根据ASCII table将分配给指定循环变量tokens=1,2的第一个子字符串分配给下一个循环变量B的第二子字符串。用选项C指定的定界符列表导致属性名称或多或少地只是用双引号括起来的属性名称,例如delims=,并且其值也用双引号括起来的"codec_name"分配给了循环变量"h264"B

如果分配给循环变量C且没有用双引号引起来的字符串的区分大小写与字符串B相等,则此行很有用。分配给循环变量"codec_name"的编解码器值不带双引号分配给环境变量CVideoCodec,这取决于之前在已处理行之一中JSON输出中已经找到的视频编解码器。另外,可能稍后使用的视频或音频选项在已经分别是所需编解码器AudioCodec copy的视频或音频编解码器上设置为h264

有必要在处理ac3的输出之后启用延迟的环境变量扩展,以便能够处理先前在同一命令块中定义的环境变量的值。阅读this answer,了解有关命令 SETLOCAL ENDLOCAL 的详细信息。

输出的第一个空行是ffprobe,然后是当前视频文件及其当前视频和音频编解码器的下一个完整合格文件名。

IF 条件比较区分大小写的视频和音频选项。仅当当前视频文件已进行h264 / ac3编码时,两个选项字符串才相同,在这种情况下,两个环境变量的值均为echo(。因此,如果两个比较的字符串相同,则必须使用copy重新对视频文件进行编码,以更改视频编解码器或音频编解码器或两个编解码器。

ffmpeg退出后,视频文件的重新编码成功,且退出代码大于或等于ffmpeg,即值为1。在这种情况下,如果当前视频文件不受只读属性或NTFS权限的写保护,则0创建的临时视频文件将移动到当前视频文件上,并覆盖现有视频文件。

这些操作导致更新FAT32和ExFAT驱动器上的文件分配表,这是外部 FOR 运行 DIR 将视频文件名列表存储到内存中的原因循环迭代。

环境变量ffmpeg增加了原始视频文件之一,可以真正地用重新编码的版本成功替换

FilesEncoded创建的临时视频文件在执行ffmpeg之后完全存在,如果出现任何错误导致该文件在其他命令行之后仍然存在,则最终将其删除。

最后,在处理了所有非隐藏的* .mkv和* .mp4文件之后,使用两个计数器环境变量输出摘要信息,并在停止批处理文件执行之前还原初始环境,以便能够看到所有输出。双击打开了批处理文件。

要了解所使用的命令及其工作方式,请打开命令提示符窗口,在其中执行以下命令,并非常仔细地阅读每个命令显示的所有帮助页面。

  • ffmpeg.exe
  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • move /?
  • pause /?
  • set /?