Комментарии

Gridmaniac
Оставлен: Автоматически
Смело оставляйте комментарии, критикуйте, задавайте вопросы

Adobe AE Automation

Опубликовано: 06.06.2020

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

На одном из проектов я занимался созданием подобного функционала. Мне передали множество шаблонов Adobe After Effects в формате *.aep, каждый из которых мог содержать несколько вариаций, существующих в форме отдельных композиций.

Основной задачей является реализация возможности программного изменения содержимого слоев проекта After Effects и запуск процесса рендеринга с параметрами.

Для подобных целей существует headless режим, который представлен в виде CLI. В простейшем случае мы можем запустить модуль aerender из командной строки с нужными нам параметрами. Для более сложных сценариев автоматизации используется Adobe ExtendScript, который похож на JavaScript.

Информацию по написанию сценариев можно почерпнуть из официальной документации.

В данной статье я буду использовать механизм автоматической генерации сценариев на основе конфигураций, но ради наглядности постараюсь показать их в разрезе.

Nexrender

Этот модуль позволяет организовать сеть для распределения процессов рендеринга между удаленными машинами и генерировать Job скрипты на основе JSON конфигурации.

Модуль nexrender позволяет быстро запустить рендеринг в режиме кластера.
Модуль nexrender позволяет быстро запустить рендеринг в режиме кластера.

Для начала необходимо запустить сервер:

$ 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

В простейшем случае 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 дает возможность управлять этапами рендеринга: например, устанавливать параметры кодирования и задавать выходные пути, используя специальные модули.

Assets

При работе с локальными путями все ресурсы, используемые в проекте 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
    ]
},

JSX

Есть возможность использования своих скриптов.

{
    "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 по шаблону на основе данных от пользователя.