Для начала необходимо запустить сервер:
$ nexrender-server --port=3050 --secret=myapisecret
А затем запустить сколько угодно воркеров на удаленных машинах, где будет происходить рендеринг, указав адрес сервера, который будет раздавать им работу:
$ nexrender-worker --host=http://127.0.0.1:3050 --secret=myapisecret
Теперь мы готовы создавать работы в формализованном виде и передавать их серверу.
const { createClient } = require('@nexrender/api')
const client = createClient({
host: 'http://127.0.0.1:3050',
secret: 'myapisecret',
})
После инициализации клиента мы можем добавлять работу на сервер, передав объект с параметрами рендеринга. Для мониторинга хода выполнения работы навесим соответствующие обработчики событий.
async function StartJob(project)
{
const result = await client.addJob(JSON.parse(project.template))
result.on('created', async job => {
project.job = job.uid
await project.save()
})
result.on('started', async job => {
project.status = 1
await project.save()
})
result.on('finished', async job => {
project.status = 2
await project.save()
queue --
})
result.on('error', async err => {
project.status = 3
await project.save()
queue --
})
}
В простейшем случае Job имеет путь к файлу шаблона и имя композиции.
{
"template": {
"src": "https://videorender.gridmaniac.com/templates/v001/DEMO_v001.aep",
"composition": "MAIN comp",
"continueOnMissing": true
},
"assets": [
...
],
"actions": {
"postrender": [
{
"module": "@nexrender/action-encode",
"preset": "mp4",
"output": "encoded.mp4"
},
{
"module": "@nexrender/action-copy",
"input": "encoded.mp4",
"output": "C:\\Projects\\videorender/public/videos/5ebc58698376b12a904ccb0a.mp4"
}
]
}
}
Раздел assets позволяет настраивать параметры слоев композиции, задавать пути к медиа-файлам, изменять текст и музыку, возможностей море.
Пути могут быть как локальными file://, так и в формате веб-ресурсов http://.
Второй вариант наиболее универсальный, так как рендеринг может происходить на удаленном узле, соответственно доступ к ресурсам в таком варианте позволяет избежать необходимости реализации механизмов копирования файлов между машинами - воркер загружает файлы по протоколу http автоматически.
Раздел actions дает возможность управлять этапами рендеринга: например, устанавливать параметры кодирования и задавать выходные пути, используя специальные модули.
При работе с локальными путями все ресурсы, используемые в проекте After Effects, подхватываются автоматически. В режиме распределенной работы необходимо вручную указывать пути ко всем файлам, используемых в проекте (изображениям, шрифтам и т.п.).
{
"type": "data",
"layerName": "BOTTOM TITLE",
"composition": "INSTASTORIES 009 1",
"property": "Source Text",
"value": "Простота использования \r\nНе требует навыков дизайна"
}
Указывается имя композиции, слоя, свойства и новое значение.
{
"type": "image",
"src": "https://videorender.gridmaniac.com/templates/v001/assets/alireza-esmaeeli-roCrQdjxbeo-unsplash.jpg",
"layerName": "alireza-esmaeeli-roCrQdjxbeo-unsplash.jpg",
"extension": "jpg"
}
Для изображений необходимо обязательно указывать расширение. Изображение может быть заменено на видео-фрагмент.
{
"type": "image",
"src": "https://videorender.gridmaniac.com/templates/v001/assets/bdb2653b25cd3813b397fbad9c9e988f.mov",
"layerName": "ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg",
"extension": "mov"
}
{
"type": "audio",
"src": "https://videorender.gridmaniac.com/templates/v001/assets/A_to_the_O.mp3",
"layerName": "A_to_the_O.mp3",
"extension": "mp3",
"composition": "MAIN comp"
}
Иногда для слоев требуется принудительно отключить звук. Например, когда изображение заменяется видео-фрагментом, в котором присутствует звук.
{
"type": "data",
"layerName": "ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg",
"property": "audioEnabled",
"value": false
}
{
"type": "data",
"composition": "INSTASTORIES 005 1",
"layerName": "CONTROL",
"property": "Effects.BG COLOR 1.Color",
"value": [
"0.514",
"0.482",
"0.525"
]
}
{
"type": "data",
"layerName": "ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg",
"property": "Position",
"value": [
540,
960
]
},
{
"type": "data",
"layerName": "ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg",
"property": "Scale",
"value": [
662.72,
662.72
]
},
Есть возможность использования своих скриптов.
{
"src": "file:///C:/sample/sampleParamInjection.jsx",
"type": "script",
"keyword": "_settings",
"parameters": [
{
"key": "name",
"value": "Dilip"
}
]
}
В результате генерации получится скрипт такого вида:
/* start of nexrender script */
var nexrender = {
renderCompositionName: 'MAIN comp',
defaultCompositionName: '*',
types: [CompItem, FolderItem, FootageItem, AVLayer, ShapeLayer, TextLayer, CameraLayer, LightLayer, Property, PropertyGroup],
};
...
(function() {
nexrender.selectLayersByName(null, 'ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg', function(layer) {
nexrender.replaceFootage(layer, 'C:\\Users\\GRIDMA~1\\AppData\\Local\\Temp\\nexrender\\lIjbuunLLYVcZugGBbfJH\\07e2e80e6d62b0443874ba81712ee9e3.mov.mov')
});
})();
(function() {
nexrender.selectLayersByName('INSTASTORIES 007 1', 'TITLE 01', function(layer) {
var parts = ["Source Text"];
var value = { "value": 'Создавай' }
nexrender.changeValueForKeypath(layer, parts, value);
return true;
});
})();
(function() {
nexrender.selectLayersByName(null, 'alireza-esmaeeli-roCrQdjxbeo-unsplash.jpg', function(layer) {
var parts = ["audioEnabled"];
var value = { "value": false }
nexrender.changeValueForKeypath(layer, parts, value);
return true;
});
})();
(function() {
nexrender.selectLayersByName(null, 'masha-kotliarenko-m6LUrN5JMN8-unsplash.jpg', function(layer) {
var parts = ["Position"];
var value = { "value": [540,616] }
nexrender.changeValueForKeypath(layer, parts, value);
return true;
});
})();
...
Существует проблема, когда заменяемый ресурс имеет иное разрешение и aspect ratio.
Дабы избежать необходимости ручного позиционирования и масштабирования нового изображения или видео для подгонки под исходное, я использовал следующий алгоритм:
Получаю размеры исходного и нового ресурса:
switch (type)
{
case 'image':
const sImage = await Jimp.read(path)
res.width = sImage.getWidth()
res.height = sImage.getHeight()
break;
case 'video':
const uVideo = await ffprobe(path)
const uStream = uVideo.streams.find(x => x['codec_type'] == 'video')
res.width = uStream.width
res.height = uStream.height
break;
}
Учитывая то, что позиция нового изображения сохранится, остается задать масштаб относительно размеров и масштаба исходного изображения.
let scale = … // source scale
if (sH > sW && uW > uH || sH < sW && uW < uH)
{
const oH = sH * scale
scale = oH / uH
}
if (sW > sH && uW > uH || sW < sH && uW < uH)
{
const oW = sW * scale
scale = oW / uW
}
x.data.push({
property: 'Scale',
value: [scale, scale]
})
Возможно добавление кастомных полей в описании Job, названия которых не коррелируют с теми, что используются для генерации скриптов. Таким образом, появляется возможность создания своих шаблонов, включающих конфигурации nexrender.
Использование YAML формата также упрощает процесс добавления и наращивания шаблонов.
name: v001
title: Demo 001
text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
file: DEMO_v001.aep
composition: MAIN comp
assets:
- type: data
layerName: TITLE
composition: INSTASTORIES 005 1
property: Source Text
value: "#КОНСТРУКТОР \r\n#ВИДЕО"
xTitle: Теги
xName: g1-tags
xType: TextArea
xIsCustomizable: true
- type: image
src: ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg
layerName: ali-abdul-rahman-3kEkV4Aj0Lc-unsplash.jpg
extension: jpg
xData:
- property: Position
value:
- 540
- 960
- property: Scale
value:
- 43.6
- 43.6
- property: audioEnabled
value: false
xTitle: Изображение 1
xName: g1-img
xType: Image
xIsCustomizable: true
- type: data
composition: INSTASTORIES 005 1
layerName: CONTROL
property: Effects.BG COLOR 1.Color
value: 8500B9
xTitle: Цвет 1
xName: color1
xType: Color
xIsCustomizable: true
Такой вариант шаблона можно смело использовать для рендеринга компонентов в React или Vue, а результат отправить в nexrender и получить на выходе уникальный видеоряд. В реальности, конечно же, сервер отдает не весь шаблон, а только необходимое, а затем собирает job по шаблону на основе данных от пользователя.