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>
This commit is contained in:
+178
-33
@@ -33,6 +33,12 @@ var SETTINGS_KEY = "cfg";
|
||||
|
||||
var CURRENT_PROJECT = null; // { code, name, showId, projectType }
|
||||
|
||||
// Only used when no project is selected, or for a comp belonging to another
|
||||
// show than the selected one. Assumes a three-letter show code, which is why
|
||||
// it cannot be the primary pattern any more -- see getShotCodeRegex.
|
||||
var LEGACY_SHOT_CODE_REGEX = /([A-Z]{3}_\d{3}_\d{3}_\d{3})/;
|
||||
var LEGACY_EPISODE_CODE_REGEX = /^[A-Z]{3}_\d{3}$/;
|
||||
|
||||
var pipelineState = {
|
||||
pollShotCode: null,
|
||||
pollTaskId: 0,
|
||||
@@ -182,24 +188,38 @@ var SETTINGS_KEY = "cfg";
|
||||
return text;
|
||||
}
|
||||
|
||||
// Episode folders on disk are named {showId}_{episode} (UNG/UNG_102/...), so
|
||||
// the prefix comes from the selected project rather than a hardcoded "UNG_".
|
||||
function deriveEpisodeCode(value) {
|
||||
var text = String(value);
|
||||
var text = trimText(value);
|
||||
var showId = getShowId();
|
||||
|
||||
if (/^[A-Z]{3}_\d{3}$/.test(text)) {
|
||||
if (showId && text.indexOf(showId + "_") === 0) {
|
||||
return text;
|
||||
}
|
||||
|
||||
if (/^\d+$/.test(text)) {
|
||||
return "UNG_" + padEpisodeNumber(text);
|
||||
return (showId ? showId + "_" : "") + padEpisodeNumber(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function deriveEpisodeAPIValue(value) {
|
||||
var text = String(value);
|
||||
var match = text.match(/[A-Z]{3}_(\d{3})/);
|
||||
var text = trimText(value);
|
||||
var showId = getShowId();
|
||||
var match;
|
||||
|
||||
if (showId && text.indexOf(showId + "_") === 0) {
|
||||
text = text.substring(showId.length + 1);
|
||||
}
|
||||
|
||||
match = text.match(/^\d+/);
|
||||
if (match) {
|
||||
return String(parseInt(match[0], 10));
|
||||
}
|
||||
|
||||
match = text.match(/[A-Z]{3}_(\d{3})/);
|
||||
if (match && match.length > 1) {
|
||||
return String(parseInt(match[1], 10));
|
||||
}
|
||||
@@ -250,8 +270,7 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
|
||||
function getShotsFromAPI(value) {
|
||||
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
|
||||
var param = isEpisodic ? "episode" : "scene";
|
||||
var param = isEpisodicProject() ? "episode" : "scene";
|
||||
var url = CONFIG.baseUrl + "/api/ext/projects/" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "") + "/shots?" + param + "=" + encodeURIComponent(value);
|
||||
var data = getAPIData(url);
|
||||
return getListFromResponse(data, "shots");
|
||||
@@ -305,28 +324,103 @@ var SETTINGS_KEY = "cfg";
|
||||
return null;
|
||||
}
|
||||
|
||||
// The showId is the single source of truth for both the on-disk show folder
|
||||
// and the shot-code prefix: UNG (episodic) and DH2_VFX (standard) both follow
|
||||
// {showId}_{...}, so anything parsing a code anchors on this rather than
|
||||
// counting underscores or assuming a three-letter code.
|
||||
function getShowId() {
|
||||
return CURRENT_PROJECT ? trimText(CURRENT_PROJECT.showId || CURRENT_PROJECT.code) : "";
|
||||
}
|
||||
|
||||
function isEpisodicProject() {
|
||||
return !!(CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC");
|
||||
}
|
||||
|
||||
function escapeRegexText(text) {
|
||||
return String(text).replace(/([.*+?^${}()|[\]\\\-])/g, "\\$1");
|
||||
}
|
||||
|
||||
// Shot codes are {showId}_{episode}_{scene}_{shot} on episodic shows and
|
||||
// {showId}_{scene}_{shot} on standard ones -- so the number of 3-digit tokens
|
||||
// after the showId is what distinguishes them.
|
||||
function getShotCodeRegex() {
|
||||
var showId = getShowId();
|
||||
|
||||
if (!showId) {
|
||||
return LEGACY_SHOT_CODE_REGEX;
|
||||
}
|
||||
|
||||
return new RegExp("(" + escapeRegexText(showId) +
|
||||
"(?:_\\d{3}){" + (isEpisodicProject() ? 3 : 2) + "})");
|
||||
}
|
||||
|
||||
function isEpisodeCode(value) {
|
||||
var showId = getShowId();
|
||||
|
||||
if (!showId) {
|
||||
return LEGACY_EPISODE_CODE_REGEX.test(String(value));
|
||||
}
|
||||
|
||||
return new RegExp("^" + escapeRegexText(showId) + "_\\d{3}$").test(String(value));
|
||||
}
|
||||
|
||||
function getShowExportRoot() {
|
||||
var showId = CURRENT_PROJECT ? (CURRENT_PROJECT.showId || CURRENT_PROJECT.code) : "";
|
||||
var showId = getShowId();
|
||||
return CONFIG.exportRoot + (showId ? "/" + showId : "");
|
||||
}
|
||||
|
||||
function getShowFootageRoot() {
|
||||
var showId = CURRENT_PROJECT ? (CURRENT_PROJECT.showId || CURRENT_PROJECT.code) : "";
|
||||
var showId = getShowId();
|
||||
return CONFIG.footageRoot + (showId ? "/" + showId : "");
|
||||
}
|
||||
|
||||
// First 3-digit token after the showId, for episodic shots whose API record
|
||||
// is missing `episode`.
|
||||
function deriveEpisodeFromShotCode(shotCode) {
|
||||
var showId = getShowId();
|
||||
var text = String(shotCode);
|
||||
var match;
|
||||
|
||||
if (showId && text.indexOf(showId + "_") === 0) {
|
||||
match = text.substring(showId.length + 1).match(/^(\d{3})_/);
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
match = text.match(/^[A-Z]{3}_(\d{3})_/);
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
// The prefix episode-wide files share: "UNG_102" for episodic shows, plain
|
||||
// "DH2_VFX" for standard ones, which have no episode level. Replaces
|
||||
// shotCode.substring(0, 7), which only produced this for a 3-letter showId.
|
||||
function getEpisodePrefix(shotCode) {
|
||||
var showId = getShowId();
|
||||
var episode;
|
||||
|
||||
if (!isEpisodicProject()) {
|
||||
return showId;
|
||||
}
|
||||
|
||||
episode = deriveEpisodeFromShotCode(shotCode);
|
||||
return episode ? showId + "_" + episode : showId;
|
||||
}
|
||||
|
||||
// Deliveries are grouped per episode ("DELIVERY/UNG_102/..."). Standard shows
|
||||
// have nothing to group by, so they get no segment at all.
|
||||
function getDeliveryGroupSegment(shotCode) {
|
||||
return isEpisodicProject() ? "/" + getEpisodePrefix(shotCode) : "";
|
||||
}
|
||||
|
||||
// Mirrors the server's export layout so farm and local renders land together:
|
||||
// {exportRoot}/{showId}/[{episode} for episodic]/{shotCode}/{version}
|
||||
function getShotExportDir(shot) {
|
||||
var path = getShowExportRoot();
|
||||
var episode;
|
||||
var match;
|
||||
|
||||
if (CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC") {
|
||||
if (isEpisodicProject()) {
|
||||
episode = trimText(shot.episode);
|
||||
if (!episode) {
|
||||
match = String(shot.shotCode).match(/^[A-Z]{3}_(\d{3})_/);
|
||||
episode = match ? match[1] : "";
|
||||
episode = deriveEpisodeFromShotCode(shot.shotCode);
|
||||
}
|
||||
if (episode) {
|
||||
path += "/" + episode;
|
||||
@@ -527,8 +621,23 @@ var SETTINGS_KEY = "cfg";
|
||||
return findFolder(episodeCode);
|
||||
}
|
||||
|
||||
function getEpisodeRootFolder(episodeCode) {
|
||||
return new Folder(getShowFootageRoot() + "/" + episodeCode);
|
||||
// The folder a shot's plates are grouped under: the {showId}_{episode} folder
|
||||
// on episodic shows, the show root on standard ones -- a scene never gets its
|
||||
// own folder on disk, so the scene value from the dropdown is not a path.
|
||||
function getFootageGroupFolder(groupCode) {
|
||||
var root = getShowFootageRoot();
|
||||
|
||||
if (isEpisodicProject() && trimText(groupCode)) {
|
||||
return new Folder(root + "/" + trimText(groupCode));
|
||||
}
|
||||
|
||||
return new Folder(root);
|
||||
}
|
||||
|
||||
// Underscore-prefixed folders are never shots or plates: UNG already uses the
|
||||
// convention for _ARCHIVE, _GFX, _PICLOCKS and _PULLS.
|
||||
function isNonShotFolderName(name) {
|
||||
return String(name).indexOf("_") === 0;
|
||||
}
|
||||
|
||||
function getEpisodeCodesFromDisk() {
|
||||
@@ -565,7 +674,7 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
|
||||
function getShotCodesForEpisode(episodeCode) {
|
||||
var root = getEpisodeRootFolder(episodeCode);
|
||||
var root = getFootageGroupFolder(episodeCode);
|
||||
var folders;
|
||||
var shotCodes = [];
|
||||
var i;
|
||||
@@ -589,8 +698,20 @@ var SETTINGS_KEY = "cfg";
|
||||
return shotCodes;
|
||||
}
|
||||
|
||||
function findSequenceFolders(shotCode, episodeCode) {
|
||||
var root = getEpisodeRootFolder(episodeCode);
|
||||
// Two layouts in the wild and neither is being migrated -- moving delivered
|
||||
// footage would break the file links in every AE project already pointing at
|
||||
// it. DH2 groups a shot's plates under a {shotCode}/ folder; UNG scatters them
|
||||
// directly in the episode folder:
|
||||
//
|
||||
// {show}/[{show}_{episode}/]{shotCode}/{shotCode}_{ELEM}_{VER}/ (standard)
|
||||
// {show}/{show}_{episode}/{shotCode}_{ELEM}_{VER}/ (UNG)
|
||||
//
|
||||
// The grouped form wins outright where it exists -- an empty shot folder means
|
||||
// "no plates yet", not "go look at the show root", where stray plate folders
|
||||
// for the same shot code would otherwise be picked up as its footage.
|
||||
function findSequenceFolders(shotCode, groupCode) {
|
||||
var root = getFootageGroupFolder(groupCode);
|
||||
var shotFolder;
|
||||
var folders;
|
||||
var matches = [];
|
||||
var i;
|
||||
@@ -599,6 +720,18 @@ var SETTINGS_KEY = "cfg";
|
||||
return matches;
|
||||
}
|
||||
|
||||
shotFolder = new Folder(root.fsName + "/" + shotCode);
|
||||
if (shotFolder.exists) {
|
||||
folders = shotFolder.getFiles();
|
||||
for (i = 0; i < folders.length; i += 1) {
|
||||
if (folders[i] instanceof Folder && !isNonShotFolderName(folders[i].name)) {
|
||||
matches.push(folders[i]);
|
||||
}
|
||||
}
|
||||
matches.sort();
|
||||
return matches;
|
||||
}
|
||||
|
||||
folders = root.getFiles();
|
||||
for (i = 0; i < folders.length; i += 1) {
|
||||
if (folders[i] instanceof Folder && folders[i].name.indexOf(shotCode + "_") === 0) {
|
||||
@@ -783,10 +916,20 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
|
||||
function extractShotCode(name) {
|
||||
var match = String(name).match(/([A-Z]{3}_\d{3}_\d{3}_\d{3})/);
|
||||
var text = String(name);
|
||||
var match = text.match(getShotCodeRegex());
|
||||
|
||||
if (match && match.length > 1) {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
// A comp from a different show than the one selected in the panel: read it
|
||||
// with the old pattern rather than reporting no shot code at all.
|
||||
match = text.match(LEGACY_SHOT_CODE_REGEX);
|
||||
if (match && match.length > 1) {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1074,13 +1217,15 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
}
|
||||
|
||||
function buildShotFromCode(episodeCode, shotCode) {
|
||||
// groupCode is the episode code for episodic shows and the scene for standard
|
||||
// ones -- see getFootageGroupFolder for how each resolves on disk.
|
||||
function buildShotFromCode(groupCode, shotCode) {
|
||||
var existingShotComp = findComp(shotCode);
|
||||
var sequenceFolders;
|
||||
var footageItems = [];
|
||||
var footageFolder = findFolder("_FOOTAGE_4K");
|
||||
var precompFolder = findFolder("_PRECOMPS");
|
||||
var episodeProjectFolder = findEpisodeProjectFolder(episodeCode);
|
||||
var episodeProjectFolder = findEpisodeProjectFolder(groupCode);
|
||||
var apiData;
|
||||
var footage;
|
||||
var footageComp;
|
||||
@@ -1105,7 +1250,7 @@ var SETTINGS_KEY = "cfg";
|
||||
return existingShotComp;
|
||||
}
|
||||
|
||||
sequenceFolders = findSequenceFolders(shotCode, episodeCode);
|
||||
sequenceFolders = findSequenceFolders(shotCode, groupCode);
|
||||
if (sequenceFolders.length < 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -1171,10 +1316,10 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
|
||||
function buildShotFromDropdown() {
|
||||
var episodeCode;
|
||||
var groupCode;
|
||||
var shotCode;
|
||||
var shotComp;
|
||||
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
|
||||
var isEpisodic = isEpisodicProject();
|
||||
|
||||
if (!uiState.episodeDropdown || !uiState.episodeDropdown.selection || !uiState.shotDropdown || !uiState.shotDropdown.selection) {
|
||||
updateStatus(getSelectedComps().length, "Choose a " + (isEpisodic ? "episode" : "scene") + " and shot first.");
|
||||
@@ -1183,15 +1328,15 @@ var SETTINGS_KEY = "cfg";
|
||||
|
||||
// For episodic, derive full episode code; for standard pass the selected scene value
|
||||
if (isEpisodic) {
|
||||
episodeCode = uiState.episodeDropdown.selection.episodeCode || deriveEpisodeCode(uiState.episodeDropdown.selection.text);
|
||||
groupCode = uiState.episodeDropdown.selection.episodeCode || deriveEpisodeCode(uiState.episodeDropdown.selection.text);
|
||||
} else {
|
||||
episodeCode = uiState.episodeDropdown.selection.filterValue || uiState.episodeDropdown.selection.text;
|
||||
groupCode = uiState.episodeDropdown.selection.filterValue || uiState.episodeDropdown.selection.text;
|
||||
}
|
||||
shotCode = uiState.shotDropdown.selection.shotCode || uiState.shotDropdown.selection.text;
|
||||
|
||||
app.beginUndoGroup("VFXReview Connector - Build Shot");
|
||||
updateStatus(getSelectedComps().length, "Building shot " + shotCode);
|
||||
shotComp = buildShotFromCode(episodeCode, shotCode);
|
||||
shotComp = buildShotFromCode(groupCode, shotCode);
|
||||
app.endUndoGroup();
|
||||
|
||||
if (shotComp) {
|
||||
@@ -1657,9 +1802,9 @@ var SETTINGS_KEY = "cfg";
|
||||
|
||||
exrFiles.sort();
|
||||
|
||||
var episodeCode = shotCode.substring(0, 7);
|
||||
var deliveryDate = getDateString().replace(/\//g, "");
|
||||
deliveryFolderPath = getShowExportRoot() + "/DELIVERY/" + episodeCode + "/" + deliveryDate + "_Delivery/" + shotCode;
|
||||
deliveryFolderPath = getShowExportRoot() + "/DELIVERY" + getDeliveryGroupSegment(shotCode) +
|
||||
"/" + deliveryDate + "_Delivery/" + shotCode;
|
||||
if (!ensureFolder(deliveryFolderPath)) {
|
||||
addFailure(result, comp.name, "Could not create delivery folder");
|
||||
continue;
|
||||
@@ -1769,7 +1914,7 @@ var SETTINGS_KEY = "cfg";
|
||||
|
||||
comp = comps[0];
|
||||
shotCode = getShotCodeFromComp(comp);
|
||||
episodePrefix = shotCode.substring(0, 7); // e.g. "UNG_106"
|
||||
episodePrefix = getEpisodePrefix(shotCode); // e.g. "UNG_106"
|
||||
|
||||
// Fetch API data for seq timecodes
|
||||
updateStatus(getSelectedComps().length, "Looking up " + shotCode + "...");
|
||||
@@ -1795,7 +1940,7 @@ var SETTINGS_KEY = "cfg";
|
||||
item = app.project.item(i);
|
||||
if (item instanceof FootageItem &&
|
||||
item.parentFolder === piclocksProjectFolder &&
|
||||
item.name.substring(0, 7) === episodePrefix) {
|
||||
item.name.indexOf(episodePrefix + "_") === 0) {
|
||||
piclockFootage = item;
|
||||
break;
|
||||
}
|
||||
@@ -2082,7 +2227,7 @@ var SETTINGS_KEY = "cfg";
|
||||
}
|
||||
|
||||
function populateEpisodeOrSceneDropdown() {
|
||||
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
|
||||
var isEpisodic = isEpisodicProject();
|
||||
var i;
|
||||
var item;
|
||||
|
||||
@@ -2104,7 +2249,7 @@ var SETTINGS_KEY = "cfg";
|
||||
item = uiState.episodeDropdown.add("item", label);
|
||||
item.filterValue = episodeValue;
|
||||
item.episodeCode = deriveEpisodeCode(label);
|
||||
if (!/^[A-Z]{3}_\d{3}$/.test(item.episodeCode)) {
|
||||
if (!isEpisodeCode(item.episodeCode)) {
|
||||
item.episodeCode = deriveEpisodeCode(episodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
+31
-19
@@ -56,20 +56,28 @@ Source EXR plates are read from **`<Footage Root>/{SHOW_ID}/`**. Configure `foot
|
||||
|
||||
```
|
||||
<Footage Root>/
|
||||
└── {SHOW_ID}/ # e.g. UNG/
|
||||
├── {EPISODE_CODE}/ # episodic: e.g. UNG_108/
|
||||
│ └── {SHOT_CODE}_{CLIP}/ # e.g. UNG_108_001_010_A315C001/
|
||||
│ └── *.exr # EXR sequence
|
||||
│
|
||||
├── {SCENE}/ # standard: e.g. 001/
|
||||
│ └── {SHOT_CODE}_{CLIP}/
|
||||
│ └── *.exr
|
||||
└── {SHOW_ID}/ # e.g. UNG/ or DH2_VFX/
|
||||
├── {EPISODE_CODE}/ # episodic only: e.g. UNG_108/
|
||||
│ └── {SHOT_CODE}/ # standard layout: plates grouped per shot
|
||||
│ └── {SHOT_CODE}_{ELEM}_{VER}/
|
||||
│ └── *.exr
|
||||
│
|
||||
└── _PICLOCKS/
|
||||
└── {EPISODE_CODE}_*.mov # matched by episode prefix
|
||||
```
|
||||
|
||||
Sequence folders must start with the shot code followed by an underscore (`{SHOT_CODE}_*`). The connector imports all matching folders as separate footage layers.
|
||||
**Scenes are not folders.** Standard projects have no level between the show
|
||||
folder and the shot folder — the scene picker filters the shot list only.
|
||||
|
||||
**Two layouts are supported, and neither is migrated.** The connector prefers the
|
||||
grouped layout above; where a `{SHOT_CODE}/` folder does not exist it falls back
|
||||
to scanning the parent for folders named `{SHOT_CODE}_*` (the original UNG
|
||||
layout, where plates sit directly in the episode folder). All matching folders
|
||||
are imported as separate footage layers. If a `{SHOT_CODE}/` folder exists but
|
||||
holds no plate subfolders, that is reported as no footage — the fallback scan is
|
||||
not attempted, so stray plate folders sitting beside the shot cannot be picked up
|
||||
as its footage. Underscore-prefixed folders (`_ARCHIVE`, `_GFX`, `_PULLS`, …) are
|
||||
never treated as shots or plates.
|
||||
|
||||
---
|
||||
|
||||
@@ -95,7 +103,7 @@ Same applies to `footageRoot`: change `V:/_FOOTAGE/UNG` → `V:/_FOOTAGE`.
|
||||
|
||||
## New production checklist
|
||||
|
||||
1. Create `<Footage Root>/{SHOW_ID}/{EPISODE or SCENE}/` and place EXR sequences inside, named `{SHOT_CODE}_{clip}`.
|
||||
1. Create `<Footage Root>/{SHOW_ID}/[{EPISODE_CODE}/]{SHOT_CODE}/` — episode level for episodic shows only, never a scene level — and place each plate in its own `{SHOT_CODE}_{ELEM}_{VER}/` folder inside it.
|
||||
2. `<Export Root>/{SHOW_ID}/` is created automatically on first render.
|
||||
3. Add the project to the VFXReview server with the correct **Show ID** set.
|
||||
4. Open the panel in After Effects, click **⚙**, enter the Server URL, API Token, bare `exportRoot`, and bare `footageRoot`, then click **Save**.
|
||||
@@ -158,7 +166,7 @@ Updates the **Status** panel to show the current count of selected `CompItem`s.
|
||||
### Fix Comp Names
|
||||
Renames selected comps so their names match the shot code extracted from their current name.
|
||||
|
||||
- Shot codes follow the pattern `XXX_000_000_000` (e.g. `UNG_106_010_020`).
|
||||
- Shot codes are read by anchoring on the selected project's showId — see [Shot Code Convention](#shot-code-convention).
|
||||
- If a comp name contains a valid shot code but has extra text, it is trimmed to the code only.
|
||||
- Wrapped in an undo group.
|
||||
|
||||
@@ -254,7 +262,7 @@ Imports a picture-lock (offline cut) video file and trims it to the correct sequ
|
||||
|
||||
**Process:**
|
||||
1. Calls the API to retrieve `seqTimecodeStart` and `seqTimecodeEnd` for the shot.
|
||||
2. Searches the `_PICLOCKS` project folder for already-imported footage matching the episode prefix (first 7 characters of the shot code, e.g. `UNG_106`).
|
||||
2. Searches the `_PICLOCKS` project folder for already-imported footage matching the episode prefix — `{showId}_{episode}` on episodic shows (e.g. `UNG_106`), plain `{showId}` on standard ones.
|
||||
3. If not found, scans `FOOTAGE_ROOT/_PICLOCKS/` for a file matching `{episodePrefix}_*` and imports it.
|
||||
4. Calculates the in/out offsets from the sequence timecodes relative to the piclock's start (assumed `01:00:00:00` / 3590 s unless embedded timecode is available), with **±8 frame handles**.
|
||||
5. Adds the piclock footage as a layer at the top of the comp, scales it to fill the frame, and sets `startTime`, `inPoint`, and `outPoint` to isolate the correct range.
|
||||
@@ -340,15 +348,19 @@ Accept: application/json
|
||||
|
||||
## Shot Code Convention
|
||||
|
||||
Shot codes follow the pattern `AAA_NNN_NNN_NNN` where:
|
||||
- `AAA` = three-letter show prefix (e.g. `UNG`)
|
||||
- First `NNN` = episode number (e.g. `106`)
|
||||
- Second `NNN` = sequence number (e.g. `010`)
|
||||
- Third `NNN` = shot number (e.g. `020`)
|
||||
Shot codes are the project's **showId** followed by three-digit tokens:
|
||||
|
||||
Example: `UNG_106_010_020`
|
||||
| Project type | Pattern | Example |
|
||||
|---|---|---|
|
||||
| Episodic | `{showId}_{episode}_{scene}_{shot}` | `UNG_106_010_020` (showId `UNG`) |
|
||||
| Standard | `{showId}_{scene}_{shot}` | `DH2_VFX_027_010` (showId `DH2_VFX`) |
|
||||
|
||||
Episode codes follow the pattern `AAA_NNN` (e.g. `UNG_106`).
|
||||
The showId is **not** always three letters and may itself contain underscores, so
|
||||
codes are parsed by stripping the known showId prefix — never by counting
|
||||
underscores or matching a fixed `AAA_` pattern. Everything after the showId is
|
||||
three digits per token.
|
||||
|
||||
Episode codes follow `{showId}_{episode}` (e.g. `UNG_106`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user