186 lines
9.9 KiB
Python
186 lines
9.9 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||
|
||
from app.enums.upload_resource import (
|
||
UploadResourceBindStatusEnum,
|
||
UploadResourceDeletePolicyEnum,
|
||
UploadResourceModuleEnum,
|
||
UploadResourceTypeEnum,
|
||
)
|
||
from app.schemas.common import NaiveDatetimeOptional
|
||
|
||
|
||
class UploadResourceMediaReferenceOut(BaseModel):
|
||
"""AI 创作可直接复用的上传素材参考结构。"""
|
||
|
||
name: str | None = Field(None, description="参考素材名称,前端展示用")
|
||
type: Literal["image", "video", "audio"] = Field(..., description="参考素材类型:image=图片,video=视频,audio=音频")
|
||
url: str = Field(..., description="参考素材 URL,创建 AI 创作任务时传入 media_references[].url")
|
||
label: str | None = Field("", description="前端展示标签,默认空,由 AI 创作页重新计算")
|
||
duration: float | None = Field(None, ge=0, description="素材时长秒数,type=video/audio 时必须有有效值")
|
||
source: Literal["upload_resource"] = Field("upload_resource", description="参考素材来源固定为 upload_resource")
|
||
upload_resource_id: str = Field(..., description="UploadResource 本地资源账本 ID")
|
||
|
||
|
||
class UploadResourceHistoryItemOut(BaseModel):
|
||
"""上传资源历史素材项。"""
|
||
|
||
model_config = ConfigDict(
|
||
json_schema_extra={
|
||
"example": {
|
||
"id": "0019e0a448a23114888",
|
||
"source_type": "upload_resource",
|
||
"history_source": "upload_resource",
|
||
"history_source_label": "历史上传素材",
|
||
"module": "common",
|
||
"module_label": "普通上传",
|
||
"resource_type": "video",
|
||
"resource_type_label": "视频",
|
||
"resource_url": "/uploads/videos/2026/07/08/video_ref_xxx.mp4",
|
||
"display_url": "/uploads/videos/2026/07/08/video_ref_xxx.mp4",
|
||
"preview_url": "/uploads/videos/2026/07/08/video_ref_xxx.mp4",
|
||
"video_url": "/uploads/videos/2026/07/08/video_ref_xxx.mp4",
|
||
"duration_seconds": 5.2,
|
||
"bind_status": "pending",
|
||
"delete_policy": "user_deletable",
|
||
"deletable": True,
|
||
"media_reference": {
|
||
"name": "video_ref_xxx.mp4",
|
||
"type": "video",
|
||
"url": "/uploads/videos/2026/07/08/video_ref_xxx.mp4",
|
||
"duration": 5.2,
|
||
"source": "upload_resource",
|
||
"upload_resource_id": "0019e0a448a23114888",
|
||
},
|
||
"created_at": "2026-07-08T13:20:00",
|
||
}
|
||
}
|
||
)
|
||
|
||
id: str = Field(..., description="UploadResource.id")
|
||
source_type: Literal["upload_resource"] = Field("upload_resource", description="前端判定来源固定值")
|
||
history_source: Literal["upload_resource"] = Field("upload_resource", description="素材云历史来源固定为 upload_resource")
|
||
history_source_label: str = Field("历史上传素材", description="素材云历史来源中文名称")
|
||
|
||
module: str = Field(..., description="上传资源所属模块:common=普通上传,hot_opening_replicate=爆款开头复刻,shot_replicate=拆镜复刻")
|
||
module_label: str = Field(..., description="上传资源所属模块中文名称")
|
||
resource_type: Literal["image", "video", "audio"] = Field(..., description="资源类型:image=图片,video=视频,audio=音频")
|
||
resource_type_label: str = Field(..., description="资源类型中文名称")
|
||
|
||
resource_url: str = Field(..., description="原始资源 URL,可传给 AI 创作 media_references[].url")
|
||
display_url: str = Field(..., description="前端展示 URL。当前普通上传资源通常与 resource_url 一致")
|
||
preview_url: str = Field(..., description="前端预览 URL。当前普通上传资源通常与 display_url 一致")
|
||
image_url: str | None = Field(None, description="图片资源 URL;resource_type=image 时有值")
|
||
video_url: str | None = Field(None, description="视频资源 URL;resource_type=video 时有值")
|
||
audio_url: str | None = Field(None, description="音频资源 URL;resource_type=audio 时有值")
|
||
|
||
file_name: str | None = Field(None, description="文件名")
|
||
file_ext: str | None = Field(None, description="文件扩展名")
|
||
mime_type: str | None = Field(None, description="MIME 类型")
|
||
file_size_bytes: int = Field(0, description="文件大小,单位字节")
|
||
duration_seconds: float | None = Field(None, description="视频/音频时长秒数")
|
||
width: int | None = Field(None, description="图片或视频宽度,未识别时为空")
|
||
height: int | None = Field(None, description="图片或视频高度,未识别时为空")
|
||
|
||
bind_status: str = Field(..., description="绑定状态。历史素材接口只返回 pending=未绑定")
|
||
delete_policy: str = Field(..., description="删除策略。历史素材接口只返回 user_deletable=用户可删除")
|
||
deletable: bool = Field(True, description="是否允许当前用户删除。历史素材接口只返回 true")
|
||
media_reference: UploadResourceMediaReferenceOut = Field(..., description="AI 创作可直接复用的参考素材对象")
|
||
|
||
created_at: NaiveDatetimeOptional = Field(None, description="上传时间")
|
||
updated_at: NaiveDatetimeOptional = Field(None, description="更新时间")
|
||
|
||
|
||
class UploadResourceHistoryDayGroupOut(BaseModel):
|
||
"""上传资源历史按天分组响应项。"""
|
||
|
||
generated_date: str = Field(..., description="上传日期,格式 YYYY-MM-DD。字段名沿用生成历史,方便前端复用分组逻辑")
|
||
total: int = Field(..., description="当前日期下可展示/可复用/可删除的上传素材总数")
|
||
page: int = Field(1, description="当前日期下 items 的页码,分组接口固定为 1")
|
||
items: list[UploadResourceHistoryItemOut] = Field(default_factory=list, description="当前日期倒序前若干条上传素材")
|
||
|
||
|
||
class UploadResourceHistoryGroupedOut(BaseModel):
|
||
"""上传资源历史日期分组列表响应体。"""
|
||
|
||
total_days: int = Field(..., description="符合条件的上传素材日期分组总数")
|
||
page: int = Field(..., description="日期分组页码,从 1 开始")
|
||
page_size: int = Field(..., description="日期分组每页数量,最大 10")
|
||
groups: list[UploadResourceHistoryDayGroupOut] = Field(default_factory=list, description="上传日期倒序分组列表")
|
||
|
||
|
||
class UploadResourceHistoryDayItemsOut(BaseModel):
|
||
"""指定日期下上传资源历史分页响应体。"""
|
||
|
||
generated_date: str = Field(..., description="当前查询上传日期,格式 YYYY-MM-DD")
|
||
total: int = Field(..., description="当前日期下符合条件的上传素材总数")
|
||
page: int = Field(..., description="当前日期下分页页码,从 1 开始")
|
||
page_size: int = Field(..., description="当前日期下每页返回数量,最大 100")
|
||
items: list[UploadResourceHistoryItemOut] = Field(default_factory=list, description="当前日期下上传素材列表,按上传时间倒序排列")
|
||
|
||
|
||
class UploadResourceHistoryBatchDeleteRequest(BaseModel):
|
||
"""批量删除上传历史素材请求体。"""
|
||
|
||
resource_ids: list[str] = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=30,
|
||
description="UploadResource.id 列表。一次最多 30 条,不允许重复",
|
||
examples=[["0019e0a448a23114888", "0019e0a448a23114889"]],
|
||
)
|
||
|
||
@field_validator("resource_ids")
|
||
@classmethod
|
||
def validate_resource_ids(cls, values: list[str]) -> list[str]:
|
||
cleaned = [str(value).strip() for value in values if str(value or "").strip()]
|
||
if not cleaned:
|
||
raise ValueError("resource_ids 不能为空")
|
||
if len(cleaned) > 30:
|
||
raise ValueError("单次最多删除 30 条上传素材")
|
||
if len(cleaned) != len(set(cleaned)):
|
||
raise ValueError("resource_ids 不允许重复")
|
||
return cleaned
|
||
|
||
|
||
class UploadResourceCleanupOut(BaseModel):
|
||
"""真实文件清理结果。"""
|
||
|
||
matched: int = Field(0, description="匹配到待清理资源数量")
|
||
deleted: int = Field(0, description="真实文件删除成功数量")
|
||
missing: int = Field(0, description="真实文件已不存在数量")
|
||
failed: int = Field(0, description="真实文件删除失败数量")
|
||
legacy_deleted: int = Field(0, description="兼容历史路径删除成功数量")
|
||
legacy_missing: int = Field(0, description="兼容历史路径不存在数量")
|
||
legacy_failed: int = Field(0, description="兼容历史路径删除失败数量")
|
||
|
||
|
||
class UploadResourceHistoryBatchDeleteOut(BaseModel):
|
||
"""批量删除上传历史素材响应体。"""
|
||
|
||
message: str = Field("删除成功", description="操作结果消息")
|
||
requested_count: int = Field(..., description="请求删除数量")
|
||
deleted_count: int = Field(..., description="成功软删数量")
|
||
requested_ids: list[str] = Field(default_factory=list, description="请求删除的 UploadResource.id 列表")
|
||
deleted_ids: list[str] = Field(default_factory=list, description="已软删的 UploadResource.id 列表")
|
||
released_size_bytes: int = Field(0, description="已释放的上传容量字节数")
|
||
cleanup: UploadResourceCleanupOut = Field(default_factory=UploadResourceCleanupOut, description="commit 成功后的真实文件清理结果")
|
||
|
||
|
||
UPLOAD_RESOURCE_HISTORY_ALLOWED_RESOURCE_TYPES = {
|
||
UploadResourceTypeEnum.IMAGE.value,
|
||
UploadResourceTypeEnum.VIDEO.value,
|
||
UploadResourceTypeEnum.AUDIO.value,
|
||
}
|
||
|
||
UPLOAD_RESOURCE_HISTORY_ALLOWED_BIND_STATUS = UploadResourceBindStatusEnum.PENDING.value
|
||
UPLOAD_RESOURCE_HISTORY_ALLOWED_DELETE_POLICY = UploadResourceDeletePolicyEnum.USER_DELETABLE.value
|
||
UPLOAD_RESOURCE_HISTORY_ALLOWED_MODULES = {
|
||
UploadResourceModuleEnum.COMMON.value,
|
||
UploadResourceModuleEnum.HOT_OPENING_REPLICATE.value,
|
||
UploadResourceModuleEnum.SHOT_REPLICATE.value,
|
||
}
|