Продолжение прерванной сборки потоком — с недописанного файла

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

Дописать в конец существующего MKV нельзя, поэтому продолжение пишется
в новый файл. MP4 продолжать нельзя вовсе: оглавление в конце, и
оборванный файл не открывается — об этом говорится прямо.

Готовый файл теперь цел до последнего: сборка идёт в файл рядом и
занимает место готового, только когда дописана. Сорвавшаяся попытка не
отбирает уже собранное — из двух недописанных остаётся тот, где кадров
больше, и считаются они даже после запроса остановки.

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

В nlvetool у render появились --cancel-after=<сек> и сочетание слов:
cache resume проверяет, что старый путь через кэш не сломался.
This commit is contained in:
4aiman 2026-09-30 16:37:28 +03:00
commit 4522f0000a
4 changed files with 346 additions and 35 deletions

View file

@ -21,9 +21,12 @@
собрать проект-образец: видео внизу, картинка
сверху посередине, дыра в начале
render <проект.nlve> <выход> [burn|keep|resume|cache]
[--cancel-after=<сек>]
уплотить проект, создать вставки, собрать ролик
и сверить число кадров; cache — собрать
по-старому, через кэш кусков
по-старому, через кэш кусков; --cancel-after
оборвёт сборку на полпути, а resume продолжит
её с недописанного файла
subs <проект.nlve>
собрать разметку субтитров из текстовых клипов
и напечатать её
@ -1525,8 +1528,34 @@ var
Rep: TExportReport;
I: Integer;
WantSum: Int64;
Ok: Boolean;
Ok, Stopped: Boolean;
CancelAfter, Waited: Double;
{ Слова после выхода идут в любом порядке и сочетаются: cache resume —
это «по-старому и продолжая». }
function HasWord(const AWord: string): Boolean;
var
J: Integer;
begin
Result := False;
for J := 4 to ParamCount do
if LowerCase(ParamStr(J)) = AWord then
Exit(True);
end;
begin
{ Отдельно от слов может стоять --cancel-after=<сек>: оборвать сборку
на полпути, чтобы было что продолжать. }
CancelAfter := 0;
for I := 4 to ParamCount do
if Copy(ParamStr(I), 1, 15) = '--cancel-after=' then
if not TryStrToFloat(Copy(ParamStr(I), 16, MaxInt), CancelAfter,
InvariantFormat) then
begin
WriteLn('Не удалось разобрать значение --cancel-after');
Halt(2);
end;
Proj := TProject.Create;
try
if not Proj.LoadFromFile(ExpandFileName(ParamStr(2)), Err) then
@ -1588,7 +1617,7 @@ begin
{ Вжигание текста — последним, уже по окончательному списку кусков:
повторное уплощение стёрло бы отметки о разметке. Так же устроено и
окно сборки. }
if (ParamCount >= 4) and (LowerCase(ParamStr(4)) = 'burn') then
if HasWord('burn') then
WriteLn('вжигаю текст, пересобрать придётся ',
SecondsToStr(BurnTextIntoSegments(Proj, Segs, Proj.CacheDir)), ' с');
@ -1597,22 +1626,34 @@ begin
чего собрался ролик. }
Thread := TExportThread.Create(Segs, AudioSegs,
ExpandFileName(ParamStr(3)),
(ParamCount >= 4) and (LowerCase(ParamStr(4)) = 'keep'),
HasWord('keep'),
Proj.Master, Default(TEncodeProfile));
{ Словом resume просят продолжить прерванную сборку: готовые куски
берутся с диска, а не считаются заново. }
Thread.Resume := (ParamCount >= 4) and (LowerCase(ParamStr(4)) = 'resume');
Thread.Resume := HasWord('resume');
{ Словом cache просят собрать по-старому, через кэш кусков: так два
способа сверяются между собой на одном и том же монтаже. }
Thread.AllowStream := not ((ParamCount >= 4) and
(LowerCase(ParamStr(4)) = 'cache'));
Thread.AllowStream := not HasWord('cache');
Watcher := TExportWatcher.Create(Thread);
try
Thread.OnProgress := @Watcher.Progress;
Thread.OnLog := @Watcher.LogLine;
Thread.Start;
{ Остановка на полпути: так получается недописанный файл, с
которого потом просят продолжить. }
Waited := 0;
Stopped := False;
while not Thread.Finished do
begin
CheckSynchronize(100);
Waited := Waited + 0.1;
if (CancelAfter > 0) and (not Stopped) and (Waited >= CancelAfter) then
begin
Stopped := True;
WriteLn('[тест] запрашиваю остановку');
Thread.RequestCancel;
end;
end;
CheckSynchronize(0);
Rep := Thread.Report;
finally