Рекурсивно запустить ffprobe для получения типов кодеков - PullRequest
0 голосов
/ 18 ноября 2018

Я немного изменил @ 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');
}

ffprobe Выход для h264

{
    "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 голосов
/ 20 ноября 2018

Похоже, что пакетный файл / гибридный сценарий JScript , написанный rojo , не предназначен для рекурсивного выполнения для всех файлов * .mkv и * .mp4 в дереве каталогов.По этой причине я полностью переписал командный файл и пропустил части скрипта JScript.

Похоже, что информация о высоте вывода видео на ffprobe, поскольку опция "stream=codec_name,height" здесь на самом деле не нужна, потому что каждое видео должнообрабатываться независимо от его высоты.По этой причине "stream=codec_name" в строке определения ProbeOptions должно быть достаточно для этой задачи, чтобы уменьшить вывод ffprobe на одну строку.

Вывод JSON ffprobe также может быть обработан в этом случае использованиянепосредственно с помощью FOR , используя в качестве разделителей запятую ,, двоеточие :, левую квадратную скобку [, горизонтальную табуляцию TAB , правую квадратную скобку ], левый { и правая скобка } и нормальное пространство ПРОБЕЛ .Строки, начинающиеся с {, могут полностью игнорироваться при обработке вывода в формате 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 из-за строки аргумента "stream=codec_name", которая позже должна быть передана в отдельный командный процесс, запускаемый FOR , требующий двойного экранирования знака равенства с двумя ^, чтобы в итоге получить= передано 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 .

. Прочтите статью Microsoft о Использование операторов перенаправления команд для объяснения 2>nul.Оператор перенаправления > должен быть экранирован с помощью символа вставки ^ в командной строке 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={ используется для полного игнорирования всех строк, начиная с {.Опция tokens=1,2 приводит к получению первой подстроки, назначенной указанной переменной цикла B, и второй подстроки следующей переменной цикла C в соответствии с ASCII-таблицей .Список разделителей, заданный параметром delims=, приводит к получению более или менее простого имени свойства, заключенного в двойные кавычки, например "codec_name", и его значения также заключенного в двойные кавычки, такие как "h264", назначенные переменным цикла B и * 1186.*.

Если строка, назначенная переменной цикла B без двойных кавычек, явно заключенных в двойные кавычки, чувствительна к регистру и равна строке "codec_name", то эта строка представляет реальный интерес. Значение кодека, присвоенное переменной цикла C, присваивается без двойных кавычек любой переменной окружения VideoCodec или AudioCodec в зависимости от того, какой видеокодек уже найден в выводе JSON в одной из обработанных строк ранее. Кроме того, опция видео или аудио, используемая, возможно, позже, устанавливается на copy, если видео или аудио кодек уже является требуемым кодеком h264 соответственно ac3.

Необходимо включить отложенное расширение переменных среды после обработки вывода ffprobe, чтобы иметь возможность обрабатывать значения переменных среды, определенные ранее в одном и том же командном блоке. Прочитайте этот ответ для получения подробной информации о командах SETLOCAL и ENDLOCAL .

Сначала выводится пустая строка с 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 /?
...