Support original-language movie requests and fix repair dialog layout
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from ..services.request_language import language_info, original_profile, is_original_profile
|
||||
from ..feature_guards import require_request_access
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
import asyncio
|
||||
@@ -3127,6 +3128,7 @@ async def request_options(
|
||||
"backdropPath": details.get("backdropPath") or details.get("backdrop_path"),
|
||||
"seasons": seasons,
|
||||
"existingRequestId": existing_request_id,
|
||||
"originalLanguage": language_info(details),
|
||||
},
|
||||
"destination": {
|
||||
"collector": destination["collector"],
|
||||
@@ -3227,7 +3229,16 @@ async def create_request(
|
||||
detail=f"Season selection is not available for this series: {invalid_seasons}",
|
||||
)
|
||||
|
||||
language = language_info(details)
|
||||
accept_original = payload.get("acceptOriginalLanguage", False)
|
||||
if not isinstance(accept_original, bool):
|
||||
raise HTTPException(400, "The language choice must be true or false.")
|
||||
if accept_original and not language:
|
||||
raise HTTPException(409, "The original language could not be verified. Reload this title.")
|
||||
destination = await _resolve_request_destination(runtime, client, media_type)
|
||||
if accept_original and media_type == "movie":
|
||||
destination["profile_id"] = await original_profile(
|
||||
RadarrClient(runtime.radarr_base_url, runtime.radarr_api_key), destination["profile_id"])
|
||||
|
||||
try:
|
||||
created = await client.create_request(
|
||||
@@ -3262,7 +3273,8 @@ async def create_request(
|
||||
"request_created",
|
||||
"Create request",
|
||||
"ok",
|
||||
f"{media_type} request created from discovery by {user.get('username')}.",
|
||||
f"{media_type} request created from discovery by {user.get('username')}."
|
||||
+ (f" Original-language audio accepted ({language['code']})." if accept_original else ""),
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -3445,6 +3457,11 @@ async def action_search_auto(request_id: str, user: Dict[str, str] = Depends(get
|
||||
raise HTTPException(status_code=400, detail="Radarr not configured")
|
||||
target_profile_id = _quality_profile_id(runtime.radarr_quality_profile_id)
|
||||
current_profile_id = _quality_profile_id(arr_item.get("qualityProfileId"))
|
||||
if current_profile_id and current_profile_id != target_profile_id:
|
||||
profiles = await client.get_quality_profiles()
|
||||
current = next((p for p in profiles if p.get("id") == current_profile_id), {})
|
||||
if is_original_profile(current):
|
||||
target_profile_id = current_profile_id
|
||||
profile_message = None
|
||||
movie_id = _quality_profile_id(arr_item.get("id"))
|
||||
if target_profile_id and movie_id and current_profile_id != target_profile_id:
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Explicit original-language requests without changing shared quality defaults."""
|
||||
import asyncio
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
||||
import httpx
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
_profile_lock = asyncio.Lock()
|
||||
_prefix = "Magent Original "
|
||||
|
||||
|
||||
def language_info(details):
|
||||
code = str(details.get("originalLanguage") or details.get("original_language") or "").lower()
|
||||
if not re.fullmatch(r"[a-z]{2}", code) or code in {"en", "xx", "zz"}:
|
||||
return None
|
||||
return {"code": code}
|
||||
|
||||
|
||||
def profile_body(profile):
|
||||
return {key: copy.deepcopy(value) for key, value in profile.items() if key not in {"id", "name"}}
|
||||
|
||||
|
||||
def profile_name(body):
|
||||
return _prefix + hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def is_original_profile(profile):
|
||||
return ((profile.get("language") or {}).get("id") == -2
|
||||
and profile.get("name") == profile_name(profile_body(profile)))
|
||||
|
||||
|
||||
async def original_profile(client, default_id):
|
||||
# Reuse immutable copies; never edit a profile already used by other titles.
|
||||
async with _profile_lock:
|
||||
try:
|
||||
profiles = await client.get_quality_profiles()
|
||||
except httpx.HTTPError as exc:
|
||||
raise HTTPException(502, "Radarr could not load the language profile. Try again.") from exc
|
||||
if not isinstance(profiles, list):
|
||||
raise HTTPException(502, "Radarr returned invalid quality profiles.")
|
||||
default = next((p for p in profiles if p.get("id") == default_id), None)
|
||||
if not default:
|
||||
raise HTTPException(409, "The default quality profile changed. Reload the request.")
|
||||
body = profile_body(default)
|
||||
body["language"] = {"id": -2, "name": "Original"}
|
||||
name = profile_name(body)
|
||||
match = next((p for p in profiles if p.get("name") == name and profile_body(p) == body), None)
|
||||
if match:
|
||||
return match["id"]
|
||||
try:
|
||||
result = await client.post("/api/v3/qualityprofile", payload={**body, "name": name})
|
||||
except httpx.HTTPError as exc:
|
||||
raise HTTPException(502, "Radarr could not prepare the original-language profile. Try again.") from exc
|
||||
if not isinstance(result, dict) or not isinstance(result.get("id"), int):
|
||||
raise HTTPException(502, "Radarr could not prepare the original-language profile. Try again.")
|
||||
return result["id"]
|
||||
Reference in New Issue
Block a user