diff --git a/src/components/brand/BrandTabMedia.tsx b/src/components/brand/BrandTabMedia.tsx index 8abd210..88b2fa5 100644 --- a/src/components/brand/BrandTabMedia.tsx +++ b/src/components/brand/BrandTabMedia.tsx @@ -131,9 +131,16 @@ export const BrandTabMedia: React.FC = ({ designMD, handleDe compact accept="audio/*" label="Subir audio" - onFiles={(files) => { - const url = URL.createObjectURL(files[0]); - handleDesignChange('brandAudioUrl', url); + onFiles={async (files) => { + const formData = new FormData(); + formData.append('file', files[0]); + try { + const res = await fetch('/api/upload', { method: 'POST', body: formData }); + const data = await res.json(); + if (data.url) handleDesignChange('brandAudioUrl', data.url); + } catch (err) { + console.error('Audio upload failed:', err); + } }} /> @@ -231,9 +238,16 @@ const VideoUploadSimple: React.FC<{ compact accept="video/*" label="Subir archivo" - onFiles={(files) => { - const url = URL.createObjectURL(files[0]); - onUrlChange(url); + onFiles={async (files) => { + const formData = new FormData(); + formData.append('file', files[0]); + try { + const res = await fetch('/api/upload', { method: 'POST', body: formData }); + const data = await res.json(); + if (data.url) onUrlChange(data.url); + } catch (err) { + console.error('Video upload failed:', err); + } }} /> diff --git a/src/components/express/ExpressEditor.tsx b/src/components/express/ExpressEditor.tsx index 9cafde5..e4e30d6 100644 --- a/src/components/express/ExpressEditor.tsx +++ b/src/components/express/ExpressEditor.tsx @@ -8,6 +8,7 @@ import { StoryboardView } from './StoryboardView'; import { SceneFieldEditor } from './SceneFieldEditor'; import { ExpressStylePanel } from './ExpressStylePanel'; import { compileExpressToTimeline, getAspectDimensions, getTemplateDuration } from '../../utils/expressCompiler'; +import { useVideoDurations } from '../../hooks/useVideoDurations'; interface ExpressEditorProps { designMD: DesignMD; @@ -62,13 +63,16 @@ export const ExpressEditor: React.FC = ({ setFieldData(prev => ({ ...prev, [fieldId]: value })); }, []); + // Probe actual video durations for form-sourced scenes + const videoDurations = useVideoDurations(selectedTemplate, fieldData); + // Compile template to timeline const compiled = useMemo(() => { if (!selectedTemplate) return null; - return compileExpressToTimeline(selectedTemplate, fieldData, designMD, company); - }, [selectedTemplate, fieldData, designMD, company]); + return compileExpressToTimeline(selectedTemplate, fieldData, designMD, company, videoDurations); + }, [selectedTemplate, fieldData, designMD, company, videoDurations]); - const totalDuration = selectedTemplate ? getTemplateDuration(selectedTemplate) : 0; + const totalDuration = selectedTemplate ? getTemplateDuration(selectedTemplate, videoDurations) : 0; const fps = 30; const totalFrames = Math.max(30, totalDuration * fps); diff --git a/src/components/express/SceneFieldEditor.tsx b/src/components/express/SceneFieldEditor.tsx index 2a712d9..d52a3cd 100644 --- a/src/components/express/SceneFieldEditor.tsx +++ b/src/components/express/SceneFieldEditor.tsx @@ -178,11 +178,18 @@ export const SceneFieldEditor: React.FC = ({ type="file" accept="image/*,video/*" className="hidden" - onChange={(e) => { + onChange={async (e) => { const file = e.target.files?.[0]; if (file) { - const url = URL.createObjectURL(file); - onFieldChange(field.id, url); + const formData = new FormData(); + formData.append('file', file); + try { + const res = await fetch('/api/upload', { method: 'POST', body: formData }); + const data = await res.json(); + if (data.url) onFieldChange(field.id, data.url); + } catch (err) { + console.error('Media upload failed:', err); + } } }} /> diff --git a/src/components/shared/TemplateFieldInput.tsx b/src/components/shared/TemplateFieldInput.tsx index d12d356..d146c34 100644 --- a/src/components/shared/TemplateFieldInput.tsx +++ b/src/components/shared/TemplateFieldInput.tsx @@ -169,11 +169,18 @@ export const TemplateFieldInput: React.FC = ({ type="file" accept={isVideoField ? 'video/*' : 'image/*'} className="hidden" - onChange={(e) => { + onChange={async (e) => { const file = e.target.files?.[0]; if (file) { - const url = URL.createObjectURL(file); - onChange(url); + const formData = new FormData(); + formData.append('file', file); + try { + const res = await fetch('/api/upload', { method: 'POST', body: formData }); + const data = await res.json(); + if (data.url) onChange(data.url); + } catch (err) { + console.error('Media upload failed:', err); + } } }} /> diff --git a/src/electron/main.ts b/src/electron/main.ts index 71da5d4..11489d2 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -269,8 +269,15 @@ app.whenReady().then(async () => { return; } - // 4. Set BRADLY_SERVE_URL for the renderer to find the app - process.env.BRADLY_SERVE_URL = `http://127.0.0.1:${expressPort}`; + // 4. Set BRADLY_SERVE_URL for the headless renderer (Puppeteer) + if (process.env.NODE_ENV === 'development') { + // In dev, the React app is served by electron-vite's Vite dev server + const rendererUrl = process.env.ELECTRON_RENDERER_URL || 'http://localhost:5173'; + process.env.BRADLY_SERVE_URL = rendererUrl; + } else { + // In prod, Express serves both API and static renderer files + process.env.BRADLY_SERVE_URL = `http://127.0.0.1:${expressPort}`; + } // 5. Create the main window createWindow(); diff --git a/src/engine/components/MediaAudio.tsx b/src/engine/components/MediaAudio.tsx index 867af03..25c4e6a 100644 --- a/src/engine/components/MediaAudio.tsx +++ b/src/engine/components/MediaAudio.tsx @@ -3,6 +3,9 @@ * * An