Remotion под микроскопом: 1,25 млн строк, 72 пакета, девять разных лицензий
Remotion — стандарт индустрии для программного видео, и его лицензия и есть его бизнес-модель. Мы проиндексировали все 1 253 947 строк на коммите 217e07f и провели полный статический аудит: как обновление React-состояния превращается в кадр видео, почему ноль циклов зависимостей за пять лет — самая впечатляющая цифра в репозитории, шесть пакетов, опубликованных как UNLICENSED, и готовые бинарники кодирования, которые сами сообщают о GPL и содержат кодек, который FFmpeg отказывается распространять.
Ключевые выводы
Remotion рендерит React в видео, управляя форкнутым headless-Chrome и Rust-компоузитором. Полный статический аудит на коммите 217e07f (v4.0.503) показал зрелую кодовую базу — ноль циклов функций и файлов на 1,25 млн строк и 124 пакетах, модулярность Q=0,590, всего две точки сочленения графа, ни одной подтверждённой уязвимости и ни одного критического или высокого хотспота техдолга — и одновременно пять провалидированных находок: 72 публикуемых пакета с девятью разными строками лицензии, publish-скрипт, кладущий текст лицензии лишь в 19 из них, шесть пакетов с SPDX UNLICENSED (включая две runtime-зависимости Lambda-рендерера), готовые бинарники компоузитора с самоотчётом GPL-2.0-or-later и энкодером Fraunhofer FDK-AAC, который выбирается дефолтным путём рендера, и bus factor, равный единице.
23 июня 2020 года швейцарский разработчик Джонни Бургер сделал первый коммит в репозиторий, предлагавший то, что индустрия тогда считала курьёзом: писать видео так же, как пишут сайт. Шесть лет спустя в этом репозитории 34 729 коммитов, 897 релизных тегов, 441 контрибьютор и 1 253 947 проиндексированных строк кода, и идея давно перестала быть курьёзом. Remotion — ответ по умолчанию, когда команде нужно генерировать видео из данных.
Это ещё и бизнес. Remotion не открытый, а source-available: частные лица, некоммерческие организации и компании до трёх сотрудников пользуются бесплатно, все остальные покупают корпоративную лицензию. Один документ — коммерческий двигатель всего проекта. Именно поэтому состояние опубликованных лицензионных метаданных оказалось самой важной находкой этого аудита. И направление у неё не то, которое хотелось бы видеть.
Мы взяли репозиторий на коммите 217e07f (v4.0.503, 31 июля 2026) и прогнали через полный статический аудит с Code Indexer — движком семантического поиска и анализа кода, который строит запрашиваемый граф кодовой базы вместо чтения файлов по одному. Индекс содержит 61 239 символов и 266 826 ссылок, разрешённых в 108 964 статических рёбер вызовов. Каждая цифра ниже вычислена по этому графу или измерена напрямую; каждая находка воспроизведена руками до того, как была записана. Там, где инструмент ошибся, об этом сказано отдельным разделом.
Часть I — как обновление React-состояния становится кадром видео
Время не замораживают. У страницы спрашивают, готова ли она
Рендер видео из браузера — это решение одной задачи: браузер является наименее детерминированной средой в массовых вычислениях, а видео требует, чтобы кадр 1847 выглядел идентично на любой машине и при любом запуске. Решений два. Можно упразднить браузерное представление о времени — заменить Date, performance.now и requestAnimationFrame виртуальными часами, которыми вы управляете. Или можно оставить время в покое и заставить страницу саму сообщать, когда она устаканилась. Remotion идёт вторым путём, и вся архитектура вытекает из этого выбора.
Механизм умещается в тридцать пять строк. packages/renderer/src/seek-to-frame.ts ждёт, пока страница отрапортует о готовности, вычисляет внутри страницы window.remotion_setFrame(frame, composition, attempt), снова ждёт готовности и лишь затем дожидается document.fonts.ready, прежде чем что-либо снимать. Интереснее вторая половина — браузерная. packages/core/src/TimelineContext.tsx устанавливает этот глобал, и делает он вещь неочевидную: открывает хэндл delayRender() с меткой «Setting the current frame to N», проталкивает кадр в React-состояние и вызывает continueRender() только из колбэка requestAnimationFrame — то есть после того, как React реально закоммитил обновление. Если кадр не изменился, ветка коротко замыкается синхронно, чтобы не жечь цикл впустую.
delayRender() — не внутренний трюк, а публичный API. Любой компонент, который грузит шрифт, тянет данные или декодирует изображение, открывает хэндл и закрывает его по завершении, а рендерер просто не двинется дальше, пока счётчик не обнулится. Таймаут по умолчанию — 30 000 миллисекунд, и когда он истекает, рендерер делает по-настоящему продуманную вещь: вычисляет window.remotion_delayRenderTimeouts прямо в странице и сообщает, какие именно именованные хэндлы остались открыты. Итоговая ошибка вида «Open delayRender() handles: 1. Loading font, 2. Fetching chart data» — лучший элемент developer experience из обеих кодовых баз, которые мы разобрали в этой серии.
Корректность здесь — это контракт с автором композиции, а не свойство песочницы. Компонент, анимирующийся от Date.now(), отрендерится неправильно, и фреймворк его не остановит.
Контракт «рендерер — страница» — это 45 изменяемых глобалов
В packages/core/src/index.ts объявлен единственный блок interface Window с 45 членами remotion_*: remotion_renderReady, remotion_setFrame, remotion_delayRenderTimeouts, remotion_cancelledError, remotion_inputProps, remotion_envVariables, remotion_mediaCacheSizeInBytes и ещё сорок. По всему репозиторию встречается 94 различных идентификатора remotion_*. Это плоское пространство имён и есть весь API между Node-рендерером и браузерным бандлом, разделяемый рендерером, Studio, Player и браузерным веб-рендерером. Это не версионируемый канал сообщений и не типизированный RPC. Это глобальное изменяемое состояние — и оно работает пять лет.
Именно поэтому один файл всплывает как структурная точка отказа. Анализ точек сочленения по всем 108 964 рёбрам вызовов возвращает ровно две таких вершины на весь репозиторий. Одна — артефакт: packages/google-fonts/src/base.ts, который импортируют 1849 сгенерированных шрифтовых модулей. Вторая — packages/core/src/delay-render.ts, степень 60, отделяющая 1852 модуля и 264 016 строк кода. Это не дефект. Это точная формулировка того, где лежит вес архитектуры, и такое выпадает только из анализа всего графа — построчный обзор файлов подобное не показывает никогда.
Стек целиком
Remotion часто описывают как «React для видео», и это занижает масштаб примерно на сотню пакетов. В монорепозитории 124 директории пакетов, 72 из них публикуемые, написанные на 22 языках, 15 из которых — код. TypeScript даёт 64% файлов, TSX — ещё 27%, но самое важное как раз то, что написано не на TypeScript.
| Слой | Что реально лежит в репозитории |
|---|---|
| Языки | TypeScript 64%, TSX 27%, плюс Rust, Go, PHP, Python, Ruby, C/WASM — 22 языка, 15 из них код |
| Сборка | Bun как пакетный менеджер и тест-раннер, Turborepo для оркестрации, oxfmt и собственный @remotion/eslint-plugin |
| Бандлеры | webpack И rspack И esbuild, с путями трансформации через Babel и SWC |
| Управление браузером | Вендоренный форк Puppeteer — packages/renderer/src/browser, 41 файл и 7952 строки. У @remotion/renderer вообще нет зависимости puppeteer |
| Нативное кодирование | Rust-бинарник (крейт remotion-renderer, 25 файлов / 4494 строки) на ffmpeg-next и mp4-rust — оба поддерживаемые форки — плюс rayon-core и sysinfo |
| Разбор медиа | @remotion/media-parser — 459 файлов и 54 189 строк демультиплексирования на чистом TypeScript по десяти семействам контейнеров: ISO-BMFF, WebM, RIFF/WAV, MP3, AAC, FLAC, AVC, M3U/HLS, MPEG-TS |
| Serverless | @remotion/serverless как провайдеро-независимое ядро, провайдеры AWS Lambda и GCP Cloud Run, тонкие клиенты на Go (1199 строк), PHP (3552), Python (2207) и Ruby (439) |
| UI редактора | React 19, Radix UI, Tailwind, Monaco и кодмоды на recast, которые записывают правки таймлайна обратно в ваш .tsx |
| Тестирование | Bun test и Vitest (включая browser mode), Playwright, плюс отдельные наборы SSR, шаблонов, Lambda, визуальных эффектов и browser-Studio в 53 пакетах |
| Агентская поверхность | @remotion/mcp (сервер Model Context Protocol), 11 скиллов на 6831 строку Markdown и отдельные плагины для Claude Code, Codex и Kimi |
| Учёт использования | @remotion/licensing шлёт usage-события на remotion.pro — но только при наличии лицензионного ключа |
Две детали заслуживают большего, чем строка в таблице. Первая: Studio пишет код. packages/studio-server/src/codemods/ и packages/studio-codemods/ применяют через recast правки таймлайна — кейфреймы, дефолтные пропсы, границы секвенций — обратно в исходники пользователя на React. Потянул клип в визуальном редакторе — получил дифф в репозитории. Это самое сложное в продукте и самое недооценённое.
Вторая: @remotion/media-parser тихо огромен. Пятьдесят четыре тысячи строк чистого TypeScript, демультиплексирующих MP4, WebM, HLS и транспортные потоки без единого обращения к FFmpeg — именно это делает возможным браузерный рендер вообще, потому что браузер не может вызвать внешний бинарник. Personalized PageRank по графу вызовов подтверждает, насколько центральным он стал: три самых зависимых символа всего репозитория живут в media-parser, во главе с map в audio-sample-map.ts с 1386 входящими ссылками. Самая новая крупная подсистема уже стала центром тяжести графа.
Учёт использования заслуживает точного описания, потому что он различается по путям рендера. packages/licensing/src/register-usage-event.ts шлёт запрос на remotion.pro с тремя ретраями и экспоненциальной задержкой. На серверном и Lambda-путях он закрыт ключом: packages/serverless/src/handlers/send-telemetry-event.ts возвращается немедленно, если лицензионный ключ равен null, — нет ключа, нет запроса. С браузерным путём иначе. packages/web-renderer/src/send-telemetry-event.ts проверяет только то, что код вообще исполняется в браузере; отсутствие ключа даёт предупреждение в консоль, а событие всё равно уходит, и передача значения «free-license» его тоже не отменяет — она лишь обнуляет поле ключа. В payload при этом летит window.location.origin, то есть домен страницы. Итого: на сервере учёт закрыт ключом, из браузерного рендерера отправка безусловна.
Часть II — аудит
Обычный ревью кода языковой моделью читает файлы. На 1,25 миллиона строк это не стратегия, а выборочная проверка. Code Indexer эмбеддит и индексирует каждый чанк, разрешает граф символов и запускает по нему графовые алгоритмы: Personalized PageRank для несущих символов, кластеризацию Лувена для архитектурных швов и точек сочленения, и джойн с историей git для владения кодом и концентрации дефектов.
Первый же результат стоит того, чтобы начать с него. На 1 253 947 строках, 124 пакетах и пяти годах непрерывных изменений разбиение даёт 4146 сообществ при модулярности Q = 0,590, а детектор циклов нашёл ноль циклов функций и ноль циклов файлов. Не мало. Ноль. Любой, кто видел, как стареет крупный TypeScript-монорепозиторий, поймёт, насколько это необычно: циклические импорты — та энтропия, которая накапливается сама собой. Свод техдолга согласен: ноль критических, ноль высоких и ноль средних хотспотов риска. Эту кодовую базу курировали, а не просто растили.
Риски лежат в другом месте — и ни один из них не в рендерере.
| № | Находка | Где | Критичность | Как проверено |
|---|---|---|---|---|
| 1 | 72 публикуемых пакета несут девять разных строк лицензии; 53 уходят без текста лицензии | publish.ts:60-66 и все packages/*/package.json | Высокая (внедрение) | Живой реестр npm на v4.0.503; архивы скачаны и просмотрены |
| 2 | Готовые бинарники компоузитора сообщают GPL-2.0-or-later и содержат FDK-AAC — внутри пакетов без лицензии | @remotion/compositor-* v4.0.503 | Высокая (юридическая) | Опубликованные бинарники запущены: ffmpeg -L, -encoders, strings, ldd |
| 3 | Дефолтный путь рендера MP4 выбирает энкодер FDK-AAC | packages/renderer/src/options/audio-codec.tsx:44-46, 89-92 | Средняя | Исходник прочитан; цепочка кодеков по умолчанию прослежена |
| 4 | Bus factor = 1: 82% коммитов за полгода, 81% владения файлами | история git | Высокая (ключевой персонал) | git shortlog плюс свёртка первичного владения по файлам |
| 5 | Контракт «рендерер — страница» — 45 изменяемых глобалов; delay-render.ts — точка сочленения графа | packages/core/src/index.ts:49-109 | Средняя | Интерфейс подсчитан; анализ сочленений по 108 964 рёбрам |
Находка 1 — девять строк лицензии на одну лицензию
LICENSE.md у Remotion — аккуратный и читаемый документ. Free License покрывает частных лиц, некоммерческие организации, тех, кто оценивает продукт, и коммерческие организации до трёх сотрудников. Всем остальным нужна платная Company License. Корневой package.json объявляет «SEE LICENSE IN LICENSE.md». Это согласованно, и на этом фундаменте стоит вся выручка компании.
Опубликованные метаданные этому не соответствуют. В 72 публикуемых манифестах поле лицензии принимает девять различных значений.
Мы проверили живой реестр npm, а не поверили репозиторию — на случай, если publish-конвейер нормализует поле. Не нормализует. На версии 4.0.503 remotion, @remotion/renderer, @remotion/player, @remotion/cli, @remotion/bundler и @remotion/lambda публикуются с «SEE LICENSE IN LICENSE.md». @remotion/studio, @remotion/studio-server, @remotion/captions, @remotion/paths и @remotion/shapes публикуются как MIT. Шесть пакетов публикуются как UNLICENSED. Тринадцать — вообще без поля лицензии, включая @remotion/media, @remotion/cloudrun и все семь платформенных бинарных пакетов @remotion/compositor-*.
UNLICENSED — не нейтральная заглушка. В семантике самого npm это означает «проприетарное, права не предоставляются», и Snyk, FOSSA, Black Duck и dependency review в GitHub разрешают его в жёсткую блокировку. Два из шести — @remotion/lambda-client и @remotion/serverless — являются runtime-зависимостями @remotion/lambda, так что организация, внедряющая рендер на Lambda, затягивает их транзитивно, ни разу не назвав по имени. @remotion/transitions, ещё один из шести, — один из самых устанавливаемых пакетов экосистемы.
| Пакет | Поле license на npm, v4.0.503 | Текст лицензии в архиве |
|---|---|---|
| remotion | SEE LICENSE IN LICENSE.md | Да |
| @remotion/renderer | SEE LICENSE IN LICENSE.md | Да |
| @remotion/studio | MIT | Нет |
| @remotion/transitions | UNLICENSED | Нет |
| @remotion/effects | UNLICENSED | Нет |
| @remotion/serverless | UNLICENSED | Нет |
| @remotion/lambda-client | UNLICENSED | Нет |
| @remotion/whisper-web | UNLICENSED | Нет |
| @remotion/media | поля нет | Нет |
| @remotion/cloudrun | поля нет | Нет |
| @remotion/compositor-linux-x64-gnu | поля нет | Нет |
| @remotion/media-parser | Remotion License + URL | Нет |
Механизм оказался одной строкой — из тех, что невидимы, пока их не начнёшь искать. В publish.ts корневой текст лицензии копируется в архив пакета только в том случае, если поле license этого пакета содержит подстроку «LICENSE.md». Подходят тринадцать пакетов; ещё шесть несут собственный закоммиченный файл лицензии. Итого 19 из 72. Оставшиеся 53 публикуются вообще без текста лицензии в архиве — все UNLICENSED, все MIT и @remotion/media-parser, чьё поле указывает на URL, который проверка подстроки не ловит.
Мы подтвердили это эмпирически, а не только чтением кода. Архивы remotion и @remotion/paths содержат package/LICENSE.md. Архивы @remotion/transitions, @remotion/effects, @remotion/serverless, @remotion/lambda-client, @remotion/studio, @remotion/media, @remotion/media-parser и @remotion/webcodecs не содержат файла лицензии вообще. Для компании, чья выручка зависит от того, читают ли люди лицензию, расширение этого условия — изменение с наивысшей отдачей во всём репозитории, и делается оно примерно за вечер.
Находка 2 — бинарники кодирования под GPL и с кодеком, который FFmpeg не распространяет
Эту находку мы не планировали, и ради неё пришлось полностью выйти за пределы репозитория. @remotion/compositor-linux-x64-gnu версии 4.0.503 — архив на 10,3 МБ, содержащий ffmpeg, ffprobe, Rust-бинарник remotion и семь разделяемых библиотек FFmpeg. Поскольку Linux-сборка запускается на Linux, её можно просто спросить, что она такое.
Она отвечает собственной конфигурацией сборки: --enable-gpl --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-libmp3lame --enable-libopus на FFmpeg n7.1. Отсюда следуют четыре вещи, и каждую может проверить любой, у кого есть npm pack и пять минут.
- Все семь поставляемых библиотек сами сообщают о GPL. Запуск strings по libavcodec.so возвращает буквальную строку «libavcodec license: GPL version 2 or later», и то же верно для libavutil, libavformat, libavfilter, libavdevice, libswscale и libswresample. Это прямое следствие --enable-gpl вместе с libx264 и libx265, которые оба под GPL-2.0-or-later.
- FDK-AAC вкомпилирован и работает. ffmpeg -encoders показывает libfdk_aac как доступный AAC-энкодер, а библиотека несёт 43 символа Fraunhofer.
- Маркер nonfree отсутствует. Ни одна из поставляемых библиотек не содержит строки «nonfree», а строка лицензии читается как «GPL version 2 or later», а не «nonfree and unredistributable». Система сборки самого FFmpeg обычно отказывается принимать --enable-libfdk-aac без --enable-nonfree, поскольку лицензия Fraunhofer считается там несовместимой с GPL, и такие сборки явно помечаются как нераспространяемые.
- Rust-компоузитор динамически слинкован со всеми семью. ldd для бинарника remotion разрешает каждую библиотеку libav и libsw из той же директории пакета — то есть они являются слинкованной зависимостью поставляемого исполняемого файла, а не отдельным процессом на расстоянии вытянутой руки.
Пакет для macOS ARM64 идентичен по конфигурации — те же GPL-флаги, тот же FDK-AAC, тот же самоотчёт о лицензии и так же ни одного файла лицензии в архиве. Значит, это не одноплатформенная случайность упаковки, а сам рецепт сборки.
GPL-2.0 накладывает обязательства по доступности исходников и уведомлениям при распространении бинарников, и распространителем здесь выступает Remotion, а не пользователь. Пакеты npm, несущие всё это, не объявляют поля лицензии и не содержат текста лицензии вовсе. Мы формулируем это как вопрос к вендору, а не как доказанное нарушение: рецепта сборки FFmpeg в этом репозитории нет, у Remotion может быть соглашение с Fraunhofer, которого мы не видим, и ничто здесь не является юридической консультацией. Но любой компетентный SCA-прогон это поднимет, а корпоративный закупочный комплаенс остановится на этом задолго до того, как заметит инженер.
Находка 3 — дефолтный рендер выбирает именно этот энкодер
Именно это превращает предыдущую находку из мелочи упаковки в решение, которое каждый пользователь Remotion принимает, не зная об этом. В packages/renderer/src/options/audio-codec.tsx функция mapAudioCodecToFfmpegAudioCodecName возвращает «libfdk_aac» всякий раз, когда запрошенный аудиокодек — «aac». На сорок пять строк ниже таблица дефолтов задаёт для h264 сжатый режим «aac». При этом h264 — видеокодек по умолчанию, а compressed — режим аудио по умолчанию.
То есть обычный npx remotion render выдаёт MP4, аудио в котором закодировано Fraunhofer FDK-AAC. В бинарнике Remotion также присутствует собственный aac-энкодер FFmpeg — он вкомпилирован и вполне конкурентен по качеству на современных битрейтах. Изменение одного оператора return сняло бы весь вопрос ценой небольшой разницы в качестве на низких битрейтах. Это действительно дешёвая правка для действительно дорогой категории риска.
Находка 4 — bus factor равен единице
Из 4941 коммита за последние полгода 4056 — 82,1% — сделаны Джонни Бургером под двумя git-идентичностями. Первичное владение по файлам, свёрнутое из git log, отдаёт ему 3787 из 4664 проанализированных файлов, то есть 81%. Контрибьютор на втором месте владеет 433 файлами при 305 коммитах. Уход одного человека осиротил бы больше половины репозитория.
Островки знания — файлы с единственным автором и активным churn — здесь не периферийные утилиты. Это таймлайн Studio (TimelineExpandedSection.tsx с 55 коммитами, TimelineFieldRow.tsx с 54, TimelineSelection.tsx с 47) и слой схем в ядре (use-schema.ts с 44, wrap-in-schema.ts с 31). То есть поверхность интерактивного редактирования — самая новая стратегическая ставка продукта и та часть, которую конкуренту скопировать сложнее всего.
Здесь нужен контекст. За пять лет коммитили 441 человек, за последние полгода — 89, и это необычно здоровая воронка контрибьюторов для source-available продукта: репозиторий явно дружелюбен к внешним патчам. Но концентрация не размылась по мере роста проекта, и bus factor, равный единице, на кодовой базе такого размера — самая серьёзная организационная находка этого отчёта. Для сравнения: у HyperFrames — пять месяцев от роду, 57 контрибьюторов — этот показатель равен двум.
Часть III — в чём аудит ошибся
Аудит, который сообщает только о попаданиях, — это маркетинг. Четыре сигнала инструментов были отклонены или существенно скорректированы, и одно из отклонений обернулось комплиментом.
Список расходящихся клонов — это сгенерированный код, а единственный настоящий клон уже защищён. В топ-3 кандидатов на рефакторинг попали packages/google-fonts/src/KiwiMaru.ts, MPLUS1Code.ts и RocknRollOne.ts, помеченные как почти-дубликаты, расходящиеся в шестнадцатеричном литерале. Это три из 1852 машинно-сгенерированных модулей по одному на шрифт. Чистое ложное срабатывание. Но ручная проверка вытащила кое-что получше: packages/cli/src/list-of-remotion-packages.ts и packages/create-video/src/list-of-remotion-packages.ts — побайтово идентичные файлы по 104 строки в двух разных пакетах без импорта между ними, ровно тот паттерн расхождения сиблингов, который дал самую серьёзную находку в нашем аудите HyperFrames. Здесь он защищён машинно: packages/it-tests/src/monorepo/package-sync.test.ts перегенерирует оба файла из канонического списка пакетов в @remotion/studio-shared и проверяет их равенство. Remotion предусмотрел ровно тот класс отказа, который подвёл конкурента.
Покрытие документацией в 2% — ошибка категории. Аудит измерил 94 функции из 4938, несущие docstring, что даёт 5-й процентиль относительно аналогов. При этом Remotion поставляет более тысячи страниц текстовой документации, 1327 проиндексированных документационных чанков, 11 агентских скиллов и MCP-сервер, вся задача которого — отдавать эту документацию кодинг-агентам. Плотность docstring — не то же самое, что качество документации. Честная формулировка: Remotion документирует свой продукт исключительно хорошо, а свои внутренности — почти никак. Это защитимое распределение сил для source-available проекта с одним доминирующим мейнтейнером и реальная стоимость онбординга для контрибьюторов.
Соотношение тестов в 11% занижает реальный набор. Эта метрика считает исходниками 1852 сгенерированных модуля Google Fonts. Если исключить сгенерированные, документационные, шаблонные и примерные деревья, соотношение составит 815 тестовых файлов против 4774 исходных — 17%. Что важнее, 53 пакета несут test-скрипты, охватывающие юнит-тесты, серверный рендеринг, интеграцию шаблонов, Lambda, сквозные тесты на Playwright, визуальные эффекты и browser-Studio, — ничего этого соотношение по количеству файлов не видит. Тестирование Remotion действительно тоньше, чем 63% и 55 283 ассерта у HyperFrames, но это 17%, а не 11%, и стратегия здесь смещена в интеграционную сторону, а не в юнитовую.
230 находок безопасности — не уязвимости. Тиринг уверенности разрешает все 230 в гипотезы: ноль доказанных, ноль вероятных, каждая — статическое совпадение по паттерну без подтверждения потоком данных. Движок занижает категорию безопасности и прямо это помечает. Со сканером зависимостей история длиннее: сначала он отчитался о нуле CVE по 222 разрешённым зависимостям, работая с пустым кэшем бюллетеней, а это означает «не проверено», а не «чисто». Мы обновили кэш и перезапустили скан — результат в апдейте в конце статьи, включая дыру в покрытии, которая оказалась крупнее самих находок.
Часть IV — профиль качества
Remotion набирает 43 балла из 100 по шкале, откалиброванной под корпоративную продакшен-готовность, что ставит его в 46-й процентиль относительно корпуса из 64 проаудированных open-source инструментов разработки с медианой 44. Читать надо процентиль, а не букву: зрелые open-source проекты регулярно попадают в диапазон C–F по этой шкале, а категории куда информативнее агрегата.
Где Remotion действительно в высшей лиге: поддерживаемость в 75-м процентиле, доля мёртвого кода и доля сложных функций обе в благоприятном хвосте, магические числа на уровне 0,0 на тысячу строк и модулярность 0,590 при нуле циклов любого вида на монорепозитории в 1,25 миллиона строк и 124 пакета, который непрерывно выпускается с 2020 года. Вот эта последняя цифра и должна впечатлять. Отсутствие циклов на таком масштабе и в таком возрасте — редкость, и оно не случается само: нужно, чтобы кто-то тысячи раз за пять лет сказал «нет» удобному импорту.
Слабейшие категории при этом честные. Архитектура получает ноль не из-за циклов, а из-за 20 хотспотов связанности, 20 файлов низкой связности и уже разобранных двух точек сочленения. Документация получает 2 из 10 по причине, изложенной выше. Покрытие тестами на 8 из 15 отражает реальный разрыв с лучшими в классе даже после корректировки соотношения вверх.
Часть V — Remotion против HyperFrames, по цифрам
На прошлой неделе мы проаудировали HyperFrames от HeyGen тем же инструментом, и это впервые позволяет сравнить их корректно. Ни один из проектов не является строгим надмножеством другого, а цифры ниже — те, что выдерживают проверку.
| Remotion (217e07f) | HyperFrames (89e970f) | |
|---|---|---|
| Возраст на момент аудита | 5 лет 1 месяц | 4 месяца 3 недели |
| Проиндексировано строк | 1 253 947 | 823 426 |
| Символов / ссылок | 61 239 / 266 826 | 69 869 / 312 603 |
| Рёбер вызовов / сообществ / модулярность | 108 964 / 4146 / 0,590 | 96 369 / 4048 / 0,534 |
| Циклы функций и файлов | 0 / 0 | 0 / 0 |
| Модель авторинга | React-компоненты | Обычный HTML с data-* атрибутами |
| Стратегия детерминизма | Протокол готовности (delayRender) | Виртуальное время (заморозка часов) |
| Путь кодирования | Rust-компоузитор + вшитый кастомный FFmpeg | Системный FFmpeg |
| Агентская поверхность | MCP-сервер + 11 скиллов / 6831 строка | 19 скиллов / 39 534 строки |
| Соотношение тестов к исходникам | 17% (скорректировано) | 63% |
| Bus factor | 1 | 2 |
| Лицензия | Source-available, платно свыше 3 сотрудников | Apache-2.0 |
| Согласованность лицензионных метаданных | 9 строк; 53 из 72 архивов без текста лицензии | 1 из 14 манифестов объявляет лицензию |
| Процентиль среди аналогов | 46-й | 93-й |
Remotion — пятилетняя платформа с четырьмя поверхностями рендера, четырьмя языковыми клиентами, написанным с нуля TypeScript-демультиплексором и редактором, который переписывает ваш исходный код. HyperFrames — четырёхмесячный рендерер с заметно лучшей тестовой дисциплиной, более узким тезисом и разрешительной лицензией. Строки, которые решают закупочные споры, — последние три. И показательно, что оба проекта независимо друг от друга публикуют пакеты, чьи лицензионные метаданные противоречат их же лицензионному замыслу.
Вердикт
Remotion — серьёзный и долгоживущий инженерный артефакт, и буквенная оценка здесь наименее информативная вещь во всём отчёте. Ноль циклов на 1,25 миллиона строк и пять лет, две точки сочленения в графе из 108 964 рёбер, ни одной подтверждённой уязвимости, ни одного критического или высокого хотспота техдолга и написанный с нуля медиапарсер, уже ставший центром тяжести кодовой базы. Рендерер работает, архитектура держит, а то, что в агрегированной оценке выглядит тревожно, при разборе оказывается осознанными компромиссами.
Настоящие риски лежат вне кода, который рендерит видео. Лицензионные метаданные противоречат лицензии девятью разными способами, и 53 из 72 опубликованных архивов уходят вообще без текста лицензии — для source-available бизнеса это правка с наивысшей отдачей и одна из самых дешёвых. Готовые бинарники кодирования сообщают о GPL-2.0-or-later и несут кодек, который FFmpeg помечает как нераспространяемый, а дефолтный путь рендера именно его и выбирает — внутри npm-пакетов, не объявляющих никакой лицензии. И bus factor равен единице, сконцентрированной на самой новой и стратегически важной поверхности.
Командам, которые оценивают Remotion: технология продакшен-готова и имеет за спиной пять лет доказательств — больше, чем может предъявить сегодня любой конкурент. Заложите реальное время на лицензионный разговор. Не потому, что лицензия Remotion непонятна — она как раз необычно понятна, — а потому что непонятны его опубликованные метаданные, и потому что бинарники кодирования несут условия, которые ваш сканер поднимет задолго до вашего инженера. И то и другое чинится за неделю. И то и другое сегодня не починено.
Методология. Каждая цифра в этой статье вычислена Code Indexer по репозиторию на коммите 217e07f — 61 239 символов, 266 826 ссылок, 108 964 ребра вызовов, 4146 сообществ — или измерена напрямую через git. Находки с 1 по 5 воспроизведены руками: перечислены манифесты по 124 директориям пакетов, опрошен живой реестр npm на v4.0.503, скачаны и просмотрены архивы, запущены опубликованные бинарники FFmpeg для отчёта о собственной конфигурации сборки и списке энкодеров, выполнен ldd для проверки линковки, открыты диапазоны строк в исходниках и прослежены все места вызова libfdk_aac. Четыре сигнала инструментов отклонены или скорректированы и задокументированы выше. Поведение фреймворка в рантайме лежит за границей этого анализа, и ничто здесь не является юридической консультацией.
Апдейт — скан зависимостей, перезапущенный как надо
После публикации мы обновили кэш бюллетеней — 276 882 записи OSV по npm, PyPI, crates.io, Maven, Packagist, Go и RubyGems — и перезапустили скан зависимостей Remotion. Он вернул 13 находок, все низкой критичности: ноль критических, ноль высоких, ноль средних. Для репозитория такого размера это чистый результат.
Прямыми зависимостями публикуемого пакета являются только две, и это одна и та же библиотека. packages/lambda-php/composer.json объявляет aws/aws-sdk-php как ^3.269 — диапазон, который разрешился бы в актуальную версию, — но composer.lock фиксирует 3.322.5, примерно на сорок девять минорных версий позади, и несёт advisory про инъекцию в policy-документ CloudFront (исправлено в 3.371.4) и проблему key commitment в S3-клиенте шифрования (CVE-2025-14761, исправлено в 3.368.0). composer update закрывает обе. Остальные находки живут в example-пакетах: старый aws-sdk-go в Go-примере, golang.org/x/net и x/text ниже исправленных версий, гем json 2.16.0 и два advisory на Rust-крейты из lock-файла компоузитора, одно из которых — просто уведомление о том, что крейт не поддерживается.
Интереснее то, чего скан не увидел. Он разобрал восемь lock-файлов — один Cargo.lock, два composer.lock, два go.sum, два Gemfile.lock и один requirements.txt — и ни один из них не был JavaScript. Для монорепозитория, состоящего на 64% из TypeScript и на 27% из TSX, скан зависимостей покрыл Rust-компоузитор и Lambda-клиенты на PHP, Go, Ruby и Python — и не покрыл сам продукт.
Причина в том, что Remotion фиксирует зависимости через Bun. Его единственный JS lock-файл — bun.lock на 3,9 МБ в корне репозитория, а bun.lock не входит в поддерживаемый набор сканера, который покрывает package-lock.json, npm-shrinkwrap.json, yarn.lock в классическом и berry-форматах и pnpm-lock.yaml. Никакого другого JS lock-файла в дереве нет, откатываться было не на что. Это не специфика Remotion — HyperFrames тоже фиксируется через Bun. JavaScript-дерево зависимостей обоих проектов этой серии невидимо для сканера, и ни один из аудитов не следует читать как покрывающий его. Прямо: мы не делаем никаких утверждений про CVE в npm-зависимостях Remotion, потому что их никто не проверял.
Обобщаемый урок здесь инструментальный, и он выходит далеко за рамки обоих проектов. Сканер зависимостей, который молча отрабатывает на восьми распознанных lock-файлах и игнорирует единственный, содержащий основную экосистему проекта, выдаёт результат, читающийся как покрытие, но покрытием не являющийся. Отсюда две правки. Первая: научиться парсить bun.lock — это документированный текстовый формат с разрешёнными версиями и хешами целостности, то есть задача на парсер, а не на исследование, а доля Bun среди новых JS-репозиториев делает это самым ценным пробелом по экосистемам. Вторая: сообщать покрытие рядом с находками — если у доминирующего языка проекта нет ни одного разобранного lock-файла, это должно попадать в вывод предупреждением, а не тишиной. Нулевой результат обязан нести знаменатель, по которому он посчитан.
Исправление, 1 августа 2026. В более ранней версии статьи утверждалось, что учёт использования у Remotion везде закрыт лицензионным ключом, с формулировкой «нет ключа — нет запроса, это лицензионный учёт, а не слежка». Вывод был сделан только по серверному обработчику и неверен для @remotion/web-renderer, который в браузере отправляет событие безусловно и передаёт origin страницы. Абзац выше исправлен. Ошибка наша: файл браузерного пути был в выдаче нашего же поиска, и мы его не открыли. Спасибо независимому аудиту, который это поймал.
📚 Источники и ссылки
| # | Source | Link |
|---|---|---|
| [1] | Remotion — make videos programmatically with React |
|
| [2] | Code Indexer — semantic code search and analysis engine |
|