feat: AI Brand Voice Translator integration and Mesh Content fix
This commit is contained in:
@@ -14,15 +14,19 @@ function resolveBrandValue(
|
||||
): string {
|
||||
if (userValue && userValue.trim()) return userValue;
|
||||
|
||||
// Resolve from brand asset ID (e.g. a logo badge piece)
|
||||
if (field.brandAssetId && brandContent) {
|
||||
const asset = brandContent.find(a => a.id === field.brandAssetId);
|
||||
if (asset) {
|
||||
// For images, return the thumbnail/image URL
|
||||
if (asset.content.imageUrl) return asset.content.imageUrl;
|
||||
if (asset.thumbnail) return asset.thumbnail;
|
||||
// For text cards, return the text
|
||||
if (asset.content.text) return asset.content.text;
|
||||
// Resolve from brand asset ID (e.g. a logo badge piece or generic brand asset)
|
||||
if (field.brandAssetId) {
|
||||
if (designMD.brandAssets) {
|
||||
const asset = designMD.brandAssets.find(a => a.id === field.brandAssetId);
|
||||
if (asset) return asset.url;
|
||||
}
|
||||
if (brandContent) {
|
||||
const asset = brandContent.find(a => a.id === field.brandAssetId);
|
||||
if (asset) {
|
||||
if (asset.content.imageUrl) return asset.content.imageUrl;
|
||||
if (asset.thumbnail) return asset.thumbnail;
|
||||
if (asset.content.text) return asset.content.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +36,6 @@ function resolveBrandValue(
|
||||
case 'brand-name': return company?.name || designMD.brandName || 'Tu Marca';
|
||||
case 'tagline': return company?.tagline || '';
|
||||
case 'logo': return designMD.logoUrl || '';
|
||||
case 'intro-video': return designMD.introVideoUrl || '';
|
||||
case 'outro-video': return designMD.outroVideoUrl || '';
|
||||
case 'primary-color': return designMD.primaryColor;
|
||||
case 'secondary-color': return designMD.secondaryColor;
|
||||
// Social handles
|
||||
@@ -90,20 +92,6 @@ export function getTemplateDuration(
|
||||
designMD?: DesignMD,
|
||||
): number {
|
||||
return template.scenes.reduce((sum, scene) => {
|
||||
// If it's a brand segment, read duration from designMD instead of template
|
||||
if (scene.segmentSource === 'brand' && designMD) {
|
||||
if (scene.type === 'intro') {
|
||||
if (!designMD.introVideoUrl) return sum; // Skipped
|
||||
const frames = designMD.introDurationFrames || (scene.durationSeconds * 30);
|
||||
return sum + (frames / 30);
|
||||
}
|
||||
if (scene.type === 'outro') {
|
||||
if (!designMD.outroVideoUrl) return sum; // Skipped
|
||||
const frames = designMD.outroDurationFrames || (scene.durationSeconds * 30);
|
||||
return sum + (frames / 30);
|
||||
}
|
||||
}
|
||||
|
||||
// If we know the actual video duration for this scene, use it
|
||||
if (videoDurations && videoDurations[scene.id]) {
|
||||
return sum + videoDurations[scene.id];
|
||||
@@ -140,18 +128,8 @@ export function compileExpressToTimeline(
|
||||
// Default to template's duration
|
||||
let sceneDuration = scene.durationSeconds;
|
||||
|
||||
// Override if brand segment
|
||||
if (scene.segmentSource === 'brand' && designMD) {
|
||||
if (scene.type === 'intro') {
|
||||
if (!designMD.introVideoUrl) { sceneDuration = 0; }
|
||||
else { sceneDuration = (designMD.introDurationFrames || (scene.durationSeconds * 30)) / 30; }
|
||||
}
|
||||
if (scene.type === 'outro') {
|
||||
if (!designMD.outroVideoUrl) { sceneDuration = 0; }
|
||||
else { sceneDuration = (designMD.outroDurationFrames || (scene.durationSeconds * 30)) / 30; }
|
||||
}
|
||||
} else if (videoDurations && videoDurations[scene.id]) {
|
||||
// Override if user uploaded video
|
||||
// Override if user uploaded video
|
||||
if (videoDurations && videoDurations[scene.id]) {
|
||||
sceneDuration = videoDurations[scene.id];
|
||||
}
|
||||
|
||||
@@ -163,50 +141,7 @@ export function compileExpressToTimeline(
|
||||
if ((scene.type === 'intro' || scene.type === 'outro') && scene.segmentSource) {
|
||||
const isIntro = scene.type === 'intro';
|
||||
|
||||
if (scene.segmentSource === 'brand') {
|
||||
// Resolve video from DesignMD
|
||||
const videoUrl = isIntro ? designMD.introVideoUrl : designMD.outroVideoUrl;
|
||||
if (!videoUrl) {
|
||||
// Brand has no video for this segment — skip entirely
|
||||
// Don't advance frameOffset so it doesn't create a gap
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert from center-based coords (SegmentVideoFrame) to top-left coords (CompositionElement)
|
||||
const segW = scene.segmentVideoW ?? 100;
|
||||
const segH = scene.segmentVideoH ?? 100;
|
||||
const segX = (scene.segmentVideoX ?? 50) - segW / 2;
|
||||
const segY = (scene.segmentVideoY ?? 50) - segH / 2;
|
||||
|
||||
elements.push({
|
||||
id: `express-segment-${scene.id}`,
|
||||
type: 'video',
|
||||
content: videoUrl,
|
||||
x: segX,
|
||||
y: segY,
|
||||
startFrame: sceneStart,
|
||||
endFrame: sceneEnd,
|
||||
width: segW,
|
||||
height: segH,
|
||||
// CompositionElement's isFullscreenBrand path reads w/h (not width/height)
|
||||
w: segW,
|
||||
h: segH,
|
||||
objectFit: (isIntro
|
||||
? (designMD.introVideoFit || 'cover')
|
||||
: (designMD.outroVideoFit || 'cover')) as 'cover' | 'contain' | 'fill',
|
||||
containBgColor: isIntro ? designMD.introVideoBgColor : designMD.outroVideoBgColor,
|
||||
layerId: 'layer-express-bg',
|
||||
isBrandElement: true,
|
||||
isLocked: true,
|
||||
elementName: isIntro ? 'Intro de marca' : 'Outro de marca',
|
||||
scale: 1,
|
||||
rotation: 0,
|
||||
opacity: 100,
|
||||
transitionIn: scene.segmentTransition
|
||||
? { type: scene.segmentTransition.type as TransitionType, duration: scene.segmentTransition.duration }
|
||||
: undefined,
|
||||
});
|
||||
} else if (scene.segmentSource === 'form') {
|
||||
if (scene.segmentSource === 'form') {
|
||||
// Form-sourced: look up the uploaded video from fieldData
|
||||
const segmentFieldId = `segment-${scene.id}`;
|
||||
const videoUrl = fieldData[segmentFieldId] || '';
|
||||
@@ -380,16 +315,7 @@ export function compileExpressToTimeline(
|
||||
...(field.type === 'image' || field.type === 'video' ? {
|
||||
width: position.w,
|
||||
height: position.h,
|
||||
objectFit: ((field.nature === 'brand-variable' && field.brandSource === 'intro-video')
|
||||
? (designMD.introVideoFit || field.style.mediaFit || 'cover')
|
||||
: (field.nature === 'brand-variable' && field.brandSource === 'outro-video')
|
||||
? (designMD.outroVideoFit || field.style.mediaFit || 'cover')
|
||||
: (field.style.mediaFit || 'cover')) as 'cover' | 'contain' | 'fill',
|
||||
containBgColor: (field.nature === 'brand-variable' && field.brandSource === 'intro-video')
|
||||
? designMD.introVideoBgColor
|
||||
: (field.nature === 'brand-variable' && field.brandSource === 'outro-video')
|
||||
? designMD.outroVideoBgColor
|
||||
: undefined,
|
||||
objectFit: (field.style.mediaFit || 'cover') as 'cover' | 'contain' | 'fill',
|
||||
} : {}),
|
||||
...(field.type === 'shape' ? {
|
||||
width: position.w,
|
||||
|
||||
Reference in New Issue
Block a user