Три жалобы из настоящей работы над фильмом, и все про одно: положить кусок туда, куда нужно, было нельзя. Файл из списка падал в конец дорожки, стоило месту под ним оказаться занятым, — а занято оно почти всегда: клип длиной в целый файл упирается хвостом в то, что правее, даже когда начало легло в пустоту. Теперь монтаж раздвигается: MakeRoomAt режет все дорожки в точке вставки и сдвигает всё, что начинается с неё и правее, на длину нового клипа. Звук и надписи едут вместе с картинкой, иначе синхрон рассыплется. Точка вставки при этом округляется к кадровой сетке. Без этого вставка давала наезд в пять миллисекунд: рез внутри раздвижки округляет время сам, а клип клался на неокруглённый курсор. Отсюда и был «кроссфейд без видимого перекрытия». При перетаскивании у кромки окна монтаж прокручивается сам: иначе при большом увеличении кусок упирается в край, и переносить его приходится в несколько приёмов. Прилипание заодно считается и по концу клипа — иначе клип встаёт началом в дыру, а хвостом на соседа, и это снова наезд. Тяжёлая сборка больше не начинается с первым движением: список кусков пересобирается сразу, а ffmpeg запускается, когда монтаж постоял тихо полторы секунды и клип никто не держит мышью. Сам наезд перестал быть сюрпризом — строка состояния называет дорожку, величину и клипы. Проверки числами: раздвижка монтажа и прилипание хвостом. С окном: insertat.sh, dragscroll.sh, genquiet.sh. Все три прогнаны и на прежней сборке, где падают: клип в конце дорожки вместо курсора, 0.667 с вместо 4.238 с, сборка через 0.20 с вместо 1.62 с.
158 lines
7.4 KiB
Bash
Executable file
158 lines
7.4 KiB
Bash
Executable file
#!/bin/sh
|
||
# Файл из списка встаёт на курсор, а не в конец дорожки.
|
||
#
|
||
# Раньше клип падал в самый конец, стоило месту под ним оказаться
|
||
# занятым, — а оно занято почти всегда: клип длиной в целый файл упирается
|
||
# хвостом в то, что правее, даже когда начало легло в свободное место.
|
||
# Потом человек тащил его на нужное время и получал наезд, то есть заказ
|
||
# на сборку перехода.
|
||
#
|
||
# Проверяется так: курсор ставится посреди первого клипа, файл из списка
|
||
# добавляется на ту же дорожку. Клип обязан встать на курсор, монтаж —
|
||
# раздвинуться ровно на его длину, наездов и дыр появиться не должно.
|
||
# Всё читается из сохранённого проекта, а не с картинки.
|
||
#
|
||
# Запуск (экран :99 поднят):
|
||
# tests/insertat.sh [<путь к nlve>]
|
||
set -e
|
||
cd "$(dirname "$0")/.."
|
||
BIN=${1:-$PWD/nlve}
|
||
DISP=${NLVE_DISPLAY:-:99}
|
||
SRC=$PWD/test/поток/пара.nlve
|
||
PROJ=$PWD/test/поток/вставка.nlve
|
||
|
||
[ -f "$SRC" ] || { echo "нет монтажа $SRC"; exit 2; }
|
||
|
||
# Невидимый экран поднят через xvfb-run, у него свой файл доступа, а в
|
||
# окружении обычно лежит файл рабочего экрана. С чужим файлом xdotool не
|
||
# видит ни одного окна, и проверка «проходит» вслепую, ничего не проверив.
|
||
export DISPLAY=$DISP
|
||
reachable() { timeout 5 xdotool getdisplaygeometry >/dev/null 2>&1; }
|
||
if ! reachable; then
|
||
for p in $(pgrep -x Xvfb); do
|
||
A=$(tr '\0' '\n' < /proc/$p/cmdline | grep -A1 -x -- -auth | tail -1)
|
||
[ -f "$A" ] || continue
|
||
XAUTHORITY=$A; export XAUTHORITY
|
||
reachable && break
|
||
done
|
||
fi
|
||
reachable ||
|
||
{ echo "нет доступа к экрану $DISP: подними его (xvfb-run) и повтори"; exit 2; }
|
||
|
||
cp "$SRC" "$PROJ"
|
||
for p in $(pgrep -x nlve); do
|
||
tr '\0' '\n' < /proc/$p/environ 2>/dev/null | grep -qx "DISPLAY=$DISP" && kill $p
|
||
done
|
||
sleep 1
|
||
SP=$PWD/test/поток/профиль-вставка
|
||
rm -rf "$SP"; mkdir -p "$SP"
|
||
rm -f "$PROJ.autosave"
|
||
XDG_CONFIG_HOME=$SP XDG_CACHE_HOME=$SP XDG_DATA_HOME=$SP \
|
||
setsid "$BIN" "$PROJ" >/dev/null 2>&1 < /dev/null &
|
||
sleep 14
|
||
|
||
X="timeout 8 xdotool"
|
||
# Окно ищем по имени монтажа и проверяем, что оно на экране: по одному
|
||
# «nlve» находятся и обломки прошлых запусков, нажатия в них уходят в
|
||
# никуда, и проверка молча показывает «ничего не произошло».
|
||
WID=
|
||
for w in $($X search --name "вставка.nlve" 2>/dev/null); do
|
||
xwininfo -id "$w" 2>/dev/null | grep -q IsViewable && { WID=$w; break; }
|
||
done
|
||
[ -n "$WID" ] || { echo "живое окно редактора не найдено"; exit 2; }
|
||
$X windowmove "$WID" 0 0 windowsize "$WID" 1270 990
|
||
sleep 2
|
||
|
||
$X mousemove 700 466 click 1 # курсор воспроизведения — посреди первого клипа
|
||
sleep 1
|
||
$X mousemove 200 512 click 1 # выбрать файл в списке проекта
|
||
sleep 1
|
||
# Меню списка: «Добавить на дорожку» — первый пункт, в подменю первой
|
||
# стоит V1, она же и нужна.
|
||
$X click 3
|
||
sleep 1.5
|
||
$X key Down; sleep 0.5
|
||
$X key Right; sleep 0.5
|
||
$X key Return; sleep 2
|
||
|
||
$X key ctrl+s
|
||
sleep 2
|
||
for p in $(pgrep -x nlve); do
|
||
tr '\0' '\n' < /proc/$p/environ 2>/dev/null | grep -qx "DISPLAY=$DISP" && kill $p
|
||
done
|
||
rm -f "$PROJ.autosave"
|
||
|
||
python3 - "$SRC" "$PROJ" <<'PY'
|
||
import json, sys
|
||
|
||
def clips(path, track):
|
||
d = json.load(open(path))
|
||
for t in d['tracks']:
|
||
if t['name'] == track:
|
||
return sorted(t['clips'], key=lambda c: c['tl_start'])
|
||
return []
|
||
|
||
было = clips(sys.argv[1], 'V1')
|
||
стало = clips(sys.argv[2], 'V1')
|
||
звук = clips(sys.argv[2], 'A1')
|
||
плохо = 0
|
||
|
||
медиа = {c['media'] for c in было}
|
||
новые = [c for c in стало if c['media'] not in медиа]
|
||
if len(новые) != 1:
|
||
print(' ПЛОХО: вставленный клип не найден (новых клипов %d)' % len(новые))
|
||
raise SystemExit(1)
|
||
новый = новые[0]
|
||
конец_было = max(c['tl_start'] + c['duration'] for c in было)
|
||
print(' вставленный клип: старт %.3f с, длина %.3f с' %
|
||
(новый['tl_start'], новый['duration']))
|
||
|
||
if новый['tl_start'] > конец_было - 0.001:
|
||
print(' ПЛОХО: клип упал в конец дорожки (%.3f с), а не на курсор'
|
||
% новый['tl_start'])
|
||
плохо = 1
|
||
elif новый['tl_start'] < 0.5:
|
||
print(' ПЛОХО: клип встал в начало, курсор был посреди первого клипа')
|
||
плохо = 1
|
||
else:
|
||
print(' ok: клип встал на курсор, внутри материала')
|
||
|
||
# Соседи отъехали ровно на длину вставки: монтаж раздвинулся, а не
|
||
# затёрся и не разъехался.
|
||
сдвиг = новый['duration']
|
||
последний_было = было[-1]
|
||
последний_стало = стало[-1]
|
||
ожидалось = последний_было['tl_start'] + сдвиг
|
||
if abs(последний_стало['tl_start'] - ожидалось) > 0.001:
|
||
print(' ПЛОХО: последний клип на %.3f с, ожидалось %.3f с'
|
||
% (последний_стало['tl_start'], ожидалось))
|
||
плохо = 1
|
||
else:
|
||
print(' ok: монтаж раздвинут ровно на длину вставки (%.3f с)' % сдвиг)
|
||
|
||
# Наезд на одной дорожке — это заказ на переход. Его тут быть не должно.
|
||
for имя, список in (('V1', стало), ('A1', звук)):
|
||
for a, b in zip(список, список[1:]):
|
||
зазор = b['tl_start'] - (a['tl_start'] + a['duration'])
|
||
if зазор < -0.001:
|
||
print(' ПЛОХО: наезд на %s в %.3f с (%.3f с)'
|
||
% (имя, b['tl_start'], -зазор))
|
||
плохо = 1
|
||
elif зазор > 0.001:
|
||
print(' ПЛОХО: дыра на %s в %.3f с (%.3f с)'
|
||
% (имя, a['tl_start'] + a['duration'], зазор))
|
||
плохо = 1
|
||
if not плохо:
|
||
print(' ok: ни наездов, ни дыр')
|
||
|
||
# Звук обязан уехать вместе с картинкой, иначе синхрон рассыплется.
|
||
if [round(c['tl_start'], 3) for c in звук] != [round(c['tl_start'], 3) for c in стало]:
|
||
print(' ПЛОХО: звук разошёлся с картинкой')
|
||
print(' картинка: %s' % ['%.3f' % c['tl_start'] for c in стало])
|
||
print(' звук: %s' % ['%.3f' % c['tl_start'] for c in звук])
|
||
плохо = 1
|
||
else:
|
||
print(' ok: звук уехал вместе с картинкой')
|
||
|
||
raise SystemExit(плохо)
|
||
PY
|