Esquema de metadatos OIM
Referencia completa para la estructura de metadatos validada con Zod en los archivos .oim.
Visión general del esquema
typescript
import { z } from "zod";
export const OimMetadataSchema = z.object({
oim: z.string().optional(), // Format identifier
version: z.string().optional(), // Schema version
image: z.string().optional(), // Base64 image (legacy)
oim_id: z.string().max(128).optional(), // Unique tracking ID
media_type: z.enum(["image", "video"]).optional(),
creator_user_id: z.string().max(128).optional(),
image_hash: z.string().max(64).optional(), // SHA-256 of original image
immutable_lock: z.boolean().optional(), // Seal file from edits
allow_external_urls: z.boolean().optional(),
redirect_membership_verified: z.boolean().optional(),
terms_accepted: z.boolean().optional(),
canvas: z.object({
natural_width: z.number().positive(),
natural_height: z.number().positive()
}).optional(),
lang: z.array(z.string().max(5)).optional(),
zones: z.array(zoneSchema).optional(),
slots: z.array(slotSchema), // Required
elements: z.array(slotSchema).optional(),
layers: z.array(slotSchema).optional(),
signature: z.string().optional(), // ECDSA signature
key_version: z.string().optional(),
timestamp: z.string().datetime().optional(),
}).passthrough();Referencia de campos
Campos de nivel superior
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
oim | string | Identificador de formato (p. ej., "1.0") | |
version | string | Versión del esquema | |
image | string | Imagen codificada en Base64 (obsoleto, evitar) | |
oim_id | string (≤128) | ID único para reedición del documento | |
media_type | "image" | "video" | Tipo de contenido multimedia | |
creator_user_id | string (≤128) | Identificador de seguimiento del creador | |
image_hash | string (≤64) | Hash SHA-256 de la imagen horneada (Capa −1) | |
immutable_lock | boolean | Si es true, el archivo no puede editarse | |
allow_external_urls | boolean | Permitir URLs de redirección externas | |
redirect_membership_verified | boolean | Bandera de verificación de membresía de pago | |
terms_accepted | boolean | Bandera de aceptación de términos y condiciones | |
canvas | object | Dimensiones del canvas { natural_width, natural_height } | |
lang | string[] (≤5 caracteres cada uno) | Códigos de idioma disponibles | |
zones | Zone[] | Zonas de cuadro delimitador con nombre | |
slots | Slot[] | Definiciones de superposición de texto/imagen | |
elements | Slot[] | Array alternativo de slots | |
layers | Slot[] | Array de slots basado en capas | |
signature | string | Firma criptográfica ECDSA | |
key_version | string | Versión de la clave utilizada para la firma | |
timestamp | ISO datetime | Marca de tiempo de creación del paquete |
Esquema de Slot
typescript
const slotSchema = z.object({
id: z.string().min(1).max(128),
type: z.enum(["text", "image", "lottie", "zone", "geo", "shape", "draw", "filter", "container", "group", "component", "mask", "svg", "pattern", "artistic-text"]).optional(),
content: z.record(z.string(), z.string().max(10000000)).optional(),
style: styleSchema.optional(),
position: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]).optional(),
tracking_data: z.array(z.array(z.number()).min(2)).optional(),
zone_id: z.string().max(128).optional(),
link: z.string().max(2048).optional(),
target: z.enum(["_blank", "_self"]).optional(),
interaction: interactionSchema.optional(),
// Abbreviated keys for size optimization
p: positionSchema.optional(), // alias for position
s: z.tuple([z.number(), z.number()]).optional(), // alias for size/dimensions
st: styleSchema.optional(), // alias for style
c: contentSchema.optional(), // alias for content
t: z.array(trackingKeyframeSchema).optional(), // alias for tracking_data
});| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string (1-128) | Identificador único del slot | |
type | "text" | "image" | "lottie" | "zone" | Tipo de contenido del slot | |
content | Record<lang, text> | Diccionario de idioma a contenido (hasta 10MB para imágenes en base64) | |
style | Style | Estilo visual (ver a continuación) | |
position | [x, y] (0-100) | Coordenadas de posición normalizadas | |
tracking_data | number[][] | Datos de animación de keyframes | |
zone_id | string (≤128) | Zona de contención de destino | |
link | string (≤2048) | URL de redirección para hotspot/e-commerce | |
target | "_blank" | "_self" | Comportamiento de apertura del enlace | |
p | [x, y] | Retirada en 1.12.0: el esquema aún la acepta, pero ningún escritor la emite; usa position | |
s | [w, h] | Retirada en 1.12.0 | |
st | Style | Retirada en 1.12.0: el esquema aún la acepta, pero ningún escritor la emite; usa style | |
c | Record<lang, text> | Retirada en 1.12.0: el esquema aún la acepta, pero ningún escritor la emite; usa content | |
t | number[][] | Retirada en 1.12.0: el esquema aún la acepta, pero ningún escritor la emite; usa tracking_data |
Esquema de Style
typescript
const styleSchema = z.object({
font: z.string().max(64).optional(),
color: z.string().max(64).optional(),
size: z.string().max(16).optional(),
fontSize: z.string().max(16).optional(),
fontWeight: z.union([z.string(), z.number()]).optional(),
fontStyle: z.enum(["normal", "italic", "oblique"]).optional(),
textDecoration: z.string().max(64).optional(),
textTransform: z.enum(["none", "uppercase", "lowercase", "capitalize"]).optional(),
fontSizeOverrides: z.record(z.string(), z.string()).optional(),
fontSizes: z.record(z.string(), z.string()).optional(),
maxWidths: z.record(z.string(), z.string()).optional(),
outline: z.string().max(128).optional(),
objectFit: z.enum(["contain", "cover", "fill", "none", "scale-down"]).optional(),
verticalAlign: z.enum(["top", "center", "bottom", "mixed", "justify"]).optional(),
animation: z.object({
loop: z.enum(["none", "pulse", "float", "bounce", "spin", "glow", "custom"]).optional(),
hover: z.enum(["none", "grow", "shrink", "lift", "glow", "custom"]).optional(),
custom_loop_css: z.string().max(500).optional(),
custom_keyframes: z.string().max(2000).optional(),
custom_hover_css: z.string().max(500).optional(),
duration: z.number().min(0.1).max(20).optional(),
delay: z.number().min(0).max(10).optional(),
}).optional(),
shader_fx: z.object({
blur: z.number().min(0).max(10).optional(),
grain: z.number().min(0).max(1).optional(),
crt_curve: z.boolean().optional(),
}).optional(),
}).passthrough();Campos de Style
| Campo | Tipo | Descripción |
|---|---|---|
font | string (≤64) | Nombre de la familia de fuente |
color | string (≤64) | Valor de color CSS |
size / fontSize | string (≤16) | Tamaño de fuente (p. ej., "48px") |
fontWeight | string | number | Peso de fuente (p. ej., "700" o 700) |
fontStyle | "normal" | "italic" | "oblique" | Estilo de fuente |
textDecoration | string (≤64) | Decoración del texto |
textTransform | "none" | "uppercase" | "lowercase" | "capitalize" | Transformación del texto |
outline | string (≤128) | CSS de contorno del texto |
objectFit | "contain" | "cover" | "fill" | "none" | "scale-down" | Modo de ajuste de la imagen |
verticalAlign | "top" | "center" | "bottom" | "mixed" | "justify" | Alineación vertical |
Animación
| Campo | Tipo | Descripción |
|---|---|---|
loop | enum | Tipo de animación en bucle |
hover | enum | Tipo de animación al pasar el cursor |
custom_loop_css | string (≤500) | CSS personalizado para el bucle |
custom_keyframes | string (≤2000) | Definición personalizada de @keyframes |
custom_hover_css | string (≤500) | CSS personalizado para hover |
duration | number (0.1-20) | Duración de la animación en segundos |
delay | number (0-10) | Retraso de la animación en segundos |
Efectos de shader
| Campo | Tipo | Descripción |
|---|---|---|
blur | number (0-10) | Intensidad del desenfoque |
grain | number (0-1) | Intensidad del grano de película |
crt_curve | boolean | Efecto de curvatura CRT |
Esquema de Zone
typescript
const zoneSchema = z.object({
id: z.string().min(1).max(128),
position: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]),
size: z.tuple([z.number().min(0).max(100), z.number().min(0).max(100)]),
style: z.record(z.string(), z.any()).optional(),
});| Campo | Tipo | Descripción |
|---|---|---|
id | string (1-128) | Identificador de la zona |
position | [x, y] (0-100) | Origen de la zona (esquina superior izquierda) |
size | [width, height] (0-100) | Dimensiones de la zona |
style | Record<string, any> | Estilos específicos de la zona |
Ejemplo completo
json
{
"oim": "1.0",
"version": "1.0",
"lang": ["en", "es"],
"canvas": {
"natural_width": 1920,
"natural_height": 1080
},
"slots": [
{
"id": "main_title",
"type": "text",
"content": {
"en": "Welcome to OIM",
"es": "Bienvenido a OIM"
},
"position": [50, 20],
"style": {
"font": "Bricolage Grotesque",
"fontSize": "48px",
"fontWeight": "800",
"color": "#f59e0b",
"textTransform": "uppercase"
}
},
{
"id": "cta_button",
"type": "text",
"content": {
"en": "Learn More",
"es": "Saber Más"
},
"position": [50, 80],
"style": {
"fontSize": "24px",
"color": "#ffffff",
"animation": {
"loop": "pulse",
"duration": 2.0
}
},
"link": "https://example.com",
"target": "_blank"
}
],
"zones": [
{
"id": "header_zone",
"position": [0, 0],
"size": [100, 30]
}
]
}Restricciones de seguridad
| Restricción | Propósito |
|---|---|
| Longitudes máximas de strings | Prevenir desbordamiento de búfer |
| Validación regex de color | Prevenir inyección de CSS |
| Máximo 50 slots | Prevenir agotamiento de recursos |
content ≤ 10MB | Permitir imágenes base64 pero limitar el tamaño |
position 0-100 | Solo coordenadas normalizadas |
.passthrough() | Permitir campos compatibles hacia adelante |
Tipos de TypeScript
typescript
export type OIMMetadata = z.infer<typeof OimMetadataSchema>;
export type Slot = z.infer<typeof slotSchema>;
export type Style = z.infer<typeof styleSchema>;
export type Zone = z.infer<typeof zoneSchema>;