Commit Graph

10 Commits

Author SHA1 Message Date
twotalesanimation bada122f42 feat(pipeline): per-project AE templates, and a workspace that fits any show
Deploy / deploy (push) Successful in 7m4s
Initialise Workspace built UNG_101..UNG_117 from a hardcoded range and
imported one hardcoded AEP, and fourteen findComp calls named UNG's comps
literally. None of it worked for a second show.

Comp names now follow a convention derived from the show id —
{showId}_VFX_OVERLAY, {showId}_EXPORT_TEMPLATE, _SHOW LUT — which
reproduces UNG's existing names exactly, so nothing changes for the show
it was written for. A project whose template departs from the pattern can
override any of the three.

Paths cannot be derived: UNG's show id is UNG while its folder is
UNGO_VFX. Those live on Project.pipelineConfig and are edited under
Project → Settings → Pipeline. They are resolved server-side and handed
to the panel with the project list, and read by buildPreviewManifest for
the headless build, so the panel and the farm cannot disagree about which
template — or which show LUT — a shot was built with. That mattered more
than the folder names: a preview rendered through another show's LUT is
quietly the wrong colour rather than obviously broken.

Episode folders come from the project's episode list, falling back to the
folders under its footage root for a show whose episodes are not entered
yet.

Panel 2.5.0. Adds migration 20260814010000_project_pipeline_config.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 08:24:40 +02:00
twotalesanimation 483db65eb3 fix(connector): build shots at the shot's frame rate, not a hardcoded 24
Deploy / deploy (push) Successful in 6m23s
Shot.fps existed, the lookup API returned it, and one button honoured it
— Set Start Timecode — while the shot builder ignored it entirely. Comps
were created at 24 and every plate conformed to 24.

DH2 is a 25 fps show. Its plates carry framesPerSecond 25/1, so comps
built at 24 conform every plate to the wrong speed and compute every
frame-from-timecode sum against the wrong rate.

The builder now takes the rate from the shot record, which is the show's
delivery rate and therefore the authority; a plate's own rate is whatever
happens to be on disk. Import Renders follows the comp it imports into,
and Import EXR follows the shot.

Validation gains plate_fps, comparing the render against the timing
plate. timecode_fps only compares the render to its manifest and both
come from the comp, so a shot built at the wrong rate agrees with itself
and passes; the plate is the independent witness. It is a WARN, never a
FAIL: sampling DH2 found one plate at 50 fps against a 25 fps show, a
deliberate over-crank, so a whole-number multiple is reported as exactly
that and anything else as a probable rate mistake.

Note this does not fix the data: all 175 DH2 shots still carry fps 24
from the create-time default and need correcting before their comps are
rebuilt.

Panel 2.4.0, worker 1.4.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:10:10 +02:00
twotalesanimation 4642ecb11f feat(pipeline): skip the preview build and inherit the previous one
Deploy / deploy (push) Successful in 7m8s
Some exports are deliberately framed differently from their review —
unscaled or un-punched-in EXRs — and a preview rebuilt from those frames
would be the wrong picture. The previous export's MOV is the correct one.

skipPreview renders the EXRs and goes straight to READY_FOR_QC. Available
at Queue Export (a "No preview" checkbox in the panel, E1 field) and as an
option when overriding a validation failure.

The inheritance is as important as the skip: deliveryMovPath, the review
MP4, the thumbnail and the Version are carried over from the newest
earlier export of that shot which has them. Without it the delivery
builder — which takes its MOV from deliveryMovPath — would package EXRs
with no MOV at all. Where there is nothing to inherit, the export still
reaches QC and the timeline says it has no review media.

QC is untouched by any of this: a skipped preview still needs a human
pass before the export can be delivered.

Panel 2.3.0. Adds migration 20260814000000_export_skip_preview.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:28:14 +02:00
twotalesanimation 4929db45a2 fix(connector): try every matching path mapping, not just the first
Deploy / deploy (push) Successful in 6m5s
A studio whose render output root changed carries both conventions in the
database permanently — changing render.outputRoot only affects new
exports, so old rows keep the path they were queued with. On UNG that is
//SAN/renders/{projectCode}/... for older exports and //SAN/{showId}/...
for current ones, living side by side under one Export Root.

applyPathMappings returned only the first prefix match, so one rewrite
had to serve both and could not. It now returns every match and
resolveServerPath tests each for existence, which is what the candidate
ordering was there for.

Verified against the live drive: with "//SAN/=V:/_EXPORTS/UNG/" the
legacy export resolves via the mapping and current ones via the Export
Root re-base; with both rewrites mapped, all three resolve directly.

Panel 2.1.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 19:20:14 +02:00
twotalesanimation f4a2fee492 fix(connector): resolve server render paths on the workstation
Deploy / deploy (push) Successful in 6m4s
QC's Open Render used the server's outputDir verbatim. The server
addresses renders from its own root (render.outputRoot, e.g. //SAN),
which the render worker translates via its own pathMappings because it
runs as a service account — the panel had no equivalent, so Open Render
failed on any machine that could not resolve that root.

Resolution now tries, in order: an optional Path Mappings setting, the
server path as given, then re-basing on Export Root. The last one is why
this usually needs no configuration at all: both sides lay renders out as
{root}/{showId}/{episode}/{shot}/{version}, so the tail can simply be
re-attached. A failure names every candidate it tried rather than
reporting one path that was never going to work.

Diagnostics gained the same check against a real folder from the QC
queue, since "Export Root exists" and "this machine can reach a render
the server wrote" are different questions and only the second one breaks
QC.

Verified against the live V: drive: a //SAN path, a backslash variant and
an explicitly-mapped path all resolve to the real render folder, while an
unknown root and a different show correctly resolve to nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 18:39:03 +02:00
twotalesanimation a4f8226f3f feat(pipeline): report deployed tool versions from the tools themselves
"What is running where" was answerable only by asking someone. Each tool
now reports its own version on startup into a ToolVersion registry, shown
on Pipeline → Machines.

The worker's version moves out of config.json and into the assembly:
a hand-maintained string on each machine is exactly the thing that goes
stale, and a machine reporting a version it is not running is worse than
no version at all. publish.ps1 stamps the git SHA via SourceRevisionId,
so a deployed exe traces back to a commit (1.1.0+feb1c7b).

The panel gains PANEL_VERSION, shown in its title and every diagnostics
report, and pings on load.

Staleness is derived rather than declared: the newest version seen for a
tool is the benchmark, so updating one machine is what flags the rest.
There is no expected-version list to keep in sync — which would have been
one more thing to forget.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:13:44 +02:00
twotalesanimation feb1c7b3e9 feat(render-pipeline): validation, QC and delivery builder
Completes RenderPipeline2 phases 3-5. Additive throughout: with
validation.enabled=false the pipeline behaves exactly as before.

Validation (§8) runs on the worker straight after a render, while it
still holds the job and the files are local. Everything is read from the
EXR header — one 64 KB read per sampled frame, no pixel decode — so the
engine needs no oiiotool. The render job deliberately stays open through
the stage, so a worker that dies there is reaped and retried instead of
stranding the export in VALIDATING.

Timecode is the primary check and is anchored on the plate, never the
API: measured on the live show, Shot.timecodeStart is the editorial
in-point and sits one handle-length (8f) ahead of the plate, so checking
against it would pass the defective exports and fail the correct ones.
Reference precedence is manifest timing plate -> plate found on disk ->
API value, and the API rank is WARN-only. The panel now resolves plate
provenance by walking the comp's own footage layers, so the worker never
guesses which element carries the comp's timing.

Verified against real studio renders via the new --validate mode: it
catches both defect classes seen in shipped work — the +8 handle offset
and frame-derived timecode (00:00:41:17 = frame 1001 at 24fps).

QC (§10) is one service behind two front doors, API-key for the panel
and session for the web. PASS means ready to upload and nothing else;
FAIL sets the comp task to CHANGES. The panel gains a QC section that
imports the real EXR sequence at full res rather than the H.264.

Delivery (§11) rides the existing queue as a DELIVERY_BUILD job: hard
links with a copy fallback, verification by re-hashing delivered bytes
against the validation-time digest, manifest.json and the CSV report.
fsutil hardlink list is unavailable over SMB, so link identity is never
queried — reading the bytes back covers links and copies alike.

Also fixes ApiClient.ProgressAsync throwing on transport failure, which
would have aborted work in flight during a brief server outage; progress
reporting is best-effort by contract.

Removes the stale RenderWorker/ rule from the root .gitignore. The
worker's source is tracked like the rest of the repo and its own
.gitignore already covers bin/, obj/ and publish/; the rule silently
excluded new files while the existing 20 stayed tracked.

Not yet applied: migration 20260813000000 was hand-written because the
dev database was unavailable — run prisma migrate deploy and verify
before relying on it. None of this has run against a live server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:49:51 +02:00
twotalesanimation 7f2f5e1005 feat(connector): resolve footage per project type, anchor parsing on showId
Build Shot only ever worked on UNG. Two assumptions were baked in: that a shot's
plates sit flat in a folder named after the episode, and that a shot code is
three letters followed by three 3-digit tokens. DH2_VFX breaks both -- it groups
a shot's plates under a {shotCode}/ folder, has no episode level, and its showId
contains a digit and an underscore.

- findSequenceFolders prefers {group}/{shotCode}/ and falls back to UNG's flat
  {group}/{shotCode}_* scan, so both layouts work with no migration -- moving
  delivered footage would break the links in every AE project pointing at it. An
  existing-but-empty shot folder returns nothing rather than falling through, so
  stray plate folders sitting beside a shot cannot be read as its footage.
  Underscore-prefixed folders are never shots or plates.
- getFootageGroupFolder replaces getEpisodeRootFolder: the {showId}_{episode}
  folder for episodic shows, the show root otherwise. A scene filters the shot
  list, it is not a directory -- which is why Build Shot on DH2 was looking in
  V:/_FOOTAGE/DH2/027.
- Shot codes are now parsed by stripping the known showId prefix
  ({showId}_{episode}_{scene}_{shot} for episodic, {showId}_{scene}_{shot} for
  standard) rather than matching /[A-Z]{3}_.../ or counting underscores.
  extractShotCode keeps the old pattern as a fallback so a comp from a different
  show than the selected one still reads. Same anchoring in deriveEpisodeCode
  (no more hardcoded "UNG_"), deriveEpisodeAPIValue, isEpisodeCode and the
  getShotExportDir episode fallback.
- Two positional cuts of the same class, found on the way: the DELIVERY path and
  Pull Picture Lock's episode-prefix match both used shotCode.substring(0, 7).
  Both now go through getEpisodePrefix(). Output is identical for UNG and DH2
  (DH2_VFX happens to be 7 characters); the one behaviour change is that
  standard shows no longer get a constant group level under DELIVERY/, and DH2
  has no delivery history to move.

Verified against the live V:/_FOOTAGE tree by running the real functions under a
Folder stub: UNG_102_019_010 resolves its BG01/FG01 plates via the flat scan, and
DH2_VFX_027_010 and DH2_VFX_066_380 resolve theirs via the grouped layout. Not
yet exercised in After Effects.

Initialise Workspace is untouched and still hardcoded to UNG -- next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 18:15:42 +02:00
twotalesanimation 7dcb9303e5 updated export paths
Deploy / deploy (push) Successful in 6m32s
2026-08-07 12:07:16 +02:00
twotalesanimation ab062a589a updated client review page
Deploy / deploy (push) Successful in 2m35s
2026-06-22 07:48:27 +02:00