mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-11 19:45:06 +00:00
feat(tracks): add subtitle.dedupe_codecs to keep one codec per subtitle variant
This commit is contained in:
@@ -441,6 +441,7 @@ subtitle:
|
||||
| `sidecar_format` | `srt` | a format name (e.g. `srt`, `vtt`) or `original` | Format used for sidecar files. |
|
||||
| `group_by` | `type` | `type`, `language` | Whether all tracks of one type stay together, or each language stays next to its own variants. |
|
||||
| `language_priority` | *(unset)* | a list of languages | Languages to sort to the top. The rest keep the default order. Nothing is removed. |
|
||||
| `dedupe_codecs` | *(unset)* | a list of codecs, e.g. `[VTT, STPP, WVTT]` | Keep one copy of a subtitle that the service sends in several codecs: the earliest listed codec wins. |
|
||||
|
||||
### Which settings to change
|
||||
|
||||
@@ -454,6 +455,7 @@ only when you want something the defaults do not do:
|
||||
| Music and lyric lines stripped along with the sound effects | `sdh_method: filter-subs` |
|
||||
| Lyric lines kept | `sdh_method: subby`, or install SubtitleEdit |
|
||||
| No stripped copies made at all | `strip_sdh: false` |
|
||||
| One copy of each subtitle when the service sends the same one in several codecs | `dedupe_codecs: [VTT, STPP, WVTT]` |
|
||||
| One backend for every conversion, whatever you install | `conversion_method: <backend>` |
|
||||
|
||||
Two keys read as if they control markup, and do not:
|
||||
|
||||
@@ -194,6 +194,7 @@ in or writes them as sidecar files. See [Subtitles](../../guide/subtitles.md) fo
|
||||
| `type_priority` | list | *(unset)* | Ordered ranking of subtitle types (`forced`, `normal`, `sdh`; CC counts as SDH). Types you leave out fall to the end. |
|
||||
| `group_by` | str | `"type"` | Which key groups the subtitle tracks. `type` puts all forced tracks together, then all normal, then all SDH. `language` keeps each language next to its own variants. |
|
||||
| `language_priority` | list | *(unset)* | Languages to sort to the top, in the order you give. The rest keep the default order: original language first, then alphabetical. Nothing is removed. |
|
||||
| `dedupe_codecs` | list | *(unset)* | Subtitle codecs in order of preference, by value or name, for example `[VTT, STPP, WVTT]`. When tracks of the same language, forced/SDH/CC type and name come in several listed codecs, unshackle drops the ones in a later codec. It always keeps codecs you leave out. Unset keeps every track. |
|
||||
|
||||
```yaml
|
||||
subtitle:
|
||||
|
||||
@@ -3,11 +3,19 @@ import pytest
|
||||
from unshackle.core.tracks import Subtitle, Tracks
|
||||
|
||||
|
||||
def sub(lang: str, forced: bool = False, sdh: bool = False, cc: bool = False, orig: bool = False) -> Subtitle:
|
||||
def sub(
|
||||
lang: str,
|
||||
forced: bool = False,
|
||||
sdh: bool = False,
|
||||
cc: bool = False,
|
||||
orig: bool = False,
|
||||
codec: Subtitle.Codec = Subtitle.Codec.SubRip,
|
||||
tag: str = "",
|
||||
) -> Subtitle:
|
||||
track = Subtitle(
|
||||
url=f"https://example.test/{lang}-{forced}-{sdh}-{cc}",
|
||||
url=f"https://example.test/{lang}-{forced}-{sdh}-{cc}-{codec.value}{tag}",
|
||||
language=lang,
|
||||
codec=Subtitle.Codec.SubRip,
|
||||
codec=codec,
|
||||
forced=forced,
|
||||
sdh=sdh,
|
||||
cc=cc,
|
||||
@@ -266,3 +274,20 @@ def test_all_sorts_the_original_track_then_its_other_regional_forms() -> None:
|
||||
)
|
||||
t.sort_audio(by_language=["all"])
|
||||
assert [str(a.language) for a in t.audio] == ["fr-CA", "fr-FR", "en"]
|
||||
|
||||
|
||||
def test_dedupe_subtitle_codecs_keeps_the_first_listed_codec_of_each_variant() -> None:
|
||||
codec = Subtitle.Codec
|
||||
vtt = sub("en", codec=codec.WebVTT)
|
||||
wvtt = sub("en", codec=codec.fVTT)
|
||||
stpp = sub("en", codec=codec.fTTML)
|
||||
stpp_cc = sub("en", cc=True, codec=codec.fTTML)
|
||||
srt = sub("en") # unlisted codec: always kept
|
||||
de_wvtt = sub("de", codec=codec.fVTT)
|
||||
vtt_second = sub("en", codec=codec.WebVTT, tag="-second") # same winning codec: kept, it may differ in content
|
||||
tracks = Tracks()
|
||||
tracks.subtitles = [wvtt, stpp, vtt, stpp_cc, srt, de_wvtt, vtt_second]
|
||||
|
||||
tracks.dedupe_subtitle_codecs([codec.WebVTT, codec.fTTML, codec.fVTT])
|
||||
|
||||
assert tracks.subtitles == [vtt, stpp_cc, srt, de_wvtt, vtt_second]
|
||||
|
||||
@@ -2453,6 +2453,10 @@ class dl:
|
||||
level="INFO", operation="get_tracks", service=self.service, context=tracks_info
|
||||
)
|
||||
|
||||
if dedupe_codecs := config.subtitle.get("dedupe_codecs"):
|
||||
codecs = map(SUBTITLE_CODEC.convert, dedupe_codecs)
|
||||
title.tracks.dedupe_subtitle_codecs([c for c in codecs if isinstance(c, Subtitle.Codec)])
|
||||
|
||||
# a plain track covers an SDH track only when it is the same variant, as -sl selects them,
|
||||
# so en-US SDH is stripped next to a plain en-GB: -sl en-US would otherwise keep only the SDH
|
||||
if config.subtitle.get("strip_sdh", True):
|
||||
|
||||
@@ -308,6 +308,23 @@ class Tracks:
|
||||
kept.append(track)
|
||||
setattr(self, kind, kept)
|
||||
|
||||
def dedupe_subtitle_codecs(self, priority: Sequence[Subtitle.Codec]) -> None:
|
||||
"""
|
||||
Drop a subtitle when the same language, flag set and name exists in a codec earlier in priority.
|
||||
A subtitle in an unlisted codec is always kept.
|
||||
"""
|
||||
rank = {codec: i for i, codec in reversed(list(enumerate(priority)))}
|
||||
|
||||
def variant(sub: Subtitle) -> tuple:
|
||||
signature = self.signature(sub)
|
||||
return signature[:2] + signature[3:] # the signature without its codec
|
||||
|
||||
best: dict[tuple, int] = {}
|
||||
for sub in self.subtitles:
|
||||
if sub.codec in rank:
|
||||
best[variant(sub)] = min(best.get(variant(sub), rank[sub.codec]), rank[sub.codec])
|
||||
self.subtitles = [s for s in self.subtitles if s.codec not in rank or rank[s.codec] == best[variant(s)]]
|
||||
|
||||
def sort_videos(
|
||||
self, by_language: Optional[Sequence[Union[str, Language]]] = None, exact_match: bool = False
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user