Крест перехода там, где клипы не накладываются
На монтаже рисовался косой крест через целый клип, строка состояния обещала переход, а наложения не было: ролик собирался верно. Крест уходил сам после Ctrl+Z, после любого действия с отменой и после схлопывания. Это был не наезд, а сбитый порядок клипов в списке дорожки. Список отсортирован по времени начала, и соседи по списку обязаны быть соседями по времени — на этом стоят и отрисовка креста, и сообщение о наезде. Перетаскивание же двигает не одну дорожку: связка ведёт звук за картинкой, схлопывание ведёт соседей, в режиме «все дорожки» — сразу на нескольких. Сортировалась только та дорожка, с которой тащили. Клип, уехавший с конца монтажа в середину, оставался в списке чужой дорожки последним, и её соседями по списку становились клипы, далёкие друг от друга по времени. Отмена и схлопывание лечили ровно потому, что пересортировывают списки. Сборка всё это время была права: она идёт по границам и сортирует дорожку сама. Теперь порядок возвращается всем дорожкам, чьи клипы поехали (SortMovedTracks). Оба места, считающие соседей по списку, заодно пропускают пару, идущую не по времени: страховка от того же симптома в другом месте. Проверка tests/dragorder.sh тащит связку за звук с конца монтажа в середину и читает порядок из сохранённого файла. На прежней сборке дорожка картинки остаётся в порядке 0, 14, 7.991 — проверка падает.
This commit is contained in:
parent
b078700fc7
commit
95d92a7582
4 changed files with 230 additions and 8 deletions
28
README.md
28
README.md
|
|
@ -537,7 +537,17 @@ Linux потом искала по нему `.dll`.
|
|||
дорожку, величину перекрытия и сами клипы. Переход заказывают мышью, а
|
||||
промахнуться легко — на общем плане одна точка экрана это секунды, — и
|
||||
раньше он появлялся сюрпризом: человек искал, почему сборка опять не
|
||||
кончается.
|
||||
кончается. Меряется наезд той же мерой, что и в сборке: короче половины
|
||||
кадра — не наезд, и говорить о нём незачем.
|
||||
|
||||
Косой крест наезда и это сообщение берут соседей из списка клипов
|
||||
дорожки, а он отсортирован по времени. Держать этот порядок обязан
|
||||
каждый, кто клипы двигает: перетаскивание ведёт за собой связку на
|
||||
звуковой дорожке и соседей при схлопывании, и все их дорожки
|
||||
пересортировываются (`SortMovedTracks`). Пока сортировалась только та
|
||||
дорожка, с которой тащили, монтаж рисовал крест через целый клип там,
|
||||
где ничего не накладывалось, и обещал переход, которого никто не
|
||||
заказывал.
|
||||
|
||||
Сама сборка при этом не начинается с первым же движением. Список кусков
|
||||
пересобирается сразу, а ffmpeg запускается, когда монтаж постоял тихо
|
||||
|
|
@ -1010,7 +1020,7 @@ Wine с новой библиотекой не грузилось ничего.
|
|||
уход выглядит как накапливающийся рассинхрон, которого нет. На это уже
|
||||
попались один раз.
|
||||
|
||||
Ещё семь проверок гоняют саму программу на невидимом экране
|
||||
Ещё девять проверок гоняют саму программу на невидимом экране
|
||||
(`Xvfb :99`). Рабочий экран они не занимают:
|
||||
|
||||
```
|
||||
|
|
@ -1021,9 +1031,11 @@ Wine с новой библиотекой не грузилось ничего.
|
|||
./tests/insertat.sh [<путь к nlve>] файл из списка встаёт на курсор
|
||||
./tests/dragscroll.sh [<путь к nlve>] монтаж едет следом за клипом у кромки
|
||||
./tests/genquiet.sh [<путь к nlve>] тяжёлая сборка ждёт конца правки
|
||||
./tests/dropgap.sh [<путь к nlve>] клип садится в дыру без наезда
|
||||
./tests/dragorder.sh [<путь к nlve>] связка переехала — порядок цел
|
||||
```
|
||||
|
||||
Последним пяти нужен монтаж в `test/поток/`: `длинный-фейд.nlve` — один
|
||||
Последним семи нужен монтаж в `test/поток/`: `длинный-фейд.nlve` — один
|
||||
клип с затуханием секунд на семьдесят (такой кусок сводится около
|
||||
полутора минут, и всё это время человек продолжает работать),
|
||||
`пара.nlve` — два клипа без затуханий. Путь к первому задаётся
|
||||
|
|
@ -1040,10 +1052,12 @@ xdotool не видит ни одного окна и проверка «про
|
|||
экран шире: `Xvfb :98 -screen 0 1600x1000x24` и `ЭКРАН=:98`.
|
||||
|
||||
Каждую из них стоит прогонять и на прежней сборке: проверка, которая
|
||||
проходит везде, не проверяет ничего. Все семь на сборке до своей правки
|
||||
падают. Для трёх последних замерено: клип у кромки уезжает на 0.7 с
|
||||
против 4.2 с, файл из списка падает в конец дорожки вместо курсора,
|
||||
сборка начинается через 0.2 с вместо 1.6 с.
|
||||
проходит везде, не проверяет ничего. Все девять на сборке до своей правки
|
||||
падают. Замерено: клип у кромки уезжает на 0.7 с против 4.2 с, файл из
|
||||
списка падает в конец дорожки вместо курсора, сборка начинается через
|
||||
0.2 с вместо 1.6 с, клип в дыру садится с наездом 0.007383 с, а дорожка
|
||||
картинки после переноса связки остаётся в порядке 0, 14, 7.991 — не по
|
||||
времени.
|
||||
|
||||
## Структура
|
||||
|
||||
|
|
|
|||
|
|
@ -3876,6 +3876,10 @@ begin
|
|||
begin
|
||||
A := Track[C];
|
||||
B := Track[C + 1];
|
||||
{ Соседи по списку обязаны быть соседями и по времени. Сбитый
|
||||
порядок — не наезд, и обещать по нему переход нельзя. }
|
||||
if B.TlStart < A.TlStart then
|
||||
Continue;
|
||||
{ Мера та же, какой меряет сборка (`uFlatten`): кусок короче
|
||||
половины кадра она пропускает, значит и перехода из него не
|
||||
будет. Говорить о наезде, которого никто не станет собирать,
|
||||
|
|
|
|||
|
|
@ -159,6 +159,11 @@ type
|
|||
FOnSelectionModeChange: TNotifyEvent;
|
||||
FGroupClips: array of TClip;
|
||||
FGroupOrigStarts: array of Double;
|
||||
{ Дорожки, чьи клипы поехали вместе с перетаскиваемым: связка ведёт
|
||||
звук за картинкой, схлопывание ведёт соседей, и лежат они на чужих
|
||||
дорожках. Список нужен, чтобы после каждого сдвига вернуть им
|
||||
порядок по времени. }
|
||||
FMovedTracks: array of TTrack;
|
||||
|
||||
FOnPlayheadChange: TNotifyEvent;
|
||||
FOnStructureChange: TNotifyEvent;
|
||||
|
|
@ -397,6 +402,10 @@ type
|
|||
{ Клипы одной связки с перетаскиваемым и их исходные позиции. }
|
||||
procedure CollectGroupClips(AClip: TClip);
|
||||
procedure ApplyGroupMove(const ADelta: Double);
|
||||
{ Запомнить дорожку как тронутую перетаскиванием. }
|
||||
procedure NoteMovedTrack(ATrack: TTrack);
|
||||
{ Вернуть порядок по времени всем тронутым дорожкам. }
|
||||
procedure SortMovedTracks;
|
||||
{ Сдвинуть клипы влево, закрыв дыру под курсором. }
|
||||
{ True — дыра нашлась и схлопнута. False — схлопывать было нечего:
|
||||
окно скажет об этом, чтобы команда не выглядела сломанной. }
|
||||
|
|
@ -1382,6 +1391,12 @@ begin
|
|||
begin
|
||||
A := ATrack[I];
|
||||
B := ATrack[I + 1];
|
||||
{ Соседи по списку обязаны быть соседями и по времени. Если порядок
|
||||
всё-таки сбит, это не наложение, а перепутанный список: мерять по
|
||||
нему нечего. Страховка от креста через весь клип там, где ничего не
|
||||
накладывается. }
|
||||
if B.TlStart < A.TlStart then
|
||||
Continue;
|
||||
if B.TlStart >= A.TlEnd - 0.0005 then
|
||||
Continue;
|
||||
L := TimeToX(B.TlStart);
|
||||
|
|
@ -2682,6 +2697,9 @@ var
|
|||
begin
|
||||
FGroupClips := nil;
|
||||
FGroupOrigStarts := nil;
|
||||
{ Набор тронутых дорожек начинается здесь: это первый сбор при нажатии,
|
||||
соседи по схлопыванию добавятся следом. }
|
||||
FMovedTracks := nil;
|
||||
if (FProject = nil) or (AClip = nil) or (AClip.GroupId = '') then
|
||||
Exit;
|
||||
|
||||
|
|
@ -2696,10 +2714,48 @@ begin
|
|||
SetLength(FGroupOrigStarts, N + 1);
|
||||
FGroupClips[N] := Clip;
|
||||
FGroupOrigStarts[N] := Clip.TlStart;
|
||||
NoteMovedTrack(FProject.Tracks[T]);
|
||||
Inc(N);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TTimelineControl.NoteMovedTrack(ATrack: TTrack);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
if ATrack = nil then
|
||||
Exit;
|
||||
for I := 0 to High(FMovedTracks) do
|
||||
if FMovedTracks[I] = ATrack then
|
||||
Exit;
|
||||
SetLength(FMovedTracks, Length(FMovedTracks) + 1);
|
||||
FMovedTracks[High(FMovedTracks)] := ATrack;
|
||||
end;
|
||||
|
||||
{ Порядок клипов в списке дорожки — по времени начала, и держать его
|
||||
обязан каждый, кто клипы двигает.
|
||||
|
||||
Перетаскивание двигает не одну дорожку: связка ведёт звук за картинкой,
|
||||
схлопывание ведёт соседей, а в режиме «все дорожки» — сразу на
|
||||
нескольких. Пока сортировалась только та дорожка, с которой тащили,
|
||||
клип, уехавший с конца монтажа в середину, оставался в списке
|
||||
последним. Соседями по списку становились клипы, не соседние во
|
||||
времени, и монтаж рисовал косой крест перехода через целый клип, а
|
||||
строка состояния обещала переход, которого никто не заказывал.
|
||||
|
||||
Наложения при этом не было вовсе: сборка идёт по границам и сортирует
|
||||
дорожку сама, поэтому ролик получался верный. Лечилось любой отменой и
|
||||
схлопыванием — ровно тем, что пересортировывает списки. }
|
||||
procedure TTimelineControl.SortMovedTracks;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
if FDragTrack <> nil then
|
||||
FDragTrack.Sort;
|
||||
for I := 0 to High(FMovedTracks) do
|
||||
FMovedTracks[I].Sort;
|
||||
end;
|
||||
|
||||
procedure TTimelineControl.ApplyGroupMove(const ADelta: Double);
|
||||
var
|
||||
I: Integer;
|
||||
|
|
@ -2737,6 +2793,7 @@ begin
|
|||
SetLength(FRippleOrigStarts, N + 1);
|
||||
FRippleClips[N] := Clip;
|
||||
FRippleOrigStarts[N] := Clip.TlStart;
|
||||
NoteMovedTrack(Track);
|
||||
Inc(N);
|
||||
end;
|
||||
end;
|
||||
|
|
@ -2828,7 +2885,9 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
FDragTrack.Sort;
|
||||
{ Порядок возвращаем всем, кто поехал, а не только своей дорожке:
|
||||
связка и соседи лежат на чужих. }
|
||||
SortMovedTracks;
|
||||
Invalidate;
|
||||
end;
|
||||
end;
|
||||
|
|
|
|||
145
tests/dragorder.sh
Executable file
145
tests/dragorder.sh
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
#!/bin/sh
|
||||
# После переноса связки клипы в списке дорожки идут по времени.
|
||||
#
|
||||
# Список клипов дорожки обязан быть отсортирован по началу. Двигает
|
||||
# перетаскивание не одну дорожку: связка ведёт звук за картинкой,
|
||||
# схлопывание ведёт соседей. Сортировалась же только та дорожка, с
|
||||
# которой тащили, и клип, уехавший с конца монтажа в середину, оставался
|
||||
# в списке последним. Соседями по списку становились клипы, не соседние
|
||||
# во времени, и монтаж рисовал косой крест перехода через целый клип, а
|
||||
# строка состояния обещала переход, которого никто не заказывал.
|
||||
#
|
||||
# Наложения при этом не было: сборка идёт по границам и сортирует
|
||||
# дорожку сама, поэтому ролик выходил верный, а крест уходил сам после
|
||||
# любой отмены или схлопывания — ровно от того, что списки
|
||||
# пересортировывались.
|
||||
#
|
||||
# Тащим за ЗВУК: тогда картинка едет связкой, то есть тем самым путём,
|
||||
# который порядок и ломал. Порядок читается из сохранённого проекта:
|
||||
# файл пишет клипы в порядке списка и сам их не сортирует.
|
||||
#
|
||||
# Запуск (экран :99 поднят):
|
||||
# tests/dragorder.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; }
|
||||
|
||||
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, copy
|
||||
d = json.load(open(sys.argv[1]))
|
||||
V = next(t for t in d['tracks'] if t['name'] == 'V1')
|
||||
A = next(t for t in d['tracks'] if t['name'] == 'A1')
|
||||
ов, оа = copy.deepcopy(V['clips'][0]), copy.deepcopy(A['clips'][0])
|
||||
|
||||
def клип(обр, ид, группа, старт, длина, вход):
|
||||
c = copy.deepcopy(обр)
|
||||
c.update(id=ид, group=группа, tl_start=старт, duration=длина,
|
||||
src_in=вход, fade_in=0.0, fade_out=0.0)
|
||||
return c
|
||||
|
||||
V['clips'] = [клип(ов, 'a_v', 'ga', 0.0, 4.0, 100.0),
|
||||
клип(ов, 'b_v', 'gb', 14.0, 6.0, 200.0),
|
||||
клип(ов, 'd_v', 'gd', 30.0, 3.5, 300.0)]
|
||||
A['clips'] = [клип(оа, 'a_a', 'ga', 0.0, 4.0, 100.0),
|
||||
клип(оа, 'b_a', 'gb', 14.0, 6.0, 200.0),
|
||||
клип(оа, 'd_a', 'gd', 30.0, 3.5, 300.0)]
|
||||
json.dump(d, open(sys.argv[2], 'w'), ensure_ascii=False)
|
||||
print(' связки: 0..4, 14..20 и 30..33.5 — последнюю тащим в промежуток')
|
||||
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
|
||||
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"
|
||||
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
|
||||
|
||||
AROW=${NLVE_AROW:-705} # высота звуковой дорожки A1 в окне
|
||||
|
||||
# Два щелчка колесом от себя: в окно должен попасть весь монтаж.
|
||||
$X mousemove 700 "$AROW"
|
||||
$X keydown ctrl click 5 keyup ctrl; sleep 0.4
|
||||
$X keydown ctrl click 5 keyup ctrl; sleep 1
|
||||
|
||||
# Берём последнюю связку за звук и тащим в промежуток между второй и
|
||||
# третьей.
|
||||
$X mousemove 1190 "$AROW" mousedown 1; sleep 0.4
|
||||
$X mousemove 1000 "$AROW"; sleep 0.3
|
||||
$X mousemove 850 "$AROW"; sleep 0.3
|
||||
$X mousemove 761 "$AROW"; sleep 0.5
|
||||
$X mouseup 1; sleep 1.5
|
||||
|
||||
$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 - "$PROJ" <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open(sys.argv[1]))
|
||||
плохо = 0
|
||||
двинулось = False
|
||||
for t in d['tracks']:
|
||||
кл = t['clips']
|
||||
if len(кл) < 3:
|
||||
continue
|
||||
порядок = [c['tl_start'] for c in кл]
|
||||
print(' %s: %s' % (t['name'], ' '.join('%.3f' % s for s in порядок)))
|
||||
# Клип, уехавший с конца в середину, обязан занять своё место и в
|
||||
# списке, а не остаться в нём последним.
|
||||
if порядок != sorted(порядок):
|
||||
print(' ПЛОХО: на %s клипы в списке не по времени' % t['name'])
|
||||
плохо = 1
|
||||
# Клип уехал в пустой промежуток, значит наезда быть не должно:
|
||||
# иначе проверка мерила бы заодно и настоящий переход.
|
||||
по_времени = sorted(кл, key=lambda c: c['tl_start'])
|
||||
for a, b in zip(по_времени, по_времени[1:]):
|
||||
наезд = (a['tl_start'] + a['duration']) - b['tl_start']
|
||||
if наезд > 0.5 * 1001 / 60000:
|
||||
print(' ПЛОХО: на %s наезд %.6f с — целились в пустое место'
|
||||
% (t['name'], наезд))
|
||||
плохо = 1
|
||||
for c in кл:
|
||||
if c['id'].startswith('d_') and c['tl_start'] < 13:
|
||||
двинулось = True
|
||||
if not двинулось:
|
||||
print(' ПЛОХО: клип никуда не уехал — проверка ничего не значит')
|
||||
плохо = 1
|
||||
if плохо:
|
||||
raise SystemExit(1)
|
||||
print(' ok: связка переехала, и обе дорожки остались в порядке')
|
||||
PY
|
||||
Loading…
Add table
Add a link
Reference in a new issue