From 8c43250c9b70a347750ce01b48128931a385ba71 Mon Sep 17 00:00:00 2001 From: imSp4rky Date: Mon, 24 Aug 2026 16:21:52 -0600 Subject: [PATCH] feat(dl): add --no-attachments to skip cover art and subtitle fonts Drops service attachments at track selection and skips the font attach pass; exposed as no_attachments in the REST dl params and documented. --- docs/dev/rest-api/endpoints.md | 2 +- docs/guide/cli-reference.md | 3 ++- docs/guide/downloading.md | 3 ++- docs/reference/configuration/download.md | 2 +- unshackle/commands/dl.py | 16 ++++++++++++++-- unshackle/core/api/download_manager.py | 1 + unshackle/core/api/handlers.py | 1 + unshackle/core/api/routes.py | 3 +++ 8 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/dev/rest-api/endpoints.md b/docs/dev/rest-api/endpoints.md index c9e420ed..0865a368 100644 --- a/docs/dev/rest-api/endpoints.md +++ b/docs/dev/rest-api/endpoints.md @@ -396,7 +396,7 @@ Make a download job. It requires `service` and `title_id`. Every other field is | `exact_lang` | boolean | `false` | Exact language matching. | | `sub_format` | string | `null` | Output subtitle format. | | `video_only` / `audio_only` / `subs_only` / `chapters_only` | boolean | `false` | Track-type restriction (at most one). | -| `no_subs` / `no_audio` / `no_chapters` / `no_video` | boolean | `false` | Skip a track type. | +| `no_subs` / `no_audio` / `no_chapters` / `no_video` / `no_attachments` | boolean | `false` | Skip a track type. | | `audio_description` | boolean | `false` | Download audio description. | | `slow` | boolean/string | `null` | Randomized delay between downloads. | | `split_audio` | boolean | `null` | Separate files per audio codec. | diff --git a/docs/guide/cli-reference.md b/docs/guide/cli-reference.md index 46cec04c..b416d039 100644 --- a/docs/guide/cli-reference.md +++ b/docs/guide/cli-reference.md @@ -121,7 +121,7 @@ unshackle honours the special language tokens `orig`, `all`, and `best` everywhe ### Track-type inclusion / exclusion -Keep only certain track types, or skip certain track types. Attachments are always kept. +Keep only certain track types, or skip certain track types. | Include-only | Skip | |---|---| @@ -129,6 +129,7 @@ Keep only certain track types, or skip certain track types. Attachments are alwa | `-A`, `--audio-only` | `-na`, `--no-audio` | | `-S`, `--subs-only` | `-ns`, `--no-subs` | | `-C`, `--chapters-only` | `-nc`, `--no-chapters` | +| | `-nt`, `--no-attachments` (cover art, subtitle fonts, and files the service attaches) | | Flag | Description | |---|---| diff --git a/docs/guide/downloading.md b/docs/guide/downloading.md index efdb6888..403bc93c 100644 --- a/docs/guide/downloading.md +++ b/docs/guide/downloading.md @@ -617,6 +617,7 @@ or negatively (everything but these). - `-na` / `--no-audio` - `-ns` / `--no-subs` - `-nc` / `--no-chapters` +- `-nt` / `--no-attachments` (cover art, subtitle fonts, and files the service attaches) Additional track-type flags: @@ -932,7 +933,7 @@ authoritative list. | `--select-titles` | | Interactively pick episodes or films. | | `--latest-episode` | | Only the newest episode. | | `--video-only` / `--audio-only` / `--subs-only` | `-V` / `-A` / `-S` | Restrict track types. | -| `--no-video` / `--no-audio` / `--no-subs` / `--no-chapters` | `-nv` / `-na` / `-ns` / `-nc` | Skip track types. | +| `--no-video` / `--no-audio` / `--no-subs` / `--no-chapters` / `--no-attachments` | `-nv` / `-na` / `-ns` / `-nc` / `-nt` | Skip track types. | | `--worst` | | Lowest bitrate within `-q`. | | `--require-audio` | | Audio languages that must exist. The title fails if one is missing. | | `--require-video` | | Video languages that must exist. The title fails if one is missing. | diff --git a/docs/reference/configuration/download.md b/docs/reference/configuration/download.md index 2b3fcd74..b2349163 100644 --- a/docs/reference/configuration/download.md +++ b/docs/reference/configuration/download.md @@ -43,7 +43,7 @@ Common config keys. This is a useful subset, and every `dl` flag works: | `sub_format` | str | *(unset)* | Convert subtitles to this format (`srt`, `vtt`, `original`, ...). | | `forced_subs` | bool | `false` | Include forced subtitle tracks. | | `forced_s_lang` | list | `[]` | Forced subtitle language(s); implies `forced_subs`. A `-` prefix excludes. | -| `no_subs` / `no_audio` / `no_chapters` / `no_attachments` | bool | `false` | Skip that track type. `no_attachments` also skips subtitle font attaching. | +| `no_subs` / `no_audio` / `no_chapters` / `no_attachments` | bool | `false` | Skip that track type. `no_attachments` also skips attaching subtitle fonts. | | `downloads` | int | `1` | Tracks downloaded concurrently. | | `workers` | int | *(downloader default)* | Threads per track. | | `speed_limit` | str | *(unlimited)* | Total download speed cap across all downloads combined, e.g. `500k`, `5M`, `1.5G` or plain bytes/sec. Values are bytes, not bits (`5M` = 5.0 MB/s). | diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index c7618aa4..04fccca7 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -781,6 +781,13 @@ class dl: @click.option("-na", "--no-audio", is_flag=True, default=False, help="Do not download audio tracks.") @click.option("-nc", "--no-chapters", is_flag=True, default=False, help="Do not download chapter markers.") @click.option("-nv", "--no-video", is_flag=True, default=False, help="Do not download video tracks.") + @click.option( + "-nt", + "--no-attachments", + is_flag=True, + default=False, + help="Do not download or mux attachments (cover art, subtitle fonts, and files the service attaches).", + ) @click.option("-ad", "--audio-description", is_flag=True, default=False, help="Download audio description tracks.") @click.option( "--slow", @@ -1460,6 +1467,7 @@ class dl: no_audio: bool, no_chapters: bool, no_video: bool, + no_attachments: bool, audio_description: bool, slow: Optional[tuple[int, int]], list_: bool, @@ -2215,6 +2223,9 @@ class dl: console.log("Skipped chapters as --no-chapters was used...") keep_chapters = False + if no_attachments: + console.log("Skipped attachments as --no-attachments was used...") + if no_proxy_download and any(service.session.proxies.values()): console.log("Bypassing proxy for downloads as --no-proxy-download was used...") @@ -2770,7 +2781,8 @@ class dl: kept_tracks.extend(title.tracks.subtitles) if keep_chapters: kept_tracks.extend(title.tracks.chapters) - kept_tracks.extend(title.tracks.attachments) + if not no_attachments: + kept_tracks.extend(title.tracks.attachments) title.tracks = Tracks(kept_tracks, manifest_url=title.tracks.manifest_url) @@ -3021,7 +3033,7 @@ class dl: with console.status("Checking Subtitles for Fonts..."): font_names: list[str] = [] - for subtitle in title.tracks.subtitles: + for subtitle in [] if no_attachments else title.tracks.subtitles: if subtitle.codec in (Subtitle.Codec.SubStationAlpha, Subtitle.Codec.SubStationAlphav4): font_names.extend(Subtitle.extract_fonts(subtitle.path.read_text("utf8"))) diff --git a/unshackle/core/api/download_manager.py b/unshackle/core/api/download_manager.py index d1fda532..56abc63d 100644 --- a/unshackle/core/api/download_manager.py +++ b/unshackle/core/api/download_manager.py @@ -579,6 +579,7 @@ def perform_download( no_audio=params.get("no_audio", False), no_chapters=params.get("no_chapters", False), no_video=params.get("no_video", False), + no_attachments=params.get("no_attachments", False), audio_description=params.get("audio_description", False), slow=params.get("slow", None), list_=False, diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index d311007c..e489eb16 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -59,6 +59,7 @@ DEFAULT_DOWNLOAD_PARAMS = { "no_audio": False, "no_chapters": False, "no_video": False, + "no_attachments": False, "audio_description": False, "slow": None, "split_audio": None, diff --git a/unshackle/core/api/routes.py b/unshackle/core/api/routes.py index 19c2f4d7..47b1854f 100644 --- a/unshackle/core/api/routes.py +++ b/unshackle/core/api/routes.py @@ -734,6 +734,9 @@ async def download(request: web.Request) -> web.Response: no_video: type: boolean description: Do not download video tracks (default - false) + no_attachments: + type: boolean + description: Do not download or mux attachments such as cover art or subtitle fonts (default - false) audio_description: type: boolean description: Download audio description tracks (default - false)