diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx
index 6be117b4..83510613 100644
--- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx
+++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx
@@ -265,6 +265,7 @@ type TaskInputFormProps = {
subtitleTracks?: SubtitleTrack[];
probing?: boolean;
entryType?: 'file' | 'directory';
+ hideTrackPickers?: boolean;
};
const trackLabel = (t: { title: string; lang: string; id: number }) =>
@@ -283,7 +284,7 @@ const parseSubtitleSpec = (raw?: string): SubtitleEditEntry[] => {
}
};
-const TaskInputForm = ({ inputDefs, values, onChange, autoFilledKeys, audioTracks, subtitleTracks, probing, entryType }: TaskInputFormProps) => {
+const TaskInputForm = ({ inputDefs, values, onChange, autoFilledKeys, audioTracks, subtitleTracks, probing, entryType, hideTrackPickers }: TaskInputFormProps) => {
const configurableInputs = Object.entries(inputDefs).filter(([key]) => !autoFilledKeys.has(key));
if (configurableInputs.length === 0) return null;
@@ -343,6 +344,7 @@ const TaskInputForm = ({ inputDefs, values, onChange, autoFilledKeys, audioTrack
}
if (def.type === 'audio_tracks' || def.type === 'subtitle_tracks') {
+ if (hideTrackPickers) return null; // "convert all, keep every track" mode — selection doesn't apply
const isAudio = def.type === 'audio_tracks';
const tracks = (isAudio ? audioTracks : subtitleTracks) ?? [];
const selected = new Set(
@@ -451,9 +453,13 @@ const TaskInputForm = ({ inputDefs, values, onChange, autoFilledKeys, audioTrack
type FolderSummaryProps = {
folder: { fileCount: number; majorityCount: number; skipped: string[] };
+ keepAll?: boolean;
+ // When provided (mixed-layout folder with track pickers), offers the "convert all, keep every
+ // track" escape hatch instead of only converting the matching group.
+ onKeepAllChange?: (v: boolean) => void;
};
-const FolderSummary = ({ folder }: FolderSummaryProps) => {
+const FolderSummary = ({ folder, keepAll = false, onKeepAllChange }: FolderSummaryProps) => {
const { fileCount, majorityCount, skipped } = folder;
if (fileCount === 0) {
@@ -473,22 +479,62 @@ const FolderSummary = ({ folder }: FolderSummaryProps) => {
);
}
+ // Mixed layouts — no toggle available: just report the majority run + skipped list.
+ if (!onKeepAllChange) {
+ return (
+
+
+
+ {majorityCount} of {fileCount} videos share this layout and will be converted.
+
+
+
Different layout — convert these separately:
+
+ {skipped.map((f) => (
+ -
+ • {f}
+
+ ))}
+
+
+
+ );
+ }
+
return (
-
+
- {majorityCount} of {fileCount} videos share this layout and will be converted.
+ {majorityCount} of {fileCount} videos share a track layout.
-
-
Different layout — convert these separately:
-
- {skipped.map((f) => (
- -
- • {f}
-
- ))}
-
+
+
+
+ {!keepAll && (
+
+
Skipped (convert separately):
+
+ {skipped.map((f) => (
+ -
+ • {f}
+
+ ))}
+
+
+ )}
);
};
@@ -519,6 +565,8 @@ const ScriptRunner = ({ taskDirName, autoInputs, context, cwd, entryType, filePa
const [folder, setFolder] = useState<{ fileCount: number; majorityCount: number; skipped: string[] } | null>(null);
// Newline-separated folder-relative paths to restrict a batch to the majority group (empty = all).
const [includeFiles, setIncludeFiles] = useState('');
+ // Mixed-layout escape hatch: convert every video keeping all tracks (no picking, nothing skipped).
+ const [keepAll, setKeepAll] = useState(false);
// Fetch task detail to get input definitions
useEffect(() => {
@@ -638,8 +686,22 @@ const ScriptRunner = ({ taskDirName, autoInputs, context, cwd, entryType, filePa
setTimeout(() => setCopied(false), 2000);
};
+ // Folder has a track-selection picker (convert-video style) → the "keep every track" escape hatch applies.
+ const hasSelectablePickers = Object.values(inputDefs ?? {}).some(
+ (d) => d.type === 'audio_tracks' || d.type === 'subtitle_tracks',
+ );
+
const handleRun = () => {
- const allInputs = { ...formValues, ...(includeFiles ? { include: includeFiles } : {}), ...autoInputs };
+ // "Keep every track" mode: clear track selections (empty = keep all) and convert everything.
+ const overrides: Record
= {};
+ if (keepAll && inputDefs) {
+ for (const [key, def] of Object.entries(inputDefs)) {
+ if (def.type === 'audio_tracks' || def.type === 'subtitle_tracks') overrides[key] = '';
+ }
+ }
+ // Include list: keepAll on a whole folder = all files (none); on a multi-selection = all selected.
+ const inc = keepAll ? (selectedNames && selectedNames.length > 1 ? selectedNames.join('\n') : '') : includeFiles;
+ const allInputs = { ...formValues, ...overrides, ...(inc ? { include: inc } : {}), ...autoInputs };
runner.run(taskDirName, allInputs, cwd);
};
@@ -651,7 +713,13 @@ const ScriptRunner = ({ taskDirName, autoInputs, context, cwd, entryType, filePa
Running on {selectedNames.length} selected items
)}
- {entryType === 'directory' && folder && !probing &&
}
+ {entryType === 'directory' && folder && !probing && (
+
0 ? setKeepAll : undefined}
+ />
+ )}
{inputDefs && (
)}