"""Mock 宿主内存剪辑模型(tests/mock_host 可 import 包)。 形状对齐计划文档 §3.1 / §3.3: - 不透明 id(``EntityId``),删除后失效(再访问抛 ``EntityNotFound``); - clip 携带 footage 引用、in/out(素材内入出点)、media_in、enabled; - clip 上挂 effects(node id + 参数),参数带 min/max/choices; - sequence 含 fps 有理数、video/audio 轨道、markers、播放状态; - snapshot = 对 ``MockState`` 的深拷贝(带时间戳与会话 id)。 """ from __future__ import annotations from dataclasses import dataclass, field from typing import Any from quercus_core.types import EntityId, Rational @dataclass class MockKeyframe: """效果参数关键帧(时间 + 值)。""" time: Rational value: float @dataclass class MockEffect: """片段上的效果节点。params 值需满足 effects.py 注册表的 min/max/choices。""" node_id: EntityId effect_type: str preset: str = "" params: dict[str, Any] = field(default_factory=dict) keyframes: dict[str, list[MockKeyframe]] = field(default_factory=dict) @dataclass class MockTransition: """片段转场(mock 仅记录,不参与像素渲染)。""" transition_type: str duration: Rational = Rational(0) @dataclass class MockClip: """时间线片段。media_in/media_out 为素材内入出点(源区间)。""" id: EntityId name: str footage_id: str track_index: int media_in: Rational media_out: Rational start: Rational # 时间线上位置 enabled: bool = True effects: list[MockEffect] = field(default_factory=list) transition: MockTransition | None = None @property def duration(self) -> Rational: return self.media_out - self.media_in @dataclass class MockTrack: """时间线轨道。""" index: int kind: str = "video" # video | audio | subtitle clips: list[MockClip] = field(default_factory=list) @dataclass class MockMarker: """时间线标记(可携带 AI 元数据 custom_data)。""" id: EntityId time: Rational label: str = "" color: str = "yellow" custom_data: dict = field(default_factory=dict) @dataclass class MockSequence: """时间线序列。fps 为有理数(对齐决策 D5)。""" id: EntityId name: str fps: Rational tracks: list[MockTrack] = field(default_factory=list) markers: list[MockMarker] = field(default_factory=list) playhead: Rational = Rational(0) playing: bool = False @dataclass class MockFootage: """媒体池素材(id 不透明,内容由 frames.py 按 id 确定性渲染)。""" id: EntityId name: str path: str duration: Rational width: int = 1920 height: int = 1080 fps: Rational = Rational(25, 1) @dataclass class MockProject: """工程:name / modified(计划文档 §3.3 的工程状态)。""" name: str = "未命名工程" path: str = "" modified: bool = False @dataclass class MockState: """宿主全部状态;快照 = 对它的深拷贝。""" project: MockProject = field(default_factory=MockProject) footage: dict[str, MockFootage] = field(default_factory=dict) sequences: dict[str, MockSequence] = field(default_factory=dict) current_seq_id: EntityId | None = None @dataclass class MockSnapshot: """一份时间线快照(带时间戳与会话 id,有上限见 Limits.max_snapshot_count)。""" id: str session_id: str created_at: str label: str action_count: int state: Any # deepcopy(MockState)