From 77b20dc4baed15cfd093731b6ff6566c11839132 Mon Sep 17 00:00:00 2001 From: Nico Behnke Date: Mon, 29 Jun 2026 15:50:41 +0200 Subject: [PATCH] add auto-delete options for empty channels and playlists --- backend/appsettings/serializers.py | 2 + backend/appsettings/src/config.py | 4 ++ backend/common/src/helper.py | 10 +++- backend/download/src/yt_dlp_handler.py | 60 +++++++++++++++++++ .../src/api/loader/loadAppsettingsConfig.ts | 2 + frontend/src/pages/SettingsApplication.tsx | 37 +++++++++++- frontend/src/stores/AppSettingsStore.ts | 2 + 7 files changed, 114 insertions(+), 3 deletions(-) diff --git a/backend/appsettings/serializers.py b/backend/appsettings/serializers.py index f26bc73d..04aa692a 100644 --- a/backend/appsettings/serializers.py +++ b/backend/appsettings/serializers.py @@ -41,6 +41,8 @@ class AppConfigDownloadsSerializer( limit_speed = serializers.IntegerField(allow_null=True) sleep_interval = serializers.IntegerField(allow_null=True) autodelete_days = serializers.IntegerField(allow_null=True) + autodelete_empty_channels = serializers.BooleanField(required=False) + autodelete_empty_playlists = serializers.BooleanField(required=False) format = serializers.CharField(allow_null=True) format_sort = serializers.CharField(allow_null=True) add_metadata = serializers.BooleanField() diff --git a/backend/appsettings/src/config.py b/backend/appsettings/src/config.py index 8bd81579..166ff434 100644 --- a/backend/appsettings/src/config.py +++ b/backend/appsettings/src/config.py @@ -32,6 +32,8 @@ class DownloadsConfigType(TypedDict): limit_speed: int | None sleep_interval: int | None autodelete_days: int | None + autodelete_empty_channels: bool + autodelete_empty_playlists: bool format: str | None format_sort: str | None add_metadata: bool @@ -81,6 +83,8 @@ class AppConfig: "limit_speed": None, "sleep_interval": 10, "autodelete_days": None, + "autodelete_empty_channels": False, + "autodelete_empty_playlists": False, "format": None, "format_sort": None, "add_metadata": False, diff --git a/backend/common/src/helper.py b/backend/common/src/helper.py index c6f0f06c..e539cccc 100644 --- a/backend/common/src/helper.py +++ b/backend/common/src/helper.py @@ -332,7 +332,9 @@ def get_channels( def get_playlists( - subscribed_only: bool, source: list[str] | None = None + subscribed_only: bool, + source: list[str] | None = None, + channel_id: str | None = None, ) -> list[dict]: """get list of playlists""" @@ -343,8 +345,12 @@ def get_playlists( must_list = [{"term": {"playlist_active": {"value": True}}}] if subscribed_only: must_list.append({"term": {"playlist_subscribed": {"value": True}}}) + if channel_id: + must_list.append( + {"term": {"playlist_channel_id": {"value": channel_id}}} + ) - data = {"query": {"bool": {"must": must_list}}} # type: ignore + data["query"] = {"bool": {"must": must_list}} # type: ignore if source: data["_source"] = source # type: ignore diff --git a/backend/download/src/yt_dlp_handler.py b/backend/download/src/yt_dlp_handler.py index 8aaf0234..5938b488 100644 --- a/backend/download/src/yt_dlp_handler.py +++ b/backend/download/src/yt_dlp_handler.py @@ -16,6 +16,7 @@ from common.src.env_settings import EnvironmentSettings from common.src.es_connect import ElasticWrap, IndexPaginate from common.src.helper import ( get_channel_overwrites, + get_channels, get_playlists, ignore_filelist, rand_sleep, @@ -280,6 +281,8 @@ class DownloadPostProcess(DownloaderBase): """run all functions""" self.auto_delete_all() self.auto_delete_overwrites() + self.auto_delete_empty_playlists() + self.auto_delete_empty_channels() self.refresh_playlist() self.match_videos() self.get_comments() @@ -361,6 +364,63 @@ class DownloadPostProcess(DownloaderBase): PendingList(youtube_ids=parsed_ids).parse_url_list(status="ignore") + def auto_delete_empty_playlists(self): + """handle auto delete empty playlists""" + if not self.config["downloads"]["autodelete_empty_playlists"]: + return + + print("auto delete empty playlists") + + playlists = get_playlists( + subscribed_only=False, + source=[ + "playlist_id", + "playlist_subscribed", + "playlist_entries", + "playlist_type", + ], + ) + for playlist in playlists: + if playlist.get("playlist_type") == "custom": + continue + if playlist.get("playlist_subscribed"): + continue + entries = playlist.get("playlist_entries", []) + if any(entry.get("downloaded") for entry in entries): + continue + + playlist_id = playlist["playlist_id"] + print(f"{playlist_id}: auto delete empty playlist") + YoutubePlaylist(playlist_id).delete_metadata() + + def auto_delete_empty_channels(self): + """handle auto delete empty channels""" + if not self.config["downloads"]["autodelete_empty_channels"]: + return + + print("auto delete empty channels") + + channels = get_channels( + subscribed_only=False, + source=["channel_id", "channel_subscribed"], + ) + for channel in channels: + if channel.get("channel_subscribed"): + continue + channel_id = channel["channel_id"] + channel_handler = YoutubeChannel(channel_id) + if get_playlists( + subscribed_only=False, + source=["playlist_id"], + channel_id=channel_id, + ): + continue + if channel_handler.get_channel_videos(): + continue + + print(f"{channel_id}: auto delete empty channel") + channel_handler.delete_channel() + def refresh_playlist(self) -> None: """match videos with playlists""" self.add_playlists_to_refresh() diff --git a/frontend/src/api/loader/loadAppsettingsConfig.ts b/frontend/src/api/loader/loadAppsettingsConfig.ts index 47b1ba3a..465562a7 100644 --- a/frontend/src/api/loader/loadAppsettingsConfig.ts +++ b/frontend/src/api/loader/loadAppsettingsConfig.ts @@ -13,6 +13,8 @@ export type AppSettingsConfigType = { limit_speed: number | null; sleep_interval: number | null; autodelete_days: number | null; + autodelete_empty_channels: boolean; + autodelete_empty_playlists: boolean; format: string | null; format_sort: string | null; add_metadata: boolean; diff --git a/frontend/src/pages/SettingsApplication.tsx b/frontend/src/pages/SettingsApplication.tsx index d5a56e9f..41c7df55 100644 --- a/frontend/src/pages/SettingsApplication.tsx +++ b/frontend/src/pages/SettingsApplication.tsx @@ -50,7 +50,8 @@ const SettingsApplication = () => { const [currentThrottledRate, setCurrentThrottledRate] = useState(null); const [currentScrapingSleep, setCurrentScrapingSleep] = useState(null); const [currentAutodelete, setCurrentAutodelete] = useState(null); - + const [currentAutodeleteEmptyChannels, setCurrentAutodeleteEmptyChannels] = useState(false); + const [currentAutodeleteEmptyPlaylists, setCurrentAutodeleteEmptyPlaylists] = useState(false); // Download Format const [downloadsFormat, setDownloadsFormat] = useState(null); const [downloadsFormatSort, setDownloadsFormatSort] = useState(null); @@ -106,6 +107,8 @@ const SettingsApplication = () => { setCurrentThrottledRate(appSettingsConfigData?.downloads.throttledratelimit || null); setCurrentScrapingSleep(appSettingsConfigData?.downloads.sleep_interval || null); setCurrentAutodelete(appSettingsConfigData?.downloads.autodelete_days || null); + setCurrentAutodeleteEmptyChannels(appSettingsConfigData?.downloads.autodelete_empty_channels || false); + setCurrentAutodeleteEmptyPlaylists(appSettingsConfigData?.downloads.autodelete_empty_playlists || false); // Download Format setDownloadsFormat(appSettingsConfigData?.downloads.format || null); @@ -315,6 +318,18 @@ const SettingsApplication = () => {
  • Can also be configured on a per channel basis.
  • +
  • + Auto delete empty unsubscribed channels. +
      +
    • The cleanup task triggers after the download finishes.
    • +
    +
  • +
  • + Auto delete empty unsubscribed playlists. +
      +
    • The cleanup task triggers after the download finishes.
    • +
    +
  • )} @@ -372,6 +387,26 @@ const SettingsApplication = () => { updateCallback={handleUpdateConfig} /> +
    +
    +

    Auto delete empty playlists

    +
    + +
    +
    +
    +

    Auto delete empty channels

    +
    + +

    Download Format

    diff --git a/frontend/src/stores/AppSettingsStore.ts b/frontend/src/stores/AppSettingsStore.ts index 1f8654e2..bc162de4 100644 --- a/frontend/src/stores/AppSettingsStore.ts +++ b/frontend/src/stores/AppSettingsStore.ts @@ -20,6 +20,8 @@ export const useAppSettingsStore = create(set => ({ limit_speed: null, sleep_interval: null, autodelete_days: null, + autodelete_empty_channels: false, + autodelete_empty_playlists: false, format: null, format_sort: null, add_metadata: false,