<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Javid Jamae</title>
    <description>The latest articles on DEV Community by Javid Jamae (@javidjamae).</description>
    <link>https://dev.to/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>DEV Community: Javid Jamae</title>
      <link>https://dev.to/javidjamae</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/javidjamae"/>
    <language>en</language>
    <item>
      <title>Auto-Add Captions to Every Video Your Team Uploads (n8n + FFmpeg)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Fri, 12 Jun 2026 10:15:42 +0000</pubDate>
      <link>https://dev.to/javidjamae/auto-add-captions-to-every-video-your-team-uploads-n8n-ffmpeg-2l83</link>
      <guid>https://dev.to/javidjamae/auto-add-captions-to-every-video-your-team-uploads-n8n-ffmpeg-2l83</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/auto-caption-videos-n8n-ffmpeg" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every video your team publishes needs captions. Accessibility requirements, social media autoplay, SEO value. But nobody wants to sit there adding subtitles by hand, and outsourcing transcription adds days of turnaround.&lt;/p&gt;

&lt;p&gt;You can automate the entire thing. This n8n workflow watches a folder (Google Drive, Dropbox, or an S3 bucket), transcribes new videos with FFmpeg Micro's Whisper-based API, burns the captions directly into the video, and drops the result wherever you need it. Your team uploads a video. A few minutes later, the captioned version appears. No manual steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you're building
&lt;/h2&gt;

&lt;p&gt;The workflow has four stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger:&lt;/strong&gt; n8n watches for new video files in a cloud folder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcribe:&lt;/strong&gt; FFmpeg Micro generates an SRT subtitle file from the audio track&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burn captions:&lt;/strong&gt; A second FFmpeg Micro call overlays the SRT onto the video&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliver:&lt;/strong&gt; The captioned video gets saved back to your storage or sent to Slack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each step is a single HTTP request. No FFmpeg installed anywhere. No GPU instances. No transcription service accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You'll need an &lt;a href="https://n8n.io" rel="noopener noreferrer"&gt;n8n&lt;/a&gt; instance (cloud or self-hosted), an FFmpeg Micro API key from &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;, and a cloud storage folder your team already uses for video files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up the trigger
&lt;/h2&gt;

&lt;p&gt;In n8n, create a new workflow and add a trigger node for your storage provider. Google Drive and Dropbox both have native trigger nodes. For S3, use the Schedule Trigger and check for new objects.&lt;/p&gt;

&lt;p&gt;The trigger should fire when a new video file lands in a specific folder. Filter for video MIME types (video/mp4, video/quicktime, video/webm) so you don't accidentally process PDFs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Transcribe the audio to SRT
&lt;/h2&gt;

&lt;p&gt;Add an HTTP Request node that calls FFmpeg Micro's transcribe endpoint. This uses Whisper under the hood to generate an SRT subtitle file from the video's audio track.&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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://api.ffmpeg-micro.com/v1/transcribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&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;"body"&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;"media_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;"{{ $json.fileUrl }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&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;The &lt;code&gt;media_url&lt;/code&gt; field accepts any public URL. If your storage provides signed URLs (like S3 presigned URLs or Supabase signed URLs), those work too. The &lt;code&gt;language&lt;/code&gt; field is optional. Leave it out and Whisper will auto-detect.&lt;/p&gt;

&lt;p&gt;This returns a job with &lt;code&gt;status: "queued"&lt;/code&gt;. You need to poll until it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Poll until transcription completes
&lt;/h2&gt;

&lt;p&gt;Add a loop that checks the transcribe job status every 3 seconds. In n8n, use an HTTP Request node inside a Loop Over Items node, or use a Wait node with a loop-back connection.&lt;/p&gt;

&lt;p&gt;The simplest pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Request&lt;/strong&gt; to &lt;code&gt;GET https://api.ffmpeg-micro.com/v1/transcribe/{{ $json.id }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IF node&lt;/strong&gt; checking &lt;code&gt;{{ $json.status }}&lt;/code&gt; equals &lt;code&gt;completed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If not completed, &lt;strong&gt;Wait&lt;/strong&gt; 3 seconds and loop back to the GET request&lt;/li&gt;
&lt;li&gt;If completed, continue to the next step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the status is &lt;code&gt;completed&lt;/code&gt;, grab the SRT download URL:&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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://api.ffmpeg-micro.com/v1/transcribe/{{ $json.id }}/download"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer 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="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 returns a signed URL for the SRT file that's valid for 10 minutes. You don't need to download the SRT yourself. Just pass the URL to the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Burn captions into the video
&lt;/h2&gt;

&lt;p&gt;Now you have two URLs: the original video and the generated SRT file. Send both to FFmpeg Micro's transcode endpoint to burn the subtitles directly into the video.&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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://api.ffmpeg-micro.com/v1/transcodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&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;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&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;"body"&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;"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="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;"{{ $json.originalVideoUrl }}"&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="w"&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;"-vf"&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;"subtitles={{ $json.srtUrl }}"&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;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;-vf subtitles=&lt;/code&gt; filter burns the SRT text onto every frame. The captions become part of the video itself, so they show up everywhere, not just players that support SRT tracks.&lt;/p&gt;

&lt;p&gt;Poll this job the same way you polled the transcribe job. When it's &lt;code&gt;completed&lt;/code&gt;, grab the download URL from &lt;code&gt;/v1/transcodes/{id}/download&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Save the captioned video
&lt;/h2&gt;

&lt;p&gt;The final step depends on where your team wants the output. Common options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Drive:&lt;/strong&gt; Use n8n's Google Drive node to upload the file to a "Captioned" subfolder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack notification:&lt;/strong&gt; Post the download URL to a channel so the team knows it's ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3/Supabase:&lt;/strong&gt; Upload to your storage bucket with a predictable naming convention like &lt;code&gt;captioned-{original-filename}.mp4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add an HTTP Request node to download the processed video from the FFmpeg Micro signed URL, then pipe it to your output node.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete n8n workflow
&lt;/h2&gt;

&lt;p&gt;The finished workflow, node by node:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Google Drive Trigger (new file in /Team Videos/)&lt;/li&gt;
&lt;li&gt;HTTP Request: POST /v1/transcribe (start transcription)&lt;/li&gt;
&lt;li&gt;Loop: Poll GET /v1/transcribe/{id} until completed&lt;/li&gt;
&lt;li&gt;HTTP Request: GET /v1/transcribe/{id}/download (get SRT URL)&lt;/li&gt;
&lt;li&gt;HTTP Request: POST /v1/transcodes (burn captions)&lt;/li&gt;
&lt;li&gt;Loop: Poll GET /v1/transcodes/{id} until completed&lt;/li&gt;
&lt;li&gt;HTTP Request: GET /v1/transcodes/{id}/download (get final video)&lt;/li&gt;
&lt;li&gt;Google Drive Upload: Save to /Team Videos/Captioned/&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total: 8 nodes, zero code, runs automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The SRT download URL expires in 10 minutes.&lt;/strong&gt; Don't add a long delay between the transcribe download and the transcode request. The signed URL from &lt;code&gt;/v1/transcribe/{id}/download&lt;/code&gt; has a 10-minute window. If you're processing long videos, start the transcode immediately after getting the SRT URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public URLs only.&lt;/strong&gt; FFmpeg Micro needs to download your video file. If your Google Drive or Dropbox file isn't publicly accessible, you'll need to generate a sharing link or use a signed URL. Most n8n storage nodes can output a public download URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caption styling is default.&lt;/strong&gt; The basic &lt;code&gt;subtitles=&lt;/code&gt; filter uses FFmpeg's default white-text-with-black-outline style. If you want custom fonts, colors, or positioning, you'll need to pass additional FFmpeg filter options. Check the &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-subtitles-filter-guide" rel="noopener noreferrer"&gt;FFmpeg subtitles filter documentation&lt;/a&gt; for styling parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language detection isn't perfect.&lt;/strong&gt; If your team records in a specific language, set the &lt;code&gt;language&lt;/code&gt; field explicitly in the transcribe request instead of relying on auto-detection. Auto-detect works well for common languages but can struggle with code-switching or heavy accents.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How accurate is the transcription?&lt;/strong&gt;&lt;br&gt;
FFmpeg Micro uses Whisper for transcription, which handles English and most European languages very well. For clean audio with a single speaker, expect 95%+ accuracy. Background noise, multiple speakers, or heavy accents will reduce accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I process videos longer than 10 minutes?&lt;/strong&gt;&lt;br&gt;
Yes. FFmpeg Micro handles videos of any reasonable length. Transcription time scales roughly linearly with audio duration. A 10-minute video typically takes 1-2 minutes to transcribe. Billing is per minute of input media.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about non-English content?&lt;/strong&gt;&lt;br&gt;
Set the &lt;code&gt;language&lt;/code&gt; field to the appropriate BCP-47 code (like "es" for Spanish or "fr" for French). You can also use &lt;code&gt;"task": "translate"&lt;/code&gt; to transcribe foreign-language audio and output the subtitles in English.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work with n8n Cloud?&lt;/strong&gt;&lt;br&gt;
Yes. Everything runs through HTTP requests to the FFmpeg Micro API. No Execute Command nodes, no local FFmpeg binaries. n8n Cloud handles it just fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does this cost per video?&lt;/strong&gt;&lt;br&gt;
Two FFmpeg Micro API calls per video: one for transcription, one for burning captions. Both bill per minute of input. A 5-minute video costs roughly the equivalent of 10 minutes of processing. On the free tier, that's enough to test the workflow. Production pricing starts at $19/month.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>automation</category>
      <category>captions</category>
    </item>
    <item>
      <title>Add Video Processing to Your Vercel + Supabase App in 10 Minutes</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Fri, 12 Jun 2026 10:15:28 +0000</pubDate>
      <link>https://dev.to/javidjamae/add-video-processing-to-your-vercel-supabase-app-in-10-minutes-3jhc</link>
      <guid>https://dev.to/javidjamae/add-video-processing-to-your-vercel-supabase-app-in-10-minutes-3jhc</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/add-video-processing-vercel-supabase" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most Vercel + Supabase apps hit the same wall: a user uploads a video, and you need to do something with it. Compress it, convert it, extract a thumbnail. You Google "ffmpeg vercel" and realize serverless functions time out after 60 seconds, and video processing takes way longer than that.&lt;/p&gt;

&lt;p&gt;You don't need to spin up a separate server. FFmpeg Micro is a cloud API that processes video with a single HTTP call. It fits into the Vercel + Supabase stack without any infrastructure changes.&lt;/p&gt;

&lt;p&gt;This guide walks through the full flow: upload to Supabase Storage, process with FFmpeg Micro, and store the result back in Supabase. You can have it running in under 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can't just run FFmpeg in a Vercel function
&lt;/h2&gt;

&lt;p&gt;Vercel Serverless Functions have a 60-second timeout on the Pro plan (10 seconds on Hobby). A 30-second video transcode can take 2-3 minutes depending on the codec and resolution. Even if you could fit it in the timeout, the function would burn compute the entire time.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro handles the processing asynchronously. You send a request, get a job ID, and poll or use a webhook to know when it's done. Your Vercel function stays fast because it's only making HTTP calls, not running FFmpeg binaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture (3 pieces)
&lt;/h2&gt;

&lt;p&gt;Your app needs three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Storage&lt;/strong&gt; for file uploads and final output storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFmpeg Micro API&lt;/strong&gt; for the actual video processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Vercel API route&lt;/strong&gt; (or Edge Function) that ties them together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The flow looks like this: user uploads a video to Supabase Storage, your API route sends the public URL to FFmpeg Micro, FFmpeg Micro processes it and returns a download URL, and your app saves the result back to Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Get your FFmpeg Micro API key
&lt;/h2&gt;

&lt;p&gt;Sign up at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt; and grab your API key from the dashboard. The free tier gives you enough minutes to test everything in this guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Set up the environment variables
&lt;/h2&gt;

&lt;p&gt;Add these to your Vercel project (or &lt;code&gt;.env.local&lt;/code&gt; for local dev):&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="nv"&gt;FFMPEG_MICRO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_api_key_here
&lt;span class="nv"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_supabase_url
&lt;span class="nv"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_service_role_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create the transcode API route
&lt;/h2&gt;

&lt;p&gt;This is a Next.js API route that accepts a Supabase Storage URL and sends it to FFmpeg Micro for processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/transcode/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputFormat&lt;/span&gt; &lt;span class="p"&gt;}&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;req&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="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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FFMPEG_MICRO_API_KEY&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="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="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="nx"&gt;videoUrl&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="nx"&gt;outputFormat&lt;/span&gt; &lt;span class="o"&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;720p&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="na"&gt;jobId&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="na"&gt;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; array takes objects with a &lt;code&gt;url&lt;/code&gt; field. You can pass a public Supabase Storage URL directly. The &lt;code&gt;preset&lt;/code&gt; field sets quality and resolution without needing to know FFmpeg flags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Poll for completion
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro processes video asynchronously. You need to check the job status until it's &lt;code&gt;completed&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/ffmpeg-micro.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pollTranscode&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxAttempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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="s2"&gt;`https://api.ffmpeg-micro.com/v1/transcodes/&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;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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FFMPEG_MICRO_API_KEY&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="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;res&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="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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dlRes&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="s2"&gt;`https://api.ffmpeg-micro.com/v1/transcodes/&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;/download`&lt;/span&gt;&lt;span class="p"&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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FFMPEG_MICRO_API_KEY&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&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;dlRes&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;return&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Transcode failed: &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;error_message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Transcode timed out&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Save the result back to Supabase
&lt;/h2&gt;

&lt;p&gt;Once you have the download URL from FFmpeg Micro, fetch the processed video and upload it to your Supabase Storage bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/save-to-supabase.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@supabase/supabase-js&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;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveProcessedVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoResponse&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="nx"&gt;downloadUrl&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;videoBuffer&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;videoResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;videos&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video/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;upsert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;publicUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;videos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getPublicUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;publicUrl&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;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;The full flow in one API route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/process-video/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pollTranscode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/ffmpeg-micro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveProcessedVideo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/save-to-supabase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputFormat&lt;/span&gt; &lt;span class="p"&gt;}&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;req&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;// 1. Start the transcode&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createRes&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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FFMPEG_MICRO_API_KEY&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="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="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="nx"&gt;videoUrl&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="nx"&gt;outputFormat&lt;/span&gt; &lt;span class="o"&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;720p&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;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;createRes&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;// 2. Wait for processing to finish&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;downloadUrl&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;pollTranscode&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="c1"&gt;// 3. Save to Supabase Storage&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`processed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-output.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;outputFormat&lt;/span&gt; &lt;span class="o"&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="s2"&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;publicUrl&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;saveProcessedVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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;publicUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;jobId&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced: use raw FFmpeg options
&lt;/h2&gt;

&lt;p&gt;The preset mode covers common cases. But if you need something specific, like extracting audio or adding a watermark, you can pass raw FFmpeg options instead:&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="w"&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-supabase-url.co/storage/v1/object/public/videos/input.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;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="w"&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="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;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;"23"&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;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="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;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 full flexibility of FFmpeg without having to install or manage it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Supabase Storage URLs must be public (or signed).&lt;/strong&gt; FFmpeg Micro needs to download your video. Either make the bucket public or generate a signed URL with a long enough expiry (the processing can take a few minutes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't poll from the client.&lt;/strong&gt; Run the poll loop server-side in your API route or use a background job. Client-side polling ties up a browser tab and exposes your API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch your function timeout.&lt;/strong&gt; The polling loop above can run for up to 60 seconds. On Vercel Pro, that's fine for Serverless Functions. On the Hobby plan, consider using a webhook callback instead of polling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;outputFormat&lt;/code&gt; not &lt;code&gt;format&lt;/code&gt;.&lt;/strong&gt; The FFmpeg Micro API uses &lt;code&gt;outputFormat&lt;/code&gt; in the request body. If you pass &lt;code&gt;format&lt;/code&gt;, it'll be silently ignored and you'll get unexpected defaults.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Can I use this with the Vercel Hobby plan?&lt;/strong&gt;&lt;br&gt;
Yes, but the 10-second function timeout means you can't poll inline. Use a webhook callback or process the video client-side by calling FFmpeg Micro directly from the browser (your API key should stay server-side, so you'd need a thin proxy route).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does FFmpeg Micro support webhooks?&lt;/strong&gt;&lt;br&gt;
Not yet as a built-in feature, but you can build your own callback by storing the job ID in your database and running a cron job or background function to check status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What video formats does FFmpeg Micro accept?&lt;/strong&gt;&lt;br&gt;
MP4, WebM, MOV, AVI, and MKV for input. Output formats are MP4, WebM, and MOV. Most Supabase apps deal with MP4, which works perfectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does it cost?&lt;/strong&gt;&lt;br&gt;
FFmpeg Micro bills per minute of video processed. The free tier includes enough for testing. For production, pricing starts at $19/month. Check &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;ffmpeg-micro.com/pricing&lt;/a&gt; for current rates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I process multiple videos in parallel?&lt;/strong&gt;&lt;br&gt;
Yes. Each API call is independent. Fire off multiple transcode requests and poll them separately. FFmpeg Micro handles the scaling.&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>supabase</category>
      <category>ffmpeg</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a YouTube to TikTok Auto-Repost Pipeline (n8n + FFmpeg)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Thu, 11 Jun 2026 10:19:37 +0000</pubDate>
      <link>https://dev.to/javidjamae/build-a-youtube-to-tiktok-auto-repost-pipeline-n8n-ffmpeg-3k2d</link>
      <guid>https://dev.to/javidjamae/build-a-youtube-to-tiktok-auto-repost-pipeline-n8n-ffmpeg-3k2d</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/youtube-tiktok-repost-pipeline-n8n" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You post a YouTube video. Then you download it, crop it to vertical, and manually upload to TikTok. Every single time.&lt;/p&gt;

&lt;p&gt;If you're posting three times a week, that's an hour of drag-and-drop work that adds zero creative value. The video already exists. You just need it in a different shape.&lt;/p&gt;

&lt;p&gt;This guide builds an n8n workflow that watches your YouTube channel, crops new videos to 9:16 vertical format using the FFmpeg Micro API, and delivers them ready for TikTok. The whole thing runs hands-free once it's set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You're Building
&lt;/h2&gt;

&lt;p&gt;The pipeline has four stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;n8n watches your YouTube channel for new uploads&lt;/li&gt;
&lt;li&gt;The workflow grabs the video URL&lt;/li&gt;
&lt;li&gt;FFmpeg Micro crops it to 9:16 vertical (1080x1920)&lt;/li&gt;
&lt;li&gt;The output goes to Google Drive or directly to TikTok&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each stage is one node in n8n. Total setup time: about 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: YouTube Trigger
&lt;/h2&gt;

&lt;p&gt;In n8n, add an &lt;strong&gt;RSS Feed Read&lt;/strong&gt; node pointed at your YouTube channel's RSS feed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://www.youtube.com/feeds/videos.xml?channel_id&lt;span class="o"&gt;=&lt;/span&gt;YOUR_CHANNEL_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the polling interval to every 15 minutes. When a new video appears, this node fires and passes the video metadata downstream.&lt;/p&gt;

&lt;p&gt;n8n doesn't have a native "YouTube upload" trigger, but the RSS feed is reliable and doesn't require OAuth or API credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Get the Video URL
&lt;/h2&gt;

&lt;p&gt;The RSS feed gives you the YouTube watch URL, not a direct download link. You need the actual video file URL to send to FFmpeg Micro.&lt;/p&gt;

&lt;p&gt;If your videos are already stored somewhere accessible (Google Drive, S3, or any public URL), use that URL directly and skip this step. This is the simplest path.&lt;/p&gt;

&lt;p&gt;For YouTube-hosted videos, use a download service or self-hosted tool like yt-dlp running on your server. The output is a direct video URL that gets passed to the next node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Crop to Vertical with FFmpeg Micro
&lt;/h2&gt;

&lt;p&gt;This is the core transformation. Add an &lt;strong&gt;HTTP Request&lt;/strong&gt; node in n8n:&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="w"&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;"{{ $json.videoUrl }}"&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="w"&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;"-vf"&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;"crop=ih*9/16:ih,scale=1080:1920"&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;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;"copy"&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;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;-vf crop=ih*9/16:ih,scale=1080:1920&lt;/code&gt; filter does two things. First, it center-crops the frame to a 9:16 aspect ratio by calculating the width as &lt;code&gt;ih*9/16&lt;/code&gt; (input height times 9/16) and keeping the full height. Then it scales the result to exactly 1080x1920 pixels.&lt;/p&gt;

&lt;p&gt;Audio stays untouched with &lt;code&gt;-c:a copy&lt;/code&gt;. No re-encoding, no quality loss on the audio track.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro processes the video in the cloud and returns a job ID immediately. You don't wait for processing to finish in this node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Poll Until Complete
&lt;/h2&gt;

&lt;p&gt;Transcode jobs are async. Add a loop in n8n to check when the job finishes.&lt;/p&gt;

&lt;p&gt;Use a &lt;strong&gt;Wait&lt;/strong&gt; node set to 10 seconds, followed by an &lt;strong&gt;HTTP Request&lt;/strong&gt; node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET https://api.ffmpeg-micro.com/v1/transcodes/&lt;span class="o"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt;.id &lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire an &lt;strong&gt;IF&lt;/strong&gt; node after the status check: if &lt;code&gt;status&lt;/code&gt; is not &lt;code&gt;completed&lt;/code&gt;, loop back to the Wait node. When it's done, move forward.&lt;/p&gt;

&lt;p&gt;Then fetch 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;GET https://api.ffmpeg-micro.com/v1/transcodes/&lt;span class="o"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt;.id &lt;span class="o"&gt;}}&lt;/span&gt;/download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a signed HTTPS URL valid for 10 minutes. Download the file in the next node before the link expires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Save or Publish
&lt;/h2&gt;

&lt;p&gt;You now have a cropped, vertical MP4. Where it goes depends on your workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Drive (recommended for most creators).&lt;/strong&gt; Add a Google Drive upload node to drop the file into a "TikTok Ready" folder. Review the video, add captions and hashtags, then post from your phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct TikTok API.&lt;/strong&gt; Use TikTok's Content Posting API to upload automatically. This requires a TikTok developer account and approved app. Add an HTTP Request node pointed at TikTok's upload endpoint with the video file.&lt;/p&gt;

&lt;p&gt;Most solo creators start with Google Drive. It adds one manual step but gives you a chance to review before publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing the Crop
&lt;/h2&gt;

&lt;p&gt;Center-crop works great for talking-head content where the speaker fills the middle of the frame. For other content types, adjust the filter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Left-aligned crop&lt;/strong&gt; (keep the left side of the frame):&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;"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;"-vf"&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;"crop=ih*9/16:ih:0:0,scale=1080:1920"&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;&lt;strong&gt;Right-aligned crop&lt;/strong&gt; (keep the right side):&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;"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;"-vf"&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;"crop=ih*9/16:ih:iw-ih*9/16:0,scale=1080:1920"&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;&lt;strong&gt;Padding instead of cropping&lt;/strong&gt; (adds black bars, keeps all content):&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;"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;"-vf"&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;"scale=1080:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2"&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;Talking head content = center crop. Screen recordings = padding. B-roll = test a few options and see what looks best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Pipeline Works
&lt;/h2&gt;

&lt;p&gt;The YouTube to TikTok repost is one of the highest-leverage automations for solo creators. You already made the content. Cross-posting is distribution, not creation. The only thing stopping most people is the manual crop-and-upload friction.&lt;/p&gt;

&lt;p&gt;n8n Cloud can't run FFmpeg directly, but it doesn't need to. The video processing happens on FFmpeg Micro's infrastructure. n8n just sends HTTP requests and orchestrates the flow. This works on both n8n Cloud and self-hosted n8n.&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" rel="noopener noreferrer"&gt;Get a free API key&lt;/a&gt; and build this pipeline in under an hour.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Can I use this for Instagram Reels and YouTube Shorts too?
&lt;/h3&gt;

&lt;p&gt;Yes. The 9:16 vertical format at 1080x1920 is the standard for TikTok, Instagram Reels, and YouTube Shorts. Add more output nodes in your n8n workflow to deliver the same cropped video to multiple platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does the crop take?
&lt;/h3&gt;

&lt;p&gt;A 10-minute landscape video typically processes in 15 to 30 seconds. FFmpeg Micro auto-scales, so processing time stays consistent regardless of how many jobs you run in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my video is longer than 10 minutes?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro handles videos of any length. Longer videos take proportionally longer to process and use more API minutes. If you only want a clip, add &lt;code&gt;-ss&lt;/code&gt; (start time) and &lt;code&gt;-t&lt;/code&gt; (duration) options alongside the crop filter to trim and crop in a single pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does n8n Cloud support this workflow?
&lt;/h3&gt;

&lt;p&gt;Yes. n8n Cloud handles the orchestration (triggers, HTTP requests, logic). FFmpeg Micro handles the video processing in its own cloud. No binary installs or server access needed on the n8n side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a TikTok developer account?
&lt;/h3&gt;

&lt;p&gt;Only for automated posting. If you save to Google Drive and post manually from your phone, no TikTok credentials are needed. Most creators prefer the manual review step anyway since it lets them customize captions and hashtags per platform.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>tiktok</category>
      <category>automation</category>
    </item>
    <item>
      <title>FFmpeg in Make.com: 7 Scenarios That Don't Require Self-Hosting</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Thu, 11 Jun 2026 10:19:28 +0000</pubDate>
      <link>https://dev.to/javidjamae/ffmpeg-in-makecom-7-scenarios-that-dont-require-self-hosting-3226</link>
      <guid>https://dev.to/javidjamae/ffmpeg-in-makecom-7-scenarios-that-dont-require-self-hosting-3226</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-make-com-scenarios" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Make.com doesn't have a native FFmpeg module. If you've searched the app directory for one, you already know. But you can still run any FFmpeg operation from a Make.com scenario using the HTTP module and a cloud FFmpeg API.&lt;/p&gt;

&lt;p&gt;No Docker container. No server. No binary to install. One HTTP request with a JSON body, and your video gets processed in the cloud.&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. All you need in Make.com is the HTTP module.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: The HTTP Module
&lt;/h2&gt;

&lt;p&gt;Every scenario below uses the same HTTP "Make a Request" module. Configure it once:&lt;/p&gt;

&lt;ul&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;Method:&lt;/strong&gt; POST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers:&lt;/strong&gt; &lt;code&gt;Authorization: Bearer YOUR_API_KEY&lt;/code&gt; and &lt;code&gt;Content-Type: application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body type:&lt;/strong&gt; Raw JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get a free API key at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;. The JSON body changes per scenario. The module config stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Convert Customer Uploads to Web-Ready MP4
&lt;/h2&gt;

&lt;p&gt;Users upload MOV files from iPhones. Your web player only handles MP4.&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="w"&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/upload.mov"&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;"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;"high"&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;"1080p"&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;The &lt;code&gt;preset&lt;/code&gt; mode handles codec and quality for you. No FFmpeg flags required.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Trim Clips for Landing Pages
&lt;/h2&gt;

&lt;p&gt;You have raw interview footage. You need a 15-second highlight for your homepage.&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="w"&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-storage.com/interview.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;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="w"&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;"-ss"&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;"00:02:15"&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;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;"-t"&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;"15"&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;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 grabs 15 seconds starting at the 2:15 mark. The output is a clean, re-encoded MP4 ready for your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Create Vertical Shorts from Landscape Video
&lt;/h2&gt;

&lt;p&gt;One horizontal video, cropped to 9:16 for TikTok, Instagram Reels, and YouTube Shorts.&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="w"&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-storage.com/landscape.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;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="w"&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;"-vf"&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;"crop=ih*9/16:ih,scale=1080:1920"&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;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 crop math (&lt;code&gt;ih*9/16:ih&lt;/code&gt;) takes the center of the frame and drops the sides. &lt;code&gt;scale=1080:1920&lt;/code&gt; outputs at standard vertical resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compress Old Video Archives
&lt;/h2&gt;

&lt;p&gt;Your Google Drive is full of uncompressed screen recordings eating storage. Compress them without visible quality loss.&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="w"&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-storage.com/raw-recording.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;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="w"&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="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;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;"23"&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;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;"-preset"&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;"slow"&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;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;"copy"&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;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;CRF 23 is visually lossless for most content. The &lt;code&gt;slow&lt;/code&gt; preset squeezes more compression at the cost of processing time. Audio stays untouched with &lt;code&gt;-c:a copy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Add Text Branding to Every Video
&lt;/h2&gt;

&lt;p&gt;Your marketing team publishes 20 videos a week. Every one needs the company name burned into the frame.&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="w"&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-storage.com/raw-video.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;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="w"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Company Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"style"&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;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"W-tw-20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"H-th-20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"boxBorderW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&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;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;The &lt;code&gt;@text-overlay&lt;/code&gt; virtual option handles font rendering, word wrap, and positioning. No &lt;code&gt;drawtext&lt;/code&gt; filter syntax to wrestle with.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Stitch Multiple Clips Into One Highlight Reel
&lt;/h2&gt;

&lt;p&gt;Three separate clips from a product demo. You need them joined into a single video for your sales deck.&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="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;"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-storage.com/clip-1.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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&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-storage.com/clip-2.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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&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-storage.com/clip-3.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;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;"high"&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;Pass multiple URLs in the &lt;code&gt;inputs&lt;/code&gt; array. FFmpeg Micro concatenates them in order and re-encodes to a consistent output format.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Transcode to WebM for Web Delivery
&lt;/h2&gt;

&lt;p&gt;Your site uses the &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag with WebM as the primary source. VP9 delivers smaller files than H.264 at the same visual quality.&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="w"&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-storage.com/source.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;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="w"&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="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;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="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;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="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;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;VP9 with CRF 30 produces great quality at roughly half the file size of H.264. Setting &lt;code&gt;-b:v 0&lt;/code&gt; lets the CRF value fully control quality instead of capping bitrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polling for Results
&lt;/h2&gt;

&lt;p&gt;Every transcode job runs asynchronously. After creating one, poll the status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET https://api.ffmpeg-micro.com/v1/transcodes/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;status&lt;/code&gt; is &lt;code&gt;completed&lt;/code&gt;, 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;GET https://api.ffmpeg-micro.com/v1/transcodes/&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;/download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Make.com, use a "Repeater" module with a delay to poll every 10 seconds until the job finishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;FFmpeg Micro has a free tier with no credit card required. Grab an API key, drop an HTTP module into your next Make.com scenario, and try one of these JSON bodies.&lt;/p&gt;

&lt;p&gt;No server. No Docker. No FFmpeg installation. Just an HTTP call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;Get your free API key&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Does Make.com have a native FFmpeg module?
&lt;/h3&gt;

&lt;p&gt;No. Make.com can't run FFmpeg directly. You need an external API like FFmpeg Micro, which you call through Make.com's HTTP module. One POST request per video operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does video processing cost in Make.com?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro charges per minute of video processed, separate from your Make.com plan. There's a free tier for getting started. Most Make.com workflows process short clips (under 5 minutes each), so costs stay low. See &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; for current rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I chain multiple FFmpeg operations in one scenario?
&lt;/h3&gt;

&lt;p&gt;Yes. Each FFmpeg Micro API call handles one operation. Chain multiple HTTP modules in your Make.com scenario to run sequential operations. For example: trim first, then compress, then add text. Each step gets its own HTTP module with a different JSON body.&lt;/p&gt;

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

&lt;p&gt;Input: MP4, WebM, AVI, MOV, MKV, plus audio formats (MP3, WAV, FLAC, AAC). Output: MP4, WebM, or MOV. Most Make.com workflows use MP4 for both input and output.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does processing take?
&lt;/h3&gt;

&lt;p&gt;Most operations complete in 5 to 30 seconds for videos under 10 minutes. Longer videos or compute-heavy encoding (like H.265 with the &lt;code&gt;slow&lt;/code&gt; preset) take proportionally longer. FFmpeg Micro scales automatically, so processing time stays consistent regardless of how many jobs you run.&lt;/p&gt;

</description>
      <category>makecom</category>
      <category>ffmpeg</category>
      <category>nocode</category>
      <category>automation</category>
    </item>
    <item>
      <title>FFmpeg in n8n: 5 Video Automations You Can Ship Today</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:04:07 +0000</pubDate>
      <link>https://dev.to/javidjamae/ffmpeg-in-n8n-5-video-automations-you-can-ship-today-10e1</link>
      <guid>https://dev.to/javidjamae/ffmpeg-in-n8n-5-video-automations-you-can-ship-today-10e1</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-n8n-video-automations" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've connected n8n to your video sources. Maybe it's a Google Drive trigger, a webhook from your CMS, or a scheduled batch job. Now you need to actually do something with those videos. Trim them, resize them, convert formats, add watermarks.&lt;/p&gt;

&lt;p&gt;Running FFmpeg locally on your n8n server is one option, but it's fragile and doesn't work on n8n Cloud. Calling a cloud FFmpeg API from the HTTP Request node is simpler and scales without touching your infrastructure.&lt;/p&gt;

&lt;p&gt;Here are five video automations you can build in n8n right now using FFmpeg Micro's API. Each one is a single HTTP Request node with a JSON body you can copy and paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Auto-generate thumbnails from uploaded videos
&lt;/h2&gt;

&lt;p&gt;Every video platform needs thumbnails. Instead of asking users to upload a separate image, extract a frame automatically.&lt;/p&gt;

&lt;p&gt;The trick: transcode a single frame at a specific timestamp into an image-friendly format. Use FFmpeg's &lt;code&gt;-ss&lt;/code&gt; flag to seek to the right moment and &lt;code&gt;-frames:v 1&lt;/code&gt; to grab exactly one frame.&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="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;"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.storage.com/uploaded-video.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;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="w"&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;"-ss"&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;"5"&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;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;"-frames: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;"1"&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;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;"-f"&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;"image2"&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;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;"-q: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;"2"&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;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;POST this to &lt;code&gt;https://api.ffmpeg-micro.com/v1/transcodes&lt;/code&gt; with your API key as a Bearer token. 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 grab the output from &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Wire this into an n8n workflow triggered by a file upload webhook, and every new video gets a thumbnail without anyone lifting a finger.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Convert videos to web-friendly MP4
&lt;/h2&gt;

&lt;p&gt;Users upload all kinds of formats. MOV from iPhones, MKV from screen recorders, AVI from legacy tools. Your web player needs MP4 with H.264 video and AAC audio.&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="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;"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.storage.com/uploaded-video.mkv"&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;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;"high"&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;"1080p"&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;The &lt;code&gt;preset&lt;/code&gt; approach is the simplest. Set &lt;code&gt;quality&lt;/code&gt; to &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, or &lt;code&gt;low&lt;/code&gt; (maps to FFmpeg CRF values of 18, 23, and 28). Set &lt;code&gt;resolution&lt;/code&gt; to cap the output size. Done.&lt;/p&gt;

&lt;p&gt;This is the automation most teams build first. Trigger it from a Google Drive watch, an S3 event, or a database row creation. The converted file lands in your output bucket ready for streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Batch resize videos for social media
&lt;/h2&gt;

&lt;p&gt;One source video needs to become three: 1080p for YouTube, 720p for Twitter/X, and 480p for mobile fallback. Instead of running three manual conversions, loop through the sizes in n8n.&lt;/p&gt;

&lt;p&gt;In your n8n workflow, use a SplitInBatches node feeding into an HTTP Request node. For each resolution, POST:&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="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;"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.storage.com/source-video.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;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;"{{ $json.resolution }}"&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;Set up the SplitInBatches node with three items:&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="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;"1080p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"youtube"&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;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="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"twitter"&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;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;"480p"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mobile"&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;Each iteration fires a separate transcode job. n8n handles the orchestration. You end up with three optimized versions from one upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Add a watermark to client deliverables
&lt;/h2&gt;

&lt;p&gt;Agencies and freelancers: you send preview videos with a watermark, then deliver the clean version after payment. Automate the watermark step so it happens the moment a video is uploaded.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro supports the &lt;code&gt;@text-overlay&lt;/code&gt; virtual option for text watermarks:&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="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;"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.storage.com/client-video.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;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="w"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PREVIEW - youragency.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"style"&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;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(w-text_w)/2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(h-text_h)/2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"boxBorderW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&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;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;The &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; expressions center the watermark on the video. &lt;code&gt;boxBorderW&lt;/code&gt; adds a background box behind the text so it's readable on any footage. Adjust &lt;code&gt;fontSize&lt;/code&gt; to match your video resolution.&lt;/p&gt;

&lt;p&gt;Build the n8n workflow: trigger on file upload, run the watermark transcode, save the result alongside the original, notify the team via Slack or email.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Compress videos before archiving
&lt;/h2&gt;

&lt;p&gt;Storage costs add up. If you're archiving old project files, user uploads, or training recordings, compress them first. A good H.264 encode at medium quality can cut file size 50-70% with barely noticeable quality loss.&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="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;"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.storage.com/archive-video.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;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="w"&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="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;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;"28"&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;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;"-preset"&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;"slow"&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;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="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;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;"96k"&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;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;CRF 28 is aggressive but perfectly fine for archival video that won't be edited again. The &lt;code&gt;slow&lt;/code&gt; preset trades encoding speed for better compression. Audio gets dropped to 96kbps AAC, which is enough for voice-heavy content.&lt;/p&gt;

&lt;p&gt;Schedule this as a weekly n8n workflow that scans for videos older than 30 days, compresses them, replaces the originals, and logs the space savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shared setup across all five
&lt;/h2&gt;

&lt;p&gt;Every automation above follows the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;POST&lt;/strong&gt; to &lt;code&gt;https://api.ffmpeg-micro.com/v1/transcodes&lt;/code&gt; with your JSON body&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poll&lt;/strong&gt; &lt;code&gt;GET /v1/transcodes/{id}&lt;/code&gt; until status is &lt;code&gt;completed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download&lt;/strong&gt; via &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt; to get a signed URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Authentication is a Bearer token in the &lt;code&gt;Authorization&lt;/code&gt; header. Set it up once as an n8n credential (Header Auth type) and reuse it across all your video nodes.&lt;/p&gt;

&lt;p&gt;The API accepts public URLs as input, so you can pass links from S3, Google Cloud Storage, Cloudflare R2, or any CDN directly. No pre-upload step needed for files that are already publicly accessible.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Do I need to install anything on my n8n server?&lt;/strong&gt;&lt;br&gt;
No. Everything runs through HTTP Request nodes. Works on n8n Cloud and self-hosted installs with zero dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I chain multiple operations?&lt;/strong&gt;&lt;br&gt;
Not in a single API call (yet). But you can chain them in n8n. Take the output URL from one transcode and use it as the input for the next. Resize first, then add a watermark, for example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if a job fails mid-workflow?&lt;/strong&gt;&lt;br&gt;
The transcode status will be &lt;code&gt;failed&lt;/code&gt; with an &lt;code&gt;error_message&lt;/code&gt;. Add an IF node after your polling loop to check for failures and route to an error handler (Slack notification, email alert, retry logic).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does this cost?&lt;/strong&gt;&lt;br&gt;
FFmpeg Micro bills per minute of video processed. There's a free tier with 10 minutes per month. Enough to test all five automations on short clips. Paid plans scale from there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I process audio files too?&lt;/strong&gt;&lt;br&gt;
Yes. The API accepts MP3, WAV, FLAC, and AAC. Same endpoint, same workflow. Useful for podcast post-processing, audio normalization, or format conversion.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>automation</category>
      <category>video</category>
    </item>
    <item>
      <title>How to Call FFmpeg from n8n Without the Execute Command Node</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Wed, 10 Jun 2026 16:03:50 +0000</pubDate>
      <link>https://dev.to/javidjamae/how-to-call-ffmpeg-from-n8n-without-the-execute-command-node-3ipj</link>
      <guid>https://dev.to/javidjamae/how-to-call-ffmpeg-from-n8n-without-the-execute-command-node-3ipj</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/n8n-ffmpeg-without-execute-command" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most n8n users who want FFmpeg reach for the Execute Command node first. Makes sense. You've got a shell, you know the command, you just want to run &lt;code&gt;ffmpeg -i input.mp4 output.webm&lt;/code&gt; and move on.&lt;/p&gt;

&lt;p&gt;Then you hit the wall. n8n Cloud doesn't support Execute Command at all. Self-hosted? You need FFmpeg installed on the same machine, and the Execute Command node is a security risk that most production setups lock down. Even when it works, you're stuck managing FFmpeg versions, codecs, and disk space on your n8n server.&lt;/p&gt;

&lt;p&gt;There's a better way. Use the HTTP Request node to call an FFmpeg API instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Execute Command node breaks in production
&lt;/h2&gt;

&lt;p&gt;The Execute Command node runs arbitrary shell commands on the n8n host machine. For video processing, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n Cloud users are locked out.&lt;/strong&gt; The Execute Command node isn't available on n8n Cloud. If you're on Cloud, you literally can't run FFmpeg this way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted setups need FFmpeg installed.&lt;/strong&gt; You need to install FFmpeg on the same server, keep it updated, and make sure the right codecs are compiled in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security exposure.&lt;/strong&gt; Any workflow with Execute Command can run any shell command. Most teams disable it in production or restrict it behind environment flags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource contention.&lt;/strong&gt; Video processing is CPU-heavy. Running FFmpeg on the same machine as n8n means your workflows compete with video jobs for CPU and memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these problems exist when you call an external API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTTP Request node approach
&lt;/h2&gt;

&lt;p&gt;Instead of running FFmpeg locally, send an HTTP request to a cloud FFmpeg service. The video gets processed on remote infrastructure, and n8n just orchestrates the workflow.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro is one such service. One POST request starts a transcode job. You poll for completion, then grab a download link. All through standard HTTP calls that work on n8n Cloud and self-hosted alike.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-step: convert a video in n8n
&lt;/h2&gt;

&lt;p&gt;Here's the complete setup. Three HTTP Request nodes: start the job, wait for it to finish, get the download URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start the 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; Predefined Credential Type &amp;gt; Header Auth

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;Authorization&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value: &lt;code&gt;Bearer YOUR_API_KEY&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body Content Type:&lt;/strong&gt; JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Body:&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="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;"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/my-video.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;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;"high"&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;"1080p"&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;The &lt;code&gt;inputs&lt;/code&gt; array takes public URLs or GCS URLs from the upload flow. &lt;code&gt;outputFormat&lt;/code&gt; accepts &lt;code&gt;mp4&lt;/code&gt;, &lt;code&gt;webm&lt;/code&gt;, or &lt;code&gt;mov&lt;/code&gt;. The &lt;code&gt;preset&lt;/code&gt; object handles quality and resolution without writing raw FFmpeg flags.&lt;/p&gt;

&lt;p&gt;This returns a job object with an &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;status: "queued"&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Poll until complete
&lt;/h3&gt;

&lt;p&gt;Add a second HTTP Request node inside a loop (or use n8n's Wait node with a 10-second delay):&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.id }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Same Header Auth credential&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response includes a &lt;code&gt;status&lt;/code&gt; field. When it changes from &lt;code&gt;queued&lt;/code&gt; to &lt;code&gt;processing&lt;/code&gt; to &lt;code&gt;completed&lt;/code&gt;, you're done. If it returns &lt;code&gt;failed&lt;/code&gt;, the &lt;code&gt;error_message&lt;/code&gt; field tells you what went wrong.&lt;/p&gt;

&lt;p&gt;A simple approach: add a Wait node (10 seconds), then an IF node that checks &lt;code&gt;{{ $json.status }} != "completed"&lt;/code&gt;. Loop back to the Wait node if it's still processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Download the result
&lt;/h3&gt;

&lt;p&gt;Once the job status is &lt;code&gt;completed&lt;/code&gt;, grab the signed download URL:&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.id }}/download&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Same Header Auth credential&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This returns a &lt;code&gt;url&lt;/code&gt; field with a signed HTTPS link valid for 10 minutes. Use another HTTP Request node to download the file, or pass the URL to the next step in your workflow (upload to S3, send via email, post to Slack).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using advanced FFmpeg options
&lt;/h2&gt;

&lt;p&gt;The preset system covers common operations, but you can also pass raw FFmpeg flags through the &lt;code&gt;options&lt;/code&gt; array. This is useful for specific codec settings, bitrate control, or audio extraction.&lt;/p&gt;

&lt;p&gt;Convert to WebM with a specific CRF value:&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="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;"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/input.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;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="w"&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="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;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="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;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="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;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;Extract audio only:&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="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;"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="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;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="w"&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;"-vn"&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="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;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="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;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="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;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;These JSON bodies go straight into the HTTP Request node. No FFmpeg installation, no command-line syntax to debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading local files from n8n
&lt;/h2&gt;

&lt;p&gt;If your workflow generates video files (screen recordings, camera uploads, files from other nodes), you need to upload them before transcoding. FFmpeg Micro uses a 3-step upload flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get a presigned URL:&lt;/strong&gt; POST to &lt;code&gt;/v1/upload/presigned-url&lt;/code&gt; with the filename, content type, and file size. The response includes an &lt;code&gt;uploadUrl&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upload the file:&lt;/strong&gt; PUT the raw file bytes to the presigned URL. This goes directly to Google Cloud Storage, not through the API server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Confirm the upload:&lt;/strong&gt; POST to &lt;code&gt;/v1/upload/confirm&lt;/code&gt; with the filename and file size. The response includes a &lt;code&gt;gcsUrl&lt;/code&gt; that you pass to the transcode endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In n8n, each step is one HTTP Request node. The presigned URL upload is fast because n8n streams directly to cloud storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Using the wrong content type.&lt;/strong&gt; The transcode endpoint needs &lt;code&gt;Content-Type: application/json&lt;/code&gt;. n8n's HTTP Request node sets this automatically when you choose JSON body, but double-check if you're building the request manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to poll.&lt;/strong&gt; Transcode jobs are async. The POST request returns immediately with &lt;code&gt;status: "queued"&lt;/code&gt;. You need the polling loop to know when the output is ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hardcoding the download URL.&lt;/strong&gt; The signed download URL expires after 10 minutes. Always call the &lt;code&gt;/download&lt;/code&gt; endpoint fresh when you need the file. Don't store the signed URL for later use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;options&lt;/code&gt; and &lt;code&gt;preset&lt;/code&gt; together.&lt;/strong&gt; If you include both in the request body, &lt;code&gt;options&lt;/code&gt; takes priority and &lt;code&gt;preset&lt;/code&gt; is ignored. Pick one approach per request.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Can I use this on n8n Cloud?&lt;/strong&gt;&lt;br&gt;
Yes. The HTTP Request node works on n8n Cloud with no restrictions. That's the whole point. You don't need Execute Command, and you don't need FFmpeg installed anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What video formats does the API accept?&lt;/strong&gt;&lt;br&gt;
MP4, WebM, AVI, QuickTime (MOV), and MKV for video. MP3, WAV, FLAC, and AAC for audio. You can also pass public URLs to files hosted anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does a transcode take?&lt;/strong&gt;&lt;br&gt;
It depends on file size and complexity. A 1-minute 1080p video typically processes in 10-30 seconds. Longer videos or complex operations (multi-input compositions, H.265 encoding) take proportionally longer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free tier?&lt;/strong&gt;&lt;br&gt;
FFmpeg Micro has a free tier with 10 minutes of processing per month. Enough to test your workflow and process a handful of videos. Paid plans start when you need more volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the job fails?&lt;/strong&gt;&lt;br&gt;
The job status changes to &lt;code&gt;failed&lt;/code&gt; and the &lt;code&gt;error_message&lt;/code&gt; field explains what went wrong. Common causes: unsupported input format, corrupted source file, or incompatible FFmpeg options. Check the error, fix the input, and retry.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>FFmpeg Segment Muxer: segment_time, strftime, and reset_timestamps</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Sat, 30 May 2026 10:18:09 +0000</pubDate>
      <link>https://dev.to/javidjamae/ffmpeg-segment-muxer-segmenttime-strftime-and-resettimestamps-4mkj</link>
      <guid>https://dev.to/javidjamae/ffmpeg-segment-muxer-segmenttime-strftime-and-resettimestamps-4mkj</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-segment-muxer-guide" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The FFmpeg segment muxer splits a video into multiple files based on duration. Three flags cause most of the confusion: &lt;code&gt;segment_time&lt;/code&gt;, &lt;code&gt;strftime&lt;/code&gt;, and &lt;code&gt;reset_timestamps&lt;/code&gt;. The official FFmpeg docs define each in one sentence. This guide shows what they actually do, with verified examples and the gotchas that trip people up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Split a video into 5-second segments with timestamped filenames and clean playback timestamps:&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; copy &lt;span class="nt"&gt;-f&lt;/span&gt; segment &lt;span class="nt"&gt;-segment_time&lt;/span&gt; 5 &lt;span class="nt"&gt;-reset_timestamps&lt;/span&gt; 1 &lt;span class="nt"&gt;-strftime&lt;/span&gt; 1 &lt;span class="s2"&gt;"segment_%Y%m%d_%H%M%S.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What segment_time Does (and Why Your Segments Are the Wrong Length)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;segment_time&lt;/code&gt; sets the target duration for each segment in seconds. With &lt;code&gt;-c copy&lt;/code&gt; (stream copy, no re-encoding), FFmpeg can only cut at keyframe boundaries. If your video has keyframes every 8 seconds and you set &lt;code&gt;-segment_time 3&lt;/code&gt;, your segments will be roughly 8 seconds each.&lt;/p&gt;

&lt;p&gt;Actual segment durations from a 13-second test video with &lt;code&gt;-segment_time 3 -c copy&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Segment&lt;/th&gt;
&lt;th&gt;Expected&lt;/th&gt;
&lt;th&gt;Actual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;output_000.mp4&lt;/td&gt;
&lt;td&gt;~3s&lt;/td&gt;
&lt;td&gt;8.34s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;output_001.mp4&lt;/td&gt;
&lt;td&gt;~3s&lt;/td&gt;
&lt;td&gt;5.01s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The muxer waited for the next keyframe after the 3-second mark. Stream copying can't split mid-GOP because there's no re-encoding to create a new keyframe at the cut point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix: force keyframes at your split points.&lt;/strong&gt; This requires re-encoding:&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;-preset&lt;/span&gt; ultrafast &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-force_key_frames&lt;/span&gt; &lt;span class="s2"&gt;"expr:gte(t,n_forced*3)"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; segment &lt;span class="nt"&gt;-segment_time&lt;/span&gt; 3 &lt;span class="nt"&gt;-reset_timestamps&lt;/span&gt; 1 &lt;span class="s2"&gt;"output_%03d.mp4"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the segments are exactly 3 seconds each.&lt;/p&gt;

&lt;h2&gt;
  
  
  reset_timestamps: Why Your Player Shows the Wrong Time
&lt;/h2&gt;

&lt;p&gt;Without &lt;code&gt;reset_timestamps&lt;/code&gt;, each segment inherits its position from the original video. Check with ffprobe: &lt;code&gt;start_time=8.408000&lt;/code&gt;. Add &lt;code&gt;-reset_timestamps 1&lt;/code&gt; and it becomes &lt;code&gt;start_time=0.000000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're generating segments for standalone playback, always use &lt;code&gt;-reset_timestamps 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  strftime: Timestamped Filenames
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;-strftime 1&lt;/code&gt;, use date/time tokens in the filename: &lt;code&gt;segment_%Y%m%d_%H%M%S.mp4&lt;/code&gt; produces files like &lt;code&gt;segment_20260530_050835.mp4&lt;/code&gt;. You can't mix &lt;code&gt;%03d&lt;/code&gt; and strftime in the same pattern.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Segments way longer than segment_time&lt;/strong&gt;: Using &lt;code&gt;-c copy&lt;/code&gt; with a GOP interval larger than segment time. Re-encode with &lt;code&gt;-force_key_frames&lt;/code&gt; or accept keyframe-aligned cuts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Player shows black frames&lt;/strong&gt;: Missing &lt;code&gt;-reset_timestamps 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Could not write header" error&lt;/strong&gt;: Use &lt;code&gt;-segment_format mp4&lt;/code&gt; explicitly.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Does segment_time guarantee exact segment durations?
&lt;/h3&gt;

&lt;p&gt;Only when re-encoding. With &lt;code&gt;-c copy&lt;/code&gt;, FFmpeg cuts at the nearest keyframe after the target time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between the segment muxer and HLS muxer?
&lt;/h3&gt;

&lt;p&gt;The HLS muxer (&lt;code&gt;-f hls&lt;/code&gt;) is purpose-built for Apple HLS streaming. The segment muxer (&lt;code&gt;-f segment&lt;/code&gt;) is a general-purpose splitter. Use HLS muxer for streaming delivery, segment muxer for batch splitting.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last verified: 2026-05-30 against FFmpeg 4.3&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>cli</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>FFmpeg as a Service: Process Video with One API Call</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Fri, 29 May 2026 10:13:49 +0000</pubDate>
      <link>https://dev.to/javidjamae/ffmpeg-as-a-service-process-video-with-one-api-call-3a43</link>
      <guid>https://dev.to/javidjamae/ffmpeg-as-a-service-process-video-with-one-api-call-3a43</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-as-a-service" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You've got FFmpeg running locally. The transcoding works. Then someone asks you to deploy it, and you realize you've signed up for a second job: server admin for a video processing pipeline.&lt;/p&gt;

&lt;p&gt;FFmpeg as a service means handing that infrastructure off. You send an HTTP request with your video and the operation you want. You get the processed result back. No FFmpeg binary to install. No server to maintain. No scaling to figure out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "FFmpeg as a Service" Actually Means
&lt;/h2&gt;

&lt;p&gt;FFmpeg is the most widely used video processing tool in the world. It handles transcoding, trimming, merging, watermarking, audio extraction, thumbnail generation, and hundreds of other operations. The tool itself isn't the problem. Everything around it is.&lt;/p&gt;

&lt;p&gt;Running FFmpeg in production means installing it on a server, managing dependencies across operating systems, building a job queue so processing doesn't block your app, and scaling horizontally when users start uploading more videos than one machine can handle. And patching it when the next security vulnerability drops.&lt;/p&gt;

&lt;p&gt;FFmpeg as a service is a cloud API that runs FFmpeg for you. You send a request, it processes the video, you download the result. Services like FFmpeg Micro, Rendi, and ffmpeg-api.com all take this approach, though they differ in how much of FFmpeg's power they actually expose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Keep Building This Themselves
&lt;/h2&gt;

&lt;p&gt;If you've ever shelled out to FFmpeg from application code, you know the pattern:&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;-preset&lt;/span&gt; medium &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works on your laptop. Then you deploy and discover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Docker image is 800MB because of FFmpeg dependencies&lt;/li&gt;
&lt;li&gt;Processing blocks your event loop or eats your worker pool&lt;/li&gt;
&lt;li&gt;A malformed input crashes the process and takes down adjacent services&lt;/li&gt;
&lt;li&gt;You need GPU acceleration for H.265 but your cloud instances don't have GPUs&lt;/li&gt;
&lt;li&gt;At 200 videos per day, one server can't keep up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams hit this wall somewhere between "it works in development" and "it needs to work in production."&lt;/p&gt;

&lt;h2&gt;
  
  
  Before and After: Local FFmpeg vs. API Call
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local approach&lt;/strong&gt; (Node.js subprocess):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;'&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;ffmpeg -i /tmp/input.mp4 -c:v libx264 -crf 23 -preset medium /tmp/output.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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="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;You need FFmpeg installed. You need disk space for temp files. You need to handle crashes, timeouts, and cleanup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API approach&lt;/strong&gt; (one HTTP 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/input.mp4"}],
    "outputFormat": "mp4",
    "preset": {
      "quality": "high",
      "resolution": "1080p"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a job ID. Poll &lt;code&gt;GET /v1/transcodes/{id}&lt;/code&gt; until status is &lt;code&gt;completed&lt;/code&gt;, then call &lt;code&gt;GET /v1/transcodes/{id}/download&lt;/code&gt; for a signed download URL. No server. No FFmpeg binary. No cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Custom FFmpeg Commands?
&lt;/h2&gt;

&lt;p&gt;Most video processing APIs lock you into preset operations. If you need something outside those presets, you're stuck.&lt;/p&gt;

&lt;p&gt;FFmpeg Micro works differently. You can pass raw FFmpeg options directly through the API:&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/input.mp4"}],
    "outputFormat": "mp4",
    "options": [
      {"option": "-c:v", "argument": "libx265"},
      {"option": "-crf", "argument": "28"},
      {"option": "-preset", "argument": "slow"}
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same FFmpeg flags you already know. The only difference is who runs the binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls When Switching to a Service
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assuming all APIs expose full FFmpeg.&lt;/strong&gt; Most don't. AWS MediaConvert, Cloudinary, and Mux give you fixed operation sets. If you need a specific FFmpeg filter or codec combination, you need a service that runs FFmpeg and lets you pass options through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring upload time.&lt;/strong&gt; For large videos, upload can take longer than processing itself. Use presigned URLs for direct-to-storage uploads instead of streaming through your backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expecting synchronous responses.&lt;/strong&gt; Video processing takes time. A 10-minute video might take 30 to 60 seconds to transcode. Don't build your integration expecting instant results. Poll the job status endpoint or set up webhooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-engineering at low volume.&lt;/strong&gt; If you're processing 5 videos a day, a curl call in a cron job is fine. You don't need Kafka and a fleet of workers yet. Start simple.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Is FFmpeg as a service the same as a video transcoding API?
&lt;/h3&gt;

&lt;p&gt;Not exactly. A video transcoding API might use FFmpeg internally but only expose a subset of its capabilities through a proprietary interface. FFmpeg as a service gives you FFmpeg itself, accessed through an API. You still use FFmpeg options and flags. The service handles the infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg as a service with Make.com, n8n, or Zapier?
&lt;/h3&gt;

&lt;p&gt;Yes. Any platform that makes HTTP requests can call an FFmpeg API. No custom code required.&lt;/p&gt;

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

&lt;p&gt;FFmpeg reads virtually every video and audio format. When using an API, supported output formats depend on the provider. FFmpeg Micro accepts any FFmpeg-readable input and outputs MP4, WebM, or MOV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is my video data secure?
&lt;/h3&gt;

&lt;p&gt;Files uploaded to FFmpeg Micro are stored in isolated cloud storage, processed in ephemeral containers, and cleaned up automatically after processing completes. No video data is shared between users or retained beyond the job lifecycle.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>api</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Create a Video from Images with FFmpeg</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Thu, 28 May 2026 10:16:13 +0000</pubDate>
      <link>https://dev.to/javidjamae/how-to-create-a-video-from-images-with-ffmpeg-1eb</link>
      <guid>https://dev.to/javidjamae/how-to-create-a-video-from-images-with-ffmpeg-1eb</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-create-video-from-images" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Turning a folder of images into a playable video file is one of the most common FFmpeg tasks. Product screenshots for a demo reel, AI-generated frames for a social post, timelapse photos from a construction site. The command is short but the flags trip people up, especially &lt;code&gt;-pix_fmt&lt;/code&gt;, &lt;code&gt;-framerate&lt;/code&gt;, and image naming patterns.&lt;/p&gt;

&lt;p&gt;FFmpeg can convert a numbered image sequence into an H.264 MP4 with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basic Command: Image Sequence to MP4
&lt;/h2&gt;

&lt;p&gt;If your images are named with sequential numbers (&lt;code&gt;frame_001.png&lt;/code&gt;, &lt;code&gt;frame_002.png&lt;/code&gt;, etc.):&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;-framerate&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; frame_%03d.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-r&lt;/span&gt; 30 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-framerate 1&lt;/code&gt; tells FFmpeg to read one image per second. Change to &lt;code&gt;2&lt;/code&gt; for half-second images, or &lt;code&gt;1/5&lt;/code&gt; for five seconds per image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-i frame_%03d.png&lt;/code&gt; is the input pattern. &lt;code&gt;%03d&lt;/code&gt; matches zero-padded three-digit numbers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c:v libx264&lt;/code&gt; encodes with H.264, the most widely supported video codec.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-pix_fmt yuv420p&lt;/code&gt; forces a pixel format compatible with every browser and player.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-r 30&lt;/code&gt; sets the output frame rate to 30fps for smooth playback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 5 input images at &lt;code&gt;-framerate 1&lt;/code&gt;, the output is a 5-second video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Glob Patterns Instead of Numbered Files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1 &lt;span class="nt"&gt;-pattern_type&lt;/span&gt; glob &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'*.png'&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-r&lt;/span&gt; 30 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FFmpeg sorts glob matches alphabetically. Rename files with a numeric prefix if the order matters. Glob patterns work on macOS and Linux only.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Image, Specific Duration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-loop&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; title-card.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-t&lt;/span&gt; 10 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-r&lt;/span&gt; 30 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-loop 1&lt;/code&gt; keeps reading the same image. &lt;code&gt;-t 10&lt;/code&gt; sets 10 seconds of output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlling Quality with CRF
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; frame_%03d.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-r&lt;/span&gt; 30 high_quality.mp4
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 1 &lt;span class="nt"&gt;-i&lt;/span&gt; frame_%03d.png &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-crf&lt;/span&gt; 28 &lt;span class="nt"&gt;-r&lt;/span&gt; 30 small_file.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For 5 images at 640x360, CRF 18 produces a 176KB file. CRF 28 produces 61KB, a 2.9x size reduction. For product screenshots, stay between 18 and 22. For social media, 23 to 28 is fine.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Dimensions must be even.&lt;/strong&gt; H.264 requires width and height divisible by 2. Fix with &lt;code&gt;-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alpha channels in PNGs cause playback failures.&lt;/strong&gt; Always use &lt;code&gt;-pix_fmt yuv420p&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Numbering gaps skip images.&lt;/strong&gt; FFmpeg stops at gaps. Switch to a glob pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framerate confusion:&lt;/strong&gt; &lt;code&gt;-framerate&lt;/code&gt; (before &lt;code&gt;-i&lt;/code&gt;) controls input read rate. &lt;code&gt;-r&lt;/code&gt; (after &lt;code&gt;-i&lt;/code&gt;) controls output frame rate.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What image formats does FFmpeg support?&lt;/strong&gt; JPEG, PNG, BMP, TIFF, WebP, and most standard formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I control duration per image?&lt;/strong&gt; &lt;code&gt;-framerate 1&lt;/code&gt; = 1 second. &lt;code&gt;-framerate 1/3&lt;/code&gt; = 3 seconds per image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I add transitions?&lt;/strong&gt; Yes. FFmpeg's &lt;code&gt;xfade&lt;/code&gt; filter supports 40+ transition styles.&lt;/p&gt;

&lt;p&gt;Read the full post with API examples at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-create-video-from-images" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>tutorial</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Use the FFmpeg MCP Server with Claude, Cursor, and Windsurf</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Wed, 27 May 2026 10:16:59 +0000</pubDate>
      <link>https://dev.to/javidjamae/how-to-use-the-ffmpeg-mcp-server-with-claude-cursor-and-windsurf-51k4</link>
      <guid>https://dev.to/javidjamae/how-to-use-the-ffmpeg-mcp-server-with-claude-cursor-and-windsurf-51k4</guid>
      <description>&lt;p&gt;Originally published at &lt;a href="https://www.ffmpeg-micro.com/blog/ffmpeg-mcp-server-tutorial" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most FFmpeg MCP servers require you to install FFmpeg locally, manage binaries, and deal with codec dependencies. The FFmpeg Micro MCP server skips all of that. It connects your AI tool directly to a cloud video processing API. You describe what you want in plain English, and the AI handles the rest.&lt;/p&gt;

&lt;p&gt;This guide shows you how to set it up in Claude Code, Claude Desktop, Cursor, or Windsurf, and walks through real examples you can run today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the FFmpeg Micro MCP Server Does
&lt;/h2&gt;

&lt;p&gt;The FFmpeg Micro MCP server is a thin wrapper around the FFmpeg Micro REST API. It exposes six tools that your AI assistant can call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;transcode_video&lt;/strong&gt; creates a transcode job and returns immediately&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;transcode_and_wait&lt;/strong&gt; creates a job and polls until it finishes (one-shot convenience)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;get_transcode&lt;/strong&gt; checks the status of a specific job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;list_transcodes&lt;/strong&gt; lists your recent jobs with optional filters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;get_download_url&lt;/strong&gt; generates a signed download link for a completed job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cancel_transcode&lt;/strong&gt; cancels a queued or in-progress job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key difference from other FFmpeg MCP servers: there's no local FFmpeg binary. Video processing runs on FFmpeg Micro's cloud infrastructure. Your machine doesn't need FFmpeg installed, and you don't manage any servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up in 2 Minutes
&lt;/h2&gt;

&lt;p&gt;You need two things: an FFmpeg Micro account (free tier available at &lt;a href="https://www.ffmpeg-micro.com" rel="noopener noreferrer"&gt;ffmpeg-micro.com&lt;/a&gt;) and an MCP-compatible AI tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: HTTP with OAuth (Zero Config)
&lt;/h3&gt;

&lt;p&gt;This is the easiest setup. Add this to your MCP config file:&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;"mcpServers"&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;"ffmpeg-micro"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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://mcp.ffmpeg-micro.com"&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;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 first time you connect, your browser opens for OAuth sign-in. After that, the token is cached. No API key to copy-paste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where's the config file?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop:&lt;/strong&gt; &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; (Mac) or &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt; (Windows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; &lt;code&gt;~/.claude.json&lt;/code&gt; or project-level &lt;code&gt;.mcp.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor:&lt;/strong&gt; Settings &amp;gt; Cursor Settings &amp;gt; MCP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windsurf:&lt;/strong&gt; &lt;code&gt;~/.codeium/windsurf/mcp_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2: stdio via npx (API Key)
&lt;/h3&gt;

&lt;p&gt;If you prefer local transport or your tool doesn't support HTTP MCP yet:&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;"mcpServers"&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;"ffmpeg-micro"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@ffmpeg-micro/mcp-server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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;"FFMPEG_MICRO_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_api_key_here"&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;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;Requires Node.js 22.14 or later. Get your API key from the &lt;a href="https://www.ffmpeg-micro.com/dashboard" rel="noopener noreferrer"&gt;FFmpeg Micro dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Examples
&lt;/h2&gt;

&lt;p&gt;Once connected, you just talk to your AI assistant. It picks the right MCP tools automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compress a Video to 720p
&lt;/h3&gt;

&lt;p&gt;You type:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compress this video to 720p medium quality: &lt;a href="https://example.com/my-video.mp4" rel="noopener noreferrer"&gt;https://example.com/my-video.mp4&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI calls &lt;code&gt;transcode_video&lt;/code&gt; with:&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="w"&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/my-video.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;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;The API returns a job ID immediately. The AI then polls with &lt;code&gt;get_transcode&lt;/code&gt; until the job completes, and fetches the download link with &lt;code&gt;get_download_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A 13-second sample video processed in about 1 second with this exact call. The API returned:&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;"e643bb26-4753-4122-bd4c-ff90ccbee415"&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;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processing_time_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"billable_minutes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;h3&gt;
  
  
  Convert MP4 to WebM
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Convert my-video.mp4 to WebM format&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI sends:&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="w"&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/my-video.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;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;No need to remember &lt;code&gt;-c:v libvpx-vp9 -crf 30 -b:v 0&lt;/code&gt;. The API handles codec selection based on the output format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Custom FFmpeg Flags
&lt;/h3&gt;

&lt;p&gt;For full control, you can ask the AI to pass raw FFmpeg options:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Encode this video with H.265 at CRF 28, slow preset&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI sends:&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="w"&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/my-video.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;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="w"&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;"libx265"&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;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;"28"&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;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;"-preset"&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;"slow"&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;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;options&lt;/code&gt; array maps directly to FFmpeg flags. You get the full FFmpeg surface area without memorizing the syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Processing
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Convert all three of these videos to 1080p MP4: video1.mp4, video2.mp4, video3.mp4&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI can call &lt;code&gt;transcode_video&lt;/code&gt; for each input, or use the multi-input feature to stitch them:&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="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;"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/video1.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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&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/video2.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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&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/video3.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;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;"high"&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;"1080p"&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;Multiple inputs get concatenated into a single output. For separate outputs, the AI makes three individual calls. Either way, you don't write any of it.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;"MCP server not found" after config change.&lt;/strong&gt; Claude Desktop requires a full restart (not just closing the window) to pick up config changes. Cursor and Windsurf detect changes automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OAuth popup doesn't appear.&lt;/strong&gt; Make sure your browser isn't blocking popups from &lt;code&gt;mcp.ffmpeg-micro.com&lt;/code&gt;. Some VPNs and corporate proxies also interfere with the OAuth redirect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Node.js version too old" with npx.&lt;/strong&gt; The stdio transport requires Node.js 22.14+. Run &lt;code&gt;node --version&lt;/code&gt; to check. If you're on an older version, use the HTTP transport instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Job stuck in "pending" status.&lt;/strong&gt; Jobs process asynchronously on cloud infrastructure. If a job stays pending for more than 60 seconds, check your input URL. The most common cause is an unreachable or expired presigned URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presigned URLs with query parameters.&lt;/strong&gt; If your input URL already has query parameters (like S3 presigned URLs), make sure the full URL is passed as a single string. Don't split it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFmpeg MCP Server vs. Local FFmpeg MCP Servers
&lt;/h2&gt;

&lt;p&gt;Other FFmpeg MCP servers (like egoist/ffmpeg-mcp, bitscorp-mcp/mcp-ffmpeg, and ZizoTheDev/ffmpeg-mcp) wrap a local FFmpeg binary. They're fine for personal use on a dev machine that already has FFmpeg installed. But they come with tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need FFmpeg installed and configured locally (codec support varies by build)&lt;/li&gt;
&lt;li&gt;Processing runs on your machine, which means CPU load and long runtimes for big files&lt;/li&gt;
&lt;li&gt;No cloud scaling. A 4K video that takes 20 minutes locally might take 3 minutes on cloud infrastructure&lt;/li&gt;
&lt;li&gt;No job management, no download URLs, no webhook notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FFmpeg Micro's MCP server processes video in the cloud. Your local machine stays idle. And because it's the same API that powers production apps, you get the same reliability, scaling, and job tracking.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Do I need FFmpeg installed locally to use this?
&lt;/h3&gt;

&lt;p&gt;No. The FFmpeg Micro MCP server processes everything in the cloud. You don't need FFmpeg, any codecs, or any video libraries on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost?
&lt;/h3&gt;

&lt;p&gt;FFmpeg Micro has a free tier for getting started. Paid plans bill per minute of video processed. Check &lt;a href="https://www.ffmpeg-micro.com/pricing" rel="noopener noreferrer"&gt;ffmpeg-micro.com/pricing&lt;/a&gt; for current rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI tools support MCP?
&lt;/h3&gt;

&lt;p&gt;Claude Code, Claude Desktop, Cursor, Windsurf, and VS Code (via GitHub Copilot MCP) all support MCP servers. Any tool that implements the Model Context Protocol can connect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this in production code, or just in AI tools?
&lt;/h3&gt;

&lt;p&gt;The MCP server uses the same REST API that you can call directly from any language. The MCP layer is a convenience for AI-assisted development. For production, call the API directly. The FFmpeg Micro API works from Node.js, Python, Go, Ruby, Java, PHP, or any language with HTTP support.&lt;/p&gt;

&lt;h3&gt;
  
  
  What video formats are supported?
&lt;/h3&gt;

&lt;p&gt;Input: MP4, WebM, AVI, MOV, MKV, plus audio formats (MP3, WAV, FLAC, AAC). Output: MP4, WebM, MOV. The API handles codec detection and selection automatically.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last verified: 2026-05-27 against FFmpeg Micro MCP server v0.1.0 and API v1.0.0&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>mcp</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Use FFmpeg with Go (Golang)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Tue, 26 May 2026 10:13:11 +0000</pubDate>
      <link>https://dev.to/javidjamae/how-to-use-ffmpeg-with-go-golang-dnf</link>
      <guid>https://dev.to/javidjamae/how-to-use-ffmpeg-with-go-golang-dnf</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-go" 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 Go app. Maybe you're building a media pipeline, transcoding user uploads, or automating thumbnail generation. You search "ffmpeg golang" and find a few options. All of them require FFmpeg installed on the machine running your code.&lt;/p&gt;

&lt;p&gt;That works on your laptop. It breaks the moment you deploy to a Docker container without FFmpeg compiled in, a serverless function with filesystem restrictions, or a CI environment that doesn't have the binary. There are three ways to approach this in Go, each with different tradeoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using os/exec to Call FFmpeg from Go
&lt;/h2&gt;

&lt;p&gt;The most direct approach. Install FFmpeg on your system, then shell out from Go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ffmpeg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"input.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-c:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"libx264"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-crf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-preset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-c:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"aac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"-b:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"128k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"output.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CombinedOutput&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FFmpeg failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Output: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Transcoding complete"&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 is what most Go developers start with. It works, but you own everything: installing FFmpeg on every deployment target, parsing stderr for progress, handling the case where the binary isn't in PATH, and making sure the right codecs are compiled in. On a minimal Docker image like &lt;code&gt;scratch&lt;/code&gt; or &lt;code&gt;distroless&lt;/code&gt;, there's no FFmpeg at all. You'll need a multi-stage build or a fat base image that adds 80-200MB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the ffmpeg-go Wrapper
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ffmpeg-go&lt;/code&gt; package (github.com/u2takey/ffmpeg-go) gives you a fluent API instead of raw command arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="n"&gt;ffmpeg&lt;/span&gt; &lt;span class="s"&gt;"github.com/u2takey/ffmpeg-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ffmpeg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.mp4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ffmpeg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KwArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"c:v"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"libx264"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"crf"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"23"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"preset"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"c:a"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"aac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"b:a"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"128k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;OverWriteOutput&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Run&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done"&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 builder pattern is cleaner than string slices. But &lt;code&gt;ffmpeg-go&lt;/code&gt; still requires FFmpeg installed on the host. It's a wrapper around &lt;code&gt;os/exec&lt;/code&gt; under the hood. You get a nicer API for constructing complex filter graphs, but the same deployment headaches apply. The library also hasn't seen frequent updates, so edge cases may go unpatched.&lt;/p&gt;

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

&lt;p&gt;If you don't want to ship FFmpeg with your binary, you can offload 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 HTTP requests. From Go, it's just &lt;code&gt;net/http&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;TranscodeRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Inputs&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="s"&gt;`json:"inputs"`&lt;/span&gt;
    &lt;span class="n"&gt;OutputFormat&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  &lt;span class="s"&gt;`json:"outputFormat"`&lt;/span&gt;
    &lt;span class="n"&gt;Preset&lt;/span&gt;       &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Preset&lt;/span&gt; &lt;span class="s"&gt;`json:"preset,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"url"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Preset&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Quality&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"quality"`&lt;/span&gt;
    &lt;span class="n"&gt;Resolution&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"resolution"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;reqBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;TranscodeRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Inputs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/input.mp4"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="n"&gt;OutputFormat&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Preset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Preset&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Quality&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Resolution&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"1080p"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"https://api.ffmpeg-micro.com/v1/transcodes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer YOUR_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;respBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Status: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Response: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;respBody&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;No binary dependency. No Docker image bloat. Your Go binary stays small and deploys anywhere. The API handles scaling, codec management, and infrastructure. 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 go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Option&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"option"`&lt;/span&gt;
    &lt;span class="n"&gt;Argument&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"argument"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;AdvancedRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Inputs&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;  &lt;span class="s"&gt;`json:"inputs"`&lt;/span&gt;
    &lt;span class="n"&gt;OutputFormat&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;   &lt;span class="s"&gt;`json:"outputFormat"`&lt;/span&gt;
    &lt;span class="n"&gt;Options&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt; &lt;span class="s"&gt;`json:"options"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;reqBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;AdvancedRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Inputs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/input.mp4"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="n"&gt;OutputFormat&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"webm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"-c:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Argument&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"libvpx-vp9"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"-crf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Argument&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"30"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"-b:v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Argument&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"0"&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;Same FFmpeg flags you already know, sent as structured data instead of a command-line string.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;os/exec&lt;/strong&gt; if you need full control over FFmpeg and you can guarantee the binary is available in every environment your code runs in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ffmpeg-go&lt;/strong&gt; if you want a cleaner API for complex filter chains and don't mind the native FFmpeg dependency. Good for local tools and media pipelines where you control the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cloud API like FFmpeg Micro&lt;/strong&gt; if you're deploying to containers, serverless, or anywhere installing FFmpeg is impractical. Also the right choice when you don't want to manage scaling, codecs, or infrastructure yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls with FFmpeg in Go
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"exec: ffmpeg: executable file not found in $PATH."&lt;/strong&gt; The classic. Your code works locally but panics in Docker because the image doesn't include FFmpeg. Always test your Docker build with &lt;code&gt;docker run --rm your-image which ffmpeg&lt;/code&gt; before deploying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stderr vs stdout confusion.&lt;/strong&gt; FFmpeg writes progress and diagnostic info to stderr, not stdout. If you're only reading &lt;code&gt;cmd.Output()&lt;/code&gt;, you'll miss error messages entirely. Use &lt;code&gt;cmd.CombinedOutput()&lt;/code&gt; or attach &lt;code&gt;cmd.Stderr&lt;/code&gt; to a buffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goroutine leaks on long jobs.&lt;/strong&gt; If you spawn a transcode in a goroutine and the HTTP request that triggered it times out, the FFmpeg process keeps running. Use &lt;code&gt;exec.CommandContext&lt;/code&gt; with a &lt;code&gt;context.WithTimeout&lt;/code&gt; to kill the process when the caller cancels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large file memory pressure.&lt;/strong&gt; Piping FFmpeg output through Go's &lt;code&gt;io.ReadAll&lt;/code&gt; on a 2GB file will spike memory. Stream the output to disk or to cloud storage instead of buffering it in memory.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Do I need FFmpeg installed to process video in Go?
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;os/exec&lt;/code&gt; and &lt;code&gt;ffmpeg-go&lt;/code&gt;, yes. Both require the FFmpeg binary on the host machine. A cloud API like FFmpeg Micro processes video entirely server-side, so your Go app never touches FFmpeg directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use FFmpeg in a Go app deployed to AWS Lambda?
&lt;/h3&gt;

&lt;p&gt;You can bundle a static FFmpeg binary in your deployment package. But Lambda has memory limits (128MB-10GB), a 15-minute execution ceiling, and a 250MB package size limit. A 5-minute 1080p transcode can easily exceed all three. Offloading to an API keeps your Lambda small and fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the fastest way to add video processing to a Go application?
&lt;/h3&gt;

&lt;p&gt;A cloud API gets you from zero to processing video in under 10 minutes. Define your structs, make an HTTP call, poll for completion. No installation, no Docker configuration, no codec debugging. &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.&lt;/p&gt;

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

&lt;p&gt;The &lt;code&gt;github.com/u2takey/ffmpeg-go&lt;/code&gt; package works for most common operations, but it hasn't had a major release recently. For simple transcoding it's fine. For bleeding-edge FFmpeg features or complex filter graphs, you may find yourself falling back to &lt;code&gt;os/exec&lt;/code&gt; with raw arguments.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last verified: 2026-05-26 against FFmpeg 7.1 and ffmpeg-micro API v1&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>go</category>
      <category>video</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Use FFmpeg with Java (No Installation Required)</title>
      <dc:creator>Javid Jamae</dc:creator>
      <pubDate>Mon, 25 May 2026 10:16:01 +0000</pubDate>
      <link>https://dev.to/javidjamae/how-to-use-ffmpeg-with-java-no-installation-required-42k5</link>
      <guid>https://dev.to/javidjamae/how-to-use-ffmpeg-with-java-no-installation-required-42k5</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-java" 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 Java app. Maybe you're transcoding user uploads in a Spring Boot service, generating thumbnails for a content platform, or converting formats in an enterprise pipeline. You search "ffmpeg java" and find a dozen approaches, half of them outdated.&lt;/p&gt;

&lt;p&gt;FFmpeg is the standard for video processing. But integrating it with Java means choosing between shelling out to a binary, using a wrapper library, or skipping the local install entirely. Each approach has real tradeoffs. This post covers all three with working code so you can pick the right one for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running FFmpeg from Java with ProcessBuilder
&lt;/h2&gt;

&lt;p&gt;The most direct approach. Install FFmpeg on the machine, then call it from Java using &lt;code&gt;ProcessBuilder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ProcessBuilder&lt;/span&gt; &lt;span class="n"&gt;pb&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;ProcessBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"ffmpeg"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-i"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"input.mp4"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-c:v"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"libx264"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-crf"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-preset"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"medium"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-c:a"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"aac"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-b:a"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"128k"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"output.mp4"&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;redirectErrorStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Process&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BufferedReader&lt;/span&gt; &lt;span class="n"&gt;reader&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;BufferedReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InputStreamReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="o"&gt;())))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLine&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitFor&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exitCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FFmpeg failed with exit code "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exitCode&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. But you own everything: installing FFmpeg on every server and CI machine, parsing stderr for progress info, handling missing codecs, and managing the binary across Linux/Mac/Windows. On Docker, FFmpeg adds 80-200MB to your image. On serverless (Lambda, Cloud Functions), you're stuck with a 250MB package limit and can't install system binaries at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Jaffree for a Cleaner API
&lt;/h2&gt;

&lt;p&gt;Jaffree is the most capable Java FFmpeg wrapper. It gives you a fluent builder API instead of raw string arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;FFmpeg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;atPath&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlInput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.mp4"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addOutput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.mp4"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-c:v"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"libx264"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-crf"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-preset"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"medium"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-c:a"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"aac"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-b:a"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"128k"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jaffree also supports progress tracking out of the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;FFmpeg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;atPath&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addInput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlInput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.mp4"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addOutput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlOutput&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.mp4"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-c:v"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"libx264"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addArguments&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-crf"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProgressListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Frame: %d, Time: %s%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFrame&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTimeMark&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is better than raw &lt;code&gt;ProcessBuilder&lt;/code&gt; code. But Jaffree still requires FFmpeg installed on the host. It's a wrapper around the CLI binary, not a replacement for it. You still manage installations, updates, and codec availability across environments.&lt;/p&gt;

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

&lt;p&gt;If you don't want FFmpeg on your servers at all, you can offload processing to a cloud API. FFmpeg Micro gives you full FFmpeg capabilities through HTTP requests. Java 11+ includes &lt;code&gt;java.net.http.HttpClient&lt;/code&gt;, so you don't even need a third-party HTTP library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newHttpClient&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
    {
        "inputs": [{"url": "https://example.com/input.mp4"}],
        "outputFormat": "mp4",
        "preset": {"quality": "high", "resolution": "1080p"}
    }
    """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.ffmpeg-micro.com/v1/transcodes"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyPublishers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;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;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// {"id":"b5f5a9c0-...","status":"queued","output_format":"mp4",...}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No binary to install. No Docker image bloat. No codec management. You send a video URL, pick your output settings, and get results back.&lt;/p&gt;

&lt;p&gt;For advanced operations, 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 java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;advancedBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
    {
        "inputs": [{"url": "https://example.com/input.mp4"}],
        "outputFormat": "webm",
        "options": [
            {"option": "-c:v", "argument": "libvpx-vp9"},
            {"option": "-crf", "argument": "30"},
            {"option": "-b:v", "argument": "0"}
        ]
    }
    """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Polling for Completion
&lt;/h3&gt;

&lt;p&gt;Transcode jobs are asynchronous. Poll the status endpoint until the job finishes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;jobId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// parse id from the create response&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;statusUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.ffmpeg-micro.com/v1/transcodes/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;jobId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;statusRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusUrl&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"queued"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"queued"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s"&gt;"processing"&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;statusResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// Parse status from JSON response&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;parseStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statusResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the status is &lt;code&gt;completed&lt;/code&gt;, grab the download URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;downloadUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.ffmpeg-micro.com/v1/transcodes/"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;jobId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/download"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;downloadRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloadUrl&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;downloadResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;downloadRequest&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BodyHandlers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// Response: {"url": "https://storage.googleapis.com/...signed-url..."}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signed URL is valid for 10 minutes. Download the file directly from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI vs. API: Side-by-Side
&lt;/h2&gt;

&lt;p&gt;The same operation (converting an MP4 to 720p) done both ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FFmpeg CLI (requires local install):&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;-vf&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-2&lt;/span&gt;:720 &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 copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FFmpeg Micro API (no install):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
    {
        "inputs": [{"url": "https://example.com/input.mp4"}],
        "outputFormat": "mp4",
        "preset": {"quality": "high", "resolution": "720p"}
    }
    """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same result. The API version runs on managed infrastructure that scales automatically. The CLI version runs on whatever machine you installed FFmpeg on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls with FFmpeg in Java
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ProcessBuilder doesn't capture stderr by default.&lt;/strong&gt; FFmpeg writes progress and error info to stderr, not stdout. If you only read &lt;code&gt;getInputStream()&lt;/code&gt;, you miss all of it. Always call &lt;code&gt;redirectErrorStream(true)&lt;/code&gt; or read both streams separately with threads to avoid deadlocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking on process output can deadlock.&lt;/strong&gt; If FFmpeg writes enough data to fill the OS pipe buffer and your Java code isn't reading it, both processes freeze. Always drain the output stream in a separate thread or use &lt;code&gt;redirectErrorStream(true)&lt;/code&gt; with a reader loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jaffree needs FFmpeg on the PATH.&lt;/strong&gt; If FFmpeg isn't on the system PATH, Jaffree throws a confusing &lt;code&gt;IOException&lt;/code&gt;. Either pass the path explicitly with &lt;code&gt;FFmpeg.atPath(Paths.get("/usr/local/bin"))&lt;/code&gt; or set it in your container's Dockerfile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text blocks need Java 13+.&lt;/strong&gt; The multi-line &lt;code&gt;"""&lt;/code&gt; syntax used in the API examples requires Java 13 or later. On older versions, use string concatenation or a JSON library like Jackson to build the request body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't forget to close processes.&lt;/strong&gt; A leaked &lt;code&gt;Process&lt;/code&gt; object holds OS resources. Use try-with-resources or call &lt;code&gt;process.destroy()&lt;/code&gt; in a finally block. Jaffree handles this automatically.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;ProcessBuilder&lt;/strong&gt; if you need full control over FFmpeg flags and you're comfortable managing FFmpeg installations across all environments. Good for batch processing on dedicated servers where you control the OS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jaffree&lt;/strong&gt; if you want a cleaner Java API and built-in progress tracking. Still requires FFmpeg on every machine, but the code is easier to maintain than raw ProcessBuilder calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cloud API like FFmpeg Micro&lt;/strong&gt; if you want zero operational overhead. Best for serverless deployments, microservices, or teams that don't want to manage FFmpeg infrastructure. You get a free API key to start processing video in under 10 minutes.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Can I run FFmpeg inside AWS Lambda with Java?
&lt;/h3&gt;

&lt;p&gt;Technically, yes. You can bundle a static FFmpeg binary in your deployment package. But Lambda has a 250MB package limit (unzipped), 512MB /tmp storage, and a 15-minute timeout. For short clips it works. For anything over a few minutes of video, you'll hit limits. A cloud API avoids these constraints entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Jaffree still maintained?
&lt;/h3&gt;

&lt;p&gt;Yes. Jaffree is actively maintained on GitHub with regular releases. It supports Java 8+ and wraps both ffmpeg and ffprobe. It's the most mature FFmpeg wrapper for Java.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about JavaCV for video processing?
&lt;/h3&gt;

&lt;p&gt;JavaCV wraps OpenCV and FFmpeg via JNI bindings. It's built for computer vision tasks (face detection, frame analysis) but heavyweight for simple transcoding. If you just need to convert formats or resize video, ProcessBuilder, Jaffree, or a cloud API are simpler options.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the FFmpeg Micro API handle large files?
&lt;/h3&gt;

&lt;p&gt;For files you can't reference by URL, use the upload flow: request a presigned URL, PUT your file directly to cloud storage, confirm the upload, then reference the cloud storage URL in your transcode request. The API supports files up to 2GB.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to learn FFmpeg syntax to use the API?
&lt;/h3&gt;

&lt;p&gt;No. Presets handle common operations (quality levels, resolution, format conversion) without any FFmpeg knowledge. If you do know FFmpeg, you can pass raw options for full control over codecs, filters, and encoding parameters.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last verified: 2026-05-25 against FFmpeg 7.x and FFmpeg Micro API v1&lt;/em&gt;&lt;/p&gt;

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