<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gary Dai</title>
    <description>The latest articles on DEV Community by Gary Dai (@garydai).</description>
    <link>https://dev.to/garydai</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4129210%2F8f021194-2579-480e-9e4f-b3c303462646.jpg</url>
      <title>DEV Community: Gary Dai</title>
      <link>https://dev.to/garydai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/garydai"/>
    <language>en</language>
    <item>
      <title>Compress videos in n8n with FFHub and cloud FFmpeg</title>
      <dc:creator>Gary Dai</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:38:48 +0000</pubDate>
      <link>https://dev.to/garydai/compress-videos-in-n8n-with-ffhub-and-cloud-ffmpeg-4ack</link>
      <guid>https://dev.to/garydai/compress-videos-in-n8n-with-ffhub-and-cloud-ffmpeg-4ack</guid>
      <description>&lt;p&gt;Video compression often starts with a single FFmpeg command. In an automation workflow, you also need to submit the job, wait for it, handle failures, and pass the result to the next step.&lt;/p&gt;

&lt;p&gt;This walkthrough uses the FFHub community node to do that in n8n. FFmpeg runs on FFHub's infrastructure, so you don't need to install it on your n8n server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I'm the developer of &lt;a href="https://ffhub.io" rel="noopener noreferrer"&gt;FFHub&lt;/a&gt;. It is a hosted service that requires an account and API key. The &lt;a href="https://github.com/ffhub-io/n8n-nodes-ffhub" rel="noopener noreferrer"&gt;n8n node source code&lt;/a&gt; is available under the MIT license; using the hosted service is separate from the node's license.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'll build
&lt;/h2&gt;

&lt;p&gt;Create this sequence of nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Manual Trigger → FFHub (Create Task) → FFHub (Wait for Completion) → IF
                                                                    ├─ true: use output URL
                                                                    └─ false: inspect error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first FFHub node submits the command and returns a task ID. The second polls that task until it succeeds or fails. The IF node decides what to do with the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install the node and add credentials
&lt;/h2&gt;

&lt;p&gt;On a self-hosted n8n instance with community nodes enabled:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings → Community nodes → Install&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;@ffhub/n8n-nodes-ffhub&lt;/code&gt;. This walkthrough uses version &lt;strong&gt;0.2.1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In your &lt;a href="https://ffhub.io/dashboard/api-keys" rel="noopener noreferrer"&gt;FFHub dashboard&lt;/a&gt;, create an API key.&lt;/li&gt;
&lt;li&gt;In n8n, create &lt;strong&gt;FFHub API&lt;/strong&gt; credentials with that key. Keep the default API Base URL: &lt;code&gt;https://api.ffhub.io/v1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select those credentials in both FFHub nodes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You'll also need a direct HTTPS URL to a video you can use. It must be accessible to the remote processing service. A local file path, a YouTube watch page, or a storage sharing page is not a direct media URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;n8n Cloud:&lt;/strong&gt; npm publication alone does not make a community node available in the Cloud node picker. If this package is unavailable on your instance, the alternative is an integration through n8n's built-in HTTP Request node and the FFHub API. The steps below use the community node.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Submit a compression command
&lt;/h2&gt;

&lt;p&gt;Add a &lt;strong&gt;Manual Trigger&lt;/strong&gt;, then an &lt;strong&gt;FFHub&lt;/strong&gt; node with operation &lt;strong&gt;Create Task&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Paste this into &lt;strong&gt;FFmpeg Command&lt;/strong&gt;, replacing the placeholder input URL with your own direct video URL:&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; https://example.com/input.mp4 &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-crf&lt;/span&gt; 28 &lt;span class="nt"&gt;-preset&lt;/span&gt; fast &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;-movflags&lt;/span&gt; +faststart output.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command encodes video as H.264 and audio as AAC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-crf 28&lt;/code&gt; selects a quality level. Lower values generally preserve more detail and produce larger files; higher values trade quality for smaller files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-preset fast&lt;/code&gt; controls the encoding speed/compression tradeoff.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-b:a 128k&lt;/code&gt; sets the audio bitrate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-movflags +faststart&lt;/code&gt; moves MP4 metadata to the beginning of the file to help playback begin before the entire file downloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a starting point, not a guaranteed size reduction. An already heavily compressed input might not become smaller. Compare the output's size and visual quality before choosing settings for your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Wait using the returned task ID
&lt;/h2&gt;

&lt;p&gt;Connect a second &lt;strong&gt;FFHub&lt;/strong&gt; node and choose &lt;strong&gt;Wait for Completion&lt;/strong&gt;. Set &lt;strong&gt;Task ID&lt;/strong&gt; to this n8n expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $json.task_id }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version 0.2.1 defaults to a &lt;strong&gt;5-second polling interval&lt;/strong&gt; and a &lt;strong&gt;600-second timeout&lt;/strong&gt;. Adjust the timeout to suit your files.&lt;/p&gt;

&lt;p&gt;A timeout while waiting does not by itself establish that the remote job failed. Keep the original task ID so you can check it later with &lt;strong&gt;Get Task&lt;/strong&gt; instead of blindly submitting another conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check success before using the output
&lt;/h2&gt;

&lt;p&gt;Add an &lt;strong&gt;IF&lt;/strong&gt; node after Wait for Completion. Configure a string condition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Value: &lt;code&gt;{{ $json.status }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Operation: &lt;strong&gt;is equal to&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Compare with: &lt;code&gt;succeeded&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the true branch, access the first output file URL with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $json.outputs[0].url }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass it to a downstream step that accepts a URL. To download the actual bytes in n8n, use an &lt;strong&gt;HTTP Request&lt;/strong&gt; node with method &lt;strong&gt;GET&lt;/strong&gt;, this expression as the URL, and response format &lt;strong&gt;File&lt;/strong&gt;. You can then connect a storage upload node appropriate to your destination.&lt;/p&gt;

&lt;p&gt;On the false branch, inspect the task's &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; fields. &lt;strong&gt;Wait for Completion returns on failure too&lt;/strong&gt;: execution reaching the next node is not evidence of a successful conversion. HTTP errors and polling timeouts also need n8n's execution error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another example: extract an MP3
&lt;/h2&gt;

&lt;p&gt;Keep the same task-submission and polling sequence, but replace the 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; https://example.com/input.mp4 &lt;span class="nt"&gt;-vn&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:a libmp3lame &lt;span class="nt"&gt;-q&lt;/span&gt;:a 2 output.mp3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;-vn&lt;/code&gt; disables video output, and &lt;code&gt;-q:a 2&lt;/code&gt; selects variable-bitrate MP3 audio quality. This example requires an input with an audio track.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before automating a batch
&lt;/h2&gt;

&lt;p&gt;Start with one short video. Confirm that FFHub can fetch the input, the task reaches &lt;code&gt;succeeded&lt;/code&gt;, the output URL downloads correctly, and the result meets your quality requirements. Then replace Manual Trigger with the event source your workflow needs.&lt;/p&gt;

&lt;p&gt;You can find the package on &lt;a href="https://www.npmjs.com/package/@ffhub/n8n-nodes-ffhub" rel="noopener noreferrer"&gt;npm&lt;/a&gt; and the implementation and setup instructions on &lt;a href="https://github.com/ffhub-io/n8n-nodes-ffhub" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which video-processing step would you want to automate in n8n: compression, audio extraction, thumbnails, or something else?&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ffmpeg</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
