-
Notifications
You must be signed in to change notification settings - Fork 0
Improve filename parsing and title generation; add browser deploy notifications and fallback UI #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,9 @@ const PRIMARY_AFTER_VARIANTS = new Set(["after", "out", "output"]); | |
| const COMPARISON_VARIANTS = new Set([ | ||
| ...PRIMARY_AFTER_VARIANTS, | ||
| "rip", | ||
| "flt", | ||
| "filter", | ||
| "filtered", | ||
| "deband", | ||
| "nodeband", | ||
| "noband", | ||
|
|
@@ -39,6 +42,9 @@ const MATCH_KEY_VARIANTS = [ | |
| "out", | ||
| "output", | ||
| "rip", | ||
| "flt", | ||
| "filter", | ||
| "filtered", | ||
| "misc", | ||
| "heatmap", | ||
| "nodeband", | ||
|
|
@@ -53,10 +59,10 @@ const MATCH_KEY_VARIANTS = [ | |
| ]; | ||
| const IGNORED_BASENAMES = new Set([".ds_store", "thumbs.db"]); | ||
| const IGNORED_SUFFIXES = new Set([".json", ".yaml", ".yml", ".txt", ".md", ".csv", ".db", ".log"]); | ||
| const FILENAME_RE = /(?<prefix>.+?)[_\-.](?<frame>\d+)(?:[_\-.](?<variant>[^_\-.]+))?$/; | ||
| const FILENAME_RE = /(?<prefix>.+?)[_\-. ]+(?<frame>\d+)(?:[_\-. ]+(?<variant>[^_\-. ]+))?$/; | ||
| const FALLBACK_FILENAME_RE = /^(?<frame>\d+)(?<variant>[A-Za-z][A-Za-z0-9]*)$/; | ||
| const STRUCTURED_SOURCE_FILENAME_RE = | ||
| /^(?:(?<fps>\d{2})_)?(?<title>.+)_(?<episode>\d+)(?:\.(?<sourceMarker>[^-]+))?-(?<frame>\d+)-(?<variant>[^_\-.]+)$/i; | ||
| /^(?:(?<fps>\d{2})_)?(?<title>.+)_(?<episode>\d+)(?:\.(?<sourceMarker>[^-]+))?[_\-. ]+(?<frame>\d+)[_\-. ]+(?<variant>[^_\-. ]+)$/i; | ||
| const GROUP_SUFFIX_NOISE_RE = /(?:[_\-. ]+\d{4,5}[_\-. ]+(?:gen[_\-. ]+vpy|m2ts|mkv|mp4|ts))$/i; | ||
| const MATCH_KEY_SUFFIX_RE = new RegExp( | ||
| `(?:[_\\-. ]+(?:${MATCH_KEY_VARIANTS.join("|")}))+$`, | ||
|
|
@@ -125,6 +131,15 @@ function titleCase(input: string) { | |
| .replace(/\b\w/g, (letter) => letter.toUpperCase()); | ||
| } | ||
|
|
||
| function volumeLabel(input: string) { | ||
| const match = input.match(/(?:^|[^a-z0-9])(?<kind>vol(?:ume)?|box)[_\-. ]*(?<number>\d+)/i); | ||
| if (!match?.groups) { | ||
| return null; | ||
| } | ||
|
|
||
| return `${match.groups.kind.toUpperCase().startsWith("BOX") ? "BOX" : "VOL"}${Number(match.groups.number)}`; | ||
| } | ||
|
|
||
| function variantLabel(variant: string) { | ||
| const normalized = variant.toLowerCase(); | ||
| if (PRIMARY_AFTER_VARIANTS.has(normalized)) { | ||
|
|
@@ -261,7 +276,7 @@ function parseCandidate(entry: BrowserUploadFile, variantOverride?: string): Sou | |
| return { | ||
| entry, | ||
| originalName: basename(entry.relativePath), | ||
| variant: (variantOverride ?? structured.variant).trim().toLowerCase(), | ||
| variant: (structured.variant ?? variantOverride ?? "output").trim().toLowerCase(), | ||
| fps: structured.fps, | ||
| episode: structured.episode, | ||
| frameNumber: structured.frameNumber, | ||
|
|
@@ -279,7 +294,7 @@ function parseCandidate(entry: BrowserUploadFile, variantOverride?: string): Sou | |
| const prefix = match.groups.prefix; | ||
| const rawFrame = match.groups.frame; | ||
| const frameNumber = Number(rawFrame.replace(/^0+/, "") || "0"); | ||
| const variant = (variantOverride ?? match.groups.variant ?? "output").trim().toLowerCase(); | ||
| const variant = (match.groups.variant ?? variantOverride ?? "output").trim().toLowerCase(); | ||
| const fps = extractFps(pathStem); | ||
| const episode = extractEpisode(prefix); | ||
| const title = `${fps}_${episode}_${frameNumber}`; | ||
|
|
@@ -305,7 +320,7 @@ function parseCandidate(entry: BrowserUploadFile, variantOverride?: string): Sou | |
| return { | ||
| entry, | ||
| originalName: basename(entry.relativePath), | ||
| variant: (variantOverride ?? fallbackMatch.groups.variant).trim().toLowerCase(), | ||
| variant: (fallbackMatch.groups.variant ?? variantOverride).trim().toLowerCase(), | ||
| fps: "00", | ||
| episode: "00", | ||
| frameNumber, | ||
|
|
@@ -346,7 +361,16 @@ function candidateFrameKey(candidate: SourceCandidate) { | |
| } | ||
|
|
||
| function afterPriority(candidate: SourceCandidate) { | ||
| const priority = candidate.variant === "out" ? 0 : candidate.variant === "output" ? 1 : candidate.variant === "after" ? 2 : 3; | ||
| const priority = | ||
| candidate.variant === "out" | ||
| ? 0 | ||
| : candidate.variant === "output" | ||
| ? 1 | ||
| : candidate.variant === "after" | ||
| ? 2 | ||
| : candidate.variant === "rip" | ||
| ? 3 | ||
| : 4; | ||
| return `${priority}:${candidate.variant}:${candidate.originalName.toLowerCase()}`; | ||
| } | ||
|
|
||
|
|
@@ -366,12 +390,30 @@ function alternatePriority(candidate: SourceCandidate) { | |
| return `${priority}:${candidate.variant}:${candidate.originalName.toLowerCase()}`; | ||
| } | ||
|
|
||
| /** Keeps scanned source column names faithful to filenames when exports use src/source/ori. */ | ||
| function sourceVariantLabel(variant: string) { | ||
| const normalized = variant.toLowerCase(); | ||
| if (normalized === "src") { | ||
| return "Src"; | ||
| } | ||
| if (normalized === "source") { | ||
| return "Source"; | ||
| } | ||
| if (normalized === "ori") { | ||
| return "Ori"; | ||
| } | ||
| if (normalized === "origin") { | ||
| return "Origin"; | ||
| } | ||
| return "Before"; | ||
| } | ||
|
|
||
| function assetPlan(kind: WebUploadAssetPlan["kind"], candidate: SourceCandidate): WebUploadAssetPlan { | ||
| const label = | ||
| kind === "before" | ||
| ? "Before" | ||
| ? sourceVariantLabel(candidate.variant) | ||
| : kind === "after" | ||
| ? "After" | ||
| ? variantLabel(candidate.variant) || "After" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the only comparison file is an explicit Useful? React with 👍 / 👎. |
||
| : kind === "heatmap" | ||
| ? "Heatmap" | ||
| : variantLabel(candidate.variant) || "Misc"; | ||
|
|
@@ -401,10 +443,12 @@ function formatCandidateFrameTitle( | |
| structuredEpisodeWidth, | ||
| "0", | ||
| ); | ||
| return `${episode}-${candidate.frameNumber}`; | ||
| const volume = volumeLabel(candidate.rootHint); | ||
| return volume ? `${volume}-${candidate.episode}-${candidate.frameNumber}` : `${episode}-${candidate.frameNumber}`; | ||
|
Comment on lines
+446
to
+447
|
||
| } | ||
|
|
||
| return candidate.title; | ||
| const volume = volumeLabel(candidate.rootHint); | ||
| return volume ? `${volume}-${candidate.title}` : candidate.title; | ||
| } | ||
|
|
||
| function structuredEpisodeWidth(candidates: SourceCandidate[]) { | ||
|
|
@@ -562,6 +606,12 @@ function parseEntries(entries: BrowserUploadFile[], variantOverride?: string) { | |
| return { candidates, ignored }; | ||
| } | ||
|
|
||
| function defaultHeatmapReferenceLabel(frames: WebUploadFramePlan[]) { | ||
| // Default heatmap generation must follow the selected primary comparison column; otherwise | ||
| // src/rip-only imports keep a stale "After" reference and fail during asset generation. | ||
| return frames[0]?.after.label ?? "After"; | ||
| } | ||
|
|
||
| function buildFlatPlan(sourceRootName: string, entries: BrowserUploadFile[], ignoredFiles: IgnoredUploadFile[]): WebUploadPlan { | ||
| const parsed = parseEntries(entries); | ||
| const grouped = new Map<string, SourceCandidate[]>(); | ||
|
|
@@ -601,7 +651,7 @@ function buildFlatPlan(sourceRootName: string, entries: BrowserUploadFile[], ign | |
| suggestedGroupSlug: identity.slug, | ||
| suggestedGroupTitle: identity.title, | ||
| frames, | ||
| heatmapReferenceLabel: "After", | ||
| heatmapReferenceLabel: defaultHeatmapReferenceLabel(frames), | ||
| ignoredFiles: [...ignoredFiles, ...parsed.ignored], | ||
| issues, | ||
| }; | ||
|
|
@@ -689,7 +739,7 @@ function buildNestedPlan(sourceRootName: string, entries: BrowserUploadFile[], i | |
| suggestedGroupSlug: identity.slug, | ||
| suggestedGroupTitle: identity.title, | ||
| frames, | ||
| heatmapReferenceLabel: "After", | ||
| heatmapReferenceLabel: defaultHeatmapReferenceLabel(frames), | ||
| ignoredFiles: ignored, | ||
| issues, | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new space-tolerant flat parser, names like
clip - 001 - src.png,clip - 001 - rip.png, andclip - 001 - flt.pngare accepted, butmatchKeyForNameonly strips variants listed inMATCH_KEY_VARIANTS; sincefltis not listed, the flt candidate keeps the keyclip-001-fltwhile src/rip useclip-001, producing a separatebefore-counterror instead of a misc asset. Add newly supported explicit comparison variants such asfltto the match-key suffix list.Useful? React with 👍 / 👎.