Заметки · 22.04.2022

Конкатенация видеофайлов

Передо мной стояла задача соединить в один видеоролик кучу файлов в формате mp4 и передать его серверу с nginx, который обеспечивал бы доступ к конечному файлу по http для VLC плеера со множества устройств.

Если обращаться к файлу по smb — плееры будут его блокировать и подменить ролик быстро уже не получится, а вот с http такой проблемы можно избежать.

Основано на собственном горьком опыте

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

Итак, прежде всего нам нужен ffmpeg.exe. Скачиваем и кладём его в папку, где у нас будет находится ps1 и сами видеоролики для склеивания. Теперь пишем скрипт.

Сначала проводим переименование всех mp4 с использованием обычного счётчика по маске оченьнужныйфайл.mp4 -> 001.mp4:

#### Переименование mp4 файлов в папке ####
dir *.mp4 | % {$i=1} {ren $_ -NewName ("{0:00#}.mp4" -f $i++)}

Когда я начал делать прямую конкатенацию файлов mp4, ffmpeg начал выдавать ошибку примерно такого вида: DTS 126000 < 2053347 out of order. Эта ошибка говорит о том, что файлы, которые склеиваются, имеют совершенно разные характеристики, начиная от частоты кадров и заканчивая весьма непредсказуемыми тонкостями кодирования.

Избежать этой ошибки можно только если создавать временные файлы в формате mts, а вот уже их склеивать в один файл и конвертировать в mp4. На этом этапе я скормил ffmpeg всё содержимое папки с переименованными ранее файлами:

#### Конвертация в MTS ####
Get-ChildItem -Filter *.mp4 | ForEach-Object {
  $cmd = "-i "+ "$_" + " -c copy " + "$($_.Name).mts"
  Start-Process -FilePath ffmpeg.exe -ArgumentList $cmd -Wait -NoNewWindow 
}

Как только временные файлы сформированы, переходим к этапу конкатенации mts и сохранения их в формат mp4. Чтобы каждый раз не передавать ffmpeg названия файлов — лучше сформировать текстовый документ в кодировке ASCII с перечнем mts файлов, которые нам нужны.

#### Сохранение перечня файлов в текстовый документ ####
$content = foreach ($i in Get-ChildItem -name .\*.mts) { echo "file '$i'" }
$content | out-file -encoding ASCII list.txt

Теперь ffmpeg нужно передать атрибуты для конкатенации и конвертирования, с соблюдением соотношения сторон всех обрабатываемых файлов. В атрибутах я задал видеопоток с битрейтом в 1500 кбит/с и кодеком x264 (разрешение 1920х1080), а к аудиопотоку применил кодек aac с частотой 44,1 кГц и битрейтом 256 килобит/с.

#### Аргументы для FFMPEG ####
$ffmpegarg = "-f concat -safe 0 -fflags +igndts -i list.txt -vsync 1 -async 1 -vf ""scale=1920:1080:force_original_aspect_ratio=decrease, pad=1920:1080:(ow-iw)/2:(oh-ih)/2, setsar=1, fps=24"" -s 1920x1080 -vcodec libx264 -b 1500k -bufsize 20M -acodec aac -strict experimental -ar 44100 -ab 256k -strict -2 -movflags +faststart reklama.mp4"

#### Запуск склеивания ####
Start-Process -FilePath ffmpeg.exe -ArgumentList $ffmpegarg -Wait -NoNewWindow

Теперь остаётся только ждать завершения процесса склеивания и конвертации.

Как только файл будет полностью сформирован — удаляем уже не нужные нам txt и mts файлы:

#### Удаление лишних файлов ####
try
{
Remove-Item -Path list.txt
Get-ChildItem -Filter *.mts | ForEach-Object { Remove-Item -Path "$($_.Name)" }
}
catch { Write-Host "В папке отсутствуют временные файлы для удаления" -ForegroundColor Red }