Kadr: видеоредактор, в котором Claude Code монтирует рядом с тобой. Claude.. Claude. claude code.. Claude. claude code. electron.. Claude. claude code. electron. kadr.. Claude. claude code. electron. kadr. Open source.. Claude. claude code. electron. kadr. Open source. TypeScript.. Claude. claude code. electron. kadr. Open source. TypeScript. видео.. Claude. claude code. electron. kadr. Open source. TypeScript. видео. видеомонтаж.. Claude. claude code. electron. kadr. Open source. TypeScript. видео. видеомонтаж. видеоредактор.. Claude. claude code. electron. kadr. Open source. TypeScript. видео. видеомонтаж. видеоредактор. искусственный интеллект.. Claude. claude code. electron. kadr. Open source. TypeScript. видео. видеомонтаж. видеоредактор. искусственный интеллект. Работа с видео.

Я задался вопросом: как должен выглядеть видеоредактор, если проектировать его в эпоху ИИ-агентов — не «редактор плюс кнопка с нейросетью», а инструмент, где агент работает на том же таймлайне, теми же операциями, что и человек? Ответа не нашлось, и я начал писать свой.

Так появился Kadr — открытый (GPL-3.0) многодорожечный видеоредактор на Electron + React + TypeScript: GPU-композитинг на WebGL2, WYSIWYG-экспорт, локальное распознавание речи, программируемая моушн-графика на React — и настоящая интерактивная сессия Claude Code в панели рядом с превью, подключённая к живому проекту через MCP.

В статье — что редактор умеет и какие технические решения под этим лежат: почему пришлось выбросить аппаратный кодировщик Chromium, как кадры доезжают до ffmpeg без единого копирования, зачем нужен ping-pong из FBO для эффектов и как убедить Whisper не сочинять субтитры из тишины.

Kadr: реальный проект на таймлайне — 10 дорожек, 99 клипов, 11 минут

Kadr: реальный проект на таймлайне — 10 дорожек, 99 клипов, 11 минут

Зачем ещё один видеоредактор

Мне нужен был монтаж для YouTube-канала, а из инструментов на Linux ничего не подходило: где-то нет GPU-эффектов, где-то экспорт живёт своей жизнью и не совпадает с превью, и нигде агент не может посмотреть на проект и что-то в нём сделать. А мне хотелось ровно этого: сказать «добавь сюда анимированные субтитры» — и смотреть, как они появляются в превью.

Отсюда два принципа, из которых выросла вся архитектура:

  1. Превью и есть рендер. Один и тот же WebGL2-композитор рисует и предпросмотр, и офлайн-кадры экспорта. Что видишь — то и получаешь, пиксель в пиксель.

  2. Всё, что может сделать человек мышью, агент может сделать через API. Каждая операция редактора доступна из window.kadrEditor, и каждая правка агента попадает в общую историю отмен.

Что умеет редактор

Коротко, чтобы дальше говорить о технике (полный разбор — в FEATURES.md, демо — в видео):

  • многодорожечный монтаж: трим, луп, фейды, связанные AV-клипы, скорость ×0.02–×100 с прилипанием, реверс клипа, полная история отмен;

  • ключевые кадры на всём — позиция, масштаб, поворот, прозрачность, громкость, маски — с изингами в стиле After Effects;

  • настоящее 3D с перспективно-корректными текстурами, «полёт камеры» целой дорожкой;

  • маски до 8 фигур с растушёвкой, эффекты (дымное сияние, размытие), 26 GPU-переходов;

  • локальный faster-whisper с пословными таймкодами, авто-субтитры с караоке-подсветкой;

  • Remotion-фрагменты: моушн-графика на React/TSX как обычные клипы;

  • импорт отовсюду: drag-and-drop в любое место окна, картинки прямо из браузера, Ctrl+V из буфера, XDG-порталы;

  • экспорт: ffmpeg x264 с честным битрейтом, motion blur, frame blending, пресеты YouTube/Shorts/WebM/MP3.

И — встроенный Claude Code, о котором ниже.

GPU-композитор: превью == рендер

Сердце редактора — квадовый композитор на WebGL2 (src/gl/compositor.ts). Каждый клип — текстурированный квад: перспективная проекция для 3D-наклонов, до 8 фигурных масок с растушёвкой в шейдере, кэш текстур по ключу источника. Функция drawFrame — чистая: ей отдают список слоёв на момент времени t, она рисует кадр. Превью вызывает её из rAF-цикла, экспорт — из офлайн-цикла с точным степпингом (k+0.5)/fps. Разойтись им негде — это один и тот же код.

Эффекты устроены как цепочка FBO. Слой рендерится в полноразмерный fx-буфер; гауссово размытие проходит горизонталь→вертикаль ping-pong’ом; для сияния альфа-канал слоя спирально размывается в поле четвертного разрешения, а композитный шейдер строит из него дымный ореол: fbm-шум рвёт край на «щупальца», дым дрейфует наружу вдоль градиента поля, круглые мерцающие искры даёт клеточный хэш. Часы эффекта привязаны к локальному времени клипа, поэтому в превью и в экспорте картинка идентична вплоть до положения искр.

Эффект «Внешнее сияние»: параметры в инспекторе, результат в превью

Эффект «Внешнее сияние»: параметры в инспекторе, результат в превью

Переходы — в стиле Vegas: просто перекройте два клипа на одной дорожке, длительность перехода равна перекрытию. A и B рендерятся в два FBO и смешиваются шейдером из реестра gl-transitions. Отдельный класс — «торцевые» переходы (вип-паны, зум-блюры, RGB-расхождение, глитч): это эффект одного клипа с пиком интенсивности в точке склейки, как в BorisFX.

Экспорт: как Chromium игнорировал битрейт и что с этим делать

Самая поучительная часть проекта. Изначально видео кодировалось через WebCodecs — красивый современный API. Но на чистом статичном 1080p Chromium-энкодер (программный OpenH264) стабильно выдавал ~0,7 Мбит/с независимо от запрошенного битрейта. Запрашиваешь 12 — получаешь 0,7 и «мыло». Это не баг конфигурации: параметр битрейта просто игнорируется.

Я измерил честно: SSIM относительно исходника — 0,9963 у WebCodecs против 0,9996 у ffmpeg x264 на той же скорости (±15%). Вывод очевиден, но возникла проблема транспорта: как отдавать сырые кадры из рендерера в ffmpeg? Кадр 1080p RGBA — это ~8 МБ, при 60 fps — полгигабайта в секунду.

Оказалось, что любой штатный канал Electron — IPC, contextBridge, даже WebSocket на localhost — копирует буфер и упирается в 350–850 МБ/с, утраивая время экспорта. Решение: contextIsolation: false (осознанный компромисс — приложение не грузит произвольный веб), и тогда preload-скрипт живёт в одном V8-контексте с рендерером. Preload сам спавнит ffmpeg (rawvideo rgba на stdin, vflip + out_color_matrix=bt709, libx264 с VBV под битрейт пресета), а буферы readPixels передаются по ссылке, без сериализации. Двойная буферизация: слот переиспользуется только после того, как stdin ffmpeg принял кадр.

Декодирование на экспорте тоже своё: mp4box демуксит контейнер, WebCodecs VideoDecoder декодирует последовательно с упреждающим чтением — выходит ~8× быстрее, чем гонять <video>-элемент перемотками. Учтён edit list (elst) со сдвигом cts; потоки без цветовых тегов перезаворачиваются с эвристикой HD→bt709, SD→bt601. Любой сбой — нет moov в первых 16 МБ, неподдерживаемый кодек — и клип прозрачно откатывается на элементные перемотки.

Поверх этого два «кинематографических» флажка, оба включены по умолчанию:

  • Motion blur: 180° затвор, 8 суб-кадровых композитов, усреднённых на GPU. Декод не трогается — суб-сэмплы переиспользуют кадры источника, движутся только трансформации, маски и переходы.

  • Frame blending: если источник не заполняет fps проекта (25-fps клип в 60-fps проекте, замедление), следующий кадр источника подмешивается с весом по фазе времени сэмпла. Источники с достаточным fps не трогаются — их экспорт остаётся бит-в-бит.

Аудио собирает системный ffmpeg одним фильтр-графом: на каждый сегмент volume,atempo*,afade,adelay,apad,atrim, затем amix с точной компенсацией уровня (все потоки паддятся до полной длительности, чтобы масштаб amix был константой и сокращался обратным volume). Ускорение любой кратности — цепочка atempo (каждый чейн держит 0,5–2×, дальше перемножаем).

Диалог экспорта: пресеты, motion blur, frame blending

Диалог экспорта: пресеты, motion blur, frame blending

Кстати, о скорости клипов: превью честно ограничено — Chromium бросает исключение при playbackRate вне жёсткого диапазона [0.0625, 16], поэтому плеер клампит значение и добирает точность ресинками, а вот экспорт точен на любой скорости. Мелочь, которую не найдёшь, пока ×50 не уронит весь rAF-цикл.

Встроенный Claude Code

Кнопка 🤖 открывает панель с xterm.js, в которой главный процесс спавнит обычный CLI claude пользователя в PTY (node-pty): та же авторизация, тот же интерактивный TUI, только внутри редактора. Никакого «обёрнутого API» — это полноценный Claude Code со всеми его инструментами.

Связь с проектом — через MCP. При старте сессии редактор поднимает HTTP-мост на 127.0.0.1 (POST /evalwebContents.executeJavaScript) и генерирует --mcp-config со stdio-сервером, который даёт агенту пять инструментов:

Инструмент

Что делает

kadr_state

весь живой проект: дорожки, клипы, пути медиа, транскрипты

kadr_eval

выполнить JS против API редактора; каждая правка — в истории отмен

kadr_export

отрендерить проект или диапазон и дождаться файла

kadr_transcribe

локальный Whisper по файлу или диапазону таймлайна

kadr_fragment_create

создать Remotion-композицию как клип таймлайна

На скриншоте — реальная сессия: я спросил, что сейчас на таймлайне, агент вызвал kadr_state, увидел, что стейт большой, сам догадался прогнать его через jq и выдал сводку — попутно заметив, что у меня две дорожки называются A7, и предложив навести порядок:

Сессия Claude Code внутри редактора: агент читает проект через MCP

Сессия Claude Code внутри редактора: агент читает проект через MCP

Самое неожиданное в этой интеграции — не MCP, а гигиена процессов. Дерево, порождённое из Electron, наследует его файловые дескрипторы, включая слушающий сокет отладочного порта Chromium. Если сессия claude переживёт жёсткую смерть редактора, следующий запуск не сможет забиндить порт — и виноватого не найти. Поэтому claude запускается exec’ом внутри bash-обёртки со сторожем, убивающим свою группу процессов, если Electron умер; закрытие панели шлёт группе SIGHUP с эскалацией до SIGKILL; при старте приложение выметает осиротевшие группы по сигнатурам в cmdline; а window-all-closed форс-экситит процесс через 2,5 секунды, чтобы зависший экспорт не оставил безоконного зомби.

Remotion-фрагменты: моушн-графика без рендеров

Программируемая графика — клипы типа remotion: React/TSX-композиции в общем workspace с vite dev-сервером. В превью фрагмент живёт как iframe с @remotion/player поверх GL-канвы, синхронизированный с часами редактора через postMessage — причём рассинхрон корректируется подталкиванием playbackRate (±8%), а не перемотками: жёсткие seek’и заставляли движущийся текст видимо заикаться.

Главный цикл работы с агентом: Claude создаёт фрагмент через kadr_fragment_create, правит его TSX обычными файловыми инструментами — vite горячо перезагружает превью за ~2 секунды. Ни одного рендера во время итераций. Рендер ровно один — при экспорте: remotion render на хэш содержимого (кэшируется), в почти-лосслесс качестве, потому что этот интермедиат ещё пройдёт финальный энкод. Тут тоже пришлось мерить: Remotion по умолчанию гонит кадры через JPEG q80, а vp8 «мылил» текст субтитров — PSNR текстовой области 28,0 дБ против 32,6 у vp9 с alpha при --crf=12.

Если на фрагмент навесить GL-фичи — маски, 3D, переходы, сияние — он автоматически переключается в режим пиксельного захвата: скрытое offscreen-окно рендерит ту же композицию, его BGRA-кадры стримятся в композитор как текстура, и фрагмент становится обычным видеослоем.

На фрагментах построены и авто-субтитры: транскрипция диапазона → пословные таймкоды → генерация прозрачного caption-фрагмента с караоке-подсветкой и анимациями появления. Сгенерированный TSX остаётся редактируемым — руками или агентом.

Локальный Whisper и борьба с галлюцинациями

Распознавание речи — faster-whisper (large-v3, int8), полностью локально, ~2× реального времени на CPU. Аудио для транскрипции готовит тот же экспортный микшер — агент слышит ровно то, что услышит зритель (WYSIWYG и здесь).

Whisper знаменит тем, что в тишине и шуме сочиняет текст. Против этого — VAD, condition_on_previous_text=False, пороги и пост-фильтры, а контракт закреплён тестом: синусоида 440 Гц обязана дать ноль реплик. Пословные таймкоды позволяют перекраивать реплики в группы по N слов без потери точности: балансировка групп (7 слов → 3+2+2, без «сирот»), разрывы по концам предложений и паузам >0,6 с, склейка продолжений («мини» + «-программами»).

Тесты, которые водят настоящее приложение

Юнит-тесты для видеоредактора почти бесполезны: всё интересное происходит на стыке GL, медиа-элементов, IPC и ffmpeg. Поэтому e2e-сьюты (их за тридцать) подключаются к работающему приложению по Chrome DevTools Protocol и работают как пользователь: двигают клипы реальными событиями мыши, роняют файлы через Input.dispatchDragEvent, проверяют PSNR экспорта против эталона, каденс frame blending по подсчёту дубликатов кадров и тот самый антигаллюцинационный контракт транскрипции.

Из CDP-специфики: awaitPromise ненадёжен под GC — асинхронные результаты паркуются в глобалы и опрашиваются; xclip -i форкает демона, наследующего stdio, и execFileSync виснет навсегда, если не редиректнуть вывод. Такие вещи узнаёшь только на живом прогоне.

Что дальше

  • MCP-сервер наружу — чтобы внешние агенты (не только встроенная сессия) управляли редактором;

  • шаблоны сцен и библиотека фрагментов;

  • больше GPU-эффектов.

Проект открытый на Гитхабе, GPL-3.0. А вживую всё показано в видео-демо:

Запуск — три команды (нужны Node ≥ 20 и ffmpeg в PATH):

git clone https://github.com/HelpFreedom/kadr.git && cd kadr
npm install   # postinstall пересоберёт node-pty под Electron
npm run dev

Панель 🤖 опциональна — без CLI claude это просто быстрый GPU-редактор. Для распознавания речи — pip install faster-whisper.

Буду рад вопросам про архитектуру, замечаниям и issue. Если у вас есть идеи, каким должен быть монтаж в паре с агентом, — обсудим в комментариях.

Автор: Black_Triangle_Habr

Источник