@agimon-ai/video-editor-mcp 0.14.2 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,16 +16,36 @@ pnpm dev
16
16
 
17
17
  ## CLI
18
18
 
19
- The package also exposes a standalone CLI for direct video analysis:
19
+ Every MCP tool has a matching CLI command, so the tools can be driven directly from the
20
+ shell, scripts, or CI without an MCP client. The command name is the tool name with dashes
21
+ (e.g. `get_media_info` → `get-media-info`). Each command writes its machine-readable JSON
22
+ result to **stdout** and human-readable status to **stderr**, so stdout pipes cleanly:
20
23
 
21
24
  ```bash
22
25
  pnpm build
23
- node dist/cli.cjs analyze-video \
24
- --video "$HOME/Downloads/clip.webm" \
25
- --prompt "What is happening in this video?" \
26
- --pretty
26
+
27
+ # List the built-in compositions
28
+ node dist/cli.cjs list-compositions --pretty
29
+
30
+ # Inspect a media file
31
+ node dist/cli.cjs get-media-info --src clip.mp4 --type video --pretty
32
+
33
+ # Search and download (yt-dlp); --proxy is available on both
34
+ node dist/cli.cjs search-videos --query "lofi beats" --max-results 5
35
+ node dist/cli.cjs download-clip --url <url> --filename my_clip --start-time 0:30 --end-time 1:00
36
+
37
+ # Captions
38
+ node dist/cli.cjs import-srt --input subs.srt --pretty
39
+
40
+ # Analyze a local video with Gemini
41
+ node dist/cli.cjs analyze-video --video clip.webm --prompt "What is happening?" --pretty
27
42
  ```
28
43
 
44
+ Run `node dist/cli.cjs --help` to list every command. `--pretty` is available on all
45
+ tool commands. Commands whose tool input is an array, object, or large text blob take a
46
+ **file path** instead of inline flags: `preview-frame --input props.json`,
47
+ `generate-captions --captions captions.json`, and `import-srt --input subs.srt`.
48
+
29
49
  ## Build
30
50
 
31
51
  ```bash
package/dist/cli.cjs CHANGED
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./stdio-1ofTrT91.cjs`);let t=require(`node:child_process`),n=require(`node:fs`);n=e.M(n,1);let r=require(`node:path`);r=e.M(r,1);let i=require(`commander`),a=require(`@agimon-ai/foundation-port-registry`),o=require(`@agimon-ai/foundation-process-registry`),s=require(`chalk`);s=e.M(s,1);var c=`0.14.1`;const l=[`pnpm-workspace.yaml`,`nx.json`,`.git`],u=`video-editor-mcp-http`,d=`tool`;function f(e=process.cwd()){let t=r.default.resolve(e);for(;;){for(let e of l)if((0,n.existsSync)(r.default.join(t,e)))return t;let e=r.default.dirname(t);if(e===t)return process.cwd();t=e}}const p=new i.Command(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,String(a.DEFAULT_PORT_RANGE.min)).action(async e=>{let n;try{let i=Number.parseInt(e.port,10),c=new a.PortRegistryService(process.env.PORT_REGISTRY_PATH),l=f(),p=i?{min:i,max:i}:a.DEFAULT_PORT_RANGE,m=await c.reservePort({repositoryPath:l,serviceName:u,serviceType:d,preferredPort:i||a.DEFAULT_PORT_RANGE.min,portRange:p,pid:process.pid,host:`127.0.0.1`,force:!0});if(!m.success||!m.record)throw Error(m.error||`Failed to reserve port ${i}`);let h=m.record.port;n=await(0,o.createProcessLease)({repositoryPath:l,serviceName:u,serviceType:d,pid:process.pid,port:h,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2)});let g=r.default.resolve(__dirname,`..`);console.error(s.default.green(`Starting Remotion Studio on port ${h}...`)),console.error(s.default.gray(` Working directory: ${g}`));let _=(0,t.spawn)(r.default.join(g,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(h)],{stdio:`inherit`,cwd:g});_.on(`error`,e=>{console.error(s.default.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)});let v=async e=>{_.kill(e),await n?.release(),await c.releasePort({repositoryPath:l,serviceName:u,serviceType:d,pid:process.pid}),process.exit(0)};process.on(`SIGINT`,()=>v(`SIGINT`)),process.on(`SIGTERM`,()=>v(`SIGTERM`))}catch(e){await n?.release(),console.error(`Error executing http-serve:`,e),process.exit(1)}}),m=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function h(e=process.cwd()){let t=r.default.resolve(e);for(;;){for(let e of m)if((0,n.existsSync)(r.default.join(t,e)))return t;let e=r.default.dirname(t);if(e===t)return process.cwd();t=e}}async function g(e,t){await e.start();let n=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await e.stop(),await t?.(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`))}const _=new i.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).action(async t=>{let n;try{let r=t.type.toLowerCase(),i=h();if(r===`stdio`){let r=new e.t(e.n(e.r({proxyUrl:t.proxy})));n=await(0,o.createProcessLease)({repositoryPath:i,serviceName:`video-editor-mcp-stdio`,pid:process.pid,metadata:{transport:`stdio`}}),await g(r,async()=>{await n?.release()})}else console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1)}catch(e){await n?.release(),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),v=new i.Command(`analyze-video`).description(`Analyze one or more local video files with Gemini Code Assist`).requiredOption(`-v, --video <paths...>`,`Paths to local video files to analyze`).requiredOption(`-p, --prompt <text>`,`Prompt to send with the video(s)`).option(`-m, --model <id>`,`Gemini model to use`,`gemini-2.5-flash-lite`).option(`--pretty`,`Pretty-print the full JSON result`,!1).action(async t=>{try{if(!Array.isArray(t.video)||t.video.length===0)throw Error(`At least one video path is required.`);process.stderr.write(s.default.blue(`🎥 Starting video analysis...
3
- `)),process.stderr.write(s.default.gray(` Videos: ${t.video.join(`, `)}\n`)),process.stderr.write(s.default.gray(` Prompt: ${t.prompt}\n`)),process.stderr.write(s.default.gray(` Model: ${t.model}\n`));let n=await e.r().get(e.h.VideoAnalysisService).analyzeVideos({videoPaths:t.video,prompt:t.prompt,model:t.model});if(process.stderr.write(s.default.green(`✓ Resolved project: ${n.resolvedProject}\n`)),process.stderr.write(s.default.green(`✓ Videos processed: ${n.videoCount}\n`)),t.pretty){process.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}if(n.responseText){process.stdout.write(`${n.responseText}\n`);return}process.stdout.write(`${JSON.stringify(n.rawResponse,null,2)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(s.default.red(`Error analyzing video: ${t}\n`)),process.exit(1)}}),y=e=>Number(e),b=e=>Number.parseInt(e,10),x=()=>(0,n.existsSync)(r.default.resolve(__dirname,`../package.json`))?r.default.resolve(__dirname,`..`):r.default.resolve(__dirname,`../..`),S=e=>{if(e.output)return r.default.resolve(e.output);let t=r.default.resolve(e.outputDir??r.default.join(x(),`public/music`)),n=e.filename??`ace-step-music`;return r.default.join(t,`${n}.${e.audioFormat}`)},C=new i.Command(`generate-music`).description(`Generate background music using ACE-Step 1.5`).requiredOption(`-p, --prompt <text>`,`Music description for ACE-Step text-to-music generation`).option(`-f, --filename <name>`,`Output filename without extension (default: ace-step-music)`).option(`-o, --output <path>`,`Explicit output audio path. Overrides filename/output-dir.`).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/music/)`).option(`--ace-step-path <path>`,`Path to local ACE-Step-1.5 checkout`).option(`--lyrics <text>`,`Lyrics to use. Defaults to [Instrumental] for background music.`).option(`--duration <seconds>`,`Target duration in seconds (default: 30)`,y).option(`--bpm <number>`,`Optional BPM metadata`,y).option(`--keyscale <text>`,`Optional key/scale metadata, e.g. C minor`).option(`--time-signature <text>`,`Optional time signature metadata, e.g. 4/4`).option(`--vocal-language <code>`,`Vocal language code, or unknown`).option(`--seed <number>`,`Optional fixed seed for reproducible generation`,b).option(`--inference-steps <number>`,`Diffusion inference steps (default: 8)`,b).option(`--audio-format <format>`,`Output audio format: wav, mp3, flac`,`wav`).option(`--backend <backend>`,`ACE-Step LM backend: mlx, pt, vllm`,`mlx`).option(`--config-path <name>`,`ACE-Step DiT model config (default: acestep-v15-turbo)`).option(`--lm-model-path <name>`,`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`).option(`--timeout-ms <number>`,`Generation timeout in milliseconds`,b).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(![`wav`,`mp3`,`flac`].includes(t.audioFormat))throw Error(`Unsupported audio format: ${t.audioFormat}`);if(![`mlx`,`pt`,`vllm`].includes(t.backend))throw Error(`Unsupported backend: ${t.backend}`);let n=S(t);process.stderr.write(s.default.blue(`Starting ACE-Step music generation...
4
- `)),process.stderr.write(s.default.gray(` Prompt: ${t.prompt}\n`)),process.stderr.write(s.default.gray(` Output: ${n}\n`)),process.stderr.write(s.default.gray(` Backend: ${t.backend}\n`));let i=e.r().get(e.h.AceStepMusicService);if(!await i.isAvailable(t.aceStepPath))throw Error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass --ace-step-path.`);let a=await i.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),o={...a,compositionSrc:r.default.dirname(n)===r.default.resolve(x(),`public/music`)?`/music/${r.default.basename(n)}`:void 0};process.stderr.write(s.default.green(`Music generated successfully: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(s.default.red(`Error generating music: ${t}\n`)),process.exit(1)}}),w=new i.Command(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).option(`--dry-run`,`Validate props and assets without rendering`).action(async t=>{try{process.stderr.write(s.default.blue(t.dryRun?`🔎 Starting render dry run...
2
+ const e=require(`./stdio-C7W9m2hj.cjs`);let t=require(`node:child_process`),n=require(`node:fs`);n=e.M(n,1);let r=require(`node:os`);r=e.M(r,1);let i=require(`node:path`);i=e.M(i,1);let a=require(`commander`),o=require(`@agimon-ai/foundation-port-registry`),s=require(`@agimon-ai/foundation-process-registry`),c=require(`node:process`);c=e.M(c,1);let l=require(`node:tty`);l=e.M(l,1);var u=`0.15.0`;const d=(e=0)=>t=>`\u001B[${t+e}m`,f=(e=0)=>t=>`\u001B[${38+e};5;${t}m`,p=(e=0)=>(t,n,r)=>`\u001B[${38+e};2;${t};${n};${r}m`,m={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};Object.keys(m.modifier);const ee=Object.keys(m.color),te=Object.keys(m.bgColor);[...ee,...te];function ne(){let e=new Map;for(let[t,n]of Object.entries(m)){for(let[t,r]of Object.entries(n))m[t]={open:`\u001B[${r[0]}m`,close:`\u001B[${r[1]}m`},n[t]=m[t],e.set(r[0],r[1]);Object.defineProperty(m,t,{value:n,enumerable:!1})}return Object.defineProperty(m,`codes`,{value:e,enumerable:!1}),m.color.close=`\x1B[39m`,m.bgColor.close=`\x1B[49m`,m.color.ansi=d(),m.color.ansi256=f(),m.color.ansi16m=p(),m.bgColor.ansi=d(10),m.bgColor.ansi256=f(10),m.bgColor.ansi16m=p(10),Object.defineProperties(m,{rgbToAnsi256:{value(e,t,n){return e===t&&t===n?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(t/255*5)+Math.round(n/255*5)},enumerable:!1},hexToRgb:{value(e){let t=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!t)return[0,0,0];let[n]=t;n.length===3&&(n=[...n].map(e=>e+e).join(``));let r=Number.parseInt(n,16);return[r>>16&255,r>>8&255,r&255]},enumerable:!1},hexToAnsi256:{value:e=>m.rgbToAnsi256(...m.hexToRgb(e)),enumerable:!1},ansi256ToAnsi:{value(e){if(e<8)return 30+e;if(e<16)return 90+(e-8);let t,n,r;if(e>=232)t=((e-232)*10+8)/255,n=t,r=t;else{e-=16;let i=e%36;t=Math.floor(e/36)/5,n=Math.floor(i/6)/5,r=i%6/5}let i=Math.max(t,n,r)*2;if(i===0)return 30;let a=30+(Math.round(r)<<2|Math.round(n)<<1|Math.round(t));return i===2&&(a+=60),a},enumerable:!1},rgbToAnsi:{value:(e,t,n)=>m.ansi256ToAnsi(m.rgbToAnsi256(e,t,n)),enumerable:!1},hexToAnsi:{value:e=>m.ansi256ToAnsi(m.hexToAnsi256(e)),enumerable:!1}}),m}const h=ne();function g(e,t=globalThis.Deno?globalThis.Deno.args:c.default.argv){let n=e.startsWith(`-`)?``:e.length===1?`-`:`--`,r=t.indexOf(n+e),i=t.indexOf(`--`);return r!==-1&&(i===-1||r<i)}const{env:_}=c.default;let v;g(`no-color`)||g(`no-colors`)||g(`color=false`)||g(`color=never`)?v=0:(g(`color`)||g(`colors`)||g(`color=true`)||g(`color=always`))&&(v=1);function re(){if(`FORCE_COLOR`in _)return _.FORCE_COLOR===`true`?1:_.FORCE_COLOR===`false`?0:_.FORCE_COLOR.length===0?1:Math.min(Number.parseInt(_.FORCE_COLOR,10),3)}function y(e){return e===0?!1:{level:e,hasBasic:!0,has256:e>=2,has16m:e>=3}}function b(e,{streamIsTTY:t,sniffFlags:n=!0}={}){let i=re();i!==void 0&&(v=i);let a=n?v:i;if(a===0)return 0;if(n){if(g(`color=16m`)||g(`color=full`)||g(`color=truecolor`))return 3;if(g(`color=256`))return 2}if(`TF_BUILD`in _&&`AGENT_NAME`in _)return 1;if(e&&!t&&a===void 0)return 0;let o=a||0;if(_.TERM===`dumb`)return o;if(c.default.platform===`win32`){let e=r.default.release().split(`.`);return Number(e[0])>=10&&Number(e[2])>=10586?Number(e[2])>=14931?3:2:1}if(`CI`in _)return[`GITHUB_ACTIONS`,`GITEA_ACTIONS`,`CIRCLECI`].some(e=>e in _)?3:[`TRAVIS`,`APPVEYOR`,`GITLAB_CI`,`BUILDKITE`,`DRONE`].some(e=>e in _)||_.CI_NAME===`codeship`?1:o;if(`TEAMCITY_VERSION`in _)return+!!/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(_.TEAMCITY_VERSION);if(_.COLORTERM===`truecolor`||_.TERM===`xterm-kitty`||_.TERM===`xterm-ghostty`||_.TERM===`wezterm`)return 3;if(`TERM_PROGRAM`in _){let e=Number.parseInt((_.TERM_PROGRAM_VERSION||``).split(`.`)[0],10);switch(_.TERM_PROGRAM){case`iTerm.app`:return e>=3?3:2;case`Apple_Terminal`:return 2}}return/-256(color)?$/i.test(_.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(_.TERM)||`COLORTERM`in _?1:o}function x(e,t={}){return y(b(e,{streamIsTTY:e&&e.isTTY,...t}))}const ie={stdout:x({isTTY:l.default.isatty(1)}),stderr:x({isTTY:l.default.isatty(2)})};function S(e,t,n){let r=e.indexOf(t);if(r===-1)return e;let i=t.length,a=0,o=``;do o+=e.slice(a,r)+t+n,a=r+i,r=e.indexOf(t,a);while(r!==-1);return o+=e.slice(a),o}function ae(e,t,n,r){let i=0,a=``;do{let o=e[r-1]===`\r`;a+=e.slice(i,o?r-1:r)+t+(o?`\r
3
+ `:`
4
+ `)+n,i=r+1,r=e.indexOf(`
5
+ `,i)}while(r!==-1);return a+=e.slice(i),a}const{stdout:C,stderr:w}=ie,T=Symbol(`GENERATOR`),E=Symbol(`STYLER`),D=Symbol(`IS_EMPTY`),O=[`ansi`,`ansi`,`ansi256`,`ansi16m`],k=Object.create(null),oe=(e,t={})=>{if(t.level&&!(Number.isInteger(t.level)&&t.level>=0&&t.level<=3))throw Error("The `level` option should be an integer from 0 to 3");let n=C?C.level:0;e.level=t.level===void 0?n:t.level},se=e=>{let t=(...e)=>e.join(` `);return oe(t,e),Object.setPrototypeOf(t,A.prototype),t};function A(e){return se(e)}Object.setPrototypeOf(A.prototype,Function.prototype);for(let[e,t]of Object.entries(h))k[e]={get(){let n=N(this,M(t.open,t.close,this[E]),this[D]);return Object.defineProperty(this,e,{value:n}),n}};k.visible={get(){let e=N(this,this[E],!0);return Object.defineProperty(this,`visible`,{value:e}),e}};const j=(e,t,n,...r)=>e===`rgb`?t===`ansi16m`?h[n].ansi16m(...r):t===`ansi256`?h[n].ansi256(h.rgbToAnsi256(...r)):h[n].ansi(h.rgbToAnsi(...r)):e===`hex`?j(`rgb`,t,n,...h.hexToRgb(...r)):h[n][e](...r);for(let e of[`rgb`,`hex`,`ansi256`]){k[e]={get(){let{level:t}=this;return function(...n){let r=M(j(e,O[t],`color`,...n),h.color.close,this[E]);return N(this,r,this[D])}}};let t=`bg`+e[0].toUpperCase()+e.slice(1);k[t]={get(){let{level:t}=this;return function(...n){let r=M(j(e,O[t],`bgColor`,...n),h.bgColor.close,this[E]);return N(this,r,this[D])}}}}const ce=Object.defineProperties(()=>{},{...k,level:{enumerable:!0,get(){return this[T].level},set(e){this[T].level=e}}}),M=(e,t,n)=>{let r,i;return n===void 0?(r=e,i=t):(r=n.openAll+e,i=t+n.closeAll),{open:e,close:t,openAll:r,closeAll:i,parent:n}},N=(e,t,n)=>{let r=(...e)=>P(r,e.length===1?``+e[0]:e.join(` `));return Object.setPrototypeOf(r,ce),r[T]=e,r[E]=t,r[D]=n,r},P=(e,t)=>{if(e.level<=0||!t)return e[D]?``:t;let n=e[E];if(n===void 0)return t;let{openAll:r,closeAll:i}=n;if(t.includes(`\x1B`))for(;n!==void 0;)t=S(t,n.close,n.open),n=n.parent;let a=t.indexOf(`
6
+ `);return a!==-1&&(t=ae(t,i,r,a)),r+t+i};Object.defineProperties(A.prototype,k);const F=A();A({level:w?w.level:0});const I=[`pnpm-workspace.yaml`,`nx.json`,`.git`],L=`video-editor-mcp-http`,R=`tool`;function z(e=process.cwd()){let t=i.default.resolve(e);for(;;){for(let e of I)if((0,n.existsSync)(i.default.join(t,e)))return t;let e=i.default.dirname(t);if(e===t)return process.cwd();t=e}}const B=new a.Command(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,String(o.DEFAULT_PORT_RANGE.min)).action(async e=>{let n;try{let r=Number.parseInt(e.port,10),a=new o.PortRegistryService(process.env.PORT_REGISTRY_PATH),c=z(),l=r?{min:r,max:r}:o.DEFAULT_PORT_RANGE,u=await a.reservePort({repositoryPath:c,serviceName:L,serviceType:R,preferredPort:r||o.DEFAULT_PORT_RANGE.min,portRange:l,pid:process.pid,host:`127.0.0.1`,force:!0});if(!u.success||!u.record)throw Error(u.error||`Failed to reserve port ${r}`);let d=u.record.port;n=await(0,s.createProcessLease)({repositoryPath:c,serviceName:L,serviceType:R,pid:process.pid,port:d,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2)});let f=i.default.resolve(__dirname,`..`);console.error(F.green(`Starting Remotion Studio on port ${d}...`)),console.error(F.gray(` Working directory: ${f}`));let p=(0,t.spawn)(i.default.join(f,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(d)],{stdio:`inherit`,cwd:f});p.on(`error`,e=>{console.error(F.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)});let m=async e=>{p.kill(e),await n?.release(),await a.releasePort({repositoryPath:c,serviceName:L,serviceType:R,pid:process.pid}),process.exit(0)};process.on(`SIGINT`,()=>m(`SIGINT`)),process.on(`SIGTERM`,()=>m(`SIGTERM`))}catch(e){await n?.release(),console.error(`Error executing http-serve:`,e),process.exit(1)}}),V=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function H(e=process.cwd()){let t=i.default.resolve(e);for(;;){for(let e of V)if((0,n.existsSync)(i.default.join(t,e)))return t;let e=i.default.dirname(t);if(e===t)return process.cwd();t=e}}async function U(e,t){await e.start();let n=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await e.stop(),await t?.(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`))}const W=new a.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).action(async t=>{let n;try{let r=t.type.toLowerCase(),i=H();if(r===`stdio`){let r=new e.t(e.n(e.r({proxyUrl:t.proxy})));n=await(0,s.createProcessLease)({repositoryPath:i,serviceName:`video-editor-mcp-stdio`,pid:process.pid,metadata:{transport:`stdio`}}),await U(r,async()=>{await n?.release()})}else console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1)}catch(e){await n?.release(),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),G=new a.Command(`analyze-video`).description(`Analyze one or more local video files with Gemini Code Assist`).requiredOption(`-v, --video <paths...>`,`Paths to local video files to analyze`).requiredOption(`-p, --prompt <text>`,`Prompt to send with the video(s)`).option(`-m, --model <id>`,`Gemini model to use`,`gemini-2.5-flash-lite`).option(`--pretty`,`Pretty-print the full JSON result`,!1).action(async t=>{try{if(!Array.isArray(t.video)||t.video.length===0)throw Error(`At least one video path is required.`);process.stderr.write(F.blue(`🎥 Starting video analysis...
7
+ `)),process.stderr.write(F.gray(` Videos: ${t.video.join(`, `)}\n`)),process.stderr.write(F.gray(` Prompt: ${t.prompt}\n`)),process.stderr.write(F.gray(` Model: ${t.model}\n`));let n=await e.r().get(e.h.VideoAnalysisService).analyzeVideos({videoPaths:t.video,prompt:t.prompt,model:t.model});if(process.stderr.write(F.green(`✓ Resolved project: ${n.resolvedProject}\n`)),process.stderr.write(F.green(`✓ Videos processed: ${n.videoCount}\n`)),t.pretty){process.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}if(n.responseText){process.stdout.write(`${n.responseText}\n`);return}process.stdout.write(`${JSON.stringify(n.rawResponse,null,2)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error analyzing video: ${t}\n`)),process.exit(1)}}),le=e=>Number.parseInt(e,10),ue=()=>(0,n.existsSync)(i.default.resolve(__dirname,`../package.json`))?i.default.resolve(__dirname,`..`):i.default.resolve(__dirname,`../..`),de=new a.Command(`download-clip`).description(`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming`).requiredOption(`-u, --url <url>`,`Video URL (YouTube, TikTok, Instagram, or any yt-dlp supported site)`).requiredOption(`-f, --filename <name>`,`Output filename without extension (e.g. my_clip)`).option(`--start-time <time>`,`Start time for clip extraction (e.g. 0:30, 1:15)`).option(`--end-time <time>`,`End time for clip extraction (e.g. 1:00, 2:30)`).option(`--max-height <number>`,`Maximum video height in pixels (default: 1080)`,le,1080).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/videos/)`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{let n=i.default.resolve(ue(),`public/videos`),r=t.outputDir??n;process.stderr.write(F.blue(`Downloading clip...
8
+ `)),process.stderr.write(F.gray(` URL: ${t.url}\n`)),process.stderr.write(F.gray(` Output: ${i.default.join(r,`${t.filename}.mp4`)}\n`));let a=await e.r({proxyUrl:t.proxy}).get(e.h.VideoDownloadService).downloadClip(t.url,r,t.filename,t.startTime,t.endTime,t.maxHeight),o={...a,compositionSrc:t.outputDir?void 0:`/videos/${t.filename}.mp4`,suggestedComposition:a.height>a.width?`Vertical`:`Main`};process.stderr.write(F.green(`✓ Downloaded: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error downloading clip: ${t}\n`)),process.exit(1)}}),fe=e=>Number(e),K=e=>Number.parseInt(e,10),pe=[`jpeg`,`png`],me=new a.Command(`extract-video-context`).description(`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`).requiredOption(`-v, --video-path <path>`,`Path to the local video file`).option(`-o, --output-dir <path>`,`Directory to write transcript, filmstrips, and manifest`).option(`--basename <name>`,`Base name for the generated context directory when output-dir is omitted`).option(`--frames-per-second <number>`,`Frames per second to sample (default: 3)`,fe,3).option(`--image-format <format>`,`Frame image format: jpeg or png`,`jpeg`).option(`--tile-width <number>`,`Width of each filmstrip tile in pixels (default: 250)`,K,250).option(`--filmstrip-columns <number>`,`Maximum columns per segment filmstrip (default: 6)`,K,6).option(`--filmstrip-rows <number>`,`Maximum rows per segment filmstrip (default: 4)`,K,4).option(`--no-transcribe`,`Skip local Whisper transcription`).option(`--language <code>`,`Optional Whisper language hint`).option(`--whisper-model <name>`,`Python Whisper model name (default: base)`).option(`--whisper-model-path <path>`,`Whisper.cpp model path override`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(!pe.includes(t.imageFormat))throw Error(`Unsupported image format: ${t.imageFormat}. Use: jpeg, png`);process.stderr.write(F.blue(`Extracting video context...
9
+ `)),process.stderr.write(F.gray(` Video: ${t.videoPath}\n`)),process.stderr.write(F.gray(` Transcribe: ${t.transcribe?`yes`:`no`}\n`));let n=e.r().get(e.h.VideoContextService),r={videoPath:t.videoPath,outputDir:t.outputDir,basename:t.basename,framesPerSecond:t.framesPerSecond,imageFormat:t.imageFormat,tileWidth:t.tileWidth,filmstripColumns:t.filmstripColumns,filmstripRows:t.filmstripRows,transcribe:t.transcribe,language:t.language,whisperModel:t.whisperModel,whisperModelPath:t.whisperModelPath},i=await n.extractVideoContext(r);process.stderr.write(F.green(`✓ Context extracted (${i.transcript.segments.length} segments)\n`)),process.stdout.write(`${JSON.stringify(i,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error extracting video context: ${t}\n`)),process.exit(1)}}),he=e=>Number(e),q=e=>Number.parseInt(e,10),J=[`tiktok`,`reels`,`shorts`,`universal`],Y=[`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`],ge=new a.Command(`generate-captions`).description(`Create TikTok-style caption pages from a caption array for video overlay`).requiredOption(`-c, --captions <path>`,`Path to a JSON file containing the captions array`).option(`--combine-ms <number>`,`Milliseconds threshold for combining captions into pages`,he,100).option(`--max-words-per-page <number>`,`Maximum words per caption page (default: 6)`,q).option(`--max-chars-per-line <number>`,`Target maximum characters per caption line (default: 22)`,q).option(`--platform <platform>`,`Platform safe-zone preset: tiktok, reels, shorts, universal`).option(`--preset <preset>`,`Caption visual style preset: tiktok-bold, karaoke, premium-pill, minimal, ugc`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(t=>{try{if(t.platform&&!J.includes(t.platform))throw Error(`Unsupported platform: ${t.platform}. Use: ${J.join(`, `)}`);if(t.preset&&!Y.includes(t.preset))throw Error(`Unsupported preset: ${t.preset}. Use: ${Y.join(`, `)}`);if(!n.default.existsSync(t.captions))throw Error(`Captions file not found: ${t.captions}`);process.stderr.write(F.blue(`Generating caption pages...
10
+ `));let r=JSON.parse(n.default.readFileSync(t.captions,`utf-8`)),i=e.r().get(e.h.CaptionService).createTikTokCaptions(r,{combineMs:t.combineMs,maxWordsPerPage:t.maxWordsPerPage,maxCharsPerLine:t.maxCharsPerLine,platform:t.platform,preset:t.preset});process.stderr.write(F.green(`✓ ${i.pages.length} caption pages\n`)),process.stdout.write(`${JSON.stringify(i,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error generating captions: ${t}\n`)),process.exit(1)}}),X=e=>Number(e),Z=e=>Number.parseInt(e,10),Q=()=>(0,n.existsSync)(i.default.resolve(__dirname,`../package.json`))?i.default.resolve(__dirname,`..`):i.default.resolve(__dirname,`../..`),_e=e=>{if(e.output)return i.default.resolve(e.output);let t=i.default.resolve(e.outputDir??i.default.join(Q(),`public/music`)),n=e.filename??`ace-step-music`;return i.default.join(t,`${n}.${e.audioFormat}`)},ve=new a.Command(`generate-music`).description(`Generate background music using ACE-Step 1.5`).requiredOption(`-p, --prompt <text>`,`Music description for ACE-Step text-to-music generation`).option(`-f, --filename <name>`,`Output filename without extension (default: ace-step-music)`).option(`-o, --output <path>`,`Explicit output audio path. Overrides filename/output-dir.`).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/music/)`).option(`--ace-step-path <path>`,`Path to local ACE-Step-1.5 checkout`).option(`--lyrics <text>`,`Lyrics to use. Defaults to [Instrumental] for background music.`).option(`--duration <seconds>`,`Target duration in seconds (default: 30)`,X).option(`--bpm <number>`,`Optional BPM metadata`,X).option(`--keyscale <text>`,`Optional key/scale metadata, e.g. C minor`).option(`--time-signature <text>`,`Optional time signature metadata, e.g. 4/4`).option(`--vocal-language <code>`,`Vocal language code, or unknown`).option(`--seed <number>`,`Optional fixed seed for reproducible generation`,Z).option(`--inference-steps <number>`,`Diffusion inference steps (default: 8)`,Z).option(`--audio-format <format>`,`Output audio format: wav, mp3, flac`,`wav`).option(`--backend <backend>`,`ACE-Step LM backend: mlx, pt, vllm`,`mlx`).option(`--config-path <name>`,`ACE-Step DiT model config (default: acestep-v15-turbo)`).option(`--lm-model-path <name>`,`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`).option(`--timeout-ms <number>`,`Generation timeout in milliseconds`,Z).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(![`wav`,`mp3`,`flac`].includes(t.audioFormat))throw Error(`Unsupported audio format: ${t.audioFormat}`);if(![`mlx`,`pt`,`vllm`].includes(t.backend))throw Error(`Unsupported backend: ${t.backend}`);let n=_e(t);process.stderr.write(F.blue(`Starting ACE-Step music generation...
11
+ `)),process.stderr.write(F.gray(` Prompt: ${t.prompt}\n`)),process.stderr.write(F.gray(` Output: ${n}\n`)),process.stderr.write(F.gray(` Backend: ${t.backend}\n`));let r=e.r().get(e.h.AceStepMusicService);if(!await r.isAvailable(t.aceStepPath))throw Error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass --ace-step-path.`);let a=await r.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),o={...a,compositionSrc:i.default.dirname(n)===i.default.resolve(Q(),`public/music`)?`/music/${i.default.basename(n)}`:void 0};process.stderr.write(F.green(`Music generated successfully: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error generating music: ${t}\n`)),process.exit(1)}}),ye=new a.Command(`generate-voiceover`).description(`Generate voiceover audio from text using Kokoro TTS local engine`).requiredOption(`-t, --text <text>`,`Text to convert to speech`).requiredOption(`-o, --output <path>`,`Output audio file path`).option(`--voice <id>`,`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`).option(`--speed <number>`,`Speech speed multiplier (default: 1.0)`,e=>Number(e)).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{process.stderr.write(F.blue(`Generating voiceover...
12
+ `)),process.stderr.write(F.gray(` Voice: ${t.voice??`af_sarah`}\n`)),process.stderr.write(F.gray(` Output: ${t.output}\n`));let n=e.r().get(e.h.VoiceoverService);if(!await n.isAvailable())throw Error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let r=await n.generateVoiceover({text:t.text,voice:t.voice,speed:t.speed,outputPath:t.output});process.stderr.write(F.green(`✓ Voiceover generated: ${r.outputPath}\n`)),process.stdout.write(`${JSON.stringify(r,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error generating voiceover: ${t}\n`)),process.exit(1)}}),be=[`video`,`audio`],$=new a.Command(`get-media-info`).description(`Get duration, dimensions, and decodability info for video/audio files`).requiredOption(`-s, --src <path>`,`Path to the media file`).option(`-t, --type <type>`,`Media type: video or audio`,`video`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(!be.includes(t.type))throw Error(`Unsupported media type: ${t.type}. Use: video, audio`);process.stderr.write(F.blue(`Inspecting media...
13
+ `)),process.stderr.write(F.gray(` Src: ${t.src}\n`)),process.stderr.write(F.gray(` Type: ${t.type}\n`));let n=e.r().get(e.h.MediaInfoService),r=await n.canDecode(t.src),i;if(t.type===`audio`){let e=await n.getAudioDuration(t.src);i={src:t.src,type:`audio`,duration:e,canDecode:r}}else{let[e,a]=await Promise.all([n.getVideoDuration(t.src),n.getVideoDimensions(t.src)]);i={src:t.src,type:`video`,duration:e,width:a.width,height:a.height,canDecode:r}}process.stderr.write(F.green(`✓ decodable: ${r}\n`)),process.stdout.write(`${JSON.stringify(i,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error getting media info: ${t}\n`)),process.exit(1)}}),xe=new a.Command(`import-srt`).description(`Parse SRT subtitle file content into structured Caption format for video overlay`).requiredOption(`-i, --input <path>`,`Path to the .srt subtitle file`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(t=>{try{if(!n.default.existsSync(t.input))throw Error(`SRT file not found: ${t.input}`);process.stderr.write(F.blue(`Parsing SRT...
14
+ `));let r=n.default.readFileSync(t.input,`utf-8`),i=e.r().get(e.h.CaptionService).parseSrt(r);process.stderr.write(F.green(`✓ ${i.length} captions parsed\n`)),process.stdout.write(`${JSON.stringify({captionCount:i.length,captions:i},null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error importing SRT: ${t}\n`)),process.exit(1)}}),Se=new a.Command(`list-compositions`).description(`List all available Remotion compositions with their schemas, dimensions, and format info`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(t=>{try{process.stderr.write(F.blue(`Listing compositions...
15
+ `));let n=e.r().get(e.h.CompositionService).listCompositions();process.stderr.write(F.green(`✓ ${n.compositionCount} compositions\n`)),process.stdout.write(`${JSON.stringify(n,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error listing compositions: ${t}\n`)),process.exit(1)}}),Ce=e=>Number.parseInt(e,10),we=[`png`,`jpeg`],Te=new a.Command(`preview-frame`).description(`Render a single frame from a composition as a PNG or JPEG image for preview`).requiredOption(`-c, --composition <id>`,`Composition ID (e.g. Main, Vertical, Square)`).requiredOption(`-i, --input <path>`,`Path to JSON inputProps file`).requiredOption(`-o, --output <path>`,`Output image file path`).option(`--frame <number>`,`Frame number to render (default: 0)`,Ce).option(`--image-format <format>`,`Image format: png or jpeg`,`png`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(!we.includes(t.imageFormat))throw Error(`Unsupported image format: ${t.imageFormat}. Use: png, jpeg`);if(!n.default.existsSync(t.input))throw Error(`Input file not found: ${t.input}`);process.stderr.write(F.blue(`Rendering preview frame...
16
+ `)),process.stderr.write(F.gray(` Composition: ${t.composition}\n`)),process.stderr.write(F.gray(` Frame: ${t.frame??0}\n`)),process.stderr.write(F.gray(` Output: ${t.output}\n`));let r=JSON.parse(n.default.readFileSync(t.input,`utf-8`)),i=e.r().get(e.h.RenderService),a={compositionId:t.composition,inputProps:r,outputPath:t.output,frame:t.frame,imageFormat:t.imageFormat},o=await i.renderStill(a);process.stderr.write(F.green(`✓ Frame rendered: ${o.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error rendering preview frame: ${t}\n`)),process.exit(1)}}),Ee=new a.Command(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).option(`--dry-run`,`Validate props and assets without rendering`).action(async t=>{try{process.stderr.write(F.blue(t.dryRun?`🔎 Starting render dry run...
5
17
  `:`🎬 Starting video render...
6
- `)),process.stderr.write(s.default.gray(` Input: ${t.input}\n`)),process.stderr.write(s.default.gray(` Output: ${t.output}\n`)),process.stderr.write(s.default.gray(` Composition: ${t.composition}\n`)),process.stderr.write(s.default.gray(` Codec: ${t.codec}\n`)),process.stderr.write(s.default.gray(` Dry run: ${t.dryRun?`yes`:`no`}\n`)),n.default.existsSync(t.input)||(process.stderr.write(s.default.red(`Error: Input file not found: ${t.input}\n`)),process.exit(1));let r=n.default.readFileSync(t.input,`utf-8`),i=JSON.parse(r),a=e.r().get(e.h.RenderService);process.stderr.write(s.default.blue(t.dryRun?`🔎 Validating render input...
18
+ `)),process.stderr.write(F.gray(` Input: ${t.input}\n`)),process.stderr.write(F.gray(` Output: ${t.output}\n`)),process.stderr.write(F.gray(` Composition: ${t.composition}\n`)),process.stderr.write(F.gray(` Codec: ${t.codec}\n`)),process.stderr.write(F.gray(` Dry run: ${t.dryRun?`yes`:`no`}\n`)),n.default.existsSync(t.input)||(process.stderr.write(F.red(`Error: Input file not found: ${t.input}\n`)),process.exit(1));let r=n.default.readFileSync(t.input,`utf-8`),i=JSON.parse(r),a=e.r().get(e.h.RenderService);process.stderr.write(F.blue(t.dryRun?`🔎 Validating render input...
7
19
  `:`📦 Bundling Remotion project...
8
- `));let o=await a.render({compositionId:t.composition,inputProps:i,outputPath:t.output,codec:t.codec,dryRun:t.dryRun});o.dryRun?(process.stdout.write(`${JSON.stringify(o.validation,null,2)}\n`),process.stderr.write(s.default.green(`✓ Render dry run passed
9
- `))):process.stderr.write(s.default.green(`✓ Video rendered successfully: ${o.outputPath}\n`))}catch(t){t instanceof e._&&(process.stdout.write(`${JSON.stringify({error:`render_validation_failed`,message:t.message,summary:t.validation.summary,issues:t.validation.errors,warnings:t.validation.warnings,suggestedFixes:t.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}\n`),process.stderr.write(s.default.red(`Render validation failed; see JSON output for actionable fixes.
10
- `)),process.exit(1)),process.stderr.write(s.default.red(`Error rendering video: ${t}\n`)),process.exit(1)}});async function T(){let e=new i.Command;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(c),e.addCommand(_),e.addCommand(p),e.addCommand(w),e.addCommand(v),e.addCommand(C),await e.parseAsync(process.argv)}T();
20
+ `));let o=await a.render({compositionId:t.composition,inputProps:i,outputPath:t.output,codec:t.codec,dryRun:t.dryRun});o.dryRun?(process.stdout.write(`${JSON.stringify(o.validation,null,2)}\n`),process.stderr.write(F.green(`✓ Render dry run passed
21
+ `))):process.stderr.write(F.green(`✓ Video rendered successfully: ${o.outputPath}\n`))}catch(t){t instanceof e._&&(process.stdout.write(`${JSON.stringify({error:`render_validation_failed`,message:t.message,summary:t.validation.summary,issues:t.validation.errors,warnings:t.validation.warnings,suggestedFixes:t.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}\n`),process.stderr.write(F.red(`Render validation failed; see JSON output for actionable fixes.
22
+ `)),process.exit(1)),process.stderr.write(F.red(`Error rendering video: ${t}\n`)),process.exit(1)}}),De=new a.Command(`search-videos`).description(`Search YouTube for videos by query and return video metadata without downloading`).requiredOption(`-q, --query <text>`,`Search query for YouTube videos`).option(`-n, --max-results <number>`,`Maximum number of results (default: 5, max: 20)`,e=>Number.parseInt(e,10),5).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async t=>{try{if(!Number.isInteger(t.maxResults)||t.maxResults<1||t.maxResults>20)throw Error(`max-results must be an integer between 1 and 20`);process.stderr.write(F.blue(`Searching videos...
23
+ `)),process.stderr.write(F.gray(` Query: ${t.query}\n`)),process.stderr.write(F.gray(` Max results: ${t.maxResults}\n`));let n=await e.r({proxyUrl:t.proxy}).get(e.h.VideoDownloadService).search(t.query,t.maxResults);process.stderr.write(F.green(`✓ ${n.length} results\n`)),process.stdout.write(`${JSON.stringify({query:t.query,resultCount:n.length,results:n},null,t.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(F.red(`Error searching videos: ${t}\n`)),process.exit(1)}});async function Oe(){let e=new a.Command;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(u),e.addCommand(W),e.addCommand(B),e.addCommand(Ee),e.addCommand(G),e.addCommand(ve),e.addCommand(Se),e.addCommand($),e.addCommand(Te),e.addCommand(ge),e.addCommand(xe),e.addCommand(ye),e.addCommand(De),e.addCommand(de),e.addCommand(me),await e.parseAsync(process.argv)}Oe();
package/dist/cli.mjs CHANGED
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import{_ as e,h as t,n,r,t as i}from"./stdio-KMqWPqUm.mjs";import{spawn as a}from"node:child_process";import o,{existsSync as s}from"node:fs";import c from"node:path";import{Command as l}from"commander";import{DEFAULT_PORT_RANGE as u,PortRegistryService as d}from"@agimon-ai/foundation-port-registry";import{createProcessLease as f}from"@agimon-ai/foundation-process-registry";import p from"chalk";var m=`0.14.1`;const h=[`pnpm-workspace.yaml`,`nx.json`,`.git`],g=`video-editor-mcp-http`,_=`tool`;function v(e=process.cwd()){let t=c.resolve(e);for(;;){for(let e of h)if(s(c.join(t,e)))return t;let e=c.dirname(t);if(e===t)return process.cwd();t=e}}const y=new l(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,String(u.min)).action(async e=>{let t;try{let n=Number.parseInt(e.port,10),r=new d(process.env.PORT_REGISTRY_PATH),i=v(),o=n?{min:n,max:n}:u,s=await r.reservePort({repositoryPath:i,serviceName:g,serviceType:_,preferredPort:n||u.min,portRange:o,pid:process.pid,host:`127.0.0.1`,force:!0});if(!s.success||!s.record)throw Error(s.error||`Failed to reserve port ${n}`);let l=s.record.port;t=await f({repositoryPath:i,serviceName:g,serviceType:_,pid:process.pid,port:l,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2)});let m=c.resolve(import.meta.dirname,`..`);console.error(p.green(`Starting Remotion Studio on port ${l}...`)),console.error(p.gray(` Working directory: ${m}`));let h=a(c.join(m,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(l)],{stdio:`inherit`,cwd:m});h.on(`error`,e=>{console.error(p.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)});let y=async e=>{h.kill(e),await t?.release(),await r.releasePort({repositoryPath:i,serviceName:g,serviceType:_,pid:process.pid}),process.exit(0)};process.on(`SIGINT`,()=>y(`SIGINT`)),process.on(`SIGTERM`,()=>y(`SIGTERM`))}catch(e){await t?.release(),console.error(`Error executing http-serve:`,e),process.exit(1)}}),b=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function x(e=process.cwd()){let t=c.resolve(e);for(;;){for(let e of b)if(s(c.join(t,e)))return t;let e=c.dirname(t);if(e===t)return process.cwd();t=e}}async function S(e,t){await e.start();let n=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await e.stop(),await t?.(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`))}const C=new l(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).action(async e=>{let t;try{let a=e.type.toLowerCase(),o=x();if(a===`stdio`){let a=new i(n(r({proxyUrl:e.proxy})));t=await f({repositoryPath:o,serviceName:`video-editor-mcp-stdio`,pid:process.pid,metadata:{transport:`stdio`}}),await S(a,async()=>{await t?.release()})}else console.error(`Unknown transport type: ${a}. Use: stdio`),process.exit(1)}catch(e){await t?.release(),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),w=new l(`analyze-video`).description(`Analyze one or more local video files with Gemini Code Assist`).requiredOption(`-v, --video <paths...>`,`Paths to local video files to analyze`).requiredOption(`-p, --prompt <text>`,`Prompt to send with the video(s)`).option(`-m, --model <id>`,`Gemini model to use`,`gemini-2.5-flash-lite`).option(`--pretty`,`Pretty-print the full JSON result`,!1).action(async e=>{try{if(!Array.isArray(e.video)||e.video.length===0)throw Error(`At least one video path is required.`);process.stderr.write(p.blue(`🎥 Starting video analysis...
3
- `)),process.stderr.write(p.gray(` Videos: ${e.video.join(`, `)}\n`)),process.stderr.write(p.gray(` Prompt: ${e.prompt}\n`)),process.stderr.write(p.gray(` Model: ${e.model}\n`));let n=await r().get(t.VideoAnalysisService).analyzeVideos({videoPaths:e.video,prompt:e.prompt,model:e.model});if(process.stderr.write(p.green(`✓ Resolved project: ${n.resolvedProject}\n`)),process.stderr.write(p.green(`✓ Videos processed: ${n.videoCount}\n`)),e.pretty){process.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}if(n.responseText){process.stdout.write(`${n.responseText}\n`);return}process.stdout.write(`${JSON.stringify(n.rawResponse,null,2)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(p.red(`Error analyzing video: ${t}\n`)),process.exit(1)}}),T=e=>Number(e),E=e=>Number.parseInt(e,10),D=()=>s(c.resolve(import.meta.dirname,`../package.json`))?c.resolve(import.meta.dirname,`..`):c.resolve(import.meta.dirname,`../..`),O=e=>{if(e.output)return c.resolve(e.output);let t=c.resolve(e.outputDir??c.join(D(),`public/music`)),n=e.filename??`ace-step-music`;return c.join(t,`${n}.${e.audioFormat}`)},k=new l(`generate-music`).description(`Generate background music using ACE-Step 1.5`).requiredOption(`-p, --prompt <text>`,`Music description for ACE-Step text-to-music generation`).option(`-f, --filename <name>`,`Output filename without extension (default: ace-step-music)`).option(`-o, --output <path>`,`Explicit output audio path. Overrides filename/output-dir.`).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/music/)`).option(`--ace-step-path <path>`,`Path to local ACE-Step-1.5 checkout`).option(`--lyrics <text>`,`Lyrics to use. Defaults to [Instrumental] for background music.`).option(`--duration <seconds>`,`Target duration in seconds (default: 30)`,T).option(`--bpm <number>`,`Optional BPM metadata`,T).option(`--keyscale <text>`,`Optional key/scale metadata, e.g. C minor`).option(`--time-signature <text>`,`Optional time signature metadata, e.g. 4/4`).option(`--vocal-language <code>`,`Vocal language code, or unknown`).option(`--seed <number>`,`Optional fixed seed for reproducible generation`,E).option(`--inference-steps <number>`,`Diffusion inference steps (default: 8)`,E).option(`--audio-format <format>`,`Output audio format: wav, mp3, flac`,`wav`).option(`--backend <backend>`,`ACE-Step LM backend: mlx, pt, vllm`,`mlx`).option(`--config-path <name>`,`ACE-Step DiT model config (default: acestep-v15-turbo)`).option(`--lm-model-path <name>`,`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`).option(`--timeout-ms <number>`,`Generation timeout in milliseconds`,E).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(![`wav`,`mp3`,`flac`].includes(e.audioFormat))throw Error(`Unsupported audio format: ${e.audioFormat}`);if(![`mlx`,`pt`,`vllm`].includes(e.backend))throw Error(`Unsupported backend: ${e.backend}`);let n=O(e);process.stderr.write(p.blue(`Starting ACE-Step music generation...
4
- `)),process.stderr.write(p.gray(` Prompt: ${e.prompt}\n`)),process.stderr.write(p.gray(` Output: ${n}\n`)),process.stderr.write(p.gray(` Backend: ${e.backend}\n`));let i=r().get(t.AceStepMusicService);if(!await i.isAvailable(e.aceStepPath))throw Error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass --ace-step-path.`);let a=await i.generateMusic({prompt:e.prompt,outputPath:n,aceStepPath:e.aceStepPath,lyrics:e.lyrics,duration:e.duration,bpm:e.bpm,keyscale:e.keyscale,timeSignature:e.timeSignature,vocalLanguage:e.vocalLanguage,seed:e.seed,inferenceSteps:e.inferenceSteps,audioFormat:e.audioFormat,backend:e.backend,configPath:e.configPath,lmModelPath:e.lmModelPath,timeoutMs:e.timeoutMs}),o={...a,compositionSrc:c.dirname(n)===c.resolve(D(),`public/music`)?`/music/${c.basename(n)}`:void 0};process.stderr.write(p.green(`Music generated successfully: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(p.red(`Error generating music: ${t}\n`)),process.exit(1)}}),A=new l(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).option(`--dry-run`,`Validate props and assets without rendering`).action(async n=>{try{process.stderr.write(p.blue(n.dryRun?`🔎 Starting render dry run...
2
+ import{_ as e,h as t,n,r,t as i}from"./stdio-DCw8Qn1M.mjs";import{spawn as a}from"node:child_process";import o,{existsSync as s}from"node:fs";import c from"node:os";import l from"node:path";import{Command as u}from"commander";import{DEFAULT_PORT_RANGE as d,PortRegistryService as ee}from"@agimon-ai/foundation-port-registry";import{createProcessLease as f}from"@agimon-ai/foundation-process-registry";import p from"node:process";import m from"node:tty";var h=`0.15.0`;const g=(e=0)=>t=>`\u001B[${t+e}m`,_=(e=0)=>t=>`\u001B[${38+e};5;${t}m`,v=(e=0)=>(t,n,r)=>`\u001B[${38+e};2;${t};${n};${r}m`,y={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],gray:[90,39],grey:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgGray:[100,49],bgGrey:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};Object.keys(y.modifier);const te=Object.keys(y.color),ne=Object.keys(y.bgColor);[...te,...ne];function b(){let e=new Map;for(let[t,n]of Object.entries(y)){for(let[t,r]of Object.entries(n))y[t]={open:`\u001B[${r[0]}m`,close:`\u001B[${r[1]}m`},n[t]=y[t],e.set(r[0],r[1]);Object.defineProperty(y,t,{value:n,enumerable:!1})}return Object.defineProperty(y,`codes`,{value:e,enumerable:!1}),y.color.close=`\x1B[39m`,y.bgColor.close=`\x1B[49m`,y.color.ansi=g(),y.color.ansi256=_(),y.color.ansi16m=v(),y.bgColor.ansi=g(10),y.bgColor.ansi256=_(10),y.bgColor.ansi16m=v(10),Object.defineProperties(y,{rgbToAnsi256:{value(e,t,n){return e===t&&t===n?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(t/255*5)+Math.round(n/255*5)},enumerable:!1},hexToRgb:{value(e){let t=/[a-f\d]{6}|[a-f\d]{3}/i.exec(e.toString(16));if(!t)return[0,0,0];let[n]=t;n.length===3&&(n=[...n].map(e=>e+e).join(``));let r=Number.parseInt(n,16);return[r>>16&255,r>>8&255,r&255]},enumerable:!1},hexToAnsi256:{value:e=>y.rgbToAnsi256(...y.hexToRgb(e)),enumerable:!1},ansi256ToAnsi:{value(e){if(e<8)return 30+e;if(e<16)return 90+(e-8);let t,n,r;if(e>=232)t=((e-232)*10+8)/255,n=t,r=t;else{e-=16;let i=e%36;t=Math.floor(e/36)/5,n=Math.floor(i/6)/5,r=i%6/5}let i=Math.max(t,n,r)*2;if(i===0)return 30;let a=30+(Math.round(r)<<2|Math.round(n)<<1|Math.round(t));return i===2&&(a+=60),a},enumerable:!1},rgbToAnsi:{value:(e,t,n)=>y.ansi256ToAnsi(y.rgbToAnsi256(e,t,n)),enumerable:!1},hexToAnsi:{value:e=>y.ansi256ToAnsi(y.hexToAnsi256(e)),enumerable:!1}}),y}const x=b();function S(e,t=globalThis.Deno?globalThis.Deno.args:p.argv){let n=e.startsWith(`-`)?``:e.length===1?`-`:`--`,r=t.indexOf(n+e),i=t.indexOf(`--`);return r!==-1&&(i===-1||r<i)}const{env:C}=p;let w;S(`no-color`)||S(`no-colors`)||S(`color=false`)||S(`color=never`)?w=0:(S(`color`)||S(`colors`)||S(`color=true`)||S(`color=always`))&&(w=1);function T(){if(`FORCE_COLOR`in C)return C.FORCE_COLOR===`true`?1:C.FORCE_COLOR===`false`?0:C.FORCE_COLOR.length===0?1:Math.min(Number.parseInt(C.FORCE_COLOR,10),3)}function E(e){return e===0?!1:{level:e,hasBasic:!0,has256:e>=2,has16m:e>=3}}function D(e,{streamIsTTY:t,sniffFlags:n=!0}={}){let r=T();r!==void 0&&(w=r);let i=n?w:r;if(i===0)return 0;if(n){if(S(`color=16m`)||S(`color=full`)||S(`color=truecolor`))return 3;if(S(`color=256`))return 2}if(`TF_BUILD`in C&&`AGENT_NAME`in C)return 1;if(e&&!t&&i===void 0)return 0;let a=i||0;if(C.TERM===`dumb`)return a;if(p.platform===`win32`){let e=c.release().split(`.`);return Number(e[0])>=10&&Number(e[2])>=10586?Number(e[2])>=14931?3:2:1}if(`CI`in C)return[`GITHUB_ACTIONS`,`GITEA_ACTIONS`,`CIRCLECI`].some(e=>e in C)?3:[`TRAVIS`,`APPVEYOR`,`GITLAB_CI`,`BUILDKITE`,`DRONE`].some(e=>e in C)||C.CI_NAME===`codeship`?1:a;if(`TEAMCITY_VERSION`in C)return+!!/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(C.TEAMCITY_VERSION);if(C.COLORTERM===`truecolor`||C.TERM===`xterm-kitty`||C.TERM===`xterm-ghostty`||C.TERM===`wezterm`)return 3;if(`TERM_PROGRAM`in C){let e=Number.parseInt((C.TERM_PROGRAM_VERSION||``).split(`.`)[0],10);switch(C.TERM_PROGRAM){case`iTerm.app`:return e>=3?3:2;case`Apple_Terminal`:return 2}}return/-256(color)?$/i.test(C.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(C.TERM)||`COLORTERM`in C?1:a}function O(e,t={}){return E(D(e,{streamIsTTY:e&&e.isTTY,...t}))}const re={stdout:O({isTTY:m.isatty(1)}),stderr:O({isTTY:m.isatty(2)})};function ie(e,t,n){let r=e.indexOf(t);if(r===-1)return e;let i=t.length,a=0,o=``;do o+=e.slice(a,r)+t+n,a=r+i,r=e.indexOf(t,a);while(r!==-1);return o+=e.slice(a),o}function ae(e,t,n,r){let i=0,a=``;do{let o=e[r-1]===`\r`;a+=e.slice(i,o?r-1:r)+t+(o?`\r
3
+ `:`
4
+ `)+n,i=r+1,r=e.indexOf(`
5
+ `,i)}while(r!==-1);return a+=e.slice(i),a}const{stdout:k,stderr:A}=re,j=Symbol(`GENERATOR`),M=Symbol(`STYLER`),N=Symbol(`IS_EMPTY`),P=[`ansi`,`ansi`,`ansi256`,`ansi16m`],F=Object.create(null),oe=(e,t={})=>{if(t.level&&!(Number.isInteger(t.level)&&t.level>=0&&t.level<=3))throw Error("The `level` option should be an integer from 0 to 3");let n=k?k.level:0;e.level=t.level===void 0?n:t.level},se=e=>{let t=(...e)=>e.join(` `);return oe(t,e),Object.setPrototypeOf(t,I.prototype),t};function I(e){return se(e)}Object.setPrototypeOf(I.prototype,Function.prototype);for(let[e,t]of Object.entries(x))F[e]={get(){let n=z(this,R(t.open,t.close,this[M]),this[N]);return Object.defineProperty(this,e,{value:n}),n}};F.visible={get(){let e=z(this,this[M],!0);return Object.defineProperty(this,`visible`,{value:e}),e}};const L=(e,t,n,...r)=>e===`rgb`?t===`ansi16m`?x[n].ansi16m(...r):t===`ansi256`?x[n].ansi256(x.rgbToAnsi256(...r)):x[n].ansi(x.rgbToAnsi(...r)):e===`hex`?L(`rgb`,t,n,...x.hexToRgb(...r)):x[n][e](...r);for(let e of[`rgb`,`hex`,`ansi256`]){F[e]={get(){let{level:t}=this;return function(...n){let r=R(L(e,P[t],`color`,...n),x.color.close,this[M]);return z(this,r,this[N])}}};let t=`bg`+e[0].toUpperCase()+e.slice(1);F[t]={get(){let{level:t}=this;return function(...n){let r=R(L(e,P[t],`bgColor`,...n),x.bgColor.close,this[M]);return z(this,r,this[N])}}}}const ce=Object.defineProperties(()=>{},{...F,level:{enumerable:!0,get(){return this[j].level},set(e){this[j].level=e}}}),R=(e,t,n)=>{let r,i;return n===void 0?(r=e,i=t):(r=n.openAll+e,i=t+n.closeAll),{open:e,close:t,openAll:r,closeAll:i,parent:n}},z=(e,t,n)=>{let r=(...e)=>B(r,e.length===1?``+e[0]:e.join(` `));return Object.setPrototypeOf(r,ce),r[j]=e,r[M]=t,r[N]=n,r},B=(e,t)=>{if(e.level<=0||!t)return e[N]?``:t;let n=e[M];if(n===void 0)return t;let{openAll:r,closeAll:i}=n;if(t.includes(`\x1B`))for(;n!==void 0;)t=ie(t,n.close,n.open),n=n.parent;let a=t.indexOf(`
6
+ `);return a!==-1&&(t=ae(t,i,r,a)),r+t+i};Object.defineProperties(I.prototype,F);const V=I();I({level:A?A.level:0});const H=[`pnpm-workspace.yaml`,`nx.json`,`.git`],U=`video-editor-mcp-http`,W=`tool`;function G(e=process.cwd()){let t=l.resolve(e);for(;;){for(let e of H)if(s(l.join(t,e)))return t;let e=l.dirname(t);if(e===t)return process.cwd();t=e}}const le=new u(`http-serve`).description(`Start Remotion Studio for video editing`).option(`-p, --port <port>`,`Port to run Remotion Studio on`,String(d.min)).action(async e=>{let t;try{let n=Number.parseInt(e.port,10),r=new ee(process.env.PORT_REGISTRY_PATH),i=G(),o=n?{min:n,max:n}:d,s=await r.reservePort({repositoryPath:i,serviceName:U,serviceType:W,preferredPort:n||d.min,portRange:o,pid:process.pid,host:`127.0.0.1`,force:!0});if(!s.success||!s.record)throw Error(s.error||`Failed to reserve port ${n}`);let c=s.record.port;t=await f({repositoryPath:i,serviceName:U,serviceType:W,pid:process.pid,port:c,host:`127.0.0.1`,command:process.argv[1],args:process.argv.slice(2)});let u=l.resolve(import.meta.dirname,`..`);console.error(V.green(`Starting Remotion Studio on port ${c}...`)),console.error(V.gray(` Working directory: ${u}`));let p=a(l.join(u,`node_modules`,`.bin`,`remotion`),[`studio`,`--port`,String(c)],{stdio:`inherit`,cwd:u});p.on(`error`,e=>{console.error(V.red(`Failed to start Remotion Studio: ${e.message}`)),process.exit(1)});let m=async e=>{p.kill(e),await t?.release(),await r.releasePort({repositoryPath:i,serviceName:U,serviceType:W,pid:process.pid}),process.exit(0)};process.on(`SIGINT`,()=>m(`SIGINT`)),process.on(`SIGTERM`,()=>m(`SIGTERM`))}catch(e){await t?.release(),console.error(`Error executing http-serve:`,e),process.exit(1)}}),ue=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function de(e=process.cwd()){let t=l.resolve(e);for(;;){for(let e of ue)if(s(l.join(t,e)))return t;let e=l.dirname(t);if(e===t)return process.cwd();t=e}}async function fe(e,t){await e.start();let n=async n=>{console.error(`\nReceived ${n}, shutting down gracefully...`);try{await e.stop(),await t?.(),process.exit(0)}catch(e){console.error(`Error during shutdown:`,e),process.exit(1)}};process.on(`SIGINT`,()=>n(`SIGINT`)),process.on(`SIGTERM`,()=>n(`SIGTERM`))}const pe=new u(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).action(async e=>{let t;try{let a=e.type.toLowerCase(),o=de();if(a===`stdio`){let a=new i(n(r({proxyUrl:e.proxy})));t=await f({repositoryPath:o,serviceName:`video-editor-mcp-stdio`,pid:process.pid,metadata:{transport:`stdio`}}),await fe(a,async()=>{await t?.release()})}else console.error(`Unknown transport type: ${a}. Use: stdio`),process.exit(1)}catch(e){await t?.release(),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),K=new u(`analyze-video`).description(`Analyze one or more local video files with Gemini Code Assist`).requiredOption(`-v, --video <paths...>`,`Paths to local video files to analyze`).requiredOption(`-p, --prompt <text>`,`Prompt to send with the video(s)`).option(`-m, --model <id>`,`Gemini model to use`,`gemini-2.5-flash-lite`).option(`--pretty`,`Pretty-print the full JSON result`,!1).action(async e=>{try{if(!Array.isArray(e.video)||e.video.length===0)throw Error(`At least one video path is required.`);process.stderr.write(V.blue(`🎥 Starting video analysis...
7
+ `)),process.stderr.write(V.gray(` Videos: ${e.video.join(`, `)}\n`)),process.stderr.write(V.gray(` Prompt: ${e.prompt}\n`)),process.stderr.write(V.gray(` Model: ${e.model}\n`));let n=await r().get(t.VideoAnalysisService).analyzeVideos({videoPaths:e.video,prompt:e.prompt,model:e.model});if(process.stderr.write(V.green(`✓ Resolved project: ${n.resolvedProject}\n`)),process.stderr.write(V.green(`✓ Videos processed: ${n.videoCount}\n`)),e.pretty){process.stdout.write(`${JSON.stringify(n,null,2)}\n`);return}if(n.responseText){process.stdout.write(`${n.responseText}\n`);return}process.stdout.write(`${JSON.stringify(n.rawResponse,null,2)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error analyzing video: ${t}\n`)),process.exit(1)}}),me=e=>Number.parseInt(e,10),he=()=>s(l.resolve(import.meta.dirname,`../package.json`))?l.resolve(import.meta.dirname,`..`):l.resolve(import.meta.dirname,`../..`),ge=new u(`download-clip`).description(`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming`).requiredOption(`-u, --url <url>`,`Video URL (YouTube, TikTok, Instagram, or any yt-dlp supported site)`).requiredOption(`-f, --filename <name>`,`Output filename without extension (e.g. my_clip)`).option(`--start-time <time>`,`Start time for clip extraction (e.g. 0:30, 1:15)`).option(`--end-time <time>`,`End time for clip extraction (e.g. 1:00, 2:30)`).option(`--max-height <number>`,`Maximum video height in pixels (default: 1080)`,me,1080).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/videos/)`).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{let n=l.resolve(he(),`public/videos`),i=e.outputDir??n;process.stderr.write(V.blue(`Downloading clip...
8
+ `)),process.stderr.write(V.gray(` URL: ${e.url}\n`)),process.stderr.write(V.gray(` Output: ${l.join(i,`${e.filename}.mp4`)}\n`));let a=await r({proxyUrl:e.proxy}).get(t.VideoDownloadService).downloadClip(e.url,i,e.filename,e.startTime,e.endTime,e.maxHeight),o={...a,compositionSrc:e.outputDir?void 0:`/videos/${e.filename}.mp4`,suggestedComposition:a.height>a.width?`Vertical`:`Main`};process.stderr.write(V.green(`✓ Downloaded: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error downloading clip: ${t}\n`)),process.exit(1)}}),_e=e=>Number(e),q=e=>Number.parseInt(e,10),ve=[`jpeg`,`png`],ye=new u(`extract-video-context`).description(`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`).requiredOption(`-v, --video-path <path>`,`Path to the local video file`).option(`-o, --output-dir <path>`,`Directory to write transcript, filmstrips, and manifest`).option(`--basename <name>`,`Base name for the generated context directory when output-dir is omitted`).option(`--frames-per-second <number>`,`Frames per second to sample (default: 3)`,_e,3).option(`--image-format <format>`,`Frame image format: jpeg or png`,`jpeg`).option(`--tile-width <number>`,`Width of each filmstrip tile in pixels (default: 250)`,q,250).option(`--filmstrip-columns <number>`,`Maximum columns per segment filmstrip (default: 6)`,q,6).option(`--filmstrip-rows <number>`,`Maximum rows per segment filmstrip (default: 4)`,q,4).option(`--no-transcribe`,`Skip local Whisper transcription`).option(`--language <code>`,`Optional Whisper language hint`).option(`--whisper-model <name>`,`Python Whisper model name (default: base)`).option(`--whisper-model-path <path>`,`Whisper.cpp model path override`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(!ve.includes(e.imageFormat))throw Error(`Unsupported image format: ${e.imageFormat}. Use: jpeg, png`);process.stderr.write(V.blue(`Extracting video context...
9
+ `)),process.stderr.write(V.gray(` Video: ${e.videoPath}\n`)),process.stderr.write(V.gray(` Transcribe: ${e.transcribe?`yes`:`no`}\n`));let n=r().get(t.VideoContextService),i={videoPath:e.videoPath,outputDir:e.outputDir,basename:e.basename,framesPerSecond:e.framesPerSecond,imageFormat:e.imageFormat,tileWidth:e.tileWidth,filmstripColumns:e.filmstripColumns,filmstripRows:e.filmstripRows,transcribe:e.transcribe,language:e.language,whisperModel:e.whisperModel,whisperModelPath:e.whisperModelPath},a=await n.extractVideoContext(i);process.stderr.write(V.green(`✓ Context extracted (${a.transcript.segments.length} segments)\n`)),process.stdout.write(`${JSON.stringify(a,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error extracting video context: ${t}\n`)),process.exit(1)}}),be=e=>Number(e),J=e=>Number.parseInt(e,10),Y=[`tiktok`,`reels`,`shorts`,`universal`],X=[`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`],xe=new u(`generate-captions`).description(`Create TikTok-style caption pages from a caption array for video overlay`).requiredOption(`-c, --captions <path>`,`Path to a JSON file containing the captions array`).option(`--combine-ms <number>`,`Milliseconds threshold for combining captions into pages`,be,100).option(`--max-words-per-page <number>`,`Maximum words per caption page (default: 6)`,J).option(`--max-chars-per-line <number>`,`Target maximum characters per caption line (default: 22)`,J).option(`--platform <platform>`,`Platform safe-zone preset: tiktok, reels, shorts, universal`).option(`--preset <preset>`,`Caption visual style preset: tiktok-bold, karaoke, premium-pill, minimal, ugc`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(e=>{try{if(e.platform&&!Y.includes(e.platform))throw Error(`Unsupported platform: ${e.platform}. Use: ${Y.join(`, `)}`);if(e.preset&&!X.includes(e.preset))throw Error(`Unsupported preset: ${e.preset}. Use: ${X.join(`, `)}`);if(!o.existsSync(e.captions))throw Error(`Captions file not found: ${e.captions}`);process.stderr.write(V.blue(`Generating caption pages...
10
+ `));let n=JSON.parse(o.readFileSync(e.captions,`utf-8`)),i=r().get(t.CaptionService).createTikTokCaptions(n,{combineMs:e.combineMs,maxWordsPerPage:e.maxWordsPerPage,maxCharsPerLine:e.maxCharsPerLine,platform:e.platform,preset:e.preset});process.stderr.write(V.green(`✓ ${i.pages.length} caption pages\n`)),process.stdout.write(`${JSON.stringify(i,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error generating captions: ${t}\n`)),process.exit(1)}}),Z=e=>Number(e),Q=e=>Number.parseInt(e,10),$=()=>s(l.resolve(import.meta.dirname,`../package.json`))?l.resolve(import.meta.dirname,`..`):l.resolve(import.meta.dirname,`../..`),Se=e=>{if(e.output)return l.resolve(e.output);let t=l.resolve(e.outputDir??l.join($(),`public/music`)),n=e.filename??`ace-step-music`;return l.join(t,`${n}.${e.audioFormat}`)},Ce=new u(`generate-music`).description(`Generate background music using ACE-Step 1.5`).requiredOption(`-p, --prompt <text>`,`Music description for ACE-Step text-to-music generation`).option(`-f, --filename <name>`,`Output filename without extension (default: ace-step-music)`).option(`-o, --output <path>`,`Explicit output audio path. Overrides filename/output-dir.`).option(`--output-dir <path>`,`Custom output directory (default: Remotion public/music/)`).option(`--ace-step-path <path>`,`Path to local ACE-Step-1.5 checkout`).option(`--lyrics <text>`,`Lyrics to use. Defaults to [Instrumental] for background music.`).option(`--duration <seconds>`,`Target duration in seconds (default: 30)`,Z).option(`--bpm <number>`,`Optional BPM metadata`,Z).option(`--keyscale <text>`,`Optional key/scale metadata, e.g. C minor`).option(`--time-signature <text>`,`Optional time signature metadata, e.g. 4/4`).option(`--vocal-language <code>`,`Vocal language code, or unknown`).option(`--seed <number>`,`Optional fixed seed for reproducible generation`,Q).option(`--inference-steps <number>`,`Diffusion inference steps (default: 8)`,Q).option(`--audio-format <format>`,`Output audio format: wav, mp3, flac`,`wav`).option(`--backend <backend>`,`ACE-Step LM backend: mlx, pt, vllm`,`mlx`).option(`--config-path <name>`,`ACE-Step DiT model config (default: acestep-v15-turbo)`).option(`--lm-model-path <name>`,`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`).option(`--timeout-ms <number>`,`Generation timeout in milliseconds`,Q).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(![`wav`,`mp3`,`flac`].includes(e.audioFormat))throw Error(`Unsupported audio format: ${e.audioFormat}`);if(![`mlx`,`pt`,`vllm`].includes(e.backend))throw Error(`Unsupported backend: ${e.backend}`);let n=Se(e);process.stderr.write(V.blue(`Starting ACE-Step music generation...
11
+ `)),process.stderr.write(V.gray(` Prompt: ${e.prompt}\n`)),process.stderr.write(V.gray(` Output: ${n}\n`)),process.stderr.write(V.gray(` Backend: ${e.backend}\n`));let i=r().get(t.AceStepMusicService);if(!await i.isAvailable(e.aceStepPath))throw Error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass --ace-step-path.`);let a=await i.generateMusic({prompt:e.prompt,outputPath:n,aceStepPath:e.aceStepPath,lyrics:e.lyrics,duration:e.duration,bpm:e.bpm,keyscale:e.keyscale,timeSignature:e.timeSignature,vocalLanguage:e.vocalLanguage,seed:e.seed,inferenceSteps:e.inferenceSteps,audioFormat:e.audioFormat,backend:e.backend,configPath:e.configPath,lmModelPath:e.lmModelPath,timeoutMs:e.timeoutMs}),o={...a,compositionSrc:l.dirname(n)===l.resolve($(),`public/music`)?`/music/${l.basename(n)}`:void 0};process.stderr.write(V.green(`Music generated successfully: ${a.outputPath}\n`)),process.stdout.write(`${JSON.stringify(o,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error generating music: ${t}\n`)),process.exit(1)}}),we=new u(`generate-voiceover`).description(`Generate voiceover audio from text using Kokoro TTS local engine`).requiredOption(`-t, --text <text>`,`Text to convert to speech`).requiredOption(`-o, --output <path>`,`Output audio file path`).option(`--voice <id>`,`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`).option(`--speed <number>`,`Speech speed multiplier (default: 1.0)`,e=>Number(e)).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{process.stderr.write(V.blue(`Generating voiceover...
12
+ `)),process.stderr.write(V.gray(` Voice: ${e.voice??`af_sarah`}\n`)),process.stderr.write(V.gray(` Output: ${e.output}\n`));let n=r().get(t.VoiceoverService);if(!await n.isAvailable())throw Error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let i=await n.generateVoiceover({text:e.text,voice:e.voice,speed:e.speed,outputPath:e.output});process.stderr.write(V.green(`✓ Voiceover generated: ${i.outputPath}\n`)),process.stdout.write(`${JSON.stringify(i,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error generating voiceover: ${t}\n`)),process.exit(1)}}),Te=[`video`,`audio`],Ee=new u(`get-media-info`).description(`Get duration, dimensions, and decodability info for video/audio files`).requiredOption(`-s, --src <path>`,`Path to the media file`).option(`-t, --type <type>`,`Media type: video or audio`,`video`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(!Te.includes(e.type))throw Error(`Unsupported media type: ${e.type}. Use: video, audio`);process.stderr.write(V.blue(`Inspecting media...
13
+ `)),process.stderr.write(V.gray(` Src: ${e.src}\n`)),process.stderr.write(V.gray(` Type: ${e.type}\n`));let n=r().get(t.MediaInfoService),i=await n.canDecode(e.src),a;if(e.type===`audio`){let t=await n.getAudioDuration(e.src);a={src:e.src,type:`audio`,duration:t,canDecode:i}}else{let[t,r]=await Promise.all([n.getVideoDuration(e.src),n.getVideoDimensions(e.src)]);a={src:e.src,type:`video`,duration:t,width:r.width,height:r.height,canDecode:i}}process.stderr.write(V.green(`✓ decodable: ${i}\n`)),process.stdout.write(`${JSON.stringify(a,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error getting media info: ${t}\n`)),process.exit(1)}}),De=new u(`import-srt`).description(`Parse SRT subtitle file content into structured Caption format for video overlay`).requiredOption(`-i, --input <path>`,`Path to the .srt subtitle file`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(e=>{try{if(!o.existsSync(e.input))throw Error(`SRT file not found: ${e.input}`);process.stderr.write(V.blue(`Parsing SRT...
14
+ `));let n=o.readFileSync(e.input,`utf-8`),i=r().get(t.CaptionService).parseSrt(n);process.stderr.write(V.green(`✓ ${i.length} captions parsed\n`)),process.stdout.write(`${JSON.stringify({captionCount:i.length,captions:i},null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error importing SRT: ${t}\n`)),process.exit(1)}}),Oe=new u(`list-compositions`).description(`List all available Remotion compositions with their schemas, dimensions, and format info`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(e=>{try{process.stderr.write(V.blue(`Listing compositions...
15
+ `));let n=r().get(t.CompositionService).listCompositions();process.stderr.write(V.green(`✓ ${n.compositionCount} compositions\n`)),process.stdout.write(`${JSON.stringify(n,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error listing compositions: ${t}\n`)),process.exit(1)}}),ke=e=>Number.parseInt(e,10),Ae=[`png`,`jpeg`],je=new u(`preview-frame`).description(`Render a single frame from a composition as a PNG or JPEG image for preview`).requiredOption(`-c, --composition <id>`,`Composition ID (e.g. Main, Vertical, Square)`).requiredOption(`-i, --input <path>`,`Path to JSON inputProps file`).requiredOption(`-o, --output <path>`,`Output image file path`).option(`--frame <number>`,`Frame number to render (default: 0)`,ke).option(`--image-format <format>`,`Image format: png or jpeg`,`png`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(!Ae.includes(e.imageFormat))throw Error(`Unsupported image format: ${e.imageFormat}. Use: png, jpeg`);if(!o.existsSync(e.input))throw Error(`Input file not found: ${e.input}`);process.stderr.write(V.blue(`Rendering preview frame...
16
+ `)),process.stderr.write(V.gray(` Composition: ${e.composition}\n`)),process.stderr.write(V.gray(` Frame: ${e.frame??0}\n`)),process.stderr.write(V.gray(` Output: ${e.output}\n`));let n=JSON.parse(o.readFileSync(e.input,`utf-8`)),i=r().get(t.RenderService),a={compositionId:e.composition,inputProps:n,outputPath:e.output,frame:e.frame,imageFormat:e.imageFormat},s=await i.renderStill(a);process.stderr.write(V.green(`✓ Frame rendered: ${s.outputPath}\n`)),process.stdout.write(`${JSON.stringify(s,null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error rendering preview frame: ${t}\n`)),process.exit(1)}}),Me=new u(`render`).description(`Render a video from JSON props file`).requiredOption(`-i, --input <path>`,`Path to JSON props file`).requiredOption(`-o, --output <path>`,`Output video file path`).option(`-c, --composition <id>`,`Composition ID`,`Main`).option(`--codec <codec>`,`Video codec (h264, h265, vp8, vp9)`,`h264`).option(`--dry-run`,`Validate props and assets without rendering`).action(async n=>{try{process.stderr.write(V.blue(n.dryRun?`🔎 Starting render dry run...
5
17
  `:`🎬 Starting video render...
6
- `)),process.stderr.write(p.gray(` Input: ${n.input}\n`)),process.stderr.write(p.gray(` Output: ${n.output}\n`)),process.stderr.write(p.gray(` Composition: ${n.composition}\n`)),process.stderr.write(p.gray(` Codec: ${n.codec}\n`)),process.stderr.write(p.gray(` Dry run: ${n.dryRun?`yes`:`no`}\n`)),o.existsSync(n.input)||(process.stderr.write(p.red(`Error: Input file not found: ${n.input}\n`)),process.exit(1));let e=o.readFileSync(n.input,`utf-8`),i=JSON.parse(e),a=r().get(t.RenderService);process.stderr.write(p.blue(n.dryRun?`🔎 Validating render input...
18
+ `)),process.stderr.write(V.gray(` Input: ${n.input}\n`)),process.stderr.write(V.gray(` Output: ${n.output}\n`)),process.stderr.write(V.gray(` Composition: ${n.composition}\n`)),process.stderr.write(V.gray(` Codec: ${n.codec}\n`)),process.stderr.write(V.gray(` Dry run: ${n.dryRun?`yes`:`no`}\n`)),o.existsSync(n.input)||(process.stderr.write(V.red(`Error: Input file not found: ${n.input}\n`)),process.exit(1));let e=o.readFileSync(n.input,`utf-8`),i=JSON.parse(e),a=r().get(t.RenderService);process.stderr.write(V.blue(n.dryRun?`🔎 Validating render input...
7
19
  `:`📦 Bundling Remotion project...
8
- `));let s=await a.render({compositionId:n.composition,inputProps:i,outputPath:n.output,codec:n.codec,dryRun:n.dryRun});s.dryRun?(process.stdout.write(`${JSON.stringify(s.validation,null,2)}\n`),process.stderr.write(p.green(`✓ Render dry run passed
9
- `))):process.stderr.write(p.green(`✓ Video rendered successfully: ${s.outputPath}\n`))}catch(t){t instanceof e&&(process.stdout.write(`${JSON.stringify({error:`render_validation_failed`,message:t.message,summary:t.validation.summary,issues:t.validation.errors,warnings:t.validation.warnings,suggestedFixes:t.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}\n`),process.stderr.write(p.red(`Render validation failed; see JSON output for actionable fixes.
10
- `)),process.exit(1)),process.stderr.write(p.red(`Error rendering video: ${t}\n`)),process.exit(1)}});async function j(){let e=new l;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(m),e.addCommand(C),e.addCommand(y),e.addCommand(A),e.addCommand(w),e.addCommand(k),await e.parseAsync(process.argv)}j();export{};
20
+ `));let s=await a.render({compositionId:n.composition,inputProps:i,outputPath:n.output,codec:n.codec,dryRun:n.dryRun});s.dryRun?(process.stdout.write(`${JSON.stringify(s.validation,null,2)}\n`),process.stderr.write(V.green(`✓ Render dry run passed
21
+ `))):process.stderr.write(V.green(`✓ Video rendered successfully: ${s.outputPath}\n`))}catch(t){t instanceof e&&(process.stdout.write(`${JSON.stringify({error:`render_validation_failed`,message:t.message,summary:t.validation.summary,issues:t.validation.errors,warnings:t.validation.warnings,suggestedFixes:t.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}\n`),process.stderr.write(V.red(`Render validation failed; see JSON output for actionable fixes.
22
+ `)),process.exit(1)),process.stderr.write(V.red(`Error rendering video: ${t}\n`)),process.exit(1)}}),Ne=new u(`search-videos`).description(`Search YouTube for videos by query and return video metadata without downloading`).requiredOption(`-q, --query <text>`,`Search query for YouTube videos`).option(`-n, --max-results <number>`,`Maximum number of results (default: 5, max: 20)`,e=>Number.parseInt(e,10),5).option(`--proxy <url>`,`Proxy URL for yt-dlp (HTTP/HTTPS/SOCKS5, e.g. socks5://user:pass@host:port)`).option(`--pretty`,`Pretty-print the JSON result`,!1).action(async e=>{try{if(!Number.isInteger(e.maxResults)||e.maxResults<1||e.maxResults>20)throw Error(`max-results must be an integer between 1 and 20`);process.stderr.write(V.blue(`Searching videos...
23
+ `)),process.stderr.write(V.gray(` Query: ${e.query}\n`)),process.stderr.write(V.gray(` Max results: ${e.maxResults}\n`));let n=await r({proxyUrl:e.proxy}).get(t.VideoDownloadService).search(e.query,e.maxResults);process.stderr.write(V.green(`✓ ${n.length} results\n`)),process.stdout.write(`${JSON.stringify({query:e.query,resultCount:n.length,results:n},null,e.pretty?2:0)}\n`)}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(V.red(`Error searching videos: ${t}\n`)),process.exit(1)}});async function Pe(){let e=new u;e.name(`video-editor-mcp`).description(`MCP server for video editing with Remotion`).version(h),e.addCommand(pe),e.addCommand(le),e.addCommand(Me),e.addCommand(K),e.addCommand(Ce),e.addCommand(Oe),e.addCommand(Ee),e.addCommand(je),e.addCommand(xe),e.addCommand(De),e.addCommand(we),e.addCommand(Ne),e.addCommand(ge),e.addCommand(ye),await e.parseAsync(process.argv)}Pe();export{};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-1ofTrT91.cjs`);exports.AnimationConfigSchema=e.S,exports.AudioClipSchema=e.C,Object.defineProperty(exports,`BaseTool`,{enumerable:!0,get:function(){return e.p}}),exports.CaptionConfigSchema=e.v,exports.CaptionSchema=e.y,exports.ClipSchema=e.w,Object.defineProperty(exports,`ExtractVideoContextTool`,{enumerable:!0,get:function(){return e.f}}),exports.FontConfigSchema=e.b,Object.defineProperty(exports,`GenerateCaptionsTool`,{enumerable:!0,get:function(){return e.d}}),Object.defineProperty(exports,`GenerateVoiceoverTool`,{enumerable:!0,get:function(){return e.u}}),Object.defineProperty(exports,`GetMediaInfoTool`,{enumerable:!0,get:function(){return e.l}}),exports.GifClipSchema=e.T,exports.ImageClipSchema=e.E,Object.defineProperty(exports,`ImportSrtTool`,{enumerable:!0,get:function(){return e.c}}),Object.defineProperty(exports,`ListCompositionsTool`,{enumerable:!0,get:function(){return e.s}}),exports.LottieClipSchema=e.D,exports.MainCompositionSchema=e.x,Object.defineProperty(exports,`PreviewFrameTool`,{enumerable:!0,get:function(){return e.o}}),Object.defineProperty(exports,`RenderService`,{enumerable:!0,get:function(){return e.g}}),Object.defineProperty(exports,`RenderVideoTool`,{enumerable:!0,get:function(){return e.a}}),exports.StdioTransportHandler=e.t,exports.SubtitleClipSchema=e.O,exports.TYPES=e.h,exports.TextClipSchema=e.k,exports.TransitionConfigSchema=e.A,exports.VideoClipSchema=e.j,Object.defineProperty(exports,`VideoContextService`,{enumerable:!0,get:function(){return e.m}}),Object.defineProperty(exports,`VideoUnderstandingTool`,{enumerable:!0,get:function(){return e.i}}),exports.createContainer=e.r,exports.createServer=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-C7W9m2hj.cjs`);exports.AnimationConfigSchema=e.S,exports.AudioClipSchema=e.C,Object.defineProperty(exports,`BaseTool`,{enumerable:!0,get:function(){return e.p}}),exports.CaptionConfigSchema=e.v,exports.CaptionSchema=e.y,exports.ClipSchema=e.w,Object.defineProperty(exports,`ExtractVideoContextTool`,{enumerable:!0,get:function(){return e.f}}),exports.FontConfigSchema=e.b,Object.defineProperty(exports,`GenerateCaptionsTool`,{enumerable:!0,get:function(){return e.d}}),Object.defineProperty(exports,`GenerateVoiceoverTool`,{enumerable:!0,get:function(){return e.u}}),Object.defineProperty(exports,`GetMediaInfoTool`,{enumerable:!0,get:function(){return e.l}}),exports.GifClipSchema=e.T,exports.ImageClipSchema=e.E,Object.defineProperty(exports,`ImportSrtTool`,{enumerable:!0,get:function(){return e.c}}),Object.defineProperty(exports,`ListCompositionsTool`,{enumerable:!0,get:function(){return e.s}}),exports.LottieClipSchema=e.D,exports.MainCompositionSchema=e.x,Object.defineProperty(exports,`PreviewFrameTool`,{enumerable:!0,get:function(){return e.o}}),Object.defineProperty(exports,`RenderService`,{enumerable:!0,get:function(){return e.g}}),Object.defineProperty(exports,`RenderVideoTool`,{enumerable:!0,get:function(){return e.a}}),exports.StdioTransportHandler=e.t,exports.SubtitleClipSchema=e.O,exports.TYPES=e.h,exports.TextClipSchema=e.k,exports.TransitionConfigSchema=e.A,exports.VideoClipSchema=e.j,Object.defineProperty(exports,`VideoContextService`,{enumerable:!0,get:function(){return e.m}}),Object.defineProperty(exports,`VideoUnderstandingTool`,{enumerable:!0,get:function(){return e.i}}),exports.createContainer=e.r,exports.createServer=e.n;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{A as e,C as t,D as n,E as r,O as i,S as a,T as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,j as h,k as g,l as _,m as v,n as y,o as b,p as x,r as S,s as C,t as w,u as T,v as E,w as D,x as O,y as k}from"./stdio-KMqWPqUm.mjs";export{a as AnimationConfigSchema,t as AudioClipSchema,x as BaseTool,E as CaptionConfigSchema,k as CaptionSchema,D as ClipSchema,d as ExtractVideoContextTool,c as FontConfigSchema,u as GenerateCaptionsTool,T as GenerateVoiceoverTool,_ as GetMediaInfoTool,o as GifClipSchema,r as ImageClipSchema,l as ImportSrtTool,C as ListCompositionsTool,n as LottieClipSchema,O as MainCompositionSchema,b as PreviewFrameTool,f as RenderService,s as RenderVideoTool,w as StdioTransportHandler,i as SubtitleClipSchema,p as TYPES,g as TextClipSchema,e as TransitionConfigSchema,h as VideoClipSchema,v as VideoContextService,m as VideoUnderstandingTool,S as createContainer,y as createServer};
1
+ import{A as e,C as t,D as n,E as r,O as i,S as a,T as o,a as s,b as c,c as l,d as u,f as d,g as f,h as p,i as m,j as h,k as g,l as _,m as v,n as y,o as b,p as x,r as S,s as C,t as w,u as T,v as E,w as D,x as O,y as k}from"./stdio-DCw8Qn1M.mjs";export{a as AnimationConfigSchema,t as AudioClipSchema,x as BaseTool,E as CaptionConfigSchema,k as CaptionSchema,D as ClipSchema,d as ExtractVideoContextTool,c as FontConfigSchema,u as GenerateCaptionsTool,T as GenerateVoiceoverTool,_ as GetMediaInfoTool,o as GifClipSchema,r as ImageClipSchema,l as ImportSrtTool,C as ListCompositionsTool,n as LottieClipSchema,O as MainCompositionSchema,b as PreviewFrameTool,f as RenderService,s as RenderVideoTool,w as StdioTransportHandler,i as SubtitleClipSchema,p as TYPES,g as TextClipSchema,e as TransitionConfigSchema,h as VideoClipSchema,v as VideoContextService,m as VideoUnderstandingTool,S as createContainer,y as createServer};
@@ -0,0 +1,17 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));require(`reflect-metadata`);let c=require(`inversify`),l=require(`node:child_process`),u=require(`node:fs`);u=s(u,1);let d=require(`node:fs/promises`);d=s(d,1);let f=require(`node:os`);f=s(f,1);let p=require(`node:path`);p=s(p,1);let m=require(`node:util`),h=require(`node:crypto`);h=s(h,1);let g=require(`node:url`),_=require(`@remotion/bundler`),v=require(`@remotion/renderer`),y=require(`sharp`);y=s(y,1);let b=require(`zod`),ee=require(`@agimon-ai/foundation-validator`),te=require(`@modelcontextprotocol/sdk/server/index.js`),ne=require(`@modelcontextprotocol/sdk/types.js`),re=require(`@modelcontextprotocol/sdk/server/stdio.js`);function x(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}const ie=(0,m.promisify)(l.execFile);let S=class{async isAvailable(e){let t=this.resolveAceStepPath(e);if(!this.isAceStepCheckout(t))return!1;try{return await ie(`uv`,[`--version`]),!0}catch{return!1}}async generateMusic(e){let t=this.resolveAceStepPath(e.aceStepPath);if(!this.isAceStepCheckout(t))throw Error(`ACE-Step 1.5 checkout not found at ${t}. Pass aceStepPath or set ACE_STEP_15_DIR.`);let n=e.audioFormat??this.getFormatFromPath(e.outputPath),r=e.backend??`mlx`,i=e.duration??30,a=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`ace-step-music-`)),o=p.default.join(a,`generation.toml`),s=p.default.join(a,`output`);await d.default.mkdir(s,{recursive:!0}),await d.default.mkdir(p.default.dirname(e.outputPath),{recursive:!0});try{let a=this.buildConfig({...e,aceStepPath:t,audioFormat:n,backend:r,duration:i,outputDir:s});await d.default.writeFile(o,a,`utf-8`);let{stdout:c,stderr:l}=await ie(`uv`,[`run`,`python`,`cli.py`,`-c`,o,`--backend`,r,`--log-level`,`INFO`],{cwd:t,timeout:e.timeoutMs??1200*1e3,maxBuffer:50*1024*1024}),u=await this.findNewestGeneratedAudio(s,n);return await d.default.copyFile(u,e.outputPath),{outputPath:e.outputPath,sourcePath:u,duration:i,audioFormat:n,backend:r,aceStepPath:t,stdout:c,stderr:l}}catch(e){throw Error(`Failed to generate ACE-Step music: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{await d.default.rm(a,{recursive:!0,force:!0}).catch(()=>void 0)}}resolveAceStepPath(e){return p.default.resolve(e??process.env.ACE_STEP_15_DIR??process.env.ACESTEP_15_DIR??p.default.join(f.default.homedir(),`workspace`,`ACE-Step-1.5`))}isAceStepCheckout(e){return(0,u.existsSync)(p.default.join(e,`cli.py`))&&(0,u.existsSync)(p.default.join(e,`pyproject.toml`))}getFormatFromPath(e){let t=p.default.extname(e).toLowerCase().replace(`.`,``);return t===`mp3`||t===`flac`||t===`wav`?t:`wav`}async findNewestGeneratedAudio(e,t){let n=await d.default.readdir(e,{withFileTypes:!0}),r=await Promise.all(n.filter(e=>e.isFile()&&e.name.endsWith(`.${t}`)).map(async t=>{let n=p.default.join(e,t.name);return{filePath:n,mtimeMs:(await d.default.stat(n)).mtimeMs}}));if(r.sort((e,t)=>t.mtimeMs-e.mtimeMs),!r[0])throw Error(`ACE-Step completed but no .${t} file was found in ${e}`);return r[0].filePath}buildConfig(e){let t=e.lyrics?.trim()||`[Instrumental]`,n=t.toLowerCase()===`[instrumental]`,r={project_root:e.aceStepPath,checkpoint_dir:p.default.join(e.aceStepPath,`checkpoints`),config_path:e.configPath??`acestep-v15-turbo`,lm_model_path:e.lmModelPath??`acestep-5Hz-lm-0.6B`,backend:e.backend,device:`auto`,offload_to_cpu:!1,offload_dit_to_cpu:!1,save_dir:e.outputDir,audio_format:e.audioFormat,caption:e.prompt,lyrics:t,duration:e.duration,instrumental:n,bpm:e.bpm??null,keyscale:e.keyscale??``,timesignature:e.timeSignature??``,vocal_language:e.vocalLanguage??`unknown`,task_type:`text2music`,sample_mode:!1,sample_query:``,use_format:!1,inference_steps:e.inferenceSteps??8,seed:e.seed??-1,use_random_seed:e.seed===void 0,guidance_scale:7,use_adg:!1,shift:3,infer_method:`ode`,thinking:!0,lm_temperature:.85,lm_cfg_scale:2,lm_top_k:0,lm_top_p:.9,use_cot_metas:!0,use_cot_caption:!0,use_cot_lyrics:!1,use_cot_language:!0,use_constrained_decoding:!0,batch_size:1,allow_lm_batch:!1,lm_batch_chunk_size:8,constrained_decoding_debug:!1,cfg_interval_start:0,cfg_interval_end:1,lm_negative_prompt:`NO USER INPUT`,log_level:`INFO`};return Object.entries(r).filter(e=>e[1]!==null).map(([e,t])=>`${e} = ${this.toTomlValue(t)}`).join(`
2
+ `)}toTomlValue(e){return typeof e==`string`?JSON.stringify(e):String(e)}};S=x([(0,c.injectable)()],S);const ae={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};let C=class{parseSrt(e){let t=[],n=e.split(`
3
+ `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
4
+ `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t=100){let n=typeof t==`number`?{combineMs:t}:t,r=n.combineMs??100,i=n.maxWordsPerPage??6,a=n.maxCharsPerLine??22,o=n.platform??`tiktok`,s=n.preset??`tiktok-bold`,c=this.createCaptionPages(e,{combineMs:r,maxWordsPerPage:i});return{pages:c,captionConfig:{captions:e,pages:c,style:s===`karaoke`?`karaoke`:`tiktok`,preset:s,platform:o,combineTokensWithinMilliseconds:r,maxWordsPerPage:i,maxCharsPerLine:a,maxLines:2,maxWidthPct:.86,fontSize:60,minFontSize:42,fontFamily:`Arial, sans-serif`,fontWeight:800,lineHeight:1.08,color:`#ffffff`,inactiveColor:`#ffffff`,highlightColor:`#FFD700`,textColor:`#ffffff`,strokeColor:`#000000`,strokeWidth:5,shadow:!0,backgroundColor:`rgba(0, 0, 0, 0)`,backgroundRadius:8,safeZone:ae[o],position:`bottom`}}}createCaptionPages(e,t={}){let n=[],r=this.normalizeCaptionsToTokens(e),i=t.combineMs??100,a=t.maxWordsPerPage??6;if(r.length===0)return n;let o={startMs:r[0]?.startMs??0,endMs:r[0]?.endMs??0,tokens:[r[0]]};for(let e=1;e<r.length;e++){let t=r[e];if(!t)continue;let s=t.startMs-o.endMs,c=this.countWords(o.tokens.map(e=>e.text).join(` `)),l=this.countWords(t.text);s<=i&&c+l<=a?(o.tokens.push(t),o.endMs=t.endMs):(o.text=this.pageText(o.tokens),n.push(o),o={startMs:t.startMs,endMs:t.endMs,tokens:[t]})}return o.tokens.length>0&&(o.text=this.pageText(o.tokens),n.push(o)),n}normalizeCaptionsToTokens(e){return[...e].sort((e,t)=>e.startMs-t.startMs).flatMap(e=>this.captionToTokens(e))}getPlatformSafeZone(e){return ae[e]}captionToTokens(e){let t=e.text.trim().split(/\s+/).filter(Boolean);if(t.length===0)return[];if(t.length===1)return[{...e,text:t[0]??e.text,emphasis:e.emphasis??`none`}];let n=Math.max(1,e.endMs-e.startMs)/t.length;return t.map((r,i)=>{let a=Math.round(e.startMs+n*i),o=i===t.length-1?e.endMs:Math.round(e.startMs+n*(i+1));return{text:r,startMs:a,endMs:Math.max(o,a+1),timestampMs:a,confidence:e.confidence,emphasis:e.emphasis??`none`}})}pageText(e){return e.map(e=>e.text).join(` `)}countWords(e){return e.trim().split(/\s+/).filter(Boolean).length}};C=x([(0,c.injectable)()],C);let w=class{listCompositions(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return{compositionCount:e.length,compositions:e}}};w=x([(0,c.injectable)()],w);const oe=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let se=class{getAvailableGoogleFonts(){return[...oe]}validateFontFamily(e){let t=e.toLowerCase();return oe.some(e=>e.toLowerCase()===t)}};se=x([(0,c.injectable)()],se);var ce=class{tail=Promise.resolve();currentActiveCount=0;currentPendingCount=0;async run(e){let t=this.tail,n,r=new Promise(e=>{n=e});this.currentPendingCount+=1,this.tail=t.then(()=>r),await t,--this.currentPendingCount,this.currentActiveCount+=1;try{return await e()}finally{--this.currentActiveCount,n()}}getStatus(){return{activeCount:this.currentActiveCount,pendingCount:this.currentPendingCount}}};const T=(0,m.promisify)(l.execFile);let E=class{async getVideoDuration(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};E=x([(0,c.injectable)()],E);const D=b.z.coerce.number(),O=b.z.coerce.number().int(),k=b.z.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.z.boolean()),A=b.z.string().min(1).refine(e=>/^(\/|file:|https?:|data:|blob:)/i.test(e),{message:`src must be an absolute filesystem path or a file:// / http(s):// / data: / blob: URL — relative paths are not allowed.`}),le=b.z.object({frame:O.nonnegative(),volume:D.min(0).max(1)}),ue=b.z.object({type:b.z.enum([`interpolate`,`spring`]).default(`interpolate`),easing:b.z.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:b.z.object({mass:D.positive().default(1),damping:D.positive().default(10),stiffness:D.positive().default(100)}).optional(),delay:O.nonnegative().default(0)}),de=b.z.object({type:b.z.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:O.positive().default(15),direction:b.z.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),j={startFrame:O.nonnegative(),durationInFrames:O.positive(),premountFor:O.nonnegative().default(30),entrance:ue.optional(),exit:ue.optional(),transition:de.optional()},fe=b.z.object({type:b.z.literal(`video`),...j,src:A,volume:D.min(0).max(1).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),transparent:k.optional(),noFadeIn:k.optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),pe=b.z.object({type:b.z.literal(`image`),...j,src:A,fit:b.z.enum([`contain`,`cover`,`fill`]).optional(),noFadeIn:k.optional(),baseScale:D.positive().optional(),transformOriginX:b.z.string().optional(),transformOriginY:b.z.string().optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),me=b.z.object({type:b.z.literal(`text`),...j,text:b.z.string(),fontSize:D.positive().default(80),fontFamily:b.z.string().default(`sans-serif`),fontWeight:b.z.union([b.z.string(),D]).optional(),color:b.z.string().default(`#ffffff`),backgroundColor:b.z.string().optional(),position:b.z.enum([`top`,`center`,`bottom`]).default(`center`),animation:b.z.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:b.z.string().optional(),highlightWord:b.z.string().optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),he=b.z.object({type:b.z.literal(`audio`),...j,src:A,volume:D.min(0).max(1).optional(),volumeAutomation:b.z.array(le).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),toneFrequency:D.positive().optional()}),ge=b.z.object({type:b.z.literal(`subtitle`),...j,text:b.z.string(),position:b.z.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:b.z.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:D.positive().default(36),fontFamily:b.z.string().default(`Arial, sans-serif`),color:b.z.string().default(`#ffffff`),backgroundColor:b.z.string().default(`rgba(0, 0, 0, 0.7)`),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),_e=b.z.object({type:b.z.literal(`gif`),...j,src:A,width:D.positive().optional(),height:D.positive().optional(),fit:b.z.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:D.positive().optional(),loopBehavior:b.z.enum([`loop`,`pause-after-finish`]).optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),ve=b.z.object({type:b.z.literal(`lottie`),...j,src:A,width:D.positive().optional(),height:D.positive().optional(),loop:k.optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),ye=b.z.discriminatedUnion(`type`,[fe,pe,me,he,ge,_e,ve]),M=b.z.object({text:b.z.string(),startMs:D.nonnegative(),endMs:D.nonnegative(),timestampMs:D.nonnegative().optional(),confidence:D.min(0).max(1).optional(),emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()}),be=M.extend({emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).default(`none`)}),xe=b.z.object({x:D.min(0).max(1).optional(),y:D.min(0).max(1).optional(),position:b.z.enum([`top`,`center`,`bottom`]).optional()}),Se=b.z.object({text:b.z.string().optional(),startMs:D.nonnegative(),endMs:D.nonnegative(),tokens:b.z.array(be).min(1),placement:xe.optional()}),Ce=b.z.object({top:D.nonnegative().optional(),right:D.nonnegative().optional(),bottom:D.nonnegative().optional(),left:D.nonnegative().optional()}),we=b.z.object({color:b.z.string().default(`rgba(0, 0, 0, 0.85)`),blur:D.nonnegative().default(8),offsetX:D.default(0),offsetY:D.default(3)}),Te=b.z.object({captions:b.z.array(M).default([]),pages:b.z.array(Se).optional(),style:b.z.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),preset:b.z.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).default(`tiktok-bold`),platform:b.z.enum([`tiktok`,`reels`,`shorts`,`universal`]).default(`tiktok`),combineTokensWithinMilliseconds:D.nonnegative().default(800),maxWordsPerPage:O.positive().default(6),maxCharsPerLine:O.positive().default(22),maxLines:O.positive().default(2),maxWidthPct:D.positive().max(1).default(.86),fontSize:D.positive().default(60),minFontSize:D.positive().default(42),fontFamily:b.z.string().default(`Arial, sans-serif`),fontWeight:b.z.union([O.positive(),b.z.string()]).default(800),lineHeight:D.positive().default(1.08),color:b.z.string().default(`#ffffff`),inactiveColor:b.z.string().default(`#ffffff`),highlightColor:b.z.string().default(`#FFD700`),textColor:b.z.string().default(`#ffffff`),strokeColor:b.z.string().default(`#000000`),strokeWidth:D.nonnegative().default(5),shadow:b.z.union([k,we]).default(!0),backgroundColor:b.z.string().default(`rgba(0, 0, 0, 0)`),backgroundRadius:D.nonnegative().default(8),safeZone:Ce.optional(),position:b.z.enum([`top`,`center`,`bottom`]).default(`bottom`)}).superRefine((e,t)=>{e.captions.length===0&&(!e.pages||e.pages.length===0)&&t.addIssue({code:`custom`,message:`Caption config must include captions or pages.`,path:[`captions`]})}),Ee={src:A,volume:D.min(0).max(1).default(1),fadeInMs:D.nonnegative().default(0),fadeOutMs:D.nonnegative().default(0),trimBeforeMs:D.nonnegative().default(0),trimAfterMs:D.nonnegative().optional(),playbackRate:D.positive().default(1)},De=b.z.object({...Ee,definesTimeline:k.default(!0),durationMs:D.positive().optional()}),Oe=b.z.object({...Ee,loop:k.default(!0)}),ke=b.z.object({...Ee,atMs:D.nonnegative(),durationMs:D.positive().optional()}),Ae=b.z.object({voiceover:De.optional(),music:Oe.optional(),sfx:b.z.array(ke).default([])}),je=b.z.object({family:b.z.string(),source:b.z.enum([`google`,`local`]).default(`google`),weights:b.z.array(O.positive()).optional(),url:b.z.string().optional()}),Me=b.z.object({clips:b.z.array(ye).default([]),backgroundColor:b.z.string().default(`#000000`),fps:O.positive().default(30),width:O.positive().default(1920),height:O.positive().default(1080),captions:Te.optional(),globalTransition:de.optional(),fonts:b.z.array(je).optional(),audio:Ae.optional()}).passthrough(),Ne=`${p.default.sep}public${p.default.sep}`,Pe=/^(https?:|data:|blob:)/i,Fe=/\b(undefined|NaN|null)\b|blur\(px\)/i,Ie={Main:{width:1920,height:1080},Vertical:{width:1080,height:1920},Square:{width:1080,height:1080}},Le=new Set([`video`,`image`,`audio`,`gif`,`lottie`]),Re=new Set([`video`,`image`,`gif`,`lottie`,`text`,`subtitle`]),ze={tiktok:480,reels:420,shorts:420,universal:480},Be={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};var Ve=class e extends Error{constructor(t){super(e.formatMessage(t)),this.validation=t,this.name=`RenderValidationError`}static formatMessage(e){return`Render validation failed: ${e.errors.map(e=>`${e.path??e.code}: ${e.message}`).join(`; `)}`}};let N=class{bundlePromise=null;mediaInfoService=new E;projectRoot=(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);async getServeUrl(){return this.bundlePromise||=(0,_.bundle)({entryPoint:p.default.resolve(this.projectRoot,`src/remotion/index.ts`),publicDir:p.default.resolve(this.projectRoot,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})}),this.bundlePromise}isTransientRendererNavigationError(e){let t=e instanceof Error?e.message:String(e);return t.includes(`ERR_EMPTY_RESPONSE`)||t.includes(`ERR_CONNECTION_RESET`)}async withTransientRendererRetry(e){for(let t=1;t<=2;t+=1)try{return await e()}catch(e){if(t===2||!this.isTransientRendererNavigationError(e))throw e;this.bundlePromise=null,await new Promise(e=>setTimeout(e,150))}throw Error(`Renderer retry exhausted unexpectedly.`)}get publicDir(){return p.default.resolve(this.projectRoot,`public`)}toPublicRelativePath(e){let t=p.default.normalize(e),n=t.indexOf(Ne);return n===-1?null:t.slice(n+Ne.length).split(p.default.sep).join(`/`)}toLocalFilesystemPath(e){return e.startsWith(`file://`)?(0,g.fileURLToPath)(e):p.default.isAbsolute(e)?e:null}toPublicFilesystemPath(e){let t=e.replace(/^\/+/,``).replace(/^public\//,``);return p.default.join(this.publicDir,...t.split(`/`))}resolveValidationSource(e){if(Pe.test(e))return{kind:`remote`,src:e};let t=this.toLocalFilesystemPath(e);return t?{kind:`local`,src:e,filePath:t}:{kind:`local`,src:e,filePath:this.toPublicFilesystemPath(e)}}hasExplicitDimension(e,t){return Object.prototype.hasOwnProperty.call(e,t)}voiceoverFrames(e,t){let n=e?.voiceover;if(!n||n.definesTimeline===!1||!n.durationMs)return 0;let r=Math.max(0,(n.trimAfterMs??n.durationMs)-(n.trimBeforeMs??0))/(n.playbackRate??1);return Math.ceil(r/1e3*t)}framesToSeconds(e,t){return Number((e/t).toFixed(3))}canExtendVisualClip(e){return e.type===`image`||e.type===`text`||e.type===`subtitle`||e.type===`lottie`}createVisualGapIssue({gapStart:e,gapEnd:t,fps:n,previous:r,next:i,durationInFrames:a,message:o}){let s=t-e,c={gapStartFrame:e,gapEndFrame:t,gapDurationInFrames:s,gapStartSeconds:this.framesToSeconds(e,n),gapEndSeconds:this.framesToSeconds(t,n),gapDurationSeconds:this.framesToSeconds(s,n),renderDurationInFrames:a};if(r&&(c.previousVisualClip={index:r.clipIndex,type:r.type,startFrame:r.start,endFrame:r.end,durationInFrames:r.durationInFrames,canExtendSafely:r.canExtendSafely}),i&&(c.nextVisualClip={index:i.clipIndex,type:i.type,startFrame:i.start,endFrame:i.end,durationInFrames:i.durationInFrames}),r?.canExtendSafely){let n=r.durationInFrames+s;return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:`Extend clips[${r.clipIndex}].durationInFrames from ${r.durationInFrames} to ${n} so visuals cover frames ${e}..${t}.`,edits:[{path:`inputProps.clips.${r.clipIndex}.durationInFrames`,value:n,reason:`Covers the ${s}-frame visual gap without changing audio timing.`}]}}}return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:r&&!r.canExtendSafely?`Add a still/image/text visual from frame ${e} for ${s} frames, or replace/loop the preceding visual before extending it.`:`Add a visual clip starting at frame ${e} with durationInFrames ${s}.`}}}createSummary(e,t,n){let r=t.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),i=this.voiceoverFrames(t.audio,t.fps),a=Math.max(r,i);return{compositionId:e.compositionId,width:this.hasExplicitDimension(e.inputProps,`width`)?t.width:n.width,height:this.hasExplicitDimension(e.inputProps,`height`)?t.height:n.height,fps:t.fps,durationInFrames:a,clipCount:t.clips.length}}pushBadStringIssues(e,t,n,r){if(typeof e==`string`){Fe.test(e)&&n.push({code:`${r}.invalid_value`,message:`Invalid ${r} value: ${e}`,path:t});return}if(!(!e||typeof e!=`object`)){if(Array.isArray(e)){e.forEach((e,i)=>this.pushBadStringIssues(e,`${t}.${i}`,n,r));return}Object.entries(e).forEach(([e,i])=>{this.pushBadStringIssues(i,`${t}.${e}`,n,r)})}}validateTextClip(e,t,n){`text`in e&&(e.text.trim()||n.push({code:`text.empty`,message:`Text and subtitle clips must not be empty.`,path:`${t}.text`}),this.pushBadStringIssues(e.text,`${t}.text`,n,`text`))}validateStyle(e,t,n){`style`in e&&e.style&&this.pushBadStringIssues(e.style,`${t}.style`,n,`style`)}isSourceClip(e){return Le.has(e.type)}async validateLocalAsset(e,t,n,r,i){if(!t.filePath)return;let a;try{a=await(0,d.stat)(t.filePath)}catch{i.push({code:`asset.missing`,message:`Render asset does not exist: ${t.src}`,path:`${n}.src`});return}if(!a.isFile()){i.push({code:`asset.not_file`,message:`Render asset is not a file: ${t.src}`,path:`${n}.src`});return}try{if(e.type===`image`||e.type===`gif`){let n=await(0,y.default)(t.filePath,{animated:e.type===`gif`}).metadata();if(!n.width||!n.height)throw Error(`Missing image dimensions`)}else if(e.type===`lottie`)JSON.parse(await(0,d.readFile)(t.filePath,`utf-8`));else if(e.type===`video`){await this.mediaInfoService.getVideoDimensions(t.filePath);let a=await this.mediaInfoService.getVideoDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}else if(e.type===`audio`){let a=await this.mediaInfoService.getAudioDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}}catch(e){i.push({code:`asset.decode_failed`,message:`Render asset cannot be decoded: ${t.src}. ${e instanceof Error?e.message:String(e)}`,path:`${n}.src`})}}validateMediaDuration(e,t,n,r,i){let a=e.trimBefore??0,o=e.trimAfter??0,s=e.playbackRate??1;if(o>0&&o<=a){i.push({code:`media.invalid_trim`,message:`trimAfter must be greater than trimBefore when provided.`,path:r});return}if(e.loop)return;let c=t*n,l=(o>0?o-a:c-a)/s;l+2<e.durationInFrames&&i.push({code:`media.too_short`,message:`Clip duration (${e.durationInFrames} frames) exceeds available media (${Math.floor(l)} frames).`,path:r})}async validateClipAssets(e,t,n){await Promise.all(e.clips.map(async(r,i)=>{let a=`clips.${i}`;if(this.validateTextClip(r,a,n),this.validateStyle(r,a,n),!this.isSourceClip(r))return;let o=this.resolveValidationSource(r.src);if(o.kind===`remote`){t.push({code:`asset.remote_unprobed`,message:`Remote asset will be fetched during render and cannot be decoded in preflight: ${r.src}`,path:`${a}.src`});return}await this.validateLocalAsset(r,o,a,e.fps,n)}))}async validateAudioLayer(e,t,n){if(!e)return;let r=[];e.voiceover&&r.push({src:e.voiceover.src,path:`audio.voiceover.src`}),e.music&&r.push({src:e.music.src,path:`audio.music.src`}),e.sfx?.forEach((e,t)=>{r.push({src:e.src,path:`audio.sfx.${t}.src`})}),await Promise.all(r.map(async({src:e,path:r})=>{let i=this.resolveValidationSource(e);if(i.kind===`remote`){t.push({code:`audio.remote_unprobed`,message:`Remote audio asset will be fetched during render and cannot be decoded in preflight: ${e}`,path:r});return}if(i.filePath)try{if(!(await(0,d.stat)(i.filePath)).isFile()){n.push({code:`audio.not_file`,message:`Audio asset is not a file: ${e}`,path:r});return}await this.mediaInfoService.getAudioDuration(i.filePath)}catch(t){if(t.code===`ENOENT`){n.push({code:`audio.missing`,message:`Audio asset does not exist: ${e}`,path:r});return}n.push({code:`audio.decode_failed`,message:`Audio asset cannot be decoded: ${e}. ${t instanceof Error?t.message:String(t)}`,path:r})}}))}async resolveAudioDurationMs(e){let t=this.resolveValidationSource(e);if(t.kind===`remote`||!t.filePath)return null;try{return await this.mediaInfoService.getAudioDuration(t.filePath)*1e3}catch{return null}}async injectAudioDurations(e){let t=e.audio;if(!t?.voiceover||t.voiceover.durationMs)return e;let n=await this.resolveAudioDurationMs(t.voiceover.src);return n?{...e,audio:{...t,voiceover:{...t.voiceover,durationMs:n}}}:e}validateTimeline(e,t,n){let r=!!(e.audio?.voiceover||e.audio?.music||e.audio?.sfx&&e.audio.sfx.length>0);if(e.clips.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.empty`,message:`Render must contain at least one clip.`,path:`clips`});return}let i=e.clips.map((e,t)=>({clip:e,clipIndex:t})).filter(({clip:e})=>Re.has(e.type)).map(({clip:e,clipIndex:t})=>({clipIndex:t,type:e.type,start:e.startFrame,end:e.startFrame+e.durationInFrames,durationInFrames:e.durationInFrames,canExtendSafely:this.canExtendVisualClip(e)})).sort((e,t)=>e.start-t.start);if(i.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.no_visual`,message:`Render must contain at least one visual clip.`,path:`clips`});return}let a=e.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),o=Math.max(a,this.voiceoverFrames(e.audio,e.fps));i[0]&&i[0].start>3&&t.push(this.createVisualGapIssue({gapStart:0,gapEnd:i[0].start,fps:e.fps,next:i[0],durationInFrames:o,message:`Visual timeline starts at frame ${i[0].start}; expected coverage from frame 0.`}));let s=i[0]?.end??0,c=i[0];for(let n of i.slice(1))n.start>s+3&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:n.start,fps:e.fps,previous:c,next:n,durationInFrames:o,message:`Visual timeline has a gap from frame ${s} to ${n.start}.`})),n.end>s&&(c=n),s=Math.max(s,n.end);s+3<o&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:o,fps:e.fps,previous:c,durationInFrames:o,message:`Visual timeline ends at frame ${s}; render duration is ${o}.`}))}validateCaptions(e,t,n,r){if(!e.captions)return;let i=e.captions,a=t.durationInFrames/e.fps*1e3,o=-1,s=-1;i.captions.forEach((e,t)=>{let i=`captions.captions.${t}`;e.text.trim()||n.push({code:`captions.empty_text`,message:`Caption text must not be empty.`,path:`${i}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.invalid_timing`,message:`Caption endMs must be greater than startMs.`,path:i}),e.startMs<o&&n.push({code:`captions.unsorted`,message:`Captions must be sorted by startMs.`,path:i}),e.startMs<s&&r.push({code:`captions.overlap`,message:`Caption overlaps the previous caption; prefer page/token timing for intentional karaoke overlap.`,path:i}),e.endMs>a+100&&n.push({code:`captions.out_of_range`,message:`Caption ends after render duration (${Math.round(a)}ms).`,path:i}),o=e.startMs,s=Math.max(s,e.endMs),this.pushBadStringIssues(e.text,`${i}.text`,n,`text`)}),i.pages?.forEach((t,o)=>{let s=`captions.pages.${o}`;t.endMs<=t.startMs&&n.push({code:`captions.page_invalid_timing`,message:`Caption page endMs must be greater than startMs.`,path:s}),t.endMs>a+100&&n.push({code:`captions.page_out_of_range`,message:`Caption page ends after render duration (${Math.round(a)}ms).`,path:s});let c=-1,l=-1;t.tokens.forEach((e,i)=>{let a=`${s}.tokens.${i}`;e.text.trim()||n.push({code:`captions.token_empty_text`,message:`Caption token text must not be empty.`,path:`${a}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.token_invalid_timing`,message:`Caption token endMs must be greater than startMs.`,path:a}),(e.startMs<t.startMs||e.endMs>t.endMs)&&n.push({code:`captions.token_outside_page`,message:`Caption token timing must stay within its page timing.`,path:a}),e.startMs<c&&n.push({code:`captions.token_unsorted`,message:`Caption page tokens must be sorted by startMs.`,path:a}),e.startMs<l&&r.push({code:`captions.token_overlap`,message:`Caption token overlaps the previous token.`,path:a}),e.confidence!==void 0&&e.confidence<.75&&r.push({code:`captions.low_confidence`,message:`Low-confidence caption token should be reviewed before render.`,path:a}),c=e.startMs,l=Math.max(l,e.endMs),this.pushBadStringIssues(e.text,`${a}.text`,n,`text`)});let u=this.countCaptionWords(t.tokens.map(e=>e.text).join(` `));u>i.maxWordsPerPage&&r.push({code:`captions.page_too_dense`,message:`Caption page has ${u} words; configured maxWordsPerPage is ${i.maxWordsPerPage}.`,path:s});let d=t.placement?.x!==void 0,f=t.placement?.y!==void 0;if(d!==f&&r.push({code:`captions.page_partial_placement`,message:`Caption page placement should provide both x and y for predictable focal-target avoidance.`,path:`${s}.placement`}),d||f){let n={...Be[i.platform],...i.safeZone},a=n.left/e.width,o=1-n.right/e.width,c=n.top/e.height,l=1-n.bottom/e.height,u=t.placement?.x??.5,d=t.placement?.y??.5;(u<a||u>o||d<c||d>l)&&r.push({code:`captions.page_placement_safe_zone`,message:`Caption page explicit placement sits outside the configured platform safe zone.`,path:`${s}.placement`})}});let c=ze[i.platform]??480,l=i.safeZone?.bottom??c;i.position===`bottom`&&l<c&&r.push({code:`captions.safe_zone_bottom`,message:`Bottom caption safe zone ${l}px is smaller than ${i.platform} preset ${c}px.`,path:`captions.safeZone.bottom`}),i.style===`karaoke`&&!i.pages&&i.captions.some(e=>/\s/.test(e.text.trim()))&&r.push({code:`captions.karaoke_legacy_timing`,message:`Karaoke legacy captions use proportional word timing; provide pages/tokens for accurate word sync.`,path:`captions.captions`})}countCaptionWords(e){return e.trim().split(/\s+/).filter(Boolean).length}async copyToPublic(e){let t=await(0,d.stat)(e);if(!t.isFile())throw Error(`Render asset is not a file: ${e}`);let n=p.default.extname(e),r=p.default.basename(e,n).replace(/[^a-zA-Z0-9._-]/g,`-`),i=(0,h.createHash)(`sha256`).update(e).update(String(t.mtimeMs)).update(String(t.size)).digest(`hex`).slice(0,16),a=p.default.posix.join(`mcp-render-assets`,`${r}-${i}${n}`),o=p.default.join(this.publicDir,...a.split(`/`));return await(0,d.mkdir)(p.default.dirname(o),{recursive:!0}),await(0,d.copyFile)(e,o),a}async prepareValue(e){if(Array.isArray(e)){let t=!1;return{value:await Promise.all(e.map(async e=>{let n=await this.prepareValue(e);return t||=n.usesLocalAssets,n.value})),usesLocalAssets:t}}if(!e||typeof e!=`object`)return{value:e,usesLocalAssets:!1};let t=!1,n={};for(let[r,i]of Object.entries(e)){if(r===`src`&&typeof i==`string`){let e=this.toLocalFilesystemPath(i);if(e){n[r]=this.toPublicRelativePath(e)??await this.copyToPublic(e),t=!0;continue}}let e=await this.prepareValue(i);n[r]=e.value,t||=e.usesLocalAssets}return{value:n,usesLocalAssets:t}}async prepareInputProps(e){let t=await this.prepareValue(e);return{inputProps:t.value,usesLocalAssets:t.usesLocalAssets}}normalizeProvidedValues(e,t){if(Array.isArray(e)&&Array.isArray(t))return e.map((e,n)=>this.normalizeProvidedValues(e,t[n]));if(e&&t&&typeof e==`object`&&typeof t==`object`&&!Array.isArray(e)&&!Array.isArray(t)){let n={},r=t;for(let[t,i]of Object.entries(e))n[t]=this.normalizeProvidedValues(i,r[t]);return n}return t}normalizeInputProps(e){let t=Me.parse(e);return this.normalizeProvidedValues(e,t)}async validateRender(e){let t=[],n=[],r=Ie[e.compositionId];r||t.push({code:`composition.unknown`,message:`Unknown composition: ${e.compositionId}`,path:`compositionId`});let i=await this.injectAudioDurations(e.inputProps),a=Me.safeParse(i);if(!a.success)return{passed:!1,errors:a.error.issues.map(e=>({code:`schema.invalid`,message:e.message,path:e.path.join(`.`)})),warnings:n,summary:{compositionId:e.compositionId,width:r?.width??0,height:r?.height??0,fps:0,durationInFrames:0,clipCount:0}};let o=a.data,s=r??{width:o.width,height:o.height},c=this.createSummary(e,o,s);return this.hasExplicitDimension(e.inputProps,`width`)&&o.width!==s.width&&t.push({code:`composition.width_mismatch`,message:`${e.compositionId} expects width ${s.width}, received ${o.width}.`,path:`width`}),this.hasExplicitDimension(e.inputProps,`height`)&&o.height!==s.height&&t.push({code:`composition.height_mismatch`,message:`${e.compositionId} expects height ${s.height}, received ${o.height}.`,path:`height`}),this.validateTimeline(o,t,n),this.validateCaptions(o,c,t,n),await this.validateClipAssets(o,n,t),await this.validateAudioLayer(o.audio,n,t),{passed:t.length===0,errors:t,warnings:n,summary:c}}async render(e){let t=await this.validateRender(e);if(!t.passed)throw new Ve(t);if(e.dryRun)return{outputPath:e.outputPath,dryRun:!0,validation:t};let n=await this.injectAudioDurations(e.inputProps),r=this.normalizeInputProps(n),i=await this.prepareInputProps(r);return i.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await(0,v.renderMedia)({composition:await(0,v.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:i.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:i.inputProps})}),{outputPath:e.outputPath,validation:t}}async renderStill(e){let t=await this.injectAudioDurations(e.inputProps),n=this.normalizeInputProps(t),r=await this.prepareInputProps(n);return r.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await(0,v.renderStill)({composition:await(0,v.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:r.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:r.inputProps})}),{outputPath:e.outputPath}}};N=x([(0,c.injectable)()],N);const He=(0,m.promisify)(l.execFile),Ue=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let P=class{async isAvailable(){try{return await He(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`kokoro-`)),o=p.default.join(a,`input.txt`);try{return await d.default.writeFile(o,t,`utf-8`),await He(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await d.default.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...Ue]}};P=x([(0,c.injectable)()],P);const F={AceStepMusicService:Symbol.for(`AceStepMusicService`),RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),VideoDownloadService:Symbol.for(`VideoDownloadService`),VideoAnalysisService:Symbol.for(`VideoAnalysisService`),VideoContextService:Symbol.for(`VideoContextService`),CompositionService:Symbol.for(`CompositionService`),ProxyUrl:Symbol.for(`ProxyUrl`),Tool:Symbol.for(`Tool`)};function I(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function L(e,t){return function(n,r){t(n,r,e)}}const R=(0,m.promisify)(l.execFile);let z=class{constructor(e=null){this.proxyUrl=e}getProxyArgs(){let e=this.proxyUrl||process.env.YTDLP_PROXY;return e?[`--proxy`,e]:[]}async ensureYtDlp(){try{let{stdout:e}=await R(`which`,[`yt-dlp`]);return e.trim()}catch{throw Error(`yt-dlp is not installed. Install it with: brew install yt-dlp`)}}async search(e,t){let n=await this.ensureYtDlp(),r=`ytsearch${t}:${e}`,{stdout:i}=await R(n,[...this.getProxyArgs(),r,`--flat-playlist`,`--print`,`%(id)s %(title)s %(duration)s %(url)s %(channel)s %(view_count)s %(upload_date)s`,`--no-warnings`],{timeout:3e4});return i.trim().split(`
5
+ `).filter(Boolean).map(e=>{let[t,n,r,i,a,o,s]=e.split(` `);return{id:t??``,title:n??``,duration:Number.parseFloat(r??`0`)||0,url:i||`https://www.youtube.com/watch?v=${t}`,channel:a??``,viewCount:Number.parseInt(o??`0`,10)||0,uploadDate:s??``}})}async downloadClip(e,t,n,r,i,a=1080){let o=await this.ensureYtDlp();(0,u.existsSync)(t)||(0,u.mkdirSync)(t,{recursive:!0});let s=p.default.join(t,`${n}.mp4`),c=[...this.getProxyArgs(),`-f`,`bestvideo[height<=${a}][ext=mp4]+bestaudio[ext=m4a]/best[height<=${a}][ext=mp4]/best`,`--merge-output-format`,`mp4`,`-o`,s,`--no-warnings`,`--no-playlist`];if(r&&i?c.push(`--download-sections`,`*${r}-${i}`):r?c.push(`--download-sections`,`*${r}-inf`):i&&c.push(`--download-sections`,`*0:00-${i}`),c.push(e),await R(o,c,{timeout:12e4}),!(0,u.existsSync)(s))throw Error(`Download failed: output file not found at ${s}`);let{width:l,height:d,duration:f}=await this.getMediaInfo(s);return{outputPath:s,publicPath:p.default.relative(t,s),duration:f,width:l,height:d}}async getMediaInfo(e){let{stdout:t}=await R(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`);return{width:Number(r?.width??0),height:Number(r?.height??0),duration:i}}};z=x([(0,c.injectable)(),L(0,(0,c.inject)(F.ProxyUrl)),L(0,(0,c.optional)()),I(`design:paramtypes`,[Object])],z);const We=(0,m.promisify)(l.execFile),Ge=1280,Ke=p.default.join(f.default.homedir(),`.gemini`,`oauth_creds.json`);let B=class{async analyzeVideos(e){let t=e.model||`gemini-2.5-flash-lite`,n=e.videoPaths.map(e=>p.default.resolve(e));if(n.length===0)throw Error(`At least one video path is required.`);let r=this.resolveWorkspaceRoot(process.cwd()),i=h.default.randomUUID().toLowerCase(),a=h.default.randomUUID().toLowerCase(),o=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`video-editor-mcp-video.`));try{let s=await this.readAccessToken(),c=process.env.CODE_ASSIST_ENDPOINT||`https://cloudcode-pa.googleapis.com`,l=process.env.CODE_ASSIST_API_VERSION||`v1internal`,{resolvedProject:d,projectFromEnv:f}=await this.resolveProject(s,c,l),p=new AbortController,m=setTimeout(()=>p.abort(),6e5);try{let m=await this.prepareVideos(n,o),h={model:t,project:d,user_prompt_id:a,request:{contents:[{role:`user`,parts:[{text:this.buildSessionContext(r)},{text:e.prompt},...m.map(e=>({inlineData:{mimeType:e.mimeType,data:u.default.readFileSync(e.transcodedPath).toString(`base64`)}}))]}],session_id:i}},g=await fetch(`${c}/${l}:generateContent`,{method:`POST`,signal:p.signal,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${s}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`,...f?{"X-Goog-User-Project":f}:{}},body:JSON.stringify(h)}),_=await g.text(),v=this.safeJsonParse(_);if(!g.ok)throw Error(`generateContent failed with ${g.status}: ${_}`);let y=v,b=this.extractResponseText(v);return{resolvedProject:d,model:t,prompt:e.prompt,videoCount:m.length,videos:m,responseText:b,rawResponse:v??_,traceId:this.getString(v,`traceId`),responseId:this.getString(y?.response,`responseId`)}}finally{clearTimeout(m)}}finally{await d.default.rm(o,{recursive:!0,force:!0})}}resolveWorkspaceRoot(e=process.cwd()){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=p.default.resolve(e);for(;;){for(let e of t)if((0,u.existsSync)(p.default.join(n,e)))return n;let r=p.default.dirname(n);if(r===n)return p.default.resolve(e);n=r}}async readAccessToken(e=process.env.GEMINI_OAUTH_CREDS_FILE||Ke){if(!u.default.existsSync(e))throw Error(`OAuth credentials file not found: ${e}`);let t=await d.default.readFile(e,`utf8`),n=JSON.parse(t),r=this.parseToken(n);if(r.refreshToken){let e=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:r.refreshToken})});if(!e.ok){let t=await e.text();throw Error(`Failed to refresh OAuth token: ${e.status} ${t}`)}let t=await e.json();if(typeof t.access_token==`string`&&t.access_token)return t.access_token;throw Error(`OAuth refresh response did not include access_token.`)}if(r.accessToken)return r.accessToken;throw Error(`No access token found in ${e}.`)}parseToken(e){if(!e||typeof e!=`object`)return{};let t=e;return t.token&&typeof t.token==`object`?{accessToken:typeof t.token.accessToken==`string`?t.token.accessToken:void 0,refreshToken:typeof t.token.refreshToken==`string`?t.token.refreshToken:void 0}:{accessToken:typeof t.access_token==`string`?t.access_token:void 0,refreshToken:typeof t.refresh_token==`string`?t.refresh_token:void 0}}async resolveProject(e,t,n){let r=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,i=await fetch(`${t}/${n}:loadCodeAssist`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${e}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`},body:JSON.stringify({metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,...r?{duetProject:r}:{}},...r?{cloudaicompanionProject:r}:{}})}),a=await i.text();if(!i.ok)throw Error(`loadCodeAssist failed with ${i.status}: ${a}`);return{resolvedProject:this.safeJsonParse(a)?.cloudaicompanionProject||r||``,projectFromEnv:r}}async prepareVideos(e,t){let n=[];for(let[r,i]of e.entries()){if(!u.default.existsSync(i))throw Error(`Video file not found: ${i}`);let e=(await d.default.stat(i)).size,a=p.default.join(t,`${p.default.basename(i,p.default.extname(i))}-${r}.mp4`);await We(`ffmpeg`,[`-y`,`-i`,i,`-map`,`0:v:0`,`-map`,`0:a?`,`-vf`,`scale='if(gt(iw,${Ge}),${Ge},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-c:a`,`aac`,`-b:a`,`128k`,`-movflags`,`+faststart`,a],{timeout:600*1e3});let o=(await d.default.stat(a)).size;n.push({sourcePath:i,transcodedPath:a,originalSizeBytes:e,transcodedSizeBytes:o,mimeType:`video/mp4`})}return n}buildSessionContext(e){let t=new Date().toLocaleDateString(void 0,{weekday:`long`,year:`numeric`,month:`long`,day:`numeric`}),n=p.default.join(f.default.homedir(),`.gemini`,`tmp`,p.default.basename(e)),r=this.getWorkspaceTree(e);return`
6
+ <session_context>
7
+ This is the Gemini CLI. We are setting up the context for our chat.
8
+ Today's date is ${t} (formatted according to the user's locale).
9
+ My operating system is: ${process.platform}
10
+ The project's temporary directory is: ${n}
11
+ - **Workspace Directories:**
12
+ - ${e}
13
+ - **Directory Structure:**
14
+
15
+ ${r}
16
+ </session_context>`.trim()}getWorkspaceTree(e,t=3,n=200){let r=[`${e}/`],i=0,a=(e,o,s)=>{if(i>=n||s>t)return;let c;try{c=u.default.readdirSync(e,{withFileTypes:!0}).filter(e=>![`.git`,`node_modules`,`.nx-cache`].includes(e.name)).sort((e,t)=>e.name.localeCompare(t.name))}catch{return}for(let l=0;l<c.length&&i<n;l+=1){let n=c[l],u=l===c.length-1,d=u?`└───`:`├───`,f=`${o}${u?` `:`│ `}`,m=p.default.join(e,n.name);if(i+=1,n.isDirectory()){r.push(`${o}${d}${n.name}/`),s<t&&a(m,f,s+1);continue}r.push(`${o}${d}${n.name}`)}};return a(e,``,0),i>=n&&r.push(`...`),r.join(`
17
+ `)}extractResponseText(e){return(e?.response?.candidates?.[0]?.content?.parts??[]).map(e=>e.text??``).filter(Boolean).join(``)}safeJsonParse(e){try{return JSON.parse(e)}catch{return}}getString(e,t){if(!e||typeof e!=`object`)return;let n=e;return typeof n[t]==`string`?n[t]:void 0}};B=x([(0,c.injectable)()],B);const V=(0,m.promisify)(l.execFile);let H=class{constructor(e=new C){this.captionService=e}async extractVideoContext(e){let t=p.default.resolve(e.videoPath);if(!(0,u.existsSync)(t))throw Error(`Video file not found: ${t}`);let n=e.basename??p.default.basename(t,p.default.extname(t)),r=p.default.resolve(e.outputDir??this.defaultOutputDir(t,n)),i=p.default.join(r,`work`),a=p.default.join(r,`manifest.json`);await d.default.mkdir(r,{recursive:!0}),await this.probeVideo(t);let o=e.transcribe===!1?{text:``,segments:[]}:await this.transcribeVideo(t,r,e),s=o.segments.length>0?await this.createProxyVideo(t,i,e):void 0,c={transcript:{...o,segments:s?await this.attachFilmstripsToSegments(s,r,o.segments,e):[]},next_actions:[`Review segments filmstrips for visual inspection`]};return await d.default.writeFile(a,`${JSON.stringify(c,null,2)}\n`,`utf8`),c}async probeVideo(e){let{stdout:t}=await V(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`),a=Number(r?.width??0),o=Number(r?.height??0);if(!i||Number.isNaN(i))throw Error(`Invalid duration for video: ${e}`);if(!a||!o)throw Error(`No video stream found in: ${e}`);return{durationSeconds:i,width:a,height:o}}defaultOutputDir(e,t){let n=this.resolveWorkspaceRoot(p.default.dirname(e)),r=n?p.default.join(n,`.tmp`,`video-editor-mcp`):f.default.tmpdir();return p.default.join(r,`${t}-context`)}resolveWorkspaceRoot(e){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=p.default.resolve(e);for(;;){if(t.some(e=>(0,u.existsSync)(p.default.join(n,e))))return n;let e=p.default.dirname(n);if(e===n)return;n=e}}async createProxyVideo(e,t,n){let r=p.default.join(t,`proxy.mp4`),i=Math.max(n.tileWidth??250,500);return await d.default.mkdir(t,{recursive:!0}),await V(`ffmpeg`,[`-y`,`-i`,e,`-vf`,`scale='min(${i},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-an`,r]),r}async transcribeVideo(e,t,n){let r=await this.resolveWhisperCommand(),i=p.default.join(t,`audio.wav`);await V(`ffmpeg`,[`-y`,`-i`,e,`-vn`,`-ac`,`1`,`-ar`,`16000`,i]),r.engine===`whisper`?await this.runPythonWhisper(r.binary,i,t,n):await this.runWhisperCpp(r.binary,i,t,n);let a=this.firstExistingPath([p.default.join(t,`audio.txt`),p.default.join(t,`transcript.txt`),`${i}.txt`]),o=this.firstExistingPath([p.default.join(t,`audio.srt`),p.default.join(t,`transcript.srt`),`${i}.srt`]),s=a?(await d.default.readFile(a,`utf8`)).trim():``,c=o?this.captionService.parseSrt(await d.default.readFile(o,`utf8`)):[];return{engine:r.engine,text:s,txtPath:a,srtPath:o,segments:c}}async resolveWhisperCommand(){for(let e of[`whisper-cli`,`whisper.cpp`,`whisper`]){let t=await this.findBinary(e);if(t)return{binary:t,engine:e}}throw Error(`No supported Whisper binary found. Install whisper.cpp or OpenAI Whisper, then retry.`)}async findBinary(e){try{let{stdout:t}=await V(`which`,[e]);return t.trim()||void 0}catch{return}}async runPythonWhisper(e,t,n,r){let i=[t,`--model`,r.whisperModel??`base`,`--output_dir`,n,`--output_format`,`all`];r.language&&i.push(`--language`,r.language),await V(e,i,{timeout:1200*1e3})}async runWhisperCpp(e,t,n,r){let i=r.whisperModelPath??process.env.WHISPER_MODEL_PATH??this.resolveDefaultWhisperModelPath();if(!i||!(0,u.existsSync)(i))throw Error(`Whisper.cpp model not found. Provide whisperModelPath or set WHISPER_MODEL_PATH.`);let a=[`-m`,i,`-f`,t,`-otxt`,`-osrt`,`-of`,p.default.join(n,`transcript`)];r.language&&a.push(`-l`,r.language),await V(e,a,{timeout:1200*1e3})}resolveDefaultWhisperModelPath(){return[p.default.join(f.default.homedir(),`.whisper`,`ggml-base.en.bin`),p.default.join(f.default.homedir(),`.whisper`,`ggml-small.en.bin`),`/opt/homebrew/share/whisper-cpp/ggml-base.en.bin`,`/usr/local/share/whisper-cpp/ggml-base.en.bin`].find(e=>(0,u.existsSync)(e))}async attachFilmstripsToSegments(e,t,n,r){let i=p.default.join(t,`slides`);await d.default.mkdir(i,{recursive:!0});let a=[];for(let[t,o]of n.entries()){let n=await this.extractSegmentFilmstrips(e,i,t,o,r);a.push({...o,text:o.text.trim(),filmstrips:n})}return a}async extractSegmentFilmstrips(e,t,n,r,i){let a=(i.imageFormat??`jpeg`)===`png`?`png`:`jpg`,o=i.framesPerSecond??3,s=i.tileWidth??250,c=i.filmstripColumns??6,l=c*(i.filmstripRows??4),u=Math.max((r.endMs-r.startMs)/1e3,1/o),d=Math.max(1,Math.ceil(u*o)),f=[];for(let i=0,u=0;i<d;i+=l,u+=1){let m=Math.min(l,d-i),h=Math.min(c,m),g=Math.ceil(m/h),_=r.startMs+Math.round(i/o*1e3),v=Math.min(m/o,Math.max((r.endMs-_)/1e3,1/o)),y=p.default.join(t,`segment_${String(n+1).padStart(4,`0`)}_filmstrip_${String(u+1).padStart(3,`0`)}.${a}`);await V(`ffmpeg`,[`-y`,`-ss`,(_/1e3).toFixed(3),`-t`,v.toFixed(3),`-i`,e,`-vf`,[`fps=${o}`,`scale='min(${s},iw)':-2`,`pad=iw:ih+32:0:0:white`,`tile=${h}x${g}:padding=8:margin=8:color=white`].join(`,`),`-frames:v`,`1`,y]),await this.annotateFilmstrip(y,_,o,m,h,g),f.push({path:y,index:u,startMs:_,endMs:Math.min(r.endMs,_+Math.round(v*1e3)),frameCount:m,tileWidth:s,columns:h,rows:g})}return f}async annotateFilmstrip(e,t,n,r,i,a){let o=await(0,y.default)(e).metadata();if(!o.width||!o.height)throw Error(`Unable to annotate filmstrip without image dimensions: ${e}`);let s=(o.width-16-8*(i-1))/i,c=(o.height-16-8*(a-1))/a,l=Array.from({length:r},(e,r)=>{let a=r%i,o=Math.floor(r/i),l=t+Math.round(r/n*1e3);return`<text x="${8+a*(s+8)+s/2}" y="${8+o*(c+8)+c-32+22}" text-anchor="middle">${l} ms</text>`}).join(``),u=Buffer.from(`<svg width="${o.width}" height="${o.height}" xmlns="http://www.w3.org/2000/svg"><style>text{font-family:Arial,sans-serif;font-size:18px;fill:#000}</style>${l}</svg>`),f=await(0,y.default)(e).composite([{input:u,top:0,left:0}]).toBuffer();await d.default.writeFile(e,f)}firstExistingPath(e){return e.find(e=>(0,u.existsSync)(e))}};H=x([(0,c.injectable)(),I(`design:paramtypes`,[Object])],H);let U=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};U=x([(0,c.injectable)()],U);var qe,Je;const Ye=b.z.object({url:b.z.string().describe(`Video URL from YouTube, TikTok, Instagram, or other yt-dlp supported sites`),filename:b.z.string().describe(`Output filename without extension (e.g., "my_clip")`),startTime:b.z.string().optional().describe(`Start time for clip extraction (e.g., "0:30", "1:15")`),endTime:b.z.string().optional().describe(`End time for clip extraction (e.g., "1:00", "2:30")`),maxHeight:b.z.number().int().positive().default(1080).describe(`Maximum video height in pixels (default: 1080)`),outputDir:b.z.string().optional().describe(`Custom output directory (default: Remotion public/videos/ for direct use in compositions)`)}),Xe=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let Ze=class extends U{static{Je=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ye}getDefinition(){return{name:Je.TOOL_NAME,description:`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming. Saves to Remotion public/videos/ by default for direct use in compositions.`,inputSchema:b.z.toJSONSchema(Ye,{reused:`inline`})}}async execute(e){try{let t=Ye.parse(e),n=p.default.resolve(Xe(),`public/videos`),r=t.outputDir??n,i=await this.videoDownloadService.downloadClip(t.url,r,t.filename,t.startTime,t.endTime,t.maxHeight),a=t.outputDir?void 0:`/videos/${t.filename}.mp4`;return this.successJson({...i,compositionSrc:a,suggestedComposition:i.height>i.width?`Vertical`:`Main`})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Ze=Je=x([(0,c.injectable)(),L(0,(0,c.inject)(F.VideoDownloadService)),I(`design:paramtypes`,[typeof(qe=z!==void 0&&z)==`function`?qe:Object])],Ze);var Qe,$e;const et=b.z.object({videoPath:b.z.string().min(1).describe(`Path to the local video file to extract context from`),outputDir:b.z.string().optional().describe(`Directory to write transcript, filmstrips, and manifest`),basename:b.z.string().optional().describe(`Base name for the generated context directory when outputDir is omitted`),framesPerSecond:b.z.number().positive().default(3).describe(`Frames per second to sample for raw frames and segment slides`),imageFormat:b.z.enum([`jpeg`,`png`]).default(`jpeg`).describe(`Frame image format`),tileWidth:b.z.number().int().positive().default(250).describe(`Width of each filmstrip tile in pixels`),filmstripColumns:b.z.number().int().positive().default(6).describe(`Maximum columns per segment filmstrip image`),filmstripRows:b.z.number().int().positive().default(4).describe(`Maximum rows per segment filmstrip image`),transcribe:b.z.boolean().default(!0).describe(`Run local Whisper transcription`),language:b.z.string().optional().describe(`Optional Whisper language hint`),whisperModel:b.z.string().optional().describe(`Python Whisper model name, default base`),whisperModelPath:b.z.string().optional().describe(`Whisper.cpp model path override`)});let W=class extends U{static{$e=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return et}getDefinition(){return{name:$e.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.z.toJSONSchema(et,{reused:`inline`})}}async execute(e){try{let t=et.parse(e),n=await this.videoContextService.extractVideoContext(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=$e=x([(0,c.injectable)(),L(0,(0,c.inject)(F.VideoContextService)),I(`design:paramtypes`,[typeof(Qe=H!==void 0&&H)==`function`?Qe:Object])],W);var tt,nt;const rt=b.z.object({captions:b.z.array(b.z.object({text:b.z.string(),startMs:b.z.number(),endMs:b.z.number(),confidence:b.z.number().min(0).max(1).optional(),emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()})).describe(`Array of caption objects with text, startMs, and endMs`),combineMs:b.z.number().optional().describe(`Milliseconds threshold for combining captions into pages (default: 100)`),maxWordsPerPage:b.z.number().int().positive().optional().describe(`Maximum words per caption page (default: 6)`),maxCharsPerLine:b.z.number().int().positive().optional().describe(`Target maximum characters per caption line (default: 22)`),platform:b.z.enum([`tiktok`,`reels`,`shorts`,`universal`]).optional().describe(`Platform safe-zone preset`),preset:b.z.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).optional().describe(`Caption visual style preset`)});let G=class extends U{static{nt=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return rt}getDefinition(){return{name:nt.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.z.toJSONSchema(rt,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=rt.parse(e),s=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs,confidence:e.confidence,emphasis:e.emphasis})),c=this.captionService.createTikTokCaptions(s,{combineMs:n,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o});return this.successJson(c)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};G=nt=x([(0,c.injectable)(),L(0,(0,c.inject)(F.CaptionService)),I(`design:paramtypes`,[typeof(tt=C!==void 0&&C)==`function`?tt:Object])],G);var it,at;const ot=b.z.object({prompt:b.z.string().describe(`Music description for ACE-Step text-to-music generation`),filename:b.z.string().optional().describe(`Output filename without extension (default: ace-step-music)`),outputPath:b.z.string().optional().describe(`Explicit output audio path. Overrides filename/outputDir.`),outputDir:b.z.string().optional().describe(`Custom output directory (default: Remotion public/music/ for direct use in compositions)`),aceStepPath:b.z.string().optional().describe(`Path to local ACE-Step-1.5 checkout (default: ACE_STEP_15_DIR or ~/workspace/ACE-Step-1.5)`),lyrics:b.z.string().optional().describe(`Lyrics to use. Defaults to [Instrumental] for background music.`),duration:b.z.number().min(10).max(600).optional().describe(`Target duration in seconds (default: 30)`),bpm:b.z.number().min(30).max(300).optional().describe(`Optional BPM metadata`),keyscale:b.z.string().optional().describe(`Optional key/scale metadata, e.g. C minor`),timeSignature:b.z.string().optional().describe(`Optional time signature metadata, e.g. 4/4`),vocalLanguage:b.z.string().optional().describe(`Vocal language code, or unknown (default: unknown)`),seed:b.z.number().int().optional().describe(`Optional fixed seed for reproducible generation`),inferenceSteps:b.z.number().int().positive().optional().describe(`Diffusion inference steps (default: 8)`),audioFormat:b.z.enum([`wav`,`mp3`,`flac`]).default(`wav`).describe(`Output audio format (default: wav)`),backend:b.z.enum([`mlx`,`pt`,`vllm`]).default(`mlx`).describe(`ACE-Step LM backend (default: mlx for Apple Silicon)`),configPath:b.z.string().optional().describe(`ACE-Step DiT model config (default: acestep-v15-turbo)`),lmModelPath:b.z.string().optional().describe(`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`),timeoutMs:b.z.number().int().positive().optional().describe(`Generation timeout in milliseconds`)}),st=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let ct=class extends U{static{at=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return ot}getDefinition(){return{name:at.TOOL_NAME,description:`Generate background music using a local ACE-Step 1.5 checkout. Defaults to MLX on Apple Silicon and saves to Remotion public/music/.`,inputSchema:b.z.toJSONSchema(ot,{reused:`inline`})}}async execute(e){try{let t=ot.parse(e);if(!await this.aceStepMusicService.isAvailable(t.aceStepPath))return this.error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass aceStepPath.`);let n=this.resolveOutputPath(t),r=await this.aceStepMusicService.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),i=p.default.resolve(st(),`public/music`),a=p.default.dirname(n)===i?`/music/${p.default.basename(n)}`:void 0;return this.successJson({...r,compositionSrc:a,suggestedClip:a?{type:`audio`,src:a,startFrame:0,volume:.35}:void 0})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}resolveOutputPath(e){if(e.outputPath)return p.default.resolve(e.outputPath);let t=p.default.resolve(e.outputDir??p.default.join(st(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return p.default.join(t,`${n}.${r}`)}};ct=at=x([(0,c.injectable)(),L(0,(0,c.inject)(F.AceStepMusicService)),I(`design:paramtypes`,[typeof(it=S!==void 0&&S)==`function`?it:Object])],ct);var lt,ut;const dt=b.z.object({text:b.z.string().describe(`Text to convert to speech`),voice:b.z.string().optional().describe(`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`),speed:b.z.number().optional().describe(`Speech speed multiplier (default: 1.0)`),outputPath:b.z.string().describe(`Output file path for the generated audio`)});let K=class extends U{static{ut=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return dt}getDefinition(){return{name:ut.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.z.toJSONSchema(dt,{reused:`inline`})}}async execute(e){try{let t=dt.parse(e);if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let n=await this.voiceoverService.generateVoiceover({text:t.text,voice:t.voice,speed:t.speed,outputPath:t.outputPath});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};K=ut=x([(0,c.injectable)(),L(0,(0,c.inject)(F.VoiceoverService)),I(`design:paramtypes`,[typeof(lt=P!==void 0&&P)==`function`?lt:Object])],K);var ft,pt;const mt=b.z.object({src:b.z.string().describe(`Path to the media file`),type:b.z.enum([`video`,`audio`]).optional().describe(`Media type (default: video)`)});let q=class extends U{static{pt=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return mt}getDefinition(){return{name:pt.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.z.toJSONSchema(mt,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=mt.parse(e),r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=pt=x([(0,c.injectable)(),L(0,(0,c.inject)(F.MediaInfoService)),I(`design:paramtypes`,[typeof(ft=E!==void 0&&E)==`function`?ft:Object])],q);var ht,gt;const _t=b.z.object({srtContent:b.z.string().describe(`SRT file content as a string`)});let J=class extends U{static{gt=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return _t}getDefinition(){return{name:gt.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.z.toJSONSchema(_t,{reused:`inline`})}}async execute(e){try{let t=_t.parse(e),n=this.captionService.parseSrt(t.srtContent);return this.successJson({captionCount:n.length,captions:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};J=gt=x([(0,c.injectable)(),L(0,(0,c.inject)(F.CaptionService)),I(`design:paramtypes`,[typeof(ht=C!==void 0&&C)==`function`?ht:Object])],J);var vt,yt;const bt=b.z.object({});let Y=class extends U{static{yt=this}static TOOL_NAME=`list_compositions`;constructor(e){super(),this.compositionService=e}getInputSchema(){return bt}getDefinition(){return{name:yt.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.z.toJSONSchema(bt,{reused:`inline`})}}async execute(){return this.successJson(this.compositionService.listCompositions())}};Y=yt=x([(0,c.injectable)(),L(0,(0,c.inject)(F.CompositionService)),I(`design:paramtypes`,[typeof(vt=w!==void 0&&w)==`function`?vt:Object])],Y);var xt,St;const Ct=b.z.object({compositionId:b.z.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:b.z.record(b.z.string(),b.z.unknown()).describe(`JSON props to pass to the composition`),outputPath:b.z.string().describe(`Output file path for the rendered image`),frame:b.z.number().optional().describe(`Frame number to render (default: 0)`),imageFormat:b.z.enum([`png`,`jpeg`]).optional().describe(`Image format (default: png)`)});let X=class extends U{static{St=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return Ct}getDefinition(){return{name:St.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:b.z.toJSONSchema(Ct,{reused:`inline`})}}async execute(e){try{let t=Ct.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,frame:t.frame,imageFormat:t.imageFormat},r=await this.renderService.renderStill(n);return this.successJson(r)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};X=St=x([(0,c.injectable)(),L(0,(0,c.inject)(F.RenderService)),I(`design:paramtypes`,[typeof(xt=N!==void 0&&N)==`function`?xt:Object])],X);var wt,Tt;const Et=b.z.preprocess(e=>{if(typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},b.z.record(b.z.string(),b.z.unknown())),Dt=b.z.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.z.boolean()),Ot=b.z.object({compositionId:b.z.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:Et.describe(`JSON props to pass to the composition`),outputPath:b.z.string().describe(`Output file path for the rendered video`),codec:b.z.enum([`h264`,`h265`,`vp8`,`vp9`]).optional().describe(`Video codec (default: h264)`),dryRun:Dt.optional().describe(`Validate props and assets without rendering a video`)});let Z=class extends U{static{Tt=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return Ot}getDefinition(){return{name:Tt.TOOL_NAME,description:"Render a video from JSON props using Remotion. `inputProps` must conform to the Main composition schema. Full JSON Schema (generated from `src/schemas/compositions.ts` -> `MainCompositionSchema`): `schemas/main-composition.schema.json` (relative to this package; in the monorepo: `packages/mcp/video-editor-mcp/schemas/main-composition.schema.json`). Validation runs server-side; failures return a `render_validation_failed` error with per-issue paths and suggested fixes.",inputSchema:b.z.toJSONSchema(Ot,{reused:`inline`})}}async execute(e){try{let t=Ot.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,codec:t.codec,dryRun:t.dryRun},r=await this.renderService.render(n);return this.successJson(r)}catch(e){return e instanceof Ve?{content:[{type:`text`,text:JSON.stringify({error:`render_validation_failed`,message:e.message,summary:e.validation.summary,issues:e.validation.errors,warnings:e.validation.warnings,suggestedFixes:e.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}],isError:!0}:this.error(e instanceof Error?e.message:`Unknown error`)}}};Z=Tt=x([(0,c.injectable)(),L(0,(0,c.inject)(F.RenderService)),I(`design:paramtypes`,[typeof(wt=N!==void 0&&N)==`function`?wt:Object])],Z);var kt,At;const jt=b.z.object({query:b.z.string().describe(`Search query for YouTube videos`),maxResults:b.z.number().int().positive().max(20).default(5).describe(`Maximum number of results (default: 5, max: 20)`)});let Mt=class extends U{static{At=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return jt}getDefinition(){return{name:At.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.z.toJSONSchema(jt,{reused:`inline`})}}async execute(e){try{let t=jt.parse(e),n=await this.videoDownloadService.search(t.query,t.maxResults);return this.successJson({query:t.query,resultCount:n.length,results:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Mt=At=x([(0,c.injectable)(),L(0,(0,c.inject)(F.VideoDownloadService)),I(`design:paramtypes`,[typeof(kt=z!==void 0&&z)==`function`?kt:Object])],Mt);var Nt,Q;const Pt=b.z.object({videoPaths:b.z.array(b.z.string().min(1)).min(1).describe(`Paths to local video files to analyze`),prompt:b.z.string().min(1).describe(`Prompt to send with the video(s)`),model:b.z.string().default(`gemini-2.5-flash-lite`).describe(`Gemini model to use (default: gemini-2.5-flash-lite)`)});let $=class extends U{static{Q=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Pt}getDefinition(){return{name:Q.TOOL_NAME,description:`Analyze one or more local video files by transcoding them to a compact MP4 and sending them with a prompt to Gemini Code Assist.`,inputSchema:b.z.toJSONSchema(Pt,{reused:`inline`})}}async execute(e){try{let t=Pt.parse(e),n=await this.videoAnalysisService.analyzeVideos({videoPaths:t.videoPaths,prompt:t.prompt,model:t.model});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};$=Q=x([(0,c.injectable)(),L(0,(0,c.inject)(F.VideoAnalysisService)),I(`design:paramtypes`,[typeof(Nt=B!==void 0&&B)==`function`?Nt:Object])],$);const Ft=new c.ContainerModule(e=>{e.bind(F.AceStepMusicService).to(S).inSingletonScope(),e.bind(F.RenderService).to(N).inSingletonScope(),e.bind(F.MediaInfoService).to(E).inSingletonScope(),e.bind(F.CaptionService).to(C).inSingletonScope(),e.bind(F.FontService).to(se).inSingletonScope(),e.bind(F.VoiceoverService).to(P).inSingletonScope(),e.bind(F.VideoDownloadService).to(z).inSingletonScope(),e.bind(F.VideoAnalysisService).to(B).inSingletonScope(),e.bind(F.VideoContextService).to(H).inSingletonScope(),e.bind(F.CompositionService).to(w).inSingletonScope()}),It=new c.ContainerModule(e=>{e.bind(F.Tool).to(Z).inSingletonScope(),e.bind(F.Tool).to(Y).inSingletonScope(),e.bind(F.Tool).to(q).inSingletonScope(),e.bind(F.Tool).to(X).inSingletonScope(),e.bind(F.Tool).to(G).inSingletonScope(),e.bind(F.Tool).to(J).inSingletonScope(),e.bind(F.Tool).to(ct).inSingletonScope(),e.bind(F.Tool).to(K).inSingletonScope(),e.bind(F.Tool).to(Mt).inSingletonScope(),e.bind(F.Tool).to(Ze).inSingletonScope(),e.bind(F.Tool).to($).inSingletonScope(),e.bind(F.Tool).to(W).inSingletonScope()});function Lt(e={}){let t=new c.Container;return t.load(Ft,It),t.bind(F.ProxyUrl).toConstantValue(e.proxyUrl??null),t}const Rt=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function zt(e){return Rt.has(e)}async function Bt(e,t,n,r){return zt(e)?r.run(()=>t.execute(n)):t.execute(n)}function Vt(e){let t=new te.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(F.Tool),r=new ce,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(ne.ListToolsRequestSchema,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(ne.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params,a=i.get(t);if(!a)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let o=(0,ee.coerceArgs)(n??{},a.getInputSchema());try{return await Bt(t,a,o,r)}catch(e){if(e instanceof b.z.ZodError)return{content:[{type:`text`,text:(0,ee.formatZodError)(e,{schemaName:t,schema:a.getInputSchema()})}],isError:!0};throw e}}),t}var Ht=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new re.StdioServerTransport,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`A`,{enumerable:!0,get:function(){return de}}),Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return he}}),Object.defineProperty(exports,`D`,{enumerable:!0,get:function(){return ve}}),Object.defineProperty(exports,`E`,{enumerable:!0,get:function(){return pe}}),Object.defineProperty(exports,`M`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`O`,{enumerable:!0,get:function(){return ge}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return ue}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return _e}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return Ve}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Z}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return je}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return J}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return $}}),Object.defineProperty(exports,`j`,{enumerable:!0,get:function(){return fe}}),Object.defineProperty(exports,`k`,{enumerable:!0,get:function(){return me}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return Vt}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return X}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return U}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return Lt}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return Ht}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return Te}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return ye}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return Me}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return M}});
@@ -0,0 +1,17 @@
1
+ import"reflect-metadata";import{Container as e,ContainerModule as t,inject as n,injectable as r,optional as i}from"inversify";import{execFile as a}from"node:child_process";import o,{existsSync as s,mkdirSync as c}from"node:fs";import l,{copyFile as u,mkdir as d,readFile as f,stat as p}from"node:fs/promises";import m from"node:os";import h from"node:path";import{promisify as g}from"node:util";import _,{createHash as v}from"node:crypto";import{fileURLToPath as y}from"node:url";import{bundle as ee}from"@remotion/bundler";import{renderMedia as te,renderStill as ne,selectComposition as re}from"@remotion/renderer";import ie from"sharp";import{z as b}from"zod";import{coerceArgs as ae,formatZodError as oe}from"@agimon-ai/foundation-validator";import{Server as se}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as ce,ListToolsRequestSchema as le}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as ue}from"@modelcontextprotocol/sdk/server/stdio.js";function x(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}const de=g(a);let S=class{async isAvailable(e){let t=this.resolveAceStepPath(e);if(!this.isAceStepCheckout(t))return!1;try{return await de(`uv`,[`--version`]),!0}catch{return!1}}async generateMusic(e){let t=this.resolveAceStepPath(e.aceStepPath);if(!this.isAceStepCheckout(t))throw Error(`ACE-Step 1.5 checkout not found at ${t}. Pass aceStepPath or set ACE_STEP_15_DIR.`);let n=e.audioFormat??this.getFormatFromPath(e.outputPath),r=e.backend??`mlx`,i=e.duration??30,a=await l.mkdtemp(h.join(m.tmpdir(),`ace-step-music-`)),o=h.join(a,`generation.toml`),s=h.join(a,`output`);await l.mkdir(s,{recursive:!0}),await l.mkdir(h.dirname(e.outputPath),{recursive:!0});try{let a=this.buildConfig({...e,aceStepPath:t,audioFormat:n,backend:r,duration:i,outputDir:s});await l.writeFile(o,a,`utf-8`);let{stdout:c,stderr:u}=await de(`uv`,[`run`,`python`,`cli.py`,`-c`,o,`--backend`,r,`--log-level`,`INFO`],{cwd:t,timeout:e.timeoutMs??1200*1e3,maxBuffer:50*1024*1024}),d=await this.findNewestGeneratedAudio(s,n);return await l.copyFile(d,e.outputPath),{outputPath:e.outputPath,sourcePath:d,duration:i,audioFormat:n,backend:r,aceStepPath:t,stdout:c,stderr:u}}catch(e){throw Error(`Failed to generate ACE-Step music: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{await l.rm(a,{recursive:!0,force:!0}).catch(()=>void 0)}}resolveAceStepPath(e){return h.resolve(e??process.env.ACE_STEP_15_DIR??process.env.ACESTEP_15_DIR??h.join(m.homedir(),`workspace`,`ACE-Step-1.5`))}isAceStepCheckout(e){return s(h.join(e,`cli.py`))&&s(h.join(e,`pyproject.toml`))}getFormatFromPath(e){let t=h.extname(e).toLowerCase().replace(`.`,``);return t===`mp3`||t===`flac`||t===`wav`?t:`wav`}async findNewestGeneratedAudio(e,t){let n=await l.readdir(e,{withFileTypes:!0}),r=await Promise.all(n.filter(e=>e.isFile()&&e.name.endsWith(`.${t}`)).map(async t=>{let n=h.join(e,t.name);return{filePath:n,mtimeMs:(await l.stat(n)).mtimeMs}}));if(r.sort((e,t)=>t.mtimeMs-e.mtimeMs),!r[0])throw Error(`ACE-Step completed but no .${t} file was found in ${e}`);return r[0].filePath}buildConfig(e){let t=e.lyrics?.trim()||`[Instrumental]`,n=t.toLowerCase()===`[instrumental]`,r={project_root:e.aceStepPath,checkpoint_dir:h.join(e.aceStepPath,`checkpoints`),config_path:e.configPath??`acestep-v15-turbo`,lm_model_path:e.lmModelPath??`acestep-5Hz-lm-0.6B`,backend:e.backend,device:`auto`,offload_to_cpu:!1,offload_dit_to_cpu:!1,save_dir:e.outputDir,audio_format:e.audioFormat,caption:e.prompt,lyrics:t,duration:e.duration,instrumental:n,bpm:e.bpm??null,keyscale:e.keyscale??``,timesignature:e.timeSignature??``,vocal_language:e.vocalLanguage??`unknown`,task_type:`text2music`,sample_mode:!1,sample_query:``,use_format:!1,inference_steps:e.inferenceSteps??8,seed:e.seed??-1,use_random_seed:e.seed===void 0,guidance_scale:7,use_adg:!1,shift:3,infer_method:`ode`,thinking:!0,lm_temperature:.85,lm_cfg_scale:2,lm_top_k:0,lm_top_p:.9,use_cot_metas:!0,use_cot_caption:!0,use_cot_lyrics:!1,use_cot_language:!0,use_constrained_decoding:!0,batch_size:1,allow_lm_batch:!1,lm_batch_chunk_size:8,constrained_decoding_debug:!1,cfg_interval_start:0,cfg_interval_end:1,lm_negative_prompt:`NO USER INPUT`,log_level:`INFO`};return Object.entries(r).filter(e=>e[1]!==null).map(([e,t])=>`${e} = ${this.toTomlValue(t)}`).join(`
2
+ `)}toTomlValue(e){return typeof e==`string`?JSON.stringify(e):String(e)}};S=x([r()],S);const fe={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};let C=class{parseSrt(e){let t=[],n=e.split(`
3
+ `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
4
+ `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t=100){let n=typeof t==`number`?{combineMs:t}:t,r=n.combineMs??100,i=n.maxWordsPerPage??6,a=n.maxCharsPerLine??22,o=n.platform??`tiktok`,s=n.preset??`tiktok-bold`,c=this.createCaptionPages(e,{combineMs:r,maxWordsPerPage:i});return{pages:c,captionConfig:{captions:e,pages:c,style:s===`karaoke`?`karaoke`:`tiktok`,preset:s,platform:o,combineTokensWithinMilliseconds:r,maxWordsPerPage:i,maxCharsPerLine:a,maxLines:2,maxWidthPct:.86,fontSize:60,minFontSize:42,fontFamily:`Arial, sans-serif`,fontWeight:800,lineHeight:1.08,color:`#ffffff`,inactiveColor:`#ffffff`,highlightColor:`#FFD700`,textColor:`#ffffff`,strokeColor:`#000000`,strokeWidth:5,shadow:!0,backgroundColor:`rgba(0, 0, 0, 0)`,backgroundRadius:8,safeZone:fe[o],position:`bottom`}}}createCaptionPages(e,t={}){let n=[],r=this.normalizeCaptionsToTokens(e),i=t.combineMs??100,a=t.maxWordsPerPage??6;if(r.length===0)return n;let o={startMs:r[0]?.startMs??0,endMs:r[0]?.endMs??0,tokens:[r[0]]};for(let e=1;e<r.length;e++){let t=r[e];if(!t)continue;let s=t.startMs-o.endMs,c=this.countWords(o.tokens.map(e=>e.text).join(` `)),l=this.countWords(t.text);s<=i&&c+l<=a?(o.tokens.push(t),o.endMs=t.endMs):(o.text=this.pageText(o.tokens),n.push(o),o={startMs:t.startMs,endMs:t.endMs,tokens:[t]})}return o.tokens.length>0&&(o.text=this.pageText(o.tokens),n.push(o)),n}normalizeCaptionsToTokens(e){return[...e].sort((e,t)=>e.startMs-t.startMs).flatMap(e=>this.captionToTokens(e))}getPlatformSafeZone(e){return fe[e]}captionToTokens(e){let t=e.text.trim().split(/\s+/).filter(Boolean);if(t.length===0)return[];if(t.length===1)return[{...e,text:t[0]??e.text,emphasis:e.emphasis??`none`}];let n=Math.max(1,e.endMs-e.startMs)/t.length;return t.map((r,i)=>{let a=Math.round(e.startMs+n*i),o=i===t.length-1?e.endMs:Math.round(e.startMs+n*(i+1));return{text:r,startMs:a,endMs:Math.max(o,a+1),timestampMs:a,confidence:e.confidence,emphasis:e.emphasis??`none`}})}pageText(e){return e.map(e=>e.text).join(` `)}countWords(e){return e.trim().split(/\s+/).filter(Boolean).length}};C=x([r()],C);let w=class{listCompositions(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return{compositionCount:e.length,compositions:e}}};w=x([r()],w);const pe=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let me=class{getAvailableGoogleFonts(){return[...pe]}validateFontFamily(e){let t=e.toLowerCase();return pe.some(e=>e.toLowerCase()===t)}};me=x([r()],me);var he=class{tail=Promise.resolve();currentActiveCount=0;currentPendingCount=0;async run(e){let t=this.tail,n,r=new Promise(e=>{n=e});this.currentPendingCount+=1,this.tail=t.then(()=>r),await t,--this.currentPendingCount,this.currentActiveCount+=1;try{return await e()}finally{--this.currentActiveCount,n()}}getStatus(){return{activeCount:this.currentActiveCount,pendingCount:this.currentPendingCount}}};const T=g(a);let E=class{async getVideoDuration(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await T(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await T(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};E=x([r()],E);const D=b.coerce.number(),O=b.coerce.number().int(),k=b.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.boolean()),A=b.string().min(1).refine(e=>/^(\/|file:|https?:|data:|blob:)/i.test(e),{message:`src must be an absolute filesystem path or a file:// / http(s):// / data: / blob: URL — relative paths are not allowed.`}),ge=b.object({frame:O.nonnegative(),volume:D.min(0).max(1)}),_e=b.object({type:b.enum([`interpolate`,`spring`]).default(`interpolate`),easing:b.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:b.object({mass:D.positive().default(1),damping:D.positive().default(10),stiffness:D.positive().default(100)}).optional(),delay:O.nonnegative().default(0)}),ve=b.object({type:b.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:O.positive().default(15),direction:b.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),j={startFrame:O.nonnegative(),durationInFrames:O.positive(),premountFor:O.nonnegative().default(30),entrance:_e.optional(),exit:_e.optional(),transition:ve.optional()},ye=b.object({type:b.literal(`video`),...j,src:A,volume:D.min(0).max(1).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),transparent:k.optional(),noFadeIn:k.optional(),style:b.record(b.string(),b.unknown()).optional()}),be=b.object({type:b.literal(`image`),...j,src:A,fit:b.enum([`contain`,`cover`,`fill`]).optional(),noFadeIn:k.optional(),baseScale:D.positive().optional(),transformOriginX:b.string().optional(),transformOriginY:b.string().optional(),style:b.record(b.string(),b.unknown()).optional()}),xe=b.object({type:b.literal(`text`),...j,text:b.string(),fontSize:D.positive().default(80),fontFamily:b.string().default(`sans-serif`),fontWeight:b.union([b.string(),D]).optional(),color:b.string().default(`#ffffff`),backgroundColor:b.string().optional(),position:b.enum([`top`,`center`,`bottom`]).default(`center`),animation:b.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:b.string().optional(),highlightWord:b.string().optional(),style:b.record(b.string(),b.unknown()).optional()}),Se=b.object({type:b.literal(`audio`),...j,src:A,volume:D.min(0).max(1).optional(),volumeAutomation:b.array(ge).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),toneFrequency:D.positive().optional()}),Ce=b.object({type:b.literal(`subtitle`),...j,text:b.string(),position:b.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:b.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:D.positive().default(36),fontFamily:b.string().default(`Arial, sans-serif`),color:b.string().default(`#ffffff`),backgroundColor:b.string().default(`rgba(0, 0, 0, 0.7)`),style:b.record(b.string(),b.unknown()).optional()}),we=b.object({type:b.literal(`gif`),...j,src:A,width:D.positive().optional(),height:D.positive().optional(),fit:b.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:D.positive().optional(),loopBehavior:b.enum([`loop`,`pause-after-finish`]).optional(),style:b.record(b.string(),b.unknown()).optional()}),Te=b.object({type:b.literal(`lottie`),...j,src:A,width:D.positive().optional(),height:D.positive().optional(),loop:k.optional(),style:b.record(b.string(),b.unknown()).optional()}),Ee=b.discriminatedUnion(`type`,[ye,be,xe,Se,Ce,we,Te]),De=b.object({text:b.string(),startMs:D.nonnegative(),endMs:D.nonnegative(),timestampMs:D.nonnegative().optional(),confidence:D.min(0).max(1).optional(),emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()}),Oe=De.extend({emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).default(`none`)}),ke=b.object({x:D.min(0).max(1).optional(),y:D.min(0).max(1).optional(),position:b.enum([`top`,`center`,`bottom`]).optional()}),Ae=b.object({text:b.string().optional(),startMs:D.nonnegative(),endMs:D.nonnegative(),tokens:b.array(Oe).min(1),placement:ke.optional()}),je=b.object({top:D.nonnegative().optional(),right:D.nonnegative().optional(),bottom:D.nonnegative().optional(),left:D.nonnegative().optional()}),Me=b.object({color:b.string().default(`rgba(0, 0, 0, 0.85)`),blur:D.nonnegative().default(8),offsetX:D.default(0),offsetY:D.default(3)}),Ne=b.object({captions:b.array(De).default([]),pages:b.array(Ae).optional(),style:b.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),preset:b.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).default(`tiktok-bold`),platform:b.enum([`tiktok`,`reels`,`shorts`,`universal`]).default(`tiktok`),combineTokensWithinMilliseconds:D.nonnegative().default(800),maxWordsPerPage:O.positive().default(6),maxCharsPerLine:O.positive().default(22),maxLines:O.positive().default(2),maxWidthPct:D.positive().max(1).default(.86),fontSize:D.positive().default(60),minFontSize:D.positive().default(42),fontFamily:b.string().default(`Arial, sans-serif`),fontWeight:b.union([O.positive(),b.string()]).default(800),lineHeight:D.positive().default(1.08),color:b.string().default(`#ffffff`),inactiveColor:b.string().default(`#ffffff`),highlightColor:b.string().default(`#FFD700`),textColor:b.string().default(`#ffffff`),strokeColor:b.string().default(`#000000`),strokeWidth:D.nonnegative().default(5),shadow:b.union([k,Me]).default(!0),backgroundColor:b.string().default(`rgba(0, 0, 0, 0)`),backgroundRadius:D.nonnegative().default(8),safeZone:je.optional(),position:b.enum([`top`,`center`,`bottom`]).default(`bottom`)}).superRefine((e,t)=>{e.captions.length===0&&(!e.pages||e.pages.length===0)&&t.addIssue({code:`custom`,message:`Caption config must include captions or pages.`,path:[`captions`]})}),Pe={src:A,volume:D.min(0).max(1).default(1),fadeInMs:D.nonnegative().default(0),fadeOutMs:D.nonnegative().default(0),trimBeforeMs:D.nonnegative().default(0),trimAfterMs:D.nonnegative().optional(),playbackRate:D.positive().default(1)},Fe=b.object({...Pe,definesTimeline:k.default(!0),durationMs:D.positive().optional()}),Ie=b.object({...Pe,loop:k.default(!0)}),Le=b.object({...Pe,atMs:D.nonnegative(),durationMs:D.positive().optional()}),Re=b.object({voiceover:Fe.optional(),music:Ie.optional(),sfx:b.array(Le).default([])}),ze=b.object({family:b.string(),source:b.enum([`google`,`local`]).default(`google`),weights:b.array(O.positive()).optional(),url:b.string().optional()}),Be=b.object({clips:b.array(Ee).default([]),backgroundColor:b.string().default(`#000000`),fps:O.positive().default(30),width:O.positive().default(1920),height:O.positive().default(1080),captions:Ne.optional(),globalTransition:ve.optional(),fonts:b.array(ze).optional(),audio:Re.optional()}).passthrough(),Ve=`${h.sep}public${h.sep}`,He=/^(https?:|data:|blob:)/i,Ue=/\b(undefined|NaN|null)\b|blur\(px\)/i,We={Main:{width:1920,height:1080},Vertical:{width:1080,height:1920},Square:{width:1080,height:1080}},Ge=new Set([`video`,`image`,`audio`,`gif`,`lottie`]),Ke=new Set([`video`,`image`,`gif`,`lottie`,`text`,`subtitle`]),qe={tiktok:480,reels:420,shorts:420,universal:480},Je={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};var Ye=class e extends Error{constructor(t){super(e.formatMessage(t)),this.validation=t,this.name=`RenderValidationError`}static formatMessage(e){return`Render validation failed: ${e.errors.map(e=>`${e.path??e.code}: ${e.message}`).join(`; `)}`}};let M=class{bundlePromise=null;mediaInfoService=new E;projectRoot=s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);async getServeUrl(){return this.bundlePromise||=ee({entryPoint:h.resolve(this.projectRoot,`src/remotion/index.ts`),publicDir:h.resolve(this.projectRoot,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})}),this.bundlePromise}isTransientRendererNavigationError(e){let t=e instanceof Error?e.message:String(e);return t.includes(`ERR_EMPTY_RESPONSE`)||t.includes(`ERR_CONNECTION_RESET`)}async withTransientRendererRetry(e){for(let t=1;t<=2;t+=1)try{return await e()}catch(e){if(t===2||!this.isTransientRendererNavigationError(e))throw e;this.bundlePromise=null,await new Promise(e=>setTimeout(e,150))}throw Error(`Renderer retry exhausted unexpectedly.`)}get publicDir(){return h.resolve(this.projectRoot,`public`)}toPublicRelativePath(e){let t=h.normalize(e),n=t.indexOf(Ve);return n===-1?null:t.slice(n+Ve.length).split(h.sep).join(`/`)}toLocalFilesystemPath(e){return e.startsWith(`file://`)?y(e):h.isAbsolute(e)?e:null}toPublicFilesystemPath(e){let t=e.replace(/^\/+/,``).replace(/^public\//,``);return h.join(this.publicDir,...t.split(`/`))}resolveValidationSource(e){if(He.test(e))return{kind:`remote`,src:e};let t=this.toLocalFilesystemPath(e);return t?{kind:`local`,src:e,filePath:t}:{kind:`local`,src:e,filePath:this.toPublicFilesystemPath(e)}}hasExplicitDimension(e,t){return Object.prototype.hasOwnProperty.call(e,t)}voiceoverFrames(e,t){let n=e?.voiceover;if(!n||n.definesTimeline===!1||!n.durationMs)return 0;let r=Math.max(0,(n.trimAfterMs??n.durationMs)-(n.trimBeforeMs??0))/(n.playbackRate??1);return Math.ceil(r/1e3*t)}framesToSeconds(e,t){return Number((e/t).toFixed(3))}canExtendVisualClip(e){return e.type===`image`||e.type===`text`||e.type===`subtitle`||e.type===`lottie`}createVisualGapIssue({gapStart:e,gapEnd:t,fps:n,previous:r,next:i,durationInFrames:a,message:o}){let s=t-e,c={gapStartFrame:e,gapEndFrame:t,gapDurationInFrames:s,gapStartSeconds:this.framesToSeconds(e,n),gapEndSeconds:this.framesToSeconds(t,n),gapDurationSeconds:this.framesToSeconds(s,n),renderDurationInFrames:a};if(r&&(c.previousVisualClip={index:r.clipIndex,type:r.type,startFrame:r.start,endFrame:r.end,durationInFrames:r.durationInFrames,canExtendSafely:r.canExtendSafely}),i&&(c.nextVisualClip={index:i.clipIndex,type:i.type,startFrame:i.start,endFrame:i.end,durationInFrames:i.durationInFrames}),r?.canExtendSafely){let n=r.durationInFrames+s;return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:`Extend clips[${r.clipIndex}].durationInFrames from ${r.durationInFrames} to ${n} so visuals cover frames ${e}..${t}.`,edits:[{path:`inputProps.clips.${r.clipIndex}.durationInFrames`,value:n,reason:`Covers the ${s}-frame visual gap without changing audio timing.`}]}}}return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:r&&!r.canExtendSafely?`Add a still/image/text visual from frame ${e} for ${s} frames, or replace/loop the preceding visual before extending it.`:`Add a visual clip starting at frame ${e} with durationInFrames ${s}.`}}}createSummary(e,t,n){let r=t.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),i=this.voiceoverFrames(t.audio,t.fps),a=Math.max(r,i);return{compositionId:e.compositionId,width:this.hasExplicitDimension(e.inputProps,`width`)?t.width:n.width,height:this.hasExplicitDimension(e.inputProps,`height`)?t.height:n.height,fps:t.fps,durationInFrames:a,clipCount:t.clips.length}}pushBadStringIssues(e,t,n,r){if(typeof e==`string`){Ue.test(e)&&n.push({code:`${r}.invalid_value`,message:`Invalid ${r} value: ${e}`,path:t});return}if(!(!e||typeof e!=`object`)){if(Array.isArray(e)){e.forEach((e,i)=>this.pushBadStringIssues(e,`${t}.${i}`,n,r));return}Object.entries(e).forEach(([e,i])=>{this.pushBadStringIssues(i,`${t}.${e}`,n,r)})}}validateTextClip(e,t,n){`text`in e&&(e.text.trim()||n.push({code:`text.empty`,message:`Text and subtitle clips must not be empty.`,path:`${t}.text`}),this.pushBadStringIssues(e.text,`${t}.text`,n,`text`))}validateStyle(e,t,n){`style`in e&&e.style&&this.pushBadStringIssues(e.style,`${t}.style`,n,`style`)}isSourceClip(e){return Ge.has(e.type)}async validateLocalAsset(e,t,n,r,i){if(!t.filePath)return;let a;try{a=await p(t.filePath)}catch{i.push({code:`asset.missing`,message:`Render asset does not exist: ${t.src}`,path:`${n}.src`});return}if(!a.isFile()){i.push({code:`asset.not_file`,message:`Render asset is not a file: ${t.src}`,path:`${n}.src`});return}try{if(e.type===`image`||e.type===`gif`){let n=await ie(t.filePath,{animated:e.type===`gif`}).metadata();if(!n.width||!n.height)throw Error(`Missing image dimensions`)}else if(e.type===`lottie`)JSON.parse(await f(t.filePath,`utf-8`));else if(e.type===`video`){await this.mediaInfoService.getVideoDimensions(t.filePath);let a=await this.mediaInfoService.getVideoDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}else if(e.type===`audio`){let a=await this.mediaInfoService.getAudioDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}}catch(e){i.push({code:`asset.decode_failed`,message:`Render asset cannot be decoded: ${t.src}. ${e instanceof Error?e.message:String(e)}`,path:`${n}.src`})}}validateMediaDuration(e,t,n,r,i){let a=e.trimBefore??0,o=e.trimAfter??0,s=e.playbackRate??1;if(o>0&&o<=a){i.push({code:`media.invalid_trim`,message:`trimAfter must be greater than trimBefore when provided.`,path:r});return}if(e.loop)return;let c=t*n,l=(o>0?o-a:c-a)/s;l+2<e.durationInFrames&&i.push({code:`media.too_short`,message:`Clip duration (${e.durationInFrames} frames) exceeds available media (${Math.floor(l)} frames).`,path:r})}async validateClipAssets(e,t,n){await Promise.all(e.clips.map(async(r,i)=>{let a=`clips.${i}`;if(this.validateTextClip(r,a,n),this.validateStyle(r,a,n),!this.isSourceClip(r))return;let o=this.resolveValidationSource(r.src);if(o.kind===`remote`){t.push({code:`asset.remote_unprobed`,message:`Remote asset will be fetched during render and cannot be decoded in preflight: ${r.src}`,path:`${a}.src`});return}await this.validateLocalAsset(r,o,a,e.fps,n)}))}async validateAudioLayer(e,t,n){if(!e)return;let r=[];e.voiceover&&r.push({src:e.voiceover.src,path:`audio.voiceover.src`}),e.music&&r.push({src:e.music.src,path:`audio.music.src`}),e.sfx?.forEach((e,t)=>{r.push({src:e.src,path:`audio.sfx.${t}.src`})}),await Promise.all(r.map(async({src:e,path:r})=>{let i=this.resolveValidationSource(e);if(i.kind===`remote`){t.push({code:`audio.remote_unprobed`,message:`Remote audio asset will be fetched during render and cannot be decoded in preflight: ${e}`,path:r});return}if(i.filePath)try{if(!(await p(i.filePath)).isFile()){n.push({code:`audio.not_file`,message:`Audio asset is not a file: ${e}`,path:r});return}await this.mediaInfoService.getAudioDuration(i.filePath)}catch(t){if(t.code===`ENOENT`){n.push({code:`audio.missing`,message:`Audio asset does not exist: ${e}`,path:r});return}n.push({code:`audio.decode_failed`,message:`Audio asset cannot be decoded: ${e}. ${t instanceof Error?t.message:String(t)}`,path:r})}}))}async resolveAudioDurationMs(e){let t=this.resolveValidationSource(e);if(t.kind===`remote`||!t.filePath)return null;try{return await this.mediaInfoService.getAudioDuration(t.filePath)*1e3}catch{return null}}async injectAudioDurations(e){let t=e.audio;if(!t?.voiceover||t.voiceover.durationMs)return e;let n=await this.resolveAudioDurationMs(t.voiceover.src);return n?{...e,audio:{...t,voiceover:{...t.voiceover,durationMs:n}}}:e}validateTimeline(e,t,n){let r=!!(e.audio?.voiceover||e.audio?.music||e.audio?.sfx&&e.audio.sfx.length>0);if(e.clips.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.empty`,message:`Render must contain at least one clip.`,path:`clips`});return}let i=e.clips.map((e,t)=>({clip:e,clipIndex:t})).filter(({clip:e})=>Ke.has(e.type)).map(({clip:e,clipIndex:t})=>({clipIndex:t,type:e.type,start:e.startFrame,end:e.startFrame+e.durationInFrames,durationInFrames:e.durationInFrames,canExtendSafely:this.canExtendVisualClip(e)})).sort((e,t)=>e.start-t.start);if(i.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.no_visual`,message:`Render must contain at least one visual clip.`,path:`clips`});return}let a=e.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),o=Math.max(a,this.voiceoverFrames(e.audio,e.fps));i[0]&&i[0].start>3&&t.push(this.createVisualGapIssue({gapStart:0,gapEnd:i[0].start,fps:e.fps,next:i[0],durationInFrames:o,message:`Visual timeline starts at frame ${i[0].start}; expected coverage from frame 0.`}));let s=i[0]?.end??0,c=i[0];for(let n of i.slice(1))n.start>s+3&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:n.start,fps:e.fps,previous:c,next:n,durationInFrames:o,message:`Visual timeline has a gap from frame ${s} to ${n.start}.`})),n.end>s&&(c=n),s=Math.max(s,n.end);s+3<o&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:o,fps:e.fps,previous:c,durationInFrames:o,message:`Visual timeline ends at frame ${s}; render duration is ${o}.`}))}validateCaptions(e,t,n,r){if(!e.captions)return;let i=e.captions,a=t.durationInFrames/e.fps*1e3,o=-1,s=-1;i.captions.forEach((e,t)=>{let i=`captions.captions.${t}`;e.text.trim()||n.push({code:`captions.empty_text`,message:`Caption text must not be empty.`,path:`${i}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.invalid_timing`,message:`Caption endMs must be greater than startMs.`,path:i}),e.startMs<o&&n.push({code:`captions.unsorted`,message:`Captions must be sorted by startMs.`,path:i}),e.startMs<s&&r.push({code:`captions.overlap`,message:`Caption overlaps the previous caption; prefer page/token timing for intentional karaoke overlap.`,path:i}),e.endMs>a+100&&n.push({code:`captions.out_of_range`,message:`Caption ends after render duration (${Math.round(a)}ms).`,path:i}),o=e.startMs,s=Math.max(s,e.endMs),this.pushBadStringIssues(e.text,`${i}.text`,n,`text`)}),i.pages?.forEach((t,o)=>{let s=`captions.pages.${o}`;t.endMs<=t.startMs&&n.push({code:`captions.page_invalid_timing`,message:`Caption page endMs must be greater than startMs.`,path:s}),t.endMs>a+100&&n.push({code:`captions.page_out_of_range`,message:`Caption page ends after render duration (${Math.round(a)}ms).`,path:s});let c=-1,l=-1;t.tokens.forEach((e,i)=>{let a=`${s}.tokens.${i}`;e.text.trim()||n.push({code:`captions.token_empty_text`,message:`Caption token text must not be empty.`,path:`${a}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.token_invalid_timing`,message:`Caption token endMs must be greater than startMs.`,path:a}),(e.startMs<t.startMs||e.endMs>t.endMs)&&n.push({code:`captions.token_outside_page`,message:`Caption token timing must stay within its page timing.`,path:a}),e.startMs<c&&n.push({code:`captions.token_unsorted`,message:`Caption page tokens must be sorted by startMs.`,path:a}),e.startMs<l&&r.push({code:`captions.token_overlap`,message:`Caption token overlaps the previous token.`,path:a}),e.confidence!==void 0&&e.confidence<.75&&r.push({code:`captions.low_confidence`,message:`Low-confidence caption token should be reviewed before render.`,path:a}),c=e.startMs,l=Math.max(l,e.endMs),this.pushBadStringIssues(e.text,`${a}.text`,n,`text`)});let u=this.countCaptionWords(t.tokens.map(e=>e.text).join(` `));u>i.maxWordsPerPage&&r.push({code:`captions.page_too_dense`,message:`Caption page has ${u} words; configured maxWordsPerPage is ${i.maxWordsPerPage}.`,path:s});let d=t.placement?.x!==void 0,f=t.placement?.y!==void 0;if(d!==f&&r.push({code:`captions.page_partial_placement`,message:`Caption page placement should provide both x and y for predictable focal-target avoidance.`,path:`${s}.placement`}),d||f){let n={...Je[i.platform],...i.safeZone},a=n.left/e.width,o=1-n.right/e.width,c=n.top/e.height,l=1-n.bottom/e.height,u=t.placement?.x??.5,d=t.placement?.y??.5;(u<a||u>o||d<c||d>l)&&r.push({code:`captions.page_placement_safe_zone`,message:`Caption page explicit placement sits outside the configured platform safe zone.`,path:`${s}.placement`})}});let c=qe[i.platform]??480,l=i.safeZone?.bottom??c;i.position===`bottom`&&l<c&&r.push({code:`captions.safe_zone_bottom`,message:`Bottom caption safe zone ${l}px is smaller than ${i.platform} preset ${c}px.`,path:`captions.safeZone.bottom`}),i.style===`karaoke`&&!i.pages&&i.captions.some(e=>/\s/.test(e.text.trim()))&&r.push({code:`captions.karaoke_legacy_timing`,message:`Karaoke legacy captions use proportional word timing; provide pages/tokens for accurate word sync.`,path:`captions.captions`})}countCaptionWords(e){return e.trim().split(/\s+/).filter(Boolean).length}async copyToPublic(e){let t=await p(e);if(!t.isFile())throw Error(`Render asset is not a file: ${e}`);let n=h.extname(e),r=h.basename(e,n).replace(/[^a-zA-Z0-9._-]/g,`-`),i=v(`sha256`).update(e).update(String(t.mtimeMs)).update(String(t.size)).digest(`hex`).slice(0,16),a=h.posix.join(`mcp-render-assets`,`${r}-${i}${n}`),o=h.join(this.publicDir,...a.split(`/`));return await d(h.dirname(o),{recursive:!0}),await u(e,o),a}async prepareValue(e){if(Array.isArray(e)){let t=!1;return{value:await Promise.all(e.map(async e=>{let n=await this.prepareValue(e);return t||=n.usesLocalAssets,n.value})),usesLocalAssets:t}}if(!e||typeof e!=`object`)return{value:e,usesLocalAssets:!1};let t=!1,n={};for(let[r,i]of Object.entries(e)){if(r===`src`&&typeof i==`string`){let e=this.toLocalFilesystemPath(i);if(e){n[r]=this.toPublicRelativePath(e)??await this.copyToPublic(e),t=!0;continue}}let e=await this.prepareValue(i);n[r]=e.value,t||=e.usesLocalAssets}return{value:n,usesLocalAssets:t}}async prepareInputProps(e){let t=await this.prepareValue(e);return{inputProps:t.value,usesLocalAssets:t.usesLocalAssets}}normalizeProvidedValues(e,t){if(Array.isArray(e)&&Array.isArray(t))return e.map((e,n)=>this.normalizeProvidedValues(e,t[n]));if(e&&t&&typeof e==`object`&&typeof t==`object`&&!Array.isArray(e)&&!Array.isArray(t)){let n={},r=t;for(let[t,i]of Object.entries(e))n[t]=this.normalizeProvidedValues(i,r[t]);return n}return t}normalizeInputProps(e){let t=Be.parse(e);return this.normalizeProvidedValues(e,t)}async validateRender(e){let t=[],n=[],r=We[e.compositionId];r||t.push({code:`composition.unknown`,message:`Unknown composition: ${e.compositionId}`,path:`compositionId`});let i=await this.injectAudioDurations(e.inputProps),a=Be.safeParse(i);if(!a.success)return{passed:!1,errors:a.error.issues.map(e=>({code:`schema.invalid`,message:e.message,path:e.path.join(`.`)})),warnings:n,summary:{compositionId:e.compositionId,width:r?.width??0,height:r?.height??0,fps:0,durationInFrames:0,clipCount:0}};let o=a.data,s=r??{width:o.width,height:o.height},c=this.createSummary(e,o,s);return this.hasExplicitDimension(e.inputProps,`width`)&&o.width!==s.width&&t.push({code:`composition.width_mismatch`,message:`${e.compositionId} expects width ${s.width}, received ${o.width}.`,path:`width`}),this.hasExplicitDimension(e.inputProps,`height`)&&o.height!==s.height&&t.push({code:`composition.height_mismatch`,message:`${e.compositionId} expects height ${s.height}, received ${o.height}.`,path:`height`}),this.validateTimeline(o,t,n),this.validateCaptions(o,c,t,n),await this.validateClipAssets(o,n,t),await this.validateAudioLayer(o.audio,n,t),{passed:t.length===0,errors:t,warnings:n,summary:c}}async render(e){let t=await this.validateRender(e);if(!t.passed)throw new Ye(t);if(e.dryRun)return{outputPath:e.outputPath,dryRun:!0,validation:t};let n=await this.injectAudioDurations(e.inputProps),r=this.normalizeInputProps(n),i=await this.prepareInputProps(r);return i.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await te({composition:await re({serveUrl:t,id:e.compositionId,inputProps:i.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:i.inputProps})}),{outputPath:e.outputPath,validation:t}}async renderStill(e){let t=await this.injectAudioDurations(e.inputProps),n=this.normalizeInputProps(t),r=await this.prepareInputProps(n);return r.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await ne({composition:await re({serveUrl:t,id:e.compositionId,inputProps:r.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:r.inputProps})}),{outputPath:e.outputPath}}};M=x([r()],M);const Xe=g(a),Ze=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let N=class{async isAvailable(){try{return await Xe(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await l.mkdtemp(h.join(m.tmpdir(),`kokoro-`)),o=h.join(a,`input.txt`);try{return await l.writeFile(o,t,`utf-8`),await Xe(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await l.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...Ze]}};N=x([r()],N);const P={AceStepMusicService:Symbol.for(`AceStepMusicService`),RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),VideoDownloadService:Symbol.for(`VideoDownloadService`),VideoAnalysisService:Symbol.for(`VideoAnalysisService`),VideoContextService:Symbol.for(`VideoContextService`),CompositionService:Symbol.for(`CompositionService`),ProxyUrl:Symbol.for(`ProxyUrl`),Tool:Symbol.for(`Tool`)};function F(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function I(e,t){return function(n,r){t(n,r,e)}}const L=g(a);let R=class{constructor(e=null){this.proxyUrl=e}getProxyArgs(){let e=this.proxyUrl||process.env.YTDLP_PROXY;return e?[`--proxy`,e]:[]}async ensureYtDlp(){try{let{stdout:e}=await L(`which`,[`yt-dlp`]);return e.trim()}catch{throw Error(`yt-dlp is not installed. Install it with: brew install yt-dlp`)}}async search(e,t){let n=await this.ensureYtDlp(),r=`ytsearch${t}:${e}`,{stdout:i}=await L(n,[...this.getProxyArgs(),r,`--flat-playlist`,`--print`,`%(id)s %(title)s %(duration)s %(url)s %(channel)s %(view_count)s %(upload_date)s`,`--no-warnings`],{timeout:3e4});return i.trim().split(`
5
+ `).filter(Boolean).map(e=>{let[t,n,r,i,a,o,s]=e.split(` `);return{id:t??``,title:n??``,duration:Number.parseFloat(r??`0`)||0,url:i||`https://www.youtube.com/watch?v=${t}`,channel:a??``,viewCount:Number.parseInt(o??`0`,10)||0,uploadDate:s??``}})}async downloadClip(e,t,n,r,i,a=1080){let o=await this.ensureYtDlp();s(t)||c(t,{recursive:!0});let l=h.join(t,`${n}.mp4`),u=[...this.getProxyArgs(),`-f`,`bestvideo[height<=${a}][ext=mp4]+bestaudio[ext=m4a]/best[height<=${a}][ext=mp4]/best`,`--merge-output-format`,`mp4`,`-o`,l,`--no-warnings`,`--no-playlist`];if(r&&i?u.push(`--download-sections`,`*${r}-${i}`):r?u.push(`--download-sections`,`*${r}-inf`):i&&u.push(`--download-sections`,`*0:00-${i}`),u.push(e),await L(o,u,{timeout:12e4}),!s(l))throw Error(`Download failed: output file not found at ${l}`);let{width:d,height:f,duration:p}=await this.getMediaInfo(l);return{outputPath:l,publicPath:h.relative(t,l),duration:p,width:d,height:f}}async getMediaInfo(e){let{stdout:t}=await L(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`);return{width:Number(r?.width??0),height:Number(r?.height??0),duration:i}}};R=x([r(),I(0,n(P.ProxyUrl)),I(0,i()),F(`design:paramtypes`,[Object])],R);const Qe=g(a),$e=1280,et=h.join(m.homedir(),`.gemini`,`oauth_creds.json`);let z=class{async analyzeVideos(e){let t=e.model||`gemini-2.5-flash-lite`,n=e.videoPaths.map(e=>h.resolve(e));if(n.length===0)throw Error(`At least one video path is required.`);let r=this.resolveWorkspaceRoot(process.cwd()),i=_.randomUUID().toLowerCase(),a=_.randomUUID().toLowerCase(),s=await l.mkdtemp(h.join(m.tmpdir(),`video-editor-mcp-video.`));try{let c=await this.readAccessToken(),l=process.env.CODE_ASSIST_ENDPOINT||`https://cloudcode-pa.googleapis.com`,u=process.env.CODE_ASSIST_API_VERSION||`v1internal`,{resolvedProject:d,projectFromEnv:f}=await this.resolveProject(c,l,u),p=new AbortController,m=setTimeout(()=>p.abort(),6e5);try{let m=await this.prepareVideos(n,s),h={model:t,project:d,user_prompt_id:a,request:{contents:[{role:`user`,parts:[{text:this.buildSessionContext(r)},{text:e.prompt},...m.map(e=>({inlineData:{mimeType:e.mimeType,data:o.readFileSync(e.transcodedPath).toString(`base64`)}}))]}],session_id:i}},g=await fetch(`${l}/${u}:generateContent`,{method:`POST`,signal:p.signal,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${c}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`,...f?{"X-Goog-User-Project":f}:{}},body:JSON.stringify(h)}),_=await g.text(),v=this.safeJsonParse(_);if(!g.ok)throw Error(`generateContent failed with ${g.status}: ${_}`);let y=v,ee=this.extractResponseText(v);return{resolvedProject:d,model:t,prompt:e.prompt,videoCount:m.length,videos:m,responseText:ee,rawResponse:v??_,traceId:this.getString(v,`traceId`),responseId:this.getString(y?.response,`responseId`)}}finally{clearTimeout(m)}}finally{await l.rm(s,{recursive:!0,force:!0})}}resolveWorkspaceRoot(e=process.cwd()){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=h.resolve(e);for(;;){for(let e of t)if(s(h.join(n,e)))return n;let r=h.dirname(n);if(r===n)return h.resolve(e);n=r}}async readAccessToken(e=process.env.GEMINI_OAUTH_CREDS_FILE||et){if(!o.existsSync(e))throw Error(`OAuth credentials file not found: ${e}`);let t=await l.readFile(e,`utf8`),n=JSON.parse(t),r=this.parseToken(n);if(r.refreshToken){let e=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:r.refreshToken})});if(!e.ok){let t=await e.text();throw Error(`Failed to refresh OAuth token: ${e.status} ${t}`)}let t=await e.json();if(typeof t.access_token==`string`&&t.access_token)return t.access_token;throw Error(`OAuth refresh response did not include access_token.`)}if(r.accessToken)return r.accessToken;throw Error(`No access token found in ${e}.`)}parseToken(e){if(!e||typeof e!=`object`)return{};let t=e;return t.token&&typeof t.token==`object`?{accessToken:typeof t.token.accessToken==`string`?t.token.accessToken:void 0,refreshToken:typeof t.token.refreshToken==`string`?t.token.refreshToken:void 0}:{accessToken:typeof t.access_token==`string`?t.access_token:void 0,refreshToken:typeof t.refresh_token==`string`?t.refresh_token:void 0}}async resolveProject(e,t,n){let r=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,i=await fetch(`${t}/${n}:loadCodeAssist`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${e}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`},body:JSON.stringify({metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,...r?{duetProject:r}:{}},...r?{cloudaicompanionProject:r}:{}})}),a=await i.text();if(!i.ok)throw Error(`loadCodeAssist failed with ${i.status}: ${a}`);return{resolvedProject:this.safeJsonParse(a)?.cloudaicompanionProject||r||``,projectFromEnv:r}}async prepareVideos(e,t){let n=[];for(let[r,i]of e.entries()){if(!o.existsSync(i))throw Error(`Video file not found: ${i}`);let e=(await l.stat(i)).size,a=h.join(t,`${h.basename(i,h.extname(i))}-${r}.mp4`);await Qe(`ffmpeg`,[`-y`,`-i`,i,`-map`,`0:v:0`,`-map`,`0:a?`,`-vf`,`scale='if(gt(iw,${$e}),${$e},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-c:a`,`aac`,`-b:a`,`128k`,`-movflags`,`+faststart`,a],{timeout:600*1e3});let s=(await l.stat(a)).size;n.push({sourcePath:i,transcodedPath:a,originalSizeBytes:e,transcodedSizeBytes:s,mimeType:`video/mp4`})}return n}buildSessionContext(e){let t=new Date().toLocaleDateString(void 0,{weekday:`long`,year:`numeric`,month:`long`,day:`numeric`}),n=h.join(m.homedir(),`.gemini`,`tmp`,h.basename(e)),r=this.getWorkspaceTree(e);return`
6
+ <session_context>
7
+ This is the Gemini CLI. We are setting up the context for our chat.
8
+ Today's date is ${t} (formatted according to the user's locale).
9
+ My operating system is: ${process.platform}
10
+ The project's temporary directory is: ${n}
11
+ - **Workspace Directories:**
12
+ - ${e}
13
+ - **Directory Structure:**
14
+
15
+ ${r}
16
+ </session_context>`.trim()}getWorkspaceTree(e,t=3,n=200){let r=[`${e}/`],i=0,a=(e,s,c)=>{if(i>=n||c>t)return;let l;try{l=o.readdirSync(e,{withFileTypes:!0}).filter(e=>![`.git`,`node_modules`,`.nx-cache`].includes(e.name)).sort((e,t)=>e.name.localeCompare(t.name))}catch{return}for(let o=0;o<l.length&&i<n;o+=1){let n=l[o],u=o===l.length-1,d=u?`└───`:`├───`,f=`${s}${u?` `:`│ `}`,p=h.join(e,n.name);if(i+=1,n.isDirectory()){r.push(`${s}${d}${n.name}/`),c<t&&a(p,f,c+1);continue}r.push(`${s}${d}${n.name}`)}};return a(e,``,0),i>=n&&r.push(`...`),r.join(`
17
+ `)}extractResponseText(e){return(e?.response?.candidates?.[0]?.content?.parts??[]).map(e=>e.text??``).filter(Boolean).join(``)}safeJsonParse(e){try{return JSON.parse(e)}catch{return}}getString(e,t){if(!e||typeof e!=`object`)return;let n=e;return typeof n[t]==`string`?n[t]:void 0}};z=x([r()],z);const B=g(a);let V=class{constructor(e=new C){this.captionService=e}async extractVideoContext(e){let t=h.resolve(e.videoPath);if(!s(t))throw Error(`Video file not found: ${t}`);let n=e.basename??h.basename(t,h.extname(t)),r=h.resolve(e.outputDir??this.defaultOutputDir(t,n)),i=h.join(r,`work`),a=h.join(r,`manifest.json`);await l.mkdir(r,{recursive:!0}),await this.probeVideo(t);let o=e.transcribe===!1?{text:``,segments:[]}:await this.transcribeVideo(t,r,e),c=o.segments.length>0?await this.createProxyVideo(t,i,e):void 0,u={transcript:{...o,segments:c?await this.attachFilmstripsToSegments(c,r,o.segments,e):[]},next_actions:[`Review segments filmstrips for visual inspection`]};return await l.writeFile(a,`${JSON.stringify(u,null,2)}\n`,`utf8`),u}async probeVideo(e){let{stdout:t}=await B(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`),a=Number(r?.width??0),o=Number(r?.height??0);if(!i||Number.isNaN(i))throw Error(`Invalid duration for video: ${e}`);if(!a||!o)throw Error(`No video stream found in: ${e}`);return{durationSeconds:i,width:a,height:o}}defaultOutputDir(e,t){let n=this.resolveWorkspaceRoot(h.dirname(e)),r=n?h.join(n,`.tmp`,`video-editor-mcp`):m.tmpdir();return h.join(r,`${t}-context`)}resolveWorkspaceRoot(e){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=h.resolve(e);for(;;){if(t.some(e=>s(h.join(n,e))))return n;let e=h.dirname(n);if(e===n)return;n=e}}async createProxyVideo(e,t,n){let r=h.join(t,`proxy.mp4`),i=Math.max(n.tileWidth??250,500);return await l.mkdir(t,{recursive:!0}),await B(`ffmpeg`,[`-y`,`-i`,e,`-vf`,`scale='min(${i},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-an`,r]),r}async transcribeVideo(e,t,n){let r=await this.resolveWhisperCommand(),i=h.join(t,`audio.wav`);await B(`ffmpeg`,[`-y`,`-i`,e,`-vn`,`-ac`,`1`,`-ar`,`16000`,i]),r.engine===`whisper`?await this.runPythonWhisper(r.binary,i,t,n):await this.runWhisperCpp(r.binary,i,t,n);let a=this.firstExistingPath([h.join(t,`audio.txt`),h.join(t,`transcript.txt`),`${i}.txt`]),o=this.firstExistingPath([h.join(t,`audio.srt`),h.join(t,`transcript.srt`),`${i}.srt`]),s=a?(await l.readFile(a,`utf8`)).trim():``,c=o?this.captionService.parseSrt(await l.readFile(o,`utf8`)):[];return{engine:r.engine,text:s,txtPath:a,srtPath:o,segments:c}}async resolveWhisperCommand(){for(let e of[`whisper-cli`,`whisper.cpp`,`whisper`]){let t=await this.findBinary(e);if(t)return{binary:t,engine:e}}throw Error(`No supported Whisper binary found. Install whisper.cpp or OpenAI Whisper, then retry.`)}async findBinary(e){try{let{stdout:t}=await B(`which`,[e]);return t.trim()||void 0}catch{return}}async runPythonWhisper(e,t,n,r){let i=[t,`--model`,r.whisperModel??`base`,`--output_dir`,n,`--output_format`,`all`];r.language&&i.push(`--language`,r.language),await B(e,i,{timeout:1200*1e3})}async runWhisperCpp(e,t,n,r){let i=r.whisperModelPath??process.env.WHISPER_MODEL_PATH??this.resolveDefaultWhisperModelPath();if(!i||!s(i))throw Error(`Whisper.cpp model not found. Provide whisperModelPath or set WHISPER_MODEL_PATH.`);let a=[`-m`,i,`-f`,t,`-otxt`,`-osrt`,`-of`,h.join(n,`transcript`)];r.language&&a.push(`-l`,r.language),await B(e,a,{timeout:1200*1e3})}resolveDefaultWhisperModelPath(){return[h.join(m.homedir(),`.whisper`,`ggml-base.en.bin`),h.join(m.homedir(),`.whisper`,`ggml-small.en.bin`),`/opt/homebrew/share/whisper-cpp/ggml-base.en.bin`,`/usr/local/share/whisper-cpp/ggml-base.en.bin`].find(e=>s(e))}async attachFilmstripsToSegments(e,t,n,r){let i=h.join(t,`slides`);await l.mkdir(i,{recursive:!0});let a=[];for(let[t,o]of n.entries()){let n=await this.extractSegmentFilmstrips(e,i,t,o,r);a.push({...o,text:o.text.trim(),filmstrips:n})}return a}async extractSegmentFilmstrips(e,t,n,r,i){let a=(i.imageFormat??`jpeg`)===`png`?`png`:`jpg`,o=i.framesPerSecond??3,s=i.tileWidth??250,c=i.filmstripColumns??6,l=c*(i.filmstripRows??4),u=Math.max((r.endMs-r.startMs)/1e3,1/o),d=Math.max(1,Math.ceil(u*o)),f=[];for(let i=0,u=0;i<d;i+=l,u+=1){let p=Math.min(l,d-i),m=Math.min(c,p),g=Math.ceil(p/m),_=r.startMs+Math.round(i/o*1e3),v=Math.min(p/o,Math.max((r.endMs-_)/1e3,1/o)),y=h.join(t,`segment_${String(n+1).padStart(4,`0`)}_filmstrip_${String(u+1).padStart(3,`0`)}.${a}`);await B(`ffmpeg`,[`-y`,`-ss`,(_/1e3).toFixed(3),`-t`,v.toFixed(3),`-i`,e,`-vf`,[`fps=${o}`,`scale='min(${s},iw)':-2`,`pad=iw:ih+32:0:0:white`,`tile=${m}x${g}:padding=8:margin=8:color=white`].join(`,`),`-frames:v`,`1`,y]),await this.annotateFilmstrip(y,_,o,p,m,g),f.push({path:y,index:u,startMs:_,endMs:Math.min(r.endMs,_+Math.round(v*1e3)),frameCount:p,tileWidth:s,columns:m,rows:g})}return f}async annotateFilmstrip(e,t,n,r,i,a){let o=await ie(e).metadata();if(!o.width||!o.height)throw Error(`Unable to annotate filmstrip without image dimensions: ${e}`);let s=(o.width-16-8*(i-1))/i,c=(o.height-16-8*(a-1))/a,u=Array.from({length:r},(e,r)=>{let a=r%i,o=Math.floor(r/i),l=t+Math.round(r/n*1e3);return`<text x="${8+a*(s+8)+s/2}" y="${8+o*(c+8)+c-32+22}" text-anchor="middle">${l} ms</text>`}).join(``),d=Buffer.from(`<svg width="${o.width}" height="${o.height}" xmlns="http://www.w3.org/2000/svg"><style>text{font-family:Arial,sans-serif;font-size:18px;fill:#000}</style>${u}</svg>`),f=await ie(e).composite([{input:d,top:0,left:0}]).toBuffer();await l.writeFile(e,f)}firstExistingPath(e){return e.find(e=>s(e))}};V=x([r(),F(`design:paramtypes`,[Object])],V);let H=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};H=x([r()],H);var tt,nt;const rt=b.object({url:b.string().describe(`Video URL from YouTube, TikTok, Instagram, or other yt-dlp supported sites`),filename:b.string().describe(`Output filename without extension (e.g., "my_clip")`),startTime:b.string().optional().describe(`Start time for clip extraction (e.g., "0:30", "1:15")`),endTime:b.string().optional().describe(`End time for clip extraction (e.g., "1:00", "2:30")`),maxHeight:b.number().int().positive().default(1080).describe(`Maximum video height in pixels (default: 1080)`),outputDir:b.string().optional().describe(`Custom output directory (default: Remotion public/videos/ for direct use in compositions)`)}),it=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let at=class extends H{static{nt=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return rt}getDefinition(){return{name:nt.TOOL_NAME,description:`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming. Saves to Remotion public/videos/ by default for direct use in compositions.`,inputSchema:b.toJSONSchema(rt,{reused:`inline`})}}async execute(e){try{let t=rt.parse(e),n=h.resolve(it(),`public/videos`),r=t.outputDir??n,i=await this.videoDownloadService.downloadClip(t.url,r,t.filename,t.startTime,t.endTime,t.maxHeight),a=t.outputDir?void 0:`/videos/${t.filename}.mp4`;return this.successJson({...i,compositionSrc:a,suggestedComposition:i.height>i.width?`Vertical`:`Main`})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};at=nt=x([r(),I(0,n(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(tt=R!==void 0&&R)==`function`?tt:Object])],at);var ot,st;const ct=b.object({videoPath:b.string().min(1).describe(`Path to the local video file to extract context from`),outputDir:b.string().optional().describe(`Directory to write transcript, filmstrips, and manifest`),basename:b.string().optional().describe(`Base name for the generated context directory when outputDir is omitted`),framesPerSecond:b.number().positive().default(3).describe(`Frames per second to sample for raw frames and segment slides`),imageFormat:b.enum([`jpeg`,`png`]).default(`jpeg`).describe(`Frame image format`),tileWidth:b.number().int().positive().default(250).describe(`Width of each filmstrip tile in pixels`),filmstripColumns:b.number().int().positive().default(6).describe(`Maximum columns per segment filmstrip image`),filmstripRows:b.number().int().positive().default(4).describe(`Maximum rows per segment filmstrip image`),transcribe:b.boolean().default(!0).describe(`Run local Whisper transcription`),language:b.string().optional().describe(`Optional Whisper language hint`),whisperModel:b.string().optional().describe(`Python Whisper model name, default base`),whisperModelPath:b.string().optional().describe(`Whisper.cpp model path override`)});let U=class extends H{static{st=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return ct}getDefinition(){return{name:st.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.toJSONSchema(ct,{reused:`inline`})}}async execute(e){try{let t=ct.parse(e),n=await this.videoContextService.extractVideoContext(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};U=st=x([r(),I(0,n(P.VideoContextService)),F(`design:paramtypes`,[typeof(ot=V!==void 0&&V)==`function`?ot:Object])],U);var lt,ut;const dt=b.object({captions:b.array(b.object({text:b.string(),startMs:b.number(),endMs:b.number(),confidence:b.number().min(0).max(1).optional(),emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()})).describe(`Array of caption objects with text, startMs, and endMs`),combineMs:b.number().optional().describe(`Milliseconds threshold for combining captions into pages (default: 100)`),maxWordsPerPage:b.number().int().positive().optional().describe(`Maximum words per caption page (default: 6)`),maxCharsPerLine:b.number().int().positive().optional().describe(`Target maximum characters per caption line (default: 22)`),platform:b.enum([`tiktok`,`reels`,`shorts`,`universal`]).optional().describe(`Platform safe-zone preset`),preset:b.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).optional().describe(`Caption visual style preset`)});let W=class extends H{static{ut=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return dt}getDefinition(){return{name:ut.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.toJSONSchema(dt,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=dt.parse(e),s=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs,confidence:e.confidence,emphasis:e.emphasis})),c=this.captionService.createTikTokCaptions(s,{combineMs:n,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o});return this.successJson(c)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=ut=x([r(),I(0,n(P.CaptionService)),F(`design:paramtypes`,[typeof(lt=C!==void 0&&C)==`function`?lt:Object])],W);var ft,pt;const mt=b.object({prompt:b.string().describe(`Music description for ACE-Step text-to-music generation`),filename:b.string().optional().describe(`Output filename without extension (default: ace-step-music)`),outputPath:b.string().optional().describe(`Explicit output audio path. Overrides filename/outputDir.`),outputDir:b.string().optional().describe(`Custom output directory (default: Remotion public/music/ for direct use in compositions)`),aceStepPath:b.string().optional().describe(`Path to local ACE-Step-1.5 checkout (default: ACE_STEP_15_DIR or ~/workspace/ACE-Step-1.5)`),lyrics:b.string().optional().describe(`Lyrics to use. Defaults to [Instrumental] for background music.`),duration:b.number().min(10).max(600).optional().describe(`Target duration in seconds (default: 30)`),bpm:b.number().min(30).max(300).optional().describe(`Optional BPM metadata`),keyscale:b.string().optional().describe(`Optional key/scale metadata, e.g. C minor`),timeSignature:b.string().optional().describe(`Optional time signature metadata, e.g. 4/4`),vocalLanguage:b.string().optional().describe(`Vocal language code, or unknown (default: unknown)`),seed:b.number().int().optional().describe(`Optional fixed seed for reproducible generation`),inferenceSteps:b.number().int().positive().optional().describe(`Diffusion inference steps (default: 8)`),audioFormat:b.enum([`wav`,`mp3`,`flac`]).default(`wav`).describe(`Output audio format (default: wav)`),backend:b.enum([`mlx`,`pt`,`vllm`]).default(`mlx`).describe(`ACE-Step LM backend (default: mlx for Apple Silicon)`),configPath:b.string().optional().describe(`ACE-Step DiT model config (default: acestep-v15-turbo)`),lmModelPath:b.string().optional().describe(`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`),timeoutMs:b.number().int().positive().optional().describe(`Generation timeout in milliseconds`)}),ht=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let gt=class extends H{static{pt=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return mt}getDefinition(){return{name:pt.TOOL_NAME,description:`Generate background music using a local ACE-Step 1.5 checkout. Defaults to MLX on Apple Silicon and saves to Remotion public/music/.`,inputSchema:b.toJSONSchema(mt,{reused:`inline`})}}async execute(e){try{let t=mt.parse(e);if(!await this.aceStepMusicService.isAvailable(t.aceStepPath))return this.error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass aceStepPath.`);let n=this.resolveOutputPath(t),r=await this.aceStepMusicService.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),i=h.resolve(ht(),`public/music`),a=h.dirname(n)===i?`/music/${h.basename(n)}`:void 0;return this.successJson({...r,compositionSrc:a,suggestedClip:a?{type:`audio`,src:a,startFrame:0,volume:.35}:void 0})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}resolveOutputPath(e){if(e.outputPath)return h.resolve(e.outputPath);let t=h.resolve(e.outputDir??h.join(ht(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return h.join(t,`${n}.${r}`)}};gt=pt=x([r(),I(0,n(P.AceStepMusicService)),F(`design:paramtypes`,[typeof(ft=S!==void 0&&S)==`function`?ft:Object])],gt);var _t,vt;const yt=b.object({text:b.string().describe(`Text to convert to speech`),voice:b.string().optional().describe(`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`),speed:b.number().optional().describe(`Speech speed multiplier (default: 1.0)`),outputPath:b.string().describe(`Output file path for the generated audio`)});let G=class extends H{static{vt=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return yt}getDefinition(){return{name:vt.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.toJSONSchema(yt,{reused:`inline`})}}async execute(e){try{let t=yt.parse(e);if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let n=await this.voiceoverService.generateVoiceover({text:t.text,voice:t.voice,speed:t.speed,outputPath:t.outputPath});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};G=vt=x([r(),I(0,n(P.VoiceoverService)),F(`design:paramtypes`,[typeof(_t=N!==void 0&&N)==`function`?_t:Object])],G);var bt,xt;const St=b.object({src:b.string().describe(`Path to the media file`),type:b.enum([`video`,`audio`]).optional().describe(`Media type (default: video)`)});let K=class extends H{static{xt=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return St}getDefinition(){return{name:xt.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.toJSONSchema(St,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=St.parse(e),r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};K=xt=x([r(),I(0,n(P.MediaInfoService)),F(`design:paramtypes`,[typeof(bt=E!==void 0&&E)==`function`?bt:Object])],K);var Ct,wt;const Tt=b.object({srtContent:b.string().describe(`SRT file content as a string`)});let q=class extends H{static{wt=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return Tt}getDefinition(){return{name:wt.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.toJSONSchema(Tt,{reused:`inline`})}}async execute(e){try{let t=Tt.parse(e),n=this.captionService.parseSrt(t.srtContent);return this.successJson({captionCount:n.length,captions:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=wt=x([r(),I(0,n(P.CaptionService)),F(`design:paramtypes`,[typeof(Ct=C!==void 0&&C)==`function`?Ct:Object])],q);var Et,Dt;const Ot=b.object({});let J=class extends H{static{Dt=this}static TOOL_NAME=`list_compositions`;constructor(e){super(),this.compositionService=e}getInputSchema(){return Ot}getDefinition(){return{name:Dt.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.toJSONSchema(Ot,{reused:`inline`})}}async execute(){return this.successJson(this.compositionService.listCompositions())}};J=Dt=x([r(),I(0,n(P.CompositionService)),F(`design:paramtypes`,[typeof(Et=w!==void 0&&w)==`function`?Et:Object])],J);var kt,At;const jt=b.object({compositionId:b.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:b.record(b.string(),b.unknown()).describe(`JSON props to pass to the composition`),outputPath:b.string().describe(`Output file path for the rendered image`),frame:b.number().optional().describe(`Frame number to render (default: 0)`),imageFormat:b.enum([`png`,`jpeg`]).optional().describe(`Image format (default: png)`)});let Y=class extends H{static{At=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return jt}getDefinition(){return{name:At.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:b.toJSONSchema(jt,{reused:`inline`})}}async execute(e){try{let t=jt.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,frame:t.frame,imageFormat:t.imageFormat},r=await this.renderService.renderStill(n);return this.successJson(r)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Y=At=x([r(),I(0,n(P.RenderService)),F(`design:paramtypes`,[typeof(kt=M!==void 0&&M)==`function`?kt:Object])],Y);var Mt,Nt;const Pt=b.preprocess(e=>{if(typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},b.record(b.string(),b.unknown())),Ft=b.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.boolean()),It=b.object({compositionId:b.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:Pt.describe(`JSON props to pass to the composition`),outputPath:b.string().describe(`Output file path for the rendered video`),codec:b.enum([`h264`,`h265`,`vp8`,`vp9`]).optional().describe(`Video codec (default: h264)`),dryRun:Ft.optional().describe(`Validate props and assets without rendering a video`)});let X=class extends H{static{Nt=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return It}getDefinition(){return{name:Nt.TOOL_NAME,description:"Render a video from JSON props using Remotion. `inputProps` must conform to the Main composition schema. Full JSON Schema (generated from `src/schemas/compositions.ts` -> `MainCompositionSchema`): `schemas/main-composition.schema.json` (relative to this package; in the monorepo: `packages/mcp/video-editor-mcp/schemas/main-composition.schema.json`). Validation runs server-side; failures return a `render_validation_failed` error with per-issue paths and suggested fixes.",inputSchema:b.toJSONSchema(It,{reused:`inline`})}}async execute(e){try{let t=It.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,codec:t.codec,dryRun:t.dryRun},r=await this.renderService.render(n);return this.successJson(r)}catch(e){return e instanceof Ye?{content:[{type:`text`,text:JSON.stringify({error:`render_validation_failed`,message:e.message,summary:e.validation.summary,issues:e.validation.errors,warnings:e.validation.warnings,suggestedFixes:e.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}],isError:!0}:this.error(e instanceof Error?e.message:`Unknown error`)}}};X=Nt=x([r(),I(0,n(P.RenderService)),F(`design:paramtypes`,[typeof(Mt=M!==void 0&&M)==`function`?Mt:Object])],X);var Lt,Rt;const zt=b.object({query:b.string().describe(`Search query for YouTube videos`),maxResults:b.number().int().positive().max(20).default(5).describe(`Maximum number of results (default: 5, max: 20)`)});let Bt=class extends H{static{Rt=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return zt}getDefinition(){return{name:Rt.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.toJSONSchema(zt,{reused:`inline`})}}async execute(e){try{let t=zt.parse(e),n=await this.videoDownloadService.search(t.query,t.maxResults);return this.successJson({query:t.query,resultCount:n.length,results:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Bt=Rt=x([r(),I(0,n(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(Lt=R!==void 0&&R)==`function`?Lt:Object])],Bt);var Vt,Z;const Q=b.object({videoPaths:b.array(b.string().min(1)).min(1).describe(`Paths to local video files to analyze`),prompt:b.string().min(1).describe(`Prompt to send with the video(s)`),model:b.string().default(`gemini-2.5-flash-lite`).describe(`Gemini model to use (default: gemini-2.5-flash-lite)`)});let $=class extends H{static{Z=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Q}getDefinition(){return{name:Z.TOOL_NAME,description:`Analyze one or more local video files by transcoding them to a compact MP4 and sending them with a prompt to Gemini Code Assist.`,inputSchema:b.toJSONSchema(Q,{reused:`inline`})}}async execute(e){try{let t=Q.parse(e),n=await this.videoAnalysisService.analyzeVideos({videoPaths:t.videoPaths,prompt:t.prompt,model:t.model});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};$=Z=x([r(),I(0,n(P.VideoAnalysisService)),F(`design:paramtypes`,[typeof(Vt=z!==void 0&&z)==`function`?Vt:Object])],$);const Ht=new t(e=>{e.bind(P.AceStepMusicService).to(S).inSingletonScope(),e.bind(P.RenderService).to(M).inSingletonScope(),e.bind(P.MediaInfoService).to(E).inSingletonScope(),e.bind(P.CaptionService).to(C).inSingletonScope(),e.bind(P.FontService).to(me).inSingletonScope(),e.bind(P.VoiceoverService).to(N).inSingletonScope(),e.bind(P.VideoDownloadService).to(R).inSingletonScope(),e.bind(P.VideoAnalysisService).to(z).inSingletonScope(),e.bind(P.VideoContextService).to(V).inSingletonScope(),e.bind(P.CompositionService).to(w).inSingletonScope()}),Ut=new t(e=>{e.bind(P.Tool).to(X).inSingletonScope(),e.bind(P.Tool).to(J).inSingletonScope(),e.bind(P.Tool).to(K).inSingletonScope(),e.bind(P.Tool).to(Y).inSingletonScope(),e.bind(P.Tool).to(W).inSingletonScope(),e.bind(P.Tool).to(q).inSingletonScope(),e.bind(P.Tool).to(gt).inSingletonScope(),e.bind(P.Tool).to(G).inSingletonScope(),e.bind(P.Tool).to(Bt).inSingletonScope(),e.bind(P.Tool).to(at).inSingletonScope(),e.bind(P.Tool).to($).inSingletonScope(),e.bind(P.Tool).to(U).inSingletonScope()});function Wt(t={}){let n=new e;return n.load(Ht,Ut),n.bind(P.ProxyUrl).toConstantValue(t.proxyUrl??null),n}const Gt=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function Kt(e){return Gt.has(e)}async function qt(e,t,n,r){return Kt(e)?r.run(()=>t.execute(n)):t.execute(n)}function Jt(e){let t=new se({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(P.Tool),r=new he,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(le,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(ce,async e=>{let{name:t,arguments:n}=e.params,a=i.get(t);if(!a)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let o=ae(n??{},a.getInputSchema());try{return await qt(t,a,o,r)}catch(e){if(e instanceof b.ZodError)return{content:[{type:`text`,text:oe(e,{schemaName:t,schema:a.getInputSchema()})}],isError:!0};throw e}}),t}var Yt=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new ue,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{ve as A,Se as C,Te as D,be as E,Ce as O,_e as S,we as T,Ye as _,X as a,ze as b,q as c,W as d,U as f,M as g,P as h,$ as i,ye as j,xe as k,K as l,V as m,Jt as n,Y as o,H as p,Wt as r,J as s,Yt as t,G as u,Ne as v,Ee as w,Be as x,De as y};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agimon-ai/video-editor-mcp",
3
3
  "description": "MCP server for video editing with Remotion",
4
- "version": "0.14.2",
4
+ "version": "0.15.1",
5
5
  "license": "AGPL-3.0",
6
6
  "keywords": [
7
7
  "mcp",
@@ -45,9 +45,9 @@
45
45
  "remotion": "4.0.454",
46
46
  "sharp": "0.34.5",
47
47
  "zod": "4.4.1",
48
- "@agimon-ai/foundation-process-registry": "0.14.2",
49
- "@agimon-ai/foundation-port-registry": "0.14.2",
50
- "@agimon-ai/foundation-validator": "0.11.2"
48
+ "@agimon-ai/foundation-validator": "0.12.1",
49
+ "@agimon-ai/foundation-port-registry": "0.15.1",
50
+ "@agimon-ai/foundation-process-registry": "0.15.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "25.6.0",
@@ -69,6 +69,9 @@
69
69
  },
70
70
  "./package.json": "./package.json"
71
71
  },
72
+ "inlinedDependencies": {
73
+ "chalk": "5.6.2"
74
+ },
72
75
  "scripts": {
73
76
  "dev": "node --loader ts-node/esm src/cli.ts mcp-serve",
74
77
  "generate-schemas": "tsx scripts/generate-schemas.ts",
@@ -1,17 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));require(`reflect-metadata`);let c=require(`inversify`),l=require(`node:child_process`),u=require(`node:fs`);u=s(u,1);let d=require(`node:fs/promises`);d=s(d,1);let f=require(`node:os`);f=s(f,1);let p=require(`node:path`);p=s(p,1);let m=require(`node:util`),h=require(`node:crypto`);h=s(h,1);let g=require(`node:url`),_=require(`@remotion/bundler`),v=require(`@remotion/renderer`),y=require(`sharp`);y=s(y,1);let b=require(`zod`),ee=require(`@agimon-ai/foundation-validator`),te=require(`@modelcontextprotocol/sdk/server/index.js`),ne=require(`@modelcontextprotocol/sdk/types.js`),re=require(`@modelcontextprotocol/sdk/server/stdio.js`);function x(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}const ie=(0,m.promisify)(l.execFile);let S=class{async isAvailable(e){let t=this.resolveAceStepPath(e);if(!this.isAceStepCheckout(t))return!1;try{return await ie(`uv`,[`--version`]),!0}catch{return!1}}async generateMusic(e){let t=this.resolveAceStepPath(e.aceStepPath);if(!this.isAceStepCheckout(t))throw Error(`ACE-Step 1.5 checkout not found at ${t}. Pass aceStepPath or set ACE_STEP_15_DIR.`);let n=e.audioFormat??this.getFormatFromPath(e.outputPath),r=e.backend??`mlx`,i=e.duration??30,a=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`ace-step-music-`)),o=p.default.join(a,`generation.toml`),s=p.default.join(a,`output`);await d.default.mkdir(s,{recursive:!0}),await d.default.mkdir(p.default.dirname(e.outputPath),{recursive:!0});try{let a=this.buildConfig({...e,aceStepPath:t,audioFormat:n,backend:r,duration:i,outputDir:s});await d.default.writeFile(o,a,`utf-8`);let{stdout:c,stderr:l}=await ie(`uv`,[`run`,`python`,`cli.py`,`-c`,o,`--backend`,r,`--log-level`,`INFO`],{cwd:t,timeout:e.timeoutMs??1200*1e3,maxBuffer:50*1024*1024}),u=await this.findNewestGeneratedAudio(s,n);return await d.default.copyFile(u,e.outputPath),{outputPath:e.outputPath,sourcePath:u,duration:i,audioFormat:n,backend:r,aceStepPath:t,stdout:c,stderr:l}}catch(e){throw Error(`Failed to generate ACE-Step music: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{await d.default.rm(a,{recursive:!0,force:!0}).catch(()=>void 0)}}resolveAceStepPath(e){return p.default.resolve(e??process.env.ACE_STEP_15_DIR??process.env.ACESTEP_15_DIR??p.default.join(f.default.homedir(),`workspace`,`ACE-Step-1.5`))}isAceStepCheckout(e){return(0,u.existsSync)(p.default.join(e,`cli.py`))&&(0,u.existsSync)(p.default.join(e,`pyproject.toml`))}getFormatFromPath(e){let t=p.default.extname(e).toLowerCase().replace(`.`,``);return t===`mp3`||t===`flac`||t===`wav`?t:`wav`}async findNewestGeneratedAudio(e,t){let n=await d.default.readdir(e,{withFileTypes:!0}),r=await Promise.all(n.filter(e=>e.isFile()&&e.name.endsWith(`.${t}`)).map(async t=>{let n=p.default.join(e,t.name);return{filePath:n,mtimeMs:(await d.default.stat(n)).mtimeMs}}));if(r.sort((e,t)=>t.mtimeMs-e.mtimeMs),!r[0])throw Error(`ACE-Step completed but no .${t} file was found in ${e}`);return r[0].filePath}buildConfig(e){let t=e.lyrics?.trim()||`[Instrumental]`,n=t.toLowerCase()===`[instrumental]`,r={project_root:e.aceStepPath,checkpoint_dir:p.default.join(e.aceStepPath,`checkpoints`),config_path:e.configPath??`acestep-v15-turbo`,lm_model_path:e.lmModelPath??`acestep-5Hz-lm-0.6B`,backend:e.backend,device:`auto`,offload_to_cpu:!1,offload_dit_to_cpu:!1,save_dir:e.outputDir,audio_format:e.audioFormat,caption:e.prompt,lyrics:t,duration:e.duration,instrumental:n,bpm:e.bpm??null,keyscale:e.keyscale??``,timesignature:e.timeSignature??``,vocal_language:e.vocalLanguage??`unknown`,task_type:`text2music`,sample_mode:!1,sample_query:``,use_format:!1,inference_steps:e.inferenceSteps??8,seed:e.seed??-1,use_random_seed:e.seed===void 0,guidance_scale:7,use_adg:!1,shift:3,infer_method:`ode`,thinking:!0,lm_temperature:.85,lm_cfg_scale:2,lm_top_k:0,lm_top_p:.9,use_cot_metas:!0,use_cot_caption:!0,use_cot_lyrics:!1,use_cot_language:!0,use_constrained_decoding:!0,batch_size:1,allow_lm_batch:!1,lm_batch_chunk_size:8,constrained_decoding_debug:!1,cfg_interval_start:0,cfg_interval_end:1,lm_negative_prompt:`NO USER INPUT`,log_level:`INFO`};return Object.entries(r).filter(e=>e[1]!==null).map(([e,t])=>`${e} = ${this.toTomlValue(t)}`).join(`
2
- `)}toTomlValue(e){return typeof e==`string`?JSON.stringify(e):String(e)}};S=x([(0,c.injectable)()],S);const ae={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};let C=class{parseSrt(e){let t=[],n=e.split(`
3
- `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
4
- `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t=100){let n=typeof t==`number`?{combineMs:t}:t,r=n.combineMs??100,i=n.maxWordsPerPage??6,a=n.maxCharsPerLine??22,o=n.platform??`tiktok`,s=n.preset??`tiktok-bold`,c=this.createCaptionPages(e,{combineMs:r,maxWordsPerPage:i});return{pages:c,captionConfig:{captions:e,pages:c,style:s===`karaoke`?`karaoke`:`tiktok`,preset:s,platform:o,combineTokensWithinMilliseconds:r,maxWordsPerPage:i,maxCharsPerLine:a,maxLines:2,maxWidthPct:.86,fontSize:60,minFontSize:42,fontFamily:`Arial, sans-serif`,fontWeight:800,lineHeight:1.08,color:`#ffffff`,inactiveColor:`#ffffff`,highlightColor:`#FFD700`,textColor:`#ffffff`,strokeColor:`#000000`,strokeWidth:5,shadow:!0,backgroundColor:`rgba(0, 0, 0, 0)`,backgroundRadius:8,safeZone:ae[o],position:`bottom`}}}createCaptionPages(e,t={}){let n=[],r=this.normalizeCaptionsToTokens(e),i=t.combineMs??100,a=t.maxWordsPerPage??6;if(r.length===0)return n;let o={startMs:r[0]?.startMs??0,endMs:r[0]?.endMs??0,tokens:[r[0]]};for(let e=1;e<r.length;e++){let t=r[e];if(!t)continue;let s=t.startMs-o.endMs,c=this.countWords(o.tokens.map(e=>e.text).join(` `)),l=this.countWords(t.text);s<=i&&c+l<=a?(o.tokens.push(t),o.endMs=t.endMs):(o.text=this.pageText(o.tokens),n.push(o),o={startMs:t.startMs,endMs:t.endMs,tokens:[t]})}return o.tokens.length>0&&(o.text=this.pageText(o.tokens),n.push(o)),n}normalizeCaptionsToTokens(e){return[...e].sort((e,t)=>e.startMs-t.startMs).flatMap(e=>this.captionToTokens(e))}getPlatformSafeZone(e){return ae[e]}captionToTokens(e){let t=e.text.trim().split(/\s+/).filter(Boolean);if(t.length===0)return[];if(t.length===1)return[{...e,text:t[0]??e.text,emphasis:e.emphasis??`none`}];let n=Math.max(1,e.endMs-e.startMs)/t.length;return t.map((r,i)=>{let a=Math.round(e.startMs+n*i),o=i===t.length-1?e.endMs:Math.round(e.startMs+n*(i+1));return{text:r,startMs:a,endMs:Math.max(o,a+1),timestampMs:a,confidence:e.confidence,emphasis:e.emphasis??`none`}})}pageText(e){return e.map(e=>e.text).join(` `)}countWords(e){return e.trim().split(/\s+/).filter(Boolean).length}};C=x([(0,c.injectable)()],C);const oe=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let se=class{getAvailableGoogleFonts(){return[...oe]}validateFontFamily(e){let t=e.toLowerCase();return oe.some(e=>e.toLowerCase()===t)}};se=x([(0,c.injectable)()],se);var ce=class{tail=Promise.resolve();currentActiveCount=0;currentPendingCount=0;async run(e){let t=this.tail,n,r=new Promise(e=>{n=e});this.currentPendingCount+=1,this.tail=t.then(()=>r),await t,--this.currentPendingCount,this.currentActiveCount+=1;try{return await e()}finally{--this.currentActiveCount,n()}}getStatus(){return{activeCount:this.currentActiveCount,pendingCount:this.currentPendingCount}}};const w=(0,m.promisify)(l.execFile);let T=class{async getVideoDuration(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};T=x([(0,c.injectable)()],T);const E=b.z.coerce.number(),D=b.z.coerce.number().int(),O=b.z.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.z.boolean()),k=b.z.string().min(1).refine(e=>/^(\/|file:|https?:|data:|blob:)/i.test(e),{message:`src must be an absolute filesystem path or a file:// / http(s):// / data: / blob: URL — relative paths are not allowed.`}),le=b.z.object({frame:D.nonnegative(),volume:E.min(0).max(1)}),A=b.z.object({type:b.z.enum([`interpolate`,`spring`]).default(`interpolate`),easing:b.z.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:b.z.object({mass:E.positive().default(1),damping:E.positive().default(10),stiffness:E.positive().default(100)}).optional(),delay:D.nonnegative().default(0)}),ue=b.z.object({type:b.z.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:D.positive().default(15),direction:b.z.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),j={startFrame:D.nonnegative(),durationInFrames:D.positive(),premountFor:D.nonnegative().default(30),entrance:A.optional(),exit:A.optional(),transition:ue.optional()},de=b.z.object({type:b.z.literal(`video`),...j,src:k,volume:E.min(0).max(1).optional(),playbackRate:E.positive().optional(),muted:O.optional(),loop:O.optional(),trimBefore:E.nonnegative().optional(),trimAfter:E.nonnegative().optional(),transparent:O.optional(),noFadeIn:O.optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),fe=b.z.object({type:b.z.literal(`image`),...j,src:k,fit:b.z.enum([`contain`,`cover`,`fill`]).optional(),noFadeIn:O.optional(),baseScale:E.positive().optional(),transformOriginX:b.z.string().optional(),transformOriginY:b.z.string().optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),pe=b.z.object({type:b.z.literal(`text`),...j,text:b.z.string(),fontSize:E.positive().default(80),fontFamily:b.z.string().default(`sans-serif`),fontWeight:b.z.union([b.z.string(),E]).optional(),color:b.z.string().default(`#ffffff`),backgroundColor:b.z.string().optional(),position:b.z.enum([`top`,`center`,`bottom`]).default(`center`),animation:b.z.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:b.z.string().optional(),highlightWord:b.z.string().optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),me=b.z.object({type:b.z.literal(`audio`),...j,src:k,volume:E.min(0).max(1).optional(),volumeAutomation:b.z.array(le).optional(),playbackRate:E.positive().optional(),muted:O.optional(),loop:O.optional(),trimBefore:E.nonnegative().optional(),trimAfter:E.nonnegative().optional(),toneFrequency:E.positive().optional()}),he=b.z.object({type:b.z.literal(`subtitle`),...j,text:b.z.string(),position:b.z.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:b.z.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:E.positive().default(36),fontFamily:b.z.string().default(`Arial, sans-serif`),color:b.z.string().default(`#ffffff`),backgroundColor:b.z.string().default(`rgba(0, 0, 0, 0.7)`),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),ge=b.z.object({type:b.z.literal(`gif`),...j,src:k,width:E.positive().optional(),height:E.positive().optional(),fit:b.z.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:E.positive().optional(),loopBehavior:b.z.enum([`loop`,`pause-after-finish`]).optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),_e=b.z.object({type:b.z.literal(`lottie`),...j,src:k,width:E.positive().optional(),height:E.positive().optional(),loop:O.optional(),style:b.z.record(b.z.string(),b.z.unknown()).optional()}),ve=b.z.discriminatedUnion(`type`,[de,fe,pe,me,he,ge,_e]),ye=b.z.object({text:b.z.string(),startMs:E.nonnegative(),endMs:E.nonnegative(),timestampMs:E.nonnegative().optional(),confidence:E.min(0).max(1).optional(),emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()}),be=ye.extend({emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).default(`none`)}),xe=b.z.object({x:E.min(0).max(1).optional(),y:E.min(0).max(1).optional(),position:b.z.enum([`top`,`center`,`bottom`]).optional()}),Se=b.z.object({text:b.z.string().optional(),startMs:E.nonnegative(),endMs:E.nonnegative(),tokens:b.z.array(be).min(1),placement:xe.optional()}),Ce=b.z.object({top:E.nonnegative().optional(),right:E.nonnegative().optional(),bottom:E.nonnegative().optional(),left:E.nonnegative().optional()}),we=b.z.object({color:b.z.string().default(`rgba(0, 0, 0, 0.85)`),blur:E.nonnegative().default(8),offsetX:E.default(0),offsetY:E.default(3)}),Te=b.z.object({captions:b.z.array(ye).default([]),pages:b.z.array(Se).optional(),style:b.z.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),preset:b.z.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).default(`tiktok-bold`),platform:b.z.enum([`tiktok`,`reels`,`shorts`,`universal`]).default(`tiktok`),combineTokensWithinMilliseconds:E.nonnegative().default(800),maxWordsPerPage:D.positive().default(6),maxCharsPerLine:D.positive().default(22),maxLines:D.positive().default(2),maxWidthPct:E.positive().max(1).default(.86),fontSize:E.positive().default(60),minFontSize:E.positive().default(42),fontFamily:b.z.string().default(`Arial, sans-serif`),fontWeight:b.z.union([D.positive(),b.z.string()]).default(800),lineHeight:E.positive().default(1.08),color:b.z.string().default(`#ffffff`),inactiveColor:b.z.string().default(`#ffffff`),highlightColor:b.z.string().default(`#FFD700`),textColor:b.z.string().default(`#ffffff`),strokeColor:b.z.string().default(`#000000`),strokeWidth:E.nonnegative().default(5),shadow:b.z.union([O,we]).default(!0),backgroundColor:b.z.string().default(`rgba(0, 0, 0, 0)`),backgroundRadius:E.nonnegative().default(8),safeZone:Ce.optional(),position:b.z.enum([`top`,`center`,`bottom`]).default(`bottom`)}).superRefine((e,t)=>{e.captions.length===0&&(!e.pages||e.pages.length===0)&&t.addIssue({code:`custom`,message:`Caption config must include captions or pages.`,path:[`captions`]})}),Ee={src:k,volume:E.min(0).max(1).default(1),fadeInMs:E.nonnegative().default(0),fadeOutMs:E.nonnegative().default(0),trimBeforeMs:E.nonnegative().default(0),trimAfterMs:E.nonnegative().optional(),playbackRate:E.positive().default(1)},De=b.z.object({...Ee,definesTimeline:O.default(!0),durationMs:E.positive().optional()}),Oe=b.z.object({...Ee,loop:O.default(!0)}),ke=b.z.object({...Ee,atMs:E.nonnegative(),durationMs:E.positive().optional()}),Ae=b.z.object({voiceover:De.optional(),music:Oe.optional(),sfx:b.z.array(ke).default([])}),je=b.z.object({family:b.z.string(),source:b.z.enum([`google`,`local`]).default(`google`),weights:b.z.array(D.positive()).optional(),url:b.z.string().optional()}),Me=b.z.object({clips:b.z.array(ve).default([]),backgroundColor:b.z.string().default(`#000000`),fps:D.positive().default(30),width:D.positive().default(1920),height:D.positive().default(1080),captions:Te.optional(),globalTransition:ue.optional(),fonts:b.z.array(je).optional(),audio:Ae.optional()}).passthrough(),Ne=`${p.default.sep}public${p.default.sep}`,Pe=/^(https?:|data:|blob:)/i,Fe=/\b(undefined|NaN|null)\b|blur\(px\)/i,Ie={Main:{width:1920,height:1080},Vertical:{width:1080,height:1920},Square:{width:1080,height:1080}},Le=new Set([`video`,`image`,`audio`,`gif`,`lottie`]),Re=new Set([`video`,`image`,`gif`,`lottie`,`text`,`subtitle`]),ze={tiktok:480,reels:420,shorts:420,universal:480},Be={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};var Ve=class e extends Error{constructor(t){super(e.formatMessage(t)),this.validation=t,this.name=`RenderValidationError`}static formatMessage(e){return`Render validation failed: ${e.errors.map(e=>`${e.path??e.code}: ${e.message}`).join(`; `)}`}};let M=class{bundlePromise=null;mediaInfoService=new T;projectRoot=(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);async getServeUrl(){return this.bundlePromise||=(0,_.bundle)({entryPoint:p.default.resolve(this.projectRoot,`src/remotion/index.ts`),publicDir:p.default.resolve(this.projectRoot,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})}),this.bundlePromise}isTransientRendererNavigationError(e){let t=e instanceof Error?e.message:String(e);return t.includes(`ERR_EMPTY_RESPONSE`)||t.includes(`ERR_CONNECTION_RESET`)}async withTransientRendererRetry(e){for(let t=1;t<=2;t+=1)try{return await e()}catch(e){if(t===2||!this.isTransientRendererNavigationError(e))throw e;this.bundlePromise=null,await new Promise(e=>setTimeout(e,150))}throw Error(`Renderer retry exhausted unexpectedly.`)}get publicDir(){return p.default.resolve(this.projectRoot,`public`)}toPublicRelativePath(e){let t=p.default.normalize(e),n=t.indexOf(Ne);return n===-1?null:t.slice(n+Ne.length).split(p.default.sep).join(`/`)}toLocalFilesystemPath(e){return e.startsWith(`file://`)?(0,g.fileURLToPath)(e):p.default.isAbsolute(e)?e:null}toPublicFilesystemPath(e){let t=e.replace(/^\/+/,``).replace(/^public\//,``);return p.default.join(this.publicDir,...t.split(`/`))}resolveValidationSource(e){if(Pe.test(e))return{kind:`remote`,src:e};let t=this.toLocalFilesystemPath(e);return t?{kind:`local`,src:e,filePath:t}:{kind:`local`,src:e,filePath:this.toPublicFilesystemPath(e)}}hasExplicitDimension(e,t){return Object.prototype.hasOwnProperty.call(e,t)}voiceoverFrames(e,t){let n=e?.voiceover;if(!n||n.definesTimeline===!1||!n.durationMs)return 0;let r=Math.max(0,(n.trimAfterMs??n.durationMs)-(n.trimBeforeMs??0))/(n.playbackRate??1);return Math.ceil(r/1e3*t)}framesToSeconds(e,t){return Number((e/t).toFixed(3))}canExtendVisualClip(e){return e.type===`image`||e.type===`text`||e.type===`subtitle`||e.type===`lottie`}createVisualGapIssue({gapStart:e,gapEnd:t,fps:n,previous:r,next:i,durationInFrames:a,message:o}){let s=t-e,c={gapStartFrame:e,gapEndFrame:t,gapDurationInFrames:s,gapStartSeconds:this.framesToSeconds(e,n),gapEndSeconds:this.framesToSeconds(t,n),gapDurationSeconds:this.framesToSeconds(s,n),renderDurationInFrames:a};if(r&&(c.previousVisualClip={index:r.clipIndex,type:r.type,startFrame:r.start,endFrame:r.end,durationInFrames:r.durationInFrames,canExtendSafely:r.canExtendSafely}),i&&(c.nextVisualClip={index:i.clipIndex,type:i.type,startFrame:i.start,endFrame:i.end,durationInFrames:i.durationInFrames}),r?.canExtendSafely){let n=r.durationInFrames+s;return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:`Extend clips[${r.clipIndex}].durationInFrames from ${r.durationInFrames} to ${n} so visuals cover frames ${e}..${t}.`,edits:[{path:`inputProps.clips.${r.clipIndex}.durationInFrames`,value:n,reason:`Covers the ${s}-frame visual gap without changing audio timing.`}]}}}return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:r&&!r.canExtendSafely?`Add a still/image/text visual from frame ${e} for ${s} frames, or replace/loop the preceding visual before extending it.`:`Add a visual clip starting at frame ${e} with durationInFrames ${s}.`}}}createSummary(e,t,n){let r=t.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),i=this.voiceoverFrames(t.audio,t.fps),a=Math.max(r,i);return{compositionId:e.compositionId,width:this.hasExplicitDimension(e.inputProps,`width`)?t.width:n.width,height:this.hasExplicitDimension(e.inputProps,`height`)?t.height:n.height,fps:t.fps,durationInFrames:a,clipCount:t.clips.length}}pushBadStringIssues(e,t,n,r){if(typeof e==`string`){Fe.test(e)&&n.push({code:`${r}.invalid_value`,message:`Invalid ${r} value: ${e}`,path:t});return}if(!(!e||typeof e!=`object`)){if(Array.isArray(e)){e.forEach((e,i)=>this.pushBadStringIssues(e,`${t}.${i}`,n,r));return}Object.entries(e).forEach(([e,i])=>{this.pushBadStringIssues(i,`${t}.${e}`,n,r)})}}validateTextClip(e,t,n){`text`in e&&(e.text.trim()||n.push({code:`text.empty`,message:`Text and subtitle clips must not be empty.`,path:`${t}.text`}),this.pushBadStringIssues(e.text,`${t}.text`,n,`text`))}validateStyle(e,t,n){`style`in e&&e.style&&this.pushBadStringIssues(e.style,`${t}.style`,n,`style`)}isSourceClip(e){return Le.has(e.type)}async validateLocalAsset(e,t,n,r,i){if(!t.filePath)return;let a;try{a=await(0,d.stat)(t.filePath)}catch{i.push({code:`asset.missing`,message:`Render asset does not exist: ${t.src}`,path:`${n}.src`});return}if(!a.isFile()){i.push({code:`asset.not_file`,message:`Render asset is not a file: ${t.src}`,path:`${n}.src`});return}try{if(e.type===`image`||e.type===`gif`){let n=await(0,y.default)(t.filePath,{animated:e.type===`gif`}).metadata();if(!n.width||!n.height)throw Error(`Missing image dimensions`)}else if(e.type===`lottie`)JSON.parse(await(0,d.readFile)(t.filePath,`utf-8`));else if(e.type===`video`){await this.mediaInfoService.getVideoDimensions(t.filePath);let a=await this.mediaInfoService.getVideoDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}else if(e.type===`audio`){let a=await this.mediaInfoService.getAudioDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}}catch(e){i.push({code:`asset.decode_failed`,message:`Render asset cannot be decoded: ${t.src}. ${e instanceof Error?e.message:String(e)}`,path:`${n}.src`})}}validateMediaDuration(e,t,n,r,i){let a=e.trimBefore??0,o=e.trimAfter??0,s=e.playbackRate??1;if(o>0&&o<=a){i.push({code:`media.invalid_trim`,message:`trimAfter must be greater than trimBefore when provided.`,path:r});return}if(e.loop)return;let c=t*n,l=(o>0?o-a:c-a)/s;l+2<e.durationInFrames&&i.push({code:`media.too_short`,message:`Clip duration (${e.durationInFrames} frames) exceeds available media (${Math.floor(l)} frames).`,path:r})}async validateClipAssets(e,t,n){await Promise.all(e.clips.map(async(r,i)=>{let a=`clips.${i}`;if(this.validateTextClip(r,a,n),this.validateStyle(r,a,n),!this.isSourceClip(r))return;let o=this.resolveValidationSource(r.src);if(o.kind===`remote`){t.push({code:`asset.remote_unprobed`,message:`Remote asset will be fetched during render and cannot be decoded in preflight: ${r.src}`,path:`${a}.src`});return}await this.validateLocalAsset(r,o,a,e.fps,n)}))}async validateAudioLayer(e,t,n){if(!e)return;let r=[];e.voiceover&&r.push({src:e.voiceover.src,path:`audio.voiceover.src`}),e.music&&r.push({src:e.music.src,path:`audio.music.src`}),e.sfx?.forEach((e,t)=>{r.push({src:e.src,path:`audio.sfx.${t}.src`})}),await Promise.all(r.map(async({src:e,path:r})=>{let i=this.resolveValidationSource(e);if(i.kind===`remote`){t.push({code:`audio.remote_unprobed`,message:`Remote audio asset will be fetched during render and cannot be decoded in preflight: ${e}`,path:r});return}if(i.filePath)try{if(!(await(0,d.stat)(i.filePath)).isFile()){n.push({code:`audio.not_file`,message:`Audio asset is not a file: ${e}`,path:r});return}await this.mediaInfoService.getAudioDuration(i.filePath)}catch(t){if(t.code===`ENOENT`){n.push({code:`audio.missing`,message:`Audio asset does not exist: ${e}`,path:r});return}n.push({code:`audio.decode_failed`,message:`Audio asset cannot be decoded: ${e}. ${t instanceof Error?t.message:String(t)}`,path:r})}}))}async resolveAudioDurationMs(e){let t=this.resolveValidationSource(e);if(t.kind===`remote`||!t.filePath)return null;try{return await this.mediaInfoService.getAudioDuration(t.filePath)*1e3}catch{return null}}async injectAudioDurations(e){let t=e.audio;if(!t?.voiceover||t.voiceover.durationMs)return e;let n=await this.resolveAudioDurationMs(t.voiceover.src);return n?{...e,audio:{...t,voiceover:{...t.voiceover,durationMs:n}}}:e}validateTimeline(e,t,n){let r=!!(e.audio?.voiceover||e.audio?.music||e.audio?.sfx&&e.audio.sfx.length>0);if(e.clips.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.empty`,message:`Render must contain at least one clip.`,path:`clips`});return}let i=e.clips.map((e,t)=>({clip:e,clipIndex:t})).filter(({clip:e})=>Re.has(e.type)).map(({clip:e,clipIndex:t})=>({clipIndex:t,type:e.type,start:e.startFrame,end:e.startFrame+e.durationInFrames,durationInFrames:e.durationInFrames,canExtendSafely:this.canExtendVisualClip(e)})).sort((e,t)=>e.start-t.start);if(i.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.no_visual`,message:`Render must contain at least one visual clip.`,path:`clips`});return}let a=e.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),o=Math.max(a,this.voiceoverFrames(e.audio,e.fps));i[0]&&i[0].start>3&&t.push(this.createVisualGapIssue({gapStart:0,gapEnd:i[0].start,fps:e.fps,next:i[0],durationInFrames:o,message:`Visual timeline starts at frame ${i[0].start}; expected coverage from frame 0.`}));let s=i[0]?.end??0,c=i[0];for(let n of i.slice(1))n.start>s+3&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:n.start,fps:e.fps,previous:c,next:n,durationInFrames:o,message:`Visual timeline has a gap from frame ${s} to ${n.start}.`})),n.end>s&&(c=n),s=Math.max(s,n.end);s+3<o&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:o,fps:e.fps,previous:c,durationInFrames:o,message:`Visual timeline ends at frame ${s}; render duration is ${o}.`}))}validateCaptions(e,t,n,r){if(!e.captions)return;let i=e.captions,a=t.durationInFrames/e.fps*1e3,o=-1,s=-1;i.captions.forEach((e,t)=>{let i=`captions.captions.${t}`;e.text.trim()||n.push({code:`captions.empty_text`,message:`Caption text must not be empty.`,path:`${i}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.invalid_timing`,message:`Caption endMs must be greater than startMs.`,path:i}),e.startMs<o&&n.push({code:`captions.unsorted`,message:`Captions must be sorted by startMs.`,path:i}),e.startMs<s&&r.push({code:`captions.overlap`,message:`Caption overlaps the previous caption; prefer page/token timing for intentional karaoke overlap.`,path:i}),e.endMs>a+100&&n.push({code:`captions.out_of_range`,message:`Caption ends after render duration (${Math.round(a)}ms).`,path:i}),o=e.startMs,s=Math.max(s,e.endMs),this.pushBadStringIssues(e.text,`${i}.text`,n,`text`)}),i.pages?.forEach((t,o)=>{let s=`captions.pages.${o}`;t.endMs<=t.startMs&&n.push({code:`captions.page_invalid_timing`,message:`Caption page endMs must be greater than startMs.`,path:s}),t.endMs>a+100&&n.push({code:`captions.page_out_of_range`,message:`Caption page ends after render duration (${Math.round(a)}ms).`,path:s});let c=-1,l=-1;t.tokens.forEach((e,i)=>{let a=`${s}.tokens.${i}`;e.text.trim()||n.push({code:`captions.token_empty_text`,message:`Caption token text must not be empty.`,path:`${a}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.token_invalid_timing`,message:`Caption token endMs must be greater than startMs.`,path:a}),(e.startMs<t.startMs||e.endMs>t.endMs)&&n.push({code:`captions.token_outside_page`,message:`Caption token timing must stay within its page timing.`,path:a}),e.startMs<c&&n.push({code:`captions.token_unsorted`,message:`Caption page tokens must be sorted by startMs.`,path:a}),e.startMs<l&&r.push({code:`captions.token_overlap`,message:`Caption token overlaps the previous token.`,path:a}),e.confidence!==void 0&&e.confidence<.75&&r.push({code:`captions.low_confidence`,message:`Low-confidence caption token should be reviewed before render.`,path:a}),c=e.startMs,l=Math.max(l,e.endMs),this.pushBadStringIssues(e.text,`${a}.text`,n,`text`)});let u=this.countCaptionWords(t.tokens.map(e=>e.text).join(` `));u>i.maxWordsPerPage&&r.push({code:`captions.page_too_dense`,message:`Caption page has ${u} words; configured maxWordsPerPage is ${i.maxWordsPerPage}.`,path:s});let d=t.placement?.x!==void 0,f=t.placement?.y!==void 0;if(d!==f&&r.push({code:`captions.page_partial_placement`,message:`Caption page placement should provide both x and y for predictable focal-target avoidance.`,path:`${s}.placement`}),d||f){let n={...Be[i.platform],...i.safeZone},a=n.left/e.width,o=1-n.right/e.width,c=n.top/e.height,l=1-n.bottom/e.height,u=t.placement?.x??.5,d=t.placement?.y??.5;(u<a||u>o||d<c||d>l)&&r.push({code:`captions.page_placement_safe_zone`,message:`Caption page explicit placement sits outside the configured platform safe zone.`,path:`${s}.placement`})}});let c=ze[i.platform]??480,l=i.safeZone?.bottom??c;i.position===`bottom`&&l<c&&r.push({code:`captions.safe_zone_bottom`,message:`Bottom caption safe zone ${l}px is smaller than ${i.platform} preset ${c}px.`,path:`captions.safeZone.bottom`}),i.style===`karaoke`&&!i.pages&&i.captions.some(e=>/\s/.test(e.text.trim()))&&r.push({code:`captions.karaoke_legacy_timing`,message:`Karaoke legacy captions use proportional word timing; provide pages/tokens for accurate word sync.`,path:`captions.captions`})}countCaptionWords(e){return e.trim().split(/\s+/).filter(Boolean).length}async copyToPublic(e){let t=await(0,d.stat)(e);if(!t.isFile())throw Error(`Render asset is not a file: ${e}`);let n=p.default.extname(e),r=p.default.basename(e,n).replace(/[^a-zA-Z0-9._-]/g,`-`),i=(0,h.createHash)(`sha256`).update(e).update(String(t.mtimeMs)).update(String(t.size)).digest(`hex`).slice(0,16),a=p.default.posix.join(`mcp-render-assets`,`${r}-${i}${n}`),o=p.default.join(this.publicDir,...a.split(`/`));return await(0,d.mkdir)(p.default.dirname(o),{recursive:!0}),await(0,d.copyFile)(e,o),a}async prepareValue(e){if(Array.isArray(e)){let t=!1;return{value:await Promise.all(e.map(async e=>{let n=await this.prepareValue(e);return t||=n.usesLocalAssets,n.value})),usesLocalAssets:t}}if(!e||typeof e!=`object`)return{value:e,usesLocalAssets:!1};let t=!1,n={};for(let[r,i]of Object.entries(e)){if(r===`src`&&typeof i==`string`){let e=this.toLocalFilesystemPath(i);if(e){n[r]=this.toPublicRelativePath(e)??await this.copyToPublic(e),t=!0;continue}}let e=await this.prepareValue(i);n[r]=e.value,t||=e.usesLocalAssets}return{value:n,usesLocalAssets:t}}async prepareInputProps(e){let t=await this.prepareValue(e);return{inputProps:t.value,usesLocalAssets:t.usesLocalAssets}}normalizeProvidedValues(e,t){if(Array.isArray(e)&&Array.isArray(t))return e.map((e,n)=>this.normalizeProvidedValues(e,t[n]));if(e&&t&&typeof e==`object`&&typeof t==`object`&&!Array.isArray(e)&&!Array.isArray(t)){let n={},r=t;for(let[t,i]of Object.entries(e))n[t]=this.normalizeProvidedValues(i,r[t]);return n}return t}normalizeInputProps(e){let t=Me.parse(e);return this.normalizeProvidedValues(e,t)}async validateRender(e){let t=[],n=[],r=Ie[e.compositionId];r||t.push({code:`composition.unknown`,message:`Unknown composition: ${e.compositionId}`,path:`compositionId`});let i=await this.injectAudioDurations(e.inputProps),a=Me.safeParse(i);if(!a.success)return{passed:!1,errors:a.error.issues.map(e=>({code:`schema.invalid`,message:e.message,path:e.path.join(`.`)})),warnings:n,summary:{compositionId:e.compositionId,width:r?.width??0,height:r?.height??0,fps:0,durationInFrames:0,clipCount:0}};let o=a.data,s=r??{width:o.width,height:o.height},c=this.createSummary(e,o,s);return this.hasExplicitDimension(e.inputProps,`width`)&&o.width!==s.width&&t.push({code:`composition.width_mismatch`,message:`${e.compositionId} expects width ${s.width}, received ${o.width}.`,path:`width`}),this.hasExplicitDimension(e.inputProps,`height`)&&o.height!==s.height&&t.push({code:`composition.height_mismatch`,message:`${e.compositionId} expects height ${s.height}, received ${o.height}.`,path:`height`}),this.validateTimeline(o,t,n),this.validateCaptions(o,c,t,n),await this.validateClipAssets(o,n,t),await this.validateAudioLayer(o.audio,n,t),{passed:t.length===0,errors:t,warnings:n,summary:c}}async render(e){let t=await this.validateRender(e);if(!t.passed)throw new Ve(t);if(e.dryRun)return{outputPath:e.outputPath,dryRun:!0,validation:t};let n=await this.injectAudioDurations(e.inputProps),r=this.normalizeInputProps(n),i=await this.prepareInputProps(r);return i.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await(0,v.renderMedia)({composition:await(0,v.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:i.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:i.inputProps})}),{outputPath:e.outputPath,validation:t}}async renderStill(e){let t=await this.injectAudioDurations(e.inputProps),n=this.normalizeInputProps(t),r=await this.prepareInputProps(n);return r.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await(0,v.renderStill)({composition:await(0,v.selectComposition)({serveUrl:t,id:e.compositionId,inputProps:r.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:r.inputProps})}),{outputPath:e.outputPath}}};M=x([(0,c.injectable)()],M);const He=(0,m.promisify)(l.execFile),Ue=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let N=class{async isAvailable(){try{return await He(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`kokoro-`)),o=p.default.join(a,`input.txt`);try{return await d.default.writeFile(o,t,`utf-8`),await He(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await d.default.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...Ue]}};N=x([(0,c.injectable)()],N);const P={AceStepMusicService:Symbol.for(`AceStepMusicService`),RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),VideoDownloadService:Symbol.for(`VideoDownloadService`),VideoAnalysisService:Symbol.for(`VideoAnalysisService`),VideoContextService:Symbol.for(`VideoContextService`),ProxyUrl:Symbol.for(`ProxyUrl`),Tool:Symbol.for(`Tool`)};function F(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function I(e,t){return function(n,r){t(n,r,e)}}const L=(0,m.promisify)(l.execFile);let R=class{constructor(e=null){this.proxyUrl=e}getProxyArgs(){let e=this.proxyUrl||process.env.YTDLP_PROXY;return e?[`--proxy`,e]:[]}async ensureYtDlp(){try{let{stdout:e}=await L(`which`,[`yt-dlp`]);return e.trim()}catch{throw Error(`yt-dlp is not installed. Install it with: brew install yt-dlp`)}}async search(e,t){let n=await this.ensureYtDlp(),r=`ytsearch${t}:${e}`,{stdout:i}=await L(n,[...this.getProxyArgs(),r,`--flat-playlist`,`--print`,`%(id)s %(title)s %(duration)s %(url)s %(channel)s %(view_count)s %(upload_date)s`,`--no-warnings`],{timeout:3e4});return i.trim().split(`
5
- `).filter(Boolean).map(e=>{let[t,n,r,i,a,o,s]=e.split(` `);return{id:t??``,title:n??``,duration:Number.parseFloat(r??`0`)||0,url:i||`https://www.youtube.com/watch?v=${t}`,channel:a??``,viewCount:Number.parseInt(o??`0`,10)||0,uploadDate:s??``}})}async downloadClip(e,t,n,r,i,a=1080){let o=await this.ensureYtDlp();(0,u.existsSync)(t)||(0,u.mkdirSync)(t,{recursive:!0});let s=p.default.join(t,`${n}.mp4`),c=[...this.getProxyArgs(),`-f`,`bestvideo[height<=${a}][ext=mp4]+bestaudio[ext=m4a]/best[height<=${a}][ext=mp4]/best`,`--merge-output-format`,`mp4`,`-o`,s,`--no-warnings`,`--no-playlist`];if(r&&i?c.push(`--download-sections`,`*${r}-${i}`):r?c.push(`--download-sections`,`*${r}-inf`):i&&c.push(`--download-sections`,`*0:00-${i}`),c.push(e),await L(o,c,{timeout:12e4}),!(0,u.existsSync)(s))throw Error(`Download failed: output file not found at ${s}`);let{width:l,height:d,duration:f}=await this.getMediaInfo(s);return{outputPath:s,publicPath:p.default.relative(t,s),duration:f,width:l,height:d}}async getMediaInfo(e){let{stdout:t}=await L(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`);return{width:Number(r?.width??0),height:Number(r?.height??0),duration:i}}};R=x([(0,c.injectable)(),I(0,(0,c.inject)(P.ProxyUrl)),I(0,(0,c.optional)()),F(`design:paramtypes`,[Object])],R);const We=(0,m.promisify)(l.execFile),Ge=1280,Ke=p.default.join(f.default.homedir(),`.gemini`,`oauth_creds.json`);let z=class{async analyzeVideos(e){let t=e.model||`gemini-2.5-flash-lite`,n=e.videoPaths.map(e=>p.default.resolve(e));if(n.length===0)throw Error(`At least one video path is required.`);let r=this.resolveWorkspaceRoot(process.cwd()),i=h.default.randomUUID().toLowerCase(),a=h.default.randomUUID().toLowerCase(),o=await d.default.mkdtemp(p.default.join(f.default.tmpdir(),`video-editor-mcp-video.`));try{let s=await this.readAccessToken(),c=process.env.CODE_ASSIST_ENDPOINT||`https://cloudcode-pa.googleapis.com`,l=process.env.CODE_ASSIST_API_VERSION||`v1internal`,{resolvedProject:d,projectFromEnv:f}=await this.resolveProject(s,c,l),p=new AbortController,m=setTimeout(()=>p.abort(),6e5);try{let m=await this.prepareVideos(n,o),h={model:t,project:d,user_prompt_id:a,request:{contents:[{role:`user`,parts:[{text:this.buildSessionContext(r)},{text:e.prompt},...m.map(e=>({inlineData:{mimeType:e.mimeType,data:u.default.readFileSync(e.transcodedPath).toString(`base64`)}}))]}],session_id:i}},g=await fetch(`${c}/${l}:generateContent`,{method:`POST`,signal:p.signal,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${s}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`,...f?{"X-Goog-User-Project":f}:{}},body:JSON.stringify(h)}),_=await g.text(),v=this.safeJsonParse(_);if(!g.ok)throw Error(`generateContent failed with ${g.status}: ${_}`);let y=v,b=this.extractResponseText(v);return{resolvedProject:d,model:t,prompt:e.prompt,videoCount:m.length,videos:m,responseText:b,rawResponse:v??_,traceId:this.getString(v,`traceId`),responseId:this.getString(y?.response,`responseId`)}}finally{clearTimeout(m)}}finally{await d.default.rm(o,{recursive:!0,force:!0})}}resolveWorkspaceRoot(e=process.cwd()){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=p.default.resolve(e);for(;;){for(let e of t)if((0,u.existsSync)(p.default.join(n,e)))return n;let r=p.default.dirname(n);if(r===n)return p.default.resolve(e);n=r}}async readAccessToken(e=process.env.GEMINI_OAUTH_CREDS_FILE||Ke){if(!u.default.existsSync(e))throw Error(`OAuth credentials file not found: ${e}`);let t=await d.default.readFile(e,`utf8`),n=JSON.parse(t),r=this.parseToken(n);if(r.refreshToken){let e=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:r.refreshToken})});if(!e.ok){let t=await e.text();throw Error(`Failed to refresh OAuth token: ${e.status} ${t}`)}let t=await e.json();if(typeof t.access_token==`string`&&t.access_token)return t.access_token;throw Error(`OAuth refresh response did not include access_token.`)}if(r.accessToken)return r.accessToken;throw Error(`No access token found in ${e}.`)}parseToken(e){if(!e||typeof e!=`object`)return{};let t=e;return t.token&&typeof t.token==`object`?{accessToken:typeof t.token.accessToken==`string`?t.token.accessToken:void 0,refreshToken:typeof t.token.refreshToken==`string`?t.token.refreshToken:void 0}:{accessToken:typeof t.access_token==`string`?t.access_token:void 0,refreshToken:typeof t.refresh_token==`string`?t.refresh_token:void 0}}async resolveProject(e,t,n){let r=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,i=await fetch(`${t}/${n}:loadCodeAssist`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${e}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`},body:JSON.stringify({metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,...r?{duetProject:r}:{}},...r?{cloudaicompanionProject:r}:{}})}),a=await i.text();if(!i.ok)throw Error(`loadCodeAssist failed with ${i.status}: ${a}`);return{resolvedProject:this.safeJsonParse(a)?.cloudaicompanionProject||r||``,projectFromEnv:r}}async prepareVideos(e,t){let n=[];for(let[r,i]of e.entries()){if(!u.default.existsSync(i))throw Error(`Video file not found: ${i}`);let e=(await d.default.stat(i)).size,a=p.default.join(t,`${p.default.basename(i,p.default.extname(i))}-${r}.mp4`);await We(`ffmpeg`,[`-y`,`-i`,i,`-map`,`0:v:0`,`-map`,`0:a?`,`-vf`,`scale='if(gt(iw,${Ge}),${Ge},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-c:a`,`aac`,`-b:a`,`128k`,`-movflags`,`+faststart`,a],{timeout:600*1e3});let o=(await d.default.stat(a)).size;n.push({sourcePath:i,transcodedPath:a,originalSizeBytes:e,transcodedSizeBytes:o,mimeType:`video/mp4`})}return n}buildSessionContext(e){let t=new Date().toLocaleDateString(void 0,{weekday:`long`,year:`numeric`,month:`long`,day:`numeric`}),n=p.default.join(f.default.homedir(),`.gemini`,`tmp`,p.default.basename(e)),r=this.getWorkspaceTree(e);return`
6
- <session_context>
7
- This is the Gemini CLI. We are setting up the context for our chat.
8
- Today's date is ${t} (formatted according to the user's locale).
9
- My operating system is: ${process.platform}
10
- The project's temporary directory is: ${n}
11
- - **Workspace Directories:**
12
- - ${e}
13
- - **Directory Structure:**
14
-
15
- ${r}
16
- </session_context>`.trim()}getWorkspaceTree(e,t=3,n=200){let r=[`${e}/`],i=0,a=(e,o,s)=>{if(i>=n||s>t)return;let c;try{c=u.default.readdirSync(e,{withFileTypes:!0}).filter(e=>![`.git`,`node_modules`,`.nx-cache`].includes(e.name)).sort((e,t)=>e.name.localeCompare(t.name))}catch{return}for(let l=0;l<c.length&&i<n;l+=1){let n=c[l],u=l===c.length-1,d=u?`└───`:`├───`,f=`${o}${u?` `:`│ `}`,m=p.default.join(e,n.name);if(i+=1,n.isDirectory()){r.push(`${o}${d}${n.name}/`),s<t&&a(m,f,s+1);continue}r.push(`${o}${d}${n.name}`)}};return a(e,``,0),i>=n&&r.push(`...`),r.join(`
17
- `)}extractResponseText(e){return(e?.response?.candidates?.[0]?.content?.parts??[]).map(e=>e.text??``).filter(Boolean).join(``)}safeJsonParse(e){try{return JSON.parse(e)}catch{return}}getString(e,t){if(!e||typeof e!=`object`)return;let n=e;return typeof n[t]==`string`?n[t]:void 0}};z=x([(0,c.injectable)()],z);const B=(0,m.promisify)(l.execFile);let V=class{constructor(e=new C){this.captionService=e}async extractVideoContext(e){let t=p.default.resolve(e.videoPath);if(!(0,u.existsSync)(t))throw Error(`Video file not found: ${t}`);let n=e.basename??p.default.basename(t,p.default.extname(t)),r=p.default.resolve(e.outputDir??this.defaultOutputDir(t,n)),i=p.default.join(r,`work`),a=p.default.join(r,`manifest.json`);await d.default.mkdir(r,{recursive:!0}),await this.probeVideo(t);let o=e.transcribe===!1?{text:``,segments:[]}:await this.transcribeVideo(t,r,e),s=o.segments.length>0?await this.createProxyVideo(t,i,e):void 0,c={transcript:{...o,segments:s?await this.attachFilmstripsToSegments(s,r,o.segments,e):[]},next_actions:[`Review segments filmstrips for visual inspection`]};return await d.default.writeFile(a,`${JSON.stringify(c,null,2)}\n`,`utf8`),c}async probeVideo(e){let{stdout:t}=await B(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`),a=Number(r?.width??0),o=Number(r?.height??0);if(!i||Number.isNaN(i))throw Error(`Invalid duration for video: ${e}`);if(!a||!o)throw Error(`No video stream found in: ${e}`);return{durationSeconds:i,width:a,height:o}}defaultOutputDir(e,t){let n=this.resolveWorkspaceRoot(p.default.dirname(e)),r=n?p.default.join(n,`.tmp`,`video-editor-mcp`):f.default.tmpdir();return p.default.join(r,`${t}-context`)}resolveWorkspaceRoot(e){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=p.default.resolve(e);for(;;){if(t.some(e=>(0,u.existsSync)(p.default.join(n,e))))return n;let e=p.default.dirname(n);if(e===n)return;n=e}}async createProxyVideo(e,t,n){let r=p.default.join(t,`proxy.mp4`),i=Math.max(n.tileWidth??250,500);return await d.default.mkdir(t,{recursive:!0}),await B(`ffmpeg`,[`-y`,`-i`,e,`-vf`,`scale='min(${i},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-an`,r]),r}async transcribeVideo(e,t,n){let r=await this.resolveWhisperCommand(),i=p.default.join(t,`audio.wav`);await B(`ffmpeg`,[`-y`,`-i`,e,`-vn`,`-ac`,`1`,`-ar`,`16000`,i]),r.engine===`whisper`?await this.runPythonWhisper(r.binary,i,t,n):await this.runWhisperCpp(r.binary,i,t,n);let a=this.firstExistingPath([p.default.join(t,`audio.txt`),p.default.join(t,`transcript.txt`),`${i}.txt`]),o=this.firstExistingPath([p.default.join(t,`audio.srt`),p.default.join(t,`transcript.srt`),`${i}.srt`]),s=a?(await d.default.readFile(a,`utf8`)).trim():``,c=o?this.captionService.parseSrt(await d.default.readFile(o,`utf8`)):[];return{engine:r.engine,text:s,txtPath:a,srtPath:o,segments:c}}async resolveWhisperCommand(){for(let e of[`whisper-cli`,`whisper.cpp`,`whisper`]){let t=await this.findBinary(e);if(t)return{binary:t,engine:e}}throw Error(`No supported Whisper binary found. Install whisper.cpp or OpenAI Whisper, then retry.`)}async findBinary(e){try{let{stdout:t}=await B(`which`,[e]);return t.trim()||void 0}catch{return}}async runPythonWhisper(e,t,n,r){let i=[t,`--model`,r.whisperModel??`base`,`--output_dir`,n,`--output_format`,`all`];r.language&&i.push(`--language`,r.language),await B(e,i,{timeout:1200*1e3})}async runWhisperCpp(e,t,n,r){let i=r.whisperModelPath??process.env.WHISPER_MODEL_PATH??this.resolveDefaultWhisperModelPath();if(!i||!(0,u.existsSync)(i))throw Error(`Whisper.cpp model not found. Provide whisperModelPath or set WHISPER_MODEL_PATH.`);let a=[`-m`,i,`-f`,t,`-otxt`,`-osrt`,`-of`,p.default.join(n,`transcript`)];r.language&&a.push(`-l`,r.language),await B(e,a,{timeout:1200*1e3})}resolveDefaultWhisperModelPath(){return[p.default.join(f.default.homedir(),`.whisper`,`ggml-base.en.bin`),p.default.join(f.default.homedir(),`.whisper`,`ggml-small.en.bin`),`/opt/homebrew/share/whisper-cpp/ggml-base.en.bin`,`/usr/local/share/whisper-cpp/ggml-base.en.bin`].find(e=>(0,u.existsSync)(e))}async attachFilmstripsToSegments(e,t,n,r){let i=p.default.join(t,`slides`);await d.default.mkdir(i,{recursive:!0});let a=[];for(let[t,o]of n.entries()){let n=await this.extractSegmentFilmstrips(e,i,t,o,r);a.push({...o,text:o.text.trim(),filmstrips:n})}return a}async extractSegmentFilmstrips(e,t,n,r,i){let a=(i.imageFormat??`jpeg`)===`png`?`png`:`jpg`,o=i.framesPerSecond??3,s=i.tileWidth??250,c=i.filmstripColumns??6,l=c*(i.filmstripRows??4),u=Math.max((r.endMs-r.startMs)/1e3,1/o),d=Math.max(1,Math.ceil(u*o)),f=[];for(let i=0,u=0;i<d;i+=l,u+=1){let m=Math.min(l,d-i),h=Math.min(c,m),g=Math.ceil(m/h),_=r.startMs+Math.round(i/o*1e3),v=Math.min(m/o,Math.max((r.endMs-_)/1e3,1/o)),y=p.default.join(t,`segment_${String(n+1).padStart(4,`0`)}_filmstrip_${String(u+1).padStart(3,`0`)}.${a}`);await B(`ffmpeg`,[`-y`,`-ss`,(_/1e3).toFixed(3),`-t`,v.toFixed(3),`-i`,e,`-vf`,[`fps=${o}`,`scale='min(${s},iw)':-2`,`pad=iw:ih+32:0:0:white`,`tile=${h}x${g}:padding=8:margin=8:color=white`].join(`,`),`-frames:v`,`1`,y]),await this.annotateFilmstrip(y,_,o,m,h,g),f.push({path:y,index:u,startMs:_,endMs:Math.min(r.endMs,_+Math.round(v*1e3)),frameCount:m,tileWidth:s,columns:h,rows:g})}return f}async annotateFilmstrip(e,t,n,r,i,a){let o=await(0,y.default)(e).metadata();if(!o.width||!o.height)throw Error(`Unable to annotate filmstrip without image dimensions: ${e}`);let s=(o.width-16-8*(i-1))/i,c=(o.height-16-8*(a-1))/a,l=Array.from({length:r},(e,r)=>{let a=r%i,o=Math.floor(r/i),l=t+Math.round(r/n*1e3);return`<text x="${8+a*(s+8)+s/2}" y="${8+o*(c+8)+c-32+22}" text-anchor="middle">${l} ms</text>`}).join(``),u=Buffer.from(`<svg width="${o.width}" height="${o.height}" xmlns="http://www.w3.org/2000/svg"><style>text{font-family:Arial,sans-serif;font-size:18px;fill:#000}</style>${l}</svg>`),f=await(0,y.default)(e).composite([{input:u,top:0,left:0}]).toBuffer();await d.default.writeFile(e,f)}firstExistingPath(e){return e.find(e=>(0,u.existsSync)(e))}};V=x([(0,c.injectable)(),F(`design:paramtypes`,[Object])],V);let H=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};H=x([(0,c.injectable)()],H);var qe,Je;const Ye=b.z.object({url:b.z.string().describe(`Video URL from YouTube, TikTok, Instagram, or other yt-dlp supported sites`),filename:b.z.string().describe(`Output filename without extension (e.g., "my_clip")`),startTime:b.z.string().optional().describe(`Start time for clip extraction (e.g., "0:30", "1:15")`),endTime:b.z.string().optional().describe(`End time for clip extraction (e.g., "1:00", "2:30")`),maxHeight:b.z.number().int().positive().default(1080).describe(`Maximum video height in pixels (default: 1080)`),outputDir:b.z.string().optional().describe(`Custom output directory (default: Remotion public/videos/ for direct use in compositions)`)}),Xe=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let Ze=class extends H{static{Je=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ye}getDefinition(){return{name:Je.TOOL_NAME,description:`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming. Saves to Remotion public/videos/ by default for direct use in compositions.`,inputSchema:b.z.toJSONSchema(Ye,{reused:`inline`})}}async execute(e){try{let t=Ye.parse(e),n=p.default.resolve(Xe(),`public/videos`),r=t.outputDir??n,i=await this.videoDownloadService.downloadClip(t.url,r,t.filename,t.startTime,t.endTime,t.maxHeight),a=t.outputDir?void 0:`/videos/${t.filename}.mp4`;return this.successJson({...i,compositionSrc:a,suggestedComposition:i.height>i.width?`Vertical`:`Main`})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Ze=Je=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(qe=R!==void 0&&R)==`function`?qe:Object])],Ze);var Qe,$e;const U=b.z.object({videoPath:b.z.string().min(1).describe(`Path to the local video file to extract context from`),outputDir:b.z.string().optional().describe(`Directory to write transcript, filmstrips, and manifest`),basename:b.z.string().optional().describe(`Base name for the generated context directory when outputDir is omitted`),framesPerSecond:b.z.number().positive().default(3).describe(`Frames per second to sample for raw frames and segment slides`),imageFormat:b.z.enum([`jpeg`,`png`]).default(`jpeg`).describe(`Frame image format`),tileWidth:b.z.number().int().positive().default(250).describe(`Width of each filmstrip tile in pixels`),filmstripColumns:b.z.number().int().positive().default(6).describe(`Maximum columns per segment filmstrip image`),filmstripRows:b.z.number().int().positive().default(4).describe(`Maximum rows per segment filmstrip image`),transcribe:b.z.boolean().default(!0).describe(`Run local Whisper transcription`),language:b.z.string().optional().describe(`Optional Whisper language hint`),whisperModel:b.z.string().optional().describe(`Python Whisper model name, default base`),whisperModelPath:b.z.string().optional().describe(`Whisper.cpp model path override`)});let W=class extends H{static{$e=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return U}getDefinition(){return{name:$e.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.z.toJSONSchema(U,{reused:`inline`})}}async execute(e){try{let t=U.parse(e),n=await this.videoContextService.extractVideoContext(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=$e=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoContextService)),F(`design:paramtypes`,[typeof(Qe=V!==void 0&&V)==`function`?Qe:Object])],W);var et,tt;const nt=b.z.object({captions:b.z.array(b.z.object({text:b.z.string(),startMs:b.z.number(),endMs:b.z.number(),confidence:b.z.number().min(0).max(1).optional(),emphasis:b.z.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()})).describe(`Array of caption objects with text, startMs, and endMs`),combineMs:b.z.number().optional().describe(`Milliseconds threshold for combining captions into pages (default: 100)`),maxWordsPerPage:b.z.number().int().positive().optional().describe(`Maximum words per caption page (default: 6)`),maxCharsPerLine:b.z.number().int().positive().optional().describe(`Target maximum characters per caption line (default: 22)`),platform:b.z.enum([`tiktok`,`reels`,`shorts`,`universal`]).optional().describe(`Platform safe-zone preset`),preset:b.z.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).optional().describe(`Caption visual style preset`)});let G=class extends H{static{tt=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return nt}getDefinition(){return{name:tt.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.z.toJSONSchema(nt,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=nt.parse(e),s=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs,confidence:e.confidence,emphasis:e.emphasis})),c=this.captionService.createTikTokCaptions(s,{combineMs:n,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o});return this.successJson(c)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};G=tt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(et=C!==void 0&&C)==`function`?et:Object])],G);var rt,it;const at=b.z.object({prompt:b.z.string().describe(`Music description for ACE-Step text-to-music generation`),filename:b.z.string().optional().describe(`Output filename without extension (default: ace-step-music)`),outputPath:b.z.string().optional().describe(`Explicit output audio path. Overrides filename/outputDir.`),outputDir:b.z.string().optional().describe(`Custom output directory (default: Remotion public/music/ for direct use in compositions)`),aceStepPath:b.z.string().optional().describe(`Path to local ACE-Step-1.5 checkout (default: ACE_STEP_15_DIR or ~/workspace/ACE-Step-1.5)`),lyrics:b.z.string().optional().describe(`Lyrics to use. Defaults to [Instrumental] for background music.`),duration:b.z.number().min(10).max(600).optional().describe(`Target duration in seconds (default: 30)`),bpm:b.z.number().min(30).max(300).optional().describe(`Optional BPM metadata`),keyscale:b.z.string().optional().describe(`Optional key/scale metadata, e.g. C minor`),timeSignature:b.z.string().optional().describe(`Optional time signature metadata, e.g. 4/4`),vocalLanguage:b.z.string().optional().describe(`Vocal language code, or unknown (default: unknown)`),seed:b.z.number().int().optional().describe(`Optional fixed seed for reproducible generation`),inferenceSteps:b.z.number().int().positive().optional().describe(`Diffusion inference steps (default: 8)`),audioFormat:b.z.enum([`wav`,`mp3`,`flac`]).default(`wav`).describe(`Output audio format (default: wav)`),backend:b.z.enum([`mlx`,`pt`,`vllm`]).default(`mlx`).describe(`ACE-Step LM backend (default: mlx for Apple Silicon)`),configPath:b.z.string().optional().describe(`ACE-Step DiT model config (default: acestep-v15-turbo)`),lmModelPath:b.z.string().optional().describe(`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`),timeoutMs:b.z.number().int().positive().optional().describe(`Generation timeout in milliseconds`)}),ot=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let st=class extends H{static{it=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return at}getDefinition(){return{name:it.TOOL_NAME,description:`Generate background music using a local ACE-Step 1.5 checkout. Defaults to MLX on Apple Silicon and saves to Remotion public/music/.`,inputSchema:b.z.toJSONSchema(at,{reused:`inline`})}}async execute(e){try{let t=at.parse(e);if(!await this.aceStepMusicService.isAvailable(t.aceStepPath))return this.error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass aceStepPath.`);let n=this.resolveOutputPath(t),r=await this.aceStepMusicService.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),i=p.default.resolve(ot(),`public/music`),a=p.default.dirname(n)===i?`/music/${p.default.basename(n)}`:void 0;return this.successJson({...r,compositionSrc:a,suggestedClip:a?{type:`audio`,src:a,startFrame:0,volume:.35}:void 0})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}resolveOutputPath(e){if(e.outputPath)return p.default.resolve(e.outputPath);let t=p.default.resolve(e.outputDir??p.default.join(ot(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return p.default.join(t,`${n}.${r}`)}};st=it=x([(0,c.injectable)(),I(0,(0,c.inject)(P.AceStepMusicService)),F(`design:paramtypes`,[typeof(rt=S!==void 0&&S)==`function`?rt:Object])],st);var ct,lt;const ut=b.z.object({text:b.z.string().describe(`Text to convert to speech`),voice:b.z.string().optional().describe(`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`),speed:b.z.number().optional().describe(`Speech speed multiplier (default: 1.0)`),outputPath:b.z.string().describe(`Output file path for the generated audio`)});let K=class extends H{static{lt=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return ut}getDefinition(){return{name:lt.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.z.toJSONSchema(ut,{reused:`inline`})}}async execute(e){try{let t=ut.parse(e);if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let n=await this.voiceoverService.generateVoiceover({text:t.text,voice:t.voice,speed:t.speed,outputPath:t.outputPath});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};K=lt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VoiceoverService)),F(`design:paramtypes`,[typeof(ct=N!==void 0&&N)==`function`?ct:Object])],K);var dt,ft;const pt=b.z.object({src:b.z.string().describe(`Path to the media file`),type:b.z.enum([`video`,`audio`]).optional().describe(`Media type (default: video)`)});let q=class extends H{static{ft=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return pt}getDefinition(){return{name:ft.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.z.toJSONSchema(pt,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=pt.parse(e),r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=ft=x([(0,c.injectable)(),I(0,(0,c.inject)(P.MediaInfoService)),F(`design:paramtypes`,[typeof(dt=T!==void 0&&T)==`function`?dt:Object])],q);var mt,ht;const gt=b.z.object({srtContent:b.z.string().describe(`SRT file content as a string`)});let J=class extends H{static{ht=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return gt}getDefinition(){return{name:ht.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.z.toJSONSchema(gt,{reused:`inline`})}}async execute(e){try{let t=gt.parse(e),n=this.captionService.parseSrt(t.srtContent);return this.successJson({captionCount:n.length,captions:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};J=ht=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(mt=C!==void 0&&C)==`function`?mt:Object])],J);var _t;const vt=b.z.object({});let Y=class extends H{static{_t=this}static TOOL_NAME=`list_compositions`;getInputSchema(){return vt}getDefinition(){return{name:_t.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.z.toJSONSchema(vt,{reused:`inline`})}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};Y=_t=x([(0,c.injectable)()],Y);var yt,bt;const X=b.z.object({compositionId:b.z.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:b.z.record(b.z.string(),b.z.unknown()).describe(`JSON props to pass to the composition`),outputPath:b.z.string().describe(`Output file path for the rendered image`),frame:b.z.number().optional().describe(`Frame number to render (default: 0)`),imageFormat:b.z.enum([`png`,`jpeg`]).optional().describe(`Image format (default: png)`)});let Z=class extends H{static{bt=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return X}getDefinition(){return{name:bt.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:b.z.toJSONSchema(X,{reused:`inline`})}}async execute(e){try{let t=X.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,frame:t.frame,imageFormat:t.imageFormat},r=await this.renderService.renderStill(n);return this.successJson(r)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Z=bt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(yt=M!==void 0&&M)==`function`?yt:Object])],Z);var xt,St;const Ct=b.z.preprocess(e=>{if(typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},b.z.record(b.z.string(),b.z.unknown())),wt=b.z.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.z.boolean()),Tt=b.z.object({compositionId:b.z.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:Ct.describe(`JSON props to pass to the composition`),outputPath:b.z.string().describe(`Output file path for the rendered video`),codec:b.z.enum([`h264`,`h265`,`vp8`,`vp9`]).optional().describe(`Video codec (default: h264)`),dryRun:wt.optional().describe(`Validate props and assets without rendering a video`)});let Q=class extends H{static{St=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return Tt}getDefinition(){return{name:St.TOOL_NAME,description:"Render a video from JSON props using Remotion. `inputProps` must conform to the Main composition schema. Full JSON Schema (generated from `src/schemas/compositions.ts` -> `MainCompositionSchema`): `schemas/main-composition.schema.json` (relative to this package; in the monorepo: `packages/mcp/video-editor-mcp/schemas/main-composition.schema.json`). Validation runs server-side; failures return a `render_validation_failed` error with per-issue paths and suggested fixes.",inputSchema:b.z.toJSONSchema(Tt,{reused:`inline`})}}async execute(e){try{let t=Tt.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,codec:t.codec,dryRun:t.dryRun},r=await this.renderService.render(n);return this.successJson(r)}catch(e){return e instanceof Ve?{content:[{type:`text`,text:JSON.stringify({error:`render_validation_failed`,message:e.message,summary:e.validation.summary,issues:e.validation.errors,warnings:e.validation.warnings,suggestedFixes:e.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}],isError:!0}:this.error(e instanceof Error?e.message:`Unknown error`)}}};Q=St=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(xt=M!==void 0&&M)==`function`?xt:Object])],Q);var Et,Dt;const Ot=b.z.object({query:b.z.string().describe(`Search query for YouTube videos`),maxResults:b.z.number().int().positive().max(20).default(5).describe(`Maximum number of results (default: 5, max: 20)`)});let kt=class extends H{static{Dt=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ot}getDefinition(){return{name:Dt.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.z.toJSONSchema(Ot,{reused:`inline`})}}async execute(e){try{let t=Ot.parse(e),n=await this.videoDownloadService.search(t.query,t.maxResults);return this.successJson({query:t.query,resultCount:n.length,results:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};kt=Dt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(Et=R!==void 0&&R)==`function`?Et:Object])],kt);var At,jt;const Mt=b.z.object({videoPaths:b.z.array(b.z.string().min(1)).min(1).describe(`Paths to local video files to analyze`),prompt:b.z.string().min(1).describe(`Prompt to send with the video(s)`),model:b.z.string().default(`gemini-2.5-flash-lite`).describe(`Gemini model to use (default: gemini-2.5-flash-lite)`)});let $=class extends H{static{jt=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Mt}getDefinition(){return{name:jt.TOOL_NAME,description:`Analyze one or more local video files by transcoding them to a compact MP4 and sending them with a prompt to Gemini Code Assist.`,inputSchema:b.z.toJSONSchema(Mt,{reused:`inline`})}}async execute(e){try{let t=Mt.parse(e),n=await this.videoAnalysisService.analyzeVideos({videoPaths:t.videoPaths,prompt:t.prompt,model:t.model});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};$=jt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoAnalysisService)),F(`design:paramtypes`,[typeof(At=z!==void 0&&z)==`function`?At:Object])],$);const Nt=new c.ContainerModule(e=>{e.bind(P.AceStepMusicService).to(S).inSingletonScope(),e.bind(P.RenderService).to(M).inSingletonScope(),e.bind(P.MediaInfoService).to(T).inSingletonScope(),e.bind(P.CaptionService).to(C).inSingletonScope(),e.bind(P.FontService).to(se).inSingletonScope(),e.bind(P.VoiceoverService).to(N).inSingletonScope(),e.bind(P.VideoDownloadService).to(R).inSingletonScope(),e.bind(P.VideoAnalysisService).to(z).inSingletonScope(),e.bind(P.VideoContextService).to(V).inSingletonScope()}),Pt=new c.ContainerModule(e=>{e.bind(P.Tool).to(Q).inSingletonScope(),e.bind(P.Tool).to(Y).inSingletonScope(),e.bind(P.Tool).to(q).inSingletonScope(),e.bind(P.Tool).to(Z).inSingletonScope(),e.bind(P.Tool).to(G).inSingletonScope(),e.bind(P.Tool).to(J).inSingletonScope(),e.bind(P.Tool).to(st).inSingletonScope(),e.bind(P.Tool).to(K).inSingletonScope(),e.bind(P.Tool).to(kt).inSingletonScope(),e.bind(P.Tool).to(Ze).inSingletonScope(),e.bind(P.Tool).to($).inSingletonScope(),e.bind(P.Tool).to(W).inSingletonScope()});function Ft(e={}){let t=new c.Container;return t.load(Nt,Pt),t.bind(P.ProxyUrl).toConstantValue(e.proxyUrl??null),t}const It=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function Lt(e){return It.has(e)}async function Rt(e,t,n,r){return Lt(e)?r.run(()=>t.execute(n)):t.execute(n)}function zt(e){let t=new te.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(P.Tool),r=new ce,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(ne.ListToolsRequestSchema,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(ne.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params,a=i.get(t);if(!a)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let o=(0,ee.coerceArgs)(n??{},a.getInputSchema());try{return await Rt(t,a,o,r)}catch(e){if(e instanceof b.z.ZodError)return{content:[{type:`text`,text:(0,ee.formatZodError)(e,{schemaName:t,schema:a.getInputSchema()})}],isError:!0};throw e}}),t}var Bt=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new re.StdioServerTransport,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`A`,{enumerable:!0,get:function(){return ue}}),Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return me}}),Object.defineProperty(exports,`D`,{enumerable:!0,get:function(){return _e}}),Object.defineProperty(exports,`E`,{enumerable:!0,get:function(){return fe}}),Object.defineProperty(exports,`M`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`O`,{enumerable:!0,get:function(){return he}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return ge}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return Ve}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Q}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return je}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return J}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return G}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`g`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`h`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return $}}),Object.defineProperty(exports,`j`,{enumerable:!0,get:function(){return de}}),Object.defineProperty(exports,`k`,{enumerable:!0,get:function(){return pe}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return q}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return V}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return zt}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return Z}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return Ft}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return Bt}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return Te}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return ve}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return Me}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return ye}});
@@ -1,17 +0,0 @@
1
- import"reflect-metadata";import{Container as e,ContainerModule as t,inject as n,injectable as r,optional as i}from"inversify";import{execFile as a}from"node:child_process";import o,{existsSync as s,mkdirSync as c}from"node:fs";import l,{copyFile as u,mkdir as d,readFile as f,stat as p}from"node:fs/promises";import m from"node:os";import h from"node:path";import{promisify as g}from"node:util";import _,{createHash as v}from"node:crypto";import{fileURLToPath as y}from"node:url";import{bundle as ee}from"@remotion/bundler";import{renderMedia as te,renderStill as ne,selectComposition as re}from"@remotion/renderer";import ie from"sharp";import{z as b}from"zod";import{coerceArgs as ae,formatZodError as oe}from"@agimon-ai/foundation-validator";import{Server as se}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as ce,ListToolsRequestSchema as le}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as ue}from"@modelcontextprotocol/sdk/server/stdio.js";function x(e,t,n,r){var i=arguments.length,a=i<3?t:r===null?r=Object.getOwnPropertyDescriptor(t,n):r,o;if(typeof Reflect==`object`&&typeof Reflect.decorate==`function`)a=Reflect.decorate(e,t,n,r);else for(var s=e.length-1;s>=0;s--)(o=e[s])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}const de=g(a);let S=class{async isAvailable(e){let t=this.resolveAceStepPath(e);if(!this.isAceStepCheckout(t))return!1;try{return await de(`uv`,[`--version`]),!0}catch{return!1}}async generateMusic(e){let t=this.resolveAceStepPath(e.aceStepPath);if(!this.isAceStepCheckout(t))throw Error(`ACE-Step 1.5 checkout not found at ${t}. Pass aceStepPath or set ACE_STEP_15_DIR.`);let n=e.audioFormat??this.getFormatFromPath(e.outputPath),r=e.backend??`mlx`,i=e.duration??30,a=await l.mkdtemp(h.join(m.tmpdir(),`ace-step-music-`)),o=h.join(a,`generation.toml`),s=h.join(a,`output`);await l.mkdir(s,{recursive:!0}),await l.mkdir(h.dirname(e.outputPath),{recursive:!0});try{let a=this.buildConfig({...e,aceStepPath:t,audioFormat:n,backend:r,duration:i,outputDir:s});await l.writeFile(o,a,`utf-8`);let{stdout:c,stderr:u}=await de(`uv`,[`run`,`python`,`cli.py`,`-c`,o,`--backend`,r,`--log-level`,`INFO`],{cwd:t,timeout:e.timeoutMs??1200*1e3,maxBuffer:50*1024*1024}),d=await this.findNewestGeneratedAudio(s,n);return await l.copyFile(d,e.outputPath),{outputPath:e.outputPath,sourcePath:d,duration:i,audioFormat:n,backend:r,aceStepPath:t,stdout:c,stderr:u}}catch(e){throw Error(`Failed to generate ACE-Step music: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{await l.rm(a,{recursive:!0,force:!0}).catch(()=>void 0)}}resolveAceStepPath(e){return h.resolve(e??process.env.ACE_STEP_15_DIR??process.env.ACESTEP_15_DIR??h.join(m.homedir(),`workspace`,`ACE-Step-1.5`))}isAceStepCheckout(e){return s(h.join(e,`cli.py`))&&s(h.join(e,`pyproject.toml`))}getFormatFromPath(e){let t=h.extname(e).toLowerCase().replace(`.`,``);return t===`mp3`||t===`flac`||t===`wav`?t:`wav`}async findNewestGeneratedAudio(e,t){let n=await l.readdir(e,{withFileTypes:!0}),r=await Promise.all(n.filter(e=>e.isFile()&&e.name.endsWith(`.${t}`)).map(async t=>{let n=h.join(e,t.name);return{filePath:n,mtimeMs:(await l.stat(n)).mtimeMs}}));if(r.sort((e,t)=>t.mtimeMs-e.mtimeMs),!r[0])throw Error(`ACE-Step completed but no .${t} file was found in ${e}`);return r[0].filePath}buildConfig(e){let t=e.lyrics?.trim()||`[Instrumental]`,n=t.toLowerCase()===`[instrumental]`,r={project_root:e.aceStepPath,checkpoint_dir:h.join(e.aceStepPath,`checkpoints`),config_path:e.configPath??`acestep-v15-turbo`,lm_model_path:e.lmModelPath??`acestep-5Hz-lm-0.6B`,backend:e.backend,device:`auto`,offload_to_cpu:!1,offload_dit_to_cpu:!1,save_dir:e.outputDir,audio_format:e.audioFormat,caption:e.prompt,lyrics:t,duration:e.duration,instrumental:n,bpm:e.bpm??null,keyscale:e.keyscale??``,timesignature:e.timeSignature??``,vocal_language:e.vocalLanguage??`unknown`,task_type:`text2music`,sample_mode:!1,sample_query:``,use_format:!1,inference_steps:e.inferenceSteps??8,seed:e.seed??-1,use_random_seed:e.seed===void 0,guidance_scale:7,use_adg:!1,shift:3,infer_method:`ode`,thinking:!0,lm_temperature:.85,lm_cfg_scale:2,lm_top_k:0,lm_top_p:.9,use_cot_metas:!0,use_cot_caption:!0,use_cot_lyrics:!1,use_cot_language:!0,use_constrained_decoding:!0,batch_size:1,allow_lm_batch:!1,lm_batch_chunk_size:8,constrained_decoding_debug:!1,cfg_interval_start:0,cfg_interval_end:1,lm_negative_prompt:`NO USER INPUT`,log_level:`INFO`};return Object.entries(r).filter(e=>e[1]!==null).map(([e,t])=>`${e} = ${this.toTomlValue(t)}`).join(`
2
- `)}toTomlValue(e){return typeof e==`string`?JSON.stringify(e):String(e)}};S=x([r()],S);const fe={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};let C=class{parseSrt(e){let t=[],n=e.split(`
3
- `),r=0;for(;r<n.length;){if(!n[r]?.trim()){r++;continue}r++;let e=n[r];if(!e?.includes(`-->`)){r++;continue}let[i,a]=e.split(`-->`).map(e=>e.trim());if(!i||!a){r++;continue}let o=this.parseTimestamp(i),s=this.parseTimestamp(a);r++;let c=[];for(;r<n.length&&n[r]?.trim();)c.push(n[r]??``),r++;let l=c.join(`
4
- `);l&&t.push({text:l,startMs:o,endMs:s})}return t}parseTimestamp(e){let[t,n]=e.split(`,`);if(!t||!n)throw Error(`Invalid timestamp format: ${e}`);let[r,i,a]=t.split(`:`).map(Number);if(r===void 0||i===void 0||a===void 0)throw Error(`Invalid time format: ${t}`);return(r*3600+i*60+a)*1e3+Number(n)}createTikTokCaptions(e,t=100){let n=typeof t==`number`?{combineMs:t}:t,r=n.combineMs??100,i=n.maxWordsPerPage??6,a=n.maxCharsPerLine??22,o=n.platform??`tiktok`,s=n.preset??`tiktok-bold`,c=this.createCaptionPages(e,{combineMs:r,maxWordsPerPage:i});return{pages:c,captionConfig:{captions:e,pages:c,style:s===`karaoke`?`karaoke`:`tiktok`,preset:s,platform:o,combineTokensWithinMilliseconds:r,maxWordsPerPage:i,maxCharsPerLine:a,maxLines:2,maxWidthPct:.86,fontSize:60,minFontSize:42,fontFamily:`Arial, sans-serif`,fontWeight:800,lineHeight:1.08,color:`#ffffff`,inactiveColor:`#ffffff`,highlightColor:`#FFD700`,textColor:`#ffffff`,strokeColor:`#000000`,strokeWidth:5,shadow:!0,backgroundColor:`rgba(0, 0, 0, 0)`,backgroundRadius:8,safeZone:fe[o],position:`bottom`}}}createCaptionPages(e,t={}){let n=[],r=this.normalizeCaptionsToTokens(e),i=t.combineMs??100,a=t.maxWordsPerPage??6;if(r.length===0)return n;let o={startMs:r[0]?.startMs??0,endMs:r[0]?.endMs??0,tokens:[r[0]]};for(let e=1;e<r.length;e++){let t=r[e];if(!t)continue;let s=t.startMs-o.endMs,c=this.countWords(o.tokens.map(e=>e.text).join(` `)),l=this.countWords(t.text);s<=i&&c+l<=a?(o.tokens.push(t),o.endMs=t.endMs):(o.text=this.pageText(o.tokens),n.push(o),o={startMs:t.startMs,endMs:t.endMs,tokens:[t]})}return o.tokens.length>0&&(o.text=this.pageText(o.tokens),n.push(o)),n}normalizeCaptionsToTokens(e){return[...e].sort((e,t)=>e.startMs-t.startMs).flatMap(e=>this.captionToTokens(e))}getPlatformSafeZone(e){return fe[e]}captionToTokens(e){let t=e.text.trim().split(/\s+/).filter(Boolean);if(t.length===0)return[];if(t.length===1)return[{...e,text:t[0]??e.text,emphasis:e.emphasis??`none`}];let n=Math.max(1,e.endMs-e.startMs)/t.length;return t.map((r,i)=>{let a=Math.round(e.startMs+n*i),o=i===t.length-1?e.endMs:Math.round(e.startMs+n*(i+1));return{text:r,startMs:a,endMs:Math.max(o,a+1),timestampMs:a,confidence:e.confidence,emphasis:e.emphasis??`none`}})}pageText(e){return e.map(e=>e.text).join(` `)}countWords(e){return e.trim().split(/\s+/).filter(Boolean).length}};C=x([r()],C);const pe=[`Inter`,`Roboto`,`Open Sans`,`Montserrat`,`Lato`,`Poppins`,`Oswald`,`Raleway`,`Playfair Display`,`Merriweather`,`Source Sans Pro`,`PT Sans`,`Ubuntu`,`Nunito`,`Rubik`,`Work Sans`,`Karla`,`Noto Sans`,`Fira Sans`,`DM Sans`];let me=class{getAvailableGoogleFonts(){return[...pe]}validateFontFamily(e){let t=e.toLowerCase();return pe.some(e=>e.toLowerCase()===t)}};me=x([r()],me);var he=class{tail=Promise.resolve();currentActiveCount=0;currentPendingCount=0;async run(e){let t=this.tail,n,r=new Promise(e=>{n=e});this.currentPendingCount+=1,this.tail=t.then(()=>r),await t,--this.currentPendingCount,this.currentActiveCount+=1;try{return await e()}finally{--this.currentActiveCount,n()}}getStatus(){return{activeCount:this.currentActiveCount,pendingCount:this.currentPendingCount}}};const w=g(a);let T=class{async getVideoDuration(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for video: ${e}`);return r}catch(t){throw Error(`Failed to get video duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getAudioDuration(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=Number.parseFloat(n.format?.duration??`0`);if(Number.isNaN(r)||r<=0)throw Error(`Invalid duration for audio: ${e}`);return r}catch(t){throw Error(`Failed to get audio duration for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async getVideoDimensions(e){try{let{stdout:t}=await w(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height`,`-of`,`json`,e]),n=JSON.parse(t).streams?.[0];if(!n?.width||!n?.height)throw Error(`No video stream found in: ${e}`);return{width:Number(n.width),height:Number(n.height)}}catch(t){throw Error(`Failed to get video dimensions for ${e}: ${t instanceof Error?t.message:String(t)}`,{cause:t})}}async canDecode(e){try{return await w(`ffprobe`,[`-v`,`error`,`-show_entries`,`format=duration`,`-of`,`json`,e]),!0}catch{return!1}}};T=x([r()],T);const E=b.coerce.number(),D=b.coerce.number().int(),O=b.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.boolean()),k=b.string().min(1).refine(e=>/^(\/|file:|https?:|data:|blob:)/i.test(e),{message:`src must be an absolute filesystem path or a file:// / http(s):// / data: / blob: URL — relative paths are not allowed.`}),ge=b.object({frame:D.nonnegative(),volume:E.min(0).max(1)}),_e=b.object({type:b.enum([`interpolate`,`spring`]).default(`interpolate`),easing:b.enum([`linear`,`ease-in`,`ease-out`,`ease-in-out`]).optional(),springConfig:b.object({mass:E.positive().default(1),damping:E.positive().default(10),stiffness:E.positive().default(100)}).optional(),delay:D.nonnegative().default(0)}),ve=b.object({type:b.enum([`fade`,`slide`,`wipe`,`flip`,`clockWipe`,`none`]).default(`fade`),durationInFrames:D.positive().default(15),direction:b.enum([`from-left`,`from-right`,`from-top`,`from-bottom`]).optional()}),A={startFrame:D.nonnegative(),durationInFrames:D.positive(),premountFor:D.nonnegative().default(30),entrance:_e.optional(),exit:_e.optional(),transition:ve.optional()},ye=b.object({type:b.literal(`video`),...A,src:k,volume:E.min(0).max(1).optional(),playbackRate:E.positive().optional(),muted:O.optional(),loop:O.optional(),trimBefore:E.nonnegative().optional(),trimAfter:E.nonnegative().optional(),transparent:O.optional(),noFadeIn:O.optional(),style:b.record(b.string(),b.unknown()).optional()}),be=b.object({type:b.literal(`image`),...A,src:k,fit:b.enum([`contain`,`cover`,`fill`]).optional(),noFadeIn:O.optional(),baseScale:E.positive().optional(),transformOriginX:b.string().optional(),transformOriginY:b.string().optional(),style:b.record(b.string(),b.unknown()).optional()}),xe=b.object({type:b.literal(`text`),...A,text:b.string(),fontSize:E.positive().default(80),fontFamily:b.string().default(`sans-serif`),fontWeight:b.union([b.string(),E]).optional(),color:b.string().default(`#ffffff`),backgroundColor:b.string().optional(),position:b.enum([`top`,`center`,`bottom`]).default(`center`),animation:b.enum([`none`,`fade`,`slide`,`typewriter`,`highlight`]).default(`none`),highlightColor:b.string().optional(),highlightWord:b.string().optional(),style:b.record(b.string(),b.unknown()).optional()}),Se=b.object({type:b.literal(`audio`),...A,src:k,volume:E.min(0).max(1).optional(),volumeAutomation:b.array(ge).optional(),playbackRate:E.positive().optional(),muted:O.optional(),loop:O.optional(),trimBefore:E.nonnegative().optional(),trimAfter:E.nonnegative().optional(),toneFrequency:E.positive().optional()}),Ce=b.object({type:b.literal(`subtitle`),...A,text:b.string(),position:b.enum([`top`,`center`,`bottom`]).default(`bottom`),animation:b.enum([`none`,`fade`,`slide`]).default(`fade`),fontSize:E.positive().default(36),fontFamily:b.string().default(`Arial, sans-serif`),color:b.string().default(`#ffffff`),backgroundColor:b.string().default(`rgba(0, 0, 0, 0.7)`),style:b.record(b.string(),b.unknown()).optional()}),we=b.object({type:b.literal(`gif`),...A,src:k,width:E.positive().optional(),height:E.positive().optional(),fit:b.enum([`contain`,`cover`,`fill`]).optional(),playbackRate:E.positive().optional(),loopBehavior:b.enum([`loop`,`pause-after-finish`]).optional(),style:b.record(b.string(),b.unknown()).optional()}),Te=b.object({type:b.literal(`lottie`),...A,src:k,width:E.positive().optional(),height:E.positive().optional(),loop:O.optional(),style:b.record(b.string(),b.unknown()).optional()}),Ee=b.discriminatedUnion(`type`,[ye,be,xe,Se,Ce,we,Te]),j=b.object({text:b.string(),startMs:E.nonnegative(),endMs:E.nonnegative(),timestampMs:E.nonnegative().optional(),confidence:E.min(0).max(1).optional(),emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()}),De=j.extend({emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).default(`none`)}),Oe=b.object({x:E.min(0).max(1).optional(),y:E.min(0).max(1).optional(),position:b.enum([`top`,`center`,`bottom`]).optional()}),ke=b.object({text:b.string().optional(),startMs:E.nonnegative(),endMs:E.nonnegative(),tokens:b.array(De).min(1),placement:Oe.optional()}),Ae=b.object({top:E.nonnegative().optional(),right:E.nonnegative().optional(),bottom:E.nonnegative().optional(),left:E.nonnegative().optional()}),je=b.object({color:b.string().default(`rgba(0, 0, 0, 0.85)`),blur:E.nonnegative().default(8),offsetX:E.default(0),offsetY:E.default(3)}),Me=b.object({captions:b.array(j).default([]),pages:b.array(ke).optional(),style:b.enum([`tiktok`,`standard`,`karaoke`]).default(`tiktok`),preset:b.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).default(`tiktok-bold`),platform:b.enum([`tiktok`,`reels`,`shorts`,`universal`]).default(`tiktok`),combineTokensWithinMilliseconds:E.nonnegative().default(800),maxWordsPerPage:D.positive().default(6),maxCharsPerLine:D.positive().default(22),maxLines:D.positive().default(2),maxWidthPct:E.positive().max(1).default(.86),fontSize:E.positive().default(60),minFontSize:E.positive().default(42),fontFamily:b.string().default(`Arial, sans-serif`),fontWeight:b.union([D.positive(),b.string()]).default(800),lineHeight:E.positive().default(1.08),color:b.string().default(`#ffffff`),inactiveColor:b.string().default(`#ffffff`),highlightColor:b.string().default(`#FFD700`),textColor:b.string().default(`#ffffff`),strokeColor:b.string().default(`#000000`),strokeWidth:E.nonnegative().default(5),shadow:b.union([O,je]).default(!0),backgroundColor:b.string().default(`rgba(0, 0, 0, 0)`),backgroundRadius:E.nonnegative().default(8),safeZone:Ae.optional(),position:b.enum([`top`,`center`,`bottom`]).default(`bottom`)}).superRefine((e,t)=>{e.captions.length===0&&(!e.pages||e.pages.length===0)&&t.addIssue({code:`custom`,message:`Caption config must include captions or pages.`,path:[`captions`]})}),M={src:k,volume:E.min(0).max(1).default(1),fadeInMs:E.nonnegative().default(0),fadeOutMs:E.nonnegative().default(0),trimBeforeMs:E.nonnegative().default(0),trimAfterMs:E.nonnegative().optional(),playbackRate:E.positive().default(1)},Ne=b.object({...M,definesTimeline:O.default(!0),durationMs:E.positive().optional()}),Pe=b.object({...M,loop:O.default(!0)}),Fe=b.object({...M,atMs:E.nonnegative(),durationMs:E.positive().optional()}),Ie=b.object({voiceover:Ne.optional(),music:Pe.optional(),sfx:b.array(Fe).default([])}),Le=b.object({family:b.string(),source:b.enum([`google`,`local`]).default(`google`),weights:b.array(D.positive()).optional(),url:b.string().optional()}),Re=b.object({clips:b.array(Ee).default([]),backgroundColor:b.string().default(`#000000`),fps:D.positive().default(30),width:D.positive().default(1920),height:D.positive().default(1080),captions:Me.optional(),globalTransition:ve.optional(),fonts:b.array(Le).optional(),audio:Ie.optional()}).passthrough(),ze=`${h.sep}public${h.sep}`,Be=/^(https?:|data:|blob:)/i,Ve=/\b(undefined|NaN|null)\b|blur\(px\)/i,He={Main:{width:1920,height:1080},Vertical:{width:1080,height:1920},Square:{width:1080,height:1080}},Ue=new Set([`video`,`image`,`audio`,`gif`,`lottie`]),We=new Set([`video`,`image`,`gif`,`lottie`,`text`,`subtitle`]),Ge={tiktok:480,reels:420,shorts:420,universal:480},Ke={tiktok:{top:270,right:140,bottom:480,left:80},reels:{top:220,right:110,bottom:420,left:80},shorts:{top:220,right:100,bottom:420,left:80},universal:{top:240,right:140,bottom:480,left:80}};var qe=class e extends Error{constructor(t){super(e.formatMessage(t)),this.validation=t,this.name=`RenderValidationError`}static formatMessage(e){return`Render validation failed: ${e.errors.map(e=>`${e.path??e.code}: ${e.message}`).join(`; `)}`}};let N=class{bundlePromise=null;mediaInfoService=new T;projectRoot=s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);async getServeUrl(){return this.bundlePromise||=ee({entryPoint:h.resolve(this.projectRoot,`src/remotion/index.ts`),publicDir:h.resolve(this.projectRoot,`public`),webpackOverride:e=>({...e,resolve:{...e.resolve,extensionAlias:{".js":[`.ts`,`.tsx`,`.js`,`.jsx`]}}})}),this.bundlePromise}isTransientRendererNavigationError(e){let t=e instanceof Error?e.message:String(e);return t.includes(`ERR_EMPTY_RESPONSE`)||t.includes(`ERR_CONNECTION_RESET`)}async withTransientRendererRetry(e){for(let t=1;t<=2;t+=1)try{return await e()}catch(e){if(t===2||!this.isTransientRendererNavigationError(e))throw e;this.bundlePromise=null,await new Promise(e=>setTimeout(e,150))}throw Error(`Renderer retry exhausted unexpectedly.`)}get publicDir(){return h.resolve(this.projectRoot,`public`)}toPublicRelativePath(e){let t=h.normalize(e),n=t.indexOf(ze);return n===-1?null:t.slice(n+ze.length).split(h.sep).join(`/`)}toLocalFilesystemPath(e){return e.startsWith(`file://`)?y(e):h.isAbsolute(e)?e:null}toPublicFilesystemPath(e){let t=e.replace(/^\/+/,``).replace(/^public\//,``);return h.join(this.publicDir,...t.split(`/`))}resolveValidationSource(e){if(Be.test(e))return{kind:`remote`,src:e};let t=this.toLocalFilesystemPath(e);return t?{kind:`local`,src:e,filePath:t}:{kind:`local`,src:e,filePath:this.toPublicFilesystemPath(e)}}hasExplicitDimension(e,t){return Object.prototype.hasOwnProperty.call(e,t)}voiceoverFrames(e,t){let n=e?.voiceover;if(!n||n.definesTimeline===!1||!n.durationMs)return 0;let r=Math.max(0,(n.trimAfterMs??n.durationMs)-(n.trimBeforeMs??0))/(n.playbackRate??1);return Math.ceil(r/1e3*t)}framesToSeconds(e,t){return Number((e/t).toFixed(3))}canExtendVisualClip(e){return e.type===`image`||e.type===`text`||e.type===`subtitle`||e.type===`lottie`}createVisualGapIssue({gapStart:e,gapEnd:t,fps:n,previous:r,next:i,durationInFrames:a,message:o}){let s=t-e,c={gapStartFrame:e,gapEndFrame:t,gapDurationInFrames:s,gapStartSeconds:this.framesToSeconds(e,n),gapEndSeconds:this.framesToSeconds(t,n),gapDurationSeconds:this.framesToSeconds(s,n),renderDurationInFrames:a};if(r&&(c.previousVisualClip={index:r.clipIndex,type:r.type,startFrame:r.start,endFrame:r.end,durationInFrames:r.durationInFrames,canExtendSafely:r.canExtendSafely}),i&&(c.nextVisualClip={index:i.clipIndex,type:i.type,startFrame:i.start,endFrame:i.end,durationInFrames:i.durationInFrames}),r?.canExtendSafely){let n=r.durationInFrames+s;return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:`Extend clips[${r.clipIndex}].durationInFrames from ${r.durationInFrames} to ${n} so visuals cover frames ${e}..${t}.`,edits:[{path:`inputProps.clips.${r.clipIndex}.durationInFrames`,value:n,reason:`Covers the ${s}-frame visual gap without changing audio timing.`}]}}}return{code:`timeline.visual_gap`,message:o,path:`clips`,details:c,fix:{instruction:r&&!r.canExtendSafely?`Add a still/image/text visual from frame ${e} for ${s} frames, or replace/loop the preceding visual before extending it.`:`Add a visual clip starting at frame ${e} with durationInFrames ${s}.`}}}createSummary(e,t,n){let r=t.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),i=this.voiceoverFrames(t.audio,t.fps),a=Math.max(r,i);return{compositionId:e.compositionId,width:this.hasExplicitDimension(e.inputProps,`width`)?t.width:n.width,height:this.hasExplicitDimension(e.inputProps,`height`)?t.height:n.height,fps:t.fps,durationInFrames:a,clipCount:t.clips.length}}pushBadStringIssues(e,t,n,r){if(typeof e==`string`){Ve.test(e)&&n.push({code:`${r}.invalid_value`,message:`Invalid ${r} value: ${e}`,path:t});return}if(!(!e||typeof e!=`object`)){if(Array.isArray(e)){e.forEach((e,i)=>this.pushBadStringIssues(e,`${t}.${i}`,n,r));return}Object.entries(e).forEach(([e,i])=>{this.pushBadStringIssues(i,`${t}.${e}`,n,r)})}}validateTextClip(e,t,n){`text`in e&&(e.text.trim()||n.push({code:`text.empty`,message:`Text and subtitle clips must not be empty.`,path:`${t}.text`}),this.pushBadStringIssues(e.text,`${t}.text`,n,`text`))}validateStyle(e,t,n){`style`in e&&e.style&&this.pushBadStringIssues(e.style,`${t}.style`,n,`style`)}isSourceClip(e){return Ue.has(e.type)}async validateLocalAsset(e,t,n,r,i){if(!t.filePath)return;let a;try{a=await p(t.filePath)}catch{i.push({code:`asset.missing`,message:`Render asset does not exist: ${t.src}`,path:`${n}.src`});return}if(!a.isFile()){i.push({code:`asset.not_file`,message:`Render asset is not a file: ${t.src}`,path:`${n}.src`});return}try{if(e.type===`image`||e.type===`gif`){let n=await ie(t.filePath,{animated:e.type===`gif`}).metadata();if(!n.width||!n.height)throw Error(`Missing image dimensions`)}else if(e.type===`lottie`)JSON.parse(await f(t.filePath,`utf-8`));else if(e.type===`video`){await this.mediaInfoService.getVideoDimensions(t.filePath);let a=await this.mediaInfoService.getVideoDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}else if(e.type===`audio`){let a=await this.mediaInfoService.getAudioDuration(t.filePath);this.validateMediaDuration(e,a,r,n,i)}}catch(e){i.push({code:`asset.decode_failed`,message:`Render asset cannot be decoded: ${t.src}. ${e instanceof Error?e.message:String(e)}`,path:`${n}.src`})}}validateMediaDuration(e,t,n,r,i){let a=e.trimBefore??0,o=e.trimAfter??0,s=e.playbackRate??1;if(o>0&&o<=a){i.push({code:`media.invalid_trim`,message:`trimAfter must be greater than trimBefore when provided.`,path:r});return}if(e.loop)return;let c=t*n,l=(o>0?o-a:c-a)/s;l+2<e.durationInFrames&&i.push({code:`media.too_short`,message:`Clip duration (${e.durationInFrames} frames) exceeds available media (${Math.floor(l)} frames).`,path:r})}async validateClipAssets(e,t,n){await Promise.all(e.clips.map(async(r,i)=>{let a=`clips.${i}`;if(this.validateTextClip(r,a,n),this.validateStyle(r,a,n),!this.isSourceClip(r))return;let o=this.resolveValidationSource(r.src);if(o.kind===`remote`){t.push({code:`asset.remote_unprobed`,message:`Remote asset will be fetched during render and cannot be decoded in preflight: ${r.src}`,path:`${a}.src`});return}await this.validateLocalAsset(r,o,a,e.fps,n)}))}async validateAudioLayer(e,t,n){if(!e)return;let r=[];e.voiceover&&r.push({src:e.voiceover.src,path:`audio.voiceover.src`}),e.music&&r.push({src:e.music.src,path:`audio.music.src`}),e.sfx?.forEach((e,t)=>{r.push({src:e.src,path:`audio.sfx.${t}.src`})}),await Promise.all(r.map(async({src:e,path:r})=>{let i=this.resolveValidationSource(e);if(i.kind===`remote`){t.push({code:`audio.remote_unprobed`,message:`Remote audio asset will be fetched during render and cannot be decoded in preflight: ${e}`,path:r});return}if(i.filePath)try{if(!(await p(i.filePath)).isFile()){n.push({code:`audio.not_file`,message:`Audio asset is not a file: ${e}`,path:r});return}await this.mediaInfoService.getAudioDuration(i.filePath)}catch(t){if(t.code===`ENOENT`){n.push({code:`audio.missing`,message:`Audio asset does not exist: ${e}`,path:r});return}n.push({code:`audio.decode_failed`,message:`Audio asset cannot be decoded: ${e}. ${t instanceof Error?t.message:String(t)}`,path:r})}}))}async resolveAudioDurationMs(e){let t=this.resolveValidationSource(e);if(t.kind===`remote`||!t.filePath)return null;try{return await this.mediaInfoService.getAudioDuration(t.filePath)*1e3}catch{return null}}async injectAudioDurations(e){let t=e.audio;if(!t?.voiceover||t.voiceover.durationMs)return e;let n=await this.resolveAudioDurationMs(t.voiceover.src);return n?{...e,audio:{...t,voiceover:{...t.voiceover,durationMs:n}}}:e}validateTimeline(e,t,n){let r=!!(e.audio?.voiceover||e.audio?.music||e.audio?.sfx&&e.audio.sfx.length>0);if(e.clips.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.empty`,message:`Render must contain at least one clip.`,path:`clips`});return}let i=e.clips.map((e,t)=>({clip:e,clipIndex:t})).filter(({clip:e})=>We.has(e.type)).map(({clip:e,clipIndex:t})=>({clipIndex:t,type:e.type,start:e.startFrame,end:e.startFrame+e.durationInFrames,durationInFrames:e.durationInFrames,canExtendSafely:this.canExtendVisualClip(e)})).sort((e,t)=>e.start-t.start);if(i.length===0){if(r){n.push({code:`timeline.audio_only`,message:`Render has no visual clips; output will show backgroundColor under the audio layer.`,path:`clips`});return}t.push({code:`timeline.no_visual`,message:`Render must contain at least one visual clip.`,path:`clips`});return}let a=e.clips.reduce((e,t)=>Math.max(e,t.startFrame+t.durationInFrames),0),o=Math.max(a,this.voiceoverFrames(e.audio,e.fps));i[0]&&i[0].start>3&&t.push(this.createVisualGapIssue({gapStart:0,gapEnd:i[0].start,fps:e.fps,next:i[0],durationInFrames:o,message:`Visual timeline starts at frame ${i[0].start}; expected coverage from frame 0.`}));let s=i[0]?.end??0,c=i[0];for(let n of i.slice(1))n.start>s+3&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:n.start,fps:e.fps,previous:c,next:n,durationInFrames:o,message:`Visual timeline has a gap from frame ${s} to ${n.start}.`})),n.end>s&&(c=n),s=Math.max(s,n.end);s+3<o&&t.push(this.createVisualGapIssue({gapStart:s,gapEnd:o,fps:e.fps,previous:c,durationInFrames:o,message:`Visual timeline ends at frame ${s}; render duration is ${o}.`}))}validateCaptions(e,t,n,r){if(!e.captions)return;let i=e.captions,a=t.durationInFrames/e.fps*1e3,o=-1,s=-1;i.captions.forEach((e,t)=>{let i=`captions.captions.${t}`;e.text.trim()||n.push({code:`captions.empty_text`,message:`Caption text must not be empty.`,path:`${i}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.invalid_timing`,message:`Caption endMs must be greater than startMs.`,path:i}),e.startMs<o&&n.push({code:`captions.unsorted`,message:`Captions must be sorted by startMs.`,path:i}),e.startMs<s&&r.push({code:`captions.overlap`,message:`Caption overlaps the previous caption; prefer page/token timing for intentional karaoke overlap.`,path:i}),e.endMs>a+100&&n.push({code:`captions.out_of_range`,message:`Caption ends after render duration (${Math.round(a)}ms).`,path:i}),o=e.startMs,s=Math.max(s,e.endMs),this.pushBadStringIssues(e.text,`${i}.text`,n,`text`)}),i.pages?.forEach((t,o)=>{let s=`captions.pages.${o}`;t.endMs<=t.startMs&&n.push({code:`captions.page_invalid_timing`,message:`Caption page endMs must be greater than startMs.`,path:s}),t.endMs>a+100&&n.push({code:`captions.page_out_of_range`,message:`Caption page ends after render duration (${Math.round(a)}ms).`,path:s});let c=-1,l=-1;t.tokens.forEach((e,i)=>{let a=`${s}.tokens.${i}`;e.text.trim()||n.push({code:`captions.token_empty_text`,message:`Caption token text must not be empty.`,path:`${a}.text`}),e.endMs<=e.startMs&&n.push({code:`captions.token_invalid_timing`,message:`Caption token endMs must be greater than startMs.`,path:a}),(e.startMs<t.startMs||e.endMs>t.endMs)&&n.push({code:`captions.token_outside_page`,message:`Caption token timing must stay within its page timing.`,path:a}),e.startMs<c&&n.push({code:`captions.token_unsorted`,message:`Caption page tokens must be sorted by startMs.`,path:a}),e.startMs<l&&r.push({code:`captions.token_overlap`,message:`Caption token overlaps the previous token.`,path:a}),e.confidence!==void 0&&e.confidence<.75&&r.push({code:`captions.low_confidence`,message:`Low-confidence caption token should be reviewed before render.`,path:a}),c=e.startMs,l=Math.max(l,e.endMs),this.pushBadStringIssues(e.text,`${a}.text`,n,`text`)});let u=this.countCaptionWords(t.tokens.map(e=>e.text).join(` `));u>i.maxWordsPerPage&&r.push({code:`captions.page_too_dense`,message:`Caption page has ${u} words; configured maxWordsPerPage is ${i.maxWordsPerPage}.`,path:s});let d=t.placement?.x!==void 0,f=t.placement?.y!==void 0;if(d!==f&&r.push({code:`captions.page_partial_placement`,message:`Caption page placement should provide both x and y for predictable focal-target avoidance.`,path:`${s}.placement`}),d||f){let n={...Ke[i.platform],...i.safeZone},a=n.left/e.width,o=1-n.right/e.width,c=n.top/e.height,l=1-n.bottom/e.height,u=t.placement?.x??.5,d=t.placement?.y??.5;(u<a||u>o||d<c||d>l)&&r.push({code:`captions.page_placement_safe_zone`,message:`Caption page explicit placement sits outside the configured platform safe zone.`,path:`${s}.placement`})}});let c=Ge[i.platform]??480,l=i.safeZone?.bottom??c;i.position===`bottom`&&l<c&&r.push({code:`captions.safe_zone_bottom`,message:`Bottom caption safe zone ${l}px is smaller than ${i.platform} preset ${c}px.`,path:`captions.safeZone.bottom`}),i.style===`karaoke`&&!i.pages&&i.captions.some(e=>/\s/.test(e.text.trim()))&&r.push({code:`captions.karaoke_legacy_timing`,message:`Karaoke legacy captions use proportional word timing; provide pages/tokens for accurate word sync.`,path:`captions.captions`})}countCaptionWords(e){return e.trim().split(/\s+/).filter(Boolean).length}async copyToPublic(e){let t=await p(e);if(!t.isFile())throw Error(`Render asset is not a file: ${e}`);let n=h.extname(e),r=h.basename(e,n).replace(/[^a-zA-Z0-9._-]/g,`-`),i=v(`sha256`).update(e).update(String(t.mtimeMs)).update(String(t.size)).digest(`hex`).slice(0,16),a=h.posix.join(`mcp-render-assets`,`${r}-${i}${n}`),o=h.join(this.publicDir,...a.split(`/`));return await d(h.dirname(o),{recursive:!0}),await u(e,o),a}async prepareValue(e){if(Array.isArray(e)){let t=!1;return{value:await Promise.all(e.map(async e=>{let n=await this.prepareValue(e);return t||=n.usesLocalAssets,n.value})),usesLocalAssets:t}}if(!e||typeof e!=`object`)return{value:e,usesLocalAssets:!1};let t=!1,n={};for(let[r,i]of Object.entries(e)){if(r===`src`&&typeof i==`string`){let e=this.toLocalFilesystemPath(i);if(e){n[r]=this.toPublicRelativePath(e)??await this.copyToPublic(e),t=!0;continue}}let e=await this.prepareValue(i);n[r]=e.value,t||=e.usesLocalAssets}return{value:n,usesLocalAssets:t}}async prepareInputProps(e){let t=await this.prepareValue(e);return{inputProps:t.value,usesLocalAssets:t.usesLocalAssets}}normalizeProvidedValues(e,t){if(Array.isArray(e)&&Array.isArray(t))return e.map((e,n)=>this.normalizeProvidedValues(e,t[n]));if(e&&t&&typeof e==`object`&&typeof t==`object`&&!Array.isArray(e)&&!Array.isArray(t)){let n={},r=t;for(let[t,i]of Object.entries(e))n[t]=this.normalizeProvidedValues(i,r[t]);return n}return t}normalizeInputProps(e){let t=Re.parse(e);return this.normalizeProvidedValues(e,t)}async validateRender(e){let t=[],n=[],r=He[e.compositionId];r||t.push({code:`composition.unknown`,message:`Unknown composition: ${e.compositionId}`,path:`compositionId`});let i=await this.injectAudioDurations(e.inputProps),a=Re.safeParse(i);if(!a.success)return{passed:!1,errors:a.error.issues.map(e=>({code:`schema.invalid`,message:e.message,path:e.path.join(`.`)})),warnings:n,summary:{compositionId:e.compositionId,width:r?.width??0,height:r?.height??0,fps:0,durationInFrames:0,clipCount:0}};let o=a.data,s=r??{width:o.width,height:o.height},c=this.createSummary(e,o,s);return this.hasExplicitDimension(e.inputProps,`width`)&&o.width!==s.width&&t.push({code:`composition.width_mismatch`,message:`${e.compositionId} expects width ${s.width}, received ${o.width}.`,path:`width`}),this.hasExplicitDimension(e.inputProps,`height`)&&o.height!==s.height&&t.push({code:`composition.height_mismatch`,message:`${e.compositionId} expects height ${s.height}, received ${o.height}.`,path:`height`}),this.validateTimeline(o,t,n),this.validateCaptions(o,c,t,n),await this.validateClipAssets(o,n,t),await this.validateAudioLayer(o.audio,n,t),{passed:t.length===0,errors:t,warnings:n,summary:c}}async render(e){let t=await this.validateRender(e);if(!t.passed)throw new qe(t);if(e.dryRun)return{outputPath:e.outputPath,dryRun:!0,validation:t};let n=await this.injectAudioDurations(e.inputProps),r=this.normalizeInputProps(n),i=await this.prepareInputProps(r);return i.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await te({composition:await re({serveUrl:t,id:e.compositionId,inputProps:i.inputProps}),serveUrl:t,codec:e.codec??`h264`,outputLocation:e.outputPath,inputProps:i.inputProps})}),{outputPath:e.outputPath,validation:t}}async renderStill(e){let t=await this.injectAudioDurations(e.inputProps),n=this.normalizeInputProps(t),r=await this.prepareInputProps(n);return r.usesLocalAssets&&(this.bundlePromise=null),await this.withTransientRendererRetry(async()=>{let t=await this.getServeUrl();await ne({composition:await re({serveUrl:t,id:e.compositionId,inputProps:r.inputProps}),serveUrl:t,output:e.outputPath,frame:e.frame??0,imageFormat:e.imageFormat??`png`,inputProps:r.inputProps})}),{outputPath:e.outputPath}}};N=x([r()],N);const Je=g(a),Ye=[`af_sarah`,`af_nicole`,`af_bella`,`af_sky`,`am_adam`,`am_michael`,`bf_emma`,`bf_isabella`,`bm_george`,`bm_lewis`];let P=class{async isAvailable(){try{return await Je(`kokoro-tts`,[`--version`]),!0}catch{return!1}}async generateVoiceover(e){let{text:t,voice:n=`af_sarah`,speed:r=1,outputPath:i}=e,a=await l.mkdtemp(h.join(m.tmpdir(),`kokoro-`)),o=h.join(a,`input.txt`);try{return await l.writeFile(o,t,`utf-8`),await Je(`kokoro-tts`,[o,i,`--voice`,n,`--speed`,r.toString()]),{outputPath:i}}catch(e){throw Error(`Failed to generate voiceover: ${e instanceof Error?e.message:String(e)}`,{cause:e})}finally{try{await l.rm(a,{recursive:!0,force:!0})}catch{}}}listVoices(){return[...Ye]}};P=x([r()],P);const F={AceStepMusicService:Symbol.for(`AceStepMusicService`),RenderService:Symbol.for(`RenderService`),MediaInfoService:Symbol.for(`MediaInfoService`),CaptionService:Symbol.for(`CaptionService`),FontService:Symbol.for(`FontService`),VoiceoverService:Symbol.for(`VoiceoverService`),VideoDownloadService:Symbol.for(`VideoDownloadService`),VideoAnalysisService:Symbol.for(`VideoAnalysisService`),VideoContextService:Symbol.for(`VideoContextService`),ProxyUrl:Symbol.for(`ProxyUrl`),Tool:Symbol.for(`Tool`)};function I(e,t){if(typeof Reflect==`object`&&typeof Reflect.metadata==`function`)return Reflect.metadata(e,t)}function L(e,t){return function(n,r){t(n,r,e)}}const R=g(a);let z=class{constructor(e=null){this.proxyUrl=e}getProxyArgs(){let e=this.proxyUrl||process.env.YTDLP_PROXY;return e?[`--proxy`,e]:[]}async ensureYtDlp(){try{let{stdout:e}=await R(`which`,[`yt-dlp`]);return e.trim()}catch{throw Error(`yt-dlp is not installed. Install it with: brew install yt-dlp`)}}async search(e,t){let n=await this.ensureYtDlp(),r=`ytsearch${t}:${e}`,{stdout:i}=await R(n,[...this.getProxyArgs(),r,`--flat-playlist`,`--print`,`%(id)s %(title)s %(duration)s %(url)s %(channel)s %(view_count)s %(upload_date)s`,`--no-warnings`],{timeout:3e4});return i.trim().split(`
5
- `).filter(Boolean).map(e=>{let[t,n,r,i,a,o,s]=e.split(` `);return{id:t??``,title:n??``,duration:Number.parseFloat(r??`0`)||0,url:i||`https://www.youtube.com/watch?v=${t}`,channel:a??``,viewCount:Number.parseInt(o??`0`,10)||0,uploadDate:s??``}})}async downloadClip(e,t,n,r,i,a=1080){let o=await this.ensureYtDlp();s(t)||c(t,{recursive:!0});let l=h.join(t,`${n}.mp4`),u=[...this.getProxyArgs(),`-f`,`bestvideo[height<=${a}][ext=mp4]+bestaudio[ext=m4a]/best[height<=${a}][ext=mp4]/best`,`--merge-output-format`,`mp4`,`-o`,l,`--no-warnings`,`--no-playlist`];if(r&&i?u.push(`--download-sections`,`*${r}-${i}`):r?u.push(`--download-sections`,`*${r}-inf`):i&&u.push(`--download-sections`,`*0:00-${i}`),u.push(e),await R(o,u,{timeout:12e4}),!s(l))throw Error(`Download failed: output file not found at ${l}`);let{width:d,height:f,duration:p}=await this.getMediaInfo(l);return{outputPath:l,publicPath:h.relative(t,l),duration:p,width:d,height:f}}async getMediaInfo(e){let{stdout:t}=await R(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`);return{width:Number(r?.width??0),height:Number(r?.height??0),duration:i}}};z=x([r(),L(0,n(F.ProxyUrl)),L(0,i()),I(`design:paramtypes`,[Object])],z);const Xe=g(a),Ze=1280,Qe=h.join(m.homedir(),`.gemini`,`oauth_creds.json`);let B=class{async analyzeVideos(e){let t=e.model||`gemini-2.5-flash-lite`,n=e.videoPaths.map(e=>h.resolve(e));if(n.length===0)throw Error(`At least one video path is required.`);let r=this.resolveWorkspaceRoot(process.cwd()),i=_.randomUUID().toLowerCase(),a=_.randomUUID().toLowerCase(),s=await l.mkdtemp(h.join(m.tmpdir(),`video-editor-mcp-video.`));try{let c=await this.readAccessToken(),l=process.env.CODE_ASSIST_ENDPOINT||`https://cloudcode-pa.googleapis.com`,u=process.env.CODE_ASSIST_API_VERSION||`v1internal`,{resolvedProject:d,projectFromEnv:f}=await this.resolveProject(c,l,u),p=new AbortController,m=setTimeout(()=>p.abort(),6e5);try{let m=await this.prepareVideos(n,s),h={model:t,project:d,user_prompt_id:a,request:{contents:[{role:`user`,parts:[{text:this.buildSessionContext(r)},{text:e.prompt},...m.map(e=>({inlineData:{mimeType:e.mimeType,data:o.readFileSync(e.transcodedPath).toString(`base64`)}}))]}],session_id:i}},g=await fetch(`${l}/${u}:generateContent`,{method:`POST`,signal:p.signal,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${c}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`,...f?{"X-Goog-User-Project":f}:{}},body:JSON.stringify(h)}),_=await g.text(),v=this.safeJsonParse(_);if(!g.ok)throw Error(`generateContent failed with ${g.status}: ${_}`);let y=v,ee=this.extractResponseText(v);return{resolvedProject:d,model:t,prompt:e.prompt,videoCount:m.length,videos:m,responseText:ee,rawResponse:v??_,traceId:this.getString(v,`traceId`),responseId:this.getString(y?.response,`responseId`)}}finally{clearTimeout(m)}}finally{await l.rm(s,{recursive:!0,force:!0})}}resolveWorkspaceRoot(e=process.cwd()){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=h.resolve(e);for(;;){for(let e of t)if(s(h.join(n,e)))return n;let r=h.dirname(n);if(r===n)return h.resolve(e);n=r}}async readAccessToken(e=process.env.GEMINI_OAUTH_CREDS_FILE||Qe){if(!o.existsSync(e))throw Error(`OAuth credentials file not found: ${e}`);let t=await l.readFile(e,`utf8`),n=JSON.parse(t),r=this.parseToken(n);if(r.refreshToken){let e=await fetch(`https://oauth2.googleapis.com/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({client_id:`681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com`,client_secret:`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`,grant_type:`refresh_token`,refresh_token:r.refreshToken})});if(!e.ok){let t=await e.text();throw Error(`Failed to refresh OAuth token: ${e.status} ${t}`)}let t=await e.json();if(typeof t.access_token==`string`&&t.access_token)return t.access_token;throw Error(`OAuth refresh response did not include access_token.`)}if(r.accessToken)return r.accessToken;throw Error(`No access token found in ${e}.`)}parseToken(e){if(!e||typeof e!=`object`)return{};let t=e;return t.token&&typeof t.token==`object`?{accessToken:typeof t.token.accessToken==`string`?t.token.accessToken:void 0,refreshToken:typeof t.token.refreshToken==`string`?t.token.refreshToken:void 0}:{accessToken:typeof t.access_token==`string`?t.access_token:void 0,refreshToken:typeof t.refresh_token==`string`?t.refresh_token:void 0}}async resolveProject(e,t,n){let r=process.env.GOOGLE_CLOUD_PROJECT||process.env.GOOGLE_CLOUD_PROJECT_ID||void 0,i=await fetch(`${t}/${n}:loadCodeAssist`,{method:`POST`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${e}`,"User-Agent":`GeminiCLI-mcp-video-editor/0.0.0`},body:JSON.stringify({metadata:{ideType:`IDE_UNSPECIFIED`,platform:`PLATFORM_UNSPECIFIED`,pluginType:`GEMINI`,...r?{duetProject:r}:{}},...r?{cloudaicompanionProject:r}:{}})}),a=await i.text();if(!i.ok)throw Error(`loadCodeAssist failed with ${i.status}: ${a}`);return{resolvedProject:this.safeJsonParse(a)?.cloudaicompanionProject||r||``,projectFromEnv:r}}async prepareVideos(e,t){let n=[];for(let[r,i]of e.entries()){if(!o.existsSync(i))throw Error(`Video file not found: ${i}`);let e=(await l.stat(i)).size,a=h.join(t,`${h.basename(i,h.extname(i))}-${r}.mp4`);await Xe(`ffmpeg`,[`-y`,`-i`,i,`-map`,`0:v:0`,`-map`,`0:a?`,`-vf`,`scale='if(gt(iw,${Ze}),${Ze},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-c:a`,`aac`,`-b:a`,`128k`,`-movflags`,`+faststart`,a],{timeout:600*1e3});let s=(await l.stat(a)).size;n.push({sourcePath:i,transcodedPath:a,originalSizeBytes:e,transcodedSizeBytes:s,mimeType:`video/mp4`})}return n}buildSessionContext(e){let t=new Date().toLocaleDateString(void 0,{weekday:`long`,year:`numeric`,month:`long`,day:`numeric`}),n=h.join(m.homedir(),`.gemini`,`tmp`,h.basename(e)),r=this.getWorkspaceTree(e);return`
6
- <session_context>
7
- This is the Gemini CLI. We are setting up the context for our chat.
8
- Today's date is ${t} (formatted according to the user's locale).
9
- My operating system is: ${process.platform}
10
- The project's temporary directory is: ${n}
11
- - **Workspace Directories:**
12
- - ${e}
13
- - **Directory Structure:**
14
-
15
- ${r}
16
- </session_context>`.trim()}getWorkspaceTree(e,t=3,n=200){let r=[`${e}/`],i=0,a=(e,s,c)=>{if(i>=n||c>t)return;let l;try{l=o.readdirSync(e,{withFileTypes:!0}).filter(e=>![`.git`,`node_modules`,`.nx-cache`].includes(e.name)).sort((e,t)=>e.name.localeCompare(t.name))}catch{return}for(let o=0;o<l.length&&i<n;o+=1){let n=l[o],u=o===l.length-1,d=u?`└───`:`├───`,f=`${s}${u?` `:`│ `}`,p=h.join(e,n.name);if(i+=1,n.isDirectory()){r.push(`${s}${d}${n.name}/`),c<t&&a(p,f,c+1);continue}r.push(`${s}${d}${n.name}`)}};return a(e,``,0),i>=n&&r.push(`...`),r.join(`
17
- `)}extractResponseText(e){return(e?.response?.candidates?.[0]?.content?.parts??[]).map(e=>e.text??``).filter(Boolean).join(``)}safeJsonParse(e){try{return JSON.parse(e)}catch{return}}getString(e,t){if(!e||typeof e!=`object`)return;let n=e;return typeof n[t]==`string`?n[t]:void 0}};B=x([r()],B);const V=g(a);let H=class{constructor(e=new C){this.captionService=e}async extractVideoContext(e){let t=h.resolve(e.videoPath);if(!s(t))throw Error(`Video file not found: ${t}`);let n=e.basename??h.basename(t,h.extname(t)),r=h.resolve(e.outputDir??this.defaultOutputDir(t,n)),i=h.join(r,`work`),a=h.join(r,`manifest.json`);await l.mkdir(r,{recursive:!0}),await this.probeVideo(t);let o=e.transcribe===!1?{text:``,segments:[]}:await this.transcribeVideo(t,r,e),c=o.segments.length>0?await this.createProxyVideo(t,i,e):void 0,u={transcript:{...o,segments:c?await this.attachFilmstripsToSegments(c,r,o.segments,e):[]},next_actions:[`Review segments filmstrips for visual inspection`]};return await l.writeFile(a,`${JSON.stringify(u,null,2)}\n`,`utf8`),u}async probeVideo(e){let{stdout:t}=await V(`ffprobe`,[`-v`,`error`,`-select_streams`,`v:0`,`-show_entries`,`stream=width,height:format=duration`,`-of`,`json`,e]),n=JSON.parse(t),r=n.streams?.[0],i=Number.parseFloat(n.format?.duration??`0`),a=Number(r?.width??0),o=Number(r?.height??0);if(!i||Number.isNaN(i))throw Error(`Invalid duration for video: ${e}`);if(!a||!o)throw Error(`No video stream found in: ${e}`);return{durationSeconds:i,width:a,height:o}}defaultOutputDir(e,t){let n=this.resolveWorkspaceRoot(h.dirname(e)),r=n?h.join(n,`.tmp`,`video-editor-mcp`):m.tmpdir();return h.join(r,`${t}-context`)}resolveWorkspaceRoot(e){let t=[`pnpm-workspace.yaml`,`nx.json`,`.git`],n=h.resolve(e);for(;;){if(t.some(e=>s(h.join(n,e))))return n;let e=h.dirname(n);if(e===n)return;n=e}}async createProxyVideo(e,t,n){let r=h.join(t,`proxy.mp4`),i=Math.max(n.tileWidth??250,500);return await l.mkdir(t,{recursive:!0}),await V(`ffmpeg`,[`-y`,`-i`,e,`-vf`,`scale='min(${i},iw)':-2`,`-c:v`,`libx264`,`-preset`,`veryfast`,`-crf`,`28`,`-pix_fmt`,`yuv420p`,`-an`,r]),r}async transcribeVideo(e,t,n){let r=await this.resolveWhisperCommand(),i=h.join(t,`audio.wav`);await V(`ffmpeg`,[`-y`,`-i`,e,`-vn`,`-ac`,`1`,`-ar`,`16000`,i]),r.engine===`whisper`?await this.runPythonWhisper(r.binary,i,t,n):await this.runWhisperCpp(r.binary,i,t,n);let a=this.firstExistingPath([h.join(t,`audio.txt`),h.join(t,`transcript.txt`),`${i}.txt`]),o=this.firstExistingPath([h.join(t,`audio.srt`),h.join(t,`transcript.srt`),`${i}.srt`]),s=a?(await l.readFile(a,`utf8`)).trim():``,c=o?this.captionService.parseSrt(await l.readFile(o,`utf8`)):[];return{engine:r.engine,text:s,txtPath:a,srtPath:o,segments:c}}async resolveWhisperCommand(){for(let e of[`whisper-cli`,`whisper.cpp`,`whisper`]){let t=await this.findBinary(e);if(t)return{binary:t,engine:e}}throw Error(`No supported Whisper binary found. Install whisper.cpp or OpenAI Whisper, then retry.`)}async findBinary(e){try{let{stdout:t}=await V(`which`,[e]);return t.trim()||void 0}catch{return}}async runPythonWhisper(e,t,n,r){let i=[t,`--model`,r.whisperModel??`base`,`--output_dir`,n,`--output_format`,`all`];r.language&&i.push(`--language`,r.language),await V(e,i,{timeout:1200*1e3})}async runWhisperCpp(e,t,n,r){let i=r.whisperModelPath??process.env.WHISPER_MODEL_PATH??this.resolveDefaultWhisperModelPath();if(!i||!s(i))throw Error(`Whisper.cpp model not found. Provide whisperModelPath or set WHISPER_MODEL_PATH.`);let a=[`-m`,i,`-f`,t,`-otxt`,`-osrt`,`-of`,h.join(n,`transcript`)];r.language&&a.push(`-l`,r.language),await V(e,a,{timeout:1200*1e3})}resolveDefaultWhisperModelPath(){return[h.join(m.homedir(),`.whisper`,`ggml-base.en.bin`),h.join(m.homedir(),`.whisper`,`ggml-small.en.bin`),`/opt/homebrew/share/whisper-cpp/ggml-base.en.bin`,`/usr/local/share/whisper-cpp/ggml-base.en.bin`].find(e=>s(e))}async attachFilmstripsToSegments(e,t,n,r){let i=h.join(t,`slides`);await l.mkdir(i,{recursive:!0});let a=[];for(let[t,o]of n.entries()){let n=await this.extractSegmentFilmstrips(e,i,t,o,r);a.push({...o,text:o.text.trim(),filmstrips:n})}return a}async extractSegmentFilmstrips(e,t,n,r,i){let a=(i.imageFormat??`jpeg`)===`png`?`png`:`jpg`,o=i.framesPerSecond??3,s=i.tileWidth??250,c=i.filmstripColumns??6,l=c*(i.filmstripRows??4),u=Math.max((r.endMs-r.startMs)/1e3,1/o),d=Math.max(1,Math.ceil(u*o)),f=[];for(let i=0,u=0;i<d;i+=l,u+=1){let p=Math.min(l,d-i),m=Math.min(c,p),g=Math.ceil(p/m),_=r.startMs+Math.round(i/o*1e3),v=Math.min(p/o,Math.max((r.endMs-_)/1e3,1/o)),y=h.join(t,`segment_${String(n+1).padStart(4,`0`)}_filmstrip_${String(u+1).padStart(3,`0`)}.${a}`);await V(`ffmpeg`,[`-y`,`-ss`,(_/1e3).toFixed(3),`-t`,v.toFixed(3),`-i`,e,`-vf`,[`fps=${o}`,`scale='min(${s},iw)':-2`,`pad=iw:ih+32:0:0:white`,`tile=${m}x${g}:padding=8:margin=8:color=white`].join(`,`),`-frames:v`,`1`,y]),await this.annotateFilmstrip(y,_,o,p,m,g),f.push({path:y,index:u,startMs:_,endMs:Math.min(r.endMs,_+Math.round(v*1e3)),frameCount:p,tileWidth:s,columns:m,rows:g})}return f}async annotateFilmstrip(e,t,n,r,i,a){let o=await ie(e).metadata();if(!o.width||!o.height)throw Error(`Unable to annotate filmstrip without image dimensions: ${e}`);let s=(o.width-16-8*(i-1))/i,c=(o.height-16-8*(a-1))/a,u=Array.from({length:r},(e,r)=>{let a=r%i,o=Math.floor(r/i),l=t+Math.round(r/n*1e3);return`<text x="${8+a*(s+8)+s/2}" y="${8+o*(c+8)+c-32+22}" text-anchor="middle">${l} ms</text>`}).join(``),d=Buffer.from(`<svg width="${o.width}" height="${o.height}" xmlns="http://www.w3.org/2000/svg"><style>text{font-family:Arial,sans-serif;font-size:18px;fill:#000}</style>${u}</svg>`),f=await ie(e).composite([{input:d,top:0,left:0}]).toBuffer();await l.writeFile(e,f)}firstExistingPath(e){return e.find(e=>s(e))}};H=x([r(),I(`design:paramtypes`,[Object])],H);let U=class{success(e){return{content:[{type:`text`,text:e}]}}successJson(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}error(e){return{content:[{type:`text`,text:`Error: ${e}`}],isError:!0}}async safeExecute(e){try{return await e()}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};U=x([r()],U);var $e,et;const tt=b.object({url:b.string().describe(`Video URL from YouTube, TikTok, Instagram, or other yt-dlp supported sites`),filename:b.string().describe(`Output filename without extension (e.g., "my_clip")`),startTime:b.string().optional().describe(`Start time for clip extraction (e.g., "0:30", "1:15")`),endTime:b.string().optional().describe(`End time for clip extraction (e.g., "1:00", "2:30")`),maxHeight:b.number().int().positive().default(1080).describe(`Maximum video height in pixels (default: 1080)`),outputDir:b.string().optional().describe(`Custom output directory (default: Remotion public/videos/ for direct use in compositions)`)}),nt=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let W=class extends U{static{et=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return tt}getDefinition(){return{name:et.TOOL_NAME,description:`Download a video clip from YouTube, TikTok, Instagram, or any yt-dlp supported site with optional time range trimming. Saves to Remotion public/videos/ by default for direct use in compositions.`,inputSchema:b.toJSONSchema(tt,{reused:`inline`})}}async execute(e){try{let t=tt.parse(e),n=h.resolve(nt(),`public/videos`),r=t.outputDir??n,i=await this.videoDownloadService.downloadClip(t.url,r,t.filename,t.startTime,t.endTime,t.maxHeight),a=t.outputDir?void 0:`/videos/${t.filename}.mp4`;return this.successJson({...i,compositionSrc:a,suggestedComposition:i.height>i.width?`Vertical`:`Main`})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};W=et=x([r(),L(0,n(F.VideoDownloadService)),I(`design:paramtypes`,[typeof($e=z!==void 0&&z)==`function`?$e:Object])],W);var rt,it;const at=b.object({videoPath:b.string().min(1).describe(`Path to the local video file to extract context from`),outputDir:b.string().optional().describe(`Directory to write transcript, filmstrips, and manifest`),basename:b.string().optional().describe(`Base name for the generated context directory when outputDir is omitted`),framesPerSecond:b.number().positive().default(3).describe(`Frames per second to sample for raw frames and segment slides`),imageFormat:b.enum([`jpeg`,`png`]).default(`jpeg`).describe(`Frame image format`),tileWidth:b.number().int().positive().default(250).describe(`Width of each filmstrip tile in pixels`),filmstripColumns:b.number().int().positive().default(6).describe(`Maximum columns per segment filmstrip image`),filmstripRows:b.number().int().positive().default(4).describe(`Maximum rows per segment filmstrip image`),transcribe:b.boolean().default(!0).describe(`Run local Whisper transcription`),language:b.string().optional().describe(`Optional Whisper language hint`),whisperModel:b.string().optional().describe(`Python Whisper model name, default base`),whisperModelPath:b.string().optional().describe(`Whisper.cpp model path override`)});let G=class extends U{static{it=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return at}getDefinition(){return{name:it.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.toJSONSchema(at,{reused:`inline`})}}async execute(e){try{let t=at.parse(e),n=await this.videoContextService.extractVideoContext(t);return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};G=it=x([r(),L(0,n(F.VideoContextService)),I(`design:paramtypes`,[typeof(rt=H!==void 0&&H)==`function`?rt:Object])],G);var ot,st;const ct=b.object({captions:b.array(b.object({text:b.string(),startMs:b.number(),endMs:b.number(),confidence:b.number().min(0).max(1).optional(),emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).optional()})).describe(`Array of caption objects with text, startMs, and endMs`),combineMs:b.number().optional().describe(`Milliseconds threshold for combining captions into pages (default: 100)`),maxWordsPerPage:b.number().int().positive().optional().describe(`Maximum words per caption page (default: 6)`),maxCharsPerLine:b.number().int().positive().optional().describe(`Target maximum characters per caption line (default: 22)`),platform:b.enum([`tiktok`,`reels`,`shorts`,`universal`]).optional().describe(`Platform safe-zone preset`),preset:b.enum([`tiktok-bold`,`karaoke`,`premium-pill`,`minimal`,`ugc`]).optional().describe(`Caption visual style preset`)});let K=class extends U{static{st=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return ct}getDefinition(){return{name:st.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.toJSONSchema(ct,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=ct.parse(e),s=t.map(e=>({text:e.text,startMs:e.startMs,endMs:e.endMs,confidence:e.confidence,emphasis:e.emphasis})),c=this.captionService.createTikTokCaptions(s,{combineMs:n,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o});return this.successJson(c)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};K=st=x([r(),L(0,n(F.CaptionService)),I(`design:paramtypes`,[typeof(ot=C!==void 0&&C)==`function`?ot:Object])],K);var lt,ut;const dt=b.object({prompt:b.string().describe(`Music description for ACE-Step text-to-music generation`),filename:b.string().optional().describe(`Output filename without extension (default: ace-step-music)`),outputPath:b.string().optional().describe(`Explicit output audio path. Overrides filename/outputDir.`),outputDir:b.string().optional().describe(`Custom output directory (default: Remotion public/music/ for direct use in compositions)`),aceStepPath:b.string().optional().describe(`Path to local ACE-Step-1.5 checkout (default: ACE_STEP_15_DIR or ~/workspace/ACE-Step-1.5)`),lyrics:b.string().optional().describe(`Lyrics to use. Defaults to [Instrumental] for background music.`),duration:b.number().min(10).max(600).optional().describe(`Target duration in seconds (default: 30)`),bpm:b.number().min(30).max(300).optional().describe(`Optional BPM metadata`),keyscale:b.string().optional().describe(`Optional key/scale metadata, e.g. C minor`),timeSignature:b.string().optional().describe(`Optional time signature metadata, e.g. 4/4`),vocalLanguage:b.string().optional().describe(`Vocal language code, or unknown (default: unknown)`),seed:b.number().int().optional().describe(`Optional fixed seed for reproducible generation`),inferenceSteps:b.number().int().positive().optional().describe(`Diffusion inference steps (default: 8)`),audioFormat:b.enum([`wav`,`mp3`,`flac`]).default(`wav`).describe(`Output audio format (default: wav)`),backend:b.enum([`mlx`,`pt`,`vllm`]).default(`mlx`).describe(`ACE-Step LM backend (default: mlx for Apple Silicon)`),configPath:b.string().optional().describe(`ACE-Step DiT model config (default: acestep-v15-turbo)`),lmModelPath:b.string().optional().describe(`ACE-Step LM model (default: acestep-5Hz-lm-0.6B)`),timeoutMs:b.number().int().positive().optional().describe(`Generation timeout in milliseconds`)}),ft=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let pt=class extends U{static{ut=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return dt}getDefinition(){return{name:ut.TOOL_NAME,description:`Generate background music using a local ACE-Step 1.5 checkout. Defaults to MLX on Apple Silicon and saves to Remotion public/music/.`,inputSchema:b.toJSONSchema(dt,{reused:`inline`})}}async execute(e){try{let t=dt.parse(e);if(!await this.aceStepMusicService.isAvailable(t.aceStepPath))return this.error(`ACE-Step 1.5 is not available. Clone https://github.com/ACE-Step/ACE-Step-1.5 and set ACE_STEP_15_DIR or pass aceStepPath.`);let n=this.resolveOutputPath(t),r=await this.aceStepMusicService.generateMusic({prompt:t.prompt,outputPath:n,aceStepPath:t.aceStepPath,lyrics:t.lyrics,duration:t.duration,bpm:t.bpm,keyscale:t.keyscale,timeSignature:t.timeSignature,vocalLanguage:t.vocalLanguage,seed:t.seed,inferenceSteps:t.inferenceSteps,audioFormat:t.audioFormat,backend:t.backend,configPath:t.configPath,lmModelPath:t.lmModelPath,timeoutMs:t.timeoutMs}),i=h.resolve(ft(),`public/music`),a=h.dirname(n)===i?`/music/${h.basename(n)}`:void 0;return this.successJson({...r,compositionSrc:a,suggestedClip:a?{type:`audio`,src:a,startFrame:0,volume:.35}:void 0})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}resolveOutputPath(e){if(e.outputPath)return h.resolve(e.outputPath);let t=h.resolve(e.outputDir??h.join(ft(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return h.join(t,`${n}.${r}`)}};pt=ut=x([r(),L(0,n(F.AceStepMusicService)),I(`design:paramtypes`,[typeof(lt=S!==void 0&&S)==`function`?lt:Object])],pt);var mt,ht;const gt=b.object({text:b.string().describe(`Text to convert to speech`),voice:b.string().optional().describe(`Voice ID (default: af_sarah). Options: af_sarah, af_nicole, af_bella, af_sky, am_adam, am_michael, bf_emma, bf_isabella, bm_george, bm_lewis`),speed:b.number().optional().describe(`Speech speed multiplier (default: 1.0)`),outputPath:b.string().describe(`Output file path for the generated audio`)});let q=class extends U{static{ht=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return gt}getDefinition(){return{name:ht.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.toJSONSchema(gt,{reused:`inline`})}}async execute(e){try{let t=gt.parse(e);if(!await this.voiceoverService.isAvailable())return this.error(`Kokoro TTS is not installed. Install it with: pip install kokoro-tts`);let n=await this.voiceoverService.generateVoiceover({text:t.text,voice:t.voice,speed:t.speed,outputPath:t.outputPath});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};q=ht=x([r(),L(0,n(F.VoiceoverService)),I(`design:paramtypes`,[typeof(mt=P!==void 0&&P)==`function`?mt:Object])],q);var _t,vt;const yt=b.object({src:b.string().describe(`Path to the media file`),type:b.enum([`video`,`audio`]).optional().describe(`Media type (default: video)`)});let J=class extends U{static{vt=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return yt}getDefinition(){return{name:vt.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.toJSONSchema(yt,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=yt.parse(e),r=await this.mediaInfoService.canDecode(t);if(n===`audio`){let e=await this.mediaInfoService.getAudioDuration(t);return this.successJson({src:t,type:`audio`,duration:e,canDecode:r})}let[i,a]=await Promise.all([this.mediaInfoService.getVideoDuration(t),this.mediaInfoService.getVideoDimensions(t)]);return this.successJson({src:t,type:`video`,duration:i,width:a.width,height:a.height,canDecode:r})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};J=vt=x([r(),L(0,n(F.MediaInfoService)),I(`design:paramtypes`,[typeof(_t=T!==void 0&&T)==`function`?_t:Object])],J);var bt,xt;const St=b.object({srtContent:b.string().describe(`SRT file content as a string`)});let Y=class extends U{static{xt=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return St}getDefinition(){return{name:xt.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.toJSONSchema(St,{reused:`inline`})}}async execute(e){try{let t=St.parse(e),n=this.captionService.parseSrt(t.srtContent);return this.successJson({captionCount:n.length,captions:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Y=xt=x([r(),L(0,n(F.CaptionService)),I(`design:paramtypes`,[typeof(bt=C!==void 0&&C)==`function`?bt:Object])],Y);var Ct;const wt=b.object({});let X=class extends U{static{Ct=this}static TOOL_NAME=`list_compositions`;getInputSchema(){return wt}getDefinition(){return{name:Ct.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.toJSONSchema(wt,{reused:`inline`})}}async execute(){let e=[{id:`Main`,description:`Main 16:9 landscape composition`,width:1920,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Vertical`,description:`Vertical 9:16 portrait composition (TikTok, Instagram Stories)`,width:1080,height:1920,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}},{id:`Square`,description:`Square 1:1 composition (Instagram posts)`,width:1080,height:1080,fps:30,durationInFrames:null,defaultProps:{clips:[],audioVolume:1,backgroundColor:`#000000`}}];return this.successJson({compositionCount:e.length,compositions:e})}};X=Ct=x([r()],X);var Tt,Et;const Dt=b.object({compositionId:b.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:b.record(b.string(),b.unknown()).describe(`JSON props to pass to the composition`),outputPath:b.string().describe(`Output file path for the rendered image`),frame:b.number().optional().describe(`Frame number to render (default: 0)`),imageFormat:b.enum([`png`,`jpeg`]).optional().describe(`Image format (default: png)`)});let Z=class extends U{static{Et=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return Dt}getDefinition(){return{name:Et.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:b.toJSONSchema(Dt,{reused:`inline`})}}async execute(e){try{let t=Dt.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,frame:t.frame,imageFormat:t.imageFormat},r=await this.renderService.renderStill(n);return this.successJson(r)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};Z=Et=x([r(),L(0,n(F.RenderService)),I(`design:paramtypes`,[typeof(Tt=N!==void 0&&N)==`function`?Tt:Object])],Z);var Ot,kt;const At=b.preprocess(e=>{if(typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},b.record(b.string(),b.unknown())),jt=b.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.boolean()),Mt=b.object({compositionId:b.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:At.describe(`JSON props to pass to the composition`),outputPath:b.string().describe(`Output file path for the rendered video`),codec:b.enum([`h264`,`h265`,`vp8`,`vp9`]).optional().describe(`Video codec (default: h264)`),dryRun:jt.optional().describe(`Validate props and assets without rendering a video`)});let Q=class extends U{static{kt=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return Mt}getDefinition(){return{name:kt.TOOL_NAME,description:"Render a video from JSON props using Remotion. `inputProps` must conform to the Main composition schema. Full JSON Schema (generated from `src/schemas/compositions.ts` -> `MainCompositionSchema`): `schemas/main-composition.schema.json` (relative to this package; in the monorepo: `packages/mcp/video-editor-mcp/schemas/main-composition.schema.json`). Validation runs server-side; failures return a `render_validation_failed` error with per-issue paths and suggested fixes.",inputSchema:b.toJSONSchema(Mt,{reused:`inline`})}}async execute(e){try{let t=Mt.parse(e),n={compositionId:t.compositionId,inputProps:t.inputProps,outputPath:t.outputPath,codec:t.codec,dryRun:t.dryRun},r=await this.renderService.render(n);return this.successJson(r)}catch(e){return e instanceof qe?{content:[{type:`text`,text:JSON.stringify({error:`render_validation_failed`,message:e.message,summary:e.validation.summary,issues:e.validation.errors,warnings:e.validation.warnings,suggestedFixes:e.validation.errors.filter(e=>e.fix).map(e=>({code:e.code,path:e.path,...e.fix}))},null,2)}],isError:!0}:this.error(e instanceof Error?e.message:`Unknown error`)}}};Q=kt=x([r(),L(0,n(F.RenderService)),I(`design:paramtypes`,[typeof(Ot=N!==void 0&&N)==`function`?Ot:Object])],Q);var Nt,Pt;const Ft=b.object({query:b.string().describe(`Search query for YouTube videos`),maxResults:b.number().int().positive().max(20).default(5).describe(`Maximum number of results (default: 5, max: 20)`)});let It=class extends U{static{Pt=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ft}getDefinition(){return{name:Pt.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.toJSONSchema(Ft,{reused:`inline`})}}async execute(e){try{let t=Ft.parse(e),n=await this.videoDownloadService.search(t.query,t.maxResults);return this.successJson({query:t.query,resultCount:n.length,results:n})}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};It=Pt=x([r(),L(0,n(F.VideoDownloadService)),I(`design:paramtypes`,[typeof(Nt=z!==void 0&&z)==`function`?Nt:Object])],It);var Lt,Rt;const zt=b.object({videoPaths:b.array(b.string().min(1)).min(1).describe(`Paths to local video files to analyze`),prompt:b.string().min(1).describe(`Prompt to send with the video(s)`),model:b.string().default(`gemini-2.5-flash-lite`).describe(`Gemini model to use (default: gemini-2.5-flash-lite)`)});let $=class extends U{static{Rt=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return zt}getDefinition(){return{name:Rt.TOOL_NAME,description:`Analyze one or more local video files by transcoding them to a compact MP4 and sending them with a prompt to Gemini Code Assist.`,inputSchema:b.toJSONSchema(zt,{reused:`inline`})}}async execute(e){try{let t=zt.parse(e),n=await this.videoAnalysisService.analyzeVideos({videoPaths:t.videoPaths,prompt:t.prompt,model:t.model});return this.successJson(n)}catch(e){return this.error(e instanceof Error?e.message:`Unknown error`)}}};$=Rt=x([r(),L(0,n(F.VideoAnalysisService)),I(`design:paramtypes`,[typeof(Lt=B!==void 0&&B)==`function`?Lt:Object])],$);const Bt=new t(e=>{e.bind(F.AceStepMusicService).to(S).inSingletonScope(),e.bind(F.RenderService).to(N).inSingletonScope(),e.bind(F.MediaInfoService).to(T).inSingletonScope(),e.bind(F.CaptionService).to(C).inSingletonScope(),e.bind(F.FontService).to(me).inSingletonScope(),e.bind(F.VoiceoverService).to(P).inSingletonScope(),e.bind(F.VideoDownloadService).to(z).inSingletonScope(),e.bind(F.VideoAnalysisService).to(B).inSingletonScope(),e.bind(F.VideoContextService).to(H).inSingletonScope()}),Vt=new t(e=>{e.bind(F.Tool).to(Q).inSingletonScope(),e.bind(F.Tool).to(X).inSingletonScope(),e.bind(F.Tool).to(J).inSingletonScope(),e.bind(F.Tool).to(Z).inSingletonScope(),e.bind(F.Tool).to(K).inSingletonScope(),e.bind(F.Tool).to(Y).inSingletonScope(),e.bind(F.Tool).to(pt).inSingletonScope(),e.bind(F.Tool).to(q).inSingletonScope(),e.bind(F.Tool).to(It).inSingletonScope(),e.bind(F.Tool).to(W).inSingletonScope(),e.bind(F.Tool).to($).inSingletonScope(),e.bind(F.Tool).to(G).inSingletonScope()});function Ht(t={}){let n=new e;return n.load(Bt,Vt),n.bind(F.ProxyUrl).toConstantValue(t.proxyUrl??null),n}const Ut=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function Wt(e){return Ut.has(e)}async function Gt(e,t,n,r){return Wt(e)?r.run(()=>t.execute(n)):t.execute(n)}function Kt(e){let t=new se({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(F.Tool),r=new he,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(le,async()=>({tools:n.map(e=>e.getDefinition())})),t.setRequestHandler(ce,async e=>{let{name:t,arguments:n}=e.params,a=i.get(t);if(!a)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let o=ae(n??{},a.getInputSchema());try{return await Gt(t,a,o,r)}catch(e){if(e instanceof b.ZodError)return{content:[{type:`text`,text:oe(e,{schemaName:t,schema:a.getInputSchema()})}],isError:!0};throw e}}),t}var qt=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new ue,await this.server.connect(this.transport),console.error(`video-editor MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{ve as A,Se as C,Te as D,be as E,Ce as O,_e as S,we as T,qe as _,Q as a,Le as b,Y as c,K as d,G as f,N as g,F as h,$ as i,ye as j,xe as k,J as l,H as m,Kt as n,Z as o,U as p,Ht as r,X as s,qt as t,q as u,Me as v,Ee as w,Re as x,j as y};