@agimon-ai/video-editor-mcp 0.18.4 → 0.19.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/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const e=require(`./stdio-
|
|
2
|
+
const e=require(`./stdio-DShK9eFX.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.19.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
3
|
`:`
|
|
4
4
|
`)+n,i=r+1,r=e.indexOf(`
|
|
5
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(`
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{_ as e,h as t,n,r,t as i}from"./stdio-
|
|
2
|
+
import{_ as e,h as t,n,r,t as i}from"./stdio-B6SZMnln.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.19.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
3
|
`:`
|
|
4
4
|
`)+n,i=r+1,r=e.indexOf(`
|
|
5
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(`
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-DShK9eFX.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-
|
|
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-B6SZMnln.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,8 +1,8 @@
|
|
|
1
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{createNodeTelemetry as se}from"@agimon-ai/log-sink-mcp";import{Server as ce}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as le,ListToolsRequestSchema as ue}from"@modelcontextprotocol/sdk/types.js";import{StdioServerTransport as de}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 fe=g(a);let S=class{async isAvailable(e){let t=this.resolveAceStepPath(e);if(!this.isAceStepCheckout(t))return!1;try{return await fe(`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 fe(`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
2
|
`)}toTomlValue(e){return typeof e==`string`?JSON.stringify(e):String(e)}};S=x([r()],S);const pe={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
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:pe[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 pe[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 me=[`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 he=class{getAvailableGoogleFonts(){return[...me]}validateFontFamily(e){let t=e.toLowerCase();return me.some(e=>e.toLowerCase()===t)}};he=x([r()],he);var ge=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.`}),_e=b.object({frame:O.nonnegative(),volume:D.min(0).max(1)}),ve=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)}),ye=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:ve.optional(),exit:ve.optional(),transition:ye.optional()},be=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()}),xe=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()}),Se=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()}),Ce=b.object({type:b.literal(`audio`),...j,src:A,volume:D.min(0).max(1).optional(),volumeAutomation:b.array(_e).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),toneFrequency:D.positive().optional()}),we=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()}),Te=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()}),Ee=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()}),De=b.discriminatedUnion(`type`,[be,xe,Se,Ce,we,Te,Ee]),M=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=M.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(M).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(De).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:ye.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 N=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}}};N=x([r()],N);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 P=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]}};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`),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=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
|
|
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:pe[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 pe[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 me=[`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 he=class{getAvailableGoogleFonts(){return[...me]}validateFontFamily(e){let t=e.toLowerCase();return me.some(e=>e.toLowerCase()===t)}};he=x([r()],he);var ge=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.`}),_e=b.object({frame:O.nonnegative(),volume:D.min(0).max(1)}),ve=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)}),ye=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:ve.optional(),exit:ve.optional(),transition:ye.optional()},be=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()}),xe=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()}),Se=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()}),Ce=b.object({type:b.literal(`audio`),...j,src:A,volume:D.min(0).max(1).optional(),volumeAutomation:b.array(_e).optional(),playbackRate:D.positive().optional(),muted:k.optional(),loop:k.optional(),trimBefore:D.nonnegative().optional(),trimAfter:D.nonnegative().optional(),toneFrequency:D.positive().optional()}),we=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()}),Te=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()}),Ee=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()}),De=b.discriminatedUnion(`type`,[be,xe,Se,Ce,we,Te,Ee]),Oe=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()}),ke=Oe.extend({emphasis:b.enum([`none`,`keyword`,`stat`,`negation`,`cta`]).default(`none`)}),Ae=b.object({x:D.min(0).max(1).optional(),y:D.min(0).max(1).optional(),position:b.enum([`top`,`center`,`bottom`]).optional()}),je=b.object({text:b.string().optional(),startMs:D.nonnegative(),endMs:D.nonnegative(),tokens:b.array(ke).min(1),placement:Ae.optional()}),Me=b.object({top:D.nonnegative().optional(),right:D.nonnegative().optional(),bottom:D.nonnegative().optional(),left:D.nonnegative().optional()}),Ne=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)}),Pe=b.object({captions:b.array(Oe).default([]),pages:b.array(je).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,Ne]).default(!0),backgroundColor:b.string().default(`rgba(0, 0, 0, 0)`),backgroundRadius:D.nonnegative().default(8),safeZone:Me.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`]})}),Fe={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)},Ie=b.object({...Fe,definesTimeline:k.default(!0),durationMs:D.positive().optional()}),Le=b.object({...Fe,loop:k.default(!0)}),Re=b.object({...Fe,atMs:D.nonnegative(),durationMs:D.positive().optional()}),ze=b.object({voiceover:Ie.optional(),music:Le.optional(),sfx:b.array(Re).default([])}),Be=b.object({family:b.string(),source:b.enum([`google`,`local`]).default(`google`),weights:b.array(O.positive()).optional(),url:b.string().optional()}),Ve=b.object({clips:b.array(De).default([]),backgroundColor:b.string().default(`#000000`),fps:O.positive().default(30),width:O.positive().default(1920),height:O.positive().default(1080),captions:Pe.optional(),globalTransition:ye.optional(),fonts:b.array(Be).optional(),audio:ze.optional()}).passthrough(),He=`${h.sep}public${h.sep}`,Ue=/^(https?:|data:|blob:)/i,We=/\b(undefined|NaN|null)\b|blur\(px\)/i,Ge={Main:{width:1920,height:1080},Vertical:{width:1080,height:1920},Square:{width:1080,height:1080}},Ke=new Set([`video`,`image`,`audio`,`gif`,`lottie`]),qe=new Set([`video`,`image`,`gif`,`lottie`,`text`,`subtitle`]),Je={tiktok:480,reels:420,shorts:420,universal:480},Ye={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 Xe=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(He);return n===-1?null:t.slice(n+He.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(Ue.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`){We.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 Ke.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})=>qe.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={...Ye[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=Je[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=Ve.parse(e);return this.normalizeProvidedValues(e,t)}async validateRender(e){let t=[],n=[],r=Ge[e.compositionId];r||t.push({code:`composition.unknown`,message:`Unknown composition: ${e.compositionId}`,path:`compositionId`});let i=await this.injectAudioDurations(e.inputProps),a=Ve.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 Xe(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 Ze=g(a),Qe=[`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 Ze(`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 Ze(`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[...Qe]}};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 $e=g(a),et=1280,tt=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||tt){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 $e(`ffmpeg`,[`-y`,`-i`,i,`-map`,`0:v:0`,`-map`,`0:a?`,`-vf`,`scale='if(gt(iw,${et}),${et},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
6
|
<session_context>
|
|
7
7
|
This is the Gemini CLI. We are setting up the context for our chat.
|
|
8
8
|
Today's date is ${t} (formatted according to the user's locale).
|
|
@@ -14,4 +14,4 @@ The project's temporary directory is: ${n}
|
|
|
14
14
|
|
|
15
15
|
${r}
|
|
16
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 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 U{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(),L(0,n(F.VideoDownloadService)),I(`design:paramtypes`,[typeof(tt=z!==void 0&&z)==`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 W=class extends U{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`)}}};W=st=x([r(),L(0,n(F.VideoContextService)),I(`design:paramtypes`,[typeof(ot=H!==void 0&&H)==`function`?ot:Object])],W);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 G=class extends U{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`)}}};G=ut=x([r(),L(0,n(F.CaptionService)),I(`design:paramtypes`,[typeof(lt=C!==void 0&&C)==`function`?lt:Object])],G);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 U{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(),L(0,n(F.AceStepMusicService)),I(`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 K=class extends U{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`)}}};K=vt=x([r(),L(0,n(F.VoiceoverService)),I(`design:paramtypes`,[typeof(_t=P!==void 0&&P)==`function`?_t:Object])],K);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 q=class extends U{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`)}}};q=xt=x([r(),L(0,n(F.MediaInfoService)),I(`design:paramtypes`,[typeof(bt=E!==void 0&&E)==`function`?bt:Object])],q);var Ct,wt;const Tt=b.object({srtContent:b.string().describe(`SRT file content as a string`)});let J=class extends U{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`)}}};J=wt=x([r(),L(0,n(F.CaptionService)),I(`design:paramtypes`,[typeof(Ct=C!==void 0&&C)==`function`?Ct:Object])],J);var Et,Dt;const Ot=b.object({});let Y=class extends U{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())}};Y=Dt=x([r(),L(0,n(F.CompositionService)),I(`design:paramtypes`,[typeof(Et=w!==void 0&&w)==`function`?Et:Object])],Y);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 X=class extends U{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`)}}};X=At=x([r(),L(0,n(F.RenderService)),I(`design:paramtypes`,[typeof(kt=N!==void 0&&N)==`function`?kt:Object])],X);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 Z=class extends U{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`)}}};Z=Nt=x([r(),L(0,n(F.RenderService)),I(`design:paramtypes`,[typeof(Mt=N!==void 0&&N)==`function`?Mt:Object])],Z);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 U{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(),L(0,n(F.VideoDownloadService)),I(`design:paramtypes`,[typeof(Lt=z!==void 0&&z)==`function`?Lt:Object])],Bt);var Vt,Q;const Ht=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{Q=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Ht}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.toJSONSchema(Ht,{reused:`inline`})}}async execute(e){try{let t=Ht.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([r(),L(0,n(F.VideoAnalysisService)),I(`design:paramtypes`,[typeof(Vt=B!==void 0&&B)==`function`?Vt:Object])],$);const Ut=new t(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(he).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()}),Wt=new t(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(gt).inSingletonScope(),e.bind(F.Tool).to(K).inSingletonScope(),e.bind(F.Tool).to(Bt).inSingletonScope(),e.bind(F.Tool).to(at).inSingletonScope(),e.bind(F.Tool).to($).inSingletonScope(),e.bind(F.Tool).to(W).inSingletonScope()});function Gt(t={}){let n=new e;return n.load(Ut,Wt),n.bind(F.ProxyUrl).toConstantValue(t.proxyUrl??null),n}const Kt=`@agimon-ai/video-editor-mcp`;let qt;function Jt(){return qt??=se({serviceName:Kt,workspaceRoot:process.cwd()}),qt}async function Yt(e,t,n){let r=await Jt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),n}})}const Xt=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function Zt(e){return Xt.has(e)}async function Qt(e,t,n,r){return Zt(e)?r.run(()=>t.execute(n)):t.execute(n)}function $t(e){let t=new ce({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(F.Tool),r=new ge,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(ue,async()=>Yt(`mcp.server.list_tools`,{"mcp.service":Kt},async()=>({tools:n.map(e=>e.getDefinition())}))),t.setRequestHandler(le,async e=>{let{name:t,arguments:n}=e.params;return Yt(`mcp.server.call_tool`,{"mcp.service":Kt,"mcp.tool.name":t,"mcp.tool.intensive":Zt(t)},async()=>{let e=i.get(t);if(!e)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let a=ae(n??{},e.getInputSchema());try{return await Qt(t,e,a,r)}catch(n){if(n instanceof b.ZodError)return{content:[{type:`text`,text:oe(n,{schemaName:t,schema:e.getInputSchema()})}],isError:!0};throw n}})}),t}var en=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new de,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{ye as A,Ce as C,Ee as D,xe as E,we as O,ve as S,Te as T,Ye as _,Z as a,ze as b,J as c,G as d,W as f,N as g,F as h,$ as i,be as j,Se as k,q as l,H as m,$t as n,X as o,U as p,Gt as r,Y as s,en as t,K as u,Ne as v,De as w,Be as x,M as y};
|
|
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 nt,rt;const it=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)`)}),at=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let ot=class extends H{static{rt=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return it}getDefinition(){return{name:rt.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(it,{reused:`inline`})}}async execute(e){try{let t=it.parse(e),n=h.resolve(at(),`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`)}}};ot=rt=x([r(),I(0,n(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(nt=R!==void 0&&R)==`function`?nt:Object])],ot);var st,ct;const lt=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{ct=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return lt}getDefinition(){return{name:ct.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.toJSONSchema(lt,{reused:`inline`})}}async execute(e){try{let t=lt.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=ct=x([r(),I(0,n(P.VideoContextService)),F(`design:paramtypes`,[typeof(st=V!==void 0&&V)==`function`?st:Object])],U);var ut,dt;const ft=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{dt=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return ft}getDefinition(){return{name:dt.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.toJSONSchema(ft,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=ft.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=dt=x([r(),I(0,n(P.CaptionService)),F(`design:paramtypes`,[typeof(ut=C!==void 0&&C)==`function`?ut:Object])],W);var pt,mt;const ht=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`)}),gt=()=>s(h.resolve(import.meta.dirname,`../package.json`))?h.resolve(import.meta.dirname,`..`):h.resolve(import.meta.dirname,`../..`);let _t=class extends H{static{mt=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return ht}getDefinition(){return{name:mt.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(ht,{reused:`inline`})}}async execute(e){try{let t=ht.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(gt(),`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(gt(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return h.join(t,`${n}.${r}`)}};_t=mt=x([r(),I(0,n(P.AceStepMusicService)),F(`design:paramtypes`,[typeof(pt=S!==void 0&&S)==`function`?pt:Object])],_t);var vt,yt;const bt=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{yt=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return bt}getDefinition(){return{name:yt.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.toJSONSchema(bt,{reused:`inline`})}}async execute(e){try{let t=bt.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=yt=x([r(),I(0,n(P.VoiceoverService)),F(`design:paramtypes`,[typeof(vt=N!==void 0&&N)==`function`?vt:Object])],G);var xt,St;const Ct=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{St=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return Ct}getDefinition(){return{name:St.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.toJSONSchema(Ct,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=Ct.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=St=x([r(),I(0,n(P.MediaInfoService)),F(`design:paramtypes`,[typeof(xt=E!==void 0&&E)==`function`?xt:Object])],K);var wt,Tt;const Et=b.object({srtContent:b.string().describe(`SRT file content as a string`)});let q=class extends H{static{Tt=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return Et}getDefinition(){return{name:Tt.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.toJSONSchema(Et,{reused:`inline`})}}async execute(e){try{let t=Et.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=Tt=x([r(),I(0,n(P.CaptionService)),F(`design:paramtypes`,[typeof(wt=C!==void 0&&C)==`function`?wt:Object])],q);var Dt,Ot;const kt=b.object({});let J=class extends H{static{Ot=this}static TOOL_NAME=`list_compositions`;constructor(e){super(),this.compositionService=e}getInputSchema(){return kt}getDefinition(){return{name:Ot.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.toJSONSchema(kt,{reused:`inline`})}}async execute(){return this.successJson(this.compositionService.listCompositions())}};J=Ot=x([r(),I(0,n(P.CompositionService)),F(`design:paramtypes`,[typeof(Dt=w!==void 0&&w)==`function`?Dt:Object])],J);var At,jt;const Mt=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{jt=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return Mt}getDefinition(){return{name:jt.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,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,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=jt=x([r(),I(0,n(P.RenderService)),F(`design:paramtypes`,[typeof(At=M!==void 0&&M)==`function`?At:Object])],Y);var Nt,Pt;const Ft=b.preprocess(e=>{if(typeof e!=`string`)return e;try{return JSON.parse(e)}catch{return e}},b.record(b.string(),b.unknown())),It=b.preprocess(e=>{if(typeof e!=`string`)return e;let t=e.trim().toLowerCase();return t===`true`?!0:t===`false`?!1:e},b.boolean()),Lt=b.object({compositionId:b.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:Ft.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:It.optional().describe(`Validate props and assets without rendering a video`)});let X=class extends H{static{Pt=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return Lt}getDefinition(){return{name:Pt.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(Lt,{reused:`inline`})}}async execute(e){try{let t=Lt.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 Xe?{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=Pt=x([r(),I(0,n(P.RenderService)),F(`design:paramtypes`,[typeof(Nt=M!==void 0&&M)==`function`?Nt:Object])],X);var Rt,zt;const Bt=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 Vt=class extends H{static{zt=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Bt}getDefinition(){return{name:zt.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.toJSONSchema(Bt,{reused:`inline`})}}async execute(e){try{let t=Bt.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`)}}};Vt=zt=x([r(),I(0,n(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(Rt=R!==void 0&&R)==`function`?Rt:Object])],Vt);var Ht,Z;const Ut=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 Q=class extends H{static{Z=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Ut}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(Ut,{reused:`inline`})}}async execute(e){try{let t=Ut.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=Z=x([r(),I(0,n(P.VideoAnalysisService)),F(`design:paramtypes`,[typeof(Ht=z!==void 0&&z)==`function`?Ht:Object])],Q);const Wt=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(he).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()}),Gt=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(_t).inSingletonScope(),e.bind(P.Tool).to(G).inSingletonScope(),e.bind(P.Tool).to(Vt).inSingletonScope(),e.bind(P.Tool).to(ot).inSingletonScope(),e.bind(P.Tool).to(Q).inSingletonScope(),e.bind(P.Tool).to(U).inSingletonScope()});function Kt(t={}){let n=new e;return n.load(Wt,Gt),n.bind(P.ProxyUrl).toConstantValue(t.proxyUrl??null),n}const $=`@agimon-ai/video-editor-mcp`;let qt;function Jt(){return qt??=se({packageName:$,serviceName:$,workspaceRoot:process.cwd()}),qt}async function Yt(e,t,n){let r=await Jt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),n}})}const Xt=new Set([`render_video`,`preview_frame`,`generate_music`,`generate_voiceover`,`download_clip`,`extract_video_context`,`analyze_video`]);function Zt(e){return Xt.has(e)}async function Qt(e,t,n,r){return Zt(e)?r.run(()=>t.execute(n)):t.execute(n)}function $t(e){let t=new ce({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(P.Tool),r=new ge,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(ue,async()=>Yt(`mcp.server.list_tools`,{"mcp.service":$},async()=>({tools:n.map(e=>e.getDefinition())}))),t.setRequestHandler(le,async e=>{let{name:t,arguments:n}=e.params;return Yt(`mcp.server.call_tool`,{"mcp.service":$,"mcp.tool.name":t,"mcp.tool.intensive":Zt(t)},async()=>{let e=i.get(t);if(!e)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let a=ae(n??{},e.getInputSchema());try{return await Qt(t,e,a,r)}catch(n){if(n instanceof b.ZodError)return{content:[{type:`text`,text:oe(n,{schemaName:t,schema:e.getInputSchema()})}],isError:!0};throw n}})}),t}var en=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new de,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{ye as A,Ce as C,Ee as D,xe as E,we as O,ve as S,Te as T,Xe as _,X as a,Be as b,q as c,W as d,U as f,M as g,P as h,Q as i,be as j,Se as k,K as l,V as m,$t as n,Y as o,H as p,Kt as r,J as s,en as t,G as u,Pe as v,De as w,Ve as x,Oe as y};
|
|
@@ -14,4 +14,4 @@ The project's temporary directory is: ${n}
|
|
|
14
14
|
|
|
15
15
|
${r}
|
|
16
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 Ye,Xe;const Ze=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)`)}),Qe=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let $e=class extends H{static{Xe=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ze}getDefinition(){return{name:Xe.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(Ze,{reused:`inline`})}}async execute(e){try{let t=Ze.parse(e),n=p.default.resolve(Qe(),`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`)}}};$e=Xe=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(Ye=R!==void 0&&R)==`function`?Ye:Object])],$e);var et,tt;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{tt=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return U}getDefinition(){return{name:tt.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=tt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoContextService)),F(`design:paramtypes`,[typeof(et=V!==void 0&&V)==`function`?et:Object])],W);var nt,rt;const it=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{rt=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return it}getDefinition(){return{name:rt.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.z.toJSONSchema(it,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=it.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=rt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(nt=C!==void 0&&C)==`function`?nt:Object])],G);var at,ot;const st=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`)}),ct=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let lt=class extends H{static{ot=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return st}getDefinition(){return{name:ot.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(st,{reused:`inline`})}}async execute(e){try{let t=st.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(ct(),`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(ct(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return p.default.join(t,`${n}.${r}`)}};lt=ot=x([(0,c.injectable)(),I(0,(0,c.inject)(P.AceStepMusicService)),F(`design:paramtypes`,[typeof(at=S!==void 0&&S)==`function`?at:Object])],lt);var ut,dt;const ft=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{dt=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return ft}getDefinition(){return{name:dt.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.z.toJSONSchema(ft,{reused:`inline`})}}async execute(e){try{let t=ft.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=dt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VoiceoverService)),F(`design:paramtypes`,[typeof(ut=N!==void 0&&N)==`function`?ut:Object])],K);var pt,mt;const ht=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{mt=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return ht}getDefinition(){return{name:mt.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.z.toJSONSchema(ht,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=ht.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=mt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.MediaInfoService)),F(`design:paramtypes`,[typeof(pt=E!==void 0&&E)==`function`?pt:Object])],q);var gt,_t;const vt=b.z.object({srtContent:b.z.string().describe(`SRT file content as a string`)});let J=class extends H{static{_t=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return vt}getDefinition(){return{name:_t.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.z.toJSONSchema(vt,{reused:`inline`})}}async execute(e){try{let t=vt.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=_t=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(gt=C!==void 0&&C)==`function`?gt:Object])],J);var yt,bt;const xt=b.z.object({});let Y=class extends H{static{bt=this}static TOOL_NAME=`list_compositions`;constructor(e){super(),this.compositionService=e}getInputSchema(){return xt}getDefinition(){return{name:bt.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.z.toJSONSchema(xt,{reused:`inline`})}}async execute(){return this.successJson(this.compositionService.listCompositions())}};Y=bt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CompositionService)),F(`design:paramtypes`,[typeof(yt=w!==void 0&&w)==`function`?yt:Object])],Y);var St,X;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 Z=class extends H{static{X=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return Ct}getDefinition(){return{name:X.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`)}}};Z=X=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(St=M!==void 0&&M)==`function`?St:Object])],Z);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 Q=class extends H{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 Ue?{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=Tt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(wt=M!==void 0&&M)==`function`?wt:Object])],Q);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 H{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)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(kt=R!==void 0&&R)==`function`?kt:Object])],Mt);var Nt,Pt;const Ft=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{Pt=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return Ft}getDefinition(){return{name:Pt.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(Ft,{reused:`inline`})}}async execute(e){try{let t=Ft.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`)}}};$=Pt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoAnalysisService)),F(`design:paramtypes`,[typeof(Nt=z!==void 0&&z)==`function`?Nt:Object])],$);const It=new c.ContainerModule(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(ce).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()}),Lt=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(lt).inSingletonScope(),e.bind(P.Tool).to(K).inSingletonScope(),e.bind(P.Tool).to(Mt).inSingletonScope(),e.bind(P.Tool).to($e).inSingletonScope(),e.bind(P.Tool).to($).inSingletonScope(),e.bind(P.Tool).to(W).inSingletonScope()});function Rt(e={}){let t=new c.Container;return t.load(It,Lt),t.bind(P.ProxyUrl).toConstantValue(e.proxyUrl??null),t}const zt=`@agimon-ai/video-editor-mcp`;let Bt;function Vt(){return Bt??=(0,te.createNodeTelemetry)({serviceName:zt,workspaceRoot:process.cwd()}),Bt}async function Ht(e,t,n){let r=await Vt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),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 ne.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(P.Tool),r=new le,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(re.ListToolsRequestSchema,async()=>Ht(`mcp.server.list_tools`,{"mcp.service":zt},async()=>({tools:n.map(e=>e.getDefinition())}))),t.setRequestHandler(re.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;return Ht(`mcp.server.call_tool`,{"mcp.service":zt,"mcp.tool.name":t,"mcp.tool.intensive":Wt(t)},async()=>{let e=i.get(t);if(!e)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let a=(0,ee.coerceArgs)(n??{},e.getInputSchema());try{return await Gt(t,e,a,r)}catch(n){if(n instanceof b.z.ZodError)return{content:[{type:`text`,text:(0,ee.formatZodError)(n,{schemaName:t,schema:e.getInputSchema()})}],isError:!0};throw n}})}),t}var qt=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new ie.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 fe}}),Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return ge}}),Object.defineProperty(exports,`D`,{enumerable:!0,get:function(){return ye}}),Object.defineProperty(exports,`E`,{enumerable:!0,get:function(){return me}}),Object.defineProperty(exports,`M`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`O`,{enumerable:!0,get:function(){return _e}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return de}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return ve}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return Ue}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Q}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return Ne}}),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 pe}}),Object.defineProperty(exports,`k`,{enumerable:!0,get:function(){return he}}),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 Kt}}),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 Rt}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return qt}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return De}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return be}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return Pe}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return xe}});
|
|
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 Ye,Xe;const Ze=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)`)}),Qe=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let $e=class extends H{static{Xe=this}static TOOL_NAME=`download_clip`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Ze}getDefinition(){return{name:Xe.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(Ze,{reused:`inline`})}}async execute(e){try{let t=Ze.parse(e),n=p.default.resolve(Qe(),`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`)}}};$e=Xe=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(Ye=R!==void 0&&R)==`function`?Ye:Object])],$e);var et,tt;const nt=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 U=class extends H{static{tt=this}static TOOL_NAME=`extract_video_context`;constructor(e){super(),this.videoContextService=e}getInputSchema(){return nt}getDefinition(){return{name:tt.TOOL_NAME,description:`Break a local video into timestamped transcript segments with paged filmstrip images for image-understanding tools`,inputSchema:b.z.toJSONSchema(nt,{reused:`inline`})}}async execute(e){try{let t=nt.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=tt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoContextService)),F(`design:paramtypes`,[typeof(et=V!==void 0&&V)==`function`?et:Object])],U);var rt,it;const at=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 W=class extends H{static{it=this}static TOOL_NAME=`generate_captions`;constructor(e){super(),this.captionService=e}getInputSchema(){return at}getDefinition(){return{name:it.TOOL_NAME,description:`Create TikTok-style caption pages from a caption array for video overlay`,inputSchema:b.z.toJSONSchema(at,{reused:`inline`})}}async execute(e){try{let{captions:t,combineMs:n=100,maxWordsPerPage:r,maxCharsPerLine:i,platform:a,preset:o}=at.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=it=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(rt=C!==void 0&&C)==`function`?rt:Object])],W);var ot,st;const ct=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`)}),lt=()=>(0,u.existsSync)(p.default.resolve(__dirname,`../package.json`))?p.default.resolve(__dirname,`..`):p.default.resolve(__dirname,`../..`);let ut=class extends H{static{st=this}static TOOL_NAME=`generate_music`;constructor(e){super(),this.aceStepMusicService=e}getInputSchema(){return ct}getDefinition(){return{name:st.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(ct,{reused:`inline`})}}async execute(e){try{let t=ct.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(lt(),`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(lt(),`public/music`)),n=e.filename??`ace-step-music`,r=e.audioFormat??`wav`;return p.default.join(t,`${n}.${r}`)}};ut=st=x([(0,c.injectable)(),I(0,(0,c.inject)(P.AceStepMusicService)),F(`design:paramtypes`,[typeof(ot=S!==void 0&&S)==`function`?ot:Object])],ut);var dt,G;const ft=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{G=this}static TOOL_NAME=`generate_voiceover`;constructor(e){super(),this.voiceoverService=e}getInputSchema(){return ft}getDefinition(){return{name:G.TOOL_NAME,description:`Generate voiceover audio from text using Kokoro TTS local engine`,inputSchema:b.z.toJSONSchema(ft,{reused:`inline`})}}async execute(e){try{let t=ft.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=G=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VoiceoverService)),F(`design:paramtypes`,[typeof(dt=N!==void 0&&N)==`function`?dt:Object])],K);var pt,mt;const ht=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{mt=this}static TOOL_NAME=`get_media_info`;constructor(e){super(),this.mediaInfoService=e}getInputSchema(){return ht}getDefinition(){return{name:mt.TOOL_NAME,description:`Get duration, dimensions, and decodability info for video/audio files`,inputSchema:b.z.toJSONSchema(ht,{reused:`inline`})}}async execute(e){try{let{src:t,type:n=`video`}=ht.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=mt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.MediaInfoService)),F(`design:paramtypes`,[typeof(pt=E!==void 0&&E)==`function`?pt:Object])],q);var gt,_t;const vt=b.z.object({srtContent:b.z.string().describe(`SRT file content as a string`)});let J=class extends H{static{_t=this}static TOOL_NAME=`import_srt`;constructor(e){super(),this.captionService=e}getInputSchema(){return vt}getDefinition(){return{name:_t.TOOL_NAME,description:`Parse SRT subtitle file content into structured Caption format for video overlay`,inputSchema:b.z.toJSONSchema(vt,{reused:`inline`})}}async execute(e){try{let t=vt.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=_t=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CaptionService)),F(`design:paramtypes`,[typeof(gt=C!==void 0&&C)==`function`?gt:Object])],J);var yt,bt;const xt=b.z.object({});let Y=class extends H{static{bt=this}static TOOL_NAME=`list_compositions`;constructor(e){super(),this.compositionService=e}getInputSchema(){return xt}getDefinition(){return{name:bt.TOOL_NAME,description:`List all available Remotion compositions with their schemas, dimensions, and format info`,inputSchema:b.z.toJSONSchema(xt,{reused:`inline`})}}async execute(){return this.successJson(this.compositionService.listCompositions())}};Y=bt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.CompositionService)),F(`design:paramtypes`,[typeof(yt=w!==void 0&&w)==`function`?yt:Object])],Y);var St,Ct;const wt=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 H{static{Ct=this}static TOOL_NAME=`preview_frame`;constructor(e){super(),this.renderService=e}getInputSchema(){return wt}getDefinition(){return{name:Ct.TOOL_NAME,description:`Render a single frame from a composition as a PNG or JPEG image for preview`,inputSchema:b.z.toJSONSchema(wt,{reused:`inline`})}}async execute(e){try{let t=wt.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=Ct=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(St=M!==void 0&&M)==`function`?St:Object])],X);var Tt,Et;const Dt=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())),Ot=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()),kt=b.z.object({compositionId:b.z.string().describe(`Remotion composition ID (e.g., "Main", "Vertical", "Square")`),inputProps:Dt.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:Ot.optional().describe(`Validate props and assets without rendering a video`)});let Z=class extends H{static{Et=this}static TOOL_NAME=`render_video`;constructor(e){super(),this.renderService=e}getInputSchema(){return kt}getDefinition(){return{name:Et.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(kt,{reused:`inline`})}}async execute(e){try{let t=kt.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 Ue?{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=Et=x([(0,c.injectable)(),I(0,(0,c.inject)(P.RenderService)),F(`design:paramtypes`,[typeof(Tt=M!==void 0&&M)==`function`?Tt:Object])],Z);var At,jt;const Mt=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 Nt=class extends H{static{jt=this}static TOOL_NAME=`search_videos`;constructor(e){super(),this.videoDownloadService=e}getInputSchema(){return Mt}getDefinition(){return{name:jt.TOOL_NAME,description:`Search YouTube for videos by query. Returns video metadata (title, duration, URL, channel) without downloading.`,inputSchema:b.z.toJSONSchema(Mt,{reused:`inline`})}}async execute(e){try{let t=Mt.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`)}}};Nt=jt=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoDownloadService)),F(`design:paramtypes`,[typeof(At=R!==void 0&&R)==`function`?At:Object])],Nt);var Pt,Ft;const It=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 Q=class extends H{static{Ft=this}static TOOL_NAME=`analyze_video`;constructor(e){super(),this.videoAnalysisService=e}getInputSchema(){return It}getDefinition(){return{name:Ft.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(It,{reused:`inline`})}}async execute(e){try{let t=It.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=Ft=x([(0,c.injectable)(),I(0,(0,c.inject)(P.VideoAnalysisService)),F(`design:paramtypes`,[typeof(Pt=z!==void 0&&z)==`function`?Pt:Object])],Q);const Lt=new c.ContainerModule(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(ce).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()}),Rt=new c.ContainerModule(e=>{e.bind(P.Tool).to(Z).inSingletonScope(),e.bind(P.Tool).to(Y).inSingletonScope(),e.bind(P.Tool).to(q).inSingletonScope(),e.bind(P.Tool).to(X).inSingletonScope(),e.bind(P.Tool).to(W).inSingletonScope(),e.bind(P.Tool).to(J).inSingletonScope(),e.bind(P.Tool).to(ut).inSingletonScope(),e.bind(P.Tool).to(K).inSingletonScope(),e.bind(P.Tool).to(Nt).inSingletonScope(),e.bind(P.Tool).to($e).inSingletonScope(),e.bind(P.Tool).to(Q).inSingletonScope(),e.bind(P.Tool).to(U).inSingletonScope()});function zt(e={}){let t=new c.Container;return t.load(Lt,Rt),t.bind(P.ProxyUrl).toConstantValue(e.proxyUrl??null),t}const $=`@agimon-ai/video-editor-mcp`;let Bt;function Vt(){return Bt??=(0,te.createNodeTelemetry)({packageName:$,serviceName:$,workspaceRoot:process.cwd()}),Bt}async function Ht(e,t,n){let r=await Vt();return r.runInSpan(e,{attributes:t},async()=>{try{return await n()}catch(n){throw r.logger.error(`${e} failed`,{attributes:t,exception:n}),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 ne.Server({name:`video-editor-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),n=e.getAll(P.Tool),r=new le,i=new Map;for(let e of n)i.set(e.getDefinition().name,e);return t.setRequestHandler(re.ListToolsRequestSchema,async()=>Ht(`mcp.server.list_tools`,{"mcp.service":$},async()=>({tools:n.map(e=>e.getDefinition())}))),t.setRequestHandler(re.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;return Ht(`mcp.server.call_tool`,{"mcp.service":$,"mcp.tool.name":t,"mcp.tool.intensive":Wt(t)},async()=>{let e=i.get(t);if(!e)return{content:[{type:`text`,text:`Unknown tool: ${t}`}],isError:!0};let a=(0,ee.coerceArgs)(n??{},e.getInputSchema());try{return await Gt(t,e,a,r)}catch(n){if(n instanceof b.z.ZodError)return{content:[{type:`text`,text:(0,ee.formatZodError)(n,{schemaName:t,schema:e.getInputSchema()})}],isError:!0};throw n}})}),t}var qt=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new ie.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 fe}}),Object.defineProperty(exports,`C`,{enumerable:!0,get:function(){return ge}}),Object.defineProperty(exports,`D`,{enumerable:!0,get:function(){return ye}}),Object.defineProperty(exports,`E`,{enumerable:!0,get:function(){return me}}),Object.defineProperty(exports,`M`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`O`,{enumerable:!0,get:function(){return _e}}),Object.defineProperty(exports,`S`,{enumerable:!0,get:function(){return de}}),Object.defineProperty(exports,`T`,{enumerable:!0,get:function(){return ve}}),Object.defineProperty(exports,`_`,{enumerable:!0,get:function(){return Ue}}),Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Z}}),Object.defineProperty(exports,`b`,{enumerable:!0,get:function(){return Ne}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return J}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return W}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return U}}),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 Q}}),Object.defineProperty(exports,`j`,{enumerable:!0,get:function(){return pe}}),Object.defineProperty(exports,`k`,{enumerable:!0,get:function(){return he}}),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 Kt}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return X}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return H}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return zt}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return Y}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return qt}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`v`,{enumerable:!0,get:function(){return De}}),Object.defineProperty(exports,`w`,{enumerable:!0,get:function(){return be}}),Object.defineProperty(exports,`x`,{enumerable:!0,get:function(){return Pe}}),Object.defineProperty(exports,`y`,{enumerable:!0,get:function(){return xe}});
|
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.
|
|
4
|
+
"version": "0.19.1",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"remotion": "4.0.454",
|
|
46
46
|
"sharp": "0.34.5",
|
|
47
47
|
"zod": "4.4.1",
|
|
48
|
-
"@agimon-ai/foundation-port-registry": "0.
|
|
49
|
-
"@agimon-ai/foundation-process-registry": "0.
|
|
50
|
-
"@agimon-ai/log-sink-mcp": "0.
|
|
51
|
-
"@agimon-ai/foundation-validator": "0.
|
|
48
|
+
"@agimon-ai/foundation-port-registry": "0.19.1",
|
|
49
|
+
"@agimon-ai/foundation-process-registry": "0.19.1",
|
|
50
|
+
"@agimon-ai/log-sink-mcp": "0.19.1",
|
|
51
|
+
"@agimon-ai/foundation-validator": "0.16.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "25.6.0",
|