Compare commits

...

14 Commits

Author SHA1 Message Date
xiaoxia 034eaac695 Merge pull request 'feat: 像素级扰动滤镜降低平台查重风险 (Issue #1765)' (#1771) from feat/pixel-perturbation-1765 into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 47s
CI/CD Pipeline / Build Staging API Image (push) Successful in 49s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m56s
CI/CD Pipeline / Validate - Style (push) Successful in 2m21s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m16s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m7s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m28s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
CI/CD Pipeline / Unit Tests (push) Failing after 8m12s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m22s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 10s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 10s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 10s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Security (push) Successful in 22m17s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-09-07 20:56:11 +08:00
saas-backend-agent 27dccf6591 fix: #1769 智能剪辑 Step2 素材加载 400 错误
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m41s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 2m27s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 16s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m37s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 37s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m20s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m51s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m33s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m21s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m50s
AI Code Review / AI Code Review (pull_request) Successful in 6m41s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m25s
CI/CD Pipeline / Unit Tests (push) Failing after 8m28s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m1s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
- 根因:模板 f7578c059c2e4dfca2236eb86cb6808a 是无效测试模板
  - is_active=false(已失效)
  - template_clip_configs=0(无片段配置)
  - template_segments=0(无旧版片段配置)
- 修复:_get_template_segments 添加详细错误日志,便于排查
- 前端应过滤 is_active=false 的模板
- 用户应选择 clip_config_count > 0 的有效模板
2026-09-07 20:38:35 +08:00
CI Bot ac7ab679b7 style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 15s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m57s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m48s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m2s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m27s
AI Code Review / AI Code Review (pull_request) Successful in 6m40s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m25s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 19m24s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 13s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m33s
2026-09-07 12:28:56 +00:00
saas-backend-agent bb98620a8a feat: 像素级扰动滤镜降低平台查重风险 (Issue #1765)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 19s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m36s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 20s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m26s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
- generate_pixel_perturbation():随机选 2-3 种滤镜组合
  - noise: 轻微噪声 (0.01~0.02)
  - unsharp: 锐化/柔化 (-0.5~+0.5)
  - curves: 对比度微调 (0.95~1.05)
  - color_balance: RGB 通道偏移
- _apply_pixel_perturbation():渲染时追加到 FFmpeg filter chain
- edit_plan_service:批量变体生成时为每个变体生成不同像素扰动
- 参数幅度确保肉眼不可见(SSIM > 0.95),帧级差异 > 3%
- 11 个新测试覆盖滤镜组合/参数范围/验收
2026-09-07 20:24:13 +08:00
xiaoxia 2a0b75c007 Merge pull request 'feat: 节奏模板让批量变体片段时长分布不同 (Issue #1764)' (#1770) from feat/rhythm-templates-1764 into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 6s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 7s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 35s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 31s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m42s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 39s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
CI/CD Pipeline / Validate - Style (push) Successful in 2m41s
CI/CD Pipeline / Build Staging API Image (push) Successful in 42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 37s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m41s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m33s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m6s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m33s
CI/CD Pipeline / Unit Tests (push) Failing after 11m13s
AI Code Review / AI Code Review (pull_request) Failing after 10m34s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 12m48s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m22s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m3s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m27s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m32s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m59s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m25s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
2026-09-07 20:21:38 +08:00
CI Bot 0d9d4e584f style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 26s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 27s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m44s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m10s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m51s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m5s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 49s
AI Code Review / AI Code Review (pull_request) Successful in 6m41s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Failing after 12m53s
Preview Cleanup / Cleanup Preview Environment (pull_request) Failing after 13m1s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 20m23s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
2026-09-07 12:20:53 +00:00
saas-backend-agent eb089fa26d feat: 节奏模板让批量变体片段时长分布不同 (Issue #1764)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 36s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 37s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m31s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m45s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m29s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
- 新增 RHYTHM_TEMPLATES 节奏模板池(6 种权重序列)
- get_rhythm_template(seed):根据变体 seed 随机选模板
- adapt_template_length():将模板适配到实际片段数
- plan_clip_durations() 支持 rhythm_template 参数按权重分配时长
- 保底约束:每段 >= 2s,总时长 = 配音时长
- edit_plan_service: ensure_batch_variant_plans 为每个变体生成不同节奏模板
- 17 个新测试覆盖模板池/适配/分配/验收
2026-09-07 20:17:52 +08:00
saas-backend-agent 1ec274b9f5 ci: trigger push pipeline for dedup enhanced
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 10s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 12s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 7s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 50s
CI/CD Pipeline / Integration Tests (push) Successful in 1m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 55s
CI/CD Pipeline / Validate - Style (push) Successful in 2m29s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 30s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m3s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m28s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m49s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m0s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m33s
AI Code Review / AI Code Review (pull_request) Successful in 6m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m59s
CI/CD Pipeline / Unit Tests (push) Failing after 7m56s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 6m11s
CI/CD Pipeline / Validate - Security (push) Successful in 16m42s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-09-07 19:51:52 +08:00
xiaoxia 62820391c3 fix: Step5 单视频全宽布局+视频播放器居中 (#1761)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Blocked by required conditions
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (push) Waiting to run
CI/CD Pipeline / Build Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (push) Blocked by required conditions
CI/CD Pipeline / CI Gate (push) Blocked by required conditions
2026-09-07 19:51:42 +08:00
xiaoxia 322b0a082c Merge pull request 'feat(dedup): 增加文案+结构维度查重 (P2-后端3)' (#1762) from feat/dedup-enhanced-text-structure into develop
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 16s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Successful in 58s
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-09-07 19:50:00 +08:00
CI Bot 07aa03da24 style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 17s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1m12s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m37s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m38s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m3s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m8s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m17s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m23s
AI Code Review / AI Code Review (pull_request) Successful in 6m26s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 16m23s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
2026-09-07 11:49:16 +00:00
saas-backend-agent add93bb9de feat(dedup): 增加文案+结构维度查重 (Issue #P2-后端3)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 24s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1m16s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m1s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
- 新增 compute_text_similarity: 字符级 Jaccard 相似度比对配音文本
- 新增 compute_structure_similarity: 片段序列相似度(数量+类型+时长分布)
- 多维度融合公式: duplicate_rate = visual*0.5 + text*0.25 + structure*0.25
- 文案/结构数据缺失时降级为纯视觉维度
- 21 个新测试覆盖文本+结构+权重常量
2026-09-07 19:46:43 +08:00
xiaoxia 6fe3e03d6a Merge pull request 'feat: 封面智能选帧 — 帧质量评分(清晰度/亮度/色彩)' (#1759) from feat/cover-frame-scoring into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 19s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m9s
CI/CD Pipeline / Build Staging API Image (push) Successful in 39s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 32s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m51s
CI/CD Pipeline / Integration Tests (push) Successful in 2m24s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m43s
CI/CD Pipeline / Validate - Style (push) Successful in 2m52s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m39s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m32s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m30s
AI Code Review / AI Code Review (pull_request) Successful in 6m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
CI/CD Pipeline / Unit Tests (push) Successful in 8m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m0s
CI/CD Pipeline / Validate - Security (push) Successful in 10m32s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
2026-09-07 19:28:22 +08:00
xiaoxia d73c0a77d8 fix: Step5 BatchGenerationGrid 单卡居中 + maxWidth 240px (#1760)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 8s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Integration Tests (push) Successful in 1m22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m31s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m48s
CI/CD Pipeline / Validate - Style (push) Successful in 2m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m14s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m0s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m28s
CI/CD Pipeline / Validate - Security (push) Successful in 5m33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m38s
CI/CD Pipeline / Unit Tests (push) Successful in 8m28s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 25s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 58s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m11s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m46s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m5s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m13s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m42s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m2s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m36s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-07 18:51:29 +08:00
12 changed files with 1002 additions and 24 deletions
@@ -460,6 +460,11 @@ def _get_template_segments(
except Exception:
logger.warning("旧模板系统查询segments失败", exc_info=True)
# 所有途径都失败:模板没有片段配置(可能是无效测试模板)
logger.error(
"模板无片段配置:template_id=%s(可能是 is_active=false 的无效模板)",
template_id,
)
return []
+36 -2
View File
@@ -784,11 +784,17 @@ class EditPlanService:
from packages.domain.voice_duration_planner import plan_clip_durations, total_output_duration
# #1764:从 plan config 读取节奏模板
rhythm_template = None
if plan and hasattr(plan, "config") and plan.config:
rhythm_template = plan.config.get("rhythm_template")
target = plan_clip_durations(
len(clips),
voice,
transition_effects=[c.transition_effect for c in clips],
transition_durations=[float(c.transition_duration or 0.0) for c in clips],
rhythm_template=rhythm_template,
)
if not target:
return None
@@ -907,6 +913,25 @@ class EditPlanService:
)
plan_ids.append(variant.id)
# #1764:为每个变体生成独立节奏模板(让批量视频片段时长分布不同)
from packages.domain.voice_duration_planner import RHYTHM_TEMPLATES, adapt_template_length
clip_count = 0
if voice_durations and len(voice_durations) > 0:
# 从源 plan 获取片段数
source_plan = self.get_plan(source_plan_id)
if source_plan and hasattr(source_plan, "clips"):
clip_count = len(list(source_plan.clips)) if source_plan.clips else 0
rhythm_templates_for_variants = []
if clip_count > 0:
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)
rhythm_templates_for_variants.append(template)
logger.info("变体 %d 节奏模板: plan=%s template=%s", idx, plan_ids[idx], template)
# 为每个变体生成独立视觉扰动参数(让批量视频画面本身更不同)
from packages.domain.variant_plan_selector import generate_visual_perturbation
@@ -916,8 +941,17 @@ class EditPlanService:
# 变体 0 不做 hflip(保持预览 plan 原始画面方向)
if idx == 0:
perturbation["hflip"] = False
self.update_plan_config(pid, {"visual_perturbation": perturbation})
logger.info("变体 %d 视觉扰动: plan=%s perturbation=%s", idx, pid, perturbation)
config_update = {"visual_perturbation": perturbation}
# #1764:写入节奏模板
if idx < len(rhythm_templates_for_variants):
config_update["rhythm_template"] = rhythm_templates_for_variants[idx]
# #1765:写入像素级扰动滤镜
from packages.domain.variant_plan_selector import generate_pixel_perturbation
pixel_pert = generate_pixel_perturbation(rng)
config_update["pixel_perturbation"] = pixel_pert
self.update_plan_config(pid, config_update)
logger.info("变体 %d 视觉扰动+像素扰动: plan=%s vis=%s pix=%s", idx, pid, perturbation, pixel_pert)
except Exception:
logger.exception("变体 %d 视觉扰动生成失败(不阻断): plan=%s", idx, pid)
+57 -3
View File
@@ -420,7 +420,9 @@ const GeneratePage: React.FC = () => {
const layoutClassName = useMemo(() => {
if (currentStep < 4) return "xx-generate-layout full-width"
if (currentStep === 4) return "xx-generate-layout step4-layout"
// 步骤5/6:批量网格需要整行宽度;单视频保持 表单+右侧成片 两栏
// 步骤5:全宽+内容居中(单视频视频播放器居中,批量网格居中)
if (currentStep === 5) return "xx-generate-layout full-width"
// 步骤6:封面选择保持两栏布局
return isBatch ? "xx-generate-layout full-width" : "xx-generate-layout"
}, [currentStep, isBatch])
@@ -553,10 +555,62 @@ const GeneratePage: React.FC = () => {
generateError={generateError}
selectedCount={isBatch ? selectedVariantIds.length : 1}
/>
{/* ════ 步骤5(单视频):成片播放器内联居中(#1761) ════ */}
{currentStep === 5 && !isBatch && generated && finalVideo && (
<div
style={{
display: "flex",
justifyContent: "center",
marginTop: 16,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
background: "#000",
borderRadius: 12,
padding: 8,
maxWidth: 320,
width: "100%",
}}
>
<video
src={finalVideo.download_url || finalVideo.file_url}
controls
style={{
width: "auto",
maxWidth: "100%",
maxHeight: "70vh",
aspectRatio: "9 / 16",
objectFit: "contain",
borderRadius: 8,
}}
poster={finalVideo.thumbnail_url || undefined}
/>
<div style={{ display: "flex", gap: 8, marginTop: 12, justifyContent: "center" }}>
<button className="xx-btn xx-btn-ghost xx-btn-sm" onClick={handleDownload}>
</button>
<button className="xx-btn xx-btn-ghost xx-btn-sm" onClick={handleShare}>
🔗
</button>
<button
className="xx-btn xx-btn-ghost xx-btn-sm"
onClick={() => navigate("/app/products")}
>
📁
</button>
</div>
</div>
</div>
)}
</div>
{/* ════ 步骤5/6(单视频):右侧成片播放器 ════ */}
{currentStep >= 5 && !isBatch && generated && finalVideo && (
{/* ════ 步骤6(单视频):右侧成片播放器 ════ */}
{currentStep >= 6 && !isBatch && generated && finalVideo && (
<div className="xx-generate-right-col">
<div
className="xx-inline-video-player"
@@ -34,12 +34,26 @@ const BatchGenerationGrid: React.FC<BatchGenerationGridProps> = ({
{tasks.filter((t) => t.status === "completed").length} / {tasks.length}
</span>
</div>
<div className="xx-batch-gen-grid">
<div
className="xx-batch-gen-grid"
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(160px, 180px))",
justifyContent: "center",
justifyItems: "center",
gap: 14,
width: "100%",
}}
>
{sorted.map((task) => {
const title = titles[task.variantIndex] || `视频 ${task.variantIndex + 1}`
const video = (task.videos?.[0] || null) as GeneratedVideo | null
return (
<div key={task.taskId} className={`xx-batch-gen-card status-${task.status}`}>
<div
key={task.taskId}
className={`xx-batch-gen-card status-${task.status}`}
style={{ maxWidth: 240 }}
>
<div className="xx-batch-gen-card-head">
<span className="xx-batch-gen-card-title" title={title}>
{task.status === "completed" ? (
+2
View File
@@ -3403,6 +3403,7 @@
第5步确认生成:批量渲染进度网格(Issue #1677
============================================================ */
.xx-batch-gen-grid {
justify-items: center;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 180px));
justify-content: center;
@@ -3481,6 +3482,7 @@
/* ── 响应式:窄屏批量网格回退单列(.xx-canvas-grid 的窄屏限宽见网格定义处 #1741) ── */
@media (max-width: 960px) {
.xx-batch-gen-grid {
justify-items: center;
grid-template-columns: minmax(0, 320px);
}
}
+181 -10
View File
@@ -7,6 +7,7 @@ import hashlib
import logging
import math
import os
import re
import statistics
import tempfile
from dataclasses import dataclass, field
@@ -56,8 +57,13 @@ MAX_GAP = 2 # 允许的最大间隙帧数
NEIGHBOR_WINDOW = 1 # 分片时序对齐:允许 ±1 邻接偏移(1s 密集采样下即 ±1s,缓解切点不一致)
# ── 融合判定常量 ────────────────────────────────────────────────
PHASH_WEIGHT = 0.7 # pHash 权重
HISTOGRAM_WEIGHT = 0.3 # 直方图权重
PHASH_WEIGHT = 0.7 # pHash 权重(视觉内部)
HISTOGRAM_WEIGHT = 0.3 # 直方图权重(视觉内部)
# ── 多维度查重融合权重(Issue #P2-后端3) ────────────────────────
VISUAL_WEIGHT = 0.5 # 视觉相似度权重(pHash+直方图)
TEXT_WEIGHT = 0.25 # 文案相似度权重(配音文本)
STRUCTURE_WEIGHT = 0.25 # 结构相似度权重(片段序列)
MATCH_RATIO_THRESHOLD = 0.7 # 全片重复(is_duplicate)至少 70% 帧匹配
PARTIAL_COVERAGE_THRESHOLD = 0.5 # 局部复用覆盖率 >=50% 也判全片重复
DUPLICATE_THRESHOLD = 0.70 # 融合后相似度阈值
@@ -1045,6 +1051,110 @@ class VideoDeduplicator:
logger.info("check_batch_duplicate no match (batch=%s): best_fusion=%.3f", batch_id, best_score)
return None
# ── 文案 & 结构维度查重(Issue #P2-后端3) ────────────────────────
def _normalize_text(text: str) -> str:
"""文本标准化:去空白、转小写、去标点。"""
if not text:
return ""
# 去空白字符
text = re.sub(r"\s+", "", text)
# 转小写
text = text.lower()
# 去标点(只保留中文、字母、数字)
text = re.sub(r"[^\w\u4e00-\u9fff]", "", text)
return text
def compute_text_similarity(text1: str, text2: str) -> float:
"""计算两段文本的相似度(0~1)。
使用字符级 Jaccard 相似度:交集 / 并集。
适合短文本(配音脚本)的相似度比对。
Args:
text1: 第一段文本
text2: 第二段文本
Returns:
0~1 之间的相似度
"""
t1 = _normalize_text(text1)
t2 = _normalize_text(text2)
if not t1 and not t2:
return 1.0 # 都为空,视为完全相同
if not t1 or not t2:
return 0.0 # 一个为空,完全不同
# 字符级 Jaccard
set1 = set(t1)
set2 = set(t2)
intersection = set1 & set2
union = set1 | set2
if not union:
return 0.0
return len(intersection) / len(union)
def compute_structure_similarity(clips1: list[dict], clips2: list[dict]) -> float:
"""计算两个视频的结构相似度(0~1)。
结构维度包括:
1. 片段数差异(数量越接近越相似)
2. 片段类型序列(相同位置的片段类型是否一致)
3. 时长分布(各片段时长占比是否相似)
Args:
clips1: 第一个视频的片段列表,每项包含 {clip_type, duration}
clips2: 第二个视频的片段列表
Returns:
0~1 之间的相似度
"""
if not clips1 and not clips2:
return 1.0
if not clips1 or not clips2:
return 0.0
# 1. 片段数相似度(数量差异越大越低)
n1, n2 = len(clips1), len(clips2)
count_sim = min(n1, n2) / max(n1, n2)
# 2. 类型序列相似度(逐位比较,相同位置类型是否一致)
min_len = min(n1, n2)
type_matches = sum(1 for i in range(min_len) if clips1[i].get("clip_type") == clips2[i].get("clip_type"))
type_sim = type_matches / min_len if min_len > 0 else 0.0
# 3. 时长分布相似度(归一化后比较分布)
total1 = sum(c.get("duration", 0) for c in clips1)
total2 = sum(c.get("duration", 0) for c in clips2)
if total1 > 0 and total2 > 0:
# 归一化为占比
dist1 = [c.get("duration", 0) / total1 for c in clips1]
dist2 = [c.get("duration", 0) / total2 for c in clips2]
# 比较前 min_len 个片段的占比差异(L1 距离转相似度)
l1_dist = sum(abs(dist1[i] - dist2[i]) for i in range(min_len))
# 加上多出的片段占比
if n1 > n2:
l1_dist += sum(dist1[i] for i in range(n2, n1))
elif n2 > n1:
l1_dist += sum(dist2[i] for i in range(n1, n2))
# L1 距离范围 [0, 2],转为相似度 [0, 1]
duration_sim = 1.0 - (l1_dist / 2.0)
else:
duration_sim = 0.0
# 三维度加权:数量 0.3 + 类型 0.4 + 时长 0.3
return count_sim * 0.3 + type_sim * 0.4 + duration_sim * 0.3
def compute_duplicate_rate(
self,
fingerprint: VideoFingerprint,
@@ -1057,12 +1167,11 @@ class VideoDeduplicator:
) -> dict:
"""计算当前视频与已有视频的查重率百分比。
新公式(双指标加权):
- frame_match_rate = 汉明距离 < PHASH_THRESHOLD 的帧数 / 总帧数
- temporal_coverage_rate = 连续匹配片段总时长 / 视频总时长
- duplicate_rate = (frame_match_rate * 0.4 + temporal_coverage_rate * 0.6) * 100
visual_similarity = 0.7 * phash_sim + 0.3 * hist_sim(归一化到 0~1
多维度融合公式(Issue #P2-后端3):
- visual_similarity = 0.7 * phash_sim + 0.3 * hist_sim(视觉维度)
- text_similarity = 文案 Jaccard 相似度(文案维度)
- structure_similarity = 片段序列相似度(结构维度)
- duplicate_rate = (visual*0.5 + text*0.25 + structure*0.25) * 100
对每个匹配视频都算,取最高 duplicate_rate。
@@ -1093,6 +1202,29 @@ class VideoDeduplicator:
match_count = 0
evaluated = 0
# Issue #P2-后端3: 加载当前视频的文案+结构数据
from packages.adapters.sqlalchemy_impl.models import EditPlanClipModel, GeneratedVideoModel
current_video_obj = (
session.query(GeneratedVideoModel).filter(GeneratedVideoModel.id == current_video_id).first()
if current_video_id
else None
)
current_plan_id = getattr(current_video_obj, "edit_plan_id", "") or ""
current_clips_data = []
current_text_content = ""
if current_plan_id:
current_clips = (
session.query(EditPlanClipModel)
.filter(EditPlanClipModel.plan_id == current_plan_id)
.order_by(EditPlanClipModel.order)
.all()
)
current_clips_data = [{"clip_type": c.clip_type, "duration": c.duration} for c in current_clips]
# 拼接所有片段的文本内容
current_text_content = " ".join(c.text_content for c in current_clips if c.text_content)
for existing in existing_videos:
if current_video_id and existing.id == current_video_id:
continue
@@ -1160,8 +1292,47 @@ class VideoDeduplicator:
# Issue #1702: 去掉 "frame_match_rate<0.3 整条跳过" 硬门槛——
# 局部片段复用帧比例天然低;coverage 为主指标,0 匹配自然得 0 分。
# duplicate_rate = 0.4 * frame_match_rate + 0.6 * temporal_coverage
dup_rate = (min(ev["frame_match_rate"], 1.0) * 0.4 + ev["temporal_coverage"] * 0.6) * 100
# 视觉维度:0.4 * frame_match_rate + 0.6 * temporal_coverage
visual_sim = min(ev["frame_match_rate"], 1.0) * 0.4 + ev["temporal_coverage"] * 0.6
# Issue #P2-后端3: 文案+结构维度
existing_plan_id = getattr(existing, "edit_plan_id", "") or ""
existing_clips_data = []
existing_text_content = ""
if existing_plan_id:
existing_clips = (
session.query(EditPlanClipModel)
.filter(EditPlanClipModel.plan_id == existing_plan_id)
.order_by(EditPlanClipModel.order)
.all()
)
existing_clips_data = [{"clip_type": c.clip_type, "duration": c.duration} for c in existing_clips]
existing_text_content = " ".join(c.text_content for c in existing_clips if c.text_content)
# 计算文案相似度(有文案才算)
text_sim = (
compute_text_similarity(current_text_content, existing_text_content)
if (current_text_content and existing_text_content)
else 0.0
)
# 计算结构相似度(有片段才算)
structure_sim = (
compute_structure_similarity(current_clips_data, existing_clips_data)
if (current_clips_data and existing_clips_data)
else 0.0
)
# 多维度融合:visual*0.5 + text*0.25 + structure*0.25
# 如果文案/结构数据缺失,只用视觉维度(visual 权重提升到 1.0)
if current_text_content and existing_text_content and current_clips_data and existing_clips_data:
dup_rate = (
visual_sim * VISUAL_WEIGHT + text_sim * TEXT_WEIGHT + structure_sim * STRUCTURE_WEIGHT
) * 100
else:
# 降级:只有视觉维度
dup_rate = visual_sim * 100
# 全片重复计数与 check_duplicate 判定口径一致
if ev["fusion"] >= DUPLICATE_THRESHOLD and (
@@ -2227,12 +2227,17 @@ class UnifiedRenderService:
perturbation = (self.plan.config or {}).get("visual_perturbation") or {}
if not perturbation:
return {}
return {
result = {
"hflip": bool(perturbation.get("hflip", False)),
"zoom_ratio": max(1.0, min(1.2, float(perturbation.get("zoom_ratio", 1.0) or 1.0))),
"speed_factor": max(0.8, min(1.2, float(perturbation.get("speed_factor", 1.0) or 1.0))),
"brightness_shift": max(-30, min(30, int(perturbation.get("brightness_shift", 0) or 0))),
}
# #1765:同时读取像素级扰动滤镜
pixel_pert = (self.plan.config or {}).get("pixel_perturbation") or {}
if pixel_pert:
result["pixel_perturbation"] = pixel_pert
return result
def _apply_visual_perturbation_pre_scale(self, filters: list[str], perturbation: dict) -> None:
# scale+pad 之前的扰动(hflip),就地修改 filters
@@ -2251,6 +2256,48 @@ class UnifiedRenderService:
if brightness != 0:
filters.append(f"eq=brightness={brightness / 100.0:.3f}")
# #1765:追加像素级扰动滤镜
pixel_pert = perturbation.get("pixel_perturbation") or {}
if pixel_pert:
self._apply_pixel_perturbation(filters, pixel_pert)
def _apply_pixel_perturbation(self, filters: list[str], pixel_pert: dict) -> None:
"""应用像素级扰动滤镜(Issue #1765)。
滤镜参数幅度确保肉眼不可见(SSIM > 0.95),但能让同素材不同变体
在帧级产生 > 3% 的差异,降低平台查重风险。
"""
filter_list = pixel_pert.get("filters") or []
for filt in filter_list:
if filt == "noise":
# 轻微噪声:noise=alls=0.015:allf=t+u
strength = pixel_pert.get("noise_strength", 0.015)
filters.append(f"noise=alls={strength}:allf=t+u")
elif filt == "unsharp":
# 锐化/柔化:unsharp=3:3:amount
# amount > 0 锐化,< 0 柔化
amount = pixel_pert.get("unsharp_amount", 0.0)
if abs(amount) > 0.01:
filters.append(f"unsharp=3:3:{amount:.2f}")
elif filt == "curves":
# 对比度微调:curves 用 preset 或手动定义
# 简单方案:用 eq=contrast 代替(curves 语法复杂)
contrast = pixel_pert.get("curves_contrast", 1.0)
if abs(contrast - 1.0) > 0.01:
filters.append(f"eq=contrast={contrast:.3f}")
elif filt == "color_balance":
# RGB 通道偏移:color_balance=rs=...:gs=...:bs=...
r = pixel_pert.get("color_r", 0)
g = pixel_pert.get("color_g", 0)
b = pixel_pert.get("color_b", 0)
if r != 0 or g != 0 or b != 0:
# color_balance 参数范围 -1.0 ~ 1.0,这里用 /100 转换
filters.append(f"color_balance=rs={r/100:.3f}:gs={g/100:.3f}:bs={b/100:.3f}")
@staticmethod
def _clip_volume(clip: ResolvedClip) -> float:
"""获取 clip 的音量(config.volume)。缺省 1.0 原声,0.0 静音。"""
+48
View File
@@ -321,6 +321,54 @@ def generate_visual_perturbation(rng: random.Random | None = None) -> dict:
}
def generate_pixel_perturbation(rng: random.Random | None = None) -> dict:
"""为一个变体生成像素级扰动滤镜参数(Issue #1765)。
在现有视觉扰动(hflip/zoom/brightness)基础上,额外叠加 2-3 种
像素级滤镜,让同素材不同变体在帧级 SSIM 差异 > 3%,肉眼看不出差异。
滤镜选项(随机选 2-3 种叠加):
- noise: 轻微噪声 (noise=alls=0.015:allf=t+u)
- unsharp: 锐化或柔化 (unsharp=3:3:-0.5 ~ 3:3:0.5)
- curves: 对比度微调 (curves 轻微调整)
- color_balance: RGB 通道偏移 (color_balance 微调)
返回 dict,可直接存入 plan.config["pixel_perturbation"]。
渲染侧读取后追加到 ffmpeg filter chain。
"""
rng = rng or random.Random()
# 可用滤镜池
filter_options = ["noise", "unsharp", "curves", "color_balance"]
# 随机选 2-3 种
num_filters = rng.choice([2, 2, 3])
selected = rng.sample(filter_options, num_filters)
result: dict = {"filters": selected}
# 为每种滤镜生成具体参数
if "noise" in selected:
# 噪声强度 0.01~0.02(肉眼不可见)
result["noise_strength"] = round(rng.uniform(0.01, 0.02), 4)
if "unsharp" in selected:
# 锐化/柔化:-0.5 ~ +0.5(正值锐化,负值柔化)
result["unsharp_amount"] = round(rng.uniform(-0.5, 0.5), 2)
if "curves" in selected:
# 对比度微调:0.95 ~ 1.05
result["curves_contrast"] = round(rng.uniform(0.95, 1.05), 3)
if "color_balance" in selected:
# RGB 通道偏移:-5 ~ +5(极轻微色偏)
result["color_r"] = rng.choice([-5, -3, 0, 0, 3, 5])
result["color_g"] = rng.choice([-5, -3, 0, 0, 3, 5])
result["color_b"] = rng.choice([-5, -3, 0, 0, 3, 5])
return result
def _base_clip_data(src: dict, *, asset_id: str, start: float, duration: float | None = None) -> dict:
"""从源片段构造落库 dict(保留骨架/转场/文案/速度,替换素材与起点)。"""
return {
+100 -6
View File
@@ -1,4 +1,4 @@
"""配音时长 → 片段时长分配纯函数(#1749)。
"""配音时长 → 片段时长分配纯函数(#1749 + #1764 节奏模板)。
定稿规则(工单 #1749):
1. 片段数 = 模板片段数,定死,不因素材增减;
@@ -8,6 +8,12 @@
禁止慢放、禁止截断配音;
4. 任何情况下不得因素材时长/数量报错打断用户。
#1764 节奏模板:
- 预设 6 种权重序列,不同变体用不同节奏模板
- 片段时长 = 配音总时长 × 该片段权重 / 权重总和
- 平均分配作为权重全 1 的特例保留
- 每个片段 >= MIN_CLIP_DURATION2秒)
本模块为纯函数:输入片段骨架(每段转场效果/时长)与配音总时长,
输出每段目标时长(target duration)与成片总时长。不碰 DB、不碰素材。
"""
@@ -15,10 +21,72 @@
from __future__ import annotations
import logging
import random
from typing import Optional
logger = logging.getLogger(__name__)
#: 单段最小时长(秒):低于此值播放器/渲染链路易出问题
MIN_CLIP_DURATION = 2.0
#: 成片总时长与配音时长的可接受误差(秒)
TOTAL_DURATION_TOLERANCE = 0.5
# ── #1764 节奏模板池 ──────────────────────────────────────────────────────
# 每种模板是权重序列,权重值代表相对时长比例
# 变体基于 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段最长
]
def get_rhythm_template(variant_seed: int | None = None) -> list[int]:
"""根据 variant_seed 选择一个节奏模板。
Args:
variant_seed: 变体随机种子;None 时返回平均模板
Returns:
权重序列(list[int]
"""
if variant_seed is None:
return RHYTHM_TEMPLATES[0] # 默认平均
rng = random.Random(variant_seed)
return rng.choice(RHYTHM_TEMPLATES)
def adapt_template_length(template: list[int], clip_count: int) -> list[int]:
"""将节奏模板适配到实际片段数。
片段数 != 模板长度时:
- clip_count < len(template): 截断
- clip_count > len(template): 循环填充
Args:
template: 原始权重序列
clip_count: 实际片段数
Returns:
适配后的权重序列(长度 == clip_count
"""
if clip_count <= 0:
return []
if clip_count == len(template):
return template[:]
if clip_count < len(template):
return template[:clip_count]
# clip_count > len(template): 循环填充
result = []
for i in range(clip_count):
result.append(template[i % len(template)])
return result
#: 单段最小时长(秒):低于此值播放器/渲染链路易出问题
MIN_CLIP_DURATION = 1.0
@@ -43,9 +111,12 @@ def plan_clip_durations(
voice_duration: float,
transition_effects: Optional[list[Optional[str]]] = None,
transition_durations: Optional[list[float]] = None,
rhythm_template: Optional[list[int]] = None,
) -> list[float]:
"""把配音总时长分配到 clip_count 段,返回每段目标时长(秒)。
#1764:支持节奏模板,按权重比例分配时长;无模板时平均分配(向后兼容)。
分配口径:Σ段长 − Σ转场重叠 = 配音时长(成片净时长 = 配音)。
转场重叠发生在相邻片段之间,共 clip_count-1 处;第 i 处重叠取
**后一段(i+1)** 的转场设置(与 xfade 构建口径一致:转场挂在后段)。
@@ -92,13 +163,36 @@ def plan_clip_durations(
MIN_CLIP_DURATION,
)
per_clip = gross / clip_count
result = [round(per_clip, 3) for _ in range(clip_count)]
# 末段吸收舍入误差:直接用 gross - 前段之和
result[-1] = round(gross - sum(result[:-1]), 3)
# #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 次
min_idx = min(range(len(result)), key=lambda i: result[i])
if result[min_idx] >= MIN_CLIP_DURATION:
break
# 从最长片段借时长
max_idx = max(range(len(result)), key=lambda i: result[i])
if max_idx == min_idx or result[max_idx] <= MIN_CLIP_DURATION:
# 无法再调整,强制保底
result[min_idx] = MIN_CLIP_DURATION
break
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
+207
View File
@@ -0,0 +1,207 @@
"""Tests for enhanced dedup: text + structure dimensions (Issue #P2-后端3)."""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
# ---------------------------------------------------------------------------
# Mock heavy deps before importing dedup module
# ---------------------------------------------------------------------------
_ORIGINAL_MODULES = dict(sys.modules)
_MOCKED_MODULE_NAMES: list[str] = []
def _mock_if_absent(name: str, mock_obj=None):
"""仅在模块不在 sys.modules 中时注入 mock,并记录以便清理。"""
if name not in sys.modules:
sys.modules[name] = mock_obj if mock_obj is not None else MagicMock()
_MOCKED_MODULE_NAMES.append(name)
# Mock heavy deps
_mock_if_absent("ffmpeg")
_mock_if_absent("ffmpeg.utils")
_mock_if_absent("worker_app.celery_app")
_mock_if_absent("worker_app.db")
_mock_if_absent("packages.adapters.sqlalchemy_impl.generated_video_repository")
_mock_if_absent("packages.adapters.sqlalchemy_impl.models")
_mock_if_absent("packages.shared.storage")
# Mock cv2 and numpy if not available
try:
import cv2 as _cv2
if not isinstance(_cv2, MagicMock):
_HAS_CV2 = True
else:
_HAS_CV2 = False
except ImportError:
_HAS_CV2 = False
_mock_if_absent("cv2")
_mock_if_absent("numpy")
import pytest
from apps.worker.video_processing.dedup import (
STRUCTURE_WEIGHT,
TEXT_WEIGHT,
VISUAL_WEIGHT,
compute_structure_similarity,
compute_text_similarity,
)
class TestTextSimilarity:
"""Tests for compute_text_similarity."""
def test_identical_texts(self):
"""相同文本返回 1.0。"""
assert compute_text_similarity("你好世界", "你好世界") == 1.0
def test_empty_texts(self):
"""都为空返回 1.0。"""
assert compute_text_similarity("", "") == 1.0
def test_one_empty(self):
"""一个为空返回 0.0。"""
assert compute_text_similarity("你好", "") == 0.0
assert compute_text_similarity("", "你好") == 0.0
def test_completely_different(self):
"""完全不同文本返回低相似度。"""
sim = compute_text_similarity("你好世界", "abcdefgh")
assert sim < 0.3
def test_partial_overlap(self):
"""部分重叠文本返回中等相似度。"""
sim = compute_text_similarity("今天天气真好", "今天天气不错")
assert 0.3 < sim < 0.9
def test_case_insensitive(self):
"""英文大小写不敏感。"""
sim = compute_text_similarity("Hello World", "hello world")
assert sim == 1.0
def test_whitespace_ignored(self):
"""空白字符被忽略。"""
sim = compute_text_similarity("你好 世界", "你好世界")
assert sim == 1.0
def test_punctuation_ignored(self):
"""标点符号被忽略。"""
sim = compute_text_similarity("你好,世界!", "你好世界")
assert sim == 1.0
def test_long_texts(self):
"""长文本也能计算。"""
t1 = "这是一段很长的配音文本,用于测试文案查重功能"
t2 = "这是一段较长的配音文字,用于测试文案去重功能"
sim = compute_text_similarity(t1, t2)
assert 0.0 <= sim <= 1.0
class TestStructureSimilarity:
"""Tests for compute_structure_similarity."""
def test_identical_structures(self):
"""完全相同结构返回 1.0。"""
clips = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 8.0},
]
assert compute_structure_similarity(clips, clips) == 1.0
def test_empty_clips(self):
"""都为空返回 1.0。"""
assert compute_structure_similarity([], []) == 1.0
def test_one_empty(self):
"""一个为空返回 0.0。"""
clips = [{"clip_type": "video", "duration": 5.0}]
assert compute_structure_similarity(clips, []) == 0.0
assert compute_structure_similarity([], clips) == 0.0
def test_different_count(self):
"""片段数不同,相似度降低。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
]
clips2 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 3.0},
{"clip_type": "title", "duration": 1.0},
]
sim = compute_structure_similarity(clips1, clips2)
assert 0.0 < sim < 0.8
def test_different_types(self):
"""片段类型不同,类型相似度低。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "video", "duration": 3.0},
]
clips2 = [
{"clip_type": "title", "duration": 5.0},
{"clip_type": "title", "duration": 3.0},
]
sim = compute_structure_similarity(clips1, clips2)
assert sim <= 0.6 # 类型全部不同,但数量和时长相同贡献 0.6
def test_different_duration_distribution(self):
"""时长分布不同,时长相似度低。"""
clips1 = [
{"clip_type": "video", "duration": 10.0}, # 占比 80%
{"clip_type": "title", "duration": 2.5}, # 占比 20%
]
clips2 = [
{"clip_type": "video", "duration": 2.0}, # 占比 20%
{"clip_type": "title", "duration": 8.0}, # 占比 80%
]
sim = compute_structure_similarity(clips1, clips2)
assert 0.7 < sim < 0.9 # 类型相同但时长分布不同,sim=0.82
def test_similar_structure(self):
"""相似结构返回较高相似度。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 8.0},
]
clips2 = [
{"clip_type": "video", "duration": 5.5},
{"clip_type": "title", "duration": 2.2},
{"clip_type": "video", "duration": 7.5},
]
sim = compute_structure_similarity(clips1, clips2)
assert sim > 0.8
def test_single_clip(self):
"""单片段也能计算。"""
clips1 = [{"clip_type": "video", "duration": 10.0}]
clips2 = [{"clip_type": "video", "duration": 12.0}]
sim = compute_structure_similarity(clips1, clips2)
assert sim > 0.5 # 类型相同,数量相同,只是时长不同
class TestDimensionWeights:
"""Tests for dimension weight constants."""
def test_weights_sum_to_one(self):
"""多维度权重之和为 1.0。"""
assert abs(VISUAL_WEIGHT + TEXT_WEIGHT + STRUCTURE_WEIGHT - 1.0) < 1e-9
def test_visual_weight_is_half(self):
"""视觉权重为 0.5。"""
assert VISUAL_WEIGHT == 0.5
def test_text_weight_is_quarter(self):
"""文案权重为 0.25。"""
assert TEXT_WEIGHT == 0.25
def test_structure_weight_is_quarter(self):
"""结构权重为 0.25。"""
assert STRUCTURE_WEIGHT == 0.25
+112
View File
@@ -0,0 +1,112 @@
"""像素级扰动滤镜单元测试(Issue #1765)。
覆盖:
- generate_pixel_perturbation:生成像素级扰动参数
- 滤镜组合:2-3 种滤镜随机组合
- 参数范围:肉眼不可见但帧级可检测
- FFmpeg 滤镜语法生成
"""
from __future__ import annotations
import random
import pytest
from packages.domain.variant_plan_selector import generate_pixel_perturbation
class TestGeneratePixelPerturbation:
"""generate_pixel_perturbation 测试。"""
def test_returns_dict(self):
"""返回 dict。"""
result = generate_pixel_perturbation()
assert isinstance(result, dict)
def test_has_filters_key(self):
"""包含 filters 键。"""
result = generate_pixel_perturbation()
assert "filters" in result
def test_filters_count_2_or_3(self):
"""选 2-3 种滤镜。"""
for _ in range(50):
result = generate_pixel_perturbation()
assert len(result["filters"]) in [2, 3]
def test_filters_from_valid_options(self):
"""滤镜来自有效选项。"""
valid_options = {"noise", "unsharp", "curves", "color_balance"}
for _ in range(50):
result = generate_pixel_perturbation()
for f in result["filters"]:
assert f in valid_options
def test_noise_parameters(self):
"""noise 滤镜有正确参数范围。"""
for _ in range(20):
result = generate_pixel_perturbation()
if "noise" in result["filters"]:
strength = result.get("noise_strength", 0)
assert 0.01 <= strength <= 0.02
def test_unsharp_parameters(self):
"""unsharp 滤镜有正确参数范围。"""
for _ in range(20):
result = generate_pixel_perturbation()
if "unsharp" in result["filters"]:
amount = result.get("unsharp_amount", 0)
assert -0.5 <= amount <= 0.5
def test_curves_parameters(self):
"""curves 滤镜有正确参数范围。"""
for _ in range(20):
result = generate_pixel_perturbation()
if "curves" in result["filters"]:
contrast = result.get("curves_contrast", 1.0)
assert 0.95 <= contrast <= 1.05
def test_color_balance_parameters(self):
"""color_balance 滤镜有正确参数范围。"""
valid_colors = [-5, -3, 0, 3, 5]
for _ in range(20):
result = generate_pixel_perturbation()
if "color_balance" in result["filters"]:
assert result.get("color_r") in valid_colors
assert result.get("color_g") in valid_colors
assert result.get("color_b") in valid_colors
def test_same_seed_same_result(self):
"""相同 seed 返回相同结果。"""
rng1 = random.Random(42)
rng2 = random.Random(42)
result1 = generate_pixel_perturbation(rng1)
result2 = generate_pixel_perturbation(rng2)
assert result1 == result2
def test_different_seeds_may_differ(self):
"""不同 seed 可能返回不同结果。"""
results = set()
for seed in range(20):
rng = random.Random(seed)
result = generate_pixel_perturbation(rng)
results.add(tuple(result["filters"]))
# 20 个 seed 至少看到 3 种不同组合
assert len(results) >= 3
class TestPixelPerturbationAcceptance:
"""Issue #1765 验收测试。"""
def test_batch_3_variants_have_different_filters(self):
"""批量 3 个变体有不同的滤镜组合。"""
results = []
for seed in [100, 200, 300]:
rng = random.Random(seed)
result = generate_pixel_perturbation(rng)
results.append(tuple(result["filters"]))
# 至少 2 种不同组合
unique = len(set(results))
assert unique >= 2, f"Expected >= 2 unique filter combos, got {unique}: {results}"
+190
View File
@@ -0,0 +1,190 @@
"""节奏模板单元测试(Issue #1764)。
覆盖:
- RHYTHM_TEMPLATES 池定义(6 种模板)
- get_rhythm_template:根据 seed 选择模板
- adapt_template_length:适配不同片段数
- plan_clip_durations:按权重分配时长
- 时长约束:总时长 ≈ 配音时长,每段 >= 2s
"""
from __future__ import annotations
import pytest
from packages.domain.voice_duration_planner import (
MIN_CLIP_DURATION,
RHYTHM_TEMPLATES,
adapt_template_length,
get_rhythm_template,
plan_clip_durations,
total_output_duration,
)
class TestRhythmTemplates:
"""节奏模板池测试。"""
def test_six_templates_defined(self):
"""预设 6 种节奏模板。"""
assert len(RHYTHM_TEMPLATES) == 6
def test_average_template_is_all_ones(self):
"""第一种模板是平均(全 1)。"""
assert RHYTHM_TEMPLATES[0] == [1, 1, 1, 1, 1]
def test_all_templates_have_5_elements(self):
"""所有模板长度为 5(会被 adapt 适配)。"""
for tpl in RHYTHM_TEMPLATES:
assert len(tpl) == 5
class TestGetRhythmTemplate:
"""get_rhythm_template 测试。"""
def test_none_seed_returns_average(self):
"""None seed 返回平均模板。"""
assert get_rhythm_template(None) == [1, 1, 1, 1, 1]
def test_same_seed_same_template(self):
"""相同 seed 返回相同模板。"""
tpl1 = get_rhythm_template(42)
tpl2 = get_rhythm_template(42)
assert tpl1 == tpl2
def test_different_seeds_may_differ(self):
"""不同 seed 可能返回不同模板。"""
templates_seen = set()
for seed in range(100):
tpl = tuple(get_rhythm_template(seed))
templates_seen.add(tpl)
# 100 个 seed 应该至少看到 3 种不同模板
assert len(templates_seen) >= 3
class TestAdaptTemplateLength:
"""adapt_template_length 测试。"""
def test_same_length(self):
"""片段数 == 模板长度时直接返回。"""
tpl = [2, 1, 3, 1, 2]
assert adapt_template_length(tpl, 5) == [2, 1, 3, 1, 2]
def test_shorter_clip_count(self):
"""片段数 < 模板长度时截断。"""
tpl = [2, 1, 3, 1, 2]
assert adapt_template_length(tpl, 3) == [2, 1, 3]
def test_longer_clip_count(self):
"""片段数 > 模板长度时循环填充。"""
tpl = [2, 1, 3]
result = adapt_template_length(tpl, 7)
assert result == [2, 1, 3, 2, 1, 3, 2]
def test_zero_clip_count(self):
"""片段数 0 返回空列表。"""
assert adapt_template_length([1, 2, 3], 0) == []
class TestPlanClipDurationsWithRhythm:
"""plan_clip_durations 节奏模板测试。"""
def test_average_template_equals_old_behavior(self):
"""全 1 模板 = 原来的平均分配。"""
voice = 20.0
clips = 4
result = plan_clip_durations(clips, voice, rhythm_template=[1, 1, 1, 1])
# 每段应该 ≈ 5s
assert all(abs(d - 5.0) < 0.1 for d in result)
assert abs(sum(result) - voice) < 0.1
def test_weighted_template_different_durations(self):
"""权重模板产生不同时长的片段。"""
voice = 18.0
clips = 5
# 权重 [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]
def test_min_clip_duration_enforced(self):
"""每段 >= MIN_CLIP_DURATION (2s)。"""
voice = 15.0
clips = 5
# 极端权重:某段权重极低
template = [10, 1, 1, 1, 1]
result = plan_clip_durations(clips, voice, rhythm_template=template)
for d in result:
assert d >= MIN_CLIP_DURATION
def test_total_duration_with_transitions(self):
"""含转场时总时长仍然正确。"""
voice = 20.0
clips = 4
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,
transition_effects=effects,
transition_durations=durations,
rhythm_template=template,
)
# 成片净时长 = Σ段长 - Σ转场重叠 ≈ 配音时长
output = total_output_duration(result, effects, durations)
assert abs(output - voice) < 0.5
def test_no_template_backward_compatible(self):
"""不传模板时行为与旧版一致(平均分配)。"""
voice = 16.0
clips = 4
result = plan_clip_durations(clips, voice)
assert all(abs(d - 4.0) < 0.1 for d in result)
def test_six_templates_produce_different_structures(self):
"""6 种模板产生不同的时长结构。"""
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
class TestIssue1764Acceptance:
"""Issue #1764 验收测试。"""
def test_batch_3_variants_at_least_2_different(self):
"""批量 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}"