Files
root 0c511f3451 1、增加调用 AI 视频生成能力和虚拟素材库管理的对外api
2、增加后台apikkey管理
3、增加apikey单独的模型定价
4、增加apikey调用情况
5、完善所有数据的注释增加
2026-08-06 13:13:28 +08:00

149 lines
4.2 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.
"""API v3 文件下载服务。
下载用户提供的图片/视频/音频到本地存储。
"""
import base64
import logging
import os
import re
import uuid
from datetime import datetime
from urllib.parse import urlparse
import httpx
from app.config import settings
logger = logging.getLogger("videogen")
def _get_date_str() -> str:
"""获取当前日期字符串。"""
return datetime.now().strftime("%Y%m%d")
def _get_uploads_dir() -> str:
"""获取上传文件存储目录。"""
upload_dir = os.path.join(os.path.dirname(settings.STORAGE_LOCAL_PATH), "uploads", "api")
os.makedirs(upload_dir, exist_ok=True)
return upload_dir
async def download_file_from_url(url: str, sub_dir: str = "") -> str:
"""从 URL 下载文件到本地。
Args:
url: 文件 URL
sub_dir: 子目录(如 images/videos/audios
Returns:
相对路径: /uploads/api/{sub_dir}/{date}/{filename}
"""
upload_dir = _get_uploads_dir()
date_str = _get_date_str()
# 创建目标目录
dest_dir = os.path.join(upload_dir, sub_dir, date_str)
os.makedirs(dest_dir, exist_ok=True)
# 从 URL 提取扩展名
parsed = urlparse(url)
path = parsed.path
ext = os.path.splitext(path)[1].lower()
if not ext or len(ext) > 10:
ext = ".bin" # 默认扩展名
# 生成唯一文件名
filename = f"{uuid.uuid4().hex}{ext}"
dest_path = os.path.join(dest_dir, filename)
# 下载文件
async with httpx.AsyncClient(timeout=300, follow_redirects=True) as client:
async with client.stream("GET", url) as response:
response.raise_for_status()
with open(dest_path, "wb") as f:
async for chunk in response.aiter_bytes(chunk_size=8192):
f.write(chunk)
# 返回相对路径
rel_path = f"/uploads/api/{sub_dir}/{date_str}/{filename}"
logger.info("Downloaded file: %s -> %s", url[:80], rel_path)
return rel_path
def save_base64_file(data: str, sub_dir: str = "") -> str:
"""保存 Base64 编码的文件到本地。
Args:
data: Base64 编码的数据(可包含 data:...;base64, 前缀)
sub_dir: 子目录
Returns:
相对路径: /uploads/api/{sub_dir}/{date}/{filename}
"""
upload_dir = _get_uploads_dir()
date_str = _get_date_str()
# 创建目标目录
dest_dir = os.path.join(upload_dir, sub_dir, date_str)
os.makedirs(dest_dir, exist_ok=True)
# 解析 Base64 数据
if "," in data:
header, b64_data = data.split(",", 1)
# 从 header 提取 MIME 类型
mime_match = re.search(r"data:([^;]+)", header)
mime_type = mime_match.group(1) if mime_match else "application/octet-stream"
# 根据 MIME 类型确定扩展名
ext_map = {
"image/jpeg": ".jpg",
"image/png": ".png",
"image/webp": ".webp",
"image/gif": ".gif",
"video/mp4": ".mp4",
"video/webm": ".webm",
"audio/mpeg": ".mp3",
"audio/wav": ".wav",
"audio/ogg": ".ogg",
}
ext = ext_map.get(mime_type, ".bin")
else:
b64_data = data
ext = ".bin"
# 解码并保存
try:
file_data = base64.b64decode(b64_data)
except Exception as exc:
raise ValueError(f"Base64 解码失败: {exc}")
filename = f"{uuid.uuid4().hex}{ext}"
dest_path = os.path.join(dest_dir, filename)
with open(dest_path, "wb") as f:
f.write(file_data)
rel_path = f"/uploads/api/{sub_dir}/{date_str}/{filename}"
logger.info("Saved base64 file: %s (%d bytes)", rel_path, len(file_data))
return rel_path
async def process_media_url(url: str, media_type: str) -> str:
"""处理媒体 URL:下载到本地或保存 Base64。
Args:
url: URL 或 Base64 数据
media_type: image / video / audio
Returns:
相对路径: /uploads/api/{type}/{date}/{filename}
"""
sub_dir = {"image": "images", "video": "videos", "audio": "audios"}.get(media_type, "files")
# 判断是 Base64 还是 URL
if url.startswith("data:"):
return save_base64_file(url, sub_dir)
else:
return await download_file_from_url(url, sub_dir)