<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alan Allard</title>
    <description>The latest articles on DEV Community by Alan Allard (@sinewave440hz).</description>
    <link>https://dev.to/sinewave440hz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F567199%2F489004d7-a7bf-45f8-9e07-299a11fb6a43.jpeg</url>
      <title>DEV Community: Alan Allard</title>
      <link>https://dev.to/sinewave440hz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sinewave440hz"/>
    <language>en</language>
    <item>
      <title>Exploring video generators in FFMPEG...</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 24 Feb 2023 11:05:04 +0000</pubDate>
      <link>https://dev.to/video/exploring-video-generators-in-ffmpeg-4ehc</link>
      <guid>https://dev.to/video/exploring-video-generators-in-ffmpeg-4ehc</guid>
      <description>&lt;p&gt;&lt;em&gt;...in which the author creates entirely inappropriate amounts of eye candy by testing the ranges of most of the video generation options within ffmpeg - but also manages, as luck would have it, to identify some valuable testing tools in the process...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you haven't started following Eyevinn's &lt;em&gt;ffmpeg of the day&lt;/em&gt; series &lt;a href="https://www.instagram.com/eyevinntechnology/" rel="noopener noreferrer"&gt;on Instagram&lt;/a&gt; (of which I am the author), perhaps you should! Anyway, the very first command that I looked at in the series was an example of the &lt;code&gt;cellauto&lt;/code&gt; filter in ffmpeg. While trawling through the long, long list of ffmpeg filters available for audio and video, I noticed that video generators have their own quite sizeable section in the list. There are  so many of these generators available, but many of them may turn out to be useful for various purposes. Not only are there many separate generators in the list, several of them are extensible with code or plugins in a specific format. In this article, I will try and get a notion of what all of these generators can do, by examining their various parameter ranges.&lt;/p&gt;

&lt;p&gt;A caveat, first. I am on macOS, so I will be focusing on the filters that are readily available for me - not least of all the &lt;code&gt;coreimagesrc&lt;/code&gt; generator with it's considerable list of built-in effects.&lt;/p&gt;

&lt;p&gt;In fact, &lt;code&gt;coreimagesrc&lt;/code&gt; seems like a pretty good place to start...&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;coreimagesrc&lt;/code&gt; generator
&lt;/h2&gt;

&lt;p&gt;Several of these generators we will look at are also available as more general filters, with some slight variation in options. Generators are more specific in that they are designed to be first in the filter chain, as inputs. This &lt;code&gt;coreimagesrc&lt;/code&gt; has a more general counterpart in the filter &lt;code&gt;coreimage&lt;/code&gt;, for example.&lt;/p&gt;

&lt;p&gt;The first thing we want to do is identify which particular generators are available on our Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i coreimagesrc=list_generators=true null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will give you a long list of generators with all their parameters. Let's grep it (notice the little pirouette of piping everything from stderr to stdout, which is necessary in order to pipe the ffmpeg output to &lt;code&gt;grep&lt;/code&gt; successfully). We may as well &lt;code&gt;cut&lt;/code&gt; the first part of the line too for a nice tidy list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i coreimagesrc=list_generators=true 2&amp;gt;&amp;amp;1 | grep Filter: | cut -c 32-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which gives us the following list (in my case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quite a mixed bag. How about a lenticular halo generator?&lt;br&gt;
With many filters and/or generators in ffmpeg, you can just give the filter name and the default parameters will be used. That's only partially true with the &lt;code&gt;coreimage&lt;/code&gt; generators; there are default parameters available, but all of these generators have one or more parameters that have no default, so you will get an error if you try something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
"coreimagesrc=s=600x600:\
filter=CILenticularHaloGenerator" \
-t 30 -pix_fmt yuv420p output.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will give:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's examine the parameters of the lenticular halo generator in the list from earlier (we filtered them out last time):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i coreimagesrc=list_generators=true 2&amp;gt;&amp;amp;1 | grep -A 8 'Filter: CILenticularHaloGenerator' | cut -c 32-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;giving&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is very useful in that we can see which parameters have defaults (the second pair of square brackets are the defaults). So at the very least we need a &lt;code&gt;CIVector&lt;/code&gt; and a &lt;code&gt;CIColor&lt;/code&gt;. Next problem, how do we represent a &lt;code&gt;CIVector&lt;/code&gt; and &lt;code&gt;CIColor&lt;/code&gt; as parameters to the generator within an ffmpeg command? It took me a while to work this out, but with the help of the very sparse ffmpeg documentation, the ffmpeg source code and a quick look at the CoreImage apple documentation, I came up with the answer, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which gives us five seconds of a rather nice sort of focusing iris effect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu96y5ojwfdcmneuvtico.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu96y5ojwfdcmneuvtico.gif" alt="Iris" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, how about a star shine generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which yields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12gy2ta35vkwb72hilnp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12gy2ta35vkwb72hilnp.gif" alt="Starshine" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;gradients&lt;/code&gt; generator
&lt;/h2&gt;

&lt;p&gt;There are several more of these &lt;code&gt;coreimagesrc&lt;/code&gt; generators to explore, maybe we'll come back to them in a bit. As there are so many more types of ffmpeg generator, let's move on for the moment. Let's examine the &lt;code&gt;gradients&lt;/code&gt; generator next. This is a powerful filter that can generate several different types of gradient, with random or designated colours and even rotate them if you wish (merely a convenience of course, as it's pretty trivial to add a rotation filter to any filter graph). An example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
gradients=duration=5\
:nb_colors=5:x0=320:y0=240\
:type=spiral:speed=0.1 \
-pix_fmt yuv420p gradients.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, in fact, it becomes apparent that rotation is very much meant to be part of the gradient effect - the lowest speed is not 0 but 1e-05. So in fact it always rotates even at the lowest setting, just very slowly. You could always use the rotate filter in the opposite direction to keep it stationary of course...for that you would need to know what exactly that &lt;code&gt;speed&lt;/code&gt; parameter represents. Here's a clue: in the ffmpeg source code, video sources are sensibly prefixed with &lt;code&gt;vsrc_&lt;/code&gt;, so here we can take a look in &lt;code&gt;vsrc_gradients.c&lt;/code&gt; and see that the speed parameter is used to calculate an angle as follows: &lt;code&gt;float angle = fmodf(s-&amp;gt;pts * s-&amp;gt;speed, 2.f * M_PI);&lt;/code&gt; So basically the angle is in radians and is a function of &lt;code&gt;pts&lt;/code&gt;. We'll leave that topic there this time round, just as a little note on how to compensate for the occasional shortfalls in the documentation by taking a quick look at the source code. Anyway, this is what we got from the last command: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7xt9z665guhrurvjjnt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7xt9z665guhrurvjjnt.gif" alt="gradients1" width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Certainly pleasing to the eye, but the back and forth partial rotation is a bit odd. I haven't yet found a combination of &lt;code&gt;gradients&lt;/code&gt; parameters to make a spiral gradient rotate around its centre (and I welcome suggestions), but I did notice that setting parameters &lt;code&gt;x1&lt;/code&gt; and &lt;code&gt;y1&lt;/code&gt; to the same as &lt;code&gt;x0&lt;/code&gt; and &lt;code&gt;y0&lt;/code&gt;is a handy way to stop all rotation. After that we can simply apply the &lt;code&gt;rotate&lt;/code&gt; filter instead, which looks...pretty good, I suppose:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vser4x531bnngqxx9kc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vser4x531bnngqxx9kc.gif" alt="gradients2" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I'm going through all the options of a filter or generator to get a feel for them, I often want to see some kind of overview of all the options together. In those cases, I typically reach for javascript and build an ffmpeg command in that way, one that results in either a grid showing all the different options or one option after another in a concatenated video. For example, here is a grid of all four gradient types in the &lt;code&gt;gradients&lt;/code&gt; generator. In this case, some of the default options are used, so the colours are randomly chosen and will vary each time the video is generated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kyjczuzpkrixt1pu857.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kyjczuzpkrixt1pu857.gif" alt="gradients3" width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a look at the output ffmpeg command to understand why you might like to offload things like this to a custom command! :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is clearly a whole load of repetition, so this should be fairly easy to build and parameterise. Essentially this will all just be string building so we won't need to use any particular libraries for most of this script. We will need a way to call ffmpeg though - and ffmpeg will need to be present too, of course. To call a CLI command we can use the package &lt;a href="https://www.npmjs.com/package/commander" rel="noopener noreferrer"&gt;commander&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can see from a glance at the above command that we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The start line:  &lt;code&gt;ffmpeg -y -filter_complex&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A line for each gradient type with a uniquely-named output at the end: &lt;code&gt;gradients=duration=10:type=spiral[out0];\&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A line for each text label with the matching inputs from 2. and uniquely-named outputs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[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];\
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;An &lt;code&gt;xstack&lt;/code&gt; of the appropriate size that collects all of the uniquely-named &lt;code&gt;drawtext&lt;/code&gt;-outputs as inputs to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apart from that, there will be a few syntax issues to deal with as regards adding line ends, quotation marks and then building the final command. When it's built we can call  &lt;code&gt;exec()&lt;/code&gt; from commander to invoke ffmpeg. I tend to build in line breaks to the final commands so that they are easy to debug in the console. &lt;/p&gt;

&lt;p&gt;Without further ado here is the code for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gradientsDemo.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set up variables used to construct lines&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spiral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_FONT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;fonts/crystal-radio-kit/crystal radio kit.ttf&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_POS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x=(w-text_w)/2:y=(h-text_h)/10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FONT_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Setup lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ffmpeg -y -filter_complex &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// generator lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// text lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;` -t &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TIME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -an -pix_fmt yuv420p gradients.mp4`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Loop through lines&lt;/span&gt;
&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gradients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`gradients=duration=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TIME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:type=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;];`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`[out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]drawtext=fontfile=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_FONT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\
:text=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:fontsize=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FONT_SIZE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_POS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:fontcolor=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_COLOR&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[text_out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;];`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`[text_out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gradients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Finish up lines&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`xstack=inputs=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:grid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Build command...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesStart&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesLast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Command is: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stdout: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stderr: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Generated a file with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gradient demos in a &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; by &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; grid`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as we get completely different colours each time, let's test that code and produce another instance of the video:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ibjkzwbuy7rrzsswcui.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ibjkzwbuy7rrzsswcui.gif" alt="Gradients4" width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Already at this stage we are able to alter some of the text properties (as you have probably noticed you will want to have a path to the font in question on your machine). It's now fairly easy to add other parameters. For example, if the intention is to use these generated videos for analytic purposes, you may want to understand how the colours look for each of the gradients, so why not make the colours settable. By default, &lt;code&gt;gradients&lt;/code&gt; sets only 2 random colours, but if we pass in the colour list as an array we can set the colour count parameter &lt;code&gt;nb_colors&lt;/code&gt; too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gradientsDemo2.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Set up variables used to construct lines&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spiral&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;radial&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linear&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// NEW - define the colours&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yellow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_FONT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;fonts/crystal-radio-kit/crystal radio kit.ttf&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_POS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x=(w-text_w)/2:y=(h-text_h)/10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TEXT_COLOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FONT_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TIME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Setup lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ffmpeg -y -filter_complex &lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// generator lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// text lines&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filterLinesLast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;` -t &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TIME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -an -pix_fmt yuv420p gradients2.mp4`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// NEW - Build colours list&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;colourParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;colours&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;colour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colours&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;colourParams&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`:c&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;colour&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Loop through lines&lt;/span&gt;
&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gradients&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`gradients=duration=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TIME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:type=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;colourParams&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;];`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// NEW - add colours to command&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`[out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]drawtext=fontfile=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_FONT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\
:text=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:fontsize=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FONT_SIZE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_POS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:fontcolor=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TEXT_COLOR&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;[text_out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;];`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`[text_out&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gradients&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Finish up lines&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`xstack=inputs=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:grid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;filterLinesText&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Build command...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesStart&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesGenerator&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesXStackInputs&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesStacker&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filterLinesLast&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Command is: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stdout: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`stderr: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// NEW - Show the colours&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Generated a file with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allGradientsTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gradient demos in a &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; by &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; grid with colours: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;colours&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls8r4amuukzmpwhzk7kg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls8r4amuukzmpwhzk7kg.gif" alt="Gradients5" width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This, to me, makes it a lot easier to understand how each gradient uses the colours it is given...well, I'm sure code like this will be useful again soon. Perhaps we should move on to the next generator - &lt;code&gt;mandelbrot&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;mandelbrot&lt;/code&gt; generator
&lt;/h2&gt;

&lt;p&gt;This filter will take you right back to the 80s or 90s with it's classic (and slightly cheesy) psychedelic effects. Here's one example of a rapid descent into the world of chaos fractals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
mandelbrot=end_pts=100 \
-pix_fmt yuv420p -t 30 mandelbrot1.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Here it is the &lt;code&gt;end_pts&lt;/code&gt; value that speeds up the journey).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf3zpjm70rw2gpukrxig.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf3zpjm70rw2gpukrxig.gif" alt="mandelbrot1" width="200" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's do something similar to last time in javascript to explore the different shading effect presets for the parameter &lt;code&gt;inner&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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) =&amp;gt; {
    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) =&amp;gt; {
    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;
}); 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This yields even more trippiness:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For4siqnav735beb09n3x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For4siqnav735beb09n3x.gif" alt="mandelbrot2" width="200" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a next step, we could use a similar process to handle the &lt;code&gt;outer&lt;/code&gt; parameter for example, using a nested loop. This is also something we might return to later. Moving on though, we can use very similar code for the next filter which is &lt;code&gt;mptestsrc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;mptestsrc&lt;/code&gt; filter
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mptestsrc&lt;/code&gt; is yet another option for generating useful test patterns and is apparently based on the Mplayer test filter. It only generates patterns of size 256x256 and has an option to cycle through all of them which is handy (and otherwise something we might do using the &lt;code&gt;concat&lt;/code&gt; filter):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
mptestsrc=d=40\
:max_frames=100 \
-pix_fmt yuv420p mptestsrc.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives us 4 seconds of each test source: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxyqzqrifdagyr2dzj56.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxyqzqrifdagyr2dzj56.gif" alt="mptestsrc1" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It becomes apparent that the size is in fact different for some of the tests, which could be useful to know. We could try applying a similar approach to the above javascript, but using this list of filters instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const allPresets = [
    'dc_luma',
    'dc_chroma',
    'freq_luma',
    'freq_chroma',
    'amp_luma',
    'amp_chroma',
    'cbp',
    'mv',
    'ring1',
    'ring2'
];

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which produces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbjhwi530i9a86kgnu6u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbjhwi530i9a86kgnu6u.gif" alt="mptestsrc2" width="600" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The mysterious section '15.10'...
&lt;/h2&gt;

&lt;p&gt;We could theoretically do a similar thing with another generator - a generator that is basically another list of test sources. It doesn't seem to have a group name, but at the time of writing it is under section 15.10. This is the list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const allPresets = [
    'allrgb', 
    'allyuv',
    'color',
    'colorchart',
    'colorspectrum',
    'haldclutsrc',
    'nullsrc',
    'pal75bars',
    'pal100bars',
    'rgbtestsrc',
    'smptebars',
    'smptehdbars',
    'testsrc',
    'testsrc2',
    'yuvtestsrc'
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, in this particular case several of the filters are differently sized and best viewed in full resolution, so we can't really present them all at once (well, I'm sure we could really and maybe we will, although not right now). This is an example that is probably best suited to the approach of concatenating the test source outputs (much like the &lt;code&gt;mptestsrc&lt;/code&gt;). That, too, is an exercise for a later article. Here are a couple of the available test sources though:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
allrgb \
-update 1 -frames:v 1 testSrc1.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfdijx3qd0o2yt6qy3qk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfdijx3qd0o2yt6qy3qk.png" alt="allrgb" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(this one is static so I present it as an image here).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i \
testsrc \
-pix_fmt yuv420p -t 6 testSrc2.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb1rlipdt20qiczwbud0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpb1rlipdt20qiczwbud0.gif" alt="testsrc" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Various other generators...
&lt;/h2&gt;

&lt;p&gt;So we have several more generators to explore... Just to round things off nicely here are a couple more - there is an implementation of The Game of Life as source video in which you can alter various parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj5kkqi0p9q36b30s31r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj5kkqi0p9q36b30s31r.gif" alt="life" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And there are also some other simpler Fractal generators - the &lt;code&gt;sierpinski&lt;/code&gt; and &lt;code&gt;cellauto&lt;/code&gt; test sources shown here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cj5yoff36amb7ezjld3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cj5yoff36amb7ezjld3.gif" alt="Sierpinski generator" width="200" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u0pxcsgv9gczd4cbge4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9u0pxcsgv9gczd4cbge4.gif" alt="cellauto" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All three of the above have many parameters to test out, so would be ideal to present with different parameters in an &lt;code&gt;xstack&lt;/code&gt; demo. Also something to explore in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generators with plug-in capability
&lt;/h2&gt;

&lt;p&gt;Beyond that, we still have yet to explore two larger generators. These are the &lt;code&gt;openclsrc&lt;/code&gt; generator which is a way of using OpenCL code to generate video sources, and the &lt;code&gt;frei0r_src&lt;/code&gt; generator which enables using frei0r scripts as video generators. As you might expect, there is a lot to examine there. And that is what we'll do in a later article.&lt;/p&gt;

&lt;p&gt;Finally, all of the videos in this article were converted to gifs with code such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team" rel="noopener noreferrer"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Exploring OCR and text-to-speech in FFMPEG...</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 20 Jan 2023 13:13:10 +0000</pubDate>
      <link>https://dev.to/video/exploring-ocr-and-text-to-speech-in-ffmpeg-2o61</link>
      <guid>https://dev.to/video/exploring-ocr-and-text-to-speech-in-ffmpeg-2o61</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;...in which the author pursues a hare-brained idea to create a system to recognise text in any given image, reproduce the text, place it next to the original image and have the text be spoken aloud - all within ffmpeg, in order to explore several techniques and features within said tool. How far did the author get with all this madness? Read on to find out...&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While spending an unhealthy amount of time browsing through the various filters available in ffmpeg, I discovered a couple that I really wanted to investigate. The &lt;em&gt;ocr&lt;/em&gt; filter (as in &lt;em&gt;Optical Character Recognition&lt;/em&gt;) is not documented in great detail - as is the case in several parts of the ffmpeg documentation. (On the other hand, some of the filters are extremely well-documented with several intriguing examples. But not this one!) Nonetheless, I wanted to try it out. Also, while writing my earlier blogs about audio in ffmpeg, I had come across the &lt;em&gt;flite&lt;/em&gt; filter. I recognised the name (from my days in the past working with the open-source audio software Pure Data) to be text-to-speech generation software. This was also interesting to me, so I started experimenting...&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the ocr filter
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#ocr" rel="noopener noreferrer"&gt;ocr&lt;/a&gt; filter in ffmpeg is powered by the &lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;Tesseract&lt;/a&gt; library. As you will often find in ffmpeg, the build within ffmpeg has only a subset of the functionality of the original library - at least, for the moment. There's always the possibility of APIs being expanded in later ffmpeg releases. And it is open source of course, so there's the option of instigating those changes yourself - or using the original library in conjunction with ffmpeg if that suits your needs better.&lt;/p&gt;

&lt;p&gt;To use the ocr filter, we need to be sure that ffmpeg was built with Tesseract enabled. You can check this by running ffmpeg without any options. If you see &lt;code&gt;--enable-libtesseract&lt;/code&gt; in the configuration line in the first few lines, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; % ffmpeg
ffmpeg version N-109469-g62da0b4a74 Copyright (c) 2000-2023 the FFmpeg developers
  built with Apple clang version 14.0.0 (clang-1400.0.29.202)
  configuration: --enable-libx264 --enable-gpl --enable-lv2 --enable-libfreetype --enable-libflite --enable-cross-compile --enable-libtesseract --enable-libfontconfig --enable-libfribidi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;then you don't need to do anything. Otherwise add &lt;code&gt;--enable-libtesseract&lt;/code&gt; to and run your &lt;code&gt;./configure&lt;/code&gt; command, then &lt;code&gt;make&lt;/code&gt; and &lt;code&gt;make install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To test the ocr filter, a video containing text would be useful. We can generate that fairly easily in ffmpeg:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i color=c=red:size=400x400:duration=5 -filter_complex \
drawtext=fontfile='fonts/lazenby-computer/LazenbyCompSmooth.ttf'\
:text='SYNTHESIZER':fontsize=40:x=25:y=20:fontcolor=white,\
drawtext=fontfile='fonts/lazenby-computer/LazenbyCompSmooth.ttf'\
:text='DRUM MACHINE':fontsize=40:x=25:y=150:fontcolor=white,\
drawtext=fontfile='fonts/lazenby-computer/LazenbyCompSmooth.ttf'\
:text='REVERB':fontsize=40:x=25:y=310:fontcolor=white\
 ocrTest1.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is probably fairly self-explanatory. It: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;generates a single colour video of dimensions 400x400 for 5 seconds&lt;/li&gt;
&lt;li&gt;draws three texts over this, using a specified font (one that I downloaded to a local folder). We do this with the &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#drawtext-1" rel="noopener noreferrer"&gt;drawtext&lt;/a&gt; filter.&lt;/li&gt;
&lt;/ol&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/shorts/6kh0Vh6mFGI?feature=share" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;This was done using only the most basic font support available in ffmpeg. This installation of ffmpeg was built with only &lt;code&gt;--enable-libfreetype&lt;/code&gt; configured. If we also configure &lt;code&gt;--enable-libfontconfig&lt;/code&gt; and &lt;code&gt;--enable-libfribidi&lt;/code&gt;, things get a little easier. Among other things, we can then use the default font option, meaning that we don't have to pass in a long, unwieldy path to the drawtext filter (unless we have specific font requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i color=c=red:size=400x400:duration=5 -filter_complex \
drawtext=text='SYNTHESIZER':fontsize=40:x=25:y=20:fontcolor=white,\
drawtext=text='DRUM MACHINE':fontsize=40:x=25:y=150:fontcolor=white,\
drawtext=text='REVERB':fontsize=40:x=25:y=310:fontcolor=white\
 ocrTest2.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So we have a video with text in, in a default font:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/shorts/Y6R-MUJ7T_Q?feature=share" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;Now it would be nice to feed that through the ocr filter and see what text it recognises. Before we do that, let's also stagger the display of the texts over time but in the same position, which will allow us to demonstrate the OCR output clearly by printing it below the source text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -f lavfi -i color=c=red:size=400x400:duration=9.5 -filter_complex \
"[0]drawtext=text='SYNTHESIZER':fontsize=40:x=(w-text_w)/2:y=20:\
fontcolor=white:enable='between(t,1,3)'[1];\
[1]drawtext=text='DRUM MACHINE':fontsize=40:x=(w-text_w)/2:y=20:\
fontcolor=white:enable='between(t,4,6)'[2];\
[2]drawtext=text='REVERB':fontsize=40:x=(w-text_w)/2:y=20:\
fontcolor=white:enable='between(t,7,9)'"\
 ocrTest3.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Notice that we centred the texts horizontally (using the text width &lt;code&gt;text_w&lt;/code&gt;, which is available within the drawtext filter). Also, if you take a moment to run &lt;code&gt;ffmpeg -filters&lt;/code&gt;, you will see a list of all the available filters with three characters preceding each filter. Here is the one for drawtext:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T.C drawtext          V-&amp;gt;V       Draw text on top of video frames using libfreetype library.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The T is an indication that drawtext has timeline support. This means that we can enable the filter based on the time we are at in the video. There are several expressions available for evaluating this and we are using a common one, the &lt;em&gt;between&lt;/em&gt; expression. For example, the first drawtext instance is only enabled between 1 and 3 seconds in the video. This is how it looks for all three:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/shorts/hPK7k6onxc8?feature=share" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;So now we have something that our ocr filter can respond to. This is how we arrange that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i ocrTest3.mov -filter_complex \
"ocr,drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'" ocrOutput1.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;resulting in this video:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/shorts/R5atp8XveYw?feature=share" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;Here we are using drawtext to display the data that the ocr filter outputs to the metadata that accompanies each frame of the video. This is why the OCR output changes; it's being updated per frame. (We will examine this metadata shortly).&lt;/p&gt;

&lt;p&gt;To explore things further, let's use a text pulled from the internet as a source. We will then position it next to the ocr output in an output image. Unfortunately for this particular (surely atypical) use-case, the ocr stream leaves the source input unchanged, which means our recognised text would be output on top of the existing image. So let's cover the stream with a white colour overlay before outputting the ocr&lt;br&gt;
results using drawtext on top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i test.png -f lavfi -i "color=white" -filter_complex \
"split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=iw:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'[drawtextOut];\
[origOut][drawtextOut]hstack" -t 1 -update 1 ocrOutput2.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful things to note here are that we resize the white color image to the original .png using the &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#scale2ref" rel="noopener noreferrer"&gt;scale2ref&lt;/a&gt; filter with the (source video) input height and input width constants &lt;code&gt;ih&lt;/code&gt; and &lt;code&gt;iw&lt;/code&gt;.  Then we overlay the OCR output text onto that white background. Finally, we line up the two video streams horizontally using the &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#hstack" rel="noopener noreferrer"&gt;hstack&lt;/a&gt; filter. If we didn't add the &lt;code&gt;-t 1&lt;/code&gt;, this stream would continue until we shut it down manually. And the &lt;code&gt;-update 1&lt;/code&gt; is necessary for outputting a single image instead of a video, video being what ffmpeg is primarily designed to work with, of course. Here is &lt;code&gt;ocrOutput2.png&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vinduk4kdzbin101j7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1vinduk4kdzbin101j7v.png" alt="Image ocr test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was curious as to how the ocr filter handled angled text - and if it didn't really, then how much of an angle can cause distortion of the recognised text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i test.png -f lavfi -i "color=red" -filter_complex \
"rotate='PI/24:ow=hypot(iw,ih):oh=ow'[rotOut];\
[rotOut]split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=oh*mdar:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'[drawtextOut];\
[origOut][drawtextOut]hstack" -t 1 -update 1 ocrOutput2.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's new here is the &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#rotate" rel="noopener noreferrer"&gt;rotate&lt;/a&gt; filter, which rotates the test image by 7.5 degrees (expressed in radians). The parameters after the angle expression - &lt;code&gt;ow=hypot(iw,ih):oh=ow&lt;/code&gt; - control the output height and width and ensure that the image is contained within its frame when we rotate it (this will be particularly useful when we animate the rotation shortly). I coloured the text underlay red for clarity, giving us this for ocrOutput2.png&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5f2hhvprjdu77oj3ywlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5f2hhvprjdu77oj3ywlh.png" alt="Image ocr test 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And rotating by &lt;code&gt;PI/12&lt;/code&gt; (15 degrees) gives us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwxf713txmozjfusktya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxwxf713txmozjfusktya.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here you can see, perhaps unsurprisingly, that the OCR results are starting to break up a bit. 30 degrees looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqegepa3f58es1vxe41b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqegepa3f58es1vxe41b.png" alt="Image ocr test 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's test with a couple of real-world images. First this (the image shown here is much smaller than the large output file I got):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs737324xz5kxxhr1yha4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs737324xz5kxxhr1yha4.png" alt="Image ocr real world test 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's quite impressive - as well as recognising the neon sign, it did actually manage to capture some of the text on the synthesizers too!&lt;/p&gt;

&lt;p&gt;Now this (same here, much-reduced version of the resulting image):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhxeo0xnrfpl3k08c1bp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhxeo0xnrfpl3k08c1bp.png" alt="Image ocr real world test 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty extreme, the texture of the book and/or the resulting pixels got recognised as characters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v3luywi89z5993q45sf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v3luywi89z5993q45sf.png" alt="Image ocr real world test 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much better, especially considering that the ocr filter defaults to interpreting the English language (this is Swedish). (It can be changed using the &lt;code&gt;language&lt;/code&gt; parameter though).&lt;/p&gt;

&lt;p&gt;For a quick overview of how rotating the text affects the ocr output - and mostly because it's instructive - let's animate the rotation in a 12 second video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -loop 1 -i test.png -f lavfi -i "color=white" -filter_complex \
"rotate='PI/6*t:ow=hypot(iw,ih):oh=ow'[rotOut];\
[rotOut]split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=oh:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]drawtext=fontsize=40:fontcolor=black:\
x=(w-text_w)/2:y=(h-text_h)/2:text='%{metadata\:lavfi.ocr.text\:NA}'[drawtextOut];\
[origOut][drawtextOut]hstack" -t 12 ocrOutput8.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/FuHFIuoYdEg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Here we are animating by multiplying the rotation by time &lt;code&gt;t&lt;/code&gt;. Otherwise most of this is the same as previously, except for the fact that we have a default value of "NA" for when the &lt;code&gt;lavfi.ocr.text&lt;/code&gt; data is empty or non-existent. It's quite clear that most of the OCR output is junk, but the effect is quite entertaining (I think so, in any case). If you did want to use this text in some way, we can analyse the output video in ffprobe like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffprobe -show_entries frame_tags=lavfi.ocr.text,lavfi.ocr.confidence -f lavfi -i "movie=ocrOutput8.mov,ocr"  &amp;gt; ocrText.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a couple of the frame outputs from that text file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[FRAME]
TAG:lavfi.ocr.text=It was the best of

times, it was the worst Fe REG oT
of times, it was the age St eee
of wisdom, jas the age of foolishness...
age of foolishness...

TAG:lavfi.ocr.confidence=96 96 96 96 96 96 96 95 96 96 31 4 5 97 96 96 96 96 96 1 0 96 96 21 96 96 96 96 95 95 96 
[SIDE_DATA]
[/SIDE_DATA]
[/FRAME]
[FRAME]
TAG:lavfi.ocr.text=It was the best of

times, it was the worst times it was the worst
of times, it was the age oF Wego twas they.
of wisdom, eae fie age of foolishness...
age of foolishness...

TAG:lavfi.ocr.confidence=96 96 96 96 96 96 95 96 96 96 70 80 80 96 96 96 96 96 96 96 96 73 27 26 23 96 96 41 46 95 95 96 96 97 95 
[/FRAME]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is quite early in the rotation and most of the text is intact. The ocr filter also outputs a confidence rating for each word, which is fairly high at this stage. Later on it drops to 0 at worst:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FRAME]
TAG:lavfi.ocr.text=aa QB
s oO a omMmMs 4
2 m3 gf =o a0 F
man &amp;amp;
% 24Gb &amp;amp; 04,9
4.3 2 a 04.520
oC n ae Cofnae
mm OY 4 oO m OY a)
a oS a oO OA2e4Cc
a&amp;gt; 327% 6 $3 2% 9
a. Se % 5 DP
Ce eS a,
Rae et Eee &amp;amp;S
O
S40 &amp;amp; S470 &amp;amp;
Lo) eo &amp;amp; 4 Bec4
%% 2 4 %&amp;amp; 2S
Pah GY Paet %
, TP we , De we
@ 9 aj "00 &amp;amp;

TAG:lavfi.ocr.confidence=59 95 0 8 74 90 96 67 37 95 57 57 75 64 74 79 16 6 47 48 58 47 79 0 0 67 90 11 51 94 26 92 96 96 43 22 53 53 90 42 45 36 89 89 96 42 9 0 96 95 18 27 49 47 41 93 92 0 0 0 91 91 0 38 27 89 90 13 61 81 36 90 0 37 91 92 37 56 56 90 95 95 48 32 43 81 81 
[/FRAME]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we have a good idea of how the ocr filter works and can now move on to...speech!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the flite filter
&lt;/h2&gt;

&lt;p&gt;As I mentioned, it is the &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#flite" rel="noopener noreferrer"&gt;flite&lt;/a&gt; filter that can generate speech in ffmpeg. The name flite derives from the fact that the library is designed as a lightweight and portable version of the Festival speech synthesis library. In the ffmpeg implementation there are six speech synthesis voices available. Flite itself has support for downloading voices on the fly, but this is currently unavailable within ffmpeg. Here is an example of text being spoken in the flite filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -filter_complex \
"flite=text='This is the slt voice':\
voice=slt" fliteOut1.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Text can also be spoken from a text file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -filter_complex \
"flite=textfile=speech.txt:\
voice=rms" fliteOut2.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NB: You may need to re-configure ffmpeg in order to use it - in this case, we'll also need to clone the flite repo to get it working:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; fetch the repo: &lt;code&gt;git clone https://github.com/festvox/flite.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;move to the directory: &lt;code&gt;cd flite/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;configure, make and install:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./configure&lt;/code&gt;&lt;/li&gt;
&lt;li&gt; &lt;code&gt;make&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;sudo make install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Then in &lt;code&gt;ffmpeg&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./configure --enable-libflite --enable-cross-compile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make install&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(These are macOS Monterey instructions, others are &lt;a href="http://johnriselvato.com/how-to-install-flite-flitevox-for-ffmpeg/" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining OCR and text-to-speech...
&lt;/h2&gt;

&lt;p&gt;So could we build an ffmpeg command that takes in an image of text, recognises the text and then speak it aloud? Well, there are limitations. For example, if the flite filter had had the ability to handle text expansion we could simply build it into the filtergraph in the same way as the drawtext filter. &lt;br&gt;
Instead we are going to have to read the text from a file. This means that we will have to resort to some sort of compound command. Here's one possibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i test.png -f lavfi -i "color=red" -filter_complex \
"[0]split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=iw*mdar:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]metadata=mode=print:key=lavfi.ocr.text:file=metadata.txt:direct=1[metadataOut];\
[metadataOut]drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'[drawtextOut];\
[origOut][drawtextOut]hstack" -update 1 -frames:v 1 textOutSpeak.png \
&amp;amp;&amp;amp; ffmpeg -y -loop 1 -i textOutSpeak.png -filter_complex \
"flite=textfile=metadata.txt:\
voice=rms[speakOut]" -t 15 -map 0 -map "[speakOut]" ocrAndFliteOutput1.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the other way to do this would be to pipe the output from the first ffmpeg command to the next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i test.png -f lavfi -i "color=red" -filter_complex \
"[0]split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=iw*mdar:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]metadata=mode=print:key=lavfi.ocr.text:file=metadata.txt:direct=1[metadataOut];\
[metadataOut]drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'[drawtextOut];\
[origOut][drawtextOut]hstack" -update 1 -frames:v 1 -f image2 - \
| ffmpeg -y -loop 1 -i pipe:0 -filter_complex \
"flite=textfile=metadata.txt:\
voice=rms[speakOut]" -t 15 -map 0 -map "[speakOut]" ocrAndFliteOutput2.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these will give us the following, which sort of works:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WeieBLfTjgs"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The problem is that flite is reading the whole output of &lt;code&gt;metadata.txt&lt;/code&gt; rather than just the text itself. We need to trim this text data before it is read. One way is to use the &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;tail&lt;/code&gt; in the following way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep -A50 '=' metadata.txt | tail -c+16 &amp;gt; massagedData.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will work up to a fairly reasonable maximum of 50 lines. It trims away the first part of the text, giving us the whole readable text. And so, finally, we have the following three-part compound command that takes in an image and produces OCR output and text-to-speech, tidily packaged in a short video:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i test.png -f lavfi -i "color=red" -filter_complex \
"[0]split[textOut][origOut];\
[textOut]ocr[ocrOut];\
[1:v][ocrOut]scale2ref=w=iw*mdar:h=ih[textScaled][colorScaled];\
[colorScaled][textScaled]overlay[overlayOut];\
[overlayOut]metadata=mode=print:key=lavfi.ocr.text:file=metadata.txt:direct=1[metadataOut];\
[metadataOut]drawtext=fontsize=40:fontcolor=black:x=(w-text_w)/2:y=(h-text_h)/2:\
text='%{metadata\:lavfi.ocr.text}'[drawtextOut];\
[origOut][drawtextOut]hstack" -update 1 -frames:v 1 textOutSpeak.png \
&amp;amp;&amp;amp; grep -A50 '=' metadata.txt | tail -c+16 &amp;gt; massagedData.txt \
&amp;amp;&amp;amp; ffmpeg -y -loop 1 -i textOutSpeak.png -filter_complex \
"flite=textfile=massagedData.txt:\
voice=rms[speakOut]" -t 15 -map 0 -map "[speakOut]" ocrAndFliteOutput3.mov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result, a tidied-up version of the last video:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/4xteii8Kxt0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Obviously, this is a rather cumbersome and fragile end result.  We can summarise by stating that there are better ways to do this. However, it has been quite an educational investigation into some useful tools within ffmpeg.&lt;/p&gt;

&lt;h3&gt;
  
  
  About the cover image
&lt;/h3&gt;

&lt;p&gt;The cover image of this post was also generated with ffmpeg. I used two GLSL shaders (captured to .png files) as input. These are the shaders used for the design of two cards from the &lt;a href="http://pixelspiritdeck.com" rel="noopener noreferrer"&gt;Pixelspirit Deck&lt;/a&gt;. The first card is called "Vision" and the second card is called "Judgement". They seemed appropriate ;) &lt;/p&gt;

&lt;p&gt;I used the ffmpeg filter &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#minterpolate" rel="noopener noreferrer"&gt;minterpolate&lt;/a&gt; to morph from the "Vision" image to the "Judgement" image (the morph effect is pretty basic, but it works):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -r 0.3 -stream_loop 1 -i 33_vision.png -r 0.3 -stream_loop 2 -i 31_Judgement.png -filter_complex "[0][1]concat=n=2:v=1:a=0[v];[v]minterpolate=fps=24:scd=none,trim=3:7,setpts=PTS-STARTPTS" -pix_fmt yuv420p visionToJudgement.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I first captured the output images from the &lt;code&gt;visionToJudgement.mp4&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i visionToJudgement.mp4 -r 1 morphtest1/test-%09d.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and tiled them horizontally (new filter alert: &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#tile-1" rel="noopener noreferrer"&gt;tile&lt;/a&gt;) at the right size to create the header image&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i morphtest1/test-%09d.png -filter_complex "scale=594:1496,tile=6x1" -update 1 -frames:v 1 output.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team" rel="noopener noreferrer"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ocr</category>
      <category>texttospeech</category>
      <category>ffmpeg</category>
      <category>ffprobe</category>
    </item>
    <item>
      <title>Working with audio in ffmpeg - Part 2</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 06 Jan 2023 16:26:30 +0000</pubDate>
      <link>https://dev.to/video/working-with-audio-in-ffmpeg-part-2-1ip</link>
      <guid>https://dev.to/video/working-with-audio-in-ffmpeg-part-2-1ip</guid>
      <description>&lt;p&gt;Last year I examined &lt;a href="https://dev.to/video/working-with-audio-in-ffmpeg-4631"&gt;the basics of working with audio in ffmpeg&lt;/a&gt; and promised a follow-up in which I demonstrate how to use LV2 plugins to further extend the possibilities for audio manipulation in every video streaming professional's favourite command line tool. And here, at last, is Part 2 of &lt;em&gt;"Working with audio in ffmpeg"&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;This demonstration, similarly to last time, uses a short song excerpt which I have made available for you on &lt;a href="https://freesound.org/people/sinewave440hz/sounds/668293/" rel="noopener noreferrer"&gt;freesound.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To pick up where we left off, we established that ffmpeg already has a wealth of audio manipulation tools built right in as audio filters. There is however much more that you can do without too much effort. By using LV2 plugins, for example, you can take advantage of any number of free plugins and even code your own.&lt;/p&gt;

&lt;p&gt;LV2 plugins are the second generation version of an earlier plugin system known as LADSPA - LV2 is an acronym for &lt;em&gt;LADSPA Version 2&lt;/em&gt; plugins. And it's also worth noting that LADSPA (version 1) plugins are also usable inside ffmpeg (this may well be a topic for a future blog too ;) ) and there are plenty of those available too, of course.&lt;/p&gt;

&lt;p&gt;You may find that LV2 is not immediately available within your current ffmpeg build. In that case, you will need to build from source. Luckily, this process is well-documented in ffmpeg. I am on macOS so I tried my hand at following those specific instructions, detailed &lt;a href="https://trac.ffmpeg.org/wiki/CompilationGuide/macOS" rel="noopener noreferrer"&gt;here&lt;/a&gt;. They were fairly straightforward, so I may as well document them here. This is also the process for enabling any of the other filters that are not included by default in any particular build. The documentation will usually indicate when this is necessary. There will be occasions when the documentation falls short though, as we will see, so be aware of that.&lt;/p&gt;

&lt;p&gt;If you, like me, already had a version of ffmpeg on your machine, getting rid of it is probably the best option. For me, that meant doing a &lt;code&gt;homebrew uninstall ffmpeg&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Next, grab ffmpeg from GitHub:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;then you'll want to get all the necessary dependencies to successfully build ffmpeg. I chose the simplest option in my case; installing those dependencies via Homebrew:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install automake fdk-aac git lame libass libtool libvorbis libvpx \
opus sdl shtool texi2html theora wget x264 x265 xvid nasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then:&lt;br&gt;
&lt;code&gt;cd ffmpeg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At which point it's probably best to try a vanilla build with the following trinity of commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./configure&lt;/code&gt;&lt;br&gt;
which will take a while then:&lt;br&gt;
&lt;code&gt;make&lt;/code&gt;&lt;br&gt;
which can also take a bit then:&lt;br&gt;
&lt;code&gt;make install&lt;/code&gt;&lt;br&gt;
which usually doesn't take too long...&lt;/p&gt;

&lt;p&gt;If all is well, and you can run something like, say:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffmpeg -filters&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and see a nice long list of filters and their features, then we're ready to setup LV2!&lt;/p&gt;

&lt;p&gt;First we also need to install lv2 from Homebrew:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install lv2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To use LV2 plugins then, we need to build with the &lt;code&gt;-enable-lv2&lt;/code&gt; flag:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./configure --enable-lv2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;then once again:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;make&lt;/code&gt;&lt;br&gt;
and&lt;br&gt;
&lt;code&gt;make install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now here you may run into an issue:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ERROR: lilv-0 not found using pkg-config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Don't stress, though. Have a sip of tea/beer and rest easy in &lt;a href="https://stackoverflow.com/questions/58600620/ffmpeg-complex-filtering-how-to-get-around" rel="noopener noreferrer"&gt;the knowledge&lt;/a&gt; that all you need to do is another brew install:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew install lilv&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;so after this...&lt;/p&gt;

&lt;p&gt;&lt;code&gt;make&lt;/code&gt;&lt;br&gt;
and&lt;br&gt;
&lt;code&gt;make install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;....you'll have had time to finish your tea/beer and get right down to testing LV2 plugins...&lt;/p&gt;

&lt;p&gt;Let's dive into an &lt;a href="https://ffmpeg.org/ffmpeg-all.html#Examples-68" rel="noopener noreferrer"&gt;example&lt;/a&gt; in the ffmpeg LV2 filter documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=drone=0.2\
|aging=0.5' vinylizedADD.wav

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BUT - notice that I found it necessary to alter the escaped characters in the url for it be correctly formed. So that's one thing to think about. The other is that this doesn't work at all...because you need to install the Vinyl plugin!&lt;/p&gt;

&lt;p&gt;The Vinyl plugin, as you may have guessed from the url, is part of the &lt;a href="https://calf-studio-gear.org" rel="noopener noreferrer"&gt;Calf plugin suite&lt;/a&gt;, an impressive selection of free - in all senses - professional audio tools. Let's grab them:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;brew tap david0/homebrew-audio&lt;/code&gt;&lt;br&gt;
and&lt;br&gt;
&lt;code&gt;brew install calf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you will be able to run the last ffmpeg command and hear a vinyl effect on the audio excerpt. Nice! Let's immediately make that more drastic with our own version. &lt;/p&gt;

&lt;p&gt;I noticed in the source code documentation that the plugin GUI has several other parameters, some of them controlled by switches. So how on earth would we access those in the command line? Let's find out more about our parameters with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af\
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=help' \
vinylizedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which unpolitely - but, admittedly, helpfully - spews out the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The 'http://calf.sourceforge.net/plugins/Vinyl' plugin has the following input controls:
[Parsed_lv2_0 @ 0x7faa2c005d00] bypass          &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Bypass
[Parsed_lv2_0 @ 0x7faa2c005d00] level_in                &amp;lt;float&amp;gt; (from 0.015625 to 64.000000) (default 1.000000)            Input Gain
[Parsed_lv2_0 @ 0x7faa2c005d00] level_out               &amp;lt;float&amp;gt; (from 0.015625 to 64.000000) (default 1.000000)            Output Gain
[Parsed_lv2_0 @ 0x7faa2c005d00] drone           &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Drone
[Parsed_lv2_0 @ 0x7faa2c005d00] speed           &amp;lt;float&amp;gt; (from 33.000000 to 78.000000) (default 33.000000)          Speed
[Parsed_lv2_0 @ 0x7faa2c005d00] aging           &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Aging
[Parsed_lv2_0 @ 0x7faa2c005d00] freq            &amp;lt;float&amp;gt; (from 600.000000 to 1800.000000) (default 1000.000000)             Frequency
[Parsed_lv2_0 @ 0x7faa2c005d00] gain0           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.007812)             Vol 0
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch0          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 0
[Parsed_lv2_0 @ 0x7faa2c005d00] active0         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 0
[Parsed_lv2_0 @ 0x7faa2c005d00] gain1           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.007812)             Vol 1
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch1          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 1
[Parsed_lv2_0 @ 0x7faa2c005d00] active1         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 1
[Parsed_lv2_0 @ 0x7faa2c005d00] gain2           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.015625)             Vol 2
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch2          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 2
[Parsed_lv2_0 @ 0x7faa2c005d00] active2         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 2
[Parsed_lv2_0 @ 0x7faa2c005d00] gain3           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.007812)             Vol 3
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch3          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 3
[Parsed_lv2_0 @ 0x7faa2c005d00] active3         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 3
[Parsed_lv2_0 @ 0x7faa2c005d00] gain4           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.031250)             Vol 4
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch4          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 4
[Parsed_lv2_0 @ 0x7faa2c005d00] active4         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 4
[Parsed_lv2_0 @ 0x7faa2c005d00] gain5           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.062500)             Vol 5
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch5          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 5
[Parsed_lv2_0 @ 0x7faa2c005d00] active5         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 5
[Parsed_lv2_0 @ 0x7faa2c005d00] gain6           &amp;lt;float&amp;gt; (from 0.000016 to 1.000000) (default 0.062500)             Vol 6
[Parsed_lv2_0 @ 0x7faa2c005d00] pitch6          &amp;lt;float&amp;gt; (from -1.000000 to 1.000000) (default 0.000000)            Pitch 6
[Parsed_lv2_0 @ 0x7faa2c005d00] active6         &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.000000)             Activate 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's try that more drastic version of the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af\
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=drone=1.0|aging=1.0' \
vinylizedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm, needs to be louder (not much though, those resonant frequencies can be a bit nasty). And let's add a speed change too (this changes the LFO speed of the drone, in fact).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=drone=1.0|aging=1.0\
|level_out=2.0|speed=45.0' vinylizedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what about these several gain, pitch and activate parameters? It seems from examining the GUI of this plugin that these are basically seven file players with various different vinyl-style effects supplied as looping audio files. Let's add a couple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=drone=0.6|aging=0.6\
|level_out=2.0|speed=45.0|active5=1.0|gain5=1.0|pitch5=1.0|active6=1.0\
|gain6=1.0|pitch6=-1.0' vinylizedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oddly enough, those audio effects don't loop. Why is that? Well, who knows...but you can track that bug &lt;a href="https://github.com/calf-studio-gear/calf/issues/296" rel="noopener noreferrer"&gt;here&lt;/a&gt;. From reading that, it transpires that one of them does actually loop correctly. By trial and error, I found it - gain/pitch/active1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Vinyl:c=drone=0.6|aging=0.6\
|level_out=2.0|speed=45.0|active1=1.0|\
gain1=0.1|pitch1=1.0' vinylizedADD.wav

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well that's lovely, how about some reverb?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Reverb:c=help' \
vinylizedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok then, these are the params this time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The 'http://calf.sourceforge.net/plugins/Reverb' plugin has the following input controls:
[Parsed_lv2_0 @ 0x7f954b00c700] decay_time              &amp;lt;float&amp;gt; (from 0.400000 to 15.000000) (default 1.500000)            Decay time
[Parsed_lv2_0 @ 0x7f954b00c700] hf_damp         &amp;lt;float&amp;gt; (from 2000.000000 to 20000.000000) (default 5000.000000)           High Frq Damp
[Parsed_lv2_0 @ 0x7f954b00c700] room_size               &amp;lt;float&amp;gt; (from 0.000000 to 5.000000) (default 2.000000)             Room size
[Parsed_lv2_0 @ 0x7f954b00c700] diffusion               &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 0.500000)             Diffusion
[Parsed_lv2_0 @ 0x7f954b00c700] amount          &amp;lt;float&amp;gt; (from 0.000000 to 2.000000) (default 0.250000)             Wet Amount
[Parsed_lv2_0 @ 0x7f954b00c700] dry             &amp;lt;float&amp;gt; (from 0.000000 to 2.000000) (default 1.000000)             Dry Amount
[Parsed_lv2_0 @ 0x7f954b00c700] predelay                &amp;lt;float&amp;gt; (from 0.000000 to 500.000000) (default 0.000000)           Pre Delay
[Parsed_lv2_0 @ 0x7f954b00c700] bass_cut                &amp;lt;float&amp;gt; (from 20.000000 to 20000.000000) (default 300.000000)              Bass Cut
[Parsed_lv2_0 @ 0x7f954b00c700] treble_cut              &amp;lt;float&amp;gt; (from 20.000000 to 20000.000000) (default 5000.000000)             Treble Cut
[Parsed_lv2_0 @ 0x7f954b00c700] on              &amp;lt;float&amp;gt; (from 0.000000 to 1.000000) (default 1.000000)             Active
[Parsed_lv2_0 @ 0x7f954b00c700] level_in                &amp;lt;float&amp;gt; (from 0.015625 to 64.000000) (default 1.000000)            Input Gain
[Parsed_lv2_0 @ 0x7f954b00c700] level_out               &amp;lt;float&amp;gt; (from 0.015625 to 64.000000) (default 1.000000)            Output Gain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -y -i "a different day intro excerpt.wav" -af \
'lv2=p=http\\://calf.sourceforge.net/plugins/Reverb:c=amount=0.9\
|amount=1.0|room_size=5.0|hf_damp=2000.0|predelay=5.0\
|diffusion=1.0' reverbedADD.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would be nice to hear the reverb tail on that, but I haven't yet given thought on how to do that. (Please let me know if you work it out). There is a lot more to explore within just the Calf audio suite, let alone all of the other free alternatives out there. Perhaps one day you might find you need one of these tools, be it for a quick fix or a professional editing repair...&lt;/p&gt;

&lt;p&gt;One final note: the image for this blog post is a frequency plot generated from exactly the audio excerpt that I have been using in this blog post. Naturally, this was also generated within ffmpeg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i "a different day intro excerpt.wav" -lavfi \
showspectrumpic=s=1280x480:scale=log:color=fiery:legend=0 add.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Chances are I'll look closer at &lt;code&gt;showspectrumpic&lt;/code&gt; in a future blog post...)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team" rel="noopener noreferrer"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Working with audio in ffmpeg</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 25 Feb 2022 15:20:11 +0000</pubDate>
      <link>https://dev.to/video/working-with-audio-in-ffmpeg-4631</link>
      <guid>https://dev.to/video/working-with-audio-in-ffmpeg-4631</guid>
      <description>&lt;p&gt;Everyone working in video streaming development knows about &lt;code&gt;ffmpeg&lt;/code&gt; and most of us use it from time to time in our daily assignments. It's also well-known that &lt;code&gt;ffmpeg&lt;/code&gt; has a whole bunch of functionality for audio. Not as many of us delve into that aspect I suspect though, beyond perhaps converting the occasional audio track format in some way. &lt;code&gt;Ffmpeg&lt;/code&gt; does go way beyond that though, offering a plethora of conversion, analysis and even sound generation tools.&lt;/p&gt;

&lt;p&gt;Re-capping simply and very briefly, a typical &lt;code&gt;ffmpeg&lt;/code&gt; command consists of one or more inputs (&lt;code&gt;-i&lt;/code&gt;), any number of operations and a number of outputs. (There are many more aspects to the command syntax though - see &lt;a href="https://ffmpeg.org/documentation.html"&gt;the ffmpeg docs&lt;/a&gt; for the full spec).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i bunny.mp4 -c:v libvpx-vp9 -c:a libvorbis bunny.webm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The power lies in what the inputs can be and in the operations, which are extremely numerous, chainable - and also extensible (that's something we will adress in the next article...). Here is a slightly more complex example, by a colleague of mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Low Latency ScreenGrab
`ffmpeg -f avfoundation -capture_cursor 1 -i 3 -r 30 -c:v libx264 -an -tune zerolatency -preset ultrafast -pix_fmt yuv420p -fflags flush_packets -f mpegts - | mpv - --no-cache --untimed --no-demuxer-thread --video-sync=audio --vd-lavc-threads=1`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, we are using the screen as an input. (This is quite a specific command and will need some adjustment when running on another device though).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ffmpeg.org/download.html"&gt;Download FFmpeg&lt;/a&gt; and try the following examples. I am using a track snippet that you can download from &lt;a href="https://freesound.org/people/sinewave440hz/sounds/621778/"&gt;Freesound&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audio conversion
&lt;/h2&gt;

&lt;p&gt;A video streaming developer might work with encoding or transcoding various audio tracks for video. For example, you might want to convert from mono to stereo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.flac -ac 1 critics_mono.flac
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or 5.1 to LR stereo (&lt;a href="https://freesound.org/people/sinewave440hz/sounds/621779/"&gt;here is the same file mixed as 5.1&lt;/a&gt; so you can try it out):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics_surround.wav -ac 2 critics.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or converting formats, which can be even simpler, for example to mp3 or flac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav critics.mp3
ffmpeg -i critics.wav critics.flac
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also worth mentioning the &lt;code&gt;ffprobe&lt;/code&gt; tool that is typically bundled with &lt;code&gt;ffmpeg&lt;/code&gt;. Use it on any of the above files to see all the available file info. In the case of the surround example, it reveals my song metadata from Logic Pro X:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input #0, wav, from 'critics_surround.wav':
  Metadata:
    encoded_by      : Logic Pro X
    date            : 2022-02-25
    creation_time   : 12:04:39
    time_reference  : 163250181
    umid            : 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500FA50
    coding_history  : 
  Duration: 00:00:15.02, bitrate: 4237 kb/s
  Chapters:
    Chapter #0:0: start 0.000000, end 15.018662
      Metadata:
        title           : Tempo: 132.0
    Chapter #0:1: start 0.000000, end 0.113628
      Metadata:
        title           : Bridge 1
    Chapter #0:2: start 0.113628, end 14.545442
      Metadata:
        title           : Chorus
    Chapter #0:3: start 14.545442, end 15.018662
      Metadata:
        title           : Chorus w/ Vox
  Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 5.1, s16, 4233 kb/s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we're playing around with various channels, what happens if you want to convert from left-right stereo to mid-sides stereo...? Well, then you'll want to make use of a filter...&lt;/p&gt;

&lt;h2&gt;
  
  
  filters
&lt;/h2&gt;

&lt;p&gt;Simple audio filters are invoked with the &lt;code&gt;-af&lt;/code&gt; flag. A filter in this case is a tool with an input and output that manipulates the incoming data in some way. Some examples of when you can make use of audio filters are as follows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you want to fade out some audio:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -a 'afade=t=out:st=4:d=9' critics_faded.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To create a fairly naive stereo-widening effect by delaying the left and right channels by different, small amounts:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -af 'adelay=5|0|10' critics_wide.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To create a flange effect using an echo algorithm with a very short delay time and regeneration:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -af 'aecho=0.8:0.88:6:0.9' critics_echo_flange.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;To pitch down by 2 octaves and filter audio (this one sounds wild...):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -af 'afreqshift=shift=-2400' critics_shifted.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So back to the stereo conversion we wanted to do. We could use the &lt;code&gt;stereotools&lt;/code&gt; audio filter for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -af 'stereotools=mode=lr&amp;gt;ms' critics_ms.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complex filters
&lt;/h2&gt;

&lt;p&gt;If you want to work with several inputs and/or outputs, you will need to use complex filters instead, using the &lt;code&gt;-filter_complex&lt;/code&gt; option instead of &lt;code&gt;-af&lt;/code&gt;. Here is an example that outputs three separately filtered audio files from the original source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -filter_complex 'acrossover=split=900 7000:order=8th[LOW][MID][HIGH]' -map '[LOW]' critics_low.wav -map '[MID]' critics_mid.wav -map '[HIGH]' critics_high.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an example - with multiple inputs - of performing a crossfade of two of the earlier files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -i critics_shifted.wav -filter_complex acrossfade=d=10:c1=exp:c2=exp critics_xfade.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can chain complex_filters one after the other using an array-like syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -filter_complex 'afreqshift=shift=-1200[shifted];[shifted]chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3[chorused];[chorused]areverse' critics_destroyed.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To break this down into its three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;afreqshift=shift=-1200[shifted]&lt;/code&gt; - shifts the audio down an octave&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[shifted]chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3[chorused]&lt;/code&gt; - takes the output of the shifted filter and applies chorus to it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[chorused]areverse&lt;/code&gt; - takes the output of the chorused filter and reverses it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can do things like this on parallel inputs and outputs, then mix them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i critics.wav -i critics_low.wav -filter_complex '[0]afreqshift=shift=-1200[shifted];[shifted]chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3[chorused];[chorused]areverse[reversed];[1]afreqshift=shift=-3600[shifted2];[reversed][shifted2]amix=inputs=2' critics_annihilated.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the similarity to the previous statement, but here we have added two input files and a new filter path for the second file. We add a &lt;code&gt;[0]&lt;/code&gt; to the first audio path to capture the input from the first file then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;[1]afreqshift=shift=-3600[shifted2];&lt;/code&gt; - captures the input from the second audio file and shifts it down 3 octaves.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[reversed][shifted2]amix=inputs=2&lt;/code&gt; - combines the result of both audio flows into one file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These kinds of flows are called audiographs and can be much more complex than this if necessary. There are even tools included in the &lt;code&gt;ffmpeg&lt;/code&gt; suite to represent such audiographs graphically. I hope to take a more detailed look at this in the future.&lt;/p&gt;

&lt;p&gt;There are many more built-in audio filters available in &lt;code&gt;ffmpeg&lt;/code&gt;. Beyond that though, it's also possible to use audio plugins within &lt;code&gt;ffmpeg&lt;/code&gt;. One of the formats you can use for this is the widely used LV2 format, the same format used in some recent audio effects hardware (eg. Mod Devices' &lt;a href="https://moddevices.com/product/mod-duo-x/"&gt;Mod Duo X&lt;/a&gt;). Support for this is not necessarily included in a typical &lt;code&gt;ffmpeg&lt;/code&gt; installation though. In the next article I will look at custom &lt;code&gt;ffmpeg&lt;/code&gt; builds and getting LV2 plugins working within them.&lt;/p&gt;

&lt;p&gt;I recommend &lt;a href="https://developers.deepgram.com/blog/2021/11/ffmpeg-beginners/"&gt;another article&lt;/a&gt; on this subject that was very much the inspiration for my own. I've tried not to repeat what was said there and instead provided yet more useful examples as a complement to that article.&lt;/p&gt;

&lt;p&gt;UPDATE: Similarly to Part 2 of this blog, which is now available, I added a frequency plot of the audio file as the blog post image using ffmpeg. I also converted the file to 16 bit, 44.1 kHz prior to plotting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -i "critics (Excerpt).wav" -acodec pcm_s16le -ar 44100 critics16bit.wav

ffmpeg -i "critics16bit.wav" -lavfi \
showspectrumpic=s=1000x420:scale=sqrt:color=cividis:legend=0:gain=20 c.png
ffmpeg -i c.png -vf crop=900:600:0:0 -update 1 c1.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>audio</category>
    </item>
    <item>
      <title>Building React Native UI Components and Modules in 2021 </title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Sun, 12 Dec 2021 19:07:29 +0000</pubDate>
      <link>https://dev.to/video/building-react-native-ui-components-and-modules-in-2021-50e6</link>
      <guid>https://dev.to/video/building-react-native-ui-components-and-modules-in-2021-50e6</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;React Native has been around for some time now. And as many of us know, one of the advantages of the platform are the many ready-made components available for use. Another is of course, the other side of that same coin - that you can build your own components for those more specific needs that so often arise in a project.&lt;/p&gt;

&lt;p&gt;In a recent assignment, I had occasion to build a wrapper around two separate proprietary video players for iOS and Android respectively. The players in question were mature and well-built but the interfaces had led their own lives, so to speak, and were quite different to each other - due mostly to the platform-specific differences. Also, the team that were intending to use the component in their React Native project had specific needs of their own, not necessarily met by the existing components, these having been developed previously for other apps with different requirements.&lt;/p&gt;

&lt;p&gt;This is a fairly common scenario when building a React Native component wrapper, where the development needs to occur across several different teams - in this case, the app team, the iOS team and the android team. Also, while the official React Native documentation is ever-improving in this area, there are still large gaps that you will often come across as soon as you dig deeper into component development. In this article, based on my recent work in the area, I will discuss some useful tools and processes as well as some of the undocumented techniques that you will often want to use when wrapping platform-specific components.&lt;/p&gt;

&lt;p&gt;I will also mention some interesting and radical changes in the codebase that will become the new way to develop React Native components in future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the project
&lt;/h2&gt;

&lt;p&gt;A typical React Native component project will benefit from having a simple example project embedded into the repo. This can simplify development of the component as well as provide a decent demo of what it can do when it's ready for publishing. In our case, we also wanted typescript set up and ready to go. There is usually a fair amount of boilerplate code needed on both the native sides and in javascript. Given all of this, I chose to generate the component project with a tool by Callstack, called &lt;a href="https://github.com/callstack/react-native-builder-bob" rel="noopener noreferrer"&gt;create-react-native-library&lt;/a&gt;. This is a CLI that will create a project for you that meets all the above demands (and more): &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdc4ix5wy0bc19sdqalbc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdc4ix5wy0bc19sdqalbc.gif" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is of course the usual caveat that, as with any wizard-type tool, you will want to at least have some idea of what it's doing behind the scenes. And most likely you will need to tweak your typescript and linting setup according to what you need. As you can see from the above GIF though, another plus is that you also have the opportunity to choose which languages to set up your boilerplate for. As Android has both Java and Kotlin available for React Native development and iOS has both Objective-C and Swift available, you can really save some time here and fit smoothly in to whatever project you're building for.&lt;/p&gt;

&lt;p&gt;To get up and running with &lt;code&gt;create-react-native-library&lt;/code&gt; is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-react-native-library your-new-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give you iOS and Android modules in the languages of your choice and a fully functional embedded example project, so you can just focus on building out what you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating your platform-specific packages
&lt;/h2&gt;

&lt;p&gt;The next step is to get your project building with the packages you intend to provide a wrapper around. Perhaps you are only wrapping a component for a specific platform, or two platform-specific components from the same vendor or perhaps - as in the case of my most recent project - two separate platform-specific components that do not even share the same common interface, beyond a more general use-case. If you are developing for both platforms here, there will be some focus later on on developing a common interface.&lt;/p&gt;

&lt;p&gt;This step is where you will need to work in the platform-specific part of the generated project - in XCode or Android Studio typically. As these components are designed specifically for integration into other projects, you will likely simply need to follow the iOS or Android guides for the component in question. In my case, it was necessary to collect some information from the respective development teams, as there were new use-cases planned for the React Native component (for example, videos were going to be expected to play in a newsfeed-style list instead of fullscreen). The example project can be very useful at this stage, to show that each integration is working properly in its most basic form. In my case, this meant showing and autoplaying on focus a list of videos in both platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Module or UI Component
&lt;/h3&gt;

&lt;p&gt;You may need to build one or both of the two possible types of component in React Native - a module or a UI component. Modules are good wrappers for API components, while components or packages with a user interface need of course to be UI Components.&lt;/p&gt;

&lt;p&gt;Here, I set out to build a UI Component for each platform with the understanding that certain parts of the project may benefit from being separated from the UI as an API module. In fact, while this may be planned for the future, what actually happened was that it turned out that the only way to get the functionality we were after in Android was to provide both a module part and a UI component part in the same package. &lt;/p&gt;

&lt;h3&gt;
  
  
  View controller vs view
&lt;/h3&gt;

&lt;p&gt;When wrapping a UI component in iOS, you will quite often find that the component you are wrapping makes use of a view controller. In a React UI Component the RCTViewManager (and more often its SimpleViewManager descendent) is responsible for all instances of the component in your app. The thing is the view manager manages views (obviously) and not view controllers. To deal with this, you need to attach the view controller's view to the component's view (as a subview). That is done in the following way:&lt;/p&gt;

&lt;p&gt;In the view you will be providing to the view manager, add a variable of the type of the view controller you want to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;RNPlayerView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;viewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RNPlayerViewController&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Init that view controller var in your view and add the view controller's view as a subview:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;RNPlayerViewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, make sure that the view controller's view matches the view's dimensions. In your view class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;layoutSubviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;layoutSubviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all you need. &lt;/p&gt;

&lt;h3&gt;
  
  
  Properties and methods in iOS
&lt;/h3&gt;

&lt;p&gt;It's worth recapping that the view mentioned in the last section is where you will typically handle all of the properties that you expect your component to expose in react native. If you are using a view controller as above most, or probably all, of these properties (apart from any event callbacks you may be handling) will need forwarding to the view controller.&lt;/p&gt;

&lt;p&gt;A colleague of mine and I came up with a fairly tidy way to handle this.&lt;/p&gt;

&lt;p&gt;Take for example an assetId property for a video player UI component. &lt;/p&gt;

&lt;p&gt;Provide the usual react native property setter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setAssetId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;assetId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then respond to changes in the variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;didSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_updateAsset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, update the variable in your view controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_updateAsset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;assetId&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateAssetId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assetId&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nice thing is that this adapts well to the situation where you have several properties that need setting together in a group. This is fairly common in the components I have developed, where you might, for example, have a "setup" or "config" function in your component that takes several parameters, and if any one of those parameters changes, the function should be called anew. The above pattern allows you to group variables together before sending them off to the view controller in question:&lt;/p&gt;

&lt;p&gt;1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setParam1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setParam2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;param2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;didSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_updateParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;didSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_updateParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_updateParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple and maintainable.&lt;/p&gt;

&lt;p&gt;You will often want to react to events in your component on the react native side. The official docs have an objective-c example, but we prefer to operate in Swift, given the choice ;) Your view should have an obj-c property for each event you are responding to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RCTDirectEventBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RCTDirectEventBlock&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a method for each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendOnReadyToRN&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;onReady&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onReady&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"Ready"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Ready"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendOnErrorToRN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;onError&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;"Error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you will typically have some switch statement or similar in your view controller that calls back to these view methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;eventMonitoring&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiComponent&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;uiComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;errorMsg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"uiComponent Error : &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendErrorToView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;errorMsg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;uiComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendOnReadyToView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendErrorToView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;componentView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reactSuperview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;ComponentView&lt;/span&gt;
        &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendErrorToRN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendOnReadyToView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;componentView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reactSuperview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;ComponentView&lt;/span&gt;
        &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendPlaybackReadyToRN&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally on the iOS/tvOS side, you will want to know how to call methods on your component. In your ViewManager class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;componentView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forReactTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;ComponentView&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;componentView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uiManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forReactTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;ComponentView&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See below for an idea of what the javascript side could look like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup iOS/tvOS/Android
&lt;/h3&gt;

&lt;p&gt;Occasionally, it's necessary to configure a component before viewing it. One way to make that possible is to add a method similar to the start/stop methods above (for iOS/tvOS), but containing several parameters, which can then be consumed in the view controller or the view as required.&lt;/p&gt;

&lt;p&gt;In Android, you are not immediately able to call such a setup method on a view component. Instead you will need to implement a small Android native module that forwards the necessary setup info to the view component (or at least, this is one way to do it that we successfully implemented). This might look something like this (in Kotlin, also our language of choice...):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RNComponentSetupManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactApplicationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RNComponentViewManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactContextBaseJavaModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RNComponentViewManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewManager&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"RNComponentSetupManager"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@ReactMethod&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can set up our ReactPackage class to initialise either the view manager or the setup manager, whichever is called first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;lass&lt;/span&gt; &lt;span class="nc"&gt;RNComponentPackage&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactPackage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;inner&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;setupManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RNComponentSetupManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RNComponentViewManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getInitiatedModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactApplicationContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Modules&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentModules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentModules&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;currentModules&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RNComponentViewManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;setupManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RNComponentSetupManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;//&amp;lt;-- Send in viewManager so we can use it.&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;initializedModules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setupManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initializedModules&lt;/span&gt;
      &lt;span class="n"&gt;initializedModules&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createNativeModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactApplicationContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NativeModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getInitiatedModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setupManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createViewManagers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReactApplicationContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ViewManager&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getInitiatedModules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reactContext&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;viewManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Other properties and methods in Android
&lt;/h3&gt;

&lt;p&gt;Setting properties is a little simpler in Android, where there is no view controller as such.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="nd"&gt;@ReactProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setAssetId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;componentView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ComponentView&lt;/span&gt;
    &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAssetId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in your view class, handle that as necessary.&lt;/p&gt;

&lt;p&gt;For methods on the view, you will need to override the &lt;code&gt;receiveCommand&lt;/code&gt; method, the equivalent of the two iOS/tvOS methods above looking like this in the view manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;receiveCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commandId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ReadableArray&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commandId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"startFromManager"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="s"&gt;"stopFromManager"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;componentView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ComponentView&lt;/span&gt;
    &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;componentView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;ComponentView&lt;/span&gt;
    &lt;span class="n"&gt;componentView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And similarly in the view, handle those start() and stop() commands within your native component as appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Javascript side
&lt;/h3&gt;

&lt;p&gt;Finally, the typescript for our theoretical component would look something like this...notice in particular the way we handle the typing of iOS/tvOS and Android setup/view managers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;requireNativeComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ViewStyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UIManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;findNodeHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;useImperativeHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;forwardRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;SyntheticEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RNComponentManagerProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyntheticEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyntheticEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RNComponentProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViewStyle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;?():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RNComponentSetupManagerProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;param1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;param2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RNComponentSetupManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RNComponentViewManager&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RNComponentSetupManagerProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;NativeModules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RNComponentSetupManager&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;RNComponentSetupManagerProps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RNComponentViewManager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requireNativeComponent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RNComponentManagerProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RNComponentView&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RNComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;forwardRef&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RNComponentProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;extRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;useImperativeHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;start&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startFromManager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;UIManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchViewManagerCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;findNodeHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="c1"&gt;// @ts-ignore: Issue in RN ts defs &lt;/span&gt;
          &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;UIManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViewManagerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RNComponentView&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startFromManager&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kc"&gt;undefined&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stopFromManager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;UIManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchViewManagerCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;findNodeHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="c1"&gt;// @ts-ignore: Issue in RN ts defs &lt;/span&gt;
          &lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;UIManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViewManagerConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RNComponentView&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Commands&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stopFromManager&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="kc"&gt;undefined&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getErrorString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RNComponentViewManager&lt;/span&gt;
        &lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
        &lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
        &lt;span class="nx"&gt;onReady&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="na"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyntheticEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onReady&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onReady&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SyntheticEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getErrorString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;RNComponent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RNComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;RNComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cocoapods tips
&lt;/h3&gt;

&lt;p&gt;As well as being aware of general iOS/tvOS and Android best practices it is as well to be aware of the power of Cocoapods on the iOS/tvOS side. Your iOS/tvOS project will already have a .podfile and you will occasionally have cause to make some specific changes here that can solve issues you may be having.&lt;/p&gt;

&lt;h4&gt;
  
  
  User defined build types
&lt;/h4&gt;

&lt;p&gt;You may find yourself in the position of having to integrate several different types of third-party tools in order to get a plugin up and running. This may mean that you need to mix dynamic and/or static frameworks and/or libraries in your project. Unfortunately, there is no support for this at present in Cocopods. But, fortunately, there is &lt;a href="https://github.com/joncardasis/cocoapods-user-defined-build-types" rel="noopener noreferrer"&gt;a Cocopods plugin&lt;/a&gt; available that will allow you to do just this! This means you can write things like this in your pod file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugin 'cocoapods-user-defined-build-types'

enable_user_defined_build_types!

target "PlayerApp" do
  pod 'PlayerUI', :build_type =&amp;gt; :static_framework
  pod 'Player', :build_type =&amp;gt; :static_framework
  pod "SwiftyJSON", :build_type =&amp;gt; :dynamic_framework
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Combining package managers
&lt;/h4&gt;

&lt;p&gt;Occasionally you might end up being forced to use both Cocoapods and another package manager, such as Carthage. It's not actually that complicated but you can get some confusing error messages along the way. &lt;/p&gt;

&lt;p&gt;To take Carthage as an example, you can simply set up your Carthage installation in the iOS root directory of the generated plugin according to the standard way of working for Carthage, but be sure to (in your .podspec):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;exclude certain Carthage files such as:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exclude_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ios/Carthage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ios/cartfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ios/Cartfile.resolved"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ios/carthage.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Make sure that the generated frameworks are indicated in your .podspec:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vendored_frameworks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ios/Frameworks/Player.xcframework"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Here we have copied the Carthage framework from the build directory to a Frameworks directory in our plugin).&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbomodules
&lt;/h2&gt;

&lt;p&gt;There are changes coming in React Native that will have a big impact on how to implement native modules...in fact, these changes are already in place to an extent that it is possible to build modules using this technology. At React Native EU this year, a &lt;a href="https://www.youtube.com/watch?v=bT4VL8t2JBM&amp;amp;list=PLZ3MwD-soTTG-8Ix3lQ8zHvk94juXpYjl&amp;amp;index=18" rel="noopener noreferrer"&gt;talk was given&lt;/a&gt; that described a camera library that utilises the power of JSI. This gives some insight into how turbo modules will work when they are released. This promises great things for the future of React Native modules.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team" rel="noopener noreferrer"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>kotlin</category>
      <category>swift</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Working with VideoToolbox for more control over video encoding and decoding - Part 2.</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 27 Aug 2021 15:49:16 +0000</pubDate>
      <link>https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-part-2-1b5k</link>
      <guid>https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-part-2-1b5k</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-6n1"&gt;last article&lt;/a&gt;, I presented the structure of a MacOS app to encode and decode video using VideoToolbox. This time, I would like to focus on the actual encoding process. I will also look at ways to improve on and restructure the existing code, bearing in mind the various ways you could build such a project.&lt;/p&gt;

&lt;p&gt;As we left the project in the last article, we had a working macOS app that encoded video from the camera and then sent it off directly to be encoded. In that naive implementation, we are essentially doing the same thing as - or even less than, really - what AVFoundation can do for you. AVFoundation provides access to hardware-accelerated compression and decompression by default. What you don't get there, though, is the ability to fine-tune and customise the encoding and decoding. That is the whole point of VideoToolbox. So let's see how to access the details of the encoding process. The changes I have made in conjunction with this article were merged in from my branch &lt;code&gt;encoder-improvements&lt;/code&gt;. Once again, I have referred to existing projects for some of the approach I have taken. In this case, I have updated the encoder code in &lt;a href="https://github.com/Dcell/iOSH264"&gt;this iOS project&lt;/a&gt; from Objective-C to Swift 5.&lt;/p&gt;

&lt;p&gt;The first improvement to make is to set up our encoder before we actually start using it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func prepareToEncodeFrames() {
        let encoderSpecification = [
            kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder: true as CFBoolean
        ] as CFDictionary
        let status = VTCompressionSessionCreate(allocator: kCFAllocatorDefault, width: self.width, height: self.height, codecType: kCMVideoCodecType_H264, encoderSpecification: encoderSpecification, imageBufferAttributes: nil, compressedDataAllocator: nil, outputCallback: outputCallback, refcon: Unmanaged.passUnretained(self).toOpaque(), compressionSessionOut: &amp;amp;session)
        print("H264Coder init \(status == noErr) \(status)")
        // This demonstrates setting a property after the session has been created
        guard let compressionSession = session else { return }
        VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanTrue)
        VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_ProfileLevel, value: kVTProfileLevel_H264_Main_AutoLevel)
        VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_AllowFrameReordering, value: kCFBooleanFalse)
        VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_ExpectedFrameRate, value: CFNumberCreate(kCFAllocatorDefault, CFNumberType.intType, &amp;amp;self.fps))
        VTCompressionSessionPrepareToEncodeFrames(compressionSession)
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are wrapping the method &lt;code&gt;VTCompressionSessionPrepareToEncodeFrames(compressionSession)&lt;/code&gt; which you can read more about in the &lt;a href="https://developer.apple.com/documentation/videotoolbox/1428283-vtcompressionsessionpreparetoenc/"&gt;Apple docs&lt;/a&gt;.&lt;br&gt;
We've also taken the opportunity to set some session properties before we begin encoding. Most are set inline here, but frames-per-second is an example of a property exposed in the class, set in appDelegate when creating the encoder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Create encoder here (at the expense of dynamic setting of height and width)
    encoder = H264Encoder(width: 1280, height: 720, callback: { encodedBuffer in
      // self.sampleBufferNoOpProcessor(encodedBuffer) // Logs the buffers to the console for inspection
      // self.decodeCompressedFrame(encodedBuffer) // uncomment to see decoded video
    })
    encoder?.delegate = self
    encoder?.fps = 15
    encoder?.prepareToEncodeFrames()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(As commented here, in the previous code we could dynamically set the width and height based on the incoming buffer of data. I sacrificed that here for the sake of other demonstrations, but you may need to find a way to keep that functionality in another application.)&lt;/p&gt;

&lt;p&gt;I want to receive the compressed data in my appDelegate, so I can do something with it later. To this end I created an extension for two delegate functions I created in the encoder. First, the encoder protocol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol H264EncoderDelegate: AnyObject {
    func dataCallBack(_ data: Data!, frameType: FrameType)
    func spsppsDataCallBack(_ sps:Data!, pps: Data!)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extension AppDelegate : H264EncoderDelegate {
    func dataCallBack(_ data: Data!, frameType: FrameType) {
        let byteHeader:[UInt8] = [0,0,0,1]
        var byteHeaderData = Data(byteHeader)
        byteHeaderData.append(data)
        // Could decode here
        // H264Decoder.decode(byteHeaderData)
    }

    func spsppsDataCallBack(_ sps: Data!, pps: Data!) {
        let spsbyteHeader:[UInt8] = [0,0,0,1]
        var spsbyteHeaderData = Data(spsbyteHeader)
        var ppsbyteHeaderData = Data(spsbyteHeader)
        spsbyteHeaderData.append(sps)   
        ppsbyteHeaderData.append(pps)
        // Could decode here
        // H264Decoder.decode(spsbyteHeaderData)
        // H264Decoder.decode(ppsbyteHeaderData)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll discuss those byte headers shortly ;)&lt;/p&gt;

&lt;p&gt;While we're there, to keep things tidy, we may as well make the existing AVManagerDelegate into an extension too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// MARK: - AVManagerDelegate
extension AppDelegate : AVManagerDelegate {
    func onSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
        cameraView.render(sampleBuffer)
        encoder?.encode(sampleBuffer)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short, we are going to keep the existing buffer-to-buffer encoding for now and extend the callback method such that it will also call the above callbacks each time. And just to keep an eye on the overall plan, what we are doing here is preparing (encoding) the data as an elementary stream. The data we receive in our sampleBuffer is in AVCC format, whereas the format we want out is an elementary stream in the so-called Annex B format. Everything we do in the callback has to do with converting from the AVCC format to the Annex B format, while allowing us to tweak the details of that process in various ways.&lt;/p&gt;

&lt;p&gt;If a sample buffer contains a keyframe we also know it will contain data describing how the decoder should handle these frames when it receives them.&lt;/p&gt;

&lt;p&gt;So the first part of our callback looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let outputCallback: VTCompressionOutputCallback = { refcon, sourceFrameRefCon, status, infoFlags, sampleBuffer in
        guard let refcon = refcon,
              status == noErr,
              let sampleBuffer = sampleBuffer else {
            print("H264Coder outputCallback sampleBuffer NULL or status: \(status)")
            return
        }

        if (!CMSampleBufferDataIsReady(sampleBuffer))
        {
            print("didCompressH264 data is not ready...");
            return;
        }
        let encoder: H264Encoder = Unmanaged&amp;lt;H264Encoder&amp;gt;.fromOpaque(refcon).takeUnretainedValue()
        if(encoder.shouldUnpack) {
            var isKeyFrame:Bool = false

    //      Attempting to get keyFrame
            guard let attachmentsArray:CFArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, createIfNecessary: false) else { return }
            if (CFArrayGetCount(attachmentsArray) &amp;gt; 0) {
                let cfDict = CFArrayGetValueAtIndex(attachmentsArray, 0)
                let dictRef: CFDictionary = unsafeBitCast(cfDict, to: CFDictionary.self)

                let value = CFDictionaryGetValue(dictRef, unsafeBitCast(kCMSampleAttachmentKey_NotSync, to: UnsafeRawPointer.self))
                if(value == nil) {
                    isKeyFrame = true
                }
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note the encoder property shouldUnpack, which simply wraps all this unpacking code in an if-statement so you can activate it as required). &lt;br&gt;
The callback receives a sample buffer on each call, which we need to check is ready to be processed. Next we need to know what kind of data is in the buffer. To do this, we need to take a look at what are known as "attachments" on the buffer - essentially an array of dictionaries providing information about the data. You can see a list of the many attachment keys used &lt;a href="https://developer.apple.com/documentation/coremedia/cmsamplebuffer/sample_attachment_keys"&gt;here&lt;/a&gt;. The one we need is &lt;code&gt;kCMSampleAttachmentKey_NotSync&lt;/code&gt;, the &lt;em&gt;absence of which&lt;/em&gt; indicates that the data we are dealing with is a keyframe. (And as you can see, it can get a bit messy working with CFDictionaries in Swift...)&lt;/p&gt;

&lt;p&gt;So to the data contained in a keyframe sample data; once we know that we are dealing with a keyframe, we can extract two sets of data from our buffer. These are the Sequence Parameter Set and the Picture Parameter Set. A buffer with a keyframe will have both of these sets of data.&lt;/p&gt;

&lt;p&gt;This is how we go about extracting these and sending them to the SPS and PPS callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            if(isKeyFrame) {
                var description: CMFormatDescription = CMSampleBufferGetFormatDescription(sampleBuffer)!
                // First, get SPS
                var sparamSetCount: size_t = 0
                var sparamSetSize: size_t = 0
                var sparameterSetPointer: UnsafePointer&amp;lt;UInt8&amp;gt;?
                var statusCode: OSStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description, parameterSetIndex: 0, parameterSetPointerOut: &amp;amp;sparameterSetPointer, parameterSetSizeOut: &amp;amp;sparamSetSize, parameterSetCountOut: &amp;amp;sparamSetCount, nalUnitHeaderLengthOut: nil)

                if(statusCode == noErr) {
                    // Then, get PPS
                    var pparamSetCount: size_t = 0
                    var pparamSetSize: size_t = 0
                    var pparameterSetPointer: UnsafePointer&amp;lt;UInt8&amp;gt;?
                    var statusCode: OSStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(description, parameterSetIndex: 0, parameterSetPointerOut: &amp;amp;pparameterSetPointer, parameterSetSizeOut: &amp;amp;pparamSetSize, parameterSetCountOut: &amp;amp;pparamSetCount, nalUnitHeaderLengthOut: nil)
                    if(statusCode == noErr) {
                        var sps = NSData(bytes: sparameterSetPointer, length: sparamSetSize)
                        var pps = NSData(bytes: pparameterSetPointer, length: pparamSetSize)
                        encoder.delegate?.spsppsDataCallBack(sps as Data, pps: pps as Data)
                    }
                }
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoder will know how to handle these correctly once it receives them (the idea being that we call the decoder from said callback, as indicated earlier). We can also come back to the byte headers I referred to earlier - for elementary streams, all so-called NAL units (each packet of data, basically) must begin with a byte header array of [0,0,0,1]. Thus, we prepend the data with that header.&lt;/p&gt;

&lt;p&gt;After that we can handle the actual image data, which we send to our callback with an indication whether it is a keyframe (a.k.a. PFrame) or not (Iframe):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            var dataBuffer: CMBlockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer)!
            var length: size_t = 0
            var totalLength: size_t = 0
            var bufferDataPointer: UnsafeMutablePointer&amp;lt;Int8&amp;gt;?
            var statusCodePtr: OSStatus = CMBlockBufferGetDataPointer(dataBuffer, atOffset: 0, lengthAtOffsetOut: &amp;amp;length, totalLengthOut: &amp;amp;totalLength, dataPointerOut: &amp;amp;bufferDataPointer)
            if(statusCodePtr == noErr) {
                var bufferOffset: size_t = 0
                let AVCCHeaderLength: Int = 4
                while(bufferOffset &amp;lt; totalLength - AVCCHeaderLength) {
                    // Read the NAL unit length
                    var NALUnitLength: UInt32 = 0
                    memcpy(&amp;amp;NALUnitLength, bufferDataPointer! + bufferOffset, AVCCHeaderLength)
                    //Big-Endian to Little-Endian
                    NALUnitLength = CFSwapInt32BigToHost(NALUnitLength)

                    var data = NSData(bytes:(bufferDataPointer! + bufferOffset + AVCCHeaderLength), length: Int(Int32(NALUnitLength)))
                    var frameType: FrameType = .FrameType_PFrame
                    var dataBytes = Data(bytes: data.bytes, count: data.length)
                    if((dataBytes[0] &amp;amp; 0x1F) == 5) {
                        // I-Frame
                        print("is IFrame")
                        frameType = .FrameType_IFrame
                    }

                    encoder.delegate?.dataCallBack(data as Data, frameType: frameType)
                    // Move to the next NAL unit in the block buffer
                    bufferOffset += AVCCHeaderLength + size_t(NALUnitLength);
                }
            }
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few important details here. When building an elementary stream, we need to include the length of each packet as a requirement. So we need to get that length - see the comment "Read the NAL unit length" in the above code to see how we do that with memcpy...then we also need to convert the data from Big-endian to Little-endian - for which Core Foundation provides the method &lt;code&gt;CFSwapInt32BigToHost(NALUnitLength)&lt;/code&gt;. Further down (the last line) we can use that length to move to the next NAL unit in the buffer.&lt;/p&gt;

&lt;p&gt;One final detail is the analysis of the dataBytes variable as a convenient way top know if we are dealing with an iframe or not - info that we use to update the frameType variable with for use in the dataCallback. &lt;/p&gt;

&lt;p&gt;So at this stage, we have acquired everything we need to process the elementary stream of data. We could send this data to a decoder of our choice now. In the next article we will decode the data using an updated version of our decoder class. Part 3 is on its way...&lt;/p&gt;

&lt;p&gt;Those of you keen to dig deeper may find &lt;a href="https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream/24890903#24890903"&gt;this SO discussion&lt;/a&gt; and &lt;a href="https://stackoverflow.com/questions/28396622/extracting-h264-from-cmblockbuffer"&gt;this one&lt;/a&gt; useful.&lt;/p&gt;

&lt;p&gt;Here, again, is &lt;a href="https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-6n1"&gt;a link to the previous article&lt;/a&gt; on this subject.&lt;/p&gt;

&lt;p&gt;The repository accompanying this post is available &lt;a href="https://github.com/Eyevinn/VideoToolboxMacOSExample"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>videotoolbox</category>
      <category>swift</category>
      <category>avfoundation</category>
      <category>macos</category>
    </item>
    <item>
      <title>Working with VideoToolbox for more control over video encoding and decoding.</title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Fri, 20 Aug 2021 09:18:51 +0000</pubDate>
      <link>https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-6n1</link>
      <guid>https://dev.to/video/working-with-videotoolbox-for-more-control-over-video-encoding-and-decoding-6n1</guid>
      <description>&lt;p&gt;Apple's framework VideoToolbox has been around for many years now. This year it had an update to handle low-latency encoding, among other things, so it's certainly worth getting familiar with the framework both generally and in order to make use of the newer features. Unfortunately, there are not a great number of examples on how to do this and the official documentation is not much more than a collection of header files. Hopefully we can provide some useful examples in this and future articles.&lt;/p&gt;

&lt;p&gt;VideoToolbox is a fairly low-level framework, positioned as it is below AVKit and AVFoundation but on top of CoreMedia and CoreVideo. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatrg139ux521vqv100lk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatrg139ux521vqv100lk.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can do many things with both AVKit and AVFoundation. AVKit provides a lot of functionality at the view level. AVFoundation can also encode and decode streams of compressed video data (in the example accompanying this post, there is a display view based on AVFoundation's &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt;). Sometimes, however, you will want greater control over the processes of compression and decompression. This is where VideoToolbox can be useful.&lt;/p&gt;

&lt;p&gt;As an introduction to VideoToolbox, I will walk through the process of building a simple camera capture view that can display the captured data using either a Metal view layer or an &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt;. The captured data is then also encoded with the help of VideoToolbox. Once a stream of data is being produced in this way, a next step would be to use that stream in some fashion - streaming it over a network, for example. And on the receiving side, VideoToolbox can then decode the data for display on that device. In fact, there is an example of the decoding process included in the code, which simply takes the encoded buffers and decodes them immediately. Not a realistic example, but useful as a reference.&lt;/p&gt;

&lt;p&gt;Credit where it's due before we go further; I based my code on &lt;a href="https://github.com/standinga/VideoToolboxStoreFrames" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; which I initially forked and adjusted to taste. I've since rebuilt the project from scratch but the basic inspiration for the code remains. There is a useful &lt;a href="https://developer.apple.com/videos/play/wwdc2014/513/" rel="noopener noreferrer"&gt;WWDC video on VideoToolbox&lt;/a&gt; (seven years old now!) and the more recent one on low-latency encoding can be found &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10158/" rel="noopener noreferrer"&gt;here&lt;/a&gt; . I will keep the descriptions here mostly to the VideoToolbox-related code and just provide a high-level description of the steps. The accompanying code should suffice as a more detailed example.&lt;/p&gt;

&lt;p&gt;This project can be built using SwiftUI, so the first step is to create a new Xcode macOS app project selecting SwiftUI as the interface, but AppKit App Delegate for the Life cycle option. If you're taking the step-by-step approach, you can then delete the generated ContentView as we will be using our own &lt;code&gt;VideoView.swift&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct VideoView: View {

    let displayView = RenderViewRep() // Metal or AVDisplayViewRep to use AVSampleBufferDisplayLayer
    // let displayView = AVDisplayViewRep()

    var body: some View {
        displayView
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }

    func render(_ sampleBuffer: CMSampleBuffer)  {
        displayView.render(sampleBuffer)
    }
}

struct CameraView_Previews: PreviewProvider {
    static var previews: some View {
        VideoView()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that we have the option to use either a metal layer or an &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt;, both are available in the sample code. We use this &lt;code&gt;VideoView&lt;/code&gt; in our &lt;code&gt;AppDelegate&lt;/code&gt; and create a window in which to use the view, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let cameraView = VideoView()
var cameraWindow: NSWindow!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // Create the windows and set the content view.
    cameraWindow = NSWindow(
        contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false)
    cameraWindow.isReleasedWhenClosed = false
    cameraWindow.center()
    cameraWindow.setTitleWithRepresentedFilename("Camera view")
    cameraWindow.contentView = NSHostingView(rootView: cameraView)
    cameraWindow.makeKeyAndOrderFront(nil)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we set the &lt;code&gt;contentView&lt;/code&gt; to a &lt;code&gt;NSHostingView&lt;/code&gt; in order to use SwiftUI.&lt;/p&gt;

&lt;p&gt;To handle the camera capture, we can build an &lt;code&gt;AVManager&lt;/code&gt; class that implements &lt;code&gt;AVCaptureVideoDataOutputSampleBufferDelegate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import AVFoundation

protocol AVManagerDelegate: AnyObject {

    func onSampleBuffer(_ sampleBuffer: CMSampleBuffer)

}

class AVManager: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {

    // MARK: - Properties

    weak var delegate: AVManagerDelegate?

    var session: AVCaptureSession!

    private let sessionQueue = DispatchQueue(label:"se.eyevinn.sessionQueue")

    private let videoQueue = DispatchQueue(label: "videoQueue")

    private var connection: AVCaptureConnection!

    private var camera: AVCaptureDevice?

    func start() {
        requestCameraPermission { [weak self] granted in
            guard granted else {
                print("no camera access")
                return
            }
            self?.setupCaptureSession()
        }
    }

    private func setupCaptureSession() {
        sessionQueue.async {
            let deviceTypes: [AVCaptureDevice.DeviceType] = [
                .builtInWideAngleCamera
            ]
            let discoverySession = AVCaptureDevice.DiscoverySession(
                deviceTypes: deviceTypes,
                mediaType: .video,
                position: .front)
            let session = AVCaptureSession()
            session.sessionPreset = .vga640x480

            guard let camera = discoverySession.devices.first,
                  let format = camera.formats.first(where: {
                    let dimens = CMVideoFormatDescriptionGetDimensions($0.formatDescription)
                    return dimens.width * dimens.height == 640 * 480
                  }) else { fatalError("no camera of camera format") }

            do {
                try camera.lockForConfiguration()
                camera.activeFormat = format
                camera.unlockForConfiguration()
            } catch {
                print("failed to set up format")
            }
            self.camera = camera

            session.beginConfiguration()
            do {
                let videoIn = try AVCaptureDeviceInput(device: camera)
                if session.canAddInput(videoIn) {
                    session.addInput(videoIn)
                } else {
                    print("failed to add video input")
                    return
                }
            } catch {
                print("failed to initialized video input")
                return
            }

            let videoOut = AVCaptureVideoDataOutput()
            videoOut.videoSettings = [ kCVPixelBufferPixelFormatTypeKey as String:  Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
            ]
            videoOut.alwaysDiscardsLateVideoFrames = true
            videoOut.setSampleBufferDelegate(self, queue: self.videoQueue)
            if session.canAddOutput(videoOut) {
                session.addOutput(videoOut)
            } else {
                print("failed to add video output")
                return
            }
            session.commitConfiguration()
            session.startRunning()
            self.session = session
        }
    }

    private func requestCameraPermission(handler: @escaping (Bool) -&amp;gt; Void) {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
            case .authorized: // The user has previously granted access to the camera.
                handler(true)

            case .notDetermined: // The user has not yet been asked for camera access.
                AVCaptureDevice.requestAccess(for: .video) { granted in
                    handler(granted)
                }

            case .denied, .restricted: // The user can't grant access due to restrictions.
                handler(false)
        @unknown default:
            fatalError()
        }
    }

    // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate

    func captureOutput(_ output: AVCaptureOutput,
                       didOutput sampleBuffer: CMSampleBuffer,
                       from connection: AVCaptureConnection) {
        delegate?.onSampleBuffer(sampleBuffer)
    }

    // MARK: - Types

    enum AVError: Error {

        case noCamera
        case cameraAccess

    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means, by the way, that we will need to add an entitlement to our entitlements file for &lt;code&gt;Camera&lt;/code&gt; set to &lt;code&gt;YES&lt;/code&gt;, and we will also need to add the usual usage text for camera to the &lt;code&gt;info.plist&lt;/code&gt; file, stating the reason why we need to use the camera.&lt;/p&gt;

&lt;p&gt;Notice the method &lt;code&gt;onSampleBuffer&lt;/code&gt;, where we render the current buffer to the camera view and then send it on to the encoder to compress the data according to our needs. The encoder in this case is the &lt;code&gt;H264Encoder&lt;/code&gt; class. The buffers we are working with here are, as you can see, &lt;code&gt;CMSampleBuffer&lt;/code&gt;s. These are typically, but not necessarily, compressed sample data. In the case of the camera capture output they are CoreVideo pixel buffers, but these are compressed after running through the &lt;code&gt;H264Encoder&lt;/code&gt;. Taking a look at the &lt;code&gt;H264Encoder&lt;/code&gt; class, this is where VideoToolbox is used. To encode we need to create a "compression session" using VideoToolbox's &lt;code&gt;VTCompressionSessionCreate&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    init(width: Int32, height: Int32, callback: @escaping (CMSampleBuffer) -&amp;gt; Void) {
        self.callback = callback
      let encoderSpecification = [
          kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder: true as CFBoolean
      ] as CFDictionary
        let status = VTCompressionSessionCreate(allocator: kCFAllocatorDefault, width: width, height: height, codecType: kCMVideoCodecType_H264, encoderSpecification: encoderSpecification, imageBufferAttributes: nil, compressedDataAllocator: nil, outputCallback: outputCallback, refcon: Unmanaged.passUnretained(self).toOpaque(), compressionSessionOut: &amp;amp;session)
        print("H264Coder init \(status == noErr) \(status)")
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the session, of type &lt;code&gt;VTCompressionSession&lt;/code&gt;, is a member of the &lt;code&gt;H264Encoder&lt;/code&gt; class. We can pass in an &lt;code&gt;encoderSpecification&lt;/code&gt; on creation - this gives us control over hardware acceleration and, in the latest update, low-latency control. The init also assigns the necessary callback of type &lt;code&gt;VTCompressionOutputCallback&lt;/code&gt; which provides the compressed result. &lt;/p&gt;

&lt;p&gt;With these building blocks in place, we are able to capture camera output and encode the result. I have also included an example of setting properties on the compression session, after it has been created. In the example I indicate that the stream is in realtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        guard let compressionSession = session else { return }
        status = VTSessionSetProperty(compressionSession, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanTrue)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Next steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are additional methods available in VideoToolbox for preparing &lt;code&gt;CMSampleBuffer&lt;/code&gt;s for network transmission as NAL units (Network Abstraction Layer). This would be a good next step for this project, as would an investigation into the details of decoding sessions in a similar fashion. Watch this space...&lt;/p&gt;

&lt;p&gt;The repository accompanying this post is &lt;a href="https://github.com/Eyevinn/VideoToolboxMacOSExample" rel="noopener noreferrer"&gt;available here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Alan Allard is a developer at Eyevinn Technology, the European leading independent consultancy firm specializing in video technology and media distribution.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team" rel="noopener noreferrer"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>macos</category>
      <category>videotoolbox</category>
      <category>swift</category>
      <category>metal</category>
    </item>
    <item>
      <title>Introducing hls-truncate, the new tool in our collection of VOD manipulators! </title>
      <dc:creator>Alan Allard</dc:creator>
      <pubDate>Wed, 24 Feb 2021 10:17:46 +0000</pubDate>
      <link>https://dev.to/video/introducing-hls-truncate-the-new-tool-in-our-collection-of-vod-manipulators-6hp</link>
      <guid>https://dev.to/video/introducing-hls-truncate-the-new-tool-in-our-collection-of-vod-manipulators-6hp</guid>
      <description>&lt;p&gt;Over the last year or so, we at &lt;a href="https://www.eyevinntechnology.se"&gt;Eyevinn Technology&lt;/a&gt; have been developing a series of tools for the manipulation of HLS files. There is an increasing interest for presenting VOD content as live channels. That approach makes it possible for companies to provide themed channels and fixed schedules based on existing video material. Among other things, this can be a way to make use of older content in new packaging that interests consumers.&lt;/p&gt;

&lt;p&gt;We have built several open source tools to provide every aspect of delivering a livestream from pre-existing content. As we develop these tools further, we often see that a need arises for convenient helper utilities. A previous example was &lt;a href="https://github.com/Eyevinn/hls-repeat"&gt;hls-repeat&lt;/a&gt; which takes a HLS VOD as an input and outputs a new VOD that is repeated the required number of times. This can be useful when working dynamically with slates, for example, for display during schedule gaps. Another example, &lt;a href="https://github.com/Eyevinn/hls-splice"&gt;hls-splice&lt;/a&gt; is discussed in one of our &lt;a href="https://dev.to/video/aws-lambda-function-to-insert-pre-roll-ads-in-hls-stream-4gip"&gt;previous articles&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Last week we released &lt;a href="https://github.com/Eyevinn/hls-truncate"&gt;hls-truncate&lt;/a&gt;, a similar tool which also takes a HLS VOD as an input. In this case, it shortens the VOD in question to the nearest segment, providing that as the output. Again, this has proven to be useful for adjusting slate lengths on-the-fly for customers.&lt;/p&gt;

&lt;p&gt;Wherever one wishes to use a VOD, it's as simple as making a call to create the new VOD:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const hlsVod = new HLSTruncateVod('http://testcontent.eyevinn.technology/slates/30seconds/playlist.m3u8', 4);
hlsVod.load()
.then(() =&amp;gt; {
  const mediaManifest = hlsVod.getMediaManifest(3496000);
  console.log(mediaManifest);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all bandwidths are available at the truncated length. This all simplifies a code base considerably and has the added advantage of you knowing that a number of fiddly edge cases are taken care of and backed up by a good suite of tests, that can be developed in isolation over time.&lt;/p&gt;

&lt;p&gt;Read more on &lt;a href="https://github.com/Eyevinn/hls-truncate"&gt;our repo&lt;/a&gt;&lt;br&gt;
Release 0.1.0 is up on npm now: &lt;a href="https://www.npmjs.com/package/@eyevinn/hls-truncate"&gt;@eyevinn/hls-truncate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you need assistance in the development and implementation of this, our &lt;a href="https://video-dev.team"&gt;team of video developers&lt;/a&gt; are happy to help out. If you have any questions or comments just drop us a line in the comments section to this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vodtolive</category>
      <category>slate</category>
      <category>hls</category>
      <category>truncate</category>
    </item>
  </channel>
</rss>
