Главная / Заметки / Нетрадиционные методы видеомонтажа


Нетрадиционные методы видеомонтажа

31 декабря 2022 г.

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

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

Для видеомонтажа использовались:

Стоит отметить, что для генерации титров можно использовать любое другое средство. Сам по себе ImageMagick может накладывать титры.

Сначала я изучил кое-какие материалы по этому поводу. В этой статье автор рассказывает, как он монтировал видео примерно таким же образом. При монтаже он не перекодировал видео и не использовал каких-либо сложных склеек. Я стал пробовать.

Сначала я подготовил видео, которое будет использоваться в качестве исходного материала. Непосредственно снятое видео весило 5,5 ГБ, так что я его пережал. К тому же, три четверти площади видео были абсолютно не нужны, поэтому кадр был обрезан в четыре раза (была взята первая четверть).

Для монтажа я написал специальный скрипт, в котором определил несколько подпрограмм. Одна подпрограмма извлекала нужный фрагмент видео, другая — делала видео из картинки и накладывала фрагмент звука из оригинала. Затем эти подпрограммы писались в том порядке, в каком порядке будет произведён монтаж. Каждая из этих подпрограмм генерировала видеофайл в формате MPEGTS, при этом оригинальный поток обязательно копировался.

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

Другая проблема заключалась в том, что видео на стыке оригинала и слайда зависало на несколько секунд. Всё это из-за того, что не совпадала кадровая частота: у оригинала было 30 кадров в секунду, а у слайда — 25. Проблема эта тоже была быстро решена путём замены кадровой частоты у слайдов.

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

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

Скрипт выглядит таким образом:

#!/bin/sh

# Файл lekciya.mp4 является исходным файлом, полученным из оригинала с телефона
# при помощи следующей команды:
#     
#     ffmpeg -i orig.mp4 -vf crop=960:540:960:0 -r 30 \
#         -x264opts keyint=10 lekciya.mp4
#
# Файл proglan.pdf представляет из себя презентацию, размещённую по адресу
# <https://danila-kondr.codeberg.page/papers/proglan.pdf>
#
# Результат размещён по адресу: 
# <https://www.youtube.com/watch?v=eeRyMqWUruk>

HEIGHT=540
DPI=300

TITLE=

SRC1=lekciya.mp4
PROGLAN=proglan.pdf
FPS=30
SND=snd.m4a
OUT=vid.mp4
DURATION=0

CONCAT='concat:'

title() {
  echo $1
  latex "$1"
  dvipng -D $DPI "$1"
  find . -name "$TITLE*.png" -exec mogrify -resize x$HEIGHT {} \;

  TITLE="$1"
}

dezero() {
  NUM=$1
  if [ $NUM -eq 0 ]
  then
    echo 0
  else
    echo $NUM | sed 's/^0*//g'
  fi
}

t() {
  MIN=`dezero $1`
  SEC=`dezero $2`
  FRAME=`dezero $3`

  TIME=`echo "scale=6; ($MIN * 60) + $SEC + $FRAME/$FPS" | bc`
  if [ $((MIN*60+SEC)) -eq 0 ]
  then
    TIME="0.$TIME"
  fi
  echo $TIME
}

td() {
  echo "$2-$1" | bc
}

gl() {
  SS=`t $1 $2 $3`
  TO=`t $4 $5 $6`
   T=`td $SS $TO`

  DURATION=`echo "$DURATION+$T" | bc`

  shift 6
  OUT=$1

  echo pipe $OUT
  CONCAT="$CONCAT$OUT|"
  mkfifo $OUT

  ( ffmpeg -y -loglevel warning \
    -ss "$SS" -i $SRC1 -t "$T" \
    -c copy -bsf:v h264_mp4toannexb \
    "$OUT"; echo "$OUT consumed" ) &
}

gi() {
  N=$1
  IN=`printf "proglan/%02d.png" $N`
  shift

  SS=`t $1 $2 $3`
  TO=`t $4 $5 $6`
   T=`td $SS $TO`

  DURATION=`echo "$DURATION+$T" | bc`

  shift 6
  OUT=$1

  echo pipe $OUT
  CONCAT="$CONCAT$OUT|"
  mkfifo $OUT

  ( ffmpeg -y -loglevel error \
    -loop 1 -i "$IN" \
    -ss $SS -i $SND -t $T \
    -r $FPS -pix_fmt yuvj420p \
    -vf 'pad=960:540:(ow-iw)/2:(oh-ih)/2' \
    -c:v libx264 -c:a copy \
    -shortest $OUT; echo "$OUT consumed" ) &
}

gt() {
   T=`t $1 $2 $3`

  DURATION=`echo "$DURATION+$T" | bc`

  shift 3
  OUT=$1
  IN=$2
  PNG="$TITLE$IN.png"

  echo "$PNG"

  echo pipe $OUT
  CONCAT="$CONCAT$OUT|"
  mkfifo $OUT

  ( ffmpeg -y -loglevel error \
    -loop 1 -i "$PNG" \
    -f lavfi -i "anullsrc=r=48000" -t $T \
    -r $FPS -pix_fmt yuvj420p \
    -vf 'scale=h=540,pad=960:540:(ow-iw)/2:(oh-ih)/2' \
    -c:v libx264 -c:a aac \
    -shortest $OUT; echo "$OUT consumed" ) &
}

snd() {
  echo $1
  ffmpeg -loglevel error \
    -i "$SRC1" -vn -c:a copy \
    "$1"
}

img() {
  echo proglan/*.png
  mkdir proglan
  gs -dBATCH -dNOPAUSE \
    -g720x540 -r143 -sDEVICE=png48 \
    -dTextAlphaBits=4 \
    -dGraphicsAlphaBits=4 \
    -sOutputFile=proglan/%02d.png \
    $PROGLAN
}

test -e $SND || snd $SND
test -d proglan || img
title title

# Раскадровка

gt             00 05 00 lek00.ts 1
gl    06 14 00 06 55 00 lek01.ts
gi 01 06 55 00 07 05 00 lek02.ts
gl    07 05 00 17 24 00 lek03.ts
gi 13 17 24 00 18 03 00 lek04.ts
gl    18 03 00 20 05 00 lek05.ts
gi 17 20 05 00 20 20 00 lek06.ts
gi 18 20 20 00 20 38 00 lek07.ts
gl    20 38 00 22 30 00 lek08.ts
gi 20 22 30 00 22 38 00 lek09.ts
gl    22 38 00 22 54 00 lek10.ts
gi 21 22 54 00 23 00 00 lek11.ts
gi 22 23 00 00 23 08 00 lek12.ts
gi 23 23 08 00 23 12 00 lek13.ts
gl    23 12 00 24 42 00 lek14.ts
gi 26 24 42 00 24 48 00 lek15.ts
gl    24 48 00 26 06 00 lek16.ts 
gi 28 26 06 00 26 38 00 lek17.ts
gl    26 38 00 27 24 00 lek18.ts
gi 31 27 24 00 27 48 00 lek19.ts
gl    27 48 00 40 48 00 lek20.ts
gi 48 40 48 00 40 58 00 lek21.ts
gl    40 58 00 41 50 00 lek22.ts
gt             00 05 00 lek23.ts 2

echo $OUT
ffmpeg -loglevel error -i "$CONCAT" -c copy -bsf:a aac_adtstoasc "$OUT"

В итоге видео было смонтировано полностью. Оно корректно отображается во всех видеопроигрывателях.

Преимущества данного метода монтажа:

Главный же недостаток этого метода в том, что он довольно неудобен. Сначала надо просмотреть видео, записать, где и как надо его обрезать, потом написать нужный скрипт. Для всего этого нужны определённые знания.

Кроме того, если необходимо наложить текст или сделать непрямую склейку, необходимо перекодировать видео, либо разложить его на картинки и применять этот эффект к каждому кадру. Это приводит к увеличению объёма, необходимого для видеомонтажа.

P. S. Я выяснил, что существует более удобное средство для видеомонтажа из консоли: Melt из MLT Framework.