mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-11 19:45:06 +00:00
feat(tracks): add a display-only note shown in --list and over --remote
This commit is contained in:
@@ -340,7 +340,7 @@ When you give both `season` and `episode`, the server combines them into a `"{se
|
||||
"range_display": "HDR", "scan_type": null, "closed_captions": [],
|
||||
"dv_compatible_bitstream": false, "hdr10plus": false, "hdr10_base": false, "language": "en",
|
||||
"is_original_lang": true,
|
||||
"name": null, "track_name": "English", "needs_repack": false, "edition": [], "data": null,
|
||||
"name": null, "track_name": "English", "needs_repack": false, "edition": [], "note": null, "data": null,
|
||||
"drm": [], "descriptor": "DASH"
|
||||
}
|
||||
],
|
||||
@@ -350,13 +350,13 @@ When you give both `season` and `episode`, the server combines them into a `"{se
|
||||
"bitrate": 640, "bitrate_bps": 640000, "channels": "5.1", "language": "en",
|
||||
"is_original": true, "is_original_lang": true, "atmos": false, "joc": 0,
|
||||
"descriptive": false, "name": null, "track_name": "English", "needs_repack": false, "edition": [],
|
||||
"data": null, "drm": [], "descriptor": "DASH"
|
||||
"note": null, "data": null, "drm": [], "descriptor": "DASH"
|
||||
}
|
||||
],
|
||||
"subtitles": [
|
||||
{ "id": "s-1", "codec": "vtt", "language": "en",
|
||||
"forced": false, "sdh": false, "cc": false, "is_original_lang": false,
|
||||
"name": null, "track_name": "English", "needs_repack": false, "edition": [], "data": null,
|
||||
"name": null, "track_name": "English", "needs_repack": false, "edition": [], "note": null, "data": null,
|
||||
"drm": [], "descriptor": "DASH" }
|
||||
]
|
||||
}
|
||||
@@ -384,7 +384,7 @@ The server sorts tracks by bitrate, descending. Episodes the server cannot get g
|
||||
|
||||
The `drm` array on a track is a list of `{type, pssh?, kids?, content_keys?, license_url?}` objects.
|
||||
|
||||
Every track carries the same fields as a local track: `is_original_lang`, `name`, `needs_repack`, `edition`, and `data`. `name` is the name the service set, or null. `track_name` is the name `--list` shows and the mux writes to an audio or subtitle track: `name` when the service set one, else the name of the language (English, or in the language itself when the server sets `muxing.track_names: native`, or no name when it sets `none`), then the server's `muxing.original_label` on an original-language track, then the SDH, CC, Forced or Descriptive label. `bitrate` is in kb/s, and `bitrate_bps` gives the exact bits/s. `data` holds the part of the service-set `track.data` that fits in JSON, without the `hls`, `dash`, and `ism` parser keys. Audio keeps `is_original` and `atmos` for clients that read them, and adds the exact `joc` object count.
|
||||
Every track carries the same fields as a local track: `is_original_lang`, `name`, `needs_repack`, `edition`, `note`, and `data`. `name` is the name the service set, or null. `note` is display text the service set, such as an encoding profile, or null: `--list` shows it, but the filename and the mux do not use it. `track_name` is the name `--list` shows and the mux writes to an audio or subtitle track: `name` when the service set one, else the name of the language (English, or in the language itself when the server sets `muxing.track_names: native`, or no name when it sets `none`), then the server's `muxing.original_label` on an original-language track, then the SDH, CC, Forced or Descriptive label. `bitrate` is in kb/s, and `bitrate_bps` gives the exact bits/s. `data` holds the part of the service-set `track.data` that fits in JSON, without the `hls`, `dash`, and `ism` parser keys. Audio keeps `is_original` and `atmos` for clients that read them, and adds the exact `joc` object count.
|
||||
|
||||
| Status | Error code | Meaning |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -6,7 +6,7 @@ from enum import Enum
|
||||
|
||||
import pytest
|
||||
|
||||
from unshackle.core.api.handlers import serialize_video_track
|
||||
from unshackle.core.api.handlers import serialize_audio_track, serialize_subtitle_track, serialize_video_track
|
||||
from unshackle.core.remote_service import (
|
||||
RemoteService,
|
||||
build_title,
|
||||
@@ -58,6 +58,24 @@ def test_a_dv_track_keeps_its_hdr10_base_label_over_remote() -> None:
|
||||
assert deserialize_video(serialize_video_track(v)).range_label == "DV+HDR10"
|
||||
|
||||
|
||||
def test_a_track_note_shows_over_remote_and_export_and_is_not_an_edition() -> None:
|
||||
from unshackle.core.tracks.track import Track
|
||||
|
||||
v = Video(url="https://x/v.mp4", language="ja", codec=Video.Codec.AVC, note="h264mpl40-dash")
|
||||
a = Audio(url="https://x/a.mp4", language="ja", codec=Audio.Codec.AAC, note="heaac-2-dash")
|
||||
s = Subtitle(url="https://x/s.vtt", language="en", codec=Subtitle.Codec.WebVTT, note="webvtt-lssdh")
|
||||
copies = [deserialize_video(serialize_video_track(v)), deserialize_audio(serialize_audio_track(a))]
|
||||
copies += [deserialize_subtitle(serialize_subtitle_track(s))]
|
||||
copies += [Track.from_dict(t.to_dict()) for t in (v, a, s)]
|
||||
for copy in copies:
|
||||
assert copy.note and str(copy).endswith(f"| {copy.note}")
|
||||
assert copy.edition == []
|
||||
|
||||
|
||||
def test_a_note_that_is_not_text_is_dropped_not_fatal() -> None:
|
||||
assert deserialize_video({"id": "v1", "codec": "AVC", "width": 1, "height": 1, "note": {"x": 1}}).note is None
|
||||
|
||||
|
||||
def test_deserialize_video_unknown_codec_falls_back_to_none() -> None:
|
||||
v = deserialize_video({"id": "v2", "codec": "MADE_UP", "width": 0, "height": 0})
|
||||
assert v.codec is None
|
||||
|
||||
@@ -1722,6 +1722,7 @@ def serialize_track_common(track: Any) -> Dict[str, Any]:
|
||||
"track_name": track.get_track_name(),
|
||||
"needs_repack": bool(getattr(track, "needs_repack", False)),
|
||||
"edition": list(getattr(track, "edition", None) or []),
|
||||
"note": getattr(track, "note", None),
|
||||
"descriptor": descriptor_name(track),
|
||||
"drm": serialize_drm(track.drm) if getattr(track, "drm", None) else None,
|
||||
"drm_preference": getattr(track, "drm_preference", None),
|
||||
|
||||
@@ -288,6 +288,7 @@ def base_track_kwargs(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"needs_repack": bool(data.get("needs_repack", False)),
|
||||
"name": data.get("name"),
|
||||
"edition": data.get("edition") or None,
|
||||
"note": data["note"] if isinstance(data.get("note"), str) else None,
|
||||
"id_": data.get("id"),
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ class Audio(Track):
|
||||
f"{self.bitrate // 1000} kb/s" if self.bitrate else None,
|
||||
self.get_track_name(),
|
||||
", ".join(self.edition) if self.edition else None,
|
||||
self.note,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -231,7 +231,13 @@ class Subtitle(Track):
|
||||
return " | ".join(
|
||||
filter(
|
||||
bool,
|
||||
["SUB", f"[{self.codec.value}]" if self.codec else None, str(self.language), self.get_track_name()],
|
||||
[
|
||||
"SUB",
|
||||
f"[{self.codec.value}]" if self.codec else None,
|
||||
str(self.language),
|
||||
self.get_track_name(),
|
||||
self.note,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -427,6 +427,7 @@ class Track:
|
||||
name: Optional[str] = None,
|
||||
drm: Optional[Iterable[DRM_T]] = None,
|
||||
edition: Optional[str] = None,
|
||||
note: Optional[str] = None,
|
||||
session: Optional[Union[Session, "RnetSession"]] = None,
|
||||
downloader: Optional[Callable] = None,
|
||||
downloader_args: Optional[dict] = None,
|
||||
@@ -451,6 +452,8 @@ class Track:
|
||||
raise TypeError(f"Expected id_ to be a {str}, not {type(id_)}")
|
||||
if not isinstance(edition, (str, list, type(None))):
|
||||
raise TypeError(f"Expected edition to be a {str}, {list}, or None, not {type(edition)}")
|
||||
if not isinstance(note, (str, type(None))):
|
||||
raise TypeError(f"Expected note to be a {str}, not {type(note)}")
|
||||
if not isinstance(downloader, (Callable, type(None))):
|
||||
raise TypeError(f"Expected downloader to be a {Callable}, not {type(downloader)}")
|
||||
if not isinstance(downloader_args, (dict, type(None))):
|
||||
@@ -483,6 +486,7 @@ class Track:
|
||||
self.drm = drm
|
||||
self._drm_preference: Optional[str] = None
|
||||
self.edition: list[str] = [edition] if isinstance(edition, str) else (edition or [])
|
||||
self.note = note
|
||||
self.session = session
|
||||
self.downloader = downloader
|
||||
self.downloader_args = downloader_args
|
||||
@@ -981,6 +985,7 @@ class Track:
|
||||
"needs_repack": self.needs_repack,
|
||||
"name": self.name,
|
||||
"edition": self.edition,
|
||||
"note": self.note,
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -999,6 +1004,7 @@ class Track:
|
||||
"needs_repack": data.get("needs_repack", False),
|
||||
"name": data.get("name"),
|
||||
"edition": data.get("edition") or None,
|
||||
"note": data.get("note"),
|
||||
"id_": data.get("id"),
|
||||
}
|
||||
|
||||
|
||||
@@ -412,6 +412,7 @@ class Video(Track):
|
||||
)
|
||||
),
|
||||
", ".join(self.edition) if self.edition else None,
|
||||
self.note,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user