9660cdc21c
Deploy / deploy (push) Successful in 3m23s
Worker 1.7.0 (preview.highQuality, default on) now, after the AE render: - encodes a High quality MP4 from the delivery MOV (full-res H.264 High, CRF 14 capped at 100 Mbps) -> Version.hqFileUrl - uploads the MOV itself -> Version.highResKey/Filename/Size Both best-effort (the preview never fails over them), with a lease keep-alive during the long encode/uploads; the export timeline records what was attached. Player: HQ switch when a High quality source exists; keeps frame, play state, speed and zoom, remembered per browser, falls back to Standard if the file won't load. ProRes itself stays download-only (browser support). Downloads: the client portal's High Res button serves the version's ProRes (client-code filename), falling back to the shot's manual file; staff get a ProRes button on the review page. Housekeeping (lib/render-pipeline/high-res.ts): HQ MP4 + ProRes are kept while a version is latest, client-shared, approved or younger than preview.highResRetentionDays (14); otherwise deleted from storage. Runs per task after finalize and hourly from the reaper; deleting a version or a reslate removes its files. The Standard MP4 is never touched. Also: ProRes/Handles top-bar buttons go icon-only below lg so they don't overlap the version selector. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
44 lines
1.5 KiB
C#
44 lines
1.5 KiB
C#
namespace VFXReviewWorker;
|
|
|
|
/// <summary>
|
|
/// Renews a job's lease with progress reports while the worker does something
|
|
/// long that reports nothing on its own — the High quality encode and the
|
|
/// ProRes upload can each outlast a 300 s lease, and an expired lease lets the
|
|
/// reaper hand the job to another machine mid-upload.
|
|
/// </summary>
|
|
internal sealed class LeaseKeepAlive : IAsyncDisposable
|
|
{
|
|
private readonly CancellationTokenSource _cts;
|
|
private readonly Task _loop;
|
|
|
|
public LeaseKeepAlive(ApiClient api, string jobId, string machineId, TimeSpan interval, CancellationToken shutdown)
|
|
{
|
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(shutdown);
|
|
var token = _cts.Token;
|
|
_loop = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
await Task.Delay(interval, token);
|
|
// Best-effort by contract (ApiClient.ProgressAsync never throws
|
|
// on transport errors); progress stays at 1.0 — the render is done.
|
|
await api.ProgressAsync(jobId, machineId, 1.0, null, null, null, null, token);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// stopped
|
|
}
|
|
});
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_cts.Cancel();
|
|
try { await _loop; } catch { /* already reported */ }
|
|
_cts.Dispose();
|
|
}
|
|
}
|