fix: source_edit_plan_id 正确传递 + clips时长与预览一致 #1473
@@ -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_id;URL 没有时回退为 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_id:URL 优先,否则回退到 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,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user