Как известно, ренедеринг видео и трёхмерных изображений занимает много времени и требует много ресурсов компьютера. Ещё свежи воспоминания, как будучи студентом я ставил на ночь жужжащий компьютер с запущенной программой сборки фильма, а на утро оказывалось, что либо не хватило места на жёстком диске, либо что-то забыл добавить в ролик и всё приходилось начинать сначала. Сейчас настоящие профессионалы делают эту операцию удалённо. Например, режиссёр Джеймс Камерон во время съёмок фильма «Аватар» специально для себя сделал заказ на создания целого дата-центра, в котором запускались рендеринги сцен. Я пока ещё не настолько крут, чтобы строить свой отдельный дата-центр, но идея того, что эта операция могла бы быть запущена отдельно на другом сервере не давала мне покоя долгое время.
Вторая проблема – это монтаж самого видео. Несмотря на то, что современные компьютеры становятся мощнее, развитие видеокамер тоже не стоит на месте и получившиеся видео файлы становятся всё тяжелее и тяжелее. И, как следствие, их становится проблематично обрабатывать. Когда монтируешь продолжительный фильм, то предпросмотр трека с наложенными фильтрами и переходами начинает подгружать процессор и изображение начинает лагать, делая творческий процесс монтажа утомительным. Из того, что я пробовал, самый продуктивный был iMovie, что предустановлен на всех Маках. Даже фильм продолжительностью в 45 минут можно было редактировать без особых проблем на относительно маломощном макбуке. Можно было применить любой фильтр и увидеть результат в окне предпросмотра без каких-либо лагов. Так что владельцам маков тут повезло. Один минус у iMovie: во время работы он начинает потреблять непомерное количество дискового пространства. Видимо, это связано с агрессивным кэшированием для редактирования и предпросмотра.
В этой статье я опишу свой опыт, как я пытался решить эти две проблемы монтажа и рендеринга на примере домашнего видео ролика.
Инструмент
Если сравнивать с виндой или МакОсью, для линукса как правило бывает не так уж и много десктопных приложений, но выбор именно редакторов видео оказался на удивление широк. Поиграв с нескольким вариантами, я пока остановился на Kdenlive для работы с небольшими видео. При сравнении с другими программами там есть хороший набор функций, но в рамках данной статьи нас интересует всего одна, но приятная особенность: при сведении уже готового фильма, программа предлагает две возможности – как обычно срендерить в файл или сгенерировать скрипт.
При этом она просто создаст обычный shell-скрипт, который можно запустить потом из командной строки. В том числе и на другом компьютере. К чему я клоню? Да просто таким образом можно сводить проект на удалённом сервере. Для этого можно использовать облака, свой сервер, ну или даже мощный рабочий компьютер, который остаётся постоянно включённым в офисе (но я, конечно же, этого не говорил). План простой: сводим проект, генерируем скрипт, перекидываем весь проект на сервер, запускаем процесс удалённо.
Но это звучит слишком просто для настоящих бизонов. Добавим немного условий в задачу. Я уже жаловался, что монтаж оригинальных видео-файлов является задачей, требующей много ресурсов. Во время работы мы просматриваем результат на маленьком окошке предпросмотра, десятками раз прокручивая отдельные сюжеты, подгоняя субтитры, меняя фильтры и добавляя музыкальное сопровождение. А что если монтировать проект с маленькими видео-файлами, а уже окончательную сборку проводить с оригиналами? Всё равно мы скорее всего редко раскрываем предпросмотр во весь экран во время работы. В итоге, процесс выглядел бы таким образом:
- Для каждого видео исходника мы создадим его легковесный клон.
- Оригиналы мы перекинем на сервер.
- Монтаж будет производиться на личном компьютере с использованием легковесных копий (я использовал свой лаптоп).
- Перед сборкой мы проект перекидываем на сервер, где уже имеются исходники в оригинальном качестве.
- Замещаем легковесные клоны оригинальными.
- Запускаем рендеринг фильма в высоком разрешении.
- Опционально: оповещаем о завершении процесса по емайлу или СМС.
- Скачиваем готовый результат на личный компьютер.
- Тащимся от готового видео (и собственного мастерства).
Обо всём по порядку.
Легковесные копии
Для линукса есть довольно популярная программа для конвертирования и сжатия видео — HandBrake. Так как задача у нас просто сжать исходники, я особо выдумывать с настройками не буду и просто использую готовые пресеты. Выбираем iPod, которые даёт неплохой результат (напоминаю, видео для домашнего использования) и пробую сжать исходники.
Иногда после отличного отпуска у нас этих исходников могут быть десятки, если не сотни, и нужно эту работу как-то автоматизировать. Конкретно у HandBrake есть свой командный интерфейс. Поэтому можно быстро накатать скриптик, ну что-то типа этого:
#!/bin/bash
# convert.sh
quality=30
for file in ls $1/*
do
tempfile="${file##*/}"
filename="${tempfile%.*}"
outputFile="$2/$filename.mp4"
echo -e "\n\n Compress file $file"
HandBrakeCLI -i $file -o $outputFile --preset="iPod" -q $quality -w 160
done
Запустив его командой convert.sh ./original-videos ./small-videos, мы создадим для каждого файла его уменьшенную и оптимизированную копию. Единственное условие, чтобы облегчить нам жизнь в будущем, мы условимся, что все файлы и их клоны будут иметь одинаковое имя (хотя, скорее всего, разные расширения). Вот пример того, что у меня получилось. Две папки:
/small-videos /original-videos FILE0001.mp4 FILE0001.MOV FILE0002.mp4 FILE0002.MOV FILE0003.mp4 FILE0003.MOV ... ...
Таким образом, потом можно по имени программно заменить копии на их оригиналы. После этого папка с оригинальными видео отправляются на сервер, а маленькие копии остаются на нашем лаптопе для дальнейшей работы.
Пример оригинального видео и его легковесного клона:
Эмм… не переборщил ли я со сжатием? Всё только ради чистоты эксперимента!
Проект
Так, хорошо, начали проект. Качество предпросмотра отвратительное, конечно, но что поделать: мы же хотели избавиться от гнусных лагов, тормозящих всю работу. Продолжая плакать и колоться, монтируем фильм. Пока всё работает шустро, особо не напрягая процессор. Критический момент: настало время сведения. Мы жмём заветную кнопку Generate Script..., сохраняем результат. Теперь разберёмся, что именно только что произошло и что программа вообще сохранила.
В домашней папке KDEnlive (по умолчанию это ~/kdenlive/scripts/) появилось два файла:
- shell скрипт my-movie_001.sh
- и файл my-movie_001.sh.mlt
Shell-файл my-movie_001.sh имеет короткий набор команд:
#! /bin/sh
SOURCE="/home/w32blaster/kdenlive/scripts/my-movie_001.sh.mlt"
TARGET="/home/w32blaster/kdenlive/my-movie.mp4"
RENDERER="/usr/bin/kdenlive_render"
MELT="/usr/bin/melt"
PARAMETERS="-pid:8613 $MELT hdv_1080_50i avformat - $SOURCE $TARGET properties=x264-medium g=120 crf=20 ab=160k threads=1 real_time=-1"
$RENDERER $PARAMETERS
Особой уличной магии тут нет: он просто хранит в себе пути и настройки, а потом запускает рендер с необходимыми параметрами. При желании их можно редактировать, но обратить внимания стоит на один параметр – «threads=1», который, как нетрудно догадаться, устанавливает количество потоков. В зависимости от мощности процессора, это число можно увеличить, значительно повысив производительность рендеринга. Тем более, мы же собираемся запустить его на более мощном компьютере, не нагружая наш лаптоп.
Но особый интерес для этого эксперимента всё же представляет другой файл, который помечен многозначительным объявлением
SOURCE="/home/w32blaster/kdenlive/scripts/my-movie_001.sh.mlt"
Что за MLT файл такой?
MLT и описание проекта
Оказывается (по крайней мере, для меня), в мире открытого программного обеспечения существует такой формат для передачи видео-трансляций, а также одноимённый фреймворк, называемый MLT. На сайте сказано, что это:
MLT is an open source multimedia framework, designed and developed for television broadcasting. It provides a toolkit for broadcasters, video editors, media players, transcoders, web streamers and many more types of applications. The functionality of the system is provided via an assortment of ready to use tools, XML authoring components, and an extensible plug-in based API.
С помощью консольной программы melt, можно сводить видеопроекты прямо в консоли. То есть в теории, можно даже сделать целый фильм, не выходя из консоли. Просто для этого нужно написать и запустить километровую команду, что большинство делать, конечно же, не любит. Вместо этого весь проект описывается специальным форматом MLT, который основан на вездесущем XML. Таким образом, рендеринг видео – это запуск консольной программы melt, которой скармливают как параметры будущего фильма, так и сам проект в MLT формате. Сам же MLT файл генерируется GUI программой. В моём случае, как я уже упоминал, это была KDenlive, с которой кстати поставляется melt в качестве зависимости. Подробнее об использовании этой утилиты в офф.доках.
Теперь давайте глянем на структуру самого MLT файла, сгенерированного из нашего фильма:
<?xml version='1.0' encoding='utf-8'?>
<mlt title="Anonymous Submission" version="0.9.3" root="/home/w32blaster" LC_NUMERIC="en_GB.UTF-8">
<producer in="0" out="5367" id="117_4">
<property name="resource">Videos/small/FILE0001.mp4</property>
... тут идут параметры видео-исходника
</producer>
<playlist id="playlist1">
<entry in="0" out="2033" producer="117_4">
<filter out="1838" id="volume">
... параметры фильтра ...
</filter>
</playlist>
<tractor title="Anonymous Submission" global_feed="1" in="0" out="17018" id="maintractor">
<property name="meta.volume">1</property>
<track producer="black_track"/>
<track hide="video" producer="playlist1"/>
<track hide="video" producer="playlist2"/>
<track producer="playlist3"/>
<track producer="playlist4"/>
<track producer="playlist5"/>
<transition id="transition0">
<property name="a_track">1</property>
<property name="b_track">2</property>
<property name="mlt_type">transition</property>
<property name="mlt_service">mix</property>
<property name="always_active">1</property>
<property name="combine">1</property>
<property name="internal_added">237</property>
</transition>
.... много-много переходов, наложений и прочих параметров ....
</tractor>
</mlt>
Несмотря на объём и кажущуюся сумбурность, этот файл имеет довольно простую структуру:
- mlt – главный тэг, который объявляет, что это не что иное, как описание видео в mlt-формате. Так же первыми объявляются параметры проекта, которые удалены из примера для простоты.
- producer – это объект, являющийся исходником картинки. Это может быть видео, музыка, изображение, просто цветовая заливка, титры и даже другой mlt-файл (об этом чуть ниже). Внутри имеется много параметров, и в случае работы с файлами, важным является путь к ресурсу. Для каждого ресурса, который добавлен в наш проект, генерируется отдельный «продюсер».
- playlist – плейлист, который состоит из всех «продюсеров». Тут же могут быть фильтры (в примере выше, это фильтр затухания звука). Для каждого ресурса указываем какой именно отрезок следует вставить. То есть когда мы обрезаем у ролика края на timeline, то на уровне формата MLT мы просто устанавливаем параметры in и out у тэга entry. Ну, и сам тэг entry имеет ссылку на «продюсер».
- tractor – собственно, это и есть весь наш фильм, или timeline, как его ещё принято называть. То, что официально называется нелинейным монтажом, когда мы изменяем порядок и перетаскиваем видео-ролики между собой на трекере – всё это описывается тут. Все переходы, наложения, титры и всё-всё-всё описывается именно тэгом «tractor».
Подробнее о структуре mlt-файла тут.
Итак, мы сгенерировали первый mlt-файл, который будет содержать структуру самого фильма, составленного из сжатых видео-клонов. Каждый раз, когда мы будем готовы рендерить фильм, мы будем генерировать этот файл со структурой. Вроде разобрались с тем, как проект получается на стороне клиента (т.е. на лаптопе), теперь нужно разобраться, как с этим работать на сервере перед собственно самим рендерингом. План состоит в том, чтобы перед началом сборки заменить все «продюсеры» в mlt-файле с маленькими видео на точно такие же, но с оригинальными и уж потом запускать сведение. В идеале, никто не заметит подмены.
Для этой операции нужно узнать, какие именно маленькие файлы соответствуют большим оригиналам. Проблема осложняется ещё и тем, что у «продюсера» используются технические данные видео ролика. Вот пример одного из них:
<producer in="0" out="1327" id="65_2">
<property name="mlt_type">producer</property>
<property name="length">1328</property>
<property name="eof">pause</property>
<property name="resource">Videos/Austria2015-small/FILE1235.mp4</property>
<property name="meta.media.nb_streams">2</property>
<property name="meta.media.0.stream.type">video</property>
<property name="meta.media.0.stream.frame_rate">30</property>
<property name="meta.media.0.stream.sample_aspect_ratio">0</property>
<property name="meta.media.0.codec.width">160</property>
<property name="meta.media.0.codec.height">90</property>
<property name="meta.media.0.codec.frame_rate">90000</property>
<property name="meta.media.0.codec.pix_fmt">yuv420p</property>
<property name="meta.media.0.codec.sample_aspect_ratio">0</property>
<property name="meta.media.0.codec.colorspace">709</property>
<property name="meta.media.0.codec.color_trc">1</property>
<property name="meta.media.0.codec.name">h264</property>
<property name="meta.media.0.codec.long_name">H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10</property>
<property name="meta.media.0.codec.bit_rate">52987</property>
<property name="meta.media.1.stream.type">audio</property>
<property name="meta.media.1.codec.sample_fmt">fltp</property>
<property name="meta.media.1.codec.sample_rate">48000</property>
<property name="meta.media.1.codec.channels">2</property>
<property name="meta.media.1.codec.name">aac</property>
<property name="meta.media.1.codec.long_name">AAC (Advanced Audio Coding)</property>
<property name="meta.media.1.codec.bit_rate">163117</property>
<property name="meta.attr.1.stream.language.markup">eng</property>
<property name="meta.attr.major_brand.markup">mp42</property>
<property name="meta.attr.minor_version.markup">512</property>
<property name="meta.attr.compatible_brands.markup">isomiso2avc1</property>
<property name="meta.attr.encoder.markup">HandBrake 0.10.1 2015030800</property>
<property name="seekable">1</property>
<property name="meta.media.sample_aspect_num">1</property>
<property name="meta.media.sample_aspect_den">1</property>
<property name="aspect_ratio">1</property>
<property name="audio_index">1</property>
<property name="video_index">0</property>
<property name="mlt_service">avformat</property>
<property name="meta.media.frame_rate_num">30</property>
<property name="meta.media.frame_rate_den">1</property>
<property name="meta.media.colorspace">709</property>
<property name="meta.media.width">160</property>
<property name="meta.media.height">90</property>
<property name="meta.media.top_field_first">0</property>
<property name="meta.media.progressive">1</property>
<property name="meta.media.color_trc">1</property>
</producer>
Не плохо так, да? Нужно как-то заменить все эти параметры ресурса и желательно не вручную. Проблема осложняется ещё и тем, что для mlt нет схемы, таким образом программно трудно проверить корректность файла. Рискнём и постараемся обойтись без схемы.
Как будем замещать мета-данные клонов оригинальными (которые, естественно, будут разными, поскольку сами видео разные)? К счастью, melt достаточно умён, чтобы самому сделать корректный mlt, который он сам же потом и будет потреблять. Когда мы запускаем рендеринг с помощью команды melt нам нужно указать источники данных («producer», как показано выше) и потребителя, куда поток перенаправляется («consumer»). Потребитель бывает разный, все варианты можно посмотреть тут или же набрав команду из консоли:
$ melt -query "consumers"
---
consumers:
- decklink
- xgl
- qglsl
- sdl
- sdl_audio
- sdl_preview
- sdl_still
- cbrts
- xml
- sdi
- jack
- avformat
- multi
- null
- gtk2_preview
- blipflash
- rtaudio
...
Как видно, перенаправить вывод рендеринга можно во что угодно. Например, во время обычного рендеринга в файл используется avformat. Всё, что мы смонтируем, сохранится в один файл. В качестве второго примера, возможно направить вывод сразу же в stdout, используя команду:
$ melt original.mlt -consumer libdv
Тогда откроется окно с проигрыванием видео и melt будет сводить фильм, одновременно перенаправляя его на лету в открывшееся окно.
Но если указать в качестве потребителя «xml», а источником указать видео-файлы, то melt перечислит все файлы и их технические параметры и выведет весь контент в XML-формате в красиво оформленный MLT-файл. Вот пример команды:
$ melt *.MOV -consumer xml:original.mlt
Она сгенерирует MLT-файл, содержащий только лишь «продюсеры» на видеофайлы в данной директории с разрешением MOV. Естественно, с корректными мета-данными оригинальных (больших) видеофайлов. Так именно это и нужно для нашего коварного плана! Итак:
- Мы знаем, где на сервере лежат оригиналы.
- На основе этих оригиналов мы генерируем в командной строке MLT-файл, который содержит только одни «продюсеры» с оригинальными видео и их мета-данными. Назовём его original.mlt. В этом файле нет описания фильма (то есть тэга tractor), только одни исходники. Мы его будем использовать как источник т.н. правильных «продюсеров».
- Мы перекидываем с лаптопа на сервер текущий MLT-файл, который содержит наш фильм с актуальными изменениями, но с «подпорченными» маленькими копиями. Назовём его small.mlt
- Помятуя о том, что оригиналы и копии имеют одинаковые имена (но, возможно, разные расширения), мы можем сопоставить «правильные» продюсеры из первого MLT файла с «обрезанными» из второго MLT файла
- Нужно заменить не только сами продюсеры, но и их ID, поскольку они наверняка будут разными.
- Можно приступать к сведению.
В результате каждый продюсер наподобие такого:
<producer>
<property name="resource">Videos/small/FILE1234.mp4</property>
... его мета-данные
</producer>
Должен быть замещён продюсером подобным этому:
<producer>
<property name="resource">Videos/original/FILE1234.MOV</property>
... его мета-данные
</producer>
Для того, чтобы совершить эту операцию, я набросал небольшой питоновский скрипт с тестом. На самом деле, я – приверженец Джавы, так что это мой первый код на Питоне. Если после прочтения вы посчитаете, что мои кривые руки нужно таки выпрямить, то ваши мысли и предложения приветствуются в виде пулл-реквеста.
#!/usr/bin/env python
import xml.etree.ElementTree as ET
import os.path
import sys, getopt
def main(argv):
'''
The main purpose of this programm is to take the given MLT file containing
producers with an compressed ("small") resources (video, images, sound) and
replace them with identical producers with uncompressed ("original") resources
from another MLT file
Example of usage:
python process.py -s ~/Videos/small.sh.mlt -p ~/Videos/original/original.mlt
'''
smallVideosFile, originalVideosFile, homeDir = _extractCLArguments(argv)
if (smallVideosFile == '' or originalVideosFile == ''):
print "Error. Both arguments -s and -p must be provided!"
sys.exit(2)
print "Start MLT file processor. We will modify %s using data from %s" % (smallVideosFile, originalVideosFile)
# Parse the MLT file with small resources
smallTree = ET.parse(smallVideosFile)
smallTreeRoot = smallTree.getroot()
# and MLT file with original (big, full size) resources
originalTree = ET.parse(originalVideosFile)
originalTreeRoot = originalTree.getroot()
# prepare maps "video file name" <==> "producer ID"
mapSmallProducers = _getMapOfProducerIds(smallTreeRoot)
mapOriginalProducers = _getMapOfProducerIds(originalTreeRoot)
# build map "small producer ID" <==> "original producer ID", having the same video resource
mapID = _mergeMaps(mapSmallProducers, mapOriginalProducers)
# update 'root' value in MLT tag
_updateRootTag(homeDir, smallTreeRoot)
# then replace all the producers containing small videos with those with original videos
_replaceAllSmallProducersWithOriginal(mapOriginalProducers, smallTreeRoot, originalTreeRoot)
# print modified MLT to output file
ET.ElementTree(smallTreeRoot).write(smallVideosFile, encoding='utf-8', xml_declaration=True)
def _extractResourceName(fullName):
'''
Extracts only the resource name from full path. For example,
if the fullName is Videos/small/FILE1114.mkv this method returns FILE1114
'''
if "?" in fullName:
withoutParam = fullName.split("?",1)[0]
return os.path.splitext(os.path.basename(withoutParam))[0]
else:
return os.path.splitext(os.path.basename(fullName))[0]
def _mergeMaps(mapSmallProducers, mapOriginalProducers):
'''
Builds map of producer IDs: each small producer's ID should
match appropriate big producer's ID, having the same resource.
'''
mapID = {}
for fileName, oldProducerId in mapSmallProducers.items():
if (fileName in mapOriginalProducers):
mapID[oldProducerId] = mapOriginalProducers[fileName]
return mapID
def _getMapOfProducerIds(tree):
'''
Builds the map for the given file: "resource (video file name)" <=> "producer ID".
'''
arrProducers = tree.findall('producer')
# Collect the map "resourse (video file name)" <==> "id of the producer"
mapIdToResource = {}
for producer in arrProducers:
resourceType = producer.find("*[@name='mlt_service']").text
isReplacementNeeded = (resourceType == "avformat" or resourceType == "framebuffer" or resourceType == "xml")
if (isReplacementNeeded):
resourceValue = producer.find("*[@name='resource']").text
# extract only filename (without path and extension)
fileName = _extractResourceName(resourceValue)
mapIdToResource[fileName] = producer.attrib.get('id')
else:
print "[Ignored] the producer %s (resource type is %s) is ignored" % (producer.attrib.get('id'), resourceType)
pass
return mapIdToResource
def _replaceAllSmallProducersWithOriginal(mapOriginalProducers, rootToBeModified, rootOriginal):
'''
Replaces all DOM-element 'Producer' containing small resources with
identical producers, containing the original resource
'''
print "Replace old producer having small size with another one having original resource:"
arrSmallProducers = rootToBeModified.findall('producer')
for producer in arrSmallProducers:
resourceType = producer.find("*[@name='mlt_service']").text
if (resourceType == "avformat"):
_replaceProducerAvformat(producer, mapOriginalProducers, rootToBeModified, rootOriginal)
elif (resourceType == "framebuffer"):
_replaceProducerFramebuffer(producer, mapOriginalProducers, rootToBeModified, rootOriginal)
elif (resourceType == "xml"):
_replaceProducerXml(producer, mapOriginalProducers, rootToBeModified, rootOriginal)
def _replaceProducerAvformat(producer, mapOriginalProducers, rootToBeModified, rootOriginal):
'''
Replace entirely one producer, containing small resource
with similar producer from original tree. Resulting producer
should have the same ID, but it must get all the
values from original tree (URLs, codec properties and other technical
information)
'''
resourceValue = producer.find("*[@name='resource']").text
fileName = _extractResourceName(resourceValue)
if(fileName in mapOriginalProducers):
# remember in, out and length from small producer
attrIn = producer.attrib.get('in')
attrOut = producer.attrib.get('out')
length = producer.find("*[@name='length']").text
producerId = producer.attrib.get('id')
originalId = mapOriginalProducers[fileName]
origProducer = rootOriginal.find('.//producer[@id="' + originalId + '"]')
if origProducer is not None:
rootToBeModified.remove(producer)
# do not touch original node, deal with clone instead
origProducerClone = origProducer.copy()
# update in, out and length to match with small producer
origProducerClone.set('in', attrIn)
origProducerClone.set('out', attrOut)
origProducerClone.set('id', producerId)
origProducerClone.find("*[@name='length']").text = length
rootToBeModified.insert(1, origProducerClone)
print " - Avformat resource (id=%s) is replaced with resource (id=%s)" % (producerId, originalId)
else:
print " - [Ignored] Avformat resource (id=%s) was not found. File name is '%s' and found original ID is '%s'" % (producerId, fileName, originalId)
def _replaceProducerFramebuffer(producer, mapOriginalProducers, rootToBeModified, rootOriginal):
'''
Update given producer's resource. The resulting
producer should have the same properties except resource path.
Keep in mind, that we need to keep the parameter after "?" sign.
'''
resourceNode = producer.find("*[@name='resource']")
resourceValue = resourceNode.text
producerId = producer.attrib.get('id')
fileName = _extractResourceName(resourceValue)
if(fileName in mapOriginalProducers):
originalId = mapOriginalProducers[fileName]
origProducer = rootOriginal.find('.//producer[@id="' + originalId + '"]')
if origProducer is not None:
originalResourcePath = origProducer.find("*[@name='resource']").text
extension = resourceValue.split("?",1)[1]
resourceNode.text = originalResourcePath + "?" + extension
print " - Framebuffer resource (id=%s) is replaced with resource (id=%s)" % (producerId, originalId)
else:
print " - [Ignored] the Framebuffer resource (id=%s) is ignored!" % (producerId)
def _replaceProducerXml(producer, mapOriginalProducers, rootToBeModified, rootOriginal):
'''
Update given producer in case if it is XML resource.
The resulting producer should have updated resource path
including .mlt extension.
'''
resourceNode = producer.find("*[@name='resource']")
resourceValue = resourceNode.text[:-4] # trim .mlt extension
producerId = producer.attrib.get('id')
fileName = _extractResourceName(resourceValue)
if(fileName in mapOriginalProducers):
originalId = mapOriginalProducers[fileName]
origProducer = rootOriginal.find('.//producer[@id="' + originalId + '"]')
if origProducer is not None:
originalResourcePath = origProducer.find("*[@name='resource']").text
resourceNode.text = originalResourcePath + ".mlt"
print " - XML resource (id=%s) is replaced with resource (id=%s)" % (producerId, originalId)
def _updateRootTag(homedir, rootToBeModified):
'''
In order to find all the resources, MLT uses the arggument "root"
placed in tag MLT. When we work on different computers, we must also
update this value in order MELT would be able to find resources.
'''
if (homedir != ''):
rootToBeModified.set('root', homedir)
def _extractCLArguments(argv):
inputfile = ''
outputfile = ''
homedir = ''
try:
opts, args = getopt.getopt(argv,"hs:p:u:",["smallfile=","producersfile=", "userhomedir="])
except getopt.GetoptError:
print 'process.py -s <smallFileToBeModified> -p <producersFile>'
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print '''process.py -s <smallFile> -p <producersFile> -u <userHomeDir>
where "smallFile" is a mlt file to be modified
and "producersFile" is a mlt file containing the list of producers
with original resources.
'''
sys.exit()
elif opt in ("-s", "--smallfile"):
inputfile = arg
elif opt in ("-p", "--producersfile"):
outputfile = arg
elif opt in ("-u", "--userhomedir"):
homedir = arg
return inputfile, outputfile, homedir
if __name__ == "__main__":
main(sys.argv[1:])
Запускать этот скрипт вот такой командой:
# python process.py -s small.mlt -p original.mlt
которая скажет «возьми все продюсеры из файла original.mlt и замени их на соответсвующие в файле small.mlt, но остальное не трогай».
Собираем всё вместе
Ну что же, теперь пора попробовать собрать проект удалённо. Как полагается в линуксе, создадим на серверной стороне понятный и простой shell-скрипт,…
#!/bin/bash
SMALL_RESORCES="Small"
ORIGINAL_RESOURCES="Original"
KDENLIVE_SCRIPT="austria-2015_001.sh"
PY_SCRIPT="/home/ilya/home-workspace/mlt-producer-replacer/process.py"
CURRENT_DIR="/home/ilya/Videos/MltProcessor"
rm log
echo -e "\n\n Step 1. Rename all the .mp4.mlt resources to .MOV.mlt"
rm $ORIGINAL_RESOURCES/*.MOV.mlt
for file in $ORIGINAL_RESOURCES/*.mlt; do
cp "$file" "$ORIGINAL_RESOURCES/`basename $file .mp4.mlt`.MOV.mlt"
done
echo -e "\n\n Step 2. Generate fresh list of producers with original sources"
rm $CURRENT_DIR/original.mlt
cd ~
melt $CURRENT_DIR/$ORIGINAL_RESOURCES/*.{MOV,mp3} -consumer xml:original.mlt
cd $CURRENT_DIR
mv ~/original.mlt $CURRENT_DIR
echo -e "\n\n Step 3. Update MLT and replace small videos with original ones"
# process the main MLT file
cp $KDENLIVE_SCRIPT.mlt $KDENLIVE_SCRIPT.mlt-BACKUP
python $PY_SCRIPT -s $KDENLIVE_SCRIPT.mlt -p $CURRENT_DIR/original.mlt -u $HOME
echo -e "\n\n Step 4. Update additional MLT files"
# process other MLT resources, that are producers with type=xml
for i in $ORIGINAL_RESOURCES/*.MOV.mlt; do
python $PY_SCRIPT -s $i -p $CURRENT_DIR/original.mlt -u $HOME
done
echo -e "\n\n Step 5. Run rendering"
./$KDENLIVE_SCRIPT
echo -e "\n\n Step 6. Make it smaller"
HandBrakeCLI -i austria.mp4 -o austria.small.m4v --preset="Universal"
echo "FINISHED" >> log
… который делает всё, что нам нужно: генерирует свежий MLT из оригиналов, запускает питоновский скрипт по замещению исходников и запускает рендеринг. В конце можно воткнуть оповещение по емайлу или СМС.
Примечание. Хотелось бы прокомментировать небольшую тонкость, которая описана в Step 1 и Step 4 в приведённом выше скрипте. Как я уже упоминал выше, помимо видео и картинок в качестве исходников могут выступать также и другие mlt файлы. С такой ситуацией вы можете столкнуться, если воспользоваться одной из т.н. Clip Job. Допустим, вы хотите проиграть клип задом наперёд.
В таком случае будет создан отдельный файл, который будет содержать всего один продюсер, с наложенными на видеоролик фильтрами. Называться этот файл будет {fileName}.{extension}.mlt. Использоваться в проекте он будет примерно так:
<producer id="12_1" in="0" out="375">
<property name="mlt_type">producer</property>
<property name="resource">Videos/MltProcessor/Original/IMG_3764.MOV.mlt</property>
<property name="mlt_service">xml</property>
....
</producer>
Поскольку у нас оригиналы и копии могут иметь разные расширения, то нам в Step 1 следует переименовать их, чтобы melt смог корректно найти эти ролики. Скажем, файл IMG_3764.mp4.mlt должен быть переименован в IMG_3764.MOV.mlt. Ну и в завершение нам нужно их обработать нашим питоновским скриптом на шаге Step 4 точно так же, как мы обработали наш основной mlt.
Полная картина
Теперь суммируем, как создание фильма выглядит с самого начала и до конца.
- Мы собираем все исходники (можно даже и музыку), создаём уменьшенные клоны.
- Создаём новый проект в GUI программе типа Kdenlive, добавляем в него все клоны.
- Несмотря на то, что проект ещё не готов, жмём на Generate file, чтобы сгенерировать shell-скрипт. По желанию, можно установить число threads в параметрах. Больше этот файл меняться не будет.
- Закидывам на сервер оригиналы видео, питоновский скрипт и shell-скрипт из предыдущего пункта.
- Начинаем монтаж фильма. Когда готовы свести фильм и посмотреть его в полном качестве, рендерим заново свежий файл mlt.
- Закидываем только один этот файл на сервер и запускаем самописный shell-скрипт (Листинг 2).
Естественно, во время монтажа мы можем рендерить фильм бесчисленное количество раз, так что каждый раз нам нужно отправлять на сервер всего лишь один файл mlt и продолжать работу на лаптопе. Всё остальное будет оставаться неизменным. Но главная фишка в том, что запустив рендеринг, мы можем без труда заняться своими делами, продолжить монтаж или пойти спать, выключив лаптоп, так как процесс будет запущен на удалённом сервере.
В итоге, снова напишем совсем небольшой скрипт, который будем запускать каждый раз с лаптопа:
#!/bin/bash
SERVER_HOSTNAME="10.20.30.40"
USERNAME="ilya"
REMOTE_PROJECT_DIR="/home/ilya/Videos/MltProcessor"
REMOTE_ORIGINAL_DIR="/home/ilya/Videos/MltProcessor/Original"
LOCAL_MLT="/home/w32blaster/kdenlive/scripts/austria-2015_001.sh.mlt"
LOCAL_SOURCES_DIR="/home/w32blaster/Videos/Austria2015-small"
# 1. Upload main MLT file
scp $LOCAL_MLT $USERNAME@$SERVER_HOSTNAME:$REMOTE_PROJECT_DIR
# 2. Upload all others MLT files, that represents producer sources
scp $LOCAL_SOURCES_DIR/*.mlt $USERNAME@$SERVER_HOSTNAME:$REMOTE_ORIGINAL_DIR
# 3. Execute rendering on the remote server
ssh $USERNAME@$SERVER_HOSTNAME "$REMOVE_PROJECT_DIR/run.sh"
Конечно, можно автоматизировать и это, например, синхронизировать наш MLT-файл через облачное хранилище и на сервере мониторить, когда файл изменится, после чего автоматически запускать рендеринг, и прочее, и прочее, но я предлагаю читателю заняться этим увлекательным делом уже самостоятельно.
Вывод
Конечно, на этой основе пока рано начинать свой стартап, придумывать броское название и рисовать логотип с усами-очками. В этом случае я всего лишь попытался воплотить саму идею процесса. Такой способ пока не для общего пользования, а скорее для
Немного мыслей.
- Вполне возможно, что данный метод работает не со всеми фильтрами Kdenlive, у меня не было возможность протестировать всё
- Желательно иметь физический доступ к серверу, потому что перекинуть на него все оригиналы в высоком качестве по сети со средним каналом будет долго. Идеальный вариант: перекинуть видео файлы с помощью флешки или переноски.
- И да, если вы считаете, что я сам придумал проблему и сам же её решил, то в оправдание могу сказать, что я нашёл отличный предлог, чтобы наконец-то ознакомиться с Питоном, чему весьма рад :)
Всем хороших выходных! А тем, кто уже парафинит скользяк, точит канты и мечтает о сугробах – удачного катания в будущем сезоне!
Комментарии (20)
zno
10.07.2015 10:07Спасибо, очень познавательно.
У меня давно было желание «соскочить» с iMovie, равно как и iTunes-iPhoto.
Если процесс миграции с 2х последних относительно очевиден, то вот в случае с iMovie я имею библиотеку «эвентов» и несколько проектов. И хотелось бы это все как-то смигрировать без потери структуры и с возможностью открытия проектов в альтернативном софта типа kdenlive.
И вот тут засада, хорошего решения (да и плохого) не видно.
Есть ли у автора статьи подобный опыт, тем более что iMovie в тексте упоминается? :)w32blaster Автор
10.07.2015 10:34Увы, я только пробовал разные редакторы в монтаже, но вот мигрировать с одного на другое не приходилось :/
zno
10.07.2015 10:49Спасибо за ответ.
Я правильно понимаю, что kdenlive — достаточно полноценная замена iMovie в принципе?w32blaster Автор
10.07.2015 11:08Если говорить только о функционале, то думаю, KDEnlive может всё то, что и iMovie. Но если говорить о дизайне графического интерфейса и удобстве использования, то я уверен, что будете немного обескуражены поначалу :) всё ж таки это open source и насколько я знаю, в данный момент KDEnlive активно поддерживается только лишь одним человеком.
zno
10.07.2015 11:14Да бог с ним с дизайном, лишь бы работало. Жаль, конечно поддерживает один человек. Значит скоро забьет =(.
Ugputu
10.07.2015 11:12Кстати что из себя представляет файл mlt что описал автор? Это не XML случайно? Если да (а скорее всего так и есть), то проблем с конвертацией между пакетами быть не должно (в теории). Что у FCP что у премьера XML на сколько я помню. И у iMovie наверняка тоже. А значит кто-то уже писал конвертер скорее всего.
Ugputu
10.07.2015 11:06+2Я кину свою балалайку.
Прочел только введение, остальные технические подробности не читал, т.к. заголовок оказался не о том, о чем в статье.
Ферма это все-же не два компа, и понятие фермы это параллельная деятельность относительно одной задачи. Например в том-же FCP есть возможность построить ферму (кластер) из нескольких компов и параллелить рендер сцены. У тебя нечто другое, к фермам не имеющее никакого отношения.
Потом меня как-то зацепила фраза о том что iMovie может 45 минут с эффектами на маломощном компе. Тут секрета нет. Оно после того как ты кусок положил считает его для превью (так-же сейчас делает FCX). Т.е. нет необходимости после применения эффекта жать рендер. Оно само за тебя все это делает и через некоторое время ты получаешь плавное видео в качестве превью.
Третье. Если ты посмотришь какой формат для твоего редактора нативный, И сконвертишь предварительно куски в этот формат (например для FCP я делаю в 422, а для Premiere в mpeg2) то весь процесс монтажа будет немного проще, хотя место эти файлы сожрут прилично. Но и тут есть вариант, ты о нем писал. Это называется proxy. Т.е. когда ты жмешь файлы из 1080 до 640 и их крячишь, а потом подменяешь оригиналами на финальном рендере. Хотя тут стоит оговориться, что с цветом лучше на больших кадрах работать. В том-же FCP проксироваие есть из коробки (хотя я им пользовался только один раз).
Что касается простого подхода, то если бы у меня был *nix я бы ffmpeg ом сконвертил все куски в что-то легкое и нативное, поработал с ними, а потом уже рендерил на больших кадрах. Твои 10 минут даже в 1080 посчитаются с простыми транзишинами и цветом за час максимум. И смысла кидать файлы на сервер с сервера я не вижу.
А, ну и девиз этого коммента: Пост не читай, сразу пиши коммент =)
Loxmatiymamont
10.07.2015 11:48+3Я возможно упускаю, но давно делаю вот так и не вижу проблем:
1. Сырое видео со съёмок закидываю в папку на сервер, которую штатно мониторит media encoder, ухожу пить кофе, по возвращении имею набор файлов в нужном мне даунскейле на монтажке
2. Делаю монтаж
3. Беру килобайтный файлик проекта, закидываю к оригинальным футажам на сервере + музыка и остальное, перекидываю всё в другую мониторющуюся папочку
4…
5. PROFIT?!
Ок, не хотите проклятый adobe, всё тоже самое делается через ffmpeg + cron
Что дают описанные в статье фортеля?Fess
10.07.2015 13:09+3Все то же самое, только более подробно о «костылях», поиске решения и с меньшим снобизмом.
ComodoHacker
11.07.2015 00:27Обязательно попробую ваш метод. Давно хотел освоить Kdenlive.
Пара мыслей.
1. Для правки melt файлов можно использовать XSLT. Думаю, код подсократится на порядок.
2. Настройки собственно кодера наверное нужно подбирать на оригинале, многие из них ведь зависят от разрешения. Найти баланс размера, качества и времени рендеринга на легковесной копии невозможно. Хотя бы на куске оригинала. Сделать его «легковесным» можно не сжатием, а, например, сильно кропнув по краям.w32blaster Автор
13.07.2015 23:18Попробуйте лучше так, как описано в комментарии ниже: habrahabr.ru/post/260837/#comment_8496165
ollycat
13.07.2015 13:11+1Про разницу между названием и содержимым уже писали, так что не буду повторяться. ;) Но меня удивило не это. Я когда-то тоже проходил этот этап, с «подменой» в kdenlive полноразмерных видео на мелкие. И копаясь в документации вдруг обнаружил, что это давно уже сущейтвует в штатном функционале. Так что откройте для себя clip proxy, уважаемый! ;) И облечете себе жизнь. :) Не сочтите за рекламу, но я писал уже об этом, а так же об рендере в melt у себя ещё в 12 году вот тут. Рад, если мой опыт кому-то ещё пригодится.
w32blaster Автор
13.07.2015 23:16Согласен, навелосипедил хорошенько. Уж больно хотелось разобраться, как оно работает внутри ) Спасибо за советы!
rule
Сводили сводили, а результат не показали.
w32blaster Автор
Ну так это же домашнее видео, я не собирался его публиковать. Но раз уж вы настаиваете… :)
ToSHiC
Оффтоп: фастеки на склоне удобно встёгивать?
koltykov
У меня Flow 4 валяются. Крайне отрицательные впечатления, особенно для фристайла. Брал их для сноукайтинга, так и валяются… Ничего лучше стандартных креп все же нет
w32blaster Автор
Наровиться можно. Фастеки сделаны чтобы всёгиваться стоя или на ходу. Так что сидя не удобно и, боюсь, на очень крутом склоне тоже будут проблемы. И ещё очень сложно встегнуться и расстегнуться в пухляке: если зарылся в снегу то довольно сложно откинуть задник (это, кстати, к Флоу тоже относится). В остальном — просто замечательно: встал, встегнулся, поехал. Мне нравится встёгиваться сразу же после подъёника — без пауз можно слезть с креселки и не останавливаясь докатиться до склона, на ходу застегнув крепления ))) Профи не особо любят фастеки, так как откидывающийся задник не столь точно передаёт усилия от ноги к доске, но это на любителя. Моё имо )