В предыдущей статье я подробно описал, как мы пришли к идее рендеринга кадров WebGL нашего музыкального визуализатора в веб-браузере и отправки их на сервер для сборки в финальное видео. В этой статье я буду обсуждать ранний прогресс моего исследования этого подхода.

Настройка хоста

Изначально мы планировали использовать Amazon Web Services для хостинга, но остановились на Google Cloud, так как отсутствует плата за пропускную способность на входе. Через сокет к Node будет передаваться много кадров, так что это будет настоящей проблемой для нас на AWS. Как минимум, это повлияет на нашу модель ценообразования.

Итак, я запустил виртуальную машину Bitnami со стеком Node.js. Если вы выберете самый маленький диск (10 ГБ) и самую маленькую машину (f1-micro: 1vCPU, 0,6 ГБ ОЗУ), работа обойдется вам в колоссальные 4,95 доллара США. Я уже был знаком с консолью, которая выгодно отличается от AWS практически во всех деталях, после того как недавно перенес мои корпоративные и личные сайты из Godaddy в Google Cloud на этой настройке. Они превосходны по сравнению с моим предыдущим провайдером Godaddy, а их размещение стоит в два раза меньше. Кроме того, я могу делать снимки ВМ в любой момент, чтобы убедиться, что если что-то пойдет не так на сервере (например, ложный sudo rm -rf, выполненный в неправильной папке), я смогу вернуться к здравомыслию одним нажатием кнопки. В Google даже есть калькулятор совокупной стоимости владения, который показывает, сколько будет стоить ваша установка, если вы развернете ее на AWS. Большинство конфигураций приближаются к трети цены.

Создание микросервиса

Node.js — естественный выбор для микросервисов, когда они разрабатываются рука об руку с клиентом HTML5 (особенно если один и тот же разработчик работает над обоими в роли «полного стека»). Написание Javascript в обоих местах снижает когнитивные трения, связанные с переключением внимания между клиентским кодом и серверным кодом.

А поскольку наш клиент построен на PureMVC, микросервисам будет еще удобнее использовать ту же архитектуру. PureMVC носит предписывающий характер, поэтому, увидев одно приложение PureMVC, вы увидите их все. И настройка одного требует очень мало усилий. По этой причине я не буду описывать структуру микросервиса, а только важные элементы, которые необходимо будет реализовать. Единственное дополнительное замечание по PureMVC — это указание на пакет npmvc для Node.js, который прекрасно работает.

Как мне создать видео?

Я вернулся к совету I из прошлого поста, который убедил меня, что это можно легко сделать в Node, итогом которого было:

Чтобы преобразовать последовательности кадров в видеофайлы, либо используйте последовательность в качестве источника в Adobe Media Encoder, либо используйте инструмент командной строки, такой как ffmpeg или avconv.

Поэтому я исследовал их в первую очередь. И, честно говоря, я был слишком взвинчен, чтобы смотреть на других, как только закончил.

Первый существует с 2000 года и в целом реализовал все функции второго. Как ни странно, avconv на самом деле является LibAv, злобным ответвлением ffmpeg, поддерживаемым группой разработчиков, которые взяли на себя руководство проектом ffmpeg. Как сообщается, они повторно реализовали, иногда с большими затратами для своего фонда, почти все функции, впоследствии представленные ffmpeg, самостоятельно (обычно для этого вводили новый язык API), а не объединяли изменения ffmpeg, даже когда нет конфликта. В свою очередь, ffmpeg добавил псевдонимы, чтобы пользователи могли использовать API ffmpeg или avconv/LibAv. "Так". "Много". Драма.

Возможно, есть лучший выбор, чем любой из этих двух противоборствующих, но, по-видимому, эквивалентных проектов, но в конце концов я выбрал ffmpeg, потому что он хорошо поддерживается пакетами npm, и он использовался в примере с советом из предыдущего поста. Быстрый прогресс, которого я добился, когда начал работать с ffmpeg, меня порадовал.

Установка FFMpeg

Как я уже упоминал, существует ряд пакетов npm, использующих ffmpeg, но первый, который я предлагаю изучить, это @ffmpeg-installer/ffmpeg.

Как только вы добавите этот модуль в свой проект, он автоматически установит соответствующий исполняемый файл ffmpeg для вашей системы прямо в папку node_modules вашего проекта. Это правильный и правильный подход, так как локально мне нужен исполняемый файл Darwin для моего Macbook Air, но на той виртуальной машине Google Cloud, которую я засидел, я запускаю Debian.

Это было просто. Теперь, как мне его использовать?

Взглянув на документацию, вы можете увидеть, что там прямо-таки безумное количество вариантов. Командные строки могут быть довольно длинными, указывая миллионы параметров для видео, изображений, аудио и сложной фильтрации. Зоуи, это полноценная штука. Парализует так. Поэтому рекомендую сразу не зарывать голову в документацию. Вместо этого взгляните на пакет fluent-ffmpeg. Это самый простой способ поговорить с ним из Node, который я нашел. Пожалуйста, напишите в комментариях, если у вас есть другие фавориты, которыми вы хотели бы поделиться.

Fluent API — это интерфейсы, в которых каждый вызов метода возвращает объект, который является владельцем этого метода. Во многих языках это позволяет вам связать ряд вызовов методов в одном операторе, таком как конвейер, вместо того, чтобы писать столько отдельных операторов, каждый из которых вызывает метод для объекта. Этот подход хорошо подходит для таких задач, как настройка множества параметров команды ffmpeg.

Шаг 1

Получите команду ffmpeg для установки всех этих параметров:

var ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
var ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);
var command = ffmpeg();

Шаг 2

Определите, какие опции вам нужно предоставить. FFMpeg довольно умно выясняет, как «делать правильные вещи» с как можно меньшим направлением. Например, вы можете предоставить ему ширину и/или высоту и/или соотношение сторон как для ввода, так и для вывода. Но если вы предоставите ему входные данные того же размера, что и предполагаемый выход, вы можете вообще пропустить параметры изменения размера.

Какой размер и соотношение сторон следует использовать?

Мы ориентируемся в первую очередь на YouTube, и наша целевая аудитория также хочет использовать Full HD, поэтому быстрый взгляд на рекомендации Google говорит нам, что видео 1080p должно иметь размер 1920x1080. Итак, для моего первого эксперимента я создал серию из 8 помеченных PNG-файлов с разными цветами переднего плана и фона и соответствующего размера.

Как насчет частоты кадров?

Выходная частота кадров: совет Google по этому поводу — выбрать ту же частоту кадров, с которой был записан ввод. У меня есть полный контроль над этим, как в этом эксперименте, так и в клиенте, который скоро будет отображать сумасшествие WebGL и передавать его этому микросервису. Промышленный стандарт для кино — 24 (48 для замедленного воспроизведения), а в видео вы видите 24, 25 и 30. Я выбрал 30 кадров в секунду, потому что это примерно половина эффективной скорости, которую я видел в браузере (60). и потому что это будет самый плавный из трех.

Входная частота кадров: в этом случае я просто хочу взять эти 8 кадров и показать их каждый в течение примерно 5 секунд, выводя видео. Поскольку скорость выражается в кадрах в секунду, мне пришлось указать входную частоту кадров как обратную 5 в секунду, 1/5.

Входные файлы. Здесь я использовал выражение последовательности файлов, в котором я могу указать количество десятичных разрядов, которое должно быть заменено целым числом, дополненным нулями. Я решил пронумеровать кадры как 001–008, поэтому в этой части имени файла я использовал %03d, используя стандартный формат printf.

Выходной файл. Опять же, я последовал совету Google и выбрал формат mp4 без аудиокодека для этого эксперимента. На данный момент я не стал заглядывать в видеокодек, потому что мне это было не нужно.

Шаг 3

Настройте команду с параметрами и запустите ее.

// Use FFMpeg to create a video.
// 8 consecutive frames, held for 5 seconds each, 30fps output, no audio
command
    .input('assets/demo1/Sinewave3-1920x1080_%03d.png')
    .inputFPS(1/5)
    .output('assets/demo1/Sinewave3-1920x1080.mp4')
    .outputFPS(30)
    .noAudio()
    .run();

Он создал файл, я закинул его в браузер, и он заиграл. Ура! Я сделал видео. Однако довольно скучно, так как в нем не было звука, а были просто разноцветные слайды с одним и тем же словом. Я не стал загружать его на YouTube и уж точно не собираюсь утомлять вас этим здесь.

Добавление аудио, водяных знаков и отчетов о проделанной работе

Прежде чем загружать что-либо, я хотел убедиться, что могу добавить звук. И вдобавок я хотел протестировать водяные знаки на видео, что мы и сделаем в поддержку бизнес-модели фримиум. Наконец, процесс займет время и может завершиться неудачей, поэтому я хотел узнать, как справиться с этим во втором эксперименте.

Первое видео длилось 40 секунд. Где я мог найти около 40 секунд аудио? Так случилось, что у меня есть пригоршня лапши на сайте Propellerhead Allihoopa, и все они имеют примерно такую ​​длину, потому что их мобильное приложение Рисунок создает 8-тактные частушки для загрузки и ремикширования другими. Сайт воспроизводит их дважды, и с учетом разумного BPM это обычно составляет от 30 до 40 секунд.

Трек, который я выбрал, был длиной 35 секунд, поэтому я использовал 6 кадров изображения, удерживая их на экране по 6 секунд каждый, оставляя мне секунду тишины в конце. Я рассматривал их перекрестное затухание, но сложность, связанная с созданием такой команды, выходила за рамки того, что я пытался сделать. Просто обратите внимание на сложность выражения видеофильтра, которое мне пришлось использовать, чтобы сделать водяные знаки в коде ниже. В качестве водяного знака я поместил свой логотип Sea of ​​Arrows в нижнюю левую часть видео.

var ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
var ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);
var command = ffmpeg();
var timemark = null;
// 6 consecutive 1920x1080 frames, held 6 seconds each, 30fps, with m4a audio, watermarked
command
 .on('end', onEnd )
 .on('progress', onProgress)
 .on('error', onError)
 .input('assets/demo2/folds-of-spacetime_%03d.png')
 .inputFPS(1/6)
 .videoFilter(["movie=assets/demo2/soa-watermark.png [watermark]; [in][watermark] overlay=10:main_h-overlay_h-10 [out]"])
 .input('assets/demo2/folds-of-spacetime.m4a')
 .output('assets/demo2/folds-of-spacetime.mp4')
 .outputFPS(30)
 .run();
function onProgress(progress){
 if (progress.timemark != timemark) {
 timemark = progress.timemark;
 console.log('Time mark: ' + timemark + "...");
}
function onError(err, stdout, stderr) {
 console.log('Cannot process video: ' + err.message);
}
function onEnd() {
 console.log('Finished processing');
}

Выполнение этого дает следующий результат:

Временная метка: 00:00:04.26...
Временная метка: 00:00:10.26...
Временная метка: 00:00:10.28...
Метка времени: 00:00:16.26...
Метка времени: 00:00:22.26. ..
Метка времени: 00:00:28.26...
Метка времени: 00:00:34.20...
Отметка времени: 00:00:35.86...
Завершена обработка

Процесс завершен с кодом выхода 0

Выходное видео без проблем загружается на YouTube и воспроизводится в формате Full HD. Бада Бинг.

Следующие шаги

  1. Мне нужно создать сокет в микросервисе и подключиться к нему с помощью telnet и начать разговор.
  2. Выясните, каким будет протокол для загрузки изображений и аудио.
  3. Затем мне нужно отправить несколько изображений через сокет из браузера. Я, вероятно, захочу создать рабочий веб-процесс для этого, чтобы цикл рендеринга в визуализаторе не замедлялся из-за связи через сокет. Я могу сделать POC вместо того, чтобы вставлять это в наш клиент с первого раза, чтобы я мог протестировать закадровый рендеринг. У клиента может не хватить разрешения для рендеринга сцены в 1080p, и в любом случае, я подозреваю, что эффективнее будет рендерить его вне экрана и просто сообщать о прогрессе в графическом интерфейсе.

Я сообщу снова после следующей важной вехи.

Примечание автора.Эта статья является частью серии, в которой мой партнер и я тестируем разработку нашего продукта "на открытом воздухе".

Это диаметрально противоположно моему типичному подходу «работы скунса», заключающемуся в том, чтобы месяцами сидеть взаперти в бессмысленных попытках сохранить в секрете то, что в конечном итоге все равно станет достоянием общественности. Мы не строим ничего ошеломляющего, изменяющего парадигму или строящего империю. Просто что-то классное, что служит нише, которую мы знаем и заинтересованы в помощи. Это визуализатор 3D-музыки, созданный на HTML5/WebGL с использованием Three.js, PureMVC, React и Node.js. Когда мы закончим, вы сможете создать классное видео для своей аудиодорожки и загрузить его на YouTube.

Преимущество ведения блога об этом в том, что у нас есть возможность передать некоторые из наших мыслительных процессов, когда мы преодолеваем препятствия и выбоины, разбросанные по нашему пути. Изложение этих мыслей, пока они еще свежи в памяти, может направить кого-то другого на тот же путь. Если мы потерпим неудачу из-за этих решений, возможно, это поможет вам избежать собственного дымящегося кратера. В любом случае, позже мы будем заняты погоней за разными белками в каком-нибудь другом парке.

The previous article in this series is: Should I Render Three.js to Video on the Client or Server?
The next article in this series is: Persistent Connections with Node.js and Socket.io
This article has been reblogged at the following sites:
DZone: http://bit.ly/create-video-with-nodejs