diff --git a/src/App.tsx b/src/App.tsx index 2bbca3f..4c18df9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -87,6 +87,7 @@ export default function App({ children }: { children: React.ReactNode }) { const FORCE_INTERNET_PROTOCOL = useSettingsPageStatesStore(state => state.settings.force_internet_protocol); const USE_CUSTOM_COMMANDS = useSettingsPageStatesStore(state => state.settings.use_custom_commands); const CUSTOM_COMMANDS = useSettingsPageStatesStore(state => state.settings.custom_commands); + const FILENAME_TEMPLATE = useSettingsPageStatesStore(state => state.settings.filename_template); const isErrored = useDownloaderPageStatesStore((state) => state.isErrored); const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected); @@ -124,7 +125,7 @@ export default function App({ children }: { children: React.ReactNode }) { const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState, downloadConfig?: DownloadConfiguration): Promise => { try { const args = [url, '--dump-single-json', '--no-warnings']; - if (formatId) args.push('-f', formatId); + if (formatId) args.push('--format', formatId); if (selectedSubtitles) args.push('--embed-subs', '--sub-lang', selectedSubtitles); if (playlistIndex) args.push('--playlist-items', playlistIndex); if (PREFER_VIDEO_OVER_PLAYLIST && !playlistIndex) args.push('--no-playlist'); @@ -285,12 +286,12 @@ export default function App({ children }: { children: React.ReactNode }) { '--paths', `home:${downloadDirPath}`, '--output', - `%(title)s_%(resolution|unknown)s[${downloadId}].%(ext)s`, + `${FILENAME_TEMPLATE}[${downloadId}].%(ext)s`, '--windows-filenames', '--restrict-filenames', '--exec', 'after_move:echo Finalpath: {}', - '-f', + '--format', selectedFormat, '--no-mtime', '--no-warnings', diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 259a82a..f135cf7 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -72,6 +72,10 @@ const addCustomCommandSchema = z.object({ args: z.string().min(1, { message: "Arguments are required" }), }); +const filenameTemplateShcema = z.object({ + template: z.string().min(1, { message: "Filename Template is required" }), +}); + export default function SettingsPage() { const { setTheme } = useTheme(); @@ -118,6 +122,7 @@ export default function SettingsPage() { const forceInternetProtocol = useSettingsPageStatesStore(state => state.settings.force_internet_protocol); const useCustomCommands = useSettingsPageStatesStore(state => state.settings.use_custom_commands); const customCommands = useSettingsPageStatesStore(state => state.settings.custom_commands); + const filenameTemplate = useSettingsPageStatesStore(state => state.settings.filename_template); const websocketPort = useSettingsPageStatesStore(state => state.settings.websocket_port); const isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort); @@ -297,6 +302,30 @@ export default function SettingsPage() { } } + const filenameTemplateForm = useForm>({ + resolver: zodResolver(filenameTemplateShcema), + defaultValues: { + template: filenameTemplate, + }, + mode: "onChange", + }); + const watchedFilenameTemplate = filenameTemplateForm.watch("template"); + const { errors: filenameTemplateFormErrors } = filenameTemplateForm.formState; + + function handleFilenameTemplateSubmit(values: z.infer) { + try { + saveSettingsKey('filename_template', values.template); + toast.success("Filename Template updated", { + description: `Filename Template changed to ${values.template}`, + }); + } catch (error) { + console.error("Error changing filename template:", error); + toast.error("Failed to change filename template", { + description: "An error occurred while trying to change the filename template. Please try again.", + }); + } + } + interface Config { port: number; } @@ -372,7 +401,14 @@ export default function SettingsPage() { Cancel resetSettings() + () => { + resetSettings() + proxyUrlForm.reset(); + rateLimitForm.reset(); + addCustomCommandForm.reset(); + filenameTemplateForm.reset(); + websocketPortForm.reset(); + } }>Reset @@ -629,6 +665,36 @@ export default function SettingsPage() { +
+

Filename Template

+

Set the template for naming downloaded files (download id and file extension will be auto-appended at the end, changing template may cause paused downloads to re-start from begining)

+
+ + ( + + + + + + + )} + /> + + + +
diff --git a/src/services/store.ts b/src/services/store.ts index f9cdcbf..2da4dfa 100644 --- a/src/services/store.ts +++ b/src/services/store.ts @@ -179,6 +179,7 @@ export const useSettingsPageStatesStore = create((set) force_internet_protocol: 'ipv4', use_custom_commands: false, custom_commands: [], + filename_template: '%(title)s_%(resolution|unknown)s', // extension settings websocket_port: 53511 }, @@ -239,6 +240,7 @@ export const useSettingsPageStatesStore = create((set) force_internet_protocol: 'ipv4', use_custom_commands: false, custom_commands: [], + filename_template: '%(title)s_%(resolution|unknown)s', // extension settings websocket_port: 53511 }, diff --git a/src/types/settings.ts b/src/types/settings.ts index ff60e10..f097125 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -43,6 +43,7 @@ export interface Settings { force_internet_protocol: string; use_custom_commands: boolean; custom_commands: CustomCommand[]; + filename_template: string; // extension settings websocket_port: number; } @@ -52,4 +53,4 @@ export interface DownloadConfiguration { embed_metadata: boolean | null; embed_thumbnail: boolean | null; custom_command: string | null; -} \ No newline at end of file +}