mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-06-10 03:02:09 +00:00
feat(tracks): configurable audio codec priority for tie-breaking
Adds optional `audio.codec_priority` list in unshackle.yaml to define preferred audio codec order when tracks share the same bitrate and language. Listed codecs rank in the order given; unlisted codecs retain bitrate-based order and fall after the listed group (soft priority - nothing dropped). Atmos and descriptive rules still apply last.
This commit is contained in:
@@ -168,6 +168,41 @@ dl:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## audio (dict)
|
||||||
|
|
||||||
|
Configuration for audio track selection.
|
||||||
|
|
||||||
|
- `codec_priority`
|
||||||
|
Optional list of audio codec names defining the preferred order when multiple audio
|
||||||
|
tracks share the same bitrate and language. Listed codecs are ranked in the order given.
|
||||||
|
Codecs not in the list retain their bitrate-based ordering and are placed after all
|
||||||
|
listed codecs (i.e. soft priority — nothing is dropped).
|
||||||
|
|
||||||
|
Atmos tracks still take precedence over codec priority, and audio description tracks
|
||||||
|
are still moved to the end.
|
||||||
|
|
||||||
|
Valid codec names: `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `OGG`, `DTS`, `ALAC`, `FLAC`.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
audio:
|
||||||
|
codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG]
|
||||||
|
```
|
||||||
|
|
||||||
|
Or to only prefer a subset (e.g. surround codecs first, everything else falls back to
|
||||||
|
bitrate order):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
audio:
|
||||||
|
codec_priority: [EC3, DTS, AC3, AAC]
|
||||||
|
```
|
||||||
|
|
||||||
|
When unset, audio tracks are sorted by bitrate alone (with Atmos/descriptive rules still
|
||||||
|
applied).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## subtitle (dict)
|
## subtitle (dict)
|
||||||
|
|
||||||
Configuration for subtitle processing and conversion.
|
Configuration for subtitle processing and conversion.
|
||||||
|
|||||||
@@ -1562,7 +1562,10 @@ class dl:
|
|||||||
processed_audio_sort_lang.append(language)
|
processed_audio_sort_lang.append(language)
|
||||||
|
|
||||||
title.tracks.sort_videos(by_language=processed_video_sort_lang)
|
title.tracks.sort_videos(by_language=processed_video_sort_lang)
|
||||||
title.tracks.sort_audio(by_language=processed_audio_sort_lang)
|
title.tracks.sort_audio(
|
||||||
|
by_language=processed_audio_sort_lang,
|
||||||
|
codec_priority=config.audio.get("codec_priority"),
|
||||||
|
)
|
||||||
title.tracks.sort_subtitles(by_language=s_lang)
|
title.tracks.sort_subtitles(by_language=s_lang)
|
||||||
|
|
||||||
if list_:
|
if list_:
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class Config:
|
|||||||
for name, filename in (kwargs.get("filenames") or {}).items():
|
for name, filename in (kwargs.get("filenames") or {}).items():
|
||||||
setattr(self.filenames, name, filename)
|
setattr(self.filenames, name, filename)
|
||||||
|
|
||||||
|
self.audio: dict = kwargs.get("audio") or {}
|
||||||
self.headers: dict = kwargs.get("headers") or {}
|
self.headers: dict = kwargs.get("headers") or {}
|
||||||
self.key_vaults: list[dict[str, Any]] = kwargs.get("key_vaults", [])
|
self.key_vaults: list[dict[str, Any]] = kwargs.get("key_vaults", [])
|
||||||
self.muxing: dict = kwargs.get("muxing") or {}
|
self.muxing: dict = kwargs.get("muxing") or {}
|
||||||
|
|||||||
@@ -249,12 +249,21 @@ class Tracks:
|
|||||||
self.videos.sort(key=lambda x: str(x.language))
|
self.videos.sort(key=lambda x: str(x.language))
|
||||||
self.videos.sort(key=lambda x: not is_close_match(language, [x.language]))
|
self.videos.sort(key=lambda x: not is_close_match(language, [x.language]))
|
||||||
|
|
||||||
def sort_audio(self, by_language: Optional[Sequence[Union[str, Language]]] = None) -> None:
|
def sort_audio(
|
||||||
"""Sort audio tracks by bitrate, Atmos, descriptive, and optionally language."""
|
self,
|
||||||
|
by_language: Optional[Sequence[Union[str, Language]]] = None,
|
||||||
|
codec_priority: Optional[Sequence[str]] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Sort audio tracks by bitrate, codec priority, Atmos, descriptive, and optionally language."""
|
||||||
if not self.audio:
|
if not self.audio:
|
||||||
return
|
return
|
||||||
# bitrate (highest first)
|
# bitrate (highest first)
|
||||||
self.audio.sort(key=lambda x: float(x.bitrate or 0.0), reverse=True)
|
self.audio.sort(key=lambda x: float(x.bitrate or 0.0), reverse=True)
|
||||||
|
# codec priority (listed codecs ranked in order; unlisted fall to end with bitrate order preserved)
|
||||||
|
if codec_priority:
|
||||||
|
rank = {str(c).upper(): i for i, c in enumerate(codec_priority)}
|
||||||
|
default_rank = len(rank)
|
||||||
|
self.audio.sort(key=lambda x: rank.get(x.codec.name if x.codec else "", default_rank))
|
||||||
# Atmos tracks first (prioritize over higher bitrate non-Atmos)
|
# Atmos tracks first (prioritize over higher bitrate non-Atmos)
|
||||||
self.audio.sort(key=lambda x: not x.atmos)
|
self.audio.sort(key=lambda x: not x.atmos)
|
||||||
# descriptive tracks last
|
# descriptive tracks last
|
||||||
|
|||||||
@@ -422,6 +422,14 @@ curl_impersonate:
|
|||||||
browser: chrome120
|
browser: chrome120
|
||||||
|
|
||||||
# Pre-define default options and switches of the dl command
|
# Pre-define default options and switches of the dl command
|
||||||
|
# Audio track selection preferences
|
||||||
|
audio:
|
||||||
|
# Codec priority order used as a tiebreaker when multiple audio tracks share the same
|
||||||
|
# bitrate and language. Listed codecs are ranked in the order given; codecs not in the
|
||||||
|
# list keep their bitrate-based ordering and are placed after all listed codecs.
|
||||||
|
# Atmos still trumps codec priority. Valid names: AAC, AC3, EC3, AC4, OPUS, OGG, DTS, ALAC, FLAC.
|
||||||
|
# codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG]
|
||||||
|
|
||||||
dl:
|
dl:
|
||||||
sub_format: srt
|
sub_format: srt
|
||||||
downloads: 4
|
downloads: 4
|
||||||
|
|||||||
Reference in New Issue
Block a user