video quality profile: rebuild as rich-curated Radarr-class model (logic + tests)
Replaces the simplified resolutions+4-sources+codec model with a real quality
ladder to actually stand in for Radarr/Sonarr:
- tiers: source×resolution ladder (Remux-2160p … SDTV) as one ranked, toggleable
list, best→worst, with a cutoff (stop upgrading once the library holds a tier
at/above it — no endless re-grabbing).
- rejects: hard blocks (cam/screener/workprint/3d, optional x264).
- soft preferences (score/tie-break, never reject alone): prefer_codec (any/hevc/av1),
prefer_hdr (off/prefer/require), prefer_audio (any/surround/lossless/atmos),
prefer_repack.
- size guard: min/max GB per item (0 = no limit), min pinned to a real cap.
Pure normalize/load/save (no DB/network), isolated from music. API endpoint is an
unchanged passthrough. 12 tests green, ruff clean. UI next.
This commit is contained in:
parent
975f81f476
commit
23cefd3c15
3 changed files with 189 additions and 99 deletions
|
|
@ -1,15 +1,23 @@
|
||||||
"""Video quality profile — ONE unified profile applied to every video download
|
"""Video quality profile — ONE unified, Radarr/Sonarr-class profile applied to
|
||||||
source (slskd / torrent / usenet).
|
every video download source (slskd / torrent / usenet).
|
||||||
|
|
||||||
Unlike the music side (where quality is bitrate density), video quality is
|
Unlike the music side (where quality is bitrate density), video quality is a
|
||||||
**resolution + source + codec**, parsed from a release title or filename. So the
|
**source×resolution tier** parsed from a release title, refined by codec / HDR /
|
||||||
profile is a small, source-agnostic ranking the (later-phase) download engine will
|
audio preferences. The (later-phase) download engine uses this profile to pick the
|
||||||
use to pick the best candidate:
|
best candidate and to decide when a library item is "good enough" (the cutoff).
|
||||||
|
|
||||||
- resolution tiers (2160p / 1080p / 720p / 480p), each enabled + priority-ordered
|
The model (rich-curated — Radarr-competitive without a full custom-formats engine):
|
||||||
- a preferred source order (bluray > web-dl > webrip > hdtv)
|
|
||||||
- a codec preference (any / x265 / x264) and an HDR preference
|
- ``tiers`` : the source×resolution quality ladder (Remux-2160p … SDTV)
|
||||||
- an optional max size cap per item, and a fallback toggle
|
as ONE ranked list; each tier enabled + ordered best→worst.
|
||||||
|
- ``cutoff`` : once the library holds a tier at/above this rank, stop
|
||||||
|
upgrading (prevents endless re-grabbing).
|
||||||
|
- ``rejects`` : hard blocks — never grab these (cam / screener / workprint /
|
||||||
|
3d / optionally x264).
|
||||||
|
- preferences (SOFT — they score/tie-break, they never reject on their own):
|
||||||
|
``prefer_codec`` (any|hevc|av1), ``prefer_hdr`` (off|prefer|require),
|
||||||
|
``prefer_audio`` (any|surround|lossless|atmos), ``prefer_repack`` (bool).
|
||||||
|
- ``min_size_gb`` / ``max_size_gb`` : size guard per item (0 = no limit).
|
||||||
|
|
||||||
Pure data + normalize/validate here (no DB, no network) so it's unit-tested in
|
Pure data + normalize/validate here (no DB, no network) so it's unit-tested in
|
||||||
isolation. Persisted as a JSON blob in video.db's ``video_settings['quality_profile']``.
|
isolation. Persisted as a JSON blob in video.db's ``video_settings['quality_profile']``.
|
||||||
|
|
@ -21,28 +29,48 @@ from __future__ import annotations
|
||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# Ordered best→worst; the UI renders these as the default priority order.
|
# The quality ladder, ordered best→worst. ``key`` = ``<source>-<resolution>`` (plus
|
||||||
RESOLUTIONS = ("2160p", "1080p", "720p", "480p")
|
# the two resolution-less SD tiers). This is the default ranking the UI renders.
|
||||||
SOURCES = ("bluray", "web-dl", "webrip", "hdtv")
|
TIERS = (
|
||||||
CODECS = ("any", "x265", "x264")
|
"remux-2160p", "bluray-2160p", "web-2160p",
|
||||||
MAX_SIZE_CAP_GB = 200 # slider ceiling; 0 means "no cap"
|
"remux-1080p", "bluray-1080p", "web-1080p", "webrip-1080p", "hdtv-1080p",
|
||||||
|
"bluray-720p", "web-720p", "hdtv-720p",
|
||||||
|
"dvd", "sdtv",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Default-enabled tiers: solid 1080p + 720p coverage. 4K tiers off (size) and the
|
||||||
|
# SD tiers (dvd/sdtv) off — users opt into those deliberately.
|
||||||
|
_DEFAULT_ON = frozenset({
|
||||||
|
"remux-1080p", "bluray-1080p", "web-1080p", "webrip-1080p", "hdtv-1080p",
|
||||||
|
"bluray-720p", "web-720p", "hdtv-720p",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Hard rejects (never grabbed). x264 is offered but OFF by default (rejecting it
|
||||||
|
# would drop most releases) — power users who only want HEVC/AV1 can enable it.
|
||||||
|
REJECTS = ("cam", "screener", "workprint", "3d", "x264")
|
||||||
|
|
||||||
|
CODECS = ("any", "hevc", "av1") # SOFT codec preference (tie-breaker)
|
||||||
|
HDR_MODES = ("off", "prefer", "require") # require = HDR-only (a real filter)
|
||||||
|
AUDIO_MODES = ("any", "surround", "lossless", "atmos")
|
||||||
|
MAX_SIZE_CAP_GB = 200 # slider ceiling; 0 means "no limit"
|
||||||
|
|
||||||
|
_TIER_SET = frozenset(TIERS)
|
||||||
|
|
||||||
|
|
||||||
def default_profile() -> dict:
|
def default_profile() -> dict:
|
||||||
"""A sensible default: 1080p/720p on, 4K off (size), SD off."""
|
"""A sensible best-in-class default: full 1080p/720p ladder, cutoff at
|
||||||
|
BluRay-1080p, junk rejected, HEVC + HDR preferred (soft)."""
|
||||||
return {
|
return {
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"resolutions": {
|
"tiers": [{"key": k, "enabled": k in _DEFAULT_ON} for k in TIERS],
|
||||||
"2160p": {"enabled": False, "priority": 1},
|
"cutoff": "bluray-1080p",
|
||||||
"1080p": {"enabled": True, "priority": 2},
|
"rejects": ["cam", "screener", "workprint", "3d"],
|
||||||
"720p": {"enabled": True, "priority": 3},
|
"prefer_codec": "hevc",
|
||||||
"480p": {"enabled": False, "priority": 4},
|
"prefer_hdr": "prefer",
|
||||||
},
|
"prefer_audio": "any",
|
||||||
"source_priority": list(SOURCES),
|
"prefer_repack": True,
|
||||||
"codec": "any",
|
"min_size_gb": 0,
|
||||||
"prefer_hdr": False,
|
|
||||||
"max_size_gb": 0,
|
"max_size_gb": 0,
|
||||||
"fallback_enabled": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,39 +81,64 @@ def _coerce_int(value: Any, default: int) -> int:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _clamp_size(value: Any) -> int:
|
||||||
|
return min(MAX_SIZE_CAP_GB, max(0, _coerce_int(value, 0)))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_tiers(value: Any) -> list:
|
||||||
|
"""Rebuild the ranked tier ladder: keep the caller's order for known tiers,
|
||||||
|
coerce each ``enabled``, drop junk/dupes, then append any missing tiers in
|
||||||
|
canonical order so the ladder is always complete. Defaults from ``_DEFAULT_ON``."""
|
||||||
|
enabled = {k: (k in _DEFAULT_ON) for k in TIERS}
|
||||||
|
order: list = []
|
||||||
|
if isinstance(value, list):
|
||||||
|
for item in value:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
k = str(item.get("key") or "").strip().lower()
|
||||||
|
else:
|
||||||
|
k = str(item or "").strip().lower()
|
||||||
|
if k in _TIER_SET and k not in order:
|
||||||
|
order.append(k)
|
||||||
|
if isinstance(item, dict) and "enabled" in item:
|
||||||
|
enabled[k] = bool(item.get("enabled"))
|
||||||
|
for k in TIERS: # complete the ladder, canonical order
|
||||||
|
if k not in order:
|
||||||
|
order.append(k)
|
||||||
|
return [{"key": k, "enabled": enabled[k]} for k in order]
|
||||||
|
|
||||||
|
|
||||||
def normalize(raw: Any) -> dict:
|
def normalize(raw: Any) -> dict:
|
||||||
"""Coerce a stored/posted profile to a valid shape, filling gaps from the
|
"""Coerce a stored/posted profile to a valid shape, filling gaps from the
|
||||||
default. Unknown keys are dropped; invalid values fall back. Never raises."""
|
default. Unknown keys dropped; invalid values fall back. Never raises."""
|
||||||
d = default_profile()
|
d = default_profile()
|
||||||
if not isinstance(raw, dict):
|
if not isinstance(raw, dict):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
res = raw.get("resolutions")
|
d["tiers"] = normalize_tiers(raw.get("tiers"))
|
||||||
if isinstance(res, dict):
|
|
||||||
for i, key in enumerate(RESOLUTIONS):
|
|
||||||
r = res.get(key)
|
|
||||||
if isinstance(r, dict):
|
|
||||||
d["resolutions"][key] = {
|
|
||||||
"enabled": bool(r.get("enabled", d["resolutions"][key]["enabled"])),
|
|
||||||
"priority": _coerce_int(r.get("priority"), i + 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
sp = raw.get("source_priority")
|
cut = str(raw.get("cutoff") or "").strip().lower()
|
||||||
if isinstance(sp, list):
|
if cut in _TIER_SET:
|
||||||
clean = []
|
d["cutoff"] = cut
|
||||||
for s in sp: # keep known sources, in order, no dupes/junk
|
|
||||||
if s in SOURCES and s not in clean:
|
|
||||||
clean.append(s)
|
|
||||||
for s in SOURCES: # append any the caller dropped, canonical order
|
|
||||||
if s not in clean:
|
|
||||||
clean.append(s)
|
|
||||||
d["source_priority"] = clean
|
|
||||||
|
|
||||||
if raw.get("codec") in CODECS:
|
rj = raw.get("rejects")
|
||||||
d["codec"] = raw["codec"]
|
if isinstance(rj, list):
|
||||||
d["prefer_hdr"] = bool(raw.get("prefer_hdr", d["prefer_hdr"]))
|
chosen = {str(x or "").strip().lower() for x in rj}
|
||||||
d["max_size_gb"] = min(MAX_SIZE_CAP_GB, max(0, _coerce_int(raw.get("max_size_gb"), 0)))
|
d["rejects"] = [r for r in REJECTS if r in chosen] # canonical order, valid only
|
||||||
d["fallback_enabled"] = bool(raw.get("fallback_enabled", True))
|
|
||||||
|
if raw.get("prefer_codec") in CODECS:
|
||||||
|
d["prefer_codec"] = raw["prefer_codec"]
|
||||||
|
if raw.get("prefer_hdr") in HDR_MODES:
|
||||||
|
d["prefer_hdr"] = raw["prefer_hdr"]
|
||||||
|
if raw.get("prefer_audio") in AUDIO_MODES:
|
||||||
|
d["prefer_audio"] = raw["prefer_audio"]
|
||||||
|
d["prefer_repack"] = bool(raw.get("prefer_repack", d["prefer_repack"]))
|
||||||
|
|
||||||
|
mn = _clamp_size(raw.get("min_size_gb"))
|
||||||
|
mx = _clamp_size(raw.get("max_size_gb"))
|
||||||
|
if mx and mn > mx: # a min above a real cap is nonsense — pin it to the cap
|
||||||
|
mn = mx
|
||||||
|
d["min_size_gb"] = mn
|
||||||
|
d["max_size_gb"] = mx
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -108,6 +161,6 @@ def save(db, raw: Any) -> dict:
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"RESOLUTIONS", "SOURCES", "CODECS", "MAX_SIZE_CAP_GB",
|
"TIERS", "REJECTS", "CODECS", "HDR_MODES", "AUDIO_MODES", "MAX_SIZE_CAP_GB",
|
||||||
"default_profile", "normalize", "load", "save",
|
"default_profile", "normalize", "normalize_tiers", "load", "save",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -392,13 +392,16 @@ def test_quality_profile_endpoint_roundtrips(tmp_path):
|
||||||
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
|
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
try:
|
try:
|
||||||
# Default profile served when unset.
|
# Default profile served when unset (rich-curated model: tier ladder + cutoff).
|
||||||
d = client.get("/api/video/downloads/quality").get_json()
|
d = client.get("/api/video/downloads/quality").get_json()
|
||||||
assert d["resolutions"]["1080p"]["enabled"] is True and d["codec"] == "any"
|
assert [t["key"] for t in d["tiers"]][0] == "remux-2160p"
|
||||||
# POST normalizes + persists; bad codec rejected.
|
assert d["cutoff"] == "bluray-1080p" and d["prefer_codec"] == "hevc"
|
||||||
|
# POST normalizes + persists; bad codec rejected, valid cutoff kept.
|
||||||
out = client.post("/api/video/downloads/quality",
|
out = client.post("/api/video/downloads/quality",
|
||||||
json={"codec": "bogus", "max_size_gb": 50, "prefer_hdr": True}).get_json()
|
json={"prefer_codec": "bogus", "max_size_gb": 50,
|
||||||
assert out["codec"] == "any" and out["max_size_gb"] == 50 and out["prefer_hdr"] is True
|
"cutoff": "web-720p", "prefer_hdr": "require"}).get_json()
|
||||||
|
assert out["prefer_codec"] == "hevc" and out["max_size_gb"] == 50
|
||||||
|
assert out["cutoff"] == "web-720p" and out["prefer_hdr"] == "require"
|
||||||
assert client.get("/api/video/downloads/quality").get_json()["max_size_gb"] == 50
|
assert client.get("/api/video/downloads/quality").get_json()["max_size_gb"] == 50
|
||||||
finally:
|
finally:
|
||||||
videoapi._video_db = None
|
videoapi._video_db = None
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,42 @@
|
||||||
"""Video quality profile — the pure default/normalize/load/save seam (resolution
|
"""Video quality profile — the pure default/normalize/load/save seam for the
|
||||||
tiers + source/codec/HDR + size cap), isolated from the music side."""
|
rich-curated (Radarr-class) model: a ranked source×resolution tier ladder +
|
||||||
|
cutoff + hard rejects + soft codec/HDR/audio/repack preferences + size guard,
|
||||||
|
isolated from the music side."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from core.video.quality_profile import (
|
from core.video.quality_profile import (
|
||||||
|
AUDIO_MODES,
|
||||||
CODECS,
|
CODECS,
|
||||||
|
HDR_MODES,
|
||||||
MAX_SIZE_CAP_GB,
|
MAX_SIZE_CAP_GB,
|
||||||
RESOLUTIONS,
|
REJECTS,
|
||||||
SOURCES,
|
TIERS,
|
||||||
default_profile,
|
default_profile,
|
||||||
load,
|
load,
|
||||||
normalize,
|
normalize,
|
||||||
|
normalize_tiers,
|
||||||
save,
|
save,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _keys(tiers):
|
||||||
|
return [t["key"] for t in tiers]
|
||||||
|
|
||||||
|
|
||||||
def test_default_shape():
|
def test_default_shape():
|
||||||
d = default_profile()
|
d = default_profile()
|
||||||
assert set(d["resolutions"]) == set(RESOLUTIONS)
|
assert _keys(d["tiers"]) == list(TIERS) # complete ladder, canonical order
|
||||||
assert d["resolutions"]["1080p"]["enabled"] is True
|
on = {t["key"] for t in d["tiers"] if t["enabled"]}
|
||||||
assert d["resolutions"]["2160p"]["enabled"] is False # 4K off by default (size)
|
assert "bluray-1080p" in on and "web-720p" in on # 1080p/720p on
|
||||||
assert d["source_priority"] == list(SOURCES)
|
assert "remux-2160p" not in on and "sdtv" not in on # 4K + SD off by default
|
||||||
assert d["codec"] == "any" and d["prefer_hdr"] is False
|
assert d["cutoff"] == "bluray-1080p"
|
||||||
assert d["max_size_gb"] == 0 and d["fallback_enabled"] is True
|
assert "cam" in d["rejects"] and "x264" not in d["rejects"] # junk blocked, x264 allowed
|
||||||
|
assert d["prefer_codec"] == "hevc" and d["prefer_hdr"] == "prefer"
|
||||||
|
assert d["prefer_audio"] == "any" and d["prefer_repack"] is True
|
||||||
|
assert d["min_size_gb"] == 0 and d["max_size_gb"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_garbage_returns_default():
|
def test_normalize_garbage_returns_default():
|
||||||
|
|
@ -33,32 +45,57 @@ def test_normalize_garbage_returns_default():
|
||||||
assert normalize(123) == default_profile()
|
assert normalize(123) == default_profile()
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_fills_gaps_and_coerces():
|
def test_normalize_tiers_preserves_order_completes_and_coerces():
|
||||||
out = normalize({
|
# caller sends a re-ranked subset with a couple toggled; rest must be appended
|
||||||
"resolutions": {"2160p": {"enabled": True, "priority": "1"}}, # str priority coerced
|
out = normalize_tiers([
|
||||||
"codec": "x265",
|
{"key": "web-1080p", "enabled": False},
|
||||||
"prefer_hdr": 1,
|
{"key": "bogus", "enabled": True}, # junk dropped
|
||||||
"max_size_gb": "75",
|
{"key": "remux-2160p", "enabled": True},
|
||||||
"fallback_enabled": False,
|
"web-1080p", # dupe ignored
|
||||||
})
|
])
|
||||||
assert out["resolutions"]["2160p"] == {"enabled": True, "priority": 1}
|
keys = _keys(out)
|
||||||
assert out["resolutions"]["1080p"]["enabled"] is True # untouched tier kept from default
|
assert keys[0] == "web-1080p" and keys[1] == "remux-2160p" # caller order kept
|
||||||
assert out["codec"] == "x265" and out["prefer_hdr"] is True
|
assert set(keys) == set(TIERS) and len(keys) == len(TIERS) # ladder completed
|
||||||
assert out["max_size_gb"] == 75 and out["fallback_enabled"] is False
|
by = {t["key"]: t["enabled"] for t in out}
|
||||||
|
assert by["web-1080p"] is False and by["remux-2160p"] is True
|
||||||
|
assert by["bluray-1080p"] is True # untouched default stays on
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_rejects_bad_codec_and_clamps_size():
|
def test_normalize_cutoff_must_be_a_known_tier():
|
||||||
assert normalize({"codec": "vp9"})["codec"] == "any" # unknown codec rejected
|
assert normalize({"cutoff": "web-720p"})["cutoff"] == "web-720p"
|
||||||
assert normalize({"max_size_gb": -5})["max_size_gb"] == 0 # negative clamped
|
assert normalize({"cutoff": "nonsense"})["cutoff"] == "bluray-1080p" # falls back
|
||||||
assert normalize({"max_size_gb": 99999})["max_size_gb"] == MAX_SIZE_CAP_GB # capped
|
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_source_priority_dedupes_and_completes():
|
def test_normalize_rejects_keep_canonical_order_and_drop_junk():
|
||||||
out = normalize({"source_priority": ["web-dl", "bogus", "web-dl"]})
|
out = normalize({"rejects": ["x264", "bogus", "cam", "x264"]})
|
||||||
# known one first, rest appended in canonical order, no dupes, no junk
|
assert out["rejects"] == ["cam", "x264"] # canonical order, valid only, deduped
|
||||||
assert out["source_priority"][0] == "web-dl"
|
|
||||||
assert set(out["source_priority"]) == set(SOURCES)
|
|
||||||
assert len(out["source_priority"]) == len(SOURCES)
|
def test_normalize_soft_prefs_validate():
|
||||||
|
out = normalize({"prefer_codec": "av1", "prefer_hdr": "require",
|
||||||
|
"prefer_audio": "atmos", "prefer_repack": 0})
|
||||||
|
assert out["prefer_codec"] == "av1" and out["prefer_hdr"] == "require"
|
||||||
|
assert out["prefer_audio"] == "atmos" and out["prefer_repack"] is False
|
||||||
|
# unknown enum values fall back to the default
|
||||||
|
bad = normalize({"prefer_codec": "vp9", "prefer_hdr": "maybe", "prefer_audio": "8ch"})
|
||||||
|
assert bad["prefer_codec"] == "hevc" and bad["prefer_hdr"] == "prefer"
|
||||||
|
assert bad["prefer_audio"] == "any"
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_size_clamps_and_pins_min_to_cap():
|
||||||
|
assert normalize({"max_size_gb": -5})["max_size_gb"] == 0
|
||||||
|
assert normalize({"max_size_gb": 99999})["max_size_gb"] == MAX_SIZE_CAP_GB
|
||||||
|
out = normalize({"min_size_gb": 50, "max_size_gb": 20}) # min above the cap
|
||||||
|
assert out["min_size_gb"] == 20 and out["max_size_gb"] == 20
|
||||||
|
keep = normalize({"min_size_gb": 5, "max_size_gb": 0}) # 0 cap = no limit, min stays
|
||||||
|
assert keep["min_size_gb"] == 5 and keep["max_size_gb"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_constants():
|
||||||
|
assert CODECS == ("any", "hevc", "av1")
|
||||||
|
assert HDR_MODES == ("off", "prefer", "require")
|
||||||
|
assert AUDIO_MODES == ("any", "surround", "lossless", "atmos")
|
||||||
|
assert "cam" in REJECTS and "screener" in REJECTS
|
||||||
|
|
||||||
|
|
||||||
# ── DB round-trip via an injected fake ────────────────────────────────────────
|
# ── DB round-trip via an injected fake ────────────────────────────────────────
|
||||||
|
|
@ -79,10 +116,11 @@ def test_load_default_when_unset():
|
||||||
|
|
||||||
def test_save_then_load_roundtrips_normalized():
|
def test_save_then_load_roundtrips_normalized():
|
||||||
db = _FakeDB()
|
db = _FakeDB()
|
||||||
saved = save(db, {"codec": "x264", "max_size_gb": 40,
|
saved = save(db, {"prefer_codec": "av1", "max_size_gb": 40, "cutoff": "web-720p",
|
||||||
"resolutions": {"480p": {"enabled": True, "priority": 4}}})
|
"tiers": [{"key": "remux-2160p", "enabled": True}]})
|
||||||
assert saved["codec"] == "x264" and saved["max_size_gb"] == 40
|
assert saved["prefer_codec"] == "av1" and saved["max_size_gb"] == 40
|
||||||
assert json.loads(db.get_setting("quality_profile"))["codec"] == "x264"
|
assert saved["cutoff"] == "web-720p"
|
||||||
|
assert json.loads(db.get_setting("quality_profile"))["prefer_codec"] == "av1"
|
||||||
assert load(db) == saved
|
assert load(db) == saved
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,7 +128,3 @@ def test_load_recovers_from_corrupt_json():
|
||||||
db = _FakeDB()
|
db = _FakeDB()
|
||||||
db.set_setting("quality_profile", "{not json")
|
db.set_setting("quality_profile", "{not json")
|
||||||
assert load(db) == default_profile()
|
assert load(db) == default_profile()
|
||||||
|
|
||||||
|
|
||||||
def test_codecs_constant():
|
|
||||||
assert CODECS == ("any", "x265", "x264")
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue