Compare commits

...

169 Commits

Author SHA1 Message Date
xiaoxia f651422582 fix(deploy): 修复生产部署脚本502——worker/web容器启动失败 (#1887)
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 / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 11s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 39s
CI/CD Pipeline / Integration Tests (push) Successful in 3m18s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m21s
CI/CD Pipeline / Validate - Style (push) Successful in 3m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m16s
CI/CD Pipeline / Validate - Security (push) Successful in 8m17s
CI/CD Pipeline / Unit Tests (push) Successful in 8m42s
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 / Build Staging API Image (push) Successful in 14m38s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18m14s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 5m19s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m58s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m1s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-14 11:29:58 +08:00
xiaoxia 14580f536a refactor(backend): 智能剪辑后端公共逻辑抽取+解耦 (#1884)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 13s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m27s
CI/CD Pipeline / Integration Tests (push) Successful in 3m38s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m45s
CI/CD Pipeline / Validate - Style (push) Successful in 4m14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m42s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m14s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m58s
CI/CD Pipeline / Validate - Security (push) Successful in 8m1s
CI/CD Pipeline / Unit Tests (push) Successful in 9m0s
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
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 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 / 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 / PR Build 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 / Validate - Style (pull_request) Successful in 1m32s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m6s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m6s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m27s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m50s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m52s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m36s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m31s
AI Code Review / AI Code Review (pull_request) Successful in 6m21s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m48s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 51s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m4s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-14 03:15:00 +08:00
xiaoxia 6ff8d48c15 refactor(web): 智能剪辑 & AI数字人 前端代码审查与重构 (#1883)
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 / 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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m39s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m45s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m9s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m10s
CI/CD Pipeline / Validate - Style (push) Successful in 4m33s
CI/CD Pipeline / Integration Tests (push) Successful in 5m1s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m47s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m10s
CI/CD Pipeline / Validate - Security (push) Successful in 10m36s
CI/CD Pipeline / Unit Tests (push) Successful in 10m43s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-14 02:44:41 +08:00
LingYing Agent da1d55ef4e chore: 触发staging部署(批量变体P0修复)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 57s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m25s
CI/CD Pipeline / Integration Tests (push) Successful in 3m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m41s
CI/CD Pipeline / Validate - Style (push) Successful in 4m0s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m7s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m34s
CI/CD Pipeline / Validate - Security (push) Successful in 8m42s
CI/CD Pipeline / Unit Tests (push) Successful in 10m0s
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 / 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
2026-09-14 02:15:43 +08:00
xiaoxia 00f8c0b566 fix(web): 修复批量变体预览3个P0 bug (#1881)
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 / Check push changed paths (push) Successful in 5s
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 / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 45s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m4s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Integration Tests (push) Successful in 3m57s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m3s
CI/CD Pipeline / Validate - Style (push) Successful in 4m41s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m53s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m7s
CI/CD Pipeline / Validate - Security (push) Successful in 9m9s
CI/CD Pipeline / Unit Tests (push) Successful in 11m6s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-14 01:50:15 +08:00
xiaoxia 1f01d6df25 fix(worker): 修复批量变体3个P0 bug (#1882)
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 / 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 2m13s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m29s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m14s
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 / Frontend Unit Tests (push) Successful in 3m16s
CI/CD Pipeline / Integration Tests (push) Successful in 3m43s
CI/CD Pipeline / Validate - Style (push) Successful in 4m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m16s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m31s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m37s
CI/CD Pipeline / Validate - Security (push) Successful in 10m15s
CI/CD Pipeline / Unit Tests (push) Successful in 11m28s
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 / 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
CI/CD Pipeline / CI Gate (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-14 01:29:57 +08:00
xiaoxia 5b931438e0 Merge pull request 'fix(worker): 修复像素扰动color_balance滤镜名错误导致模板编辑器FFmpeg exit=8' (#1880) from fix/colorbalance-filter-name-0913 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 / Check push changed paths (push) Successful in 3s
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 / Build Staging API Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m11s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m6s
CI/CD Pipeline / Integration Tests (push) Successful in 3m2s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m26s
CI/CD Pipeline / Validate - Style (push) Successful in 4m0s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m44s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m47s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m19s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m27s
CI/CD Pipeline / Validate - Security (push) Successful in 7m57s
CI/CD Pipeline / Unit Tests (push) Successful in 8m35s
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 / 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 / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 0s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
AI Code Review / AI Code Review (pull_request) Successful in 24s
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) 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-14 00:07:35 +08:00
灵应 b9acf8c86f fix(worker): 修复像素扰动color_balance滤镜名错误导致ffmpeg exit=8
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 5s
CI/CD Pipeline / PR Build Web Image (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 / 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 21s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 32s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 13m45s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m21s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 6m49s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m5s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m39s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 15m14s
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 1s
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
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 6s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m4s
根因:像素扰动功能(bb98620)在ffmpeg滤镜链中使用了color_balance滤镜名,
但ffmpeg的实际滤镜名是colorbalance(无下划线),导致滤镜解析失败,
报错'No such filter: color_balance',ffmpeg退出码8。

影响:任何启用了像素扰动的变体渲染(默认批量变体会随机启用)都会失败,
表现为进度卡在56%,error_message只显示ffmpeg版本banner。

修复:将ffmpeg滤镜名从color_balance=改为colorbalance=。
内部config key保持color_balance不变(避免兼容问题)。
2026-09-13 23:36:02 +08:00
xiaoxia 6f4a95e00f Merge pull request 'fix(worker): 改进FFmpeg错误信息可见性便于exit=8排查' (#1879) from fix/ffmpeg-error-visibility-0913 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 / Check push changed paths (push) 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 / Build Staging Web Image (push) Successful in 1m31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m50s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m12s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m17s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m39s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m57s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m59s
CI/CD Pipeline / Validate - Style (push) Successful in 7m13s
CI/CD Pipeline / Integration Tests (push) Successful in 7m45s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 8m31s
CI/CD Pipeline / Unit Tests (push) Successful in 12m18s
CI/CD Pipeline / Validate - Security (push) Successful in 21m39s
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-13 23:30:09 +08:00
灵应 ccef935ca7 fix(worker): 改进ffmpeg错误信息可见性,stderr尾部优先+error_detail不丢弃
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 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 / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API 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 27s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m16s
AI Code Review / AI Code Review (pull_request) Successful in 6m27s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m11s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m43s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m45s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 9m9s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m32s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 6s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m0s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 22m33s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
1. error_message显示stderr尾部500字符(而非头部ffmpeg banner)
2. error_detail保留stderr尾部5000字符
3. generation任务RuntimeError保留error_detail
4. 缩略图ffmpeg错误同理
2026-09-13 23:11:53 +08:00
xiaoxia d7f2e68707 Merge pull request 'fix(lipsync): video_url扩展名校验放宽,支持MOV/M4V/WebM等容器' (#1878) from fix/lipsync-video-ext-0913 into develop
CI/CD Pipeline / Check if frontend-only change (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 / 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 / Check push changed paths (push) Successful in 14s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m23s
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 / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m53s
CI/CD Pipeline / Integration Tests (push) Successful in 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m1s
CI/CD Pipeline / Validate - Style (push) Successful in 6m45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m47s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 7m59s
CI/CD Pipeline / Unit Tests (push) Successful in 11m20s
CI/CD Pipeline / Validate - Security (push) Successful in 22m57s
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 / 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 / Staging E2E Tests (push) Failing after 21m15s
2026-09-13 22:51:08 +08:00
灵应 78a618f7d7 fix(lint): B904 raise from exc for finalize 500 error handler
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 / 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 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 32s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m34s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m34s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m20s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m39s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m29s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m39s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 22m44s
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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 1m24s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m52s
2026-09-13 22:25:04 +08:00
灵应 5ab8a66e1b fix(test): 更新smart cover轮询参数断言以匹配新值2s/30次
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 / 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 / Build Staging API Image (pull_request) Has been skipped
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 / 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 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m37s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m57s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m47s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m34s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 8m17s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m47s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 13m10s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m36s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 25m38s
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) Failing after 1s
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
2026-09-13 21:58:12 +08:00
CI Bot a7fc67b822 style: auto-format with black + isort + ruff + 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 Worker Image (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 / Check if frontend-only change (pull_request) Successful in 3s
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 Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API 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 1m21s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m44s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m46s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m43s
AI Code Review / AI Code Review (pull_request) Successful in 6m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m58s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 7m10s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m28s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 11m58s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m6s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 23m43s
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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
2026-09-13 13:31:12 +00:00
灵应 fc925ef3b3 fix(lipsync): video_url扩展名校验放宽,支持MOV/M4V/WebM/AVI/MKV/3GP
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 2s
CI/CD Pipeline / Build Staging Worker Image (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 / 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 / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m19s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m18s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
AI Code Review / AI Code Review (pull_request) Successful in 6m45s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m44s
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 / 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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
iPhone等设备拍摄的视频转h264后容器保留.MOV扩展名,
原校验仅允许.mp4导致对口型接口返回422。
同步更新单测。
2026-09-13 21:23:19 +08:00
CI Bot b69595bd28 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (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 37s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 30s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 55s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m23s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m24s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Failing after 12m7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m53s
CI/CD Pipeline / Integration Tests (push) Successful in 5m57s
CI/CD Pipeline / Validate - Style (push) Failing after 7m28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 8m1s
CI/CD Pipeline / Unit Tests (push) Failing after 11m3s
CI/CD Pipeline / Validate - Security (push) Successful in 23m12s
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
2026-09-13 11:37:46 +00:00
xiaoxia 5c91bb21a7 Merge pull request 'fix(ai-avatar): 智能封面抽帧超时修复 + 封面流程改造(选封面后点完成才入库)' (#1877) from fix/ai-avatar-cover-flow-0913 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 / 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 14s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m35s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m52s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m15s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m23s
CI/CD Pipeline / Integration Tests (push) Successful in 4m30s
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 / Frontend Unit Tests (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
2026-09-13 19:32:43 +08:00
CI Bot 135e422d45 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 51s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 51s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 51s
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 / Validate - Python (mypy + alembic) (pull_request) Successful in 1m39s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m53s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 2m1s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m11s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 2m14s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m13s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m2s
AI Code Review / AI Code Review (pull_request) Successful in 6m21s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 8m51s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 5m47s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 36s
2026-09-13 11:16:39 +00:00
灵应 60d19704bb fix(ai-avatar): 抽帧超时延长至60s + 封面选完才入库流程
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 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 / Frontend Lint (pull_request) Failing after 1m25s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m29s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m11s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m41s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m49s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 3m9s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m9s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m0s
CI/CD Pipeline / Validate - Style (pull_request) 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 / 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 / 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 Merge on CI Green + Approved (pull_request) Has been cancelled
- ai_avatar_cover_service: COVER_POLL_INTERVAL=2s × COVER_MAX_POLL_ATTEMPTS=30 (最长 60s),适配合成视频(叠加B-roll/标题)较大的下载+抽帧耗时
- ai_avatar_render_service: execute_render 完成后停留在 completed 状态不再自动入库成片库;新增 _persist_to_library(cover_url 可选覆盖) 与 finalize_job(job_id, user_id, cover_url?) 方法,供用户点「完成」时显式入库,幂等(通过 generation_task_id=render_job_id 判重)
- 路由: 新增 POST /{job_id}/finalize 接口,含 404/400/幂等/异常分支
- Schema: 新增 FinalizeRenderResponse
- 前端:
  - aiAvatar.ts 新增 finalizeRenderJob API
  - PanelCoverAndGenerate 拆分 variant=setup(主页面配置+生成按钮,不含封面区) 与 select-cover(弹窗内封面选择);新增 onClose/onCoverSelected props,智能/上传成功后回调通知父组件
  - 新建 ModalCoverSelect 弹窗组件
  - AiAvatarPage: render 完成后不再自动跳转/generated,而是停留在主页面显示封面预览+「🎬选择封面」「完成」按钮;弹窗内选封面、点「完成」才调 finalize 入库并跳转到成片库
- 单元测试: 更新原有 execute_render 自动入库测试为不入库断言,新增 finalize_job 入库/幂等/状态校验 3 个测试
2026-09-13 19:09:39 +08:00
xiaoxia c613f35662 Merge pull request 'fix(ai-avatar): 修复三个bug——封面重影/标题字号缩放/对口型音频截断' (#1876) from fix/ai-avatar-three-bugs-0913 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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web 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 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Validate - Python (mypy + alembic) (push) Successful in 1m53s
CI/CD Pipeline / Integration Tests (push) Successful in 1m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m0s
CI/CD Pipeline / Validate - Style (push) Successful in 2m39s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
CI/CD Pipeline / Validate - Security (push) Successful in 5m37s
CI/CD Pipeline / Unit Tests (push) Successful in 8m32s
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-13 14:15:31 +08:00
CI Bot 9be89484e6 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 5s
CI/CD Pipeline / Check push changed paths (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 / 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 / Retag skipped Staging API 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 26s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 47s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 49s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m50s
AI Code Review / AI Code Review (pull_request) Successful in 6m16s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 12m8s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m2s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m36s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m43s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m28s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 9m34s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 10s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 17s
2026-09-13 05:32:51 +00:00
灵应 5f128d175d fix(ai-avatar): 修复三个bug——封面重影/标题字号缩放/对口型音频截断
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 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 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 1m12s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m28s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m33s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m49s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m48s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m16s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m54s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m8s
CI/CD Pipeline / Validate - Style (pull_request) 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
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
1. 删除封面展示区重复叠加的标题div(成片帧已含标题,不再CSS叠字)
2. 标题字号按视频分辨率等比缩放,预览容器动态测量宽度计算scale
3. enable_video_loop默认改为true,防止TTS音频长于出镜视频时被截断
2026-09-13 13:28:51 +08:00
xiaoxia 053b00634a feat(ai-avatar): 配音前置 (#1875)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 49s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m38s
CI/CD Pipeline / Integration Tests (push) Successful in 3m50s
CI/CD Pipeline / Validate - Style (push) Successful in 4m2s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m26s
CI/CD Pipeline / Unit Tests (push) Successful in 8m55s
CI/CD Pipeline / Validate - Security (push) Successful in 9m7s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-13 04:22:21 +08:00
xiaoxia 3858acf377 Merge pull request 'fix(ai-avatar): B-roll时间戳根因——逗号分句+优先后端时间戳' (#1874) from fix/ai-avatar-broll-comma-split 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 / Frontend Lint (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 / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 15s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m37s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m32s
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 3m25s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m22s
CI/CD Pipeline / Validate - Style (push) Successful in 4m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m28s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m25s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m7s
CI/CD Pipeline / Validate - Security (push) Successful in 9m30s
CI/CD Pipeline / Unit Tests (push) Successful in 9m39s
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 / Build Production API 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
fix(ai-avatar): B-roll逗号分句+标题PNG图层+TTS竞态修复+死代码清理

- B-roll分句正则加入中文逗号,放宽后端时间戳校验直接使用
- 标题改为Canvas渲染透明PNG,FFmpeg overlay图片图层替代drawtext,所见即所得
- Celery事务竞态修复:先commit后发任务+worker侧retry防御
- TTS任务超时防护(180s soft/200s hard)+入口日志
- 删除封面死代码(POST /smart-cover裸视频抽帧,所有封面从成片获取)
- 删除DRAWTEXT_BOLD_FONT_SEARCH_PATHS冗余代码
2026-09-13 02:49:04 +08:00
xiaoxia f607b0cec9 fix(test): mock storage.get_download_url,修复_sign_media_url内部调用mock泄漏问题
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 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 Web Image (pull_request) Successful in 46s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 48s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 53s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m20s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m35s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m32s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m46s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m51s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m28s
AI Code Review / AI Code Review (pull_request) Successful in 6m17s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 9m9s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 11m12s
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) Failing after 1s
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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 8m35s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 8s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 32s
2026-09-13 02:21:08 +08:00
xiaoxia 688b35efa8 fix(test): 统一test_lipsync_tts持久化测试upload_url参数与key一致,消除flaky
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 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 27s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 26s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 28s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 49s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m0s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m42s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m40s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m3s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m8s
AI Code Review / AI Code Review (pull_request) Successful in 6m19s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 8m28s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m29s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m4s
2026-09-13 02:05:30 +08:00
xiaoxia ce6c831cf3 style: prettier format sentences.ts & titleCanvas.ts [skip ci-format-check]
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m1s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m51s
AI Code Review / AI Code Review (pull_request) Successful in 6m47s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m42s
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 / 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 API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (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 / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 34s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 34s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 34s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m6s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m14s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m49s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 3m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m58s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 8m0s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m27s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 5s
2026-09-13 01:34:47 +08:00
xiaoxia 3267b24433 fix(ai-avatar): 删除未使用变量 startY(TS6133修复)
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 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 / PR Build API Image (pull_request) Successful in 36s
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 / PR Build Worker Image (pull_request) Successful in 39s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m13s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m20s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m31s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m45s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m46s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m45s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 3m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m16s
AI Code Review / AI Code Review (pull_request) Successful in 6m21s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 8m8s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m51s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 3s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m16s
2026-09-13 01:14:15 +08:00
CI Bot 222c4d15a9 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 / 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 / Frontend Lint (pull_request) Failing after 2m26s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 2m35s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 2m40s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m42s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 3m9s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m52s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m28s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m3s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m31s
AI Code Review / AI Code Review (pull_request) Successful in 6m41s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 8m37s
CI/CD Pipeline / Validate - Security (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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
2026-09-12 17:05:14 +00:00
xiaoxia 63fb0508be refactor(ai-avatar): 删除封面标题叠加逻辑+废弃路由(封面一律从成片抽帧)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 53s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2m8s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 3m12s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m17s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 3m56s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m51s
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 / 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 Merge on CI Green + Approved (pull_request) Has been cancelled
- 删除前端 generateSmartCover(0处调用的死代码)
- 删除后端 POST /ai-avatar/render/smart-cover(对口型阶段抽封面入口,已废弃)
- 删除 SmartCoverRequest schema(无引用)
- 精简 ai_avatar_cover_service:删除 apply_title_to_cover / _overlay_title_png_on_image,
  封面一律从最终成片(已叠加标题/B-roll)抽帧,不再额外叠加
- 删除 persist_cover_to_oss / generate_smart_cover 的 title_config 参数
2026-09-13 00:58:02 +08:00
xiaoxia 577ec83636 feat(ai-avatar): 标题改为Canvas渲染PNG图层叠加,所见即所得 2026-09-13 00:41:38 +08:00
xiaoxia 6503a74a7c fix(ai-avatar): B-roll时间戳为0根因——逗号分句+优先后端时间戳
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 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 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 / Frontend Lint (pull_request) Failing after 1m27s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m33s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m56s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m57s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 3m0s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m55s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m50s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 5m56s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m59s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 6m11s
AI Code Review / AI Code Review (pull_request) Successful in 6m44s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 11m28s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 8m23s
根因(用户截图文案:'卖花的叫花无缺,卖姜的叫姜子牙,卖菜的蔡文姬,那我修脚阔头的呢,应该叫什么呢'):
1. 分句正则没包含中文逗号「,」,整段被识别为1句;后端静音检测按TTS音频停顿
   切出多句,前端校验 sentenceTimings.length===rawParts.length 条数不等 →
   整个后端精确时间戳被丢弃走降级
2. 降级路径 outputDuration=0(对口型预览阶段最终视频未渲染)→ 所有句子时间估算为0

修复:
- 前端 sentences.ts:
  a. 分句正则加上中英文逗号「,,」
  b. sentenceTimings 校验放宽:只要是有效数组就直接用后端句子列表,
     不再强制条数相等(后端按音频停顿的切法才是真实边界)
- 后端 _split_script_into_sentences:正则同步加逗号,前后端一致
- 补单测验证逗号分隔文案分句
2026-09-12 23:55:07 +08:00
xiaoxia 4a93aaaf4c Merge pull request 'fix(ai-avatar): B-roll时间戳/标题重影/TTS卡死防护' (#1873) from fix/ai-avatar-broll-title-worker-issues 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 17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 51s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m4s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m25s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m51s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m26s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m2s
CI/CD Pipeline / Integration Tests (push) Successful in 6m0s
CI/CD Pipeline / Validate - Style (push) Successful in 6m13s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 7m59s
CI/CD Pipeline / Unit Tests (push) Successful in 11m28s
CI/CD Pipeline / Validate - Security (push) Successful in 22m15s
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 / 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
fix(ai-avatar): B-roll时间戳/标题重影/Celery事务竞态/TTS超时防护

- 问题1:B-roll时间戳全0,sentences.ts参数顺序错位导致后端精确时间戳被忽略
- 问题2:标题粗体同色描边borderw=3导致字形重影,改为黑色细描边
- 问题3P0:Celery事务竞态(先发任务后commit),worker查不到job静默return永远卡tts_processing;改为先commit再发任务+worker侧self.retry防御
- 补充:TTS任务加soft_time_limit=180s、autoretry_for网络错误、入口INFO日志

单测覆盖:新增198+相关用例通过,全量15054 passed
2026-09-12 22:37:11 +08:00
CI Bot 1a4f475fbf style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 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 / PR Build Web Image (pull_request) Successful in 42s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 43s
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 45s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m4s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m9s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m41s
AI Code Review / AI Code Review (pull_request) Successful in 6m31s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m40s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m55s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 8m8s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 9m43s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m31s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 23m32s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 8s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 58s
2026-09-12 14:10:26 +00:00
xiaoxia 2fa6de29bc fix(lipsync): 修复Celery事务竞态导致job永远卡在tts_processing(P0)
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 2s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 30s
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 API Image (pull_request) Successful in 33s
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 / Frontend Lint (pull_request) Successful in 1m39s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m39s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m44s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m55s
AI Code Review / AI Code Review (pull_request) Successful in 6m31s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 8m15s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 8m17s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 10m28s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
根因:create_job() 先 apply_async() 发送 Celery 任务,再 db.commit() 提交事务。
worker 是独立进程+独立DB连接,任务<4ms就被消费,但此时 API 事务还未 commit,
worker 查询 job 返回 None → 静默 return 不重试 → job 永远卡在 tts_processing。

修复:
1. API侧(lipsync_service.py):先 db.commit()+db.refresh(job),再 apply_async() 发任务;
   投递失败/MediaKit提交失败分支也各自 commit,确保状态及时落库。
2. Worker侧(lipsync_tts.py):job not found 时改用 self.retry() 递增重试3次
   (1s/3s/7s退避),作为竞态场景的第二道防线;
   增加 autoretry_for=(OSError, ConnectionError) 自动重试网络抖动;
   max_retries 从2调整到5。

子agent在staging实锤:受影响job共5个,手工重投递后全部3秒内完成TTS+提交MediaKit,
证实TTS本身只需要2秒,卡顿完全是因为竞态。

单测:新增 TestCreateJobCommitOrder 验证 commit 在 apply_async 之前。
2026-09-12 21:50:02 +08:00
xiaoxia 831075a9c0 test(ai-avatar): 补充B-roll/标题单测,修正断言
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (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 / 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 / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging API 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 Web Image (pull_request) Successful in 1m29s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m31s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m49s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m6s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m20s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m49s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m22s
AI Code Review / AI Code Review (pull_request) Successful in 6m28s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m40s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m5s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m13s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m24s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m38s
CI/CD Pipeline / Validate - Security (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-12 21:39:00 +08:00
xiaoxia a83b53ae58 fix(ai-avatar): B-roll时间戳、标题粗体重影、TTS卡死防护
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 / Check if frontend-only change (pull_request) Successful in 2s
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 / Frontend Lint (pull_request) Successful in 1m23s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m19s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m18s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m38s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m40s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m11s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 3m45s
AI Code Review / AI Code Review (pull_request) Successful in 6m59s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m58s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m11s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m14s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 9m16s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m40s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 25m22s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
1. B-roll时间戳全0修复:
   - sentences.ts 新增 sentenceTimings 参数优先使用后端精确时间戳,
     降级才按字数比例估算
   - 修正参数顺序,与 ModalBRollEditor 现有调用 (scriptText, timings, duration) 对齐
   - 前端 AiAvatarPage 已通过 props 传 sentenceTimings(旧版已传但因顺序错位被忽略)

2. 标题粗体重影修复:
   - 之前 bold=true 时使用 borderw=3 + font_color 同色描边模拟粗体,
     会在小字号/竖屏视频上造成字形边缘偏移,视觉上文字像被打印了两次
     (用户截图中的'曝光曝光…'重影)
   - 改为黑色细描边(borderw=2, 黑色),既保留清晰加粗效果又不重影
   - 用户显式开启 stroke 时仍按用户配置走
   - 新增 _resolve_font_path(bold=True) 预留粗体字体查找能力(当前镜像
     无独立 Bold 字体文件,沿用 VF 常规字重)

3. TTS 任务防卡死:
   - 给 tts_synthesize_and_submit 加 soft_time_limit=180s / time_limit=200s,
     避免因网络/上游问题导致 Celery 任务永久挂起(用户之前卡10+分钟
     tts_processing 不失败)
   - 任务开头加 INFO 日志(job_id/voice_id/text_len),方便排查 worker
     是否真的收到任务

相关:staging 用户反馈 5 问题中的 1/2/3 项(B-roll时间、标题重影、对口型卡死)
2026-09-12 21:07:34 +08:00
frontend-dev e250132ace perf(ai-avatar): 对口型加速+封面流程重构 (#1872)
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 / 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 42s
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 1m51s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m56s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
CI/CD Pipeline / Validate - Style (push) Successful in 2m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m39s
CI/CD Pipeline / Validate - Security (push) Successful in 4m42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m56s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m6s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m5s
CI/CD Pipeline / Unit Tests (push) Failing after 7m43s
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 / 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
Co-authored-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
Co-committed-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
2026-09-12 19:10:03 +08:00
frontend-dev 774dd27844 fix(ai-avatar): 修复B-roll文案时长全为0.0s的问题 (#1871)
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
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 / Build Staging Web Image (push) Successful in 23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Integration Tests (push) Successful in 3m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m28s
CI/CD Pipeline / Validate - Style (push) Successful in 3m51s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m42s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m28s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m49s
CI/CD Pipeline / Validate - Security (push) Successful in 8m11s
CI/CD Pipeline / Unit Tests (push) Successful in 8m48s
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 / 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
Co-authored-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
Co-committed-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
2026-09-12 15:00:56 +08:00
frontend-dev ed7af0642d fix(ai-avatar): B-roll文案显示时长+标题字号调大 (#1870)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
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 / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m21s
CI/CD Pipeline / Integration Tests (push) Successful in 2m19s
CI/CD Pipeline / Validate - Style (push) Successful in 3m0s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m14s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m48s
CI/CD Pipeline / Validate - Security (push) Successful in 4m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m17s
CI/CD Pipeline / Unit Tests (push) Successful in 8m8s
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 / Staging API Integration Tests (push) Successful in 23m3s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
Co-committed-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
2026-09-12 10:02:34 +08:00
frontend-dev 938ef0b8cc fix(ai-avatar): 端到端一致性修复(标题/封面/B-roll位置/B-roll时长) (#1869)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 51s
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 3m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m37s
CI/CD Pipeline / Validate - Style (push) Successful in 4m6s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m29s
CI/CD Pipeline / Validate - Security (push) Successful in 8m25s
CI/CD Pipeline / Unit Tests (push) Successful in 8m59s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 18m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m7s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m42s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
Co-committed-by: frontend-dev <frontend-dev@xiaoxiajianji.com>
2026-09-12 02:41:11 +08:00
frontend-dev 982daac6e5 Merge pull request 'fix(ai-avatar): 全盘修复 FFmpeg 渲染滤镜链路(exit 234 P0)' (#1868) from fix/ffmpeg-filter-comprehensive-fix 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 / 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m29s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m2s
CI/CD Pipeline / Integration Tests (push) Successful in 7m24s
CI/CD Pipeline / Validate - Style (push) Successful in 9m16s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 9m56s
CI/CD Pipeline / Unit Tests (push) Successful in 12m12s
CI/CD Pipeline / Validate - Security (push) Successful in 23m10s
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 / Build Staging API Image (push) Successful in 39m39s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 41m2s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m50s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m27s
2026-09-11 23:40:21 +08:00
CI Bot a7067c8171 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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 / 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 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 32s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 33s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m42s
AI Code Review / AI Code Review (pull_request) Successful in 6m37s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 7m55s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 8m12s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 8m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 10m50s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m0s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 5s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 2m55s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 28m58s
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 1s
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-11 15:20:35 +00:00
LingYing Agent 32c3d2f263 fix(ai-avatar): 全盘修复 FFmpeg 渲染滤镜链路(exit 234 P0)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 / 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 API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web 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 27s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m43s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m55s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m44s
AI Code Review / AI Code Review (pull_request) Successful in 6m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 8m17s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 8m45s
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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
根因分析(退出码 234 = Invalid argument):
1) drawtext 使用了无效参数 font=bold,导致整个 filter_complex 解析失败
   —— FFmpeg drawtext 没有 bold 参数;改为通过 borderw 模拟粗体视觉效果
2) DRAWTEXT_FONT_SEARCH_PATHS 未包含 Dockerfile 中 COPY 的
   NotoSansSC-VF.ttf 路径,且把不支持中文的 DejaVuSans 放在 fallback 首位,
   导致字体 fallback 到拉丁字体,中文渲染乱码/方框
3) build_broll_overlay_filter 硬编码 1280:720 横屏尺寸,AI 数字人是 9:16 竖屏
4) B-roll PIP 输入索引错误(用 len(sorted_segments) 而非原始下标映射),
   PIP 标签链断裂([pip0]→[vout0] 而非 [vout])
5) 渲染命令缺 -map 0:a?,合成后音频丢失
6) 标题滤镜与 B-roll 输出标签拼接用分号有缺陷,末尾分号处理偶发问题

修复内容(packages/domain/video_filter_builder.py):
- DRAWTEXT_FONT_SEARCH_PATHS: VF 字体置顶、移除 DejaVuSans、加 Bold.ttc 路径
- DRAWTEXT_FONT_MAP: 思源黑体等关键字改为 NotoSansSC(匹配 VF 文件名)
- 删除 font=bold 无效参数;bold 无显式描边时自动用 borderw=3+同色描边模拟粗体
- build_broll_overlay_filter 返回 (filter_str, final_label) 元组,解决标签问题
- 重写 fullscreen/PIP 拆分:原始列表下标决定 -i 输入序号,sorted 只用于时序处理
- PIP overlay 正确基于 fullscreen 输出([vout_fs])或主视频([0:v])链接
- output_width/output_height 贯穿所有 scale/pad/overlay,默认 1280x720 兼容旧调用

修复内容(apps/api/app/services/ai_avatar_render_service.py):
- 新增 _probe_video_resolution() 用 ffprobe 探测输入视频实际分辨率
- AI 数字人默认竖屏 720x1280,探测失败兜底不阻断渲染
- build_broll_overlay_filter 调用传实际 output_width/output_height
- 标题滤镜传实际尺寸,保证位置/坐标计算正确
- filter_complex 拼接重写:broll+title/broll-only/title-only/无滤镜四分支清晰
- _build_ffmpeg_command 补 -map 0:a? + -c:a aac,音频不再丢失
- 清理冗余内联 import

测试:
- 更新 test_ai_avatar_render_routes.py / test_video_filter_builder.py 适配新元组签名
- 新增 test_bold_true_does_not_use_font_bold_param 防回归
- 本地 ffmpeg 实测中文标题+B-roll+竖屏720x1280合成成功
- 相关 3524 个单测全通过
2026-09-11 23:09:22 +08:00
xiaoxia 7198cfe980 Merge pull request 'fix(ai-avatar, P0): 数字人成片入库兜底project_id + 封面抽帧加速 + cv2依赖' (#1864) from fix/ai-avatar-generated-video-and-cover-speed 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
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 / Build Staging Web Image (push) Successful in 20s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m43s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m52s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m2s
CI/CD Pipeline / Integration Tests (push) Successful in 8m35s
CI/CD Pipeline / Validate - Style (push) Successful in 9m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 11m32s
CI/CD Pipeline / Unit Tests (push) Successful in 14m10s
CI/CD Pipeline / Validate - Security (push) Successful in 31m22s
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
API Base Image Build / Build API Base Image (push) Successful in 58m15s
Worker Base Image Build / Build Worker Base Image (push) Successful in 1h11m23s
P0: AI数字人视频不入成品库+封面抽帧加速 (#1864)

- GeneratedVideo.create() 允许 project_id/generation_task_id 为空字符串,支持 AI数字人无项目场景
- ai_avatar_render_service: project_id空兜底ai_avatar, generation_task_id空兜底job.id; except升error+exc_info
- 封面抽帧轮询 poll_interval=1s × max_attempts=15 → 最长15s(原60s)
- requirements.txt 追加 numpy==1.26.4 + opencv-python-headless==4.10.0.84,恢复cv2帧评分能力
- 单测适配:允许空project_id、poll参数更新、cv2评分阈值+patch方式调整
2026-09-11 20:06:57 +08:00
xiaoxia b0cfa98e20 Merge pull request 'fix(ai-avatar): 封面title_config字段名与后端契约对齐' (#1865) from fix/ai-avatar-cover-title-contract 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 / 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 14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 16s
CI/CD Pipeline / Integration Tests (push) Successful in 1m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m30s
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 - Style (push) Successful in 1m56s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m47s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m4s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Unit Tests (push) Successful in 7m18s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m34s
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
2026-09-11 19:58:52 +08:00
xiaoying-agent e905989695 test: 适配 cv2 可用后的单测阈值与 mock 方式
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 / 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 Web Image (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 / 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 / PR Build API Image (pull_request) Successful in 14s
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 14s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m38s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m40s
AI Code Review / AI Code Review (pull_request) Successful in 6m25s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m39s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 11m33s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m0s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 8s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m16s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 25m55s
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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
- test_cover_frame_scorer: 渐变图高分阈值 50→40(实测41.27,纯渐变拉普拉斯方差中等);全黑阈值 5→10(兼容cv2浮点/直方图微小差异)
- test_dedup_v2: cv2.VideoCapture 改为 patch.object 方式 mock,确保在真实 cv2 可用环境下mock生效(之前直接赋值 cv2_mock.VideoCapture.return_value 在sys.modules恢复后可能引用丢失)
2026-09-11 19:42:21 +08:00
xiaoxia 3dcf1079a9 fix(ai-avatar): 封面title_config字段名与后端契约对齐
CI/CD Pipeline / Check push changed paths (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 / 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 / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build 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 / Frontend Unit Tests (pull_request) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m43s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m19s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m33s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 17m33s
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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m13s
封面接口传入的titleConfig用buildTitleConfigPayload转换:
- 前端字段 title -> 后端 text
- 前端字段 color -> 后端 font_color (#开头保留)
- 前端字段 size -> 后端 font_size
- 前端字段 position 默认bottom

之前直接传原始AiAvatarTitleConfig对象,后端只认text/font_color/font_size,
导致封面标题内容、颜色、字号、位置全部取默认值(白色居中36px),与对口型预览不一致。
渲染提交路径已经用了buildTitleConfigPayload,是封面路径漏了转换。
2026-09-11 19:38:28 +08:00
xiaoxia da22c2e834 Merge pull request 'fix(ai-avatar): 素材预览不叠标题+封面传title_config+渲染后封面避免双标题' (#1863) from fix/ai-avatar-title-cover-source 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
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 / Build Staging API Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m6s
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 1m41s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m58s
CI/CD Pipeline / Validate - Style (push) Successful in 2m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m54s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m56s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m5s
CI/CD Pipeline / Validate - Security (push) Successful in 7m37s
CI/CD Pipeline / Unit Tests (push) Successful in 8m17s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (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 / 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
2026-09-11 19:05:40 +08:00
xiaoxia c49c855533 Merge branch 'fix/ai-avatar-title-cover-source' into develop 2026-09-11 19:05:25 +08:00
xiaoying-agent baed0c6431 fix(ai-avatar, P0): 数字人成片入库兜底project_id + 封面抽帧加速 + cv2依赖
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
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 / Check if frontend-only change (pull_request) Successful in 2s
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 / Retag skipped Staging Web 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 / 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 / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 22s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 56s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 55s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m45s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m12s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 3m26s
AI Code Review / AI Code Review (pull_request) Successful in 6m25s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 8m4s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 9m55s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m15s
P0 Bug:AI数字人视频渲染成功但成片库看不到
- packages/domain/generated_video.py: GeneratedVideo.create 去掉 project_id/generation_task_id 非空必填校验(AI数字人无项目概念,project_id为空;lipsync路径下task_id也可能为空),name/file_url仍强制非空
- apps/api/app/services/ai_avatar_render_service.py: 第7步保存成片时,project_id为空兜底为'ai_avatar',generation_task_id为空兜底为job.id;except块日志从logger.warning改为logger.error+exc_info=True,避免异常被吞

优化:封面抽帧加速(解决60s超时)
- apps/api/app/services/ai_avatar_cover_service.py: COVER_POLL_INTERVAL 3s→1s,COVER_MAX_POLL_ATTEMPTS 20→15(最长15s,之前60s);max_frames默认已是5
- requirements.txt: 加 numpy==1.26.4 + opencv-python-headless==4.10.0.84(cover_frame_scorer 用cv2做清晰度/亮度/色彩评分,API镜像之前缺cv2只能回退默认分50.0)

测试适配:
- 4处GeneratedVideo单测:从期望ValueError改为允许空串/空白归一化
- test_ai_avatar_emotion_tts_lipsync.py: poll_interval断言1.0,max_poll_attempts断言15
2026-09-11 18:45:21 +08:00
xiaoxia 3c817a2ffe fix(ai-avatar): 素材预览不叠加标题 + 封面接口传title_config + 渲染后封面避免双标题
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
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 / 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 33s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 33s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m14s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m32s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m56s
AI Code Review / AI Code Review (pull_request) Successful in 6m37s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 10m16s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m18s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m20s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m42s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m49s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m36s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 6m39s
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 1s
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
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 55s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m11s
1. PanelVideoSelector: 移除素材视频上的标题叠加预览,标题只在对口型预览和最终成片上展示
2. generateSmartCover API: 传入title_config参数,让后端在对口型视频帧上用drawtext叠加标题
3. AiAvatarPage: 调用generateSmartCover时传入state.titleConfig;移除传给PanelVideoSelector的titleConfig prop
4. ai_avatar_render_service: 渲染完成后自动抽封面时不传title_config,避免最终视频已有标题再叠加导致双重标题
2026-09-11 18:29:23 +08:00
xiaoxia 96bf62b00c fix(P0): 成品库分页加载更多 + AI数字人渲染传 project_id 入库 (#1862)
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 0s
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Validate - Style (push) Successful in 1m26s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m46s
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 2m24s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Integration Tests (push) Successful in 3m31s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m33s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m36s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m9s
CI/CD Pipeline / Validate - Security (push) Successful in 7m14s
CI/CD Pipeline / Unit Tests (push) Successful in 8m47s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 18:23:32 +08:00
xiaoxia 0b16e08d09 chore(ai-avatar): generateSmartCover 接口超时 60s → 120s (#1861)
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 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 16s
CI/CD Pipeline / Integration Tests (push) Successful in 1m16s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m27s
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 - Style (push) Successful in 1m52s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m36s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m24s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m27s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m9s
CI/CD Pipeline / Unit Tests (push) Successful in 6m34s
CI/CD Pipeline / Validate - Security (push) Successful in 10m41s
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 / Build Production API Image (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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 15:58:19 +08:00
xiaoxia 33510b8dbf Merge pull request 'fix(ai-avatar, P0): FFmpeg subprocess list修复32512 + Celery异常raise + 投递失败写DB + 封面drawtext标题 + audio/x-wav白名单' (#1859) from fix/ai-avatar-ffmpeg-cover-wav 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 / 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 19s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 20s
CI/CD Pipeline / Build Staging API Image (push) Successful in 37s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m4s
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 1m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m14s
CI/CD Pipeline / Validate - Style (push) Successful in 2m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-11 15:52:55 +08:00
xiaoying-agent ec2fb1c241 test(ai-avatar): 适配 subprocess list 形式的单测 mock 与 preset 断言
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 / PR Build Web Image (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 / 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 28s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m4s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m1s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m42s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m57s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m43s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m3s
AI Code Review / AI Code Review (pull_request) Successful in 6m20s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m58s
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) Failing after 2s
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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m29s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 32s
- execute_render 两个用例: os.system mock → subprocess.run mock,返回 CompletedProcess(returncode=0)
- preset 三个用例: 断言从 '-preset veryfast' 字符串包含改为 list 元素相邻校验,兼容 list[str] 形式
- 42/42 相关测试通过
2026-09-11 15:12:55 +08:00
xiaoxia 3cd8910f73 feat(ai-avatar): 封面预览实时显示标题叠加效果 + getRenderJob 60s 超时 (#1860)
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 / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (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 / Build Staging Web Image (push) Successful in 20s
CI/CD Pipeline / Build Staging API Image (push) Successful in 57s
CI/CD Pipeline / Integration Tests (push) Successful in 1m23s
CI/CD Pipeline / Validate - Style (push) Successful in 2m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m14s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m13s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m2s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m0s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m3s
CI/CD Pipeline / Unit Tests (push) Successful in 8m1s
CI/CD Pipeline / Validate - Security (push) Successful in 9m20s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 15:00:22 +08:00
CI Bot 1b76821307 style: auto-format with black + isort + ruff + 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 1s
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 3s
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 / 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 / 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 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m6s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m14s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m41s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m52s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m24s
AI Code Review / AI Code Review (pull_request) Successful in 6m29s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m39s
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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 4s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m58s
2026-09-11 06:27:13 +00:00
xiaoxia-agent 2c76d55d2b fix(ai-avatar, P0): FFmpeg命令改为subprocess list形式修复32512 + Celery异常raise + 投递失败写DB + 封面drawtext标题 + TTS audio/x-wav白名单
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 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 / 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 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 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m19s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m29s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m47s
CI/CD Pipeline / Validate - Style (pull_request) 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
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
P0:
- ai_avatar_render_service._build_ffmpeg_command返回list, subprocess.run(shell=False)
  修复OSS预签名URL含&被sh解释为命令分隔符导致exit 127(32512)
- cover_extract命令同步改为list+subprocess.run, 超时60s, 非致命失败
- execute_render两个except块末尾加raise, 确保Celery标记任务FAILURE而非SUCCESS
- ffmpeg stderr写入error_message末尾800字符, 前端可见具体错误
- routes两处delay()异常: job.status=failed + error_message='任务提交失败:...' + db.commit
- lipsync_tts.safe_download_bytes白名单补audio/x-wav(CosyVoice可能返回该MIME)

P1:
- ai_avatar_cover_service新增apply_title_to_cover: ffmpeg drawtext叠加标题
  竖屏封面720x1280, 失败回退无标题原始帧
- generate_smart_cover/persist_cover_to_oss新增title_config参数
- SmartCoverRequest schema新增可选title_config字段
- smart-cover接口透传body.title_config; execute_render管线透传job.title_config

仅修改5个后端文件, 不改其他功能.
2026-09-11 14:22:45 +08:00
xiaoxia 6f36abae9c fix(worker): 注册 ai_avatar_render Celery 任务到 Worker,修复 AI数字人渲染卡 0% (#1846)
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 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
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 1m54s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m38s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m18s
CI/CD Pipeline / Validate - Style (push) Successful in 2m22s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m40s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m22s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m29s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m29s
CI/CD Pipeline / Validate - Security (push) Successful in 7m45s
CI/CD Pipeline / Unit Tests (push) Successful in 8m45s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 11:22:47 +08:00
xiaoxia e7ab963ae3 fix(web, P0): nginx entrypoint 在 CI bind mount 场景不应 rm default.conf(staging 502 阻塞部署) (#1856)
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 / 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 6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 34s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m3s
CI/CD Pipeline / Integration Tests (push) Successful in 2m29s
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 - Style (push) Successful in 3m19s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m53s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m1s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m14s
CI/CD Pipeline / Unit Tests (push) Successful in 9m15s
CI/CD Pipeline / Validate - Security (push) Successful in 9m34s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 11:11:49 +08:00
xiaoxia 9c0d4b136f chore: trigger redeploy after nginx entrypoint fix
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 58s
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 1m27s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m45s
CI/CD Pipeline / Validate - Style (push) Successful in 1m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m58s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m34s
CI/CD Pipeline / Validate - Security (push) Successful in 6m3s
CI/CD Pipeline / Unit Tests (push) Successful in 6m22s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (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-11 10:49:25 +08:00
xiaoxia 387514c111 fix(web, P0): nginx entrypoint ln -sf 替换已有文件失败导致web容器无限重启,阻塞staging部署 (#1855)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (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 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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Build Staging API Image (push) Successful in 36s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m12s
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 1m54s
CI/CD Pipeline / Integration Tests (push) Successful in 1m54s
CI/CD Pipeline / Validate - Style (push) Successful in 2m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m9s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m33s
CI/CD Pipeline / Validate - Security (push) Successful in 6m46s
CI/CD Pipeline / Unit Tests (push) Successful in 7m32s
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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / CI Gate (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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 10:35:21 +08:00
xiaoxia 76cdb15c6b fix(lipsync, P0): Worker运行时ModuleNotFoundError导致任务卡在tts_processing (#1854)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Build Staging API Image (push) Successful in 28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m27s
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 1m47s
CI/CD Pipeline / Integration Tests (push) Successful in 1m58s
CI/CD Pipeline / Validate - Style (push) Successful in 2m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m0s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m47s
CI/CD Pipeline / Validate - Security (push) Successful in 5m40s
CI/CD Pipeline / Unit Tests (push) Successful in 7m43s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 09:52:05 +08:00
xiaoxia 29ca51da9c fix(web): 烤入所有 nginx 配置,按 APP_ENV 运行时切换,彻底解决 staging/prod 配置错乱 (#1853)
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Integration Tests (push) Successful in 1m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m40s
CI/CD Pipeline / Validate - Style (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m16s
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 / Frontend Unit Tests (push) Successful in 4m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m12s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 5m30s
CI/CD Pipeline / Unit Tests (push) Successful in 7m44s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 09:02:01 +08:00
xiaoxia a582d3b4dc fix(lipsync, P0): Worker imports路径修复,任务卡在tts_processing (#1852)
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 / 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 12s
CI/CD Pipeline / Build Staging API Image (push) Successful in 19s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m28s
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 1m49s
CI/CD Pipeline / Integration Tests (push) Successful in 1m51s
CI/CD Pipeline / Validate - Style (push) Successful in 2m34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m33s
CI/CD Pipeline / Validate - Security (push) Successful in 4m23s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m3s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m43s
CI/CD Pipeline / Unit Tests (push) Successful in 8m4s
CI/CD Pipeline / Build Production API 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 / Build Production Web 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 08:05:11 +08:00
xiaoxia 0ad33d429d fix(lipsync, P0): TTS异步任务改用@shared_task,解决Worker不注册任务导致卡tts_processing (#1851)
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 / Frontend Lint (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 / Build Staging Web Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 59s
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 1m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m55s
CI/CD Pipeline / Validate - Style (push) Successful in 2m1s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m2s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m36s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m4s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m24s
CI/CD Pipeline / Validate - Security (push) Successful in 6m2s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m54s
CI/CD Pipeline / Unit Tests (push) Successful in 8m3s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-11 01:07:09 +08:00
xiaoxia 582f73c2f2 fix(worker): 修复 worker 镜像漏 COPY apps/api/app 目录导致转码队列不消费(Staging E2E 阻塞) (#1850)
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 / 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 9s
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m1s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m13s
CI/CD Pipeline / Integration Tests (push) Successful in 1m36s
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 - Style (push) Successful in 2m23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m46s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m33s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
CI/CD Pipeline / Unit Tests (push) Successful in 8m11s
CI/CD Pipeline / Validate - Security (push) Successful in 24m36s
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 / 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
2026-09-10 22:56:39 +08:00
xiaoxia 9ea014c39b fix(lipsync-tts): 修正 celery_app 导入路径,修复 staging api 启动阻断 (#1848)
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 / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (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 19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 11s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 17s
CI/CD Pipeline / Build Staging API Image (push) Successful in 56s
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 / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m39s
CI/CD Pipeline / Validate - Style (push) Successful in 2m28s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m34s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m18s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m15s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m24s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m59s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m22s
CI/CD Pipeline / Unit Tests (push) Successful in 8m20s
CI/CD Pipeline / Validate - Security (push) Successful in 26m42s
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-10 21:19:41 +08:00
xiaoxia 00a6516543 fix(ci): auto-fix 脚本补充 ruff check --fix(含 F401 等 unsafe fix)(#1847)
CI/CD Pipeline / Check if frontend-only change (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 API Image (push) Successful in 36s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 37s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m23s
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 / Deploy Staging (Watchtower auto-deploy) (push) Failing after 4m18s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Failing after 12m58s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m31s
CI/CD Pipeline / Validate - Style (push) Successful in 2m13s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m17s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m25s
CI/CD Pipeline / Unit Tests (push) Successful in 8m23s
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
2026-09-10 20:48:14 +08:00
xiaoxia caa4ce118c fix(ci): auto-fix 脚本补充 ruff check --fix(含 F401 等 unsafe fix)
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 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 / 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 / Integration Tests (pull_request) Successful in 1m8s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m31s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m1s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m41s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m57s
AI Code Review / AI Code Review (pull_request) Successful in 6m40s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 9m25s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m31s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 24m37s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 11s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m42s
- fix_python() 在 black/isort 之后追加 ruff check --fix --unsafe-fixes
- 目标范围与 validate_style.sh 对齐:apps packages tests(alembic 被 ruff exclude,scripts 不在检查范围)
- ruff 不可用或修复后仍有残余问题时不阻断,剩余 lint 由 style check 继续拦截
- workflow step 名同步更新

解决 F401 等 ruff 可自动修复错误无法自动推修复、开发者反复打回的问题
2026-09-10 20:20:25 +08:00
xiaoxia b3f8c00522 fix(ai-avatar): 封面预览改为竖屏 9:16 比例 (#1845)
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
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 / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 50s
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 / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Validate - Style (push) Successful in 2m31s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m27s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 4m58s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 8m21s
CI/CD Pipeline / Validate - Security (push) Successful in 24m35s
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-10 20:07:57 +08:00
xiaoxia f73837c7b4 feat: AI数字人对口型视频生成速度优化 - FFmpeg编码提速 + TTS合成异步化 (#1844)
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 11s
CI/CD Pipeline / Build Staging API Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 28s
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 / Integration Tests (push) Successful in 2m3s
CI/CD Pipeline / Validate - Style (push) Successful in 2m44s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 4m15s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m40s
CI/CD Pipeline / Unit Tests (push) Successful in 9m28s
CI/CD Pipeline / Validate - Security (push) Successful in 22m56s
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 / 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
2026-09-10 19:43:08 +08:00
xiaoxia f40375749b feat(ai-avatar): 渲染进度弹窗 + 实时百分比轮询 (#1839)
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 / 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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 12s
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 1m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m27s
CI/CD Pipeline / Validate - Style (push) Successful in 2m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m44s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m34s
CI/CD Pipeline / Validate - Security (push) Successful in 6m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m11s
CI/CD Pipeline / Unit Tests (push) Successful in 9m30s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-10 18:48:22 +08:00
xiaoxia af6772f1ba Merge pull request 'feat(ai-avatar): 渲染完成后自动保存成片记录到成片库' (#1841) from feat/auto-save-clip-on-render-complete 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 / 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 11s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 58s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 58s
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 1m49s
CI/CD Pipeline / Integration Tests (push) Successful in 1m51s
CI/CD Pipeline / Validate - Style (push) Successful in 2m31s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m24s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m45s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m36s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m29s
CI/CD Pipeline / Validate - Security (push) Successful in 6m46s
CI/CD Pipeline / Unit Tests (push) Successful in 8m58s
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 cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
2026-09-10 18:33:14 +08:00
CI Bot 168fcce36e 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 1s
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 / 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 15s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 16s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m11s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m23s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m38s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m41s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m22s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 6m46s
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
AI Code Review / AI Code Review (pull_request) Successful in 6m54s
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 4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m59s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 9s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
2026-09-10 10:25:41 +00:00
xiaoxia ad580c1a4d test: 新增成片自动创建单元测试 + 修复 smart_cover mock
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 / PR Build Web Image (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 / 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 23s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 25s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m0s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m16s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m23s
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
- 新增 test_execute_render_success_creates_clip_record:验证渲染完成后自动创建成片记录
- 新增 test_execute_render_clip_failure_does_not_affect_render:验证成片创建失败不影响渲染任务
- 修复 test_smart_cover_selects_best_frame_and_persists:补充 get_download_url mock
2026-09-10 18:23:26 +08:00
CI Bot bcaf1d967a style: auto-format with black + isort + prettier [skip ci-format-check] 2026-09-10 18:23:03 +08:00
xiaoxia dc12c62190 feat(ai-avatar): 渲染完成后自动保存成片记录到成片库
- 在 execute_render 方法的 job.status=completed 之后,自动创建 GeneratedVideo 记录
- 复用现有 GeneratedVideo 模型和 SQLAlchemyGeneratedVideoRepository
- 成片记录包含:视频URL、封面URL、时长、用户ID、项目ID
- 生成参数中记录来源(render_job_id)以便追溯
- 创建失败时仅打 warning 日志,不影响渲染任务状态
2026-09-10 18:23:03 +08:00
xiaoxia 4a2140cd8b Merge pull request 'fix: 清理22个F841 unused-variable,解除CI Validate-Style阻塞' (#1842) from fix/f841-unused-variables into develop
CI/CD Pipeline / Check if frontend-only change (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 12s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 42s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m29s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Failing after 14m35s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m48s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m55s
CI/CD Pipeline / Validate - Style (push) Successful in 2m25s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m55s
CI/CD Pipeline / Validate - Security (push) Successful in 6m46s
CI/CD Pipeline / Unit Tests (push) Successful in 7m58s
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 / 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
2026-09-10 18:01:33 +08:00
xiaoxia a526a28865 fix(ai-avatar): 修复智能封面502 - cover_service视频URL重签+轮询扩容+连接池+路由异常兜底 (#1843)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 12s
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 - Python (mypy + alembic) (push) Successful in 1m25s
CI/CD Pipeline / Build Staging API Image (push) Successful in 29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m5s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 51s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Validate - Style (push) Failing after 3m1s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
2026-09-10 17:55:41 +08:00
xiaoxia ff833ce7e0 fix: 清理22个F841 unused-variable,解除CI Validate-Style阻塞
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 / 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 Web Image (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 / 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 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 / PR Build API Image (pull_request) Successful in 30s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m6s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1m8s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m1s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
AI Code Review / AI Code Review (pull_request) Successful in 6m20s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m23s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Failing after 3s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m33s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 41s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 37s
全部为 except Exception as e: 中变量e未使用的模式,
ruff自动修复将 e: 改为 :。不涉及任何业务逻辑变更。
2026-09-10 17:39:08 +08:00
CI Bot 129e5a957d style: auto-format with black + isort + prettier [skip ci-format-check]
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 / Frontend Lint (push) Has been skipped
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 11s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 44s
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 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 1m54s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m5s
CI/CD Pipeline / Validate - Style (push) Failing after 2m52s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m38s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m34s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m21s
CI/CD Pipeline / Unit Tests (push) Failing after 7m50s
CI/CD Pipeline / Validate - Security (push) Successful in 7m53s
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 / 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
2026-09-10 07:54:48 +00:00
灵应 9a323a2a11 feat(ai-avatar): 对口型预览标题支持拖拽定位
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
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 / Build Staging API Image (push) Successful in 12s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 14s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 24s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m26s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m14s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m47s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Failing after 12m56s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m23s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
对齐智能剪辑 FrontendPreviewPlayer 的拖拽逻辑:
- pointer 事件捕获 + 拖拽时直接改 DOM(不触发 React 重渲染)
- 拖拽结束时调用 onTitlePositionChange 保存 pos_x/pos_y
- 标题样式加 cursor: grab/grabbing + pointerEvents: auto
- 主页面传 updateTitleConfig 回调
2026-09-10 15:38:16 +08:00
灵应 bf6d02f26f fix(ai-avatar): 标题分行/封面签名URL/渲染b_roll结构对齐后端
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 (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 / Build Staging API Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m5s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
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 1m57s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Style (push) Failing after 2m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m47s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m53s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m36s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
1. 标题 / 分行: 对齐智能剪辑 FrontendPreviewPlayer 的 split(/[//]/) 逻辑,/ 和 / 都会分行
2. 封面签名URL: 私有桶 OSS upload 后必须 get_download_url 签名,否则前端无法加载
3. 渲染 b_roll_segments 结构: 前端 {asset: AssetItem} 转后端 {asset_url: string},对齐 BRollSegment schema
4. #1821 对口型加速工单已关闭(收益有限,MediaKit GPU 排队是主要瓶颈)
2026-09-10 15:33:33 +08:00
CI Bot e730043b2f style: auto-format with black + isort + prettier [skip ci-format-check]
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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 17s
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
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 1m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m38s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m27s
CI/CD Pipeline / Validate - Style (push) Failing after 2m3s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m9s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m16s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m50s
CI/CD Pipeline / Validate - Security (push) Successful in 6m4s
CI/CD Pipeline / Unit Tests (push) Successful in 6m22s
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
CI/CD Pipeline / Staging E2E Tests (push) Failing after 20m53s
2026-09-10 05:47:42 +00:00
CI Test 6b925a6f4d fix(#1830): 改进异常处理 - 缩小异常类型并添加日志记录
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 / PR Build Worker Image (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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 20s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
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 1m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m25s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
2026-09-10 13:44:51 +08:00
灵应 211166f203 fix(ai-avatar): 预览缩放/标题叠加/渲染422/智能封面超时
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
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 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 1m43s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
1. 对口型预览缩小: .aa-lipsync-preview 加 max-width:240px + 居中,9:16 等比不变,约原大小 1/2
2. 标题实时叠加预览: 视频容器 relative + 标题 absolute 定位(top/bottom/center 按配置),字号按 0.55 倍等比缩
3. 渲染 422 修复: submitRender 删掉 resolution 字段(后端 CreateAiAvatarRenderRequest schema 无此字段,Pydantic 校验失败)
4. 智能封面超时: generateSmartCover 接口超时从默认 10s 加到 60s(MediaKit 抽帧+评分+OSS转存耗时 >10s)

不改其他功能,无冗余代码。
2026-09-10 13:42:16 +08:00
灵应 7dc2954d8f fix(ai-avatar): 恢复原始UI样式,页面拆分为两步骤仅做UI调整
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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m5s
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 1m28s
CI/CD Pipeline / Integration Tests (push) Successful in 1m33s
CI/CD Pipeline / Validate - Style (push) Successful in 2m3s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m28s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m38s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m29s
CI/CD Pipeline / Validate - Security (push) Successful in 5m50s
CI/CD Pipeline / Unit Tests (push) Successful in 7m46s
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 / Build Production API 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
- 恢复 AiAvatar.css 为原始版本(1178行逐字节一致),仅在末尾追加70行步骤导航/分步面板宽度样式
- 保持原始横向 flex 布局与全部原有 class/样式,撤销上一版纵向堆叠改动
- 步骤1:出镜视频/配音库/文案(3个横向面板)+ 下一步按钮
- 步骤2:对口型预览(含插入画面)/标题配置/封面&生成(3个横向面板)+ 上一步按钮
- PanelScriptAndLipsync 拆为 PanelScript + PanelLipsyncPreview,业务逻辑/props/弹窗全部原样保留
- 不改任何功能,不动其他页面
2026-09-10 13:16:19 +08:00
xiaoxia 6c77b066de fix(lipsync): 私有桶 OSS 媒体 URL 重签修复对口型 403 (#1839) (#1837)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (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 Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 14s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
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 1m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Style (push) Successful in 1m55s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m43s
CI/CD Pipeline / Validate - Security (push) Successful in 5m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m19s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m10s
CI/CD Pipeline / Unit Tests (push) Successful in 7m49s
CI/CD Pipeline / Build Production API 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 / Build Production Web Image (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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-10 13:05:28 +08:00
xiaoxia b5b324d94e chore: 删除前端冗余代码 (-1215行) #1831 (#1836)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
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 / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 54s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m17s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Validate - Python (mypy + alembic) (push) Successful in 1m41s
CI/CD Pipeline / Integration Tests (push) Successful in 1m58s
CI/CD Pipeline / Validate - Style (push) Successful in 2m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m13s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m45s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m9s
CI/CD Pipeline / Validate - Security (push) Successful in 5m21s
CI/CD Pipeline / Unit Tests (push) Successful in 7m37s
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 / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-10 12:47:29 +08:00
xiaoxia 9fff6ff8e2 feat(ai-avatar): AI数字人页面拆分为两步
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 (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
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 / Build Staging API Image (push) Successful in 32s
CI/CD Pipeline / Validate - Style (push) Successful in 1m52s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m59s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m10s
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 2m42s
CI/CD Pipeline / Integration Tests (push) Successful in 3m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Security (push) Successful in 4m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m24s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m13s
CI/CD Pipeline / Unit Tests (push) Successful in 7m59s
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 / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
- 步骤1:出镜视频 / 配音库 / 文案
- 步骤2:对口型预览(含插入画面) / 标题配置 / 封面&生成
- 新增 PanelScript.tsx(文案面板)
- 新增 PanelLipsyncPreview.tsx(对口型预览面板)
- 删除 PanelScriptAndLipsync.tsx(已拆分)
- 补充对应 CSS 样式
2026-09-10 12:28:40 +08:00
xiaoxia e679b54ebc Merge PR #1827: AI数字人v3 完整联调版(TTS直生+smart-cover+前后端冲突解决)(#1827)
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 / Frontend Lint (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 Worker Image (push) Successful in 22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m25s
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 / Integration Tests (push) Successful in 1m44s
CI/CD Pipeline / Validate - Style (push) Successful in 2m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m8s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m40s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m51s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m11s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m21s
CI/CD Pipeline / Unit Tests (push) Successful in 8m33s
CI/CD Pipeline / Validate - Security (push) Successful in 18m28s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (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
2026-09-09 23:45:16 +08:00
CI Bot 01ae42edc6 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 1s
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 / 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 32s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
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 Web Image (pull_request) Successful in 1m17s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m26s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m32s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m48s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m7s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m12s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 3m59s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 6m58s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m33s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 21m10s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 9s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m19s
2026-09-09 15:21:46 +00:00
deploy-agent a199fb0fc4 test(lipsync): 采用后端解冲突版 test_lipsync_routes.py(v3 submit_synthesize_task 契约)
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 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 / PR Build Web Image (pull_request) Successful in 44s
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 / PR Build Worker Image (pull_request) Successful in 45s
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 / PR Build API Image (pull_request) Successful in 55s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m39s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m53s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m0s
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 / 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
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
4efa71ec 合并时 tests/unit/test_lipsync_routes.py 误取了 develop #1809 版本
(synthesize_speech/route._resolve_voice_id/断言 audio_url 移除),与合并后
v3 #1822 双模式实现不一致,导致 7 个单测失败。改用后端解冲突分支
backend/ai-avatar-merge-1827 (0e994072) 中已适配 v3 的版本:
mock submit_synthesize_task、emotion 兴奋→excited、双模式 audio_url 保留。
本地验证该文件 28 passed、lipsync/avatar/tts 相关 79 passed。
2026-09-09 23:19:19 +08:00
CI Bot 4efa71ec36 merge: 合并 develop 解决 AI数字人冲突(v3新契约 + develop增强 双保留)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 0s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
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 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 47s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 48s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m0s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m9s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m9s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m26s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 7m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m32s
CI/CD Pipeline / Validate - Security (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
前端7文件(基于 v3 契约为主干融合 develop 增强,#1828口径):
- 对口型模式A: video_url+voice_id+script_text+speed+emotion(英文枚举,禁传video_asset_id)
- title_config 按 build_title_drawtext_filter 真实字段映射(text/font_size/font_color/position默认bottom/enabled/bold/stroke/shadow), 不传 titles[]/fontSize/frame
- 智能封面 POST /ai-avatar/render/smart-cover; 语速情绪全链路透传
- 保留 develop: MediaKit中间状态/非阻塞轮询(running中间态)/标题TextArea实时预览+标题库(TitleLibraryModal)
后端4文件取 backend/ai-avatar-merge-1827(0e994072) 已解版本(双模式/TTS直生/emotion透传/中间状态)
tsc ai-avatar 零错误, prettier 全合规
2026-09-09 23:02:15 +08:00
deploy-agent f72d640e8c style(ai-avatar): prettier 格式化 PanelTitleConfig/TitleLibraryModal
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 2s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 29s
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 33s
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 Web Image (pull_request) Successful in 1m23s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m40s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m46s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m2s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m14s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 5m21s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 8m15s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m40s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 24m23s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
修复 #1828 前端对接代码的 prettier check 失败,仅格式调整无逻辑改动。
2026-09-09 21:11:17 +08:00
xiaoxia f4cac1dc6e feat(ai-avatar): 前端对接 #1826 后端契约 — 模式A对口型/语速情绪/智能封面/标题字段映射 (#1828)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
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 / 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 / PR Build API Image (pull_request) Successful in 33s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m1s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m26s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 2m59s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m32s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 3m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m9s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m48s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 6m29s
AI Code Review / AI Code Review (pull_request) Successful in 7m8s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 9m28s
CI/CD Pipeline / Validate - Security (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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
2026-09-09 21:00:46 +08:00
xiaoxia 9f94ef24c1 test(ai-avatar): 同步 #1822 过期测试断言
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 2s
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 / PR Build API Image (pull_request) Successful in 53s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m7s
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 / Validate - Style (pull_request) Successful in 1m25s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m38s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m58s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m9s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m12s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m1s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m14s
AI Code Review / AI Code Review (pull_request) Successful in 6m48s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m11s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m40s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 28m21s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
- test_tts_preview: preview 路由现在总是透传 emotion(默认空串),
  4 处 synthesize_speech 断言补充 emotion=''
- test_ai_avatar_render_service: script_id 改为可选后,
  空值应规范化为空串而非抛 ValueError;lipsync_job_id 仍必填

全量 14967 passed, 0 failed
2026-09-09 20:31:58 +08:00
CI Bot 7adcf28b76 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 / 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 / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
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 API Image (pull_request) Successful in 42s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
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 / Validate - Style (pull_request) Successful in 1m41s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m56s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m56s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m57s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m8s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m27s
AI Code Review / AI Code Review (pull_request) Successful in 6m40s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m35s
CI/CD Pipeline / Validate - Security (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-09 12:11:22 +00:00
xiaoxia 6e99e64f73 feat(ai-avatar): #1822 TTS直生对口型 + 语速/情绪透传 + MediaKit智能封面 (#1826)
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 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 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 44s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 45s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m41s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m51s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m58s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m0s
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 / 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
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
TTS直生/情绪语速透传/智能封面/契约文档/script_id可选 #1797 #1822
2026-09-09 20:03:33 +08:00
xiaoxia 4d09bd630e fix(ai-avatar): 契约文档标题字段纠正 + script_id 改为可选
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m37s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 6m25s
- 文档 §5/§6:标题是单个 title_config dict(非 titles[] 数组),字段以
  build_title_drawtext_filter() 为准:text/content、font_size/size、
  font_color/color、position(top/center/bottom/custom)、pos_x/pos_y、bold、
  stroke、shadow、enabled;删除不存在的 titles[]/fontSize/frame/start/end 描述
- script_id 改为可选:手动输入文案/TTS 直生场景不关联文案库条目,留空不校验,
  避免手动文案用户被 ScriptNotFound 卡住;选了文案库时仍做归属校验
- 迁移 074:ai_avatar_render_jobs.script_id 默认空串
- 新增 schema 单测(script_id 空/空白/title_config 单 dict),94 测试全绿

Refs #1797 #1826
2026-09-09 19:55:01 +08:00
xiaoxia 97359ef77f fix: 在 worker Dockerfile 中添加 HEALTHCHECK 指令
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 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 / Frontend Lint (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 19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 37s
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 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 2m1s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Style (push) Successful in 2m54s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m51s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m23s
CI/CD Pipeline / Unit Tests (push) Successful in 9m8s
CI/CD Pipeline / Validate - Security (push) Successful in 9m31s
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 / 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
将健康检查命令直接写入 Docker 镜像,确保 Watchtower 重建容器时使用正确的健康检查。
修复 Worker 容器持续显示 unhealthy 的问题。
2026-09-09 19:16:28 +08:00
xiaoxia 8052f245ec fix: 在 worker Dockerfile 中添加 HEALTHCHECK 指令
将健康检查命令直接写入 Docker 镜像,确保 Watchtower 重建容器时使用正确的健康检查。
修复 Worker 容器持续显示 unhealthy 的问题。
2026-09-09 19:15:46 +08:00
xiaoxia a27ed596b4 feat(ai-avatar): TTS直生对口型 + 语速/情绪透传 + MediaKit智能封面 + 契约文档
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m52s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 6m37s
- 对口型支持 TTS 直生模式:POST /lipsync/jobs 传 voice_id+script_text(+speed/emotion),
  后端内部调 CosyVoice 合成音频→转存OSS→提交 MediaKit;保留 audio_url 直接音频模式
- cosyvoice_service: 新增 emotion 参数 + normalize_emotion(中文 自然/兴奋/沉稳/亲切
  映射 natural/excited/calm/friendly),空值不透传
- TTS 链路 speed/emotion 全链路透传:schema→use_case(metadata)→workflow(单段/分段/重合成)
  →submit_synthesize_task(rate/emotion);preview 即时试听同步
- lipsync refresh: 中间状态(running/processing)同步DB;completed 输出视频转存自家OSS防过期
- 修复 lipsync/ai-avatar 路由 current_user.id → current_user.user.id(AuthenticatedUser 无 .id)
- 新增 POST /ai-avatar/render/smart-cover 独立封面接口:复用 MediaKit extract_frames
  + cover_frame_scorer 评分选最佳帧(非 FFmpeg 首帧),渲染管线封面同样优先智能选帧
- 迁移 073: lipsync_jobs 增加 voice_id/script_text/speed/emotion 列,audio_url 改可空
- docs/ai-avatar-api-contract-1822.md: 前后端接口契约 + title_config 字段清单
- 新增 11 个单测(情绪归一化/payload透传/TTS直生/中间状态/智能封面),相关 82 测试全绿

Refs #1797 #1822
2026-09-09 19:13:03 +08:00
xiaoxia 1bd0f0c1ae Merge pull request 'fix: Worker health check 扫描全部 /proc 进程修复 unhealthy 误判' (#1825) from fix/worker-healthcheck-ci-e2e into develop
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 5s
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
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 1m16s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m37s
CI/CD Pipeline / Validate - Style (push) Successful in 3m6s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m27s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 19:09:14 +08:00
xiaoxia 54defa4429 fix: Worker health check 扫描全部 /proc 进程修复 unhealthy 误判
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m1s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
PR Automation / Auto Approve on CI Green (pull_request) Failing after 13m19s
PR Automation / Auto Merge on CI Green + Approved (pull_request) 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 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 / 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 15s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 16s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 11s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 30s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m16s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m58s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m53s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 6m33s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m20s
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 / 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
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
问题:Worker 容器内无 pgrep/ps 工具,旧 health check 使用
'grep -q celery /proc/1/cmdline' 检查 PID 1 的 cmdline,
但 PID 1 是 entrypoint-worker.sh(bash 脚本),不含 celery 关键字,
导致 health check 永远返回 unhealthy。

同时部署脚本中 sh -c 嵌套引号导致 $pid 变量被外层 shell 提前展开为空。

修复:改用 grep -lq celery /proc/[0-9]*/cmdline 扫描所有进程 cmdline,
找到任意 celery 进程即判定 healthy。无需变量、无嵌套 shell、无 pgrep 依赖。

影响文件:
- infra/docker/compose.yml
- infra/docker/deploy-staging-registry.sh
- infra/docker/deploy-production-registry.sh
2026-09-09 18:42:17 +08:00
CI Bot da2302b7ad fix: 移除 PanelTitleConfig 中未使用的 getFontFamily 导入
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (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 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 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 19s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 37s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m11s
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 1m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m58s
CI/CD Pipeline / Validate - Style (push) Successful in 2m31s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m26s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m8s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m55s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m0s
CI/CD Pipeline / Validate - Security (push) Successful in 7m6s
CI/CD Pipeline / Unit Tests (push) Successful in 8m14s
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
2026-09-09 18:35:45 +08:00
CI Bot c06c095b74 fix: 修复 PanelTitleConfig 未使用导入导致的 TS6133 编译错误,完整实现 TextArea + TitleLibraryAutoComplete
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 (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 / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Integration Tests (push) Successful in 1m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m9s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m10s
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 - Style (push) Successful in 2m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m44s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m5s
CI/CD Pipeline / Validate - Security (push) Successful in 6m51s
CI/CD Pipeline / Unit Tests (push) Successful in 8m8s
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
2026-09-09 18:26:27 +08:00
CI Bot 5f54ac3707 feat: 标题输入改用TextArea多行+标题库自动补全,标题预览增大字号加描边阴影
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 / 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 4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Integration Tests (push) Successful in 1m17s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m43s
CI/CD Pipeline / Validate - Style (push) Successful in 1m50s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m50s
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 / Frontend Unit Tests (push) Successful in 4m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m35s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 5m59s
CI/CD Pipeline / Validate - Security (push) Successful in 7m16s
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
2026-09-09 18:16:28 +08:00
CI Bot 02b82466da fix: 使用 getFontFamily 替代原始字体字符串,修复 TS6133
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 / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 38s
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 1m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 55s
CI/CD Pipeline / Validate - Style (push) Successful in 1m41s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m50s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m50s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m57s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m0s
CI/CD Pipeline / Unit Tests (push) Successful in 6m32s
CI/CD Pipeline / Validate - Security (push) Successful in 7m36s
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
2026-09-09 17:42:23 +08:00
CI Bot 005cff1ce5 fix: 实现标题实时预览覆盖层,修复 titleConfig 未使用 TS6133 错误
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 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Integration Tests (push) Successful in 1m9s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m1s
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 2m9s
CI/CD Pipeline / Validate - Style (push) Successful in 2m16s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m5s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m48s
CI/CD Pipeline / Unit Tests (push) Successful in 7m41s
CI/CD Pipeline / Validate - Security (push) Successful in 8m25s
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 / Build Production API 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
2026-09-09 17:32:12 +08:00
CI Bot b1930e594d fix: 修复 TypeScript 编译错误(移除未使用 import、补充 props 类型、修正 TitleItem 字段名)
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 (push) Successful in 3s
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 / Build Staging API Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Integration Tests (push) Successful in 1m3s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m42s
CI/CD Pipeline / Validate - Style (push) Successful in 1m45s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Validate - Python (mypy + alembic) (push) Successful in 2m8s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m6s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m6s
CI/CD Pipeline / Unit Tests (push) Successful in 7m17s
CI/CD Pipeline / Validate - Security (push) Successful in 9m21s
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 / 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
2026-09-09 17:19:52 +08:00
CI Bot 8467f3fa94 fix: 修复 PanelTitleConfig.tsx 重复return语法错误 + lipsync.py B904 ruff告警
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 0s
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
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 / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 35s
CI/CD Pipeline / Integration Tests (push) Successful in 1m57s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m12s
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 2m26s
CI/CD Pipeline / Validate - Style (push) Successful in 2m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m49s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m18s
CI/CD Pipeline / Unit Tests (push) Successful in 8m9s
CI/CD Pipeline / Validate - Security (push) Successful in 9m24s
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-09 17:07:50 +08:00
CI Bot 93a4c1b639 style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m16s
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 / Integration Tests (push) Successful in 1m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 2m5s
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Validate - Style (push) Failing after 4m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m28s
CI/CD Pipeline / Unit Tests (push) Successful in 8m13s
CI/CD Pipeline / Validate - Security (push) Successful in 9m39s
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 / 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
2026-09-09 08:23:55 +00:00
xiaoxia a2f43926a5 feat: 克隆音色情绪参数传递(natural/excited/calm/friendly)
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 / Frontend Lint (push) Has been skipped
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 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 16s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 17s
CI/CD Pipeline / Integration Tests (push) Successful in 1m20s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m15s
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 2m6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m44s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m49s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 16:19:27 +08:00
saas-backend-agent eca530bd6f feat: CosyVoice 语音合成支持 emotion 情绪参数(natural/excited/calm/friendly)
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 (push) Successful in 3s
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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m36s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m38s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Integration Tests (push) Successful in 1m47s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
2026-09-09 16:15:49 +08:00
CI Bot 4556dff14c style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 36s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m2s
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 / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m43s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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) 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 Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 08:05:03 +00:00
xiaoxia 36df99d106 feat: 对口型视频编辑功能增强(标题库选择+实时预览+封面自动获取)
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 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 29s
CI/CD Pipeline / Integration Tests (push) Successful in 1m31s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m38s
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 - 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 / Frontend Unit Tests (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
2026-09-09 16:03:02 +08:00
saas-backend-agent 54deb9b549 fix: MediaKit 中间状态(running/processing)同步到 DB,不再卡在 submitted
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 (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 / Build Staging Web Image (push) Successful in 16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
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 53s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m3s
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m46s
CI/CD Pipeline / Validate - Style (push) Failing after 3m41s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m28s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m30s
CI/CD Pipeline / Unit Tests (push) Successful in 8m25s
CI/CD Pipeline / Validate - Security (push) Successful in 8m58s
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 / 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
2026-09-09 15:24:07 +08:00
CI Bot 288e0760df style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (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 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 33s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m41s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m13s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m9s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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) 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 Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 07:14:01 +00:00
saas-backend-agent 0c76967453 fix: GET /lipsync/jobs/{id} 加调试日志捕获 502 根因
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
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 1m10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m8s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m7s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
2026-09-09 15:11:29 +08:00
xiaoxia 8c7b1ff16a fix: 对口型轮询加调试日志,failed 状态显示错误信息
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 17s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m15s
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 / Integration Tests (push) Successful in 1m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m10s
CI/CD Pipeline / Validate - Style (push) Successful in 2m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m4s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m20s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 15:04:43 +08:00
saas-backend-agent 44f973c168 fix: GET /lipsync/jobs/{id} 改为非阻塞 — 先返回缓存,后台异步刷新
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 / Check push changed paths (push) 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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m2s
CI/CD Pipeline / Integration Tests (push) Successful in 1m44s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m0s
CI/CD Pipeline / Validate - Style (push) Successful in 2m21s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m51s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m52s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m49s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m32s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
上次修复让 GET 同步调 MediaKit,MediaKit 偶尔慢 60s 直接拖死轮询。
改为 BackgroundTasks 异步刷新:响应立即返回 DB 缓存状态,
MediaKit 刷新在 response 发送后后台执行,不阻塞前端。
最坏情况:某次轮询拿到旧状态,3 秒后下次轮询拿到新状态。
2026-09-09 14:58:53 +08:00
saas-backend-agent a17726c963 fix: GET /lipsync/jobs/{id} 非终态自动刷新 MediaKit 状态
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 (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
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 / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m6s
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 1m39s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m47s
CI/CD Pipeline / Validate - Style (push) Successful in 2m11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 57s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m33s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m22s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m18s
CI/CD Pipeline / Validate - Security (push) Successful in 6m54s
CI/CD Pipeline / Unit Tests (push) Successful in 8m45s
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
根因:前端轮询用 GET 接口查状态,但 GET 只读 DB 不刷新,
导致任务提交后 status 一直是 submitted,前端永远等不到 completed。

修复:GET 端点对非终态任务(pending/submitted/processing)自动
调用 refresh_job_status 从 MediaKit 拉最新状态,前端轮询即触发。
2026-09-09 14:46:19 +08:00
xiaoxia 3d179528cf fix: 对口型弹窗只能点取消按钮关闭,点击空白不关闭
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 (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 / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 53s
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 / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m49s
CI/CD Pipeline / Integration Tests (push) Successful in 1m54s
CI/CD Pipeline / Validate - Style (push) Successful in 2m26s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m16s
CI/CD Pipeline / Validate - Security (push) Successful in 6m28s
CI/CD Pipeline / Unit Tests (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
2026-09-09 14:39:01 +08:00
xiaoxia f33e2962e9 feat: 对口型生成弹窗(加载动画+进度+取消按钮)
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 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m5s
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 1m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Style (push) Successful in 2m32s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m3s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m41s
CI/CD Pipeline / Validate - Security (push) Successful in 6m28s
CI/CD Pipeline / Unit Tests (push) Successful in 8m30s
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 / 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
2026-09-09 14:26:54 +08:00
xiaoxia e96cf541b1 fix: safeUrl 未使用导致 TS6133 编译失败,Docker build 连续挂
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 (push) Successful in 3s
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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 35s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 44s
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 1m40s
CI/CD Pipeline / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / Validate - Style (push) Successful in 2m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m45s
CI/CD Pipeline / Validate - Security (push) Successful in 5m5s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m27s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m9s
CI/CD Pipeline / Unit Tests (push) Successful in 8m18s
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-09 13:44:31 +08:00
saas-backend-agent c7d611c827 fix: CosyVoice 返回的 audio_url 从 http:// 升级为 https://
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 25s
CI/CD Pipeline / Validate - Style (push) Successful in 1m8s
CI/CD Pipeline / Integration Tests (push) Successful in 1m36s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m47s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m3s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m52s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m7s
CI/CD Pipeline / Validate - Security (push) Successful in 5m39s
CI/CD Pipeline / Unit Tests (push) Successful in 7m58s
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 / Deploy Production (push) Has been skipped
CI/CD Pipeline / CI Gate (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
DashScope CosyVoice API 返回的音频 URL 是 http://,
前端 mixed content 策略下无法加载。
在 submit_synthesize_task 源头统一替换为 https://,
覆盖 TTS preview、对口型、TTS 异步任务全链路。
2026-09-09 13:33:16 +08:00
xiaoxia 5c8d27c3c1 debug: 对口型生成加 Console 调试日志
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 0s
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 6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m30s
CI/CD Pipeline / Integration Tests (push) Successful in 1m31s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m43s
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 - Style (push) Successful in 2m17s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
- 打印 videoId/voiceId/voiceType/textLen
- 打印 getAssetById 响应(file_url 前100字符)
- 打印 createLipsyncJob 请求 payload 和响应
- catch 打印 HTTP status + response data
- 帮助定位 422/400/500 具体原因
2026-09-09 13:30:17 +08:00
xiaoxia c4e0dcaee4 fix: add heading to Step 5 single video for E2E test (#1820)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 12s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 21s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m2s
CI/CD Pipeline / Integration Tests (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m16s
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 - Style (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m53s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 5m6s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m13s
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
Closes E2E 测试连续失败问题
2026-09-09 13:22:19 +08:00
xiaoxia 12696f35f8 fix: 克隆音色试听音频 URL HTTP→HTTPS 兼容(Mixed Content)
CI/CD Pipeline / Check if frontend-only change (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 / 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 / Check push changed paths (push) Successful in 12s
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
后端 /tts/preview 返回 dashscope OSS HTTP URL,
staging HTTPS 页面因 Mixed Content 阻止加载。
OSS 同时支持 HTTPS,前端临时替换协议。
后端应改用 sign_url 返回 HTTPS(已通知后端修复)。
2026-09-09 13:21:53 +08:00
saas-backend-agent 6601b8facb fix: lipsync/ai-avatar 路由 current_user.id → current_user.user.id
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 / Frontend Lint (push) Has been skipped
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 6s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m3s
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 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 1m57s
CI/CD Pipeline / Validate - Style (push) Successful in 2m42s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Validate - Security (push) Successful in 5m11s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m51s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m24s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m9s
CI/CD Pipeline / Unit Tests (push) Successful in 9m17s
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
根因:AuthenticatedUser 是 dataclass {user: User, session_id, token_type},
没有 .id 属性,用户 ID 在 .user.id。
staging 上所有 lipsync 端点 500(GET/POST)、ai-avatar 端点同理。
修复 11 处:lipsync.py(6) + ai_avatar_render.py(5)。
2026-09-09 13:04:34 +08:00
saas-backend-agent d6c5e66bba fix: lipsync/ai-avatar 路由 current_user.id → current_user.user.id
CI/CD Pipeline / Check if frontend-only change (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 39s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Dedup Check - skip PR tests when covered by push pipeline (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) 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 Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
根因:AuthenticatedUser 是 dataclass {user: User, session_id, token_type},
没有 .id 属性,用户 ID 在 .user.id。
staging 上所有 lipsync 端点 500(GET/POST)、ai-avatar 端点同理。
修复 11 处:lipsync.py(6) + ai_avatar_render.py(5)。
2026-09-09 13:02:01 +08:00
saas-backend-agent c0c9765eb0 debug: lipsync 路由增加详细错误暴露,定位 staging 500 根因
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 Worker Image (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 / Check push changed paths (push) Successful in 11s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
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 1m1s
CI/CD Pipeline / Integration Tests (push) Successful in 1m42s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m24s
CI/CD Pipeline / Validate - Style (push) Successful in 2m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m33s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m14s
CI/CD Pipeline / Validate - Security (push) Successful in 5m9s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m30s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m22s
CI/CD Pipeline / Unit Tests (push) Successful in 8m31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (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
CI/CD Pipeline / CI Gate (push) Has been skipped
- _get_service 依赖注入加 try/except,暴露 LipsyncService 初始化错误
- list_lipsync_jobs 加 try/except,暴露 DB 查询错误
- 错误信息包含异常类型和详情,方便 staging 实测定位
2026-09-09 12:53:25 +08:00
xiaoxia ecedfc4381 fix: 克隆音色试听按钮不禁用(preview_url/audio_url 为空但可走 TTS 合成)
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 (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 / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m8s
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 1m47s
CI/CD Pipeline / Integration Tests (push) Successful in 2m17s
CI/CD Pipeline / Validate - Style (push) Successful in 2m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m29s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m43s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
2026-09-09 12:48:21 +08:00
xiaoxia 5b778fbb3e debug: AI数字人克隆音色试听加 Network/Console 调试日志 (#1819)
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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 11s
CI/CD Pipeline / Build Staging API Image (push) Successful in 16s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 18s
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 1m24s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / Validate - Style (push) Successful in 2m5s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m52s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m16s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m35s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m9s
CI/CD Pipeline / Validate - Security (push) Successful in 5m18s
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 12:40:17 +08:00
xiaoxia 5eb8d6b31c debug: AI数字人克隆音色试听加 Network/Console 调试日志
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 (push) Successful in 3s
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 / Build Staging Web Image (push) Successful in 16s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 56s
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 1m25s
CI/CD Pipeline / Validate - Style (push) Successful in 1m29s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m28s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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
- 请求前打印 voice_id/voice_name/voice_clone_profile_id
- 响应后打印 audio_url 前80字符 + duration
- catch 打印 HTTP status + response data + message
- 帮助定位 /tts/preview 400/422/500/502 具体原因
2026-09-09 12:37:34 +08:00
xiaoxia 9121341f1b fix: AI数字人克隆音色试听传 profile UUID 而非 voice_id
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 0s
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 6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
CI/CD Pipeline / Build Staging API Image (push) Successful in 38s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m18s
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 / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m59s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m1s
CI/CD Pipeline / Validate - Style (push) Successful in 2m34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m32s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m15s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m27s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m24s
CI/CD Pipeline / Validate - Security (push) Successful in 7m46s
CI/CD Pipeline / Unit Tests (push) Successful in 8m16s
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
根因:后端 /tts/preview 用传入的 voice_id 去查 VoiceCloneProfile,
期望前端传 profile UUID(voice_clone_profile_id 或 clone.id)。
此前 clone 分支传了 voice_id(CosyVoice 模型 ID),
后端查不到 profile 静默返回,用户点播放无反应。

修复:clone 分支 targetId 改用 voice.voice_clone_profile_id || voice.id。
2026-09-09 11:34:36 +08:00
xiaoxia c3023cc11b fix: #1809 补充 对口型接口参数校验返回400而非500 (#1818)
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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 22s
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 1m25s
CI/CD Pipeline / Integration Tests (push) Successful in 1m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Style (push) Successful in 2m22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m29s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m31s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
CI/CD Pipeline / Validate - Security (push) Successful in 6m42s
CI/CD Pipeline / Unit Tests (push) Successful in 7m31s
CI/CD Pipeline / Build Production API 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 / Build Production Web 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 11:12:08 +08:00
xiaoxia 547561f473 fix: AI数字人对口型接口对齐后端新参数 video_url (#1809 补充) (#1816)
CI/CD Pipeline / Check if frontend-only change (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 / PR Build Web Image (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 (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 25s
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 1m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m25s
CI/CD Pipeline / Integration Tests (push) Successful in 1m55s
CI/CD Pipeline / Validate - Style (push) Successful in 2m37s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m52s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Validate - Security (push) Successful in 5m59s
CI/CD Pipeline / Unit Tests (push) Successful in 7m49s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 10:35:50 +08:00
xiaoxia a10e05654c fix: AI数字人「我的音色」克隆音色试听改用 TTS 合成 (#1815)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 21s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 24s
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 - Style (push) Successful in 1m8s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m39s
CI/CD Pipeline / Integration Tests (push) Successful in 1m34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Validate - Security (push) Successful in 5m51s
CI/CD Pipeline / Unit Tests (push) Successful in 7m53s
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 / 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
CI/CD Pipeline / CI Gate (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 10:22:28 +08:00
xiaoxia c954e334e6 fix: #1809 对口型接口参数调整,后端内部调TTS合成音频 (#1814)
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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 16s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
CI/CD Pipeline / Integration Tests (push) Successful in 2m14s
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 2m48s
CI/CD Pipeline / Validate - Style (push) Successful in 2m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m43s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m29s
CI/CD Pipeline / Validate - Security (push) Successful in 6m50s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m15s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 8m18s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m30s
CI/CD Pipeline / Unit Tests (push) Successful in 10m41s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 10:11:27 +08:00
xiaoxia 7c738ee64d fix: AI数字人「我的音色」试听无音频时弹提示而非静默返回 (#1813)
CI/CD Pipeline / Check if frontend-only change (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 / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
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 1m30s
CI/CD Pipeline / Integration Tests (push) Successful in 1m40s
CI/CD Pipeline / Validate - Style (push) Successful in 1m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m51s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m56s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m19s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m22s
CI/CD Pipeline / Unit Tests (push) Successful in 8m56s
CI/CD Pipeline / Validate - Security (push) Successful in 9m9s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 09:55:10 +08:00
xiaoxia 7209b7d481 feat: #1811 新增文案库独立页面 (#1812)
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 / 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 4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 31s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 36s
CI/CD Pipeline / Validate - Style (push) Successful in 1m21s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m46s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m48s
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 3m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m29s
CI/CD Pipeline / Validate - Security (push) Successful in 4m30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m14s
CI/CD Pipeline / Unit Tests (push) Successful in 7m47s
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 / Staging API Integration Tests (push) Successful in 22m59s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 08:59:27 +08:00
xiaoxia 165574f826 fix: #1809 AI数字人页面 7 项实测问题修复 (#1810)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
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 - Style (push) Successful in 1m49s
CI/CD Pipeline / Integration Tests (push) Successful in 2m14s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m21s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m26s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m7s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m59s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Validate - Security (push) Successful in 6m2s
CI/CD Pipeline / Unit Tests (push) Successful in 8m35s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 01:00:17 +08:00
xiaoxia a7e6145bb1 fix: #1807 侧边栏缩窄 240px→128px + 页面内容区拓宽 (#1808)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 50s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Integration Tests (push) Successful in 1m45s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m0s
CI/CD Pipeline / Validate - Style (push) Successful in 2m28s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m33s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m48s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m35s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m9s
CI/CD Pipeline / Unit Tests (push) Successful in 7m41s
CI/CD Pipeline / Validate - Security (push) Successful in 8m31s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-09 00:18:00 +08:00
xiaoxia 6ee99a652c fix: 部署脚本 worker health-cmd 未转义导致 staging 部署失败回滚 (#1806)
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 0s
CI/CD Pipeline / Check push changed paths (push) 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 Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 59s
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 1m31s
CI/CD Pipeline / Validate - Style (push) Successful in 2m4s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m3s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m34s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m0s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Unit Tests (push) Successful in 6m47s
CI/CD Pipeline / Validate - Security (push) Successful in 20m59s
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-08 22:41:28 +08:00
xiaoxia bf7c8b8a08 feat: #1797 AI数字人前端页面v3 — 素材库选择/配音库复用/TitleStylePanel直接import (#1805)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
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 / Build Staging API Image (push) Successful in 23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 53s
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 1m40s
CI/CD Pipeline / Validate - Style (push) Successful in 1m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1m54s
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m24s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m50s
CI/CD Pipeline / Unit Tests (push) Successful in 8m50s
CI/CD Pipeline / Validate - Security (push) Successful in 26m4s
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
2026-09-08 21:46:35 +08:00
xiaoxia b5bc285aff feat: #1797 AI数字人前端页面v3 — 素材库选择/配音库复用/TitleStylePanel直接import
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 4s
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 / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m35s
CI/CD Pipeline / PR Build 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 / PR Build Web Image (pull_request) Successful in 1m36s
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 / Frontend Lint (pull_request) Successful in 1m57s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m12s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m35s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m49s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m4s
AI Code Review / AI Code Review (pull_request) Successful in 6m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m35s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 23m11s
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 / Deploy Production (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 / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 5s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m20s
- 面板1:出镜视频改为从素材库弹窗选择(ModalAssetPicker),竖屏9:16预览
- 面板2:配音库改为系统预设/我的音色双tab切换,复用 /api/v1/voices
- 面板3:对口型三态(生成/进度/完成+重新生成),竖屏9:16预览
- 面板4:标题配置直接 import TitleStylePanel + TITLE_PRESETS,与智能剪辑完全一致
- 面板5:封面+分辨率+配置汇总+渐变生成按钮,删除多余画面插入模式
- B-roll弹窗:已选素材标灰pointer-events:none防重选,画中画四角位置
- 路由 /app/ai-avatar + 侧边栏AI数字人导航项
2026-09-08 21:21:27 +08:00
CI Bot e4ac398397 fix: Worker 健康检查彻底修复 - 移除不可用的 pgrep 和 PID1 检查
根因:#1714 队列隔离后 entrypoint 启动两个 celery worker 子进程,
PID 1 变为 bash。旧健康检查依赖 pgrep(容器内不存在)或
/proc/1/cmdline(PID 1 是 bash 不是 celery),永远返回失败。
此前靠 cron 脚本 fix-worker-healthcheck.sh 每 30 分钟擦屁股。

修复:统一改为扫描 /proc/[0-9]*/cmdline 查找 celery 进程,
覆盖所有部署/回滚/CI 脚本(10 处替换)。已移除 cron 补丁。
2026-09-08 19:07:37 +08:00
xiaoxia a0ca13d463 fix: AI数字人渲染 OSS 上传集成 SharedStorageService (#1804)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (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 / Build Staging API Image (push) Successful in 39s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 41s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 58s
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 2m24s
CI/CD Pipeline / Validate - Style (push) Successful in 2m40s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m23s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m23s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m1s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m11s
CI/CD Pipeline / Unit Tests (push) Successful in 8m39s
CI/CD Pipeline / Validate - Security (push) Successful in 22m54s
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 / Build Production API Image (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-08 19:04:34 +08:00
xiaoxia e45a8fe775 feat: #1798 AI数字人前端页面 — 5列水平面板布局 (#1803)
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 17s
CI/CD Pipeline / Build Staging API Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 19s
CI/CD Pipeline / Integration Tests (push) Successful in 1m19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m9s
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 - Style (push) Successful in 1m48s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m36s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m36s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m50s
CI/CD Pipeline / Validate - Security (push) Successful in 5m8s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m23s
CI/CD Pipeline / Unit Tests (push) Successful in 7m46s
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 / Build Production API 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-08 18:31:02 +08:00
xiaoxia 800f90d8c6 feat: #1798 AI数字人渲染合成管线 (#1802)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
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 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m15s
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 1m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m58s
CI/CD Pipeline / Validate - Style (push) Successful in 2m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m38s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m9s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m57s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m49s
CI/CD Pipeline / Validate - Security (push) Successful in 8m27s
CI/CD Pipeline / Unit Tests (push) Successful in 8m34s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 18:03:06 +08:00
xiaoxia f7825e3956 feat: #1796 MediaKit 对口型后端对接 (#1801)
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 15s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 16s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 17s
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 1m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m26s
CI/CD Pipeline / Validate - Style (push) Successful in 2m3s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m17s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m23s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m47s
CI/CD Pipeline / Unit Tests (push) Successful in 8m7s
CI/CD Pipeline / Validate - Security (push) Successful in 9m43s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 16:47:57 +08:00
xiaoxia 2cca03f680 Merge pull request 'feat: #1795 文案库 CRUD(Script 模型 + Service + API + 迁移 + 35 测试)' (#1799) from feat/scripts-crud-1795 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 / 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 7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 29s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 31s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m3s
CI/CD Pipeline / Integration Tests (push) Successful in 2m14s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) 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 / Validate - Python (mypy + alembic) (push) Successful in 2m40s
CI/CD Pipeline / Validate - Style (push) Successful in 2m47s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m4s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m53s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m6s
CI/CD Pipeline / Unit Tests (push) Successful in 8m52s
CI/CD Pipeline / Validate - Security (push) Successful in 10m38s
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 / 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 / CI Gate (push) Has been skipped
2026-09-08 15:42:54 +08:00
saas-backend-agent 277ff5428e fix: 修 test_rollback_value_error_400 pre-existing failure
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API 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 / PR Build Worker Image (pull_request) Successful in 30s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 45s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m20s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m13s
AI Code Review / AI Code Review (pull_request) Successful in 6m43s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 11m6s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m17s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m56s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m56s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m47s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 11s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m18s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m52s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 10m1s
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 / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
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
endpoint 对 ValueError 返回 404(版本不存在=Not Found),
测试断言写的 400 是错的,改为 404 与 endpoint 行为一致。
2026-09-08 15:28:29 +08:00
xiaoxia a4c008e829 fix: #1799 批量生成进度卡片 grid 列宽过窄导致标题和进度条重叠
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 (push) Successful in 3s
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 / Build Staging API Image (push) Successful in 27s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / Build Staging Web Image (push) 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 / Integration Tests (push) Successful in 1m44s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m15s
CI/CD Pipeline / Validate - Style (push) Successful in 2m19s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m2s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m28s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m36s
CI/CD Pipeline / Unit Tests (push) Failing after 6m25s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m27s
CI/CD Pipeline / Validate - Security (push) Successful in 9m9s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-09-08 15:27:21 +08:00
saas-backend-agent 24b28bfc89 feat: #1795 文案库 CRUD(Script 模型 + Service + API + 迁移 + 35 测试)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 17s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 51s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m44s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m58s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m16s
AI Code Review / AI Code Review (pull_request) Successful in 6m23s
CI/CD Pipeline / Validate - Security (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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 5m28s
- 新建 ScriptModel (packages/adapters/sqlalchemy_impl/models.py)
  字段: id, user_id(indexed), title, content, segments(JSON), tags(JSON), created_at, updated_at
- 新建 script_service.py: CRUD 封装,用户隔离,tag 筛选,分页
- 新建 schemas/script.py: Pydantic request/response schemas
- 新建 routes/scripts.py: RESTful API (GET/POST/PUT/DELETE /api/v1/scripts)
- 新建 alembic 070_add_scripts_table.py: scripts 表 + user_id 索引
- 注册路由到 router.py (prefix=/scripts, tag=ScriptLibrary)
- 35 单元测试: service 15 + schema/route 20
2026-09-08 15:19:57 +08:00
xiaoxia 6904fce511 fix: #1789 标题字号滑块拖动回弹 — useMemo 稳定引用 + ref 防御 (#1793)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
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 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 20s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m20s
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 / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m42s
CI/CD Pipeline / Validate - Style (push) Successful in 2m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m27s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m30s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m10s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m14s
CI/CD Pipeline / Validate - Security (push) Successful in 6m17s
CI/CD Pipeline / Unit Tests (push) Failing after 8m32s
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 / 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
2026-09-08 13:28:50 +08:00
xiaoxia 9dec75c365 feat: #1789 标题 drawtext 滤镜渲染 — 单视频+批量生成都生效 (#1792)
CI/CD Pipeline / Check if frontend-only change (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 / 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 / Check push changed paths (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m12s
CI/CD Pipeline / Integration Tests (push) Successful in 1m33s
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 1m45s
CI/CD Pipeline / Validate - Style (push) Successful in 2m17s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m14s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m5s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m29s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m41s
CI/CD Pipeline / Validate - Security (push) Successful in 6m20s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m30s
CI/CD Pipeline / Unit Tests (push) Failing after 7m32s
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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 13:14:13 +08:00
xiaoxia 7997f083a5 fix: #1789 标题设置 UI 控件 + #1790 空模板时间线不显示刻度 (#1791)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
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 4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 21s
CI/CD Pipeline / Build Staging API Image (push) Successful in 22s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 52s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
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 / Integration Tests (push) Successful in 1m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m6s
CI/CD Pipeline / Validate - Style (push) Successful in 2m18s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m47s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m31s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m38s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m31s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m32s
CI/CD Pipeline / Unit Tests (push) Failing after 7m51s
CI/CD Pipeline / Validate - Security (push) Successful in 9m14s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 11:53:47 +08:00
xiaoxia cf195685b9 Merge pull request 'feat: #1768 节奏曲线模板多样化 — 8种预设 + 时长钳制 + 误差校验' (#1788) from feat/rhythm-templates-diversify-1768 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 / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (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 (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (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 / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit 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 / 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 / Check push changed paths (push) Successful in 10s
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 / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 26s
CI/CD Pipeline / Deploy Production (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 / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 22s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 55s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 45s
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 2m27s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m35s
CI/CD Pipeline / Validate - Style (push) Successful in 2m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m18s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m5s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m36s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m22s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m57s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m28s
AI Code Review / AI Code Review (pull_request) Successful in 7m19s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 13s
CI/CD Pipeline / Unit Tests (push) Failing after 8m30s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 53s
CI/CD Pipeline / Validate - Security (push) Successful in 8m54s
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-08 11:04:03 +08:00
xiaoxia ee4636e087 feat: #1768 节奏曲线模板多样化 — 8种预设 + 时长钳制 + 误差校验
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 / 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 1s
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 / 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 / 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 30s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 55s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m27s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m30s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m48s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 1m56s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m51s
AI Code Review / AI Code Review (pull_request) Successful in 6m18s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 6m17s
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 / CI Gate (pull_request) Successful in 4s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m13s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 34s
变更:
- RHYTHM_TEMPLATES 从 6 种扩展到 8 种(新增 [2,2,1,1,2] 和 [3,2,1,2,1])
- 修复 MIN_CLIP_DURATION 被重复定义为 1.0 的 bug(恢复为 2.0)
- plan_clip_durations 新增 asset_durations 参数,钳制最大片段时长 <= 素材可用时长 × 90%
- 新增时长总和误差校验(成片净时长与配音时长误差 <= 0.5s),超限时末段补偿修正
- edit_plan_service.apply_voice_duration_to_plan 传入素材时长参与钳制
- 48 个新增单元测试全部通过

向后兼容:asset_durations 默认 None,不影响现有调用方
2026-09-08 10:57:16 +08:00
xiaoxia 5678779232 feat: #1776 素材库 asset_count 自动同步维护 (#1787)
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 3s
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 / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
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 push changed paths (push) Successful in 12s
CI/CD Pipeline / Frontend Lint (push) 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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m58s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m1s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m2s
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 / PR Build Worker Image (pull_request) Successful in 34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (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
CI/CD Pipeline / Validate - Style (push) Successful in 2m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 26s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m56s
CI/CD Pipeline / Build Staging API Image (push) Successful in 59s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m9s
PR Automation / Auto Merge on CI Green + Approved (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 / CI Gate (pull_request) Successful in 13s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m5s
AI Code Review / AI Code Review (pull_request) Successful in 6m29s
CI/CD Pipeline / Validate - Security (push) Successful in 6m24s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m53s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 8m1s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m40s
CI/CD Pipeline / Unit Tests (push) Failing after 10m13s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 10:30:21 +08:00
xiaoxia 44224cfaf6 feat: 转场位置与类型随机化 (#1766) (#1786)
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 4s
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 7s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 5s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 10s
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 / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (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 Web Image (pull_request) Successful in 36s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 38s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 44s
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
CI/CD Pipeline / Build Staging API Image (push) Successful in 28s
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 / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 23s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 29s
CI/CD Pipeline / CI Gate (pull_request) Successful in 18s
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 / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 1m43s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m57s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m0s
CI/CD Pipeline / Integration Tests (push) Successful in 2m3s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m5s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m7s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m56s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 5m13s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m24s
AI Code Review / AI Code Review (pull_request) Successful in 6m35s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m33s
CI/CD Pipeline / Unit Tests (push) Failing after 9m24s
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 / 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
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 10:17:35 +08:00
xiaoxia 98cf571ab3 feat: #1767 BGM 池差异化分配,打破变体间音频指纹一致性 (#1785)
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 / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (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 / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
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 Unit Tests (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 / PR Build API Image (pull_request) Successful in 30s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 51s
CI/CD Pipeline / Integration Tests (push) Successful in 1m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m49s
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 / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Validate - Style (push) Successful in 2m34s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m20s
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 / Deploy Staging (Watchtower auto-deploy) (push) Successful in 55s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (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 / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (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
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-09-08 10:12:52 +08:00
xiaoxia 12a9efc65d fix(test): 移除 InMemory 并发测试改为顺序幂等验证 (#1784)
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 / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) 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 / 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 / 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 / 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 / Frontend Lint (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 10s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 26s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 29s
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 35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 49s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (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
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 36s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m55s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 48s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m7s
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 2m16s
CI/CD Pipeline / Validate - Style (push) Successful in 2m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m12s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m36s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 4m46s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m47s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m31s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m12s
AI Code Review / AI Code Review (pull_request) Successful in 6m23s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m23s
CI/CD Pipeline / Unit Tests (push) Failing after 7m59s
CI/CD Pipeline / Build Production API 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 / Build Production Web Image (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
Squash merge PR #1784: 修复 #1775 并发测试在 CI 失败(InMemory 无 DB 约束)
2026-09-08 09:11:05 +08:00
xiaoxia 9c0474ef9c feat: #1775 默认项目/素材库幂等化(DB 唯一约束兜底)
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 / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
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 (push) Successful in 7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (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 / 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 API Image (pull_request) Successful in 41s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 42s
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 / 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 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 / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34s
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 / Validate - Python (mypy + alembic) (push) Successful in 2m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m50s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m53s
CI/CD Pipeline / Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m43s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
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 / Validate - Style (push) Successful in 3m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m7s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 5m45s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m6s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m13s
AI Code Review / AI Code Review (pull_request) Successful in 6m38s
CI/CD Pipeline / Unit Tests (push) Failing after 7m58s
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 / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
Squash merge PR #1783 into develop
2026-09-08 08:43:31 +08:00
204 changed files with 20951 additions and 3396 deletions
+18 -13
View File
@@ -20,6 +20,7 @@ on:
default: "手动触发 - CI漏触发补跑"
permissions:
contents: read
pull-requests: read
concurrency:
group: ci-pipeline-${{ gitea.ref }}
cancel-in-progress: true
@@ -88,9 +89,22 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -eu
# 优先用 git diff 判断 PR 改动范围(比 API 稳定)
PR_NUMBER=$(echo "$GITHUB_REF" | sed 's|refs/pull/||; s|/.*||')
API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=300"
FILES=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" | python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin)]")
if command -v git >/dev/null 2>&1 && [ -d .git ]; then
FILES=$(git diff --name-only origin/develop...HEAD 2>/dev/null || true)
fi
if [ -z "${FILES:-}" ]; then
# fallback 到 API
API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=300"
FILES=$(curl -sf -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL" | python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin)]" 2>/dev/null || true)
fi
if [ -z "${FILES:-}" ]; then
echo "⚠️ 无法获取变更文件列表,保守运行完整 CI"
echo "skip_backend=false" >> $GITHUB_OUTPUT
echo "skip_frontend=false" >> $GITHUB_OUTPUT
exit 0
fi
FRONTEND_COUNT=$(echo "$FILES" | grep -c '^apps/web/' || true)
BACKEND_COUNT=$(echo "$FILES" | grep -cv '^apps/web/' || true)
TOTAL=$(echo "$FILES" | grep -cv '^$' || true)
@@ -182,7 +196,7 @@ jobs:
- name: Run style checks
shell: bash
run: bash scripts/ci/validate_style.sh
- name: Auto-fix formatting (black + isort)
- name: Auto-fix formatting (black + isort + ruff)
if: failure()
shell: sh
env:
@@ -813,9 +827,6 @@ jobs:
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop"
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\""
if [ "${{ matrix.service }}" = "web" ]; then
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf"
fi
# Worker 与 API/Web 统一走持久 builderci-builder-persist),共享宿主机层缓存
NO_CACHE_FLAG=""
@@ -1012,9 +1023,6 @@ jobs:
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:${GITHUB_REF_NAME}"
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\""
if [ "${{ matrix.service }}" = "web" ]; then
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf"
fi
NO_CACHE_FLAG=""
for i in 1 2 3; do
@@ -1462,7 +1470,6 @@ jobs:
- validate-security
- validate-python
- unit-tests
- frontend-lint
- frontend-unit-test
if: github.event_name == 'push' && github.ref_name == 'main' && !failure() && !cancelled()
strategy:
@@ -1553,9 +1560,6 @@ jobs:
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:main"
EXTRA_BUILD_ARGS="APP_VERSION=\"${TAG_NAME}\""
if [ "${{ matrix.service }}" = "web" ]; then
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-production.conf"
fi
# Docker build 带重试:失败自动重试2次,第2次重试加--no-cache
NO_CACHE_FLAG=""
@@ -2119,3 +2123,4 @@ jobs:
START_TIME=""
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
curl -sfH "Authorization: token ${GITHUB_TOKEN:-$GITEA_TOKEN}" -o /tmp/_ci_trace.py "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/ci_trace_report.py?ref=${GITHUB_SHA}" 2>/dev/null && python3 /tmp/_ci_trace.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
# CI retry trigger
+60
View File
@@ -0,0 +1,60 @@
name: "Debug: Web container v2 (mount conflict)"
on:
push:
branches: [debug/web-crash-v2]
workflow_dispatch:
jobs:
web-diag:
runs-on: runtime-builder
timeout-minutes: 10
steps:
- name: Setup SSH and diagnose
shell: bash
env:
STAGING_SSH_KEY: ${{ secrets.PREVIEW_SSH_KEY }}
run: |
set -x
which ssh || (apt-get update -qq && apt-get install -y -qq openssh-client)
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf "%s" "$STAGING_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
H=47.98.113.167; P=22222
ssh-keyscan -p $P -H $H >> ~/.ssh/known_hosts 2>/dev/null
ssh -p $P -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no root@$H 'bash -s' <<'REMOTE'
set -x
echo "=== Current staging containers ==="
docker ps -a --filter name=xiaoxia-*-staging --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
echo ""
echo "=== Web container logs (current/current-rolledback) ==="
docker logs xiaoxia-web-staging 2>&1 | tail -40
echo ""
echo "=== Web inspect: env & mounts ==="
docker inspect xiaoxia-web-staging --format 'Entrypoint: {{.Config.Entrypoint}} Cmd: {{.Config.Cmd}}'
docker inspect xiaoxia-web-staging --format '{{range .Config.Env}}{{.}}{{"\n"}}{{end}}' | grep -E "APP_ENV|VERSION"
echo "Mounts:"
docker inspect xiaoxia-web-staging --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}} (rw={{.RW}}){{"\n"}}{{end}}'
echo ""
echo "=== Reproduce: rm on read-only bind mount ==="
docker run --rm --name nginx-ro-test \
-v /var/lib/xiaoxia-saas-staging/nginx-staging.conf:/etc/nginx/conf.d/default.conf:ro \
git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas/xiaoxia-saas-web:387514c \
sh -c '
set -x
echo "Before:"
ls -la /etc/nginx/conf.d/
echo "Try rm (as entrypoint does):"
rm -f /etc/nginx/conf.d/default.conf
echo "rm exitcode=$?"
echo "After rm:"
ls -la /etc/nginx/conf.d/
echo "Test ln:"
ln -s /etc/nginx/nginx-staging.conf /etc/nginx/conf.d/default.conf
echo "ln exitcode=$?"
ls -la /etc/nginx/conf.d/
echo "nginx -t:"
nginx -t 2>&1
' 2>&1
echo ""
echo "=== Also test with NEW fixed image (9c0d4b1 if present) ==="
docker images | grep xiaoxia-saas-web | head -5
REMOTE
+1
View File
@@ -0,0 +1 @@
retrigger3
+1
View File
@@ -263,3 +263,4 @@ pytest --cov=packages --cov-report=html
---
**License**: MIT
<!-- CI trigger: 1788229339 -->
@@ -0,0 +1,72 @@
"""Projects is_default + partial unique index for idempotent default project (Issue #1775)
Revision ID: 069_project_is_default
Revises: 068_user_profile_completed
Create Date: 2026-09-08
背景:
小程序端 getOrCreateDefaultProject 在重试/并发/前端重复调用下,
仅靠应用层"先查再插"不保证幂等,会给同一用户重复创建默认项目。
改动:
1. projects 表新增 is_default 布尔列(默认 false
2. 部分唯一索引 uq_projects_owner_default(owner_user_id) WHERE is_default = true
—— 保证每个用户至多一个默认项目
3. 存量数据回填:把名为"默认项目"的存量项目按创建时间最早者标记为 is_default=true
(只标记不删除;存量重复项目的清理另行确认后单独执行)
注意:部分唯一索引依赖 PostgreSQL,不支持 downgrade 到其他方言。
"""
import sqlalchemy as sa
from alembic import op
revision = "069_project_is_default"
down_revision = "068_user_profile_completed"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 1. 新增 is_default 列
op.add_column(
"projects",
sa.Column(
"is_default",
sa.Boolean(),
nullable=False,
server_default=sa.text("false"),
),
)
# 2. 存量回填:每个拥有"默认项目"的用户,只把最早创建的那一个标记为默认。
# 用 ROW_NUMBER() 取每组第一条;非"默认项目"命名的项目不标记(保守,不动用户自建项目)。
op.execute("""
UPDATE projects p
SET is_default = true
WHERE p.id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY owner_user_id
ORDER BY created_at ASC, id ASC
) AS rn
FROM projects
WHERE name = '默认项目'
) t
WHERE t.rn = 1
)
""")
# 3. 部分唯一索引:每用户至多一个默认项目(只约束 is_default = true 的行)
op.execute("""
CREATE UNIQUE INDEX uq_projects_owner_default
ON projects (owner_user_id)
WHERE is_default = true
""")
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS uq_projects_owner_default")
op.drop_column("projects", "is_default")
+48
View File
@@ -0,0 +1,48 @@
"""Add scripts table for oral broadcast script library (Issue #1795)
Revision ID: 070_add_scripts
Revises: 069_project_is_default
Create Date: 2026-09-08
新建 scripts 表,支持口播文案 CRUD + 分段存储。
"""
import sqlalchemy as sa
from alembic import op
revision = "070_add_scripts"
down_revision = "069_project_is_default"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"scripts",
sa.Column("id", sa.String(36), nullable=False),
sa.Column("user_id", sa.String(36), nullable=False),
sa.Column("title", sa.String(255), nullable=False),
sa.Column("content", sa.Text(), nullable=False, server_default=""),
sa.Column("segments", sa.JSON(), nullable=False, server_default="[]"),
sa.Column("tags", sa.JSON(), nullable=False, server_default="[]"),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_scripts_user_id", "scripts", ["user_id"])
def downgrade() -> None:
op.drop_index("ix_scripts_user_id", table_name="scripts")
op.drop_table("scripts")
@@ -0,0 +1,47 @@
"""add lipsync jobs table
Revision ID: 071_add_lipsync_jobs
Revises: 070_add_scripts
Create Date: 2026-09-08
"""
import sqlalchemy as sa
from alembic import op
revision = "071_add_lipsync_jobs"
down_revision = "070_add_scripts"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"lipsync_jobs",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, index=True),
sa.Column("project_id", sa.String(36), nullable=False, server_default=""),
sa.Column("video_url", sa.Text(), nullable=False),
sa.Column("audio_url", sa.Text(), nullable=False),
sa.Column("enable_video_loop", sa.Boolean(), nullable=False, server_default=sa.text("false")),
sa.Column("mediakit_task_id", sa.String(200), nullable=False, server_default="", index=True),
sa.Column("status", sa.String(20), nullable=False, server_default="pending", index=True),
sa.Column("output_video_url", sa.Text(), nullable=False, server_default=""),
sa.Column("output_duration", sa.Float(), nullable=False, server_default=sa.text("0.0")),
sa.Column("error_message", sa.Text(), nullable=False, server_default=""),
sa.Column("error_code", sa.String(100), nullable=False, server_default=""),
sa.Column("submitted_at", sa.DateTime(), nullable=True),
sa.Column("completed_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
)
# 复合索引:用户 + 状态(列表查询常用)
op.create_index("ix_lipsync_jobs_user_status", "lipsync_jobs", ["user_id", "status"])
# 项目 + 用户(项目维度查询)
op.create_index("ix_lipsync_jobs_project_user", "lipsync_jobs", ["project_id", "user_id"])
def downgrade() -> None:
op.drop_index("ix_lipsync_jobs_project_user", table_name="lipsync_jobs")
op.drop_index("ix_lipsync_jobs_user_status", table_name="lipsync_jobs")
op.drop_table("lipsync_jobs")
@@ -0,0 +1,48 @@
"""add ai avatar render jobs table
Revision ID: 072_add_ai_avatar_render
Revises: 071_add_lipsync_jobs
Create Date: 2026-09-09
"""
import sqlalchemy as sa
from alembic import op
revision = "072_add_ai_avatar_render"
down_revision = "071_add_lipsync_jobs"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"ai_avatar_render_jobs",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, index=True),
sa.Column("project_id", sa.String(36), nullable=False, server_default=""),
sa.Column("lipsync_job_id", sa.String(36), nullable=False),
sa.Column("script_id", sa.String(36), nullable=False),
sa.Column("b_roll_segments", sa.JSON(), nullable=False, server_default="[]"),
sa.Column("title_config", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("cover_config", sa.JSON(), nullable=False, server_default="{}"),
sa.Column("status", sa.String(20), nullable=False, server_default="pending", index=True),
sa.Column("progress", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("output_video_url", sa.Text(), nullable=False, server_default=""),
sa.Column("output_cover_url", sa.Text(), nullable=False, server_default=""),
sa.Column("output_duration", sa.Float(), nullable=False, server_default=sa.text("0.0")),
sa.Column("error_message", sa.Text(), nullable=False, server_default=""),
sa.Column("submitted_at", sa.DateTime(), nullable=True),
sa.Column("started_at", sa.DateTime(), nullable=True),
sa.Column("completed_at", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now()),
)
op.create_index("ix_ai_avatar_render_user_status", "ai_avatar_render_jobs", ["user_id", "status"])
op.create_index("ix_ai_avatar_render_project_user", "ai_avatar_render_jobs", ["project_id", "user_id"])
def downgrade() -> None:
op.drop_index("ix_ai_avatar_render_project_user", table_name="ai_avatar_render_jobs")
op.drop_index("ix_ai_avatar_render_user_status", table_name="ai_avatar_render_jobs")
op.drop_table("ai_avatar_render_jobs")
@@ -0,0 +1,45 @@
"""lipsync_jobs 增加 TTS 直生字段(voice_id/script_text/speed/emotion
Revision ID: 073_add_lipsync_tts_fields
Revises: 072_add_ai_avatar_render
Create Date: 2026-09-09
"""
import sqlalchemy as sa
from alembic import op
revision = "073_add_lipsync_tts_fields"
down_revision = "072_add_ai_avatar_render"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 对口型支持「传音色 + 文案直接生成」:后端内部先 TTS 合成音频再提交对口型
op.add_column(
"lipsync_jobs",
sa.Column("voice_id", sa.String(200), nullable=False, server_default=""),
)
op.add_column(
"lipsync_jobs",
sa.Column("script_text", sa.Text(), nullable=False, server_default=""),
)
op.add_column(
"lipsync_jobs",
sa.Column("speed", sa.Float(), nullable=False, server_default=sa.text("1.0")),
)
op.add_column(
"lipsync_jobs",
sa.Column("emotion", sa.String(20), nullable=False, server_default=""),
)
# audio_url 改为可空:直生模式下音频由后端 TTS 合成后回填
op.alter_column("lipsync_jobs", "audio_url", existing_type=sa.Text(), nullable=True)
def downgrade() -> None:
op.alter_column("lipsync_jobs", "audio_url", existing_type=sa.Text(), nullable=False)
op.drop_column("lipsync_jobs", "emotion")
op.drop_column("lipsync_jobs", "speed")
op.drop_column("lipsync_jobs", "script_text")
op.drop_column("lipsync_jobs", "voice_id")
@@ -0,0 +1,36 @@
"""ai_avatar_render_jobs.script_id 放宽为可空串(手动文案直生场景不关联文案库)
Revision ID: 074_render_script_id_optional
Revises: 073_add_lipsync_tts_fields
Create Date: 2026-09-09
"""
import sqlalchemy as sa
from alembic import op
revision = "074_render_script_id_optional"
down_revision = "073_add_lipsync_tts_fields"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 列保持 NOT NULL(空串占位),仅应用层允许不传;这里显式补 server_default 防止历史约束歧义
with op.batch_alter_table("ai_avatar_render_jobs") as batch:
batch.alter_column(
"script_id",
existing_type=sa.String(length=36),
nullable=False,
server_default="",
)
def downgrade() -> None:
with op.batch_alter_table("ai_avatar_render_jobs") as batch:
batch.alter_column(
"script_id",
existing_type=sa.String(length=36),
nullable=False,
server_default=None,
)
@@ -0,0 +1,27 @@
"""add sentence_timings to lipsync_jobs
Revision ID: 075_add_sentence_timings
Revises: 074_ai_avatar_render_script_id_optional
Create Date: 2026-09-12
"""
import sqlalchemy as sa
from alembic import op
revision = "075_add_sentence_timings"
down_revision = "074_render_script_id_optional"
branch_labels = None
depends_on = None
def upgrade() -> None:
with op.batch_alter_table("lipsync_jobs") as batch:
batch.add_column(
sa.Column("sentence_timings", sa.JSON(), nullable=True),
)
def downgrade() -> None:
with op.batch_alter_table("lipsync_jobs") as batch:
batch.drop_column("sentence_timings")
View File
+18
View File
@@ -1,4 +1,5 @@
from app.api.routes.ai import router as ai_router
from app.api.routes.ai_avatar_render import router as ai_avatar_render_router
from app.api.routes.asset_diagnosis import router as asset_diagnosis_router
from app.api.routes.asset_libraries import router as asset_libraries_router
from app.api.routes.assets import router as assets_router
@@ -15,7 +16,9 @@ from app.api.routes.generation_variant_plans import router as generation_variant
from app.api.routes.health import router as health_check_router
from app.api.routes.ingest_jobs import router as ingest_jobs_router
from app.api.routes.internal_render import router as internal_render_router
from app.api.routes.lipsync import router as lipsync_router
from app.api.routes.projects import router as projects_router
from app.api.routes.scripts import router as scripts_router
from app.api.routes.share import router as share_router
from app.api.routes.subscription import router as subscription_router
from app.api.routes.tags import router as tags_router
@@ -38,6 +41,11 @@ api_router.include_router(
auth_router,
tags=["Auth"],
)
api_router.include_router(
lipsync_router,
prefix="/lipsync",
tags=["Lipsync"],
)
api_router.include_router(
projects_router,
prefix="/projects",
@@ -171,3 +179,13 @@ api_router.include_router(
internal_render_router,
tags=["Internal"],
)
api_router.include_router(
scripts_router,
prefix="/scripts",
tags=["ScriptLibrary"],
)
api_router.include_router(
ai_avatar_render_router,
prefix="/ai-avatar/render",
tags=["AI Avatar Render"],
)
+324
View File
@@ -0,0 +1,324 @@
"""AI数字人渲染合成 API 路由 — #1798.
接口:
POST /api/v1/ai-avatar/render 提交渲染任务
GET /api/v1/ai-avatar/render/jobs 任务列表
GET /api/v1/ai-avatar/render/{job_id} 任务详情
POST /api/v1/ai-avatar/render/{job_id}/cancel 取消任务
POST /api/v1/ai-avatar/render/{job_id}/retry 重试失败任务
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.schemas.ai_avatar_render import (
AiAvatarRenderJobResponse,
CreateAiAvatarRenderRequest,
FinalizeRenderResponse,
SmartCoverResponse,
)
from app.services.ai_avatar_cover_service import generate_smart_cover
from app.services.ai_avatar_render_service import (
AiAvatarRenderError,
AiAvatarRenderService,
)
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
router = APIRouter()
def _get_service(db: Session = Depends(get_db_session)) -> AiAvatarRenderService:
return AiAvatarRenderService(db)
# ── POST / — 提交渲染任务 ────────────────────────────────────────────────
@router.post("", response_model=AiAvatarRenderJobResponse, status_code=201)
def create_render_job(
body: CreateAiAvatarRenderRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: AiAvatarRenderService = Depends(_get_service),
):
"""提交 AI 数字人渲染任务.
将对口型视频 + B-roll 素材 + 标题叠加 + 封面提取合成最终输出视频。
"""
try:
job = svc.create_render_job(
user_id=current_user.user.id,
lipsync_job_id=body.lipsync_job_id,
script_id=body.script_id,
b_roll_segments=[s.model_dump() for s in body.b_roll_segments],
title_config=body.title_config,
cover_config=body.cover_config,
project_id=body.project_id,
)
except AiAvatarRenderError as exc:
status_map = {
"LipsyncJobNotFound": 404,
"LipsyncJobNotCompleted": 400,
"LipsyncJobNoOutput": 400,
"ScriptNotFound": 404,
}
raise HTTPException(
status_code=status_map.get(exc.code, 400),
detail={"code": exc.code, "message": str(exc)},
) from exc
# 异步触发渲染
try:
from app.tasks.ai_avatar_render import execute_ai_avatar_render
execute_ai_avatar_render.delay(job.id)
except Exception as exc:
logger.exception("Celery 任务投递失败(创建): job_id=%s err=%s", job.id, exc)
job.status = "failed"
job.error_message = f"任务提交失败:{exc}"
job.updated_at = datetime.now(timezone.utc)
svc.db.commit()
svc.db.refresh(job)
return AiAvatarRenderJobResponse.model_validate(job)
return AiAvatarRenderJobResponse.model_validate(job)
# ── GET /jobs — 任务列表 ─────────────────────────────────────────────────
@router.get("/jobs", response_model=dict)
def list_render_jobs(
project_id: str = Query("", description="项目 ID 过滤"),
status: str = Query("", description="状态过滤"),
offset: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: AuthenticatedUser = Depends(get_current_user),
svc: AiAvatarRenderService = Depends(_get_service),
):
"""获取 AI 数字人渲染任务列表."""
items, total = svc.list_render_jobs(
user_id=current_user.user.id,
project_id=project_id,
status=status,
offset=offset,
limit=limit,
)
return {
"items": [AiAvatarRenderJobResponse.model_validate(j) for j in items],
"total": total,
"offset": offset,
"limit": limit,
}
# ── GET /{job_id} — 任务详情 ─────────────────────────────────────────────
@router.get("/{job_id}", response_model=AiAvatarRenderJobResponse)
def get_render_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: AiAvatarRenderService = Depends(_get_service),
):
"""获取渲染任务详情."""
job = svc.get_render_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="渲染任务不存在")
return job
# ── POST /{job_id}/cancel — 取消任务 ─────────────────────────────────────
@router.post("/{job_id}/cancel", response_model=AiAvatarRenderJobResponse)
def cancel_render_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: AiAvatarRenderService = Depends(_get_service),
):
"""取消渲染任务(仅 pending 状态可取消)."""
job = svc.cancel_render_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="渲染任务不存在")
if job.status != "cancelled":
raise HTTPException(
status_code=400,
detail=f"任务状态 {job.status} 不可取消,仅 pending 可取消",
)
return job
# ── POST /{job_id}/retry — 重试失败任务 ──────────────────────────────────
@router.post("/{job_id}/retry", response_model=AiAvatarRenderJobResponse)
def retry_render_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: AiAvatarRenderService = Depends(_get_service),
):
"""重试失败的渲染任务."""
job = svc.retry_render_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="渲染任务不存在")
if job.status != "pending":
raise HTTPException(
status_code=400,
detail=f"仅 failed 状态的任务可重试,当前状态: {job.status}",
)
# 重新触发渲染
try:
from app.tasks.ai_avatar_render import execute_ai_avatar_render
execute_ai_avatar_render.delay(job.id)
except Exception as exc:
logger.exception("Celery 任务投递失败(重试): job_id=%s err=%s", job.id, exc)
job.status = "failed"
job.error_message = f"任务提交失败:{exc}"
job.updated_at = datetime.now(timezone.utc)
svc.db.commit()
svc.db.refresh(job)
return AiAvatarRenderJobResponse.model_validate(job)
return AiAvatarRenderJobResponse.model_validate(job)
# ── POST /{job_id}/smart-cover — 从最终成片智能抽封面(步骤②)────────
@router.post("/{job_id}/smart-cover", response_model=SmartCoverResponse)
def generate_render_smart_cover(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""从最终渲染成片智能抽帧生成封面(MediaKit 抽帧 + 评分选最佳帧 + 转存 OSS).
- 必须等渲染任务 completed 后才可调用(否则返回 400)
- 生成成功后自动更新 render_job 的 cover_config 与 output_cover_url
"""
from app.services.ai_avatar_render_service import AiAvatarRenderService
svc = AiAvatarRenderService(db)
job = svc.get_render_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="渲染任务不存在")
if job.status != "completed":
raise HTTPException(status_code=400, detail="请先完成视频生成")
video_url = (job.output_video_url or "").strip()
if not video_url:
raise HTTPException(status_code=400, detail="渲染成片视频 URL 为空")
try:
# 从最终成片抽帧,帧本身已含标题/B-roll,直接转存 OSS
cover_url = generate_smart_cover(video_url, job_id=job_id, max_frames=5)
except Exception as exc:
logger.error(
"渲染成片智能封面生成异常: user=%s render_id=%s video_url=%s err=%s",
current_user.user.id,
job_id,
video_url[:80],
exc,
exc_info=True,
)
cover_url = ""
if not cover_url:
return SmartCoverResponse(
cover_url="",
status="fallback_failed",
message="智能抽帧失败(MediaKit 不可用或抽帧异常),请稍后重试",
)
# 更新 render_job 的封面字段(异步写入 DB;失败不影响返回)
try:
job.cover_config = {
**(job.cover_config if isinstance(job.cover_config, dict) else {}),
"mode": "auto_frame",
"url": cover_url,
}
job.output_cover_url = cover_url
job.updated_at = datetime.now(timezone.utc)
db.commit()
except Exception as exc:
logger.warning("更新 render_job 封面字段失败(不影响返回): job_id=%s err=%s", job_id, exc)
logger.info(
"渲染成片智能封面生成成功: user=%s render_id=%s cover_url=%s",
current_user.user.id,
job_id,
cover_url[:120],
)
return SmartCoverResponse(cover_url=cover_url, status="completed")
# ── POST /{job_id}/finalize — 封面选定后正式入库成片库 ────────────────────
@router.post("/{job_id}/finalize", response_model=FinalizeRenderResponse)
def finalize_render_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""用户完成封面选择后,将视频正式保存到成片库.
- 必须等渲染任务 completed 后才可调用
- 如果已通过 smart-cover/custom-cover 设置了封面,会自动带上
- 返回成片库视频ID
- 幂等:已 finalize 的任务重复调用会返回 existing 记录
"""
from app.services.ai_avatar_render_service import AiAvatarRenderError, AiAvatarRenderService
svc = AiAvatarRenderService(db)
job = svc.get_render_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="渲染任务不存在")
if job.status != "completed":
raise HTTPException(status_code=400, detail="请先完成视频生成")
# 幂等检查(通过 generation_task_id=job_id 识别,finalize_job 内部也做了一次,这里提前返回简化)
from packages.adapters.sqlalchemy_impl.models import GeneratedVideoModel
existing = (
db.query(GeneratedVideoModel)
.filter(
GeneratedVideoModel.user_id == current_user.user.id,
GeneratedVideoModel.generation_task_id == job_id,
)
.first()
)
if existing is not None:
return FinalizeRenderResponse(
video_id=existing.id,
cover_url=existing.thumbnail_url or "",
status="already_finalized",
)
try:
video = svc.finalize_job(job_id, current_user.user.id)
return FinalizeRenderResponse(
video_id=video.id,
cover_url=video.thumbnail_url or job.output_cover_url or "",
status="success",
)
except AiAvatarRenderError as exc:
status_map = {
"RenderJobNotFound": 404,
"RenderNotCompleted": 400,
"OutputVideoMissing": 400,
}
raise HTTPException(
status_code=status_map.get(exc.code, 400),
detail=str(exc),
) from exc
except Exception as exc:
logger.error("渲染任务finalize失败: job_id=%s err=%s", job_id, exc, exc_info=True)
raise HTTPException(status_code=500, detail=f"保存到成片库失败: {str(exc)}") from exc
+5 -24
View File
@@ -20,7 +20,7 @@ from packages.application import (
GetProjectUseCase,
ListAssetLibrariesUseCase,
)
from packages.domain import AssetLibrary, AssetLibraryKind
from packages.domain import AssetLibraryKind
from ._helpers import check_project_access
@@ -120,30 +120,11 @@ def ensure_default_library(
kind = AssetLibraryKind(request.kind)
# 查找该项目下同 kind 的素材库,返回第一个
existing = asset_library_repository.find_by_project(request.project_id)
for lib in existing:
if lib.kind == kind:
return _to_asset_library_response(lib)
# 不存在 → 自动创建
import uuid
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
# Issue #1775: 幂等获取/创建——依赖唯一约束 uq_asset_libraries_project_kind
# 并发创建冲突时回滚重查返回已有记录,不再依赖应用层"先查后插",也不会 500。
default_name = _DEFAULT_LIBRARY_NAMES.get(request.kind, f"{request.kind}素材库")
library = AssetLibrary(
id=str(uuid.uuid4()),
project_id=request.project_id,
name=default_name,
kind=kind,
asset_count=0,
total_size=0,
created_at=now,
updated_at=now,
)
created = asset_library_repository.create(library)
return _to_asset_library_response(created)
library = asset_library_repository.get_or_create_default_library(request.project_id, kind, name=default_name)
return _to_asset_library_response(library)
@router.delete("/{library_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response)
+2 -2
View File
@@ -177,8 +177,8 @@ def _cleanup_expired_uploads() -> int:
meta_file.unlink()
cleaned += 1
logger.info(f"Cleaned up expired upload: {upload_id}")
except Exception as e:
logger.warning(f"Failed to cleanup upload metadata {meta_file}: {e}")
except Exception:
logger.exception("Failed to cleanup upload metadata: %s", meta_file)
return cleaned
+36 -111
View File
@@ -58,27 +58,10 @@ def _variant_value(values: list[str], index: int, fallback: str = "") -> str:
def _query_voice_durations(db: Session, voice_ids: list[str]) -> list[float]:
"""批量查询配音素材时长(秒),#1749 配音时长分配用。
"""[已下沉] 路由层兼容别名 → app.services.generation_common.query_voice_durations。"""
from app.services.generation_common import query_voice_durations
逐项 try/float 硬化:MagicMock/异常/缺失 → 0.0(无配音不分配,不阻断)。
"""
ids = [v for v in dict.fromkeys(voice_ids or []) if v]
if not ids:
return []
try:
from packages.adapters.sqlalchemy_impl.models import AssetModel
rows = db.query(AssetModel.id, AssetModel.duration).filter(AssetModel.id.in_(ids)).all()
dur_map: dict[str, float] = {}
for row in rows:
try:
dur_map[row[0]] = float(row[1] or 0.0)
except (TypeError, ValueError):
dur_map[row[0]] = 0.0
return [dur_map.get(v, 0.0) for v in ids]
except Exception:
logger.warning("[生成任务] 配音时长查询失败(按无配音处理,不阻断)", exc_info=True)
return [0.0 for _ in ids]
return query_voice_durations(db, voice_ids)
def _to_generation_task_response(task) -> GenerationTaskResponse:
@@ -184,61 +167,10 @@ def _writeback_edit_plan_config(
title_config: dict | None,
db: Session,
) -> None:
"""任务入队成功后,回写 EditPlan.configgeneration_task_id + title_config。
"""[已下沉] 路由层兼容别名 → app.services.generation_common.writeback_edit_plan_config。"""
from app.services.generation_common import writeback_edit_plan_config
用 merge 方式更新,不整体覆盖 config,避免丢失其他字段。
失败只记日志,不影响任务创建。
"""
if not plan_id:
return
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
plan_model = db.query(EditPlanModel).filter(EditPlanModel.id == plan_id).first()
if plan_model is None:
logger.warning("[生成任务] 回写plan.config失败: plan不存在 plan_id=%s", plan_id)
return
current_config = plan_model.config if isinstance(plan_model.config, dict) else {}
merged = dict(current_config)
merged["generation_task_id"] = task_id
# 检查标题是否发生变化,如果变化则清除 cover 字段强制重新生成封面
if title_config:
old_title_config = merged.get("title_config", {}) or {}
old_title_text = (old_title_config.get("text") or "").strip()
new_title_text = (title_config.get("text") or "").strip()
if old_title_text != new_title_text:
# 标题变化,清除旧封面
if "cover" in merged:
del merged["cover"]
logger.info(
"[生成任务] 标题变化,清除旧封面: plan_id=%s old_title=%s new_title=%s",
plan_id,
old_title_text,
new_title_text,
)
merged["title_config"] = title_config
plan_model.config = merged
db.commit()
logger.info(
"[生成任务] 回写plan.config成功: plan_id=%s task_id=%s keys=%s",
plan_id,
task_id,
list(merged.keys()),
)
except Exception as e:
logger.warning(
"[生成任务] 回写plan.config异常(不影响任务创建): plan_id=%s error=%s",
plan_id,
e,
exc_info=True,
)
try:
db.rollback()
except Exception:
pass
return writeback_edit_plan_config(plan_id, task_id, title_config, db)
def _resolve_project_and_library(
@@ -489,25 +421,14 @@ def create_generation_task(
# 各变体配音时长(查询硬化:异常 → 0.0 不阻断)
voice_durations = _query_voice_durations(db, variant_voices)
# 解析批量源 plan:优先前端传入;否则按 template_id + user 查最新(与单任务兜底同源
batch_source_plan_id = request.source_edit_plan_id
if not batch_source_plan_id and request.template_id:
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
# 解析批量源 plan:优先前端传入;否则按 template_id + user 查最新(公共函数
from app.services.generation_common import resolve_latest_plan_by_template
_latest = (
db.query(EditPlanModel)
.filter(
EditPlanModel.template_id == request.template_id,
EditPlanModel.created_by_user_id == user_id,
)
.order_by(EditPlanModel.created_at.desc())
.first()
)
if _latest:
batch_source_plan_id = _latest.id
except Exception:
logger.warning("[生成任务] 批量源 plan 解析失败", exc_info=True)
batch_source_plan_id = (
request.source_edit_plan_id
or resolve_latest_plan_by_template(db, template_id=request.template_id, user_id=user_id)
or ""
)
if not batch_source_plan_id and not request.variant_plan_ids:
# 无任何可用源 plan:批量变体无从选片,明确报错,严禁静默共用/同源
@@ -552,7 +473,12 @@ def create_generation_task(
) from clone_err
variant_plan_ids.append(_plan0.id)
# 变体 1..N-1 独立选片
# #1855 P0:批次区间避让表,从变体0实际clips构建初始值(公共函数)
from app.services.generation_common import collect_plan_segments as _collect_segments
_batch_segments = _collect_segments(_plan0.id, _plan_svc._clip_repo)
# 变体 1..N-1 独立选片(传入累积batch_segments做素材区间避让)
for task_index in range(1, count):
variant = None
last_err: Exception | None = None
@@ -564,6 +490,7 @@ def create_generation_task(
created_by_user_id=user_id,
name_suffix=f"批量{task_index + 1}",
voice_duration=voice_durations[task_index] if task_index < len(voice_durations) else 0.0,
batch_segments=_batch_segments,
)
break
except ValueError as ve:
@@ -595,7 +522,16 @@ def create_generation_task(
) from last_err
variant_plan_ids.append(variant.id)
# ③ 配音时长分配(回传 plan / clone 变体0 均需幂等分配;reselect 已在选片时分配)
# #1855 P0:把新变体的clips区间追加到batch_segments,供下一变体避让
try:
_new_segs = _collect_segments(variant.id, _plan_svc._clip_repo)
for _aid, _ivs in _new_segs.items():
_batch_segments.setdefault(_aid, []).extend(_ivs)
except Exception:
logger.exception("[生成任务] 变体%d 区间收集失败(不阻断)", task_index)
# ③ 配音时长分配(回传 plan / clone 变体0 均需幂等分配;reselect 已在选片时分配,
# #1855apply_voice_duration_to_plan 已内置幂等判断,重复调用安全)
for _vi, _pid in enumerate(variant_plan_ids):
_vd = voice_durations[_vi] if _vi < len(voice_durations) else 0.0
if _vd > 0:
@@ -616,24 +552,13 @@ def create_generation_task(
)
_single_vd: list[float] = _query_voice_durations(db, _voices)
_single_dur = _single_vd[0] if _single_vd else 0.0
_single_plan = request.source_edit_plan_id
if not _single_plan and request.template_id:
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
from app.services.generation_common import resolve_latest_plan_by_template
_latest = (
db.query(EditPlanModel)
.filter(
EditPlanModel.template_id == request.template_id,
EditPlanModel.created_by_user_id == user_id,
)
.order_by(EditPlanModel.created_at.desc())
.first()
)
if _latest:
_single_plan = _latest.id
except Exception:
logger.warning("[生成任务] 单任务源 plan 解析失败", exc_info=True)
_single_plan = (
request.source_edit_plan_id
or resolve_latest_plan_by_template(db, template_id=request.template_id, user_id=user_id)
or ""
)
if _single_dur > 0 and _single_plan:
from app.services.edit_plan_service import EditPlanService
@@ -90,25 +90,12 @@ def create_variant_plans(
except VariantVoiceError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
# 解析源 plan:显式传入优先;否则按 template_id + user 查最新
# 解析源 plan:显式传入优先;否则按 template_id + user 查最新(公共函数)
from app.services.generation_common import resolve_latest_plan_by_template
source_plan_id = request.source_edit_plan_id.strip()
if not source_plan_id and request.template_id.strip():
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
_latest = (
db.query(EditPlanModel)
.filter(
EditPlanModel.template_id == request.template_id.strip(),
EditPlanModel.created_by_user_id == user_id,
)
.order_by(EditPlanModel.created_at.desc())
.first()
)
if _latest:
source_plan_id = _latest.id
except Exception:
logger.warning("[variant-plans] 源 plan 解析失败", exc_info=True)
source_plan_id = resolve_latest_plan_by_template(db, template_id=request.template_id, user_id=user_id) or ""
if not source_plan_id:
raise HTTPException(
@@ -122,7 +109,7 @@ def create_variant_plans(
voice_durations = _query_voice_durations(db, voices)
except Exception:
logger.warning("[variant-plans] 配音时长查询失败(按占位段长选片)", exc_info=True)
logger.exception("[variant-plans] 配音时长查询失败(按占位段长选片)")
voice_durations = [0.0] * request.count
from app.services.edit_plan_service import EditPlanService
@@ -143,7 +130,7 @@ def create_variant_plans(
except HTTPException:
raise
except Exception as e:
logger.error("[variant-plans] 选片异常: %s", e, exc_info=True)
logger.exception("[variant-plans] 选片异常")
raise HTTPException(status_code=500, detail="选片失败,请稍后重试") from e
# 组装 clips 响应
+235
View File
@@ -0,0 +1,235 @@
"""对口型 API 路由 — #1796 MediaKit 对口型, #1809 参数调整, #1845 配音前置.
接口:
POST /api/v1/lipsync/jobs 提交对口型任务(支持 TTS/直传/预合成 三种模式)
GET /api/v1/lipsync/jobs 任务列表
GET /api/v1/lipsync/jobs/{id} 任务详情
POST /api/v1/lipsync/jobs/{id}/refresh 刷新任务状态
POST /api/v1/lipsync/jobs/{id}/cancel 取消任务
POST /api/v1/lipsync/tts-preview #1845 步骤1 TTS 预合成(同步 HTTP~2-3s
"""
from __future__ import annotations
import logging
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import (
get_db_session,
get_voice_clone_profile_repository,
)
from app.schemas.lipsync import (
AiAvatarTtsPreviewRequest,
AiAvatarTtsPreviewResponse,
CreateLipsyncJobRequest,
LipsyncJobResponse,
)
from app.services.lipsync_service import LipsyncService
from app.services.mediakit_client import MediaKitError
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
router = APIRouter()
def _get_service(
db: Session = Depends(get_db_session),
voice_clone_repo=Depends(get_voice_clone_profile_repository),
) -> LipsyncService:
# voice_clone_repo 用于克隆音色 profile 解析
return LipsyncService(
db,
voice_clone_repo=voice_clone_repo,
)
# ── POST /jobs — 提交对口型任务 ───────────────────────────────────────────
@router.post("/jobs", response_model=LipsyncJobResponse, status_code=201)
def create_lipsync_job(
body: CreateLipsyncJobRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""提交对口型任务.
三种模式:
- TTS 直生(旧版/降级):传 {video_url, voice_id, script_text, speed?, emotion?}
后端 dispatch Celery 异步任务。
- 直接音频:传 {video_url, audio_url},后端同步下载+算timings+提交MediaKit。
- 预合成音频(#1845 新主路径):传 {video_url, audio_url, audio_duration, sentence_timings}
后端同步ffprobe+写入timings+直接提交MediaKit~2-3s)。
"""
try:
job = svc.create_job(
user_id=current_user.user.id,
video_url=body.video_url,
audio_url=body.audio_url,
audio_duration=body.audio_duration,
sentence_timings=body.sentence_timings,
voice_id=body.voice_id,
script_text=body.script_text,
speed=body.speed,
emotion=body.emotion,
enable_video_loop=body.enable_video_loop,
project_id=body.project_id,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except MediaKitError as exc:
status_code = 502
if exc.code in ("VoiceForbidden",):
status_code = 403
elif exc.code in ("InvalidInput", "TTSInvalidParam", "VoiceNotReady"):
status_code = 400
raise HTTPException(
status_code=status_code,
detail={
"code": exc.code,
"message": str(exc),
"request_id": getattr(exc, "request_id", ""),
},
) from exc
except Exception as exc:
logger.error("创建对口型任务异常: %s", exc, exc_info=True)
raise HTTPException(
status_code=400,
detail=f"创建对口型任务失败: {exc}",
) from exc
return job
# ── POST /tts-preview — #1845 步骤1 TTS 预合成 ──────────────────────────
@router.post("/tts-preview", response_model=AiAvatarTtsPreviewResponse)
def preview_tts(
body: AiAvatarTtsPreviewRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""步骤1「生成配音」同步 TTS 预合成.
同步执行 TTS 合成 → 下载音频 → ffprobe 时长 → 句子时间戳计算,
不创建 LipsyncJob、不转存 OSS,直接返回 CosyVoice 临时 URL~24h 有效)。
耗时约 2-3 秒。
"""
try:
result = svc.preview_tts(
user_id=current_user.user.id,
voice_id=body.voice_id,
script_text=body.script_text,
speed=body.speed,
emotion=body.emotion,
)
except MediaKitError as exc:
status_code = 400
if exc.code in ("VoiceForbidden",):
status_code = 403
elif exc.code in ("TTSNoAudio",):
status_code = 502
raise HTTPException(
status_code=status_code,
detail={
"code": exc.code,
"message": str(exc),
},
) from exc
except Exception as exc:
logger.error("TTS 预合成异常: %s", exc, exc_info=True)
raise HTTPException(
status_code=400,
detail=f"TTS 合成失败: {exc}",
) from exc
return result
# ── GET /jobs — 任务列表 ─────────────────────────────────────────────────
@router.get("/jobs", response_model=dict)
def list_lipsync_jobs(
project_id: str = Query("", description="项目 ID 过滤"),
status: str = Query("", description="状态过滤"),
offset: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""获取对口型任务列表."""
items, total = svc.list_jobs(
user_id=current_user.user.id,
project_id=project_id,
status=status,
offset=offset,
limit=limit,
)
return {
"items": [LipsyncJobResponse.model_validate(j) for j in items],
"total": total,
"offset": offset,
"limit": limit,
}
# ── GET /jobs/{job_id} — 任务详情 ────────────────────────────────────────
@router.get("/jobs/{job_id}", response_model=LipsyncJobResponse)
def get_lipsync_job(
job_id: str,
background: BackgroundTasks,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""获取对口型任务详情."""
job = svc.get_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="任务不存在")
if job.status not in ("completed", "failed"):
background.add_task(svc.refresh_job_status, job_id, current_user.user.id)
return job
# ── POST /jobs/{job_id}/refresh — 刷新状态 ───────────────────────────────
@router.post("/jobs/{job_id}/refresh", response_model=LipsyncJobResponse)
def refresh_lipsync_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""从 MediaKit 拉取最新状态并更新."""
job = svc.refresh_job_status(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="任务不存在")
return job
# ── POST /jobs/{job_id}/cancel — 取消任务 ────────────────────────────────
@router.post("/jobs/{job_id}/cancel", response_model=LipsyncJobResponse)
def cancel_lipsync_job(
job_id: str,
current_user: AuthenticatedUser = Depends(get_current_user),
svc: LipsyncService = Depends(_get_service),
):
"""取消对口型任务(仅 pending/tts_processing/submitted 状态可取消)."""
job = svc.cancel_job(job_id, current_user.user.id)
if job is None:
raise HTTPException(status_code=404, detail="任务不存在")
if job.status != "cancelled":
raise HTTPException(
status_code=400,
detail=f"任务状态 {job.status} 不可取消,仅 pending/tts_processing/submitted 可取消",
)
return job
+41 -1
View File
@@ -1,13 +1,14 @@
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_project_repository
from app.dependencies import get_asset_library_repository, get_project_repository
from app.schemas.project import (
CreateProjectRequest,
ListProjectsResponse,
ProjectResponse,
)
from fastapi import APIRouter, Depends, HTTPException, Response, status
from pydantic import BaseModel
from packages.application import (
CreateProjectCommand,
@@ -16,10 +17,20 @@ from packages.application import (
GetProjectUseCase,
ListProjectsUseCase,
)
from packages.domain import AssetLibraryKind
router = APIRouter()
class DefaultContextResponse(BaseModel):
"""幂等默认上下文响应(Issue #1775):默认项目 + 各类型默认素材库 ID。"""
project_id: str
image_library_id: str
video_library_id: str
voice_library_id: str
def _to_project_response(item) -> ProjectResponse:
return ProjectResponse(
id=item.id,
@@ -72,6 +83,35 @@ def create_project(
return _to_project_response(project)
@router.post("/ensure-default", response_model=DefaultContextResponse)
def ensure_default_project_and_libraries(
authenticated_user: AuthenticatedUser = Depends(get_current_user),
project_repository: Any = Depends(get_project_repository),
asset_library_repository: Any = Depends(get_asset_library_repository),
) -> DefaultContextResponse:
"""幂等获取/创建当前用户的默认项目和三类默认素材库(Issue #1775)。
- 同一用户永远只有一个默认项目(部分唯一索引 uq_projects_owner_default
- 同一项目同 kind 永远只有一个默认素材库(唯一约束 uq_asset_libraries_project_kind
- 并发调用/失败重试:唯一约束冲突时返回已存在记录,不报 500
- 项目和素材库的创建各自在仓储事务内幂等,冲突回滚后重查返回同一条
"""
user_id = authenticated_user.user.id
project = project_repository.get_or_create_default_project(user_id)
libraries = {}
for kind in (AssetLibraryKind.VIDEO, AssetLibraryKind.VOICE, AssetLibraryKind.IMAGE):
library = asset_library_repository.get_or_create_default_library(project.id, kind)
libraries[kind] = library.id
return DefaultContextResponse(
project_id=project.id,
image_library_id=libraries[AssetLibraryKind.IMAGE],
video_library_id=libraries[AssetLibraryKind.VIDEO],
voice_library_id=libraries[AssetLibraryKind.VOICE],
)
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
def delete_project(
project_id: str,
+123
View File
@@ -0,0 +1,123 @@
"""Script (口播文案库) CRUD routes — Issue #1795."""
from __future__ import annotations
from typing import Optional
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.schemas.script import (
CreateScriptRequest,
ScriptListResponse,
ScriptResponse,
ScriptSegment,
UpdateScriptRequest,
)
from app.services.script_service import ScriptNotFoundError, ScriptService
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy.orm import Session
router = APIRouter()
def _get_service(session: Session = Depends(get_db_session)) -> ScriptService:
return ScriptService(session)
def _to_response(script) -> ScriptResponse:
segments = script.segments or []
return ScriptResponse(
id=script.id,
user_id=script.user_id,
title=script.title,
content=script.content,
segments=[
ScriptSegment(text=s.get("text", ""), duration=s.get("duration")) if isinstance(s, dict) else s
for s in segments
],
tags=script.tags or [],
created_at=script.created_at,
updated_at=script.updated_at,
)
@router.get("", response_model=ScriptListResponse)
def list_scripts(
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
tag: Optional[str] = Query(None, description="按标签筛选"),
authenticated_user: AuthenticatedUser = Depends(get_current_user),
svc: ScriptService = Depends(_get_service),
) -> ScriptListResponse:
user_id = authenticated_user.user.id
items, total = svc.list_scripts(user_id, skip=skip, limit=limit, tag=tag)
return ScriptListResponse(
items=[_to_response(i) for i in items],
total=total,
)
@router.post("", response_model=ScriptResponse, status_code=status.HTTP_201_CREATED)
def create_script(
request: CreateScriptRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
svc: ScriptService = Depends(_get_service),
) -> ScriptResponse:
user_id = authenticated_user.user.id
script = svc.create_script(
user_id=user_id,
title=request.title,
content=request.content,
segments=[s.model_dump() for s in request.segments],
tags=request.tags,
)
return _to_response(script)
@router.get("/{script_id}", response_model=ScriptResponse)
def get_script(
script_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
svc: ScriptService = Depends(_get_service),
) -> ScriptResponse:
user_id = authenticated_user.user.id
try:
script = svc.get_script(script_id, user_id)
except ScriptNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Script not found") from exc
return _to_response(script)
@router.put("/{script_id}", response_model=ScriptResponse)
def update_script(
script_id: str,
request: UpdateScriptRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
svc: ScriptService = Depends(_get_service),
) -> ScriptResponse:
user_id = authenticated_user.user.id
try:
script = svc.update_script(
script_id=script_id,
user_id=user_id,
title=request.title,
content=request.content,
segments=[s.model_dump() for s in request.segments] if request.segments is not None else None,
tags=request.tags,
)
except ScriptNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Script not found") from exc
return _to_response(script)
@router.delete("/{script_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
def delete_script(
script_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
svc: ScriptService = Depends(_get_service),
) -> Response:
user_id = authenticated_user.user.id
deleted = svc.delete_script(script_id, user_id)
if not deleted:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Script not found")
return
@@ -65,8 +65,8 @@ def _build_asset_analyses(
if url:
video_urls.append(url)
valid_asset_ids.append(aid)
except Exception as e:
logger.warning("获取素材URL失败: asset_id=%s error=%s", aid, str(e))
except Exception:
logger.exception("获取素材URL失败: asset_id=%s", aid)
if not video_urls:
logger.info("无可用视频素材,跳过视频理解分析")
@@ -108,7 +108,7 @@ def _build_asset_analyses(
return analyses
except Exception as e:
logger.warning("MediaKit 视频理解异常,将降级到无分析模式: %s", str(e))
logger.exception("MediaKit 视频理解异常,将降级到无分析模式: %s", e)
return {}
@@ -177,7 +177,7 @@ def editor_ai_recommend(
try:
db.rollback()
except Exception:
pass
logger.exception("db rollback failed in ai_recommend")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI推荐结果保存失败,请稍后重试",
@@ -132,8 +132,8 @@ def _build_asset_url_map(
result: dict[str, str | None] = {}
try:
storage = get_storage_service()
except Exception:
logger.warning("获取存储服务失败,跳过asset_url生成")
except Exception as e:
logger.exception("获取存储服务失败,跳过asset_url生成: %s", e)
return {aid: None for aid in asset_ids}
# 批量查询所有 Asset(单次 SQL IN 查询,避免 N+1)
@@ -141,7 +141,7 @@ def _build_asset_url_map(
assets = asset_repo.find_by_ids(unique_ids)
asset_map = {a.id: a for a in assets}
except Exception:
logger.warning("批量查询素材失败: asset_ids=%s", asset_ids, exc_info=True)
logger.exception("批量查询素材失败: asset_ids=%s", asset_ids)
return {aid: None for aid in asset_ids if aid}
for aid in unique_ids:
@@ -156,7 +156,7 @@ def _build_asset_url_map(
continue
result[aid] = storage.get_download_url(storage_key, expires_seconds=3600)
except Exception:
logger.warning("生成素材签名URL失败: asset_id=%s", aid, exc_info=True)
logger.exception("生成素材签名URL失败: asset_id=%s", aid)
result[aid] = None
return result
@@ -486,8 +486,8 @@ def _get_mediakit_recommendations(
if url:
video_urls.append(url)
valid_asset_ids.append(asset_id)
except Exception as e:
logger.warning("获取素材URL失败: asset_id=%s error=%s", asset_id, e)
except Exception:
logger.exception("获取素材URL失败: asset_id=%s", asset_id)
if not video_urls:
return {}
@@ -563,7 +563,7 @@ def _get_mediakit_recommendations(
return recommendations
except Exception as e:
logger.warning("MediaKit 智能选片异常,降级为随机选择: %s", e)
logger.exception("MediaKit 智能选片异常,降级为随机选择: %s", e)
return {}
@@ -860,7 +860,7 @@ def create_clips_from_assets_editor(
duplicate_warning = None
if dup_rate > 50:
duplicate_warning = f"查重率 {dup_rate:.1f}% 超过50%,建议更换素材或模板"
logger.warning(
logger.exception(
"from-assets 成片查重率超标: plan_id=%s dup_rate=%.1f%%",
plan_id,
dup_rate,
@@ -960,8 +960,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
# 尝试获取存储服务(用于生成视频 URL)
try:
storage = get_storage_service()
except Exception:
logger.warning("后台任务: 获取存储服务失败,跳过 SceneChange 更新")
except Exception as e:
logger.exception("后台任务: 获取存储服务失败,跳过 SceneChange 更新: %s", e)
return
# 获取 MediaKit 客户端
@@ -987,8 +987,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
if storage_key and mime.startswith("video/"):
try:
video_url = storage.get_download_url(storage_key)
except Exception as e:
logger.warning("后台任务: 获取素材URL失败: asset_id=%s error=%s", asset_id, e)
except Exception:
logger.exception("后台任务: 获取素材URL失败: asset_id=%s", asset_id)
# 构建该素材的占用区间列表(排除已更新片段)
def _get_other_segments(asset_id_inner, clip_id_inner):
@@ -1039,12 +1039,11 @@ def _update_mediakit_recommendations_async( # pragma: no cover
asset_id,
len(scene_changes),
)
except Exception as cache_err:
except Exception:
# 缓存写入失败不影响本次片段更新
logger.warning(
"后台任务: 场景点缓存写入失败: asset_id=%s error=%s",
logger.exception(
"后台任务: 场景点缓存写入失败: asset_id=%s",
asset_id,
cache_err,
)
# SceneChange 未获得有效结果 → 尝试 analyze_videos 作为 fallback
@@ -1124,11 +1123,10 @@ def _update_mediakit_recommendations_async( # pragma: no cover
recommended_start + clip_duration,
plan_id,
)
except Exception as me:
logger.warning(
"后台任务: 同步素材区间记录失败,回滚本次片段更新: clip_id=%s error=%s",
except Exception:
logger.exception(
"后台任务: 同步素材区间记录失败,回滚本次片段更新: clip_id=%s",
clip.id,
me,
)
db.rollback()
continue
@@ -1144,8 +1142,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
asset_id,
recommended_start,
)
except Exception as ue:
logger.warning("后台任务: 单个片段更新失败: clip_id=%s error=%s", clip.id, ue)
except Exception:
logger.exception("后台任务: 单个片段更新失败: clip_id=%s", clip.id)
try:
db.rollback()
except Exception:
@@ -1154,9 +1152,9 @@ def _update_mediakit_recommendations_async( # pragma: no cover
logger.info("后台任务完成: plan_id=%s 成功更新 %d 个片段", plan_id, updated_count)
except Exception as e:
except Exception:
# 后台任务失败不影响已创建的片段,静默处理
logger.warning("后台任务异常: plan_id=%s error=%s", plan_id, e, exc_info=True)
logger.exception("后台任务异常: plan_id=%s", plan_id)
if db:
try:
db.rollback()
+10 -1
View File
@@ -173,6 +173,14 @@ def synthesize(
# job.voice_id 统一存解析后的 CosyVoice voice_id
actual_voice_id = resolved_profile.voice_id
# 语速/情绪等合成参数随 metadata 落库,workflow 提交 CosyVoice 时读取透传
synthesis_meta = {
"speed": request.speed,
"emotion": request.emotion or "",
}
if request.metadata_:
synthesis_meta.update(request.metadata_)
use_case = CreateTTSJobUseCase(repository)
job = use_case.execute(
user_id=user_id,
@@ -180,7 +188,7 @@ def synthesize(
voice_id=actual_voice_id,
voice_model=request.voice_model,
voice_clone_profile_id=voice_clone_profile_id,
metadata=request.metadata_,
metadata=synthesis_meta,
)
# 提交 CosyVoice 合成任务
@@ -567,6 +575,7 @@ def preview_tts(
text=request.text,
voice_id=actual_voice_id,
speed=request.speed,
emotion=request.emotion,
)
except CosyVoiceError as e:
raise HTTPException(
+5
View File
@@ -208,6 +208,8 @@ def _create_pending_asset(
find-or-createprepare 阶段已按 file_hash/client_upload_id 预建的占位记录
会被 find_by_library_and_file_hash/find_by_library_and_client_upload_id 命中,
直接复用并补齐字段(避免 pre-create + complete 重复建两条)。
Issue #1776: 素材库计数由 asset_repository.create() 自动维护。
"""
# 1. 按 client_upload_id / file_hash 查找现有记录
existing = None
@@ -389,6 +391,7 @@ async def prepare_direct_upload(
file_size=request.file_size,
)
pending_asset_id = pending.id
# Issue #1776: 计数由 asset_repository.create() 自动维护
except Exception as error:
# 预建失败不阻塞签名:complete 仍可按 OSS 文件 + hash 兜底去重
logger.warning("预建 asset 占位失败,降级走 old flow: %s", error)
@@ -474,6 +477,7 @@ async def complete_direct_upload(
client_upload_id=request.client_upload_id,
file_size=request.file_size,
)
# Issue #1776: 计数由 asset_repository.create() 自动维护
job = _submit_ingest_job(
project_id=request.project_id,
@@ -568,6 +572,7 @@ async def upload_asset(
file_hash=file_hash,
client_upload_id=client_upload_id,
)
# Issue #1776: 计数由 asset_repository.create() 自动维护
job = _submit_ingest_job(
project_id=project_id,
+6 -6
View File
@@ -163,12 +163,12 @@ def create_voice_clone(
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
logger.exception("Failed to dispatch Celery task")
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
try:
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
except Exception as inner_e:
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
except Exception:
logger.exception("Failed to mark profile as failed after dispatch error")
return _to_response(profile)
@@ -277,12 +277,12 @@ def retry_voice_clone(
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone retry {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
logger.exception("Failed to dispatch Celery task")
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
try:
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
except Exception as inner_e:
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
except Exception:
logger.exception("Failed to mark profile as failed after dispatch error")
return _to_response(profile)
+4 -3
View File
@@ -105,8 +105,8 @@ def _resolve_preset_preview_url(
_preset_preview_cache[voice_id] = (audio_url, time.time())
logger.info("Preset voice preview generated: %s", voice_id)
return audio_url
except Exception as e:
logger.warning("Failed to generate preview for %s, using fallback: %s", voice_id, e)
except Exception:
logger.exception("Failed to generate preset voice preview: voice_id=%s", voice_id)
return fallback_url
@@ -127,6 +127,7 @@ def _resolve_all_preset_preview_urls(
try:
result_map[p.voice_id] = _resolve_preset_preview_url(p.voice_id, p.preview_url, cosyvoice)
except Exception:
logger.exception("Failed to resolve preset preview URL: voice_id=%s", p.voice_id)
result_map[p.voice_id] = p.preview_url
return result_map
@@ -732,7 +733,7 @@ def _find_or_create_voice_library_for_extract(*, user_id, project_repository, as
try:
session.rollback()
except Exception:
pass
logger.exception("session rollback failed in _find_or_create_voice_library")
for lib in asset_library_repository.find_by_project(project.id):
kind = lib.kind.value if hasattr(lib.kind, "value") else lib.kind
if kind == AssetLibraryKind.VOICE.value:
View File
+126
View File
@@ -0,0 +1,126 @@
"""AI数字人渲染合成管线 API Schema — #1798."""
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from pydantic import BaseModel, Field, field_validator
class BRollSegment(BaseModel):
"""B-roll 片段配置."""
script_segment_index: int = Field(..., ge=0, description="对应文案片段索引")
asset_url: str = Field(..., description="B-roll 素材 URL")
mode: str = Field(..., description="插入模式: fullscreen 或 pip")
start_time: float = Field(..., ge=0.0, description="在对口型视频中的起始时间(秒)")
end_time: float = Field(..., ge=0.0, description="在对口型视频中的结束时间(秒)")
pip_position: Optional[str] = Field("bottom_right", description="pip 模式位置")
pip_scale: Optional[float] = Field(0.3, ge=0.05, le=1.0, description="pip 模式缩放比例")
@field_validator("mode")
@classmethod
def validate_mode(cls, v: str) -> str:
v = v.strip().lower()
if v not in ("fullscreen", "pip"):
raise ValueError("mode 必须为 fullscreen 或 pip")
return v
@field_validator("asset_url")
@classmethod
def validate_asset_url(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("asset_url 不能为空")
if not v.startswith(("http://", "https://")):
raise ValueError("asset_url 必须是 HTTP/HTTPS URL")
return v
@field_validator("end_time")
@classmethod
def validate_end_time(cls, v: float, info: Any) -> float:
start = info.data.get("start_time", 0.0)
if v <= start:
raise ValueError("end_time 必须大于 start_time")
return v
class CreateAiAvatarRenderRequest(BaseModel):
"""创建渲染任务请求."""
lipsync_job_id: str = Field(..., description="对口型任务 ID")
script_id: str = Field("", description="文案 ID(选自文案库时传;手动输入文案直生场景可留空)")
b_roll_segments: list[BRollSegment] = Field(default_factory=list, description="B-roll 片段列表")
title_config: dict[str, Any] = Field(
default_factory=dict, description="标题配置(可含 title_image_dataurl:前端 Canvas 渲染的标题 PNG dataURL"
)
cover_config: dict[str, Any] = Field(default_factory=dict, description="封面配置")
project_id: str = Field("", description="项目 ID")
@field_validator("lipsync_job_id")
@classmethod
def validate_lipsync_job_id(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("lipsync_job_id 不能为空")
return v
@field_validator("script_id")
@classmethod
def validate_script_id(cls, v: str) -> str:
return (v or "").strip()
class AiAvatarRenderJobResponse(BaseModel):
"""渲染任务响应."""
id: str
user_id: str
project_id: str
lipsync_job_id: str
script_id: str = ""
b_roll_segments: list[dict[str, Any]]
title_config: dict[str, Any]
cover_config: dict[str, Any]
status: str
progress: int
output_video_url: str
output_cover_url: str
output_duration: float
error_message: str
submitted_at: Optional[datetime] = None
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class AiAvatarRenderProgressResponse(BaseModel):
"""渲染进度响应."""
status: str
progress: int
output_video_url: str
output_cover_url: str
output_duration: float
error_message: str
class SmartCoverResponse(BaseModel):
"""智能封面响应(封面从最终成片抽帧,不再叠加标题)."""
cover_url: str = Field("", description="封面图公网 URL(OSS,非临时);失败为空")
status: str = Field("completed", description="completed / fallback_failed")
message: str = Field("", description="失败原因(如有)")
class FinalizeRenderResponse(BaseModel):
"""封面选好后点「完成」,正式入库成片库的响应."""
video_id: str = Field(..., description="成片库视频ID")
cover_url: str = Field("", description="封面URL")
status: str = Field("success", description="success/already_finalized")
+131
View File
@@ -0,0 +1,131 @@
"""对口型 API Schema 定义 — #1796 / #1809 / #1822 / #1845(配音前置).
支持三种输入模式:
1. TTS 直生模式(兼容旧版前端):传 voice_id + script_text+ speed/emotion),
后端 Celery 异步做 TTS 合成 + MediaKit 提交。
2. 直接音频模式:传 video_url + audio_url(音频已由调用方准备好)。
3. 预合成音频模式(#1845 配音前置新主路径):前端先调 POST /lipsync/tts-preview
拿到 audio_url + sentence_timings,再在 create_job 时传 audio_url + audio_duration
+ sentence_timings,后端跳过 TTS 和时间戳计算,直接 ffprobe 校验后提交 MediaKit。
"""
from __future__ import annotations
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field, model_validator
class LipsyncJobResponse(BaseModel):
"""对口型任务响应."""
id: str
user_id: str
project_id: str
video_url: str
audio_url: str
enable_video_loop: bool
voice_id: str = ""
script_text: str = ""
speed: float = 1.0
emotion: str = ""
mediakit_task_id: str
status: str
output_video_url: str
output_duration: float
error_message: str
error_code: str
sentence_timings: Optional[list] = None
submitted_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class CreateLipsyncJobRequest(BaseModel):
"""创建对口型任务请求.
三种模式(三选一):
- TTS 直生(旧版/降级):voice_id + script_text 必填;audio_url 留空。
- 直接音频:video_url + audio_url 必填。
- 预合成音频(#1845 新主路径):audio_url 必填 + 可选 audio_duration/sentence_timings
后端同步 ffprobe 校验时长、写入 timings,直接提交 MediaKit。
"""
video_url: str = Field(..., description="人物视频 URL(MP4,≤30min,单人真人)")
# 模式 2/3:直接/预合成音频
audio_url: str = Field("", description="驱动音频 URLmp3/aac/wav/m4a/flac);直生模式留空")
audio_duration: Optional[float] = Field(None, ge=0, description="预合成音频时长(秒),可选;后端会 ffprobe 校验")
sentence_timings: Optional[list] = Field(None, description="预合成接口返回的句子时间戳,可选;若传入则直接写入 job")
# 模式 1TTS 直生
voice_id: str = Field("", description="音色 ID(预置音色或克隆音色 profile UUID")
script_text: str = Field("", description="要合成的文案(直生模式必填,最长 5000 字符)")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速(0.5-2.0),默认 1.0")
emotion: str = Field("", description="情绪(natural/excited/calm/friendly 或中文 自然/兴奋/沉稳/亲切)")
enable_video_loop: bool = Field(
True, description="音频长于视频时是否循环画面(AI数字人默认开启,防止音频长于视频被截断)"
)
project_id: str = Field("", description="项目 ID(可选)")
@model_validator(mode="after")
def _validate_input_mode(self) -> "CreateLipsyncJobRequest":
video = (self.video_url or "").strip()
if not video:
raise ValueError("video_url 不能为空")
if not video.startswith(("http://", "https://")):
raise ValueError("video_url 必须是 HTTP/HTTPS URL")
lower = video.lower().split("?")[0]
allowed_video_exts = (".mp4", ".mov", ".m4v", ".webm", ".avi", ".mkv", ".3gp")
if not any(lower.endswith(ext) for ext in allowed_video_exts):
raise ValueError("video_url 格式不支持,仅支持: " + ", ".join(allowed_video_exts))
has_audio = bool((self.audio_url or "").strip())
has_tts = bool((self.voice_id or "").strip()) and bool((self.script_text or "").strip())
if not has_audio and not has_tts:
raise ValueError(
"必须提供驱动音频:要么传 audio_url(直接/预合成音频模式),"
"要么同时传 voice_id + script_textTTS 直生模式)"
)
if has_tts and len(self.script_text) > 5000:
raise ValueError("script_text 最长 5000 字符")
if has_audio:
au = self.audio_url.strip()
if not au.startswith(("http://", "https://")):
raise ValueError("audio_url 必须是 HTTP/HTTPS URL")
au_lower = au.lower().split("?")[0]
allowed = (".mp3", ".aac", ".wav", ".m4a", ".flac")
if not any(au_lower.endswith(ext) for ext in allowed):
raise ValueError(f"audio_url 格式不支持,仅支持: {', '.join(allowed)}")
self.audio_url = au
return self
# ── #1845 TTS 预合成接口 ────────────────────────────────────────────────
class AiAvatarTtsPreviewRequest(BaseModel):
"""步骤1「生成配音」预合成请求(同步 HTTP,~2-3s)."""
voice_id: str = Field(..., min_length=1, max_length=128, description="音色 ID")
script_text: str = Field(..., min_length=1, max_length=5000, description="要合成的文案")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速(0.5-2.0),默认 1.0")
emotion: str = Field("natural", max_length=32, description="情绪")
class AiAvatarTtsPreviewResponse(BaseModel):
"""TTS 预合成响应(临时 URL,24h 内有效,足够当前会话使用)."""
audio_url: str = Field(..., description="CosyVoice 临时音频 URL")
duration: float = Field(..., ge=0, description="音频总时长(秒),ffprobe 测得")
sentence_timings: list[dict] = Field(..., description="句子级精确时间戳")
+45
View File
@@ -0,0 +1,45 @@
"""Script (口播文案库) Pydantic schemas — Issue #1795."""
from __future__ import annotations
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, Field
class ScriptSegment(BaseModel):
"""单段文案."""
text: str
duration: Optional[float] = None
class ScriptResponse(BaseModel):
id: str
user_id: str
title: str
content: str
segments: List[ScriptSegment] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
created_at: datetime
updated_at: datetime
class ScriptListResponse(BaseModel):
items: list[ScriptResponse]
total: int = 0
class CreateScriptRequest(BaseModel):
title: str = Field(..., min_length=1, max_length=255)
content: str = ""
segments: List[ScriptSegment] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
class UpdateScriptRequest(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=255)
content: Optional[str] = None
segments: Optional[List[ScriptSegment]] = None
tags: Optional[List[str]] = None
+2
View File
@@ -16,6 +16,7 @@ class TTSSynthesizeRequest(BaseModel):
output_name: str = Field("", description="输出文件名")
language: str = Field("zh-CN", description="语言")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文 自然/兴奋/沉稳/亲切)")
voice_model: str = Field("", description="语音模型名称")
voice_clone_profile_id: str = Field("", description="关联的音色克隆档案 ID")
format: str = Field("mp3", description="输出格式(mp3/wav/pcm")
@@ -109,6 +110,7 @@ class TTSPreviewRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=200, description="合成文本,限制 200 字")
voice_id: str = Field(..., min_length=1, description="音色 ID")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文)")
pitch: float = Field(1.0, ge=0.5, le=2.0, description="音调(预留,当前未使用)")
@@ -0,0 +1,215 @@
"""AI 数字人封面服务 — MediaKit 抽帧 + 质量评分选最佳帧 + 转存 OSS.
与 generation_cover.py 的智能选帧能力对齐(不再用 FFmpeg 简单截帧):
1. MediaKit extract_frames 抽取多帧(默认 5 帧,SpecifiedFrames 策略)
2. cover_frame_scorer.score_frames 按清晰度/亮度/色彩评分选最佳
3. 下载最佳帧并转存 OSS,返回公网封面 URL
设计原则:封面一律从最终成片(已叠加标题/B-roll)抽帧,帧本身已含标题,
本服务**不再叠加标题**。对口型阶段的裸视频封面入口已删除(废弃)。
降级:MediaKit 不可用或抽帧失败时返回空字符串,由调用方决定回退策略。
"""
from __future__ import annotations
import logging
import tempfile
import uuid
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse
logger = logging.getLogger(__name__)
# MediaKit 抽帧轮询参数:poll_interval=2s × max_poll=30 → 最长 60s(与 mediakit_client 默认值/lipsync 轮询保持一致,防止合成视频下载+抽帧超时)
COVER_POLL_INTERVAL = 2.0
COVER_MAX_POLL_ATTEMPTS = 30
# 帧图片下载超时(秒)
FRAME_DOWNLOAD_TIMEOUT = 20
# 最佳帧下载超时(用于 persist)
BEST_FRAME_DOWNLOAD_TIMEOUT = 30
# 自家 OSS 私有桶 URL 重签有效期(供 MediaKit GPU worker 拉取)
MEDIAKIT_URL_TTL_SECONDS = 7 * 24 * 3600
def _sign_video_url_for_mediakit(video_url: str) -> str:
"""如果 video_url 是自家 OSS 私有桶 URL,重新签名为长有效期预签名 URL。
MediaKit GPU worker 需要能公网访问 video_url,裸 public_url 在私有桶下会 403。
"""
if not video_url:
return video_url
try:
from packages.shared.storage import get_shared_storage_service
storage = get_shared_storage_service()
public_base = getattr(storage, "public_url", "")
if not isinstance(public_base, str) or not public_base:
return video_url
own_host = urlparse(public_base).netloc.lower()
url_host = urlparse(video_url).netloc.lower()
if own_host and url_host == own_host:
signed = storage.get_download_url(video_url, expires_seconds=MEDIAKIT_URL_TTL_SECONDS)
if signed:
logger.info("[数字人封面] video_url 已重签(自家 OSS 私有桶)")
return signed
except Exception:
logger.warning("[数字人封面] video_url 重签失败,使用原始 URL", exc_info=True)
return video_url
def select_best_cover_frame(video_url: str, *, max_frames: int = 5) -> str:
"""从视频抽取多帧并评分选最佳帧,返回最佳帧的临时 URL."""
if not video_url:
return ""
video_url = _sign_video_url_for_mediakit(video_url)
try:
from packages.shared.cover_frame_scorer import score_frames
from packages.shared.mediakit_client import get_mediakit_client
mk = get_mediakit_client()
if not mk.is_available:
logger.warning("[数字人封面] MediaKit 未配置,无法智能抽帧")
return ""
logger.info(
"[数字人封面] 开始抽帧: video_url=%s max_frames=%d",
video_url[:80],
max_frames,
)
snapshots = mk.extract_frames(
video_url=video_url,
strategy="SpecifiedFrames",
max_frames=max_frames,
poll_interval=COVER_POLL_INTERVAL,
max_poll_attempts=COVER_MAX_POLL_ATTEMPTS,
max_retries=1,
)
if not snapshots:
logger.warning("[数字人封面] MediaKit 未返回帧: %s", video_url[:80])
return ""
if len(snapshots) == 1:
return snapshots[0].get("image_url") or snapshots[0].get("url") or ""
import httpx
candidates = []
with httpx.Client(timeout=FRAME_DOWNLOAD_TIMEOUT, follow_redirects=True) as client:
for snap in snapshots:
url = snap.get("image_url") or snap.get("url") or ""
if not url:
continue
tmp_path: Optional[str] = None
try:
resp = client.get(url)
resp.raise_for_status()
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
tmp.write(resp.content)
tmp_path = tmp.name
candidates.append({"image_path": tmp_path, "url": url})
except Exception as e:
logger.warning("[数字人封面] 帧下载失败,跳过: url=%s err=%s", url[:80], e)
candidates.append({"image_path": None, "url": url, "score": 0.0})
if not candidates:
return snapshots[0].get("image_url") or snapshots[0].get("url") or ""
scored = score_frames(candidates)
best = scored[0] if scored else None
best_url = best.get("url", "") if best else ""
for c in candidates:
p = c.get("image_path")
if p:
try:
Path(p).unlink(missing_ok=True)
except Exception:
pass
logger.info(
"[数字人封面] 智能选帧完成: candidates=%d best_score=%s",
len(candidates),
best.get("score") if best else "n/a",
)
return best_url
except Exception:
logger.warning("[数字人封面] 智能选帧失败", exc_info=True)
return ""
def persist_cover_to_oss(
frame_url: str,
*,
job_id: str = "",
prefix: str = "ai-avatar/covers",
) -> str:
"""下载最佳帧图并转存到 OSS,返回公网封面 URL(预签名).
封面来自最终成片抽帧,帧本身已含标题,本函数不再做任何文字/图片叠加。
"""
if not frame_url:
return ""
tmp_path: Optional[str] = None
try:
import httpx
with httpx.Client(timeout=BEST_FRAME_DOWNLOAD_TIMEOUT, follow_redirects=True) as client:
resp = client.get(frame_url)
resp.raise_for_status()
if not resp.content:
logger.warning("[数字人封面] 帧图内容为空: %s", frame_url[:80])
return frame_url
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
tmp.write(resp.content)
tmp_path = tmp.name
from packages.shared.storage import get_shared_storage_service
storage = get_shared_storage_service()
token = job_id or uuid.uuid4().hex[:12]
cover_key = f"{prefix}/{token}/cover_{uuid.uuid4().hex[:8]}.jpg"
public_url = storage.upload_file(
file_or_path=tmp_path,
storage_key=cover_key,
content_type="image/jpeg",
)
logger.info("[数字人封面] 封面已转存 OSS: key=%s", cover_key)
if public_url:
signed = storage.get_download_url(cover_key, expires_seconds=86400)
return signed
return frame_url
except Exception:
logger.warning("[数字人封面] 封面转存 OSS 失败,返回原始 URL", exc_info=True)
return frame_url
finally:
if tmp_path:
try:
Path(tmp_path).unlink(missing_ok=True)
except Exception:
pass
def generate_smart_cover(
video_url: str,
*,
job_id: str = "",
max_frames: int = 5,
) -> str:
"""一站式:MediaKit 智能抽帧选最佳 → 转存 OSS。失败返回空字符串。
封面从最终成片抽帧,不再叠加任何标题(帧本身已含)。
"""
best_frame = select_best_cover_frame(video_url, max_frames=max_frames)
if not best_frame:
return ""
return persist_cover_to_oss(best_frame, job_id=job_id)
@@ -0,0 +1,658 @@
"""AI数字人渲染合成 Service — #1798.
职责:
- 创建/查询/取消渲染任务
- 调用 Celery 异步任务执行渲染
- B-roll 合成 + 标题叠加 + 封面提取
- 用户隔离
"""
from __future__ import annotations
import base64
import binascii
import logging
import os
import subprocess
import tempfile
import uuid
from datetime import datetime, timezone
from typing import Any, Optional
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import (
AiAvatarRenderJob,
LipsyncJobModel,
ScriptModel,
)
from packages.domain.video_filter_builder import (
build_broll_overlay_filter,
build_title_drawtext_filter,
build_title_overlay_filter,
)
from packages.shared.storage import get_shared_storage_service
logger = logging.getLogger(__name__)
class AiAvatarRenderError(Exception):
"""渲染服务异常."""
def __init__(self, message: str, code: str = "RenderError"):
self.code = code
super().__init__(message)
class AiAvatarRenderService:
"""AI数字人渲染合成 Service."""
def __init__(self, db: Session):
self.db = db
# ── 创建任务 ──────────────────────────────────────────────────────────
def create_render_job(
self,
*,
user_id: str,
lipsync_job_id: str,
script_id: str = "",
b_roll_segments: list[dict[str, Any]] | None = None,
title_config: dict[str, Any],
cover_config: dict[str, Any],
project_id: str = "",
) -> AiAvatarRenderJob:
"""创建渲染任务.
Raises:
AiAvatarRenderError: 校验失败
"""
# 1. 验证对口型任务
lipsync_job = (
self.db.query(LipsyncJobModel)
.filter(
LipsyncJobModel.id == lipsync_job_id,
LipsyncJobModel.user_id == user_id,
)
.first()
)
if lipsync_job is None:
raise AiAvatarRenderError("对口型任务不存在", code="LipsyncJobNotFound")
if lipsync_job.status != "completed":
raise AiAvatarRenderError(
f"对口型任务状态为 {lipsync_job.status},仅 completed 状态可渲染",
code="LipsyncJobNotCompleted",
)
if not lipsync_job.output_video_url:
raise AiAvatarRenderError("对口型任务输出视频 URL 为空", code="LipsyncJobNoOutput")
# 2. 验证文案归属(仅当选了文案库条目时;手动输入文案直生场景 script_id 可空)
script_id = (script_id or "").strip()
if script_id:
script = (
self.db.query(ScriptModel)
.filter(
ScriptModel.id == script_id,
ScriptModel.user_id == user_id,
)
.first()
)
if script is None:
raise AiAvatarRenderError("文案不存在或无权访问", code="ScriptNotFound")
# 3. 创建渲染任务
job_id = str(uuid.uuid4())
job = AiAvatarRenderJob(
id=job_id,
user_id=user_id,
project_id=project_id,
lipsync_job_id=lipsync_job_id,
script_id=script_id,
b_roll_segments=[s if isinstance(s, dict) else s.model_dump() for s in (b_roll_segments or [])],
title_config=title_config,
cover_config=cover_config,
status="pending",
)
self.db.add(job)
self.db.flush()
job.submitted_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
# ── 查询任务 ──────────────────────────────────────────────────────────
def get_render_job(self, job_id: str, user_id: str) -> Optional[AiAvatarRenderJob]:
"""获取渲染任务详情(用户隔离)."""
return (
self.db.query(AiAvatarRenderJob)
.filter(
AiAvatarRenderJob.id == job_id,
AiAvatarRenderJob.user_id == user_id,
)
.first()
)
def list_render_jobs(
self,
*,
user_id: str,
project_id: str = "",
status: str = "",
offset: int = 0,
limit: int = 20,
) -> tuple[list[AiAvatarRenderJob], int]:
"""获取渲染任务列表(分页 + 用户隔离)."""
query = self.db.query(AiAvatarRenderJob).filter(AiAvatarRenderJob.user_id == user_id)
if project_id:
query = query.filter(AiAvatarRenderJob.project_id == project_id)
if status:
query = query.filter(AiAvatarRenderJob.status == status)
total = query.count()
items = query.order_by(AiAvatarRenderJob.created_at.desc()).offset(offset).limit(limit).all()
return items, total
# ── 取消任务 ──────────────────────────────────────────────────────────
def cancel_render_job(self, job_id: str, user_id: str) -> Optional[AiAvatarRenderJob]:
"""取消渲染任务(仅 pending 状态可取消)."""
job = self.get_render_job(job_id, user_id)
if job is None:
return None
if job.status in ("pending", "submitted"):
job.status = "cancelled"
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
# ── 重试任务 ──────────────────────────────────────────────────────────
def retry_render_job(self, job_id: str, user_id: str) -> Optional[AiAvatarRenderJob]:
"""重试失败的渲染任务."""
job = self.get_render_job(job_id, user_id)
if job is None:
return None
if job.status != "failed":
return None
job.status = "pending"
job.progress = 0
job.error_message = ""
job.output_video_url = ""
job.output_cover_url = ""
job.output_duration = 0.0
job.started_at = None
job.completed_at = None
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
# ── 执行渲染(Celery 异步调用) ──────────────────────────────────────
def execute_render(self, job_id: str) -> None:
"""执行渲染管线.
由 Celery 异步任务调用,流程:
1. 下载对口型输出视频 (20%)
2. 构建 FFmpeg 滤镜链 (40%)
3. 执行 FFmpeg 渲染 (80%)
4. 上传到 OSS (95%) — 封面不再自动生成,改由前端主动抽帧
5. 更新任务状态 (100%)
"""
job = self.db.query(AiAvatarRenderJob).filter(AiAvatarRenderJob.id == job_id).first()
if job is None:
logger.error("渲染任务不存在: %s", job_id)
return
if job.status == "cancelled":
logger.info("渲染任务已取消: %s", job_id)
return
try:
# 更新状态为 processing
job.status = "processing"
job.started_at = datetime.now(timezone.utc)
job.progress = 5
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
# 获取对口型任务信息
lipsync_job = self.db.query(LipsyncJobModel).filter(LipsyncJobModel.id == job.lipsync_job_id).first()
if lipsync_job is None:
raise AiAvatarRenderError("关联的对口型任务不存在", code="LipsyncJobNotFound")
# 1. 下载对口型输出视频 (20%)
input_video_path = self._download_video(lipsync_job.output_video_url)
job.progress = 20
self.db.commit()
# 2. 构建 FFmpeg 滤镜链 (40%)
# 用 ffprobe 探测输入视频分辨率,确保 B-roll 缩放与标题位置与实际输出一致。
# AI 数字人对口型输出为 9:16 竖屏,默认兜底 720x1280;探测失败时使用默认值不阻断渲染。
output_width, output_height = self._probe_video_resolution(input_video_path)
if output_width <= 0 or output_height <= 0:
output_width, output_height = 720, 1280
logger.info(
"[数字人渲染] ffprobe 探测分辨率失败或无效,使用默认竖屏尺寸 %sx%s",
output_width,
output_height,
)
else:
logger.info("[数字人渲染] 探测输入视频分辨率: %sx%s", output_width, output_height)
broll_filter, broll_label = build_broll_overlay_filter(
b_roll_segments=job.b_roll_segments,
video_duration=lipsync_job.output_duration,
output_width=output_width,
output_height=output_height,
)
# 标题叠加路径:优先前端 Canvas 渲染的 PNG 图层(所见即所得),
# 无 title_image_dataurl 时降级到 drawtext 重画文字。
title_cfg = job.title_config if isinstance(job.title_config, dict) else {}
title_dataurl = (title_cfg or {}).get("title_image_dataurl") if title_cfg else None
use_title_png = isinstance(title_dataurl, str) and title_dataurl.startswith("data:image/")
title_input_index = 1 + len(job.b_roll_segments or []) if use_title_png else None
job.progress = 40
self.db.commit()
# 3. 执行 FFmpeg 渲染 (80%)
with tempfile.TemporaryDirectory() as tmpdir:
output_video_path = os.path.join(tmpdir, "output.mp4")
# 在临时目录里解码保存标题 PNG(with 退出自动清理)
title_png_path: Optional[str] = None
extra_inputs: list[str] = []
title_filter = None
if use_title_png:
try:
title_png_path = os.path.join(tmpdir, f"title_{job.id}.png")
self._save_title_dataurl_to_file(title_dataurl, dst_path=title_png_path)
extra_inputs.append(title_png_path)
logger.info(
"[数字人渲染] 标题 PNG 已保存: %s (input index %d)", title_png_path, title_input_index
)
except Exception as exc:
logger.warning("[数字人渲染] 标题 PNG 解码/保存失败,降级 drawtext: %s", exc)
title_png_path = None
extra_inputs = []
# 构建标题滤镜
final_label = None
if title_png_path and title_input_index is not None:
title_input_label = f"[{title_input_index}:v]"
base_label = f"[{broll_label}]" if broll_label else "[0:v]"
title_filter = build_title_overlay_filter(
title_cfg,
output_width=output_width,
output_height=output_height,
title_png_path=title_png_path,
title_input_label=title_input_label,
base_label=base_label,
output_label="vout_titled",
)
if not title_filter:
# build 返回 None → 文件不存在(极端并发情况),降级 drawtext
title_png_path = None
extra_inputs = []
if title_png_path:
# overlay 路径
if broll_filter and title_filter:
filter_complex = broll_filter + f";{title_filter}"
elif broll_filter:
filter_complex = broll_filter
final_label = broll_label
elif title_filter:
filter_complex = title_filter
else:
filter_complex = ""
if title_filter:
final_label = "vout_titled"
elif not final_label:
final_label = None
else:
# 降级:drawtext 重画文字
title_filter = build_title_drawtext_filter(
title_cfg,
output_width=output_width,
output_height=output_height,
)
if broll_filter and title_filter:
filter_complex = broll_filter + f";[{broll_label}]{title_filter}[vout_titled]"
final_label = "vout_titled"
elif broll_filter:
filter_complex = broll_filter
final_label = broll_label
elif title_filter:
filter_complex = f"[0:v]{title_filter}[vout_titled]"
final_label = "vout_titled"
else:
filter_complex = ""
final_label = None
cmd_list = self._build_ffmpeg_command(
input_video=input_video_path,
b_roll_segments=job.b_roll_segments,
extra_inputs=extra_inputs,
filter_complex=filter_complex,
final_label=final_label,
output_path=output_video_path,
)
try:
render_result = subprocess.run(
cmd_list,
capture_output=True,
text=True,
timeout=600,
)
except subprocess.TimeoutExpired as exc:
raise AiAvatarRenderError(
"FFmpeg 渲染超时(600s",
code="FFmpegTimeout",
) from exc
if render_result.returncode != 0:
stderr_tail = (render_result.stderr or "").strip()[-800:]
raise AiAvatarRenderError(
f"FFmpeg 渲染失败,退出码: {render_result.returncode}, stderr: {stderr_tail}",
code="FFmpegFailed",
)
job.progress = 80
self.db.commit()
# 4/5. 上传成片到 OSS (95%) —— 已砍掉自动抽封面逻辑(步骤⑤);
# 封面由前端在渲染完成后通过 /smart-cover 接口主动从成片抽帧,不阻塞渲染链路。
output_video_url = self._upload_to_oss(output_video_path, f"ai-avatar/{job_id}/output.mp4")
job.output_video_url = output_video_url
# 封面透传:如果用户已在 cover_config 中选定封面 URLmode=upload 的自定义上传 或
# mode=auto_frame 已有的智能封面结果),直接透传到 output_cover_url,不再重新截帧。
if isinstance(job.cover_config, dict):
_pre_cover_url = (
job.cover_config.get("url")
or job.cover_config.get("imageUrl")
or job.cover_config.get("cover_url")
or ""
)
if _pre_cover_url:
job.output_cover_url = _pre_cover_url
logger.info("[数字人渲染] 使用用户已选定封面 URL: job_id=%s", job_id)
# 获取输出视频时长
job.output_duration = lipsync_job.output_duration
job.progress = 95
self.db.commit()
# 6. 完成
job.status = "completed"
job.progress = 100
job.completed_at = datetime.now(timezone.utc)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.info("渲染任务完成: %s", job_id)
# 7. 渲染完成,停留在「待选封面」状态:不自动入库。
# 用户在前端选好封面、点「完成」后,由 /{job_id}/finalize 接口显式入库。
logger.info("渲染任务完成,等待用户选择封面后入库: job_id=%s", job_id)
except AiAvatarRenderError as exc:
job.status = "failed"
job.error_message = str(exc)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.error("渲染任务失败 [%s]: %s", job_id, exc)
raise
except Exception as exc:
job.status = "failed"
job.error_message = f"渲染异常: {str(exc)}"
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.exception("渲染任务异常 [%s]", job_id)
raise
def _persist_to_library(self, job: AiAvatarRenderJob, cover_url: Optional[str] = None):
"""将渲染结果写入成片库,返回 GeneratedVideo 领域对象.
Args:
job: 渲染任务(必须 status=completed 且 output_video_url 非空)
cover_url: 可选的封面 URL 覆盖(finalize 时传入即优先使用,否则取 job.output_cover_url
"""
from packages.adapters.sqlalchemy_impl.generated_video_repository import (
SQLAlchemyGeneratedVideoRepository,
)
from packages.domain.generated_video import GeneratedVideo
clip_name = f"AI数字人_{job.id[:8]}"
# AI数字人入口是独立页面,前端可能不传 project_id(无项目概念),
# 兜底为 "ai_avatar" 避免 DB 非空约束/查询问题;generation_task_id 用 render_job_id 便于反查。
clip_project_id = (job.project_id or "").strip() or "ai_avatar"
clip_generation_task_id = job.id
effective_cover = (cover_url or "").strip() if cover_url else (job.output_cover_url or "").strip()
clip = GeneratedVideo.create(
project_id=clip_project_id,
generation_task_id=clip_generation_task_id,
name=clip_name,
file_url=job.output_video_url,
user_id=job.user_id,
duration=job.output_duration or 0.0,
thumbnail_url=effective_cover or None,
generation_params={
"source": "ai_avatar_render",
"render_job_id": job.id,
},
)
video_repo = SQLAlchemyGeneratedVideoRepository(self.db)
video_repo.create(clip)
logger.info("[数字人渲染] 成片已入库: clip_id=%s render_job=%s", clip.id, job.id)
return clip
def finalize_job(self, job_id: str, user_id: str, cover_url: Optional[str] = None):
"""用户在前端点「完成」后调用:将已 completed 的渲染任务正式入库到成片库.
- 必须 status=completed 才可调用
- cover_url 若传入则优先使用并回写 job.output_cover_url;否则使用 job.output_cover_urlsmart-cover/custom-cover 已写入)
- 幂等:已入库则返回已存在的 GeneratedVideo
"""
from packages.adapters.sqlalchemy_impl.generated_video_repository import (
SQLAlchemyGeneratedVideoRepository,
)
from packages.adapters.sqlalchemy_impl.models import GeneratedVideoModel
job = self.get_render_job(job_id, user_id)
if job is None:
raise AiAvatarRenderError("渲染任务不存在", code="RenderJobNotFound")
if job.status != "completed":
raise AiAvatarRenderError(f"渲染任务未完成(当前状态: {job.status}),无法入库", code="RenderNotCompleted")
if not (job.output_video_url or "").strip():
raise AiAvatarRenderError("渲染成片视频 URL 为空,无法入库", code="OutputVideoMissing")
# 幂等检查:已入库直接返回现有记录(通过 generation_task_id=job_id 识别,
# 因为入库时 generation_task_id 被设置为 render_job_id 自身)
existing = (
self.db.query(GeneratedVideoModel)
.filter(
GeneratedVideoModel.user_id == user_id,
GeneratedVideoModel.generation_task_id == job_id,
)
.first()
)
if existing is not None:
logger.info("[数字人渲染] finalize 幂等命中,返回已存在记录: clip_id=%s job_id=%s", existing.id, job_id)
return SQLAlchemyGeneratedVideoRepository(self.db).get(existing.id)
# 传入 cover_url 时回写到 job
if cover_url and cover_url.strip():
job.output_cover_url = cover_url.strip()
# 同步更新 cover_config,保持 smart-cover 路径一致
if isinstance(job.cover_config, dict):
job.cover_config = {**job.cover_config, "mode": "auto_frame", "url": cover_url.strip()}
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
return self._persist_to_library(job, cover_url=cover_url)
def _download_video(self, url: str) -> str:
"""下载视频到临时文件."""
import httpx
tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False)
try:
with httpx.Client(timeout=120) as client:
resp = client.get(url)
resp.raise_for_status()
tmp.write(resp.content)
return tmp.name
except Exception:
if os.path.exists(tmp.name):
os.unlink(tmp.name)
raise
@staticmethod
def _save_title_dataurl_to_file(dataurl: str, *, dst_path: str | None = None, job_id: str = "") -> str:
"""解码前端传来的 data:image/png;base64,... 并保存为本地 PNG 文件。
Args:
dataurl: 完整 dataURL 字符串
dst_path: 指定输出路径;为 None 时创建临时文件并返回路径
job_id: 仅在 dst_path 为空时用于临时文件命名
Returns:
保存后的本地文件路径
"""
if not isinstance(dataurl, str) or not dataurl.startswith("data:image/"):
raise ValueError("title_image_dataurl 不是合法的 data:image URL")
# 拆分 data:image/png;base64,<payload>
try:
header, b64 = dataurl.split(",", 1)
except ValueError as exc:
raise ValueError("title_image_dataurl 缺少 base64 payload") from exc
if "base64" not in header:
raise ValueError("title_image_dataurl 不是 base64 编码")
try:
png_bytes = base64.b64decode(b64, validate=True)
except (binascii.Error, ValueError) as exc:
raise ValueError(f"title_image_dataurl base64 解码失败: {exc}") from exc
if not png_bytes:
raise ValueError("title_image_dataurl 解码后为空")
if dst_path:
out_path = dst_path
with open(out_path, "wb") as f:
f.write(png_bytes)
return out_path
suffix = f"_title_{job_id}.png" if job_id else "_title.png"
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
tmp.write(png_bytes)
return tmp.name
@staticmethod
def _probe_video_resolution(video_path: str) -> tuple[int, int]:
"""用 ffprobe 探测视频分辨率,返回 (width, height);失败返回 (0, 0)。"""
try:
result = subprocess.run(
[
"ffprobe",
"-v",
"error",
"-select_streams",
"v:0",
"-show_entries",
"stream=width,height",
"-of",
"csv=p=0:s=x",
video_path,
],
capture_output=True,
text=True,
timeout=15,
)
if result.returncode == 0 and result.stdout.strip():
parts = result.stdout.strip().split("x")
if len(parts) == 2:
w, h = int(parts[0]), int(parts[1])
if w > 0 and h > 0:
return w, h
except Exception as exc:
logger.warning("[数字人渲染] ffprobe 探测分辨率失败: %s", exc)
return 0, 0
def _build_ffmpeg_command(
self,
*,
input_video: str,
b_roll_segments: list[dict[str, Any]],
extra_inputs: list[str] | None = None,
filter_complex: str,
final_label: Optional[str],
output_path: str,
) -> list[str]:
"""构建 FFmpeg 命令(list 形式,shell=False.
根因修复 #1798 P0OSS 预签名 URL 含 `&Expires=...&Signature=...` 特殊字符,
os.system(shell=True) 会把 `&` 解释为后台命令分隔符,导致 -filter_complex 被
当成独立命令报 sh: -filter_complex: not foundexit 127 → Python 32512)。
list + shell=False 彻底规避 shell 转义问题。
"""
cmd: list[str] = ["ffmpeg", "-i", input_video]
for seg in b_roll_segments:
asset_url = seg.get("asset_url", "")
if asset_url:
cmd.extend(["-i", asset_url])
# 额外输入(例如前端 Canvas 渲染的标题 PNG)
for extra in extra_inputs or []:
cmd.extend(["-i", extra])
if filter_complex and final_label:
cmd.extend(
[
"-filter_complex",
filter_complex,
"-map",
f"[{final_label}]",
"-map",
"0:a?",
]
)
elif filter_complex:
cmd.extend(["-filter_complex", filter_complex])
cmd.extend(
[
"-c:v",
"libx264",
"-preset",
"veryfast",
"-crf",
"23",
"-c:a",
"aac",
"-b:a",
"128k",
"-y",
output_path,
]
)
return cmd
def _upload_to_oss(self, local_path: str, oss_key: str) -> str:
"""上传文件到 OSS,返回 URL.
使用 SharedStorageService 统一存储服务。
"""
storage = get_shared_storage_service()
url = storage.upload_file_smart(local_path, oss_key)
if url is None:
raise AiAvatarRenderError(
f"上传文件到 OSS 失败: {oss_key}",
code="OSSUploadFailed",
)
logger.info("上传文件到 OSS 成功: %s -> %s", local_path, url)
return url
+151 -54
View File
@@ -473,6 +473,7 @@ class EditPlanService:
name_suffix: str = "变体",
voice_duration: float = 0.0,
rng=None,
batch_segments: dict[str, list[tuple[float, float]]] | None = None,
) -> EditPlan:
"""为批量变体生成独立 plan:完整重跑单视频选片流程(#1743)。
@@ -489,6 +490,8 @@ class EditPlanService:
created_by_user_id: 新 plan 归属用户。
name_suffix: plan 名后缀。
rng: 可选随机数(测试注入种子)。
batch_segments: 可选,外部传入的批次内已使用素材区间(前序变体避让用)。
传入时作为初始避让对象;未传则保持原逻辑从源 plan clips 自建(向后兼容)。
Raises:
ValueError: 源 plan 不存在/无片段、素材池为空或时长全未知。
@@ -537,16 +540,26 @@ class EditPlanService:
voice = float(voice_duration or 0.0)
except (TypeError, ValueError):
voice = 0.0
rhythm_template_for_reselect = None
if source.config:
rhythm_template_for_reselect = source.config.get("rhythm_template")
if voice > 0 and source_clips_data:
from packages.domain.voice_duration_planner import plan_clip_durations
_effects: list[str | None] = [c.get("transition_effect") for c in source_clips_data]
_tdurs: list[float] = [float(c.get("transition_duration") or 0.0) for c in source_clips_data]
# #1855 P0:先占位durations为空dict,真正查durations在后面pool_ids确定后执行;
# plan_clip_durations 的 asset_durations 参数在该函数中仅作最大段长钳制,
# 这里先不依赖它(durations 还没查),传 None 让planner用默认策略;
# 真正的asset_durations会在后面 clips_data 生成时传入 reselect_clips_for_variant
target_durations = plan_clip_durations(
len(source_clips_data),
voice,
transition_effects=_effects,
transition_durations=_tdurs,
rhythm_template=rhythm_template_for_reselect,
asset_durations=None,
)
if target_durations:
for _c, _d in zip(source_clips_data, target_durations, strict=False):
@@ -582,12 +595,18 @@ class EditPlanService:
created_by_user_id=created_by_user_id or (source.created_by_user_id or ""),
)
# 批次内区间:以源 plan(变体 0)片段为初始避让对象
batch_segments: dict[str, list[tuple[float, float]]] = {}
for c in clips:
if c.asset_id and float(c.duration or 0) > 0:
st = float(c.start_time or 0.0)
batch_segments.setdefault(c.asset_id, []).append((st, st + float(c.duration)))
# 批次内区间:外部传入时使用外部传入(含前序变体已用区间);
# 否则保持原逻辑从源 plan clips 自建(向后兼容)
if batch_segments is not None:
batch_segments_resolved: dict[str, list[tuple[float, float]]] = {
k: list(v) for k, v in batch_segments.items()
}
else:
batch_segments_resolved = {}
for c in clips:
if c.asset_id and float(c.duration or 0) > 0:
st = float(c.start_time or 0.0)
batch_segments_resolved.setdefault(c.asset_id, []).append((st, st + float(c.duration)))
clips_data = reselect_clips_for_variant(
source_clips_data,
@@ -595,7 +614,7 @@ class EditPlanService:
asset_durations=durations,
asset_scene_points=scene_points,
historical_used_segments=historical,
batch_segments=batch_segments,
batch_segments=batch_segments_resolved,
target_durations=target_durations,
rng=rng,
)
@@ -767,6 +786,17 @@ class EditPlanService:
if plan is None:
return None
# #1855 P0:幂等判断——如果已成功分配过且当前 total_duration 已接近 voice_duration,直接返回
try:
existing_mark = None
if plan.config:
existing_mark = plan.config.get("voice_duration_applied")
cur_total = float(plan.total_duration or 0.0)
if existing_mark is not None and abs(existing_mark - voice) < 1e-6 and abs(cur_total - voice) < 0.5:
return plan
except Exception:
pass
clips: List[EditPlanClip] = []
skip, page = 0, 500
while True:
@@ -789,20 +819,22 @@ class EditPlanService:
if plan and hasattr(plan, "config") and plan.config:
rhythm_template = plan.config.get("rhythm_template")
# #1768:先获取素材时长,传入 plan_clip_durations 用于最大片段钳制
asset_ids = [c.asset_id for c in clips if c.asset_id]
durations = self.get_asset_durations(asset_ids)
asset_durations_for_plan = [durations.get(c.asset_id, 0.0) for c in clips]
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,
asset_durations=asset_durations_for_plan,
)
if not target:
return None
# 素材时长(短素材起点钳 0
asset_ids = [c.asset_id for c in clips if c.asset_id]
durations = self.get_asset_durations(asset_ids)
clips_data: list[dict] = []
for i, c in enumerate(clips):
dur = float(target[i])
@@ -836,6 +868,10 @@ class EditPlanService:
)
try:
plan.total_duration = net
# #1855 P0:写入幂等标记,避免二次调用时只重分配 duration 不重算 start_time
new_cfg = dict(plan.config or {})
new_cfg["voice_duration_applied"] = voice
plan.config = new_cfg
db = self._clip_repo.session
db.commit()
except Exception:
@@ -876,6 +912,69 @@ class EditPlanService:
rng = rng or _random.Random()
plan_ids: list[str] = []
# #1855 P0:先确定片段数 clip_count(用于节奏模板生成长度匹配)
from packages.domain.bgm_pool import allocate_bgm_pool_for_variants
from packages.domain.variant_plan_selector import (
generate_pixel_perturbation,
generate_visual_perturbation,
)
from packages.domain.voice_duration_planner import RHYTHM_TEMPLATES, adapt_template_length
clip_count = 0
# 从源 plan 获取片段数(分页读,避免关系加载问题)
_sclips: list = []
_sk, _pg = 0, 500
while True:
_b = self._clip_repo.list_by_plan(source_plan_id, skip=_sk, limit=_pg)
if not _b:
break
_sclips.extend(_b)
if len(_b) < _pg:
break
_sk += _pg
clip_count = len(_sclips)
# 预先生成所有 N 个变体的节奏模板/BGM/扰动参数(时机提前到选片前写入config)
rhythm_templates_for_variants: list = []
for _idx in range(count):
if clip_count > 0:
variant_seed = rng.randint(0, 999999)
_tpl = adapt_template_length(RHYTHM_TEMPLATES[variant_seed % len(RHYTHM_TEMPLATES)], clip_count)
rhythm_templates_for_variants.append(_tpl)
else:
rhythm_templates_for_variants.append(None)
source_bgm_config: dict = {}
source_plan = self.get_plan(source_plan_id)
if source_plan and source_plan.config:
source_bgm_config = source_plan.config.get("bgm", {}) or {}
variant_seeds_for_bgm = [rng.randint(0, 999999) for _ in range(count)]
bgm_pool_assignments = allocate_bgm_pool_for_variants(source_bgm_config, variant_seeds_for_bgm)
def _build_variant_config_update(idx: int) -> dict:
"""构建单个变体的 config 更新(节奏模板/BGM/视觉/像素扰动)。"""
upd: dict = {}
try:
perturbation = generate_visual_perturbation(rng)
if idx == 0:
perturbation["hflip"] = False
upd["visual_perturbation"] = perturbation
except Exception:
logger.exception("变体 %d 视觉扰动生成失败(不阻断)", idx)
try:
pixel_pert = generate_pixel_perturbation(rng)
upd["pixel_perturbation"] = pixel_pert
except Exception:
logger.exception("变体 %d 像素扰动生成失败(不阻断)", idx)
rt = rhythm_templates_for_variants[idx] if idx < len(rhythm_templates_for_variants) else None
if rt is not None:
upd["rhythm_template"] = rt
if idx < len(bgm_pool_assignments):
existing_bgm = dict((source_plan.config or {}).get("bgm", {}) or {})
existing_bgm.update(bgm_pool_assignments[idx])
upd["bgm"] = existing_bgm
return upd
# 变体 0:clone(片段结构同源 plan,起点重算),不污染源 plan
plan0 = self.clone_plan_for_variant(
source_plan_id,
@@ -888,6 +987,15 @@ class EditPlanService:
v0_voice = float(voice_durations[0] or 0.0)
except (TypeError, ValueError):
v0_voice = 0.0
# #1855 P0:在配音分配前先写入变体0的节奏模板/扰动/BGM,确保 apply_voice_duration_to_plan 能读到 rhythm_template
try:
_cfg0 = _build_variant_config_update(0)
if _cfg0:
self.update_plan_config(plan0.id, _cfg0)
except Exception:
logger.exception("变体0 配置写入失败(不阻断): plan=%s", plan0.id)
if v0_voice > 0:
try:
self.apply_voice_duration_to_plan(plan0.id, v0_voice)
@@ -895,7 +1003,12 @@ class EditPlanService:
logger.exception("变体0 配音分配失败(不阻断): plan=%s", plan0.id)
plan_ids.append(plan0.id)
# 变体 1..N-1:独立选片
# #1855 P0:批次内素材区间避让表——从变体0实际落库的clips构建初始值(公共函数)
from app.services.generation_common import collect_plan_segments as _collect_plan_segments
batch_segments_acc: dict[str, list[tuple[float, float]]] = _collect_plan_segments(plan0.id, self._clip_repo)
# 变体 1..N-1:独立选片(传入累积的 batch_segments 做区间避让)
for i in range(1, count):
voice = 0.0
if voice_durations and i < len(voice_durations):
@@ -903,6 +1016,14 @@ class EditPlanService:
voice = float(voice_durations[i] or 0.0)
except (TypeError, ValueError):
voice = 0.0
# #1855 P0:在reselect前先为"变体i"准备配置更新——但reselect内部复制的是source.config
# 所以每个变体独立的节奏模板需要在reselect后单独写入config
# 但 plan_clip_durations 用的是 source.config.rhythm_template(即源plan的节奏模板),
# 为了让每个变体在选片阶段就使用自己的节奏模板分配段长,这里采用:
# - reselect 仍使用源 plan 的 rhythm_template(保持片段骨架一致)
# - 选片完成后立即写入该变体自己的 rhythm_template/扰动/BGM 到config
# 后续不再二次 apply_voice_duration_to_plan(由幂等标记跳过)
variant = self.reselect_plan_for_variant(
source_plan_id,
candidate_asset_ids,
@@ -910,50 +1031,26 @@ class EditPlanService:
name_suffix=f"变体{i + 1}",
voice_duration=voice,
rng=rng,
batch_segments=batch_segments_acc,
)
# 选片完成后写入该变体的独立配置(节奏模板/扰动/BGM)
try:
_cfgi = _build_variant_config_update(i)
if _cfgi:
self.update_plan_config(variant.id, _cfgi)
except Exception:
logger.exception("变体 %d 配置写入失败(不阻断): plan=%s", i, variant.id)
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
for idx, pid in enumerate(plan_ids):
# #1855 P0:把当前新变体的 clips 区间追加到 batch_segments,供下一变体避让
try:
perturbation = generate_visual_perturbation(rng)
# 变体 0 不做 hflip(保持预览 plan 原始画面方向)
if idx == 0:
perturbation["hflip"] = False
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)
_new_segs = _collect_plan_segments(variant.id, self._clip_repo)
for _aid, _ivs in _new_segs.items():
batch_segments_acc.setdefault(_aid, []).extend(_ivs)
except Exception:
logger.exception("变体 %d 视觉扰动生成失败(不阻断): plan=%s", idx, pid)
logger.exception("变体 %d 区间收集失败(不阻断): plan=%s", i, variant.id)
# 标记所有变体 plan 的 clips 为 ready(已分配素材+起点,语义上就是 ready)
for pid in plan_ids:
@@ -1187,7 +1284,7 @@ class EditPlanService:
config_asset_ids_count = len((plan.config or {}).get("asset_ids", []))
clips_with_asset_count = sum(1 for c in clips if c.asset_id)
logger.info(
"can_generate 诊断: plan=%s status=%s total_clips=%d " "clips_with_asset=%d config_asset_ids_count=%d",
"can_generate 诊断: plan=%s status=%s total_clips=%d clips_with_asset=%d config_asset_ids_count=%d",
plan_id,
plan.status,
len(clips),
@@ -1199,7 +1296,7 @@ class EditPlanService:
config_asset_ids = (plan.config or {}).get("asset_ids", [])
if config_asset_ids:
logger.warning(
"can_generate 最后防线触发: plan=%s clips=%d 均无素材," "从 config.asset_ids(%d个) 自动分配",
"can_generate 最后防线触发: plan=%s clips=%d 均无素材,从 config.asset_ids(%d个) 自动分配",
plan_id,
len(clips),
len(config_asset_ids),
@@ -1231,7 +1328,7 @@ class EditPlanService:
return False, "没有可渲染的就绪片段,自动修复后仍未分配素材"
else:
logger.warning(
"can_generate 失败: plan=%s clips=%d 均无素材," "且 config.asset_ids 为空,无法自动修复",
"can_generate 失败: plan=%s clips=%d 均无素材,且 config.asset_ids 为空,无法自动修复",
plan_id,
len(clips),
)
+175
View File
@@ -0,0 +1,175 @@
"""智能剪辑公共服务辅助函数(从 route 层下沉)。
集中管理:
- query_voice_durations:批量查询配音素材时长
- writeback_edit_plan_config:任务入队后回写 EditPlan.config
- collect_plan_segments:分页读取 plan clips 构建素材区间表(变体避让用)
- resolve_latest_plan_by_template:按 template_id + user_id 查最新 EditPlan
设计原则:
- 无副作用的纯查询 / 幂等写回;失败一律不阻断主流程(记日志 + 返回安全默认值)
- 不依赖 FastAPI / HTTPException,便于 service 层和 worker 复用
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
def query_voice_durations(db: Session, voice_ids: list[str]) -> list[float]:
"""批量查询配音素材时长(秒),#1749 配音时长分配用。
逐项 try/float 硬化:MagicMock/异常/缺失 → 0.0(无配音不分配,不阻断)。
#1855 P0修复:不再对 voice_ids 去重,保持与调用方传入顺序/长度一致,
允许同配音id多次出现时返回相同时长(支持"同配音N变体"的时长对齐)。
"""
raw_ids = list(voice_ids or [])
if not raw_ids:
return []
unique_ids: list[str] = []
_seen: set[str] = set()
for v in raw_ids:
if v and v not in _seen:
_seen.add(v)
unique_ids.append(v)
if not unique_ids:
return [0.0 for _ in raw_ids]
try:
from packages.adapters.sqlalchemy_impl.models import AssetModel
rows = db.query(AssetModel.id, AssetModel.duration).filter(AssetModel.id.in_(unique_ids)).all()
dur_map: dict[str, float] = {}
for row in rows:
try:
dur_map[row[0]] = float(row[1] or 0.0)
except (TypeError, ValueError):
dur_map[row[0]] = 0.0
return [dur_map.get(v, 0.0) if v else 0.0 for v in raw_ids]
except Exception:
logger.warning("[generation_common] 配音时长查询失败(按无配音处理,不阻断)", exc_info=True)
return [0.0 for _ in raw_ids]
def writeback_edit_plan_config(
plan_id: str,
task_id: str,
title_config: dict | None,
db: Session,
) -> None:
"""任务入队成功后,回写 EditPlan.configgeneration_task_id + title_config。
用 merge 方式更新,不整体覆盖 config,避免丢失其他字段。
失败只记日志,不影响任务创建。
"""
if not plan_id:
return
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
plan_model = db.query(EditPlanModel).filter(EditPlanModel.id == plan_id).first()
if plan_model is None:
logger.warning("[generation_common] 回写plan.config失败: plan不存在 plan_id=%s", plan_id)
return
current_config = plan_model.config if isinstance(plan_model.config, dict) else {}
merged = dict(current_config)
merged["generation_task_id"] = task_id
if title_config:
old_title_config = merged.get("title_config", {}) or {}
old_title_text = (old_title_config.get("text") or "").strip()
new_title_text = (title_config.get("text") or "").strip()
if old_title_text != new_title_text:
if "cover" in merged:
del merged["cover"]
logger.info(
"[generation_common] 标题变化,清除旧封面: plan_id=%s old_title=%s new_title=%s",
plan_id,
old_title_text,
new_title_text,
)
merged["title_config"] = title_config
plan_model.config = merged
db.commit()
logger.info(
"[generation_common] 回写plan.config成功: plan_id=%s task_id=%s keys=%s",
plan_id,
task_id,
list(merged.keys()),
)
except Exception as e:
logger.warning(
"[generation_common] 回写plan.config异常(不影响任务创建): plan_id=%s error=%s",
plan_id,
e,
exc_info=True,
)
try:
db.rollback()
except Exception:
pass
def collect_plan_segments(
plan_id: str,
clip_repo: Any,
*,
page_size: int = 500,
) -> dict[str, list[tuple[float, float]]]:
"""分页读取 plan 所有 clips,构建 {asset_id: [(start, end), ...]} 素材区间表。
用于 #1855 P0 批次内素材区间避让(变体间素材片段重叠控制)。
"""
segs: dict[str, list[tuple[float, float]]] = {}
sk, pg = 0, page_size
while True:
batch = clip_repo.list_by_plan(plan_id, skip=sk, limit=pg)
if not batch:
break
for c in batch:
if c.asset_id and float(c.duration or 0) > 0:
st = float(c.start_time or 0.0)
segs.setdefault(c.asset_id, []).append((st, st + float(c.duration)))
if len(batch) < pg:
break
sk += pg
return segs
def resolve_latest_plan_by_template(
db: Session,
*,
template_id: str,
user_id: str,
) -> Optional[str]:
"""按 template_id + user_id 查找最新的 EditPlan.id(模板兜底用)。找不到返回 None。"""
if not (template_id or "").strip():
return None
try:
from packages.adapters.sqlalchemy_impl.models import EditPlanModel
latest = (
db.query(EditPlanModel)
.filter(
EditPlanModel.template_id == template_id.strip(),
EditPlanModel.created_by_user_id == user_id,
)
.order_by(EditPlanModel.created_at.desc())
.first()
)
return latest.id if latest else None
except Exception:
logger.warning(
"[generation_common] 按template查找最新plan失败: template=%s user=%s",
template_id,
user_id,
exc_info=True,
)
return None
+581
View File
@@ -0,0 +1,581 @@
"""对口型 Service — #1796 MediaKit 对口型业务逻辑, #1809 参数调整, #1845 配音前置.
职责:
- 创建/查询对口型任务
- 三输入模式:
1. TTS 直生(voice_id + script_text)→ 走 Celery 异步(降级路径)
2. 直接音频(audio_url,前端未传 timings)→ 同步下载 + 算 timings + 提交 MediaKit
3. 预合成音频(audio_url + sentence_timings#1845 新主路径)→ 同步 ffprobe 校验时长 +
写入前端传来的 timings → 直接提交 MediaKit~2-3s
- 调用 MediaKit 客户端提交异步任务
- 轮询更新任务状态(中间状态同步 DB,成片转存自家 OSS)
- 用户隔离(每个用户只能操作自己的任务)
"""
from __future__ import annotations
import io
import logging
import uuid
from datetime import datetime, timezone
from typing import Optional
from urllib.parse import urlparse
from app.services.mediakit_client import (
STATUS_COMPLETED,
STATUS_FAILED,
STATUS_RUNNING,
MediaKitClient,
MediaKitError,
get_mediakit_client,
)
# Celery 异步任务:TTS 合成 + MediaKit 提交(降级路径)
from app.tasks.lipsync_tts import tts_synthesize_and_submit
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import LipsyncJobModel
from packages.application.cosyvoice_service import CosyVoiceError, normalize_emotion
from packages.domain.sentence_timings import (
compute_sentence_timings,
probe_audio_duration,
)
from packages.shared.storage import get_shared_storage_service
from packages.shared.url_security import ALLOWED_AUDIO_MIME_TYPES, safe_download_bytes
logger = logging.getLogger(__name__)
# 传给 MediaKit GPU worker / 回给前端播放的 OSS 预签名有效期:7 天。
MEDIAKIT_URL_TTL_SECONDS = 7 * 24 * 3600
class LipsyncService:
"""对口型任务 Service."""
def __init__(
self,
db: Session,
client: Optional[MediaKitClient] = None,
cosyvoice_service=None,
voice_clone_repo=None,
):
self.db = db
self.client = client or get_mediakit_client()
self._cosyvoice = cosyvoice_service
self._voice_clone_repo = voice_clone_repo
def _get_cosyvoice(self):
"""延迟获取 CosyVoiceService(与 tts 路由一致,含 OSS 预签名配置)."""
if self._cosyvoice is None:
from app.dependencies import get_cosyvoice_service
self._cosyvoice = get_cosyvoice_service()
return self._cosyvoice
def _resolve_voice_id(self, voice_id: str, user_id: str) -> str:
"""将克隆音色 profile UUID 解析为 CosyVoice voice_id。
与 /tts/synthesize 保持一致:命中 profile → 校验归属 → 返回其 voice_id;
未命中(预置音色 ID 或克隆 CosyVoice voice_id)原样返回。
"""
if not voice_id:
return ""
if self._voice_clone_repo is None:
try:
from app.dependencies import get_voice_clone_profile_repository
self._voice_clone_repo = get_voice_clone_profile_repository(self.db)
except Exception:
return voice_id
try:
profile = self._voice_clone_repo.get(voice_id)
except Exception:
return voice_id
if profile is None:
return voice_id
if getattr(profile, "user_id", "") != user_id:
raise MediaKitError("无权访问该音色", code="VoiceForbidden")
if not getattr(profile, "voice_id", ""):
raise MediaKitError("音色克隆尚未完成,请稍后再试", code="VoiceNotReady")
return profile.voice_id
def _synthesize_and_persist_audio(
self,
*,
user_id: str,
job_id: str,
voice_id: str,
script_text: str,
speed: float,
emotion: str,
) -> str:
"""TTS 直生:调 CosyVoice 合成音频并转存 OSS,返回可公网访问的音频 URL.
Raises:
MediaKitError: 合成失败
"""
actual_voice_id = self._resolve_voice_id(voice_id, user_id)
cosyvoice = self._get_cosyvoice()
try:
result = cosyvoice.submit_synthesize_task(
text=script_text,
voice_id=actual_voice_id,
speed=speed,
emotion=normalize_emotion(emotion),
)
except CosyVoiceError as exc:
raise MediaKitError(f"TTS 合成失败: {exc}", code="TTSSynthesisFailed") from exc
except ValueError as exc:
raise MediaKitError(f"TTS 参数错误: {exc}", code="TTSInvalidParam") from exc
temp_url = result.get("audio_url", "")
if not temp_url:
raise MediaKitError("TTS 未返回音频 URL", code="TTSNoAudio")
# 转存到自家 OSS,避免临时 URL 过期导致 MediaKit 拉取失败
try:
audio_data = safe_download_bytes(
temp_url,
purpose="lipsync_tts_audio",
allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES,
timeout=60.0,
)
storage = get_shared_storage_service()
storage_key = f"lipsync-tts/{user_id}/{job_id}.mp3"
permanent_url = storage.upload_file(io.BytesIO(audio_data), storage_key, content_type="audio/mpeg")
logger.info("对口型 TTS 音频已转存 OSS: job_id=%s key=%s", job_id, storage_key)
return permanent_url
except Exception as exc:
logger.warning("TTS 音频转存 OSS 失败,回退临时 URL: job_id=%s err=%s", job_id, exc)
return temp_url
def _submit_audio_direct(
self,
*,
job: LipsyncJobModel,
supplied_timings: Optional[list] = None,
supplied_duration: Optional[float] = None,
) -> None:
"""音频直传模式(包含 #1845 预合成路径):同步下载 → ffprobe → timings → 提交 MediaKit.
直接在 HTTP 请求内完成,不走 Celery。job.status 成功后置为 submitted。
失败时把 job 标成 failed 并 commit,然后抛 MediaKitError。
Args:
job: 已 commit 的 LipsyncJobModelaudio_url / video_url 已写入)
supplied_timings: 前端传来的预合成 timings(可选,可信时直接用)
supplied_duration: 前端传来的预合成时长(可选,用于优先避免重复探测)
"""
# 1. 下载音频
audio_data: bytes | None = None
try:
audio_data = safe_download_bytes(
job.audio_url,
purpose="lipsync_direct_audio",
allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES,
timeout=60.0,
)
logger.info(
"[lipsync] 直传音频下载完成: job_id=%s size=%d",
job.id,
len(audio_data) if audio_data else 0,
)
except Exception as exc:
logger.warning("[lipsync] 直传音频下载失败,跳过 timings 计算: job_id=%s err=%s", job.id, exc)
# 2. ffprobe 探测时长(优先用前端传入的预合成时长,但以 ffprobe 为准做兜底校验)
audio_duration = 0.0
if audio_data:
audio_duration = probe_audio_duration(audio_data)
if audio_duration <= 0 and supplied_duration and supplied_duration > 0:
audio_duration = supplied_duration
logger.info(
"[lipsync] ffprobe 失败,使用前端传入的预合成时长: job_id=%s duration=%.2f", job.id, audio_duration
)
# 3. 句子时间戳:优先用前端预合成传入的 timings(后端预合成接口已经算过,可信);
# 否则若音频下载成功则重算;否则不设置(不阻塞主流程)
timings: Optional[list] = None
if supplied_timings:
timings = supplied_timings
logger.info("[lipsync] 使用前端预合成句子时间戳: job_id=%s sentences=%d", job.id, len(timings))
elif audio_data and audio_duration > 0 and job.script_text:
try:
timings = compute_sentence_timings(audio_data, job.script_text, audio_duration)
logger.info(
"[lipsync] 后端重算句子时间戳: job_id=%s sentences=%d duration=%.2f",
job.id,
len(timings) if timings else 0,
audio_duration,
)
except Exception as exc:
logger.warning("[lipsync] 句子时间戳计算失败(不阻塞): job_id=%s err=%s", job.id, exc)
if timings:
job.sentence_timings = timings
# 4. 签名 URL 并提交 MediaKit
video_url = self._sign_media_url(job.video_url)
signed_audio_url = self._sign_media_url(job.audio_url)
job.audio_url = signed_audio_url
try:
result = self.client.submit_lipsync(
video_url=video_url,
audio_url=signed_audio_url,
enable_video_loop=job.enable_video_loop,
client_token=job.id,
)
job.mediakit_task_id = result["task_id"]
job.status = "submitted"
job.submitted_at = datetime.now(timezone.utc)
self.db.commit()
logger.info(
"[lipsync] 直传音频已提交 MediaKit: job_id=%s task_id=%s",
job.id,
result["task_id"],
)
except MediaKitError as exc:
job.status = "failed"
job.error_message = str(exc)
job.error_code = exc.code
logger.error("[lipsync] 直传音频提交 MediaKit 失败: job_id=%s err=%s", job.id, exc)
self.db.commit()
raise
# ── 创建任务 ──────────────────────────────────────────────────────────
def create_job(
self,
*,
user_id: str,
video_url: str,
audio_url: str = "",
audio_duration: Optional[float] = None,
sentence_timings: Optional[list] = None,
voice_id: str = "",
script_text: str = "",
speed: float = 1.0,
emotion: str = "",
enable_video_loop: bool = True,
project_id: str = "",
) -> LipsyncJobModel:
"""创建对口型任务.
三种输入模式:
- TTS 直生:voice_id + script_textaudio_url 留空)
→ 创建 DB 记录(状态 tts_processing),dispatch Celery 异步任务(降级路径)。
API 响应 <1s。
- 直接音频:audio_url 非空 + 无 sentence_timings
→ 同步下载音频 + 重算 timings + 提交 MediaKit(几秒完成)。
- 预合成音频(#1845 新主路径):audio_url 非空 + 传 sentence_timings
→ 同步 ffprobe 校验时长 + 写入 timings + 提交 MediaKit~2-3s)。
Raises:
MediaKitError: 参数校验失败或 MediaKit 提交失败
"""
# 0. 输入校验
is_pre_synth = bool(audio_url) and bool(sentence_timings)
bool(audio_url) and not is_pre_synth
is_tts_mode = not bool(audio_url)
if is_tts_mode:
if not (voice_id and script_text):
raise MediaKitError(
"必须提供 audio_url 或 voice_id+script_text",
code="InvalidInput",
)
# TTS 模式:在 HTTP 请求中同步校验音色归属,快速失败
self._resolve_voice_id(voice_id, user_id)
elif is_pre_synth:
# 预合成模式:script_text 可空(因为 timings 已自带句子文本),但仍建议传
if not isinstance(sentence_timings, list) or len(sentence_timings) == 0:
raise MediaKitError("预合成模式 sentence_timings 不能为空", code="InvalidInput")
# 1. 创建数据库记录
job_id = str(uuid.uuid4())
job = LipsyncJobModel(
id=job_id,
user_id=user_id,
project_id=project_id,
video_url=video_url,
audio_url=audio_url,
enable_video_loop=enable_video_loop,
voice_id=voice_id or "",
script_text=script_text or "",
speed=speed,
emotion=normalize_emotion(emotion) if is_tts_mode else (emotion or ""),
# 音频直传(含预合成)直接进入 pending(后续同步改为 submitted);TTS 模式进入 tts_processing
status="tts_processing" if is_tts_mode else "pending",
)
self.db.add(job)
self.db.flush()
# ⚠️ 必须先 commit 再发 Celery 任务 / 后续同步操作,避免事务竞态
self.db.commit()
self.db.refresh(job)
if is_tts_mode:
# 2a. TTS 模式:dispatch Celery 异步任务处理 TTS 合成 + MediaKit 提交(降级路径)
try:
tts_synthesize_and_submit.apply_async(
args=(
job_id,
user_id,
voice_id,
script_text,
speed,
normalize_emotion(emotion),
)
)
except Exception as exc:
logger.exception(
"Celery 任务提交失败,TTS 任务已创建但未触发执行: job_id=%s err=%s",
job_id,
exc,
)
job.status = "failed"
job.error_message = f"Celery 任务投递失败: {exc}"
job.error_code = "AsyncDispatchFailed"
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
else:
# 2b/2c. 直接音频 / 预合成音频:同步路径
self._submit_audio_direct(
job=job,
supplied_timings=sentence_timings,
supplied_duration=audio_duration,
)
self.db.refresh(job)
return job
# ── TTS 预合成(#1845 步骤1「生成配音」同步接口使用) ──────────────────
def preview_tts(
self,
*,
user_id: str,
voice_id: str,
script_text: str,
speed: float = 1.0,
emotion: str = "natural",
) -> dict:
"""同步做 TTS 合成 + 下载 + ffprobe + 句子时间戳计算.
不创建 LipsyncJob、不转存 OSS,直接返回 CosyVoice 临时 URL~24h 有效期)。
耗时约 2-3 秒,由前端在步骤1点「生成配音」时同步等待。
Returns:
{"audio_url": str, "duration": float, "sentence_timings": list[dict]}
Raises:
MediaKitError: TTS 合成失败 / 下载失败 / ffprobe 失败
"""
# 1. 音色解析(校验克隆音色归属)
actual_voice_id = self._resolve_voice_id(voice_id, user_id)
cosyvoice = self._get_cosyvoice()
# 2. TTS 合成(同步,~2-3s
try:
result = cosyvoice.submit_synthesize_task(
text=script_text,
voice_id=actual_voice_id,
speed=speed,
emotion=normalize_emotion(emotion),
)
except CosyVoiceError as exc:
raise MediaKitError(f"TTS 合成失败: {exc}", code="TTSSynthesisFailed") from exc
except ValueError as exc:
raise MediaKitError(f"TTS 参数错误: {exc}", code="TTSInvalidParam") from exc
temp_url = result.get("audio_url", "")
if not temp_url:
raise MediaKitError("TTS 未返回音频 URL", code="TTSNoAudio")
# 3. 下载音频到内存(用于 ffprobe + 静音检测)
try:
audio_data = safe_download_bytes(
temp_url,
purpose="tts_preview_audio",
allowed_mime_types=ALLOWED_AUDIO_MIME_TYPES,
timeout=60.0,
)
except Exception as exc:
logger.warning("[tts-preview] TTS 音频下载失败,仍返回 audio_url: user_id=%s err=%s", user_id, exc)
return {
"audio_url": temp_url,
"duration": 0.0,
"sentence_timings": [],
}
# 4. ffprobe 时长
duration = probe_audio_duration(audio_data)
if duration <= 0:
logger.warning("[tts-preview] ffprobe 未返回有效时长,timings 留空: user_id=%s", user_id)
return {
"audio_url": temp_url,
"duration": 0.0,
"sentence_timings": [],
}
# 5. 句子时间戳
timings = compute_sentence_timings(audio_data, script_text, duration)
logger.info(
"[tts-preview] TTS 预合成完成: user_id=%s duration=%.2f sentences=%d",
user_id,
duration,
len(timings),
)
return {
"audio_url": temp_url,
"duration": round(duration, 2),
"sentence_timings": timings,
}
# ── 查询任务 ──────────────────────────────────────────────────────────
def get_job(self, job_id: str, user_id: str) -> Optional[LipsyncJobModel]:
"""获取任务详情(用户隔离)."""
return (
self.db.query(LipsyncJobModel)
.filter(LipsyncJobModel.id == job_id, LipsyncJobModel.user_id == user_id)
.first()
)
def list_jobs(
self,
*,
user_id: str,
project_id: str = "",
status: str = "",
offset: int = 0,
limit: int = 20,
) -> tuple[list[LipsyncJobModel], int]:
"""获取任务列表(分页 + 用户隔离)."""
query = self.db.query(LipsyncJobModel).filter(LipsyncJobModel.user_id == user_id)
if project_id:
query = query.filter(LipsyncJobModel.project_id == project_id)
if status:
query = query.filter(LipsyncJobModel.status == status)
total = query.count()
items = query.order_by(LipsyncJobModel.created_at.desc()).offset(offset).limit(limit).all()
return items, total
# ── 更新任务状态(轮询) ──────────────────────────────────────────────
def refresh_job_status(self, job_id: str, user_id: str) -> Optional[LipsyncJobModel]:
"""从 MediaKit 拉取最新状态并更新本地记录."""
job = self.get_job(job_id, user_id)
if job is None:
return None
# 终态不需要再轮询
if job.status in (STATUS_COMPLETED, "failed"):
return job
# 未提交的任务不轮询
if not job.mediakit_task_id:
return job
try:
status_data = self.client.get_task_status(job.mediakit_task_id)
except MediaKitError as exc:
logger.error("轮询对口型任务状态失败 [%s]: %s", job_id, exc)
return job
mk_status = status_data.get("status", STATUS_RUNNING)
logger.info("MediaKit 对口型状态 [%s]: %s", job_id, mk_status)
if mk_status == STATUS_COMPLETED:
result = status_data.get("result", {})
job.status = STATUS_COMPLETED
temp_url = result.get("video_url", "")
job.output_video_url = temp_url
job.output_duration = result.get("duration", 0.0)
job.completed_at = datetime.now(timezone.utc)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
# 异步转存自家 OSS
try:
from app.tasks.lipsync_tts import persist_output_video_task
persist_output_video_task.apply_async(args=(job_id, user_id, temp_url))
except Exception as exc:
logger.warning(
"提交输出视频异步转存任务失败,保留临时 URL: job_id=%s err=%s",
job_id,
exc,
)
self.db.refresh(job)
return job
elif mk_status == STATUS_FAILED:
error = status_data.get("error", {})
job.status = "failed"
job.error_message = error.get("message", "任务执行失败")
job.error_code = error.get("code", "TaskFailed")
job.completed_at = datetime.now(timezone.utc)
else:
if isinstance(mk_status, str) and mk_status:
job.status = mk_status
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
def _persist_output_video(self, temp_url: str, job_id: str, user_id: str) -> str:
"""将 MediaKit 输出的临时视频 URL 转存到自家 OSS. 失败时回退返回原始临时 URL."""
if not temp_url:
return ""
try:
import httpx
with httpx.Client(timeout=180.0, follow_redirects=True) as client:
resp = client.get(temp_url)
resp.raise_for_status()
data = resp.content
storage = get_shared_storage_service()
storage_key = f"lipsync-outputs/{user_id}/{job_id}.mp4"
permanent_url = storage.upload_file(io.BytesIO(data), storage_key, content_type="video/mp4")
logger.info("对口型输出视频已转存 OSS: job_id=%s key=%s", job_id, storage_key)
return self._sign_media_url(permanent_url) or temp_url
except Exception as exc:
logger.warning("对口型输出视频转存 OSS 失败,回退临时 URL: job_id=%s err=%s", job_id, exc)
return temp_url
def _sign_media_url(self, url: str) -> str:
"""对自家 OSS 私有桶 URL 重签长有效期预签名."""
if not url:
return url
try:
storage = get_shared_storage_service()
public_base = getattr(storage, "public_url", "")
if not isinstance(public_base, str) or not public_base:
return url
own_host = urlparse(public_base).netloc.lower()
host = urlparse(url).netloc.lower()
if not own_host or host != own_host:
return url # 外部临时链接原样透传
signed = storage.get_download_url(url, expires_seconds=MEDIAKIT_URL_TTL_SECONDS)
return signed or url
except Exception as exc:
logger.warning("对口型 URL 重签失败,原样返回: url_prefix=%s err=%s", url[:80], exc)
return url
# ── 取消任务 ──────────────────────────────────────────────────────────
def cancel_job(self, job_id: str, user_id: str) -> Optional[LipsyncJobModel]:
"""取消任务(仅 pending/tts_processing/submitted 状态可取消)."""
job = self.get_job(job_id, user_id)
if job is None:
return None
if job.status in ("pending", "tts_processing", "submitted"):
job.status = "cancelled"
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
+242
View File
@@ -0,0 +1,242 @@
"""MediaKit 客户端 — 封装火山引擎 AI MediaKit 对口型 API.
接口文档:https://docs.volcengine.com/docs/6448/2656064
异步任务流程:
1. POST /api/v1/tools/lip-sync 提交对口型任务 → 返回 task_id
2. GET /api/v1/tasks/{task_id} 轮询任务状态 → running/completed/failed
3. completed 时 result.video_url 为口型对齐视频(临时链接 24h 有效)
设计原则:
- API Key 从配置读取(settings.mediakit_api_key
- 未配置 API Key 时所有方法返回降级响应,不阻塞主流程
- HTTP 超时/网络异常统一包装为 MediaKitError
"""
from __future__ import annotations
import logging
from typing import Any, Optional
import httpx
from packages.config import get_api_settings
logger = logging.getLogger(__name__)
# ── 任务状态常量 ──────────────────────────────────────────────────────────
STATUS_RUNNING = "running"
STATUS_COMPLETED = "completed"
STATUS_FAILED = "failed"
class MediaKitError(Exception):
"""MediaKit API 调用异常."""
def __init__(self, message: str, code: str = "", request_id: str = ""):
self.code = code
self.request_id = request_id
super().__init__(message)
class MediaKitClient:
"""火山引擎 AI MediaKit 对口型 API 客户端.
用法:
client = get_mediakit_client()
result = client.submit_lipsync(video_url="...", audio_url="...")
task_id = result["task_id"]
status = client.get_task_status(task_id)
# {"status": "completed", "result": {"video_url": "...", "duration": 60.5}}
"""
def __init__(self) -> None:
settings = get_api_settings()
self._api_key = settings.mediakit_api_key
self._base_url = settings.mediakit_base_url.rstrip("/")
self._timeout = settings.mediakit_timeout
@property
def is_available(self) -> bool:
"""是否已配置 API Key(未配置时自动降级)."""
return bool(self._api_key)
def _headers(self) -> dict[str, str]:
return {
"Authorization": f"Bearer {self._api_key}",
"Content-Type": "application/json",
}
# ── 提交对口型任务 ────────────────────────────────────────────────────
def submit_lipsync(
self,
*,
video_url: str,
audio_url: str,
enable_video_loop: bool = True,
callback_url: Optional[str] = None,
callback_args: Optional[str] = None,
client_token: Optional[str] = None,
) -> dict[str, Any]:
"""提交视频口型对齐任务.
Args:
video_url: 人物视频 URLMP4,≤30min,单人真人)
audio_url: 驱动音频 URLmp3/aac/wav/m4a/flac
enable_video_loop: 音频长于视频时是否循环画面
callback_url: 任务完成回调 URL
callback_args: 回调时原样返回的自定义参数
client_token: 幂等控制 token
Returns:
{"success": True, "task_id": "...", "request_id": "..."}
Raises:
MediaKitError: API 调用失败
"""
if not self.is_available:
raise MediaKitError("MediaKit API Key 未配置", code="NotConfigured")
payload: dict[str, Any] = {
"video_url": video_url,
"audio_url": audio_url,
}
payload["enable_video_loop"] = bool(enable_video_loop)
if callback_url:
payload["callback_url"] = callback_url
if callback_args:
payload["callback_args"] = callback_args[:512] # API 限制 512 字节
if client_token:
payload["client_token"] = client_token[:64] # API 限制 64 字符
try:
with httpx.Client(timeout=self._timeout) as client:
resp = client.post(
f"{self._base_url}/tools/lip-sync",
headers=self._headers(),
json=payload,
)
resp.raise_for_status()
data = resp.json()
except httpx.TimeoutException as exc:
raise MediaKitError(f"MediaKit API 超时 ({self._timeout}s)", code="Timeout") from exc
except httpx.HTTPStatusError as exc:
body = exc.response.text[:500]
raise MediaKitError(
f"MediaKit API HTTP {exc.response.status_code}: {body}",
code="HttpError",
) from exc
except httpx.RequestError as exc:
raise MediaKitError(f"MediaKit API 网络错误: {exc}", code="NetworkError") from exc
except Exception as exc:
raise MediaKitError(f"MediaKit API 未知错误: {exc}", code="UnknownError") from exc
if not data.get("success"):
error = data.get("error", {})
raise MediaKitError(
error.get("message", "提交任务失败"),
code=error.get("code", "SubmitFailed"),
request_id=data.get("request_id", ""),
)
return {
"success": True,
"task_id": data["task_id"],
"request_id": data.get("request_id", ""),
}
# ── 查询任务状态 ──────────────────────────────────────────────────────
def get_task_status(self, task_id: str) -> dict[str, Any]:
"""查询异步任务状态和结果.
Args:
task_id: 提交任务时返回的任务 ID
Returns:
{
"success": True,
"task_id": "...",
"status": "running" | "completed" | "failed",
"result": {"video_url": "...", "duration": 60.5} | None,
"error": {"code": "...", "message": "..."} | None,
"created_at": 1777291767,
"finished_at": 1777291851 | None,
"expires_at": 1777464650 | None,
}
Raises:
MediaKitError: API 调用失败
"""
if not self.is_available:
raise MediaKitError("MediaKit API Key 未配置", code="NotConfigured")
try:
with httpx.Client(timeout=self._timeout) as client:
resp = client.get(
f"{self._base_url}/tasks/{task_id}",
headers=self._headers(),
)
resp.raise_for_status()
data = resp.json()
except httpx.TimeoutException as exc:
raise MediaKitError(f"MediaKit API 超时 ({self._timeout}s)", code="Timeout") from exc
except httpx.HTTPStatusError as exc:
body = exc.response.text[:500]
raise MediaKitError(
f"MediaKit API HTTP {exc.response.status_code}: {body}",
code="HttpError",
) from exc
except httpx.RequestError as exc:
raise MediaKitError(f"MediaKit API 网络错误: {exc}", code="NetworkError") from exc
except Exception as exc:
raise MediaKitError(f"MediaKit API 未知错误: {exc}", code="UnknownError") from exc
if not data.get("success"):
error = data.get("error", {})
raise MediaKitError(
error.get("message", "查询任务失败"),
code=error.get("code", "QueryFailed"),
request_id=data.get("request_id", ""),
)
result: dict[str, Any] = {
"success": True,
"task_id": data.get("task_id", task_id),
"status": data.get("status", STATUS_RUNNING),
"result": data.get("result"),
"created_at": data.get("created_at"),
"finished_at": data.get("finished_at"),
"expires_at": data.get("expires_at"),
}
# 失败时提取错误信息
if data.get("status") == STATUS_FAILED:
error_obj = data.get("error", {})
result["error"] = {
"code": error_obj.get("code", "TaskFailed"),
"message": error_obj.get("message", "任务执行失败"),
}
return result
# ── 单例 ──────────────────────────────────────────────────────────────────
_client: Optional[MediaKitClient] = None
def get_mediakit_client() -> MediaKitClient:
"""获取 MediaKit 客户端单例."""
global _client
if _client is None:
_client = MediaKitClient()
return _client
def reset_mediakit_client() -> None:
"""重置客户端(测试用)."""
global _client
_client = None
+109
View File
@@ -0,0 +1,109 @@
"""ScriptService — Issue #1795 口播文案库 CRUD.
纯 Service 层封装,routes 直接调用。
"""
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import ScriptModel
class ScriptNotFoundError(Exception):
"""文案不存在或不属于当前用户."""
class ScriptService:
"""口播文案 CRUD."""
def __init__(self, db: Session) -> None:
self.db = db
# ── list ──────────────────────────────────────────────────────────────
def list_scripts(
self,
user_id: str,
skip: int = 0,
limit: int = 50,
tag: Optional[str] = None,
) -> tuple[list[ScriptModel], int]:
"""返回 (items, total)."""
q = self.db.query(ScriptModel).filter(ScriptModel.user_id == user_id)
if tag:
# JSON 数组包含查询
q = q.filter(ScriptModel.tags.contains([tag]))
total = q.count()
items = q.order_by(ScriptModel.created_at.desc()).offset(skip).limit(limit).all()
return items, total
# ── create ────────────────────────────────────────────────────────────
def create_script(
self,
user_id: str,
title: str,
content: str = "",
segments: list | None = None,
tags: list | None = None,
) -> ScriptModel:
script = ScriptModel(
id=str(uuid.uuid4()),
user_id=user_id,
title=title,
content=content,
segments=segments if segments is not None else [],
tags=tags if tags is not None else [],
)
self.db.add(script)
self.db.commit()
self.db.refresh(script)
return script
# ── get ───────────────────────────────────────────────────────────────
def get_script(self, script_id: str, user_id: str) -> ScriptModel:
script = self.db.query(ScriptModel).filter(ScriptModel.id == script_id, ScriptModel.user_id == user_id).first()
if script is None:
raise ScriptNotFoundError(f"Script {script_id} not found")
return script
# ── update ────────────────────────────────────────────────────────────
def update_script(
self,
script_id: str,
user_id: str,
title: Optional[str] = None,
content: Optional[str] = None,
segments: Optional[list] = None,
tags: Optional[list] = None,
) -> ScriptModel:
script = self.get_script(script_id, user_id)
if title is not None:
script.title = title
if content is not None:
script.content = content
if segments is not None:
script.segments = segments
if tags is not None:
script.tags = tags
script.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(script)
return script
# ── delete ────────────────────────────────────────────────────────────
def delete_script(self, script_id: str, user_id: str) -> bool:
script = self.db.query(ScriptModel).filter(ScriptModel.id == script_id, ScriptModel.user_id == user_id).first()
if script is None:
return False
self.db.delete(script)
self.db.commit()
return True
@@ -39,6 +39,9 @@ from packages.domain.video_filter_builder import (
)
from packages.domain.video_filter_builder import build_concat_filter as _build_concat_filter_func
from packages.domain.video_filter_builder import build_filter_complex as _build_filter_complex
from packages.domain.video_filter_builder import (
build_title_drawtext_filter,
)
from packages.domain.video_filter_builder import build_xfade_filter as _build_xfade_filter_func
from packages.domain.video_filter_builder import chain_filters as _chain_filters_func
from packages.domain.video_filter_builder import has_audio as _has_audio_func
@@ -248,6 +251,27 @@ class VideoComposeService:
transitions=[c.transition_effect for c in ready_clips],
)
# ── #1789 标题 drawtext 滤镜叠加 ──
# 从 plan.config 读取 title_config,生成 drawtext 滤镜链入 filter_complex
title_cfg = (plan.config or {}).get("title", {}) or {}
if not isinstance(title_cfg, dict):
title_cfg = {}
# 同时兼容 plan.config["title_config"]API 回写路径)
if not title_cfg.get("text") and not title_cfg.get("content"):
title_cfg_alt = (plan.config or {}).get("title_config", {}) or {}
if isinstance(title_cfg_alt, dict) and (title_cfg_alt.get("text") or title_cfg_alt.get("content")):
title_cfg = title_cfg_alt
drawtext_filter = build_title_drawtext_filter(title_cfg, output_width, output_height)
if drawtext_filter:
# 将最终输出标签从 [outv] 改为 [composed],再链入 drawtext → [outv]
filter_complex = filter_complex.replace("[outv]", "[composed]")
filter_complex += f";[composed]{drawtext_filter}[outv]"
logger.info(
"[#1789] 标题 drawtext 滤镜已注入: plan_id=%s text=%s",
plan_id,
(title_cfg.get("text") or title_cfg.get("content") or "")[:30],
)
# 构建完整命令
command: list[str] = ["ffmpeg", "-y"]
+1
View File
@@ -0,0 +1 @@
"""Celery 异步任务模块."""
+48
View File
@@ -0,0 +1,48 @@
"""AI数字人渲染 Celery 异步任务 — #1798."""
from __future__ import annotations
import logging
from app.core.celery_app import celery_app
from app.dependencies import get_db_session
logger = logging.getLogger(__name__)
@celery_app.task(bind=True, name="ai_avatar_render.execute", max_retries=2)
def execute_ai_avatar_render(self, job_id: str) -> dict:
"""执行 AI 数字人渲染管线.
进度更新:
- 0%: 任务开始
- 20%: 下载对口型视频完成
- 40%: 滤镜链构建完成
- 80%: FFmpeg 渲染完成
- 95%: 上传 OSS 完成
- 100%: 任务完成
"""
logger.info("开始执行渲染任务: %s", job_id)
self.update_state(state="PROCESSING", meta={"progress": 0, "job_id": job_id})
try:
# 获取数据库 session
db_gen = get_db_session()
db = next(db_gen)
try:
from app.services.ai_avatar_render_service import AiAvatarRenderService
service = AiAvatarRenderService(db)
service.execute_render(job_id)
finally:
try:
next(db_gen)
except StopIteration:
pass
return {"status": "completed", "job_id": job_id}
except Exception as exc:
logger.exception("渲染任务执行异常 [%s]: %s", job_id, exc)
self.update_state(state="FAILED", meta={"progress": 0, "error": str(exc)})
raise
+355
View File
@@ -0,0 +1,355 @@
"""AI 数字人对口型 TTS 异步任务 — 将 TTS 合成从 HTTP 请求移至 Celery 后台执行.
优化目标:将 create_job 的 API 响应时间从 6~35s 降到 <1s。
任务流程:
1. 创建新 DB session,加载 job 记录
2. 调用 CosyVoice 合成音频
3. 下载音频并转存到自家 OSS
4. 更新 job 的 audio_url
5. 签名 URL 并提交到 MediaKit
6. 更新 job 状态为 submitted
7. 异常时标记 job 为 failed
注意:使用 @shared_task 而非绑定到某个 celery_app 实例,
确保任务能被 Worker 侧 celery_app 正确注册,同时 API 侧 send_task/apply_async 仍可正常调用。
#1845:句子时间戳计算已提取至 packages/domain/sentence_timings.py,本模块保留
_ 开头别名兼容历史导入,但 _compute_sentence_timings/_split_script_into_sentences/
_estimate_sentence_timings_by_chars 等内部函数已复用共享实现,避免重复代码。
"""
import io
import logging
from datetime import datetime, timezone
from urllib.parse import urlparse
from celery import shared_task
# 复用共享的句子时间戳工具(#1845 配音前置)
from packages.domain.sentence_timings import compute_sentence_timings as _compute_sentence_timings
from packages.domain.sentence_timings import (
probe_audio_duration,
)
logger = logging.getLogger(__name__)
# MediaKit 预签名 URL 有效期(7天,秒),与 LipsyncService._sign_media_url 保持一致
_MEDIAKIT_URL_TTL_SECONDS = 7 * 24 * 3600
def _sign_media_url(url: str) -> str:
"""对自家 OSS 私有桶 URL 重签长有效期预签名.
- 自家 OSS URL → 重签 7 天有效期
- 外部临时 URL → 原样透传
- 任何异常降级原样返回,不阻断主流程
"""
if not url:
return url
try:
from packages.shared.storage import get_shared_storage_service
storage = get_shared_storage_service()
public_base = getattr(storage, "public_url", "")
if not isinstance(public_base, str) or not public_base:
return url
own_host = urlparse(public_base).netloc.lower()
host = urlparse(url).netloc.lower()
if not own_host or host != own_host:
return url
signed = storage.get_download_url(url, expires_seconds=_MEDIAKIT_URL_TTL_SECONDS)
return signed or url
except Exception as exc: # noqa: BLE001
logger.warning("[lipsync_tts] URL 重签失败,原样返回: url_prefix=%s err=%s", url[:80], exc)
return url
@shared_task(
bind=True,
name="lipsync_tts.synthesize_and_submit",
max_retries=5, # 事务竞态重试3次(job not found+ TTS偶发错误2次
default_retry_delay=30,
autoretry_for=(OSError, ConnectionError), # 网络/连接错误自动重试
retry_backoff=True,
retry_backoff_max=30,
soft_time_limit=180,
time_limit=200,
)
def tts_synthesize_and_submit(
self,
job_id: str,
user_id: str,
voice_id: str,
script_text: str,
speed: float,
emotion: str,
):
"""异步执行 TTS 合成 + OSS 转存 + MediaKit 提交.
在 Celery worker 中运行,不阻塞 HTTP 请求。保留作为降级路径
(预合成失败 / 旧版前端未传 audio_url 时走此路径)。
"""
from app.services.mediakit_client import MediaKitError, get_mediakit_client
from sqlalchemy.orm import Session as DBSession
from packages.adapters.sqlalchemy_impl.models import LipsyncJobModel
from packages.application.cosyvoice_service import CosyVoiceError, CosyVoiceService
from packages.shared.url_security import safe_download_bytes
# SessionLocal 获取:
# - API 容器:app.db.SessionLocal(环境变量完整,导入即建引擎)
# - Worker 容器:worker_app.db.SessionLocalWorker 自己的 settings 初始化引擎)
# API 侧没有 worker_app 模块 → ImportError 直接回退;
# Worker 侧 app.db 会因缺少 API 专有环境变量抛 pydantic ValidationError
# 此时也要回退到 worker_app.db。
try:
from worker_app.db import SessionLocal # type: ignore
except Exception: # noqa: BLE001
from app.db import SessionLocal # type: ignore
db: DBSession = SessionLocal()
try:
job = (
db.query(LipsyncJobModel)
.filter(
LipsyncJobModel.id == job_id,
LipsyncJobModel.user_id == user_id,
)
.first()
)
if job is None:
# 事务竞态防御:API 在 commit 前投递了任务,worker 消费时事务尚未提交。
retries = getattr(self.request, "retries", 0)
max_retries = 3
if retries < max_retries:
backoff = (2**retries) + (retries * 1) # 1s, 3s, 7s
logger.warning(
"[lipsync_tts] Job not found yet (retry %d/%d, backoff %ds): job_id=%s",
retries + 1,
max_retries,
backoff,
job_id,
)
self.db.close()
raise self.retry(countdown=backoff, max_retries=max_retries)
logger.error(
"[lipsync_tts] Job not found after %d retries, giving up: job_id=%s",
max_retries,
job_id,
)
return
# 已取消的任务不再处理
if job.status == "cancelled":
logger.info("[lipsync_tts] Job already cancelled, skipping: job_id=%s", job_id)
return
# 1. TTS 合成
logger.info(
"[lipsync_tts] 开始 TTS 合成: job_id=%s voice_id=%s text_len=%d speed=%.2f",
job_id,
voice_id,
len(script_text),
speed,
)
try:
cosyvoice = CosyVoiceService()
result = cosyvoice.submit_synthesize_task(
text=script_text,
voice_id=voice_id,
speed=speed,
emotion=emotion,
)
except CosyVoiceError as exc:
logger.error("[lipsync_tts] TTS 合成失败: job_id=%s err=%s", job_id, exc)
job.status = "failed"
job.error_message = f"TTS 合成失败: {exc}"
job.error_code = "TTSSynthesisFailed"
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
except ValueError as exc:
logger.error("[lipsync_tts] TTS 参数错误: job_id=%s err=%s", job_id, exc)
job.status = "failed"
job.error_message = f"TTS 参数错误: {exc}"
job.error_code = "TTSInvalidParam"
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
temp_url = result.get("audio_url", "")
if not temp_url:
logger.error("[lipsync_tts] TTS 未返回音频 URL: job_id=%s", job_id)
job.status = "failed"
job.error_message = "TTS 未返回音频 URL"
job.error_code = "TTSNoAudio"
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
# 2. 下载 TTS 音频到内存(用于 2.5 静音检测;不转存自家 OSS,直接使用 CosyVoice 临时 URL
audio_data: bytes | None = None
try:
audio_data = safe_download_bytes(
temp_url,
purpose="lipsync_tts_audio",
allowed_mime_types={
"audio/mpeg",
"audio/mp3",
"audio/wav",
"audio/x-wav", # CosyVoice 部分接口返回 audio/x-wav
"audio/mp4",
"audio/x-m4a",
},
timeout=60.0,
)
logger.info(
"[lipsync_tts] TTS 音频已下载到内存: job_id=%s size=%d",
job_id,
len(audio_data) if audio_data else 0,
)
except Exception as exc:
logger.warning(
"[lipsync_tts] TTS 音频下载失败,跳过静音检测,直接使用临时 URL 提交: job_id=%s err=%s",
job_id,
exc,
)
# TTS 音频使用 CosyVoice 临时 URL,跳过自家 OSS 转存(加速,步骤⑥)
job.audio_url = temp_url
logger.info("[lipsync_tts] TTS 音频使用 CosyVoice 临时 URL(跳过 OSS 转存): job_id=%s", job_id)
db.commit()
# 2.5 计算精确句子时间戳(基于 TTS 音频静音检测)—— 复用共享工具
try:
if not audio_data:
logger.warning("[lipsync_tts] 无音频数据,跳过句子时间戳计算: job_id=%s", job_id)
else:
_audio_duration = probe_audio_duration(audio_data)
logger.info(
"[lipsync_tts] 音频时长探测: job_id=%s duration=%.2f",
job_id,
_audio_duration,
)
if _audio_duration > 0:
_timings = _compute_sentence_timings(audio_data, script_text, _audio_duration)
if _timings:
job.sentence_timings = _timings
logger.info(
"[lipsync_tts] 句子时间戳已计算: job_id=%s sentences=%d duration=%.1f",
job_id,
len(_timings),
_audio_duration,
)
else:
logger.warning("[lipsync_tts] 句子时间戳计算返回空结果: job_id=%s", job_id)
else:
logger.warning(
"[lipsync_tts] ffprobe 未获取到有效时长,跳过句子时间戳: job_id=%s",
job_id,
)
db.commit()
except Exception as _st_err:
logger.warning(
"[lipsync_tts] 句子时间戳计算失败(不影响主流程): job_id=%s err=%s", job_id, _st_err, exc_info=True
)
# 3. 签名 URL 并提交到 MediaKit(复用模块内 _sign_media_url,避免对 LipsyncService 的耦合)
audio_url = _sign_media_url(job.audio_url)
video_url = _sign_media_url(job.video_url)
client = get_mediakit_client()
try:
mk_result = client.submit_lipsync(
video_url=video_url,
audio_url=audio_url,
enable_video_loop=job.enable_video_loop,
client_token=job_id,
)
job.mediakit_task_id = mk_result["task_id"]
job.status = "submitted"
job.submitted_at = datetime.now(timezone.utc)
logger.info(
"[lipsync_tts] 已提交 MediaKit: job_id=%s task_id=%s",
job_id,
mk_result["task_id"],
)
except MediaKitError as exc:
job.status = "failed"
job.error_message = str(exc)
job.error_code = exc.code
logger.error("[lipsync_tts] 提交 MediaKit 失败: job_id=%s err=%s", job_id, exc)
db.commit()
except Exception:
logger.exception("[lipsync_tts] 未预期的异常: job_id=%s", job_id)
try:
job = db.query(LipsyncJobModel).filter(LipsyncJobModel.id == job_id).first()
if job and job.status not in ("cancelled", "failed", "completed"):
job.status = "failed"
job.error_message = "TTS 异步任务执行异常"
job.error_code = "AsyncTaskError"
job.updated_at = datetime.now(timezone.utc)
db.commit()
except Exception:
logger.exception("[lipsync_tts] 回写失败状态时异常: job_id=%s", job_id)
finally:
db.close()
@shared_task(
name="lipsync_tts.persist_output_video",
max_retries=2,
default_retry_delay=30,
)
def persist_output_video_task(job_id: str, user_id: str, temp_url: str):
"""异步转存对口型输出视频到自家 OSS(步骤⑦ — 将同步阻塞挪到后台,加速前端响应)."""
try:
from worker_app.db import SessionLocal # type: ignore
except Exception: # noqa: BLE001
from app.db import SessionLocal # type: ignore
from packages.adapters.sqlalchemy_impl.models import LipsyncJobModel
from packages.shared.storage import get_shared_storage_service
db = SessionLocal()
try:
job = db.query(LipsyncJobModel).filter(LipsyncJobModel.id == job_id, LipsyncJobModel.user_id == user_id).first()
if job is None:
logger.error("[lipsync_tts.persist] Job not found: job_id=%s", job_id)
return
if not temp_url:
logger.warning("[lipsync_tts.persist] temp_url 为空,跳过转存: job_id=%s", job_id)
return
try:
import httpx
with httpx.Client(timeout=180.0, follow_redirects=True) as client:
resp = client.get(temp_url)
resp.raise_for_status()
data = resp.content
storage = get_shared_storage_service()
storage_key = f"lipsync-outputs/{user_id}/{job_id}.mp4"
permanent_url = storage.upload_file(io.BytesIO(data), storage_key, content_type="video/mp4")
final_url = _sign_media_url(permanent_url) if permanent_url else temp_url
job.output_video_url = final_url
job.updated_at = datetime.now(timezone.utc)
db.commit()
logger.info("[lipsync_tts.persist] 输出视频已转存 OSS: job_id=%s key=%s", job_id, storage_key)
except Exception as exc:
logger.warning(
"[lipsync_tts.persist] 输出视频转存失败,保留临时 URL: job_id=%s err=%s",
job_id,
exc,
)
except Exception:
logger.exception("[lipsync_tts.persist] 未预期异常: job_id=%s", job_id)
finally:
db.close()
@@ -26,6 +26,10 @@ export interface BatchVariantPlansRequest {
count: number
/** 源剪辑计划 ID:优先取预览/草稿关联的 plan;不传由后端按 template_id+user 兜底最新 plan */
source_edit_plan_id?: string
/** 统一配音 ID(共用配音模式);独立配音模式不传,改传 voice_library_ids */
voice_library_id?: string
/** 独立配音 ID 列表(长度=count,按变体序号一一对应);共用配音模式不传 */
voice_library_ids?: string[]
}
/** 单个变体的计划片段 */
@@ -36,6 +40,8 @@ export interface VariantPlan {
plan_id: string
/** 该变体的真实片段(顺序/素材/起点与正式成片一致) */
clips: EditPlanClip[]
/** 该变体实际配音时长(秒),用于前端预览按配音时长对齐音画;后端暂未返回时缺省 */
voice_duration?: number
}
/** 批量变体计划响应 */
+34 -11
View File
@@ -1,6 +1,6 @@
/**
* 成品 / 视频相关 API 函数
* 后端实际接口:/videos
* 后端实际接口:/videos(分页:page/page_size,返回 {items, total, page, page_size}
*/
import apiClient from "../client"
import type {
@@ -12,16 +12,39 @@ import type {
} from "./types"
import { mapVideoToProductItem } from "./utils"
/** 获取成品列表(支持分页和筛选 */
export const getProducts = async (params?: ProductListParams): Promise<ProductItem[]> => {
const response = await apiClient.get("/videos", { params })
const data = response.data
const videos: VideoItem[] = Array.isArray(data?.items)
? data.items
: Array.isArray(data)
? data
: []
return videos.map(mapVideoToProductItem)
/** 分页列表响应(前端消费用 */
export interface ProductListResult {
items: ProductItem[]
total: number
page: number
page_size: number
}
/**
* 获取成品列表(分页)
* @param params 分页与筛选参数:page 默认 1page_size 默认 20
*/
export const getProducts = async (params?: ProductListParams): Promise<ProductListResult> => {
const response = await apiClient.get("/videos", {
params: {
page: 1,
page_size: 20,
...params,
},
})
const data = response.data as {
items?: VideoItem[]
total?: number
page?: number
page_size?: number
}
const items: VideoItem[] = Array.isArray(data?.items) ? data.items : []
return {
items: items.map(mapVideoToProductItem),
total: data.total ?? items.length,
page: data.page ?? params?.page ?? 1,
page_size: data.page_size ?? params?.page_size ?? 20,
}
}
/** 获取单个成品详情 */
+2
View File
@@ -0,0 +1,2 @@
export * from "./scripts"
export * from "./types"
+37
View File
@@ -0,0 +1,37 @@
/**
* 文案库 API
* 对接后端 /api/v1/scriptsCRUD + 列表解包)
*/
import apiClient from "../client"
import type {
ScriptItem,
ScriptListResponse,
CreateScriptRequest,
UpdateScriptRequest,
} from "./types"
/** 获取文案列表 — 必须解包 items(后端返回 {items,total}*/
export const getScripts = async (): Promise<ScriptItem[]> => {
const response = await apiClient.get<ScriptListResponse | ScriptItem[]>("/scripts")
const data = response.data as unknown
if (Array.isArray(data)) return data
const items = (data as { items?: ScriptItem[] })?.items
return Array.isArray(items) ? items : []
}
/** 新建文案 */
export const createScript = async (data: CreateScriptRequest): Promise<ScriptItem> => {
const response = await apiClient.post<ScriptItem>("/scripts", data)
return response.data
}
/** 更新文案 */
export const updateScript = async (id: string, data: UpdateScriptRequest): Promise<ScriptItem> => {
const response = await apiClient.put<ScriptItem>(`/scripts/${id}`, data)
return response.data
}
/** 删除文案 */
export const deleteScript = async (id: string): Promise<void> => {
await apiClient.delete(`/scripts/${id}`)
}
+24
View File
@@ -0,0 +1,24 @@
/**
* 文案库 API — 类型定义
* 对接后端 /api/v1/scripts
*/
export interface ScriptItem {
id: string
title: string
content: string
char_count: number
created_at: string
updated_at?: string
}
export interface ScriptListResponse {
items: ScriptItem[]
total: number
}
export interface CreateScriptRequest {
title: string
content: string
}
export type UpdateScriptRequest = Partial<CreateScriptRequest>
+1
View File
@@ -103,6 +103,7 @@ export interface TTSPreviewRequest {
voice_id: string
speed?: number
pitch?: number
emotion?: string // 情绪参数:natural/excited/calm/friendly
}
/** TTS 试听响应 */
+10 -4
View File
@@ -5,7 +5,7 @@
.xx-app-shell 全屏 flex 容器
├── header (xx-top-nav) 顶部导航(Header.tsx 管理)
└── .xx-app-body 水平 flex 行
├── .xx-app-sidebar 左侧侧边栏(240px / 64px 折叠)
├── .xx-app-sidebar 左侧侧边栏(128px / 64px 折叠)
└── .xx-app-content 主内容区(自适应)
所有尺寸/颜色均使用 global.css 设计系统变量
@@ -31,7 +31,7 @@
/* ── 侧边栏 ───────────────────────────────────────────────── */
.xx-app-sidebar {
width: 240px;
width: 128px;
flex-shrink: 0;
position: sticky;
top: 0;
@@ -103,6 +103,12 @@
padding: var(--space-sm);
}
/* 展开态(侧边栏 128px)水平 padding 收窄,为菜单文字留出完整一行空间 */
.xx-app-sidebar:not(.xx-collapsed) .xx-sidebar-content {
padding-left: var(--space-xs);
padding-right: var(--space-xs);
}
/* ── 主内容区 ─────────────────────────────────────────────── */
.xx-app-content {
flex: 1;
@@ -136,7 +142,7 @@
/* 展开态恢复完整宽度 */
.xx-app-sidebar:not(.xx-collapsed) {
width: 240px;
width: 128px;
}
.xx-app-sidebar:not(.xx-collapsed) .xx-sidebar-toggle {
@@ -159,7 +165,7 @@
top: 56px; /* 移动端 Header 高度 */
left: 0;
bottom: 0;
width: 240px;
width: 128px;
transform: translateX(-100%);
transition: transform var(--transition-slow);
box-shadow: none;
@@ -2,7 +2,7 @@
* MainLayout - 主布局组件(Task 1.2
*
* 三栏布局:左侧侧边栏 + 顶部导航栏 + 主内容区
* - 侧边栏:240px 固定宽度,可折叠至 64px 图标栏
* - 侧边栏:128px 固定宽度,可折叠至 64px 图标栏
* - 顶部导航:复用 Header 组件(68px 固定高度)
* - 主内容区:自适应填充剩余空间
* - 响应式:移动端(<768px)隐藏侧边栏
+20 -10
View File
@@ -30,7 +30,7 @@
/* 分组标题 */
.xx-sidebar-group-title {
padding: var(--space-sm) var(--space-md) var(--space-xs);
padding: var(--space-sm) var(--space-sm) var(--space-xs);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
color: var(--text-tertiary);
@@ -56,9 +56,9 @@
.xx-sidebar-menu-item {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
margin: 0 var(--space-xs);
gap: var(--space-xs);
padding: var(--space-sm) var(--space-xs);
margin: 0 var(--space-xxs);
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--text-secondary);
@@ -97,12 +97,12 @@
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 36px;
height: 36px;
border-radius: 10px;
width: 28px;
height: 28px;
border-radius: 8px;
background: #f1f5f9;
color: var(--text-secondary);
font-size: 18px;
font-size: 16px;
line-height: 1;
transition: 0.15s ease;
}
@@ -119,7 +119,9 @@
/* ── 菜单项文字 ───────────────────────────────────────────── */
.xx-sidebar-menu-label {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
@@ -133,8 +135,16 @@
/* 折叠时菜单项居中,仅图标 */
.xx-sidebar-nav--collapsed .xx-sidebar-menu-item {
justify-content: center;
padding: var(--space-sm);
margin: 0 var(--space-xxs);
padding: var(--space-xs);
margin: 0;
}
/* 折叠态图标恢复更大尺寸居中 */
.xx-sidebar-nav--collapsed .xx-sidebar-menu-icon {
width: 32px;
height: 32px;
border-radius: 8px;
font-size: 16px;
}
/* 折叠时隐藏分组标题 */
+25
View File
@@ -18,6 +18,7 @@ import {
ControlOutlined,
CrownOutlined,
UnorderedListOutlined,
UserOutlined,
} from "@ant-design/icons"
/** 导航项类型 */
@@ -57,6 +58,12 @@ export const NAV_ITEMS: NavItem[] = [
path: "/app/titles",
icon: React.createElement(FileTextOutlined),
},
{
key: "scripts",
label: "文案库",
path: "/app/scripts",
icon: React.createElement(EditOutlined),
},
{
key: "voices",
label: "配音库",
@@ -88,6 +95,12 @@ export const NAV_ITEMS: NavItem[] = [
path: "/app/generate",
icon: React.createElement(VideoCameraOutlined),
},
{
key: "ai-avatar",
label: "AI数字人",
path: "/app/ai-avatar",
icon: React.createElement(UserOutlined),
},
{
key: "history",
label: "任务历史",
@@ -131,6 +144,12 @@ export const NAV_GROUPS: NavGroup[] = [
path: "/app/generate",
icon: React.createElement(VideoCameraOutlined),
},
{
key: "ai-avatar",
label: "AI数字人",
path: "/app/ai-avatar",
icon: React.createElement(UserOutlined),
},
{
key: "editing-planner",
label: "剪辑模板",
@@ -160,6 +179,12 @@ export const NAV_GROUPS: NavGroup[] = [
path: "/app/titles",
icon: React.createElement(FileTextOutlined),
},
{
key: "scripts",
label: "文案库",
path: "/app/scripts",
icon: React.createElement(EditOutlined),
},
{
key: "products",
label: "成品库",
+1 -1
View File
@@ -11,7 +11,7 @@
.admin-coming-soon-page {
padding: 32px;
max-width: 1400px;
max-width: 1680px;
margin: 0 auto;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,131 @@
/**
* AI数字人 — API 封装(#1822 契约对齐)
*/
import apiClient from "@/api/client"
import type { Script, LipsyncJob, RenderJob, BRollSegment, SentenceTiming } from "../types"
/* ── 文案库 ── */
export const getScripts = async (): Promise<Script[]> => {
const response = await apiClient.get<{ items?: Script[] } | Script[]>("/scripts")
// 后端列表返回 { items, total } 分页对象,做兼容解包 + 数组防御(#1809 白屏修复)
const data = response.data as unknown
if (Array.isArray(data)) return data
const items = (data as { items?: Script[] })?.items
return Array.isArray(items) ? items : []
}
export const getScriptById = async (id: string): Promise<Script> => {
const response = await apiClient.get<Script>(`/scripts/${id}`)
return response.data
}
export const createScript = async (data: { title: string; content: string }): Promise<Script> => {
const response = await apiClient.post<Script>("/scripts", data)
return response.data
}
export const deleteScript = async (id: string): Promise<void> => {
await apiClient.delete(`/scripts/${id}`)
}
/* ── 素材单查(拿到 file_url 作为对口型的 video_url ── */
export const getAssetById = async (id: string): Promise<{ file_url?: string; id: string }> => {
const response = await apiClient.get<{ file_url?: string; id: string }>(`/assets/${id}`)
return response.data
}
/* ── 对口型(支持三种模式) ──
* 1. TTS 直生(降级/旧版):传 voice_id + script_text+speed/emotion),后端 Celery 异步合成
* 2. 直接音频:传 video_url + audio_url,后端同步下载+算timings+提交MediaKit
* 3. 预合成音频(#1845 新主路径):先调 previewTts 拿 audio_url+sentence_timings
* 再把 audio_url + audio_duration + sentence_timings 一起传过来,后端直接提交 MediaKit
*/
export const createLipsyncJob = async (data: {
/** 人物视频 URLMP4);由素材 id 经 getAssetById 拿 file_url */
video_url: string
/** 预合成/直接音频模式:音频 URL(#1845 步骤1 预合成的 CosyVoice 临时 URL,或外部音频 URL */
audio_url?: string
/** 预合成音频时长(秒),由 previewTts 返回 */
audio_duration?: number
/** 预合成接口返回的句子时间戳(精确),后端直接写入 job */
sentence_timings?: SentenceTiming[]
/** 音色 IDTTS 直生模式用) */
voice_id?: string
/** 要合成的文案(TTS 直生模式用) */
script_text?: string
/** 语速 0.5~2.0,默认 1.0TTS 直生模式用) */
speed?: number
/** 情绪英文枚举:natural/excited/calm/friendlyTTS 直生模式用) */
emotion?: string
enable_video_loop?: boolean
project_id?: string
}): Promise<LipsyncJob> => {
const response = await apiClient.post<LipsyncJob>("/lipsync/jobs", data)
return response.data
}
/* ── #1845 TTS 预合成(步骤1「生成配音」同步接口,~2-3s) ── */
export const previewTts = async (data: {
voice_id: string
script_text: string
speed?: number
emotion?: string
}): Promise<{
audio_url: string
duration: number
sentence_timings: SentenceTiming[]
}> => {
const response = await apiClient.post<{
audio_url: string
duration: number
sentence_timings: SentenceTiming[]
}>("/lipsync/tts-preview", data, { timeout: 30000 })
return response.data
}
export const getLipsyncJob = async (id: string): Promise<LipsyncJob> => {
const response = await apiClient.get<LipsyncJob>(`/lipsync/jobs/${id}`, { timeout: 60000 })
return response.data
}
/* ── 渲染 ── */
export const submitRender = async (data: {
lipsync_job_id: string
script_id?: string
b_roll_segments?: BRollSegment[]
title_config?: Record<string, unknown>
cover_config?: Record<string, unknown>
project_id?: string
}): Promise<RenderJob> => {
// title_config 内可含 title_image_dataurl(前端 Canvas 渲染的 PNG dataURL
const response = await apiClient.post<RenderJob>("/ai-avatar/render", data)
return response.data
}
export const getRenderJob = async (jobId: string): Promise<RenderJob> => {
const response = await apiClient.get<RenderJob>(`/ai-avatar/render/${jobId}`, { timeout: 60000 })
return response.data
}
export const cancelRenderJob = async (jobId: string): Promise<void> => {
await apiClient.post(`/ai-avatar/render/${jobId}/cancel`)
}
/* ── 从最终渲染成片智能抽封面(POST /ai-avatar/renders/{job_id}/smart-cover ── */
export const generateRenderSmartCover = async (
jobId: string,
): Promise<{ cover_url: string; status: string; message: string }> => {
const response = await apiClient.post<{ cover_url: string; status: string; message: string }>(
`/ai-avatar/render/${jobId}/smart-cover`,
{},
// 抽帧+评分+转存 OSS 链路较长,120s 超时
{ timeout: 120000 },
)
return response.data
}
/* ── 封面选定后点「完成」正式入库(POST /ai-avatar/render/{job_id}/finalize ── */
export const finalizeRenderJob = (renderId: string) =>
apiClient.post<{ video_id: string; cover_url: string; status: string }>(
`/ai-avatar/render/${renderId}/finalize`,
)
@@ -0,0 +1,209 @@
/**
* AI数字人 — 出镜视频选择弹窗(#1809 ③)
* 交互对齐智能剪辑 Step2:先选素材库(video 库)→ 再选该库内视频。
* 搜索框 + 素材库下拉 + 竖屏 9:16 视频缩略图网格 + 底部确认选择。
*/
import { useEffect, useState } from "react"
import { getAssets, getAssetLibraries, type AssetItem, type AssetLibraryItem } from "@/api/assets"
export interface ModalAssetPickerProps {
open: boolean
onClose: () => void
onSelect: (asset: AssetItem) => void
/** 已选中的素材 ID(用于高亮) */
selectedId?: string
}
export function ModalAssetPicker({ open, onClose, onSelect, selectedId }: ModalAssetPickerProps) {
const [keyword, setKeyword] = useState("")
const [libraries, setLibraries] = useState<AssetLibraryItem[]>([])
const [libraryId, setLibraryId] = useState<string>("")
const [assets, setAssets] = useState<AssetItem[]>([])
const [pickedId, setPickedId] = useState<string | null>(null)
const [loadingLibs, setLoadingLibs] = useState(false)
const [loadingAssets, setLoadingAssets] = useState(false)
const [error, setError] = useState("")
/* 弹窗打开:重置状态 */
useEffect(() => {
if (!open) return
setKeyword("")
setLibraries([])
setLibraryId("")
setAssets([])
setError("")
setPickedId(selectedId ?? null)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open])
/* 第一步:加载视频素材库列表(仅 kind=video,对齐智能剪辑 #1777 */
useEffect(() => {
if (!open) return
let cancelled = false
setLoadingLibs(true)
getAssetLibraries("video")
.then((libs) => {
if (cancelled) return
const list = Array.isArray(libs) ? libs : []
setLibraries(list)
// 默认选中第一个视频库
if (list.length > 0) setLibraryId((prev) => prev || list[0].id)
})
.catch(() => {
if (!cancelled) setError("素材库加载失败,请重试")
})
.finally(() => {
if (!cancelled) setLoadingLibs(false)
})
return () => {
cancelled = true
}
}, [open])
/* 第二步:选中库后拉取该库视频素材(关键字防抖) */
useEffect(() => {
if (!open || !libraryId) return
let cancelled = false
setLoadingAssets(true)
const load = async () => {
try {
// getAssets 返回 { items, total };拉满一页(数字人口播视频库通常不大)
const { items } = await getAssets(libraryId, { page_size: 100 })
if (cancelled) return
let list = Array.isArray(items) ? items : []
// 仅保留视频素材(出镜视频要求)
list = list.filter((a) => a.mime_type?.includes("video"))
const kw = keyword.trim()
if (kw) list = list.filter((a) => a.name?.includes(kw))
setAssets(list)
setError("")
} catch {
if (!cancelled) {
setError("素材加载失败,请重试")
setAssets([])
}
} finally {
if (!cancelled) setLoadingAssets(false)
}
}
const timer = window.setTimeout(load, 300)
return () => {
cancelled = true
window.clearTimeout(timer)
}
}, [open, libraryId, keyword])
if (!open) return null
const handleConfirm = () => {
if (!pickedId) return
const asset = assets.find((a) => a.id === pickedId)
if (asset) onSelect(asset)
onClose()
}
return (
<div className="aa-modal-overlay" onClick={onClose}>
<div className="aa-modal" onClick={(e) => e.stopPropagation()}>
{/* 头部 */}
<div className="aa-modal__header">
<span className="aa-modal__title"></span>
<button type="button" className="aa-modal__close" onClick={onClose} aria-label="关闭">
×
</button>
</div>
{/* 主体:素材库选择 + 搜索 + 网格 */}
<div className="aa-modal__body">
{/* 第一步:选素材库 */}
<div className="aa-asset-search">
<select
className="aa-select"
style={{ width: 160, flex: "0 0 auto" }}
value={libraryId}
onChange={(e) => setLibraryId(e.target.value)}
disabled={loadingLibs || libraries.length === 0}
>
{libraries.length === 0 ? (
<option value="">{loadingLibs ? "素材库加载中…" : "暂无视频素材库"}</option>
) : (
libraries.map((lib) => (
<option key={lib.id} value={lib.id}>
📁 {lib.name}
</option>
))
)}
</select>
<input
className="aa-input"
type="text"
placeholder="搜索素材名称…"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
</div>
{libraries.length === 0 && !loadingLibs ? (
<div className="aa-empty">
<div className="aa-empty__icon">📁</div>
</div>
) : loadingAssets ? (
<div className="aa-empty">
<div className="aa-empty__icon"></div>
</div>
) : error ? (
<div className="aa-empty">
<div className="aa-empty__icon"></div>
{error}
</div>
) : assets.length === 0 ? (
<div className="aa-empty">
<div className="aa-empty__icon">🎬</div>
</div>
) : (
<div className="aa-asset-grid">
{assets.map((asset) => {
const isActive = asset.id === pickedId
const thumb = asset.thumbnail_url || asset.file_url
const isVideo = asset.mime_type?.includes("video")
return (
<div
key={asset.id}
className={`aa-asset-card${isActive ? " selected" : ""}`}
onClick={() => setPickedId(asset.id)}
>
{isVideo && !asset.thumbnail_url ? (
<video src={asset.file_url} muted preload="metadata" />
) : (
<img src={thumb} alt={asset.name} />
)}
{isActive && <div className="aa-asset-card__check"></div>}
<div className="aa-asset-card__name">{asset.name}</div>
</div>
)
})}
</div>
)}
</div>
{/* 底部:取消 + 确认选择 */}
<div className="aa-modal__footer">
<button type="button" className="aa-btn aa-btn--ghost" onClick={onClose}>
</button>
<button
type="button"
className="aa-btn aa-btn--primary"
onClick={handleConfirm}
disabled={!pickedId}
>
</button>
</div>
</div>
</div>
)
}
@@ -0,0 +1,422 @@
/**
* AI数字人 — B-roll 画面插入编辑器弹窗(#1809 ④⑤⑥)
*
* 布局:
* - 左侧:先选素材库(video 库)→ 再选该库视频素材(已被其他 segment 使用的素材
* 标灰 + "已选择" 遮罩,pointer-events:none 防重复选择)
* - 右侧:文案句子列表(点选对应段落,替代原数字索引框)/ 全屏 or 画中画 / 四角位置+大小
* (开始/结束时间来自后端精确句子时间戳,基于 TTS 音频静音检测)
* - 底部:已配置的画面插入列表(可删除)
*/
import React, { useEffect, useMemo, useState } from "react"
import { getAssets, getAssetLibraries, type AssetItem, type AssetLibraryItem } from "@/api/assets"
import type { BRollSegment, BRollInsertMode, PipPosition, SentenceTiming } from "../types"
import { splitScriptIntoSentences, type ScriptSentence } from "../utils/sentences"
interface ModalBRollEditorProps {
open: boolean
onClose: () => void
/** 当前已有的 B-roll segments(用于标灰已选素材) */
existingSegments: BRollSegment[]
/** 文案全文(优先使用对口型时锁定的 scriptText */
scriptText: string
/** 对口型成片总时长(秒) */
outputDuration: number
/** 后端精确句子时间戳(来自 lipsyncJob.sentence_timings */
sentenceTimings?: SentenceTiming[] | null
onConfirm: (segment: BRollSegment) => void
onRemove: (id: string) => void
}
const PIP_POSITION_OPTIONS: { value: PipPosition; label: string }[] = [
{ value: "top-left", label: "左上" },
{ value: "top-right", label: "右上" },
{ value: "bottom-left", label: "左下" },
{ value: "bottom-right", label: "右下" },
]
const MODE_LABEL: Record<BRollInsertMode, string> = {
fullscreen: "全屏切换",
pip: "画中画",
}
const ModalBRollEditor: React.FC<ModalBRollEditorProps> = ({
open,
onClose,
existingSegments,
scriptText,
outputDuration: _outputDuration,
sentenceTimings,
onConfirm,
onRemove,
}) => {
/* ── 素材库(④ 先选库再选素材) ── */
const [libraries, setLibraries] = useState<AssetLibraryItem[]>([])
const [libraryId, setLibraryId] = useState<string>("")
const [availableAssets, setAvailableAssets] = useState<AssetItem[]>([])
const [loadingLibs, setLoadingLibs] = useState(false)
const [loadingAssets, setLoadingAssets] = useState(false)
const [assetError, setAssetError] = useState("")
/* ── 右侧设置本地状态 ── */
const [selectedAsset, setSelectedAsset] = useState<AssetItem | null>(null)
const [selectedSentence, setSelectedSentence] = useState<ScriptSentence | null>(null)
const [mode, setMode] = useState<BRollInsertMode>("fullscreen")
const [pipPosition, setPipPosition] = useState<PipPosition>("top-right")
const [pipScale, setPipScale] = useState(0.3)
/** 文案分句(优先使用后端精确时间戳,降级为字数比例估算) */
const sentences = useMemo(
() => splitScriptIntoSentences(scriptText, sentenceTimings, _outputDuration),
[scriptText, sentenceTimings, _outputDuration],
)
/** 已被现有 segments 占用的素材 id 集合(标灰、禁止重复选择) */
const usedAssetIds = useMemo(
() => new Set(existingSegments.map((seg) => seg.asset.id)),
[existingSegments],
)
/* 弹窗打开:重置选择 + 加载视频库列表 */
useEffect(() => {
if (!open) return
setSelectedAsset(null)
setSelectedSentence(null)
setMode("fullscreen")
setPipPosition("top-right")
setPipScale(0.3)
setLibraries([])
setLibraryId("")
setAvailableAssets([])
setAssetError("")
setLoadingLibs(true)
let cancelled = false
getAssetLibraries("video")
.then((libs) => {
if (cancelled) return
const list = Array.isArray(libs) ? libs : []
setLibraries(list)
if (list.length > 0) setLibraryId(list[0].id)
})
.catch(() => {
if (!cancelled) setAssetError("素材库加载失败,请重试")
})
.finally(() => {
if (!cancelled) setLoadingLibs(false)
})
return () => {
cancelled = true
}
}, [open])
/* 选中库后拉取该库视频素材 */
useEffect(() => {
if (!open || !libraryId) return
let cancelled = false
setLoadingAssets(true)
getAssets(libraryId, { page_size: 100 })
.then(({ items }) => {
if (cancelled) return
const list = (Array.isArray(items) ? items : []).filter((a) =>
a.mime_type?.includes("video"),
)
setAvailableAssets(list)
setAssetError("")
})
.catch(() => {
if (!cancelled) {
setAssetError("素材加载失败,请重试")
setAvailableAssets([])
}
})
.finally(() => {
if (!cancelled) setLoadingAssets(false)
})
return () => {
cancelled = true
}
}, [open, libraryId])
if (!open) return null
/** 选择素材(已选素材因 pointer-events:none 不会触发) */
const handleSelectAsset = (asset: AssetItem) => {
if (usedAssetIds.has(asset.id)) return
setSelectedAsset(asset)
}
/** 确认添加一段 B-roll(⑥ 时间取所选句子的精确起止,后端静音检测 / 前端字数比例降级) */
const handleConfirm = () => {
if (!selectedAsset || !selectedSentence) return
const startTime = selectedSentence.startTime
const endTime = Math.max(selectedSentence.endTime, startTime + 0.5)
const segment: BRollSegment = {
id: crypto.randomUUID(),
asset: selectedAsset,
script_segment_index: selectedSentence.index,
start_time: startTime,
end_time: endTime,
mode,
pip_position: pipPosition,
pip_scale: mode === "pip" ? pipScale : 0.3,
}
onConfirm(segment)
// 重置素材/句子选择,保留模式设置便于连续添加
setSelectedAsset(null)
setSelectedSentence(null)
}
const canConfirm = selectedAsset !== null && selectedSentence !== null
return (
<div className="aa-modal-overlay" onClick={onClose}>
<div className="aa-modal aa-modal--wide" onClick={(e) => e.stopPropagation()}>
{/* 头部 */}
<div className="aa-modal__header">
<span className="aa-modal__title">🎞 B-roll</span>
<button type="button" className="aa-modal__close" onClick={onClose}>
</button>
</div>
{/* 主体:左素材 + 右设置 */}
<div className="aa-modal__body">
<div className="aa-broll-modal-body">
{/* 左侧:选库 + 素材网格 */}
<div className="aa-broll-left">
<div className="aa-broll-lib-row">
<select
className="aa-select"
value={libraryId}
onChange={(e) => setLibraryId(e.target.value)}
disabled={loadingLibs || libraries.length === 0}
>
{libraries.length === 0 ? (
<option value="">{loadingLibs ? "素材库加载中…" : "暂无视频素材库"}</option>
) : (
libraries.map((lib) => (
<option key={lib.id} value={lib.id}>
📁 {lib.name}
</option>
))
)}
</select>
</div>
<div className="aa-broll-asset-grid">
{availableAssets.map((asset) => {
const alreadySelected = usedAssetIds.has(asset.id)
const isCurrent = selectedAsset?.id === asset.id
const classNames = [
"aa-broll-asset-thumb",
isCurrent ? "selected" : "",
alreadySelected ? "already-selected" : "",
]
.filter(Boolean)
.join(" ")
return (
<div
key={asset.id}
className={classNames}
onClick={() => handleSelectAsset(asset)}
title={asset.name}
>
{asset.thumbnail_url ? (
<img src={asset.thumbnail_url} alt={asset.name} />
) : (
<div className="aa-broll-asset-placeholder">🎬</div>
)}
<span className="aa-asset-card__name">{asset.name}</span>
</div>
)
})}
{loadingAssets ? (
<div className="aa-empty" style={{ gridColumn: "1 / -1" }}>
<div className="aa-empty__icon"></div>
</div>
) : availableAssets.length === 0 ? (
<div className="aa-empty" style={{ gridColumn: "1 / -1" }}>
<div className="aa-empty__icon">🎬</div>
{assetError || "该素材库暂无视频素材"}
</div>
) : null}
</div>
</div>
{/* 右侧:插入设置 */}
<div className="aa-broll-right">
<div className="aa-broll-settings">
{/* ⑤ 文案句子列表(替代段落索引数字框) */}
<div className="aa-form-field">
<label className="aa-label"></label>
{sentences.length === 0 ? (
<div className="aa-sentence-empty">
&
</div>
) : (
<div className="aa-sentence-list">
{sentences.map((sent) => {
const active = selectedSentence?.index === sent.index
return (
<button
key={sent.index}
type="button"
className={`aa-sentence-item${active ? " active" : ""}`}
onClick={() => setSelectedSentence(sent)}
title={sent.text}
>
<span className="aa-sentence-item__idx">{sent.index + 1}</span>
<span className="aa-sentence-item__text">{sent.text}</span>
<span className="aa-sentence-item__time">
{sent.startTime.toFixed(1)}-{sent.endTime.toFixed(1)}s
</span>
</button>
)
})}
</div>
)}
</div>
{/* 插入模式 */}
<div className="aa-form-field">
<label className="aa-label"></label>
<div className="aa-broll-mode-toggle">
<button
type="button"
className={`aa-broll-mode-btn${mode === "fullscreen" ? " active" : ""}`}
onClick={() => setMode("fullscreen")}
>
</button>
<button
type="button"
className={`aa-broll-mode-btn${mode === "pip" ? " active" : ""}`}
onClick={() => setMode("pip")}
>
</button>
</div>
</div>
{/* 画中画:四角位置 + 大小 */}
{mode === "pip" && (
<>
<div className="aa-form-field">
<label className="aa-label"></label>
<div className="aa-pip-positions">
{PIP_POSITION_OPTIONS.map((opt) => (
<button
key={opt.value}
type="button"
className={`aa-pip-pos-btn${
pipPosition === opt.value ? " active" : ""
}`}
onClick={() => setPipPosition(opt.value)}
>
{opt.label}
</button>
))}
</div>
</div>
<div className="aa-form-field">
<div className="aa-field-label-row">
<label className="aa-label"></label>
<span style={{ fontSize: 12, color: "#8c8ca1" }}>
{Math.round(pipScale * 100)}%
</span>
</div>
<input
type="range"
min={0.1}
max={0.6}
step={0.05}
value={pipScale}
onChange={(e) => setPipScale(Number(e.target.value))}
style={{ width: "100%" }}
/>
</div>
</>
)}
{/* 当前选择提示(⑥ 自动估算时间在这里展示) */}
<div className="aa-broll-hint">
{selectedAsset && selectedSentence ? (
<>
<div>{selectedAsset.name}</div>
<div>
{selectedSentence.index + 1} · {" "}
{selectedSentence.startTime.toFixed(1)}s -{" "}
{Math.max(
selectedSentence.endTime,
selectedSentence.startTime + 0.5,
).toFixed(1)}
s
</div>
</>
) : (
<div style={{ color: "#8c8ca1" }}>
{!selectedAsset ? "请从左侧选择一段素材" : "请在上方点选对应的文案句子"}
</div>
)}
</div>
</div>
</div>
</div>
{/* 底部:已配置的画面插入列表 */}
<div className="aa-broll-list">
<div className="aa-broll-list__title">{existingSegments.length}</div>
{existingSegments.length === 0 ? (
<div className="aa-empty" style={{ padding: 12 }}>
</div>
) : (
existingSegments.map((seg) => (
<div key={seg.id} className="aa-broll-item">
{seg.asset.thumbnail_url ? (
<img className="aa-broll-item__thumb" src={seg.asset.thumbnail_url} alt="" />
) : (
<div className="aa-broll-item__thumb" />
)}
<div className="aa-broll-item__info">
<div style={{ fontWeight: 500, color: "#1a1a2e" }}>{seg.asset.name}</div>
<div style={{ color: "#8c8ca1", fontSize: 11 }}>
{seg.script_segment_index + 1} · {MODE_LABEL[seg.mode]}
{seg.mode === "pip" ? ` · ${seg.pip_position}` : ""} ·{" "}
{seg.start_time.toFixed(1)}s - {seg.end_time.toFixed(1)}s
</div>
</div>
<button
type="button"
className="aa-broll-item__remove"
title="删除"
onClick={() => onRemove(seg.id)}
>
🗑
</button>
</div>
))
)}
</div>
</div>
{/* 底部按钮 */}
<div className="aa-modal__footer">
<button type="button" className="aa-btn" onClick={onClose}>
</button>
<button
type="button"
className="aa-btn aa-btn--primary"
disabled={!canConfirm}
onClick={handleConfirm}
>
</button>
</div>
</div>
</div>
)
}
export default ModalBRollEditor
@@ -0,0 +1,59 @@
/**
* AI数字人 — 封面选择弹窗
* 渲染完成后由主页面唤起,内部用 PanelCoverAndGenerateselect-cover 变体)提供
* 智能抽帧 + 自定义上传 + 预览 + 确定按钮。
*/
import React from "react"
import type { AiAvatarCoverConfig, RenderJob } from "../types"
import PanelCoverAndGenerate from "./PanelCoverAndGenerate"
interface ModalCoverSelectProps {
open: boolean
onClose: () => void
renderJob: RenderJob | null
coverConfig: AiAvatarCoverConfig
onCoverConfigChange: (partial: Partial<AiAvatarCoverConfig>) => void
onGenerateRenderSmartCover: (renderId: string) => Promise<{ cover_url: string; message?: string }>
onUploadCover?: (file: File) => void
onCoverSelected: (coverUrl: string) => void
}
const ModalCoverSelect: React.FC<ModalCoverSelectProps> = ({
open,
onClose,
renderJob,
coverConfig,
onCoverConfigChange,
onGenerateRenderSmartCover,
onUploadCover,
onCoverSelected,
}) => {
if (!open) return null
return (
<div className="aa-modal-overlay" onClick={onClose}>
<div className="aa-modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 480 }}>
<div className="aa-modal__header">
<span className="aa-modal__title"></span>
<button type="button" className="aa-modal__close" onClick={onClose} aria-label="关闭">
×
</button>
</div>
<div className="aa-modal__body" style={{ padding: 20 }}>
<PanelCoverAndGenerate
variant="select-cover"
coverConfig={coverConfig}
onCoverConfigChange={onCoverConfigChange}
renderJob={renderJob}
onGenerateRenderSmartCover={onGenerateRenderSmartCover}
onUploadCover={onUploadCover}
onClose={onClose}
onCoverSelected={onCoverSelected}
/>
</div>
</div>
</div>
)
}
export default ModalCoverSelect
@@ -0,0 +1,318 @@
/**
* AI数字人 — 面板5 / 封面选择弹窗内容:
* - variant="setup"(默认):分辨率 / 配置摘要 / 「开始生成视频」按钮,用于主页面步骤2配置阶段;
* 渲染完成后仍内嵌封面预览与按钮,方便不打开弹窗直接操作。
* - variant="select-cover":只渲染封面选择区(智能获取封面 + 自定义上传 + 预览),
* 用于 ModalCoverSelect 弹窗中;传 onClose 时底部显示「确定」按钮。
*
* 封面一律从最终成片(已叠加标题/B-roll)抽帧,本面板不再叠加标题。
*/
import React, { useRef, useState } from "react"
import type { AiAvatarCoverConfig, RenderJob } from "../types"
type PanelVariant = "setup" | "select-cover"
interface PanelCoverAndGenerateProps {
variant?: PanelVariant
coverConfig: AiAvatarCoverConfig
onCoverConfigChange: (partial: Partial<AiAvatarCoverConfig>) => void
resolution?: string
onResolutionChange?: (r: string) => void
isGenerating?: boolean
onGenerate?: () => void
/** 当前渲染任务(渲染完成后才有 output_video_url,才能抽封面) */
renderJob: RenderJob | null
/** 从最终成片智能抽帧(参数 renderId),返回 { cover_url } */
onGenerateRenderSmartCover: (renderId: string) => Promise<{ cover_url: string; message?: string }>
/** 自定义上传封面(选择本地文件后由父组件处理实际上传) */
onUploadCover?: (file: File) => void
/** 弹窗关闭回调(传入则表示在弹窗中使用,底部显示「确定」按钮) */
onClose?: () => void
/** 封面选好(智能抽帧/自定义上传成功)后通知父组件,参数为封面 URL */
onCoverSelected?: (coverUrl: string) => void
/** 配置汇总信息(仅 variant="setup" 使用) */
summary?: {
videoName: string | null
voiceName: string | null
scriptLength: number
lipsyncStatus: string | null
brollCount: number
hasTitle: boolean
/** 封面状态:'not_ready'(视频未生成) / 'pending'(视频生成了但未选) / 'selected'(已选) */
coverStatus: "not_ready" | "pending" | "selected"
}
}
const RESOLUTION_OPTIONS = [
{ value: "720p", label: "720p(高清)" },
{ value: "1080p", label: "1080p(全高清)" },
{ value: "4k", label: "4K(超清)" },
]
const LIPSYNC_STATUS_LABEL: Record<string, { text: string; cls: string }> = {
idle: { text: "未开始", cls: "aa-status-badge--idle" },
pending: { text: "排队中", cls: "aa-status-badge--pending" },
processing: { text: "生成中", cls: "aa-status-badge--processing" },
completed: { text: "已完成", cls: "aa-status-badge--completed" },
failed: { text: "失败", cls: "aa-status-badge--failed" },
}
const PanelCoverAndGenerate: React.FC<PanelCoverAndGenerateProps> = ({
variant = "setup",
coverConfig,
onCoverConfigChange,
resolution = "720p",
onResolutionChange,
isGenerating = false,
onGenerate,
renderJob,
onGenerateRenderSmartCover,
onUploadCover,
onClose,
onCoverSelected,
summary,
}) => {
const uploadInputRef = useRef<HTMLInputElement>(null)
// 内部维护智能封面加载态(修复点 2 次 bug:不依赖外层异步 setState 顺序)
const [smartCoverLoading, setSmartCoverLoading] = useState(false)
/** 自定义上传封面 */
const handleUploadClick = () => {
uploadInputRef.current?.click()
}
const _applyCoverUrl = (url: string, mode: "upload" | "auto_frame") => {
const partial: Partial<AiAvatarCoverConfig> = {
mode,
thumbnail_url: url,
}
if (mode === "auto_frame") {
partial.smart_cover_url = url
} else {
partial.upload_url = url
}
onCoverConfigChange(partial)
onCoverSelected?.(url)
}
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (!file) return
if (onUploadCover) {
onUploadCover(file)
e.target.value = ""
return
}
// 本地预览兜底(实际上传由父级处理;blob URL 仅作本地展示)
const url = URL.createObjectURL(file)
_applyCoverUrl(url, "upload")
e.target.value = ""
}
/** 智能获取封面(从最终成片抽帧;必须等 render 完成) */
const handleSmartCover = async () => {
if (!renderJob || renderJob.status !== "completed" || !renderJob.id) return
setSmartCoverLoading(true)
try {
const res = await onGenerateRenderSmartCover(renderJob.id)
if (res.cover_url) {
_applyCoverUrl(res.cover_url, "auto_frame")
} else {
// 失败由父组件 message 提示,这里不重复弹窗
console.warn("[智能封面] 返回空 cover_url:", res.message)
}
} catch (err) {
console.error("[智能封面] 调用失败:", err)
} finally {
setSmartCoverLoading(false)
}
}
const lipsync = summary?.lipsyncStatus ? LIPSYNC_STATUS_LABEL[summary.lipsyncStatus] : null
const canGenerate = summary?.lipsyncStatus === "completed" && !isGenerating
// 渲染已完成 → 封面区可用
const isRenderCompleted = renderJob?.status === "completed"
const canSmartCover = isRenderCompleted && !smartCoverLoading
/** 封面图实际展示的 url:智能封面 > 自定义上传 > 空 */
const coverUrl =
coverConfig.smart_cover_url || coverConfig.thumbnail_url || coverConfig.upload_url
const hasCoverImage = Boolean(coverUrl)
/** 封面区占位文字 */
const coverPlaceholder = isRenderCompleted ? "暂无封面" : "视频生成后可选择封面"
/** 配置摘要中的封面状态标签 */
const coverSummaryNode = (() => {
if (!summary) return null
if (summary.coverStatus === "selected") {
return <span className="aa-config-summary__value"></span>
}
if (summary.coverStatus === "pending") {
return <span className="aa-config-summary__value"></span>
}
return <span className="aa-config-summary__empty"></span>
})()
// ── 封面选择区(两种 variant 共用) ─────────────────────────────────
const coverSection = (
<div className="aa-cover-section" style={{ marginTop: variant === "select-cover" ? 0 : 16 }}>
<div className="aa-label" style={{ marginBottom: 8 }}>
{variant === "select-cover" ? "选择封面" : "封面"}
</div>
{/* 封面预览(竖屏 9:16)——成片帧已经通过 Canvas PNG overlay 带有标题,直接展示原图即可 */}
<div className="aa-cover-preview" style={{ opacity: isRenderCompleted ? 1 : 0.5 }}>
{hasCoverImage ? (
<img src={coverUrl!} alt="封面预览" draggable={false} />
) : (
<span className="aa-cover-preview__placeholder">{coverPlaceholder}</span>
)}
{smartCoverLoading && <div className="aa-cover-preview__loading"> </div>}
</div>
<div className="aa-cover-actions">
<button
type="button"
className={`aa-btn aa-btn--ghost${coverConfig.mode === "auto_frame" ? " active" : ""}`}
onClick={handleSmartCover}
disabled={!canSmartCover}
title={isRenderCompleted ? "从成片智能选帧" : "请先生成视频"}
>
{smartCoverLoading ? "⏳ 智能选帧中…" : "🎬 智能获取封面"}
</button>
<button
type="button"
className={`aa-btn aa-btn--ghost${coverConfig.mode === "upload" ? " active" : ""}`}
onClick={handleUploadClick}
disabled={!isRenderCompleted || smartCoverLoading}
title={isRenderCompleted ? "自定义上传封面" : "请先生成视频"}
>
📷
</button>
<input
ref={uploadInputRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={handleFileChange}
/>
</div>
</div>
)
// ── select-cover 变体:只渲染封面区 + 弹窗确定按钮 ──
if (variant === "select-cover") {
return (
<div className="aa-cover-generate">
{coverSection}
{onClose && (
<div style={{ marginTop: 16, display: "flex", justifyContent: "flex-end" }}>
<button type="button" className="aa-btn aa-btn--primary" onClick={onClose}>
</button>
</div>
)}
</div>
)
}
// ── setup 变体:分辨率 / 配置摘要 / 生成按钮(渲染完成后内嵌封面区) ──
return (
<div className="aa-cover-generate">
{/* 分辨率选择 */}
<div className="aa-form-field">
<label className="aa-label"></label>
<select
className="aa-select"
value={resolution}
onChange={(e) => onResolutionChange?.(e.target.value)}
disabled={isGenerating}
>
{RESOLUTION_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
{/* 配置汇总 */}
<div className="aa-generate-section">
<div className="aa-config-summary">
<div className="aa-config-summary__row">
<span></span>
{summary?.videoName ? (
<span className="aa-config-summary__value">{summary.videoName}</span>
) : (
<span className="aa-config-summary__empty"></span>
)}
</div>
<div className="aa-config-summary__row">
<span></span>
{summary?.voiceName ? (
<span className="aa-config-summary__value">{summary.voiceName}</span>
) : (
<span className="aa-config-summary__empty"></span>
)}
</div>
<div className="aa-config-summary__row">
<span></span>
{summary && summary.scriptLength > 0 ? (
<span className="aa-config-summary__value">{summary.scriptLength} </span>
) : (
<span className="aa-config-summary__empty"></span>
)}
</div>
<div className="aa-config-summary__row">
<span></span>
{lipsync ? (
<span className={`aa-status-badge ${lipsync.cls}`}>{lipsync.text}</span>
) : (
<span className="aa-config-summary__empty"></span>
)}
</div>
<div className="aa-config-summary__row">
<span>B-roll </span>
<span className="aa-config-summary__value">
{summary && summary.brollCount > 0 ? `${summary.brollCount}` : "无"}
</span>
</div>
<div className="aa-config-summary__row">
<span></span>
{summary?.hasTitle ? (
<span className="aa-config-summary__value"></span>
) : (
<span className="aa-config-summary__empty"></span>
)}
</div>
<div className="aa-config-summary__row">
<span></span>
{coverSummaryNode}
</div>
</div>
{/* 生成按钮 */}
<button
type="button"
className="aa-btn aa-btn--generate aa-btn--full"
disabled={!canGenerate}
onClick={onGenerate}
>
{isGenerating ? "⏳ 生成中..." : "🚀 开始生成视频"}
</button>
{summary?.lipsyncStatus !== "completed" && !isGenerating && (
<div style={{ marginTop: 8, fontSize: 11, color: "#8c8ca1", textAlign: "center" }}>
</div>
)}
{isGenerating && (
<div style={{ marginTop: 8, fontSize: 11, color: "#8c8ca1", textAlign: "center" }}>
</div>
)}
</div>
</div>
)
}
export default PanelCoverAndGenerate
@@ -0,0 +1,338 @@
/**
* AI数字人 — 对口型预览面板(步骤2用)
* B-roll 画面插入 + 对口型视频预览 + 生成/重新生成按钮
* v3.1: 标题字号按预览容器实际宽度动态计算 previewScale(基准 720p),与成片一致
*/
import React, { useCallback, useEffect, useRef, useState } from "react"
import type { LipsyncJob, BRollSegment, AiAvatarTitleConfig } from "../types"
interface PanelLipsyncPreviewProps {
lipsyncJob: LipsyncJob | null
onGenerateLipsync: () => void
bRollSegments: BRollSegment[]
onOpenBRollModal: () => void
onRemoveBRoll: (id: string) => void
/** 标题配置(实时叠加预览用) */
titleConfig?: AiAvatarTitleConfig
/** 标题位置变更回调(拖拽结束时调用,发送百分比坐标 + position:"custom" */
onTitlePositionChange?: (pos: { pos_x: number; pos_y: number; position: string }) => void
}
const BROLL_MODE_LABEL: Record<BRollSegment["mode"], string> = {
fullscreen: "全屏",
pip: "画中画",
}
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60)
const s = Math.round(seconds % 60)
return `${m}:${s.toString().padStart(2, "0")}`
}
/** 字体名 → CSS font-family 映射(与 titleCanvas 字体链对齐) */
const FONT_FAMILY_MAP: Record<string, string> = {
:
"'Noto Sans CJK SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif",
: "'Noto Serif SC', 'Source Han Serif SC', 'SimSun', serif",
: "KaiTi, 'STKaiti', serif",
: "'Heiti SC', 'SimHei', 'Microsoft YaHei', sans-serif",
}
const getFontFamily = (font: string): string => FONT_FAMILY_MAP[font] || FONT_FAMILY_MAP["思源黑体"]
export function PanelLipsyncPreview({
lipsyncJob,
onGenerateLipsync,
bRollSegments,
onOpenBRollModal,
onRemoveBRoll,
titleConfig,
onTitlePositionChange,
}: PanelLipsyncPreviewProps) {
const titleDragRef = useRef<HTMLDivElement>(null)
const draggingTitleRef = useRef(false)
const previewContainerRef = useRef<HTMLDivElement>(null)
// 预览容器实际宽度(通过 ResizeObserver 监听),用于动态计算 previewScale
const [containerWidth, setContainerWidth] = useState(0)
const isGenerating = lipsyncJob?.status === "pending" || lipsyncJob?.status === "processing"
const isDone = lipsyncJob?.status === "completed"
const isFailed = lipsyncJob?.status === "failed"
const statusText =
lipsyncJob?.status === "processing"
? "对口型生成中…"
: lipsyncJob?.status === "pending"
? "排队中…"
: "对口型生成中…"
// 监听预览容器尺寸变化,动态测量宽度以计算 previewScale(基准 720p
useEffect(() => {
const el = previewContainerRef.current
if (!el) return
const update = () => setContainerWidth(el.clientWidth || 0)
update()
if (typeof ResizeObserver !== "undefined") {
const ro = new ResizeObserver(update)
ro.observe(el)
return () => ro.disconnect()
}
window.addEventListener("resize", update)
return () => window.removeEventListener("resize", update)
}, [])
// 预览缩放比:预览宽度 / 720(基准宽度)
const previewScale = containerWidth > 0 ? containerWidth / 720 : 0.35
const ps = useCallback((v: number) => Math.round(v * previewScale * 100) / 100, [previewScale])
/** 标题叠加样式(字号/padding/描边/阴影均按 previewScale 缩放,保持与成片视觉一致) */
const titleOverlayStyle: React.CSSProperties | null =
titleConfig?.title && containerWidth > 0
? (() => {
const baseSize = titleConfig.size || 48
const fontSize = ps(baseSize)
// 描边宽度基准 ≈ size * 0.06,最小 1.5px @720p
const strokeW = Math.max(ps(1.5), +(baseSize * 0.06 * previewScale).toFixed(2))
// 阴影按比例缩放
const shadowBlur = ps(4)
const shadowOffsetY = ps(2)
// padding / top 边距按比例(基准 8px 对应预览小窗,成片基准 16px,这里 8px 对应约 0.33 缩放)
const padV = ps(16) * 0.5 // ≈ 8px in ~240px container
const padH = ps(24) * 0.5
const style: React.CSSProperties = {
position: "absolute",
color: titleConfig.color || "#ffffff",
fontFamily: getFontFamily(titleConfig.font || "思源黑体"),
fontSize: `${fontSize}px`,
fontWeight: titleConfig.bold ? 700 : 400,
fontStyle: titleConfig.italic ? "italic" : "normal",
textAlign: "center",
width: "90%",
lineHeight: 1.2,
padding: `${ps(4)}px ${padH}px`,
textShadow: titleConfig.shadow
? `0 ${shadowOffsetY}px ${shadowBlur}px rgba(0,0,0,0.8), 0 0 ${ps(2)}px rgba(0,0,0,0.5)`
: undefined,
WebkitTextStroke: titleConfig.stroke ? `${strokeW}px #000` : undefined,
boxSizing: "border-box",
wordBreak: "break-word",
whiteSpace: "pre-wrap",
}
if (
titleConfig.position === "custom" &&
titleConfig.pos_x != null &&
titleConfig.pos_y != null
) {
style.left = `${titleConfig.pos_x}%`
style.top = `${titleConfig.pos_y}%`
style.transform = "translateX(-50%) translateY(-50%)"
} else if (titleConfig.position === "top") {
style.left = "50%"
style.top = padV
style.transform = "translateX(-50%)"
} else if (titleConfig.position === "bottom") {
style.left = "50%"
style.bottom = padV
style.transform = "translateX(-50%)"
} else {
style.left = "50%"
style.top = "50%"
style.transform = "translateX(-50%) translateY(-50%)"
}
return style
})()
: null
const handleTitlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (!onTitlePositionChange || !previewContainerRef.current) return
e.preventDefault()
e.stopPropagation()
;(e.target as Element).setPointerCapture(e.pointerId)
draggingTitleRef.current = true
;(e.currentTarget as HTMLDivElement).style.cursor = "grabbing"
}
const handleTitlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (!draggingTitleRef.current || !previewContainerRef.current) return
e.preventDefault()
e.stopPropagation()
if (titleDragRef.current) {
const rect = previewContainerRef.current.getBoundingClientRect()
const relX = Math.max(0, Math.min(rect.width, e.clientX - rect.left))
const relY = Math.max(0, Math.min(rect.height, e.clientY - rect.top))
const xpct = (relX / rect.width) * 100
const ypct = (relY / rect.height) * 100
titleDragRef.current.style.left = `${xpct}%`
titleDragRef.current.style.top = `${ypct}%`
}
}
const handleTitlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
if (!draggingTitleRef.current) return
draggingTitleRef.current = false
if (onTitlePositionChange && previewContainerRef.current) {
const rect = previewContainerRef.current.getBoundingClientRect()
const relX = Math.max(0, Math.min(rect.width, e.clientX - rect.left))
const relY = Math.max(0, Math.min(rect.height, e.clientY - rect.top))
// 发送百分比坐标(0-100),与后端 drawtext 百分比表达式对齐
const xpct = Math.round((relX / rect.width) * 1000) / 10
const ypct = Math.round((relY / rect.height) * 1000) / 10
onTitlePositionChange({ pos_x: xpct, pos_y: ypct, position: "custom" })
}
;(e.currentTarget as HTMLDivElement).style.cursor = "grab"
}
return (
<div className="aa-script-lipsync">
{/* ── B-roll 画面 ── */}
<div className="aa-lipsync-section">
<div className="aa-lipsync-section__title">
<span style={{ marginRight: 8 }}>🎞 </span>
{bRollSegments.length > 0 && (
<span className="aa-broll-badge">🎬 {bRollSegments.length} </span>
)}
</div>
<div className="aa-lipsync-actions">
<button
type="button"
className="aa-btn aa-btn--primary aa-btn--full"
onClick={onOpenBRollModal}
>
🎬
</button>
</div>
{bRollSegments.length > 0 && (
<div className="aa-broll-list">
{bRollSegments.map((seg) => (
<div key={seg.id} className="aa-broll-item">
{seg.asset.thumbnail_url || seg.asset.file_url ? (
<img
className="aa-broll-item__thumb"
src={seg.asset.thumbnail_url || seg.asset.file_url}
alt={seg.asset.name}
/>
) : (
<span className="aa-broll-item__thumb" style={{ padding: "6px 4px" }}>
🎬
</span>
)}
<div className="aa-broll-item__info">
<div
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{seg.asset.name}
</div>
<div style={{ fontSize: 11, color: "#8c8ca1", marginTop: 2 }}>
{BROLL_MODE_LABEL[seg.mode]} · {formatTime(seg.start_time)}-
{formatTime(seg.end_time)}
</div>
</div>
<button
type="button"
className="aa-broll-item__remove"
title="删除"
onClick={() => onRemoveBRoll(seg.id)}
>
</button>
</div>
))}
</div>
)}
</div>
{/* ── 对口型预览(标题字号按 previewScale 动态缩放) ─ */}
<div className="aa-lipsync-section">
<div className="aa-lipsync-section__title"></div>
<div className="aa-lipsync-preview" ref={previewContainerRef}>
{isDone && lipsyncJob?.output_video_url ? (
<div style={{ position: "relative", width: "100%", height: "100%" }}>
<video src={lipsyncJob.output_video_url} controls />
{titleOverlayStyle && (
<div
ref={titleDragRef}
style={{
...titleOverlayStyle,
cursor: onTitlePositionChange ? "grab" : "default",
pointerEvents: onTitlePositionChange ? "auto" : "none",
}}
onPointerDown={handleTitlePointerDown}
onPointerMove={handleTitlePointerMove}
onPointerUp={handleTitlePointerUp}
onPointerCancel={handleTitlePointerUp}
>
{titleConfig!.title.split(/[/]/).map((part, i) => (
<span key={i}>
{i > 0 && <br />}
{part}
</span>
))}
</div>
)}
</div>
) : isGenerating ? (
<div style={{ width: "80%", textAlign: "center", color: "#fff" }}>
<div style={{ fontSize: 13, marginBottom: 8 }}>
{statusText} {Math.round(lipsyncJob?.progress ?? 0)}%
</div>
<div className="aa-progress">
<div
className="aa-progress__bar"
style={{ width: `${lipsyncJob?.progress ?? 0}%` }}
/>
</div>
</div>
) : (
<div className="aa-video-preview__placeholder">
{isFailed ? (
<>
<div style={{ fontSize: 28, marginBottom: 8 }}></div>
<div></div>
{lipsyncJob?.error_message && (
<div
style={{
fontSize: 11,
marginTop: 4,
color: "#fca5a5",
}}
>
{lipsyncJob.error_message}
</div>
)}
</>
) : (
"生成对口型视频后在此预览"
)}
</div>
)}
</div>
<div className="aa-lipsync-actions">
{isDone ? (
<button type="button" className="aa-btn aa-btn--full" onClick={onGenerateLipsync}>
🔄
</button>
) : isGenerating ? (
<button type="button" className="aa-btn aa-btn--full" disabled>
</button>
) : (
<button
type="button"
className="aa-btn aa-btn--primary aa-btn--full"
onClick={onGenerateLipsync}
>
🎬
</button>
)}
</div>
</div>
</div>
)
}
export default PanelLipsyncPreview
+67
View File
@@ -0,0 +1,67 @@
/**
* AI数字人 — 文案面板(步骤1用)
* 文案库选择 / 手动输入 + 字数统计
*/
import { useState } from "react"
interface PanelScriptProps {
scriptText: string
onScriptTextChange: (text: string) => void
onOpenScriptModal: () => void
}
type ScriptTab = "library" | "manual"
export function PanelScript({
scriptText,
onScriptTextChange,
onOpenScriptModal,
}: PanelScriptProps) {
const [scriptTab, setScriptTab] = useState<ScriptTab>("library")
return (
<div className="aa-script-lipsync">
{/* ── Tab 切换 ── */}
<div className="aa-script-tabs">
<button
type="button"
className={`aa-script-tab${scriptTab === "library" ? " active" : ""}`}
onClick={() => setScriptTab("library")}
>
</button>
<button
type="button"
className={`aa-script-tab${scriptTab === "manual" ? " active" : ""}`}
onClick={() => setScriptTab("manual")}
>
</button>
</div>
{scriptTab === "library" && (
<button
type="button"
className="aa-btn aa-btn--ghost aa-btn--full"
style={{ marginBottom: 8 }}
onClick={onOpenScriptModal}
>
📚
</button>
)}
<textarea
className="aa-textarea"
value={scriptText}
readOnly={scriptTab === "library"}
placeholder={
scriptTab === "library" ? "点击上方按钮,从文案库选择文案…" : "请输入数字人口播文案…"
}
onChange={(e) => onScriptTextChange(e.target.value)}
/>
<div className="aa-char-count">{scriptText.length} </div>
</div>
)
}
export default PanelScript
@@ -0,0 +1,134 @@
/**
* AI数字人 — 面板4:标题配置
*
* 关键:直接复用智能剪辑(generate)模块的 TitleStylePanel 标题样式面板,
* 不重新开发标题预设/字体/位置等样式能力。本组件只负责:
* - 主标题文字输入
* - AiAvatarTitleConfig ↔ TitleSettings 的双向适配
* - 自动生成字幕开关
*/
import React, { useMemo, useState, useEffect } from "react"
import { Input } from "antd"
import TitleStylePanel from "@/pages/generate/components/title/TitleStylePanel"
import TitleLibraryAutoComplete from "@/pages/generate/components/title/TitleLibraryAutoComplete"
import type { TitleOption } from "@/pages/generate/components/title/TitleLibraryAutoComplete"
import type { TitleSettings } from "@/pages/generate/types"
import { POSITION_OPTIONS, FONT_OPTIONS, TITLE_PRESETS } from "@/pages/generate/constants"
import type { AiAvatarTitleConfig } from "../types"
import { getTitles } from "@/api/titles"
const { TextArea } = Input
interface PanelTitleConfigProps {
titleConfig: AiAvatarTitleConfig
onUpdate: (partial: Partial<AiAvatarTitleConfig>) => void
}
const PanelTitleConfig: React.FC<PanelTitleConfigProps> = ({ titleConfig, onUpdate }) => {
/** TitleStylePanel 内部高亮的预设 key(面板本地状态) */
const [activePreset, setActivePreset] = useState<string | null>(null)
/** 标题库选项(复用智能剪辑的标题库) */
const [titleOptions, setTitleOptions] = useState<TitleOption[]>([])
useEffect(() => {
getTitles()
.then((items) => setTitleOptions(items.map((t) => ({ label: t.content, value: t.content }))))
.catch(() => setTitleOptions([]))
}, [])
/** AiAvatarTitleConfig → TitleSettings(补齐 aiAutoSelect / 自由坐标字段) */
const titleSettings: TitleSettings = useMemo(
() => ({
aiAutoSelect: false,
title: titleConfig.title,
position: titleConfig.position,
font: titleConfig.font,
size: titleConfig.size,
bold: titleConfig.bold,
italic: titleConfig.italic,
stroke: titleConfig.stroke,
shadow: titleConfig.shadow,
color: titleConfig.color,
posX: null,
posY: null,
}),
[titleConfig],
)
/** 应用预设:与智能剪辑一致,只覆盖 color/bold/italic/stroke/shadow,不改变字号 */
const handleApplyPreset = (presetKey: string) => {
const preset = TITLE_PRESETS.find((p) => p.key === presetKey)
if (!preset) return
setActivePreset(presetKey)
onUpdate({
color: preset.style.color,
bold: preset.style.bold,
italic: preset.style.italic,
stroke: preset.style.stroke,
shadow: preset.style.shadow,
})
}
return (
<div className="aa-title-config">
{/* 主标题输入 — TextArea 多行 + 标题库选择 */}
<div className="aa-form-field">
<label className="aa-label"></label>
<TextArea
className="aa-title-input"
placeholder="输入视频标题(支持 / 分行)"
value={titleConfig.title}
autoSize={{ minRows: 2, maxRows: 4 }}
maxLength={200}
onChange={(e) => onUpdate({ title: e.target.value })}
style={{ fontSize: 15 }}
/>
<div style={{ marginTop: 8, display: "flex", alignItems: "center", gap: 8 }}>
<span style={{ fontSize: 12, color: "#8c8ca1", whiteSpace: "nowrap" }}>📚 </span>
<TitleLibraryAutoComplete
key={titleConfig.title}
placeholder="选择标题填入上方"
value=""
onChange={(val) => {
if (val) onUpdate({ title: val })
}}
options={titleOptions}
maxLength={200}
style={{ flex: 1 }}
/>
</div>
</div>
{/* 标题样式:直接复用智能剪辑 TitleStylePanel(位置/字体/字号/样式/预设) */}
<TitleStylePanel
settings={titleSettings}
onUpdatePosition={(position) => onUpdate({ position })}
onUpdateFont={(font) => onUpdate({ font })}
onUpdateSize={(size) => onUpdate({ size: Math.min(128, Math.max(16, size)) })}
onToggleBold={() => onUpdate({ bold: !titleConfig.bold })}
onToggleItalic={() => onUpdate({ italic: !titleConfig.italic })}
onToggleStroke={() => onUpdate({ stroke: !titleConfig.stroke })}
onToggleShadow={() => onUpdate({ shadow: !titleConfig.shadow })}
onApplyPreset={handleApplyPreset}
activePreset={activePreset}
titlePresets={TITLE_PRESETS}
POSITION_OPTIONS={POSITION_OPTIONS}
FONT_OPTIONS={FONT_OPTIONS}
/>
{/* 自动生成字幕 */}
<div className="aa-subtitle-toggle">
<label className="aa-checkbox-row">
<input
type="checkbox"
checked={titleConfig.auto_subtitle}
onChange={(e) => onUpdate({ auto_subtitle: e.target.checked })}
/>
</label>
</div>
</div>
)
}
export default PanelTitleConfig
@@ -0,0 +1,93 @@
/**
* AI数字人 — 出镜视频选择面板
* - 未选视频:虚线上传区,点击打开素材库弹窗
* - 已选视频:竖屏 9:16 预览播放器 + 视频信息卡片 + 移除按钮
*
* 注意:本面板只展示原始素材视频,不叠加标题(标题在对口型预览和最终成片上展示)
*/
import type { AssetItem } from "@/api/assets"
export interface PanelVideoSelectorProps {
selectedVideo: AssetItem | null
/** 触发打开素材库弹窗 */
onSelectVideo: () => void
onRemoveVideo: () => void
}
/** 格式化时长(秒 → mm:ss */
function formatDuration(seconds?: number): string {
if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0) {
return "00:00"
}
return `${Math.floor(seconds / 60)}:${String(Math.floor(seconds % 60)).padStart(2, "0")}`
}
export function PanelVideoSelector({
selectedVideo,
onSelectVideo,
onRemoveVideo,
}: PanelVideoSelectorProps) {
/* 未选视频:虚线上传区,点击打开素材库弹窗 */
if (!selectedVideo) {
return (
<div
className="aa-upload-zone"
role="button"
tabIndex={0}
onClick={onSelectVideo}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
onSelectVideo()
}
}}
>
<div className="aa-upload-zone__icon">🎬</div>
<div className="aa-upload-zone__text"></div>
</div>
)
}
const width = selectedVideo.metadata?.width
const height = selectedVideo.metadata?.height
const duration = selectedVideo.duration ?? selectedVideo.metadata?.duration
const fileUrl = selectedVideo.file_url ?? ""
return (
<div>
{/* 竖屏 9:16 视频预览播放器(纯素材预览,不叠加标题) */}
<div className="aa-video-preview">
{fileUrl ? (
<video src={fileUrl} poster={selectedVideo.thumbnail_url} controls playsInline />
) : (
<div className="aa-video-preview__placeholder"></div>
)}
</div>
{/* 视频信息卡片:文件名 / 时长 / 分辨率 */}
<div className="aa-video-info">
<div className="aa-video-info__row">
<span></span>
<span title={selectedVideo.name}>{selectedVideo.name}</span>
</div>
<div className="aa-video-info__row">
<span></span>
<span>{formatDuration(duration)}</span>
</div>
<div className="aa-video-info__row">
<span></span>
<span>{width && height ? `${width}×${height}` : "—"}</span>
</div>
</div>
<button
type="button"
className="aa-btn aa-btn--danger aa-btn--full"
style={{ marginTop: 10 }}
onClick={onRemoveVideo}
>
</button>
</div>
)
}
@@ -0,0 +1,323 @@
/**
* AI数字人 — 配音库面板(面板3)
* 音色来源切换(系统预设 / 我的音色)、音色选择与试听、情绪/语速/语言参数
*/
import { useEffect, useRef, useState } from "react"
import { message } from "antd"
import { fetchVoices } from "@/api/voices/voices"
import { previewTts } from "@/api/tts"
import { normalizeEmotion } from "../utils/contract"
import type { UnifiedVoiceItem } from "@/api/voices/types"
import {
type VoiceSource,
type VoiceEmotion,
type VoiceLanguage,
VOICE_EMOTION_OPTIONS,
VOICE_LANGUAGE_OPTIONS,
} from "../types"
interface PanelVoiceSelectorProps {
voiceSource: VoiceSource
onVoiceSourceChange: (source: VoiceSource) => void
selectedVoice: UnifiedVoiceItem | null
onSelectVoice: (voice: UnifiedVoiceItem) => void
emotion: VoiceEmotion
onEmotionChange: (e: VoiceEmotion) => void
speed: number
onSpeedChange: (s: number) => void
language: VoiceLanguage
onLanguageChange: (l: VoiceLanguage) => void
}
export function PanelVoiceSelector({
voiceSource,
onVoiceSourceChange,
selectedVoice,
onSelectVoice,
emotion,
onEmotionChange,
speed,
onSpeedChange,
language,
onLanguageChange,
}: PanelVoiceSelectorProps) {
const [voices, setVoices] = useState<UnifiedVoiceItem[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [previewingId, setPreviewingId] = useState<string | null>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
/** 克隆音色试听合成缓存:voiceId -> url,对齐配音库 useAudioPlayer */
const previewCacheRef = useRef<Map<string, string>>(new Map())
const VOICE_PREVIEW_TEXT = "你好呀,欢迎使用小虾智剪,这是我的配音效果,希望你喜欢。"
/* 切换来源时重新获取音色列表 */
useEffect(() => {
let cancelled = false
const loadVoices = async () => {
setLoading(true)
setError(null)
try {
const res = await fetchVoices({ type: voiceSource })
if (!cancelled) setVoices(Array.isArray(res?.items) ? res.items : [])
} catch (err) {
if (!cancelled) setError(err instanceof Error ? err.message : "音色加载失败")
} finally {
if (!cancelled) setLoading(false)
}
}
loadVoices()
return () => {
cancelled = true
}
}, [voiceSource])
/* 卸载时停止试听 */
useEffect(() => {
return () => {
if (audioRef.current) {
audioRef.current.pause()
audioRef.current = null
}
}
}, [])
const stopPreview = () => {
if (audioRef.current) {
audioRef.current.pause()
audioRef.current = null
}
setPreviewingId(null)
}
const NO_PREVIEW_TIP = "该音色暂无试听音频,请先用此音色生成一段配音后再试听"
/** 用指定 URL 真实播放(抽取公共) */
const playAudioUrl = (voiceId: string, url: string) => {
// 临时兼容:后端 /tts/preview 返回 HTTP URLstaging 是 HTTPSMixed Content 会阻止加载
// OSS 同时支持 HTTP/HTTPS,直接替换协议即可
const safeUrl = url.startsWith("http://") ? url.replace("http://", "https://") : url
if (audioRef.current) {
audioRef.current.pause()
audioRef.current = null
}
const audio = new Audio(safeUrl)
audioRef.current = audio
setPreviewingId(voiceId)
audio.onended = () => {
if (audioRef.current === audio) {
audioRef.current = null
setPreviewingId(null)
}
}
audio.onerror = () => {
if (audioRef.current === audio) {
audioRef.current = null
setPreviewingId(null)
message.error("试听音频加载失败")
}
}
void audio.play().catch(() => {
setPreviewingId(null)
message.error("试听播放失败")
})
}
const handlePreview = async (voice: UnifiedVoiceItem) => {
/* 再次点击当前试听音色 → 停止 */
if (previewingId === voice.id) {
stopPreview()
return
}
/* 克隆音色:preview_url/audio_url 通常为空,需走 POST /tts/preview
* 现合成示例文案再播放,对齐配音库 useAudioPlayer 行为 */
if (voice.type === "clone") {
const cached = previewCacheRef.current.get(voice.voice_clone_profile_id || voice.id)
if (cached) {
playAudioUrl(voice.id, cached)
return
}
const targetId = voice.voice_clone_profile_id || voice.id
// DEBUG: 打印请求参数,帮助定位 /tts/preview 失败原因
setPreviewingId(voice.id)
try {
const res = await previewTts({
text: VOICE_PREVIEW_TEXT,
voice_id: targetId,
speed: speed, // 透传用户选择的语速(#1822)
emotion: normalizeEmotion(emotion), // 情绪中文→英文枚举
})
if (!res.audio_url) {
setPreviewingId(null)
message.error("合成试听失败:未返回音频")
return
}
previewCacheRef.current.set(targetId, res.audio_url)
playAudioUrl(voice.id, res.audio_url)
} catch (err) {
setPreviewingId(null)
// DEBUG: 打印详细错误信息
console.error("[AI数字人-克隆试听] previewTts 失败:", {
status: (err as { response?: { status?: number } })?.response?.status,
data: (err as { response?: { data?: unknown } })?.response?.data,
message: err instanceof Error ? err.message : String(err),
})
// apiClient 拦截器已统一 toast
}
return
}
/* 系统预设音色:沿用 preview_url/audio_url 直链播放 */
const url = voice.preview_url || voice.audio_url
if (!url) {
message.warning(NO_PREVIEW_TIP)
return
}
playAudioUrl(voice.id, url)
}
const handleSpeedChange = (value: string) => {
const parsed = parseFloat(value)
if (Number.isNaN(parsed)) return
const clamped = Math.min(2.0, Math.max(0.5, parsed))
onSpeedChange(clamped)
}
return (
<div className="aa-voice-selector">
{/* 音色来源切换 */}
<div className="aa-voice-source-toggle">
<button
type="button"
className={`aa-voice-source-btn${voiceSource === "preset" ? " active" : ""}`}
onClick={() => onVoiceSourceChange("preset")}
>
</button>
<button
type="button"
className={`aa-voice-source-btn${voiceSource === "clone" ? " active" : ""}`}
onClick={() => onVoiceSourceChange("clone")}
>
</button>
</div>
{/* 音色列表 */}
{loading ? (
<div className="aa-empty">
<div className="aa-empty__icon"></div>
<div></div>
</div>
) : error ? (
<div className="aa-empty">
<div className="aa-empty__icon"></div>
<div>{error}</div>
</div>
) : voices.length === 0 ? (
<div className="aa-empty">
<div className="aa-empty__icon">🎙</div>
<div>{voiceSource === "clone" ? "还没有克隆音色" : "暂无预置音色"}</div>
</div>
) : (
<div className="aa-voice-list">
{voices.map((voice) => {
const selected = selectedVoice?.id === voice.id
const previewUrl = voice.preview_url || voice.audio_url
return (
<div
key={voice.id}
className={`aa-voice-card${selected ? " selected" : ""}`}
onClick={() => onSelectVoice(voice)}
>
<span className="aa-voice-card__radio" />
<div className="aa-voice-card__info">
<div className="aa-voice-card__name">{voice.name}</div>
{voice.description && (
<div className="aa-voice-card__desc">{voice.description}</div>
)}
</div>
<button
type="button"
className="aa-voice-card__preview"
title={previewingId === voice.id ? "停止试听" : "试听"}
disabled={voice.type === "preset" && !previewUrl}
onClick={(e) => {
e.stopPropagation()
handlePreview(voice)
}}
>
{previewingId === voice.id ? "⏸" : "▶"}
</button>
</div>
)
})}
</div>
)}
{/* 我的音色:克隆入口 */}
{voiceSource === "clone" && (
<div className="aa-clone-entry">
<a href="/app/voice-clone">+ </a>
</div>
)}
{/* 配音参数 */}
<div className="aa-voice-params">
<div className="aa-voice-params__row">
<div className="aa-voice-params__field">
<label className="aa-label" htmlFor="aa-voice-emotion">
</label>
<select
id="aa-voice-emotion"
className="aa-select"
value={emotion}
onChange={(e) => onEmotionChange(e.target.value as VoiceEmotion)}
>
{VOICE_EMOTION_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<div className="aa-voice-params__field">
<label className="aa-label" htmlFor="aa-voice-language">
</label>
<select
id="aa-voice-language"
className="aa-select"
value={language}
onChange={(e) => onLanguageChange(e.target.value as VoiceLanguage)}
>
{VOICE_LANGUAGE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>
<div className="aa-voice-params__field">
<label className="aa-label" htmlFor="aa-voice-speed">
{speed.toFixed(1)}x
</label>
<input
id="aa-voice-speed"
type="number"
className="aa-input"
min={0.5}
max={2.0}
step={0.1}
value={speed}
onChange={(e) => handleSpeedChange(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default PanelVoiceSelector
@@ -0,0 +1,97 @@
/**
* AI数字人 — 标题库选择弹窗
* 复用智能剪辑的标题库 API,选择标题后填入输入框
*/
import React, { useEffect, useState } from "react"
import { getTitles } from "@/api/titles"
import type { TitleItem } from "@/api/titles/types"
interface TitleLibraryModalProps {
open: boolean
onClose: () => void
onSelect: (title: string) => void
}
const TitleLibraryModal: React.FC<TitleLibraryModalProps> = ({ open, onClose, onSelect }) => {
const [titles, setTitles] = useState<TitleItem[]>([])
const [loading, setLoading] = useState(false)
const [search, setSearch] = useState("")
useEffect(() => {
if (!open) return
setLoading(true)
getTitles()
.then((items) => setTitles(items))
.catch(() => setTitles([]))
.finally(() => setLoading(false))
}, [open])
const filtered = titles.filter(
(t) => !search || t.content.toLowerCase().includes(search.toLowerCase()),
)
if (!open) return null
return (
<div className="aa-modal-overlay" onClick={onClose}>
<div className="aa-modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 600 }}>
<div className="aa-modal__header">
<span className="aa-modal__title"></span>
<button className="aa-modal__close" onClick={onClose}></button>
</div>
<div className="aa-modal__body">
<div style={{ marginBottom: 12 }}>
<input
className="aa-input"
placeholder="搜索标题..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
{loading ? (
<div style={{ textAlign: "center", padding: 40, color: "#8c8ca1" }}>...</div>
) : filtered.length === 0 ? (
<div style={{ textAlign: "center", padding: 40, color: "#8c8ca1" }}>
</div>
) : (
<div style={{ maxHeight: 400, overflowY: "auto" }}>
{filtered.map((t) => (
<div
key={t.id}
style={{
padding: "12px 16px",
marginBottom: 8,
background: "#f8f8fc",
borderRadius: 8,
cursor: "pointer",
transition: "background 0.2s",
}}
onMouseEnter={(e) => (e.currentTarget.style.background = "#eef0ff")}
onMouseLeave={(e) => (e.currentTarget.style.background = "#f8f8fc")}
onClick={() => {
onSelect(t.content)
onClose()
}}
>
<div style={{ fontSize: 14, color: "#1a1a2e", marginBottom: 4 }}>{t.content}</div>
<div style={{ fontSize: 12, color: "#8c8ca1" }}>
{t.word_count ?? t.content.length} ·{" "}
{t.created_at ? new Date(t.created_at).toLocaleDateString() : ""}
</div>
</div>
))}
</div>
)}
</div>
<div className="aa-modal__footer">
<button className="aa-btn" onClick={onClose}>
</button>
</div>
</div>
</div>
)
}
export default TitleLibraryModal
@@ -0,0 +1,158 @@
/**
* AI数字人 — 页面全局状态管理 hook(v3 + #1845 配音前置)
*/
import { useState, useCallback } from "react"
import type { AssetItem } from "@/api/assets"
import type { UnifiedVoiceItem } from "@/api/voices/types"
import {
type VoiceSource,
type VoiceEmotion,
type VoiceLanguage,
type Script,
type LipsyncJob,
type BRollSegment,
type AiAvatarTitleConfig,
type AiAvatarCoverConfig,
type TtsPreviewResult,
DEFAULT_TITLE_CONFIG,
DEFAULT_COVER_CONFIG,
} from "../types"
const DEFAULT_TTS_PREVIEW: TtsPreviewResult = {
audioUrl: null,
duration: 0,
sentenceTimings: [],
status: "idle",
error: null,
}
export function useAiAvatar() {
/* ── 面板1:出镜视频 ── */
const [selectedVideo, setSelectedVideo] = useState<AssetItem | null>(null)
const [showAssetPicker, setShowAssetPicker] = useState(false)
/* ── 面板2:配音库 ── */
const [voiceSource, setVoiceSource] = useState<VoiceSource>("preset")
const [selectedVoice, setSelectedVoice] = useState<UnifiedVoiceItem | null>(null)
const [emotion, setEmotion] = useState<VoiceEmotion>("natural")
const [speed, setSpeed] = useState(1.0)
const [language, setLanguage] = useState<VoiceLanguage>("mandarin")
/* ── 面板3:文案 & 对口型 ── */
const [script, setScript] = useState<Script | null>(null)
const [scriptText, setScriptText] = useState("")
const [lipsyncJob, setLipsyncJob] = useState<LipsyncJob | null>(null)
const [showScriptModal, setShowScriptModal] = useState(false)
const [showBRollModal, setShowBRollModal] = useState(false)
/* ── #1845 TTS 预合成(步骤1「生成配音」) ── */
const [ttsPreview, setTtsPreview] = useState<TtsPreviewResult>(DEFAULT_TTS_PREVIEW)
/* ── 面板3.5B-roll ── */
const [bRollSegments, setBRollSegments] = useState<BRollSegment[]>([])
/* ── 面板4:标题配置 ── */
const [titleConfig, setTitleConfig] = useState<AiAvatarTitleConfig>(DEFAULT_TITLE_CONFIG)
/* ── 面板5:封面 & 生成 ── */
const [coverConfig, setCoverConfig] = useState<AiAvatarCoverConfig>(DEFAULT_COVER_CONFIG)
const [resolution, setResolution] = useState("1080p")
const [isGenerating, setIsGenerating] = useState(false)
/* ── Actions ── */
const selectVideo = useCallback((asset: AssetItem) => {
setSelectedVideo(asset)
setShowAssetPicker(false)
}, [])
const removeVideo = useCallback(() => {
setSelectedVideo(null)
}, [])
const selectScript = useCallback((s: Script) => {
setScript(s)
setScriptText(s.content)
setShowScriptModal(false)
}, [])
const addBRollSegment = useCallback((segment: BRollSegment) => {
setBRollSegments((prev) => [...prev, segment])
}, [])
const removeBRollSegment = useCallback((id: string) => {
setBRollSegments((prev) => prev.filter((s) => s.id !== id))
}, [])
const updateTitleConfig = useCallback((partial: Partial<AiAvatarTitleConfig>) => {
setTitleConfig((prev) => ({ ...prev, ...partial }))
}, [])
const reset = useCallback(() => {
setSelectedVideo(null)
setSelectedVoice(null)
setScript(null)
setScriptText("")
setLipsyncJob(null)
setTtsPreview(DEFAULT_TTS_PREVIEW)
setBRollSegments([])
setTitleConfig(DEFAULT_TITLE_CONFIG)
setCoverConfig(DEFAULT_COVER_CONFIG)
setResolution("1080p")
setIsGenerating(false)
}, [])
return {
// 面板1
selectedVideo,
showAssetPicker,
setShowAssetPicker,
selectVideo,
removeVideo,
// 面板2
voiceSource,
setVoiceSource,
selectedVoice,
setSelectedVoice,
emotion,
setEmotion,
speed,
setSpeed,
language,
setLanguage,
// 面板3
script,
setScript,
scriptText,
setScriptText,
lipsyncJob,
setLipsyncJob,
showScriptModal,
setShowScriptModal,
showBRollModal,
setShowBRollModal,
selectScript,
// #1845 TTS 预合成
ttsPreview,
setTtsPreview,
resetTtsPreview: useCallback(() => setTtsPreview(DEFAULT_TTS_PREVIEW), []),
// B-roll
bRollSegments,
addBRollSegment,
removeBRollSegment,
// 面板4
titleConfig,
updateTitleConfig,
setTitleConfig,
// 面板5
coverConfig,
setCoverConfig,
resolution,
setResolution,
isGenerating,
setIsGenerating,
// 全局
reset,
}
}
export type UseAiAvatarReturn = ReturnType<typeof useAiAvatar>
+154
View File
@@ -0,0 +1,154 @@
/**
* AI数字人 — TypeScript 类型定义(v3
*/
import type { AssetItem } from "@/api/assets"
/* ── 音色来源切换 ── */
export type VoiceSource = "preset" | "clone"
/* ── 情绪 ── */
export type VoiceEmotion = "natural" | "excited" | "calm" | "friendly"
export const VOICE_EMOTION_OPTIONS: { value: VoiceEmotion; label: string }[] = [
{ value: "natural", label: "自然" },
{ value: "excited", label: "兴奋" },
{ value: "calm", label: "沉稳" },
{ value: "friendly", label: "亲切" },
]
/* ── 语言 ── */
export type VoiceLanguage = "mandarin" | "english" | "cantonese"
export const VOICE_LANGUAGE_OPTIONS: { value: VoiceLanguage; label: string }[] = [
{ value: "mandarin", label: "普通话" },
{ value: "english", label: "English" },
{ value: "cantonese", label: "粤语" },
]
/* ── 对口型任务状态 ── */
export type LipsyncStatus = "idle" | "pending" | "processing" | "completed" | "failed"
/* ── TTS 预合成(#1845 配音前置:步骤1「生成配音」状态) ── */
export type TtsPreviewStatus = "idle" | "generating" | "done" | "failed"
export interface TtsPreviewResult {
audioUrl: string | null
duration: number
sentenceTimings: SentenceTiming[]
status: TtsPreviewStatus
error: string | null
}
/* ── 文案 ── */
export interface Script {
id: string
title: string
content: string
char_count: number
created_at: string
updated_at?: string
}
/* ── 对口型任务 ── */
export interface LipsyncJob {
id: string
status: LipsyncStatus
progress: number
output_video_url: string | null
/** 对口型成片总时长(秒),后端返回 */
script_text: string
output_duration?: number
/** 精确句子时间戳(后端基于 TTS 音频静音检测计算) */
sentence_timings?: SentenceTiming[] | null
error_message: string | null
created_at: string
}
/* ── 句子时间戳(后端精确计算) ── */
export interface SentenceTiming {
index: number
text: string
start_time: number
end_time: number
}
/* ── B-roll 画面插入 ── */
export type BRollInsertMode = "fullscreen" | "pip"
export type PipPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right"
export interface BRollSegment {
id: string
asset: AssetItem
script_segment_index: number
start_time: number
end_time: number
mode: BRollInsertMode
pip_position: PipPosition
pip_scale: number
}
/* ── 标题配置 ── */
export interface AiAvatarTitleConfig {
title: string
position: string
font: string
size: number
bold: boolean
italic: boolean
stroke: boolean
shadow: boolean
color: string
auto_subtitle: boolean
/** 自定义位置坐标(position=custom 时生效,百分比 0-100 */
pos_x?: number
pos_y?: number
}
/* ── 封面配置 ── */
export interface AiAvatarCoverConfig {
enabled: boolean
mode: "auto_frame" | "upload"
frame_time: number
upload_url: string | null
thumbnail_url: string | null
/** 智能封面(MediaKit 选帧)返回的 OSS 非临时 URL#1822 */
smart_cover_url: string | null
}
/* ── 渲染任务 ── */
export type RenderStatus = "pending" | "processing" | "completed" | "failed" | "cancelled"
export interface RenderJob {
id: string
status: RenderStatus
progress: number
output_video_url: string | null
output_cover_url: string | null
error_message: string | null
created_at: string
}
/* ── 默认值 ── */
export const DEFAULT_TITLE_CONFIG: AiAvatarTitleConfig = {
title: "",
position: "bottom",
font: "思源黑体",
size: 48,
bold: true,
italic: false,
stroke: false,
shadow: false,
color: "#ffffff",
auto_subtitle: true,
pos_x: undefined,
pos_y: undefined,
}
export const DEFAULT_COVER_CONFIG: AiAvatarCoverConfig = {
enabled: true,
mode: "auto_frame",
frame_time: 0,
upload_url: null,
thumbnail_url: null,
smart_cover_url: null,
}
@@ -0,0 +1,88 @@
/**
* AI数字人 — 前后端接口契约转换工具(#1822)
*
* 以 packages/domain/video_filter_builder.py 的 build_title_drawtext_filter() 为唯一口径
* (契约文档第 5 节的 titles[]/fontSize/frame/start/end 为误写,后端不认,禁止使用)。
*/
import type { AiAvatarTitleConfig, AiAvatarCoverConfig, VoiceEmotion } from "../types"
/* ── 情绪:中文 → 英文(防御性映射;state 默认已是英文) ── */
const EMOTION_ZH_TO_EN: Record<string, VoiceEmotion> = {
: "natural",
: "excited",
: "calm",
: "friendly",
}
const VALID_EMOTIONS: VoiceEmotion[] = ["natural", "excited", "calm", "friendly"]
/** 归一化为后端英文枚举 natural/excited/calm/friendly;非法/空值回退 natural。 */
export function normalizeEmotion(raw: string | undefined | null): VoiceEmotion {
if (!raw) return "natural"
const v = raw.trim()
if ((VALID_EMOTIONS as string[]).includes(v)) return v as VoiceEmotion
return EMOTION_ZH_TO_EN[v] ?? "natural"
}
/* ── 标题:前端 state → 后端 build_title_drawtext_filter 字段(单个 title_config dict ── */
/**
* 后端真实字段:text(或content)、font(或font_preset)、font_size(或size)、
* font_color(或color,可传 #RRGGBB)、position(top/center/bottom/custom)、
* enabled、bold、stroke{enabled,width,color}、shadow{enabled,color,offset_x,offset_y}、
* pos_x/pos_y(custom 时)、title_image_dataurl(前端 Canvas 渲染的 PNG dataURLWYSIWYG 路径优先)。
* 口播标题默认 position=bottom(不传后端会默认 top 跑到画面顶部)。
*/
export function buildTitleConfigPayload(
cfg: AiAvatarTitleConfig,
titleImageDataUrl?: string | null,
): Record<string, unknown> {
const text = (cfg.title || "").trim()
if (!text) return {}
const position = cfg.position || "bottom"
const payload: Record<string, unknown> = {
text,
enabled: true,
font: cfg.font || "思源黑体",
font_size: Math.round(cfg.size) || 48,
font_color: cfg.color || "#ffffff",
position,
bold: !!cfg.bold,
stroke: cfg.stroke ? { enabled: true, width: 2, color: "#000000" } : { enabled: false },
shadow: cfg.shadow
? { enabled: true, color: "#000000", offset_x: 2, offset_y: 2 }
: { enabled: false },
}
// 自定义坐标(custom 位置)
if (position === "custom" && typeof cfg.pos_x === "number" && typeof cfg.pos_y === "number") {
payload.pos_x = cfg.pos_x
payload.pos_y = cfg.pos_y
}
// 前端 Canvas 渲染好的 PNG dataURL(所见即所得,后端优先 overlay 此图片图层)
if (titleImageDataUrl) {
payload.title_image_dataurl = titleImageDataUrl
}
return payload
}
/* ── 封面:前端 state → 后端 render cover_config ── */
export function buildCoverConfigPayload(
cfg: AiAvatarCoverConfig,
smartCoverUrl: string | null,
): Record<string, unknown> {
const payload: Record<string, unknown> = {
enabled: !!cfg.enabled,
mode: cfg.mode,
// build_cover_extract_command 读取 timestamp(截帧秒数)
timestamp: cfg.frame_time || 0,
}
// 智能封面 URL(后端字段名为 url/imageUrl/cover_url 都兼容,优先 url
if (smartCoverUrl) {
payload.url = smartCoverUrl
payload.cover_url = smartCoverUrl
}
// 自定义上传:blob: 本地预览地址无法给后端,仅 OSS URL 可用
if (cfg.mode === "upload" && cfg.upload_url && !cfg.upload_url.startsWith("blob:")) {
payload.url = cfg.upload_url
payload.upload_url = cfg.upload_url
}
return payload
}
@@ -0,0 +1,109 @@
/**
* AI数字人 — 文案分句 & B-roll 时间计算
*
* 数据来源优先级:
* 1. 后端 sentence_timings(基于 TTS 音频静音检测,精确到句子边界)—— 直接使用,不重新分句
* 2. 后端 output_duration(最终渲染视频时长) + 本地分句 —— 按字数比例估算
* 3. 两者都没有(对口型还在生成中)—— 返回分句文本但 startTime/endTime 全部 0,等数据到位重算
*/
export interface ScriptSentence {
/** 句子序号(从 0 开始,对应提交给后端的 script_segment_index */
index: number
/** 句子文本(去掉首尾空白) */
text: string
/** 句子字数(按中文/字符计,去除空白) */
charCount: number
/** 累计起始字数(用于时间估算) */
startChar: number
/** 对口型视频内起始时间(秒)——后端精确值或前端估算 */
startTime: number
/** 对口型视频内结束时间(秒)——后端精确值或前端估算 */
endTime: number
}
/** 句子分隔符:中英文句号/问号/感叹号/分号/逗号/换行(覆盖中文短视频常用断句) */
const SENTENCE_SPLIT_RE = /[。!?!??!;,\n\r]+/
/**
* 分句并计算每句的起止时间。
*
* @param sentenceTimings 后端返回的精确句子时间戳(来自 lipsync_job.sentence_timings)。
* 非空时直接按后端返回的句子列表渲染,不再本地分句(避免前后端分句不一致导致时间错位)。
* @param outputDuration 最终视频时长(秒)。对口型预览阶段可能为 0,此时降级估算只能给 0。
*/
export function splitScriptIntoSentences(
scriptText: string,
sentenceTimings?:
{ index?: number; text?: string; start_time: number; end_time: number }[] | null,
outputDuration: number = 0,
): ScriptSentence[] {
const text = (scriptText || "").trim()
if (!text) return []
// 1. 后端返回了 sentence_timings:校验通过就直接用,跳过本地分句
// 校验条件放宽:只要是数组、至少1条、每条 start_time/end_time 是数字即可
// (不再强制要求条数相等——后端静音检测可能按停顿切出更多/更少边界,
// 比如文案用逗号连写时本地只分1句、后端按停顿切4句,后端的切法才是对的)
if (Array.isArray(sentenceTimings) && sentenceTimings.length > 0) {
const valid = sentenceTimings.every(
(t) =>
t &&
typeof t.start_time === "number" &&
typeof t.end_time === "number" &&
isFinite(t.start_time) &&
isFinite(t.end_time) &&
t.end_time >= t.start_time,
)
if (valid) {
let accChar = 0
return sentenceTimings.map((t, i) => {
const sentenceText = (t.text || "").trim() || `句子${i + 1}`
const charCount = sentenceText.replace(/\s/g, "").length
const sentence: ScriptSentence = {
index: typeof t.index === "number" ? t.index : i,
text: sentenceText,
charCount,
startChar: accChar,
startTime: round1(t.start_time),
endTime: round1(t.end_time),
}
accChar += charCount
return sentence
})
}
}
// 2. 本地分句 + 按字数比例估算(降级路径)
const rawParts = text
.split(SENTENCE_SPLIT_RE)
.map((part) => part.trim())
.filter((part) => part.length > 0)
const totalChars = rawParts.reduce((sum, part) => sum + part.replace(/\s/g, "").length, 0)
const duration = outputDuration > 0 ? outputDuration : 0
const sentences: ScriptSentence[] = []
let accChar = 0
rawParts.forEach((part, i) => {
const charCount = part.replace(/\s/g, "").length
const startTime = duration > 0 && totalChars > 0 ? (accChar / totalChars) * duration : 0
const endTime =
duration > 0 && totalChars > 0 ? ((accChar + charCount) / totalChars) * duration : 0
sentences.push({
index: i,
text: part,
charCount,
startChar: accChar,
startTime: round1(startTime),
endTime: round1(endTime),
})
accChar += charCount
})
return sentences
}
function round1(n: number): number {
return Math.round(n * 10) / 10
}
@@ -0,0 +1,179 @@
/**
* AI数字人 — 标题 Canvas 渲染工具
*
* 把标题按前端预览的 HTML/CSS 效果画到透明背景 PNG 上(与视频同分辨率),
* 以 dataURL 形式传给后端,后端用 FFmpeg overlay 直接叠加图层,
* 彻底解决前端 HTML/CSS 预览 ≠ FFmpeg drawtext 成片的 WYSIWYG 问题。
*
* 约定:titleConfig.size 的语义是"720p 基准宽度下的字号(px",
* 按 videoWidth / 720 得到 scale,所有长度类参数乘以 scale,
* 保证 1080p / 4K 成片里标题视觉大小与预览一致。
*/
import type { AiAvatarTitleConfig } from "../types"
export interface RenderTitlePngOptions {
/** 标题配置 */
titleConfig: AiAvatarTitleConfig
/** 视频宽度(像素),默认 720 */
videoWidth?: number
/** 视频高度(像素),默认 1280 */
videoHeight?: number
}
/**
* 将标题渲染为透明背景 PNG 的 dataURLdata:image/png;base64,...
* Canvas 尺寸与视频一致,保证叠加时 1:1 像素对齐。
*
* 标题为空时返回 null。
*/
export function renderTitleToPngDataUrl(opts: RenderTitlePngOptions): string | null {
const { titleConfig, videoWidth = 720, videoHeight = 1280 } = opts
if (!titleConfig) return null
const rawTitle = (titleConfig.title || "").trim()
if (!rawTitle) return null
// 按 / 或 分割为多行
const lines = rawTitle
.split(/[/]/)
.map((l) => l.trim())
.filter((l) => l.length > 0)
if (lines.length === 0) return null
// 分辨率缩放系数:基准 720p,所有长度类参数乘以 scale
const scale = videoWidth / 720
const r = (v: number) => Math.round(v * scale)
const canvas = document.createElement("canvas")
canvas.width = videoWidth
canvas.height = videoHeight
const ctx = canvas.getContext("2d")
if (!ctx) return null
const baseSize = Math.max(12, Math.round(titleConfig.size || 48))
const size = r(baseSize)
const bold = !!titleConfig.bold
const italic = !!titleConfig.italic
const color = titleConfig.color || "#ffffff"
const stroke = !!titleConfig.stroke
const shadow = !!titleConfig.shadow
// 字体族 fallback 链:优先中文字体
const fontFamily =
'"Noto Sans CJK SC","Source Han Sans CN","PingFang SC","Microsoft YaHei",sans-serif'
const fontParts: string[] = []
if (italic) fontParts.push("italic")
if (bold) fontParts.push("bold")
fontParts.push(`${size}px`, fontFamily)
ctx.font = fontParts.join(" ")
ctx.fillStyle = color
ctx.textAlign = "center"
ctx.textBaseline = "middle"
// 阴影(shadow=true 时开启)——按 scale 缩放
if (shadow) {
ctx.shadowColor = "rgba(0,0,0,0.8)"
ctx.shadowBlur = r(4)
ctx.shadowOffsetX = 0
ctx.shadowOffsetY = r(2)
}
// 位置计算:与 PanelLipsyncPreview 的 CSS 对齐(按 scale 缩放 PAD
const PAD = r(16)
let centerX = videoWidth / 2
const position = titleConfig.position || "bottom"
const lineGap = size * 1.2
const totalTextH = lines.length * lineGap - (lineGap - size) // 所有行的总高度
// 文本块顶部 ytextBaseline=middle 时首行基线)
let firstLineY: number
if (
position === "custom" &&
typeof titleConfig.pos_x === "number" &&
typeof titleConfig.pos_y === "number"
) {
centerX = (Math.max(0, Math.min(100, titleConfig.pos_x)) / 100) * videoWidth
const centerY = (Math.max(0, Math.min(100, titleConfig.pos_y)) / 100) * videoHeight
firstLineY = centerY - totalTextH / 2 + size / 2
} else if (position === "top") {
// 顶部:y = size/2 + PAD
firstLineY = size / 2 + PAD
} else if (position === "center") {
firstLineY = videoHeight / 2 - totalTextH / 2 + size / 2
} else {
// bottom(默认)
firstLineY = videoHeight - totalTextH - PAD + size / 2
}
// 描边参数:描边 lineWidth 按 scale 缩放(基准 size * 0.06,最小 2px @720p
const doStroke = stroke
const strokeWidth = Math.max(r(2), Math.round(size * 0.06))
// 逐行绘制
lines.forEach((line, idx) => {
const y = firstLineY + idx * lineGap
if (doStroke) {
const prevShadowColor = ctx.shadowColor
const prevShadowBlur = ctx.shadowBlur
// 描边不要带阴影(避免黑色描边发虚)
ctx.shadowColor = "rgba(0,0,0,0)"
ctx.shadowBlur = 0
ctx.lineWidth = strokeWidth
ctx.strokeStyle = "#000000"
ctx.lineJoin = "round"
ctx.strokeText(line, centerX, y)
// 恢复阴影
if (shadow) {
ctx.shadowColor = "rgba(0,0,0,0.8)"
ctx.shadowBlur = r(4)
} else {
ctx.shadowColor = prevShadowColor
ctx.shadowBlur = prevShadowBlur
}
}
ctx.fillText(line, centerX, y)
})
try {
return canvas.toDataURL("image/png")
} catch {
return null
}
}
/**
* 获取视频真实分辨率(HTMLVideoElement + loadedmetadata,超时 3 秒兜底 720×1280)。
*/
export function getVideoResolution(
videoUrl: string,
timeoutMs = 3000,
): Promise<{ width: number; height: number }> {
return new Promise((resolve) => {
if (!videoUrl) {
resolve({ width: 720, height: 1280 })
return
}
const video = document.createElement("video")
video.preload = "metadata"
video.muted = true
video.playsInline = true
video.crossOrigin = "anonymous"
let settled = false
const done = (w: number, h: number) => {
if (settled) return
settled = true
video.removeAttribute("src")
video.load()
resolve({ width: w, height: h })
}
const timer = window.setTimeout(() => done(720, 1280), timeoutMs)
video.onloadedmetadata = () => {
window.clearTimeout(timer)
const w = video.videoWidth || 720
const h = video.videoHeight || 1280
done(w, h)
}
video.onerror = () => {
window.clearTimeout(timer)
done(720, 1280)
}
video.src = videoUrl
})
}
@@ -1,4 +0,0 @@
export { useBatchDelete } from "./useBatchDelete"
export { useBatchTag } from "./useBatchTag"
export { useBatchClassify } from "./useBatchClassify"
export { useBatchMark } from "./useBatchMark"
@@ -1,58 +0,0 @@
import { useState, useCallback } from "react"
import { message } from "antd"
import { batchClassifyAssets, type BatchOperationResult } from "@/api/assets"
interface UseBatchClassifyOptions {
selectedIds: Set<string>
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void
}
export const useBatchClassify = ({
selectedIds,
queryClient,
showResult,
}: UseBatchClassifyOptions) => {
const [classifyModalOpen, setClassifyModalOpen] = useState(false)
const [batchCategory, setBatchCategory] = useState("")
const [batchLoading, setBatchLoading] = useState(false)
const handleBatchClassify = useCallback(async () => {
if (!batchCategory) {
message.warning("请选择分类")
return
}
const ids = Array.from(selectedIds)
setBatchLoading(true)
try {
const result = await batchClassifyAssets({
asset_ids: ids,
category: batchCategory,
})
queryClient.invalidateQueries({ queryKey: ["assets"] })
showResult(result, "批量改分类")
setClassifyModalOpen(false)
setBatchCategory("")
if (result.failure_count === 0) {
message.success(`成功将 ${result.success_count} 个素材改为「${batchCategory}`)
} else {
message.warning(
`改分类完成:成功 ${result.success_count} 个,失败 ${result.failure_count}`,
)
}
} catch {
message.error("批量改分类失败,请重试")
} finally {
setBatchLoading(false)
}
}, [batchCategory, selectedIds, queryClient, showResult])
return {
classifyModalOpen,
setClassifyModalOpen,
batchCategory,
setBatchCategory,
batchLoading,
handleBatchClassify,
}
}
@@ -1,40 +0,0 @@
import { useState, useCallback } from "react"
import { message } from "antd"
import { batchDeleteAssets, type BatchOperationResult } from "@/api/assets"
interface UseBatchDeleteOptions {
selectedIds: Set<string>
invalidateAssets: () => void
showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void
}
export const useBatchDelete = ({
selectedIds,
invalidateAssets,
showResult,
}: UseBatchDeleteOptions) => {
const [batchLoading, setBatchLoading] = useState(false)
const handleBatchDelete = useCallback(async () => {
const ids = Array.from(selectedIds)
setBatchLoading(true)
try {
const result = await batchDeleteAssets(ids)
invalidateAssets()
showResult(result, "批量删除")
if (result.failure_count === 0) {
message.success(`成功删除 ${result.success_count} 个素材`)
} else {
message.warning(
`删除完成:成功 ${result.success_count} 个,失败 ${result.failure_count}`,
)
}
} catch {
message.error("批量删除失败,请重试")
} finally {
setBatchLoading(false)
}
}, [selectedIds, invalidateAssets, showResult])
return { batchLoading, handleBatchDelete }
}
@@ -1,53 +0,0 @@
import { useState, useCallback } from "react"
import { message } from "antd"
import { batchMarkAssets, type BatchOperationResult } from "@/api/assets"
import type { SmartViewType } from "../../../components/BatchMarkModal"
import { SMART_VIEW_LABELS } from "../constants"
interface UseBatchMarkOptions {
selectedIds: Set<string>
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void
}
export const useBatchMark = ({ selectedIds, queryClient, showResult }: UseBatchMarkOptions) => {
const [markModalOpen, setMarkModalOpen] = useState(false)
const [batchSmartView, setBatchSmartView] = useState<SmartViewType>("recommended")
const [batchLoading, setBatchLoading] = useState(false)
const handleBatchMark = useCallback(async () => {
const ids = Array.from(selectedIds)
setBatchLoading(true)
try {
const result = await batchMarkAssets({
asset_ids: ids,
smart_view: batchSmartView,
})
queryClient.invalidateQueries({ queryKey: ["assets"] })
showResult(result, "批量智能标记")
setMarkModalOpen(false)
if (result.failure_count === 0) {
message.success(
`成功将 ${result.success_count} 个素材标记为「${SMART_VIEW_LABELS[batchSmartView]}`,
)
} else {
message.warning(
`智能标记完成:成功 ${result.success_count} 个,失败 ${result.failure_count}`,
)
}
} catch {
message.error("批量智能标记失败,请重试")
} finally {
setBatchLoading(false)
}
}, [batchSmartView, selectedIds, queryClient, showResult])
return {
markModalOpen,
setMarkModalOpen,
batchSmartView,
setBatchSmartView,
batchLoading,
handleBatchMark,
}
}
@@ -1,86 +0,0 @@
import { useState, useCallback } from "react"
import { message } from "antd"
import { batchTagAssets, type BatchOperationResult } from "@/api/assets"
interface UseBatchTagOptions {
selectedIds: Set<string>
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
showResult: (result: BatchOperationResult, title: string, clear?: boolean) => void
}
export const useBatchTag = ({ selectedIds, queryClient, showResult }: UseBatchTagOptions) => {
const [tagModalOpen, setTagModalOpen] = useState(false)
const [batchTagInput, setBatchTagInput] = useState("")
const [batchTags, setBatchTags] = useState<string[]>([])
const [tagMode, setTagMode] = useState<"add" | "replace">("add")
const [batchLoading, setBatchLoading] = useState(false)
const handleBatchTag = useCallback(async () => {
if (batchTags.length === 0) {
message.warning("请至少输入一个标签")
return
}
const ids = Array.from(selectedIds)
setBatchLoading(true)
try {
const result = await batchTagAssets({
asset_ids: ids,
tags: batchTags,
mode: tagMode,
})
queryClient.invalidateQueries({ queryKey: ["assets"] })
showResult(result, "批量打标签")
setTagModalOpen(false)
setBatchTags([])
setBatchTagInput("")
setTagMode("add")
if (result.failure_count === 0) {
message.success(`成功为 ${result.success_count} 个素材打标签`)
} else {
message.warning(
`打标签完成:成功 ${result.success_count} 个,失败 ${result.failure_count}`,
)
}
} catch {
message.error("批量打标签失败,请重试")
} finally {
setBatchLoading(false)
}
}, [batchTags, selectedIds, tagMode, queryClient, showResult])
const handleTagInputKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" && batchTagInput.trim()) {
e.preventDefault()
const tag = batchTagInput.trim()
if (!batchTags.includes(tag)) {
setBatchTags([...batchTags, tag])
}
setBatchTagInput("")
}
},
[batchTagInput, batchTags],
)
const removeBatchTag = useCallback(
(tag: string) => {
setBatchTags(batchTags.filter((t) => t !== tag))
},
[batchTags],
)
return {
tagModalOpen,
setTagModalOpen,
batchTagInput,
setBatchTagInput,
batchTags,
setBatchTags,
tagMode,
setTagMode,
batchLoading,
handleBatchTag,
handleTagInputKeyDown,
removeBatchTag,
}
}
@@ -1,8 +0,0 @@
/**
* @deprecated 请从 ./batch/ 目录导入子模块
* 保持向后兼容,re-export 所有批量操作 Hook
*/
export { useBatchDelete } from "./batch/useBatchDelete"
export { useBatchTag } from "./batch/useBatchTag"
export { useBatchClassify } from "./batch/useBatchClassify"
export { useBatchMark } from "./batch/useBatchMark"
@@ -10,7 +10,7 @@
============================================================ */
.dup-page {
padding: var(--space-2xl) var(--space-lg);
max-width: 1100px;
max-width: 1400px;
margin: 0 auto;
}
@@ -6440,3 +6440,49 @@
opacity: 0.6;
cursor: not-allowed;
}
/* ═══ 标题设置 — 颜色预设 ═══ */
.ep-color-presets {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.ep-color-swatch {
width: 24px;
height: 24px;
border-radius: 4px;
border: 2px solid transparent;
cursor: pointer;
transition:
border-color 0.15s,
transform 0.1s;
}
.ep-color-swatch:hover {
transform: scale(1.1);
}
.ep-color-swatch.active {
border-color: var(--ep-primary, #4f8cff);
}
.ep-color-picker {
width: 28px;
height: 28px;
border: none;
border-radius: 4px;
cursor: pointer;
padding: 0;
background: none;
}
.ep-color-picker::-webkit-color-swatch-wrapper {
padding: 0;
}
.ep-color-picker::-webkit-color-swatch {
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
@@ -196,6 +196,8 @@ const EditingPlanner: React.FC = () => {
{/* 右栏 260px:设置面板 */}
<RightPanel
titleConfig={titleConfig}
onTitleConfigChange={setTitleConfig}
rightTab={rightTab}
onTabChange={setRightTab}
selectedClip={clipOps.selectedClip}
@@ -5,12 +5,15 @@
import React from "react"
import type { ClipPropertiesPanelProps } from "@/pages/editing-planner/types/clipProperties"
import SubtitleSettingsSection from "./clip-properties/SubtitleSettingsSection"
import TitleSettingsSection from "./clip-properties/TitleSettingsSection"
import BgmSettingsSection from "./clip-properties/BgmSettingsSection"
import ClipDetailSection from "./clip-properties/ClipDetailSection"
import StatsSection from "./clip-properties/StatsSection"
import { useVoicePreview } from "@/pages/editing-planner/hooks/useVoicePreview"
const ClipPropertiesPanel: React.FC<ClipPropertiesPanelProps> = ({
titleConfig,
onTitleConfigChange,
selectedClip,
subtitleSettings,
bgmSettings,
@@ -40,6 +43,11 @@ const ClipPropertiesPanel: React.FC<ClipPropertiesPanelProps> = ({
return (
<div className="ep-right-panel">
{/* ═══ 标题设置 — #1789 ═══ */}
{titleConfig && onTitleConfigChange && (
<TitleSettingsSection config={titleConfig} onChange={onTitleConfigChange} />
)}
{/* ═══ 字幕设置 ═══ */}
<SubtitleSettingsSection
settings={subtitleSettings}
@@ -5,9 +5,12 @@ import type { ClipData } from "../types"
import type { SubtitleStyleConfig } from "../types/subtitle"
import type { BgmMixConfig } from "@/api/bgm"
import type { TemplateMode } from "@/api/editing-planner"
import type { TitleConfig } from "@/api/template-editor"
import type { AssetItem } from "@/api/assets"
interface RightPanelProps {
titleConfig?: TitleConfig
onTitleConfigChange?: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
rightTab: "properties" | "clips"
onTabChange: (tab: "properties" | "clips") => void
// 属性 tab
@@ -46,6 +49,8 @@ interface RightPanelProps {
}
const RightPanel: React.FC<RightPanelProps> = ({
titleConfig,
onTitleConfigChange,
rightTab,
onTabChange,
selectedClip,
@@ -118,6 +123,8 @@ const RightPanel: React.FC<RightPanelProps> = ({
>["onBgmSettingsChange"]
return (
<ClipPropertiesPanel
titleConfig={titleConfig}
onTitleConfigChange={onTitleConfigChange}
selectedClip={selectedClip}
subtitleSettings={sub}
bgmSettings={bgm}
@@ -0,0 +1,136 @@
/**
* 标题设置区块 — #1789
* 提供字号滑块、字体预设、位置、颜色等控制入口
*/
import React from "react"
import type { TitleConfig } from "@/api/template-editor"
import { POSITION_OPTIONS, FONT_OPTIONS } from "@/pages/editing-planner/constants/clipProperties"
interface TitleSettingsSectionProps {
config: TitleConfig
onChange: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
}
const TITLE_COLOR_PRESETS = [
"#ffffff",
"#000000",
"#ff4444",
"#ffaa00",
"#44ff44",
"#4488ff",
"#ff44ff",
"#ffff44",
]
const TitleSettingsSection: React.FC<TitleSettingsSectionProps> = ({ config, onChange }) => {
const update = (partial: Partial<TitleConfig>) => {
onChange((prev: TitleConfig) => ({ ...prev, ...partial }))
}
return (
<div className="ep-settings-section">
<div className="ep-section-title">
<span className="ep-section-icon">📝</span>
</div>
{/* AI 自动选择开关 */}
<div className="ep-toggle-row">
<span className="ep-toggle-label">AI </span>
<div
className={`ep-toggle ${config.ai_auto_select ? "active" : ""}`}
onClick={() => update({ ai_auto_select: !config.ai_auto_select })}
>
<div className="ep-toggle-knob" />
</div>
</div>
{!config.ai_auto_select && (
<>
{/* 标题文本 */}
<div className="ep-field">
<label className="ep-field-label"></label>
<input
className="ep-form-select"
type="text"
placeholder="输入标题内容"
value={config.content}
onChange={(e) => update({ content: e.target.value })}
/>
</div>
{/* 位置 */}
<div className="ep-field">
<label className="ep-field-label"></label>
<select
className="ep-form-select"
value={config.position}
onChange={(e) => update({ position: e.target.value })}
>
{POSITION_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
{/* 字体预设 */}
<div className="ep-field">
<label className="ep-field-label"></label>
<select
className="ep-form-select"
value={config.font_preset}
onChange={(e) => update({ font_preset: e.target.value })}
>
{FONT_OPTIONS.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</select>
</div>
{/* 字号滑块 */}
<div className="ep-field">
<label className="ep-field-label"></label>
<div className="ep-slider-row">
<input
className="ep-slider"
type="range"
min={12}
max={72}
value={config.font_size}
onChange={(e) => update({ font_size: Number(e.target.value) })}
/>
<span className="ep-slider-value">{config.font_size}px</span>
</div>
</div>
{/* 颜色 */}
<div className="ep-field">
<label className="ep-field-label"></label>
<div className="ep-color-presets">
{TITLE_COLOR_PRESETS.map((color) => (
<div
key={color}
className={`ep-color-swatch${config.font_color === color ? " active" : ""}`}
style={{ backgroundColor: color }}
onClick={() => update({ font_color: color })}
/>
))}
<input
type="color"
className="ep-color-picker"
value={config.font_color}
onChange={(e) => update({ font_color: e.target.value })}
/>
</div>
</div>
</>
)}
</div>
)
}
export default TitleSettingsSection
@@ -1,5 +0,0 @@
/**
* LayerConfig 入口(向后兼容)
* 实际实现位于 ./layer-config/ 目录
*/
export { default } from "./layer-config"
@@ -1,65 +0,0 @@
/**
* 混剪图层列表
*/
import React from "react"
import type { PipLayer } from "@/pages/editing-planner/types"
import { LAYER_COLORS } from "@/pages/editing-planner/constants/pipConfig"
interface LayerListProps {
layers: PipLayer[]
selectedId: string
onSelect: (id: string) => void
onAdd: () => void
onDelete: (id: string) => void
}
const LayerList: React.FC<LayerListProps> = ({ layers, selectedId, onSelect, onAdd, onDelete }) => {
return (
<div className="pip-layer-list">
<div className="pip-toolbar" style={{ marginBottom: 8 }}>
<button className="pip-add-btn" onClick={onAdd}>
+
</button>
</div>
{layers.length === 0 ? (
<div className="pip-layer-empty"></div>
) : (
layers.map((layer, idx) => (
<div
key={layer.id}
className={`pip-layer-item${selectedId === layer.id ? " active" : ""}`}
onClick={() => onSelect(layer.id)}
>
{layer.thumbnail_url || layer.material_url ? (
<img
className="pip-layer-thumb"
src={layer.thumbnail_url || layer.material_url}
alt={layer.name}
/>
) : (
<div
className="pip-layer-thumb"
style={{
background: LAYER_COLORS[idx % LAYER_COLORS.length],
}}
/>
)}
<span className="pip-layer-name">{layer.name}</span>
<button
className="pip-layer-delete"
onClick={(e) => {
e.stopPropagation()
onDelete(layer.id)
}}
title="删除图层"
>
</button>
</div>
))
)}
</div>
)
}
export default LayerList
@@ -1,172 +0,0 @@
import React from "react"
import type { PipLayer, PipGridPosition } from "@/pages/editing-planner/types"
import { GRID_POSITIONS } from "@/pages/editing-planner/constants/pipConfig"
interface LayerPositionSizeProps {
layer: PipLayer
onUpdate: (id: string, partial: Partial<PipLayer>) => void
onGridClick: (pos: PipGridPosition) => void
onWidthChange: (val: number) => void
onHeightChange: (val: number) => void
}
/**
* 图层位置与尺寸配置面板
*/
export const LayerPositionSize: React.FC<LayerPositionSizeProps> = ({
layer,
onUpdate,
onGridClick,
onWidthChange,
onHeightChange,
}) => (
<>
{/* 素材类型 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div className="pip-type-btns">
<button
className={`pip-type-btn${layer.material_type === "image" ? " active" : ""}`}
onClick={() => onUpdate(layer.id, { material_type: "image" })}
>
🖼
</button>
<button
className={`pip-type-btn${layer.material_type === "video" ? " active" : ""}`}
onClick={() => onUpdate(layer.id, { material_type: "video" })}
>
🎬
</button>
</div>
</div>
{/* 素材 URL */}
<div className="pip-field">
<label className="pip-field-label">
{layer.material_type === "image" ? "图片" : "视频"} URL
</label>
<input
className="pip-input"
type="text"
placeholder={
layer.material_type === "image"
? "https://example.com/image.png"
: "https://example.com/video.mp4"
}
value={layer.material_url}
onChange={(e) => onUpdate(layer.id, { material_url: e.target.value })}
/>
</div>
{/* 位置:九宫格 + 坐标 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div style={{ display: "flex", gap: 16, alignItems: "flex-start" }}>
<div className="pip-grid">
{GRID_POSITIONS.map((pos) => (
<button
key={pos}
className={`pip-grid-btn${layer.grid_position === pos ? " active" : ""}`}
onClick={() => onGridClick(pos)}
>
<span className="pip-grid-dot" />
</button>
))}
</div>
<div className="pip-field-row" style={{ flex: 1 }}>
<div>
<label className="pip-field-label">X (%)</label>
<input
className="pip-number"
type="number"
min={0}
max={100}
value={layer.x}
onChange={(e) => onUpdate(layer.id, { x: Number(e.target.value) })}
/>
</div>
<div>
<label className="pip-field-label">Y (%)</label>
<input
className="pip-number"
type="number"
min={0}
max={100}
value={layer.y}
onChange={(e) => onUpdate(layer.id, { y: Number(e.target.value) })}
/>
</div>
</div>
</div>
</div>
{/* 尺寸 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div className="pip-slider-row">
<span style={{ fontSize: 12, color: "#999", width: 20 }}></span>
<input
className="pip-slider"
type="range"
min={10}
max={80}
value={layer.width}
onChange={(e) => onWidthChange(Number(e.target.value))}
/>
<span className="pip-slider-value">{layer.width}%</span>
</div>
<div className="pip-slider-row" style={{ marginTop: 6 }}>
<span style={{ fontSize: 12, color: "#999", width: 20 }}></span>
<input
className="pip-slider"
type="range"
min={10}
max={80}
value={layer.height}
onChange={(e) => onHeightChange(Number(e.target.value))}
/>
<span className="pip-slider-value">{layer.height}%</span>
</div>
<div
className="pip-lock-row"
style={{ marginTop: 6 }}
onClick={() => onUpdate(layer.id, { aspect_lock: !layer.aspect_lock })}
>
<span className="pip-lock-icon">{layer.aspect_lock ? "🔒" : "🔓"}</span>
<span>{layer.aspect_lock ? "已锁定比例" : "锁定宽高比"}</span>
</div>
</div>
{/* 圆角 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div className="pip-slider-row">
<input
className="pip-slider"
type="range"
min={0}
max={50}
value={layer.border_radius}
onChange={(e) => onUpdate(layer.id, { border_radius: Number(e.target.value) })}
/>
<span className="pip-slider-value">{layer.border_radius}%</span>
</div>
</div>
{/* 透明度 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div className="pip-slider-row">
<input
className="pip-slider"
type="range"
min={0}
max={100}
value={layer.opacity}
onChange={(e) => onUpdate(layer.id, { opacity: Number(e.target.value) })}
/>
<span className="pip-slider-value">{layer.opacity}%</span>
</div>
</div>
</>
)
@@ -1,87 +0,0 @@
import React from "react"
import type { PipLayer, PipAnimType, PipSlideDirection } from "@/pages/editing-planner/types"
import { ANIM_OPTIONS, SLIDE_DIR_OPTIONS } from "@/pages/editing-planner/constants/pipConfig"
interface LayerTimingAnimationProps {
layer: PipLayer
totalDuration: number
onUpdate: (id: string, partial: Partial<PipLayer>) => void
}
/**
* 图层时间与动画配置面板
*/
export const LayerTimingAnimation: React.FC<LayerTimingAnimationProps> = ({
layer,
totalDuration,
onUpdate,
}) => (
<>
{/* 时间 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<div className="pip-field-row">
<div>
<label className="pip-field-label"> (s)</label>
<input
className="pip-number"
type="number"
min={0}
max={totalDuration || 999}
step={0.1}
value={layer.start_time}
onChange={(e) => onUpdate(layer.id, { start_time: Number(e.target.value) })}
/>
</div>
<div>
<label className="pip-field-label"> (s)</label>
<input
className="pip-number"
type="number"
min={0.1}
max={totalDuration || 999}
step={0.1}
value={layer.duration}
onChange={(e) => onUpdate(layer.id, { duration: Number(e.target.value) })}
/>
</div>
</div>
</div>
{/* 入场动画 */}
<div className="pip-field">
<label className="pip-field-label"></label>
<select
className="pip-select"
value={layer.animation}
onChange={(e) => onUpdate(layer.id, { animation: e.target.value as PipAnimType })}
>
{ANIM_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
{/* 滑入方向(仅 slide_in 时显示) */}
{layer.animation === "slide_in" && (
<div className="pip-field">
<label className="pip-field-label"></label>
<select
className="pip-select"
value={layer.slide_direction}
onChange={(e) =>
onUpdate(layer.id, { slide_direction: e.target.value as PipSlideDirection })
}
>
{SLIDE_DIR_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
)}
</>
)
@@ -1,33 +0,0 @@
import React from "react"
import type { PipLayer } from "@/pages/editing-planner/types"
import { LAYER_COLORS } from "@/pages/editing-planner/constants/pipConfig"
interface PipPreviewProps {
layers: PipLayer[]
selectedId: string
}
/**
* PIP 图层迷你预览组件
*/
export const PipPreview: React.FC<PipPreviewProps> = ({ layers, selectedId }) => (
<div className="pip-preview-box">
{layers.map((l, idx) => (
<div
key={l.id}
className={`pip-preview-layer${selectedId === l.id ? " selected" : ""}`}
style={{
left: `${l.x}%`,
top: `${l.y}%`,
width: `${l.width}%`,
height: `${l.height}%`,
background: LAYER_COLORS[idx % LAYER_COLORS.length],
opacity: l.opacity / 100,
borderRadius: `${l.border_radius}%`,
}}
>
<span className="pip-preview-label">{l.name}</span>
</div>
))}
</div>
)
@@ -1,57 +0,0 @@
/**
* 混剪单图层配置区
*/
import React from "react"
import type { PipLayer, PipGridPosition } from "@/pages/editing-planner/types"
import { PipPreview } from "./PipPreview"
import { LayerPositionSize } from "./LayerPositionSize"
import { LayerTimingAnimation } from "./LayerTimingAnimation"
interface LayerConfigProps {
layer: PipLayer | null
layers: PipLayer[]
totalDuration: number
onUpdate: (id: string, partial: Partial<PipLayer>) => void
onGridClick: (pos: PipGridPosition) => void
onWidthChange: (val: number) => void
onHeightChange: (val: number) => void
}
const LayerConfig: React.FC<LayerConfigProps> = ({
layer,
layers,
totalDuration,
onUpdate,
onGridClick,
onWidthChange,
onHeightChange,
}) => {
if (!layer) {
return (
<div className="pip-config-area">
<div className="pip-config-empty"></div>
</div>
)
}
return (
<div className="pip-config-area">
{/* 迷你预览 */}
<PipPreview layers={layers} selectedId={layer.id} />
{/* 位置与尺寸 */}
<LayerPositionSize
layer={layer}
onUpdate={onUpdate}
onGridClick={onGridClick}
onWidthChange={onWidthChange}
onHeightChange={onHeightChange}
/>
{/* 时间与动画 */}
<LayerTimingAnimation layer={layer} totalDuration={totalDuration} onUpdate={onUpdate} />
</div>
)
}
export default LayerConfig
@@ -1,142 +0,0 @@
/**
* 贴纸素材库(emoji / 图片 / 文字花字)
*/
import React, { useState } from "react"
import type { StickerType, TextStickerPreset } from "@/pages/editing-planner/types"
import {
EMOJI_LIST,
STICKER_TYPE_TABS,
TEXT_PRESET_STYLES,
TEXT_STICKER_PRESET_LABELS,
} from "@/pages/editing-planner/constants/sticker"
interface StickerLibraryProps {
activeTab: StickerType
onTabChange: (tab: StickerType) => void
onAddSticker: (type: StickerType, content: string) => void
}
const StickerLibrary: React.FC<StickerLibraryProps> = ({
activeTab,
onTabChange,
onAddSticker,
}) => {
const [textInput, setTextInput] = useState("")
const imageInputRef = React.useRef<HTMLInputElement>(null)
const handleAddImage = () => {
const val = imageInputRef.current?.value.trim()
if (val) {
onAddSticker("image", val)
if (imageInputRef.current) imageInputRef.current.value = ""
}
}
const handleAddText = () => {
if (textInput.trim()) {
onAddSticker("text", textInput.trim())
setTextInput("")
}
}
return (
<>
{/* 类型 Tab */}
<div className="sticker-tabs">
{STICKER_TYPE_TABS.map((t) => (
<button
key={t.value}
className={`sticker-tab${activeTab === t.value ? " active" : ""}`}
onClick={() => onTabChange(t.value)}
>
{t.label}
</button>
))}
</div>
{/* Tab 内容区 */}
<div className="sticker-tab-content">
{/* Emoji 素材库 */}
{activeTab === "emoji" && (
<div className="sticker-emoji-grid">
{EMOJI_LIST.map((emoji) => (
<button
key={emoji}
className="sticker-emoji-btn"
onClick={() => onAddSticker("emoji", emoji)}
>
{emoji}
</button>
))}
</div>
)}
{/* 图片贴纸 */}
{activeTab === "image" && (
<div className="sticker-image-input">
<input
ref={imageInputRef}
type="text"
className="sticker-url-input"
placeholder="输入图片 URL 添加贴纸..."
onKeyDown={(e) => {
if (e.key === "Enter" && e.currentTarget.value.trim()) {
handleAddImage()
}
}}
/>
<button className="sticker-url-add-btn" onClick={handleAddImage}>
</button>
</div>
)}
{/* 文字花字 */}
{activeTab === "text" && (
<div className="sticker-text-section">
<div className="sticker-text-input-row">
<input
type="text"
className="sticker-text-input"
placeholder="输入文字内容..."
value={textInput}
onChange={(e) => setTextInput(e.target.value)}
/>
<button
className="sticker-text-add-btn"
disabled={!textInput.trim()}
onClick={handleAddText}
>
</button>
</div>
<div className="sticker-text-presets">
<div className="sticker-preset-title"></div>
<div className="sticker-preset-grid">
{(Object.keys(TEXT_STICKER_PRESET_LABELS) as TextStickerPreset[]).map((p) => (
<div
key={p}
className="sticker-preset-preview"
style={{
background:
p === "bubble"
? "rgba(0,0,0,0.5)"
: p === "gradient"
? "linear-gradient(90deg,#f093fb,#f5576c)"
: "#1a1a2e",
}}
>
<span style={TEXT_PRESET_STYLES[p]}></span>
<div className="sticker-preset-name">{TEXT_STICKER_PRESET_LABELS[p]}</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
</>
)
}
export default StickerLibrary
@@ -1,54 +0,0 @@
/**
* 已添加贴纸列表
*/
import React from "react"
import type { StickerItem } from "@/pages/editing-planner/types"
interface StickerListProps {
items: StickerItem[]
selectedId: string | null
onSelect: (id: string) => void
onDelete: (id: string) => void
}
const StickerList: React.FC<StickerListProps> = ({ items, selectedId, onSelect, onDelete }) => {
if (items.length === 0) return null
const getDisplayContent = (item: StickerItem) => {
if (item.type === "emoji") return { icon: item.content, name: "表情贴纸" }
if (item.type === "text") return { icon: "T", name: item.content.slice(0, 10) }
return { icon: "🖼", name: "图片贴纸" }
}
return (
<div className="sticker-list-section">
<div className="sticker-section-title"> ({items.length})</div>
<div className="sticker-list">
{items.map((item) => {
const { icon, name } = getDisplayContent(item)
return (
<div
key={item.id}
className={`sticker-list-item${selectedId === item.id ? " active" : ""}`}
onClick={() => onSelect(item.id)}
>
<span className="sticker-list-icon">{icon}</span>
<span className="sticker-list-name">{name}</span>
<button
className="sticker-list-delete"
onClick={(e) => {
e.stopPropagation()
onDelete(item.id)
}}
>
</button>
</div>
)
})}
</div>
</div>
)
}
export default StickerList
@@ -1,39 +0,0 @@
import React from "react"
import type { StickerItem } from "@/pages/editing-planner/types"
import { TEXT_PRESET_STYLES } from "@/pages/editing-planner/constants/sticker"
interface StickerPreviewProps {
sticker: StickerItem
}
export const StickerPreview: React.FC<StickerPreviewProps> = ({ sticker }) => (
<div className="sticker-preview-box">
<div
className="sticker-preview-item"
style={{
left: `${sticker.x}%`,
top: `${sticker.y}%`,
width: `${sticker.width}%`,
height: `${sticker.height}%`,
transform: `translate(-50%, -50%) rotate(${sticker.rotation}deg)`,
opacity: sticker.opacity / 100,
fontSize: sticker.type === "text" ? `${sticker.font_size}px` : undefined,
...TEXT_PRESET_STYLES[sticker.text_preset],
}}
>
{sticker.type === "emoji" && sticker.content}
{sticker.type === "text" && sticker.content}
{sticker.type === "image" && (
<img
src={sticker.content}
alt="sticker"
style={{
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
)}
</div>
</div>
)
@@ -1,130 +0,0 @@
/**
* 选中贴纸的属性编辑器
*/
import React from "react"
import type { StickerItem } from "@/pages/editing-planner/types"
import { StickerPreview } from "./StickerPreview"
import { TextStickerPropsEditor } from "./TextStickerPropsEditor"
interface StickerPropsEditorProps {
sticker: StickerItem
totalDuration: number
onUpdate: (id: string, partial: Partial<StickerItem>) => void
}
const StickerPropsEditor: React.FC<StickerPropsEditorProps> = ({
sticker,
totalDuration,
onUpdate,
}) => {
return (
<div className="sticker-props-section">
<div className="sticker-section-title"></div>
{/* 位置 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label"> X</span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.x}
onChange={(e) => onUpdate(sticker.id, { x: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.x}%</span>
</div>
<div className="sticker-prop-row">
<span className="sticker-prop-label"> Y</span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.y}
onChange={(e) => onUpdate(sticker.id, { y: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.y}%</span>
</div>
{/* 尺寸 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="range"
className="sticker-prop-slider"
min={5}
max={50}
value={sticker.width}
onChange={(e) =>
onUpdate(sticker.id, {
width: Number(e.target.value),
height: Number(e.target.value),
})
}
/>
<span className="sticker-prop-value">{sticker.width}%</span>
</div>
{/* 旋转 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="range"
className="sticker-prop-slider"
min={-180}
max={180}
value={sticker.rotation}
onChange={(e) => onUpdate(sticker.id, { rotation: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.rotation}°</span>
</div>
{/* 透明度 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.opacity}
onChange={(e) => onUpdate(sticker.id, { opacity: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.opacity}%</span>
</div>
{/* 时间 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="number"
className="sticker-prop-number"
min={0}
max={totalDuration}
step={0.1}
value={sticker.start_time}
onChange={(e) => onUpdate(sticker.id, { start_time: Number(e.target.value) })}
/>
<span className="sticker-prop-label"></span>
<input
type="number"
className="sticker-prop-number"
min={0}
max={totalDuration}
step={0.1}
value={sticker.duration}
onChange={(e) => onUpdate(sticker.id, { duration: Number(e.target.value) })}
/>
</div>
{/* 文字贴纸特有属性 */}
<TextStickerPropsEditor sticker={sticker} onUpdate={onUpdate} />
{/* 预览 */}
<StickerPreview sticker={sticker} />
</div>
)
}
export default StickerPropsEditor
@@ -1,57 +0,0 @@
import React from "react"
import type { StickerItem, TextStickerPreset } from "@/pages/editing-planner/types"
import { TEXT_STICKER_PRESET_LABELS } from "@/pages/editing-planner/constants/sticker"
interface TextStickerPropsEditorProps {
sticker: StickerItem
onUpdate: (id: string, partial: Partial<StickerItem>) => void
}
export const TextStickerPropsEditor: React.FC<TextStickerPropsEditorProps> = ({
sticker,
onUpdate,
}) => {
if (sticker.type !== "text") return null
return (
<>
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<select
className="sticker-prop-select"
value={sticker.text_preset}
onChange={(e) =>
onUpdate(sticker.id, { text_preset: e.target.value as TextStickerPreset })
}
>
{(Object.keys(TEXT_STICKER_PRESET_LABELS) as TextStickerPreset[]).map((p) => (
<option key={p} value={p}>
{TEXT_STICKER_PRESET_LABELS[p]}
</option>
))}
</select>
</div>
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="range"
className="sticker-prop-slider"
min={12}
max={72}
value={sticker.font_size}
onChange={(e) => onUpdate(sticker.id, { font_size: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.font_size}px</span>
</div>
<div className="sticker-prop-row">
<span className="sticker-prop-label"></span>
<input
type="color"
className="sticker-prop-color"
value={sticker.text_color}
onChange={(e) => onUpdate(sticker.id, { text_color: e.target.value })}
/>
</div>
</>
)
}
@@ -1,62 +0,0 @@
/**
* TTS 滑块组件(语速/语调/音量)
*/
import React from "react"
import { Slider } from "antd"
interface TtsSliderProps {
label: string
value: number
min: number
max: number
step: number
unit?: string
onChange: (val: number) => void
marks?: string[]
tooltipFormatter?: (v: number) => string
}
const TtsSlider: React.FC<TtsSliderProps> = ({
label,
value,
min,
max,
step,
unit = "",
onChange,
marks,
tooltipFormatter,
}) => {
const displayValue =
unit === "x"
? `${value.toFixed(2)}x`
: label === "语调"
? `${value > 0 ? "+" : ""}${value} 半音`
: `${value}${unit}`
return (
<div className="tts-slider-section">
<div className="tts-slider-header">
<span className="tts-slider-label">{label}</span>
<span className="tts-slider-value">{displayValue}</span>
</div>
<Slider
min={min}
max={max}
step={step}
value={value}
onChange={(v) => onChange(v as number)}
tooltip={tooltipFormatter ? { formatter: (v) => tooltipFormatter(v as number) } : undefined}
/>
{marks && (
<div className="tts-slider-marks">
{marks.map((m, i) => (
<span key={i}>{m}</span>
))}
</div>
)}
</div>
)
}
export default TtsSlider
@@ -1,50 +0,0 @@
/**
* TTS 音色选择组件
*/
import React from "react"
import type { TTSVoice } from "@/api/tts"
import { VOICE_CATEGORY_MAP } from "../../constants/tts"
interface VoiceSelectorProps {
voices: TTSVoice[]
voicesLoading: boolean
selectedVoiceId: string
onVoiceSelect: (voiceId: string) => void
}
const VoiceSelector: React.FC<VoiceSelectorProps> = ({
voices,
voicesLoading,
selectedVoiceId,
onVoiceSelect,
}) => {
const voiceCategories = Object.entries(VOICE_CATEGORY_MAP)
return (
<div className="tts-voice-section">
<div className="tts-voice-label">
{voicesLoading && <span className="tts-voice-loading">...</span>}
</div>
<div className="tts-voice-grid">
{voiceCategories.map(([cat, info]) => {
const voice = voices.find((v) => v.category === cat)
const isSelected = voice && selectedVoiceId === voice.id
return (
<button
key={cat}
className={`tts-voice-card${isSelected ? " active" : ""}`}
onClick={() => voice && onVoiceSelect(voice.id)}
disabled={!voice || voicesLoading}
>
<span className="tts-voice-card-icon">{info.icon}</span>
<span className="tts-voice-card-name">{voice?.name || info.label}</span>
</button>
)
})}
</div>
</div>
)
}
export default VoiceSelector
@@ -4,6 +4,7 @@
import type { ClipData } from "./clip"
import type { TemplateMode } from "@/api/editing-planner"
import type { AssetItem } from "@/api/assets"
import type { TitleConfig } from "@/api/template-editor"
export interface SubtitleSettings {
enabled: boolean
@@ -28,6 +29,10 @@ export interface BgmSettings {
}
export interface ClipPropertiesPanelProps {
/** 标题配置 — #1789 */
titleConfig?: TitleConfig
/** 标题配置变更 */
onTitleConfigChange?: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
selectedClip: ClipData | null
subtitleSettings: SubtitleSettings
bgmSettings: BgmSettings
@@ -13,6 +13,8 @@ export const formatTrimTime = (sec: number): string => {
/** 生成时间标尺刻度 */
export const generateRulerMarks = (totalDuration: number, step: number): number[] => {
const marks: number[] = []
// #1790: 无片段时不显示时间刻度
if (totalDuration <= 0) return marks
for (let t = 0; t <= totalDuration + step; t += step) {
marks.push(t)
}
+20 -1
View File
@@ -231,9 +231,13 @@ const GeneratePage: React.FC = () => {
/* ── 批量变体真实片段(#1744):后端独立选片,预览即成片;失败静默降级本地模拟 ──
仅批量(N>1)且在第 4 步预览时申请,避免选素材阶段频繁请求;
变体 0 沿用草稿 plan(与单视频一致),变体 1..N-1 后端 reselect 独立选片 */
// P0 fix:批量变体计划请求需携带配音参数,避免后端按"无配音"选片导致 clips 时长与配音错位
const batchVoiceLibraryId =
voiceMode === "clone" ? selectedClonedVoice || selectedVoice || "" : selectedVoice || ""
const {
clipsByVariant: variantClips,
planIdsByVariant: variantPlanIds,
voiceDurationsByVariant: variantVoiceDurations,
loading: variantClipsLoading,
error: variantClipsError,
retry: retryVariantClips,
@@ -243,6 +247,9 @@ const GeneratePage: React.FC = () => {
templateId: selectedTemplate || "",
assetIds: previewAssetIds,
sourcePlanId: storedSourceEditPlanId || sourceEditPlanId || "",
voiceLibraryId: batchVoiceLibraryId,
voiceLibraryIds: voiceLibraryIds || [],
voiceModePerVideo,
})
/* ── 批量变体配音预览 URL(#1750):独立模式每变体挂各自配音,共用模式全挂同一条;
@@ -476,6 +483,7 @@ const GeneratePage: React.FC = () => {
titles={previewTitles}
titleSettings={titleSettings}
voiceAudioUrls={variantVoiceAudioUrls}
voiceDurations={variantVoiceDurations}
variantClips={variantClips}
clipsLoading={variantClipsLoading}
clipsError={variantClipsError}
@@ -548,10 +556,21 @@ const GeneratePage: React.FC = () => {
<div
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
alignItems: "center",
marginTop: 16,
}}
>
<h2
style={{
textAlign: "center",
marginBottom: 12,
fontSize: "1.5rem",
fontWeight: 600,
}}
>
🎬
</h2>
<div
style={{
display: "flex",

Some files were not shown because too many files have changed in this diff Show More