【问题标题】:Recursively run ffprobe to get codec types递归运行 ffprobe 获取编解码器类型
【发布时间】:2019-04-20 19:35:53
【问题描述】:

我稍微修改了 here 中的 @rojo 代码以查找 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"
        }
    ]
}

【问题讨论】:

    标签: batch-file ffmpeg ffprobe


    【解决方案1】:

    看起来rojo 编写的batch file / JScript hybrid script 并不是为递归执行目录树中的所有 *.mkv 和 *.mp4 文件而设计的。出于这个原因,我完全重写了批处理文件并省略了 JScript 脚本部分。

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

    ffprobe 的 JSON 输出也可以在此用例中直接使用 FOR 循环处理,使用逗号,、冒号:、左方括号[、水平制表符 TAB,右方括号],左{和右大括号}普通空格 SPACE kbd>。在处理 JSON 格式的输出时,可以完全忽略以 { 开头的行。区分大小写的字符串比较用于确定该行是否包含 codec_name 值,并将第一个 coder/decoder 值解释为视频编解码器,第二个解释为音频编解码器。

    @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 的定义有些特别,因为参数字符串"stream=codec_name" 稍后必须传递给由FOR 启动的单独命令进程,需要用两个^ 双重转义等号到finally已将 = 传递给 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&gt;nul 的解释。重定向运算符 &gt; 必须在 FOR 命令行上使用插入符 ^ 转义,以便在 Windows 命令解释器在执行命令 FOR 之前处理此命令行时解释为文字字符> 在后台启动的单独命令进程中执行嵌入的dir 命令行。

    FOR 捕获所有输出到后台命令进程的STDOUT 的行,并在启动cmd.exe 终止后处理它们。所以 FOR 正在处理一个完整的限定文件名列表,在运行循环时不会改变。

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

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

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

    FOR 与选项 /F 在这种情况下默认跳过 DIR 不输出的空行以及以分号开头的行,这在此处也是不可能的,因为每一行以驱动器号C 开头。但是 FOR 将使用普通空格和水平制表符作为字符串分隔符将每个捕获的行拆分为子字符串(标记),并将仅将第一个空格/制表符分隔的字符串分配给指定的循环变量I。此处不需要此行为,因为即使包含一个或多个空格,也始终需要完整的限定文件名。出于这个原因,delims= 用于定义字符串分隔符的空列表,从而导致完全关闭字符串拆分行为并分配给循环变量I,始终使用找到的 *.mkv 或 *.mp4 文件的文件名路径、名称和扩展名。

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

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

    然后再使用一个 FOR 在后台运行ffprobe 命令行,cmd.exe /C。在这种特殊情况下,由于参数字符串 "stream=codec_name" 需要将整个命令行括在双引号中,以便将整个命令行正确传递给 FOR 启动的附加命令进程。

    内部FOR捕获ffprobe以JSON格式写入的输出,处理启动的命令进程的STDOUT,并逐行处理这个输出。感兴趣的只有包含"codec_name" 的行。因此选项eol={ 用于完全忽略以{ 开头的所有行。根据ASCII table,选项tokens=1,2 导致分配给指定循环变量B 的第一个子字符串和下一个循环变量C 的第二个子字符串。使用选项delims= 指定的分隔符列表会或多或少地获得用双引号括起来的属性名称,如"codec_name",其值也用双引号括起来,如分配给循环变量B 和@987654391 的"h264" @。

    如果分配给循环变量B 的字符串不带双引号明确地用双引号括起来是区分大小写的,等于字符串"codec_name",那么这行是真正有趣的。分配给循环变量C 的编解码器值在不带双引号的情况下分配给环境变量VideoCodecAudioCodec,具体取决于之前已在其中一个处理行的JSON 输出中找到的视频编解码器。此外,可能稍后使用的视频或音频选项设置为 copy,视频或音频编解码器已经是所需的编解码器 h264ac3

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

    输出首先是带有echo( 的空行,下一个是当前视频文件及其当前视频和音频编解码器的完整限定文件名。

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

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

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

    环境变量FilesEncoded 增加一个原始视频文件可以真正成功地替换为重新编码的版本。

    ffmpeg在执行ffmpeg.exe后创建的临时视频文件最终被删除,以防出现任何错误导致该文件在其他命令行之后仍然存在。

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

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

    • del /?
    • dir /?
    • echo /?
    • endlocal /?
    • for /?
    • if /?
    • move /?
    • pause /?
    • set /?
    • setlocal /?

    【讨论】:

      猜你喜欢
      • 2016-02-15
      • 1970-01-01
      • 2016-01-12
      • 1970-01-01
      • 2018-07-03
      • 2016-10-27
      • 2017-11-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多