<?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>Forem: Javid Jamae</title>
    <description>The latest articles on Forem by Javid Jamae (@javidjamae).</description>
    <link>https://forem.com/javidjamae</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%2F3873707%2F97b6ee49-1448-4e30-b41a-14c1f4eec9cc.jpg</url>
      <title>Forem: Javid Jamae</title>
      <link>https://forem.com/javidjamae</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/javidjamae"/>
    <language>en</language>
    <item>
      <title>How to Use FFmpeg with Node.js (No Installation Required)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Sat, 02 May 2026 10:14:30 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-use-ffmpeg-with-nodejs-no-installation-required-3fl7</link>
      <guid>https://forem.com/javidjamae/how-to-use-ffmpeg-with-nodejs-no-installation-required-3fl7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/how-to-use-ffmpeg-with-nodejs" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need to process video in your Node.js app. Maybe you're building a SaaS that generates thumbnails, transcoding user uploads, or automating video workflows. The first thing you Google is "ffmpeg node.js." And that's where the pain starts.&lt;/p&gt;

&lt;p&gt;FFmpeg is the most capable video processing tool out there. But getting it to work reliably in a Node.js environment means dealing with binary dependencies, platform-specific builds, memory management, and deployment headaches. There are several ways to approach this, each with real tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using child_process.spawn with FFmpeg
&lt;/h2&gt;

&lt;p&gt;The most direct approach. Install FFmpeg on your system, then call it from Node.js:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spawn&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="s1"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ffmpeg&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i&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;input.mp4&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;-c:v&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;libx264&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;-crf&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;23&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;-preset&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;medium&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;-c:a&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;aac&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;-b:a&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;192k&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;output.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;ffmpeg&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="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&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;data&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;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;`FFmpeg: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&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;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&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;code&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;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;`FFmpeg exited with code &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine. But you're responsible for everything: installing FFmpeg on every machine that runs your code, parsing stderr for progress, handling errors when the binary isn't found, and making sure the right codecs are compiled in. On Docker, you're adding 80-200MB to your image just for the FFmpeg binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using fluent-ffmpeg in Node.js
&lt;/h2&gt;

&lt;p&gt;fluent-ffmpeg is the most popular Node.js wrapper for FFmpeg, with over 2 million weekly npm downloads. It gives you a chainable API instead of raw command-line arguments:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&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="s1"&gt;fluent-ffmpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.mp4&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="nf"&gt;videoCodec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;libx264&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="nf"&gt;audioCodec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aac&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="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1280x720&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="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&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="o"&gt;=&amp;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Done&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="nf"&gt;on&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;err&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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="nx"&gt;err&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.mp4&lt;/span&gt;&lt;span class="dl"&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 API is cleaner, but fluent-ffmpeg still requires FFmpeg installed on the host machine. It's a wrapper, not a replacement. You'll also run into maintenance concerns. The GitHub repo has hundreds of open issues and updates are infrequent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running FFmpeg in the Browser with ffmpeg.wasm
&lt;/h2&gt;

&lt;p&gt;ffmpeg.wasm compiles FFmpeg to WebAssembly, so it runs entirely in the browser or in Node.js without a native binary:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FFmpeg&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="s1"&gt;@ffmpeg/ffmpeg&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;ffmpeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FFmpeg&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i&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;input.mp4&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;-c:v&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;libx264&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;output.mp4&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No installation needed. But the tradeoffs are significant: processing speed is 10-20x slower than native FFmpeg, memory is limited (large files will crash the tab), and not all codecs are available in the WASM build. It works for lightweight client-side operations like trimming a clip or extracting a frame. Don't try to transcode a 2GB file with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing Video with a Cloud API (Zero Install)
&lt;/h2&gt;

&lt;p&gt;If you don't want to manage FFmpeg binaries at all, you can offload video processing to a cloud API. &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;FFmpeg Micro&lt;/a&gt; gives you full FFmpeg capabilities through simple HTTP requests:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.ffmpeg-micro.com/v1/transcodes&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Authorization&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;Bearer YOUR_API_KEY&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/input.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1080p&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="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; queued, status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No binary to install. No Docker image bloat. No memory management. You send a URL, pick your output format, and get results back. For advanced use cases, pass raw FFmpeg options instead of presets:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.ffmpeg-micro.com/v1/transcodes&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&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;Authorization&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;Bearer YOUR_API_KEY&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/input.mp4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&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="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-c:v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;libvpx-vp9&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="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-crf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30&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="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-b:v&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&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="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 API handles scaling, codec management, and infrastructure. You write a fetch call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Approach Should You Use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;child_process.spawn&lt;/strong&gt; if you need full control and you're comfortable managing FFmpeg installations across all your environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;fluent-ffmpeg&lt;/strong&gt; if you want a nicer API on top of child_process and don't mind the native FFmpeg dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ffmpeg.wasm&lt;/strong&gt; if you need client-side processing for small files and can accept slower performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cloud API like FFmpeg Micro&lt;/strong&gt; if you want zero operational overhead, your app processes video at any scale, or you're deploying to serverless environments where installing FFmpeg isn't practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls with FFmpeg in Node.js
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Binary not found errors.&lt;/strong&gt; The most common issue. Your code works locally but fails in CI or production because FFmpeg isn't in the PATH. With child_process and fluent-ffmpeg, always verify the binary exists before processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory spikes on large files.&lt;/strong&gt; Spawning FFmpeg as a child process for a 2GB video can spike your Node.js process memory. Stream the output instead of buffering it all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Codec mismatches.&lt;/strong&gt; Your local FFmpeg build might include libx265 but the production server's build doesn't. Always run &lt;code&gt;ffmpeg -codecs&lt;/code&gt; on your deployment target to confirm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent failures.&lt;/strong&gt; FFmpeg writes most of its output to stderr, not stdout. If you're only listening to stdout, you'll miss errors entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to install FFmpeg to use it with Node.js?
&lt;/h3&gt;

&lt;p&gt;It depends on the approach. child_process and fluent-ffmpeg both require FFmpeg installed on the machine. ffmpeg.wasm bundles a WebAssembly version so no install is needed, but it's 10-20x slower. Cloud APIs like FFmpeg Micro handle processing entirely server-side, so your Node.js app never touches FFmpeg.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is fluent-ffmpeg still maintained?
&lt;/h3&gt;

&lt;p&gt;fluent-ffmpeg has over 2 million weekly npm downloads, but the GitHub repository has limited recent activity and hundreds of open issues. It works for common use cases, but you may hit edge cases that won't get patched quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg in a serverless function like AWS Lambda?
&lt;/h3&gt;

&lt;p&gt;You can bundle a static FFmpeg binary in your deployment package. But Lambda functions have memory limits (256MB-10GB), execution time limits (15 minutes max), and cold start penalties from the large binary. A cloud API is a better fit for serverless architectures because you offload the heavy processing entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the fastest way to add video processing to a Node.js app?
&lt;/h3&gt;

&lt;p&gt;A cloud API gets you from zero to processing video in under 10 minutes. One fetch call. No installation, no configuration, no infrastructure. &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;FFmpeg Micro's free tier&lt;/a&gt; gives you enough processing time to build and test your integration. For a deeper look at FFmpeg concepts, check out the &lt;a href="https://www.ffmpeg-micro.com/training/learn-ffmpeg" rel="noopener noreferrer"&gt;Learn FFmpeg course&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>node</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Convert MP4 to GIF with FFmpeg (High Quality, Small File)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Fri, 01 May 2026 10:15:18 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-convert-mp4-to-gif-with-ffmpeg-high-quality-small-file-2251</link>
      <guid>https://forem.com/javidjamae/how-to-convert-mp4-to-gif-with-ffmpeg-high-quality-small-file-2251</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/convert-mp4-to-gif-ffmpeg" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Converting an MP4 to a GIF with FFmpeg sounds simple until you actually try it. The default output is either a blurry mess or a 50MB file that nobody wants to load. The trick is knowing which FFmpeg flags control quality and file size independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basic Command (and Why It Looks Bad)
&lt;/h2&gt;

&lt;p&gt;The most basic FFmpeg conversion looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 output.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This technically works. But the result will look terrible.&lt;/p&gt;

&lt;p&gt;GIFs are limited to 256 colors per frame. FFmpeg's default color selection doesn't pick the right 256 to keep, so you get banding, dithering artifacts, and washed-out colors. The file will also be massive because there's no compression optimization happening. A 10-second 1080p clip can easily produce a 100MB+ GIF.&lt;/p&gt;

&lt;h2&gt;
  
  
  High-Quality GIFs with Palette Generation
&lt;/h2&gt;

&lt;p&gt;The fix is FFmpeg's &lt;code&gt;palettegen&lt;/code&gt; and &lt;code&gt;paletteuse&lt;/code&gt; filters. This two-pass approach first analyzes your video to build an optimal 256-color palette, then converts using that palette instead of guessing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pass 1: Generate the optimal color palette&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"fps=10,scale=480:-1:flags=lanczos,palettegen"&lt;/span&gt; palette.png

&lt;span class="c"&gt;# Pass 2: Convert using that palette&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; palette.png &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse"&lt;/span&gt; output.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is dramatic. Colors stay vibrant, gradients are smooth, and dithering artifacts mostly disappear.&lt;/p&gt;

&lt;p&gt;What each flag does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fps=10&lt;/code&gt; reduces the frame rate from the source (usually 24-30fps) to 10fps, cutting file size significantly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scale=480:-1&lt;/code&gt; resizes to 480px wide while maintaining aspect ratio&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flags=lanczos&lt;/code&gt; uses Lanczos resampling for sharper downscaling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;palettegen&lt;/code&gt; analyzes all frames and builds the best 256-color palette&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paletteuse&lt;/code&gt; applies that palette during conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also do this in a single command using &lt;code&gt;split&lt;/code&gt; to avoid the intermediate palette file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v] fps=10,scale=480:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse"&lt;/span&gt; output.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same quality, one command, no temp files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling File Size
&lt;/h2&gt;

&lt;p&gt;GIF files get big fast. Three things control the size: duration, resolution, and frame rate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trim to a specific clip&lt;/strong&gt; with &lt;code&gt;-ss&lt;/code&gt; (start time) and &lt;code&gt;-t&lt;/code&gt; (duration):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:00:05 &lt;span class="nt"&gt;-t&lt;/span&gt; 3 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v] fps=10,scale=320:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse"&lt;/span&gt; output.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extracts 3 seconds starting at the 5-second mark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing reference:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;FPS&lt;/th&gt;
&lt;th&gt;5s clip&lt;/th&gt;
&lt;th&gt;10s clip&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;320px wide&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;~1-3MB&lt;/td&gt;
&lt;td&gt;~2-6MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;480px wide&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;~3-6MB&lt;/td&gt;
&lt;td&gt;~5-12MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;640px wide&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;~8-15MB&lt;/td&gt;
&lt;td&gt;~15-30MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1080px wide&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;~30-80MB&lt;/td&gt;
&lt;td&gt;~60-150MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For web use, 480px wide at 10fps is the sweet spot. Anything over 640px is usually overkill for a GIF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loop Control
&lt;/h2&gt;

&lt;p&gt;GIFs loop forever by default. Control this with the &lt;code&gt;-loop&lt;/code&gt; output option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Play 3 times then stop&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v] fps=10,scale=480:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse"&lt;/span&gt; &lt;span class="nt"&gt;-loop&lt;/span&gt; 3 output.gif

&lt;span class="c"&gt;# Play once, no loop&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v] fps=10,scale=480:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse"&lt;/span&gt; &lt;span class="nt"&gt;-loop&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; output.gif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-loop 0&lt;/code&gt; is infinite (the default). &lt;code&gt;-loop -1&lt;/code&gt; plays once. &lt;code&gt;-loop N&lt;/code&gt; plays N+1 times total.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The 256-color limit hits hardest on gradients and transitions.&lt;/strong&gt; If your video has rapid color changes between scenes, you'll see color flickering. Adding &lt;code&gt;dither=bayer:bayer_scale=5&lt;/code&gt; to the &lt;code&gt;paletteuse&lt;/code&gt; filter smooths this out: &lt;code&gt;paletteuse=dither=bayer:bayer_scale=5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GIF transparency is binary.&lt;/strong&gt; No semi-transparent pixels. If you're converting video with an alpha channel, expect hard edges instead of smooth blending.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio gets silently dropped.&lt;/strong&gt; GIFs don't support audio and FFmpeg won't warn you about it. If you need sound, stick with MP4 or WebM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping palette generation is the single biggest mistake.&lt;/strong&gt; The quality difference between a naive conversion and the palette method is night and day. Always use &lt;code&gt;palettegen&lt;/code&gt; + &lt;code&gt;paletteuse&lt;/code&gt; unless file size truly doesn't matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert MP4 to GIF with an API
&lt;/h2&gt;

&lt;p&gt;If you'd rather skip the FFmpeg installation and two-pass workflow, FFmpeg Micro converts videos to GIF with a single API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "gif",
    "options": [
      {"option": "-vf", "argument": "fps=10,scale=480:-1:flags=lanczos"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back a job ID, poll for completion, and download the result. No infrastructure to manage, no FFmpeg to install, no server resources to worry about when processing volume spikes.&lt;/p&gt;

&lt;p&gt;Trim the source video before conversion with input-level options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{
      "url": "https://example.com/video.mp4",
      "options": [
        {"option": "-ss", "argument": "5"},
        {"option": "-t", "argument": "3"}
      ]
    }],
    "outputFormat": "gif",
    "options": [
      {"option": "-vf", "argument": "fps=10,scale=480:-1:flags=lanczos"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg Micro handles the infrastructure and auto-scales with your workload. Pay per minute of video processed, with a free tier to start. &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;Create your free account to get an API key&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a deeper dive on FFmpeg encoding fundamentals, check out the &lt;a href="https://dev.to/training/learn-ffmpeg"&gt;Learn FFmpeg course&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the best resolution for a web GIF?
&lt;/h3&gt;

&lt;p&gt;480px wide at 10fps works for most use cases. Short clips stay under 5MB, which loads quickly on any connection. Drop to 320px if the GIF is for chat or messaging apps where bandwidth matters more than detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my FFmpeg GIF look grainy or washed out?
&lt;/h3&gt;

&lt;p&gt;You're probably running &lt;code&gt;ffmpeg -i input.mp4 output.gif&lt;/code&gt; without palette generation. FFmpeg's default color quantization produces poor results with only 256 colors to work with. The two-pass &lt;code&gt;palettegen&lt;/code&gt; and &lt;code&gt;paletteuse&lt;/code&gt; method described above fixes this completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I convert a GIF back to MP4 with FFmpeg?
&lt;/h3&gt;

&lt;p&gt;Yes. Run &lt;code&gt;ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p output.mp4&lt;/code&gt;. The MP4 will be much smaller because H.264 compression is far more efficient than GIF's LZW compression. Platforms like Twitter and Imgur convert uploaded GIFs to MP4 behind the scenes for this exact reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is animated WebP better than GIF?
&lt;/h3&gt;

&lt;p&gt;For most cases, yes. Animated WebP supports 24-bit color (16.7 million colors vs GIF's 256), produces smaller files at equivalent quality, and handles transparency with proper alpha blending. GIF's advantage is universal compatibility: it works in email clients, legacy browsers, and systems where WebP isn't supported.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make a GIF from just part of a video?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;-ss&lt;/code&gt; for the start time and &lt;code&gt;-t&lt;/code&gt; for duration before the input: &lt;code&gt;ffmpeg -ss 00:00:30 -t 5 -i input.mp4 output.gif&lt;/code&gt;. This grabs 5 seconds starting at the 30-second mark. Combine it with the palette method for the best quality.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>gif</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Merge and Concatenate Videos with FFmpeg</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Thu, 30 Apr 2026 10:10:54 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-merge-and-concatenate-videos-with-ffmpeg-259m</link>
      <guid>https://forem.com/javidjamae/how-to-merge-and-concatenate-videos-with-ffmpeg-259m</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-concat-merge-videos" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need to join two or more video files into one. Maybe you're stitching together clips from a multi-camera shoot, assembling a highlight reel, or building an automated video pipeline. FFmpeg can do it, but the syntax trips up even experienced developers.&lt;/p&gt;

&lt;p&gt;FFmpeg has three different ways to concatenate videos: the concat demuxer, the concat filter, and the concat protocol. Each one works differently, and picking the wrong one will give you corrupted output, missing audio, or a file that won't play. This guide covers all three methods, shows you when to use each, and includes working API examples so you can skip the CLI entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concat Demuxer (Same Codec, Fastest Method)
&lt;/h2&gt;

&lt;p&gt;The concat demuxer is the fastest way to merge videos because it doesn't re-encode. It copies the streams directly, which means zero quality loss and near-instant processing. The catch: all your input files need the same codec, resolution, and frame rate.&lt;/p&gt;

&lt;p&gt;Create a text file listing your inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# concat-list.txt&lt;/span&gt;
file &lt;span class="s1"&gt;'clip1.mp4'&lt;/span&gt;
file &lt;span class="s1"&gt;'clip2.mp4'&lt;/span&gt;
file &lt;span class="s1"&gt;'clip3.mp4'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-f&lt;/span&gt; concat &lt;span class="nt"&gt;-safe&lt;/span&gt; 0 &lt;span class="nt"&gt;-i&lt;/span&gt; concat-list.txt &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-c copy&lt;/code&gt; flag tells FFmpeg to copy streams without re-encoding. The &lt;code&gt;-safe 0&lt;/code&gt; flag allows absolute file paths. This approach processes a 10-minute video in seconds because it's just copying data.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the concat demuxer breaks
&lt;/h3&gt;

&lt;p&gt;If your clips have different codecs (one is H.264, another is H.265), different resolutions, or different audio sample rates, the demuxer will either fail or produce a broken file. You won't always get a clear error message. Sometimes the output just freezes at the transition point between clips.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concat Filter (Different Codecs or Resolutions)
&lt;/h2&gt;

&lt;p&gt;When your input files don't match, you need the concat filter. It re-encodes everything, which takes longer but handles mismatched inputs gracefully.&lt;/p&gt;

&lt;p&gt;Here's how to merge two videos with different codecs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip1.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip2.mov &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;n=2&lt;/code&gt; tells FFmpeg you have 2 segments. &lt;code&gt;v=1&lt;/code&gt; means one video stream per segment, &lt;code&gt;a=1&lt;/code&gt; means one audio stream. If your clips don't have audio, drop the audio parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip1.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip2.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v][1:v]concat=n=2:v=1[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Merging three or more videos
&lt;/h3&gt;

&lt;p&gt;Scaling beyond two clips follows the same pattern. For three clips with audio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip1.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip2.mov &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; clip3.avi &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Concat Protocol (MPEG-TS Only)
&lt;/h2&gt;

&lt;p&gt;The concat protocol is the simplest syntax but the most limited. It only works with MPEG-TS files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"concat:segment1.ts|segment2.ts|segment3.ts"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll rarely use this unless you're working with HLS segments or transport streams. For most video formats (MP4, MOV, WebM), use the demuxer or filter instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Method Should You Use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same codec, resolution, frame rate&lt;/td&gt;
&lt;td&gt;Concat demuxer&lt;/td&gt;
&lt;td&gt;No re-encoding, fastest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different codecs or resolutions&lt;/td&gt;
&lt;td&gt;Concat filter&lt;/td&gt;
&lt;td&gt;Handles mismatches, re-encodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need transitions (fade, slide)&lt;/td&gt;
&lt;td&gt;Concat filter + xfade&lt;/td&gt;
&lt;td&gt;Only filter supports transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MPEG-TS segments&lt;/td&gt;
&lt;td&gt;Concat protocol&lt;/td&gt;
&lt;td&gt;Simplest for transport streams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated pipeline&lt;/td&gt;
&lt;td&gt;FFmpeg Micro API&lt;/td&gt;
&lt;td&gt;No CLI, handles infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Concatenating Videos with the FFmpeg Micro API
&lt;/h2&gt;

&lt;p&gt;If you're building a pipeline that merges videos programmatically, you don't need to manage FFmpeg on a server. FFmpeg Micro is a cloud API that lets you add video processing to any app with a single HTTP call. No FFmpeg installation, no server management.&lt;/p&gt;

&lt;p&gt;The API accepts multiple inputs natively. Pass all your video URLs in the &lt;code&gt;inputs&lt;/code&gt; array and use &lt;code&gt;filters&lt;/code&gt; for the concat filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [
      {"url": "https://storage.example.com/clip1.mp4"},
      {"url": "https://storage.example.com/clip2.mp4"}
    ],
    "outputFormat": "mp4",
    "filters": [
      {"filter": "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]"}
    ],
    "options": [
      {"option": "-map", "argument": "[v]"},
      {"option": "-map", "argument": "[a]"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For three clips, add another input and adjust the filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [
      {"url": "https://storage.example.com/clip1.mp4"},
      {"url": "https://storage.example.com/clip2.mp4"},
      {"url": "https://storage.example.com/clip3.mov"}
    ],
    "outputFormat": "mp4",
    "filters": [
      {"filter": "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]"}
    ],
    "options": [
      {"option": "-map", "argument": "[v]"},
      {"option": "-map", "argument": "[a]"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API supports up to 10 input files per request. Check job status with &lt;code&gt;GET /v1/transcodes/{id}&lt;/code&gt; and download the result with &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls When Concatenating Videos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Resolution mismatch silently fails.&lt;/strong&gt; If you use the concat demuxer with files at different resolutions, the output might play fine in VLC but break on mobile or in browsers. Always check with &lt;code&gt;ffprobe -v error -select_streams v:0 -show_entries stream=width,height&lt;/code&gt; on each input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio channel mismatch causes sync drift.&lt;/strong&gt; One clip in stereo, another in mono. FFmpeg won't always warn you. The output audio can go out of sync after the first transition. Fix it by normalizing audio channels before concatenating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ac&lt;/span&gt; 2 &lt;span class="nt"&gt;-c&lt;/span&gt;:v copy normalized.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Missing audio stream crashes the concat filter.&lt;/strong&gt; If one clip has audio and another doesn't, the concat filter fails because it expects the same number of streams per segment. Either add a silent audio track to the video-only clip or use &lt;code&gt;v=1:a=0&lt;/code&gt; and handle audio separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container format matters.&lt;/strong&gt; MP4 puts the moov atom at the end by default. If your pipeline streams the concatenated output, add &lt;code&gt;-movflags +faststart&lt;/code&gt; to move the moov atom to the beginning so playback can start before the full download completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I merge MP4 and MOV files with FFmpeg?
&lt;/h3&gt;

&lt;p&gt;Yes. Use the concat filter (not the demuxer). The concat filter re-encodes both inputs into a common format, so different containers and codecs work fine. FFmpeg Micro handles this automatically when you pass multiple URLs in the &lt;code&gt;inputs&lt;/code&gt; array with a concat filter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the fastest way to join videos without re-encoding?
&lt;/h3&gt;

&lt;p&gt;The concat demuxer with &lt;code&gt;-c copy&lt;/code&gt; is the fastest method. It copies streams directly with zero quality loss. But all inputs must share the same codec, resolution, and frame rate. If they don't match, you'll need the concat filter, which re-encodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I add a fade transition between concatenated clips?
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;xfade&lt;/code&gt; filter instead of concat. For a 1-second crossfade between two 10-second clips: &lt;code&gt;[0:v][1:v]xfade=transition=fade:duration=1:offset=9[v]&lt;/code&gt;. FFmpeg supports over 30 transition types including fade, wipeleft, slidedown, and dissolve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does FFmpeg concat work with audio-only files?
&lt;/h3&gt;

&lt;p&gt;Yes. The concat demuxer and filter both work with audio files like MP3, AAC, and WAV. For the filter, use &lt;code&gt;a=1:v=0&lt;/code&gt; to specify audio-only concatenation. The demuxer approach with &lt;code&gt;-c copy&lt;/code&gt; works if all audio files share the same codec and sample rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many videos can I concatenate at once?
&lt;/h3&gt;

&lt;p&gt;FFmpeg itself has no hard limit. The concat demuxer handles hundreds of files via the file list. The concat filter gets complex beyond 5-10 inputs because you need to map every stream. FFmpeg Micro's API supports up to 10 inputs per request, which covers most production workflows.&lt;/p&gt;

&lt;p&gt;For a deeper dive on FFmpeg filters and encoding fundamentals, check out the &lt;a href="https://dev.to/training/learn-ffmpeg"&gt;Learn FFmpeg course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to concatenate videos programmatically without managing FFmpeg servers, &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;try FFmpeg Micro for free&lt;/a&gt;. Send a JSON request, get a merged video back.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Compress Video with FFmpeg Without Losing Quality</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Wed, 29 Apr 2026 10:15:29 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-compress-video-with-ffmpeg-without-losing-quality-i5a</link>
      <guid>https://forem.com/javidjamae/how-to-compress-video-with-ffmpeg-without-losing-quality-i5a</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-compress-video-without-losing-quality" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've got a 500MB video that needs to be 50MB. The catch? You can't make it look terrible. This is the most common FFmpeg problem developers run into, and the solution is simpler than you'd think.&lt;/p&gt;

&lt;p&gt;FFmpeg gives you precise control over the quality-to-size tradeoff through a setting called CRF (Constant Rate Factor). Get it right, and you'll cut file sizes by 50-80% with no visible quality loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is CRF and Why Does It Matter for Video Compression?
&lt;/h2&gt;

&lt;p&gt;CRF stands for Constant Rate Factor. It tells the encoder how much quality you're willing to sacrifice for a smaller file. Lower CRF = higher quality = bigger file. Higher CRF = lower quality = smaller file.&lt;/p&gt;

&lt;p&gt;For H.264 (libx264), the CRF scale runs from 0 (lossless) to 51 (worst). The sweet spot for most developers is &lt;strong&gt;CRF 18-28&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CRF 18&lt;/strong&gt;: Visually lossless. You won't see the difference from the original. Files are still large.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRF 23&lt;/strong&gt;: The default. Good balance between quality and size. Most projects should start here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRF 28&lt;/strong&gt;: Noticeable quality loss on close inspection, but perfectly fine for web delivery and social media.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every 6 CRF points roughly doubles or halves the file size. So going from CRF 18 to CRF 24 cuts your file size by about 75%.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Compress Video with FFmpeg Using CRF
&lt;/h2&gt;

&lt;p&gt;The basic command is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That takes your input video, re-encodes it with H.264 at CRF 23, and compresses the audio to 128kbps AAC. For most 1080p content, this drops a 500MB file down to roughly 50-100MB.&lt;/p&gt;

&lt;p&gt;Want better quality? Lower the CRF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-preset&lt;/span&gt; slow &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Presets control how much time FFmpeg spends optimizing compression. Slower presets produce smaller files at the same quality. In practice, &lt;code&gt;slow&lt;/code&gt; gives you about 5-10% better compression than &lt;code&gt;medium&lt;/code&gt; with a meaningful increase in encoding time.&lt;/p&gt;

&lt;h2&gt;
  
  
  H.264 vs H.265 vs AV1: Which Codec Should You Use?
&lt;/h2&gt;

&lt;p&gt;The codec you choose has a bigger impact on file size than almost any other setting.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codec&lt;/th&gt;
&lt;th&gt;FFmpeg encoder&lt;/th&gt;
&lt;th&gt;Size reduction vs H.264&lt;/th&gt;
&lt;th&gt;Browser support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;H.264&lt;/td&gt;
&lt;td&gt;libx264&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;td&gt;Universal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H.265&lt;/td&gt;
&lt;td&gt;libx265&lt;/td&gt;
&lt;td&gt;25-50% smaller&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AV1&lt;/td&gt;
&lt;td&gt;libsvtav1&lt;/td&gt;
&lt;td&gt;30-50% smaller&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Edge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;H.264 is the safe default. H.265 is great when you control playback. AV1 is the future but encoding is slow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two-Pass Encoding for Target File Size
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to hit a specific file size. Two-pass encoding analyzes the video first, then encodes with a target bitrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pass 1: Analyze&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-b&lt;/span&gt;:v 3200k &lt;span class="nt"&gt;-pass&lt;/span&gt; 1 &lt;span class="nt"&gt;-an&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; null /dev/null

&lt;span class="c"&gt;# Pass 2: Encode&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-b&lt;/span&gt;:v 3200k &lt;span class="nt"&gt;-pass&lt;/span&gt; 2 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CRF values mean different things for different codecs&lt;/li&gt;
&lt;li&gt;Hardware encoding trades quality for speed&lt;/li&gt;
&lt;li&gt;Don't re-encode already compressed video&lt;/li&gt;
&lt;li&gt;Container format matters (MP4 vs WebM vs MKV)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compress Video with the FFmpeg Micro API
&lt;/h2&gt;

&lt;p&gt;If you don't want to manage FFmpeg servers, &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;FFmpeg Micro&lt;/a&gt; handles compression with a single API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "mp4",
    "options": [
      {"option": "-c:v", "argument": "libx264"},
      {"option": "-crf", "argument": "23"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the full guide with more examples at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-compress-video-without-losing-quality" rel="noopener noreferrer"&gt;ffmpeg-micro.com/blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Trim and Cut Video with FFmpeg (CLI + API Guide)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Tue, 28 Apr 2026 10:13:06 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-trim-and-cut-video-with-ffmpeg-cli-api-guide-86e</link>
      <guid>https://forem.com/javidjamae/how-to-trim-and-cut-video-with-ffmpeg-cli-api-guide-86e</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-trim-cut-video" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need to extract a 30-second clip from a 2-hour video. Or trim the first 5 seconds of dead air. Or cut everything after the outro. FFmpeg handles all of this, but the syntax trips people up more than it should.&lt;/p&gt;

&lt;p&gt;This guide covers every way to trim and cut video with FFmpeg, from basic &lt;code&gt;-ss&lt;/code&gt;/&lt;code&gt;-t&lt;/code&gt; flags to keyframe-accurate cutting. Then we'll show how to do the same thing with a single API call using FFmpeg Micro.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Trim Video with FFmpeg Using -ss and -t
&lt;/h2&gt;

&lt;p&gt;The two core flags for trimming are &lt;code&gt;-ss&lt;/code&gt; (seek to a start time) and &lt;code&gt;-t&lt;/code&gt; (set the duration).&lt;/p&gt;

&lt;p&gt;To extract 30 seconds starting at the 1-minute mark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-t&lt;/span&gt; 00:00:30 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-ss 00:01:00&lt;/code&gt; seeks to 1 minute into the video&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-t 00:00:30&lt;/code&gt; limits the output to 30 seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c copy&lt;/code&gt; copies the streams without re-encoding (fast, but see the gotchas below)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use seconds instead of timestamps. This does the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 60 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FFmpeg -ss Before vs After -i (This Matters)
&lt;/h2&gt;

&lt;p&gt;Where you place &lt;code&gt;-ss&lt;/code&gt; relative to &lt;code&gt;-i&lt;/code&gt; changes how FFmpeg seeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before &lt;code&gt;-i&lt;/code&gt; (input seeking, fast):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg seeks to the nearest keyframe before your timestamp, then starts decoding. This is fast because it doesn't decode the entire file from the beginning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After &lt;code&gt;-i&lt;/code&gt; (output seeking, accurate):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg decodes from the start and discards frames until it hits your timestamp. Slower, but frame-accurate.&lt;/p&gt;

&lt;p&gt;For most use cases, put &lt;code&gt;-ss&lt;/code&gt; before &lt;code&gt;-i&lt;/code&gt;. It's fast and accurate enough. If you need frame-perfect cuts (like editing dialogue), put it after &lt;code&gt;-i&lt;/code&gt; and drop &lt;code&gt;-c copy&lt;/code&gt; so FFmpeg re-encodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Cut Video with FFmpeg Using -to
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;-to&lt;/code&gt; flag specifies an end timestamp instead of a duration. To extract from 1:00 to 1:30:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-to&lt;/span&gt; 00:01:30 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between &lt;code&gt;-t&lt;/code&gt; and &lt;code&gt;-to&lt;/code&gt;: &lt;code&gt;-t 30&lt;/code&gt; means "30 seconds of output." &lt;code&gt;-to 00:01:30&lt;/code&gt; means "stop at the 1:30 mark of the input." If you use &lt;code&gt;-ss&lt;/code&gt; with &lt;code&gt;-to&lt;/code&gt;, the &lt;code&gt;-to&lt;/code&gt; timestamp is relative to the seeked position when &lt;code&gt;-ss&lt;/code&gt; is before &lt;code&gt;-i&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream Copy vs Re-encode: The Trim Quality Tradeoff
&lt;/h2&gt;

&lt;p&gt;When you trim with &lt;code&gt;-c copy&lt;/code&gt;, FFmpeg doesn't re-encode. It just copies the compressed data. This is 10-100x faster, but there's a catch.&lt;/p&gt;

&lt;p&gt;Video is compressed in groups of pictures (GOPs), and each GOP starts with a keyframe. When you seek to a specific time, the nearest keyframe might be a few seconds earlier. With &lt;code&gt;-c copy&lt;/code&gt;, your trim point snaps to that keyframe, so the actual start time might not match your &lt;code&gt;-ss&lt;/code&gt; value exactly.&lt;/p&gt;

&lt;p&gt;If precise cuts matter, re-encode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This decodes and re-encodes every frame, giving you an exact cut at the cost of processing time and a slight quality change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use &lt;code&gt;-c copy&lt;/code&gt;:&lt;/strong&gt; Quick rough cuts, splitting long recordings, removing dead air at the start or end. Speed matters more than frame accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to re-encode:&lt;/strong&gt; Editing dialogue, creating clips for social media, any time a few extra frames at the start would look wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Gotchas When Trimming with FFmpeg
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Audio drift with stream copy.&lt;/strong&gt; Some containers store audio and video with slightly different timebase references. When you &lt;code&gt;-c copy&lt;/code&gt; a trim, the audio can shift by a fraction of a second relative to the video. Re-encoding fixes this, or you can re-encode just the audio: &lt;code&gt;-c:v copy -c:a aac&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container format matters.&lt;/strong&gt; MP4 and MKV handle seeking differently. MP4 stores its index (moov atom) either at the start or end of the file. If the moov atom is at the end, FFmpeg has to read the entire file before it can seek. Use &lt;code&gt;-movflags +faststart&lt;/code&gt; when outputting MP4 to move the moov atom to the front.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Negative timestamps.&lt;/strong&gt; When trimming with &lt;code&gt;-c copy&lt;/code&gt; and &lt;code&gt;-ss&lt;/code&gt; before &lt;code&gt;-i&lt;/code&gt;, FFmpeg might produce frames with negative timestamps. Add &lt;code&gt;-avoid_negative_ts make_zero&lt;/code&gt; to fix this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-ss&lt;/span&gt; 00:01:00 &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-avoid_negative_ts&lt;/span&gt; make_zero output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Duration mismatch with -to and -ss before -i.&lt;/strong&gt; When &lt;code&gt;-ss&lt;/code&gt; appears before &lt;code&gt;-i&lt;/code&gt;, the &lt;code&gt;-to&lt;/code&gt; timestamp is relative to the seeked position, not the original file. So &lt;code&gt;-ss 60 -to 90&lt;/code&gt; gives you 30 seconds, not the segment from 1:00 to 1:30 as you might expect. If that's confusing, just use &lt;code&gt;-t&lt;/code&gt; for duration instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Trim Video with FFmpeg Micro's API
&lt;/h2&gt;

&lt;p&gt;All the examples above work great locally, but running FFmpeg on a server means managing binaries, scaling infrastructure, and handling edge cases yourself. FFmpeg Micro is a cloud API that handles all of this with a single HTTP request.&lt;/p&gt;

&lt;p&gt;To trim a video to 30 seconds starting at the 1-minute mark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "mp4",
    "options": [
      {"option": "-ss", "argument": "00:01:00"},
      {"option": "-t", "argument": "30"},
      {"option": "-c", "argument": "copy"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass &lt;code&gt;-ss&lt;/code&gt; and &lt;code&gt;-t&lt;/code&gt; as input-level options to get the fast input-seeking behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{
      "url": "https://example.com/video.mp4",
      "options": [
        {"option": "-ss", "argument": "00:01:00"},
        {"option": "-t", "argument": "30"}
      ]
    }],
    "outputFormat": "mp4",
    "options": [
      {"option": "-c", "argument": "copy"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For frame-accurate trimming with re-encoding, drop the &lt;code&gt;-c copy&lt;/code&gt; and specify codecs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "mp4",
    "options": [
      {"option": "-ss", "argument": "00:01:00"},
      {"option": "-t", "argument": "30"},
      {"option": "-c:v", "argument": "libx264"},
      {"option": "-crf", "argument": "23"},
      {"option": "-c:a", "argument": "aac"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg Micro handles the infrastructure, scaling, and edge cases. You get the full power of FFmpeg's trimming capabilities without managing servers.&lt;/p&gt;

&lt;p&gt;For a deeper dive on FFmpeg encoding fundamentals, check out our &lt;a href="https://dev.to/training/learn-ffmpeg/transcode"&gt;Learn FFmpeg: Transcoding lesson&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between FFmpeg -t and -to?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;-t&lt;/code&gt; sets a duration (how long the output should be). &lt;code&gt;-to&lt;/code&gt; sets an end timestamp (where to stop). If you use &lt;code&gt;-ss 60 -t 30&lt;/code&gt;, you get 30 seconds of output starting at the 1-minute mark. If you use &lt;code&gt;-ss 60 -to 90&lt;/code&gt;, you also get 30 seconds, but &lt;code&gt;-to 90&lt;/code&gt; is interpreted relative to the seek position when &lt;code&gt;-ss&lt;/code&gt; is placed before &lt;code&gt;-i&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I trim video without re-encoding in FFmpeg?
&lt;/h3&gt;

&lt;p&gt;Yes. Use &lt;code&gt;-c copy&lt;/code&gt; to copy streams without re-encoding. This is fast but trims to the nearest keyframe, so the start point might be off by a few frames. For exact cuts, you need to re-encode with &lt;code&gt;-c:v libx264 -c:a aac&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I trim the last 10 seconds off a video with FFmpeg?
&lt;/h3&gt;

&lt;p&gt;First, get the total duration with &lt;code&gt;ffprobe -v error -show_entries format=duration -of csv=p=0 input.mp4&lt;/code&gt;. Then use &lt;code&gt;-t&lt;/code&gt; with the duration minus 10 seconds. There's no native "trim from end" flag in FFmpeg.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does FFmpeg Micro support -to for trimming?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro supports &lt;code&gt;-ss&lt;/code&gt; (seek) and &lt;code&gt;-t&lt;/code&gt; (duration) for trimming. The &lt;code&gt;-to&lt;/code&gt; flag is not currently in the supported options list. Use &lt;code&gt;-t&lt;/code&gt; with the desired duration instead, which achieves the same result.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I trim multiple sections from a video?
&lt;/h3&gt;

&lt;p&gt;You can't do it in a single FFmpeg command without &lt;code&gt;-filter_complex&lt;/code&gt;. The simplest approach: run separate trim commands for each section, then concatenate the results. With FFmpeg Micro, you'd make separate API calls for each segment.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Compress Video with FFmpeg API (No Server Required)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Mon, 27 Apr 2026 10:10:57 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-compress-video-with-ffmpeg-api-no-server-required-4jl5</link>
      <guid>https://forem.com/javidjamae/how-to-compress-video-with-ffmpeg-api-no-server-required-4jl5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-compress-video-api" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need to compress video in your app. Maybe users are uploading 500MB screen recordings. Maybe you're batch-processing marketing clips that need to be half their current size. Either way, you're staring down FFmpeg's compression flags and wondering how long this rabbit hole goes.&lt;/p&gt;

&lt;p&gt;It doesn't have to be a rabbit hole. You can compress video with a single API call, no FFmpeg binary on your server, no worker queues, no guessing at CRF values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Video Compression Gets Complicated Fast
&lt;/h2&gt;

&lt;p&gt;FFmpeg is the gold standard for video compression. But "gold standard" comes with baggage. You need to pick the right codec (libx264, libx265, libvpx-vp9), choose a CRF value that balances quality against file size, decide on a resolution, and handle edge cases like variable frame rates or audio stream mismatches.&lt;/p&gt;

&lt;p&gt;Then there's infrastructure. Running FFmpeg on a server means CPU-intensive processes that block other work. You either overprovision (expensive) or queue jobs and hope things don't back up. And if you're on a serverless platform like Vercel or Cloudflare Workers, you can't run FFmpeg at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compress Video with FFmpeg Micro's API
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that lets you compress video with a single HTTP request. No FFmpeg installation, no server management. You send the video URL, specify the output format and quality, and get back a compressed file.&lt;/p&gt;

&lt;p&gt;The simplest compression call looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/large-video.mp4"}],
    "outputFormat": "mp4",
    "preset": {"quality": "medium", "resolution": "1080p"}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The API returns a job ID, and your video gets compressed in the cloud. You poll for status or set up a webhook, then download the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality presets&lt;/strong&gt; map to FFmpeg CRF values under the hood: &lt;code&gt;low&lt;/code&gt; (CRF 28, smallest files), &lt;code&gt;medium&lt;/code&gt; (CRF 23, good balance), and &lt;code&gt;high&lt;/code&gt; (CRF 18, near-lossless). If you've ever spent an afternoon testing CRF values, you know how much time this saves.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Reduce Video File Size with Custom FFmpeg Options
&lt;/h2&gt;

&lt;p&gt;Presets cover 90% of use cases. But sometimes you need more control. Maybe you want VP9 encoding for WebM output, or you need a specific bitrate cap for streaming.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro's advanced mode lets you pass raw FFmpeg options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/large-video.mp4"}],
    "outputFormat": "webm",
    "options": [
      {"option": "-c:v", "argument": "libvpx-vp9"},
      {"option": "-crf", "argument": "30"},
      {"option": "-b:v", "argument": "0"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you full FFmpeg power without managing a single server. The API validates your options, runs the transcode on auto-scaling infrastructure, and handles cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compress Video in Python (No FFmpeg Install)
&lt;/h2&gt;

&lt;p&gt;If you're building in Python, the same API call works with &lt;code&gt;requests&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.ffmpeg-micro.com/v1/transcodes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/raw-upload.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resolution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;720p&lt;/span&gt;&lt;span class="sh"&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;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Job ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jobId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;subprocess.run(["ffmpeg", ...])&lt;/code&gt;. No checking if FFmpeg is installed on the container. No dealing with stdout parsing to track progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compress Video in Node.js Without a Server
&lt;/h2&gt;

&lt;p&gt;Same idea in Node.js with &lt;code&gt;fetch&lt;/code&gt;:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.ffmpeg-micro.com/v1/transcodes&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&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="s2"&gt;Bearer YOUR_API_KEY&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="s2"&gt;Content-Type&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="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/raw-upload.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1080p&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="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;`Job ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works in Next.js API routes, Express servers, Deno, Bun, or any environment that supports &lt;code&gt;fetch&lt;/code&gt;. No native dependencies to install.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Videos Before Compression
&lt;/h2&gt;

&lt;p&gt;If your video isn't already hosted at a public URL, upload it first using the three-step upload flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get a presigned URL:&lt;/strong&gt; &lt;code&gt;POST /v1/upload/presigned-url&lt;/code&gt; with &lt;code&gt;filename&lt;/code&gt;, &lt;code&gt;contentType&lt;/code&gt;, and &lt;code&gt;fileSize&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload the file:&lt;/strong&gt; &lt;code&gt;PUT&lt;/code&gt; the file bytes to the returned &lt;code&gt;uploadUrl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm the upload:&lt;/strong&gt; &lt;code&gt;POST /v1/upload/confirm&lt;/code&gt; with &lt;code&gt;filename&lt;/code&gt; and &lt;code&gt;fileSize&lt;/code&gt; to get back a &lt;code&gt;fileUrl&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use that &lt;code&gt;fileUrl&lt;/code&gt; as the input URL in your transcode request. The whole flow takes four HTTP calls total: presigned URL, upload, confirm, transcode.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFmpeg Video Compression API vs. Self-Hosted FFmpeg
&lt;/h2&gt;

&lt;p&gt;Running FFmpeg yourself means managing servers, scaling workers, and debugging codec issues at 3am. FFmpeg Micro handles all of that. You pay per minute of video processed, starting with a free tier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Self-Hosted FFmpeg&lt;/th&gt;
&lt;th&gt;FFmpeg Micro API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;Hours to days&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Your servers, your problem&lt;/td&gt;
&lt;td&gt;Auto-scaling cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codec updates&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Fixed server costs&lt;/td&gt;
&lt;td&gt;Pay per use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Manual provisioning&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For teams processing fewer than 10,000 videos a month, the API approach is almost always cheaper than running your own infrastructure. And you skip the maintenance entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I compress video without installing FFmpeg?
&lt;/h3&gt;

&lt;p&gt;Yes. FFmpeg Micro is a cloud API that runs FFmpeg on managed infrastructure. You send an HTTP request with your video URL and compression settings, and get back a compressed file. No local FFmpeg installation needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What video formats does FFmpeg Micro support for compression?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro supports MP4, WebM, AVI, MOV, MKV, and FLV for video output. Audio formats include MP3, M4A, AAC, WAV, OGG, Opus, and FLAC. You can convert between formats and compress in the same request.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I choose the right compression quality?
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;quality&lt;/code&gt; preset: &lt;code&gt;low&lt;/code&gt; for maximum compression (smallest files), &lt;code&gt;medium&lt;/code&gt; for a balanced tradeoff, and &lt;code&gt;high&lt;/code&gt; for near-original quality. If you need exact control, pass raw FFmpeg options like &lt;code&gt;-crf 23&lt;/code&gt; directly through the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a free tier for video compression?
&lt;/h3&gt;

&lt;p&gt;Yes. FFmpeg Micro includes a free tier so you can test compression without committing. Sign up at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt; and get an API key in minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I batch compress multiple videos?
&lt;/h3&gt;

&lt;p&gt;You can submit multiple transcode requests in parallel. Each request processes independently on auto-scaling infrastructure, so compressing 100 videos takes roughly the same time as compressing one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;Sign up for FFmpeg Micro&lt;/a&gt; and compress your first video in minutes. No server setup, no FFmpeg installation, no infrastructure to manage.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>videoprocessing</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Process Video with a JSON API (FFmpeg Micro Request Examples)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Sun, 26 Apr 2026 10:12:26 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-process-video-with-a-json-api-ffmpeg-micro-request-examples-5dn8</link>
      <guid>https://forem.com/javidjamae/how-to-process-video-with-a-json-api-ffmpeg-micro-request-examples-5dn8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-json-api-request-examples" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You want to process video in your app, but you don't want to install FFmpeg, wrangle dependencies, or manage a transcoding server. You just want to send some JSON and get a result back. That's exactly what FFmpeg Micro does.&lt;/p&gt;

&lt;p&gt;This post walks through real JSON request payloads for the most common video operations: compressing, converting formats, adding text overlays, and using custom FFmpeg options. Every example uses the actual FFmpeg Micro API, and every request is a single HTTP call.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the FFmpeg Micro JSON API Works
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that lets you add video processing to any app with a single HTTP call. No FFmpeg installation, no server management.&lt;/p&gt;

&lt;p&gt;The core endpoint is &lt;code&gt;POST /v1/transcodes&lt;/code&gt;. You send a JSON body with your input video URL, the output format you want, and optionally a preset or custom FFmpeg options. The API returns a job ID, and you poll for the result.&lt;/p&gt;

&lt;p&gt;Every request needs an &lt;code&gt;Authorization: Bearer &amp;lt;YOUR_API_KEY&amp;gt;&lt;/code&gt; header. You get an API key when you sign up for a free account at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compress a Video (Simple Preset Mode)
&lt;/h2&gt;

&lt;p&gt;Most developers start here. You have a large MP4 and you need a smaller one. With the preset mode, you don't need to know any FFmpeg flags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/raw-video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"720p"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send that to &lt;code&gt;POST /v1/transcodes&lt;/code&gt; and you get back a job object with an &lt;code&gt;id&lt;/code&gt;. The &lt;code&gt;quality&lt;/code&gt; field maps to FFmpeg's CRF values: &lt;code&gt;low&lt;/code&gt; (CRF 28, smallest file), &lt;code&gt;medium&lt;/code&gt; (CRF 23, balanced), and &lt;code&gt;high&lt;/code&gt; (CRF 18, best quality). Resolution options are &lt;code&gt;480p&lt;/code&gt;, &lt;code&gt;720p&lt;/code&gt;, and &lt;code&gt;1080p&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a1b2c3d4-e5f6-7890-abcd-ef1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poll &lt;code&gt;GET /v1/transcodes/{id}&lt;/code&gt; until &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;completed&lt;/code&gt;, then hit &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt; to get a signed download URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert Video Format (MP4 to WebM)
&lt;/h2&gt;

&lt;p&gt;Format conversion is one line different. Change &lt;code&gt;outputFormat&lt;/code&gt; and the API handles codec selection automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webm"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg Micro supports MP4, WebM, AVI, MOV, MKV, and FLV for video. For audio, you can output to MP3, M4A, AAC, WAV, OGG, Opus, and FLAC. Image extraction supports JPEG, PNG, GIF, and WebP.&lt;/p&gt;

&lt;p&gt;No preset means the API uses sensible defaults for the target format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Mode: Custom FFmpeg Options
&lt;/h2&gt;

&lt;p&gt;Presets cover the common cases, but sometimes you need full control. The &lt;code&gt;options&lt;/code&gt; array lets you pass raw FFmpeg flags as key-value pairs.&lt;/p&gt;

&lt;p&gt;To compress with a specific codec and bitrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-c:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"libx264"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-crf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-c:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aac"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-b:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"128k"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the same flexibility as a raw FFmpeg command line, but without the server. The API validates your options before processing, so you get clear error messages if something is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a Text Overlay with Virtual Options
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro has "virtual options" for operations that would normally require complex filter chains. The &lt;code&gt;@text-overlay&lt;/code&gt; virtual option adds text to your video with a single request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@text-overlay"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;text&lt;/span&gt;&lt;span class="se"&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;Subscribe!&lt;/span&gt;&lt;span class="se"&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;position&lt;/span&gt;&lt;span class="se"&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;bottom-center&lt;/span&gt;&lt;span class="se"&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;fontSize&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 48, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;fontColor&lt;/span&gt;&lt;span class="se"&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;white&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="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No filter graph syntax. No escaped colons. Just a JSON string describing what you want. The API translates it to the correct FFmpeg drawtext filter internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload Your Own Files First
&lt;/h2&gt;

&lt;p&gt;The examples above use public HTTP URLs for the input video. If you need to process a file from your local system or a private source, use the three-step upload flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Request a presigned upload URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://www.ffmpeg-micro.com/v1/upload/presigned-url &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"filename": "my-video.mp4", "contentType": "video/mp4", "fileSize": 10485760}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; PUT your file to the returned &lt;code&gt;uploadUrl&lt;/code&gt; (direct to cloud storage).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Confirm the upload to get a &lt;code&gt;fileUrl&lt;/code&gt; you can use in your transcode request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://www.ffmpeg-micro.com/v1/upload/confirm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"filename": "my-video.mp4", "fileSize": 10485760}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fileUrl&lt;/code&gt; from Step 3 is a &lt;code&gt;gs://&lt;/code&gt; URL you pass as the &lt;code&gt;url&lt;/code&gt; in your &lt;code&gt;inputs&lt;/code&gt; array.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together with cURL
&lt;/h2&gt;

&lt;p&gt;A complete cURL example that compresses a video to 720p:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://www.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "mp4",
    "preset": {"quality": "medium", "resolution": "720p"}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One HTTP call, and your video is being processed in the cloud. No FFmpeg binary, no Docker container, no scaling headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI vs. JSON API: A Quick Comparison
&lt;/h2&gt;

&lt;p&gt;If you're used to FFmpeg on the command line, the mental model shift looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;FFmpeg CLI&lt;/th&gt;
&lt;th&gt;FFmpeg Micro JSON API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"inputs": [{"url": "..."}], "outputFormat": "mp4", "options": [{"option": "-c:v", "argument": "libx264"}, {"option": "-crf", "argument": "23"}]}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install FFmpeg on every machine&lt;/td&gt;
&lt;td&gt;One API key, works from anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale = more servers&lt;/td&gt;
&lt;td&gt;Scale = more API calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug FFmpeg errors yourself&lt;/td&gt;
&lt;td&gt;API validates options before processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The JSON is more verbose, but it's also portable. Any language or platform that can make HTTP requests can process video. That includes no-code tools like Make.com and n8n, not just backend services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I process video from any URL or only uploaded files?
&lt;/h3&gt;

&lt;p&gt;Both. You can pass any public HTTP/HTTPS URL as the input, or upload files through the presigned URL flow to get a private &lt;code&gt;gs://&lt;/code&gt; URL. Most developers start with public URLs for testing and switch to uploads for production.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if I send invalid FFmpeg options?
&lt;/h3&gt;

&lt;p&gt;The API validates your options before starting the job. You get a 400 error with a clear description of what went wrong, instead of a cryptic FFmpeg stderr dump.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check if my video is done processing?
&lt;/h3&gt;

&lt;p&gt;Poll &lt;code&gt;GET /v1/transcodes/{id}&lt;/code&gt;. The &lt;code&gt;status&lt;/code&gt; field will be &lt;code&gt;processing&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, or &lt;code&gt;failed&lt;/code&gt;. For completed jobs, &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt; returns a signed URL to grab the output file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a free tier?
&lt;/h3&gt;

&lt;p&gt;Yes. Sign up at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt; and you get free processing minutes to test the API. Paid plans start at $19/month for production workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  What formats does the API support?
&lt;/h3&gt;

&lt;p&gt;Video: MP4, WebM, AVI, MOV, MKV, FLV. Audio: MP3, M4A, AAC, WAV, OGG, Opus, FLAC. Images: JPEG, PNG, GIF, WebP. You can also extract audio from video or generate thumbnails.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Self-Hosting FFmpeg vs. Using an API: What Developers Get Wrong</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Sat, 25 Apr 2026 10:11:03 +0000</pubDate>
      <link>https://forem.com/javidjamae/self-hosting-ffmpeg-vs-using-an-api-what-developers-get-wrong-1el9</link>
      <guid>https://forem.com/javidjamae/self-hosting-ffmpeg-vs-using-an-api-what-developers-get-wrong-1el9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/self-hosting-ffmpeg-vs-api" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You spun up a Docker container, installed FFmpeg, and got your first video to transcode. It works. Ship it, right?&lt;/p&gt;

&lt;p&gt;Not so fast. Self-hosting FFmpeg feels simple at first. But the gap between "it works on my machine" and "it works in production at 3am when traffic spikes" is where most teams get burned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Docker Image Problem
&lt;/h2&gt;

&lt;p&gt;A minimal FFmpeg Docker image starts around 80MB. That's the version without most codecs. Once you add libx264, libx265, libvpx, and audio codecs like AAC and Opus, you're looking at 300-500MB images. Some teams end up with 1GB+ images after adding font rendering for subtitles or hardware acceleration libraries.&lt;/p&gt;

&lt;p&gt;Every deploy pulls that image. Every autoscale event pulls that image. At scale, your container registry becomes a bottleneck you never planned for.&lt;/p&gt;

&lt;p&gt;And that's before codec licensing enters the picture. H.264 and H.265 are patent-encumbered. If you're encoding video commercially with self-hosted FFmpeg, you technically owe royalties to the MPEG-LA patent pool. Most startups ignore this. Some get letters from lawyers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Is Not Just "Add More Containers"
&lt;/h2&gt;

&lt;p&gt;Video processing is CPU-intensive. A single 1080p transcode can peg an entire core for minutes. If three users upload simultaneously, you need three cores available right now, not in 90 seconds when Kubernetes finishes pulling your 500MB image.&lt;/p&gt;

&lt;p&gt;The typical self-hosted setup looks like this: a queue (Redis or SQS), a pool of workers, and a prayer that the queue doesn't back up during peak hours. You end up building retry logic, dead letter queues, progress tracking, and health checks. That's weeks of engineering work before you process a single production video.&lt;/p&gt;

&lt;p&gt;Compare that to an API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://storage.example.com/raw-upload.mp4"}],
    "outputFormat": "mp4",
    "preset": {"quality": "high", "resolution": "1080p"}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No worker pool. No queue. No container orchestration. FFmpeg Micro handles the scaling, the codec licensing, and the infrastructure. You get back a job ID, poll for completion, and download the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Library Drift Will Bite You
&lt;/h2&gt;

&lt;p&gt;FFmpeg releases new versions constantly. Your Docker image pins a specific version, which means you're responsible for tracking security patches, codec updates, and breaking changes. Skip an update for six months and you'll find that the bug you're debugging was fixed three releases ago.&lt;/p&gt;

&lt;p&gt;Worse, different FFmpeg builds have different codec support. An image built on Ubuntu might behave differently than one built on Alpine. A filter that works locally might fail in CI because the CI image was built with a different configuration flag. I've seen teams spend days debugging transcoding failures that turned out to be a missing &lt;code&gt;--enable-libfreetype&lt;/code&gt; flag in their Docker build.&lt;/p&gt;

&lt;p&gt;With an API, you don't think about any of this. The service maintains the FFmpeg version, the codec support, and the build configuration. Your code stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Self-Hosting Actually Makes Sense
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn't always wrong. If you process millions of videos per month and have a dedicated infrastructure team, running your own FFmpeg cluster can be cost-effective. If you need custom FFmpeg patches or exotic codecs that no API supports, self-hosting is your only option.&lt;/p&gt;

&lt;p&gt;But for most teams building a SaaS product, a content platform, or an automation workflow, self-hosting FFmpeg is a distraction. You don't self-host your database anymore. You don't self-host your email delivery. Video processing shouldn't be different.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Cloud FFmpeg API Actually Gives You
&lt;/h2&gt;

&lt;p&gt;When you use a service like FFmpeg Micro instead of self-hosting, you're not just outsourcing compute. You're skipping an entire category of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Docker images to maintain.&lt;/strong&gt; No 500MB pulls, no codec licensing headaches, no Alpine-vs-Ubuntu debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic scaling.&lt;/strong&gt; Ten videos or ten thousand. The API handles it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No worker infrastructure.&lt;/strong&gt; No Redis queues, no dead letter handling, no retry logic to build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always-current codecs.&lt;/strong&gt; H.264, H.265, VP9, AV1, Opus. All supported, all licensed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay per job, not per server.&lt;/strong&gt; No idle instances burning money at 2am.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For advanced use cases, you still get full FFmpeg power. Pass raw FFmpeg options directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://storage.example.com/raw-upload.mp4"}],
    "outputFormat": "webm",
    "options": [
      {"option": "-c:v", "argument": "libvpx-vp9"},
      {"option": "-crf", "argument": "30"},
      {"option": "-b:v", "argument": "0"},
      {"option": "-c:a", "argument": "libopus"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same flexibility as running FFmpeg yourself. None of the infrastructure overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is self-hosted FFmpeg free?
&lt;/h3&gt;

&lt;p&gt;FFmpeg itself is open source, but running it in production isn't free. You pay for compute, storage, container orchestration, and engineer time. H.264 and H.265 encoding also carries patent licensing obligations for commercial use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg in Docker without managing infrastructure?
&lt;/h3&gt;

&lt;p&gt;You can use a cloud FFmpeg API like FFmpeg Micro to skip Docker entirely. Send an HTTP request with your video URL and desired output format, and the service handles everything. Works from any language, any platform, including no-code tools like n8n and Make.com.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does FFmpeg Micro compare to AWS MediaConvert?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro gives you FFmpeg's full power at API-call pricing, without the complexity and cost of AWS MediaConvert. MediaConvert requires IAM roles, S3 bucket policies, and CloudWatch monitoring. FFmpeg Micro requires one API key and one HTTP call.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the biggest risk of self-hosting FFmpeg?
&lt;/h3&gt;

&lt;p&gt;The biggest risk is maintenance burden creeping up over time. Codec updates, security patches, Docker image bloat, and scaling challenges all compound. Most teams underestimate the ongoing cost by 3-5x compared to initial setup.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that lets you add video processing to any app with a single HTTP call. No FFmpeg installation, no server management. &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;Try the free tier&lt;/a&gt; and run your first transcode in under five minutes.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>docker</category>
      <category>devops</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Merge Audio and Video with FFmpeg (CLI and API)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Fri, 24 Apr 2026 10:12:14 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-merge-audio-and-video-with-ffmpeg-cli-and-api-3e54</link>
      <guid>https://forem.com/javidjamae/how-to-merge-audio-and-video-with-ffmpeg-cli-and-api-3e54</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-merge-audio-video" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You have a video file and a separate audio file. Maybe it's an AI voiceover you generated with ElevenLabs. Maybe it's a podcast intro you recorded separately. Either way, you need to combine them into one file, and FFmpeg is the tool that does it.&lt;/p&gt;

&lt;p&gt;The problem? FFmpeg's muxing syntax is confusing. You need &lt;code&gt;-map&lt;/code&gt;, &lt;code&gt;-shortest&lt;/code&gt;, codec flags, and a server to run it on. If you're building this into an app or automation, you also need to figure out scaling, error handling, and file storage.&lt;/p&gt;

&lt;p&gt;You can skip all of that with an API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "muxing" actually mean in FFmpeg?
&lt;/h2&gt;

&lt;p&gt;Muxing (short for multiplexing) is the process of combining separate media streams into a single container file. When you merge an audio track with a video track, you're muxing them together into one MP4, WebM, or MKV file.&lt;/p&gt;

&lt;p&gt;FFmpeg handles this with the &lt;code&gt;-map&lt;/code&gt; flag, which tells it which streams to pull from which inputs. A typical CLI command looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; video.mp4 &lt;span class="nt"&gt;-i&lt;/span&gt; voiceover.mp3 &lt;span class="nt"&gt;-map&lt;/span&gt; 0:v:0 &lt;span class="nt"&gt;-map&lt;/span&gt; 1:a:0 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-shortest&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command takes the video stream from the first input and the audio stream from the second input, then combines them. The &lt;code&gt;-shortest&lt;/code&gt; flag cuts the output to match whichever input is shorter, so you don't get trailing silence or a frozen frame.&lt;/p&gt;

&lt;p&gt;It works. But deploying this on a server, handling file uploads, and managing FFmpeg installations across environments is where things get painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to merge audio and video with the FFmpeg Micro API
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that lets you run FFmpeg operations with a single HTTP call. No FFmpeg installation, no server management. You send your files, specify the operation, and get the result back.&lt;/p&gt;

&lt;p&gt;For muxing, you pass two inputs (your video and your audio) and use the &lt;code&gt;options&lt;/code&gt; array to control how the streams are mapped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Upload your files
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro uses a 3-step upload flow. First, get a presigned URL. Then upload your file directly. Then confirm the upload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get presigned URL for the video&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/upload/presigned-url &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"filename": "video.mp4", "contentType": "video/mp4", "fileSize": 15000000}'&lt;/span&gt;

&lt;span class="c"&gt;# Upload to the returned URL&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"PRESIGNED_URL_FROM_RESPONSE"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: video/mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-binary&lt;/span&gt; @video.mp4

&lt;span class="c"&gt;# Confirm the upload&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/upload/confirm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"filename": "video.mp4", "fileSize": 15000000}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat for your audio file. Each confirm response returns a &lt;code&gt;fileUrl&lt;/code&gt; (a &lt;code&gt;gs://&lt;/code&gt; URL) that you'll use in the transcode request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Run the mux job
&lt;/h3&gt;

&lt;p&gt;Now send both files to the transcode endpoint with &lt;code&gt;-map&lt;/code&gt; options to control which streams go where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/transcodes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Authorization:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bearer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gs://your-bucket/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gs://your-bucket/voiceover.mp3"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0:v:0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1:a:0"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-c:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"libx264"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-c:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aac"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-shortest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does exactly what the CLI command does, but without any server setup. The API accepts up to 10 inputs per job, so you could mux multiple audio tracks or combine several video segments in one request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Download the result
&lt;/h3&gt;

&lt;p&gt;Poll the job status until it's complete, then grab the download URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check job status&lt;/span&gt;
curl https://api.ffmpeg-micro.com/v1/transcodes/JOB_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt;

&lt;span class="c"&gt;# Get download URL when status is "completed"&lt;/span&gt;
curl https://api.ffmpeg-micro.com/v1/transcodes/JOB_ID/download &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The download endpoint returns a signed URL. Your file is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common muxing scenarios developers run into
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Replacing audio on a video.&lt;/strong&gt; This is the most common case. You have a screen recording or stock footage and need to swap in a different audio track. The &lt;code&gt;-map 0:v:0 -map 1:a:0&lt;/code&gt; pattern handles this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding a voiceover to a silent video.&lt;/strong&gt; Same approach, but your source video has no audio track at all. FFmpeg doesn't care. It pulls the video from input 0 and audio from input 1 regardless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keeping original audio and adding a second track.&lt;/strong&gt; If you want both the original audio and a new track (like background music), you'd adjust the mapping to include both audio streams. That's a more advanced use case involving &lt;code&gt;-filter_complex amix&lt;/code&gt;, but the API supports it through the &lt;code&gt;options&lt;/code&gt; array.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just run FFmpeg on your server?
&lt;/h2&gt;

&lt;p&gt;You can. Plenty of teams do. But if you're building this into a product or automation, you'll hit a few walls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FFmpeg takes 500MB+ of disk space and has platform-specific dependencies&lt;/li&gt;
&lt;li&gt;Video processing is CPU-intensive. One mux job won't kill your server, but ten concurrent ones will&lt;/li&gt;
&lt;li&gt;Error handling for FFmpeg processes is tedious. Exit codes, stderr parsing, timeout management&lt;/li&gt;
&lt;li&gt;File storage and cleanup become your problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FFmpeg Micro handles all of this. You pay per minute of video processed, and the infrastructure scales automatically. For a 1-minute video mux job, you're looking at a few seconds of processing time and a fraction of a cent.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I merge audio and video files that are different lengths?
&lt;/h3&gt;

&lt;p&gt;Yes. Use the &lt;code&gt;-shortest&lt;/code&gt; option to cut the output to the shorter input's duration. Without it, FFmpeg will pad the shorter stream. In the API, add &lt;code&gt;{"option": "-shortest", "argument": ""}&lt;/code&gt; to your options array.&lt;/p&gt;

&lt;h3&gt;
  
  
  What audio formats work with FFmpeg muxing?
&lt;/h3&gt;

&lt;p&gt;FFmpeg supports MP3, AAC, WAV, OGG, FLAC, Opus, and M4A. FFmpeg Micro supports all of these as inputs. For the output container, MP4 works best with AAC audio, and WebM works best with Opus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to re-encode the video when muxing?
&lt;/h3&gt;

&lt;p&gt;Not always. If you use &lt;code&gt;-c:v copy&lt;/code&gt; instead of &lt;code&gt;-c:v libx264&lt;/code&gt;, FFmpeg copies the video stream without re-encoding. This is faster but only works if the codec is compatible with the output container. MP4 in, MP4 out with H.264 is a safe bet for stream copying.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many inputs can I combine in one API call?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro accepts up to 10 inputs per transcode job. You can mux multiple audio tracks, combine video segments, or mix and match. Each input gets its own index for &lt;code&gt;-map&lt;/code&gt; references.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a file size limit?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro supports files up to 500MB on the free tier. Paid plans handle larger files. Check &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;the pricing page&lt;/a&gt; for current limits.&lt;/p&gt;

&lt;p&gt;Sign up for a &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;free FFmpeg Micro account&lt;/a&gt; and try muxing your first audio and video file. The free tier includes 10 minutes of processing per month, which is plenty for testing.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>api</category>
      <category>tutorial</category>
      <category>video</category>
    </item>
    <item>
      <title>How to Use FFmpeg in Python Without Installing It</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Thu, 23 Apr 2026 14:58:12 +0000</pubDate>
      <link>https://forem.com/javidjamae/how-to-use-ffmpeg-in-python-without-installing-it-3nnn</link>
      <guid>https://forem.com/javidjamae/how-to-use-ffmpeg-in-python-without-installing-it-3nnn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-python-no-install" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've written a Python script that processes video. It works on your laptop. You deploy it to Lambda (or Render, or Vercel) and get the dreaded error: &lt;code&gt;FileNotFoundError: [Errno 2] No such file or directory: 'ffmpeg'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The problem is simple: FFmpeg is a system binary, and most cloud platforms don't ship it. The typical fix involves Docker layers, custom build packs, or static binaries crammed into a Lambda layer. It works, but it's fragile and annoying to maintain.&lt;/p&gt;

&lt;p&gt;There's a better approach. Instead of installing FFmpeg on every platform, call it as an API. Python's &lt;code&gt;requests&lt;/code&gt; library is all you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Subprocess Approach (And Why It Breaks)
&lt;/h2&gt;

&lt;p&gt;Most Python FFmpeg tutorials show something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffmpeg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c:v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;libx264&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-crf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;23&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locally, this is fine. But it assumes FFmpeg is installed at the OS level. On AWS Lambda, Google Cloud Functions, Vercel serverless functions, or any container without FFmpeg pre-installed, this fails instantly.&lt;/p&gt;

&lt;p&gt;The workarounds are all painful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda layers&lt;/strong&gt; with a static FFmpeg binary (70MB+ zip, architecture-specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker images&lt;/strong&gt; with &lt;code&gt;apt-get install ffmpeg&lt;/code&gt; in the Dockerfile&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python wrappers&lt;/strong&gt; like &lt;code&gt;ffmpeg-python&lt;/code&gt; or &lt;code&gt;imageio-ffmpeg&lt;/code&gt; that still need the binary underneath&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these adds deployment complexity. And when FFmpeg updates or you switch platforms, you're rebuilding the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using FFmpeg as an API from Python
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that runs FFmpeg for you. One HTTP call, and your Python code can transcode, resize, or watermark video. No binary, no Docker, no Lambda layer.&lt;/p&gt;

&lt;p&gt;The entire integration is just Python's &lt;code&gt;requests&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.ffmpeg-micro.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/transcodes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;preset&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resolution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;720p&lt;/span&gt;&lt;span class="sh"&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;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Job ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jobId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No subprocess, no binary, no system dependency. This runs the same way on Lambda, Render, Vercel, your laptop, or anywhere Python runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking Job Status and Downloading Results
&lt;/h2&gt;

&lt;p&gt;Transcoding takes a few seconds to a few minutes depending on file size. Poll the job status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jobId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/transcodes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once complete, grab the download URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;download_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/transcodes/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/download&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;download_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;download_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Download: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;download_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced: Custom FFmpeg Options
&lt;/h2&gt;

&lt;p&gt;The preset mode covers most cases, but you can pass raw FFmpeg options for full control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/transcodes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outputFormat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;option&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c:v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argument&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;libvpx-vp9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;option&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-crf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argument&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;option&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-b:v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argument&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;option&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c:a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argument&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;libopus&lt;/span&gt;&lt;span class="sh"&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;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts MP4 to WebM using VP9 and Opus codecs. The same FFmpeg flags you'd use on the command line, just passed as structured data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Files First
&lt;/h2&gt;

&lt;p&gt;If your video isn't already hosted at a public URL, upload it through the API first. It's a three-step process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Step 1: Get a presigned upload URL
&lt;/span&gt;&lt;span class="n"&gt;presign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/upload/presigned-url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contentType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video/mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fileSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000000&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Upload directly to cloud storage
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-video.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;presign&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uploadUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video/mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 3: Confirm the upload
&lt;/span&gt;&lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v1/upload/confirm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;presign&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fileSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000000&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Now use the file URL for transcoding
&lt;/span&gt;&lt;span class="n"&gt;file_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fileUrl&lt;/span&gt;&lt;span class="sh"&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 &lt;code&gt;fileUrl&lt;/code&gt; from the confirm step is what you pass in the &lt;code&gt;inputs&lt;/code&gt; array when creating a transcode job.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use an API vs. Local FFmpeg
&lt;/h2&gt;

&lt;p&gt;Local FFmpeg still makes sense for some cases. If you're running batch processing on a dedicated server that you control, a local install is simpler and cheaper at high volumes.&lt;/p&gt;

&lt;p&gt;But if you're building a web app, running serverless functions, or deploying to platforms where you don't control the OS, an API removes an entire category of deployment problems. You also don't have to think about FFmpeg version mismatches, codec availability, or scaling compute for video-heavy workloads.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro has a free tier with 10 minutes of processing per month. Enough to build and test your integration before you commit to anything. &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;Sign up and get your API key&lt;/a&gt; to try the examples above.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>python</category>
      <category>serverless</category>
      <category>api</category>
    </item>
    <item>
      <title>n8n Execute Command Node vs. FFmpeg API: Why Cloud Users Are Switching</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Wed, 22 Apr 2026 10:11:12 +0000</pubDate>
      <link>https://forem.com/javidjamae/n8n-execute-command-node-vs-ffmpeg-api-why-cloud-users-are-switching-4khe</link>
      <guid>https://forem.com/javidjamae/n8n-execute-command-node-vs-ffmpeg-api-why-cloud-users-are-switching-4khe</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/n8n-execute-command-vs-ffmpeg-api" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You built a video processing workflow in n8n using the Execute Command node. It worked on your laptop. Then you deployed to n8n Cloud and nothing happened.&lt;/p&gt;

&lt;p&gt;That's because &lt;strong&gt;n8n Cloud doesn't include FFmpeg&lt;/strong&gt;. And even if you're self-hosting, the Execute Command node is disabled by default since n8n v2.0 for security reasons. You can re-enable it, but now you're maintaining a custom Docker image with FFmpeg baked in, managing dependencies, and hoping nothing breaks on the next update.&lt;/p&gt;

&lt;p&gt;There's a simpler approach: skip Execute Command entirely and call an FFmpeg API from an HTTP Request node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Execute Command Node Breaks for FFmpeg
&lt;/h2&gt;

&lt;p&gt;The Execute Command node runs shell commands on the n8n server. For FFmpeg, that means the server needs FFmpeg installed, enough CPU and RAM to process video, and disk space for temp files.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;n8n Cloud&lt;/strong&gt;, this is a non-starter. The hosted environment doesn't ship FFmpeg, and you can't install system packages. Your workflow just fails silently or throws a "command not found" error.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;self-hosted n8n&lt;/strong&gt;, you have two problems. First, the Execute Command node is disabled by default in n8n v2.0 and later. The n8n team locked it down because arbitrary shell execution is a security risk in shared environments. You need to set the &lt;code&gt;NODES_EXCLUDE&lt;/code&gt; environment variable or use &lt;code&gt;N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true&lt;/code&gt; plus additional config to re-enable it.&lt;/p&gt;

&lt;p&gt;Second, even after re-enabling it, you need FFmpeg on the server. The official n8n Docker image doesn't include it. So you're building a custom Dockerfile, installing FFmpeg, keeping it updated, and making sure your server has enough resources to handle video transcoding alongside your n8n workflows.&lt;/p&gt;

&lt;p&gt;If you're processing one video a week, that might be fine. If you're building anything production-grade, it becomes a maintenance headache fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an FFmpeg API Replaces Execute Command
&lt;/h2&gt;

&lt;p&gt;Instead of running FFmpeg on your n8n server, you offload the processing to a cloud API. The workflow stays in n8n, but the heavy lifting happens somewhere else.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro is a cloud API built specifically for this. One HTTP request, and your video gets processed on auto-scaling infrastructure. No FFmpeg install. No Docker customization. Works on n8n Cloud and self-hosted identically.&lt;/p&gt;

&lt;p&gt;The pattern is simple: replace your Execute Command node with an HTTP Request node that calls the FFmpeg Micro API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up FFmpeg Processing in n8n (Without Execute Command)
&lt;/h2&gt;

&lt;p&gt;You need two things: an FFmpeg Micro API key (free tier available at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;) and an HTTP Request node in your n8n workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start a Transcode Job
&lt;/h3&gt;

&lt;p&gt;Add an HTTP Request node with these settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; POST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;https://api.ffmpeg-micro.com/v1/transcodes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Header Auth, name &lt;code&gt;Authorization&lt;/code&gt;, value &lt;code&gt;Bearer YOUR_API_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body (JSON):&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-bucket.s3.amazonaws.com/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"720p"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; field takes an array of objects, each with a &lt;code&gt;url&lt;/code&gt; pointing to your source video. The API supports MP4, WebM, MOV, AVI, MKV, and more.&lt;/p&gt;

&lt;p&gt;For finer control, swap &lt;code&gt;preset&lt;/code&gt; for raw FFmpeg options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-bucket.s3.amazonaws.com/video.mp4"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-c:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"libvpx-vp9"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-crf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"30"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"option"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-b:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"argument"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the same power as a raw FFmpeg command line, without installing anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Job Status
&lt;/h3&gt;

&lt;p&gt;Add a second HTTP Request node (or use a loop with a Wait node):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; GET&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;https://api.ffmpeg-micro.com/v1/transcodes/{{ $json.jobId }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Same Bearer token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response includes &lt;code&gt;status&lt;/code&gt; (queued, processing, completed, failed) and a &lt;code&gt;progress&lt;/code&gt; percentage. When status is &lt;code&gt;completed&lt;/code&gt;, the output is ready for download.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download the Result
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; GET&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;https://api.ffmpeg-micro.com/v1/transcodes/{{ $json.jobId }}/download&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Same Bearer token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This returns a signed download URL for your processed video.&lt;/p&gt;

&lt;h2&gt;
  
  
  n8n Execute Command vs. FFmpeg API: Side by Side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Execute Command + FFmpeg&lt;/th&gt;
&lt;th&gt;HTTP Request + FFmpeg Micro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;n8n Cloud&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Works out of the box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n8n v2.0+&lt;/td&gt;
&lt;td&gt;Disabled by default&lt;/td&gt;
&lt;td&gt;No restrictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FFmpeg install&lt;/td&gt;
&lt;td&gt;Required on server&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server resources&lt;/td&gt;
&lt;td&gt;CPU/RAM used for processing&lt;/td&gt;
&lt;td&gt;Offloaded to cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Limited by server capacity&lt;/td&gt;
&lt;td&gt;Auto-scales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Custom Docker image, updates&lt;/td&gt;
&lt;td&gt;Zero&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;Hours (Docker, FFmpeg, config)&lt;/td&gt;
&lt;td&gt;Minutes (API key + HTTP node)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to Keep Using Execute Command
&lt;/h2&gt;

&lt;p&gt;If you're running n8n self-hosted, have FFmpeg installed already, and your processing volume is low and predictable, Execute Command still works. It's free (beyond your server costs) and keeps everything local.&lt;/p&gt;

&lt;p&gt;But the moment you hit any of these, it's time to switch: deploying to n8n Cloud, processing more than a handful of videos per day, needing consistent output quality across different input formats, or wanting your workflow to survive an n8n version upgrade without breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I install FFmpeg on n8n Cloud?
&lt;/h3&gt;

&lt;p&gt;No. n8n Cloud is a managed environment and you can't install system packages. The only way to use FFmpeg from n8n Cloud is through an external API like FFmpeg Micro.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the Execute Command node safe to re-enable?
&lt;/h3&gt;

&lt;p&gt;It depends on your setup. The n8n team disabled it by default in v2.0 because arbitrary shell execution is a security risk, especially in multi-user environments. If you're the only user on a locked-down server, the risk is lower. But the API approach avoids the question entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does FFmpeg Micro cost for n8n workflows?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro has a free tier that covers light usage. Paid plans start at $19/month for higher volumes. You pay per minute of video processed, so costs scale with actual usage rather than server uptime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg Micro with n8n's webhook triggers?
&lt;/h3&gt;

&lt;p&gt;Yes. A common pattern is: webhook receives a video URL, HTTP Request node sends it to FFmpeg Micro for processing, a Wait/loop polls for completion, then another HTTP Request downloads the result. The entire workflow runs in n8n with zero local FFmpeg dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  What video formats does FFmpeg Micro support?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro supports MP4, WebM, AVI, MOV, MKV, FLV for video, plus MP3, AAC, WAV, OGG, FLAC and more for audio. You can also output to image formats like JPEG, PNG, and WebP for thumbnail extraction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;FFmpeg Micro&lt;/a&gt; gives you FFmpeg's full processing power through a simple API. Sign up for a free account and swap out that Execute Command node in about five minutes.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>api</category>
      <category>automation</category>
    </item>
    <item>
      <title>Best FFmpeg API Services Compared (2026)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Tue, 21 Apr 2026 10:14:25 +0000</pubDate>
      <link>https://forem.com/javidjamae/best-ffmpeg-api-services-compared-2026-35p5</link>
      <guid>https://forem.com/javidjamae/best-ffmpeg-api-services-compared-2026-35p5</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/best-ffmpeg-api-services-compared" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need video processing in your app. You don't want to manage FFmpeg servers. So which FFmpeg API do you actually pick?&lt;/p&gt;

&lt;p&gt;There are more options than you'd think. Some wrap FFmpeg behind a REST endpoint. Others bolt on AI features or template engines. The pricing models are all over the place.&lt;/p&gt;

&lt;p&gt;I tested the major FFmpeg API services available in 2026. This is what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Good FFmpeg API
&lt;/h2&gt;

&lt;p&gt;Before comparing services, know what actually matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility.&lt;/strong&gt; Can you pass custom FFmpeg options, or are you stuck with presets? Some services only expose a handful of operations. Others give you full FFmpeg control through the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing clarity.&lt;/strong&gt; GB-based pricing sounds simple until you realize a 4K file burns through your quota 10x faster than 720p. Per-minute or per-video pricing is more predictable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation support.&lt;/strong&gt; If you're building workflows in n8n, Make.com, or Zapier, the API needs to play nice with HTTP modules and webhooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency.&lt;/strong&gt; Processing one video at a time doesn't cut it for batch workloads. Check how many parallel jobs each tier supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Contenders
&lt;/h2&gt;

&lt;p&gt;These are the main players in the FFmpeg-as-a-Service space right now.&lt;/p&gt;

&lt;h3&gt;
  
  
  FFmpeg Micro
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro is a cloud API that lets you add video processing to any app with a single HTTP call. No FFmpeg installation, no server management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 100 minutes/month, 1 concurrent job&lt;/li&gt;
&lt;li&gt;Starter ($19/mo): 2,000 minutes, 3 concurrent jobs&lt;/li&gt;
&lt;li&gt;Pro ($89/mo): 12,000 minutes, 10 concurrent jobs&lt;/li&gt;
&lt;li&gt;Scale ($349/mo): 60,000 minutes, 40 concurrent jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Billing is per input minute (rounded up, minimum 1 minute per job). Annual plans save 40%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standout features:&lt;/strong&gt; Full FFmpeg option passthrough via the &lt;code&gt;options&lt;/code&gt; array, virtual options for complex effects like text overlays and quote cards, an MCP server for AI agent integration with Claude, Cursor, and Windsurf, native n8n and Make.com integration guides, and preset mode for simple jobs where you just pick quality and resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported formats:&lt;/strong&gt; MP4, WebM, AVI, MOV, MKV, FLV, MP3, M4A, AAC, WAV, OGG, Opus, FLAC, JPEG, PNG, GIF, WebP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.ffmpeg-micro.com/v1/transcodes &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "inputs": [{"url": "https://example.com/video.mp4"}],
    "outputFormat": "webm",
    "preset": {"quality": "high", "resolution": "1080p"}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rendi
&lt;/h3&gt;

&lt;p&gt;Rendi is the most established name in the FFmpeg API space. You send standard FFmpeg command strings through their REST endpoint, and they execute them on optimized hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 50 GB processing/month, 1-minute max command runtime, 4 commands/minute&lt;/li&gt;
&lt;li&gt;Pro ($25/mo): 100 GB processing, 10-minute runtime, unlimited commands, webhooks, chained commands&lt;/li&gt;
&lt;li&gt;Enterprise: Custom pricing with SLAs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rendi uses GB-based pricing instead of time-based. A 100 MB file costs the same whether it takes 5 seconds or 5 minutes to process. Good for quick operations on large files, less predictable for heavy transcoding.&lt;/p&gt;

&lt;p&gt;The free tier caps command runtime at 1 minute, which rules out most real transcoding jobs. You're essentially forced to Pro for anything beyond format detection or thumbnail extraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  ffmpegapi.net
&lt;/h3&gt;

&lt;p&gt;ffmpegapi.net takes a different approach: pre-built endpoints for specific operations rather than raw FFmpeg command passthrough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Three tiers from free to $99/month. Free tier includes a guest API key for testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standout features:&lt;/strong&gt; AI-powered caption generation using OpenAI Whisper, animated captions via Remotion, picture-in-picture overlays, and YouTube downloading. The Whisper integration is unique among FFmpeg API services.&lt;/p&gt;

&lt;p&gt;The trade-off? You don't get raw FFmpeg access. Each operation (merge, trim, split, convert) has its own endpoint with fixed parameters. Great if your use case matches their menu. Limiting if it doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eranol
&lt;/h3&gt;

&lt;p&gt;Eranol focuses on video creation rather than general-purpose processing. Their main product is a slideshow API that merges images with audio into video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Pay-per-video at $5 per 50 videos (10 min each). No monthly subscription. New users get $1 free credit.&lt;/p&gt;

&lt;p&gt;Best for programmatic video generation from images and audio. Think product slideshows, social content from image templates, or automated video ads from a spreadsheet.&lt;/p&gt;

&lt;p&gt;Not for general-purpose video transcoding, format conversion, or custom FFmpeg operations beyond their specific endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side-by-Side Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;FFmpeg Micro&lt;/th&gt;
&lt;th&gt;Rendi&lt;/th&gt;
&lt;th&gt;ffmpegapi.net&lt;/th&gt;
&lt;th&gt;Eranol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pricing model&lt;/td&gt;
&lt;td&gt;Per minute&lt;/td&gt;
&lt;td&gt;Per GB&lt;/td&gt;
&lt;td&gt;Tiered monthly&lt;/td&gt;
&lt;td&gt;Per video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;100 min/mo&lt;/td&gt;
&lt;td&gt;50 GB/mo&lt;/td&gt;
&lt;td&gt;Yes (guest key)&lt;/td&gt;
&lt;td&gt;$1 credit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid from&lt;/td&gt;
&lt;td&gt;$19/mo&lt;/td&gt;
&lt;td&gt;$25/mo&lt;/td&gt;
&lt;td&gt;~$29/mo&lt;/td&gt;
&lt;td&gt;$0.10/video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw FFmpeg passthrough&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preset mode&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n8n / Make.com support&lt;/td&gt;
&lt;td&gt;Guides included&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not documented&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP / AI agent support&lt;/td&gt;
&lt;td&gt;Yes (OAuth 2.1)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent jobs&lt;/td&gt;
&lt;td&gt;Up to 40&lt;/td&gt;
&lt;td&gt;Not published&lt;/td&gt;
&lt;td&gt;Not published&lt;/td&gt;
&lt;td&gt;Async&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI captions&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Whisper)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format support&lt;/td&gt;
&lt;td&gt;20+ formats&lt;/td&gt;
&lt;td&gt;All FFmpeg&lt;/td&gt;
&lt;td&gt;Limited set&lt;/td&gt;
&lt;td&gt;Video only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which One Should You Pick
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick FFmpeg Micro&lt;/strong&gt; if you want full FFmpeg flexibility with predictable per-minute pricing. Especially strong if you're building automation workflows with n8n or Make.com, or integrating video processing into AI agents via MCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Rendi&lt;/strong&gt; if you're already comfortable writing FFmpeg CLI commands and want to send raw command strings to the cloud. The GB-based pricing works well for quick operations on large files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick ffmpegapi.net&lt;/strong&gt; if your use case is covered by their pre-built endpoints and you want AI caption generation out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Eranol&lt;/strong&gt; if you're specifically building slideshow-style videos from images and audio at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-host&lt;/strong&gt; if you have DevOps capacity, need sub-second latency, or process enough volume that API pricing doesn't make sense. But budget for the engineering time. Most teams underestimate how much work FFmpeg infrastructure actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best FFmpeg API for developers in 2026?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro and Rendi are the two strongest options for developers who need full FFmpeg flexibility through an API. FFmpeg Micro offers per-minute pricing and automation-first integrations. Rendi offers GB-based pricing and raw command passthrough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a free FFmpeg API?
&lt;/h3&gt;

&lt;p&gt;Yes. FFmpeg Micro offers 100 free compute minutes per month. Rendi offers 50 GB of free processing with a 1-minute command runtime cap. ffmpegapi.net provides a free guest API key for testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg in Make.com or n8n without installing it?
&lt;/h3&gt;

&lt;p&gt;You can. FFmpeg Micro and Rendi both work as external APIs called from Make.com HTTP modules or n8n HTTP Request nodes. FFmpeg Micro has dedicated integration guides for both platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between an FFmpeg API and self-hosted FFmpeg?
&lt;/h3&gt;

&lt;p&gt;FFmpeg API services handle the infrastructure: scaling, updates, monitoring, and failover. Self-hosted FFmpeg gives you full control but requires managing servers, keeping FFmpeg updated, and handling concurrency yourself. For most teams, the API cost is cheaper than the engineering time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do any FFmpeg APIs support AI agent integration?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro has an MCP (Model Context Protocol) server that works with Claude Desktop, Claude Code, Cursor, Windsurf, and VS Code. This lets AI coding agents transcode video directly. No other FFmpeg API service currently offers MCP support.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro is free to start with 100 compute minutes per month. &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt; and run your first transcode in under a minute.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>api</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
