ffmpegn8nvideo-processing

Add an Intro and Outro to Video Automatically (n8n + FFmpeg)

·Javid Jamae·9 min read
Add an Intro and Outro to Video Automatically (n8n + FFmpeg)

You have a branded bumper, an outro card with your subscribe animation, and a folder of videos that need both stitched on. Doing it by hand in CapCut is fine for one video and miserable for two hundred. The FFmpeg guides you found are all single-command, single-file, run-it-in-your-terminal recipes.

Quick answer: To add an intro and outro to video automatically, concatenate three files (bumper, main video, outro) in a single FFmpeg job. If all three share the same codec, resolution, frame rate, and audio sample rate, use the concat demuxer with -c copy and it finishes in seconds without re-encoding. If they don't match, normalize each input first and use the concat filter, or send all three URLs to a video API like FFmpeg Micro as one composition job and skip the local encode entirely.

Conventional wisdom: concatenating video is easy, so branding at scale is easy

Concatenating video is easy. Three lines in a text file and one -c copy command, done. Most tutorials stop there, and for most people the command works the first time on the demo files.

Then you point it at your real assets and get a 4-second video, or audio that drifts out of sync by minute three, or Non-monotonic DTS in output stream 0:1 scrolling past. The problem isn't the concat command. It's that your intro came out of Canva at 1920x1080, 30 fps, AAC 44.1 kHz stereo, your main video is a 1080x1920 vertical render at 60 fps, and your outro has no audio stream at all. Stream copy can't reconcile any of that. It just glues bytes together and hopes.

So the real job isn't "concatenate." It's normalize, then concatenate and the normalize step is what decides whether this takes 3 seconds or 3 minutes per video.

The two FFmpeg approaches, and when each one applies

FFmpeg gives you two concat mechanisms and they behave nothing alike.

concat demuxerconcat filter
Command`-f concat -i list.txt -c copy``-filter_complex "...concat=n=3:v=1:a=1"`
Re-encodes?NoYes, always
Handles mismatched inputs?NoYes, if you normalize first
Speed on a 6-min 1080p video2 to 4 seconds60 to 120 seconds
Quality lossNoneOne generation of re-encode

The demuxer is the one you want. It's roughly disk speed because it never touches the pixels. Write a list file:

file 'intro.mp4'
file 'main.mp4'
file 'outro.mp4'

Then:

ffmpeg -f concat -safe 0 -i list.txt -c copy branded.mp4

-safe 0 is required whenever your paths are absolute or contain anything unusual. If a filename has an apostrophe in it (Client's Cut.mp4), escape it as 'Client'\''s Cut.mp4' or the demuxer silently drops the entry.

This only works when every input matches on codec, profile, resolution, frame rate, timebase, pixel aspect ratio, audio codec, sample rate, and channel layout. Miss any one and you get corruption instead of an error.

The filter is the fallback:

ffmpeg -i intro.mp4 -i main.mp4 -i outro.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" \
  -c:v libx264 -preset veryfast -crf 20 -c:a aac -movflags +faststart \
  branded.mp4

It still expects matching resolution and frame rate on the video pads. It just fails louder about it.

The move that makes this cheap: normalize the bumpers once

Here's the part the CLI guides skip. Your intro and outro are the same two files every time. Your main videos change. So normalize the bumpers once, permanently, to exactly match what your main render produces, and every subsequent concat becomes a stream copy.

ffmpeg -i intro_raw.mp4 \
  -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,fps=30,setsar=1" \
  -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p \
  -c:a aac -b:a 128k -ar 48000 -ac 2 \
  intro.mp4

setsar=1 is the one people forget. A source with a 4:3 pixel aspect ratio concatenated onto square-pixel footage gives you the stretched-face look, and nothing in the log tells you why. Run the same command on your outro. Use -preset slow here since it's a one-time cost on a 5-second file.

If your bumper has no audio track, the concat filter throws Stream specifier ':a' in filtergraph description matches no streams. Add silence:

ffmpeg -i outro_silent.mp4 \
  -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 \
  -shortest -c:v copy -c:a aac -b:a 128k outro.mp4

Do that once and your per-video cost drops from a full re-encode to about 3 seconds of file copy. Across 200 videos that's the difference between 5 hours (200 × 90 seconds of libx264 at veryfast) and about 10 minutes.

Doing it in n8n without running FFmpeg anywhere

The catch: n8n doesn't have FFmpeg. The n8n-io/n8n Docker image ships Node and a handful of system libraries, not a media toolchain, and the community nodes that shell out to ffmpeg need you to build a custom image and then keep it running long enough to finish an encode. Self-hosted n8n on a 1 vCPU box will happily OOM in the middle of a 4K render.

The alternative is to keep n8n doing what it's good at (moving JSON around) and send the actual concat to a video API as one job. The whole branding step becomes a single HTTP Request node:

{
  "method": "POST",
  "url": "https://api.ffmpeg-micro.com/v1/jobs",
  "authentication": "genericCredentialType",
  "sendBody": true,
  "specifyBody": "json",
  "jsonBody": "{\n  \"operation\": \"concat\",\n  \"inputs\": [\n    { \"url\": \"https://cdn.example.com/brand/intro.mp4\" },\n    { \"url\": \"{{ $json.videoUrl }}\" },\n    { \"url\": \"https://cdn.example.com/brand/outro.mp4\" }\n  ],\n  \"output\": { \"format\": \"mp4\" },\n  \"webhook\": \"{{ $env.N8N_WEBHOOK_URL }}/branded\"\n}"
}

Check the exact field names against the FFmpeg Micro docs before you wire it up, and test the shape in the playground first. The structure is what matters: three URLs in, one job, no binaries in your n8n container.

Two n8n specifics worth getting right:

  1. Use the webhook, not polling. A default n8n workflow execution times out well before a long concat finishes, and the HTTP Request node will abandon a request that hangs. Submit the job, let the workflow end, and catch the result on a separate Webhook trigger. We wrote up the full pattern in fixing n8n timeout errors when processing video.
  2. Batch with Split In Batches, not a loop over 200 items at once. Feed the folder listing through in chunks so a single bad file doesn't take the run down with it.

The same payload works from Make, Zapier, or an MCP tool call if your agent is the thing assembling videos. More on the general shape in concatenate clips programmatically.

Common pitfalls

Audio drifts later in the video. Almost always a sample rate mismatch: 44.1 kHz bumper on 48 kHz footage. The demuxer copies both and the timestamps slowly diverge. Fix it in the normalize step with -ar 48000, or add aresample=async=1 to the filter chain.

Variable frame rate input. Screen recordings from OBS and phone footage often carry VFR. Concat with -c copy and the timestamps go non-monotonic. Force CFR with fps=30 in your normalize filter before anything else.

The output is exactly as long as the intro. You used the concat filter but only mapped one stream, or your list file had a relative path that didn't resolve. Run ffprobe -v error -show_entries format=duration branded.mp4 on each input to confirm all three are actually being read.

Different H.264 profiles. A High 4:4:4 intro and a High main video won't stream-copy together cleanly even at identical resolution. Add -profile:v high -pix_fmt yuv420p when you normalize.

Missing -movflags +faststart. The file plays fine locally and buffers forever on upload. Always add it for anything destined for a web player.

When a template editor is the better call

If your intro needs per-video text, a dynamic title, a changing thumbnail, or a layered layout, Creatomate and Shotstack are built for that. Their template editors let a non-developer change the design without touching a payload, and that's genuinely useful when marketing owns the branding.

Raw concat is the right tool when the bumper is a fixed asset and the only variable is which main video goes in the middle. That's most faceless-channel and UGC-curation work: the same 4-second logo sting on every upload, forever. You don't need a template engine to glue three MP4s together, and you shouldn't pay a per-render template fee to do it.

If you're assembling the main video from scratch too (script, voiceover, b-roll, then branding), the pipeline is a bit bigger. Faceless YouTube channel automation walks through the full assembly.

FAQ

Can I add an intro and outro without re-encoding the whole video?

Yes, if all three files share the same codec, resolution, frame rate, and audio parameters. Use the concat demuxer with -c copy and FFmpeg copies the streams without touching the pixels, which takes a few seconds regardless of video length. Normalize your intro and outro once to match your main render settings and every job after that is stream copy.

How do I batch add an intro to hundreds of videos?

Loop the concat job over a file listing rather than running it per video by hand. In n8n, read the folder or database of video URLs, feed it through a Split In Batches node, and POST each item to a video API with the intro and outro URLs constant and the main video URL as the variable. With stream copy at roughly 3 seconds per video, 200 videos finishes in about 10 minutes.

Why does my intro look stretched after concatenating?

Pixel aspect ratio mismatch. If one source carries a non-square SAR, the concatenated output inherits inconsistent geometry and faces look horizontally squeezed. Add setsar=1 to the filter chain when you normalize each input, and confirm with ffprobe -show_streams that all inputs report sample_aspect_ratio=1:1.

Does the intro need an audio track?

For the concat filter, yes. Mapping [0:a] on a video-only input fails with a stream specifier error. Generate a silent track with -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000 -shortest and mux it in once, so the bumper permanently has audio.

Can an AI agent do this instead of a workflow tool?

Yes. The same concat job is available through the MCP server, so Claude or any MCP-capable agent can submit it as a tool call and get back a finished URL. That's useful when the agent is already deciding which clips to publish and just needs branding applied as the last step.

The normalize-once, stream-copy-forever trick works the same whether you run FFmpeg yourself or send the job out. If you'd rather not keep an encoder alive next to your automation, sign up free and run the three-URL concat against the free tier before you wire it into your whole pipeline.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free