/** * 光湖视频AI系统 · 导演编码适配器 * D136+ · 铸渊 ICE-GL-ZY001 * * 输入:Agent_06 导演编码(JSON) * 输出:video-editor.js 的 edit() 参数 * * 使用: * const { adapt } = require('./hldp-director-adapter'); * const editorInput = adapt(directorEncoding, { shotsDir: './outputs/shots/' }); * await edit(editorInput); */ // ==================== 景别 → zoom ==================== const FRAMING_ZOOM = { '远景': { from: 1.0, to: 1.0 }, '全景': { from: 1.0, to: 1.05 }, '中景': { from: 1.0, to: 1.10 }, '近景': { from: 1.0, to: 1.15 }, '特写': { from: 1.0, to: 1.25 }, '大特写': { from: 1.0, to: 1.5 }, }; // ==================== 情绪类型 → 调色 ==================== const EMOTION_COLOR = { '悬': { contrast: 1.2, saturation: 0.9, brightness: -0.05 }, '恐': { contrast: 1.2, saturation: 0.9, brightness: -0.05 }, '怒': { contrast: 1.15, saturation: 1.1, brightness: 0 }, '燃': { contrast: 1.15, saturation: 1.1, brightness: 0 }, '甜': { contrast: 1.0, saturation: 1.15, brightness: 0.05 }, '暖': { contrast: 1.0, saturation: 1.15, brightness: 0.05 }, '冷': { contrast: 0.95, saturation: 0.85, brightness: -0.08 }, '哀': { contrast: 0.95, saturation: 0.85, brightness: -0.08 }, '爽': { contrast: 1.1, saturation: 1.1, brightness: 0.02 }, '震': { contrast: 1.1, saturation: 1.1, brightness: 0.02 }, }; // ==================== 强度 → trim时长 ==================== function intensityToTrim(intensity) { if (intensity <= 3) return 2.0; if (intensity <= 6) return 3.0; if (intensity <= 8) return 3.5; return 4.5; // 9-10: 定格感 } // ==================== 节奏 → 过渡类型 ==================== function rhythmToTransition(rhythm) { switch (rhythm) { case '铺垫': case '建立': case '余波': case '余韵': return 'fade'; case '爬升': case '高潮': case '反转': case '转折': return 'cut'; default: return 'fade'; } } // ==================== 主适配函数 ==================== /** * @param {object} dc - 导演编码 (Agent_06 输出) * @param {object} opts * @param {string} [opts.shotsDir] - 分镜视频文件目录 * @param {string} [opts.voiceFile] - 配音文件 * @param {string} [opts.bgmFile] - BGM文件 * @param {string} [opts.srtFile] - 字幕文件 * @param {object} [opts.resolution] - { w, h } * @param {number} [opts.fps] * @param {string} [opts.output] - 输出路径 * @returns {object} video-editor.js edit() 参数 */ function adapt(dc, opts = {}) { const shotsDir = opts.shotsDir || './outputs/shots/'; const timeline = []; for (const s of dc.shots) { const framing = FRAMING_ZOOM[s.framing] || FRAMING_ZOOM['中景']; const trim = s.duration || intensityToTrim(s.emotion?.intensity || 5); const shotEntry = { shot: `${shotsDir}${dc.project}-${dc.episode}-${s.id}.mp4`, trim, transition: s.transition || rhythmToTransition(s.rhythm), keyframes: s.keyframes || {}, }; // 景别映射到 keyframes.zoom(如果导演没有显式指定) if (!shotEntry.keyframes.zoom && framing.from !== framing.to) { shotEntry.keyframes.zoom = framing; } timeline.push(shotEntry); } // 取主导情绪的调色参数 const mainEmotion = dc.emotion_main || '悬'; const colorGrade = EMOTION_COLOR[mainEmotion] || EMOTION_COLOR['悬']; const result = { timeline, colorGrade, resolution: opts.resolution || { w: 1920, h: 1080 }, fps: opts.fps || 24, output: opts.output || `./outputs/${dc.project}-${dc.episode}-final.mp4`, }; if (opts.voiceFile) { result.audio = { ...(result.audio || {}), voice: opts.voiceFile }; } if (opts.bgmFile) { result.audio = { ...(result.audio || {}), bgm: opts.bgmFile, bgmVolume: 0.3 }; } if (opts.srtFile) { result.subtitle = opts.srtFile; } // 附加元信息 result._meta = { project: dc.project, episode: dc.episode, tempo: dc.tempo, emotion_main: mainEmotion, hooks: dc.hooks, shotCount: timeline.length, totalDuration: timeline.reduce((sum, t) => sum + t.trim, 0), }; return result; } module.exports = { adapt, FRAMING_ZOOM, EMOTION_COLOR, intensityToTrim, rhythmToTransition };