Клип встаёт на курсор, монтаж едет следом, сборка ждёт конца правки
Три жалобы из настоящей работы над фильмом, и все про одно: положить кусок туда, куда нужно, было нельзя. Файл из списка падал в конец дорожки, стоило месту под ним оказаться занятым, — а занято оно почти всегда: клип длиной в целый файл упирается хвостом в то, что правее, даже когда начало легло в пустоту. Теперь монтаж раздвигается: MakeRoomAt режет все дорожки в точке вставки и сдвигает всё, что начинается с неё и правее, на длину нового клипа. Звук и надписи едут вместе с картинкой, иначе синхрон рассыплется. Точка вставки при этом округляется к кадровой сетке. Без этого вставка давала наезд в пять миллисекунд: рез внутри раздвижки округляет время сам, а клип клался на неокруглённый курсор. Отсюда и был «кроссфейд без видимого перекрытия». При перетаскивании у кромки окна монтаж прокручивается сам: иначе при большом увеличении кусок упирается в край, и переносить его приходится в несколько приёмов. Прилипание заодно считается и по концу клипа — иначе клип встаёт началом в дыру, а хвостом на соседа, и это снова наезд. Тяжёлая сборка больше не начинается с первым движением: список кусков пересобирается сразу, а ffmpeg запускается, когда монтаж постоял тихо полторы секунды и клип никто не держит мышью. Сам наезд перестал быть сюрпризом — строка состояния называет дорожку, величину и клипы. Проверки числами: раздвижка монтажа и прилипание хвостом. С окном: insertat.sh, dragscroll.sh, genquiet.sh. Все три прогнаны и на прежней сборке, где падают: клип в конце дорожки вместо курсора, 0.667 с вместо 4.238 с, сборка через 0.20 с вместо 1.62 с.
This commit is contained in:
parent
6c81fd0907
commit
0fa9bb6130
6 changed files with 1068 additions and 77 deletions
143
tests/dragscroll.sh
Executable file
143
tests/dragscroll.sh
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
#!/bin/sh
|
||||
# Монтаж едет следом, пока тащишь клип у кромки окна.
|
||||
#
|
||||
# При большом увеличении видно секунды, а двигать кусок надо на минуты.
|
||||
# Если монтаж не прокручивается, клип упирается в край окна и дальше не
|
||||
# идёт: приходится отпускать, прокручивать, брать снова — и так десятки
|
||||
# раз.
|
||||
#
|
||||
# Проверяется так: монтаж из одного клипа, сильное увеличение, клип
|
||||
# тащится к правому краю и там удерживается на месте. Рука стоит, значит
|
||||
# двигать может только прокрутка. Результат читается из сохранённого
|
||||
# проекта, а не с картинки.
|
||||
#
|
||||
# Запуск (экран :99 поднят):
|
||||
# tests/dragscroll.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; }
|
||||
|
||||
# Монтаж из одного клипа: второй убран, чтобы перетаскивание ни на кого
|
||||
# не наезжало и не заказывало переход.
|
||||
python3 - "$SRC" "$PROJ" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
keep = None
|
||||
for t in d['tracks']:
|
||||
for c in t['clips']:
|
||||
if c['kind'] == 'video' and c['tl_start'] < 0.001:
|
||||
keep = c.get('group')
|
||||
for t in d['tracks']:
|
||||
t['clips'] = [c for c in t['clips'] if c.get('group') == keep]
|
||||
json.dump(d, open(sys.argv[2], 'w'), ensure_ascii=False)
|
||||
PY
|
||||
|
||||
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
|
||||
rm -rf test/поток/профиль-прокрутка
|
||||
mkdir -p test/поток/профиль-прокрутка
|
||||
rm -f "$PROJ.autosave"
|
||||
SP=$PWD/test/поток/профиль-прокрутка
|
||||
XDG_CONFIG_HOME=$SP XDG_CACHE_HOME=$SP XDG_DATA_HOME=$SP DISPLAY=$DISP \
|
||||
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
|
||||
|
||||
start() {
|
||||
$X key ctrl+s
|
||||
sleep 2
|
||||
python3 -c "
|
||||
import json
|
||||
d = json.load(open('$PROJ'))
|
||||
for t in d['tracks']:
|
||||
for c in t['clips']:
|
||||
if c['kind'] == 'video':
|
||||
print('%.3f' % c['tl_start']); raise SystemExit
|
||||
print('0')
|
||||
"
|
||||
}
|
||||
|
||||
echo " начало клипа до правок: $(start) с"
|
||||
|
||||
ROW=${NLVE_ROW:-505} # высота видеодорожки в окне
|
||||
GRAB=${NLVE_GRAB:-650} # точка захвата внутри клипа (клип начинается с 588)
|
||||
EDGE=1255 # кромка у правого края окна
|
||||
|
||||
# Увеличение: шесть щелчков колесом с Ctrl, опора в точке захвата, чтобы
|
||||
# клип из-под указателя не уехал.
|
||||
I=0
|
||||
while [ $I -lt 6 ]; do
|
||||
$X mousemove $GRAB $ROW
|
||||
$X keydown ctrl click 4 keyup ctrl
|
||||
sleep 0.3
|
||||
I=$((I + 1))
|
||||
done
|
||||
sleep 1
|
||||
|
||||
# Берём клип и тащим к правому краю, где и держим: рука стоит, едет
|
||||
# только прокрутка.
|
||||
$X mousemove $GRAB $ROW mousedown 1
|
||||
sleep 0.3
|
||||
$X mousemove 800 $ROW
|
||||
sleep 0.3
|
||||
$X mousemove $EDGE $ROW
|
||||
sleep 6
|
||||
$X mouseup 1
|
||||
sleep 2
|
||||
|
||||
MOVED=$(start)
|
||||
echo " начало клипа после перетаскивания с удержанием: $MOVED с"
|
||||
|
||||
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 -c "
|
||||
moved = $MOVED
|
||||
# После шести щелчков колесом в окне видно около полутора секунд монтажа.
|
||||
# Указатель доезжает до кромки и встаёт, поэтому без прокрутки клип
|
||||
# уезжает на эти полторы секунды и больше никуда. С прокруткой за шесть
|
||||
# секунд удержания набегает втрое больше. Порог стоит между измеренными
|
||||
# величинами: 1.1 с на сборке без прокрутки, 3+ с с прокруткой.
|
||||
if moved < 2.5:
|
||||
print(' ПЛОХО: клип уехал всего на %.3f с — монтаж не прокручивался' % moved)
|
||||
raise SystemExit(1)
|
||||
print(' ok: клип уехал на %.3f с, дальше видимой части' % moved)
|
||||
"
|
||||
117
tests/genquiet.sh
Executable file
117
tests/genquiet.sh
Executable file
|
|
@ -0,0 +1,117 @@
|
|||
#!/bin/sh
|
||||
# Тяжёлая сборка начинается не сразу, а когда правка закончилась.
|
||||
#
|
||||
# Наезд двух клипов на одной дорожке — это заказ на переход, и считается
|
||||
# он минутами. Пока человек подбирает место, заказ меняется каждым
|
||||
# движением руки, и начатая работа уходит в корзину. Поэтому список
|
||||
# кусков пересобирается сразу (монтаж обязан показывать правду), а вот
|
||||
# ffmpeg запускается, когда монтаж постоял тихо.
|
||||
#
|
||||
# Проверяется так: клип тащится на соседа, создавая наезд. С момента,
|
||||
# когда кнопку отпустили, считается время до первой команды ffmpeg в
|
||||
# журнале. Оно обязано быть близко к заданной тишине, но работа всё-таки
|
||||
# обязана начаться — отложить не значит забыть.
|
||||
#
|
||||
# Журнал только читается: усечь его значит снести запись чужой работы.
|
||||
#
|
||||
# Запуск (экран :99 поднят):
|
||||
# tests/genquiet.sh [<путь к nlve>]
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
BIN=${1:-$PWD/nlve}
|
||||
DISP=${NLVE_DISPLAY:-:99}
|
||||
SRC=$PWD/test/поток/пара.nlve
|
||||
PROJ=$PWD/test/поток/тишина.nlve
|
||||
LOG=$(dirname "$BIN")/nlve.log
|
||||
|
||||
[ -f "$SRC" ] || { echo "нет монтажа $SRC"; exit 2; }
|
||||
|
||||
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 -rf "$PROJ-cache"
|
||||
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"
|
||||
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 3
|
||||
|
||||
ROW=${NLVE_ROW:-505}
|
||||
# Сколько команд ffmpeg в журнале сейчас. Считаем строки, а не чистим
|
||||
# файл: журнал общий, и усечение уносит чужую работу.
|
||||
runs() { grep -c '^> .*ffmpeg' "$LOG" 2>/dev/null || echo 0; }
|
||||
|
||||
BEFORE=$(runs)
|
||||
echo " команд в журнале до правки: $BEFORE"
|
||||
|
||||
# Тащим первый клип вправо на соседа: наезд примерно в секунду.
|
||||
$X mousemove 650 $ROW mousedown 1
|
||||
sleep 0.3
|
||||
$X mousemove 670 $ROW
|
||||
sleep 0.3
|
||||
$X mousemove 683 $ROW
|
||||
sleep 0.3
|
||||
$X mouseup 1
|
||||
|
||||
# С этой точки отсчёт. Опрашиваем журнал почаще, чтобы поймать момент
|
||||
# запуска, а не ждать наугад.
|
||||
T0=$(date +%s.%N)
|
||||
WAITED=0
|
||||
while [ "$WAITED" -lt 60 ]; do
|
||||
N=$(runs)
|
||||
[ "$N" -gt "$BEFORE" ] && break
|
||||
sleep 0.2
|
||||
WAITED=$((WAITED + 1))
|
||||
done
|
||||
T1=$(date +%s.%N)
|
||||
AFTER=$(runs)
|
||||
|
||||
for p in $(pgrep -x nlve); do
|
||||
tr '\0' '\n' < /proc/$p/environ 2>/dev/null | grep -qx "DISPLAY=$DISP" && kill $p
|
||||
done
|
||||
pkill -f "тишина.nlve-cache" 2>/dev/null || true
|
||||
rm -f "$PROJ.autosave"
|
||||
|
||||
python3 -c "
|
||||
задержка = $T1 - $T0
|
||||
было, стало = $BEFORE, $AFTER
|
||||
print(' команд после правки: %d, пауза до запуска: %.2f с' % (стало, задержка))
|
||||
if стало <= было:
|
||||
print(' ПЛОХО: сборка не началась вовсе — отложить не значит забыть')
|
||||
raise SystemExit(1)
|
||||
# Задано полторы секунды тишины. Нижняя граница с запасом на разброс
|
||||
# опроса, верхняя — чтобы отложенное не превратилось в забытое.
|
||||
if задержка < 1.2:
|
||||
print(' ПЛОХО: сборка началась через %.2f с — правку не дождались'
|
||||
% задержка)
|
||||
raise SystemExit(1)
|
||||
if задержка > 6:
|
||||
print(' ПЛОХО: сборка началась только через %.2f с' % задержка)
|
||||
raise SystemExit(1)
|
||||
print(' ok: сборка дождалась конца правки и всё-таки началась')
|
||||
"
|
||||
158
tests/insertat.sh
Executable file
158
tests/insertat.sh
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
#!/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
|
||||
|
|
@ -637,6 +637,166 @@ end;
|
|||
Раньше курсор оставался там, где был, а рез мог попасть на долю кадра.
|
||||
Теперь рез кладётся на кадровую сетку, а курсор встаёт ровно на место
|
||||
реза — с ним и работают дальше. }
|
||||
{ Раздвижка монтажа под вставку.
|
||||
|
||||
Клип из списка файлов встаёт туда, где стоит курсор, а не в конец
|
||||
дорожки. Место под ним освобождается: клип, накрывающий точку вставки,
|
||||
режется, и всё от этой точки правее едет вправо на длину нового — на
|
||||
всех дорожках сразу, иначе звук отстанет от картинки. }
|
||||
procedure CheckMakeRoom;
|
||||
var
|
||||
Proj: TProject;
|
||||
Info: TMediaInfo;
|
||||
Media: TMediaItem;
|
||||
Timeline: TTimelineControl;
|
||||
Moved, I: Integer;
|
||||
Gaps, Overlaps: Integer;
|
||||
Step, At, Shift: Double;
|
||||
Track: TTrack;
|
||||
begin
|
||||
WriteLn;
|
||||
WriteLn('Раздвижка монтажа под вставку');
|
||||
WriteLn;
|
||||
|
||||
Info := TestInfo;
|
||||
Info.FpsNum := 60000;
|
||||
Info.FpsDen := 1001;
|
||||
|
||||
Proj := TProject.Create;
|
||||
Timeline := TTimelineControl.Create(nil);
|
||||
try
|
||||
Proj.Clear;
|
||||
Proj.AddTrack('V1');
|
||||
Proj.AddTrackOfKind('A1', tkAudio);
|
||||
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
|
||||
Proj.Master := Info;
|
||||
{ Три клипа встык на картинке и столько же на звуке: 0..10, 10..20,
|
||||
20..30. }
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 0, 10, I * 10);
|
||||
Proj.AddClip(Proj.Tracks[1], Media, 0, 10, I * 10);
|
||||
end;
|
||||
Timeline.Project := Proj;
|
||||
|
||||
{ Вставляем пять секунд посреди второго клипа.
|
||||
|
||||
Ожидаемое считается по той же кадровой сетке, а не круглыми
|
||||
числами: при 60000/1001 ни 15, ни 5 на кадр не попадают, и точка
|
||||
реза с величиной сдвига садятся на ближайшие кадры. }
|
||||
Step := Info.FpsDen / Info.FpsNum;
|
||||
At := Round(15 / Step) * Step;
|
||||
Shift := Round(5 / Step) * Step;
|
||||
Moved := Timeline.MakeRoomAt(15, 5);
|
||||
CheckTrue('что-то отодвинуто: ' + IntToStr(Moved), Moved >= 4);
|
||||
CheckTrue('второй клип разошёлся надвое: клипов стало ' +
|
||||
IntToStr(Proj.Tracks[0].Count), Proj.Tracks[0].Count = 4);
|
||||
CheckTrue('звук разошёлся так же', Proj.Tracks[1].Count = 4);
|
||||
|
||||
{ Голова второго клипа осталась на месте, хвост уехал ровно на пять. }
|
||||
CheckTrue('голова на месте: ' + SecondsToStr(Proj.Tracks[0][1].TlStart),
|
||||
Abs(Proj.Tracks[0][1].TlStart - 10) < 0.001);
|
||||
CheckTrue('хвост отъехал на длину вставки: ' +
|
||||
SecondsToStr(Proj.Tracks[0][2].TlStart),
|
||||
Abs(Proj.Tracks[0][2].TlStart - (At + Shift)) < 0.001);
|
||||
CheckTrue('третий клип отъехал на столько же: ' +
|
||||
SecondsToStr(Proj.Tracks[0][3].TlStart),
|
||||
Abs(Proj.Tracks[0][3].TlStart - (20 + Shift)) < 0.001);
|
||||
CheckTrue('звук уехал вместе с картинкой',
|
||||
Abs(Proj.Tracks[1][2].TlStart - (At + Shift)) < 0.001);
|
||||
CheckTrue('освобождено ровно столько, сколько просили: ' +
|
||||
SecondsToStr(Proj.Tracks[0][2].TlStart - Proj.Tracks[0][1].TlEnd),
|
||||
Abs((Proj.Tracks[0][2].TlStart - Proj.Tracks[0][1].TlEnd) - Shift)
|
||||
< 0.001);
|
||||
|
||||
{ В освобождённом месте никого нет, и больше нигде не появилось ни
|
||||
дыр, ни наездов. }
|
||||
CheckTrue('место освобождено', Proj.Tracks[0].ClipAt(At + 1) = nil);
|
||||
Gaps := 0;
|
||||
Overlaps := 0;
|
||||
for I := 0 to Proj.TrackCount - 1 do
|
||||
begin
|
||||
Track := Proj.Tracks[I];
|
||||
for Moved := 0 to Track.Count - 2 do
|
||||
if Track[Moved].TlEnd > Track[Moved + 1].TlStart + 0.001 then
|
||||
Inc(Overlaps)
|
||||
else if (Track[Moved].TlEnd < Track[Moved + 1].TlStart - 0.001) and
|
||||
(Abs(Track[Moved].TlEnd - At) > 0.001) then
|
||||
Inc(Gaps);
|
||||
end;
|
||||
CheckTrue('наездов не появилось', Overlaps = 0);
|
||||
CheckTrue('лишних дыр не появилось', Gaps = 0);
|
||||
|
||||
{ Вставка в свободное место за концом монтажа ничего не двигает. }
|
||||
Moved := Timeline.MakeRoomAt(100, 5);
|
||||
CheckTrue('за концом монтажа двигать нечего', Moved = 0);
|
||||
finally
|
||||
Timeline.Free;
|
||||
Proj.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Клип притягивается к границе и хвостом, а не только головой.
|
||||
|
||||
Подводят кусок к началу соседа слева: голова встаёт в свободное место,
|
||||
а хвост при этом накрывает соседа. Наезд на одной дорожке — это заказ
|
||||
на переход, то есть минуты сборки из-за промаха на полпикселя. }
|
||||
procedure CheckSnapTail;
|
||||
var
|
||||
Proj: TProject;
|
||||
Info: TMediaInfo;
|
||||
Media: TMediaItem;
|
||||
Timeline: TTimelineControl;
|
||||
Mover: TClip;
|
||||
Got: Double;
|
||||
begin
|
||||
WriteLn;
|
||||
WriteLn('Прилипание хвостом');
|
||||
WriteLn;
|
||||
|
||||
Info := TestInfo;
|
||||
Info.FpsNum := 60000;
|
||||
Info.FpsDen := 1001;
|
||||
|
||||
Proj := TProject.Create;
|
||||
Timeline := TTimelineControl.Create(nil);
|
||||
try
|
||||
Proj.Clear;
|
||||
Proj.AddTrack('V1');
|
||||
Media := Proj.AddMedia('/несуществующий/видео.mp4', Info);
|
||||
Proj.Master := Info;
|
||||
{ Сосед занимает 20..30, двигаемый длиной 4 стоит пока в сторонке. }
|
||||
Proj.AddClip(Proj.Tracks[0], Media, 0, 10, 20);
|
||||
Mover := Proj.AddClip(Proj.Tracks[0], Media, 0, 4, 50);
|
||||
Timeline.Project := Proj;
|
||||
Timeline.PixelsPerSecond := 10; { допуск прилипания — 1.2 с }
|
||||
|
||||
{ Голова метит в 15.7, хвост при этом пришёлся бы на 19.7 — почти
|
||||
встык с соседом. Правильный ответ: 16, тогда хвост ровно на 20. }
|
||||
Got := Timeline.SnapTime(15.7, Mover, Mover.Duration);
|
||||
CheckTrue('хвост сел на начало соседа: ' + SecondsToStr(Got),
|
||||
Abs(Got - 16) < 0.001);
|
||||
|
||||
{ Голова у границы по-прежнему липнет головой: там ближе она. }
|
||||
Got := Timeline.SnapTime(19.8, Mover, Mover.Duration);
|
||||
CheckTrue('голова села на начало соседа: ' + SecondsToStr(Got),
|
||||
Abs(Got - 20) < 0.001);
|
||||
|
||||
{ Без длины поведение прежнее: хвост не рассматривается. }
|
||||
Got := Timeline.SnapTime(15.7, Mover);
|
||||
CheckTrue('без длины притяжения хвостом нет: ' + SecondsToStr(Got),
|
||||
Abs(Got - 15.7) < 0.001);
|
||||
|
||||
{ Хвост липнет и к концу соседа: клип встаёт вплотную справа. }
|
||||
Got := Timeline.SnapTime(25.8, Mover, Mover.Duration);
|
||||
CheckTrue('хвост сел на конец соседа: ' + SecondsToStr(Got),
|
||||
Abs(Got - 26) < 0.001);
|
||||
finally
|
||||
Timeline.Free;
|
||||
Proj.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CheckSplitAndPlayhead;
|
||||
var
|
||||
Proj: TProject;
|
||||
|
|
@ -994,6 +1154,8 @@ begin
|
|||
CheckRubberBand;
|
||||
CheckFrameGridRepair;
|
||||
CheckSplitAndPlayhead;
|
||||
CheckMakeRoom;
|
||||
CheckSnapTail;
|
||||
CheckSplitAllTracks;
|
||||
CheckGapTolerance;
|
||||
CheckSourceRect;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue