Sistema de Expresiones Dinámicas

El sistema de videomaker soporta expresiones matemáticas para calcular dinámicamente los valores de start, length, y también en effects y transitions, permitiendo referencias a otros clips y operaciones complejas.

Sintaxis Básica

Referencias a Clips

Puedes referenciar cualquier clip usando clipX donde X es el índice (order) del clip:

{
  "start": "clip0.end",
  "length": "clip1.duration"
}

Propiedades Disponibles

Cada clip tiene las siguientes propiedades:

  • clipX.start - Tiempo de inicio del clip X (en segundos)
  • clipX.end - Tiempo de fin del clip X (start + length)
  • clipX.length - Duración del clip X
  • clipX.duration - Alias de length

Atajo prev

Para mayor conveniencia, puedes usar prev para referenciar el clip anterior:

{
  "start": "prev.end",
  "length": "prev.duration"
}

Esto es equivalente a clip0.end si el clip actual es el clip 1.

Variable Especial length

Dentro de effects y transitions, puedes usar la variable length para referenciar la duración del clip actual:

{
  "effects": [{
    "type": "zoom",
    "duration": "length"  // Duración igual al clip
  }]
}

También puedes hacer operaciones:

{
  "effects": [{
    "type": "zoom",
    "duration": "length * 0.5"  // Mitad de la duración del clip
  }]
}

Operaciones Matemáticas

Operadores Soportados

  • + Suma: clip0.end + 2
  • - Resta: clip1.start - 0.5
  • * Multiplicación: clip2.duration * 2
  • / División: clip3.length / 2
  • () Paréntesis para prioridad: (clip1.duration + clip2.duration) / 2

Funciones Matemáticas

  • min(a, b) - Mínimo entre dos valores
  • max(a, b) - Máximo entre dos valores
  • abs(x) - Valor absoluto
  • floor(x) - Redondeo hacia abajo
  • ceil(x) - Redondeo hacia arriba
  • round(x) - Redondeo al entero más cercano

Ejemplos

Ejemplo 1: Secuencia Simple

{
  "timeline": [
    {
      "asset": { "type": "image", "src": "intro.png" },
      "start": 0,
      "length": 5
    },
    {
      "asset": { "type": "video", "src": "video.mp4" },
      "start": "prev.end",
      "length": 10
    },
    {
      "asset": { "type": "image", "src": "outro.png" },
      "start": "prev.end",
      "length": 5
    }
  ]
}

Resultado:

  • Clip 0: start=0, length=5 (0-5s)
  • Clip 1: start=5, length=10 (5-15s)
  • Clip 2: start=15, length=5 (15-20s)

Ejemplo 2: Overlap (Superposición)

{
  "timeline": [
    {
      "asset": { "type": "video", "src": "background.mp4" },
      "start": 0,
      "length": 30
    },
    {
      "asset": { "type": "text", "text": "Title" },
      "start": "clip0.start + 2",
      "length": 5
    },
    {
      "asset": { "type": "audio", "src": "music.mp3" },
      "start": "clip0.start",
      "length": "clip0.duration"
    }
  ]
}

Resultado:

  • Clip 0: start=0, length=30 (video de fondo)
  • Clip 1: start=2, length=5 (título aparece 2s después)
  • Clip 2: start=0, length=30 (audio sincronizado con video)

Ejemplo 3: Gaps (Espacios)

{
  "timeline": [
    {
      "asset": { "type": "image", "src": "scene1.png" },
      "start": 0,
      "length": 5
    },
    {
      "asset": { "type": "image", "src": "scene2.png" },
      "start": "prev.end + 1",
      "length": 5
    },
    {
      "asset": { "type": "image", "src": "scene3.png" },
      "start": "prev.end + 1",
      "length": 5
    }
  ]
}

Resultado:

  • Clip 0: 0-5s
  • Clip 1: 6-11s (1 segundo de gap)
  • Clip 2: 12-17s (1 segundo de gap)

Ejemplo 4: TTS con Audio Dinámico

{
  "timeline": [
    {
      "asset": {
        "type": "tts",
        "text": "Hola mundo",
        "voice": "Lucia"
      },
      "start": 0,
      "length": "{{audio.duration}}"
    },
    {
      "asset": { "type": "image", "src": "image.png" },
      "start": "prev.end",
      "length": 5
    }
  ]
}

Resultado:

  • Clip 0: start=0, length=duración_real_del_audio (ej: 1.2s)
  • Clip 1: start=1.2, length=5

Ejemplo 5: Sincronización Compleja

{
  "timeline": [
    {
      "asset": { "type": "video", "src": "intro.mp4" },
      "start": 0,
      "length": 3
    },
    {
      "asset": { "type": "video", "src": "main.mp4" },
      "start": "prev.end",
      "length": 10
    },
    {
      "asset": { "type": "image", "src": "logo.png" },
      "start": "clip0.start",
      "length": "clip0.duration + clip1.duration"
    },
    {
      "asset": { "type": "video", "src": "outro.mp4" },
      "start": "max(clip1.end, clip2.end)",
      "length": 3
    }
  ]
}

Resultado:

  • Clip 0: 0-3s (intro)
  • Clip 1: 3-13s (main video)
  • Clip 2: 0-13s (logo superpuesto durante intro + main)
  • Clip 3: 13-16s (outro empieza cuando termina el último clip)

Ejemplo 6: Cálculos Avanzados

{
  "timeline": [
    {
      "asset": { "type": "video", "src": "video1.mp4" },
      "start": 0,
      "length": 10
    },
    {
      "asset": { "type": "video", "src": "video2.mp4" },
      "start": "prev.end",
      "length": 8
    },
    {
      "asset": { "type": "text", "text": "Middle Point" },
      "start": "(clip0.duration + clip1.duration) / 2",
      "length": 2
    },
    {
      "asset": { "type": "image", "src": "thumbnail.png" },
      "start": "clip2.start - 1",
      "length": "clip2.duration + 2"
    }
  ]
}

Resultado:

  • Clip 0: 0-10s
  • Clip 1: 10-18s
  • Clip 2: 9s, length=2s (punto medio entre ambos videos)
  • Clip 3: 8-12s (1s antes del punto medio, hasta 1s después)

Compatibilidad con Formato Antiguo

El sistema sigue siendo compatible con el formato legacy:

{
  "start": 0,
  "length": "{{audio.duration}}"
}

Esto automáticamente se convierte a la duración real del audio procesado.

Valores Literales

También puedes usar números directamente:

{
  "start": 5,
  "length": 10
}

O valores decimales:

{
  "start": 2.5,
  "length": 1.75
}

Notas Importantes

  1. Orden de Procesamiento: Los clips se procesan en orden (por order). Puedes referenciar clips anteriores sin problema.

  2. Referencias Circulares: Evita referencias circulares (ej: clip1 depende de clip2, y clip2 depende de clip1).

  3. Clips No Encontrados: Si referencias un clip que no existe o aún no está resuelto, se usarán valores por defecto.

  4. Precisión: Todos los cálculos se hacen en segundos con precisión de punto flotante.

  5. Validación: Las expresiones inválidas se detectan y se usan valores por defecto para evitar fallos.

API de Validación

Puedes validar expresiones antes de enviarlas:

import { expressionResolver } from '~/services/expression-resolver.server';

const result = expressionResolver.validateExpression("clip0.end + 2");
if (!result.valid) {
  console.error('Invalid expression:', result.error);
}

Ejemplo 7: Expresiones en Effects y Transitions

{
  "timeline": [
    {
      "asset": { "type": "image", "src": "photo.jpg" },
      "start": 0,
      "length": "clip6.end - clip0.end",  // Duración dinámica
      "effects": [
        {
          "type": "zoom",
          "from": 1,
          "to": 1.2,
          "duration": "length",  // El efecto dura lo mismo que el clip
          "easing": "easeInOut"
        }
      ],
      "transition": {
        "in": [{
          "type": "fade",
          "duration": "length * 0.1"  // Fade dura 10% del clip
        }]
      }
    },
    {
      "asset": { "type": "video", "src": "video.mp4" },
      "start": "prev.end - 0.5",
      "length": 10,
      "effects": [
        {
          "type": "blur",
          "from": 0,
          "to": 5,
          "duration": "length / 2",  // Blur solo en la primera mitad
          "easing": "linear"
        }
      ]
    }
  ]
}

Resultado:

  • Clip 0: Zoom que dura exactamente lo mismo que el clip (duración dinámica)
  • Clip 0: Fade in que dura 10% de la duración del clip
  • Clip 1: Blur que dura 5 segundos (mitad de 10s)

Ejemplo 8: Sincronización Avanzada con Expresiones

{
  "timeline": [
    {
      "asset": {
        "type": "tts",
        "text": "Texto largo para narrar",
        "voice": "Lucia"
      },
      "start": 4,
      "length": "audio.length"
    },
    {
      "asset": { "type": "image", "src": "background.jpg" },
      "start": 0,
      "length": "clip0.end",  // Dura hasta que termina el audio
      "effects": [
        {
          "type": "zoom",
          "from": 1,
          "to": 1.3,
          "duration": "clip0.length",  // Zoom dura lo mismo que el audio
          "easing": "easeInOut"
        }
      ]
    }
  ]
}

Resolución de Problemas

Expresión no válida

Error: Expression "clip0.endd + 2" did not evaluate to a valid number

Solución: Verifica que los nombres de propiedades estén correctos (start, end, length, duration).

Clip no resuelto

Warning: Expression "clip5.end" references clip not yet resolved

Solución: Asegúrate de que el clip referenciado existe y tiene un order menor al clip actual.

Resultado inesperado

Si obtienes un resultado diferente al esperado, verifica:

  1. El orden de los clips (campo order)
  2. Las duraciones procesadas de clips TTS/audio
  3. Las operaciones matemáticas y precedencia (usa paréntesis si es necesario)

Campos que Soportan Expresiones

Nivel Clip

  • start - Tiempo de inicio del clip
  • length - Duración del clip

Dentro de Effects

  • duration - Duración del efecto
  • Cualquier campo numérico (ej: from, to, startTime)

Dentro de Transitions

  • duration - Duración de la transición
  • fromX, fromY, toX, toY - Posiciones
  • Cualquier campo numérico

Variable Especial

  • length - Solo disponible dentro de effects/transitions, referencia la duración del clip actual