f56b088643
Base Image Build / prepare (push) Has been cancelled
Build and Push Multi-Arch Docker Image / build-and-push (push) Has been cancelled
Frontend Tests / test (push) Has been cancelled
Base Image Build / docker (amd64, ubuntu-24.04) (push) Has been cancelled
Base Image Build / docker (arm64, ubuntu-24.04-arm) (push) Has been cancelled
Base Image Build / create-manifest (push) Has been cancelled
150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
"""VOD catalog: optional single M3U account for library listings (env DISPATCHARR_VOD_CATALOG_ACCOUNT_ID).
|
|
|
|
Plus per-user playback / catalog policies stored in User.custom_properties:
|
|
|
|
custom_properties = {
|
|
"vod_playback_mode": "strict" | "prefer" | "auto",
|
|
"vod_playback_account_ids": [10, 11],
|
|
"allowed_movie_category_ids": [3, 7, 12], # empty/missing = all
|
|
"allowed_series_category_ids": [4, 9], # empty/missing = all
|
|
}
|
|
"""
|
|
|
|
import os
|
|
|
|
|
|
def get_vod_catalog_account_id():
|
|
raw = (os.environ.get("DISPATCHARR_VOD_CATALOG_ACCOUNT_ID") or "").strip()
|
|
if raw.isdigit():
|
|
return int(raw)
|
|
return None
|
|
|
|
|
|
def get_vod_playback_allowed_account_ids():
|
|
"""
|
|
Optional comma-separated M3U account IDs used only for VOD stream selection (failover).
|
|
Example: DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS=10,11,12
|
|
When unset, any active M3U account with a relation may be used (default).
|
|
"""
|
|
raw = (os.environ.get("DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS") or "").strip()
|
|
if not raw:
|
|
return None
|
|
out = []
|
|
for part in raw.split(","):
|
|
part = part.strip()
|
|
if part.isdigit():
|
|
out.append(int(part))
|
|
return out if out else None
|
|
|
|
|
|
def filter_m3u_relation_queryset_for_playback(qs):
|
|
"""
|
|
Apply DISPATCHARR_VOD_PLAYBACK_ACCOUNT_IDS allowlist to an M3U relation queryset.
|
|
Used by VOD proxy and Xtream stream redirects so failover matches playback policy.
|
|
"""
|
|
allowed = get_vod_playback_allowed_account_ids()
|
|
if allowed is not None:
|
|
return qs.filter(m3u_account_id__in=allowed, m3u_account__is_active=True)
|
|
return qs.filter(m3u_account__is_active=True)
|
|
|
|
|
|
def _coerce_int_list(raw):
|
|
out = []
|
|
if not raw:
|
|
return out
|
|
if isinstance(raw, (list, tuple)):
|
|
items = raw
|
|
elif isinstance(raw, str):
|
|
items = raw.split(",")
|
|
else:
|
|
return out
|
|
for x in items:
|
|
try:
|
|
out.append(int(str(x).strip()))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
return out
|
|
|
|
|
|
def get_user_vod_prefs(user):
|
|
"""Return a normalized dict of per-user VOD policies. Safe with None / anonymous user."""
|
|
if not user or not getattr(user, "is_authenticated", False):
|
|
return {
|
|
"playback_mode": "auto",
|
|
"playback_account_ids": [],
|
|
"allowed_movie_category_ids": None,
|
|
"allowed_series_category_ids": None,
|
|
}
|
|
cp = getattr(user, "custom_properties", None) or {}
|
|
accounts = _coerce_int_list(cp.get("vod_playback_account_ids"))
|
|
mode = (cp.get("vod_playback_mode") or "").strip().lower()
|
|
if mode not in ("strict", "prefer", "auto"):
|
|
mode = "auto"
|
|
movie_cats = _coerce_int_list(cp.get("allowed_movie_category_ids"))
|
|
series_cats = _coerce_int_list(cp.get("allowed_series_category_ids"))
|
|
return {
|
|
"playback_mode": mode,
|
|
"playback_account_ids": accounts,
|
|
"allowed_movie_category_ids": movie_cats or None,
|
|
"allowed_series_category_ids": series_cats or None,
|
|
}
|
|
|
|
|
|
def filter_relation_qs_for_user_playback(qs, user):
|
|
"""Restrict a relation queryset based on the user's playback prefs.
|
|
|
|
Returns (queryset, prefer_account_id).
|
|
- mode=strict: only the user's accounts (queryset further restricted).
|
|
- mode=prefer: queryset unchanged, returns the first account as preference for tie-breaking.
|
|
- mode=auto (or no accounts): no per-user filter; the global allowlist already applies upstream.
|
|
"""
|
|
prefs = get_user_vod_prefs(user)
|
|
accounts = prefs.get("playback_account_ids") or []
|
|
mode = prefs.get("playback_mode") or "auto"
|
|
if accounts and mode == "strict":
|
|
qs = qs.filter(m3u_account_id__in=accounts)
|
|
prefer = accounts[0]
|
|
elif accounts and mode == "prefer":
|
|
prefer = accounts[0]
|
|
else:
|
|
prefer = None
|
|
return qs, prefer
|
|
|
|
|
|
def get_user_allowed_movie_category_ids(user):
|
|
return get_user_vod_prefs(user).get("allowed_movie_category_ids")
|
|
|
|
|
|
def get_user_allowed_series_category_ids(user):
|
|
return get_user_vod_prefs(user).get("allowed_series_category_ids")
|
|
|
|
|
|
def filter_movie_qs_for_user(qs, user):
|
|
"""Restrict a Movie queryset to only categories the user is allowed to see."""
|
|
cats = get_user_allowed_movie_category_ids(user)
|
|
if cats:
|
|
return qs.filter(m3u_relations__category_id__in=cats).distinct()
|
|
return qs
|
|
|
|
|
|
def filter_series_qs_for_user(qs, user):
|
|
"""Restrict a Series queryset to only categories the user is allowed to see."""
|
|
cats = get_user_allowed_series_category_ids(user)
|
|
if cats:
|
|
return qs.filter(m3u_relations__category_id__in=cats).distinct()
|
|
return qs
|
|
|
|
|
|
def filter_m3u_movie_relation_qs_for_user(qs, user):
|
|
cats = get_user_allowed_movie_category_ids(user)
|
|
if cats:
|
|
return qs.filter(category_id__in=cats)
|
|
return qs
|
|
|
|
|
|
def filter_m3u_series_relation_qs_for_user(qs, user):
|
|
cats = get_user_allowed_series_category_ids(user)
|
|
if cats:
|
|
return qs.filter(category_id__in=cats)
|
|
return qs
|