Сборка вставки не начинается заново от каждой правки
Затухание в семьдесят секунд не собиралось никогда: сборка сведённого куска шла полторы минуты, и любая правка в это время сбрасывала её на ноль. В журнале это видно как «Расстановка изменилась» и тот же самый запуск ffmpeg по кругу. Причина — в вопросе, который задавался. «Нужна ли ещё эта работа» проверялось по списку недостающего, а недостающее у нас — это то, чего нет на диске. У куска, который делается прямо сейчас, файл уже есть: ffmpeg создаёт его в первую же секунду. Кусок в список не попадал, ответ был «не нужна», работа отменялась. Теперь уплощение отдельно отдаёт список нужного — независимо от того, лежит файл на диске или ещё пишется. Заодно закрыта та же дыра со вставками тишины: проверка их вообще не видела, потому что звуковые дорожки считаются отдельным проходом. Проверено обеими сторонами (tests/genrestart.sh, на настоящем материале, затухание 70 с): пять резов в стороне от затухания — один запуск и ни одной отмены, было шесть запусков и десять отмен. Удалили всё — работа прекращается за две секунды, как и прежде.
This commit is contained in:
parent
235cd00e9a
commit
d2a49343c3
4 changed files with 229 additions and 7 deletions
25
README.md
25
README.md
|
|
@ -840,6 +840,16 @@ Wine с новой библиотекой не грузилось ничего.
|
|||
привычный RGB. Записанный по привычке код даёт цвет с переставленными
|
||||
красным и синим.
|
||||
|
||||
**Список недостающего — не список нужного.** Недостающее у нас — это то,
|
||||
чего нет на диске. Но у куска, который делается прямо сейчас, файл уже
|
||||
есть: ffmpeg создаёт его в первую же секунду и пишет до конца. Проверка
|
||||
«нужна ли ещё эта работа» спрашивала по списку недостающего — и отвечала
|
||||
«не нужна» про то, что делается. Работа отменялась в разгаре и начиналась
|
||||
заново. На коротком затухании это проходило незаметно, на затухании в
|
||||
семьдесят секунд сборка не заканчивалась никогда: любая правка сбрасывала
|
||||
её. Теперь уплощение отдельно отдаёт список нужного, независимо от того,
|
||||
лежит файл на диске или ещё пишется (`tests/genrestart.sh`).
|
||||
|
||||
**MousePos у колеса мыши — не экранный.** По описанию `DoMouseWheel`
|
||||
получает точку в координатах экрана, но под gtk2 в ней уже клиентские:
|
||||
`ScreenToClient` переводит их второй раз, и точка уезжает влево ровно на
|
||||
|
|
@ -935,6 +945,21 @@ Wine с новой библиотекой не грузилось ничего.
|
|||
уход выглядит как накапливающийся рассинхрон, которого нет. На это уже
|
||||
попались один раз.
|
||||
|
||||
Ещё три проверки гоняют саму программу на невидимом экране (`Xvfb :99`).
|
||||
Рабочий экран они не занимают:
|
||||
|
||||
```
|
||||
./tests/buttonclicks.sh <окно> <x> <y> каждый щелчок по кнопке считается
|
||||
./tests/zoomanchor.py <окно> масштаб берётся от точки под мышкой
|
||||
./tests/genrestart.sh [<путь к nlve>] сборка вставки не сбрасывается зря
|
||||
```
|
||||
|
||||
Последней нужен проект с одним клипом и долгим — секунд на семьдесят —
|
||||
затуханием: такой кусок сводится около полутора минут, и всё это время
|
||||
человек продолжает работать. Путь к проекту задаётся переменной
|
||||
`NLVE_FADE_PROJECT`; в репозиторий он не входит, потому что ссылается на
|
||||
съёмку, которой здесь нет.
|
||||
|
||||
## Структура
|
||||
|
||||
| Файл | Что это |
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ function FlattenStream(AProject: TProject; AKind: TTrackKind;
|
|||
списком кусков, а дальше они складываются его же средствами. }
|
||||
function FlattenAudioTrack(AProject: TProject; ATrack: TTrack;
|
||||
const ATotal: Double;
|
||||
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean;
|
||||
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests;
|
||||
AWanted: TStrings = nil): Boolean;
|
||||
|
||||
{ Длина слышимого материала одного вида дорожек. }
|
||||
function AudibleEnd(AProject: TProject; AKind: TTrackKind): Double;
|
||||
|
|
@ -196,9 +197,14 @@ function GeneratedPath(const ACacheDir: string; const AReq: TGenRequest): string
|
|||
|
||||
Для сборки ролика это не применяется: там наложение считается заново с
|
||||
параметрами вывода, и промежуточный файл был бы лишней потерей
|
||||
качества. }
|
||||
качества.
|
||||
|
||||
AWanted — куда сложить пути всех сведённых кусков, которые этой
|
||||
расстановке нужны, независимо от того, лежат они уже на диске или ещё
|
||||
делаются. По списку недостающего этого не узнать: файл появляется в
|
||||
первую же секунду работы ffmpeg и пишется до конца. }
|
||||
procedure ResolveLayers(AProject: TProject; var ASegments: TFlatSegmentArray;
|
||||
var AMissing: TGenRequests);
|
||||
var AMissing: TGenRequests; AWanted: TStrings = nil);
|
||||
|
||||
{ Заготовка чёрной вставки самой ходовой длины.
|
||||
|
||||
|
|
@ -416,7 +422,7 @@ begin
|
|||
end;
|
||||
|
||||
procedure ResolveLayers(AProject: TProject; var ASegments: TFlatSegmentArray;
|
||||
var AMissing: TGenRequests);
|
||||
var AMissing: TGenRequests; AWanted: TStrings);
|
||||
var
|
||||
I: Integer;
|
||||
Req: TGenRequest;
|
||||
|
|
@ -443,6 +449,16 @@ begin
|
|||
Req.TlEnd := At + ASegments[I].Duration;
|
||||
At := Req.TlEnd;
|
||||
|
||||
{ Список нужного — это не то же самое, что список недостающего.
|
||||
|
||||
Недостающее — чего нет на диске. Но пока кусок делается, файл уже
|
||||
есть: ffmpeg создаёт его сразу и пишет в него до самого конца.
|
||||
Спрашивать «нужен ли ещё тот кусок, что сейчас делается» по списку
|
||||
недостающего бессмысленно — его там не будет, и работа отменится в
|
||||
разгаре. }
|
||||
if AWanted <> nil then
|
||||
AWanted.Add(Req.TargetPath);
|
||||
|
||||
if not FileExists(Req.TargetPath) then
|
||||
begin
|
||||
SetLength(AMissing, Length(AMissing) + 1);
|
||||
|
|
@ -1115,7 +1131,8 @@ end;
|
|||
|
||||
function FlattenAudioTrack(AProject: TProject; ATrack: TTrack;
|
||||
const ATotal: Double;
|
||||
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests): Boolean;
|
||||
out ASegments: TFlatSegmentArray; out AMissing: TGenRequests;
|
||||
AWanted: TStrings): Boolean;
|
||||
var
|
||||
C, N: Integer;
|
||||
At: Double;
|
||||
|
|
@ -1160,6 +1177,11 @@ var
|
|||
Req.Duration := BucketFor(Take);
|
||||
Req.TargetPath := GeneratedPath(AProject.CacheDir, Req);
|
||||
Seg.FilePath := Req.TargetPath;
|
||||
{ Нужное и недостающее — разные списки: см. пояснение у
|
||||
ResolveLayers. Кусок, который делается прямо сейчас, на диске уже
|
||||
есть, и в недостающее он не попадёт. }
|
||||
if AWanted <> nil then
|
||||
AWanted.Add(Req.TargetPath);
|
||||
{ Один и тот же файл не заказываем дважды: длинная тишина набирается
|
||||
повторами, и без этой проверки минутный кусок делался бы столько
|
||||
раз, сколько его повторов. }
|
||||
|
|
|
|||
|
|
@ -2327,11 +2327,29 @@ end;
|
|||
одна из готовящихся вставок в новом списке не нужна, работу можно
|
||||
прекращать. Ошибиться тут не страшно: отменённое, если оно всё же
|
||||
понадобится, закажут заново на следующей пересборке. }
|
||||
{ Нужна ли ещё та работа, что идёт сейчас.
|
||||
|
||||
Спрашивать по одному только списку недостающего нельзя, и это стоило
|
||||
повисшей сборки. Недостающее — то, чего нет на диске; а у куска,
|
||||
который делается прямо сейчас, файл уже есть: ffmpeg создаёт его в
|
||||
первую же секунду и пишет до конца. Поэтому сведённый кусок в списке
|
||||
недостающего не появлялся, проверка отвечала «не нужен», работа
|
||||
отменялась в разгаре — и начиналась заново с нуля.
|
||||
|
||||
На коротком затухании это проходило незаметно: кусок успевал
|
||||
посчитаться между правками. На затухании в семьдесят секунд, которое
|
||||
считается полторы минуты, любая следующая правка сбрасывала работу, и
|
||||
сборка не заканчивалась никогда.
|
||||
|
||||
Поэтому спрашиваем иначе: нужен ли расстановке сам этот путь — неважно,
|
||||
лежит он уже на диске или ещё пишется. }
|
||||
function TMainForm.GenerationStillNeeded: Boolean;
|
||||
var
|
||||
Segs, Auds: TFlatSegmentArray;
|
||||
Segs, Auds, TrackSegs: TFlatSegmentArray;
|
||||
Ovl: TPreviewOverlays;
|
||||
Missing: TGenRequests;
|
||||
Missing, TrackMissing: TGenRequests;
|
||||
Wanted: TStringList;
|
||||
TotalLen: Double;
|
||||
I, J: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
|
|
@ -2341,6 +2359,33 @@ begin
|
|||
FSettings.PreviewKey) then
|
||||
Exit;
|
||||
|
||||
Wanted := TStringList.Create;
|
||||
try
|
||||
ResolveLayers(FProject, Segs, Missing, Wanted);
|
||||
|
||||
{ Звуковые дорожки считаются тем же путём, что и при пересборке:
|
||||
вставки тишины под дыры у каждой дорожки свои, и они тоже могут
|
||||
делаться прямо сейчас. }
|
||||
TotalLen := Max(AudibleEnd(FProject, tkVideo), AudibleEnd(FProject, tkAudio));
|
||||
for I := 0 to FProject.TrackCount - 1 do
|
||||
begin
|
||||
if FProject.Tracks[I].Kind <> tkAudio then
|
||||
Continue;
|
||||
if not FProject.TrackAudible(FProject.Tracks[I]) then
|
||||
Continue;
|
||||
if FProject.Tracks[I].Count = 0 then
|
||||
Continue;
|
||||
FlattenAudioTrack(FProject, FProject.Tracks[I], TotalLen,
|
||||
TrackSegs, TrackMissing, Wanted);
|
||||
end;
|
||||
|
||||
for J := 0 to FGenerate.TargetCount - 1 do
|
||||
if Wanted.IndexOf(FGenerate.TargetPath(J)) >= 0 then
|
||||
Exit(True);
|
||||
finally
|
||||
Wanted.Free;
|
||||
end;
|
||||
|
||||
for I := 0 to High(Missing) do
|
||||
for J := 0 to FGenerate.TargetCount - 1 do
|
||||
if Missing[I].TargetPath = FGenerate.TargetPath(J) then
|
||||
|
|
|
|||
130
tests/genrestart.sh
Executable file
130
tests/genrestart.sh
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
#!/bin/sh
|
||||
# Сборка сведённого куска не должна начинаться заново от каждой правки.
|
||||
#
|
||||
# Затухание, наплыв, прозрачность — всё это предпросмотр не умеет играть
|
||||
# как есть: такой кусок заранее сводится в отдельный файл. На затухании в
|
||||
# семьдесят секунд файл считается около полутора минут, и всё это время
|
||||
# человек продолжает работать.
|
||||
#
|
||||
# Проверяется два обратных случая:
|
||||
# 1. Режем монтаж в стороне от затухания. Сам кусок от этого не
|
||||
# меняется — значит сборка обязана продолжаться, а не начинаться
|
||||
# заново.
|
||||
# 2. Удаляем всё. Кусок стал не нужен — сборка обязана прекратиться.
|
||||
#
|
||||
# Раньше первый случай отменял работу: «нужен ли ещё этот кусок»
|
||||
# спрашивалось по списку недостающего, а у куска, который делается прямо
|
||||
# сейчас, файл уже есть — ffmpeg создаёт его сразу. Кусок в список не
|
||||
# попадал, работа отменялась в разгаре и начиналась с нуля.
|
||||
#
|
||||
# Запуск: tests/genrestart.sh [<путь к nlve>]
|
||||
# Проект берётся из test/поток/длинный-фейд.nlve (см. README, раздел
|
||||
# проверок): один клип с затуханием в семьдесят секунд.
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
BIN=${1:-$PWD/nlve}
|
||||
DISP=${NLVE_DISPLAY:-:99}
|
||||
# Проект для проверки в репозиторий не входит: он ссылается на съёмку,
|
||||
# которой в нём нет. Годится любой с одним клипом и долгим затуханием —
|
||||
# путь можно задать переменной.
|
||||
PROJ=${NLVE_FADE_PROJECT:-$PWD/test/поток/длинный-фейд.nlve}
|
||||
LOG=$(dirname "$BIN")/nlve.log
|
||||
|
||||
[ -f "$PROJ" ] || { echo "нет проекта для проверки: $PROJ"; exit 2; }
|
||||
|
||||
start() {
|
||||
pkill -x nlve 2>/dev/null || true
|
||||
# Закрытая программа не уносит с собой свой ffmpeg: дочерний процесс
|
||||
# остаётся и продолжает писать в кэш. Убираем только его — по пути
|
||||
# этого самого проекта, чтобы не задеть чужую работу.
|
||||
pkill -f "длинный-фейд.nlve-cache" 2>/dev/null || true
|
||||
sleep 1
|
||||
rm -rf "$PWD/test/поток/длинный-фейд.nlve-cache" "$PWD/test/поток/профиль-$1"
|
||||
rm -f "$PROJ.autosave"
|
||||
SP=$PWD/test/поток/профиль-$1
|
||||
mkdir -p "$SP"
|
||||
: > "$LOG"
|
||||
XDG_CONFIG_HOME=$SP XDG_CACHE_HOME=$SP XDG_DATA_HOME=$SP DISPLAY=$DISP \
|
||||
"$BIN" "$PROJ" >/dev/null 2>&1 &
|
||||
sleep 16
|
||||
WIN=$(DISPLAY=$DISP xdotool search --name "длинный-фейд" | tail -1)
|
||||
DISPLAY=$DISP xdotool windowmove "$WIN" 0 0 windowsize "$WIN" 1500 860
|
||||
sleep 2
|
||||
}
|
||||
|
||||
runs() { grep -c filter_complex "$LOG" || true; }
|
||||
stops() { grep -c 'Расстановка изменилась' "$LOG" || true; }
|
||||
|
||||
BAD=0
|
||||
|
||||
echo "1. Правки в стороне от затухания"
|
||||
start правки
|
||||
echo " сборка началась: запусков $(runs)"
|
||||
X=650
|
||||
I=0
|
||||
while [ $I -lt 5 ]; do
|
||||
DISPLAY=$DISP xdotool mousemove $X 640 click 1
|
||||
sleep 0.6
|
||||
DISPLAY=$DISP xdotool key s
|
||||
sleep 3
|
||||
X=$((X + 40))
|
||||
I=$((I + 1))
|
||||
done
|
||||
sleep 5
|
||||
N=$(runs)
|
||||
S=$(stops)
|
||||
echo " после пяти резов: запусков $N, отмен $S"
|
||||
case "$(DISPLAY=$DISP xdotool getwindowname "$WIN")" in
|
||||
*\*) ;;
|
||||
*) echo " ПЛОХО: монтаж не изменился, правки не дошли — проверка ничего не значит"
|
||||
BAD=$((BAD + 1)) ;;
|
||||
esac
|
||||
if [ "$N" = "1" ] && [ "$S" = "0" ]; then
|
||||
echo " ok: работа не сбрасывалась"
|
||||
else
|
||||
echo " ПЛОХО: сборка начиналась заново"
|
||||
BAD=$((BAD + 1))
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "2. Кусок стал не нужен"
|
||||
start отмена
|
||||
echo " сборка началась: запусков $(runs)"
|
||||
DISPLAY=$DISP xdotool mousemove 700 600 click 1
|
||||
sleep 0.5
|
||||
DISPLAY=$DISP xdotool key ctrl+a
|
||||
sleep 0.8
|
||||
DISPLAY=$DISP xdotool key Delete
|
||||
|
||||
# Ждём, пока работа прекратится, а не спим наугад: ffmpeg получает сигнал
|
||||
# и ещё дописывает то, что у него в руках. Именно -x у pgrep: иначе под
|
||||
# раздачу попадёт и сама эта оболочка, у которой слово ffmpeg есть в
|
||||
# командной строке.
|
||||
ALIVE=1
|
||||
WAITED=0
|
||||
while [ "$WAITED" -lt 30 ]; do
|
||||
sleep 1
|
||||
WAITED=$((WAITED + 1))
|
||||
ALIVE=$(pgrep -a -x ffmpeg | grep -c "длинный-фейд.nlve-cache" || true)
|
||||
[ "$ALIVE" = "0" ] && break
|
||||
done
|
||||
S=$(stops)
|
||||
echo " отмен $S, живых ffmpeg $ALIVE, ждали $WAITED с"
|
||||
if [ "$S" -ge 1 ] && [ "$ALIVE" = "0" ]; then
|
||||
echo " ok: ненужная работа прекращена"
|
||||
else
|
||||
echo " ПЛОХО: работа продолжается впустую"
|
||||
BAD=$((BAD + 1))
|
||||
fi
|
||||
|
||||
pkill -x nlve 2>/dev/null || true
|
||||
pkill -f "длинный-фейд.nlve-cache" 2>/dev/null || true
|
||||
rm -f "$PROJ.autosave"
|
||||
|
||||
echo
|
||||
if [ "$BAD" = "0" ]; then
|
||||
echo "ВЕРДИКТ: сборка вставок не сбрасывается зря и не идёт впустую"
|
||||
else
|
||||
echo "ВЕРДИКТ: не пройдено случаев: $BAD"
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue