-
ffmpeg -c copy로 0.5초 trim했더니 0초 trim됐다 — keyframe-aligned의 함정 (Seedance 후처리 사례)IT 2026. 5. 24. 21:00
🔧 이 글은 BytePlus Seedance 2.0 fast로 만든 영상을 ffmpeg로 후처리하다 만난 함정이지만, 일반 ffmpeg 사용자에게도 그대로 적용됩니다. AI 영상 자동화든, 일반 영상 편집이든 비슷한 sub-second 정확도 문제를 겪고 있다면 해결책이 같습니다.
"stream copy 빠르고 무손실이니까 무조건 -c copy" 라는 습관
ffmpeg에서 영상의 앞부분만 자르고 싶을 때 가장 흔히 보이는 패턴은 이겁니다.
ffmpeg -ss 0.5 -i input.mp4 -c copy output.mp4직역하면 "0.5초부터 시작해서 끝까지를, 스트림은 복사만 해서 새 파일로 저장".
-c copy는 디코딩·인코딩 과정을 건너뛰는 stream copy 모드라 빠르고 무손실입니다. 깔끔하죠. 영상이 1초든 한 시간이든 잘라내는 데 수십 ms면 끝납니다.문제는 이 방식이 약속한 정확도를 안 지킨다는 데 있습니다. BytePlus Seedance가 만든 영상에서 사진 prefix 0.458초를 잘라내려고 같은 명령을 돌렸더니, 잘라낸 파일을 ffprobe로 다시 측정해 보니 길이가 거의 그대로였습니다.
항목 원본 -c copytrim 후duration 12.04s 12.06s (오히려 0.02s 늘어남) start_time 0.00s 0.06s 첫 scene change 0.06s + 0.481s 0.06s + 0.481s (변화 없음) trim 요청 0.458초가 적용되지 않았습니다. duration이 오히려 늘어난 건 컨테이너 메타데이터 보정 때문이고, 핵심은 실제 픽셀 단위로는 거의 잘리지 않았다는 사실입니다.
원인 — fast seek은 가까운 keyframe까지만 점프
원인은 ffmpeg의 입력 측 seek 동작에 있습니다.
-ss N -i input형태로 입력 전에-ss를 두면 ffmpeg는 fast seek 모드로 들어가, N 시점에 가장 가까운 keyframe까지만 점프하고 거기서부터 처리합니다.h264 영상은 keyframe(I-frame) 사이에 일정 간격이 있는데, 이 간격을 GOP(Group of Pictures)라 부릅니다. 대부분의 영상은 GOP가 약 1초 단위라, 0.5초처럼 sub-second 시점은 그 시점에 정확히 매칭되는 keyframe이 없습니다. fast seek은 그 시점에 가장 가까운 keyframe(보통은 0초)까지만 가서 멈춥니다. 그 결과 sub-second trim은 효과가 거의 없거나 0이 됩니다.
게다가
-c copy는 stream을 그대로 복사하므로, 컨테이너의 keyframe 위치를 바꿀 수 없습니다. 즉 stream copy 모드에선 물리적으로 sub-second trim이 불가능합니다.다이어그램이 짚는 핵심은 두 줄입니다. (1) keyframe은 약 1초 간격으로 흩어져 있고, sub-second 시점에 우리가 원하는 정확한 keyframe이 없을 확률이 높습니다. (2) stream copy는 그 keyframe 격자를 절대 바꿀 수 없고, 재인코딩만이 새 keyframe을 임의 시점에 만들 수 있습니다.
해결 — CRF 18 재인코딩이 production-grade 답
정확한 sub-second trim이 필요하다면, 재인코딩을 두려워하지 말아야 합니다.
ffmpeg -ss 0.458 -i input.mp4 \ -c:v libx264 -crf 18 -preset fast \ -c:a aac -b:a 192k \ output.mp4세 가지 옵션이 production-grade의 핵심입니다.
-c:v libx264: 비디오를 H.264로 재인코딩. 시작 시점에 새 keyframe을 만들 수 있습니다.-crf 18: Constant Rate Factor 18. 시각적으로 무손실에 가까운 품질 레벨이고, VMAF로 측정하면 95+ 점이 나옵니다. 일반 시청 환경에선 원본과 구별이 거의 불가능.-preset fast: 인코딩 속도/효율 균형.slow나veryslow로 가면 같은 품질을 더 작은 파일로 만들 수 있지만 시간이 4~10배 늘어납니다. 후처리 자동화에는fast가 적절합니다.
결과는 위 표의 두 번째 줄대로입니다. 12.04초 영상을 0.458초 trim 요청 → 결과 11.58초. 정확히 0.46초가 잘렸고, photo prefix는 깨끗하게 사라졌습니다.
같은 함정의 다른 변종 — output -ss
이 함정에 빠지는 또 다른 패턴은
-ss를-i다음에 두는 것입니다.ffmpeg -i input.mp4 -ss 0.458 -c copy output.mp4이건 fast seek가 아닌 slow seek이라 정확도는 높지만,
-c copy와 같이 쓰면 또 다른 문제가 생깁니다. 첫 keyframe이 0초가 아닌 시점에 있는 컨테이너가 만들어져 일부 player에서 첫 N초 동안 검은 화면이나 깜빡임이 보일 수 있습니다. 결국 stream copy로 sub-second 정확도를 달성하려는 시도는 어떤 변종으로 가도 함정에 부딪힙니다. 정확한 sub-second는 재인코딩만이 답입니다.결과 — 자동화 도구에 결정을 박아두기
이 결정을 코드 안에 박아두면 사용자가 매번 옵션을 고민할 필요가 없어집니다.
def trim_intro(mp4_path: Path, prefix_seconds: float) -> None: """sub-second 정확도를 보장하는 intro trim.""" if prefix_seconds < 0.01: return # 의미 있는 trim 아님 tmp = mp4_path.with_suffix(".trim.mp4") subprocess.run([ "ffmpeg", "-y", "-ss", f"{prefix_seconds:.3f}", "-i", str(mp4_path), "-c:v", "libx264", "-crf", "18", "-preset", "fast", "-c:a", "aac", "-b:a", "192k", str(tmp), ], check=True) tmp.replace(mp4_path)이 함수 한 개를 만들어 두면, photo prefix 자동 제거든 사용자 요청 trim이든 같은 코드 경로로 흐릅니다. 어디서 호출하든 정확도 vs 속도의 trade-off가 이미 결정돼 있으니, 호출자는 "몇 초를 자를지"만 신경 쓰면 됩니다.
이 사건이 남긴 큰 lesson은 한 줄입니다 — "stream copy로 빠르게"라는 욕심이 정확도를 죽인다. ffmpeg에서 빠른 처리와 정확한 처리는 종종 양립하지 않습니다. 이 두 가치 사이에서 결정을 매번 새로 하지 말고, sub-second 정확도가 필요한 작업은 재인코딩으로 고정해 두면 그 자체가 production toolkit이 됩니다. 시각적 품질은 CRF 18이 책임지고, 정확도는 새 keyframe 생성이 책임집니다. 두 가치 모두를 확보하는 단순한 결정 한 줄이, 그 후의 모든 trim 작업을 평온하게 만들어 줍니다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글