024aca3557
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 if frontend-only change (pull_request) Successful in 1m17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m48s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m51s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 3m8s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 3m13s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m27s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 22s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 46s
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
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m17s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m42s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m34s
AI Code Review / AI Code Review (pull_request) Successful in 5m23s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 7m44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m47s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 7m10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 56s
CI/CD Pipeline / Integration Tests (push) Successful in 3m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m13s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m41s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m50s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m47s
CI/CD Pipeline / Unit Tests (push) Failing after 17m39s
CI/CD Pipeline / CI Gate (push) Has been skipped
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 / Unit Tests (pull_request) Failing after 15m8s
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 7s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
70 lines
2.7 KiB
TypeScript
70 lines
2.7 KiB
TypeScript
/**
|
||
* 将选中素材 + 模板 segments 构建为 edit_plan_clips 写入数据。
|
||
*
|
||
* 逻辑必须与 FrontendPreviewPlayer.tsx 中 buildPlaybackSegments 完全一致:
|
||
* assetDuration = asset.duration || asset.metadata?.duration || 30
|
||
* tplSeg = templateSegments[i] || lastSegment
|
||
* segDuration = clamp(assetDuration, tplSeg.duration_min, tplSeg.duration_max)
|
||
* start_time = 0
|
||
* // 关键:预览播放器中 endTime = min(startTime + segDuration, assetDuration)
|
||
* // 因此 clips.duration 也必须用 min(segDuration, assetDuration) 截断,
|
||
* // 避免素材实际时长比 clamp 后的 segDuration 短时,Worker 尝试读取不存在的片段
|
||
* duration = min(segDuration, assetDuration)
|
||
*
|
||
* 预览播放器(Canvas 实时预览)直接在内存中构建 segments 播放,不读 edit_plan_clips;
|
||
* 本函数产出的 clips 写入 DB 后由 Worker 渲染。两边用完全相同的时长计算,
|
||
* 保证用户在编辑过程中看到的预览与最终生成视频一致。
|
||
*/
|
||
import type { AssetItem } from "@/api/assets"
|
||
import type { TemplateSegment } from "@/api/templates/types"
|
||
import type { EditPlanClipInput } from "@/api/template-editor"
|
||
|
||
interface BuildClipsOptions {
|
||
/** 选中的素材 ID 列表(按选择顺序) */
|
||
selectedIds: string[]
|
||
/** 已加载的素材列表(用于查 duration) */
|
||
materials: AssetItem[]
|
||
/** 智能匹配返回的素材(auto 模式下可能不在 materials 列表中) */
|
||
smartMatchedAssets?: AssetItem[]
|
||
/** 模板 segments */
|
||
templateSegments?: TemplateSegment[]
|
||
}
|
||
|
||
export function buildClipsFromAssets({
|
||
selectedIds,
|
||
materials,
|
||
smartMatchedAssets = [],
|
||
templateSegments = [],
|
||
}: BuildClipsOptions): EditPlanClipInput[] {
|
||
if (!selectedIds.length) return []
|
||
|
||
// 合并两个素材来源,建立 id → asset 索引
|
||
const assetMap = new Map<string, AssetItem>()
|
||
for (const a of materials) assetMap.set(a.id, a)
|
||
for (const a of smartMatchedAssets) assetMap.set(a.id, a)
|
||
|
||
const lastSeg = templateSegments[templateSegments.length - 1]
|
||
|
||
return selectedIds.map((assetId, i) => {
|
||
const asset = assetMap.get(assetId)
|
||
const assetDuration = asset?.duration || asset?.metadata?.duration || 30
|
||
|
||
const tplSeg = templateSegments[i] || lastSeg
|
||
const segDuration = tplSeg
|
||
? Math.min(tplSeg.duration_max, Math.max(tplSeg.duration_min, assetDuration))
|
||
: Math.min(assetDuration, 10)
|
||
|
||
// 与 FrontendPreviewPlayer.buildPlaybackSegments 中
|
||
// endTime = Math.min(startTime + segDuration, assetDuration)
|
||
// 保持一致:duration 不能超过素材实际时长
|
||
const duration = Math.min(segDuration, assetDuration)
|
||
|
||
return {
|
||
asset_id: assetId,
|
||
start_time: 0,
|
||
duration,
|
||
order: i,
|
||
}
|
||
})
|
||
}
|