/** * Utilities for parsing VFX filenames into shot / task metadata. * * Expected naming convention (any separator count is fine): * {SHOW}_{EP}_{SCENE}_{CUT}_{TASK}_{ARTIST}_v{NNN}.{ext} * e.g. UNG_106_035_010_cmp_TT_v002.mp4 * * The shot code is matched against the database – the parser itself * does NOT hard-code how many underscore segments belong to the shot. */ export interface ParsedFilename { originalName: string; /** Filename without the extension */ nameWithoutExt: string; /** Lowercased extension without the leading dot (e.g. "mp4", "mov") */ ext: string; /** * Task name base – the full name without the version suffix. * e.g. "UNG_106_035_010_cmp_TT" */ taskNameBase: string; /** * Full task title including the version suffix (no extension). * This is what the task should be named after upload. * e.g. "UNG_106_035_010_cmp_TT_v002" */ taskNameWithVersion: string; /** * Version string extracted from the filename, e.g. "v002". * Empty string if no version pattern was found. */ version: string; type: "mp4" | "mov" | "other"; } /** * Parse a VFX filename into its constituent parts. */ export function parseFilename(filename: string): ParsedFilename { const lastDot = filename.lastIndexOf("."); const ext = lastDot >= 0 ? filename.slice(lastDot + 1).toLowerCase() : ""; const nameWithoutExt = lastDot >= 0 ? filename.slice(0, lastDot) : filename; // Version pattern: _v followed by one or more digits at the end (case insensitive) const versionMatch = nameWithoutExt.match(/_v(\d+)$/i); const version = versionMatch ? `v${versionMatch[1]}` : ""; const taskNameBase = versionMatch ? nameWithoutExt.slice(0, nameWithoutExt.length - versionMatch[0].length) : nameWithoutExt; const type: "mp4" | "mov" | "other" = ext === "mp4" ? "mp4" : ext === "mov" ? "mov" : "other"; return { originalName: filename, nameWithoutExt, ext, taskNameBase, taskNameWithVersion: nameWithoutExt, version, type, }; } /** * Strip a trailing version suffix (_v###) from a task title. * e.g. "UNG_106_035_010_cmp_TT_v001" → "UNG_106_035_010_cmp_TT" */ export function stripVersionFromTitle(title: string): string { return title.replace(/_v\d+$/i, ""); } /** * Given a taskNameBase and a list of shot codes, return the shot code that * is a prefix of the task name base (shot code + "_" separator). * * e.g. taskNameBase "UNG_106_035_010_cmp_TT" matches shot "UNG_106_035_010" */ export function findMatchingShotCode( taskNameBase: string, shotCodes: string[] ): string | null { // Sort by length descending so longer (more specific) codes are matched first const sorted = [...shotCodes].sort((a, b) => b.length - a.length); for (const code of sorted) { if ( taskNameBase === code || taskNameBase.toLowerCase().startsWith(code.toLowerCase() + "_") ) { return code; } } return null; }