feat(ai): 自由创作视频生成全量移植(/free-create)——Seedance 文/图生视频+按时长计费+人物素材库
后端: - free_video.py: 提交/轮询/收藏/软删全链路,复用 AITask(新增 is_deleted/is_favorited, migration 0022) - video_pricing.py: 按时长 token 计费(×1.10 buffer+clamp); video_errors.py 错误归一; media_probe.py 时长探测 - catalog/volcano: Seedance free-video 模型接入+seed(migration 0023) - 素材库: FreeAssetGroup/FreeAsset(火山 Assets API 引用登记, migration 0009)+ 上传/轮询/删除接口 - settings: FREE_VIDEO_MAX_CONCURRENT 团队并发闸(默认3); CELERY_TASK_ALWAYS_EAGER 本地联调开关(生产恒关) - 测试: test_free_video.py 新增; billing/products/projects tests 配套调整 前端: - /free-create 页面+components/free-create/ 全套(输入栏/@mention 素材引用/生成卡/视频详情弹窗/素材库弹窗) - api.ts/types.ts 扩展 free-video 与 free-assets 接口; 路由/侧边栏入口接入 bug/: 测试清单 (11)(12) 与截图归档 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,3 +178,61 @@ class AssetUsage(TimeStampedModel):
|
||||
usage_type = models.CharField(max_length=64)
|
||||
context = models.JSONField(default=dict, blank=True)
|
||||
|
||||
|
||||
class FreeAssetGroup(TeamOwnedModel):
|
||||
"""自由创作·人物素材组(一角色一组)。
|
||||
|
||||
本质是「火山 Assets API 引用登记表」:组内素材登记到火山素材库拿 remote_asset_id,
|
||||
生成时以 asset://{remote_asset_id} 引用(免重复上传 + 火山侧预审)。
|
||||
与 AssetReviewGroup(一团队一审核组)职责不同,与资产库 Asset(TOS 文件资产)也不同,独立建表。
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=128)
|
||||
description = models.TextField(blank=True)
|
||||
remote_group_id = models.CharField(max_length=128)
|
||||
thumbnail_url = models.URLField(blank=True)
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["team", "-created_at"]),
|
||||
]
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"free-group:{self.name}"
|
||||
|
||||
|
||||
class FreeAsset(TimeStampedModel):
|
||||
"""自由创作·素材组内单素材(火山 Assets API 登记项)。"""
|
||||
|
||||
class Type(models.TextChoices):
|
||||
IMAGE = "Image", "Image"
|
||||
VIDEO = "Video", "Video"
|
||||
AUDIO = "Audio", "Audio"
|
||||
|
||||
class Status(models.TextChoices):
|
||||
PROCESSING = "processing", "Processing" # 火山侧处理/审核中
|
||||
ACTIVE = "active", "Active" # 可用,可 asset:// 引用
|
||||
FAILED = "failed", "Failed" # 审核/处理失败
|
||||
|
||||
group = models.ForeignKey(FreeAssetGroup, on_delete=models.CASCADE, related_name="assets")
|
||||
remote_asset_id = models.CharField(max_length=128)
|
||||
name = models.CharField(max_length=128, blank=True)
|
||||
url = models.URLField(max_length=1024, blank=True) # TOS 公读直链(火山从这里拉源文件)
|
||||
asset_type = models.CharField(max_length=16, choices=Type.choices, default=Type.IMAGE)
|
||||
thumbnail_url = models.URLField(max_length=1024, blank=True)
|
||||
duration = models.FloatField(null=True, blank=True) # 视频/音频时长(秒)
|
||||
status = models.CharField(max_length=16, choices=Status.choices, default=Status.PROCESSING)
|
||||
error_message = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=["group", "-created_at"]),
|
||||
models.Index(fields=["remote_asset_id"]),
|
||||
]
|
||||
ordering = ["-created_at"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"free-asset:{self.name or self.remote_asset_id}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user