8935196fcd
Deploy / Staging E2E Tests (push) Has been skipped
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 138h4m33s
CI/CD Pipeline / Frontend Lint (push) Failing after 138h4m39s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 138h4m39s
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""EditPlanClip domain entity for Phase 8 模板编排引擎.
|
||
|
||
剪辑计划中的具体片段实例,关联 EditPlan 和 TemplateClipConfig,
|
||
包含实际素材、实际文案、排序和时长等信息。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from dataclasses import dataclass, field
|
||
from datetime import datetime, timezone
|
||
|
||
if sys.version_info >= (3, 11):
|
||
from enum import StrEnum
|
||
else:
|
||
from enum import Enum
|
||
|
||
class StrEnum(str, Enum):
|
||
pass
|
||
|
||
|
||
from typing import Any
|
||
from uuid import uuid4
|
||
|
||
|
||
class EditPlanClipStatus(StrEnum):
|
||
"""片段状态"""
|
||
|
||
PENDING = "pending" # 待处理(素材未就绪)
|
||
READY = "ready" # 就绪(素材已就绪,可渲染)
|
||
RENDERED = "rendered" # 已渲染
|
||
FAILED = "failed" # 渲染失败
|
||
|
||
|
||
@dataclass(slots=True)
|
||
class EditPlanClip:
|
||
"""剪辑计划片段
|
||
|
||
表示 EditPlan 中的一个具体片段实例,包含实际素材、文案和渲染状态。
|
||
可选地关联 TemplateClipConfig 以继承模板规则。
|
||
"""
|
||
|
||
id: str
|
||
plan_id: str
|
||
clip_type: str
|
||
order: int
|
||
template_clip_config_id: str = ""
|
||
asset_id: str = ""
|
||
text_content: str = ""
|
||
start_time: float = 0.0
|
||
duration: float = 0.0
|
||
transition_effect: str = "cut"
|
||
status: EditPlanClipStatus = EditPlanClipStatus.PENDING
|
||
config: dict[str, Any] = field(default_factory=dict)
|
||
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||
updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||
|
||
@classmethod
|
||
def create(
|
||
cls,
|
||
plan_id: str,
|
||
clip_type: str,
|
||
order: int,
|
||
*,
|
||
template_clip_config_id: str = "",
|
||
asset_id: str = "",
|
||
text_content: str = "",
|
||
start_time: float = 0.0,
|
||
duration: float = 0.0,
|
||
transition_effect: str = "cut",
|
||
config: dict[str, Any] | None = None,
|
||
) -> EditPlanClip:
|
||
"""创建剪辑计划片段"""
|
||
if not plan_id.strip():
|
||
raise ValueError("plan_id 不能为空")
|
||
if not clip_type.strip():
|
||
raise ValueError("clip_type 不能为空")
|
||
if start_time < 0:
|
||
raise ValueError("start_time 不能为负数")
|
||
if duration < 0:
|
||
raise ValueError("duration 不能为负数")
|
||
|
||
return cls(
|
||
id=uuid4().hex,
|
||
plan_id=plan_id.strip(),
|
||
clip_type=clip_type.strip(),
|
||
order=order,
|
||
template_clip_config_id=template_clip_config_id.strip() if template_clip_config_id else "",
|
||
asset_id=asset_id.strip() if asset_id else "",
|
||
text_content=text_content.strip(),
|
||
start_time=start_time,
|
||
duration=duration,
|
||
transition_effect=transition_effect.strip() or "cut",
|
||
status=EditPlanClipStatus.PENDING,
|
||
config=config or {},
|
||
)
|
||
|
||
def assign_asset(self, asset_id: str) -> None:
|
||
"""分配素材"""
|
||
if not asset_id.strip():
|
||
raise ValueError("asset_id 不能为空")
|
||
self.asset_id = asset_id.strip()
|
||
self.updated_at = datetime.now(timezone.utc)
|
||
|
||
def mark_ready(self) -> None:
|
||
"""标记为就绪"""
|
||
if self.status != EditPlanClipStatus.PENDING:
|
||
raise ValueError(f"只有 pending 状态的片段可以标记就绪,当前状态: {self.status}")
|
||
self.status = EditPlanClipStatus.READY
|
||
self.updated_at = datetime.now(timezone.utc)
|
||
|
||
def mark_rendered(self) -> None:
|
||
"""标记为已渲染"""
|
||
if self.status != EditPlanClipStatus.READY:
|
||
raise ValueError(f"只有 ready 状态的片段可以标记已渲染,当前状态: {self.status}")
|
||
self.status = EditPlanClipStatus.RENDERED
|
||
self.updated_at = datetime.now(timezone.utc)
|
||
|
||
def mark_failed(self) -> None:
|
||
"""标记为失败"""
|
||
if self.status != EditPlanClipStatus.READY:
|
||
raise ValueError(f"只有 ready 状态的片段可以标记失败,当前状态: {self.status}")
|
||
self.status = EditPlanClipStatus.FAILED
|
||
self.updated_at = datetime.now(timezone.utc)
|
||
|
||
@property
|
||
def end_time(self) -> float:
|
||
"""片段结束时间"""
|
||
return self.start_time + self.duration
|
||
|
||
@property
|
||
def has_asset(self) -> bool:
|
||
"""是否已分配素材"""
|
||
return bool(self.asset_id)
|