feat(video): generar a 1344x768 (máx H3) por defecto + drenaje nocturno

- route videos: 16:9=1344x768 / 9:16=768x1344 (antes 832x480), el máximo legal del
  contrato H3 (w*h<=1.032.192px, múltiplos de 32). Es la resolución estándar ahora.
- video-service: normalizeResolution ajusta cualquier resolución al contrato H3.
- mcp: story_sync_video_jobs + proyección compacta (capítulos de 100+ planos).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-08-21 12:32:47 +02:00
parent 2ff41ad2bc
commit 37f92dc402
3 changed files with 85 additions and 9 deletions

View File

@@ -108,9 +108,10 @@ export async function POST(
return NextResponse.json({ error: 'No se pudo leer la imagen del plano' }, { status: 500 });
}
// Resolución por plano: 16:9 (default) o 9:16 según body.aspect.
// Resolución por plano al máximo legal de H3 (w*h <= 1.032.192 px, múltiplos de 32):
// 16:9 (default) = 1344x768 · 9:16 = 768x1344. video-service normaliza igualmente al contrato.
const aspect = body.aspect === '9:16' ? '9:16' : '16:9';
const [width, height] = aspect === '9:16' ? [480, 832] : [832, 480];
const [width, height] = aspect === '9:16' ? [768, 1344] : [1344, 768];
console.log(`[Videos API] Encolando vídeo H3 para plano ${planoId} (${aspect})`);

View File

@@ -47,11 +47,33 @@ interface H3JobResponse {
export interface SubmitVideoOptions {
duration?: number; // segundos, 4-15 (default 5)
cameraFixed?: boolean; // añade indicación de movimiento mínimo al prompt
width?: number; // múltiplo de 32 (default 832 = 16:9)
height?: number; // múltiplo de 32 (default 480 = 16:9)
width?: number; // se ajusta a la rejilla de 32 px de H3 (default 832)
height?: number; // ídem (default 480); tope H3: width*height <= 1.032.192 px
mode?: string; // 'ref2va' (default) | 'fl2va' | 't2va'
}
// Contrato de resolución del nodo H3 (verificado 2026-08-17 contra H3ContractError del RH node):
// width/height deben ser múltiplos de 32 y width*height <= 1_032_192 px (= 1344x768, su máximo
// ~16:9). Fuera de eso el render falla a los pocos segundos ya en el server, así que ajustamos
// aquí para que ningún caller pueda encolar un job imposible (p.ej. 1280x720 o 1920x1088).
const H3_MAX_PIXELS = 1_032_192;
function normalizeResolution(width: number, height: number): { width: number; height: number } {
const snap = (v: number) => Math.max(32, Math.round(v / 32) * 32);
let w = snap(width);
let h = snap(height);
if (w * h > H3_MAX_PIXELS) {
const scale = Math.sqrt(H3_MAX_PIXELS / (w * h));
w = snap(w * scale);
h = snap(h * scale);
while (w * h > H3_MAX_PIXELS) {
if (w >= h) w -= 32;
else h -= 32;
}
}
return { width: w, height: h };
}
export const H3_COST_USD = COST_PER_VIDEO_USD;
function ensureConfigured(): void {
@@ -89,8 +111,12 @@ export async function submitVideoJob(
ensureConfigured();
const dur = Math.min(15, Math.max(4, options.duration || 5));
const width = options.width ?? 832;
const height = options.height ?? 480;
const requestedW = options.width ?? 832;
const requestedH = options.height ?? 480;
const { width, height } = normalizeResolution(requestedW, requestedH);
if (width !== requestedW || height !== requestedH) {
console.log(`[Video Service/H3] Resolución ajustada al contrato H3: ${requestedW}x${requestedH} -> ${width}x${height}`);
}
const mode = options.mode || DEFAULT_I2V_MODE;
const fullPrompt = options.cameraFixed
? `${prompt} Minimal camera movement, subtle motion only.`