From 560a413c1e8c4966675f7a60e1cc7dc5b775def3 Mon Sep 17 00:00:00 2001 From: "kevinguevaradevia@gmail.com" Date: Tue, 2 Jun 2026 09:58:37 -0500 Subject: [PATCH] fix: dynamic video duration across ProductionForm + LivePreviewCanvas - Expanded useVideoDurations to detect both form-sourced segments AND editable-slot video fields inside content scenes - Wired videoDurations into ProductionForm (getTemplateDuration, compileExpressToTimeline, LivePreviewCanvas, ExportModal) - LivePreviewCanvas now accepts videoDurations prop - Simplified getTemplateDuration/compiler: any scene with a known video duration uses it, regardless of segmentSource type - A 33s uploaded video now creates a 33s timeline, not 5s --- src/components/dashboard/ProductionForm.tsx | 11 ++- src/components/shared/LivePreviewCanvas.tsx | 9 ++- src/hooks/useVideoDurations.ts | 79 ++++++++++++++------- src/utils/expressCompiler.ts | 10 +-- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/components/dashboard/ProductionForm.tsx b/src/components/dashboard/ProductionForm.tsx index 6c0ff53..c75a18b 100644 --- a/src/components/dashboard/ProductionForm.tsx +++ b/src/components/dashboard/ProductionForm.tsx @@ -14,6 +14,7 @@ import { TemplateFieldInput } from '../shared/TemplateFieldInput'; import { LivePreviewCanvas } from '../shared/LivePreviewCanvas'; import { migrateExpressFields } from '../../context/TemplateBuilderContext'; import { useBatchProduction } from '../../hooks/useBatchProduction'; +import { useVideoDurations } from '../../hooks/useVideoDurations'; import { BatchDataPanel } from './BatchDataPanel'; import { exportBatchAsZip, BatchExportProgress } from '../../utils/batchExporter'; @@ -97,16 +98,19 @@ export const ProductionForm: React.FC = ({ const playerRef = useRef(null); + // Probe actual video durations for dynamic timeline + const videoDurations = useVideoDurations(template, fieldData); + const designMD = brand.design; const fps = 30; - const totalDuration = getTemplateDuration(template); + const totalDuration = getTemplateDuration(template, videoDurations); const totalFrames = Math.max(30, totalDuration * fps); // ─── Compile for ExportModal (only when modal is open — LivePreviewCanvas handles its own compile) ─── const compiled = useMemo( () => { if (!showExportModal) return { elements: [], layers: [] }; - const result = compileExpressToTimeline(template, fieldData, designMD, brand); + const result = compileExpressToTimeline(template, fieldData, designMD, brand, videoDurations); result.elements = result.elements.map(el => { const fieldId = el.sourceFieldId; const fitOverride = fieldId ? mediaFits[fieldId] : undefined; @@ -121,7 +125,7 @@ export const ProductionForm: React.FC = ({ }); return result; }, - [showExportModal, template, fieldData, designMD, brand, mediaFits, containBgColors] + [showExportModal, template, fieldData, designMD, brand, mediaFits, containBgColors, videoDurations] ); // ─── Collect all TemplateFields across all scenes ─── @@ -600,6 +604,7 @@ export const ProductionForm: React.FC = ({ activeSceneId={activeSceneId} onSceneChange={setActiveSceneId} playerRef={playerRef} + videoDurations={videoDurations} statusLabel={ batch.isBatchMode ? (batch.pieceCount > 0 ? `Pieza ${activeBatchPieceIndex + 1} de ${batch.pieceCount}` : 'Sin piezas') diff --git a/src/components/shared/LivePreviewCanvas.tsx b/src/components/shared/LivePreviewCanvas.tsx index 6eb6404..15f096b 100644 --- a/src/components/shared/LivePreviewCanvas.tsx +++ b/src/components/shared/LivePreviewCanvas.tsx @@ -36,6 +36,8 @@ export interface LivePreviewCanvasProps { statusLabel?: string; /** Whether all required fields are complete */ isComplete?: boolean; + /** Video duration overrides per scene (from useVideoDurations) */ + videoDurations?: Record; } /** Format frame number to mm:ss */ @@ -66,6 +68,7 @@ export const LivePreviewCanvas: React.FC = ({ playerRef: externalRef, statusLabel, isComplete = false, + videoDurations, }) => { const internalRef = useRef(null); const playerRef = externalRef || internalRef; @@ -76,13 +79,13 @@ export const LivePreviewCanvas: React.FC = ({ const isScrubbing = useRef(false); const fps = 30; - const totalDuration = getTemplateDuration(template); + const totalDuration = getTemplateDuration(template, videoDurations); const totalFrames = Math.max(30, totalDuration * fps); const dimensions = getAspectDimensions(template.aspectRatio); // Compile template to timeline (reactive to fieldData + mediaFits) const compiled = useMemo(() => { - const result = compileExpressToTimeline(template, fieldData, designMD, brand); + const result = compileExpressToTimeline(template, fieldData, designMD, brand, videoDurations); // Strip transitions and apply mediaFit overrides result.elements = result.elements.map(el => { const fieldId = el.sourceFieldId; @@ -97,7 +100,7 @@ export const LivePreviewCanvas: React.FC = ({ }; }); return result; - }, [template, fieldData, designMD, brand, mediaFits, containBgColors]); + }, [template, fieldData, designMD, brand, mediaFits, containBgColors, videoDurations]); const playerInputProps = useMemo(() => ({ designMD, diff --git a/src/hooks/useVideoDurations.ts b/src/hooks/useVideoDurations.ts index 2b68685..3995d43 100644 --- a/src/hooks/useVideoDurations.ts +++ b/src/hooks/useVideoDurations.ts @@ -2,13 +2,18 @@ import { useState, useEffect } from 'react'; import { ExpressTemplate } from '../types'; /** - * useVideoDurations — Probes actual video durations for form-sourced scenes. + * useVideoDurations — Probes actual video durations for scenes that contain + * user-uploaded videos. * - * When a user uploads a video to a form field, this hook creates a temporary - *