Files
college_craft/site/social/media.py
T
ai_ml_operations 3a14bfb996 Initial commit
2026-08-27 04:17:34 -07:00

92 lines
2.9 KiB
Python

"""Helpers for social post media (StoredFile-backed)."""
from __future__ import annotations
import json
from typing import Any
from django.conf import settings
from django.urls import reverse
from core.models import StoredFile
ALLOWED_IMAGE_TYPES = frozenset(
{"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"}
)
ALLOWED_VIDEO_TYPES = frozenset(
{"video/mp4", "video/quicktime", "video/webm"}
)
MAX_IMAGE_BYTES = 8 * 1024 * 1024 # 8 MB
MAX_VIDEO_BYTES = 100 * 1024 * 1024 # 100 MB
MAX_IMAGES = 10
def public_file_url(file_id: str) -> str:
"""Absolute URL Meta/LinkedIn can fetch (UUID = capability token)."""
base = (settings.PUBLIC_SITE_URL or "").rstrip("/")
path = reverse("core:stored_file", kwargs={"pk": file_id})
if not base:
return path
return f"{base}{path}"
def media_item_from_stored(stored: StoredFile) -> dict[str, Any]:
kind = stored.kind
media_type = "video" if kind == StoredFile.Kind.SOCIAL_VIDEO else "image"
return {
"id": str(stored.pk),
"type": media_type,
"url": public_file_url(str(stored.pk)),
"content_type": stored.content_type,
"filename": stored.filename or "",
"size": stored.size,
}
def parse_media_json(raw: str) -> list[dict[str, Any]]:
"""Parse composer hidden media JSON into a cleaned list."""
raw = (raw or "").strip()
if not raw:
return []
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
raise ValueError("Invalid media payload.") from exc
if not isinstance(data, list):
raise ValueError("Media payload must be a list.")
items: list[dict[str, Any]] = []
for entry in data:
if not isinstance(entry, dict):
continue
file_id = str(entry.get("id") or "").strip()
if not file_id:
continue
try:
stored = StoredFile.objects.get(pk=file_id)
except (StoredFile.DoesNotExist, ValueError) as exc:
raise ValueError(f"Unknown media file: {file_id}") from exc
if stored.kind not in {
StoredFile.Kind.SOCIAL_IMAGE,
StoredFile.Kind.SOCIAL_VIDEO,
StoredFile.Kind.CAMPAIGN_IMAGE,
}:
raise ValueError("File is not a social media attachment.")
items.append(media_item_from_stored(stored))
videos = [m for m in items if m["type"] == "video"]
images = [m for m in items if m["type"] == "image"]
if len(videos) > 1:
raise ValueError("Attach at most one video per post.")
if videos and images:
raise ValueError("Use either images or one video — not both.")
if len(images) > MAX_IMAGES:
raise ValueError(f"Attach at most {MAX_IMAGES} images.")
return items
def split_media(media: list[dict[str, Any]]) -> tuple[list[dict], list[dict]]:
images = [m for m in media if m.get("type") == "image"]
videos = [m for m in media if m.get("type") == "video"]
return images, videos