fix: 移除 asset_durations 的 random_preview 条件限制 #1509

Merged
auto-approve-bot merged 4 commits from fix/enable-random-start-time-for-all-plans into develop 2026-08-26 18:46:38 +08:00
3 changed files with 120 additions and 3 deletions
@@ -118,9 +118,9 @@ class PlanGeneratorService:
# 4. 按 editing_mode 分配素材
if asset_ids:
# 如果是随机预览模式,获取素材时长信息
# 获取素材时长信息,用于随机起始时间
asset_durations = None
if random_preview and self._asset_repo:
if self._asset_repo:
asset_durations = self._fetch_asset_durations(asset_ids)
self._distribute_assets(
clips,
+1 -1
View File
@@ -258,7 +258,7 @@ def _calc_random_start_time(
# 如果尝试多次仍找不到,缩短时长使用素材末尾
# 找到最后一个已使用段之后的可用空间
last_used_end = 0.0
for seg_start, seg_end in used:
for _seg_start, seg_end in used:
last_used_end = max(last_used_end, seg_end)
if last_used_end < total_duration:
+117
View File
@@ -886,3 +886,120 @@ class TestTemplateConfigPropagation:
assert clip_cfg.get("speed_ratio") == 1.2
assert clip_cfg.get("name") == "开场"
assert clip_cfg.get("custom_field") == "value"
class TestAssetDurationsAlwaysFetched:
"""验证 asset_durations 不再受 random_preview 条件限制。
修复前:asset_durations 仅在 random_preview=True 时传入 distribute_assets
修复后:只要 _asset_repo 存在,就始终获取 asset_durations
"""
def _make_service_with_asset_repo(self):
"""创建带 mock asset_repo 的 PlanGeneratorService"""
from apps.api.app.services.plan_generator_service import PlanGeneratorService
plan_repo = StubEditPlanRepository()
clip_repo = StubEditPlanClipRepository()
# mock asset_repo: 返回带 duration 的素材
asset_repo = MagicMock()
def fake_get(asset_id):
mock_asset = MagicMock()
mock_asset.duration = 30.0 # 每个素材 30 秒
return mock_asset
asset_repo.get = MagicMock(side_effect=fake_get)
with (
patch(
"apps.api.app.services.plan_generator_service.SQLAlchemyEditPlanRepository",
return_value=plan_repo,
),
patch(
"apps.api.app.services.plan_generator_service.SQLAlchemyEditPlanClipRepository",
return_value=clip_repo,
),
):
db = MagicMock()
svc = PlanGeneratorService(db, asset_repo=asset_repo)
svc._plan_repo = plan_repo
svc._clip_repo = clip_repo
return svc, asset_repo
def test_asset_durations_fetched_without_random_preview(self):
"""random_preview=False 时也应获取 asset_durations"""
svc, asset_repo = self._make_service_with_asset_repo()
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
# patch distribute_assets 以捕获传入的参数
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1", "a2"],
random_preview=False, # 关键:非随机预览模式
)
# 验证 asset_durations 被传入(不是 None
mock_distribute.assert_called_once()
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is not None, "asset_durations should be fetched even when random_preview=False"
assert "a1" in asset_durations
assert "a2" in asset_durations
assert asset_durations["a1"] == 30.0
# 验证 asset_repo.get 被调用(说明 _fetch_asset_durations 执行了)
assert asset_repo.get.call_count >= 2
def test_asset_durations_fetched_with_random_preview(self):
"""random_preview=True 时仍正常获取 asset_durations(行为不变)"""
svc, asset_repo = self._make_service_with_asset_repo()
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
random_preview=True,
)
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is not None
assert "a1" in asset_durations
def test_no_asset_repo_means_no_durations(self):
"""_asset_repo 为 None 时 asset_durations 应为 None"""
svc, _, _ = _make_generator() # 默认不带 asset_repo
template = _make_template("one_take")
clip_configs = _make_clip_configs(
template_id=template.id,
specs=[{"clip_type": ClipType.MAIN, "order": 0, "min_duration": 3.0, "max_duration": 5.0}],
)
with patch("apps.api.app.services.plan_generator_service.distribute_assets") as mock_distribute:
svc.generate_from_template(
template=template,
clip_configs=clip_configs,
asset_ids=["a1"],
)
call_kwargs = mock_distribute.call_args
asset_durations = call_kwargs.kwargs.get("asset_durations", call_kwargs[1].get("asset_durations"))
assert asset_durations is None