ffmpeg из Python: типичные ошибки и как их избежать
Скрипт зависает на длинном видео, звук рассинхронизируется, а stderr съедает всю память. Разбираем, где ломается вызов ffmpeg из Python и как обходить каждую ловушку.

Вы запускаете ffmpeg из Python, чтобы перекодировать сотню видео, и на третьем файле скрипт намертво зависает. В логах пусто, процесс жив, но не двигается. Знакомая картина: труба stderr заполнилась, ffmpeg ждёт, пока её прочитают, а ваш код ждёт завершения ffmpeg. Классический дедлок. Ниже — набор ошибок, которые всплывают при работе с ffmpeg через subprocess и обёртки вроде ffmpeg-python, и что делать в каждом случае.
Материал про практику вызова из кода, а не про сами кодеки и их параметры. Версии, на которых проверялось поведение: ffmpeg 6.x, Python 3.11, обёртка ffmpeg-python 0.2.0. Синтаксис фильтров и флагов ffmpeg между версиями меняется редко, а вот поведение subprocess стабильно с Python 3.6.
Ошибка 1. Дедлок на заполненной трубе
Самая частая и самая коварная. Код выглядит так:
import subprocess
# ПЛОХО: зависнет на большом выводе
process = subprocess.Popen(
["ffmpeg", "-i", "input.mp4", "-c:v", "libx264", "output.mp4"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
process.wait() # тут повиснет
out = process.stdout.read()
ffmpeg пишет прогресс и служебную информацию в stderr. Труба ОС имеет ограниченный буфер (обычно 64 КБ на Linux). Когда буфер заполняется, ffmpeg блокируется на записи. Ваш код в это время сидит на process.wait() и не читает трубу. Оба ждут друг друга.
Правильно — использовать communicate(), который читает обе трубы параллельно:
import subprocess
process = subprocess.Popen(
["ffmpeg", "-i", "input.mp4", "-c:v", "libx264", "output.mp4"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = process.communicate() # читает трубы и ждёт завершения
if process.returncode != 0:
raise RuntimeError(stderr.decode("utf-8", errors="replace"))
Если вывод не нужен вовсе — направьте его в subprocess.DEVNULL, тогда буфер не переполнится и дедлока не будет.
Правило простое: либо читаете трубу, либо не создаёте её. Промежуточный вариант — открыть PIPE и не читать — гарантированно вешает процесс на достаточно длинном файле.
Ошибка 2. shell=True и имена файлов с пробелами
Соблазн собрать команду одной строкой велик:
# ПЛОХО: сломается на пробелах и откроет инъекцию команд
cmd = f"ffmpeg -i {input_path} -c:v libx264 {output_path}"
subprocess.run(cmd, shell=True)
Если input_path содержит пробел, кавычку или точку с запятой, команда либо развалится, либо выполнит что-то постороннее. Файл, названный пользователем my video; rm -rf ~.mp4, — не выдумка, а реальный вектор.
Передавайте аргументы списком, без shell=True. Тогда каждый элемент списка — отдельный аргумент, и экранирование не нужно:
subprocess.run([
"ffmpeg", "-i", input_path,
"-c:v", "libx264",
output_path,
])
Ошибка 3. Молчаливая перезапись и вопрос в консоли
Если выходной файл уже существует, ffmpeg по умолчанию спрашивает в интерактивном режиме: Overwrite? [y/N]. Из скрипта отвечать некому — процесс зависает в ожидании ввода с несуществующего терминала. Симптом тот же, что при дедлоке, но причина другая.
Решают два взаимоисключающих флага:
-y— перезаписывать без вопроса;-n— никогда не перезаписывать, сразу выйти с ошибкой.
Ставьте один из них в начало команды осознанно. В пайплайнах обычно нужен -y, но тогда убедитесь, что пути к выходным файлам не совпадают между задачами.
Ошибка 4. Рассинхрон звука и картинки при обрезке
Порядок опций -ss (начало) и -i (вход) влияет на результат сильнее, чем кажется. Разница между этими двумя вариантами не косметическая:
| Вариант | Поведение | Цена |
|---|---|---|
-ss перед -i | Быстрый поиск по ключевым кадрам до декодирования | Может сдвинуться на ближайший ключевой кадр, точность до секунд |
-ss после -i | Точный поиск по кадрам, декодирует всё до нужной точки | Медленнее на длинных файлах, зато кадр в кадр |
Если при обрезке звук уезжает относительно видео — чаще всего дело в неудачном сочетании перекодирования потоков. При точной обрезке с перекодированием держите оба потока в одном проходе, а не режьте видео и звук отдельными командами: рассинхрон копится на стыках.
Ошибка 5. Копирование потока с несовместимым контейнером
Режим -c copy заманчив: он не перекодирует, а перекладывает пакеты как есть, работает мгновенно и не теряет качество. Но не каждый кодек влезает в каждый контейнер. Попытка сложить поток H.264 из MKV в контейнер, который его не принимает, завершится ошибкой вида Could not find tag for codec ... in stream.
Перед тем как копировать, проверьте, что кодеки совместимы с целевым контейнером. Разбирать структуру входного файла удобно через ffprobe с выводом в JSON — это надёжнее, чем парсить человекочитаемый вывод ffmpeg:
import subprocess
import json
result = subprocess.run(
[
"ffprobe", "-v", "quiet",
"-print_format", "json",
"-show_streams",
"input.mkv",
],
capture_output=True,
text=True,
check=True,
)
info = json.loads(result.stdout)
for stream in info["streams"]:
print(stream["codec_type"], stream["codec_name"])
capture_output=True и text=True появились в удобном виде в Python 3.7 — на более старых версиях используйте stdout=subprocess.PIPE и декодируйте вручную.
Ошибка 6. Игнорирование кода возврата
ffmpeg может завершиться с ненулевым кодом, написать ошибку в stderr и не создать выходной файл, а ваш скрипт спокойно пойдёт дальше и упадёт на следующем шаге в непонятном месте. subprocess.run по умолчанию НЕ бросает исключение на ненулевой код.
Ставьте check=True — тогда при ошибке поднимется CalledProcessError. А чтобы в исключении был текст ошибки ffmpeg, захватывайте stderr:
try:
subprocess.run(
["ffmpeg", "-y", "-i", "input.mp4", "-c:v", "libx264", "output.mp4"],
capture_output=True,
text=True,
check=True,
)
except subprocess.CalledProcessError as error:
# в error.stderr лежит текст, который ffmpeg написал перед выходом
print("ffmpeg упал:", error.stderr)
raise
Ошибка 7. Бесконечное ожидание без таймаута
Битый входной файл или неудачная комбинация фильтров могут заставить ffmpeg крутиться бесконечно. В пакетной обработке один такой файл останавливает весь конвейер. Задавайте timeout и обрабатывайте его:
try:
subprocess.run(
["ffmpeg", "-y", "-i", "input.mp4", "output.mp4"],
capture_output=True,
text=True,
check=True,
timeout=600, # 10 минут на файл
)
except subprocess.TimeoutExpired:
print("ffmpeg не уложился в лимит, файл пропущен")
После TimeoutExpired процесс убивается, но дочерние процессы ffmpeg в некоторых случаях остаются. Если запускаете много задач, следите за зависшими процессами через мониторинг, а не только за кодом Python.
Обёртка ffmpeg-python: где помогает, где мешает
Библиотека ffmpeg-python строит команду декларативно и сама собирает список аргументов — это снимает ошибки экранирования и с shell=True. Но она НЕ спасает от дедлока, если вы вызываете .run() с захватом вывода на большом файле, и её сообщения об ошибках бывают менее читаемы, чем сырой stderr. Для сложных фильтров многие в итоге возвращаются к прямому subprocess: там виден точный набор аргументов, который можно скопировать в терминал и воспроизвести один в один.
Что настроить дальше
Соберите тонкую обёртку: одна функция, которая всегда ставит -y, всегда захватывает stderr, всегда задаёт таймаут и логирует полную команду перед запуском. Воспроизводимость команды в терминале — главный инструмент отладки: если падает в проде, вы копируете залогированную строку и запускаете руками.
Для прогресса длинных задач смотрите в сторону флага -progress, который пишет машиночитаемый прогресс в отдельный поток — это удобнее, чем парсить строки со временем из stderr. Точный формат вывода уточняйте в документации ffmpeg вашей версии.
Локализатор видео: Перевод и озвучка видео на другой язык с укладкой в исходный тайминг. Глоссарий терминов, отметки проблемных реплик.
Спросить за 250 ₽Источники: ffmpeg Documentation, Python subprocess — Subprocess management
Частые вопросы
А если у меня ffmpeg-python, а не голый subprocess?
Почему скрипт зависает, хотя команда в терминале отрабатывает нормально?
Как поймать ошибку ffmpeg, а не просто узнать, что что-то пошло не так?
Что делать, если один битый файл вешает всю пакетную обработку?
Почему -c copy падает с ошибкой про тег кодека?
В чём разница, ставить -ss до или после -i?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.