fix: source_edit_plan_id 正确传递 + clips时长与预览一致 #1473

Merged
auto-approve-bot merged 1 commits from fix/source-edit-plan-id-and-clips-consistency into develop 2026-08-23 23:46:00 +08:00
4 changed files with 30 additions and 3 deletions
+2 -1
View File
@@ -72,6 +72,7 @@ const GeneratePage: React.FC = () => {
autoSubtitles,
bgm,
editPlanId,
sourceEditPlanId,
previewVideo,
setPreviewVideo,
previewModalOpen,
@@ -171,7 +172,7 @@ const GeneratePage: React.FC = () => {
autoSubtitles,
bgm,
generateCount,
sourceEditPlanId: editPlanId,
sourceEditPlanId,
})
/* ================================================================
@@ -82,6 +82,13 @@ export interface GenerateFormState {
editPlanId: string | null
planConfigStr: string | null
/**
* 传给 Worker 的 source_edit_plan_id。
* 优先使用 URL 中的 edit_plan_idURL 没有时回退为 selectedTemplate(模板 ID),
* 因为 Step2 的 clips 就是用 selectedTemplate 作为 plan_id 写入的。
*/
sourceEditPlanId: string | null
/* 预览弹窗 */
previewVideo: GeneratedVideo | null
setPreviewVideo: (v: GeneratedVideo | null) => void
@@ -100,6 +107,11 @@ export const useGenerateFormState = (): GenerateFormState => {
/* ── 模板选择 ── */
const { selectedTemplate, setSelectedTemplate, userTemplates } = useTemplateSelection()
/* ── source_edit_plan_idURL 优先,否则回退到 selectedTemplate ── */
// selectedTemplate 是异步加载的(react-query),组件重新渲染时此值会自动更新,
// 因此最终传给 useGenerateVideo 的 sourceEditPlanId 能在模板就绪后拿到正确值。
const sourceEditPlanId = editPlanId || selectedTemplate || null
/* ── 素材 ── */
const [selectedMaterials, setSelectedMaterials] = useState<string[]>([])
const [materialMode, setMaterialMode] = useState<"manual" | "auto">("manual")
@@ -189,6 +201,7 @@ export const useGenerateFormState = (): GenerateFormState => {
autoSubtitles,
bgm,
editPlanId,
sourceEditPlanId,
planConfigStr,
previewVideo,
setPreviewVideo,
@@ -154,6 +154,7 @@ export function useStep6Cover({
const previewResp = await createPreview({
template_id: selectedTemplate,
asset_ids: assetIds,
source_edit_plan_id: selectedTemplate,
duration: duration || 30,
...(titleSettings?.title
? {
@@ -6,7 +6,14 @@
* tplSeg = templateSegments[i] || lastSegment
* segDuration = clamp(assetDuration, tplSeg.duration_min, tplSeg.duration_max)
* start_time = 0
* duration = segDuration
* // 关键:预览播放器中 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"
@@ -47,10 +54,15 @@ export function buildClipsFromAssets({
? 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: segDuration,
duration,
order: i,
}
})