diff --git a/apps/api/app/services/edit_plan_service.py b/apps/api/app/services/edit_plan_service.py index 80c9c3b48..df2c5d248 100755 --- a/apps/api/app/services/edit_plan_service.py +++ b/apps/api/app/services/edit_plan_service.py @@ -928,10 +928,7 @@ class EditPlanService: for idx in range(len(plan_ids)): # 每个变体用不同的 seed 选择节奏模板 variant_seed = rng.randint(0, 999999) - template = adapt_template_length( - RHYTHM_TEMPLATES[variant_seed % len(RHYTHM_TEMPLATES)], - clip_count - ) + template = adapt_template_length(RHYTHM_TEMPLATES[variant_seed % len(RHYTHM_TEMPLATES)], clip_count) rhythm_templates_for_variants.append(template) logger.info("变体 %d 节奏模板: plan=%s template=%s", idx, plan_ids[idx], template) diff --git a/packages/domain/voice_duration_planner.py b/packages/domain/voice_duration_planner.py index c05021c48..4909c889a 100644 --- a/packages/domain/voice_duration_planner.py +++ b/packages/domain/voice_duration_planner.py @@ -36,12 +36,12 @@ TOTAL_DURATION_TOLERANCE = 0.5 # 每种模板是权重序列,权重值代表相对时长比例 # 变体基于 variant_seed 随机选一个模板,实现不同变体时长结构不同 RHYTHM_TEMPLATES: list[list[int]] = [ - [1, 1, 1, 1, 1], # 平均(基准) - [2, 1, 3, 1, 2], # 中间长,两端短 - [1, 2, 1, 2, 1], # 偶数段长 - [3, 1, 1, 1, 3], # 两端长,中间短 - [1, 1, 3, 2, 1], # 后段渐长 - [2, 1, 1, 3, 1], # 前段较长 + 第4段最长 + [1, 1, 1, 1, 1], # 平均(基准) + [2, 1, 3, 1, 2], # 中间长,两端短 + [1, 2, 1, 2, 1], # 偶数段长 + [3, 1, 1, 1, 3], # 两端长,中间短 + [1, 1, 3, 2, 1], # 后段渐长 + [2, 1, 1, 3, 1], # 前段较长 + 第4段最长 ] @@ -86,6 +86,7 @@ def adapt_template_length(template: list[int], clip_count: int) -> list[int]: result.append(template[i % len(template)]) return result + #: 单段最小时长(秒):低于此值播放器/渲染链路易出问题 MIN_CLIP_DURATION = 1.0 @@ -164,12 +165,12 @@ def plan_clip_durations( # #1764:按节奏模板权重分配(无模板时全 1 = 平均分配) weights = rhythm_template if rhythm_template and len(rhythm_template) == clip_count else [1] * clip_count - + # 确保每个片段 >= MIN_CLIP_DURATION # 先按权重分配,再检查最小值 total_weight = sum(weights) raw_durations = [(w / total_weight) * gross for w in weights] - + # 保底检查:如果有片段 < MIN_CLIP_DURATION,提升它并从最长片段扣 result = [round(d, 3) for d in raw_durations] for _ in range(3): # 最多迭代 3 次 @@ -185,13 +186,13 @@ def plan_clip_durations( deficit = MIN_CLIP_DURATION - result[min_idx] result[min_idx] = MIN_CLIP_DURATION result[max_idx] = round(result[max_idx] - deficit, 3) - + # 末段吸收舍入误差 total_assigned = sum(result[:-1]) result[-1] = round(gross - total_assigned, 3) if result[-1] < MIN_CLIP_DURATION: result[-1] = MIN_CLIP_DURATION - + return result diff --git a/tests/unit/test_rhythm_templates.py b/tests/unit/test_rhythm_templates.py index 39b751d02..efbb6b325 100644 --- a/tests/unit/test_rhythm_templates.py +++ b/tests/unit/test_rhythm_templates.py @@ -105,10 +105,10 @@ class TestPlanClipDurationsWithRhythm: # 权重 [2, 1, 3, 1, 2]:第 3 段最长,第 2/4 段最短 template = [2, 1, 3, 1, 2] result = plan_clip_durations(clips, voice, rhythm_template=template) - + # 总时长 ≈ 配音时长 assert abs(sum(result) - voice) < 0.5 - + # 第 3 段应该最长 assert result[2] > result[1] assert result[2] > result[3] @@ -120,7 +120,7 @@ class TestPlanClipDurationsWithRhythm: # 极端权重:某段权重极低 template = [10, 1, 1, 1, 1] result = plan_clip_durations(clips, voice, rhythm_template=template) - + for d in result: assert d >= MIN_CLIP_DURATION @@ -131,14 +131,15 @@ class TestPlanClipDurationsWithRhythm: effects = [None, "xfade", "fade", "cut"] durations = [0.0, 0.5, 0.3, 0.0] template = [2, 1, 1, 2] - + result = plan_clip_durations( - clips, voice, + clips, + voice, transition_effects=effects, transition_durations=durations, rhythm_template=template, ) - + # 成片净时长 = Σ段长 - Σ转场重叠 ≈ 配音时长 output = total_output_duration(result, effects, durations) assert abs(output - voice) < 0.5 @@ -155,13 +156,13 @@ class TestPlanClipDurationsWithRhythm: voice = 25.0 clips = 5 structures = set() - + for tpl in RHYTHM_TEMPLATES: result = plan_clip_durations(clips, voice, rhythm_template=tpl) # 用 round 后的元组作为结构指纹 structure = tuple(round(d, 1) for d in result) structures.add(structure) - + # 至少 4 种不同结构 assert len(structures) >= 4 @@ -173,17 +174,17 @@ class TestIssue1764Acceptance: """批量 3 个变体,至少 2 组不同片段时长序列。""" voice = 20.0 clips = 5 - + # 模拟 3 个变体用不同 seed seeds = [100, 200, 300] structures = [] - + for seed in seeds: template = get_rhythm_template(seed) adapted = adapt_template_length(template, clips) durations = plan_clip_durations(clips, voice, rhythm_template=adapted) structures.append(tuple(round(d, 1) for d in durations)) - + # 至少 2 种不同结构 unique = len(set(structures)) assert unique >= 2, f"Expected >= 2 unique structures, got {unique}: {structures}"