在FFMPEG中探索视频生成器
#javascript #generators #ffmpeg #coreimagesrc

通过测试FFMPEG中大多数视频生成选项的范围,作者完全创造了完全不合适的眼睛糖果 - 但运气好也可以管理此过程中的一些有价值的测试工具...

如果您尚未开始关注Eyevinn的 ffmpeg of Day of Day 系列on Instagram(我是作者),也许您应该!无论如何,我在系列中查看的第一个命令是FFMPEG中cellauto滤波器的一个示例。在浏览可用于音频和视频的漫长的FFMPEG过滤器列表时,我注意到视频生成器在列表中具有相当大的部分。这些发电机可用很多,但其中许多可能对各种目的有用。列表中不仅有许多独立的发电机,其中一些以特定格式使用代码或插件可扩展。在本文中,我将尝试通过检查它们的各种参数范围来获取所有这些发电机可以做什么的概念。

首先。我在MacOS上,所以我将重点关注我容易适合的过滤器 - 尤其是在所有具有大量内置效果列表的coreimagesrc Generator中。

实际上,coreimagesrc似乎是一个很好的开始...

coreimagesrc发电机

我们将要查看的几个发电机也可以作为更通用的过滤器可用,并且选项有些变化。发电机更具体,因为它们被设计为首先是过滤器链,作为输入。该coreimagesrc在滤波器coreimage中具有更一般的对应物。

我们要做的第一件事是确定我们的Mac上有哪些特定发电机:

ffmpeg -f lavfi -i coreimagesrc=list_generators=true null

这将为您提供一长串具有所有参数的生成器列表。让我们抓住它(请注意,从stderr到STDOUT的所有内容的小望远镜,这是成功地将FFMPEG输出输送到grep的所有内容)。我们也可以使用该行的第一部分来获得一个不错的清单:

ffmpeg -f lavfi -i coreimagesrc=list_generators=true 2>&1 | grep Filter: | cut -c 32-

给我们以下列表(在我的情况下):

 Filter: CIAttributedTextImageGenerator
 Filter: CIAztecCodeGenerator
 Filter: CIBarcodeGenerator
 Filter: CICheckerboardGenerator
 Filter: CICode128BarcodeGenerator
 Filter: CIConstantColorGenerator
 Filter: CILenticularHaloGenerator
 Filter: CIMeshGenerator
 Filter: CIPDF417BarcodeGenerator
 Filter: CIQRCodeGenerator
 Filter: CIRandomGenerator
 Filter: CIRoundedRectangleGenerator
 Filter: CIStarShineGenerator
 Filter: CIStripesGenerator
 Filter: CISunbeamsGenerator
 Filter: CITextImageGenerator

很混杂的袋子。毛孔光环发电机怎么样?
在FFMPEG中的许多过滤器和/或发电机中,您可以给出过滤器名称,并且将使用默认参数。 coreimage发电机仅部分正确;有默认参数可用,但是所有这些生成器都有一个或多个没有默认的参数,因此,如果您尝试类似的内容,则会出现错误

ffmpeg -f lavfi -i \
"coreimagesrc=s=600x600:\
filter=CILenticularHaloGenerator" \
-t 30 -pix_fmt yuv420p output.mov

将给出:

[coreimagesrc @ 0x7fa3fc205a00] Parsing of filters failed.
[lavfi @ 0x7fa3fb7045c0] Error initializing filter 'coreimagesrc' with args 's=600x600:filter=CIMeshGenerator'
coreimagesrc=s=600x600:filter=CIMeshGenerator: Input/output error

让我们从列表中从列表中从列表中检查透明晕发电机的参数(我们上次将其滤除):

ffmpeg -f lavfi -i coreimagesrc=list_generators=true 2>&1 | grep -A 8 'Filter: CILenticularHaloGenerator' | cut -c 32-

给予

Option: inputCenter     [CIVector]
Option: inputColor      [CIColor]
Option: inputHaloRadius [NSNumber]      [0 1000][70]
Option: inputHaloWidth  [NSNumber]      [0 300][87]
Option: inputHaloOverlap        [NSNumber]      [0 1][0.77]
Option: inputStriationStrength  [NSNumber]      [0 3][0.5]
Option: inputStriationContrast  [NSNumber]      [0 5][1]
Option: inputTime       [NSNumber]      [0 1][0]

这非常有用,因为我们可以看到哪些参数具有默认值(第二对方括号为默认值)。因此,至少我们需要一个CIVectorCIColor。下一个问题,我们如何将CIVectorCIColor表示为ffmpeg命令中发电机的参数?我花了一段时间才能解决这个问题,但是在非常稀疏的FFMPEG文档,FFMPEG源代码和快速查看Coreimage Apple文档的帮助下,我提出了答案,如下:

ffmpeg -f lavfi -i \
"coreimagesrc=s=600x600:\
filter=CILenticularHaloGenerator\
@inputCenter=200.0 200.0\
@inputColor=0.0 0.0 1.0" \
-t 5 -pix_fmt yuv420p output.mov

为我们提供了五秒钟的相当焦点虹膜效应:

Iris

好吧,恒星发光器怎么样:

ffmpeg -f lavfi -i \
"coreimagesrc=s=600x600:\
filter=CIStarShineGenerator\
@inputCenter=300.0 300.0\
@inputRadius=25\
@inputCrossAngle=0\
@inputColor=1.0 0.0 1.0" \
-t 5 -pix_fmt yuv420p starshine.mov

产生:

Starshine

gradients发电机

还有更多此类coreimagesrc发电机可以探索,也许我们会稍微回到他们身边。由于还有更多类型的FFMPEG生成器,因此让我们继续前进。接下来,让我们检查gradients发电机。这是一个强大的过滤器,可以生成几种不同类型的梯度,并具有随机或指定的颜色,甚至可以在愿意的情况下旋转(当然只是便利性,因为在任何过滤器图中添加旋转过滤器是非常琐碎的)。一个例子:

ffmpeg -f lavfi -i \
gradients=duration=5\
:nb_colors=5:x0=320:y0=240\
:type=spiral:speed=0.1 \
-pix_fmt yuv420p gradients.mov

在这里,实际上,很明显,旋转很大程度上是梯度效应的一部分 - 最低速度不是0,而是1E -05。因此,实际上,即使在最低设置,它总是旋转,只是非常缓慢。当然,您可以始终使用相反方向的旋转过滤器使其保持静止...因为您需要知道speed参数所代表的确切。这是一个线索:在FFMPEG源代码中,视频源具有vsrc_的明智前缀,因此我们可以在vsrc_gradients.c中查看,并查看速度参数用于计算一个角度,如下所示:float angle = fmodf(s->pts * s->speed, 2.f * M_PI);因此,基本上是radians In radians IN radians IN radians In并且是pts的函数。我们将在这段时间内将该主题留在那里,就像如何通过快速查看源代码来弥补文档中偶尔的短缺。无论如何,这就是我们从最后一个命令中得到的:

gradients1

当然令人愉悦的眼睛,但是来回的部分旋转有点奇怪。我还没有找到gradients参数的组合来使螺旋梯度在其中心周围旋转(我欢迎建议),但是我确实注意到将参数设置为x1y1x0y0相同,以停止所有方便的方式回转。之后,我们可以简单地将rotate过滤器应用于看起来...

的看起来不错的情况下,

gradients2

当我浏览过滤器或发电机的所有选项以使其感觉到它们时,我经常想一起查看所有选项的某种概述。在这种情况下,我通常会访问JavaScript并以这种方式构建FFMPEG命令,该命令会导致网格显示所有不同选项,或者在串联视频中进行一个接一个选项。例如,这是gradients发电机中所有四种梯度类型的网格。在这种情况下,使用了一些默认选项,因此颜色是随机选择的,每次生成视频时都会有所不同:

gradients3

查看输出FFMPEG命令,以了解为什么您可能想将类似的内容卸载到自定义命令中! :

ffmpeg -y -filter_complex \
"gradients=duration=10:type=spiral[out0];\
gradients=duration=10:type=radial[out1];\
gradients=duration=10:type=linear[out2];\
gradients=duration=10:type=circular[out3];\
[out0]drawtext=fontfile='fonts/crystal-radio-kit/crystal radio kit.ttf'\
:text=spiral:fontsize=35:x=(w-text_w)/2:y=(h-text_h)/10:fontcolor=black[text_out0];\
[out1]drawtext=fontfile='fonts/crystal-radio-kit/crystal radio kit.ttf'\
:text=radial:fontsize=35:x=(w-text_w)/2:y=(h-text_h)/10:fontcolor=black[text_out1];\
[out2]drawtext=fontfile='fonts/crystal-radio-kit/crystal radio kit.ttf'\
:text=linear:fontsize=35:x=(w-text_w)/2:y=(h-text_h)/10:fontcolor=black[text_out2];\
[out3]drawtext=fontfile='fonts/crystal-radio-kit/crystal radio kit.ttf'\
:text=circular:fontsize=35:x=(w-text_w)/2:y=(h-text_h)/10:fontcolor=black[text_out3];\
[text_out0][text_out1][text_out2][text_out3]xstack=inputs=4:grid=2x2" \
-t 10 allGradients.mp4

显然有全部重复的负载,因此这应该很容易构建和参数化。从本质上讲,这一切都将是字符串构建,因此我们不需要在此脚本中使用任何特定的库。不过,我们将需要一种致电FFMPEG的方法 - 当然,FFMPEG也需要在场。要调用CLI命令,我们可以使用包commander

我们可以从上述命令中看到我们需要的:

  1. 起跑线:ffmpeg -y -filter_complex
  2. 每种梯度类型的线,最后具有独特命名的输出:gradients=duration=10:type=spiral[out0];\
  3. 每个文本标签的行,带有来自2的匹配输入。
[out0]drawtext=fontfile='fonts/crystal-radio-kit/crystal radio kit.ttf'\
:text=spiral:fontsize=35:x=(w-text_w)/2:y=(h-text_h)/10:fontcolor=black[text_out0];\
  1. 适当大小的xstack收集所有独特命名的drawtext输出作为输入。

除此之外,还将有一些语法问题要处理,以添加行末端,报价标记,然后构建最终命令。构建后,我们可以致电指挥官的exec()调用FFMPEG。我倾向于在最终命令中构建,以便在控制台中易于调试。

不进一步的ADO,这是为此的代码:

// gradientsDemo.js

const { exec } = require("child_process");

// Set up variables used to construct lines
const allGradientsTypes = [
    'spiral',
    'radial',
    'linear',
    'circular',
];

const TEXT_FONT = '\'fonts/crystal-radio-kit/crystal radio kit.ttf\'';
const TEXT_POS = 'x=(w-text_w)/2:y=(h-text_h)/10';
const TEXT_COLOR = 'black';
const FONT_SIZE = 35;
const TIME = 10;
// Setup lines
let filterLinesStart= "ffmpeg -y -filter_complex \\";
let filterLinesGenerator = ""; // generator lines
let filterLinesText = ""; // text lines
let filterLinesXStackInputs = "";
let filterLinesStacker = "";

let filterLinesLast = ` -t ${TIME} -an -pix_fmt yuv420p gradients.mp4`;

// Loop through lines
allGradientsTypes.forEach((gradient, index, gradients) => {
    filterLinesGenerator += `gradients=duration=${TIME}:type=${gradient}[out${index}];`;
    filterLinesText += `[out${index}]drawtext=fontfile=${TEXT_FONT}\
:text=${gradient}:fontsize=${FONT_SIZE}:${TEXT_POS}:fontcolor=${TEXT_COLOR}[text_out${index}];`;
    filterLinesXStackInputs += `[text_out${index}]`;

    if (Object.is(gradients.length - 1, index)) {
        // Finish up lines
        filterLinesGenerator += `\\`;
        filterLinesText += '\\';
        filterLinesStacker = `xstack=inputs=${allGradientsTypes.length}:grid=${2}x${2}`;
        filterLinesXStackInputs += ``;
    } else {
        filterLinesGenerator += `\\\n`;
        filterLinesText += `\\\n`;
    }
});

// Build command...
const command = `${filterLinesStart}
"${filterLinesGenerator}
${filterLinesText}
${filterLinesXStackInputs}${filterLinesStacker}"\\
${filterLinesLast}`;

console.info("Command is: ", command);

exec(command, (error, stdout, stderr) => {
    if (error) {
        console.log(`error: ${error.message}`);
        return;
    }

    console.log(`stdout: ${stdout}`);

    if (stderr) {
        console.log(`stderr: ${stderr}`);
    }
    console.info(`Generated a file with ${allGradientsTypes.length} gradient demos in a ${2} by ${2} grid`);
    return;
}); 

,当我们每次获得完全不同的颜色时,让我们测试该代码并产生视频的另一个实例:

Gradients4

在此阶段,我们已经能够更改某些文本属性(您可能已经注意到,您将希望有一条通往机器上相关字体的路径)。现在,添加其他参数非常容易。例如,如果打算将这些生成的视频用于分析目的,则可能需要了解颜色如何为每个梯度看起来像,所以为什么不使颜色可安装。默认情况下,gradients仅设置2种随机颜色,但是如果我们以数组的形式传递颜色列表,我们也可以设置颜色计数参数nb_colors

// gradientsDemo2.js

const { exec } = require("child_process");

// Set up variables used to construct lines
const allGradientsTypes = [
    'spiral',
    'radial',
    'linear',
    'circular',
];

// NEW - define the colours
const colours = [
    'blue',
    'red',
    'yellow'
];

const TEXT_FONT = '\'fonts/crystal-radio-kit/crystal radio kit.ttf\'';
const TEXT_POS = 'x=(w-text_w)/2:y=(h-text_h)/10';
const TEXT_COLOR = 'black';
const FONT_SIZE = 35;
const TIME = 10;
// Setup lines
let filterLinesStart= "ffmpeg -y -filter_complex \\";
let filterLinesGenerator = ""; // generator lines
let filterLinesText = ""; // text lines
let filterLinesXStackInputs = "";
let filterLinesStacker = "";

let filterLinesLast = ` -t ${TIME} -an -pix_fmt yuv420p gradients2.mp4`;

// NEW - Build colours list
let colourParams = "";
colours.forEach((colour, index, colours) => {
    colourParams += `:c${index}=${colour}`;
});

// Loop through lines
allGradientsTypes.forEach((gradient, index, gradients) => {
    filterLinesGenerator += `gradients=duration=${TIME}:type=${gradient}${colourParams}[out${index}];`; // NEW - add colours to command
    filterLinesText += `[out${index}]drawtext=fontfile=${TEXT_FONT}\
:text=${gradient}:fontsize=${FONT_SIZE}:${TEXT_POS}:fontcolor=${TEXT_COLOR}[text_out${index}];`;
    filterLinesXStackInputs += `[text_out${index}]`;

    if (Object.is(gradients.length - 1, index)) {
        // Finish up lines
        filterLinesGenerator += `\\`;
        filterLinesText += '\\';
        filterLinesStacker = `xstack=inputs=${allGradientsTypes.length}:grid=${2}x${2}`;
        filterLinesXStackInputs += ``;
    } else {
        filterLinesGenerator += `\\\n`;
        filterLinesText += `\\\n`;
    }
});

// Build command...
const command = `${filterLinesStart}
"${filterLinesGenerator}
${filterLinesText}
${filterLinesXStackInputs}${filterLinesStacker}"\\
${filterLinesLast}`;

console.info("Command is: ", command);

exec(command, (error, stdout, stderr) => {
    if (error) {
        console.log(`error: ${error.message}`);
        return;
    }

    console.log(`stdout: ${stdout}`);

    if (stderr) {
        console.log(`stderr: ${stderr}`);
    }
    // NEW - Show the colours
    console.info(`Generated a file with ${allGradientsTypes.length} gradient demos in a ${2} by ${2} grid with colours: ${colours}`);
    return;
}); 

Gradients5

对我来说,这使得了解每个渐变如何使用它给出的颜色变得容易得多……好吧,我敢肯定,像这样的代码很快就会再次有用。也许我们应该继续进入下一个发电机-mandelbrot

mandelbrot发电机

这个过滤器将带您带回80年代或90年代,具有经典的(且有些俗气)的迷幻效果。这是一个快速下降进入混乱分形的例子:

ffmpeg -f lavfi -i \
mandelbrot=end_pts=100 \
-pix_fmt yuv420p -t 30 mandelbrot1.mov

(这是end_pts值加快旅程的速度)。

mandelbrot1

让我们做类似于JavaScript中最后一次的事情,以探索参数inner的不同阴影效应预设:

const { exec } = require("child_process");

// Set up variables used to construct lines
const allInnerPresets = [
    'black',
    'convergence',
    'mincol',
    'period',
];

const TEXT_FONT = '\'fonts/crystal-radio-kit/crystal radio kit.ttf\'';
const TEXT_POS = 'x=(w-text_w)/2:y=(h-text_h)/10';
const TEXT_COLOR = 'black';
const FONT_SIZE = 35;
const TIME = 30;
// Setup lines
let filterLinesStart= "ffmpeg -y -filter_complex \\";
let filterLinesGenerator = ""; // generator lines
let filterLinesText = ""; // text lines
let filterLinesXStackInputs = "";
let filterLinesStacker = "";

let filterLinesLast = ` -t ${TIME} -an -pix_fmt yuv420p mandelbrotDemo.mp4`;

// Loop through lines
allInnerPresets.forEach((innerPreset, index, allInnerPresets) => {
    filterLinesGenerator += `mandelbrot=inner=${innerPreset}:end_pts=100[out${index}];`;
    filterLinesText += `[out${index}]drawtext=fontfile=${TEXT_FONT}\
:text=${innerPreset}:fontsize=${FONT_SIZE}:${TEXT_POS}:fontcolor=${TEXT_COLOR}[text_out${index}];`;
filterLinesXStackInputs += `[text_out${index}]`;

    if (Object.is(allInnerPresets.length - 1, index)) {
        // Finish up lines
        filterLinesGenerator += `\\`;
        filterLinesText += '\\';
        filterLinesStacker = `xstack=inputs=${allInnerPresets.length}:grid=${2}x${2}`;
        filterLinesXStackInputs += ``;
    } else {
        filterLinesGenerator += `\\\n`;
        filterLinesText += `\\\n`;
    }
});

// Build command...
const command = `${filterLinesStart}
"${filterLinesGenerator}
${filterLinesText}
${filterLinesXStackInputs}${filterLinesStacker}"\\
${filterLinesLast}`;

console.info("Command is: ", command);

exec(command, (error, stdout, stderr) => {
    if (error) {
        console.log(`error: ${error.message}`);
        return;
    }

    console.log(`stdout: ${stdout}`);

    if (stderr) {
        console.log(`stderr: ${stderr}`);
    }

    console.info(`Generated a file with ${allInnerPresets.length} mandelbrot demos in a ${2} by ${2} grid`);
    return;
}); 

这会产生更多的trip脚:

mandelbrot2

作为下一步,我们可以使用类似的过程来处理outer参数,例如使用嵌套环。这也是我们稍后可能会返回的。不过,继续前进,我们可以使用非常相似的代码对下一个过滤器,即mptestsrc

mptestsrc滤镜

mptestsrc是生成有用的测试模式的另一种选择,显然是基于mplayer测试过滤器。它仅生成大小256x256的模式,并且可以选择循环浏览所有方便的方式(否则我们可以使用concat过滤器可以做的事情):

ffmpeg -f lavfi -i \
mptestsrc=d=40\
:max_frames=100 \
-pix_fmt yuv420p mptestsrc.mov

为我们提供了每个测试来源4秒:

mptestsrc1

很明显,对于某些测试而言,大小实际上是不同的,这很有用。我们可以尝试在上述JavaScript上应用类似的方法,但是使用此过滤器列表:

const allPresets = [
    'dc_luma',
    'dc_chroma',
    'freq_luma',
    'freq_chroma',
    'amp_luma',
    'amp_chroma',
    'cbp',
    'mv',
    'ring1',
    'ring2'
];

产生:

mptestsrc2

神秘的部分'15 .10'...

理论上我们可以与另一个发电机一起做类似的事情 - 一种基本上是测试源的另一个生成器。它似乎没有组名,但是在撰写本文时,它是根据第15.10节的。这是列表:

const allPresets = [
    'allrgb', 
    'allyuv',
    'color',
    'colorchart',
    'colorspectrum',
    'haldclutsrc',
    'nullsrc',
    'pal75bars',
    'pal100bars',
    'rgbtestsrc',
    'smptebars',
    'smptehdbars',
    'testsrc',
    'testsrc2',
    'yuvtestsrc'
];

但是,在这种特殊情况下,几个过滤器的尺寸不同,最好以全分辨率查看,因此我们不能一次真正呈现它们(好吧,我敢肯定我们可以,也许我们会,尽管不是现在)。这个示例可能最适合串联测试源输出(与mptestsrc一样)。这也是以后文章的练习。这是几个可用的测试来源:

ffmpeg -f lavfi -i \
allrgb \
-update 1 -frames:v 1 testSrc1.png

allrgb

(这是静态的

ffmpeg -f lavfi -i \
testsrc \
-pix_fmt yuv420p -t 6 testSrc2.mov

testsrc

其他各种发电机...

因此,我们还有更多的生成器可以探索...在这里很好地解决了一些问题 - 生命游戏的实现是源视频,您可以在其中更改各种参数:

ffmpeg -f lavfi -i \
life=ratio=0.1:death_color=#FF0000:\
life_color=#00FF00:mold_color=yellow:mold=1:s=400x400 \
-t 30 life3.mp4

life

,还有其他一些简单的分形发电机-sierpinskicellauto测试来源如下:

Sierpinski generator

cellauto

上面的所有三个都有许多参数要测试,因此非常适合在xstack演示中使用不同的参数。也将来要探索的东西。

具有插件功能的发电机

除此之外,我们仍然尚未探索两个较大的发电机。这些是openclsrc生成器,它是使用OpenCL代码生成视频源的一种方式,并且frei0r_src生成器可以使用FREI0R脚本作为视频生成器。如您所料,那里有很多需要检查的地方。这就是我们在以后的文章中所做的。

最后,本文中的所有视频均通过以下代码转换为GIF。

ffmpeg -t 6 -i testSrc2.mov \
-vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
-loop 0 testSrc2.gif

Alan Allard是Eyevinn Technology的开发商,Eyevinn Technology是一家专门从事视频技术和媒体发行的欧洲领先的独立咨询公司。

如果您在开发和实施方面需要帮助,我们的team of video developers很乐意提供帮助。如果您有任何疑问或评论,请在此帖子的“评论”部分中给我们一行。