import { mkdir, writeFile } from 'node:fs/promises'; import { dirname } from 'node:path'; import { probeMedia } from '../final-media.js'; import { buildSuggestedReframePlan, detectSceneBoundaries } from './suggest.js'; import type { ReframeCaptionProfile } from './types.js'; export interface SuggestedReframePlanResult { sourcePath: string; planPath: string; shotCount: number; strictReady: false; warnings: string[]; } export async function writeSuggestedReframePlan(input: { sourcePath: string; planPath: string; captionsPath?: string; captionProfile?: ReframeCaptionProfile; ffmpegPath?: string; }): Promise { const metadata = await probeMedia(input.sourcePath); if (!metadata.durationSeconds || !metadata.width || !metadata.height) { throw new Error('Source duration and dimensions are required to suggest a reframe plan.'); } const frameRateParts = metadata.frameRate?.split('/').map(Number) ?? []; const frameRate = frameRateParts.length === 2 && frameRateParts[1] ? (frameRateParts[0] ?? 0) / frameRateParts[1] : Number(metadata.frameRate ?? 25); const context = { sourceDurationSeconds: metadata.durationSeconds, sourceWidth: metadata.width, sourceHeight: metadata.height, frameRate: Number.isFinite(frameRate) && frameRate > 0 ? frameRate : 25, }; const boundaries = await detectSceneBoundaries({ sourcePath: input.sourcePath, durationSeconds: metadata.durationSeconds, ...(input.ffmpegPath ? { ffmpegPath: input.ffmpegPath } : {}), }); const plan = buildSuggestedReframePlan({ sourcePath: input.sourcePath, context, boundaries, ...(input.captionsPath ? { captionsPath: input.captionsPath } : {}), ...(input.captionProfile ? { captionProfile: input.captionProfile } : {}), }); await mkdir(dirname(input.planPath), { recursive: true }); await writeFile(input.planPath, `${JSON.stringify(plan, null, 2)}\n`, 'utf8'); return { sourcePath: input.sourcePath, planPath: input.planPath, shotCount: plan.shots.length, strictReady: false, warnings: [ 'Scene boundaries are automatic suggestions.', 'Every subject anchor is unverified; inspect each shot, correct the anchor and set verified=true before strict rendering.', ], }; }