- Дата публикации
Как Домклик делает потоковую конвертацию видео без диска: Python + FFmpeg
Что появилось / что изменилось
Команда Домклик собрала отдельный микросервис для конвертации видео — Transcode Backend. Он написан на Python и использует FFmpeg, но главное здесь не язык, а способ работы:
- Конвертация идёт потоками (chunked streaming), без сохранения исходного или итогового файла на диск.
- FFmpeg читает видео по HTTP Range прямо из файлового хранилища и сразу пишет результат в stdout.
- Python-сервис читает stdout FFmpeg чанками по 8 КБ и тут же заливает результат обратно в хранилище, тоже частями.
- На пике нагрузки в системе не возникает двух полных копий одного и того же видео.
- Не нужны временные файлы и логика их очистки после сбоев.
Для настройки качества автор собрал пять пресетов под разные сценарии: от «high_quality» (1920×1080, 4000 kbit/s, CRF 18, preset slow) до «mobile_low_size» (854×480, 800 kbit/s, CRF 30, preset slow). Все они используют кодек H.264 (libx264) и управляют качеством через CRF:
- CRF 18 — визуально близко к исходнику.
- CRF 23 — стандартное качество.
- CRF 28+ — заметно более жёсткое сжатие.
Как это работает
В основе — три ключевых компонента.
1. FS Proxy как файловое хранилище
FS Proxy — это HTTP-сервис, который хранит файлы и поддерживает запросы с заголовком Range. Пример запроса:
GET /srv/v1/files/{urn}/download
Range: bytes=0-1048575
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1048575/32925286
За счёт этого Transcode Backend может:
- скачивать исходное видео чанками;
- заливать сконвертированный результат обратно тоже по частям.
2. VideoConverter — обёртка над FFmpeg
Класс VideoConverter запускает FFmpeg как подпроцесс и отдаёт результат как AsyncIterable[bytes]:
async def __aenter__(self):
cmd = preset.render_cmd(
headers=input.headers,
input_url=input.url,
ffmpeg_path=self._ffmpeg_path,
)
self._proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.DEVNULL,
)
return self
async def convert(self) -> AsyncIterable[bytes]:
while True:
chunk = await self._proc.stdout.read(8192)
if not chunk:
break
yield chunk
FFmpeg пишет выходной поток в pipe:1, то есть в stdout. Python читает его кусками по 8192 байта и может сразу отправлять дальше — в сеть или в другое хранилище. Если потребитель читает медленно, запись в pipe блокирует FFmpeg, и он замедляется сам по себе — получается естественный backpressure.
3. Пресеты и генерация команд FFmpeg
Каждый пресет сам собирает команду для FFmpeg. В неё входят:
- заголовки HTTP для доступа к FS Proxy (
-headers), - URL исходного видео (
-i), - фильтр масштабирования
-vfс логикой: если ширина больше высоты — ограничиваем по ширине, иначе по высоте, вторую сторону считаем автоматически, - кодек
libx264, параметры-preset,-crf,-b:v,-profile:v,-level.
Пример фрагмента:
cmd = [
ffmpeg_path,
'-timeout', str(self.timeout),
'-threads', str(self.threads),
'-headers', self._build_headers_string(headers),
'-i', str(input_url),
'-vf', "scale='if(gt(iw,ih),min(1920,iw),-2)':'if(gt(iw,ih),-2,min(1080,ih))'",
'-c:v', 'libx264',
'-preset', 'fast',
'-crf', '23',
'-b:v', '2500k',
]
Что это значит для вас
Где такой подход полезен:
- Массовая перекодировка пользовательских видео, когда нельзя раздувать дисковое хранилище временными файлами.
- Обработка длинных роликов, где целиком держать файл ни в памяти, ни на диске неудобно или дорого.
- Сценарии, где важно не дублировать исходник и результат одновременно (например, при жёстких лимитах на объём хранилища).
Когда это решение особенно уместно:
- У вас уже есть HTTP-хранилище с поддержкой Range-запросов или вы готовы его внедрить.
- Вы хотите централизованно управлять качеством через набор пресетов (разные разрешения и битрейты под веб, мобильные клиенты, «лёгкие» превью).
- Важна простая интеграция: микросервис слушает очередь, принимает задачу, конвертирует и возвращает URN готового файла.
Когда лучше подумать о другом варианте:
- Нужен классический live-стриминг с HLS/DASH и плейлистами — здесь описан именно chunked streaming для файлов, а не живой эфир.
- Ваша инфраструктура не позволяет работать по HTTP Range или вы завязаны на локальные файлы без сетевого доступа.
Для разработчиков продуктовых и медийных сервисов это хороший пример того, как собрать конвейер обработки видео без временных файлов и с прозрачным контролем качества через CRF и пресеты.
Место на рынке
Transcode Backend использует классический стек: Python + FFmpeg + HTTP-хранилище. Это не конкурирует с SaaS-платформами для видео вроде готовых CDN-сервисов — это внутренняя инфраструктурная деталь.
По сути, Домклик показывает рабочий паттерн:
- FFmpeg как движок кодирования,
- микросервис как оркестратор и транспорт,
- файловое хранилище с HTTP Range как источник и приёмник.
Если вы уже используете FFmpeg в виде CLI-скриптов и временных файлов, этот подход можно рассматривать как эволюцию: тот же движок, но без промежуточного диска и с потоковой обработкой через асинхронный Python.