<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: RenderIO</title>
    <description>The latest articles on Forem by RenderIO (@renderio).</description>
    <link>https://forem.com/renderio</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%2F3863667%2Ff75376b2-0d0e-41ee-acfe-348dd81e39b0.png</url>
      <title>Forem: RenderIO</title>
      <link>https://forem.com/renderio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/renderio"/>
    <language>en</language>
    <item>
      <title>Kling AI Video Post-Processing with FFmpeg</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:41:28 +0000</pubDate>
      <link>https://forem.com/renderio/kling-ai-video-post-processing-with-ffmpeg-naf</link>
      <guid>https://forem.com/renderio/kling-ai-video-post-processing-with-ffmpeg-naf</guid>
      <description>&lt;h2&gt;
  
  
  How to fix Kling AI video artifacts with FFmpeg post-processing
&lt;/h2&gt;

&lt;p&gt;Kling AI's raw output needs FFmpeg post-processing before it's ready to publish. The motion and camera work are genuinely good — tracking shots, slow pans, spatial consistency — but the raw file comes with texture flickering, color drift, aggressive compression, and AI metadata embedded in the container. These are fixable with a single FFmpeg command in about 5 seconds.&lt;/p&gt;

&lt;p&gt;If you're working with other AI generators too, the &lt;a href="https://renderio.dev/blogs/ai-video-post-processing-tiktok" rel="noopener noreferrer"&gt;general AI video post-processing guide&lt;/a&gt; covers Runway, Pika, and Sora with their own tuning profiles. And for making AI video look organic on social platforms, the &lt;a href="https://renderio.dev/blogs/make-ai-video-look-natural" rel="noopener noreferrer"&gt;natural look guide&lt;/a&gt; goes deeper on color grading and motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Kling gets right (and why post-processing is still worth it)
&lt;/h2&gt;

&lt;p&gt;Kling's strength is camera movement. It handles tracking shots, slow pans, and dolly-style motion better than most generators as of early 2026. The motion feels deliberate rather than random. Characters maintain spatial consistency during movement, which is still something Pika struggles with.&lt;/p&gt;

&lt;p&gt;The tradeoff is that Kling's diffusion model introduces more temporal noise than Runway or Sora. Each frame looks fine in isolation, but played in sequence, surfaces shimmer. That's fixable with the right temporal filters. You're essentially smoothing the inconsistencies while keeping the strong motion intact.&lt;/p&gt;

&lt;p&gt;Kling's strong points — camera movement, spatial consistency — are worth keeping. Post-processing protects them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kling-specific output characteristics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Texture flickering
&lt;/h3&gt;

&lt;p&gt;This is the most visible artifact. Kling's diffusion process produces more temporal inconsistency than competitors. Walls, water, fabric: flat or semi-flat surfaces shift texture between frames. It looks like the surface is "breathing."&lt;/p&gt;

&lt;p&gt;The cause is per-frame noise in the diffusion sampling. Each frame gets a slightly different texture pattern for the same surface. When played at 24-30fps, these differences read as flickering.&lt;/p&gt;

&lt;p&gt;In practical terms: a wall in a Runway clip stays visually static. The same wall in a Kling clip subtly pulses. It's less noticeable with fast camera movement (the motion masks it), but very obvious in locked-off or slow-motion shots.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color temperature drift
&lt;/h3&gt;

&lt;p&gt;Over a 5-10 second clip, Kling's color balance shifts. A neutral-lit scene might start at roughly 5600K and end closer to 5100K, a visible shift from neutral to slightly warm or cool. You see it most in skin tones and neutral gray backgrounds.&lt;/p&gt;

&lt;p&gt;This happens because Kling's temporal consistency model doesn't fully constrain color across the generation window. The diffusion process optimizes per-frame quality more aggressively than cross-frame consistency. Runway and Sora have tighter temporal constraints, which is partly why their motion feels less dynamic. They trade movement freedom for consistency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolution and compression
&lt;/h3&gt;

&lt;p&gt;Kling outputs at 720p or 1080p depending on your subscription tier. Even the 1080p output uses aggressive compression. The encoded bitrate is typically 4-6 Mbps, which causes visible blocking in dark areas, gradients, and fine textures. For comparison, a well-encoded 1080p file for social media should be 8-12 Mbps.&lt;/p&gt;

&lt;p&gt;The 720p output needs upscaling for any platform that expects 1080p (TikTok, Reels, YouTube Shorts). Simple bilinear upscaling makes the compression artifacts worse. You need a sharper algorithm plus some edge enhancement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metadata
&lt;/h3&gt;

&lt;p&gt;Kling embeds generation metadata: model version, prompt fragments, generation parameters, and often Chinese-language strings from the platform. Social platforms and content detection systems can flag files based on this metadata. Stripping it is the first step in any pipeline. The &lt;a href="https://renderio.dev/blogs/strip-video-metadata-ffmpeg" rel="noopener noreferrer"&gt;metadata stripping guide&lt;/a&gt; covers all the edge cases, including the difference between container and stream-level metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Post-processing pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Strip metadata
&lt;/h3&gt;

&lt;p&gt;Remove everything Kling embeds, and prevent FFmpeg from adding its own encoder tag:&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; kling-output.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; +bitexact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; copy &lt;span class="se"&gt;\&lt;/span&gt;
  clean.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-fflags +bitexact&lt;/code&gt; flag stops FFmpeg from writing &lt;code&gt;encoder: Lavf...&lt;/code&gt; into the output. Without it, the file still carries a metadata fingerprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Fix texture flickering
&lt;/h3&gt;

&lt;p&gt;Kling needs stronger temporal smoothing than other generators:&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; clean.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"nlmeans=s=8:p=5:r=15,tmix=frames=5:weights='1 1 2 1 1'"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  denoised.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why these specific values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;nlmeans=s=8&lt;/code&gt;: spatial denoising strength. Most generators need 4-6. Kling needs 8 because its per-frame texture noise is higher. Going above 10 starts smearing real detail.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;p=5&lt;/code&gt;: patch size. Larger patches catch more of Kling's broad texture shifts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r=15&lt;/code&gt;: search radius. Wider search finds better matches in Kling's noisier frames.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tmix=frames=5:weights='1 1 2 1 1'&lt;/code&gt;: blends 5 frames with a center-weighted kernel. The center frame (weight 2) dominates, and neighboring frames smooth temporal flicker. Most generators work fine with &lt;code&gt;frames=3&lt;/code&gt;. Kling's wider temporal variance needs 5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For clips with fast camera movement, you can drop to &lt;code&gt;s=6&lt;/code&gt; and &lt;code&gt;frames=3&lt;/code&gt; since the motion already masks the flicker. For locked-off shots (no camera movement), go up to &lt;code&gt;s=10&lt;/code&gt; and &lt;code&gt;frames=7&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Stabilize color temperature
&lt;/h3&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; denoised.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"colorbalance=rs=0.02:gs=0:bs=-0.02:rm=0.01:gm=0:bm=-0.01,eq=saturation=1.05"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  color-fixed.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This applies a slight warm bias to counteract Kling's tendency to drift cool. The &lt;code&gt;saturation=1.05&lt;/code&gt; compensates for the slight desaturation that Kling's compression introduces.&lt;/p&gt;

&lt;p&gt;For clips with visible color drift across the duration, use the &lt;code&gt;deflicker&lt;/code&gt; filter instead:&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; denoised.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"deflicker=size=10:mode=am"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  stable-color.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;deflicker&lt;/code&gt; analyzes luminance across a window of frames and normalizes it. &lt;code&gt;size=10&lt;/code&gt; means it looks at 10 frames in each direction. &lt;code&gt;mode=am&lt;/code&gt; uses arithmetic mean, which handles gradual drifts better than the geometric mean mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Upscale (if starting from 720p)
&lt;/h3&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; color-fixed.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:flags=lanczos,unsharp=3:3:0.8"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a copy &lt;span class="se"&gt;\&lt;/span&gt;
  upscaled.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lanczos produces the sharpest upscale of FFmpeg's built-in algorithms. The &lt;code&gt;unsharp=3:3:0.8&lt;/code&gt; filter adds back edge detail that upscaling softens (3x3 luma matrix with 0.8 strength). Going above 1.0 on the strength introduces visible halos around edges.&lt;/p&gt;

&lt;p&gt;If the source is 1080p already, skip the scale and just apply the unsharp to recover detail lost by Kling's compression. For more on compression settings, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;video compression guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Format for target platform
&lt;/h3&gt;

&lt;p&gt;For TikTok (9:16):&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; upscaled.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; 0:a? &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-2:LRA=7"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  tiktok.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a blurred-background letterbox effect if the aspect ratio doesn't match 9:16. The &lt;code&gt;loudnorm&lt;/code&gt; filter normalizes audio to TikTok's preferred loudness level. For transcoding between formats in general, the &lt;a href="https://renderio.dev/blogs/ffmpeg-transcode-video" rel="noopener noreferrer"&gt;FFmpeg transcode guide&lt;/a&gt; covers codec selection and container compatibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combined single-command pipeline
&lt;/h2&gt;

&lt;p&gt;All steps in one FFmpeg command:&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; kling-output.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-fflags&lt;/span&gt; +bitexact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"nlmeans=s=8:p=5:r=15,tmix=frames=5:weights='1 1 2 1 1',colorbalance=rs=0.02:gs=0:bs=-0.02,eq=saturation=1.05,scale=1920:1080:flags=lanczos,unsharp=3:3:0.8"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-2:LRA=7"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  processed.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs all filters in sequence on each frame. Processing time is roughly 1-3x the clip duration on a modern CPU. The &lt;code&gt;nlmeans&lt;/code&gt; filter is the slowest part because it's doing spatial denoising on every frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  RenderIO API integration
&lt;/h2&gt;

&lt;p&gt;Send the combined pipeline as a single API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -map_metadata -1 -fflags +bitexact -vf \"nlmeans=s=8:p=5:r=15,tmix=frames=5:weights=1_1_2_1_1,colorbalance=rs=0.02:gs=0:bs=-0.02,eq=saturation=1.05,scale=1920:1080:flags=lanczos,unsharp=3:3:0.8\" -af \"loudnorm=I=-14:TP=-2:LRA=7\" -c:v libx264 -crf 18 -preset medium -c:a aac -b:a 128k -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://example.com/kling-output.mp4" },
    "output_files": { "out_video": "processed-kling.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Batch processing workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processKlingBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoUrls&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;KLING_PIPELINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -fflags +bitexact -vf "nlmeans=s=8:p=5:r=15,tmix=frames=5:weights=1_1_2_1_1,colorbalance=rs=0.02:gs=0:bs=-0.02,eq=saturation=1.05,scale=1920:1080:flags=lanczos,unsharp=3:3:0.8" -af "loudnorm=I=-14:TP=-2:LRA=7" -c:v libx264 -crf 18 -c:a aac -b:a 128k -movflags +faststart {{out_video}}`&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;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="nx"&gt;i&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;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://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&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;RENDERIO_API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KLING_PIPELINE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;in_video&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="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;out_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`kling-processed-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4`&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;klingExports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&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;results&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;processKlingBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Kling vs. other generators: filter tuning
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(Values based on our internal testing with 720p/1080p Kling exports across 50+ clips; other generator values are averages across comparable test clips.)&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Kling&lt;/th&gt;
&lt;th&gt;Runway Gen-3&lt;/th&gt;
&lt;th&gt;Pika&lt;/th&gt;
&lt;th&gt;Sora&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;nlmeans strength&lt;/td&gt;
&lt;td&gt;8-10&lt;/td&gt;
&lt;td&gt;4-6&lt;/td&gt;
&lt;td&gt;5-7&lt;/td&gt;
&lt;td&gt;3-5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tmix frames&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color correction&lt;/td&gt;
&lt;td&gt;Warm bias needed&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Slight warm&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upscale needed&lt;/td&gt;
&lt;td&gt;Often (720p base)&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deflicker needed&lt;/td&gt;
&lt;td&gt;Often&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kling needs the most post-processing of the major generators. Runway and Sora come out cleaner but with less dynamic motion. Pika sits in the middle.&lt;/p&gt;

&lt;p&gt;Here's how to tune for each generator's specific failure modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kling:&lt;/strong&gt; Start with &lt;code&gt;nlmeans=s=8&lt;/code&gt; and &lt;code&gt;tmix=frames=5&lt;/code&gt;. For locked-off shots with no camera movement, push to &lt;code&gt;s=10&lt;/code&gt; and &lt;code&gt;frames=7&lt;/code&gt; — the texture breathing is most visible when nothing is moving. Apply the warm colorbalance bias (&lt;code&gt;rs=0.02:bs=-0.02&lt;/code&gt;) on every clip; Kling drifts consistently cool over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runway Gen-3:&lt;/strong&gt; Much cleaner baseline. Use &lt;code&gt;nlmeans=s=4&lt;/code&gt; and &lt;code&gt;tmix=frames=3&lt;/code&gt;. You can skip color correction entirely on most clips. The main issue with Runway is slight temporal softness rather than texture noise — &lt;code&gt;unsharp=3:3:0.5&lt;/code&gt; after the scale step recovers detail without oversharpening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pika:&lt;/strong&gt; Falls between Kling and Runway for noise. Start with &lt;code&gt;nlmeans=s=5&lt;/code&gt; and &lt;code&gt;tmix=frames=3&lt;/code&gt;. Pika's main artifact is horizontal banding on gradients; &lt;code&gt;deflicker=size=5:mode=am&lt;/code&gt; handles it better than nlmeans alone. Check the sky in any clip with sky in frame — that's where Pika's banding shows up first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sora:&lt;/strong&gt; The cleanest output of the four. &lt;code&gt;nlmeans=s=3&lt;/code&gt; is usually enough, sometimes &lt;code&gt;s=0&lt;/code&gt; (skip denoising entirely). Sora's tradeoff is motion stiffness rather than noise, which post-processing can't fix — it's a model characteristic. Focus on the metadata strip and format conversion steps.&lt;/p&gt;

&lt;p&gt;For a complete walkthrough of processing Runway output specifically, see the &lt;a href="https://renderio.dev/blogs/runway-video-to-tiktok-format" rel="noopener noreferrer"&gt;Runway to TikTok format guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation with webhooks
&lt;/h2&gt;

&lt;p&gt;Set up a webhook to trigger post-processing automatically when Kling exports finish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhook/renderio&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command_id&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="nx"&gt;output_files&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;body&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;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;SUCCESS&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;uploadToTikTok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processed-kling.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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;RenderIO retries failed webhook deliveries with exponential backoff. If your server is down, the dead letter queue holds the notification for later.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Does Kling output need post-processing for every use case?
&lt;/h3&gt;

&lt;p&gt;Not always. If you're posting a quick test or internal review, the raw output is fine. But for anything public (TikTok, Reels, YouTube Shorts, ads), the flickering and color drift will be visible, especially on larger screens. The metadata alone is worth stripping even for casual use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use GPU acceleration for the post-processing pipeline?
&lt;/h3&gt;

&lt;p&gt;The bottleneck is &lt;code&gt;nlmeans&lt;/code&gt;, which doesn't have a CUDA equivalent in FFmpeg. The other filters (&lt;code&gt;tmix&lt;/code&gt;, &lt;code&gt;colorbalance&lt;/code&gt;, &lt;code&gt;scale&lt;/code&gt;) are fast on CPU. If you're processing many clips in parallel, an &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;FFmpeg API&lt;/a&gt; handles the scaling without you managing hardware. For GPU encoding specifically, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-cuda-nvenc-gpu-acceleration" rel="noopener noreferrer"&gt;CUDA and NVENC guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much quality does the re-encoding step lose?
&lt;/h3&gt;

&lt;p&gt;At CRF 18, the quality loss from re-encoding is minimal. You'd need a side-by-side comparison at pixel level to notice. Kling's own output compression is much more destructive than a CRF 18 x264 re-encode. You're actually improving perceived quality by denoising and sharpening, even though you're adding a re-encode step.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if my Kling output is already 1080p? Should I skip the upscale?
&lt;/h3&gt;

&lt;p&gt;Skip the &lt;code&gt;scale&lt;/code&gt; filter but keep the &lt;code&gt;unsharp=3:3:0.8&lt;/code&gt;. Even at 1080p, Kling's aggressive compression softens fine detail. The unsharp filter recovers edge definition without the upscale step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I apply this pipeline to other AI video generators?
&lt;/h3&gt;

&lt;p&gt;Yes, with different filter strengths. Runway needs weaker denoising (&lt;code&gt;s=4-6&lt;/code&gt;, &lt;code&gt;frames=3&lt;/code&gt;) and usually no color correction. Sora needs the least processing. The tuning table above has starting points for each generator. The &lt;a href="https://renderio.dev/blogs/ai-video-post-processing-tiktok" rel="noopener noreferrer"&gt;AI video post-processing for TikTok guide&lt;/a&gt; covers multi-generator pipelines.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>FFmpeg Resize Video: Scale, Crop &amp; Pad (CLI + API)</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:41:03 +0000</pubDate>
      <link>https://forem.com/renderio/ffmpeg-resize-video-scale-crop-pad-cli-api-53oh</link>
      <guid>https://forem.com/renderio/ffmpeg-resize-video-scale-crop-pad-cli-api-53oh</guid>
      <description>&lt;h2&gt;
  
  
  Resize a video in one command
&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;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:720"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the simplest way to resize video with FFmpeg, taking &lt;code&gt;input.mp4&lt;/code&gt; and outputting it at 1280x720 pixels. If your source is 1920x1080, the aspect ratio matches and everything looks fine. If your source is 4:3 or vertical, the output gets stretched. Usually not what you want.&lt;/p&gt;

&lt;p&gt;This guide covers how to resize without distortion, how to crop and pad for different platforms, which scaling algorithm to pick, and how to batch-process hundreds of files. There's also an API approach at the end for when you don't want FFmpeg on your server at all.&lt;/p&gt;

&lt;p&gt;New to FFmpeg? The &lt;a href="https://renderio.dev/blogs/ffmpeg-command-line-tutorial" rel="noopener noreferrer"&gt;command line tutorial&lt;/a&gt; covers installation and basic syntax. Already comfortable? The &lt;a href="https://renderio.dev/blogs/ffmpeg-cheat-sheet" rel="noopener noreferrer"&gt;FFmpeg cheat sheet&lt;/a&gt; has 50 commands organized by task, including several resize recipes. For a focused reference on the scale filter itself, algorithm selection, and common errors, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-scale-video" rel="noopener noreferrer"&gt;FFmpeg scale video guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scale filter, properly explained
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;scale&lt;/code&gt; filter is where all FFmpeg resize operations start. The syntax is &lt;code&gt;scale=width:height&lt;/code&gt;, but the interesting part is what happens when you don't specify both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed dimensions (force exact size):&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="s2"&gt;"scale=1920:1080"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forces the output to exactly 1920x1080 regardless of the input aspect ratio. A 4:3 source will look horizontally stretched. A 9:16 vertical video will look squished. Only use exact dimensions when you know the source ratio matches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-calculate one dimension:&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="s2"&gt;"scale=1280:-2"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting width to 1280 and height to &lt;code&gt;-2&lt;/code&gt; tells FFmpeg to figure out the height itself while keeping the aspect ratio. The &lt;code&gt;-2&lt;/code&gt; also ensures the result is divisible by 2. That last part matters because H.264 and H.265 both require even dimensions. Use &lt;code&gt;-2&lt;/code&gt; instead of &lt;code&gt;-1&lt;/code&gt; to avoid cryptic encoder errors like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[libx264 @ 0x...] height not divisible by 2 (1280x719)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can flip it around:&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="s2"&gt;"scale=-2:720"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This locks the height at 720 and auto-calculates the width. Useful when your priority is vertical resolution (720p, 1080p) regardless of aspect ratio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale by percentage:&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="s2"&gt;"scale=iw*0.5:ih*0.5"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;iw&lt;/code&gt; and &lt;code&gt;ih&lt;/code&gt; are FFmpeg variables for input width and input height. This shrinks the video to 50% of its original size. Handy for generating preview thumbnails or reducing a 4K file for quick review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cap the size without upscaling:&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="s2"&gt;"scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the one I use most. It scales down videos larger than 1920x1080 but leaves smaller videos alone. No upscaling, no distortion. The &lt;code&gt;force_original_aspect_ratio=decrease&lt;/code&gt; parameter ensures the output fits within 1920x1080 while keeping the original proportions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale with pixel format conversion:&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="s2"&gt;"scale=1280:-2,format=yuv420p"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding &lt;code&gt;format=yuv420p&lt;/code&gt; after the scale filter ensures the output uses the most compatible pixel format. Some sources (screen recordings, ProRes footage) use &lt;code&gt;yuv444p&lt;/code&gt; or &lt;code&gt;yuv422p&lt;/code&gt;, which won't play in many browsers or on mobile devices. Chaining &lt;code&gt;format=yuv420p&lt;/code&gt; after your scale avoids playback issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFmpeg crop video: cut out what you don't need
&lt;/h2&gt;

&lt;p&gt;Cropping removes pixels from the edges. The filter syntax is &lt;code&gt;crop=width:height:x:y&lt;/code&gt;, where x and y are the top-left corner of the crop rectangle. When you omit x and y, FFmpeg centers the crop automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Center crop to square (Instagram-style):&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="s2"&gt;"crop=ih:ih"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses the input height as both dimensions, cutting equally from the left and right. Works for turning landscape footage into square posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crop a specific region:&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="s2"&gt;"crop=640:480:100:50"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grabs a 640x480 rectangle starting 100 pixels from the left and 50 from the top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crop then scale (the combo you actually need):&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="s2"&gt;"crop=ih*16/9:ih,scale=1920:1080"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First crops to 16:9 aspect ratio (useful if you have a wider source), then scales to 1920x1080. Filters chain left to right, separated by commas. The crop happens on the original pixels, and the scale operates on the cropped result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remove black bars automatically:&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="s2"&gt;"cropdetect"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; null -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this first. FFmpeg analyzes the video and prints crop parameters to the terminal. You'll see lines like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Parsed_cropdetect_0 @ 0x...] x1:0 x2:1919 y1:140 y2:939 w:1920 h:800 ...
crop=1920:800:0:140
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy those values into your actual crop command:&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="s2"&gt;"crop=1920:800:0:140"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to watch: &lt;code&gt;cropdetect&lt;/code&gt; analyzes frame by frame, and the detected crop area can fluctuate if the black bars aren't perfectly uniform (common with older DVD rips). Run it for a few seconds and pick the most consistent values from the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pad and letterbox: add space instead of removing it
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to fit a video into a frame without cropping anything. Padding adds black bars (or any color) around the video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Letterbox a 4:3 video into 16:9:&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="s2"&gt;"scale=1920:-2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scales the video to 1920 pixels wide (keeping aspect ratio), then pads it to exactly 1920x1080 with centered black bars. The expressions &lt;code&gt;(ow-iw)/2&lt;/code&gt; and &lt;code&gt;(oh-ih)/2&lt;/code&gt; center the video within the padded frame.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pillarbox a vertical video into landscape:&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; vertical.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=-2:1080,pad=1920:1080:(ow-iw)/2:0:black"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scales the height to 1080, then adds black bars on the left and right to fill a 1920x1080 frame. You see this on YouTube whenever someone uploads a phone recording.&lt;/p&gt;

&lt;p&gt;You can change the bar color:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# White bars&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=-2:1080,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:white"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Blurred background (more complex, but looks professional)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"[0:v]scale=1920:1080,boxblur=20:20[bg];[0:v]scale=-2:1080[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blurred background approach scales the original video to fill the frame (blurry), then overlays the sharp version on top. TikTok compilations use this trick constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform presets: copy-paste commands for social media
&lt;/h2&gt;

&lt;p&gt;Every platform wants something different. Here are the commands I keep in a script file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YouTube 1080p (16:9):&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="s2"&gt;"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-preset&lt;/span&gt; slow &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart output_yt.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CRF 18 because YouTube re-encodes everything anyway. Give it the best source you can. The &lt;code&gt;-movflags +faststart&lt;/code&gt; moves the moov atom to the beginning of the file, which means the video can start playing before the entire file downloads. The &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;compression guide&lt;/a&gt; explains CRF tuning in more detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TikTok / Instagram Reels (9:16, 1080x1920):&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="s2"&gt;"scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 20 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k output_tiktok.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your source is landscape, this letterboxes it vertically. For a better result with landscape source footage, crop to the center first:&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; landscape.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=ih*9/16:ih,scale=1080:1920"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 20 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output_tiktok.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TikTok's upload limit is 10 minutes and 4GB as of early 2026. They re-encode aggressively on their end, so there's no benefit in going below CRF 18 or uploading at 4K. 1080x1920 at CRF 20 gives a good size-to-quality tradeoff for TikTok's re-encoding pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram Square (1:1, 1080x1080):&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="s2"&gt;"crop=min(iw&lt;/span&gt;&lt;span class="se"&gt;\,&lt;/span&gt;&lt;span class="s2"&gt;ih):min(iw&lt;/span&gt;&lt;span class="se"&gt;\,&lt;/span&gt;&lt;span class="s2"&gt;ih),scale=1080:1080"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 20 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output_ig.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crops to a square from the center, then scales to 1080x1080.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Twitter/X (16:9, 1280x720):&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="s2"&gt;"scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k output_twitter.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twitter compresses aggressively on their end. No point going above 720p.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LinkedIn (16:9, 1920x1080, max 10 min):&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="s2"&gt;"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="nt"&gt;-t&lt;/span&gt; 600 &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart output_linkedin.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LinkedIn caps video at 10 minutes, so &lt;code&gt;-t 600&lt;/code&gt; trims to that limit. If you need more precise &lt;a href="https://renderio.dev/blogs/ffmpeg-trim-video" rel="noopener noreferrer"&gt;trimming&lt;/a&gt;, combine with &lt;code&gt;-ss&lt;/code&gt; for start-point control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling algorithms: when default isn't good enough
&lt;/h2&gt;

&lt;p&gt;FFmpeg defaults to bilinear scaling. It's fast and fine for downscaling. For upscaling or when quality matters, you have options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Lanczos (sharpest, best for downscaling)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:flags=lanczos"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Bicubic (good balance, slight sharpening)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:flags=bicubic"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Bilinear (default, fastest)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:flags=bilinear"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Spline (smooth, good for upscaling)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:flags=spline"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the practical difference: lanczos preserves more fine detail when shrinking video. Text, edges, and textures stay sharper. Bilinear tends to soften everything slightly. For most content the difference is subtle, but it's visible on text-heavy screencasts or footage with fine patterns. Spline produces the smoothest interpolation when scaling up, which means fewer visible artifacts on upscaled footage, though it won't add real detail.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;lanczos&lt;/code&gt; when downscaling (1080p to 720p, 4K to 1080p). Use &lt;code&gt;spline&lt;/code&gt; when upscaling (720p to 1080p). Bilinear is fine for quick previews where sharpness doesn't matter.&lt;/p&gt;

&lt;p&gt;The speed difference is negligible. Lanczos adds maybe 2-3% to encode time compared to bilinear. Not worth worrying about unless you're processing thousands of files. If encoding speed is your bottleneck, &lt;a href="https://renderio.dev/blogs/ffmpeg-cuda-nvenc-gpu-acceleration" rel="noopener noreferrer"&gt;GPU acceleration with NVENC&lt;/a&gt; will make a bigger difference than any filter flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotate and flip: fix orientation problems
&lt;/h2&gt;

&lt;p&gt;Phone videos sometimes have wrong rotation metadata. Or you need to flip footage for a mirror effect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Rotate 90 degrees clockwise&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"transpose=1"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Rotate 90 degrees counter-clockwise&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"transpose=2"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Rotate 180 degrees&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"transpose=1,transpose=1"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Flip horizontally (mirror)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"hflip"&lt;/span&gt; output.mp4

&lt;span class="c"&gt;# Flip vertically&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"vflip"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;transpose&lt;/code&gt; values: 0 = 90° counter-clockwise + vertical flip, 1 = 90° clockwise, 2 = 90° counter-clockwise, 3 = 90° clockwise + vertical flip. I never remember these either, which is why I keep the &lt;a href="https://renderio.dev/blogs/ffmpeg-cheat-sheet" rel="noopener noreferrer"&gt;cheat sheet&lt;/a&gt; open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix metadata rotation without re-encoding:&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;-c&lt;/span&gt; copy &lt;span class="nt"&gt;-metadata&lt;/span&gt;:s:v &lt;span class="nv"&gt;rotate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some players read the rotation metadata and apply it automatically. This strips the rotation flag without touching the video data. Use this when the video plays sideways in some apps but looks correct in others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Combine rotation with resize:&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; portrait.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"transpose=1,scale=1920:1080"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rotates first, then scales. Order matters in the filter chain. If you're also adding a &lt;a href="https://renderio.dev/blogs/ffmpeg-watermark" rel="noopener noreferrer"&gt;watermark&lt;/a&gt;, put the overlay filter last: &lt;code&gt;transpose=1,scale=1920:1080,overlay=...&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch resize: process an entire folder
&lt;/h2&gt;

&lt;p&gt;Single file commands are fine until you have 200 files to resize. Here's a shell script that handles an entire directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;INPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./raw"&lt;/span&gt;
&lt;span class="nv"&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./resized"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:-2:flags=lanczos"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done. Processed &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; files."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Multi-resolution batch (generate multiple sizes at once):&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;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; ./input/&lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; .mp4&lt;span class="si"&gt;)&lt;/span&gt;
  ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:-2:flags=lanczos"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_1080p.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:-2:flags=lanczos"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_720p.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=854:-2:flags=lanczos"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 25 &lt;span class="s2"&gt;"./output/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_480p.mp4"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates 1080p, 720p, and 480p versions in one pass per file. Useful for creating multiple quality tiers for an adaptive video player.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel processing with xargs:&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;find ./raw &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.mp4"&lt;/span&gt; | xargs &lt;span class="nt"&gt;-P&lt;/span&gt; 4 &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'ffmpeg -i "{}" -vf "scale=1280:-2:flags=lanczos" -c:v libx264 -crf 23 "./resized/$(basename {})" -y'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-P 4&lt;/code&gt; flag runs 4 FFmpeg processes simultaneously. Adjust based on your CPU cores. On an 8-core machine, &lt;code&gt;-P 4&lt;/code&gt; is a safe bet that leaves headroom for the OS. Going higher risks thrashing.&lt;/p&gt;

&lt;p&gt;For heavier workloads, the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;RenderIO API&lt;/a&gt; can process files in parallel across multiple machines without tying up yours. More on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFmpeg resize video via API
&lt;/h2&gt;

&lt;p&gt;Running FFmpeg locally works until you're processing video in production. Then you deal with scaling workers, security patches, and the fact that a 4K resize can pin a CPU core for minutes. The &lt;a href="https://renderio.dev/blogs/hosted-ffmpeg-vs-self-hosted" rel="noopener noreferrer"&gt;hosted vs self-hosted tradeoffs&lt;/a&gt; break down the cost math at different volumes.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://renderio.dev" rel="noopener noreferrer"&gt;RenderIO&lt;/a&gt;, you send the same FFmpeg command over HTTP:&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://renderio.dev/api/v1/run-ffmpeg-command &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;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: ffsk_your_api_key"&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;'{
    "input_files": {
      "in_video": "https://example.com/source.mp4"
    },
    "output_files": {
      "out_video": "resized.mp4"
    },
    "ffmpeg_command": "ffmpeg -i {{in_video}} -vf \"scale=1280:-2:flags=lanczos\" -c:v libx264 -crf 23 -c:a aac {{out_video}}"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API returns a &lt;code&gt;command_id&lt;/code&gt;. Poll &lt;code&gt;GET /api/v1/commands/:commandId&lt;/code&gt; until the status is &lt;code&gt;SUCCESS&lt;/code&gt;, then grab the output URL from the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check status&lt;/span&gt;
curl https://renderio.dev/api/v1/commands/cmd_abc123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: ffsk_your_api_key"&lt;/span&gt;

&lt;span class="c"&gt;# Response when complete:&lt;/span&gt;
&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "command_id": "cmd_abc123",&lt;/span&gt;
&lt;span class="c"&gt;#   "status": "SUCCESS",&lt;/span&gt;
&lt;span class="c"&gt;#   "output_files": {&lt;/span&gt;
&lt;span class="c"&gt;#     "out_video": {&lt;/span&gt;
&lt;span class="c"&gt;#       "storage_url": "https://media.renderio.dev/files/...",&lt;/span&gt;
&lt;span class="c"&gt;#       "filename": "resized.mp4",&lt;/span&gt;
&lt;span class="c"&gt;#       "size_mbytes": 1.24&lt;/span&gt;
&lt;span class="c"&gt;#     }&lt;/span&gt;
&lt;span class="c"&gt;#   }&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The FFmpeg syntax is identical to what you'd run locally. No new API to learn. The same crop, pad, and rotate commands from this guide all work, just wrapped in JSON. &lt;a href="https://renderio.dev/get-api-key" rel="noopener noreferrer"&gt;Get an API key&lt;/a&gt; and try it with one of the commands above. For language-specific examples, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-python" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and &lt;a href="https://renderio.dev/blogs/ffmpeg-api-nodejs" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; API guides.&lt;/p&gt;

&lt;p&gt;If you're building automation workflows, the &lt;a href="https://renderio.dev/blogs/n8n-resize-video" rel="noopener noreferrer"&gt;n8n resize video guide&lt;/a&gt; and &lt;a href="https://renderio.dev/blogs/zapier-resize-video-tiktok" rel="noopener noreferrer"&gt;Zapier resize video for TikTok guide&lt;/a&gt; walk through connecting RenderIO to those platforms step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls (and how to avoid them)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Odd dimensions crash the encoder.&lt;/strong&gt; H.264 and H.265 require width and height divisible by 2. Some codecs need divisibility by 4 or even 16. Always use &lt;code&gt;-2&lt;/code&gt; instead of &lt;code&gt;-1&lt;/code&gt; in scale expressions, or add a safety-net filter:&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="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:-2,pad=ceil(iw/2)*2:ceil(ih/2)*2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Upscaling doesn't add detail.&lt;/strong&gt; Scaling a 480p video to 1080p doesn't improve quality. You're just interpolating between existing pixels. FFmpeg won't warn you about this. If you must upscale, use &lt;code&gt;flags=spline&lt;/code&gt; and accept that the output will look softer than native 1080p. Check the source resolution first with &lt;a href="https://renderio.dev/blogs/ffprobe-tutorial" rel="noopener noreferrer"&gt;ffprobe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aspect ratio distortion is silent.&lt;/strong&gt; FFmpeg won't warn you when you stretch a 4:3 video into 16:9. It does exactly what you ask. Always use &lt;code&gt;-2&lt;/code&gt; for auto-calculation or &lt;code&gt;force_original_aspect_ratio=decrease&lt;/code&gt; with &lt;code&gt;pad&lt;/code&gt; to avoid squished output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter order matters.&lt;/strong&gt; In &lt;code&gt;-vf "crop=640:480,scale=1280:720"&lt;/code&gt;, the crop happens first on the original resolution, then the scale operates on the cropped 640x480 result. Swap them and you get a completely different output. Think of the filter chain as a pipeline, left to right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing -c:v on resize means re-encoding.&lt;/strong&gt; Any filter operation triggers a decode/re-encode cycle. You cannot use &lt;code&gt;-c copy&lt;/code&gt; with scale, crop, or pad filters. If you're also &lt;a href="https://renderio.dev/blogs/ffmpeg-trim-video" rel="noopener noreferrer"&gt;trimming&lt;/a&gt; or &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;compressing&lt;/a&gt;, chain everything into one command to avoid encoding the video twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CRF matters more after resize.&lt;/strong&gt; Resizing changes the bitrate requirements. A video resized from 4K to 720p needs far fewer bits per frame. If you copy your CRF value from a 4K workflow, you might be overspending on file size. CRF 23 is a safe default for 1080p. Push to 25-28 for 720p web delivery. The &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;compression guide&lt;/a&gt; goes deeper on tuning CRF per resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Container format compatibility.&lt;/strong&gt; After resizing, make sure your output container supports the codec you're using. MP4 with H.264 works everywhere. MKV is more flexible but won't play natively in browsers. The &lt;a href="https://renderio.dev/blogs/ffmpeg-formats" rel="noopener noreferrer"&gt;formats reference&lt;/a&gt; has a compatibility table for every major codec-container combination.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;How do I resize a video without losing quality?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't avoid some quality loss when resizing, because any scale or crop filter forces FFmpeg to decode and re-encode the video. The goal is to minimize it. Use &lt;code&gt;flags=lanczos&lt;/code&gt; for the sharpest scaling, set CRF to 18 or lower for near-lossless quality, and use &lt;code&gt;-preset slow&lt;/code&gt; to give the encoder more time to optimize. If you're only changing the container or metadata (not the pixel dimensions), you can use &lt;code&gt;-c copy&lt;/code&gt; to avoid re-encoding entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between scale and crop in FFmpeg?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scale&lt;/code&gt; changes the dimensions of the entire frame. Every pixel gets resized. &lt;code&gt;crop&lt;/code&gt; cuts a rectangle out of the frame and discards the rest. Use scale when you want to keep the full picture at a different size. Use crop when you want to remove unwanted edges or change the aspect ratio by trimming content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does FFmpeg give me "width/height not divisible by 2"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;H.264 and H.265 encode video in macroblocks that require even pixel dimensions. If your scale expression produces an odd number (like 1281x719), the encoder throws this error. Fix it by using &lt;code&gt;-2&lt;/code&gt; instead of &lt;code&gt;-1&lt;/code&gt; in your scale filter: &lt;code&gt;scale=1280:-2&lt;/code&gt; instead of &lt;code&gt;scale=1280:-1&lt;/code&gt;. The &lt;code&gt;-2&lt;/code&gt; rounds to the nearest even number automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I resize video with FFmpeg without re-encoding?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. Resizing requires decoding each frame, applying the scale filter, and re-encoding the result. There's no way around this because the pixel data itself changes. The only FFmpeg operations that avoid re-encoding are container changes, stream copying, and metadata edits. If encoding speed is a concern, consider &lt;a href="https://renderio.dev/blogs/ffmpeg-cuda-nvenc-gpu-acceleration" rel="noopener noreferrer"&gt;hardware-accelerated encoding&lt;/a&gt; or offloading to an &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;FFmpeg API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I resize a batch of videos to the same resolution?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a bash &lt;code&gt;for&lt;/code&gt; loop: &lt;code&gt;for f in *.mp4; do ffmpeg -i "$f" -vf "scale=1280:-2" "resized_$f"; done&lt;/code&gt;. For parallel processing, pipe through &lt;code&gt;xargs -P 4&lt;/code&gt;. For large-scale batch jobs (hundreds or thousands of files), the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;RenderIO API&lt;/a&gt; handles concurrency and storage so you don't have to manage worker processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What resolution should I use for TikTok, Instagram, or YouTube?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TikTok and Instagram Reels: 1080x1920 (9:16 vertical). Instagram feed square: 1080x1080. YouTube: 1920x1080 (16:9). Twitter/X: 1280x720. The platform presets section above has ready-to-copy commands for each one, including proper aspect ratio handling and codec settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;p&gt;Here's everything from this guide condensed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Resize to exact dimensions&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:720"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Resize with auto-height (keep aspect ratio)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:-2"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Downscale only (no upscaling)&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Center crop to square&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=ih:ih"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Letterbox into 16:9&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=-2:1080,pad=1920:1080:(ow-iw)/2:(oh-ih)/2"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# TikTok vertical from landscape&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=ih*9/16:ih,scale=1080:1920"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Rotate 90° clockwise&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"transpose=1"&lt;/span&gt; out.mp4

&lt;span class="c"&gt;# Batch resize folder&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.mp4&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:-2:flags=lanczos"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="s2"&gt;"resized_&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want the full list of common FFmpeg operations? The &lt;a href="https://renderio.dev/blogs/ffmpeg-cheat-sheet" rel="noopener noreferrer"&gt;cheat sheet has 50 commands&lt;/a&gt;. Need to change codecs while resizing? The &lt;a href="https://renderio.dev/blogs/ffmpeg-transcode-video" rel="noopener noreferrer"&gt;transcoding guide&lt;/a&gt; covers H.264, H.265, AV1, and VP9 conversions. Working with container formats? The &lt;a href="https://renderio.dev/blogs/ffmpeg-formats" rel="noopener noreferrer"&gt;formats reference&lt;/a&gt; explains which codecs fit inside which containers.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Batch Process AI Videos for Social Media Platforms</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:40:05 +0000</pubDate>
      <link>https://forem.com/renderio/batch-process-ai-videos-for-social-media-platforms-3fbo</link>
      <guid>https://forem.com/renderio/batch-process-ai-videos-for-social-media-platforms-3fbo</guid>
      <description>&lt;h2&gt;
  
  
  Why you can't upload the same video everywhere
&lt;/h2&gt;

&lt;p&gt;You generated a great AI video. Now you need it on TikTok, Instagram Reels, YouTube Shorts, LinkedIn, and Twitter. Each platform wants different dimensions, aspect ratios, file sizes, and audio levels. If you're also looking to &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;compress video with FFmpeg&lt;/a&gt; before uploading, that guide covers CRF values tuned per platform.&lt;/p&gt;

&lt;p&gt;Manually exporting five versions in Premiere takes 15-20 minutes per video. If you're producing 10 videos per day, that's 3 hours of repetitive export work. Nobody should spend their day doing that.&lt;/p&gt;

&lt;p&gt;FFmpeg handles all five conversions. RenderIO runs them in parallel so all five finish in the time it takes to process one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform specifications for batch processing AI video
&lt;/h2&gt;

&lt;p&gt;Here are the exact specs each platform expects (verified March 2026):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Aspect Ratio&lt;/th&gt;
&lt;th&gt;Max Duration&lt;/th&gt;
&lt;th&gt;Max File Size&lt;/th&gt;
&lt;th&gt;Audio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TikTok&lt;/td&gt;
&lt;td&gt;1080x1920&lt;/td&gt;
&lt;td&gt;9:16&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;287 MB&lt;/td&gt;
&lt;td&gt;-14 LUFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instagram Reels&lt;/td&gt;
&lt;td&gt;1080x1920&lt;/td&gt;
&lt;td&gt;9:16&lt;/td&gt;
&lt;td&gt;3 min&lt;/td&gt;
&lt;td&gt;250 MB&lt;/td&gt;
&lt;td&gt;-14 LUFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YouTube Shorts&lt;/td&gt;
&lt;td&gt;1080x1920&lt;/td&gt;
&lt;td&gt;9:16&lt;/td&gt;
&lt;td&gt;3 min&lt;/td&gt;
&lt;td&gt;256 MB&lt;/td&gt;
&lt;td&gt;-14 LUFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LinkedIn&lt;/td&gt;
&lt;td&gt;1920x1080&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;-16 LUFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twitter/X&lt;/td&gt;
&lt;td&gt;1280x720&lt;/td&gt;
&lt;td&gt;16:9&lt;/td&gt;
&lt;td&gt;2:20 min&lt;/td&gt;
&lt;td&gt;512 MB&lt;/td&gt;
&lt;td&gt;-16 LUFS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The differences look subtle but they matter. Wrong aspect ratio means black bars or cropped faces. Wrong audio levels mean your video sounds too loud or too quiet next to native content. And TikTok actually rejects files over 287 MB, not 300 MB like some guides claim.&lt;/p&gt;

&lt;p&gt;Note on LinkedIn: it also accepts 9:16 vertical video now, but 16:9 still performs better in the feed because LinkedIn's desktop audience is larger than mobile. If your content is primarily mobile-targeted, you could skip the landscape conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  FFmpeg commands for each platform
&lt;/h2&gt;

&lt;p&gt;Starting from a 1920x1080 AI-generated video.&lt;/p&gt;

&lt;h3&gt;
  
  
  TikTok (9:16, 1080x1920)
&lt;/h3&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease,noise=alls=16:allf=t[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; 0:a? &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-2:LRA=7"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  tiktok.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;boxblur&lt;/code&gt; background avoids ugly black bars. The blurred version of your video fills the frame behind the properly scaled foreground. The &lt;code&gt;noise&lt;/code&gt; filter adds grain so it doesn't look sterile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instagram Reels (9:16, 1080x1920, 3 min max)
&lt;/h3&gt;

&lt;p&gt;Reels technically allows up to 3 minutes, but the algorithm pushes shorter content harder. Keeping it under 90 seconds gives you better reach in Explore.&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 90 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; 0:a? &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-2:LRA=7"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  reels.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-t 90&lt;/code&gt; flag truncates at 90 seconds, which keeps you in the algorithm's sweet spot. If your video is under 90 seconds, it has no effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  YouTube Shorts (9:16, 1080x1920, 3 min max)
&lt;/h3&gt;

&lt;p&gt;YouTube extended Shorts to 3 minutes in October 2024. The command below trims to 60 seconds since shorter Shorts get more impressions, but you can bump &lt;code&gt;-t 60&lt;/code&gt; to &lt;code&gt;-t 180&lt;/code&gt; if your content needs the extra time.&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 60 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; 0:a? &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-14:TP=-2:LRA=7"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 20 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  shorts.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lower CRF (20 vs 22) because YouTube re-encodes aggressively. Starting with higher quality gives a better result after YouTube's compression pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  LinkedIn (16:9, 1920x1080)
&lt;/h3&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-16:TP=-2:LRA=11"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 20 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 192k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  linkedin.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LinkedIn targets -16 LUFS (quieter than TikTok/Reels). Higher audio bitrate because LinkedIn's player doesn't re-encode as aggressively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Twitter/X (16:9, 1280x720, 2:20 max)
&lt;/h3&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 140 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:black"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"loudnorm=I=-16:TP=-2:LRA=11"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  twitter.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twitter limits videos to 2 minutes 20 seconds (140 seconds). CRF 23 keeps file size down since Twitter's 512 MB limit is generous but their player favors smaller files for faster loading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch all five with RenderIO API
&lt;/h2&gt;

&lt;p&gt;Send five API calls in parallel. Each produces one platform version. For more on the API, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;complete FFmpeg API guide&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INPUT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://storage.example.com/ai-video.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;platforms&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tiktok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease,noise=alls=16:allf=t[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]" -map "[v]" -map 0:a? -af "loudnorm=I=-14:TP=-2:LRA=7" -c:v libx264 -crf 22 -c:a aac -b:a 128k -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reels&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -t 90 -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]" -map "[v]" -map 0:a? -af "loudnorm=I=-14:TP=-2:LRA=7" -c:v libx264 -crf 22 -c:a aac -b:a 128k -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shorts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -t 60 -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=25[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2[v]" -map "[v]" -map 0:a? -af "loudnorm=I=-14:TP=-2:LRA=7" -c:v libx264 -crf 20 -c:a aac -b:a 128k -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;linkedin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" -af "loudnorm=I=-16:TP=-2:LRA=11" -c:v libx264 -crf 20 -c:a aac -b:a 192k -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -map_metadata -1 -t 140 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2:black" -af "loudnorm=I=-16:TP=-2:LRA=11" -c:v libx264 -crf 23 -c:a aac -b:a 128k -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processForAllPlatforms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputUrl&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;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&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;RENDERIO_API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;in_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;out_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4`&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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;results&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;processForAllPlatforms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INPUT_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 5 jobs running in parallel on Cloudflare's edge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five API calls. Five platform-optimized videos. All processing happens in parallel, so you wait for one, not five.&lt;/p&gt;

&lt;p&gt;If you also need to generate speed variations — a 1.5x version for Stories previews or a 2x cut for YouTube Shorts teasers — you can add those as additional jobs in the same batch. The &lt;a href="https://renderio.dev/blogs/ffmpeg-speed-up-video" rel="noopener noreferrer"&gt;FFmpeg speed up video guide&lt;/a&gt; covers setpts and atempo with ready-to-use API examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate batch processing with n8n or Zapier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  n8n workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Trigger: New file in Google Drive, S3, or webhook&lt;/li&gt;
&lt;li&gt;Split: Fan out to 5 parallel branches&lt;/li&gt;
&lt;li&gt;HTTP Request: Each branch calls RenderIO with platform-specific command&lt;/li&gt;
&lt;li&gt;Wait: Poll for completion (or use webhook callback)&lt;/li&gt;
&lt;li&gt;Upload: Send each output to the respective platform's upload API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Zapier workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Trigger: New file in Dropbox/Drive&lt;/li&gt;
&lt;li&gt;Webhooks by Zapier: POST to RenderIO API (TikTok version)&lt;/li&gt;
&lt;li&gt;Delay: Wait 30 seconds&lt;/li&gt;
&lt;li&gt;Webhooks by Zapier: GET command status&lt;/li&gt;
&lt;li&gt;Filter: Continue when status is "SUCCESS"&lt;/li&gt;
&lt;li&gt;Repeat for each platform using Zapier paths&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For detailed workflow setup, see the &lt;a href="https://renderio.dev/blogs/n8n-video-processing-guide" rel="noopener noreferrer"&gt;n8n video processing guide&lt;/a&gt; or the &lt;a href="https://renderio.dev/blogs/ai-ugc-video-processing-pipeline" rel="noopener noreferrer"&gt;AI UGC video processing pipeline&lt;/a&gt; guide. Brands processing creator content should also check the &lt;a href="https://renderio.dev/blogs/ugc-video-processing-for-brands" rel="noopener noreferrer"&gt;UGC video processing guide&lt;/a&gt; for normalization and branding steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing aspect ratio strategy
&lt;/h2&gt;

&lt;p&gt;When converting 16:9 to 9:16, you have three options. The right choice depends on your content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crop from center&lt;/strong&gt; works when the subject is centered (most AI talking-head videos). You lose the left and right edges but keep the subject sharp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blurred background&lt;/strong&gt; preserves the full frame at a smaller size with a blurred version filling the background. Looks clean but the video appears smaller on screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Letterbox/pillarbox&lt;/strong&gt; (black bars) is technically correct but looks terrible on mobile-first platforms. Avoid it for TikTok and Reels.&lt;/p&gt;

&lt;p&gt;For landscape-to-portrait conversion specifically, the &lt;a href="https://renderio.dev/blogs/heygen-video-to-instagram-reels" rel="noopener noreferrer"&gt;HeyGen to Reels&lt;/a&gt; guide covers cropping strategies for talking-head content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality optimization tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't over-compress for TikTok.&lt;/strong&gt; TikTok re-encodes everything you upload. If you upload at CRF 28 (low quality), TikTok's re-encoding makes it look worse. Upload at CRF 20-22 and let TikTok's encoder work with better source material.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;-movflags +faststart&lt;/code&gt; on every output.&lt;/strong&gt; This moves the metadata to the beginning of the file, which means playback starts faster. Platforms and users both prefer it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match the frame rate.&lt;/strong&gt; If your source is 24fps, don't upconvert to 30fps. It creates stuttery motion interpolation artifacts. Keep the original frame rate unless a platform specifically requires 30fps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test your audio.&lt;/strong&gt; Play your output next to a popular native video on each platform. If yours sounds noticeably quieter or louder, your LUFS target is off. The -14 LUFS target for TikTok/Reels is a guideline — some creators target -12 for punchier audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling to 10+ videos per day
&lt;/h2&gt;

&lt;p&gt;At 10 videos per day, you're making 50 API calls daily (5 platforms x 10 videos). That's 1,500 per month.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Daily videos&lt;/th&gt;
&lt;th&gt;Monthly API calls&lt;/th&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-3&lt;/td&gt;
&lt;td&gt;150-450&lt;/td&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4-6&lt;/td&gt;
&lt;td&gt;600-900&lt;/td&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-50&lt;/td&gt;
&lt;td&gt;1,500-7,500&lt;/td&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;$99/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;15,000+&lt;/td&gt;
&lt;td&gt;Business + overage&lt;/td&gt;
&lt;td&gt;$99/mo + usage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each video processed on RenderIO costs about $0.005-$0.018 depending on your plan. Compare that to building and maintaining your own FFmpeg server, which runs $50-200/month in compute alone before engineering time. For a full cost comparison, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-pricing-comparison" rel="noopener noreferrer"&gt;FFmpeg API pricing comparison&lt;/a&gt;. You can also &lt;a href="https://renderio.dev/blogs/ffmpeg-transcode-video" rel="noopener noreferrer"&gt;transcode video with FFmpeg&lt;/a&gt; locally for testing before scaling to the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting common issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Black screen on TikTok upload.&lt;/strong&gt; Usually means the video codec isn't H.264 or the pixel format isn't yuv420p. Add &lt;code&gt;-pix_fmt yuv420p&lt;/code&gt; to your command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio out of sync after conversion.&lt;/strong&gt; Common with variable frame rate sources. Add &lt;code&gt;-vsync cfr&lt;/code&gt; to force constant frame rate before other filters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instagram rejects the upload.&lt;/strong&gt; Check file size (250 MB max for Reels) and duration (3 minutes max). Also confirm the video is at least 3 seconds long, since Reels rejects shorter clips.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Can I batch process videos that aren't AI-generated?
&lt;/h3&gt;

&lt;p&gt;Yes. The commands work with any MP4 input. Screen recordings, phone clips, webcam footage, anything. The pipeline doesn't care about the video source.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does batch processing take for one video?
&lt;/h3&gt;

&lt;p&gt;About 15-45 seconds for all five platform versions to finish, depending on the video length. They process in parallel, so you're waiting for the slowest one, not all five sequentially.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if a platform changes its specs?
&lt;/h3&gt;

&lt;p&gt;Update the relevant command in your &lt;code&gt;platforms&lt;/code&gt; array. The rest of the pipeline stays the same. We update this guide when platforms change their requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add a watermark or logo during batch processing?
&lt;/h3&gt;

&lt;p&gt;Yes. Add an overlay filter to each command. For example: &lt;code&gt;-i {{in_video}} -i {{in_logo}} -filter_complex "[1:v]scale=80:-1[logo];[0:v][logo]overlay=W-w-20:20[v]"&lt;/code&gt;. The logo file is a second input in the API call.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between this and using CapCut's batch export?
&lt;/h3&gt;

&lt;p&gt;CapCut requires manual setup per video and runs on your machine. RenderIO processes via API, so it integrates into automated workflows (n8n, Zapier, custom scripts) and runs on Cloudflare's edge infrastructure. No local compute needed.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Make Duplicate TikTok Videos Unique at Scale</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Tue, 14 Apr 2026 07:39:16 +0000</pubDate>
      <link>https://forem.com/renderio/how-to-make-duplicate-tiktok-videos-unique-at-scale-4j5j</link>
      <guid>https://forem.com/renderio/how-to-make-duplicate-tiktok-videos-unique-at-scale-4j5j</guid>
      <description>&lt;h2&gt;
  
  
  TikTok kills duplicate content
&lt;/h2&gt;

&lt;p&gt;You post a video from Account A. It gets 100K views. You post the exact same video from Account B. It gets 200 views. TikTok's algorithm detected the duplicate and suppressed it.&lt;/p&gt;

&lt;p&gt;This is the core problem for anyone running multiple TikTok accounts. Same content, multiple accounts, diminishing returns on each repost.&lt;/p&gt;

&lt;p&gt;The solution isn't to create entirely new content for each account. It's to create variations that are technically unique while being visually identical to the viewer. These same techniques apply to &lt;a href="https://renderio.dev/blogs/product-video-variations-at-scale" rel="noopener noreferrer"&gt;product video variations&lt;/a&gt; and &lt;a href="https://renderio.dev/blogs/tiktok-ad-creative-automation" rel="noopener noreferrer"&gt;TikTok ad creatives&lt;/a&gt; where you need volume without manual editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How TikTok detects duplicates
&lt;/h2&gt;

&lt;p&gt;TikTok uses multiple layers of detection (for the full technical breakdown of each layer, see &lt;a href="https://renderio.dev/blogs/tiktok-duplicate-content-detection" rel="noopener noreferrer"&gt;how TikTok duplicate content detection works&lt;/a&gt;):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File hash comparison&lt;/strong&gt;: The simplest check. Identical files have identical SHA-256 hashes. Any re-encoding defeats this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Perceptual hashing&lt;/strong&gt;: A visual fingerprint of the video content. Compares frame structure, color distribution, and motion patterns. More sophisticated than file hashing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audio fingerprinting&lt;/strong&gt;: Analyzes the audio waveform to identify matching content. Similar to how Shazam identifies songs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata analysis&lt;/strong&gt;: Checks creation timestamps, encoder software, and other metadata fields.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To get past all four layers, you need to modify the visual content, the audio, and the metadata. Here are 8 techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 1: Re-encode with different CRF
&lt;/h2&gt;

&lt;p&gt;The simplest change. Different CRF values produce completely different files at the binary level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# CRF 22 for Account A&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output_a.mp4

&lt;span class="c"&gt;# CRF 23 for Account B&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac output_b.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defeats file hash comparison but NOT perceptual hashing. The visual content is too similar.&lt;/p&gt;

&lt;p&gt;API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &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;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: your_api_key"&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;'{
    "ffmpeg_command": "-i {{in_video}} -c:v libx264 -crf 22 -c:a aac {{out_video}}",
    "input_files": { "in_video": "https://example.com/source.mp4" },
    "output_files": { "out_video": "variation_a.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Technique 2: Micro-crop
&lt;/h2&gt;

&lt;p&gt;Remove 2-8 pixels from each edge. Invisible to the viewer. Changes every frame's content.&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="s2"&gt;"crop=iw-4:ih-4:2:2"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes 2 pixels from each side. The video is 4 pixels narrower and shorter. Unnoticeable on a phone screen. But every pixel coordinate shifts, defeating perceptual hashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 3: Brightness/color shift
&lt;/h2&gt;

&lt;p&gt;Shift brightness by 1-2%. Invisible to the eye. Changes pixel values across every frame.&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="s2"&gt;"eq=brightness=0.01:saturation=1.02"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;brightness=0.01&lt;/code&gt; is a 1% increase. &lt;code&gt;saturation=1.02&lt;/code&gt; is a 2% saturation boost. Both are below the threshold of human perception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 4: Audio pitch shift
&lt;/h2&gt;

&lt;p&gt;Shift the audio pitch by 0.5-1%. Inaudible. Defeats audio fingerprinting.&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;-af&lt;/span&gt; &lt;span class="s2"&gt;"asetrate=44100*1.005,aresample=44100"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v copy output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;asetrate=44100*1.005&lt;/code&gt; increases pitch by 0.5%. &lt;code&gt;aresample=44100&lt;/code&gt; resamples back to standard rate so the speed doesn't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 5: Add subtle noise
&lt;/h2&gt;

&lt;p&gt;Random noise changes every frame's pixel values. Even 3-5 units of noise is invisible but computationally significant.&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="s2"&gt;"noise=alls=5:allf=t"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;alls=5&lt;/code&gt; adds noise with strength 5 to all planes. &lt;code&gt;allf=t&lt;/code&gt; uses temporal noise (varies per frame).&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 6: Strip all metadata
&lt;/h2&gt;

&lt;p&gt;Remove encoder info, timestamps, GPS data, and software tags.&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;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v copy &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;This alone doesn't defeat content-based detection, but it removes any tool-specific fingerprints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 7: Speed micro-adjustment
&lt;/h2&gt;

&lt;p&gt;Change playback speed by 1-2%. Barely perceptible. Changes timing of every frame.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input.mp4 &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[0:v]setpts=0.98*PTS[v];[0:a]atempo=1.02[a]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[v]"&lt;/span&gt; &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2% speed increase. A 60-second video becomes 58.8 seconds. The frame timestamps all shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technique 8: Hue rotation
&lt;/h2&gt;

&lt;p&gt;Shift the color hue by 2-3 degrees. Invisible to casual viewing. Changes color data across every frame.&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="s2"&gt;"hue=h=2"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The combined approach
&lt;/h2&gt;

&lt;p&gt;No single technique is enough for aggressive duplicate detection. Combine multiple techniques:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=iw-4:ih-4:2:2,eq=brightness=0.01:saturation=1.02,noise=alls=5:allf=t,hue=h=2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"asetrate=44100*1.005,aresample=44100"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This applies crop + brightness + noise + hue shift to video, pitch shift to audio, and strips metadata. The result is indistinguishable from the original to a human viewer but completely unique to any detection algorithm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch processing with the API
&lt;/h2&gt;

&lt;p&gt;For multi-account posting, you need N variations of each video. Here's how to generate them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffsk_your_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://renderio.dev/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;source_video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/source.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;num_accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_accounts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;crop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# 2, 4, 6, 8, ... pixels
&lt;/span&gt;    &lt;span class="n"&gt;brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# -0.025 to +0.02
&lt;/span&gt;    &lt;span class="n"&gt;pitch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.002&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 0.99 to 1.008
&lt;/span&gt;    &lt;span class="n"&gt;crf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# 21-25
&lt;/span&gt;    &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# 3-6
&lt;/span&gt;    &lt;span class="n"&gt;hue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;        &lt;span class="c1"&gt;# -10 to +8 degrees
&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-i {{in_video}} &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-vf &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crop=iw-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:ih-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;crop&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;eq=brightness=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:saturation=1.01,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;noise=alls=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:allf=t,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hue=h=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-af &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;asetrate=44100*&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pitch&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,aresample=44100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-c:v libx264 -crf &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;crf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -map_metadata -1 &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{out_video}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run-ffmpeg-command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffmpeg_command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source_video&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Account &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;command_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10 API calls. 10 unique variations. Each one different enough to pass duplicate detection but visually identical to viewers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which techniques matter most
&lt;/h2&gt;

&lt;p&gt;Ranked by impact on duplicate detection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Micro-crop&lt;/strong&gt; (changes frame content) - highest impact on perceptual hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio pitch shift&lt;/strong&gt; (defeats audio fingerprint) - critical for music/voice content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-encode with different CRF&lt;/strong&gt; (changes file hash) - baseline requirement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brightness/color shift&lt;/strong&gt; (changes pixel values) - moderate impact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noise&lt;/strong&gt; (randomizes pixel data) - moderate impact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata strip&lt;/strong&gt; (removes tool fingerprints) - low but essential&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed adjustment&lt;/strong&gt; (changes timing) - moderate but noticeable if too aggressive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hue shift&lt;/strong&gt; (changes color space) - low to moderate impact&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At minimum, use techniques 1, 2, 3, and 6 together. That covers file hash, perceptual hash, audio fingerprint, and metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate with n8n or scripts
&lt;/h2&gt;

&lt;p&gt;For daily operations, wire this into an n8n workflow or a cron job. Source video goes in, N unique variations come out. Post each to a different account. For the full multi-account workflow with parameter tables and staggered uploads, see &lt;a href="https://renderio.dev/blogs/avoid-tiktok-duplicate-detection-at-scale" rel="noopener noreferrer"&gt;avoid TikTok duplicate detection at scale&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Starter plan at $9/mo includes 500 commands, enough to test batch variation generation with your real content. Scale to Growth ($29/mo) for production volume.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Extract Audio from Video in n8n</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Sun, 12 Apr 2026 07:46:07 +0000</pubDate>
      <link>https://forem.com/renderio/extract-audio-from-video-in-n8n-3jgj</link>
      <guid>https://forem.com/renderio/extract-audio-from-video-in-n8n-3jgj</guid>
      <description>&lt;h2&gt;
  
  
  Pull audio from video without touching a terminal
&lt;/h2&gt;

&lt;p&gt;You have video interviews to transcribe. Or podcast episodes recorded as video. Or a music library trapped in MP4 files. You need the audio track extracted.&lt;/p&gt;

&lt;p&gt;FFmpeg does this in one command. n8n can trigger that command automatically whenever a new video appears. No manual steps. No terminal. No server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: n8n can't extract audio natively
&lt;/h2&gt;

&lt;p&gt;n8n doesn't have an audio extraction node. The cloud version doesn't allow shell commands. Even self-hosted, running FFmpeg inside n8n blocks the worker and risks crashes on large files.&lt;/p&gt;

&lt;p&gt;The solution: send the extraction command to RenderIO's API via n8n's HTTP Request node. RenderIO runs FFmpeg in an isolated container. Your n8n instance stays responsive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the RenderIO n8n node
&lt;/h2&gt;

&lt;p&gt;RenderIO has a &lt;a href="https://n8n.io/integrations/renderio/" rel="noopener noreferrer"&gt;partner-verified community node&lt;/a&gt; on the n8n marketplace. Install from Settings → Community Nodes → search "renderio". It provides a visual interface for FFmpeg commands, including audio extraction.&lt;/p&gt;

&lt;p&gt;The node handles authentication and request formatting automatically. The extraction examples below use HTTP Request nodes for full flexibility, but the same FFmpeg commands work with the native node.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic extraction: MP4 to MP3
&lt;/h2&gt;

&lt;p&gt;The simplest workflow: video URL in, MP3 URL out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP Request node configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method: POST&lt;/li&gt;
&lt;li&gt;URL: &lt;code&gt;https://renderio.dev/api/v1/run-ffmpeg-command&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication: Header Auth (X-API-KEY)&lt;/li&gt;
&lt;li&gt;Body:
&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec libmp3lame -q:a 2 {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"extracted.mp3"&lt;/span&gt;&lt;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;&lt;code&gt;-vn&lt;/code&gt; disables video. &lt;code&gt;-q:a 2&lt;/code&gt; sets MP3 quality (0=best, 9=worst, 2 is high quality at ~190kbps).&lt;/p&gt;

&lt;p&gt;Poll for completion, then use the output URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extraction formats
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MP3 (most compatible)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec libmp3lame -q:a 2 {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.mp3"&lt;/span&gt;&lt;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;Best for: sharing, podcast distribution, general use.&lt;/p&gt;

&lt;h3&gt;
  
  
  WAV (lossless)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec pcm_s16le -ar 44100 {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.wav"&lt;/span&gt;&lt;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;Best for: transcription services (they often prefer WAV), audio editing, archival.&lt;/p&gt;

&lt;h3&gt;
  
  
  AAC (Apple/streaming)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec aac -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.m4a"&lt;/span&gt;&lt;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;Best for: Apple devices, streaming platforms, smaller files than MP3 at same quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  FLAC (lossless compressed)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec flac {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.flac"&lt;/span&gt;&lt;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;Best for: archival when you want lossless but smaller than WAV (typically 50-60% of WAV size).&lt;/p&gt;

&lt;h3&gt;
  
  
  OGG/Opus (web)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec libopus -b:a 128k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.ogg"&lt;/span&gt;&lt;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;Best for: web applications, voice recordings, VoIP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete workflow: Extract and transcribe
&lt;/h2&gt;

&lt;p&gt;Combine audio extraction with a transcription service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google Drive Trigger (new video)
  → HTTP Request: Extract audio (RenderIO)
  → Wait + Poll
  → HTTP Request: Download audio
  → HTTP Request: Send to Whisper API / AssemblyAI / Deepgram
  → Google Sheets: Write transcript
  → Slack: Notify team
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node 1: Google Drive Trigger&lt;/strong&gt;&lt;br&gt;
Watches a "Videos" folder for new uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node 2: Extract audio (HTTP Request)&lt;/strong&gt;&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec pcm_s16le -ar 16000 -ac 1 {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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.downloadUrl }}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"for_transcription.wav"&lt;/span&gt;&lt;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;Note: &lt;code&gt;-ar 16000 -ac 1&lt;/code&gt; converts to 16kHz mono. This is the format most transcription APIs prefer. Smaller files, faster uploads, same transcription quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node 3-5: Poll and get result&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard polling loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node 6: Send to transcription&lt;/strong&gt;&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.openai.com/v1/audio/transcriptions"&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 {{ $credentials.openAiApi.apiKey }}"&lt;/span&gt;&lt;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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"whisper-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;"file"&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.output_files.out_audio.storage_url }}"&lt;/span&gt;&lt;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;h2&gt;
  
  
  Batch extraction from a video library
&lt;/h2&gt;

&lt;p&gt;Process an entire folder of videos:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Get video list&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use a Code node or fetch from a spreadsheet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videos&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/interview1.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interview1&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/interview2.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interview2&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/interview3.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interview3&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;return&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Split in Batches&lt;/strong&gt; (size: 5)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Submit extraction for each&lt;/strong&gt;&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;"ffmpeg_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;"-i {{in_video}} -vn -acodec libmp3lame -q:a 2 {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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.url }}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&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.name }}.mp3"&lt;/span&gt;&lt;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;&lt;strong&gt;Step 4: Poll and collect URLs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Write results to spreadsheet&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Video&lt;/th&gt;
&lt;th&gt;Audio URL&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;interview1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://media.renderio.dev/interview1.mp3" rel="noopener noreferrer"&gt;https://media.renderio.dev/interview1.mp3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;extracted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;interview2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://media.renderio.dev/interview2.mp3" rel="noopener noreferrer"&gt;https://media.renderio.dev/interview2.mp3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;extracted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Audio processing after extraction
&lt;/h2&gt;

&lt;p&gt;Once you have the audio, you can process it further:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalize volume:&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;&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="o"&gt;{{&lt;/span&gt;in_audio&lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nv"&gt;loudnorm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-16&lt;/span&gt;:TP&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;.5:LRA&lt;span class="o"&gt;=&lt;/span&gt;11 &lt;span class="o"&gt;{{&lt;/span&gt;out_audio&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trim silence from start/end:&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;&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="o"&gt;{{&lt;/span&gt;in_audio&lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="nv"&gt;silenceremove&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;start_periods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1:start_silence&lt;span class="o"&gt;=&lt;/span&gt;0.5:start_threshold&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50dB&lt;/span&gt;,areverse,silenceremove&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;start_periods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1:start_silence&lt;span class="o"&gt;=&lt;/span&gt;0.5:start_threshold&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-50dB&lt;/span&gt;,areverse &lt;span class="o"&gt;{{&lt;/span&gt;out_audio&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Convert sample rate:&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;&lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="o"&gt;{{&lt;/span&gt;in_audio&lt;span class="o"&gt;}}&lt;/span&gt; &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 &lt;span class="o"&gt;{{&lt;/span&gt;out_audio&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chain these into your workflow as additional processing steps after extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error handling
&lt;/h2&gt;

&lt;p&gt;Common extraction failures:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No audio track&lt;/strong&gt;: Some screen recordings or animations have no audio. FFmpeg returns an error. Handle with an IF node that checks the error message for "does not contain any stream."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrupted audio&lt;/strong&gt;: Add &lt;code&gt;-err_detect ignore_err&lt;/code&gt; before &lt;code&gt;-i&lt;/code&gt; to attempt extraction despite minor corruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Very long videos&lt;/strong&gt;: Extraction is fast (typically 10-30 seconds regardless of video length) because it only copies/transcodes the audio stream, not the video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The Starter plan at $9/mo includes 500 commands -- enough to set up and test your audio extraction workflow.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>FFmpeg API with Node.js: Video Processing in 10 Lines</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Thu, 09 Apr 2026 06:32:13 +0000</pubDate>
      <link>https://forem.com/renderio/ffmpeg-api-with-nodejs-video-processing-in-10-lines-3jjo</link>
      <guid>https://forem.com/renderio/ffmpeg-api-with-nodejs-video-processing-in-10-lines-3jjo</guid>
      <description>&lt;h2&gt;
  
  
  Video processing shouldn't require child_process
&lt;/h2&gt;

&lt;p&gt;The typical Node.js approach to FFmpeg is &lt;code&gt;child_process.spawn()&lt;/code&gt;. You shell out to a binary, parse stderr for progress, handle exit codes, and hope the binary exists on your deployment target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The old way&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;spawn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ffmpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-c:v&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;libx264&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Exited with code &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works locally. It breaks in production when the FFmpeg binary isn't installed, isn't the right version, or doesn't support the codec you need. If you've compared &lt;a href="https://renderio.dev/blogs/hosted-ffmpeg-vs-self-hosted" rel="noopener noreferrer"&gt;self-hosted vs cloud FFmpeg&lt;/a&gt;, you know the tradeoffs.&lt;/p&gt;

&lt;p&gt;Here's the alternative: &lt;code&gt;fetch()&lt;/code&gt;. Ten lines, no binary dependency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ffsk_your_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libx264 -crf 22 -c:a aac {output}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.mov&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;converted.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="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;command_id&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works everywhere Node.js runs — no binary to bundle, no codec compatibility issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Zero dependencies required. Node.js 18+ has built-in &lt;code&gt;fetch&lt;/code&gt;. For older versions, use &lt;code&gt;node-fetch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ffsk_your_api_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://renderio.dev/api/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Submit the command&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitRes&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/run-ffmpeg-command`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;outputFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;submitRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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;error&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;submitRes&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;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;`Submit failed: &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="nx"&gt;error&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command_id&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;submitRes&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;// Poll for result&lt;/span&gt;
  &lt;span class="k"&gt;while &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusRes&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/commands/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command_id&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;status&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;statusRes&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;status&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;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_files&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;status&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="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;`FFmpeg failed: &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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every example below uses this &lt;code&gt;runFFmpeg&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert formats
&lt;/h2&gt;

&lt;p&gt;MOV to MP4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libx264 -preset fast -crf 22 -c:a aac -b:a 128k {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.mov&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;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;converted.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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Download: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MP4 to WebM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;web.webm&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;
  
  
  Resize for social platforms
&lt;/h2&gt;

&lt;p&gt;TikTok (1080x1920):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i {input} -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black" -c:v libx264 -crf 23 -c:a aac {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tiktok.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;YouTube Shorts (1080x1920, same dimensions):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i {input} -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2" -c:v libx264 -crf 23 -c:a aac {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shorts.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;720p for general web use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -vf scale=-2:720 -c:v libx264 -crf 23 -c:a copy {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/4k-video.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="na"&gt;output&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.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extract audio
&lt;/h2&gt;

&lt;p&gt;To MP3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -vn -acodec libmp3lame -q:a 2 {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio.mp3&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;
  
  
  Generate thumbnails
&lt;/h2&gt;

&lt;p&gt;For advanced frame extraction — keyframes, scene detection, and batch processing — see the &lt;a href="https://renderio.dev/blogs/ffmpeg-extract-frames" rel="noopener noreferrer"&gt;FFmpeg frame extraction guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Single frame at 5 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -ss 00:00:05 -vframes 1 -q:v 2 {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thumb.jpg&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;
  
  
  Trim video
&lt;/h2&gt;

&lt;p&gt;For keyframe-accurate trimming, batch operations, and the trim filter, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-trim-video" rel="noopener noreferrer"&gt;dedicated trim guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;30 seconds starting at 1:00:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -ss 00:01:00 -t 00:00:30 -c copy {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/long-video.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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clip.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add watermark
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-i {video} -i {logo} -filter_complex "overlay=W-w-10:H-h-10" {output}&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;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/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;logo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/watermark.png&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="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;watermarked.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic fixed-position overlay. For responsive scaling with &lt;code&gt;scale2ref&lt;/code&gt;, semi-transparent logos, text overlays, and moving watermarks, the &lt;a href="https://renderio.dev/blogs/ffmpeg-watermark" rel="noopener noreferrer"&gt;FFmpeg watermark guide&lt;/a&gt; covers it all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using webhooks instead of polling
&lt;/h2&gt;

&lt;p&gt;Polling works for scripts and CLIs. For web applications, webhooks are better. Configure a webhook URL in your RenderIO dashboard, then submit without polling:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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;// Submit a job (fire and forget)&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;submitJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputUrl&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/run-ffmpeg-command`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libx264 -crf 22 {output}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result.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="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;command_id&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;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="c1"&gt;// Store command_id in your database, associated with the user/job&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;command_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Receive webhook when processing completes&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/renderio&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command_id&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="nx"&gt;output_files&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;body&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;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;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update your database, notify the user, etc.&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; done: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&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;else&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;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="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="s2"&gt;`Job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &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;body&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;Your server just reacts to events instead of burning cycles polling. For a full walkthrough of the submit-poll-download flow, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-rest-api-tutorial" rel="noopener noreferrer"&gt;REST API tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch processing
&lt;/h2&gt;

&lt;p&gt;Process multiple videos concurrently with &lt;code&gt;Promise.all&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videos&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/v1.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;out1.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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/v2.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;out2.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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/v3.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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;out3.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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libx264 -crf 23 -c:a aac {output}&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;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;result&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;fulfilled&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&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;else&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Promise.allSettled&lt;/code&gt; ensures one failure doesn't cancel the rest. Each video processes in its own container. If you're doing this at scale for social media, the &lt;a href="https://renderio.dev/blogs/batch-process-ai-videos-social-media" rel="noopener noreferrer"&gt;batch video processing&lt;/a&gt; guide covers the full pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Express.js integration
&lt;/h2&gt;

&lt;p&gt;A complete Express endpoint that accepts video uploads and returns processed versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/process-video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;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;operation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;body&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;commands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;compress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -c:v libx264 -crf 28 -preset fast -c:a aac -b:a 96k {output}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -ss 00:00:03 -vframes 1 -q:v 2 {output}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -vn -acodec libmp3lame -q:a 4 {output}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resize720&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-i {input} -vf scale=-2:720 -c:v libx264 -crf 23 {output}&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;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;operation&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;command&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&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;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;Unknown operation&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;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thumbnail&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;jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;operation&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;audio&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;mp3&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;mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&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;result&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;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;input&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`processed.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;Four operations behind a single endpoint — compress, thumbnail, extract audio, resize. You could add more by extending the &lt;code&gt;commands&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript types
&lt;/h2&gt;

&lt;p&gt;If you're using TypeScript, here are the types for the API responses:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FFmpegSubmitResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;queued&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FFmpegStatusResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;command_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&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="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="nl"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&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="nl"&gt;error&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runFFmpeg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&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="nx"&gt;outputFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;submitRes&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/run-ffmpeg-command`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inputFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;outputFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;submitRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&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;`Submit failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;submitRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;command_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;submitRes&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;as&lt;/span&gt; &lt;span class="nx"&gt;FFmpegSubmitResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusRes&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/commands/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;command_id&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;statusRes&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;as&lt;/span&gt; &lt;span class="nx"&gt;FFmpegStatusResponse&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;status&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;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_files&lt;/span&gt;&lt;span class="o"&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;status&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="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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Does this work with Bun or Deno?
&lt;/h3&gt;

&lt;p&gt;Yes. Both Bun and Deno support &lt;code&gt;fetch&lt;/code&gt; natively, so the code works without changes. The only thing to adjust is how you load environment variables for the API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this with Next.js API routes?
&lt;/h3&gt;

&lt;p&gt;Absolutely. The &lt;code&gt;runFFmpeg&lt;/code&gt; function works in any server-side Node.js context. In Next.js, call it from an API route or a Server Action — just don't call it from client-side code since that would expose your API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle large files that take minutes to process?
&lt;/h3&gt;

&lt;p&gt;Use webhooks instead of polling. Submit the job, store the &lt;code&gt;command_id&lt;/code&gt; in your database, and let RenderIO POST the result to your webhook endpoint when it's done. The webhook section above shows the Express setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the maximum concurrent requests?
&lt;/h3&gt;

&lt;p&gt;It depends on your plan. The API processes each job in an isolated container, so there's no shared resource bottleneck. For heavy batch workloads, you might want to limit your &lt;code&gt;Promise.all&lt;/code&gt; concurrency to avoid hitting rate limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this with the Node.js FFmpeg cheat sheet commands?
&lt;/h3&gt;

&lt;p&gt;Yes — every command from the &lt;a href="https://renderio.dev/blogs/ffmpeg-cheat-sheet" rel="noopener noreferrer"&gt;FFmpeg cheat sheet&lt;/a&gt; works. Replace local file paths with URL placeholders (&lt;code&gt;{input}&lt;/code&gt;, &lt;code&gt;{output}&lt;/code&gt;) and pass them through &lt;code&gt;runFFmpeg&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;For a quick reference of common FFmpeg commands you can use with this setup, check the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-curl-examples" rel="noopener noreferrer"&gt;curl examples&lt;/a&gt;. The same commands work in both curl and Node.js.&lt;/p&gt;

&lt;p&gt;The Starter plan at $9/mo includes 500 commands. &lt;a href="https://dev.to/get-api-key"&gt;Get your API key&lt;/a&gt; to start building.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Extract Audio from Video in Zapier</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Wed, 08 Apr 2026 06:13:49 +0000</pubDate>
      <link>https://forem.com/renderio/extract-audio-from-video-in-zapier-ip4</link>
      <guid>https://forem.com/renderio/extract-audio-from-video-in-zapier-ip4</guid>
      <description>&lt;h2&gt;
  
  
  Video in. Audio out. Automatically.
&lt;/h2&gt;

&lt;p&gt;You record a video interview. You need the audio as an MP3 for your podcast feed. You film a webinar. You need the audio for transcription. You get UGC video. You need to check the audio quality without downloading the whole file.&lt;/p&gt;

&lt;p&gt;Each of these requires extracting audio from video. It's a 30-second FFmpeg operation that Zapier can't do natively.&lt;/p&gt;

&lt;p&gt;With RenderIO, it becomes an automated step in any Zap. Video goes in, MP3 comes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic Zap
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Trigger
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Google Drive&lt;br&gt;
&lt;strong&gt;Event&lt;/strong&gt;: New File in Folder&lt;br&gt;
&lt;strong&gt;Folder&lt;/strong&gt;: "Videos for Audio Extraction"&lt;/p&gt;

&lt;p&gt;Alternative triggers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dropbox: New file&lt;/li&gt;
&lt;li&gt;Email: New attachment&lt;/li&gt;
&lt;li&gt;Webhook: Custom trigger&lt;/li&gt;
&lt;li&gt;Typeform: New file upload&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 2: Extract audio
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Webhooks by Zapier&lt;br&gt;
&lt;strong&gt;Event&lt;/strong&gt;: POST&lt;br&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;https://renderio.dev/api/v1/run-ffmpeg-command&lt;/code&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;X-API-KEY&lt;/code&gt;: your_api_key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Content-Type&lt;/code&gt;: application/json&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Body&lt;/strong&gt;:&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;"ffmpeg_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;"-i {{in_video}} -vn -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{step1_file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"extracted-audio.mp3"&lt;/span&gt;&lt;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 flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-vn&lt;/code&gt;: No video (strip the video stream entirely)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c:a libmp3lame&lt;/code&gt;: Encode audio as MP3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b:a 192k&lt;/code&gt;: 192 kbps bitrate (good quality for speech)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Wait
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Delay by Zapier&lt;br&gt;
&lt;strong&gt;Duration&lt;/strong&gt;: 20 seconds&lt;/p&gt;

&lt;p&gt;Audio extraction is fast because FFmpeg doesn't need to decode/encode video. Even a 30-minute video extracts in under 10 seconds.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Check status
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Webhooks by Zapier&lt;br&gt;
&lt;strong&gt;Event&lt;/strong&gt;: GET&lt;br&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;https://renderio.dev/api/v1/commands/{{step2_command_id}}&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Headers&lt;/strong&gt;: &lt;code&gt;X-API-KEY: your_api_key&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Save the MP3
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;App&lt;/strong&gt;: Google Drive&lt;br&gt;
&lt;strong&gt;Event&lt;/strong&gt;: Upload File&lt;br&gt;
&lt;strong&gt;File URL&lt;/strong&gt;: {{step4_output_url}}&lt;br&gt;
&lt;strong&gt;Folder&lt;/strong&gt;: "Extracted Audio"&lt;br&gt;
&lt;strong&gt;Filename&lt;/strong&gt;: &lt;code&gt;{{step1_filename}}.mp3&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Audio format options
&lt;/h2&gt;
&lt;h3&gt;
  
  
  MP3 (most compatible)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.mp3"&lt;/span&gt;&lt;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;Best for: Podcast distribution, general sharing, email attachments.&lt;/p&gt;
&lt;h3&gt;
  
  
  WAV (lossless)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -c:a pcm_s16le {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.wav"&lt;/span&gt;&lt;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;Best for: Audio editing, music production, when you need maximum quality.&lt;/p&gt;
&lt;h3&gt;
  
  
  AAC (smaller than MP3)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -c:a aac -b:a 128k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.m4a"&lt;/span&gt;&lt;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;Best for: Apple ecosystem, when file size matters.&lt;/p&gt;
&lt;h3&gt;
  
  
  FLAC (lossless, compressed)
&lt;/h3&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;"ffmpeg_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;"-i {{in_video}} -vn -c:a flac {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio.flac"&lt;/span&gt;&lt;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;Best for: Archival, when you want lossless but smaller than WAV.&lt;/p&gt;
&lt;h2&gt;
  
  
  Audio processing options
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Normalize volume
&lt;/h3&gt;

&lt;p&gt;Ensure consistent loudness across extracted 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;"ffmpeg_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;"-i {{in_video}} -vn -af &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;loudnorm=I=-16:TP=-2:LRA=11&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normalized.mp3"&lt;/span&gt;&lt;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;&lt;code&gt;-16 LUFS&lt;/code&gt; is the podcast standard. This ensures your extracted audio plays at a consistent level regardless of the original recording volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove background noise
&lt;/h3&gt;

&lt;p&gt;Basic noise reduction with FFmpeg:&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;"ffmpeg_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;"-i {{in_video}} -vn -af &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;highpass=f=80,lowpass=f=12000,afftdn=nf=-20&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clean.mp3"&lt;/span&gt;&lt;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 applies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-pass filter at 80Hz (removes low rumble)&lt;/li&gt;
&lt;li&gt;Low-pass filter at 12kHz (removes high-frequency hiss)&lt;/li&gt;
&lt;li&gt;FFT-based noise reduction (reduces ambient noise)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Extract specific time range
&lt;/h3&gt;

&lt;p&gt;Extract audio from a specific portion of 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;"ffmpeg_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;"-i {{in_video}} -ss 00:02:30 -t 00:10:00 -vn -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"segment.mp3"&lt;/span&gt;&lt;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;&lt;code&gt;-ss 00:02:30&lt;/code&gt; starts at 2 minutes 30 seconds. &lt;code&gt;-t 00:10:00&lt;/code&gt; extracts 10 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Split into chapters
&lt;/h3&gt;

&lt;p&gt;Extract multiple segments from one video:&lt;/p&gt;

&lt;p&gt;First segment:&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;"ffmpeg_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;"-i {{in_video}} -ss 0 -t 600 -vn -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chapter-1.mp3"&lt;/span&gt;&lt;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;Second segment:&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;"ffmpeg_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;"-i {{in_video}} -ss 600 -t 600 -vn -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chapter-2.mp3"&lt;/span&gt;&lt;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;Use multiple Webhooks steps or a loop in Zapier to create all chapters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: Podcast repurposing
&lt;/h2&gt;

&lt;p&gt;A common workflow for video podcasters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;: New video uploaded to Google Drive (after recording)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract full audio&lt;/strong&gt;: MP3, 192kbps, normalized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract first 60 seconds&lt;/strong&gt;: MP3, for social media teaser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save full audio&lt;/strong&gt;: Upload to podcast hosting (Buzzsprout, Anchor)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save teaser&lt;/strong&gt;: Upload to social media scheduler
&lt;/li&gt;
&lt;/ol&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;"ffmpeg_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;"-i {{in_video}} -vn -af &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;loudnorm=I=-16:TP=-2:LRA=11&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"full-episode.mp3"&lt;/span&gt;&lt;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;Teaser:&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;"ffmpeg_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;"-i {{in_video}} -t 60 -vn -af &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;loudnorm=I=-16:TP=-2:LRA=11,afade=t=out:st=55:d=5&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c:a libmp3lame -b:a 192k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"teaser.mp3"&lt;/span&gt;&lt;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 teaser includes a 5-second fade-out at the 55-second mark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: Transcription prep
&lt;/h2&gt;

&lt;p&gt;Before sending audio to a transcription service (Otter, Rev, Whisper):&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;"ffmpeg_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;"-i {{in_video}} -vn -af &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;highpass=f=80,lowpass=f=8000,loudnorm=I=-16&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -ar 16000 -ac 1 -c:a libmp3lame -b:a 64k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"for-transcription.mp3"&lt;/span&gt;&lt;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 optimizes for transcription:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filters out non-speech frequencies (80Hz-8kHz)&lt;/li&gt;
&lt;li&gt;Normalizes volume&lt;/li&gt;
&lt;li&gt;Downsamples to 16kHz (sufficient for speech)&lt;/li&gt;
&lt;li&gt;Mono channel (speech doesn't need stereo)&lt;/li&gt;
&lt;li&gt;64kbps (small file size for upload)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting file is 80-90% smaller than the original, which means faster uploads to transcription services and lower costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: Audio quality check
&lt;/h2&gt;

&lt;p&gt;Before reviewing hours of UGC video, check audio quality quickly:&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;"ffmpeg_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;"-i {{in_video}} -t 30 -vn -c:a libmp3lame -b:a 128k {{out_audio}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{file_url}}"&lt;/span&gt;&lt;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;"output_files"&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;"out_audio"&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.mp3"&lt;/span&gt;&lt;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 the first 30 seconds as a quick preview. Listen in Slack or email without downloading the full video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;Audio extraction is one of the lightest FFmpeg operations. Processing is nearly instant.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Volume&lt;/th&gt;
&lt;th&gt;Monthly commands&lt;/th&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 videos/week&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 videos/day&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 videos/day&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Video contains audio. FFmpeg extracts it. Zapier automates it. That's the whole story.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best FFmpeg API Services in 2026: Complete Comparison</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Wed, 08 Apr 2026 06:12:49 +0000</pubDate>
      <link>https://forem.com/renderio/best-ffmpeg-api-services-in-2026-complete-comparison-bd</link>
      <guid>https://forem.com/renderio/best-ffmpeg-api-services-in-2026-complete-comparison-bd</guid>
      <description>&lt;h2&gt;
  
  
  The FFmpeg API market in 2026
&lt;/h2&gt;

&lt;p&gt;Running FFmpeg on someone else's infrastructure is a solved problem. The &lt;a href="https://renderio.dev/blogs/ffmpeg-as-a-service" rel="noopener noreferrer"&gt;FFmpeg as a service&lt;/a&gt; model means multiple services now offer REST APIs that accept FFmpeg commands and return processed files.&lt;/p&gt;

&lt;p&gt;But they're not all the same. Pricing models differ. Infrastructure varies. Feature sets diverge. The right choice depends on your specific use case, volume, and budget.&lt;/p&gt;

&lt;p&gt;This is a comparison of every major FFmpeg API option available in 2026, including self-hosted alternatives. For a pricing-only breakdown, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-pricing-compared" rel="noopener noreferrer"&gt;detailed pricing comparison&lt;/a&gt;. For a hands-on guide to making your first API call, start with the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;complete FFmpeg API guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contenders
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;RenderIO&lt;/strong&gt; - Edge-based FFmpeg API on Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendi&lt;/strong&gt; - One of the original FFmpeg API services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ffmpeg-api.com&lt;/strong&gt; - Lightweight FFmpeg API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted on AWS Lambda&lt;/strong&gt; - DIY approach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted on GCP Cloud Run&lt;/strong&gt; - Container-based DIY&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted on bare metal&lt;/strong&gt; - Full control approach&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Feature matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;RenderIO&lt;/th&gt;
&lt;th&gt;Rendi&lt;/th&gt;
&lt;th&gt;ffmpeg-api.com&lt;/th&gt;
&lt;th&gt;Lambda (DIY)&lt;/th&gt;
&lt;th&gt;Cloud Run (DIY)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full FFmpeg&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial*&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed service&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Semi&lt;/td&gt;
&lt;td&gt;Semi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry price&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;td&gt;Free ($0)&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Pay-per-use&lt;/td&gt;
&lt;td&gt;Pay-per-use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egress fees&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;$0.09/GB&lt;/td&gt;
&lt;td&gt;$0.12/GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge computing&lt;/td&gt;
&lt;td&gt;Yes (330+ cities)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Per-region&lt;/td&gt;
&lt;td&gt;Per-region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;Yes (retry + DLQ)&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max video length&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;Plan-based&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;15 min timeout&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Cold starts&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;td&gt;2-8 hours&lt;/td&gt;
&lt;td&gt;1-4 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*AWS Lambda has a 250MB deployment package limit, which restricts FFmpeg codec support. See the &lt;a href="https://renderio.dev/blogs/serverless-ffmpeg" rel="noopener noreferrer"&gt;serverless FFmpeg deep dive&lt;/a&gt; for the full breakdown of Lambda's limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  RenderIO
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Runs on Cloudflare Workers with Sandbox Durable Objects. Each FFmpeg command executes in an isolated container on the nearest edge location. Results stored in R2 (zero egress).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Commands/mo&lt;/th&gt;
&lt;th&gt;Max Duration&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Overage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$9/mo&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;$0.08/cmd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;$0.05/cmd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;$99/mo&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;td&gt;200 GB&lt;/td&gt;
&lt;td&gt;$0.02/cmd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Command-based pricing means file size doesn't affect cost. A 10MB video and a 2GB video both count as one command. Zero egress fees on all plans since output files are stored on Cloudflare R2.&lt;/p&gt;

&lt;h3&gt;
  
  
  API example
&lt;/h3&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://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: ffsk_your_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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"scale=1920:1080\" -c:v libx264 -crf 20 {{out_video}}",
    "input_files": { "in_video": "https://example.com/input.mp4" },
    "output_files": { "out_video": "output.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Teams processing up to 20,000 videos/month (and beyond with overage)&lt;/li&gt;
&lt;li&gt;Global user bases (edge processing)&lt;/li&gt;
&lt;li&gt;Workflows needing reliable webhooks&lt;/li&gt;
&lt;li&gt;Predictable billing requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Newer service (less community content)&lt;/li&gt;
&lt;li&gt;Cloudflare ecosystem lock-in for storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rendi
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Runs on traditional cloud providers (AWS/GCP/Azure). Processing in specific regions. Established service with a track record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Free: $0/mo (4 vCPUs, 50GB processing, 1 min runtime)&lt;/li&gt;
&lt;li&gt;Pro: $25/mo (4-256 vCPUs, 100GB+ processing, up to unlimited runtime)&lt;/li&gt;
&lt;li&gt;Enterprise: Custom pricing&lt;/li&gt;
&lt;li&gt;GB-based pricing model, no egress fees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rendi has a free tier, which RenderIO does not. If you want to test an FFmpeg API without entering payment details, Rendi's free plan lets you process up to 50GB with a 1-minute runtime limit. The $5 credit card verification is refunded.&lt;/p&gt;

&lt;h3&gt;
  
  
  API example
&lt;/h3&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.rendi.dev/v1/process &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer rendi_key"&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;'{
    "command": "ffmpeg -i input.mp4 -vf scale=1920:1080 output.mp4",
    "inputs": { "input.mp4": "https://example.com/input.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Teams already integrated with Rendi&lt;/li&gt;
&lt;li&gt;Workflows processing many small files (GB-based pricing advantage)&lt;/li&gt;
&lt;li&gt;Users who want a free tier to test with&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Free tier limited to 1 min runtime and 50GB processing&lt;/li&gt;
&lt;li&gt;GB-based pricing can be unpredictable with large files&lt;/li&gt;
&lt;li&gt;Pro plan starts at $25/mo vs RenderIO's $9/mo&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ffmpeg-api.com
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Third-party FFmpeg API service. The website and documentation have changed multiple times, making it hard to give a stable overview. As of early 2026, it appears to offer a REST API that accepts FFmpeg commands, similar to RenderIO and Rendi.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple, one-off conversions where you want another option&lt;/li&gt;
&lt;li&gt;Testing alongside other services to compare latency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pricing, feature set, and documentation have changed over time. Check &lt;a href="https://ffmpeg-api.com" rel="noopener noreferrer"&gt;ffmpeg-api.com&lt;/a&gt; directly for current plans before committing.&lt;/li&gt;
&lt;li&gt;We couldn't confirm SLA or uptime guarantees. Test thoroughly before using in production.&lt;/li&gt;
&lt;li&gt;Codec and filter support may be narrower than a full FFmpeg build. Verify that your specific commands work before building a pipeline around it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Self-hosted: AWS Lambda
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Package FFmpeg as a Lambda layer. Trigger via API Gateway. Process video in Lambda function. Store results in S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup (simplified pseudocode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Lambda function (simplified - real implementation needs
# download/upload logic, error handling, and temp file management)
&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ffmpeg_command&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Download input from S3
&lt;/span&gt;    &lt;span class="c1"&gt;# Run FFmpeg
&lt;/span&gt;    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ffmpeg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="c1"&gt;# Upload output to S3
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;output_url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lambda: $0.0000167/GB-s&lt;/li&gt;
&lt;li&gt;API Gateway: $3.50/million requests&lt;/li&gt;
&lt;li&gt;S3 storage: $0.023/GB&lt;/li&gt;
&lt;li&gt;S3 egress: $0.09/GB&lt;/li&gt;
&lt;li&gt;Total for 10,000 videos/month: ~$15-50 depending on video size and duration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Teams with AWS expertise&lt;/li&gt;
&lt;li&gt;Low-volume processing (under 100 videos/month)&lt;/li&gt;
&lt;li&gt;Simple operations (convert, resize)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;250MB deployment limit&lt;/strong&gt;: Can't include all FFmpeg codecs. No libx265, limited filter support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15-minute timeout&lt;/strong&gt;: Long videos or complex filter chains will fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt;: First invocation after idle takes 5-10 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex packaging&lt;/strong&gt;: FFmpeg binary must be compiled for Lambda's Amazon Linux environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No GPU&lt;/strong&gt;: Strictly CPU encoding.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Self-hosted: GCP Cloud Run
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Docker container with FFmpeg. Triggered via HTTP request. Auto-scales to zero when idle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FROM jrottenberg/ffmpeg:4.4-alpine
COPY server.js /app/
WORKDIR /app
RUN apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; nodejs npm
RUN npm &lt;span class="nb"&gt;install &lt;/span&gt;express
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;, &lt;span class="s2"&gt;"server.js"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Run: $0.00002400/vCPU-second + $0.00000250/GiB-second&lt;/li&gt;
&lt;li&gt;Cloud Storage: $0.020/GB&lt;/li&gt;
&lt;li&gt;Egress: $0.12/GB&lt;/li&gt;
&lt;li&gt;Total for 10,000 videos/month: ~$30-100+ depending on processing time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Teams with GCP expertise and existing infrastructure&lt;/li&gt;
&lt;li&gt;Need for custom FFmpeg builds or specific codec versions&lt;/li&gt;
&lt;li&gt;Long-running processes (no 15-minute timeout)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You manage everything&lt;/strong&gt;: Scaling, error handling, retries, monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt;: Container startup adds 2-5 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress costs&lt;/strong&gt;: $0.12/GB adds up quickly with video&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational overhead&lt;/strong&gt;: Logging, alerting, container updates, security patches&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Self-hosted: Bare metal / VPS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Install FFmpeg on a VPS (DigitalOcean, Hetzner, etc.). Run a web server that accepts commands and processes video.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hetzner CPX31 (4 vCPU, 8GB RAM): ~$15/month&lt;/li&gt;
&lt;li&gt;DigitalOcean Droplet (4 vCPU, 8GB): ~$48/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Maximum control over FFmpeg version and codecs&lt;/li&gt;
&lt;li&gt;Consistent, predictable workloads&lt;/li&gt;
&lt;li&gt;Teams with Linux sysadmin skills&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You manage everything&lt;/strong&gt;: OS updates, security, monitoring, backups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No auto-scaling&lt;/strong&gt;: Fixed capacity regardless of demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single point of failure&lt;/strong&gt;: One server goes down, everything stops&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU possible&lt;/strong&gt;: Can use GPU-enabled instances for hardware encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best FFmpeg API decision matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Best option&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Just works, no ops&lt;/td&gt;
&lt;td&gt;RenderIO or Rendi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cheapest at 100 videos/month&lt;/td&gt;
&lt;td&gt;RenderIO Starter ($9/mo) or Lambda&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cheapest at 10,000 videos/month&lt;/td&gt;
&lt;td&gt;RenderIO Business ($99/mo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global low latency&lt;/td&gt;
&lt;td&gt;RenderIO (edge)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum FFmpeg control&lt;/td&gt;
&lt;td&gt;Self-hosted (bare metal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing AWS infrastructure&lt;/td&gt;
&lt;td&gt;Lambda (if simple) or Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU encoding&lt;/td&gt;
&lt;td&gt;Self-hosted (bare metal with GPU)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reliable webhooks&lt;/td&gt;
&lt;td&gt;RenderIO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier to test with&lt;/td&gt;
&lt;td&gt;Rendi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum operational overhead&lt;/td&gt;
&lt;td&gt;RenderIO or Rendi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Cost comparison at scale
&lt;/h2&gt;

&lt;h3&gt;
  
  
  10,000 videos/month, average 50MB each
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RenderIO&lt;/td&gt;
&lt;td&gt;$99&lt;/td&gt;
&lt;td&gt;Business plan, 20K commands (10K well within plan)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendi&lt;/td&gt;
&lt;td&gt;$25+&lt;/td&gt;
&lt;td&gt;Pro plan base, but 10K x 50MB = 500GB processing may need Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;$15-50&lt;/td&gt;
&lt;td&gt;Varies with processing time, plus egress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;td&gt;$30-100&lt;/td&gt;
&lt;td&gt;Varies with processing time, plus egress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bare metal&lt;/td&gt;
&lt;td&gt;$15-48&lt;/td&gt;
&lt;td&gt;Fixed, but limited concurrent capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;RenderIO has the most predictable billing at this scale since you pay per command with no egress fees. Rendi's GB-based pricing is cheaper at the base rate but gets harder to predict with varying file sizes.&lt;/p&gt;

&lt;h3&gt;
  
  
  100,000 videos/month
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RenderIO&lt;/td&gt;
&lt;td&gt;Custom pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rendi&lt;/td&gt;
&lt;td&gt;Custom pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda&lt;/td&gt;
&lt;td&gt;$150-500 (with egress)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Run&lt;/td&gt;
&lt;td&gt;$300-1,000 (with egress)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bare metal cluster&lt;/td&gt;
&lt;td&gt;$200-500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At very high volume, self-hosted becomes more cost-effective but requires significant engineering investment in operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;p&gt;For most teams in 2026:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with RenderIO's Starter plan&lt;/strong&gt; ($9/month, 500 commands) or &lt;strong&gt;Rendi's free tier&lt;/strong&gt; to test the API pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale to Growth&lt;/strong&gt; ($29/month) as volume increases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move to Business&lt;/strong&gt; ($99/month) for high-volume workloads up to 20,000 commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider self-hosted&lt;/strong&gt; only if you need custom codecs, GPU encoding, or process 100K+ videos monthly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Self-hosting requires someone to set up, maintain, and debug the infrastructure. That can mean anywhere from a few hours to 40+ hours of engineering time per month depending on complexity. Whether that tradeoff makes sense depends on your team size and volume.&lt;/p&gt;

&lt;p&gt;For the step-by-step setup, read the &lt;a href="https://renderio.dev/blogs/ffmpeg-rest-api-tutorial" rel="noopener noreferrer"&gt;FFmpeg REST API tutorial&lt;/a&gt;. If you prefer language-specific guides, there are &lt;a href="https://renderio.dev/blogs/ffmpeg-api-python" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and &lt;a href="https://renderio.dev/blogs/ffmpeg-api-nodejs" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; tutorials too. The &lt;a href="https://renderio.dev/blogs/ffmpeg-cheat-sheet" rel="noopener noreferrer"&gt;FFmpeg cheat sheet&lt;/a&gt; has quick-reference commands for the most common operations.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What is the best FFmpeg API for small projects?
&lt;/h3&gt;

&lt;p&gt;For projects processing under 500 videos a month, RenderIO's Starter plan ($9/mo) or Rendi's free tier both work. Rendi gives you a free tier with a 1-minute runtime limit and 50GB processing cap. RenderIO starts at $9/mo but has no runtime or processing size limits beyond the command count.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an FFmpeg API compare to running FFmpeg locally?
&lt;/h3&gt;

&lt;p&gt;An FFmpeg API accepts the same command string you'd run locally, but executes it on cloud infrastructure. You don't install FFmpeg, manage servers, or worry about CPU capacity. The tradeoff is cost (you pay per command or per GB) and latency (network round-trip to upload/download files). For one-off personal use, local FFmpeg is free. For applications, APIs are easier to scale and maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use an FFmpeg API for real-time video processing?
&lt;/h3&gt;

&lt;p&gt;Not for live streaming. FFmpeg APIs are designed for batch processing: you submit a file, it gets processed, you get the result back. Processing typically takes 1-30 seconds depending on the operation and file size. For real-time video manipulation, you'd need a streaming-specific solution or run FFmpeg locally with pipe I/O.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between command-based and GB-based FFmpeg API pricing?
&lt;/h3&gt;

&lt;p&gt;Command-based pricing (like RenderIO) charges per FFmpeg command regardless of file size. A 5MB file and a 5GB file cost the same. GB-based pricing (like Rendi's processing model) charges based on the total data processed (input + output). Command-based is more predictable. GB-based can be cheaper for small files and more expensive for large ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is self-hosting FFmpeg cheaper than using an API?
&lt;/h3&gt;

&lt;p&gt;At low volume (under 1,000 videos/month), a managed API is almost always cheaper when you factor in engineering time for setup, maintenance, monitoring, and debugging. At very high volume (100K+ videos/month), self-hosting on dedicated hardware can be cheaper per-video, but you need someone maintaining the infrastructure. The break-even point depends on your team's hourly rate and the complexity of your FFmpeg operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do FFmpeg APIs support GPU-accelerated encoding?
&lt;/h3&gt;

&lt;p&gt;Most managed FFmpeg APIs (RenderIO, Rendi) run on CPU infrastructure. For GPU-accelerated encoding with NVENC or CUDA, you currently need to self-host on GPU-enabled instances. The &lt;a href="https://renderio.dev/blogs/ffmpeg-cuda-nvenc-gpu-acceleration" rel="noopener noreferrer"&gt;GPU acceleration guide&lt;/a&gt; covers the hardware encoding options and when they're worth the extra infrastructure cost.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>E-Commerce Video Processing API: Product Video Pipeline</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Mon, 06 Apr 2026 11:19:36 +0000</pubDate>
      <link>https://forem.com/renderio/e-commerce-video-processing-api-product-video-pipeline-36ec</link>
      <guid>https://forem.com/renderio/e-commerce-video-processing-api-product-video-pipeline-36ec</guid>
      <description>&lt;h2&gt;
  
  
  E-commerce needs video at scale
&lt;/h2&gt;

&lt;p&gt;Product pages with video convert better than static images. A 2023 Wyzowl survey found that 82% of consumers were convinced to buy a product after watching a video. Shopify merchants who added product video to their listings reported higher conversion rates, particularly in electronics and apparel. TikTok Shop requires video for every listing. Amazon encourages product video. Instagram Shopping is video-first.&lt;/p&gt;

&lt;p&gt;The problem isn't creating one video. It's processing thousands. Every product needs videos resized for each platform, watermarks for different sales channels, compressed versions for fast loading, and multiple variations for A/B testing.&lt;/p&gt;

&lt;p&gt;A video editor can handle 10-20 products per day. An API handles 10,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use a video processing API
&lt;/h2&gt;

&lt;p&gt;Building video processing in-house means installing FFmpeg on your servers, managing CPU-intensive workloads that spike and idle, handling file storage and delivery, building queuing systems for batch operations, and scaling infrastructure as your catalog grows.&lt;/p&gt;

&lt;p&gt;Or you can send an HTTP request and get a processed video back.&lt;/p&gt;

&lt;p&gt;RenderIO runs FFmpeg on Cloudflare's edge network. You send a command, it processes the video, you get a download URL. No servers, no scaling, no infrastructure. For a full walkthrough of the API, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;FFmpeg API complete guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core e-commerce video processing API operations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Resize for platforms
&lt;/h3&gt;

&lt;p&gt;Each sales channel has different requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# TikTok Shop: 9:16, 1080x1920&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:white\" -c:v libx264 -crf 20 -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://cdn.example.com/product-original.mp4" },
    "output_files": { "out_video": "product-tiktok.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Amazon: 16:9, 1920x1080, max 5GB&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:white\" -c:v libx264 -crf 18 -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://cdn.example.com/product-original.mp4" },
    "output_files": { "out_video": "product-amazon.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Instagram Shopping: 1:1, 1080x1080&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"crop=min(iw\\,ih):min(iw\\,ih),scale=1080:1080\" -c:v libx264 -crf 20 -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://cdn.example.com/product-original.mp4" },
    "output_files": { "out_video": "product-instagram.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add watermarks
&lt;/h3&gt;

&lt;p&gt;Protect product videos on different channels:&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://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -i {{in_logo}} -filter_complex \"[1:v]scale=120:-1,format=rgba,colorchannelmixer=aa=0.4[logo];[0:v][logo]overlay=W-w-20:H-h-20[v]\" -map \"[v]\" -map 0:a -c:v libx264 -crf 20 -c:a copy {{out_video}}",
    "input_files": {
      "in_video": "https://cdn.example.com/product.mp4",
      "in_logo": "https://cdn.example.com/brand-logo.png"
    },
    "output_files": { "out_video": "product-watermarked.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;colorchannelmixer=aa=0.4&lt;/code&gt; makes the watermark 40% transparent. Professional without being intrusive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compress for fast loading
&lt;/h3&gt;

&lt;p&gt;Product pages need fast-loading video. Here's how to compress without visible quality loss:&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://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -c:v libx264 -crf 28 -preset slow -vf \"scale=720:-2\" -c:a aac -b:a 96k -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://cdn.example.com/product-hd.mp4" },
    "output_files": { "out_video": "product-web.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a 720p video with aggressive compression. File size typically drops 70-80% while maintaining acceptable quality for product pages. For more compression strategies, check the &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;video compression guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create thumbnails
&lt;/h3&gt;

&lt;p&gt;Extract the best frame for product listing thumbnails:&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://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"select=eq(pict_type\\,I),scale=800:-1\" -frames:v 1 {{out_thumb}}",
    "input_files": { "in_video": "https://cdn.example.com/product.mp4" },
    "output_files": { "out_thumb": "thumbnail.jpg" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extracts the first I-frame (keyframe), which is typically the clearest frame in the video. For more thumbnail strategies including scene detection and quality optimization, see the &lt;a href="https://renderio.dev/blogs/ffmpeg-extract-frames" rel="noopener noreferrer"&gt;FFmpeg frame extraction guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the complete pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One product, all platforms
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processProductVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;logoUrl&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;platforms&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tiktok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -i {{in_logo}} -filter_complex "[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.3[logo];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:white[bg];[bg][logo]overlay=W-w-20:20[v]" -map "[v]" -map 0:a? -c:v libx264 -crf 22 -c:a aac -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:white" -c:v libx264 -crf 18 -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;instagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -i {{in_logo}} -filter_complex "[1:v]scale=80:-1,format=rgba,colorchannelmixer=aa=0.3[logo];[0:v]crop=min(iw&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;,ih):min(iw&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;,ih),scale=1080:1080[bg];[bg][logo]overlay=W-w-15:H-h-15[v]" -map "[v]" -map 0:a? -c:v libx264 -crf 20 -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;web-compressed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -c:v libx264 -crf 28 -preset slow -vf "scale=720:-2" -c:a aac -b:a 96k -movflags +faststart {{out_video}}`&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;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;platforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&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;RENDERIO_API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;in_video&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;in_logo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logoUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;out_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4`&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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;4 API calls per product. 4 platform-ready videos. All processing in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch process catalog
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processCatalog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&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;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&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;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;BATCH_SIZE&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;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;processProductVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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="nx"&gt;p&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logoUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Processed &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&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="nx"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; products`&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;1,000 products × 4 platforms = 4,000 API calls. That fits within the Business plan (20,000 commands/month).&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling processing failures
&lt;/h2&gt;

&lt;p&gt;Video processing can fail for a few reasons, and your pipeline needs to handle each one:&lt;/p&gt;

&lt;h3&gt;
  
  
  Invalid input URL
&lt;/h3&gt;

&lt;p&gt;The source video doesn't exist or requires authentication. Use signed URLs with at least 1 hour of expiry. If you're pulling from Shopify's CDN, those URLs are public. But if you're hosting on S3, make sure the bucket policy or presigned URL allows access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeout on large files
&lt;/h3&gt;

&lt;p&gt;Videos over 500MB take longer. Poll with reasonable intervals (5-10 seconds) and set a maximum retry count. If a command hasn't completed after 5 minutes, check the error status rather than polling forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  FFmpeg command errors
&lt;/h3&gt;

&lt;p&gt;A typo in your filter chain fails the whole command. Test commands locally with a sample file before putting them in your pipeline. The error response includes FFmpeg's stderr output, so read it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pollWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commandId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&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;60&lt;/span&gt;&lt;span class="p"&gt;)&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://renderio.dev/api/v1/commands/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;commandId&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiKey&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;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;data&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;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;data&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="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;`FFmpeg failed: &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="s2"&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="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;5000&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;`Timeout after &lt;/span&gt;&lt;span class="p"&gt;${&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;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s`&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;
  
  
  Webhook-based completion
&lt;/h2&gt;

&lt;p&gt;For production pipelines, polling loops waste resources. Use webhooks 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;"ffmpeg_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;"-i {{in_video}} -vf &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;scale=1080:1920:...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -c:v libx264 -crf 20 {{out_video}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_files"&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;"in_video"&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://cdn.example.com/product.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;"output_files"&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;"out_video"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product-tiktok.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;"webhook_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-server.com/api/video-complete"&lt;/span&gt;&lt;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;When the command finishes, RenderIO sends a POST to your webhook URL with the command status and output file URLs. Your server processes the callback, updates the product listing, and moves on. No polling. No wasted API calls.&lt;/p&gt;

&lt;p&gt;This matters at scale. If you're processing 500 products per day across 4 platforms, that's 2,000 commands. With polling at 5-second intervals and an average processing time of 15 seconds, you'd make 6,000 status checks. With webhooks, you make zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with e-commerce platforms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shopify
&lt;/h3&gt;

&lt;p&gt;Use Shopify's Admin API to upload processed videos back to product listings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After RenderIO processing completes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedVideoUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;completedCommand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product-web.mp4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;shopify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;media&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Product video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mediaContentType&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;originalSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processedVideoUrl&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;For automating this with Zapier (new product triggers video creation automatically), see the &lt;a href="https://renderio.dev/blogs/zapier-automate-product-videos" rel="noopener noreferrer"&gt;Zapier product video automation guide&lt;/a&gt;. The &lt;a href="https://renderio.dev/blogs/n8n-video-processing-guide" rel="noopener noreferrer"&gt;n8n video processing guide&lt;/a&gt; covers the same flow for n8n users.&lt;/p&gt;

&lt;h3&gt;
  
  
  WooCommerce
&lt;/h3&gt;

&lt;p&gt;WooCommerce doesn't have native video fields on products. You'll need either a plugin like "Product Video for WooCommerce" or a custom meta field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;woocommerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;meta_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_video_url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processedVideoUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stores the URL in &lt;code&gt;meta_data&lt;/code&gt;, but your theme won't display it automatically. You'll need a custom template snippet or a video gallery plugin that reads from meta fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  TikTok Shop
&lt;/h3&gt;

&lt;p&gt;TikTok Shop video requirements: 9:16 aspect ratio, 1080x1920 minimum, MP4 format, under 500MB, 15-60 seconds. The resize command above handles the format. Upload via TikTok Shop's Content API or manually through Seller Center.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the full workflow
&lt;/h2&gt;

&lt;p&gt;The real power is connecting video processing to your product catalog so it runs automatically. There are two good approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zapier&lt;/strong&gt;: New product trigger → RenderIO API → upload to storage. The &lt;a href="https://renderio.dev/blogs/zapier-automate-product-videos" rel="noopener noreferrer"&gt;Zapier product video guide&lt;/a&gt; walks through the complete Zap with template overlays and text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;n8n&lt;/strong&gt;: Webhook or schedule trigger → batch process → upload. More flexibility for complex logic. See the &lt;a href="https://renderio.dev/blogs/n8n-video-processing-guide" rel="noopener noreferrer"&gt;n8n video processing guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For batch processing large catalogs at once, the &lt;a href="https://renderio.dev/blogs/batch-process-ai-videos-social-media" rel="noopener noreferrer"&gt;batch process AI videos guide&lt;/a&gt; covers parallelization strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing for e-commerce
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Catalog size&lt;/th&gt;
&lt;th&gt;Platforms&lt;/th&gt;
&lt;th&gt;API calls/month&lt;/th&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Cost/month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;30 products&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;Starter&lt;/td&gt;
&lt;td&gt;$9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;250 products&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;Growth&lt;/td&gt;
&lt;td&gt;$29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,000 products&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;$99&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero egress fees because RenderIO runs on Cloudflare R2. No hidden storage costs. No bandwidth charges.&lt;/p&gt;

&lt;p&gt;The math is straightforward: count your products, multiply by the number of platform versions you need per product, and pick the plan that fits. Most small-to-medium stores land on Growth. Large catalogs or stores that reprocess on price/image changes need Business.&lt;/p&gt;

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

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

&lt;p&gt;Typical product videos (30-60 seconds, 1080p source) process in 5-15 seconds per operation. Resizing is faster than complex filter chains. A four-platform pipeline for one product finishes in about 15-20 seconds total since all four commands run in parallel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I process videos when a new product is added?
&lt;/h3&gt;

&lt;p&gt;Yes. Set up a Shopify webhook (or use Zapier/n8n) to trigger video processing whenever a product is created or updated. The &lt;a href="https://renderio.dev/blogs/zapier-automate-product-videos" rel="noopener noreferrer"&gt;Zapier product video guide&lt;/a&gt; has the complete setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  What video formats does TikTok Shop accept?
&lt;/h3&gt;

&lt;p&gt;MP4 is the safe choice. TikTok Shop requires 9:16 aspect ratio, minimum 720x1280 resolution (1080x1920 recommended), H.264 codec, under 500MB, and between 15-60 seconds duration.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I handle processing failures?
&lt;/h3&gt;

&lt;p&gt;Check the command status endpoint. Failed commands return a &lt;code&gt;status: "failed"&lt;/code&gt; with an &lt;code&gt;error&lt;/code&gt; field containing FFmpeg's stderr output. Common fixes: verify your input URL is accessible, check your FFmpeg command syntax, ensure the output format matches the filename extension.&lt;/p&gt;

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

&lt;p&gt;No hard limit on the API side. Input files are fetched via URL, so the bottleneck is download speed. Videos over 1GB work fine but take longer to fetch and process. For very large files (2GB+), expect processing times of 1-3 minutes.&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Build an AI UGC Video Processing Pipeline</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Mon, 06 Apr 2026 11:18:50 +0000</pubDate>
      <link>https://forem.com/renderio/build-an-ai-ugc-video-processing-pipeline-17kl</link>
      <guid>https://forem.com/renderio/build-an-ai-ugc-video-processing-pipeline-17kl</guid>
      <description>&lt;h2&gt;
  
  
  The real bottleneck in AI UGC video production
&lt;/h2&gt;

&lt;p&gt;AI-generated UGC for ads and social media has moved past the "can we do this" phase. Tools like HeyGen, Synthesia, and D-ID produce convincing avatar videos. The generation part works. Everything after generation is where teams get stuck.&lt;/p&gt;

&lt;p&gt;You generate a video. Then you need to post-process it so it doesn't scream "AI." Then you need variations for A/B testing across ad sets. Then each variation needs reformatting for different platforms. One base video can turn into 50-100 output files. Without a pipeline, each one is manual work in Premiere or CapCut.&lt;/p&gt;

&lt;p&gt;This guide walks through building that pipeline with FFmpeg and the &lt;a href="https://renderio.dev/blogs/ffmpeg-api-complete-guide" rel="noopener noreferrer"&gt;RenderIO API&lt;/a&gt;, from raw AI output to platform-ready content.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the AI UGC video processing pipeline works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Generation → Post-Processing → Variation → Platform Formatting → Distribution
  (HeyGen)       (RenderIO)     (RenderIO)     (RenderIO)         (n8n/Zapier)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage is a separate API call. Each call runs independently. The entire pipeline from generation to distribution takes under 10 minutes for 50+ output files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1: choose your AI generation tool
&lt;/h2&gt;

&lt;p&gt;Pick the tool that matches what you're building:&lt;/p&gt;

&lt;p&gt;HeyGen works best for talking-head UGC with custom avatars. If you're creating product demos or testimonial-style content, this is probably where you start. Their avatar quality has gotten noticeably better since late 2025. See our guide on &lt;a href="https://renderio.dev/blogs/heygen-video-to-instagram-reels" rel="noopener noreferrer"&gt;converting HeyGen output to Instagram Reels&lt;/a&gt; for the full post-processing workflow.&lt;/p&gt;

&lt;p&gt;Synthesia is more corporate. Training videos, internal comms, that sort of thing. The avatars feel professional but not "social media native."&lt;/p&gt;

&lt;p&gt;D-ID turns a single photo into a talking video. Useful when you don't have studio footage. Less realistic than HeyGen but faster to set up.&lt;/p&gt;

&lt;p&gt;Runway combined with a voice-over tool works for creative or lifestyle UGC where you want more visual flexibility than a talking head.&lt;/p&gt;

&lt;p&gt;Output from any of these: one raw MP4 file, typically 16:9, 30-60 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2: post-processing raw AI video
&lt;/h2&gt;

&lt;p&gt;Raw AI video has tells. Metadata flags it as AI-generated. Audio levels are inconsistent. The video looks "too clean" compared to native social content. Post-processing fixes all of that in one API call per base video.&lt;/p&gt;

&lt;p&gt;Here's why each step matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;map_metadata -1&lt;/code&gt; strips generation metadata that platforms can detect&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nlmeans&lt;/code&gt; + &lt;code&gt;noise&lt;/code&gt; adds natural film grain (AI video is unnaturally clean)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eq&lt;/code&gt; shifts color just enough to break perceptual fingerprints&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loudnorm&lt;/code&gt; normalizes audio to -14 LUFS (what TikTok and Reels expect)
&lt;/li&gt;
&lt;/ul&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://renderio.dev/api/v1/run-ffmpeg-command &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: 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;'{
    "ffmpeg_command": "-i {{in_video}} -map_metadata -1 -vf \"nlmeans=s=6:p=3:r=9,noise=alls=12:allf=t,eq=brightness=0.01:contrast=1.03:saturation=0.97\" -af \"loudnorm=I=-14:TP=-2:LRA=7\" -c:v libx264 -crf 18 -c:a aac -b:a 128k {{out_video}}",
    "input_files": { "in_video": "https://example.com/heygen-raw.mp4" },
    "output_files": { "out_video": "base-processed.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a clean base for variation creation. For more on &lt;a href="https://renderio.dev/blogs/make-ai-video-look-natural" rel="noopener noreferrer"&gt;making AI video look natural&lt;/a&gt;, we have a dedicated guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting post-processing
&lt;/h3&gt;

&lt;p&gt;A few things that trip people up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grain looks blocky on short videos (under 15 seconds).&lt;/strong&gt; Lower the noise value from &lt;code&gt;alls=12&lt;/code&gt; to &lt;code&gt;alls=6&lt;/code&gt;. Short clips get compressed harder by platforms, and heavy grain turns into blocky artifacts after re-encoding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio sounds distorted after loudnorm.&lt;/strong&gt; This usually happens when the source audio is already very loud (above -8 LUFS). Add a limiter before loudnorm: &lt;code&gt;-af "alimiter=limit=0.9,loudnorm=I=-14:TP=-2:LRA=7"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HeyGen output has variable frame rate.&lt;/strong&gt; Force constant frame rate early in the chain by adding &lt;code&gt;-r 30&lt;/code&gt; before the output filename. Variable frame rate causes sync issues in some platform players.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3: creating AI UGC video variations
&lt;/h2&gt;

&lt;p&gt;One base video becomes 10-20 unique variations. Each variation uses different FFmpeg parameters so every output has a distinct fingerprint. This matters for ad testing (different creatives per ad set) and for posting across accounts without duplicate detection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Color grade variations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colorVariations&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colortemperature=temperature=6500&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;colortemperature=temperature=4500&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vivid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eq=saturation=1.3:contrast=1.1&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;muted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eq=saturation=0.7:contrast=0.95&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vintage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eq=saturation=0.8:contrast=1.1,colorbalance=rs=0.05:gs=-0.02:bs=-0.05&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;h3&gt;
  
  
  Speed variations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speedVariations&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;normal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setpts=1.0*PTS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;afilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;atempo=1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setpts=0.9*PTS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;afilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;atempo=1.11&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setpts=1.1*PTS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;afilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;atempo=0.91&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;h3&gt;
  
  
  Crop variations
&lt;/h3&gt;

&lt;p&gt;Different crop positions change the video's perceptual hash, which helps if you're posting variations across multiple accounts. See our guide on &lt;a href="https://renderio.dev/blogs/batch-process-ai-videos-social-media" rel="noopener noreferrer"&gt;batch processing AI videos for social media&lt;/a&gt; for platform-specific crop strategies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cropVariations&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop=ih*9/16:ih:(iw-ih*9/16)/2:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop=ih*9/16:ih:iw*0.1:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop=ih*9/16:ih:iw*0.5:0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crop=iw*0.6:ih*0.6:iw*0.2:ih*0.1,scale=1080:1920&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;h3&gt;
  
  
  Combined variation generator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createVariations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseVideoUrl&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;variations&lt;/span&gt; &lt;span class="o"&gt;=&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;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;colorVariations&lt;/span&gt;&lt;span class="p"&gt;)&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;const&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;speedVariations&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;vf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&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;af&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;afilter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;variations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -vf "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;vf&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" -af "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;af&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" -c:v libx264 -crf 22 -c:a aac -b:a 128k {{out_video}}`&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="c1"&gt;// 5 colors x 3 speeds = 15 variations&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&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://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&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;RENDERIO_API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;in_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;baseVideoUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;out_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4`&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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;15 variations, all processing in parallel. Total time: same as processing one video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4: platform formatting for AI UGC videos
&lt;/h2&gt;

&lt;p&gt;Each variation needs platform-specific formatting. This multiplies your output count. For the full breakdown of specs per platform, see &lt;a href="https://renderio.dev/blogs/batch-process-ai-videos-social-media" rel="noopener noreferrer"&gt;batch processing AI videos for social media&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;platformConfigs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tiktok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[v]" -map "[v]" -map 0:a -c:v libx264 -crf 22 -c:a aac -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;reels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -t 90 -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[v]" -map "[v]" -map 0:a -c:v libx264 -crf 22 -c:a aac -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;shorts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -t 60 -filter_complex "[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920[v]" -map "[v]" -map 0:a -c:v libx264 -crf 20 -c:a aac -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;linkedin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`-i {{in_video}} -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" -af "loudnorm=I=-16" -c:v libx264 -crf 20 -c:a aac -movflags +faststart {{out_video}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatForPlatforms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variationUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variationName&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;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platformConfigs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&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;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://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-KEY&lt;/span&gt;&lt;span class="dl"&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;RENDERIO_API_KEY&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;ffmpeg_command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;input_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;in_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;variationUrl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;output_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;out_video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;variationName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.mp4`&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobs&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;15 variations x 4 platforms = 60 platform-ready videos. All from one AI generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 5: distribution with n8n
&lt;/h2&gt;

&lt;p&gt;Wire it all together with n8n (or Zapier). Check the &lt;a href="https://renderio.dev/blogs/n8n-video-processing-guide" rel="noopener noreferrer"&gt;n8n video processing guide&lt;/a&gt; for setup details.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Webhook trigger receives HeyGen export URL&lt;/li&gt;
&lt;li&gt;HTTP Request sends POST to RenderIO for post-processing&lt;/li&gt;
&lt;li&gt;Wait/Poll checks command status until complete&lt;/li&gt;
&lt;li&gt;Loop iterates each variation config, sends POST to RenderIO&lt;/li&gt;
&lt;li&gt;Wait/Poll checks all variation commands&lt;/li&gt;
&lt;li&gt;Loop iterates each platform, sends POST to RenderIO&lt;/li&gt;
&lt;li&gt;Wait/Poll checks all platform commands&lt;/li&gt;
&lt;li&gt;Upload sends results to respective platform APIs or scheduling tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire pipeline runs automatically. You input one HeyGen URL and get 60 platform-ready videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost analysis
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;API calls per base video&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Post-processing&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;One-time cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variations&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;5 colors x 3 speeds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform formatting&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;15 variations x 4 platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;76&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per base video&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On RenderIO's Growth plan at $29/month (1,000 commands), you can process about 13 base videos per month through the full pipeline. For higher volumes, the Business plan at $99/month (20,000 commands) handles 263 base videos per month.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost per output video (Business): ~$0.005&lt;/li&gt;
&lt;li&gt;Cost per base video on Business (76 outputs): ~$0.38&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's how it compares:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Cost per base video&lt;/th&gt;
&lt;th&gt;Your time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual processing in Premiere&lt;/td&gt;
&lt;td&gt;$100-150 (at $50/hr)&lt;/td&gt;
&lt;td&gt;2-3 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe Premiere batch export&lt;/td&gt;
&lt;td&gt;~$25 of time&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RenderIO pipeline (Business)&lt;/td&gt;
&lt;td&gt;$0.38&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Start with a simpler pipeline and expand:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Week 1&lt;/strong&gt;: Post-processing only (1 API call per video)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 2&lt;/strong&gt;: Add 3 color variations (4 API calls per video)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 3&lt;/strong&gt;: Add platform formatting (16 API calls per video)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Week 4&lt;/strong&gt;: Add speed variations and full automation (76 API calls per video)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Starter plan ($9/month, 500 commands) covers week 1-2 for most teams. Scale to Growth or Business as your volume increases. You can also &lt;a href="https://renderio.dev/blogs/ffmpeg-compress-video" rel="noopener noreferrer"&gt;compress video with FFmpeg&lt;/a&gt; to reduce storage costs before uploading.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  How long does the full pipeline take to process one base video?
&lt;/h3&gt;

&lt;p&gt;Under 10 minutes for all 76 API calls. RenderIO processes commands in parallel on Cloudflare's edge network, so 15 variation calls finish in roughly the same time as one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this pipeline with AI video tools other than HeyGen?
&lt;/h3&gt;

&lt;p&gt;Yes. The pipeline is tool-agnostic after stage 1. Any MP4 output works, whether it comes from HeyGen, Synthesia, D-ID, Runway, or even screen recordings. The post-processing and variation stages don't care how the video was generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens if an API call fails mid-pipeline?
&lt;/h3&gt;

&lt;p&gt;Each command returns a status you can poll. Failed commands return an error with details. In an n8n workflow, add an error branch that retries failed calls up to 3 times with a 10-second delay between attempts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need all 15 variations, or can I start with fewer?
&lt;/h3&gt;

&lt;p&gt;Start with 3 color variations and skip speed variations. That gives you 12 platform-ready files (3 variations x 4 platforms) from 4 API calls. Add speed variations once you're comfortable with the workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which RenderIO plan fits a UGC pipeline?
&lt;/h3&gt;

&lt;p&gt;Depends on your volume. For 1-5 base videos per month, the Starter plan ($9/month, 500 commands) is enough. For 10-13 base videos, Growth ($29/month, 1,000 commands). For 50+ base videos, Business ($99/month, 20,000 commands).&lt;/p&gt;

</description>
      <category>ffmpeg</category>
      <category>video</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Make AI-Generated Video Undetectable on TikTok</title>
      <dc:creator>RenderIO</dc:creator>
      <pubDate>Mon, 06 Apr 2026 11:16:17 +0000</pubDate>
      <link>https://forem.com/renderio/make-ai-generated-video-undetectable-on-tiktok-2cg3</link>
      <guid>https://forem.com/renderio/make-ai-generated-video-undetectable-on-tiktok-2cg3</guid>
      <description>&lt;h2&gt;
  
  
  How to make AI video undetectable on TikTok
&lt;/h2&gt;

&lt;p&gt;You generated a video with Runway, Kling, Pika, or Sora. It looks great. You upload it to TikTok. It gets suppressed or flagged.&lt;/p&gt;

&lt;p&gt;The problem is twofold: metadata fingerprints from the generation tool, and visual patterns that detection algorithms catch. Both are fixable with FFmpeg. This guide walks through each fix step by step, so you can make AI-generated video undetectable before uploading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes AI video detectable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metadata fingerprints
&lt;/h3&gt;

&lt;p&gt;Every AI video tool embeds metadata in the output file. The encoder field contains the tool's name or rendering engine. Creation timestamps are typically UTC and batch-generated (a giveaway when you upload seconds after "recording"). Some tools add proprietary tags and custom metadata fields. C2PA Content Credentials — increasingly common in 2025-2026 — explicitly declare AI origin. And the EXIF data (resolution, color space, technical settings) often matches the tool's default output exactly, which is another signal.&lt;/p&gt;

&lt;p&gt;For a full walkthrough on &lt;a href="https://renderio.dev/blogs/strip-video-metadata-ffmpeg" rel="noopener noreferrer"&gt;stripping video metadata with FFmpeg&lt;/a&gt;, that guide covers every metadata field and how to remove them.&lt;/p&gt;

&lt;p&gt;You can see this with ffprobe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffprobe &lt;span class="nt"&gt;-v&lt;/span&gt; quiet &lt;span class="nt"&gt;-print_format&lt;/span&gt; json &lt;span class="nt"&gt;-show_format&lt;/span&gt; input.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for fields like &lt;code&gt;encoder&lt;/code&gt;, &lt;code&gt;comment&lt;/code&gt;, &lt;code&gt;creation_time&lt;/code&gt;, and any tool-specific tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual patterns
&lt;/h3&gt;

&lt;p&gt;AI video has characteristic patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent frame timing&lt;/strong&gt;: AI renders at exact intervals. Natural video has micro-variations in frame timing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uniform noise patterns&lt;/strong&gt;: AI-generated frames lack the random sensor noise present in camera footage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal consistency&lt;/strong&gt;: AI maintains unnaturally smooth motion in areas that real cameras would show compression artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color space&lt;/strong&gt;: Many AI tools output in a specific color space (often BT.709 with particular gamma curves).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Strip all metadata
&lt;/h2&gt;

&lt;p&gt;Remove every metadata field:&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; ai_video.mp4 &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-fflags&lt;/span&gt; +bitexact &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 &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;&lt;code&gt;-map_metadata -1&lt;/code&gt; removes all metadata containers. &lt;code&gt;-fflags +bitexact&lt;/code&gt; prevents FFmpeg from writing its own metadata.&lt;/p&gt;

&lt;p&gt;API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &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;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: your_api_key"&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;'{
    "ffmpeg_command": "-i {{in_video}} -map_metadata -1 -fflags +bitexact -c:v libx264 -crf 22 -c:a aac {{out_video}}",
    "input_files": { "in_video": "https://example.com/ai-video.mp4" },
    "output_files": { "out_video": "clean.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add natural sensor noise
&lt;/h2&gt;

&lt;p&gt;Real cameras produce random noise from the image sensor. AI video is too clean. Add subtle noise:&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; ai_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"noise=alls=8:allf=t"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;alls=8&lt;/code&gt; adds noise at strength 8 across all color planes. &lt;code&gt;allf=t&lt;/code&gt; makes it temporal (varies per frame), mimicking real sensor behavior.&lt;/p&gt;

&lt;p&gt;For a more natural look, add gaussian noise instead of uniform:&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; ai_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"noise=alls=6:allf=t+u"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Introduce frame timing variation
&lt;/h2&gt;

&lt;p&gt;AI video has perfectly consistent frame timing. Real video from phones has slight jitter. Add micro-variations:&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; ai_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"setpts=PTS+random(0)*0.001"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds up to 1ms of random timing variation per frame. Invisible during playback but breaks the perfect timing pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Re-encode to match phone camera output
&lt;/h2&gt;

&lt;p&gt;TikTok expects video from phones. Match the encoding characteristics:&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; ai_video.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-profile&lt;/span&gt;:v high &lt;span class="nt"&gt;-level&lt;/span&gt;:v 4.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches the H.264 High Profile Level 4.0 output that modern phones produce. &lt;code&gt;yuv420p&lt;/code&gt; is the standard pixel format. &lt;code&gt;movflags +faststart&lt;/code&gt; is how phone cameras write MP4 files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Crop to remove AI artifacts
&lt;/h2&gt;

&lt;p&gt;AI videos often have subtle artifacts at frame edges (blurring, warping, or inconsistent generation). Crop a few pixels:&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; ai_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=iw-8:ih-8:4:4"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes 4 pixels from each edge. Eliminates edge artifacts and changes the perceptual hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Adjust color space
&lt;/h2&gt;

&lt;p&gt;Runway and Sora output in BT.709 with a specific gamma curve (usually 2.2 or sRGB transfer). Kling defaults to BT.709 but with flatter gamma that gives a slightly washed-out look. Pika's output varies by model version. The point is: each tool has a default color profile that detection systems can fingerprint. Shift it:&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; ai_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"eq=brightness=0.02:contrast=1.02:saturation=1.03:gamma=1.01"&lt;/span&gt; output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Slight brightness, contrast, saturation, and gamma adjustments. These shift the color profile away from the AI tool's default output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The complete naturalizer command
&lt;/h2&gt;

&lt;p&gt;Combine all steps into one FFmpeg command:&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; ai_video.mp4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=iw-6:ih-6:3:3,noise=alls=6:allf=t,eq=brightness=0.015:saturation=1.02,hue=h=1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"asetrate=44100*1.003,aresample=44100"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-profile&lt;/span&gt;:v high &lt;span class="nt"&gt;-level&lt;/span&gt;:v 4.0 &lt;span class="nt"&gt;-crf&lt;/span&gt; 23 &lt;span class="nt"&gt;-preset&lt;/span&gt; medium &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac &lt;span class="nt"&gt;-b&lt;/span&gt;:a 128k &lt;span class="nt"&gt;-ar&lt;/span&gt; 44100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map_metadata&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;-fflags&lt;/span&gt; +bitexact &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-movflags&lt;/span&gt; +faststart &lt;span class="se"&gt;\&lt;/span&gt;
  output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crops edges (removes AI artifacts, changes pHash)&lt;/li&gt;
&lt;li&gt;Adds sensor noise (naturalizes the image)&lt;/li&gt;
&lt;li&gt;Shifts brightness and color (moves away from AI defaults)&lt;/li&gt;
&lt;li&gt;Shifts audio pitch slightly (alters audio fingerprint)&lt;/li&gt;
&lt;li&gt;Encodes to phone-camera-like specs&lt;/li&gt;
&lt;li&gt;Strips all metadata&lt;/li&gt;
&lt;li&gt;Optimizes for mobile playback&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://renderio.dev/api/v1/run-ffmpeg-command &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;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-KEY: your_api_key"&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;'{
    "ffmpeg_command": "-i {{in_video}} -vf \"crop=iw-6:ih-6:3:3,noise=alls=6:allf=t,eq=brightness=0.015:saturation=1.02,hue=h=1\" -af \"asetrate=44100*1.003,aresample=44100\" -c:v libx264 -profile:v high -level:v 4.0 -crf 23 -preset medium -pix_fmt yuv420p -c:a aac -b:a 128k -ar 44100 -map_metadata -1 -fflags +bitexact -movflags +faststart {{out_video}}",
    "input_files": { "in_video": "https://example.com/ai-video.mp4" },
    "output_files": { "out_video": "naturalized.mp4" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tool-specific considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Runway Gen-3/Gen-4
&lt;/h3&gt;

&lt;p&gt;Runway writes multiple custom metadata fields: &lt;code&gt;encoder&lt;/code&gt;, &lt;code&gt;handler_name&lt;/code&gt;, and sometimes a &lt;code&gt;comment&lt;/code&gt; field with generation parameters. The &lt;code&gt;-map_metadata -1 -fflags +bitexact&lt;/code&gt; flags strip all of these.&lt;/p&gt;

&lt;p&gt;Runway's color profile tends toward high saturation with punchy contrast. The naturalizer command's brightness and saturation shifts already handle this, but if your video still looks "too clean," add a slight gamma adjustment: &lt;code&gt;gamma=0.98&lt;/code&gt; in the eq filter.&lt;/p&gt;

&lt;p&gt;Runway Gen-4 outputs at exactly 24fps with zero frame timing variation. Real phone cameras shoot at 29.97 or 30fps with slight jitter. Re-encode at 30fps with the timing variation from Step 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kling AI
&lt;/h3&gt;

&lt;p&gt;Kling has a known issue with temporal inconsistencies at scene transitions — frames sometimes stutter or repeat. The noise filter masks these, but also check for it visually before uploading. A single repeated frame is a dead giveaway to human reviewers.&lt;/p&gt;

&lt;p&gt;Kling may embed watermarks depending on your subscription tier. Check the bottom-right corner of the frame. If present, crop by 20-30 pixels from the bottom edge:&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; kling_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=iw:ih-30:0:0"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kling's audio tracks are often silent or contain synthesized ambient noise at suspiciously consistent levels. If your video has audio, verify it sounds natural or replace it entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sora
&lt;/h3&gt;

&lt;p&gt;Sora produces some of the smoothest AI video on the market, which is actually a problem. Real video has micro-jitter, slight focus shifts, and compression artifacts from the camera sensor. Sora has none of that.&lt;/p&gt;

&lt;p&gt;Beyond the noise and timing variation from the naturalizer command, consider adding a slight speed fluctuation. Slow the video by 2% and it introduces natural-feeling drag:&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; sora_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"setpts=PTS*1.02,noise=alls=7:allf=t"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sora also outputs with specific C2PA Content Credentials that explicitly declare AI generation. The metadata strip handles this, but double-check with ffprobe after processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pika Labs
&lt;/h3&gt;

&lt;p&gt;Pika's free tier adds a visible watermark in the lower-right corner. Crop it or cover it with your own overlay before running the naturalizer:&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; pika_video.mp4 &lt;span class="nt"&gt;-vf&lt;/span&gt; &lt;span class="s2"&gt;"crop=iw-40:ih-40:0:0"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 22 output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pika's output resolution varies by model version (sometimes 576p, sometimes 720p, sometimes 1080p). If you're uploading to TikTok, resize to 1080x1920 after naturalizing. A non-standard resolution is a subtle signal.&lt;/p&gt;

&lt;p&gt;For more on cleaning up AI artifacts specifically, see &lt;a href="https://renderio.dev/blogs/remove-ai-artifacts-from-video" rel="noopener noreferrer"&gt;remove AI artifacts from video&lt;/a&gt; and &lt;a href="https://renderio.dev/blogs/make-ai-video-look-natural" rel="noopener noreferrer"&gt;make AI video look natural&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch processing AI videos
&lt;/h2&gt;

&lt;p&gt;For content operations generating many AI videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffsk_your_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/ai-video-1.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/ai-video-2.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/ai-video-3.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;brightness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.005&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://renderio.dev/api/v1/run-ffmpeg-command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ffmpeg_command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-i {{in_video}} -vf &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crop=iw-6:ih-6:3:3,noise=alls=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noise&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:allf=t,eq=brightness=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;brightness&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; -c:v libx264 -crf 23 -map_metadata -1 -fflags +bitexact {{out_video}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output_files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out_video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;natural_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.mp4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Video &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;command_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;After processing, check four things:&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;ffprobe -v quiet -print_format json -show_format output.mp4&lt;/code&gt; and confirm no tool-specific metadata fields remain. Look for &lt;code&gt;encoder&lt;/code&gt;, &lt;code&gt;comment&lt;/code&gt;, &lt;code&gt;creation_time&lt;/code&gt;, and any custom tags. If any are present, your metadata strip didn't work.&lt;/p&gt;

&lt;p&gt;View the video at 200% zoom. You should see subtle grain from the noise filter. If the image is perfectly clean, the noise wasn't applied.&lt;/p&gt;

&lt;p&gt;Check file size. A 30-second 1080p video from a phone is typically 30-80MB. If your output is 5MB or 200MB, something's off with the encoding settings.&lt;/p&gt;

&lt;p&gt;Play the audio back. A 0.3-0.5% pitch shift is inaudible. Above 1%, you'll hear it. If the audio sounds slightly chipmunked, dial back the pitch multiplier.&lt;/p&gt;

&lt;p&gt;If you're posting the same AI video to multiple accounts, you'll also need to &lt;a href="https://renderio.dev/blogs/avoid-tiktok-duplicate-detection-at-scale" rel="noopener noreferrer"&gt;avoid TikTok duplicate detection at scale&lt;/a&gt; by generating unique variations. For deeper metadata removal, the &lt;a href="https://renderio.dev/blogs/remove-ai-metadata-from-video" rel="noopener noreferrer"&gt;remove AI metadata from video&lt;/a&gt; guide covers edge cases that the basic strip misses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;The Starter plan at $9/mo includes 500 commands, enough to process 10-15 AI-generated clips per day. Explore the &lt;a href="https://dev.to/ffmpeg-api"&gt;FFmpeg video API&lt;/a&gt; or &lt;a href="https://dev.to/get-api-key"&gt;get your API key&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Does TikTok actually detect AI-generated video?
&lt;/h3&gt;

&lt;p&gt;Yes, and it's getting better at it. TikTok uses a combination of metadata analysis, perceptual fingerprinting, and (increasingly) visual pattern detection. C2PA Content Credentials are the most obvious signal. Tools like Runway and Sora now embed these by default. Metadata stripping handles C2PA. The visual patterns are harder to detect algorithmically, but TikTok is investing in it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will these techniques work on Instagram and YouTube too?
&lt;/h3&gt;

&lt;p&gt;The same principles apply. Instagram uses similar fingerprinting for Reels. YouTube has its own content detection system (Content ID) but it's focused on copyright, not AI detection, at least for now. The metadata strip and noise addition work across all platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it legal to remove AI metadata from videos?
&lt;/h3&gt;

&lt;p&gt;Removing metadata is legal in most jurisdictions. However, some regions are implementing AI disclosure requirements (the EU AI Act, for example). Removing C2PA markers to avoid disclosure could have legal implications depending on how you use the video. This guide covers the technical steps; consult local regulations for compliance.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much noise should I add without making the video look bad?
&lt;/h3&gt;

&lt;p&gt;Noise strength 5-8 is the range. Below 5, the noise is too subtle to fool detection. Above 10, it's visible on mobile screens. For high-quality AI video (Sora, Runway Gen-4), start at 6. For lower-quality sources (Pika free tier, older Kling models), start at 4. They already have enough imperfections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to process audio separately?
&lt;/h3&gt;

&lt;p&gt;Not usually. The naturalizer command shifts audio pitch as part of the combined pipeline. If your AI video has no audio (many AI tools generate silent video), add a natural ambient track or keep it silent. TikTok doesn't flag silent videos specifically. If you're adding music, that replaces the audio fingerprint entirely.&lt;/p&gt;

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