ffmpegvideo-encodingtroubleshooting

Filtering and Streamcopy Cannot Be Used Together. Keep the Copy.

·Javid Jamae·10 min read
Filtering and Streamcopy Cannot Be Used Together. Keep the Copy.

You bolted a scale, a rotate, or a watermark onto a command that was working fine, and FFmpeg quit before it wrote a byte. The command had -c copy in it because copying is fast, and now FFmpeg is telling you those two things can't coexist. They can't, but the fix isn't giving up the copy.

Quick answer: "Filtering and streamcopy cannot be used together" means FFmpeg can't apply a filtergraph to a stream you asked it to stream-copy, because filters operate on decoded frames and -c copy moves compressed packets from demuxer to muxer without ever decoding them. The direct fix is to re-encode only the stream you're filtering and keep copying the rest: ffmpeg -i in.mp4 -vf "scale=1280:-2" -c copy -c:v libx264 -crf 20 out.mp4 leaves audio and subtitles untouched. If you'd rather not own encoder flags, presets, and the CPU time they cost, send the same filter to FFmpeg Micro as one API call and keep your local pipeline on pure remux.

Why FFmpeg refuses to filter a copied stream

FFmpeg's stream copy path is a byte pump. It reads packets out of the input container and writes them into the output container with the compressed data untouched, which is why remuxing a 10-minute 1080p MP4 finishes in a second or two while re-encoding the same file at -preset medium takes minutes. A filter is the opposite job: scale, overlay, drawtext, transpose, and loudnorm all need actual pixels or samples, which only exist after a decode. There's no way to run a filter on a packet that was never opened.

The error has two wordings depending on how you declared the filter. A simple filter gives you this:

Filtergraph 'scale=1280:-2' was defined for video output stream 0:0 but codec copy was selected.
Filtering and streamcopy cannot be used together.

A -filter_complex gives you a different first line for the same reason:

Streamcopy requested for output stream 0:0, which is fed from a complex filtergraph.
Filtering and streamcopy cannot be used together.

This is also why the answers you find are scattered across other projects' issue trackers instead of FFmpeg's own docs. Frigate's discussion #12305 is people trying to rotate a camera restream 90 degrees while keeping copy. sickbeard_mp4_automator issue #848 is the same collision inside an automated MP4 pipeline. MoviePy issue #588 is minterpolate hitting it from Python. Four projects, four contexts, one rule.

Split the codecs per stream instead of choosing one

Most commands that hit this error are filtering exactly one stream, usually video, and copying everything else by accident. -c copy is a default for all streams, not a promise, so you can override it for a single stream and keep the copy everywhere else. The last matching option wins, so put the broad one first:

ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "[0:v][1:v]overlay=W-w-24:H-h-24[v]" \
  -map "[v]" -map 0:a \
  -c copy -c:v libx264 -crf 20 -preset medium \
  -movflags +faststart output.mp4

Video decodes, gets the watermark, and re-encodes. Audio is copied bit for bit, so there's no second lossy AAC pass and no drift to chase. On a file with a music track plus a subtitle stream, -c copy -c:v libx264 covers both leftovers in one flag, which is why it scales better than spelling out -c:a copy -c:s copy.

The same split works in the other direction. Loudness normalizing for social without touching a single video frame:

ffmpeg -i input.mp4 -af loudnorm=I=-14:TP=-1.5:LRA=11 \
  -c copy -c:a aac -b:a 192k output.mp4

That runs at close to remux speed on a long file because the expensive stream never decodes. The -14 LUFS target is what YouTube and Spotify normalize toward, so it's the number worth hardcoding.

One trap comes with the split: the moment you add -map "[v]" you've taken over mapping, and FFmpeg stops picking streams for you. Forget -map 0:a and you ship a silent video with no error at all, which is the quieter cousin of "Stream map matches no streams".

Some changes look like filters but are container metadata

A good number of the jobs that trigger this error don't need a filter in the first place. Rotation, display aspect, and codec tags live in the container, so you can change them with -c copy intact and the encode never happens.

This table splits the jobs that stay on the copy path from the ones that force a decode:

What you wantStays `-c copy`?How
Rotate for playbackYes`-display_rotation -90` before `-i` (FFmpeg 6.0+)
Rotate the actual pixelsNo`transpose=1`, re-encode video
Fix HEVC playback on AppleYes`-tag:v hvc1`
Change display aspectYes`-aspect 16:9`
Scale, crop, pad, watermarkNofiltergraph, re-encode video
Burn in subtitlesNo`subtitles=` filter, re-encode video
Add a soft subtitle trackYes`-c:s mov_text` alongside `-c copy`
Change container (MKV to MP4)Yes`-c copy` alone
Trim on a keyframe boundaryYes`-ss`/`-to` with `-c copy`

The rotation row is where people get burned twice. -display_rotation takes counterclockwise degrees, so a 90-degree clockwise rotation is -display_rotation -90, not 90. Older forum answers use -metadata:s:v:0 rotate=90 instead, which wrote the same display matrix on builds from that era. Either way, you're setting a flag a player obeys, not turning any pixels, so a player that ignores the matrix still shows the video sideways. That's the honest boundary: metadata rotation is free and reversible, and it's the wrong tool the moment the output goes somewhere that reads raw frames.

The -tag:v hvc1 row is the same shape of fix for a different symptom, covered in the hvc1 tag post. And the trim row comes with its own catch, since -c copy can only cut where a keyframe already is, which trimming by timestamp gets into.

Normalize once, then copy for the rest of the pipeline

When the filter is genuinely required, the move that keeps your throughput is to pay for the decode exactly once. Run the filter pass to a mezzanine file with settings every downstream step can assume: one resolution, one frame rate, one pixel format, one audio sample rate. After that, trimming, concatenating, segmenting to HLS, and repackaging all run with -c copy at disk speed.

This is also the real answer behind most broken concats. Joining clips with different resolutions or frame rates with -c copy produces either a hard failure or a file that plays the first clip and glitches through the rest, and the VideoHelp threads on stream mapping circle the same conclusion: scale and pad to one canvas first, force CFR, then copy.

Running that first pass is the part nobody wants to own. It needs a real encoder build, enough CPU to matter, and somewhere to put a job that runs for minutes instead of milliseconds, which is exactly what breaks inside a Lambda timeout or an n8n container. FFmpeg Micro does that pass as one API call: hand it the input URL and the filter, get a normalized output back, and let the rest of your workflow stay on pure remux. You can try the filter in the playground before wiring it into n8n, Make, or Zapier, and the docs have the exact request shape.

Common pitfalls that cause this error

The filter isn't always visible in the command. -s 1280x720 with -c copy fails with the same message, because -s is shorthand that FFmpeg expands into a scale filter. -ar 48000 and -ac 2 are the audio versions of the same trap: resampling and downmixing need decoded samples, so they can't ride along with -c:a copy.

Option order bites people who know about the per-stream split. -c:v libx264 -c copy fails, because both options match the video stream and the last one wins, so copy overrides your encoder. Reverse them.

Autorotation changes behavior between the two paths. When you re-encode, FFmpeg reads the input's display matrix and inserts a rotation filter for you, then clears the flag. When you stream copy, it does neither and passes the matrix through untouched. So a clip that came out upright during a re-encode test can come out sideways the moment you switch that step to -c copy, and -noautorotate is the flag that makes the behavior explicit either way.

Last one: an empty or malformed filtergraph is not this error. If FFmpeg accepted your filter and still wrote nothing usable, you're looking at a different failure, and "Output file is empty, nothing was encoded" is the string to chase instead.

FAQ

Why does FFmpeg say "Filtergraph was defined but codec copy was selected"?

FFmpeg prints "Filtergraph ... was defined for video output stream 0:0 but codec copy was selected" when a simple -vf or -af filter targets a stream you also told FFmpeg to stream-copy. The filtergraph needs decoded frames and stream copy never decodes, so FFmpeg exits before writing output rather than silently dropping your filter.

Can I keep -c copy for audio if I'm only filtering the video?

Yes, and that's the standard fix for the FFmpeg codec copy filter error. Write -c copy -c:v libx264 so the broad copy applies to audio, subtitles, and data streams while video alone gets decoded, filtered, and re-encoded.

How do I rotate a video without re-encoding it?

Rotating a video for playback needs no filter and no re-encode: ffmpeg -display_rotation -90 -i input.mp4 -c copy output.mp4 writes a display matrix into the container that players read at playback time. Use the transpose filter and a real encode only when the pixels themselves have to move, such as for a consumer that ignores rotation metadata.

Does filtering always mean quality loss?

Filtering always means one decode and one re-encode of the filtered stream, so a lossy codec like H.264 or AAC loses a small amount of quality each pass. Keeping the untouched streams on -c copy limits the damage to the one stream that actually changed, and -crf 18 to -crf 20 with libx264 keeps a single generation visually transparent for most source material.

Why do I hit this error in n8n or a Docker container more often?

The error itself is the same everywhere, but pipelines built around -c copy are built for speed, and adding a filter changes a one-second remux into a multi-minute encode that outlives an n8n execution timeout or a serverless function's limit. That's usually the point people start offloading the encode step instead of provisioning more CPU.

You don't have to do flag archaeology to figure out which changes survive a stream copy. Drop a file in the playground, try the filter, and if it does what you need, sign up free and make it one call in your workflow.

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