Skip to content

[codex] support multi-folder imports and series numbering - #51

Open
Sudo-Rahman wants to merge 4 commits into
mainfrom
feature/multi-folder-imports-series-numbering
Open

Sudo-Rahman wants to merge 4 commits into
mainfrom
feature/multi-folder-imports-series-numbering

Conversation

@Sudo-Rahman

Copy link
Copy Markdown
Owner

Summary

MediaFlow previously accepted files only, which forced users importing multi-season media to reopen the picker for every season. The rename workspace also numbered every imported file as one global sequence, so season numbers had to be maintained manually by repeatedly selecting and editing subsets.

This pull request adds multi-folder import across the supported tools while preserving each tool's file-format policy. Imported files retain source-group provenance, allowing the new Series Numbering rule to assign seasons per selected folder and restart episode numbering for each group.

Root cause and user impact

Import flows were implemented separately in each tool and passed only flat file paths. There was no shared representation of selected roots or source groups, so downstream rename logic could not distinguish files originating from different season folders. Standard numbering therefore treated the entire batch as one sequence.

The initial UI integration also exposed a few usability regressions: drag-and-drop zones stopped opening the picker on click, long source-folder names could force horizontal scrolling in the rule dialog, secondary Merge track imports inherited the wrong label, and saving a loaded rename preset always created a new preset.

Changes

  • Add a native Tauri command that expands selected files and folders recursively, filters unsupported formats, deduplicates results, and returns source-group metadata.
  • Add shared frontend import policies and coordination so every tool accepts folders while enforcing its existing supported extensions.
  • Add folder-aware source groups to affected tool state without changing persisted rename preset contracts.
  • Add a Series Numbering rename rule that assigns seasons per source group and resets episode numbering per group.
  • Keep the Series Numbering configuration inside its rule dialog, with responsive wrapping and no horizontal overflow.
  • Restore click-to-browse behavior for every import drop zone and require browse callbacks so missing handlers fail during development.
  • Use Import for primary imports while preserving contextual secondary actions such as Add tracks.
  • Make Enter close a rename-rule editor after applying a single-line input value, without intercepting select or secondary-button keyboard behavior.
  • Split preset persistence into Save and Save As: Save updates the active user preset, Save As creates and activates a new preset, and built-in presets remain immutable.

Validation

  • pnpm check — 0 errors (one existing missing Node type-definition warning)
  • pnpm test — 87 files and 712 tests passed
  • pnpm build — passed
  • cargo test --manifest-path src-tauri/Cargo.toml — 439 passed, 3 ignored; build-support tests 8 passed
  • cargo clippy --all-targets --no-default-features — passed with existing warnings outside this change
  • Native macOS bundle built successfully and exercised with Computer Use for rule-dialog Enter handling, Save As presentation, active user-preset targeting, and built-in preset protection

@Sudo-Rahman
Sudo-Rahman marked this pull request as ready for review September 3, 2026 17:33
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T08:22:55.187746Z 320511b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e37571326c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 83 to +85
const ruleTypes: RuleType[] = [
'prefix', 'suffix', 'replace', 'regex',
'remove', 'case', 'number', 'move', 'timestamp',
'remove', 'case', 'number', 'series-number', 'move', 'timestamp',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block unresolved series rules in embedded output flows

When this rule is added from the shared RenameWorkspace used by Merge or Transcode output naming, a group with no detected season—or conflicting season evidence—is marked by the store as hasBlockingIssues, but MergeView.handleMerge and TranscodeView.handleStartTranscode only check filename conflicts. Those operations therefore remain executable and applySeriesNumber silently falls back to season 1, producing incorrectly named merged/transcoded files. Propagate hasBlockingIssues into both embedded execution guards, or restrict the rule to workspaces that honor it.

Useful? React with 👍 / 👎.

Comment thread src/lib/services/series-numbering.ts Outdated
Comment on lines +194 to +195
function normalizeSeason(value: number | undefined): number | undefined {
return value !== undefined && Number.isInteger(value) && value > 0 ? value : undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow season zero for specials

For files using the standard specials notation S00E01 or a Season 0 folder, the parser produces season 0, but this predicate discards it; setSeasonAssignment and the editor's min="1" also prevent correcting it manually. The group consequently remains unresolved, blocking the main Rename workflow and triggering the season-1 fallback in embedded workflows, so specials cannot be assigned the correct S00 prefix.

Useful? React with 👍 / 👎.

Comment on lines +51 to +53
function handlePaddingChange(event: Event): void {
const target = event.target as HTMLInputElement;
onUpdate({ ...config, padding: parseInt(target.value, 10) || 1 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clamp episode padding before updating the rule

When a user types or pastes a value outside the declared 1–10 range, the number input's min/max attributes do not clamp it, and this handler stores the unchecked integer. The reactive preview immediately passes that value to padStart; a sufficiently large value throws RangeError: Invalid string length and can break the rule dialog, while smaller out-of-range values generate unusable filenames. Clamp the parsed value to the same bounds advertised by the input before calling onUpdate.

Useful? React with 👍 / 👎.

…ded flows

- Block unresolved series rules in embedded Merge and Transcode output flows

- Allow season zero for specials in parser, store, and UI inputs

- Clamp episode padding to 1-10 across rule editors and rename preview/apply logic

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c3c294023

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{selectedTracksCount}
</Badge>
</Item.Root>
{#if workspace.hasSeriesNumberingIssues}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate series warnings on an enabled rule

For ordinary movie files with no detectable season, hasSeriesNumberingIssues is true even when the workspace has no enabled Series Numbering rule, so this renders a destructive “Needs selection” warning for an irrelevant feature while merging remains allowed. The same unconditional check appears in TranscodeOutputNamingView.svelte and TranscodeOutputTab.svelte; gate these warnings on an enabled series rule, consistent with hasBlockingIssues.

Useful? React with 👍 / 👎.


function handleStepChange(event: Event): void {
const target = event.target as HTMLInputElement;
onUpdate({ ...config, step: parseInt(target.value, 10) || 1 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject negative episode steps

When a user pastes a negative value such as -1, the input's min="1" does not sanitize it and this handler stores it because negative integers are truthy. Series numbering then decreases from the configured start and eventually generates tokens such as S01E0-1; clamp the parsed step to at least 1 before updating the rule.

Useful? React with 👍 / 👎.

Comment on lines +150 to +152
const activeGroupKeys = new Set(
planSeriesNumbering(sourceFiles, sortConfig, seasonAssignments).resolutions.map((resolution) => resolution.groupKey),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve assignments while a group is deselected

When the last file in a manually assigned group is temporarily deselected, planSeriesNumbering returns only selected groups, so this pruning pass deletes the assignment even though the group's files remain in the workspace. Reselecting the file then silently restores the unresolved blocker and forces the user to enter the season again; prune against groups represented by all remaining files rather than the current selection.

Useful? React with 👍 / 👎.

Comment on lines +314 to +315
async function handleAddTrackFolders(): Promise<void> {
await addFiles(await pickAndExpandToolImport(trackImportPolicy, 'merge', 'folders'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Collapse VobSub pairs during folder imports

When a selected track folder or mixed folder drop contains a matching .idx/.sub pair, recursive expansion returns both paths and addFiles creates a separate imported track for each. These files are the two parts of one VobSub subtitle, but downstream merge construction passes every attached track as its own FFmpeg input, so auto-matching or attaching all imported tracks can duplicate the subtitle or make the merge fail; collapse each matching pair into one logical import before calling addFiles.

Useful? React with 👍 / 👎.

return;
}

if (outputNamingWorkspace.hasBlockingIssues) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope series blockers to files being processed

The new guard evaluates hasBlockingIssues across every selected output-workspace file, while this function processes only selectedVideosToMerge; therefore an unresolved series group belonging to a scanning or failed video can block otherwise ready videos from merging. The Transcode guard has the same mismatch with readyQueueFiles; compute the series blocker from the actual execution subset, as the preceding scoped conflict calculation already does.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant