Dish_Recognition/central_server/media_store.py

107 lines
3.7 KiB
Python
Raw 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.

# coding=utf-8
"""本地对象存储(静态文件目录)。
图片落到本目录,通过 GET /media/... 对外访问。
生产可把 data/media 挂到 Nginx / OSS,接口形态不变。
"""
from __future__ import annotations
import os
import re
import time
from pathlib import Path
_FOLDER = os.path.dirname(os.path.abspath(__file__))
MEDIA_ROOT = os.environ.get("DISH_MEDIA_ROOT") or os.path.join(_FOLDER, "data", "media")
MEDIA_PUBLIC_BASE = os.environ.get("DISH_MEDIA_PUBLIC_BASE", "http://127.0.0.1:9000/media").rstrip("/")
_SAFE = re.compile(r"[^A-Za-z0-9._-]+")
ALLOWED_TYPES = ("source", "labeled", "crop")
ALLOWED_EXT = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
def _safe_token(value, fallback="x"):
text = _SAFE.sub("_", str(value or "").strip())[:80]
return text or fallback
def public_url(rel_path):
rel = str(rel_path or "").replace("\\", "/").lstrip("/")
return f"{MEDIA_PUBLIC_BASE}/{rel}"
def local_path(rel_path):
rel = str(rel_path or "").replace("\\", "/").lstrip("/")
return os.path.normpath(os.path.join(MEDIA_ROOT, *rel.split("/")))
def is_http_url(value):
text = (value or "").strip().lower()
return text.startswith("http://") or text.startswith("https://")
class MediaStore:
"""按 基地 / 请求号 归档图片。"""
def __init__(self, root=None, public_base=None):
self.root = os.path.abspath(root or MEDIA_ROOT)
self.public_base = (public_base or MEDIA_PUBLIC_BASE).rstrip("/")
os.makedirs(self.root, exist_ok=True)
def save_bytes(self, data, filename, media_type="source", base_id="BASE", request_id=None):
if not data:
raise ValueError("empty file")
media_type = (media_type or "source").strip().lower()
if media_type not in ALLOWED_TYPES:
media_type = "source"
ext = Path(filename or "").suffix.lower() or ".jpg"
if ext not in ALLOWED_EXT:
ext = ".jpg"
req = _safe_token(request_id or f"REQ_{int(time.time() * 1000)}")
base = _safe_token(base_id or "BASE")
stem = _safe_token(Path(filename or media_type).stem, media_type)
rel = f"{base}/{req}/{media_type}_{stem}{ext}"
dest = local_path_under(self.root, rel)
os.makedirs(os.path.dirname(dest), exist_ok=True)
with open(dest, "wb") as fh:
fh.write(data)
rel_url = rel.replace("\\", "/")
return {
"rel_path": rel_url,
"path": dest,
"url": f"{self.public_base}/{rel_url}",
"type": media_type,
}
def save_file(self, src_path, media_type="source", base_id="BASE", request_id=None):
if not src_path or not os.path.isfile(src_path):
return None
with open(src_path, "rb") as fh:
data = fh.read()
return self.save_bytes(
data,
filename=os.path.basename(src_path),
media_type=media_type,
base_id=base_id,
request_id=request_id,
)
def local_path_under(root, rel_path):
dest = os.path.normpath(os.path.join(root, *str(rel_path).replace("\\", "/").split("/")))
root_abs = os.path.abspath(root)
dest_abs = os.path.abspath(dest)
if dest_abs != root_abs and not dest_abs.startswith(root_abs + os.sep):
raise ValueError("invalid media path")
return dest_abs
def copy_if_local(src_path, media_type, base_id, request_id, store=None):
if not src_path:
return src_path
if is_http_url(src_path):
return src_path
store = store or MediaStore()
saved = store.save_file(src_path, media_type=media_type, base_id=base_id, request_id=request_id)
return saved["url"] if saved else src_path