Files

71 lines
2.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""本地文件存储:落 data/media/,由 FastAPI /media 静态托管。"""
from __future__ import annotations
import mimetypes
import uuid
from pathlib import Path
import httpx
from config import get_settings
def media_root() -> Path:
root = Path(get_settings().data_dir) / "media"
root.mkdir(parents=True, exist_ok=True)
return root
def public_url(key: str) -> str:
"""media key → 可访问 URL。"""
settings = get_settings()
return f"{settings.app_base_url.rstrip('/')}/media/{key}"
def _ext_from_url_or_type(hint: str, content_type: str = "") -> str:
if content_type:
ctype = content_type.split(";")[0].strip().lower()
mapping = {
"image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp",
"image/gif": ".gif", "image/bmp": ".bmp", "video/mp4": ".mp4",
}
if ctype in mapping:
return mapping[ctype]
ext = mimetypes.guess_extension(hint.split("?")[0].lower()) or ".jpg"
return ".jpg" if ext == ".jpe" else ext
def write_bytes(data: bytes, key_prefix: str = "", ext: str = ".jpg") -> str:
"""写文件,返回 media key(相对 media 根的路径)。"""
key = f"{key_prefix + '/' if key_prefix else ''}{uuid.uuid4().hex}{ext}"
path = media_root() / key
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return key
async def download_bytes(url: str, referer: str | None = None, timeout: float = 60.0) -> tuple[bytes, str]:
"""下载远程字节。返回 (bytes, content_type)。"""
headers = {"Referer": referer} if referer else {}
headers.setdefault("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True, verify=False) as client:
resp = await client.get(url, headers=headers)
resp.raise_for_status()
ctype = (resp.headers.get("content-type") or "application/octet-stream").split(";")[0].strip()
return resp.content, ctype
async def save_from_url(url: str, key_prefix: str = "", referer: str | None = None) -> str:
data, ctype = await download_bytes(url, referer)
key = write_bytes(data, key_prefix, _ext_from_url_or_type(url, ctype))
return public_url(key)
def local_path(stored_url_or_key: str) -> Path | None:
"""stored_urlhttp.../media/xxx)或 key → 本地文件路径。"""
s = stored_url_or_key
if "/media/" in s:
s = s.split("/media/", 1)[1]
p = media_root() / s
return p if p.exists() else None