From 1f4c4df847cd40066155cffa18c6306a5adc9ecd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:37:35 +0000 Subject: [PATCH] Implement DEFAULT_DOWNLOAD_FOLDER and CLEAR_COMPLETED_AFTER features (#875, #869) Co-authored-by: alexta69 <7450369+alexta69@users.noreply.github.com> --- README.md | 2 ++ app/main.py | 2 ++ app/ytdl.py | 14 ++++++++++++++ ui/src/app/app.ts | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/README.md b/README.md index a913a0f..48cc61c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Certain values can be set via environment variables, using the `-e` parameter on * __MAX_CONCURRENT_DOWNLOADS__: Maximum number of simultaneous downloads allowed. For example, if set to `5`, then at most five downloads will run concurrently, and any additional downloads will wait until one of the active downloads completes. Defaults to `3`. * __DELETE_FILE_ON_TRASHCAN__: if `true`, downloaded files are deleted on the server, when they are trashed from the "Completed" section of the UI. Defaults to `false`. * __DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT__: Maximum number of playlist items that can be downloaded. Defaults to `0` (no limit). +* __CLEAR_COMPLETED_AFTER__: Number of seconds after which completed (and failed) downloads are automatically removed from the "Completed" list. Defaults to `0` (disabled). ### 📁 Storage & Directories @@ -43,6 +44,7 @@ Certain values can be set via environment variables, using the `-e` parameter on * __AUDIO_DOWNLOAD_DIR__: Path to where audio-only downloads will be saved, if you wish to separate them from the video downloads. Defaults to the value of `DOWNLOAD_DIR`. * __CUSTOM_DIRS__: Whether to enable downloading videos into custom directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__). When enabled, a dropdown appears next to the Add button to specify the download directory. Defaults to `true`. * __CREATE_CUSTOM_DIRS__: Whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector supports free-text input, and the specified directory will be created recursively. Defaults to `true`. +* __DEFAULT_DOWNLOAD_FOLDER__: Default subdirectory within __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) to pre-select in the download folder field. Useful when most downloads should go to a specific subfolder. Can be overridden per-download in the UI. Defaults to empty (uses the base download directory). * __CUSTOM_DIRS_EXCLUDE_REGEX__: Regular expression to exclude some custom directories from the dropdown. Empty regex disables exclusion. Defaults to `(^|/)[.@].*$`, which means directories starting with `.` or `@`. * __DOWNLOAD_DIRS_INDEXABLE__: If `true`, the download directories (__DOWNLOAD_DIR__ and __AUDIO_DOWNLOAD_DIR__) are indexable on the web server. Defaults to `false`. * __STATE_DIR__: Path to where the queue persistence files will be saved. Defaults to `/downloads/.metube` in the Docker image, and `.` otherwise. diff --git a/app/main.py b/app/main.py index 1c4bd66..df8b105 100644 --- a/app/main.py +++ b/app/main.py @@ -60,6 +60,8 @@ class Config: 'OUTPUT_TEMPLATE_PLAYLIST': '%(playlist_title)s/%(title)s.%(ext)s', 'OUTPUT_TEMPLATE_CHANNEL': '%(channel)s/%(title)s.%(ext)s', 'DEFAULT_OPTION_PLAYLIST_ITEM_LIMIT' : '0', + 'DEFAULT_DOWNLOAD_FOLDER': '', + 'CLEAR_COMPLETED_AFTER': '0', 'YTDL_OPTIONS': '{}', 'YTDL_OPTIONS_FILE': '', 'ROBOTS_TXT': '', diff --git a/app/ytdl.py b/app/ytdl.py index bc84b74..f64f669 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -573,6 +573,20 @@ class DownloadQueue: else: self.done.put(download) asyncio.create_task(self.notifier.completed(download.info)) + try: + clear_after = int(self.config.CLEAR_COMPLETED_AFTER) + except ValueError: + log.error(f'CLEAR_COMPLETED_AFTER is set to an invalid value "{self.config.CLEAR_COMPLETED_AFTER}", expected an integer number of seconds') + clear_after = 0 + if clear_after > 0: + task = asyncio.create_task(self.__auto_clear_after_delay(download.info.url, clear_after)) + task.add_done_callback(lambda t: log.error(f'Auto-clear task failed: {t.exception()}') if not t.cancelled() and t.exception() else None) + + async def __auto_clear_after_delay(self, url, delay_seconds): + await asyncio.sleep(delay_seconds) + if self.done.exists(url): + log.debug(f'Auto-clearing completed download: {url}') + await self.clear([url]) def __extract_info(self, url): debug_logging = logging.getLogger().isEnabledFor(logging.DEBUG) diff --git a/ui/src/app/app.ts b/ui/src/app/app.ts index 15f7ce3..186ed91 100644 --- a/ui/src/app/app.ts +++ b/ui/src/app/app.ts @@ -315,6 +315,10 @@ export class App implements AfterViewInit, OnInit { if (!this.chapterTemplate) { this.chapterTemplate = config['OUTPUT_TEMPLATE_CHAPTER']; } + // Set default download folder from backend config if not already set + if (!this.folder && config['DEFAULT_DOWNLOAD_FOLDER']) { + this.folder = config['DEFAULT_DOWNLOAD_FOLDER']; + } } }); }