@tikagent/video-editor-cli 1.5.0-preview.1 → 1.5.0-preview.2

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.
@@ -1 +1 @@
1
- "use strict";const e=require("fs"),r=require("os"),o=require("path"),t=require("http"),n=require("https"),{spawn:s}=require("./processes"),{Transform:i}=require("stream"),{pipeline:a}=require("stream/promises"),{extractZip:c}=require("./browser-zip"),l="153.0.8010.52";function m(e=process.platform,r=process.arch){return{"darwin-arm64":"mac-arm64","darwin-x64":"mac-x64","linux-x64":"linux64","linux-arm64":"linux-arm64","win32-x64":"win64","win32-ia32":"win32"}[`${e}-${r}`]}function u(e){return o.join("chrome-"+e,e.startsWith("mac-")?"Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing":e.startsWith("win")?"chrome.exe":"chrome")}function w(r){try{return!!e.statSync(r).isFile()&&(e.accessSync(r,"win32"===process.platform?e.constants.F_OK:e.constants.X_OK),!0)}catch{return!1}}function d(e){if(o.isAbsolute(e)||/[/\\]/.test(e))return w(o.resolve(e))?o.resolve(e):null;for(const r of(process.env.PATH||"").split(o.delimiter).filter(Boolean))for(const t of"win32"===process.platform?["",".exe"]:[""]){const n=o.resolve(r,e+t);if(w(n))return n}return null}function p(){return["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Chromium.app/Contents/MacOS/Chromium",...[process.env.PROGRAMFILES,process.env["PROGRAMFILES(X86)"],process.env.LOCALAPPDATA].filter(Boolean).map(e=>o.join(e,"Google/Chrome/Application/chrome.exe")),"google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"]}async function h(r,o,t){const s=new AbortController,c=setTimeout(()=>s.abort(),9e5);try{const c=await async function e(r,o=0){const t=new URL(r);if("https:"!==t.protocol||t.username||t.password)throw new Error("Browser downloads require an HTTPS URL without credentials");return new Promise((r,i)=>{const a=n.get(t,{signal:s.signal,timeout:3e4},n=>{if(n.on("error",i),[301,302,303,307,308].includes(n.statusCode)){if(n.resume(),!n.headers.location||o>=5)return i(new Error("Too many browser download redirects"));try{r(e(new URL(n.headers.location,t),o+1))}catch(e){i(e)}}else 200!==n.statusCode?(n.resume(),i(new Error(`Browser download returned HTTP ${n.statusCode}`))):r(n)});a.on("timeout",()=>a.destroy(new Error("Browser download timed out"))),a.on("error",i)})}(r),l=Number(c.headers["content-length"]||0);let m=0,u=Date.now();const w=new i({transform(e,r,o){if(m+=e.length,m>536870912)return o(new Error("Browser download exceeds 512 MiB"));Date.now()-u>2e3&&(t(`Downloading browser: ${Math.round(m/1048576)}${l?"/"+Math.round(l/1048576):""} MiB`),u=Date.now()),o(null,e)}});if(await a(c,w,e.createWriteStream(o,{flags:"wx"}),{signal:s.signal}),!m||l&&m!==l)throw new Error("Incomplete browser download")}finally{clearTimeout(c),s.abort()}}async function f(n){const i=e.mkdtempSync(o.join(r.tmpdir(),"tik-browser-check-")),a=require("crypto").randomBytes(16).toString("hex");let c,l,m,u,w="";const d=new Promise(e=>{u=e}),p=t.createServer((e,r)=>{e.url==="/"+a?(r.writeHead(200,{"Content-Type":"text/html"}),r.end(`<script>fetch("/${a}/ready")<\/script>`)):e.url===`/${a}/ready`?(r.end("ok"),u()):(r.writeHead(404),r.end())});try{await new Promise((e,r)=>{p.once("error",r),p.listen(0,"127.0.0.1",e)}),c=s(n,["--headless=new","--no-first-run","--no-default-browser-check",..."darwin"===process.platform?["--use-mock-keychain"]:[],"--user-data-dir="+i,`http://127.0.0.1:${p.address().port}/${a}`],{stdio:["ignore","ignore","pipe"],windowsHide:!0}),l=new Promise(e=>c.once("close",e)),c.stderr.on("data",e=>{w=(w+e).slice(-600)}),await Promise.race([d,new Promise((e,r)=>{c.once("error",r),c.once("close",e=>r(new Error(`Chrome exited (${e})`))),m=setTimeout(()=>r(new Error("Chrome startup timed out")),3e4)})])}catch(e){throw new Error(`Downloaded Chrome could not run: ${e.message}. Check OS compatibility and, on Linux, required system libraries. ${w}`)}finally{if(clearTimeout(m),c&&null===c.exitCode&&null===c.signalCode){c.kill();const e=setTimeout(()=>c.kill("SIGKILL"),3e3);await l,clearTimeout(e)}p.closeAllConnections?.(),await new Promise(e=>p.close(e)),e.rmSync(i,{recursive:!0,force:!0})}}module.exports={ensureBrowser:async function(t,{cacheRoot:n=o.join(r.homedir(),".tik-video-editor-cli","browsers"),env:s=process.env,platform:i=process.platform,arch:a=process.arch,candidates:y=p(),download:g=h,verify:C=f,log:b=e=>process.stderr.write(e+"\n")}={}){const x=t||s.CHROME_PATH;if(x){const e=d(x);if(!e)throw new Error("Browser is not executable: "+x+"; fix --browser / CHROME_PATH or unset it for automatic setup");return e}const E=m(i,a),A=E&&o.join(n,l,E),T=E&&o.join(A,u(E)),$=()=>{try{return w(T)&&e.readFileSync(o.join(A,".complete"),"utf8")===l}catch{return!1}};if(E&&$())return T;for(const e of y){const r=d(e);if(r)return r}if(!E)throw new Error(`Automatic browser setup is unavailable on ${i}/${a}; pass --browser <path> or set CHROME_PATH`);const S=`${(s.FABLECUT_BROWSER_DOWNLOAD_BASE_URL||"https://cdn.npmmirror.com/binaries/chrome-for-testing").replace(/\/+$/,"")}/${l}/${E}/chrome-${E}.zip`,v=o.dirname(A);await e.promises.mkdir(v,{recursive:!0});const B=await e.promises.mkdtemp(o.join(v,".install-"));try{b(`Preparing Chrome for Testing ${l} (${E}); this download is cached for future exports.`);const r=o.join(B,"browser.zip"),t=o.join(B,"unpacked");await g(S,r,b),await e.promises.mkdir(t),b("Extracting browser…"),await c(r,t);const n=o.join(t,u(E));if(!w(n))throw new Error("Browser archive is missing its executable");if(await C(n),await e.promises.writeFile(o.join(t,".complete"),l),!$())try{await e.promises.rename(t,A)}catch(e){if(!$())throw new Error(`${e.message}; remove the incomplete cache at ${A} and retry`)}return b("Browser ready."),T}catch(e){throw new Error(`Automatic browser setup failed: ${e.message}. Retry export, set FABLECUT_BROWSER_DOWNLOAD_BASE_URL to a trusted HTTPS mirror, or use --browser <path>.`)}finally{await e.promises.rm(B,{recursive:!0,force:!0})}},downloadArchive:h,verifyBrowser:f,platformKey:m,executable:u,VERSION:l};
1
+ "use strict";const e=require("fs"),r=require("os"),o=require("path"),t=require("http"),n=require("https"),{spawn:s}=require("./processes"),{Transform:i}=require("stream"),{pipeline:a}=require("stream/promises"),{extractZip:c}=require("./browser-zip"),l="153.0.8010.52";function m(e=process.platform,r=process.arch){return{"darwin-arm64":"mac-arm64","darwin-x64":"mac-x64","linux-x64":"linux64","linux-arm64":"linux-arm64","win32-x64":"win64","win32-ia32":"win32"}[`${e}-${r}`]}function u(e){return o.join("chrome-"+e,e.startsWith("mac-")?"Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing":e.startsWith("win")?"chrome.exe":"chrome")}function w(r){try{return!!e.statSync(r).isFile()&&(e.accessSync(r,"win32"===process.platform?e.constants.F_OK:e.constants.X_OK),!0)}catch{return!1}}function d(e){if(o.isAbsolute(e)||/[/\\]/.test(e))return w(o.resolve(e))?o.resolve(e):null;for(const r of(process.env.PATH||"").split(o.delimiter).filter(Boolean))for(const t of"win32"===process.platform?["",".exe"]:[""]){const n=o.resolve(r,e+t);if(w(n))return n}return null}function p(){return["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Chromium.app/Contents/MacOS/Chromium",...[process.env.PROGRAMFILES,process.env["PROGRAMFILES(X86)"],process.env.LOCALAPPDATA].filter(Boolean).map(e=>o.join(e,"Google/Chrome/Application/chrome.exe")),"google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"]}async function h(r,o,t){const s=new AbortController,c=setTimeout(()=>s.abort(),9e5);try{const c=await async function e(r,o=0){const t=new URL(r);if("https:"!==t.protocol||t.username||t.password)throw new Error("Browser downloads require an HTTPS URL without credentials");return new Promise((r,i)=>{const a=n.get(t,{signal:s.signal,timeout:3e4},n=>{if(n.on("error",i),[301,302,303,307,308].includes(n.statusCode)){if(n.resume(),!n.headers.location||o>=5)return i(new Error("Too many browser download redirects"));try{r(e(new URL(n.headers.location,t),o+1))}catch(e){i(e)}}else 200!==n.statusCode?(n.resume(),i(new Error(`Browser download returned HTTP ${n.statusCode}`))):r(n)});a.on("timeout",()=>a.destroy(new Error("Browser download timed out"))),a.on("error",i)})}(r),l=Number(c.headers["content-length"]||0);let m=0,u=Date.now();const w=new i({transform(e,r,o){if(m+=e.length,m>536870912)return o(new Error("Browser download exceeds 512 MiB"));Date.now()-u>2e3&&(t(`Downloading browser: ${Math.round(m/1048576)}${l?"/"+Math.round(l/1048576):""} MiB`),u=Date.now()),o(null,e)}});if(await a(c,w,e.createWriteStream(o,{flags:"wx"}),{signal:s.signal}),!m||l&&m!==l)throw new Error("Incomplete browser download")}finally{clearTimeout(c),s.abort()}}async function f(n){const i=e.mkdtempSync(o.join(r.tmpdir(),"tik-browser-check-")),a=require("crypto").randomBytes(16).toString("hex");let c,l,m,u,w="";const d=new Promise(e=>{u=e}),p=t.createServer((e,r)=>{e.url==="/"+a?(r.writeHead(200,{"Content-Type":"text/html"}),r.end(`<script>fetch("/${a}/ready")<\/script>`)):e.url===`/${a}/ready`?(r.end("ok"),u()):(r.writeHead(404),r.end())});try{await new Promise((e,r)=>{p.once("error",r),p.listen(0,"127.0.0.1",e)}),c=s(n,["--headless=new","--no-first-run","--no-default-browser-check",..."darwin"===process.platform?["--use-mock-keychain"]:[],"--user-data-dir="+i,`http://127.0.0.1:${p.address().port}/${a}`],{stdio:["ignore","ignore","pipe"],windowsHide:!0}),l=new Promise(e=>c.once("close",e)),c.stderr.on("data",e=>{w=(w+e).slice(-600)}),await Promise.race([d,new Promise((e,r)=>{c.once("error",r),c.once("close",e=>r(new Error(`Chrome exited (${e})`))),m=setTimeout(()=>r(new Error("Chrome startup timed out")),3e4)})])}catch(e){throw new Error(`Downloaded Chrome could not run: ${e.message}. Check OS compatibility and, on Linux, required system libraries. ${w}`)}finally{if(clearTimeout(m),c&&null===c.exitCode&&null===c.signalCode){c.kill();const e=setTimeout(()=>c.kill("SIGKILL"),3e3);await l,clearTimeout(e)}p.closeAllConnections?.(),await new Promise(e=>p.close(e)),e.rmSync(i,{recursive:!0,force:!0})}}function y(e,r){try{require("./telemetry").reportError("BrowserSetupFailed",e,r)}catch{}}module.exports={ensureBrowser:async function(t,{cacheRoot:n=o.join(r.homedir(),".tik-video-editor-cli","browsers"),env:s=process.env,platform:i=process.platform,arch:a=process.arch,candidates:g=p(),download:C=h,verify:b=f,log:x=e=>process.stderr.write(e+"\n")}={}){const E=t||s.CHROME_PATH;if(E){const e=d(E);if(!e){const e=new Error("Browser is not executable: "+E+"; fix --browser / CHROME_PATH or unset it for automatic setup");throw y(e,{source:"explicit"}),e}return e}const A=m(i,a),T=A&&o.join(n,l,A),$=A&&o.join(T,u(A)),S=()=>{try{return w($)&&e.readFileSync(o.join(T,".complete"),"utf8")===l}catch{return!1}};if(A&&S())return $;for(const e of g){const r=d(e);if(r)return r}if(!A)throw new Error(`Automatic browser setup is unavailable on ${i}/${a}; pass --browser <path> or set CHROME_PATH`);const B=`${(s.FABLECUT_BROWSER_DOWNLOAD_BASE_URL||"https://cdn.npmmirror.com/binaries/chrome-for-testing").replace(/\/+$/,"")}/${l}/${A}/chrome-${A}.zip`,v=o.dirname(T);await e.promises.mkdir(v,{recursive:!0});const O=await e.promises.mkdtemp(o.join(v,".install-"));try{x(`Preparing Chrome for Testing ${l} (${A}); this download is cached for future exports.`);const r=o.join(O,"browser.zip"),t=o.join(O,"unpacked");await C(B,r,x),await e.promises.mkdir(t),x("Extracting browser…"),await c(r,t);const n=o.join(t,u(A));if(!w(n))throw new Error("Browser archive is missing its executable");if(await b(n),await e.promises.writeFile(o.join(t,".complete"),l),!S())try{await e.promises.rename(t,T)}catch(e){if(!S())throw new Error(`${e.message}; remove the incomplete cache at ${T} and retry`)}return x("Browser ready."),$}catch(e){throw y(e,{source:"download",platform:A}),new Error(`Automatic browser setup failed: ${e.message}. Retry export, set FABLECUT_BROWSER_DOWNLOAD_BASE_URL to a trusted HTTPS mirror, or use --browser <path>.`)}finally{await e.promises.rm(O,{recursive:!0,force:!0})}},downloadArchive:h,verifyBrowser:f,platformKey:m,executable:u,VERSION:l};
@@ -0,0 +1 @@
1
+ "use strict";function e(e){return"boolean"==typeof e?{boolValue:e}:"number"==typeof e&&Number.isFinite(e)?Number.isSafeInteger(e)?{intValue:String(e)}:{doubleValue:e}:"string"==typeof e&&e?{stringValue:e}:null}function t(t){const n=[];if(!t||"object"!=typeof t||Array.isArray(t))return n;for(const o of Object.keys(t)){const a=e(t[o]);a&&n.push({key:String(o),value:a})}return n}function n(e){const n={traceId:e.traceId,spanId:e.spanId,name:e.name,kind:e.kind||1,startTimeUnixNano:String(e.startTimeUnixNano),endTimeUnixNano:String(e.endTimeUnixNano||e.startTimeUnixNano),attributes:t(e.attributes),flags:null==e.flags?1:e.flags,status:e.status||{code:0}};return e.parentSpanId&&(n.parentSpanId=e.parentSpanId),e.events?.length&&(n.events=e.events.map(e=>({timeUnixNano:String(e.timeUnixNano),name:e.name,attributes:t(e.attributes)}))),n}function o(e,o,a={name:"tik-video-editor-cli"}){return{resourceSpans:[{resource:{attributes:t(e)},scopeSpans:[{scope:{name:a.name||"tik-video-editor-cli",...a.version?{version:a.version}:{}},spans:o.map(n)}]}]}}async function a(e,t,n,{fetchImpl:o,signal:a}={}){const r=o||globalThis.fetch;if("function"!=typeof r)throw new Error("fetch unavailable");const i=await r(e,{method:"POST",headers:{"Content-Type":"application/json",...t||{}},body:JSON.stringify(n),signal:a||AbortSignal.timeout(2500)});if(!i.ok)throw new Error("HTTP "+i.status);try{await(i.body?.cancel?.())}catch{}return i}module.exports={attributeValue:e,toAttributes:t,toOtlpSpan:n,encodeExport:o,postExport:a,createTraceExporter:function(e){const t=e.batchSize||64,n=null==e.delayMs?1e3:e.delayMs;let r=[],i=null,s=Promise.resolve(),u=null;function c(){i&&(clearTimeout(i),i=null)}function l(){if(!r.length)return null;const t=r.splice(0,r.length),n="function"==typeof e.scope?e.scope():e.scope;return o(e.resource(),t,n)}function p(t){const n=s.then(async()=>{c();const n=l();if(n){u={document:n,done:!1,queued:!1};try{const o="function"==typeof e.fetchImpl?e.fetchImpl():e.fetchImpl;await a(e.endpoint(),e.headers(),n,{fetchImpl:o,signal:t}),u.done=!0}catch(t){u&&(u.done=!0),u?.queued||(u&&(u.queued=!0),e.onFailure(n,t))}finally{u=null}}});return s=n.catch(()=>{}),n}return{add:e=>(r.push(e),r.length>=t?p():(i||n<0||(i=setTimeout(()=>{i=null,p().catch(()=>{})},n),i.unref?.()),Promise.resolve())),flush:p,salvage(){c(),u&&!u.queued&&(u.queued=!0,e.onFailure(u.document));const t=l();t&&e.onFailure(t)},stop(){c(),r=[]},pending:()=>r.length}}};
@@ -0,0 +1 @@
1
+ "use strict";const e=require("node:crypto"),{execFile:t}=require("node:child_process"),r=require("node:fs"),n=require("node:os"),o=require("node:path"),s=require("./otlp"),i="https://proj-xtrace-1e4934416a1cedff5956b1291b66128-cn-hangzhou.cn-hangzhou.log.aliyuncs.com/apm/trace/opentelemetry/v1/traces",a={"x-arms-license-key":"dw8wc4ogzr@98f7fc56a7038eb","x-arms-project":"proj-xtrace-1e4934416a1cedff5956b1291b66128-cn-hangzhou","x-cms-workspace":"default-cms-1411252257529863-cn-hangzhou"},c=a["x-cms-workspace"],u=2e3,d=4096,l=72e5,p=5e3,m=4e3,f=new Set(["preparing-browser","preparing-media","rendering","finalizing"]),g={queued:6e4,starting:45e3,"preparing-browser":45e3,"preparing-media":45e3,rendering:3e4,finalizing:6e4},h=["wait","stats","stderr","activity","browserGpu","detail","metrics","stack","clipIds","timeSec","browser","exitCode","project","engine"],b=/^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;let y={},k={},S=!1,v=null,I=null,j=null,w={},x=Promise.resolve(),E="",T=null,_=null,A=!1;function N(){return y.env||process.env}function M(){return o.join(y.home||n.homedir(),".tik-video-editor-cli")}function O(){return o.join(M(),"telemetry-state.json")}function F(){return o.join(M(),"telemetry-queue")}function C(e){try{const t=JSON.parse(r.readFileSync(e,"utf8").replace(/^\uFEFF/,""));return t&&"object"==typeof t&&!Array.isArray(t)?t:{}}catch{return{}}}function P(){return C(O())}function R(e){try{r.mkdirSync(M(),{recursive:!0,mode:448});const t={...P(),...e},n=O(),o=n+"."+process.pid+".tmp";r.writeFileSync(o,JSON.stringify(t)+"\n",{mode:384}),r.renameSync(o,n)}catch{}}function D(e){const t=String(e||"").trim();if(!t)return"";try{const e=new URL(t);return!["https:","http:"].includes(e.protocol)||e.username||e.password?"":e.href}catch{return""}}function L(e){const t={};for(const r of String(e||"").split(",")){const e=r.indexOf("=");if(!(e<=0))try{const n=decodeURIComponent(r.slice(0,e).trim()),o=decodeURIComponent(r.slice(e+1).trim());n&&(t[n]=o)}catch{}}return t}function U(e=N(),t=function(){return C(o.join(M(),"telemetry.json"))}()){const r={...a};if(t.headers&&"object"==typeof t.headers&&!Array.isArray(t.headers))for(const e of Object.keys(t.headers))r[e]=String(t.headers[e]);if(Object.assign(r,L(e.OTEL_EXPORTER_OTLP_TRACES_HEADERS)),"true"===String(e.OTEL_SDK_DISABLED||"").trim().toLowerCase()||!1===t.enabled)return{disabled:!0,endpoint:"",headers:r};let n=D(i);return null!=t.endpoint&&String(t.endpoint).trim()&&(n=D(t.endpoint),!n)||String(e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT||"").trim()&&(n=D(e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT),!n)?{disabled:!0,endpoint:"",headers:r}:{disabled:!1,endpoint:n,headers:r}}function $(){if((null!=N().NODE_TEST_CONTEXT||process.execArgv.includes("--test"))&&!y.env)return!1;const e=U();return!e.disabled&&""!==e.endpoint}function B(e){let t=String(e??"");const r=N();for(const e of[r.TIK_API_KEY,r.FABLECUT_TOKEN]){const r=String(e||"").trim();r&&(t=t.split(r).join("[redacted]"))}t=t.replace(/\bhttps?:\/\/[^\s]+/gi,e=>{try{const t=new URL(e);return t.username="",t.password="",t.search="",t.hash="",t.href}catch{return"[redacted-url]"}}),t=t.replace(/\bBearer\s+\S+/gi,"Bearer [redacted]"),t=t.replace(/\bsk-[A-Za-z0-9-]{8,}/g,"[redacted]"),t=t.replace(/\b[a-fA-F0-9]{64}\b/g,"[redacted]");const o=y.home||n.homedir();return o&&(t=t.split(o).join("~"),"win32"===process.platform&&(t=t.split(o.toLowerCase()).join("~"))),t}function z(e){if(!Array.isArray(e))return"";const t=[];for(const r of e){const e=String(r);e.startsWith("-")?t.push(e.split("=")[0]):t.length||t.push(e)}return t.join(" ").slice(0,500)}function J(e){if(!Array.isArray(e))return{command:"",subcommand:"",projectId:"",check:!1,help:!1};const t=[];let r="",n=!1,o=!1,s=!1;for(let i=0;i<e.length;i++){const a=String(e[i]??"");a&&("--check"===a?n=!0:"--help"===a||"-h"===a?o=!0:"--version"===a||"-v"===a?s=!0:"--project-id"===a&&i+1<e.length?r=String(e[++i]??"").trim():a.startsWith("--project-id=")?r=a.slice(13).trim():a.startsWith("-")||t.push(a))}return{command:t[0]||(s?"version":o?"help":""),subcommand:t[1]||"",projectId:r,check:n,help:o}}function V(e=k.command,t=k.rawArgv){const r=J(t),n=e||r.command;return!(n&&"version"!==n&&"help"!==n&&!r.help)||!("status"!==n||!r.check)||"storage"===n&&"info"===r.subcommand||"list-projects"===n||"auth"===n&&"status"===r.subcommand}function q(e){const t={};if(!e||"object"!=typeof e||Array.isArray(e))return t;for(const r of Object.keys(e)){if(Object.keys(t).length>=20)break;const n=String(r).slice(0,50),o=e[r];if("number"==typeof o&&Number.isFinite(o))t[n]=o;else if("boolean"==typeof o)t[n]=o?1:0;else if("string"==typeof o&&o)t[n]=B(o).slice(0,u);else{if(null==o||""===o)continue;try{t[n]=B(JSON.stringify(o)).slice(0,u)}catch{}}}return t}function G(e){if("string"==typeof e)return B(e).slice(0,d);if("number"==typeof e||"boolean"==typeof e||null==e)return e;if(Array.isArray(e))return e.slice(0,40).map(G);if("object"==typeof e){const t={};for(const r of Object.keys(e).slice(0,40))t[r]=G(e[r]);return t}return B(e)}function W(e){let t="";try{t=JSON.stringify(G(e))}catch{t=""}if(t&&"{}"!==t&&"null"!==t)return t.length>d?t.slice(0,d):t}function K(e){const t=b.exec(String(e||"").trim().toLowerCase());return t?/^0+$/.test(t[1])||/^0+$/.test(t[2])?null:{traceId:t[1],spanId:t[2],sampled:!(1&~Number.parseInt(t[3],16))}:null}function H(){return"function"==typeof y.now?y.now():Date.now()}function X(){return String(1000000n*BigInt(Math.trunc(H())))}function Q(e){const t=String(e||"").trim().slice(0,80);return!t||/[/\\]|\.\.|\s/.test(t)?"":t}function Y(e=k.projectId){const t=Q(e);if(!t)return null;const r=P().projectTraces;if(!r||"object"!=typeof r||Array.isArray(r))return null;const n=r[t];if(!n||"object"!=typeof n)return null;const o=String(n.traceId||"").toLowerCase(),s=String(n.spanId||"").toLowerCase();if(!/^[0-9a-f]{32}$/.test(o)||!/^[0-9a-f]{16}$/.test(s))return null;const i=Number(n.updatedAt)||0;return H()-i>l?null:{traceId:o,spanId:s,linked:!1!==n.linked}}function Z(e,t){const r=Q(e);if(!r||!t?.traceId||!t?.spanId)return;const n=P(),o=n.projectTraces&&"object"==typeof n.projectTraces&&!Array.isArray(n.projectTraces)?{...n.projectTraces}:{},s=H();for(const e of Object.keys(o))(!o[e]||s-(Number(o[e].updatedAt)||0)>l)&&delete o[e];o[r]={traceId:t.traceId,spanId:t.spanId,linked:!1!==t.linked,updatedAt:s};const i=Object.keys(o).sort((e,t)=>(Number(o[e]?.updatedAt)||0)-(Number(o[t]?.updatedAt)||0));for(;i.length>20;)delete o[i.shift()];R({projectTraces:o})}function ee(e){const t=String(e||"").trim().toLowerCase();return"x86_64"===t||"amd64"===t?"x64":"aarch64"===t?"arm64":String(e||"").trim()}function te(e,t,r){const n=ee(e);if(!n)return"";const o=ee(t),s=ee(r);return o&&o!==n?`${n}(rosetta:${o})`:s&&s!==n?`${n}(proc:${s})`:n}function re(){try{return String(n.cpus()?.[0]?.model||"").trim().slice(0,80)}catch{return""}}function ne(e=process.platform){return{darwin:"macOS",win32:"Windows",linux:"Linux"}[e]||e}function oe(e,t){if(!t)return"";const r=Number(t.bsize||t.frsize),n=Number(t.blocks)*r,o=Number(t.bavail)*r;if(!Number.isFinite(n)||n<=0)return"";const s=Math.max(0,Math.min(100,Math.round((n-o)/n*100)));return`${e}:${Math.round(n/1024**3)}G/${s}%`}function se(){const e=y.statfs||r.statfsSync;if("function"!=typeof e)return"";const t=[],s=function(){try{const e=require("./storage");return e.readRoot(e.configDir(y.home||n.homedir()))}catch{return o.join(M(),"storage")}}();let i=null;try{i=e(s);const r=oe("st",i);r&&t.push(r)}catch{i=null}const a=n.tmpdir();try{if(!i||!function(e,t,n=process.platform){try{return o.resolve(e)===o.resolve(t)||("win32"===n?o.parse(e).root.toLowerCase()===o.parse(t).root.toLowerCase():r.statSync(e).dev===r.statSync(t).dev)}catch{return!1}}(s,a)){const r=oe("tmp",e(a));r&&t.push(r)}}catch{}return t.join(";")}function ie(){if(y.channel)return y.channel;const e=L(N().OTEL_RESOURCE_ATTRIBUTES);if("managed"===e["tik.channel"]||"desktop"===e["tik.launcher"])return"managed";if(E)return E;const t=[o.join(__dirname,"..","package.json"),o.join(__dirname,"..","..","package.json"),o.join(__dirname,"..","..","..","package.json")];for(const e of t)try{if("@tikagent/video-editor-cli"!==JSON.parse(r.readFileSync(e,"utf8")).name)continue;let t=o.dirname(e);for(let e=0;e<4;e++){t=o.dirname(t);try{if("tikagent-managed-tik-video-editor-cli"===JSON.parse(r.readFileSync(o.join(t,"package.json"),"utf8")).name)return E="managed",E}catch{}}return E="standalone",E}catch{}return E="standalone",E}function ae(){const e=L(N().OTEL_RESOURCE_ATTRIBUTES)["tik.launcher"];return"desktop"===e||"standalone"===e?e:"managed"===ie()?"desktop":"standalone"}function ce(){const e=L(N().OTEL_RESOURCE_ATTRIBUTES),t=String(e["user.id"]||e["enduser.id"]||"").trim().slice(0,80);return t&&!/sk-|api[_-]?key|token/i.test(t)?(P().userName!==t&&R({userName:t}),t):String(P().userName||"").trim()}function ue(e=N()){return L(e.OTEL_RESOURCE_ATTRIBUTES)["deployment.environment"]||("test"===String(e.TIK_ENV||"").trim().toLowerCase()?"test":function(){if(null!=T)return T;let e=__dirname;for(let t=0;t<6;t++){try{if("fablecut"===JSON.parse(r.readFileSync(o.join(e,"package.json"),"utf8")).name)return T=!0,!0}catch{}const t=o.dirname(e);if(t===e)break;e=t}return T=!1,!1}()?"dev":"prod")}function de(){const e=String(N().FABLECUT_VERSION||"").trim();if(e)return e;const t=[o.join(__dirname,"..","package.json"),o.join(__dirname,"..","..","package.json"),o.join(__dirname,"..","..","..","package.json")];for(const e of t)try{const t=JSON.parse(r.readFileSync(e,"utf8"));if("@tikagent/video-editor-cli"===t.name&&t.version)return String(t.version)}catch{}return""}function le(){const t=P();if("string"==typeof t.deviceId&&t.deviceId.startsWith("uid_"))return t.deviceId;const r="uid_"+e.randomBytes(12).toString("hex");return R({deviceId:r}),r}function pe(e){const t=P()[e];return t&&"number"==typeof t.at?Date.now()-t.at>=(t.value||t.version||t.encoders?6048e5:864e5)?{}:t:{}}function me(){const e=N(),t=function(){if(j){try{j.memFreeMb=Math.round(n.freemem()/1048576)}catch{}return j.disk=se(),j}const e=(()=>{try{return n.cpus()||[]}catch{return[]}})();let t="";try{t="function"==typeof n.machine?n.machine():""}catch{t=""}const r=Number(e[0]?.speed);return j={cpuArch:te(n.arch(),t,process.arch),cpuCores:e.length||"",cpuMhz:r>0?r:"",cpuModel:re(),memTotalMb:Math.round(n.totalmem()/1048576)||"",memFreeMb:Math.round(n.freemem()/1048576)||"",disk:se(),locale:(()=>{try{return Intl.DateTimeFormat().resolvedOptions().locale||""}catch{return""}})()},j}(),r=U(),o=w.ffmpegVer?w:pe("ffmpeg"),s=w.gpu||pe("gpu").value||"",i=ce();return{"service.name":"tik-video-editor-cli","service.version":de(),"deployment.environment":ue(e),"acs.cms.workspace":r.headers["x-cms-workspace"]||c,"tik.role":k.role||"","tik.device_id":le(),"enduser.id":i,"user.id":i,"tik.channel":ie(),"tik.launcher":ae(),"tik.cpu.arch":t.cpuArch,"tik.cpu.cores":t.cpuCores,"tik.cpu.mhz":t.cpuMhz,"tik.cpu.model":t.cpuModel,"tik.mem.total_mb":t.memTotalMb,"tik.mem.free_mb":t.memFreeMb,"tik.disk":t.disk,"tik.locale":t.locale,"tik.gpu":s,"tik.ffmpeg.version":o.version||o.ffmpegVer||"","tik.ffprobe.version":o.probe||o.ffprobeVer||"","tik.hw.encoders":o.encoders||o.hwEncoders||"","tik.browser.gpu":w.browserGpu||k.browserGpu||"","tik.os":ne(),"tik.os.version":n.release(),"process.runtime.version":process.version,...L(e.OTEL_RESOURCE_ATTRIBUTES)}}function fe(e){return(String(e||"").split(/\r?\n/).find(Boolean)||"").replace(/\s+Copyright\b[\s\S]*$/i,"").trim().slice(0,160)}function ge(e){const t=["h264_nvenc","h264_qsv","h264_amf","h264_videotoolbox"].filter(t=>new RegExp("\\b"+t+"\\b").test(String(e||"")));return t.length?t.map(e=>e.replace(/^h264_/,"")).join(","):"none"}function he(e){const t=String(e||"").search(/[\[{]/);if(t<0)return null;try{return JSON.parse(String(e).slice(t))}catch{return null}}function be(e,r,n){const o=y.execFile||t;return new Promise(t=>{try{o(e,r,{encoding:"utf8",timeout:n,windowsHide:!0,maxBuffer:1048576},(e,r,n)=>{t({status:e?1:0,stdout:String(r||""),stderr:String(n||e?.message||"")})})}catch(e){t({status:1,stdout:"",stderr:e.message})}})}function ye(e){return(Array.isArray(e)?e:e?[e]:[]).slice(0,2).map(e=>{const t=String(e.Name||e.sppci_model||"").trim(),r=String(e.DriverVersion||"").trim();return t?r?`${t}(${r})`:t:""}).filter(Boolean).join(";").slice(0,200)}async function ke(e=process.platform){if("win32"===e){const e=o.join(process.env.SystemRoot||"C:\\Windows","System32","WindowsPowerShell","v1.0","powershell.exe"),t="Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion | ConvertTo-Json -Compress";return ye(he((await be(e,["-NoProfile","-NonInteractive","-Command",t],p)).stdout))}if("darwin"===e){const e=await be("system_profiler",["SPDisplaysDataType","-json"],p);return ye(he(e.stdout)?.SPDisplaysDataType)}const t=await be("lspci",["-mm"],p);return String(t.stdout||"").split(/\r?\n/).filter(e=>/VGA|3D|Display/i.test(e)).slice(0,2).map(e=>{const t=[...e.matchAll(/"([^"]*)"/g)].map(e=>e[1]);return[t[2],t[3]].filter(Boolean).join(" ")}).filter(Boolean).join(";").slice(0,200)}async function Se(){const e=await be("ffmpeg",["-version"],m),t=await be("ffprobe",["-version"],m),r=await be("ffmpeg",["-hide_banner","-encoders"],m),n=0===e.status&&e.stdout;return{version:n?fe(e.stdout):"",probe:0===t.status?fe(t.stdout):"",encoders:n?ge(r.stdout):""}}async function ve(e,t){const r=pe(e);if(r.at)return r;const n={...await t(),at:Date.now()};return R({[e]:n}),n}function Ie(e){e.attributes["tik.launcher"]=ae(),e.attributes["tik.trace.linked"]=e.linked?1:0;const t=ce();t&&(e.attributes["user.id"]=t),k.projectId&&!e.attributes["tik.project.id"]&&(e.attributes["tik.project.id"]=k.projectId),k.role&&(e.attributes["tik.role"]=k.role),k.command&&(e.attributes["tik.command"]=k.command),k.argvSummary&&(e.attributes["tik.argv"]=k.argvSummary)}function je(t,r={}){const n=r.parent||null,o={traceId:n?.traceId||e.randomBytes(16).toString("hex"),spanId:e.randomBytes(8).toString("hex"),parentSpanId:n?.spanId||"",name:String(t||"span").slice(0,120),kind:1,flags:1,startTimeUnixNano:X(),attributes:{...r.attributes||{}},events:[],linked:null!=r.linked?!!r.linked:!(!n||!n.linked),ended:!1};return Ie(o),o}function we(e){const t=e instanceof Error?e:new Error(B(e||"Error"));return{timeUnixNano:X(),name:"exception",attributes:{"exception.type":String(t.name||"Error").slice(0,120),"exception.message":B(t.message||"").slice(0,u),"exception.stacktrace":B(t.stack||"").slice(0,d)}}}function xe(e,t={}){if(!e||e.ended)return Promise.resolve(!1);if(e.ended=!0,e.endTimeUnixNano=X(),t.attributes&&Object.assign(e.attributes,t.attributes),t.events&&e.events.push(...t.events),t.error){const r=t.error instanceof Error?t.error:new Error(B(t.error));e.status={code:2,message:B(r.message||"").slice(0,u)},e.events.push(we(r))}else e.status=t.status||{code:1};return Ie(e),$()?((I||(I=s.createTraceExporter({resource:me,scope:()=>({name:"tik-video-editor-cli",version:de()}),endpoint:()=>U().endpoint,headers:()=>U().headers,fetchImpl:()=>y.fetch||globalThis.fetch,onFailure:e=>Ae(e)})),I).add(e),Promise.resolve(!0)):Promise.resolve(!1)}function Ee(){return v&&!v.ended?{traceId:v.traceId,spanId:v.spanId,linked:v.linked}:null}function Te(){if(v)return k.command&&"cli"===v.name&&(v.name=`cli ${k.command}`.slice(0,120)),Ie(v),v;const e=function(){const e=K(N().TRACEPARENT);return e?{traceId:e.traceId,spanId:e.spanId,linked:!0}:null}(),t=!e&&k.projectId?Y(k.projectId):null,r=e||t;return v=je(k.command?`cli ${k.command}`:"cli",{parent:r,linked:!!r,attributes:{"tik.trace.linked":r?1:0,...k.projectId?{"tik.project.id":k.projectId}:{}}}),k.projectId&&Z(k.projectId,e?{traceId:v.traceId,spanId:v.spanId,linked:!0}:t||{traceId:v.traceId,spanId:v.spanId,linked:!0}),v}function _e(e={}){if(!e.name)return Promise.resolve(!1);const t=process.memoryUsage(),r=je(e.name,{parent:Ee(),attributes:q({role:k.role,command:k.command,...e.group?{group:e.group}:{},...Number.isFinite(e.value)?{value:e.value}:{},...e.properties})}),n=W({rssMb:Math.round(t.rss/1048576),heapUsedMb:Math.round(t.heapUsed/1048576),...e.snapshots});return n&&(r.attributes["tik.snapshot"]=n),xe(r,{status:{code:1}}).then(()=>!0,()=>!1)}function Ae(t){try{r.mkdirSync(F(),{recursive:!0,mode:448});const n=r.readdirSync(F()).filter(e=>e.endsWith(".json")||e.includes(".sending-")).sort();for(;n.length>=50;)r.rmSync(o.join(F(),n.shift()),{force:!0});const s=o.join(F(),Date.now()+"-"+e.randomBytes(4).toString("hex")+".json");return r.writeFileSync(s,JSON.stringify(t),{mode:384,flag:"wx"}),s}catch{return""}}function Ne(e){const t=Number(String(e).split("-")[0]);return Number.isFinite(t)?t:0}function Me(e,t=process.pid,n=Date.now()){const s=o.basename(e),i=/\.sending-\d+$/.test(s);if(!i&&!s.endsWith(".json"))return"";if(i){let t=0;try{t=r.statSync(e).mtimeMs}catch{return""}if(n-t<6e5)return""}const a=s.replace(/\.json$/,"").replace(/\.sending-\d+$/,""),c=o.join(o.dirname(e),a+".sending-"+t);try{return r.renameSync(e,c),c}catch{return""}}async function Oe(e,t){const r=U(),n=y.fetch||globalThis.fetch;await s.postExport(r.endpoint,r.headers,e,{fetchImpl:n,signal:t})}function Fe(e){return _||(_=async function(e){if(!$())return;let t=[];try{t=r.readdirSync(F()).sort()}catch{return}let n=0;for(const s of t){if(e?.aborted||n>=10)return;const t=o.join(F(),s),i=Ne(s);if(!i||Date.now()-i>6048e5){r.rmSync(t,{force:!0});continue}const a=Me(t);if(!a)continue;let c;try{c=JSON.parse(r.readFileSync(a,"utf8"))}catch{r.rmSync(a,{force:!0});continue}try{await Oe(c,e),r.rmSync(a,{force:!0}),n+=1}catch{const e=a.replace(/\.sending-\d+$/,".json");try{e!==a&&r.renameSync(a,e)}catch{}return}}}(e)),_}function Ce(e,t){if(!e||e.done)return null;const r=g[e.state];if(!r)return null;const n=e.lastProgressAt??e.startedAt??t,o=Math.max(0,t-n);if(o<r)return null;const s="rendering"===e.state?o>=12e4:o>=2*r;return{state:e.state,idleMs:o,severe:s,threshold:r}}function Pe(e,t){if(!e||e.done)return!1;if(e.wait&&e.wait.reason)return!0;const r=e.lastProgressAt??e.startedAt??t,n=Math.max(0,t-r);return"rendering"===e.state?n>=1e4:n>=15e3&&Object.prototype.hasOwnProperty.call(g,e.state)}function Re(e){I&&I.stop(),y={...e},k={},S=!1,v=null,I=null,j=null,w={},x=Promise.resolve(),E="",T=null,_=null,A=!1}module.exports={EXPORT_WAIT_REASONS:["source-block","video-seek","source-image","frame-assets","audio-mix","encoder"],DEFAULT_ENDPOINT:i,DEFAULT_HEADERS:a,initTelemetry:function(e={}){if(k={...k,...e},Array.isArray(k.argv)){k.rawArgv=k.argv.slice();const e=J(k.argv);!k.command&&e.command&&(k.command=e.command),!k.projectId&&e.projectId&&(k.projectId=Q(e.projectId)),k.argvSummary=z(k.argv),delete k.argv}return!!$()&&(S||(S=!0,le(),ce(),function(){if(A||"cli"!==k.role||"managed"===ie())return;if(P().noticeShown)return void(A=!0);A=!0;const e=y.stderr||process.stderr;try{e.write('tik-video-editor-cli 会向阿里云 ARMS 上报命令名、导出摘要和机器信息,不含参数值与工程内容。关闭:设置 OTEL_SDK_DISABLED=true,或在 ~/.tik-video-editor-cli/telemetry.json 写入 {"enabled":false}。\n')}catch{}R({noticeShown:!0})}(),"server"===k.role&&("server"===k.role&&(x=Promise.allSettled([(async()=>{const e=await ve("gpu",async()=>({value:await ke()}));w.gpu=e.value||""})(),(async()=>{const e=await ve("ffmpeg",Se);w.ffmpegVer=e.version||"",w.ffprobeVer=e.probe||"",w.hwEncoders=e.encoders||""})()]).then(()=>{})),Fe())),"cli"===k.role&&k.command&&!V()&&Te(),!0)},isTelemetryEnabled:$,rememberUser:function(e){const t=String(e??"").trim().slice(0,80);t&&!/sk-|api[_-]?key|token/i.test(t)&&R({userName:t})},noteBrowserGpu:function(e){const t=B(e).trim().slice(0,180);t&&(w.browserGpu=t,k.browserGpu=t)},reportError:function(e,t,r,n){const o=t instanceof Error?t:new Error(B(t||e)),s=process.memoryUsage(),i=je(String(e||o.name||"Error"),{parent:Ee(),attributes:q({role:k.role,command:k.command,...r})}),a=W({rssMb:Math.round(s.rss/1048576),heapUsedMb:Math.round(s.heapUsed/1048576),...n});return a&&(i.attributes["tik.snapshot"]=a),xe(i,{error:o})},reportCustom:_e,reportFatalSync:function(e,t,r,n){if(!$())return!1;const o=t instanceof Error?t:new Error(B(t||e)),i=je(String(e||"Error"),{parent:Ee(),attributes:q({role:k.role,command:k.command,...r})});i.ended=!0,i.endTimeUnixNano=X(),i.status={code:2,message:B(o.message||"").slice(0,u)},i.events.push(we(o));const a=W(n);return a&&(i.attributes["tik.snapshot"]=a),Ie(i),Ae(s.encodeExport(me(),[i],{name:"tik-video-editor-cli",version:de()})),!0},flushTelemetry:async function(e=500){if(!$())return!1;const t=new AbortController;let r;const n=new Promise(n=>{r=setTimeout(()=>{t.abort(),n("timeout")},Math.max(0,e)),r.unref?.()}),o=(async()=>(I&&await I.flush(t.signal),await Fe(t.signal),"done"))();return await Promise.race([o,n]),clearTimeout(r),I&&I.salvage(),!0},finishCommand:function(e){if(!$())return Promise.resolve(!1);const t=e?Number.isInteger(e.exitCode)?e.exitCode:1:Number(process.exitCode)||0;if(!e&&!t&&V())return v&&(v.ended=!0),Promise.resolve(!1);if(Te(),!v||v.ended)return Promise.resolve(!1);if(e||t){const r=e instanceof Error?e:new Error(t?"exit "+t:"command failed");return xe(v,{error:r,attributes:{"tik.exit.code":t||1}})}return xe(v,{status:{code:1},attributes:{"tik.exit.code":0}})},startSpan:je,endSpan:xe,currentParent:Ee,bindProjectTrace:function(e){const t=Q(e);if(!t)return null;if(k.projectId=t,!$())return null;if(Te(),!v)return null;v.attributes["tik.project.id"]=t;const r=Y(t);if(r&&r.traceId===v.traceId)return Z(t,r),r;const n={traceId:v.traceId,spanId:v.spanId,linked:!0};return Z(t,n),n},resolveProjectTrace:Y,isQuietSuccessCommand:V,traceparent:function(){return v?`00-${v.traceId}-${v.spanId}-01`:""},createExportMonitor:function(e={}){if(!e.report&&!$())return{stop(){},upsert(){},note(){},cancel:()=>!1,fail(){},complete(){},scan(){}};const t=new Map,r=e.now||Date.now;function n(t,r){try{if(e.report)return void e.report(r.error?"exception":"custom",{name:t.name,status:r.error?2:r.status?.code??1,attributes:t.attributes,events:t.events,error:r.error,properties:t.attributes});xe(t,r)}catch{}}function o(e,t={}){for(const r of h)void 0!==t[r]&&(e[r]=t[r])}function s(t,r){!e.report&&f.has(r)&&(t.stageSpan=je(`export.${r}`,{parent:{traceId:t.span.traceId,spanId:t.span.spanId,linked:t.span.linked},linked:t.span.linked,attributes:{"tik.export.id":t.id,"tik.export.engine":t.engine||"","tik.export.stage":r,...t.projectId?{"tik.project.id":t.projectId}:{}}}))}function i(e,t={}){if(!e?.stageSpan)return;const r=e.stageSpan;e.stageSpan=null,xe(r,{status:t.error?void 0:{code:1},error:t.error,attributes:{"tik.export.id":e.id,"tik.export.engine":e.engine||"","tik.export.frames":e.frames||0,...e.totalFrames?{"tik.export.total_frames":e.totalFrames}:{}}})}function a(e,n){let o=t.get(e);if(!o){const i=Q(n.projectId),a=K(n.traceparent),c=a?{traceId:a.traceId,spanId:a.spanId,linked:!0}:i?Y(i):null,u=n.state||"queued";o={id:e,projectId:i,engine:n.engine||"",state:u,startedAt:n.startedAt??r(),lastProgressAt:n.startedAt??r(),frames:0,reported:new Set,stalled:!1,maxStallMs:0,done:!1,stageSpan:null,span:je("export",{parent:c,linked:!!c,attributes:{"tik.export.id":e,"tik.export.engine":n.engine||"",...i?{"tik.project.id":i}:{}}})},s(o,u),t.set(e,o)}return n.projectId&&!o.projectId&&(o.projectId=Q(n.projectId),o.projectId&&(o.span.attributes["tik.project.id"]=o.projectId)),n.traceparent&&function(e,t){const r=K(t);r&&!e.span.parentSpanId&&(e.span.traceId=r.traceId,e.span.parentSpanId=r.spanId,e.span.linked=!0,e.span.attributes["tik.trace.linked"]=1,e.stageSpan&&(e.stageSpan.traceId=r.traceId,e.stageSpan.linked=!0,e.stageSpan.attributes["tik.trace.linked"]=1))}(o,n.traceparent),o}function c(e){return W({project:e.project,wait:e.wait,stats:e.stats,stderr:e.stderr,activity:e.activity,detail:e.detail,browser:e.browser,browserGpu:e.browserGpu,stack:e.stack,clipIds:e.clipIds,metrics:e.metrics,timeSec:e.timeSec})}function u(t){try{const r=e.enrich?.(t.id,t);r&&"object"==typeof r&&o(t,r)}catch{}}function d(e,r){if(!e||e.done)return;i(e,r);const o=c(e);o&&(e.span.attributes["tik.snapshot"]=o),Object.assign(e.span.attributes,{"tik.export.id":e.id,"tik.export.engine":e.engine||"","tik.export.frames":e.frames||0,"tik.export.total_frames":e.totalFrames||0,"tik.export.state":e.state||"",...e.projectId?{"tik.project.id":e.projectId}:{},...r.attributes||{}}),e.done=!0,n(e.span,r),t.delete(e.id)}function l(){const o=r();for(const r of t.values()){if(r.done)continue;u(r);const t=Ce(r,o);if(!t)continue;r.maxStallMs=Math.max(r.maxStallMs,t.idleMs);const s=t.severe?"severe":"warn";if(r.reported.has(s))continue;r.reported.add(s),r.stalled=!0;const i={stage:r.state||"",stallMs:t.idleMs,frames:r.frames||0,totalFrames:r.totalFrames||0,waitReason:r.wait?.reason||"",engine:r.engine||"",severe:t.severe?1:0,"tik.export.id":r.id};if(r.span.events.push({timeUnixNano:X(),name:"export_stall",attributes:i}),e.report&&e.report("custom",{name:"export_stall",properties:i,severe:i.severe}),!t.severe)continue;const a=je("ExportStalled",{parent:{traceId:r.span.traceId,spanId:r.span.spanId,linked:r.span.linked},linked:r.span.linked,attributes:i}),d=c(r);d&&(a.attributes["tik.snapshot"]=d),n(a,{error:new Error(`导出在 ${r.state} 停滞 ${Math.round(t.idleMs/1e3)} 秒`)})}}const p=setInterval(l,e.interval||5e3);return p.unref?.(),{stop(){clearInterval(p)},upsert:function(t,n={}){if(!t)return null;const c=r(),u=a(t,n);n.engine&&(u.engine=n.engine);const d=n.state||u.state,l=d!==u.state,p=Number.isFinite(n.frames)&&n.frames>(u.frames||0),m="string"==typeof n.detail&&n.detail&&n.detail!==u.detail;if(l||p||m){const t=c-u.lastProgressAt;u.stalled&&(u.maxStallMs=Math.max(u.maxStallMs,t),u.span.events.push({timeUnixNano:X(),name:"export_stall_recovered",attributes:{stage:u.state,maxStallMs:u.maxStallMs,engine:u.engine||""}}),e.report&&e.report("custom",{name:"export_stall_recovered",properties:{stage:u.state,maxStallMs:u.maxStallMs}}),u.stalled=!1,u.reported.clear()),u.lastProgressAt=c}return p&&(u.frames=n.frames),Number.isFinite(n.totalFrames)&&(u.totalFrames=n.totalFrames),l&&(i(u),u.state=d,s(u,d)),o(u,n),u},note:function(e,r){const n=t.get(e);n&&o(n,r)},scan:l,cancel:function(e){const n=t.get(e);if(!n||n.done)return!1;u(n);const o=Pe(n,r()),s=Math.max(0,r()-n.lastProgressAt);return o?(d(n,{error:new Error(`导出在 ${n.state} 等待 ${Math.round(s/1e3)} 秒后被取消`),attributes:{"tik.export.outcome":"cancelled-stalled",stallMs:s,waitReason:n.wait?.reason||""}}),!0):(d(n,{status:{code:1},attributes:{"tik.export.outcome":"cancelled",stallMs:s}}),!1)},fail:function(e,r,n={}){const s=t.get(e)||a(e,{state:n.stage||"starting"});s&&!s.done&&(o(s,n),u(s),d(s,{error:r instanceof Error?r:new Error(String(r||"export failed")),attributes:{"tik.export.outcome":"failed",stage:n.stage||s.state||"",origin:n.origin||"server"}}))},complete:function(e,r={}){const n=t.get(e)||a(e,{});if(!n||n.done)return;o(n,r),Number.isFinite(r.frames)&&(n.frames=r.frames),Number.isFinite(r.totalFrames)&&(n.totalFrames=r.totalFrames);const s=r.metrics||n.metrics;d(n,{status:{code:1},attributes:{"tik.export.outcome":"complete",...Number.isFinite(Number(s?.totalMs))?{"tik.export.elapsed_ms":Number(s.totalMs)}:{}}});const i=s?.fallbacks||s?.cache?.fallbacks;i&&"object"==typeof i&&Object.keys(i).length&&_e({name:"export_cache_fallback",type:"export",properties:{engine:n.engine||"",count:Object.keys(i).length},snapshots:{fallbacks:i}})}}},summarizeExportProject:function(e,t={}){const r=Array.isArray(e?.clips)?e.clips:[],n=Array.isArray(e?.media)?e.media:[],o={};let s=0,i=0,a=0,c=0,u=0;for(const e of r){const t=e?.kind||"unknown";o[t]=(o[t]||0)+1;const r=e?.props||{};r.bgRemove&&(s+=1),r.chromaKey&&(i+=1),(r.filterPreset&&"none"!==r.filterPreset||r.shake||r.rgbSplit||r.grain)&&(a+=1),(Number(r.speed)&&1!==Number(r.speed)||e?.keyframes?.speed)&&(c+=1),(e?.transitionIn||e?.transitionOut)&&(u+=1)}const d=r.reduce((e,t)=>Math.max(e,(Number(t?.start)||0)+(Number(t?.duration)||0)),0),l=Number(e?.fps)||0;return{width:e?.width,height:e?.height,fps:l,revision:e?.revision,durationSec:Math.round(1e3*d)/1e3,totalFrames:l>0&&d>0?Math.max(1,Math.round(d*l)):void 0,clips:r.length,kinds:o,tracks:Array.isArray(e?.tracks)?e.tracks.length:void 0,bgRemove:s,chroma:i,effects:a,speed:c,transitions:u,media:n.length,...Number.isFinite(t.mediaBytes)?{mediaBytes:t.mediaBytes}:{}}},resolveTelemetryConfig:U,parseTraceparent:K,summarizeArgv:z,sanitizeTelemetryText:B,clipTelemetryProperties:q,evaluateExportHealth:Ce,isExportCancelStalled:Pe,formatCpuArch:te,formatDiskSegment:oe,parseFfmpegVersionLine:fe,parseHardwareEncoders:ge,tryClaimQueueFile:Me,setTelemetryDeps:Re,resetTelemetryForTests:function(){Re({})}};
@@ -1 +1 @@
1
- "use strict";const e=require("node:fs/promises"),r=require("node:os"),t=require("node:path"),{localMedia:o}=require("./export-cache"),{run:a}=require("./export-encoder");module.exports={ExportAudio:class{constructor({libraryDir:e}){this.libraryDir=e,this.queue=Promise.resolve()}async prepare(i,n,s){const c=await o(i,this.libraryDir,n),u=this.queue.then(async()=>{if(s?.aborted)throw new Error("export cancelled");const o=await e.mkdtemp(t.join(r.tmpdir(),"fablecut-export-audio-")),i=t.join(o,"source.wav"),n=()=>e.rm(o,{recursive:!0,force:!0}),u=new AbortController,l=()=>u.abort();s?.addEventListener("abort",l,{once:!0}),s?.aborted&&l();const d=setTimeout(l,6e5);try{return await a("ffmpeg",["-nostdin","-v","error","-y","-i",c,"-map","0:a:0?","-vn","-af","aresample=48000:async=1:first_pts=0","-c:a","pcm_f32le","-rf64","auto",i],u.signal),{file:i,cleanup:n}}catch(e){if(await n(),!u.signal.aborted&&/does not contain any stream/i.test(e.message))return null;throw e}finally{clearTimeout(d),s?.removeEventListener("abort",l)}});return this.queue=u.catch(()=>{}),u}}};
1
+ "use strict";const e=require("node:fs/promises"),r=require("node:os"),t=require("node:path"),{localMedia:a}=require("./export-cache"),{run:o}=require("./export-encoder");module.exports={ExportAudio:class{constructor({libraryDir:e}){this.libraryDir=e,this.queue=Promise.resolve()}async prepare(i,s,n){const c=await a(i,this.libraryDir,s),u=this.queue.then(async()=>{if(n?.aborted)throw new Error("export cancelled");const a=await e.mkdtemp(t.join(r.tmpdir(),"fablecut-export-audio-")),i=t.join(a,"source.wav"),s=()=>e.rm(a,{recursive:!0,force:!0}),u=new AbortController,l=()=>u.abort();n?.addEventListener("abort",l,{once:!0}),n?.aborted&&l();const d=setTimeout(l,6e5);try{return await o("ffmpeg",["-nostdin","-v","error","-y","-i",c,"-map","0:a:0?","-vn","-af","aresample=48000:async=1:first_pts=0","-c:a","pcm_f32le","-rf64","auto",i],u.signal),{file:i,cleanup:s}}catch(r){if(await s(),!u.signal.aborted&&/does not contain any stream/i.test(r.message))return null;let t="";try{t=" size="+(await e.stat(c)).size}catch{}throw t&&(r.message+=t),r}finally{clearTimeout(d),n?.removeEventListener("abort",l)}});return this.queue=u.catch(()=>{}),u}}};
@@ -1 +1 @@
1
- "use strict";const e=require("node:fs"),t=e.promises,s=require("node:path"),r=require("node:crypto"),{spawn:i}=require("./cli/lib/processes"),a=require("node:os"),n=Math.min(2,a.availableParallelism?.()||a.cpus().length||1),o=e=>{const[t,s=1]=String(e).split("/").map(Number);return t/s};function c(e,t,s,r){return new Promise((a,n)=>{if(s?.aborted)return n(new Error("cancelled"));const o=i(e,t,{stdio:["ignore","pipe","pipe"]});let c,l="",d="";const h=()=>{c=new Error("cancelled"),o.kill("SIGKILL")},p=setTimeout(()=>{c=new Error(e+" timed out"),o.kill("SIGKILL")},12e4);s?.addEventListener("abort",h,{once:!0}),o.stdout.on("data",e=>{l+=e,l.length>16777216&&(c=new Error("probe output too large"),o.kill("SIGKILL"))}),o.stderr.on("data",e=>{r?.(String(e)),d=(d+e).slice(-4e3)}),o.on("error",e=>{c=e}),o.on("close",t=>{clearTimeout(p),s?.removeEventListener("abort",h),c||0!==t?n(c||new Error(d||e+" failed")):a(l)})})}function l(e){const t=/ffmpeg version\s+n?(\d+)\.(\d+)/.exec(e);if(t){const e=Number(t[1]),s=Number(t[2]);return e>5||5===e&&s>=1?["-fps_mode","passthrough"]:["-vsync","0"]}return/(?:^|[\r\n])\s*-fps_mode\b/.test(e)?["-fps_mode","passthrough"]:/(?:^|[\r\n])\s*-vsync\b/.test(e)?["-vsync","0"]:null}let d;function h(e){const t=o(e.r_frame_rate),s=o(e.avg_frame_rate);return Number.isFinite(t)&&t>0&&t<=120&&Math.abs(t-s)<1e-4&&0===Number(e.start_time||0)&&["1:1","0:1",void 0].includes(e.sample_aspect_ratio)&&!e.tags?.rotate&&!e.side_data_list?.some(e=>e.rotation)&&["yuv420p","yuvj420p"].includes(e.pix_fmt)&&["bt709","smpte170m","bt470bg"].includes(e.color_space)&&[void 0,"unknown","bt709","smpte170m","bt470bg"].includes(e.color_primaries)&&[void 0,"unknown","bt709","smpte170m","gamma22","gamma28"].includes(e.color_transfer)&&e.width>0&&e.height>0}function p(e,t){const s=1/t,r=Math.max(.002,.1*s);return e.length>0&&e.every((t,i)=>Number.isFinite(t)&&(!i||t>e[i-1]&&Math.abs(t-e[i-1]-s)<=r))}async function m(e,r,i){const a=`/projects/${e.id}/media/`;let n,o;if(i?.startsWith(a))n=e.mediaDir,o=i.slice(a.length);else if("default"===e.id&&i?.startsWith("/media/"))n=e.mediaDir,o=i.slice(7);else{if(!i?.startsWith("/library/"))throw new Error("non-local or cross-project source");n=r,o=i.slice(9)}n=await t.realpath(n);const c=await t.realpath(s.resolve(n,decodeURIComponent(o)));if(!c.startsWith(n+s.sep))throw new Error("source escapes media directory");return c}class u{constructor(e=((a.availableParallelism?.()||a.cpus().length||1)>=8?2:1)){this.concurrency=e,this.pending=[],this.active=new Set,this.lastOwner=new Map}enqueue(e,t,s,r,i="default"){let a,n;const o={run:e,priority:t,neededAt:s,signal:r,owner:i,enqueuedAt:Date.now(),promise:new Promise((e,t)=>{a=e,n=t}),resolve:a,reject:n},c=()=>{const e=this.pending.indexOf(o);e>=0&&(this.pending.splice(e,1),o.cleanup(),n(new Error("cancelled")),this.pump())};return o.cleanup=()=>r.removeEventListener("abort",c),r.addEventListener("abort",c,{once:!0}),this.pending.push(o),queueMicrotask(()=>this.pump()),o}promote(e,t,s){"demand"===t&&(e.priority="demand"),e.neededAt=Math.min(e.neededAt,s),this.pump()}pump(){for(;this.active.size<this.concurrency;){const e=[...this.active].some(e=>"prefetch"===e.priority),t=this.pending.some(e=>"demand"===e.priority)?"demand":"prefetch";if("prefetch"===t&&e)return;const s=this.pending.filter(e=>e.priority===t);if(!s.length)return;const r=[...new Set(s.map(e=>e.owner))],i=r.indexOf(this.lastOwner.get(t)),a=r[(i+1)%r.length],n=s.filter(e=>e.owner===a).sort((e,t)=>e.neededAt-t.neededAt)[0];this.pending.splice(this.pending.indexOf(n),1),n.cleanup(),this.active.add(n),this.lastOwner.set(t,a),n.startedAt=Date.now(),Promise.resolve().then(()=>{if(n.signal.aborted)throw new Error("cancelled");return n.run(n.startedAt-n.enqueuedAt)}).then(n.resolve,n.reject).finally(()=>{this.active.delete(n),this.pump()})}}}module.exports={ExportCache:class{constructor({libraryDir:e,limit:t=2147483648,ttl:s=9e4,concurrency:r}={}){this.libraryDir=e,this.limit=t,this.ttl=s,this.queue=new u(r),this.sessions=new Map,this.jobs=new Map,this.pins=new Map,this.roots=new Map,this.releases=new Map,this.timer=setInterval(()=>{for(const e of this.sessions.values())!e.managed&&Date.now()-e.touched>s&&this.release(e.id,e.pp.id)},Math.min(s,15e3)),this.timer.unref()}get(e,t){const s=this.sessions.get(e);if(!s||s.pp.id!==t)throw new Error("no such cache session");return s.touched=Date.now(),s}create(t,s,i,{managed:a=!1}={}){const n=i||JSON.parse(e.readFileSync(t.projectFile,"utf8").replace(/^\uFEFF/,""));if(n.revision!==s)throw new Error("Project changed; save/reload before exporting");const o={id:r.randomBytes(16).toString("hex"),pp:t,project:n,managed:a,touched:Date.now(),operations:new Set,controller:new AbortController,sources:new Map,blocks:new Map,leases:new Map,pins:new Set,prefetchSequence:-1,prefetchPlan:null,waitSequence:-1,state:"preparing",stats:{hits:0,misses:0,fallbacks:{},extractionMs:0,queueWaitMs:0,decodeMs:0,publishMs:0,prefetchSkipped:0,capacityFallbacks:0,peakDiskBytes:0}};return this.sessions.set(o.id,o),o.ready=this.prepare(o).then(()=>{o.state="ready"}).catch(e=>{o.state="error",o.error=e.message}),o.id}async prepare(s){const i=new Set(s.project.disabledTracks||[]),a=new Map;for(const n of s.project.clips)if("video"===n.kind&&!i.has(n.track)){if(s.controller.signal.aborted)throw new Error("cancelled");try{if(n.keyframes?.speed?.length)throw new Error("speed ramp");const i=Math.max(.1,Math.min(8,Number(n.props?.speed)||1));if(![n.in,n.duration,i].every(Number.isFinite)||n.in<0||n.duration<=0)throw new Error("invalid source interval");const l=s.project.media.find(e=>e.id===n.mediaId);a.has(n.mediaId)||a.set(n.mediaId,(async()=>{const i=await m(s.pp,this.libraryDir,l?.src),a=await t.stat(i),n=JSON.parse(await c("ffprobe",["-v","error","-select_streams","v:0","-show_streams","-of","json",i],s.controller.signal)),d=n.streams?.[0];if(!d||!h(d))throw new Error("unsupported/uncertain video metadata");const p=r.createHash("sha256");for await(const t of e.createReadStream(i)){if(s.controller.signal.aborted)throw new Error("cancelled");p.update(t)}return{file:i,stamp:`${a.size}:${a.mtimeMs}`,hash:p.digest("hex"),fps:o(d.r_frame_rate),width:d.width,height:d.height,duration:Number(d.duration)}})());const d=await a.get(n.mediaId);s.sources.set(n.id,{...d,start:n.in,end:n.in+n.duration*i})}catch(e){s.stats.fallbacks[n.id]=e.message}}}status(e,t){const s=this.get(e,t);return{id:e,state:s.state,error:s.error,clips:[...s.sources.keys()],stats:s.stats}}pin(e,t){e.pins.has(t)||(e.pins.add(t),this.pins.set(t,(this.pins.get(t)||0)+1))}unpin(e,t){if(!e.pins.delete(t))return;const s=(this.pins.get(t)||1)-1;s?this.pins.set(t,s):this.pins.delete(t)}async rootIndex(e){if(!this.roots.has(e)){const s=(async()=>{await t.mkdir(e,{recursive:!0});const s={entries:new Map,partials:new Map,lock:Promise.resolve()};return await this.scan(e,s),s})();this.roots.set(e,s),s.catch(()=>this.roots.delete(e))}return this.roots.get(e)}async scan(e,r){const i=new Map;for(const a of await t.readdir(e)){const n=s.join(e,a);if(/^[a-f0-9]{64}$/.test(a))try{const e=JSON.parse(await t.readFile(s.join(n,"manifest.json"))),r=await t.stat(n);if(!Number.isFinite(e.bytes)||e.bytes<0)throw new Error("invalid cache manifest");i.set(n,{dir:n,bytes:e.bytes,time:r.mtimeMs,manifest:e})}catch(e){if("ENOENT"!==e.code)throw e}else if(/^[a-f0-9]{64}\.partial-[a-f0-9]+$/.test(a)&&!this.jobs.has(n.slice(0,n.lastIndexOf(".partial-")))){const e=await t.stat(n);if(Date.now()-e.mtimeMs>12e4)await t.rm(n,{recursive:!0,force:!0}),r.partials.delete(n);else{const e=await Promise.all((await t.readdir(n)).map(e=>t.stat(s.join(n,e))));r.partials.set(n,e.reduce((e,t)=>e+t.size,0))}}}r.entries=i}async locked(e,t){const s=await this.rootIndex(e),r=s.lock.then(()=>t(s));return s.lock=r.catch(()=>{}),r}async makeRoom(e,s,r=0,i=!0){let a=[...s.entries.values()].reduce((e,t)=>e+t.bytes,0)+[...s.partials.values()].reduce((e,t)=>e+t,0)+r;for(const r of[...s.entries.values()].sort((e,t)=>e.time-t.time)){if(a<=this.limit)break;if(!this.pins.has(r.dir)&&!this.jobs.has(r.dir)){try{await t.rm(r.dir,{recursive:!0,force:!0})}catch(t){throw await this.scan(e,s),t}s.entries.delete(r.dir),a-=r.bytes}}if(a>this.limit&&i)return await this.scan(e,s),this.makeRoom(e,s,r,!1);if(a>this.limit)throw new Error("frame cache capacity exceeded");return a}async room(e,t=0,r){const i=()=>this.locked(e,async s=>(r&&s.partials.set(r,t),this.makeRoom(e,s,r?0:t)));try{return await i()}catch(t){const a=r&&this.jobs.get(r.slice(0,r.lastIndexOf(".partial-")));if(!/capacity/.test(t.message)||"demand"!==a?.queued.priority)throw t;const n=new Set,o=new Set;for(const t of this.sessions.values())for(const r of t.leases.values())s.dirname(r.dir)===e&&("demand"===r.priority?o:n).add(r.dir);for(const e of o)n.delete(e);for(const e of n)"demand"===this.jobs.get(e)?.queued.priority&&n.delete(e);if(!n.size)throw t;const c=[...n].map(e=>this.jobs.get(e)).filter(Boolean);for(const e of this.sessions.values())for(const[t,s]of e.leases)n.has(s.dir)&&this.removeLease(e,t);return await Promise.allSettled(c.map(e=>e.promise)),i()}}async build(e,i,a,o,h,m={}){const u=i+".partial-"+r.randomBytes(6).toString("hex");await t.mkdir(u,{recursive:!0});const f=[];let w="";const b=Math.max(0,5*o-2/a.fps),g=5*(o+1)+1/a.fps,y=await(d||(d=(async()=>{const e=l(await c("ffmpeg",["-version"]));if(e)return e;const t=l(await c("ffmpeg",["-hide_banner","-h","full"]));if(!t)throw new Error("ffmpeg 缺少 -fps_mode 和 -vsync");return t})().catch(e=>{throw d=void 0,e})),d),v=["-hide_banner","-loglevel","info","-nostdin","-y","-copyts","-ss",String(Math.max(0,b-2)),"-t",String(g-Math.max(0,b-2)+1),"-i",a.file,"-map","0:v:0","-an","-vf",`select='gte(t,${b-1e-5})*lt(t,${g})',showinfo`,...y,"-threads",String(n),"-compression_level","1",s.join(u,"%06d.png")];let k,E,S=Promise.resolve(),M=!1;const j=new AbortController,I=()=>j.abort();h.addEventListener("abort",I,{once:!0});try{await this.room(e,0,u),k=setInterval(()=>{M||(M=!0,S=(async()=>{const r=await t.readdir(u),i=await Promise.all(r.map(e=>t.stat(s.join(u,e))));await this.room(e,i.reduce((e,t)=>e+t.size,0),u)})().catch(e=>{E=e,j.abort()}).finally(()=>{M=!1}))},250),h.aborted&&j.abort();const r=Date.now();try{await c("ffmpeg",v,j.signal,e=>{w+=e;const t=w.split(/[\r\n]/);w=t.pop();for(const e of t){const t=/\bn:\s*\d+.*?pts_time:([\d.e+\-]+)/.exec(e);t&&f.push(Number(t[1]))}})}finally{m.decodeMs=Date.now()-r}const n=Date.now();if(clearInterval(k),await S,E)throw E;if(!p(f,a.fps))throw new Error("variable or invalid frame timestamps");const o=(await t.readdir(u)).filter(e=>e.endsWith(".png"));if(o.length!==f.length)throw new Error("incomplete extracted frames");const l=(await Promise.all(o.map(e=>t.stat(s.join(u,e))))).reduce((e,t)=>e+t.size,0);await this.room(e,l,u);const d={times:f,bytes:l,width:a.width,height:a.height};if(await t.writeFile(s.join(u,"manifest.json"),JSON.stringify(d)),h.aborted)throw new Error("cancelled");return await this.locked(e,async s=>{await this.makeRoom(e,s),await t.rename(u,i),s.partials.delete(u),s.entries.set(i,{dir:i,bytes:l,time:Date.now(),manifest:d})}),m.publishMs=Date.now()-n,d}catch(e){if(E)throw E;throw e}finally{clearInterval(k),await S,h.removeEventListener("abort",I),await this.locked(e,async e=>{await t.rm(u,{recursive:!0,force:!0}),e.partials.delete(u)})}}async block(e,t,s,r,i={}){const a=this.get(e,t),n=this.loadBlock(a,s,r,i);a.operations.add(n);try{return await n}finally{a.operations.delete(n)}}async loadBlock(e,i,a,{priority:n="demand",neededAt:o=0,sequence:c}={}){if(!["demand","prefetch"].includes(n)||!Number.isFinite(o)||o<0)throw new Error("invalid cache priority");await e.ready;const l=e.sources.get(i);if(!l||e.stats.fallbacks[i])return{fallback:e.stats.fallbacks[i]||"source unavailable"};if(!Number.isInteger(a)||a<Math.floor(l.start/5)||a>Math.floor(l.end/5))throw new Error("block outside clip interval");const d=i+":"+a,h=()=>"demand"===n||"demand"===e.leases.get(d)?.priority||null===e.prefetchPlan||c<=e.prefetchSequence&&e.prefetchPlan.has(d);let p,m;try{if(!h())return{skipped:"obsolete prefetch"};const c=await t.stat(l.file);if(`${c.size}:${c.mtimeMs}`!==l.stamp)throw new Error("source changed during export");const f=s.join(e.pp.dir,".export-cache"),w=await this.rootIndex(f),b=(u=JSON.stringify([2,l.hash,a,l.fps,"png-1"]),r.createHash("sha256").update(u).digest("hex"));if(p=s.join(f,b),e.controller.signal.aborted)throw new Error("cancelled");if(!h())return{skipped:"obsolete prefetch"};this.pin(e,p),m={clipId:i,block:a,key:b,dir:p,priority:"demand"===e.leases.get(d)?.priority?"demand":n},e.leases.set(d,m);for(const[t,s]of e.leases)"demand"===n&&s.clipId===i&&s.block<a-1&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===s.dir)||(e.blocks.delete(s.key),this.unpin(e,s.dir)));let g;try{g=await this.locked(f,async e=>{try{await t.access(s.join(p,"manifest.json"));const r=e.entries.get(p)?.manifest;if(!r)throw new Error("cache block not indexed");return r}catch(t){throw e.entries.delete(p),t}}),e.stats.hits++}catch{if(e.controller.signal.aborted)throw new Error("cancelled");e.stats.misses++;const t=this.jobs.get(p);if(t?.controller.signal.aborted&&await t.promise.catch(()=>{}),e.controller.signal.aborted)throw new Error("cancelled");if(!this.jobs.has(p)){const t=new AbortController,s=Date.now(),r={},i=this.queue.enqueue(async s=>{e.stats.queueWaitMs+=s;try{return await this.build(f,p,l,a,t.signal,r)}finally{e.stats.decodeMs+=r.decodeMs||0,e.stats.publishMs+=r.publishMs||0}},n,o,t.signal,e.id),c=i.promise;this.jobs.set(p,{controller:t,promise:c,queued:i}),c.finally(()=>{this.jobs.delete(p),e.stats.extractionMs+=Date.now()-s}).catch(()=>{})}const s=this.jobs.get(p);this.queue.promote(s.queued,n,o),g=await s.promise}if(e.controller.signal.aborted)throw new Error("cancelled");if(!h()||!e.leases.has(d))return{skipped:"obsolete prefetch"};const y=new Date;return await t.utimes(p,y,y),w.entries.has(p)&&(w.entries.get(p).time=y.getTime()),e.blocks.set(b,{clipId:i,block:a,dir:p,manifest:g}),e.stats.peakDiskBytes=Math.max(e.stats.peakDiskBytes,[...w.entries.values()].reduce((e,t)=>e+t.bytes,0)+[...w.partials.values()].reduce((e,t)=>e+t,0)),{key:b,...g}}catch(t){if("prefetch"===n&&!/variable or invalid frame timestamps|incomplete extracted frames/.test(t.message))return e.stats.prefetchSkipped++,e.leases.get(d)===m&&"prefetch"===m?.priority&&this.removeLease(e,d),{skipped:t.message};/capacity/.test(t.message)&&e.stats.capacityFallbacks++;const s=new Set(/variable or invalid frame timestamps|incomplete extracted frames/.test(t.message)?[...e.sources].filter(([,e])=>e.hash===l.hash).map(([e])=>e):[i]);for(const r of s)e.stats.fallbacks[r]=t.message;for(const[t,r]of e.leases)s.has(r.clipId)&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===r.dir)||(e.blocks.delete(r.key),this.unpin(e,r.dir)));return{fallback:t.message}}var u}removeLease(e,t){const s=e.leases.get(t);s&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===s.dir)||(e.blocks.delete(s.key),this.unpin(e,s.dir),this.pins.has(s.dir)||this.jobs.get(s.dir)?.controller.abort()))}retain(e,t,s,r,i=[]){const a=this.get(e,t);if(!Number.isSafeInteger(s)||s<0||!Array.isArray(r)||r.length>3||r.some(e=>"string"!=typeof e.clipId||!Number.isSafeInteger(e.block)||e.block<0))throw new Error("invalid prefetch plan");if(!Array.isArray(i)||i.length>a.project.clips.length||i.some(e=>!a.sources.has(e.clipId)||!Number.isSafeInteger(e.block)||e.block<0))throw new Error("invalid current blocks");if(!(s<=a.prefetchSequence)){a.prefetchSequence=s,a.prefetchPlan=new Set(r.map(e=>e.clipId+":"+e.block));for(const e of i){const t=a.leases.get(e.clipId+":"+e.block);if(t){t.priority="demand";const e=this.jobs.get(t.dir);e&&this.queue.promote(e.queued,"demand",0)}}for(const[e,t]of a.leases)"prefetch"!==t.priority||a.prefetchPlan.has(e)||this.removeLease(a,e)}}reportWait(e,t,{sequence:s,reason:r,elapsedMs:i=0}){const a=this.get(e,t);if(!Number.isSafeInteger(s)||s<0||![null,"source-block","source-image","encoder"].includes(r)||!Number.isFinite(i)||i<0||i>864e5)throw new Error("invalid export wait");s<=a.waitSequence||(a.waitSequence=s,a.wait=r?{reason:r,since:Date.now()-i}:null)}drop(e,t,s){const r=this.get(e,t);if(!Array.isArray(s)||s.length>r.project.clips.length)throw new Error("invalid clip list");const i=new Set(s);for(const[e,t]of r.leases)i.has(t.clipId)&&(r.leases.delete(e),[...r.leases.values()].some(e=>e.dir===t.dir)||(r.blocks.delete(t.key),this.unpin(r,t.dir),this.pins.has(t.dir)||this.jobs.get(t.dir)?.controller.abort()))}frame(e,t,r,i){const a=this.get(e,t).blocks.get(r);if(!a||!Number.isInteger(i)||i<0||i>=a.manifest.times.length)throw new Error("unknown cache frame");return s.join(a.dir,String(i+1).padStart(6,"0")+".png")}release(e,t){const s=this.sessions.get(e);if(!s)return this.releases.get(e)?.projectId===t?this.releases.get(e).done:void 0;if(s.pp.id!==t)return;this.sessions.delete(e),s.controller.abort();const r=[s.ready,...s.operations];for(const e of[...s.pins])if(this.unpin(s,e),!this.pins.has(e)){const t=this.jobs.get(e);t&&(t.controller.abort(),r.push(t.promise))}const i=Promise.allSettled(r).finally(()=>this.releases.delete(e));return this.releases.set(e,{projectId:t,done:i}),i}close(){clearInterval(this.timer);for(const e of this.sessions.values())this.release(e.id,e.pp.id)}},ExtractionQueue:u,frameIndex:function(e,t){let s=0,r=e.length;for(;s<r;){const i=s+r>>>1;e[i]<=t+1e-5?s=i+1:r=i}return s-1},frameSyncArgs:l,supported:h,validFrameTimes:p,localMedia:m,command:c,BLOCK_SECONDS:5};
1
+ "use strict";const e=require("node:fs"),t=e.promises,s=require("node:path"),r=require("node:crypto"),{spawn:i}=require("./cli/lib/processes"),a=require("node:os"),n=Math.min(2,a.availableParallelism?.()||a.cpus().length||1),o=e=>{const[t,s=1]=String(e).split("/").map(Number);return t/s};function c(e,t,s,r){return new Promise((a,n)=>{if(s?.aborted)return n(new Error("cancelled"));const o=i(e,t,{stdio:["ignore","pipe","pipe"]});let c,l="",d="";const h=()=>{c=new Error("cancelled"),o.kill("SIGKILL")},p=setTimeout(()=>{c=new Error(e+" timed out"),o.kill("SIGKILL")},12e4);s?.addEventListener("abort",h,{once:!0}),o.stdout.on("data",e=>{l+=e,l.length>16777216&&(c=new Error("probe output too large"),o.kill("SIGKILL"))}),o.stderr.on("data",e=>{r?.(String(e)),d=(d+e).slice(-4e3)}),o.on("error",e=>{c=e}),o.on("close",t=>{clearTimeout(p),s?.removeEventListener("abort",h),c||0!==t?n(c||new Error(d||e+" failed")):a(l)})})}function l(e){const t=/ffmpeg version\s+n?(\d+)\.(\d+)/.exec(e);if(t){const e=Number(t[1]),s=Number(t[2]);return e>5||5===e&&s>=1?["-fps_mode","passthrough"]:["-vsync","0"]}return/(?:^|[\r\n])\s*-fps_mode\b/.test(e)?["-fps_mode","passthrough"]:/(?:^|[\r\n])\s*-vsync\b/.test(e)?["-vsync","0"]:null}let d;function h(e){const t=o(e.r_frame_rate),s=o(e.avg_frame_rate);return Number.isFinite(t)&&t>0&&t<=120&&Math.abs(t-s)<1e-4&&0===Number(e.start_time||0)&&["1:1","0:1",void 0].includes(e.sample_aspect_ratio)&&!e.tags?.rotate&&!e.side_data_list?.some(e=>e.rotation)&&["yuv420p","yuvj420p"].includes(e.pix_fmt)&&["bt709","smpte170m","bt470bg"].includes(e.color_space)&&[void 0,"unknown","bt709","smpte170m","bt470bg"].includes(e.color_primaries)&&[void 0,"unknown","bt709","smpte170m","gamma22","gamma28"].includes(e.color_transfer)&&e.width>0&&e.height>0}function p(e,t){const s=1/t,r=Math.max(.002,.1*s);return e.length>0&&e.every((t,i)=>Number.isFinite(t)&&(!i||t>e[i-1]&&Math.abs(t-e[i-1]-s)<=r))}async function m(e,r,i){const a=`/projects/${e.id}/media/`;let n,o;if(i?.startsWith(a))n=e.mediaDir,o=i.slice(a.length);else if("default"===e.id&&i?.startsWith("/media/"))n=e.mediaDir,o=i.slice(7);else{if(!i?.startsWith("/library/"))throw new Error("non-local or cross-project source");n=r,o=i.slice(9)}n=await t.realpath(n);const c=await t.realpath(s.resolve(n,decodeURIComponent(o)));if(!c.startsWith(n+s.sep))throw new Error("source escapes media directory");return c}class u{constructor(e=((a.availableParallelism?.()||a.cpus().length||1)>=8?2:1)){this.concurrency=e,this.pending=[],this.active=new Set,this.lastOwner=new Map}enqueue(e,t,s,r,i="default"){let a,n;const o={run:e,priority:t,neededAt:s,signal:r,owner:i,enqueuedAt:Date.now(),promise:new Promise((e,t)=>{a=e,n=t}),resolve:a,reject:n},c=()=>{const e=this.pending.indexOf(o);e>=0&&(this.pending.splice(e,1),o.cleanup(),n(new Error("cancelled")),this.pump())};return o.cleanup=()=>r.removeEventListener("abort",c),r.addEventListener("abort",c,{once:!0}),this.pending.push(o),queueMicrotask(()=>this.pump()),o}promote(e,t,s){"demand"===t&&(e.priority="demand"),e.neededAt=Math.min(e.neededAt,s),this.pump()}pump(){for(;this.active.size<this.concurrency;){const e=[...this.active].some(e=>"prefetch"===e.priority),t=this.pending.some(e=>"demand"===e.priority)?"demand":"prefetch";if("prefetch"===t&&e)return;const s=this.pending.filter(e=>e.priority===t);if(!s.length)return;const r=[...new Set(s.map(e=>e.owner))],i=r.indexOf(this.lastOwner.get(t)),a=r[(i+1)%r.length],n=s.filter(e=>e.owner===a).sort((e,t)=>e.neededAt-t.neededAt)[0];this.pending.splice(this.pending.indexOf(n),1),n.cleanup(),this.active.add(n),this.lastOwner.set(t,a),n.startedAt=Date.now(),Promise.resolve().then(()=>{if(n.signal.aborted)throw new Error("cancelled");return n.run(n.startedAt-n.enqueuedAt)}).then(n.resolve,n.reject).finally(()=>{this.active.delete(n),this.pump()})}}}module.exports={ExportCache:class{constructor({libraryDir:e,limit:t=2147483648,ttl:s=9e4,concurrency:r}={}){this.libraryDir=e,this.limit=t,this.ttl=s,this.queue=new u(r),this.sessions=new Map,this.jobs=new Map,this.pins=new Map,this.roots=new Map,this.releases=new Map,this.timer=setInterval(()=>{for(const e of this.sessions.values())!e.managed&&Date.now()-e.touched>s&&this.release(e.id,e.pp.id)},Math.min(s,15e3)),this.timer.unref()}get(e,t){const s=this.sessions.get(e);if(!s||s.pp.id!==t)throw new Error("no such cache session");return s.touched=Date.now(),s}create(t,s,i,{managed:a=!1}={}){const n=i||JSON.parse(e.readFileSync(t.projectFile,"utf8").replace(/^\uFEFF/,""));if(n.revision!==s)throw new Error("Project changed; save/reload before exporting");const o={id:r.randomBytes(16).toString("hex"),pp:t,project:n,managed:a,touched:Date.now(),operations:new Set,controller:new AbortController,sources:new Map,blocks:new Map,leases:new Map,pins:new Set,prefetchSequence:-1,prefetchPlan:null,waitSequence:-1,state:"preparing",stats:{hits:0,misses:0,fallbacks:{},extractionMs:0,queueWaitMs:0,decodeMs:0,publishMs:0,prefetchSkipped:0,capacityFallbacks:0,peakDiskBytes:0}};return this.sessions.set(o.id,o),o.ready=this.prepare(o).then(()=>{o.state="ready"}).catch(e=>{o.state="error",o.error=e.message}),o.id}async prepare(s){const i=new Set(s.project.disabledTracks||[]),a=new Map;for(const n of s.project.clips)if("video"===n.kind&&!i.has(n.track)){if(s.controller.signal.aborted)throw new Error("cancelled");try{if(n.keyframes?.speed?.length)throw new Error("speed ramp");const i=Math.max(.1,Math.min(8,Number(n.props?.speed)||1));if(![n.in,n.duration,i].every(Number.isFinite)||n.in<0||n.duration<=0)throw new Error("invalid source interval");const l=s.project.media.find(e=>e.id===n.mediaId);a.has(n.mediaId)||(s.activity={mediaId:n.mediaId,phase:"probe"},a.set(n.mediaId,(async()=>{const i=await m(s.pp,this.libraryDir,l?.src),a=await t.stat(i),d=JSON.parse(await c("ffprobe",["-v","error","-select_streams","v:0","-show_streams","-of","json",i],s.controller.signal)),p=d.streams?.[0];if(!p||!h(p))throw new Error("unsupported/uncertain video metadata");const u=r.createHash("sha256");s.activity={mediaId:n.mediaId,phase:"hash",bytes:a.size};for await(const t of e.createReadStream(i)){if(s.controller.signal.aborted)throw new Error("cancelled");u.update(t)}return{file:i,stamp:`${a.size}:${a.mtimeMs}`,hash:u.digest("hex"),fps:o(p.r_frame_rate),width:p.width,height:p.height,duration:Number(p.duration)}})()));const d=await a.get(n.mediaId);s.sources.set(n.id,{...d,start:n.in,end:n.in+n.duration*i})}catch(e){s.stats.fallbacks[n.id]=e.message}}s.activity=null}status(e,t){const s=this.get(e,t);return{id:e,state:s.state,error:s.error,clips:[...s.sources.keys()],stats:s.stats}}pin(e,t){e.pins.has(t)||(e.pins.add(t),this.pins.set(t,(this.pins.get(t)||0)+1))}unpin(e,t){if(!e.pins.delete(t))return;const s=(this.pins.get(t)||1)-1;s?this.pins.set(t,s):this.pins.delete(t)}async rootIndex(e){if(!this.roots.has(e)){const s=(async()=>{await t.mkdir(e,{recursive:!0});const s={entries:new Map,partials:new Map,lock:Promise.resolve()};return await this.scan(e,s),s})();this.roots.set(e,s),s.catch(()=>this.roots.delete(e))}return this.roots.get(e)}async scan(e,r){const i=new Map;for(const a of await t.readdir(e)){const n=s.join(e,a);if(/^[a-f0-9]{64}$/.test(a))try{const e=JSON.parse(await t.readFile(s.join(n,"manifest.json"))),r=await t.stat(n);if(!Number.isFinite(e.bytes)||e.bytes<0)throw new Error("invalid cache manifest");i.set(n,{dir:n,bytes:e.bytes,time:r.mtimeMs,manifest:e})}catch(e){if("ENOENT"!==e.code)throw e}else if(/^[a-f0-9]{64}\.partial-[a-f0-9]+$/.test(a)&&!this.jobs.has(n.slice(0,n.lastIndexOf(".partial-")))){const e=await t.stat(n);if(Date.now()-e.mtimeMs>12e4)await t.rm(n,{recursive:!0,force:!0}),r.partials.delete(n);else{const e=await Promise.all((await t.readdir(n)).map(e=>t.stat(s.join(n,e))));r.partials.set(n,e.reduce((e,t)=>e+t.size,0))}}}r.entries=i}async locked(e,t){const s=await this.rootIndex(e),r=s.lock.then(()=>t(s));return s.lock=r.catch(()=>{}),r}async makeRoom(e,s,r=0,i=!0){let a=[...s.entries.values()].reduce((e,t)=>e+t.bytes,0)+[...s.partials.values()].reduce((e,t)=>e+t,0)+r;for(const r of[...s.entries.values()].sort((e,t)=>e.time-t.time)){if(a<=this.limit)break;if(!this.pins.has(r.dir)&&!this.jobs.has(r.dir)){try{await t.rm(r.dir,{recursive:!0,force:!0})}catch(t){throw await this.scan(e,s),t}s.entries.delete(r.dir),a-=r.bytes}}if(a>this.limit&&i)return await this.scan(e,s),this.makeRoom(e,s,r,!1);if(a>this.limit)throw new Error("frame cache capacity exceeded");return a}async room(e,t=0,r){const i=()=>this.locked(e,async s=>(r&&s.partials.set(r,t),this.makeRoom(e,s,r?0:t)));try{return await i()}catch(t){const a=r&&this.jobs.get(r.slice(0,r.lastIndexOf(".partial-")));if(!/capacity/.test(t.message)||"demand"!==a?.queued.priority)throw t;const n=new Set,o=new Set;for(const t of this.sessions.values())for(const r of t.leases.values())s.dirname(r.dir)===e&&("demand"===r.priority?o:n).add(r.dir);for(const e of o)n.delete(e);for(const e of n)"demand"===this.jobs.get(e)?.queued.priority&&n.delete(e);if(!n.size)throw t;const c=[...n].map(e=>this.jobs.get(e)).filter(Boolean);for(const e of this.sessions.values())for(const[t,s]of e.leases)n.has(s.dir)&&this.removeLease(e,t);return await Promise.allSettled(c.map(e=>e.promise)),i()}}async build(e,i,a,o,h,m={}){const u=i+".partial-"+r.randomBytes(6).toString("hex");await t.mkdir(u,{recursive:!0});const f=[];let w="";const b=Math.max(0,5*o-2/a.fps),g=5*(o+1)+1/a.fps,y=await(d||(d=(async()=>{const e=l(await c("ffmpeg",["-version"]));if(e)return e;const t=l(await c("ffmpeg",["-hide_banner","-h","full"]));if(!t)throw new Error("ffmpeg 缺少 -fps_mode 和 -vsync");return t})().catch(e=>{throw d=void 0,e})),d),v=["-hide_banner","-loglevel","info","-nostdin","-y","-copyts","-ss",String(Math.max(0,b-2)),"-t",String(g-Math.max(0,b-2)+1),"-i",a.file,"-map","0:v:0","-an","-vf",`select='gte(t,${b-1e-5})*lt(t,${g})',showinfo`,...y,"-threads",String(n),"-compression_level","1",s.join(u,"%06d.png")];let k,E,S=Promise.resolve(),M=!1;const j=new AbortController,I=()=>j.abort();h.addEventListener("abort",I,{once:!0});try{await this.room(e,0,u),k=setInterval(()=>{M||(M=!0,S=(async()=>{const r=await t.readdir(u),i=await Promise.all(r.map(e=>t.stat(s.join(u,e))));await this.room(e,i.reduce((e,t)=>e+t.size,0),u)})().catch(e=>{E=e,j.abort()}).finally(()=>{M=!1}))},250),h.aborted&&j.abort();const r=Date.now();try{await c("ffmpeg",v,j.signal,e=>{w+=e;const t=w.split(/[\r\n]/);w=t.pop();for(const e of t){const t=/\bn:\s*\d+.*?pts_time:([\d.e+\-]+)/.exec(e);t&&f.push(Number(t[1]))}})}finally{m.decodeMs=Date.now()-r}const n=Date.now();if(clearInterval(k),await S,E)throw E;if(!p(f,a.fps))throw new Error("variable or invalid frame timestamps");const o=(await t.readdir(u)).filter(e=>e.endsWith(".png"));if(o.length!==f.length)throw new Error("incomplete extracted frames");const l=(await Promise.all(o.map(e=>t.stat(s.join(u,e))))).reduce((e,t)=>e+t.size,0);await this.room(e,l,u);const d={times:f,bytes:l,width:a.width,height:a.height};if(await t.writeFile(s.join(u,"manifest.json"),JSON.stringify(d)),h.aborted)throw new Error("cancelled");return await this.locked(e,async s=>{await this.makeRoom(e,s),await t.rename(u,i),s.partials.delete(u),s.entries.set(i,{dir:i,bytes:l,time:Date.now(),manifest:d})}),m.publishMs=Date.now()-n,d}catch(e){if(E)throw E;throw e}finally{clearInterval(k),await S,h.removeEventListener("abort",I),await this.locked(e,async e=>{await t.rm(u,{recursive:!0,force:!0}),e.partials.delete(u)})}}async block(e,t,s,r,i={}){const a=this.get(e,t),n=this.loadBlock(a,s,r,i);a.operations.add(n);try{return await n}finally{a.operations.delete(n)}}async loadBlock(e,i,a,{priority:n="demand",neededAt:o=0,sequence:c}={}){if(!["demand","prefetch"].includes(n)||!Number.isFinite(o)||o<0)throw new Error("invalid cache priority");await e.ready;const l=e.sources.get(i);if(!l||e.stats.fallbacks[i])return{fallback:e.stats.fallbacks[i]||"source unavailable"};if(!Number.isInteger(a)||a<Math.floor(l.start/5)||a>Math.floor(l.end/5))throw new Error("block outside clip interval");const d=i+":"+a,h=()=>"demand"===n||"demand"===e.leases.get(d)?.priority||null===e.prefetchPlan||c<=e.prefetchSequence&&e.prefetchPlan.has(d);let p,m;try{if(!h())return{skipped:"obsolete prefetch"};const c=await t.stat(l.file);if(`${c.size}:${c.mtimeMs}`!==l.stamp)throw new Error("source changed during export");const f=s.join(e.pp.dir,".export-cache"),w=await this.rootIndex(f),b=(u=JSON.stringify([2,l.hash,a,l.fps,"png-1"]),r.createHash("sha256").update(u).digest("hex"));if(p=s.join(f,b),e.controller.signal.aborted)throw new Error("cancelled");if(!h())return{skipped:"obsolete prefetch"};this.pin(e,p),m={clipId:i,block:a,key:b,dir:p,priority:"demand"===e.leases.get(d)?.priority?"demand":n},e.leases.set(d,m);for(const[t,s]of e.leases)"demand"===n&&s.clipId===i&&s.block<a-1&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===s.dir)||(e.blocks.delete(s.key),this.unpin(e,s.dir)));let g;try{g=await this.locked(f,async e=>{try{await t.access(s.join(p,"manifest.json"));const r=e.entries.get(p)?.manifest;if(!r)throw new Error("cache block not indexed");return r}catch(t){throw e.entries.delete(p),t}}),e.stats.hits++}catch{if(e.controller.signal.aborted)throw new Error("cancelled");e.stats.misses++;const t=this.jobs.get(p);if(t?.controller.signal.aborted&&await t.promise.catch(()=>{}),e.controller.signal.aborted)throw new Error("cancelled");if(!this.jobs.has(p)){const t=new AbortController,s=Date.now(),r={},i=this.queue.enqueue(async s=>{e.stats.queueWaitMs+=s;try{return await this.build(f,p,l,a,t.signal,r)}finally{e.stats.decodeMs+=r.decodeMs||0,e.stats.publishMs+=r.publishMs||0}},n,o,t.signal,e.id),c=i.promise;this.jobs.set(p,{controller:t,promise:c,queued:i}),c.finally(()=>{this.jobs.delete(p),e.stats.extractionMs+=Date.now()-s}).catch(()=>{})}const s=this.jobs.get(p);this.queue.promote(s.queued,n,o),g=await s.promise}if(e.controller.signal.aborted)throw new Error("cancelled");if(!h()||!e.leases.has(d))return{skipped:"obsolete prefetch"};const y=new Date;return await t.utimes(p,y,y),w.entries.has(p)&&(w.entries.get(p).time=y.getTime()),e.blocks.set(b,{clipId:i,block:a,dir:p,manifest:g}),e.stats.peakDiskBytes=Math.max(e.stats.peakDiskBytes,[...w.entries.values()].reduce((e,t)=>e+t.bytes,0)+[...w.partials.values()].reduce((e,t)=>e+t,0)),{key:b,...g}}catch(t){if("prefetch"===n&&!/variable or invalid frame timestamps|incomplete extracted frames/.test(t.message))return e.stats.prefetchSkipped++,e.leases.get(d)===m&&"prefetch"===m?.priority&&this.removeLease(e,d),{skipped:t.message};/capacity/.test(t.message)&&e.stats.capacityFallbacks++;const s=new Set(/variable or invalid frame timestamps|incomplete extracted frames/.test(t.message)?[...e.sources].filter(([,e])=>e.hash===l.hash).map(([e])=>e):[i]);for(const r of s)e.stats.fallbacks[r]=t.message;for(const[t,r]of e.leases)s.has(r.clipId)&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===r.dir)||(e.blocks.delete(r.key),this.unpin(e,r.dir)));return{fallback:t.message}}var u}removeLease(e,t){const s=e.leases.get(t);s&&(e.leases.delete(t),[...e.leases.values()].some(e=>e.dir===s.dir)||(e.blocks.delete(s.key),this.unpin(e,s.dir),this.pins.has(s.dir)||this.jobs.get(s.dir)?.controller.abort()))}retain(e,t,s,r,i=[]){const a=this.get(e,t);if(!Number.isSafeInteger(s)||s<0||!Array.isArray(r)||r.length>3||r.some(e=>"string"!=typeof e.clipId||!Number.isSafeInteger(e.block)||e.block<0))throw new Error("invalid prefetch plan");if(!Array.isArray(i)||i.length>a.project.clips.length||i.some(e=>!a.sources.has(e.clipId)||!Number.isSafeInteger(e.block)||e.block<0))throw new Error("invalid current blocks");if(!(s<=a.prefetchSequence)){a.prefetchSequence=s,a.prefetchPlan=new Set(r.map(e=>e.clipId+":"+e.block));for(const e of i){const t=a.leases.get(e.clipId+":"+e.block);if(t){t.priority="demand";const e=this.jobs.get(t.dir);e&&this.queue.promote(e.queued,"demand",0)}}for(const[e,t]of a.leases)"prefetch"!==t.priority||a.prefetchPlan.has(e)||this.removeLease(a,e)}}reportWait(e,t,{sequence:s,reason:r,elapsedMs:i=0}){const a=this.get(e,t);if(!Number.isSafeInteger(s)||s<0||![null,"source-block","source-image","encoder","video-seek","frame-assets","audio-mix"].includes(r)||!Number.isFinite(i)||i<0||i>864e5)throw new Error("invalid export wait");s<=a.waitSequence||(a.waitSequence=s,a.wait=r?{reason:r,since:Date.now()-i}:null)}drop(e,t,s){const r=this.get(e,t);if(!Array.isArray(s)||s.length>r.project.clips.length)throw new Error("invalid clip list");const i=new Set(s);for(const[e,t]of r.leases)i.has(t.clipId)&&(r.leases.delete(e),[...r.leases.values()].some(e=>e.dir===t.dir)||(r.blocks.delete(t.key),this.unpin(r,t.dir),this.pins.has(t.dir)||this.jobs.get(t.dir)?.controller.abort()))}frame(e,t,r,i){const a=this.get(e,t).blocks.get(r);if(!a||!Number.isInteger(i)||i<0||i>=a.manifest.times.length)throw new Error("unknown cache frame");return s.join(a.dir,String(i+1).padStart(6,"0")+".png")}release(e,t){const s=this.sessions.get(e);if(!s)return this.releases.get(e)?.projectId===t?this.releases.get(e).done:void 0;if(s.pp.id!==t)return;this.sessions.delete(e),s.controller.abort();const r=[s.ready,...s.operations];for(const e of[...s.pins])if(this.unpin(s,e),!this.pins.has(e)){const t=this.jobs.get(e);t&&(t.controller.abort(),r.push(t.promise))}const i=Promise.allSettled(r).finally(()=>this.releases.delete(e));return this.releases.set(e,{projectId:t,done:i}),i}close(){clearInterval(this.timer);for(const e of this.sessions.values())this.release(e.id,e.pp.id)}},ExtractionQueue:u,frameIndex:function(e,t){let s=0,r=e.length;for(;s<r;){const i=s+r>>>1;e[i]<=t+1e-5?s=i+1:r=i}return s-1},frameSyncArgs:l,supported:h,validFrameTimes:p,localMedia:m,command:c,BLOCK_SECONDS:5};
@@ -1 +1 @@
1
- "use strict";const e=require("node:fs"),r=require("node:os"),s=require("node:path"),{spawn:o}=require("./cli/lib/processes"),{ensureBrowser:t}=require("./cli/lib/browser");let i,n,c=!1;async function a(){if(!c){if(c=!0,i&&null===i.exitCode&&null===i.signalCode){const e=new Promise(e=>i.once("close",e));i.kill();const r=setTimeout(()=>i.kill("SIGKILL"),3e3);await e,clearTimeout(r)}n&&e.rmSync(n,{recursive:!0,force:!0}),process.exit()}}process.on("SIGTERM",a),process.on("disconnect",a),process.once("message",async d=>{try{process.send({state:"preparing-browser"});const l=await t(d.browser);if(c)return;n=e.mkdtempSync(s.join(r.tmpdir(),"fablecut-export-chrome-"));const p=new URL(d.url);for(const[e,r]of Object.entries({project_id:d.projectId,cliExport:d.id,cliExportName:d.name,cliExportEngine:d.engine,exportJob:d.id}))p.searchParams.set(e,r);process.send({state:"preparing-media",browser:l}),i=o(l,["--headless=new","--no-first-run","--no-default-browser-check",..."darwin"===process.platform?["--use-mock-keychain"]:[],"--autoplay-policy=no-user-gesture-required","--disable-background-timer-throttling","--disable-renderer-backgrounding","--user-data-dir="+n,"--window-size=1440,1000",p.href],{stdio:["ignore","ignore","pipe"]});let u="";i.stderr.on("data",e=>{u=(u+e).slice(-2e3)}),i.on("error",e=>process.send?.({state:"error",error:e.message})),i.once("close",()=>{c||process.send?.({state:"error",error:"Chrome exited before export completed: "+u.slice(-800)}),a()})}catch(e){process.send?.({state:"error",error:e.message}),await a()}});
1
+ "use strict";const e=require("node:fs"),r=require("node:os"),s=require("node:path"),{spawn:o}=require("./cli/lib/processes"),{ensureBrowser:t}=require("./cli/lib/browser");let i,n,c=!1;async function a(){if(!c){if(c=!0,i&&null===i.exitCode&&null===i.signalCode){const e=new Promise(e=>i.once("close",e));i.kill();const r=setTimeout(()=>i.kill("SIGKILL"),3e3);await e,clearTimeout(r)}n&&e.rmSync(n,{recursive:!0,force:!0}),process.exit()}}process.on("SIGTERM",a),process.on("disconnect",a),process.once("message",async d=>{try{process.send({state:"preparing-browser"});const l=await t(d.browser,{log:e=>{process.stderr.write(e+"\n");try{process.send?.({state:"preparing-browser",detail:String(e).slice(0,300)})}catch{}}});if(c)return;n=e.mkdtempSync(s.join(r.tmpdir(),"fablecut-export-chrome-"));const p=new URL(d.url);for(const[e,r]of Object.entries({project_id:d.projectId,cliExport:d.id,cliExportName:d.name,cliExportEngine:d.engine,exportJob:d.id}))p.searchParams.set(e,r);process.send({state:"preparing-media",browser:l}),i=o(l,["--headless=new","--no-first-run","--no-default-browser-check",..."darwin"===process.platform?["--use-mock-keychain"]:[],"--autoplay-policy=no-user-gesture-required","--disable-background-timer-throttling","--disable-renderer-backgrounding","--user-data-dir="+n,"--window-size=1440,1000",p.href],{stdio:["ignore","ignore","pipe"]});let u="";i.stderr.on("data",e=>{u=(u+e).slice(-2e3)}),i.on("error",e=>process.send?.({state:"error",error:e.message})),i.once("close",(e,r)=>{c||process.send?.({state:"error",exitCode:e,signal:r,error:`Chrome exited (${e??r}) before export completed: `+u.slice(-800)}),a()})}catch(e){process.send?.({state:"error",error:e.message}),await a()}});
@@ -1 +1 @@
1
- "use strict";class OrderedFrameQueue{constructor(e,t=4,a=33554432){this.write=e,this.maxFrames=t,this.maxBytes=a,this.pending=[],this.bytes=0,this.peakBytes=0,this.error=null}async push(e){for(;this.pending.length&&(this.pending.length>=this.maxFrames||this.bytes+e.size>this.maxBytes);)await this.pending[0];if(this.error)throw this.error;this.bytes+=e.size,this.peakBytes=Math.max(this.peakBytes,this.bytes);const t=(this.pending.at(-1)||Promise.resolve()).then(()=>{if(this.error)throw this.error;return this.write(e)}).catch(e=>{this.error=e}).finally(()=>{this.bytes-=e.size,this.pending.shift()});this.pending.push(t)}async finish(){if(await Promise.all(this.pending),this.error)throw this.error}cancel(){this.error=this.error||new Error("cancelled")}}function cachedFrameIndex(e,t){let a=0,s=e.length;for(;a<s;){const r=a+s>>>1;e[r]<=t+1e-5?a=r+1:s=r}return a-1}function sourceBlockPlan(e,t){const a=[];for(const s of e){const e=Math.max(.1,Math.min(8,Number(s.props?.speed)||1)),r=s.start+s.duration;let i=Math.max(0,Math.ceil(s.start*t-1e-7));for(;i/t<r;){const r=i/t,o=s.in+(r-s.start)*e,n=Math.floor(o/5);a.push({clip:s,time:o,block:n,neededAt:r}),i=Math.max(i+1,Math.ceil((s.start+(5*(n+1)-s.in)/e)*t-1e-7))}}return a.sort((e,t)=>e.neededAt-t.neededAt)}function sourceReadAhead(e,t,a){return sourceBlockPlan(e,a).filter(e=>e.neededAt>t).slice(0,3)}class SourceReadAhead{constructor(e,t,{concurrency:a=3,syncPlan:s=async()=>{},key:r=e=>e}={}){this.load=e,this.requested=t,this.concurrency=a,this.syncPlan=s,this.key=r,this.plan=[],this.active=new Map,this.stopped=!1,this.sequence=0,this.readySequence=0,this.syncing=Promise.resolve(),this.signature=""}update(e,t=[]){if(this.stopped)return;const a=JSON.stringify([e.map(this.key),t]);if(a===this.signature)return;this.signature=a,this.plan=e;const s=++this.sequence;this.syncing=this.syncing.catch(()=>{}).then(()=>this.syncPlan(e,t,s)).then(()=>{s===this.sequence&&(this.readySequence=s,this.pump())}).catch(()=>{})}pump(){if(!this.stopped&&this.readySequence===this.sequence)for(const e of this.plan){if(this.active.size>=this.concurrency)break;const t=this.key(e);if(this.active.has(t)||this.requested(e))continue;const a=this.sequence,s=Promise.resolve().then(()=>this.load(e,a)).catch(()=>{}).finally(()=>{this.active.delete(t),this.pump()});this.active.set(t,s)}}async stop(){this.stopped=!0,this.plan=[],await this.syncing,await Promise.allSettled(this.active.values())}}class ExportWaitTracker{constructor(e,{now:t=()=>performance.now(),interval:a=500,threshold:s=250}={}){this.report=e,this.now=t,this.threshold=s,this.active=new Set,this.sequence=0,this.last=null,this.timer=setInterval(()=>this.flush(),a)}async track(e,t){const a={reason:e,started:this.now()};this.active.add(a);try{return await t()}finally{this.active.delete(a)}}flush(){const e=[...this.active].filter(e=>this.now()-e.started>=this.threshold).sort((e,t)=>["source-block","source-image","encoder"].indexOf(e.reason)-["source-block","source-image","encoder"].indexOf(t.reason)||e.started-t.started)[0];(e||null)!==this.last&&(this.last=e||null,Promise.resolve(this.report({sequence:++this.sequence,reason:e?.reason||null,elapsedMs:e?Math.max(0,this.now()-e.started):0})).catch(()=>{this.last===(e||null)&&(this.last=void 0)}))}stop(){clearInterval(this.timer),this.active.clear(),this.flush()}}class FrameReadAhead{constructor(e,t=2){this.load=e,this.concurrency=t,this.plan=[],this.active=new Map,this.stopped=!1}update(e){this.stopped||(this.plan=e,this.pump())}pump(){for(;!this.stopped&&this.active.size<this.concurrency&&this.plan.length;){const e=this.plan.shift();if(this.active.has(e.key))continue;const t=Promise.resolve().then(()=>this.load(e)).catch(()=>{}).finally(()=>{this.active.delete(e.key),this.pump()});this.active.set(e.key,t)}}async stop(){this.stopped=!0,this.plan=[],await Promise.all(this.active.values())}}class SnapshotPipeline{constructor(e,t,a=2){this.capture=e,this.write=t,this.depth=a,this.pending=[]}async push(e){for(;this.pending.length>=this.depth;)await this.drain();const t=await this.capture(e);t.result.catch(()=>{}),this.pending.push(t)}async drain(){const e=this.pending.shift();e&&await this.write(await e.result)}async finish(){for(;this.pending.length;)await this.drain()}async settle(){await Promise.allSettled(this.pending.splice(0).map(e=>e.result))}}function createSnapshotEncoder(e){let t,a=0;const s=new Map,r=()=>{t?.terminate(),t=null;for(const{reject:e}of s.values())e(new Error("snapshot encoder stopped"));s.clear(),e.removeEventListener("abort",r)};if("function"==typeof Worker&&"function"==typeof OffscreenCanvas&&"function"==typeof createImageBitmap)try{t=new Worker("optimized-encoder-worker.js"),t.onmessage=({data:e})=>{const t=s.get(e.id);t&&(s.delete(e.id),e.error?t.reject(new Error(e.error)):t.resolve(e.blob))},t.onerror=e=>{e.preventDefault(),r()},e.addEventListener("abort",r,{once:!0})}catch{r()}return{close:r,async capture(i){if(e.aborted)throw new Error("cancelled");if(!t)return{result:Promise.resolve(await new Promise(e=>i.toBlob(e,"image/jpeg",.95)))};const o=await createImageBitmap(i);if(e.aborted)throw o.close(),new Error("cancelled");const n=a++,c=(async()=>{const a=await createImageBitmap(o);return new Promise((r,i)=>{if(e.aborted||!t)return a.close(),void i(new Error("snapshot encoder stopped"));s.set(n,{resolve:r,reject:i});try{t.postMessage({id:n,bitmap:a},[a])}catch(e){a.close(),s.delete(n),i(e)}})})().catch(async t=>{if(e.aborted)throw t;r();const a=document.createElement("canvas");a.width=o.width,a.height=o.height;try{return a.getContext("2d").drawImage(o,0,0),await new Promise(e=>a.toBlob(e,"image/jpeg",.95))}finally{a.width=a.height=0}}).finally(()=>o.close());return c.catch(()=>{}),{result:c}}}}let optimizedSources=null,optimizedAbort=null,optimizedMaskCanvas=null;function optimizedMaskSource(e){return optimizedMaskCanvas||(optimizedMaskCanvas=document.createElement("canvas")),optimizedMaskCanvas.width!==e.width&&(optimizedMaskCanvas.width=e.width),optimizedMaskCanvas.height!==e.height&&(optimizedMaskCanvas.height=e.height),optimizedMaskCanvas.getContext("2d").drawImage(e,0,0),optimizedMaskCanvas}async function optimizedExport(e={}){if(state.exporting)return;pause(),state.exporting=!0,state.rendering=!0,renderCancelled=!1;const t=performance.now(),a=new AbortController;optimizedAbort=a;const s={engine:"optimized",frames:0,phases:{},samples:{},peakDecodedBytes:0,peakQueueBytes:0},r=(e,t)=>{const a=s.samples[e]||(s.samples[e]={count:0,totalMs:0,maxMs:0,histogram:Array(24).fill(0)});a.count++,a.totalMs+=t,a.maxMs=Math.max(a.maxMs,t),a.histogram[Math.min(23,Math.max(0,Math.ceil(Math.log2(Math.max(1,t)))))]++};let i,o=!1,n=!1;const c=()=>{if(i)throw i;if(renderCancelled||a.signal.aborted)throw new Error("cancelled")},d=async(e,t,s)=>{c();const r=await fetch(e,{method:s||(void 0===t?"GET":"POST"),body:void 0===t?void 0:t instanceof Blob?t:JSON.stringify(t),signal:a.signal}),i=await r.json();if(!r.ok||i.error)throw Object.assign(new Error(i.error||"Export request failed"),{status:r.status});return i};let h,l,p,m,u,f,w,g,y,k,x,v=0,b=new Set;const M=new Map,S=new Map,E=new Map,P=new Set,z=new Set,A=(e,t="")=>projectApi(`/api/export/cache/${e}`)+`&id=${h}${t}`,j=()=>{h&&fetch(A("release"),{method:"POST",keepalive:!0}).catch(()=>{}),l&&fetch(`/api/export/end?id=${l}&discard=1`,{method:"POST",keepalive:!0}).catch(()=>{})},C=()=>{a.abort(),j()},T=e=>{"Escape"===e.key&&(renderCancelled=!0,a.abort()),e.stopImmediatePropagation(),e.preventDefault()};window.addEventListener("pagehide",C),window.addEventListener("keydown",T,!0),els.exportOverlay.classList.remove("hidden"),els.exportProgress.style.width="0%",els.exportTitle.textContent=uiText("Preparing source frames…"),els.exportNote.textContent=uiText("Pre-decoding video and caching frames. You can switch tabs.");try{const j=performance.now()+5e3;for(;;){c();const e=await d(projectApi("/api/project"));if(!runtime.saveTimer&&e.revision===project.revision)break;if(performance.now()>j)throw new Error("Project is not saved or changed externally. Reload/save before exporting.");await new Promise(e=>setTimeout(e,100))}u={...project},Object.assign(project,JSON.parse(JSON.stringify(project)));const C=await d("/api/export/ffmpeg");if(!C.available||!C.ffprobe)throw new Error("Optimized export needs ffmpeg and ffprobe");h=(await d(projectApi("/api/export/cache/begin"),{revision:project.revision})).id,x=new ExportWaitTracker(e=>d(A("wait"),e)),p=setInterval(async()=>{if(!o&&!a.signal.aborted){o=!0;try{if(f=await d(A("status")),"error"===f.state)throw Object.assign(new Error(f.error),{status:400})}catch(e){!n&&e.status>=400&&e.status<500&&(i=e,a.abort())}finally{o=!1}}},15e3);do{if(f=await d(A("status")),"error"===f.state)throw new Error(f.error);"ready"!==f.state&&await new Promise(e=>setTimeout(e,100))}while("ready"!==f.state);const T=new Set(f.clips),I=(e,t)=>{P.add(e.id),s.fallbacks||={},s.fallbacks[e.id]=t},O=(e,t,a=!1,s=0,r)=>{const i=Math.floor(t/5),o=e.id+":"+i,n=!a&&!z.has(o);if(n&&z.add(o),!S.has(o)||n){const t=E.get(o);t?.skipped&&E.delete(o);const n=d(A("block"),{clipId:e.id,block:i,priority:a?"prefetch":"demand",neededAt:s,sequence:r}).then(t=>(t.fallback&&!a&&I(e,t.fallback),S.get(o)===n&&E.set(o,t),t)).catch(t=>a?{skipped:t.message}:(I(e,t.message),{fallback:t.message}));S.set(o,n);for(const t of S.keys())t.startsWith(e.id+":")&&!a&&Number(t.slice(e.id.length+1))<i-1&&(S.delete(t),E.delete(t),z.delete(t))}return S.get(o)},q=(e,t=0)=>{for(const[a,s]of M){if(v+t<=134217728)break;!e.has(a)&&s.img&&(s.img.close(),v-=s.bytes,M.delete(a))}},B=async(e,t,i,o=!1)=>{const n=e.id+":"+Math.floor(t/5);if(o&&!E.has(n))return null;!o&&!E.has(n)&&s.frames>0&&(els.exportTitle.textContent=uiText("Preparing video frames…"),els.exportNote.textContent=uiText("Preparing the next part of your video. Export will continue automatically."));const d=!E.has(n)||E.get(n)?.skipped,h=performance.now(),l=o?E.get(n):await x.track("source-block",()=>O(e,t));if(!o&&d&&r("blockWait",performance.now()-h),l.fallback||l.skipped)return null;const p=cachedFrameIndex(l.times,t);if(p<0||t>l.times.at(-1)+(l.times.length>1?l.times[1]-l.times[0]:.1)+1e-5)return o||I(e,"source time outside cached coverage"),null;const m=l.key+":"+p;o||i.add(m);let u=M.get(m);if(u)M.delete(m),M.set(m,u);else{if(q(i,l.width*l.height*4),o&&v+l.width*l.height*4>134217728)return null;u={bytes:l.width*l.height*4,img:null},v+=u.bytes,s.peakDecodedBytes=Math.max(s.peakDecodedBytes,v),u.promise=(async()=>{let e;try{let t=performance.now();const s=await fetch(A("frame",`&key=${l.key}&index=${p}`),{signal:a.signal});if(!s.ok)throw new Error("Cached source frame unavailable");const i=await s.blob();return r("sourceFetch",performance.now()-t),t=performance.now(),e=await createImageBitmap(i),r("sourceDecode",performance.now()-t),c(),u.img=e,e}catch(t){throw e?.close(),v-=u.bytes,M.delete(m),t}})(),M.set(m,u)}return o?u.promise:x.track("source-image",()=>u.promise)},F=project.fps,R=Math.max(1/F,projDur()),N=Math.max(1,Math.round(R*F)),$=project.clips.filter(e=>"video"===e.kind&&isTrackEnabled(e.track)),W=sourceBlockPlan($.filter(e=>T.has(e.id)),F),D=e=>e.clip.id+":"+e.block;g=new SourceReadAhead((e,t)=>O(e.clip,e.time,!0,e.neededAt,t),e=>P.has(e.clip.id)||S.has(D(e)),{key:D,syncPlan:async(e,t,a)=>{const s=new Set([...e.map(D),...t.map(e=>e.clipId+":"+e.block)]);for(const e of S.keys())z.has(e)||s.has(e)||(S.delete(e),E.delete(e));await d(A("retain"),{sequence:a,blocks:e.map(e=>({clipId:e.clip.id,block:e.block})),current:t})}});const L=new Set,Q=async e=>{const t=$.filter(t=>activeAt(t,e));g.update(W.filter(t=>t.neededAt>e&&!P.has(t.clip.id)).slice(0,3),t.filter(e=>T.has(e.id)&&!P.has(e.id)).map(t=>({clipId:t.id,block:Math.floor(mediaTimeAt(t,e)/5)})));const a=$.filter(t=>t.start+t.duration<=e&&!L.has(t.id));if(a.length){await d(A("drop"),{clipIds:a.map(e=>e.id)});for(const e of a)L.add(e.id)}optimizedSources=new Map;const s=b=new Set;for(const a of t)stealExportVideo(a,e);return releaseFinishedExportVideos(e),await Promise.all(t.map(async t=>{const a=mediaTimeAt(t,e);if(T.has(t.id)&&!P.has(t.id))try{const e=await B(t,a,s);if(e)return void optimizedSources.set(t.id,e)}catch(e){c(),I(t,e.message)}await prepareVideoFrame(t,acquireExportVideo(t,e),a)})),q(s),s};await Q(0),s.phases.prepareMs=performance.now()-t,els.exportTitle.textContent=uiText("Mixing audio…");let J=performance.now();const V=await renderAudioMix(R,a.signal);c(),s.phases.audioMs=performance.now()-J;const U=e.name||project.name||"export";l=(await d(projectApi("/api/export/begin"),{fps:F,name:U,requestId:e.requestId,totalFrames:N,engine:"optimized",cacheId:h})).id,V&&await d(`/api/export/audio?id=${l}`,V),await document.fonts.ready,project.clips.some(e=>isTrackEnabled(e.track)&&e.props?.bgRemove)&&await ensureBgSeg(),m=new OrderedFrameQueue(async e=>{const t=performance.now();await d(`/api/export/frame?id=${l}`,e),r("upload",performance.now()-t)}),w=createSnapshotEncoder(a.signal);const Y=Math.max(1,Math.min(2,Math.floor(67108864/(project.width*project.height*4))));s.snapshotDepth=Y,k=new SnapshotPipeline(async e=>{const t=performance.now(),a=await w.capture(e);return r("snapshot",performance.now()-t),a.result=a.result.then(e=>(r("jpeg",performance.now()-t),e)),a},async e=>{if(!e?.size)throw new Error("Canvas returned an empty frame");const t=performance.now();await x.track("encoder",()=>m.push(e)),r("queueWait",performance.now()-t)},Y),y=new FrameReadAhead(async({clip:e,time:t})=>{if(P.has(e.id)||L.has(e.id))return;const a=performance.now();await B(e,t,b,!0),r("prefetch",performance.now()-a)});const G=performance.now();for(let e=0;e<N;e++){c();const t=e/F;state.time=t,J=performance.now();const a=await Q(t);r("source",performance.now()-J),$.some(a=>a.start>(e-1)/F&&a.start<=t)&&r("cutSourceWait",performance.now()-J),J=performance.now(),await prepareFrameAssets(t),r("assets",performance.now()-J),J=performance.now(),drawFrame(t),r("composite",performance.now()-J),J=performance.now();const i=[];for(let t=1;t<=4&&e+t<N;t++){const a=(e+t)/F;for(const s of $)activeAt(s,a)&&T.has(s.id)&&!P.has(s.id)&&i.push({key:`${s.id}:${e+t}`,clip:s,time:mediaTimeAt(s,a)})}y.update(i),await x.track("encoder",()=>k.push(els.preview)),r("pipeline",performance.now()-J),s.frames++,q(a);const o=(e+1)/N*100;els.exportProgress.style.width=o.toFixed(1)+"%",els.exportTitle.textContent=uiText("Rendering… {percent}%",{percent:o.toFixed(0)}),els.exportNote.textContent=uiText("Cached video frames · {count} compatibility fallbacks · You can switch tabs.",{count:new Set([...Object.keys(f.stats.fallbacks),...P]).size})}await x.track("encoder",async()=>{await k.finish(),await m.finish()}),await y.stop(),s.peakQueueBytes=m.peakBytes,s.phases.renderMs=performance.now()-G,f=await d(A("status")),s.cache=f.stats;for(const e of Object.values(s.samples)){let t=0;e.meanMs=e.totalMs/e.count;for(let a=0;a<e.histogram.length;a++)if(t+=e.histogram[a],t>=.95*e.count){e.p95UpperMs=2**a;break}}els.exportTitle.textContent=uiText("Encoding…"),J=performance.now(),s.totalMs=performance.now()-t,n=!0;const K=await d(`/api/export/end?id=${l}`,{metrics:s});if(l=null,s.phases.finalizeMs=performance.now()-J,s.totalMs=performance.now()-t,s.fps=N/(s.totalMs/1e3),!e.requestId){const e=document.createElement("a");e.href=K.src,e.download=decodeURIComponent(K.src.split("/").pop()),e.click(),toast(uiText("Optimized export complete: {seconds}s · {hits} cache hits",{seconds:(s.totalMs/1e3).toFixed(1),hits:s.cache.hits}))}console.info("Optimized export",s)}catch(t){m?.cancel(),a.abort(),await(m?.finish().catch(()=>{}));const s=i?.message||(renderCancelled||"AbortError"===t.name?"cancelled":t.message);e.requestId?await fetch(projectApi("/api/export/report"),{method:"POST",body:JSON.stringify({requestId:e.requestId,error:s})}).catch(()=>{}):"cancelled"!==s&&alert(uiText("Optimized export failed: ")+s)}finally{clearInterval(p),x?.stop(),j(),w?.close(),a.abort(),await(g?.stop()),await(y?.stop()),await(k?.settle()),optimizedAbort=null,optimizedSources=null;for(const e of M.values())e.img&&e.img.close();M.clear(),optimizedMaskCanvas&&(optimizedMaskCanvas.width=0,optimizedMaskCanvas.height=0,optimizedMaskCanvas=null),u&&Object.assign(project,u),state.exporting=!1,state.rendering=!1,window.removeEventListener("pagehide",C),window.removeEventListener("keydown",T,!0),els.exportOverlay.classList.add("hidden"),els.exportNote.textContent=uiText("Rendering your sequence in real time. Keep this tab focused."),runtime.pendingSync&&syncFromServer()}}"undefined"!=typeof module&&(module.exports={ExportWaitTracker,OrderedFrameQueue,cachedFrameIndex,sourceBlockPlan,sourceReadAhead,SourceReadAhead,FrameReadAhead,SnapshotPipeline,createSnapshotEncoder});
1
+ "use strict";class OrderedFrameQueue{constructor(e,t=4,a=33554432){this.write=e,this.maxFrames=t,this.maxBytes=a,this.pending=[],this.bytes=0,this.peakBytes=0,this.error=null}async push(e){for(;this.pending.length&&(this.pending.length>=this.maxFrames||this.bytes+e.size>this.maxBytes);)await this.pending[0];if(this.error)throw this.error;this.bytes+=e.size,this.peakBytes=Math.max(this.peakBytes,this.bytes);const t=(this.pending.at(-1)||Promise.resolve()).then(()=>{if(this.error)throw this.error;return this.write(e)}).catch(e=>{this.error=e}).finally(()=>{this.bytes-=e.size,this.pending.shift()});this.pending.push(t)}async finish(){if(await Promise.all(this.pending),this.error)throw this.error}cancel(){this.error=this.error||new Error("cancelled")}}function cachedFrameIndex(e,t){let a=0,s=e.length;for(;a<s;){const r=a+s>>>1;e[r]<=t+1e-5?a=r+1:s=r}return a-1}function sourceBlockPlan(e,t){const a=[];for(const s of e){const e=Math.max(.1,Math.min(8,Number(s.props?.speed)||1)),r=s.start+s.duration;let i=Math.max(0,Math.ceil(s.start*t-1e-7));for(;i/t<r;){const r=i/t,o=s.in+(r-s.start)*e,n=Math.floor(o/5);a.push({clip:s,time:o,block:n,neededAt:r}),i=Math.max(i+1,Math.ceil((s.start+(5*(n+1)-s.in)/e)*t-1e-7))}}return a.sort((e,t)=>e.neededAt-t.neededAt)}function sourceReadAhead(e,t,a){return sourceBlockPlan(e,a).filter(e=>e.neededAt>t).slice(0,3)}class SourceReadAhead{constructor(e,t,{concurrency:a=3,syncPlan:s=async()=>{},key:r=e=>e}={}){this.load=e,this.requested=t,this.concurrency=a,this.syncPlan=s,this.key=r,this.plan=[],this.active=new Map,this.stopped=!1,this.sequence=0,this.readySequence=0,this.syncing=Promise.resolve(),this.signature=""}update(e,t=[]){if(this.stopped)return;const a=JSON.stringify([e.map(this.key),t]);if(a===this.signature)return;this.signature=a,this.plan=e;const s=++this.sequence;this.syncing=this.syncing.catch(()=>{}).then(()=>this.syncPlan(e,t,s)).then(()=>{s===this.sequence&&(this.readySequence=s,this.pump())}).catch(()=>{})}pump(){if(!this.stopped&&this.readySequence===this.sequence)for(const e of this.plan){if(this.active.size>=this.concurrency)break;const t=this.key(e);if(this.active.has(t)||this.requested(e))continue;const a=this.sequence,s=Promise.resolve().then(()=>this.load(e,a)).catch(()=>{}).finally(()=>{this.active.delete(t),this.pump()});this.active.set(t,s)}}async stop(){this.stopped=!0,this.plan=[],await this.syncing,await Promise.allSettled(this.active.values())}}class ExportWaitTracker{constructor(e,{now:t=()=>performance.now(),interval:a=500,threshold:s=250}={}){this.report=e,this.now=t,this.threshold=s,this.active=new Set,this.sequence=0,this.last=null,this.timer=setInterval(()=>this.flush(),a)}async track(e,t){const a={reason:e,started:this.now()};this.active.add(a);try{return await t()}finally{this.active.delete(a)}}flush(){const e=[...this.active].filter(e=>this.now()-e.started>=this.threshold).sort((e,t)=>["source-block","video-seek","source-image","frame-assets","audio-mix","encoder"].indexOf(e.reason)-["source-block","video-seek","source-image","frame-assets","audio-mix","encoder"].indexOf(t.reason)||e.started-t.started)[0];(e||null)!==this.last&&(this.last=e||null,Promise.resolve(this.report({sequence:++this.sequence,reason:e?.reason||null,elapsedMs:e?Math.max(0,this.now()-e.started):0})).catch(()=>{this.last===(e||null)&&(this.last=void 0)}))}stop(){clearInterval(this.timer),this.active.clear(),this.flush()}}class FrameReadAhead{constructor(e,t=2){this.load=e,this.concurrency=t,this.plan=[],this.active=new Map,this.stopped=!1}update(e){this.stopped||(this.plan=e,this.pump())}pump(){for(;!this.stopped&&this.active.size<this.concurrency&&this.plan.length;){const e=this.plan.shift();if(this.active.has(e.key))continue;const t=Promise.resolve().then(()=>this.load(e)).catch(()=>{}).finally(()=>{this.active.delete(e.key),this.pump()});this.active.set(e.key,t)}}async stop(){this.stopped=!0,this.plan=[],await Promise.all(this.active.values())}}class SnapshotPipeline{constructor(e,t,a=2){this.capture=e,this.write=t,this.depth=a,this.pending=[]}async push(e){for(;this.pending.length>=this.depth;)await this.drain();const t=await this.capture(e);t.result.catch(()=>{}),this.pending.push(t)}async drain(){const e=this.pending.shift();e&&await this.write(await e.result)}async finish(){for(;this.pending.length;)await this.drain()}async settle(){await Promise.allSettled(this.pending.splice(0).map(e=>e.result))}}function createSnapshotEncoder(e){let t,a=0;const s=new Map,r=()=>{t?.terminate(),t=null;for(const{reject:e}of s.values())e(new Error("snapshot encoder stopped"));s.clear(),e.removeEventListener("abort",r)};if("function"==typeof Worker&&"function"==typeof OffscreenCanvas&&"function"==typeof createImageBitmap)try{t=new Worker("optimized-encoder-worker.js"),t.onmessage=({data:e})=>{const t=s.get(e.id);t&&(s.delete(e.id),e.error?t.reject(new Error(e.error)):t.resolve(e.blob))},t.onerror=e=>{e.preventDefault(),r()},e.addEventListener("abort",r,{once:!0})}catch{r()}return{close:r,async capture(i){if(e.aborted)throw new Error("cancelled");if(!t)return{result:Promise.resolve(await new Promise(e=>i.toBlob(e,"image/jpeg",.95)))};const o=await createImageBitmap(i);if(e.aborted)throw o.close(),new Error("cancelled");const n=a++,c=(async()=>{const a=await createImageBitmap(o);return new Promise((r,i)=>{if(e.aborted||!t)return a.close(),void i(new Error("snapshot encoder stopped"));s.set(n,{resolve:r,reject:i});try{t.postMessage({id:n,bitmap:a},[a])}catch(e){a.close(),s.delete(n),i(e)}})})().catch(async t=>{if(e.aborted)throw t;r();const a=document.createElement("canvas");a.width=o.width,a.height=o.height;try{return a.getContext("2d").drawImage(o,0,0),await new Promise(e=>a.toBlob(e,"image/jpeg",.95))}finally{a.width=a.height=0}}).finally(()=>o.close());return c.catch(()=>{}),{result:c}}}}let optimizedSources=null,optimizedAbort=null,optimizedMaskCanvas=null;function optimizedMaskSource(e){return optimizedMaskCanvas||(optimizedMaskCanvas=document.createElement("canvas")),optimizedMaskCanvas.width!==e.width&&(optimizedMaskCanvas.width=e.width),optimizedMaskCanvas.height!==e.height&&(optimizedMaskCanvas.height=e.height),optimizedMaskCanvas.getContext("2d").drawImage(e,0,0),optimizedMaskCanvas}async function optimizedExport(e={}){if(state.exporting)return;pause(),state.exporting=!0,state.rendering=!0,renderCancelled=!1;const t=performance.now(),a=new AbortController;optimizedAbort=a;const s={engine:"optimized",frames:0,phases:{},samples:{},peakDecodedBytes:0,peakQueueBytes:0},r=(e,t)=>{const a=s.samples[e]||(s.samples[e]={count:0,totalMs:0,maxMs:0,histogram:Array(24).fill(0)});a.count++,a.totalMs+=t,a.maxMs=Math.max(a.maxMs,t),a.histogram[Math.min(23,Math.max(0,Math.ceil(Math.log2(Math.max(1,t)))))]++};let i,o=!1,n=!1;const c=()=>{if(i)throw i;if(renderCancelled||a.signal.aborted)throw new Error("cancelled")},d=async(e,t,s)=>{c();const r=await fetch(e,{method:s||(void 0===t?"GET":"POST"),body:void 0===t?void 0:t instanceof Blob?t:JSON.stringify(t),signal:a.signal}),i=await r.json();if(!r.ok||i.error)throw Object.assign(new Error(i.error||"Export request failed"),{status:r.status});return i};let p,l,h,m,u,f,w,g,y,k,x,v=0,b=new Set;const M=new Map,S=new Map,E=new Map,P=new Set,A=new Set,z=(e,t="")=>projectApi(`/api/export/cache/${e}`)+`&id=${p}${t}`,j=()=>{p&&fetch(z("release"),{method:"POST",keepalive:!0}).catch(()=>{}),l&&fetch(`/api/export/end?id=${l}&discard=1`,{method:"POST",keepalive:!0}).catch(()=>{})},C=()=>{a.abort(),j()},T=e=>{"Escape"===e.key&&(renderCancelled=!0,a.abort()),e.stopImmediatePropagation(),e.preventDefault()};window.addEventListener("pagehide",C),window.addEventListener("keydown",T,!0),els.exportOverlay.classList.remove("hidden"),els.exportProgress.style.width="0%",els.exportTitle.textContent=uiText("Preparing source frames…"),els.exportNote.textContent=uiText("Pre-decoding video and caching frames. You can switch tabs.");try{if(e.requestId&&"function"==typeof readBrowserGpu){try{runtime.browserGpu=readBrowserGpu()}catch{}fetch(projectApi("/api/export/report"),{method:"POST",body:JSON.stringify({requestId:e.requestId,diagnostic:!0,browserGpu:runtime.browserGpu})}).catch(()=>{})}const j=performance.now()+5e3;for(;;){c();const e=await d(projectApi("/api/project"));if(!runtime.saveTimer&&e.revision===project.revision)break;if(performance.now()>j)throw new Error("Project is not saved or changed externally. Reload/save before exporting.");await new Promise(e=>setTimeout(e,100))}u={...project},Object.assign(project,JSON.parse(JSON.stringify(project)));const C=await d("/api/export/ffmpeg");if(!C.available||!C.ffprobe)throw new Error("Optimized export needs ffmpeg and ffprobe");p=(await d(projectApi("/api/export/cache/begin"),{revision:project.revision})).id,x=new ExportWaitTracker(e=>d(z("wait"),e)),h=setInterval(async()=>{if(!o&&!a.signal.aborted){o=!0;try{if(f=await d(z("status")),"error"===f.state)throw Object.assign(new Error(f.error),{status:400})}catch(e){!n&&e.status>=400&&e.status<500&&(i=e,a.abort())}finally{o=!1}}},15e3);do{if(f=await d(z("status")),"error"===f.state)throw new Error(f.error);"ready"!==f.state&&await new Promise(e=>setTimeout(e,100))}while("ready"!==f.state);const T=new Set(f.clips),I=(e,t)=>{P.add(e.id),s.fallbacks||={},s.fallbacks[e.id]=t},O=(e,t,a=!1,s=0,r)=>{const i=Math.floor(t/5),o=e.id+":"+i,n=!a&&!A.has(o);if(n&&A.add(o),!S.has(o)||n){const t=E.get(o);t?.skipped&&E.delete(o);const n=d(z("block"),{clipId:e.id,block:i,priority:a?"prefetch":"demand",neededAt:s,sequence:r}).then(t=>(t.fallback&&!a&&I(e,t.fallback),S.get(o)===n&&E.set(o,t),t)).catch(t=>a?{skipped:t.message}:(I(e,t.message),{fallback:t.message}));S.set(o,n);for(const t of S.keys())t.startsWith(e.id+":")&&!a&&Number(t.slice(e.id.length+1))<i-1&&(S.delete(t),E.delete(t),A.delete(t))}return S.get(o)},q=(e,t=0)=>{for(const[a,s]of M){if(v+t<=134217728)break;!e.has(a)&&s.img&&(s.img.close(),v-=s.bytes,M.delete(a))}},B=async(e,t,i,o=!1)=>{const n=e.id+":"+Math.floor(t/5);if(o&&!E.has(n))return null;!o&&!E.has(n)&&s.frames>0&&(els.exportTitle.textContent=uiText("Preparing video frames…"),els.exportNote.textContent=uiText("Preparing the next part of your video. Export will continue automatically."));const d=!E.has(n)||E.get(n)?.skipped,p=performance.now(),l=o?E.get(n):await x.track("source-block",()=>O(e,t));if(!o&&d&&r("blockWait",performance.now()-p),l.fallback||l.skipped)return null;const h=cachedFrameIndex(l.times,t);if(h<0||t>l.times.at(-1)+(l.times.length>1?l.times[1]-l.times[0]:.1)+1e-5)return o||I(e,"source time outside cached coverage"),null;const m=l.key+":"+h;o||i.add(m);let u=M.get(m);if(u)M.delete(m),M.set(m,u);else{if(q(i,l.width*l.height*4),o&&v+l.width*l.height*4>134217728)return null;u={bytes:l.width*l.height*4,img:null},v+=u.bytes,s.peakDecodedBytes=Math.max(s.peakDecodedBytes,v),u.promise=(async()=>{let e;try{let t=performance.now();const s=await fetch(z("frame",`&key=${l.key}&index=${h}`),{signal:a.signal});if(!s.ok)throw new Error("Cached source frame unavailable");const i=await s.blob();return r("sourceFetch",performance.now()-t),t=performance.now(),e=await createImageBitmap(i),r("sourceDecode",performance.now()-t),c(),u.img=e,e}catch(t){throw e?.close(),v-=u.bytes,M.delete(m),t}})(),M.set(m,u)}return o?u.promise:x.track("source-image",()=>u.promise)},F=project.fps,R=Math.max(1/F,projDur()),N=Math.max(1,Math.round(R*F)),$=project.clips.filter(e=>"video"===e.kind&&isTrackEnabled(e.track)),W=sourceBlockPlan($.filter(e=>T.has(e.id)),F),D=e=>e.clip.id+":"+e.block;g=new SourceReadAhead((e,t)=>O(e.clip,e.time,!0,e.neededAt,t),e=>P.has(e.clip.id)||S.has(D(e)),{key:D,syncPlan:async(e,t,a)=>{const s=new Set([...e.map(D),...t.map(e=>e.clipId+":"+e.block)]);for(const e of S.keys())A.has(e)||s.has(e)||(S.delete(e),E.delete(e));await d(z("retain"),{sequence:a,blocks:e.map(e=>({clipId:e.clip.id,block:e.block})),current:t})}});const G=new Set,L=async e=>{const t=$.filter(t=>activeAt(t,e));g.update(W.filter(t=>t.neededAt>e&&!P.has(t.clip.id)).slice(0,3),t.filter(e=>T.has(e.id)&&!P.has(e.id)).map(t=>({clipId:t.id,block:Math.floor(mediaTimeAt(t,e)/5)})));const a=$.filter(t=>t.start+t.duration<=e&&!G.has(t.id));if(a.length){await d(z("drop"),{clipIds:a.map(e=>e.id)});for(const e of a)G.add(e.id)}optimizedSources=new Map;const s=b=new Set;for(const a of t)stealExportVideo(a,e);return releaseFinishedExportVideos(e),await Promise.all(t.map(async t=>{const a=mediaTimeAt(t,e);if(T.has(t.id)&&!P.has(t.id))try{const e=await B(t,a,s);if(e)return void optimizedSources.set(t.id,e)}catch(e){c(),I(t,e.message)}await x.track("video-seek",()=>prepareVideoFrame(t,acquireExportVideo(t,e),a))})),q(s),s};await L(0),s.phases.prepareMs=performance.now()-t,els.exportTitle.textContent=uiText("Mixing audio…");let J=performance.now();const Q=await x.track("audio-mix",()=>renderAudioMix(R,a.signal));c(),s.phases.audioMs=performance.now()-J;const V=e.name||project.name||"export";l=(await d(projectApi("/api/export/begin"),{fps:F,name:V,requestId:e.requestId,totalFrames:N,engine:"optimized",cacheId:p})).id,Q&&await d(`/api/export/audio?id=${l}`,Q),await document.fonts.ready,project.clips.some(e=>isTrackEnabled(e.track)&&e.props?.bgRemove)&&await ensureBgSeg(),m=new OrderedFrameQueue(async e=>{const t=performance.now();await d(`/api/export/frame?id=${l}`,e),r("upload",performance.now()-t)}),w=createSnapshotEncoder(a.signal);const U=Math.max(1,Math.min(2,Math.floor(67108864/(project.width*project.height*4))));s.snapshotDepth=U,k=new SnapshotPipeline(async e=>{const t=performance.now(),a=await w.capture(e);return r("snapshot",performance.now()-t),a.result=a.result.then(e=>(r("jpeg",performance.now()-t),e)),a},async e=>{if(!e?.size)throw new Error("Canvas returned an empty frame");const t=performance.now();await x.track("encoder",()=>m.push(e)),r("queueWait",performance.now()-t)},U),y=new FrameReadAhead(async({clip:e,time:t})=>{if(P.has(e.id)||G.has(e.id))return;const a=performance.now();await B(e,t,b,!0),r("prefetch",performance.now()-a)});const Y=performance.now();for(let e=0;e<N;e++){c();const t=e/F;state.time=t,J=performance.now();const a=await L(t);r("source",performance.now()-J),$.some(a=>a.start>(e-1)/F&&a.start<=t)&&r("cutSourceWait",performance.now()-J),J=performance.now(),await x.track("frame-assets",()=>prepareFrameAssets(t)),r("assets",performance.now()-J),J=performance.now(),drawFrame(t),r("composite",performance.now()-J),J=performance.now();const i=[];for(let t=1;t<=4&&e+t<N;t++){const a=(e+t)/F;for(const s of $)activeAt(s,a)&&T.has(s.id)&&!P.has(s.id)&&i.push({key:`${s.id}:${e+t}`,clip:s,time:mediaTimeAt(s,a)})}y.update(i),await x.track("encoder",()=>k.push(els.preview)),r("pipeline",performance.now()-J),s.frames++,q(a);const o=(e+1)/N*100;els.exportProgress.style.width=o.toFixed(1)+"%",els.exportTitle.textContent=uiText("Rendering… {percent}%",{percent:o.toFixed(0)}),els.exportNote.textContent=uiText("Cached video frames · {count} compatibility fallbacks · You can switch tabs.",{count:new Set([...Object.keys(f.stats.fallbacks),...P]).size})}await x.track("encoder",async()=>{await k.finish(),await m.finish()}),await y.stop(),s.peakQueueBytes=m.peakBytes,s.phases.renderMs=performance.now()-Y,f=await d(z("status")),s.cache=f.stats;for(const e of Object.values(s.samples)){let t=0;e.meanMs=e.totalMs/e.count;for(let a=0;a<e.histogram.length;a++)if(t+=e.histogram[a],t>=.95*e.count){e.p95UpperMs=2**a;break}}els.exportTitle.textContent=uiText("Encoding…"),J=performance.now(),s.totalMs=performance.now()-t,n=!0;const K=await d(`/api/export/end?id=${l}`,{metrics:s});if(l=null,s.phases.finalizeMs=performance.now()-J,s.totalMs=performance.now()-t,s.fps=N/(s.totalMs/1e3),!e.requestId){const e=document.createElement("a");e.href=K.src,e.download=decodeURIComponent(K.src.split("/").pop()),e.click(),toast(uiText("Optimized export complete: {seconds}s · {hits} cache hits",{seconds:(s.totalMs/1e3).toFixed(1),hits:s.cache.hits}))}console.info("Optimized export",s)}catch(t){m?.cancel(),a.abort(),await(m?.finish().catch(()=>{}));const r=i?.message||(renderCancelled||"AbortError"===t.name?"cancelled":t.message);if(e.requestId){let a;try{a=project.clips.filter(e=>activeAt(e,state.time)).map(e=>e.id).slice(0,12)}catch{}await fetch(projectApi("/api/export/report"),{method:"POST",body:JSON.stringify({requestId:e.requestId,error:r,stack:t.stack,frame:s.frames,timeSec:state.time,browserGpu:runtime.browserGpu,clipIds:a})}).catch(()=>{})}else"cancelled"!==r&&alert(uiText("Optimized export failed: ")+r)}finally{clearInterval(h),x?.stop(),j(),w?.close(),a.abort(),await(g?.stop()),await(y?.stop()),await(k?.settle()),optimizedAbort=null,optimizedSources=null;for(const e of M.values())e.img&&e.img.close();M.clear(),optimizedMaskCanvas&&(optimizedMaskCanvas.width=0,optimizedMaskCanvas.height=0,optimizedMaskCanvas=null),u&&Object.assign(project,u),state.exporting=!1,state.rendering=!1,window.removeEventListener("pagehide",C),window.removeEventListener("keydown",T,!0),els.exportOverlay.classList.add("hidden"),els.exportNote.textContent=uiText("Rendering your sequence in real time. Keep this tab focused."),runtime.pendingSync&&syncFromServer()}}"undefined"!=typeof module&&(module.exports={ExportWaitTracker,OrderedFrameQueue,cachedFrameIndex,sourceBlockPlan,sourceReadAhead,SourceReadAhead,FrameReadAhead,SnapshotPipeline,createSnapshotEncoder});
@@ -1 +1 @@
1
- "use strict";const r=require("fs"),e=require("path"),t=require("crypto"),n=require("./paths"),o=require("./timeline");function i(e,t){const n=Date.now()+1e4;let o;for(;void 0===o;)try{o=r.openSync(e,"wx")}catch(r){if("EEXIST"!==r.code)throw r;if(Date.now()>=n)throw new Error(`Timed out waiting for lock: ${e}. If its owner has exited, remove this lock and retry.`);Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25)}let i=!1;const c=()=>{if(!i){i=!0;try{r.closeSync(o)}finally{r.unlinkSync(e)}}};try{r.writeFileSync(o,String(process.pid));const e=t();return e&&"function"==typeof e.then?e.finally(c):(c(),e)}catch(r){throw c(),r}}function c(r){const e=new Error("No such project: "+r);return e.code="PROJECT_NOT_FOUND",e.status=404,e}function s(e){const t=n.projectPaths(e);try{if(!r.statSync(t.projectFile).isFile())throw new Error("Invalid project file: "+t.id)}catch(r){if("ENOENT"===r.code||"ENOTDIR"===r.code)throw c(t.id);throw r}return t}function a(e){const t=s(e);let n;try{n=r.readFileSync(t.projectFile,"utf8")}catch(r){if("ENOENT"===r.code||"ENOTDIR"===r.code)throw c(t.id);throw r}return o.normalize(JSON.parse(n.replace(/^\uFEFF/,"")))}function l(e,n){const o=e+"."+process.pid+"."+t.randomBytes(8).toString("hex")+".tmp";try{r.writeFileSync(o,JSON.stringify(n,null,2),{flag:"wx"}),r.renameSync(o,e)}finally{r.rmSync(o,{force:!0})}}function u(r,t){const n=s(r);return i(e.join(n.dir,".write.lock"),()=>{const e=a(r),i=o.normalize(t(e));return l(n.projectFile,i),i})}module.exports={withLock:i,context:s,read:a,update:u,save:function(r,e,t=!1){return u(r,r=>{if(!t&&(e.revision||0)<=(r.revision||0))throw function(r){const e=new Error("CONFLICT — stale revision; project changed since it was read");return e.status=409,e.revision=r,e}(r.revision||0);return{...e,revision:Math.max(Number(e.revision||0),Number(r.revision||0)+1)}})},create:function(t,o){t=String(t||"Untitled Project").trim()||"Untitled Project";const c=String(o||t).toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,56)||"project";return i(e.join(n.DATA_DIR,".projects.lock"),()=>{let e=n.normalizeProjectId(c),o=2;for(;;)try{r.mkdirSync(n.projectPaths(e).dir);break}catch(r){if("EEXIST"!==r.code)throw r;e=n.normalizeProjectId(`${c}-${o++}`)}return n.ensureProject(e,t),{id:e,name:t}})},atomicWrite:l};
1
+ "use strict";const r=require("fs"),e=require("path"),t=require("crypto"),o=require("./paths"),n=require("./timeline");function i(e,t){const o=Date.now()+1e4;let n;for(;void 0===n;)try{n=r.openSync(e,"wx")}catch(r){if("EEXIST"!==r.code)throw r;if(Date.now()>=o){const r=new Error(`Timed out waiting for lock: ${e}. If its owner has exited, remove this lock and retry.`);try{require("./cli/lib/telemetry").reportError("ProjectLockTimeout",r)}catch{}throw r}Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25)}let i=!1;const c=()=>{if(!i){i=!0;try{r.closeSync(n)}finally{r.unlinkSync(e)}}};try{r.writeFileSync(n,String(process.pid));const e=t();return e&&"function"==typeof e.then?e.finally(c):(c(),e)}catch(r){throw c(),r}}function c(r){const e=new Error("No such project: "+r);return e.code="PROJECT_NOT_FOUND",e.status=404,e}function s(e){const t=o.projectPaths(e);try{if(!r.statSync(t.projectFile).isFile())throw new Error("Invalid project file: "+t.id)}catch(r){if("ENOENT"===r.code||"ENOTDIR"===r.code)throw c(t.id);throw r}return t}function a(e){const t=s(e);let o;try{o=r.readFileSync(t.projectFile,"utf8")}catch(r){if("ENOENT"===r.code||"ENOTDIR"===r.code)throw c(t.id);throw r}return n.normalize(JSON.parse(o.replace(/^\uFEFF/,"")))}function l(e,o){const n=e+"."+process.pid+"."+t.randomBytes(8).toString("hex")+".tmp";try{r.writeFileSync(n,JSON.stringify(o,null,2),{flag:"wx"}),r.renameSync(n,e)}finally{r.rmSync(n,{force:!0})}}function u(r,t){const o=s(r);return i(e.join(o.dir,".write.lock"),()=>{const e=a(r),i=n.normalize(t(e));return l(o.projectFile,i),i})}module.exports={withLock:i,context:s,read:a,update:u,save:function(r,e,t=!1){return u(r,r=>{if(!t&&(e.revision||0)<=(r.revision||0))throw function(r){const e=new Error("CONFLICT — stale revision; project changed since it was read");return e.status=409,e.revision=r,e}(r.revision||0);return{...e,revision:Math.max(Number(e.revision||0),Number(r.revision||0)+1)}})},create:function(t,n){t=String(t||"Untitled Project").trim()||"Untitled Project";const c=String(n||t).toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,56)||"project";return i(e.join(o.DATA_DIR,".projects.lock"),()=>{let e=o.normalizeProjectId(c),n=2;for(;;)try{r.mkdirSync(o.projectPaths(e).dir);break}catch(r){if("EEXIST"!==r.code)throw r;e=o.normalizeProjectId(`${c}-${n++}`)}return o.ensureProject(e,t),{id:e,name:t}})},atomicWrite:l};
@@ -1 +1 @@
1
- "use strict";const e=require("http"),r=require("fs"),t=require("path"),o=require("os"),{spawn:n,fork:i,spawnSync:a}=require("./cli/lib/processes"),{writeFrame:s,stopProcess:c,run:d}=require("./export-encoder"),{createExportSaver:p,resolveExportSource:l,validVideoName:m}=require("./export-save"),f=p(),{analyze:u}=require("./analyze"),h=require("./project-store"),{ExportCache:g}=require("./export-cache"),{Waveforms:w}=require("./waveform"),{ExportAudio:S}=require("./export-audio"),{pipeline:y}=require("node:stream/promises"),{APP_DIR:v,DATA_DIR:x,PROJECTS_DIR:b,LIBRARY_DIR:j,LIBRARY_SUBDIRS:I,DEFAULT_PROJECT_ID:P,normalizeProjectId:E,projectPaths:T,listProjects:O,ensureDirs:D,projectRoots:C}=require("./paths"),k=v,q=new S({libraryDir:j}),R=process.env.HOST||"127.0.0.1",$=new Set(["localhost","127.0.0.1","[::1]","::1",R.toLowerCase()]);for(const e of(process.env.FABLECUT_ALLOWED_HOSTS||"").split(","))e.trim()&&$.add(e.trim().toLowerCase());function z(e){if(!e)return!1;const r=e.replace(/^(\[[^\]]*\]|[^:]+)(:\d+)?$/,"$1").toLowerCase();return $.has(r)}let N=!1;try{N=0===a("ffmpeg",["-version"],{stdio:"ignore"}).status}catch{}let _=!1;try{_=0===a("ffprobe",["-version"],{stdio:"ignore"}).status}catch{}const A=new g({libraryDir:j}),U=new w({libraryDir:j}),F={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".json":"application/json",".mp4":"video/mp4",".webm":"video/webm",".mov":"video/quicktime",".mkv":"video/x-matroska",".m4v":"video/mp4",".mp3":"audio/mpeg",".wav":"audio/wav",".ogg":"audio/ogg",".m4a":"audio/mp4",".aac":"audio/aac",".flac":"audio/flac",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".ico":"image/x-icon",".ttf":"font/ttf",".otf":"font/otf",".woff":"font/woff",".woff2":"font/woff2"};D();const J=new Set;function L(e,r="change"){for(const t of J)e&&t.projectId!==e||t.res.write(`data: ${r}\n\n`)}const H=new Map;function W(e){clearTimeout(H.get(e)),H.set(e,setTimeout(()=>L(e),150))}const M=new Map;function B(e){const r=M.get(e);if(M.delete(e),r)for(const e of r.watchers)e.close()}function G(e){const t=T(e),o=M.get(e);if(o?.dir===t.dir)return;B(e);const n=[],i={dir:t.dir,watchers:n},a=(t,o)=>{try{const a=r.watch(t,o);a.on("error",()=>{M.get(e)===i&&B(e),W(e)}),n.push(a)}catch{}};a(t.dir,(r,t)=>{"project.json"===t&&W(e)}),a(t.mediaDir,()=>W(e)),n.length&&M.set(e,i)}for(const e of O())G(e.id);let V=null;for(const e of[b,C().legacyDir].filter(Boolean))try{r.watch(e,(e,r)=>{const t=r&&String(r),o=t&&/^[a-z0-9][a-z0-9_-]{0,63}$/.test(t)?[t]:[...M.keys()];for(const e of o){B(e);try{G(e)}catch{}W(e)}clearTimeout(V),V=setTimeout(()=>L(null,"projects"),150)}).on("error",()=>{for(const[r,o]of M)t.dirname(o.dir)===e&&B(r);L(null,"projects")})}catch{}for(const e of I)try{r.watch(t.join(j,e),()=>L(null))}catch{}function Y(e,r,t){const o=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(o)}function K(e){return new Promise((r,t)=>{const o=[];e.on("data",e=>o.push(e)),e.on("end",()=>r(Buffer.concat(o))),e.on("error",t)})}function X(e,o){let n=t.join(e,o);const i=t.extname(o),a=t.basename(o,i);let s=1;for(;r.existsSync(n);)n=t.join(e,`${a}_${s++}${i}`);return n}function Q(e){const r=E(e.searchParams.get("project")||P),t=h.context(r);return G(r),t}function Z(e,r){return"/projects/"+encodeURIComponent(e)+"/media/"+encodeURIComponent(r)}const ee=new Set([".mp4",".mov",".m4v"]),re=new Map,te=new Map,oe=new Map;function ne(){const e=new Set;for(const[r,t]of oe){const o=te.get(r)?.state;o&&["complete","error","cancelled"].includes(o)||e.add(t.projectId)}for(const r of re.values())r.projectId&&e.add(r.projectId);return[...e]}function ie(e){const r=oe.get(e);if(!r||!r.cleanupDone||r.child&&!r.childClosed)return;clearTimeout(r.timer),oe.delete(e);const t=te.get(e);t&&te.set(e,{...t,workerDone:!0}),queueMicrotask(ae)}function ae(){let e=[...oe.values()].filter(e=>e.child).length;for(const[r,t]of oe){if(e>=2)break;t.child||t.stopping||(se(r,t),t.child&&e++)}}function se(e,r){const{projectId:o,options:n}=r;try{const a=i(t.join(k,"export-worker.js"),[],{stdio:["ignore","ignore","pipe","ipc"]});r.child=a,de(e,{state:"starting",projectId:o});let s="";a.stderr.on("data",e=>{s=(s+e).slice(-2e3)}),a.on("message",r=>{const t=te.get(e);(["starting","preparing-browser","preparing-media"].includes(t?.state)||"error"===r.state)&&de(e,{...r,projectId:o})}),a.on("error",r=>de(e,{state:"error",projectId:o,error:r.message})),a.once("close",()=>{r.childClosed=!0,de(e,{state:"error",projectId:o,error:"Export worker exited: "+s.slice(-800)}),ie(e)}),r.timer=setTimeout(()=>de(e,{state:"error",projectId:o,error:"Export timed out"}),1e3*n.timeoutSeconds);const c=fe.address(),d="0.0.0.0"===R?"127.0.0.1":"::"===R?"[::1]":R.includes(":")&&!R.startsWith("[")?`[${R}]`:R;a.send({id:e,projectId:o,engine:n.engine,name:n.name,browser:n.browser,url:`http://${d}:${c.port}/`})}catch(r){de(e,{state:"error",projectId:o,error:r.message})}}function ce(e,r){const t=e.searchParams.get("exportJob");if(!t)return;const o=oe.get(t);if(!o||o.stopping||o.projectId!==r.id)throw new Error("no such export job");return o.snapshot}function de(e,r){if(!e||!/^[a-f0-9]{32}$/.test(e))return;const t=te.get(e);if((!t||!["complete","error","cancelled"].includes(t.state))&&("error"===r.state&&(r={...r,error:`${r.error} (stage=${t?.state||"starting"}, frames=${t?.frames||0})`}),te.set(e,{...t,...r,updated:Date.now()}),["complete","error","cancelled"].includes(r.state)&&function(e){const r=oe.get(e);if(!r||r.stopping)return;r.stopping=!0,r.recordingUpload?.abort(),clearTimeout(r.timer);const t=[];for(const e of r.cacheIds||[])t.push(A.release(e,r.projectId));for(const[r,o]of re)o.requestId===e&&t.push(le(r));r.child&&!r.childClosed&&r.child.kill();const o=()=>{r.cleanupDone=!0,ie(e)};t.some(Boolean)?Promise.allSettled(t).then(o):o()}(e),te.size>100)){const e=[...te].sort((e,r)=>e[1].updated-r[1].updated).slice(0,20);for(const[r]of e)oe.has(r)||te.delete(r)}}function pe(e,i,a,s,c,d){const p=Date.now().toString(36)+Math.random().toString(36).slice(2,7),l=r.mkdtempSync(t.join(o.tmpdir(),"fablecut-")),m=t.join(l,"video.mp4"),f=n("ffmpeg",[...c?["-filter_threads","1"]:[],"-y","-f","image2pipe","-vcodec","mjpeg","-framerate",String(e),"-i","-","-vf","scale=in_range=full:in_color_matrix=bt601:out_range=tv:out_color_matrix=bt709","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-colorspace","bt709","-color_primaries","bt709","-color_trc","bt709","-color_range","tv",m],{stdio:["pipe","ignore","pipe"]});let u="";f.stderr.on("data",e=>{u=(u+e).slice(-2e3)});const h={controller:new AbortController,proc:f,dir:l,videoPath:m,name:(i||"export").replace(/[<>:"/\\|?*\x00-\x1f]+/g,"_").slice(0,120).trim()||"export",projectId:a,requestId:s,cacheId:c,totalFrames:Number.isSafeInteger(d)&&d>0?d:void 0,wav:null,frames:0,frameBytes:0,err:()=>u,done:new Promise(e=>f.on("close",e))};re.set(p,h);const g=e=>{h.cleaning||(de(s,{state:"error",projectId:a,error:e.message}),le(p))};return f.on("error",g),f.stdin.on("error",g),f.once("exit",(e,r)=>{h.finalizing&&0===e||g(new Error(`ffmpeg exited (${r||e}): ${h.err()}`))}),de(s,{state:"rendering",projectId:a,frames:0,totalFrames:h.totalFrames}),p}function le(e){const t=re.get(e);if(t)return t.cleaning||(t.controller.abort(),t.cleaning=(async()=>{await Promise.allSettled([c(t.proc,t.done),t.muxDone,t.cacheId&&A.release(t.cacheId,t.projectId)]),t.output&&!t.committed&&await r.promises.rm(t.output,{force:!0}),await r.promises.rm(t.dir,{recursive:!0,force:!0})})().catch(e=>console.error("Export cleanup failed:",e.message)).finally(()=>re.delete(e))),t.cleaning}function me(e,o,n){r.stat(n,(i,a)=>{if(i||!a.isFile())return o.writeHead(404),void o.end("Not found");"HEAD"===e.method&&o.setHeader("ETag",`W/"${a.size}-${a.mtimeMs}"`);const s=F[t.extname(n).toLowerCase()]||"application/octet-stream",c=e.headers.range;if(c){const t=/bytes=(\d*)-(\d*)/.exec(c);let i=t&&t[1]?parseInt(t[1]):0,d=t&&t[2]?parseInt(t[2]):a.size-1;if(i=Math.min(i,a.size-1),d=Math.min(d,a.size-1),o.writeHead(206,{"Content-Type":s,"Accept-Ranges":"bytes","Content-Range":`bytes ${i}-${d}/${a.size}`,"Content-Length":d-i+1}),"HEAD"===e.method)return void o.end();r.createReadStream(n,{start:i,end:d}).pipe(o)}else{if(o.writeHead(200,{"Content-Type":s,"Content-Length":a.size,"Accept-Ranges":"bytes","Cache-Control":"no-cache"}),"HEAD"===e.method)return void o.end();r.createReadStream(n).pipe(o)}})}setInterval(()=>{for(const[e,r]of re)r.cacheId&&!A.sessions.has(r.cacheId)&&(de(r.requestId,{state:"error",projectId:r.projectId,error:"Optimized export session expired"}),le(e))},15e3).unref();const fe=e.createServer(async(e,o)=>{if(!function(e){if(!z(e.headers.host))return!1;const r=e.headers.origin;if(r)try{return z(new URL(r).host)}catch{return!1}return!0}(e))return void Y(o,403,{error:"forbidden: request must come from this machine (bad Host or Origin header)"});const n=new URL(e.url,"http://localhost"),i=decodeURIComponent(n.pathname);if(i.split(/[\\/]/).some(e=>e.startsWith(".")))return o.writeHead(403),void o.end();if("/api/status"===i&&"GET"===e.method)return void Y(o,200,{service:"fablecut",pid:process.pid,version:ue(),dataDir:r.realpathSync(x)});if("/api/export/active"===i&&"GET"===e.method){const e=ne(),r=n.searchParams.get("project");return void Y(o,200,{active:r?e.includes(r):e.length>0,projects:e})}if("/api/server/stop"===i&&"POST"===e.method){if(re.size>0||ne().length>0)return void Y(o,409,{error:"Export in progress"});const e=JSON.stringify({ok:!0,stopped:!0});return o.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-store","Content-Length":Buffer.byteLength(e)}),void o.end(e,()=>{we(),fe.close(()=>process.exit(0))})}if("/api/projects"===i&&"GET"===e.method)return void Y(o,200,O());if("/api/projects"===i&&"POST"===e.method){try{const r=JSON.parse((await K(e)).toString("utf8")||"{}"),t=String(r.name||"Untitled Project").trim()||"Untitled Project",{id:n}=h.create(t,r.id);G(n),L(null,"projects"),Y(o,201,{id:n,name:t})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/project"===i&&"GET"===e.method){try{const e=Q(n);Y(o,200,ce(n,e)||h.read(e.id))}catch(e){Y(o,"PROJECT_NOT_FOUND"===e.code?404:500,{error:String(e)})}return}if("/api/project"===i&&"PUT"===e.method){try{const r=Q(n),t=await K(e),i=JSON.parse(t.toString("utf8"));Y(o,200,{ok:!0,revision:h.save(r.id,i,"1"===n.searchParams.get("force")).revision})}catch(e){Y(o,e.status||400,{error:String(e),revision:e.revision})}return}if("/api/media"===i&&"GET"===e.method){try{const e=Q(n);Y(o,200,r.readdirSync(e.mediaDir).filter(o=>r.statSync(t.join(e.mediaDir,o)).isFile()).map(o=>({name:o,src:Z(e.id,o),size:r.statSync(t.join(e.mediaDir,o)).size})))}catch(e){Y(o,500,{error:String(e)})}return}if("/api/library"===i&&"GET"===e.method){const e=n.searchParams.get("dir");if(!I.includes(e))return void Y(o,400,{error:"dir must be one of "+I.join("|")});try{const n=t.join(j,e),i=[],a=(o,n)=>{for(const s of r.readdirSync(o)){const c=t.join(o,s),d=n?n+"/"+s:s,p=r.statSync(c);p.isDirectory()?a(c,d):i.push({name:s,rel:d,size:p.size,src:"/library/"+e+"/"+d.split("/").map(encodeURIComponent).join("/")})}};a(n,""),Y(o,200,i)}catch(e){Y(o,500,{error:String(e)})}return}if("/api/upload"===i&&"POST"===e.method){try{const i=Q(n);let a=function(e){return e.replace(/[^\w.\- ()\[\]]+/g,"_").slice(0,120)||"file"}(n.searchParams.get("name")||"upload.bin"),s=t.join(i.mediaDir,a),c=1;const p=t.extname(a),l=t.basename(a,p),m=await K(e);for(;;)try{r.writeFileSync(s,m,{flag:"wx"});break}catch(e){if("EEXIST"!==e.code)throw e;s=t.join(i.mediaDir,`${l}_${c++}${p}`)}await async function(e){if(!N||!ee.has(t.extname(e).toLowerCase()))return;const o=e+".fs"+t.extname(e);try{await d("ffmpeg",["-y","-i",e,"-c","copy","-movflags","+faststart",o]),r.rmSync(e),r.renameSync(o,e)}catch{try{r.rmSync(o)}catch{}}}(s),Y(o,200,{ok:!0,src:Z(i.id,t.basename(s))})}catch(e){Y(o,500,{error:String(e)})}return}if(i.startsWith("/api/export/cache/")){try{if(!N||!_)throw new Error("Optimized export needs ffmpeg and ffprobe");const r=Q(n),t=n.searchParams.get("id");if("/api/export/cache/begin"===i&&"POST"===e.method){const t=JSON.parse((await K(e)).toString()),i=ce(n,r),a=oe.get(n.searchParams.get("exportJob"));if(a&&!a.child)throw new Error("Export job has not started");const s=A.create(r,t.revision,i,{managed:!!a});a&&(a.cacheIds||=new Set).add(s),Y(o,200,{id:s})}else if("/api/export/cache/status"===i&&"GET"===e.method)Y(o,200,A.status(t,r.id));else if("/api/export/cache/block"===i&&"POST"===e.method){const n=JSON.parse((await K(e)).toString());Y(o,200,await A.block(t,r.id,n.clipId,n.block,{priority:n.priority,neededAt:n.neededAt,sequence:n.sequence}))}else if("/api/export/cache/retain"===i&&"POST"===e.method){const n=JSON.parse((await K(e)).toString());A.retain(t,r.id,n.sequence,n.blocks,n.current),Y(o,200,{ok:!0})}else if("/api/export/cache/wait"===i&&"POST"===e.method)A.reportWait(t,r.id,JSON.parse((await K(e)).toString())),Y(o,200,{ok:!0});else if("/api/export/cache/frame"===i&&"GET"===e.method)me(e,o,A.frame(t,r.id,n.searchParams.get("key"),Number(n.searchParams.get("index"))));else if("/api/export/cache/drop"===i&&"POST"===e.method){const n=JSON.parse((await K(e)).toString());A.drop(t,r.id,n.clipIds),Y(o,200,{ok:!0})}else"/api/export/cache/release"===i&&"POST"===e.method?(await A.release(t,r.id),Y(o,200,{ok:!0})):Y(o,404,{error:"unknown cache operation"})}catch(e){Y(o,400,{error:String(e.message||e)})}return}if("/api/export/source-audio"===i&&"GET"===e.method){const e=new AbortController,t=()=>e.abort();let i;o.once("close",t);try{if(!N)throw Object.assign(new Error("Audio export needs ffmpeg"),{status:503});if(i=await q.prepare(Q(n),n.searchParams.get("src"),e.signal),e.signal.aborted)return;if(!i)return o.writeHead(204),void o.end();o.writeHead(200,{"Content-Type":"audio/wav","Cache-Control":"no-store"}),await y(r.createReadStream(i.file),o,{signal:e.signal})}catch(e){o.destroyed||o.headersSent?o.destroyed||o.destroy():Y(o,e.status||400,{error:String(e.message||e)})}finally{o.removeListener("close",t),i&&await i.cleanup()}return}if(("/api/waveform"===i||"/api/audio-info"===i)&&"GET"===e.method){try{if(!N||!_)throw Object.assign(new Error("Waveforms need ffmpeg and ffprobe"),{status:503});const e=Q(n),r=n.searchParams.get("src");Y(o,200,"/api/waveform"===i?await U.peaks(e,r):{channels:await U.channels(e,r)})}catch(e){Y(o,e.status||400,{error:String(e.message||e)})}return}if("/api/export/save"===i&&"POST"===e.method){const r=["127.0.0.1","::1","::ffff:127.0.0.1"].includes(e.socket.remoteAddress);let i=!1;try{i=new URL(e.headers.origin).origin===`http://${e.headers.host}`}catch{}if(!r||!i)return e.resume(),void Y(o,403,{error:"System folder selection requires a local, same-origin editor page",browserSave:!0});const a=new AbortController,s=()=>a.abort();o.once("close",s);try{const r=Q(n),i=n.searchParams.get("src");let s,c;if(i)s=await l(r,i),c=t.basename(s),e.resume();else if(c=n.searchParams.get("name"),!["video/mp4","video/webm"].includes(e.headers["content-type"]))throw Object.assign(new Error("Expected a completed video"),{status:400});const d=await f({source:s,name:c,stream:e,signal:a.signal});e.resume(),o.destroyed||Y(o,200,d)}catch(r){e.resume(),o.destroyed||Y(o,r.status||500,{error:r.message,browserSave:!0===r.browserSave||501===r.status})}finally{o.off("close",s)}return}if("/api/export/start"===i&&"POST"===e.method){try{const r=Q(n),t=JSON.parse((await K(e)).toString("utf8")||"{}"),i=t.engine||"optimized";if(!["fast","optimized","realtime"].includes(i))throw new Error("Unknown export engine");if("realtime"!==i&&(!N||"optimized"===i&&!_))throw new Error("Export needs ffmpeg"+("optimized"===i?" and ffprobe":""));if(void 0!==t.browser&&("string"!=typeof t.browser||!t.browser))throw new Error("browser must be a nonempty executable path");const a=void 0===t.timeoutSeconds?3600:t.timeoutSeconds;if(!Number.isFinite(a)||a<=0||a>86400)throw new Error("timeoutSeconds must be between 0 and 86400");const s=h.read(r.id);if(void 0!==t.revision&&t.revision!==s.revision)return void Y(o,409,{error:"Project changed; save/reload before exporting"});if(!s.clips?.length)throw new Error("Timeline is empty");const c=t.requestId??require("crypto").randomBytes(16).toString("hex");if("string"!=typeof c||!/^[a-f0-9]{32}$/.test(c))throw new Error("Invalid export request id");if(te.has(c))return void Y(o,409,{error:"Export request id already used"});oe.set(c,{projectId:r.id,snapshot:s,options:{engine:i,name:String(t.name||s.name||"export"),browser:t.browser,timeoutSeconds:a}}),de(c,{state:"queued",projectId:r.id,engine:i}),ae(),Y(o,202,{id:c,engine:i})}catch(e){Y(o,400,{error:String(e.message||e)})}return}if("/api/export/cancel"===i&&"POST"===e.method){try{const e=Q(n),r=n.searchParams.get("id");if(te.get(r)?.projectId!==e.id)return void Y(o,404,{error:"no such export request"});de(r,{state:"cancelled",projectId:e.id}),Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/ffmpeg"===i&&"GET"===e.method)return void Y(o,200,{available:N,ffprobe:_});if("/api/export/status"===i&&"GET"===e.method){try{const e=Q(n),r=te.get(n.searchParams.get("id"));if(!r||r.projectId!==e.id)return void Y(o,404,{error:"no such export request"});let t;if(!["complete","error","cancelled","finalizing"].includes(r.state)){const e=oe.get(n.searchParams.get("id")),r=e?.cacheIds||[...re.values()].filter(e=>e.requestId===n.searchParams.get("id")).map(e=>e.cacheId);for(const e of r){const r=A.sessions.get(e)?.wait;if(r){t={reason:r.reason,elapsedMs:Math.max(0,Date.now()-r.since)};break}}}Y(o,200,{...r,...t?{wait:t}:{}})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/recording"===i&&"POST"===e.method){let i,a;try{const s=Q(n),c=n.searchParams.get("exportJob");if(a=oe.get(c),!a?.child||a.stopping||a.projectId!==s.id||"realtime"!==a.options.engine)return void Y(o,404,{error:"No active recording for this project"});if(a.recordingUpload)return void Y(o,409,{error:"Recording upload already started"});if(n.searchParams.has("frames")){const e=Number(n.searchParams.get("frames")),r=Number(n.searchParams.get("totalFrames"));if(!Number.isSafeInteger(e)||!Number.isSafeInteger(r)||e<0||r<=0||e>r)throw new Error("Invalid recording progress");de(c,{state:"rendering",frames:e,totalFrames:r})}else{const o=n.searchParams.get("name");if(!m(o)||!["video/mp4","video/webm"].includes(e.headers["content-type"]))throw new Error("Invalid recording");a.recordingUpload=new AbortController,de(c,{state:"finalizing"}),r.mkdirSync(s.exportsDir,{recursive:!0}),i=t.join(t.dirname(s.projectFile),".recording-"+c+".part");const d=r.createWriteStream(i,{flags:"wx"});if(await y(e,d,{signal:a.recordingUpload.signal}),a.recordingUpload.signal.throwIfAborted(),!d.bytesWritten)throw new Error("Empty recording");const p=X(s.exportsDir,o);r.renameSync(i,p),i=null,de(c,{state:"complete",src:"/projects/"+s.id+"/exports/"+encodeURIComponent(t.basename(p))})}Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:e.message})}finally{i&&r.rmSync(i,{force:!0})}return}if("/api/export/report"===i&&"POST"===e.method){try{const r=Q(n),t=JSON.parse((await K(e)).toString("utf8")||"{}"),i=te.get(t.requestId);i&&"error"===i.state||de(t.requestId,{state:"error",projectId:r.id,error:String(t.error||"browser export failed")}),Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/begin"===i&&"POST"===e.method){if(!N)return void Y(o,400,{error:"ffmpeg not found on PATH"});try{const r=Q(n),t=JSON.parse((await K(e)).toString("utf8")||"{}"),i=te.get(t.requestId);if(i&&(["complete","error","cancelled"].includes(i.state)||i.projectId!==r.id))throw new Error("Export request is no longer active");"optimized"===t.engine&&A.get(t.cacheId,r.id),Y(o,200,{id:pe(t.fps||30,t.name,r.id,t.requestId,"optimized"===t.engine?t.cacheId:void 0,t.totalFrames)})}catch(e){Y(o,500,{error:String(e)})}return}if("/api/export/frame"===i&&"POST"===e.method){const r=re.get(n.searchParams.get("id"));if(!r)return void Y(o,404,{error:"no such export session"});try{const t=await K(e);if(t.length<4||255!==t[0]||216!==t[1])throw new Error("export frame is not a valid JPEG");await s(r,t),r.frames++,r.frameBytes+=t.length,de(r.requestId,{state:"rendering",projectId:r.projectId,frames:r.frames,totalFrames:r.totalFrames}),Y(o,200,{ok:!0})}catch(e){de(r.requestId,{state:"error",projectId:r.projectId,error:e.message}),le(n.searchParams.get("id")),Y(o,500,{error:String(e)})}return}if("/api/export/audio"===i&&"POST"===e.method){const i=re.get(n.searchParams.get("id"));if(!i)return void Y(o,404,{error:"no such export session"});try{i.wav=t.join(i.dir,"audio.wav"),r.writeFileSync(i.wav,await K(e)),Y(o,200,{ok:!0})}catch(e){Y(o,500,{error:String(e)})}return}if("/api/export/end"===i&&"POST"===e.method){const i=n.searchParams.get("id"),a=re.get(i);if(!a)return void Y(o,404,{error:"no such export session"});let s;try{if(n.searchParams.get("discard"))return await le(i),void Y(o,200,{ok:!0});if(a.finalizing||a.cleaning)throw new Error("Export session is already ending");const c=Date.now(),p=await K(e),l=p.length?JSON.parse(p.toString()).metrics:void 0;if(!a.frames)throw new Error("export received no video frames");if(a.finalizing=!0,de(a.requestId,{state:"finalizing",projectId:a.projectId,frames:a.frames,totalFrames:a.totalFrames}),a.proc.stdin.end(),0!==await a.done)throw new Error("ffmpeg encode failed: "+a.err());if(a.controller.signal.aborted)throw new Error("export cancelled");const m=h.context(a.projectId);r.mkdirSync(m.exportsDir,{recursive:!0}),s=a.output=X(m.exportsDir,a.name.replace(/\.mp4$/i,"")+".mp4");const f=["-colorspace","bt709","-color_primaries","bt709","-color_trc","bt709","-color_range","tv"];if(a.wav&&r.existsSync(a.wav)?a.muxDone=d("ffmpeg",["-y","-i",a.videoPath,"-i",a.wav,"-c:v","copy","-c:a","aac","-b:a","192k",...a.cacheId?[]:["-shortest"],...f,"-movflags","+faststart",s],a.controller.signal):a.muxDone=d("ffmpeg",["-y","-i",a.videoPath,"-c","copy",...f,"-movflags","+faststart",s],a.controller.signal),await a.muxDone,a.controller.signal.aborted)throw new Error("export cancelled");l&&"object"==typeof l&&(l.phases||={},l.phases.finalizeMs=Date.now()-c,l.totalMs=(Number(l.totalMs)||0)+l.phases.finalizeMs,l.fps=a.frames/(l.totalMs/1e3),l.serverRssBytes=process.memoryUsage().rss),a.committed=!0,de(a.requestId,{state:"complete",projectId:m.id,metrics:l,src:"/projects/"+encodeURIComponent(m.id)+"/exports/"+encodeURIComponent(t.basename(s))}),await le(i),Y(o,200,{ok:!0,metrics:l,src:"/projects/"+encodeURIComponent(m.id)+"/exports/"+encodeURIComponent(t.basename(s))})}catch(e){const r=`${String(e)} (frames=${a.frames}, bytes=${a.frameBytes})`;de(a.requestId,{state:"error",projectId:a.projectId,error:r}),await le(i),Y(o,500,{error:r})}return}if("/api/analyze"===i&&"GET"===e.method){let e;try{e=Q(n)}catch(e){return void Y(o,400,{error:String(e)})}const i=decodeURIComponent(n.searchParams.get("src")||""),a=t.join(e.analysisDir,t.basename(i,t.extname(i))+".json");if(!i||!r.existsSync(a))return void Y(o,404,{error:"no cached analysis for that src — POST /api/analyze first"});try{Y(o,200,JSON.parse(r.readFileSync(a,"utf8")))}catch(e){Y(o,500,{error:String(e)})}return}if("/api/analyze"===i&&"POST"===e.method){if(!N)return void Y(o,400,{error:"ffmpeg not found on PATH"});try{const i=Q(n),a=JSON.parse((await K(e)).toString("utf8")||"{}"),s=t.basename(decodeURIComponent(a.src||"")),c=t.join(i.mediaDir,s);if(!s||!r.existsSync(c))return void Y(o,404,{error:"src must name an existing file under /media/"});const d=await u(c,{threshold:a.threshold,music:!1!==a.music,musicDir:i.mediaDir,srcUrl:Z(i.id,s)});d.music&&(d.music.src=Z(i.id,d.music.name)),r.writeFileSync(t.join(i.analysisDir,t.basename(s,t.extname(s))+".json"),JSON.stringify(d,null,2)),Y(o,200,d)}catch(e){Y(o,500,{error:String(e)})}return}if("/api/events"===i){let r;try{r=Q(n)}catch(e){return void Y(o,400,{error:String(e)})}o.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-store",Connection:"keep-alive"}),o.write("data: hello\n\n");const t={res:o,projectId:r.id};return J.add(t),void e.on("close",()=>J.delete(t))}const a=/^\/projects\/([a-z0-9_-]+)\/(media|exports)\/(.+)$/.exec(i);if(a){try{const n=T(a[1]);if(!r.existsSync(n.projectFile))throw new Error("no such project");const i="media"===a[2]?n.mediaDir:n.exportsDir;me(e,o,t.join(i,t.basename(a[3])))}catch{o.writeHead(404),o.end("Not found")}return}if(i.startsWith("/media/")||i.startsWith("/exports/")){try{const r=h.context(P),n=i.startsWith("/media/")?r.mediaDir:r.exportsDir;me(e,o,t.join(n,t.basename(i)))}catch{o.writeHead(404),o.end("Not found")}return}if(i.startsWith("/library/")){const r=t.normalize(t.join(j,i.slice(9)));return r.startsWith(j+t.sep)?void me(e,o,r):(o.writeHead(403),void o.end())}let c="/"===i?"/index.html":i;if(c=t.normalize(t.join(k,c)),!c.startsWith(k+t.sep))return o.writeHead(403),void o.end();me(e,o,c)});function ue(){const e=process.env.FABLECUT_VERSION;return"string"==typeof e&&e.trim()?e.trim():""}function he(e){const r="0.0.0.0"===R?"127.0.0.1":"::"===R?"::1":R;return`http://${r.includes(":")&&!r.startsWith("[")?`[${r}]`:r}:${e}`}function ge(){return t.join(x,"server.json")}function we(){try{JSON.parse(r.readFileSync(ge(),"utf8")).pid===process.pid&&r.rmSync(ge(),{force:!0})}catch{}}process.on("exit",we);const Se=function(){if("auto"===process.env.PORT)return{port:7777,fallback:!0};const e=Number(process.env.PORT||7777);return(!Number.isInteger(e)||e<0||e>65535)&&(console.error("Invalid PORT"),process.exit(1)),{port:e,fallback:"1"===process.env.FABLECUT_PORT_FALLBACK&&0!==e}}();!function e(t,o){const n=r=>{o&&r&&"EADDRINUSE"===r.code&&0!==t?e(0,!1):(console.error(r&&r.message?r.message:r),process.exit(1))};fe.once("error",n),fe.listen(t,R,()=>{fe.removeListener("error",n);const e=fe.address().port;!function(e){const t={pid:process.pid,host:R,port:e,url:he(e)+"/",version:ue(),dataDir:r.realpathSync(x),startedAt:(new Date).toISOString()},o=ge(),n=o+".tmp";r.writeFileSync(n,JSON.stringify(t)+"\n"),r.renameSync(n,o)}(e),console.log(`\n FableCut running → ${he(e)}/\n`),["127.0.0.1","localhost","::1"].includes(R)||console.log(` ⚠ WARNING: HOST=${R} exposes the editor (and its file APIs) to the network.\n`),console.log(` projects : ${b} (${O().length})`),console.log(` library : ${j} (${I.join(", ")})`),x!==v&&console.log(` app files : ${v}`),console.log(` ffmpeg : ${N?"found (fast export + faststart remux on)":"not found (real-time export only)"}\n`)})}(Se.port,Se.fallback);
1
+ "use strict";const e=require("http"),r=require("fs"),t=require("path"),o=require("os"),{spawn:i,fork:a,spawnSync:s}=require("./cli/lib/processes"),{writeFrame:n,stopProcess:c,run:d}=require("./export-encoder"),{createExportSaver:p,resolveExportSource:l,validVideoName:m}=require("./export-save"),f=p(),{analyze:u}=require("./analyze"),h=require("./project-store"),{ExportCache:g}=require("./export-cache"),{Waveforms:w}=require("./waveform"),{ExportAudio:y}=require("./export-audio"),{pipeline:S}=require("node:stream/promises"),{APP_DIR:v,DATA_DIR:b,PROJECTS_DIR:x,LIBRARY_DIR:j,LIBRARY_SUBDIRS:I,DEFAULT_PROJECT_ID:E,normalizeProjectId:P,projectPaths:T,listProjects:O,ensureDirs:D,projectRoots:k}=require("./paths"),q=v,C=new y({libraryDir:j}),F=process.env.HOST||"127.0.0.1",N=new Set(["localhost","127.0.0.1","[::1]","::1",F.toLowerCase()]);for(const e of(process.env.FABLECUT_ALLOWED_HOSTS||"").split(","))e.trim()&&N.add(e.trim().toLowerCase());function R(e){if(!e)return!1;const r=e.replace(/^(\[[^\]]*\]|[^:]+)(:\d+)?$/,"$1").toLowerCase();return N.has(r)}let $=!1;try{$=0===s("ffmpeg",["-version"],{stdio:"ignore"}).status}catch{}let z=!1;try{z=0===s("ffprobe",["-version"],{stdio:"ignore"}).status}catch{}const _=new g({libraryDir:j}),A=new w({libraryDir:j}),U={".html":"text/html; charset=utf-8",".css":"text/css; charset=utf-8",".js":"text/javascript; charset=utf-8",".json":"application/json",".mp4":"video/mp4",".webm":"video/webm",".mov":"video/quicktime",".mkv":"video/x-matroska",".m4v":"video/mp4",".mp3":"audio/mpeg",".wav":"audio/wav",".ogg":"audio/ogg",".m4a":"audio/mp4",".aac":"audio/aac",".flac":"audio/flac",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".ico":"image/x-icon",".ttf":"font/ttf",".otf":"font/otf",".woff":"font/woff",".woff2":"font/woff2"};D();const G=new Set;function J(e,r="change"){for(const t of G)e&&t.projectId!==e||t.res.write(`data: ${r}\n\n`)}const L=new Map;function H(e){clearTimeout(L.get(e)),L.set(e,setTimeout(()=>J(e),150))}const M=new Map;function W(e){const r=M.get(e);if(M.delete(e),r)for(const e of r.watchers)e.close()}function B(e){const t=T(e),o=M.get(e);if(o?.dir===t.dir)return;W(e);const i=[],a={dir:t.dir,watchers:i},s=(t,o)=>{try{const s=r.watch(t,o);s.on("error",()=>{M.get(e)===a&&W(e),H(e)}),i.push(s)}catch{}};s(t.dir,(r,t)=>{"project.json"===t&&H(e)}),s(t.mediaDir,()=>H(e)),i.length&&M.set(e,a)}for(const e of O())B(e.id);let V=null;for(const e of[x,k().legacyDir].filter(Boolean))try{r.watch(e,(e,r)=>{const t=r&&String(r),o=t&&/^[a-z0-9][a-z0-9_-]{0,63}$/.test(t)?[t]:[...M.keys()];for(const e of o){W(e);try{B(e)}catch{}H(e)}clearTimeout(V),V=setTimeout(()=>J(null,"projects"),150)}).on("error",()=>{for(const[r,o]of M)t.dirname(o.dir)===e&&W(r);J(null,"projects")})}catch{}for(const e of I)try{r.watch(t.join(j,e),()=>J(null))}catch{}function Y(e,r,t){const o=JSON.stringify(t);e.writeHead(r,{"Content-Type":"application/json","Cache-Control":"no-store"}),e.end(o)}function K(e){return new Promise((r,t)=>{const o=[];e.on("data",e=>o.push(e)),e.on("end",()=>r(Buffer.concat(o))),e.on("error",t)})}function X(e,o){let i=t.join(e,o);const a=t.extname(o),s=t.basename(o,a);let n=1;for(;r.existsSync(i);)i=t.join(e,`${s}_${n++}${a}`);return i}function Q(e){const r=P(e.searchParams.get("project")||E),t=h.context(r);return B(r),t}function Z(e,r){return"/projects/"+encodeURIComponent(e)+"/media/"+encodeURIComponent(r)}const ee=new Set([".mp4",".mov",".m4v"]),re=new Map,te=new Map,oe=new Map;function ie(){const e=new Set;for(const[r,t]of oe){const o=te.get(r)?.state;o&&["complete","error","cancelled"].includes(o)||e.add(t.projectId)}for(const r of re.values())r.projectId&&e.add(r.projectId);return[...e]}function ae(e){const r=oe.get(e);if(!r||!r.cleanupDone||r.child&&!r.childClosed)return;clearTimeout(r.timer),oe.delete(e);const t=te.get(e);t&&te.set(e,{...t,workerDone:!0}),queueMicrotask(se)}function se(){let e=[...oe.values()].filter(e=>e.child).length;for(const[r,t]of oe){if(e>=2)break;t.child||t.stopping||(ne(r,t),t.child&&e++)}}function ne(e,r){const{projectId:o,options:i}=r;try{const s=a(t.join(q,"export-worker.js"),[],{stdio:["ignore","ignore","pipe","ipc"]});r.child=s,le(e,{state:"starting",projectId:o});let n="";s.stderr.on("data",e=>{n=(n+e).slice(-4e3),r.stderrTail=n}),s.on("message",r=>{const t=te.get(e);(["starting","preparing-browser","preparing-media"].includes(t?.state)||"error"===r.state)&&le(e,{...r,projectId:o})}),s.on("error",r=>le(e,{state:"error",projectId:o,error:r.message})),s.once("close",()=>{r.childClosed=!0,le(e,{state:"error",projectId:o,error:"Export worker exited: "+n.slice(-800)}),ae(e)}),r.timer=setTimeout(()=>le(e,{state:"error",projectId:o,error:"Export timed out"}),1e3*i.timeoutSeconds);const c=he.address(),d="0.0.0.0"===F?"127.0.0.1":"::"===F?"[::1]":F.includes(":")&&!F.startsWith("[")?`[${F}]`:F;s.send({id:e,projectId:o,engine:i.engine,name:i.name,browser:i.browser,url:`http://${d}:${c.port}/`})}catch(r){le(e,{state:"error",projectId:o,error:r.message})}}function ce(e,r){const t=e.searchParams.get("exportJob");if(!t)return;const o=oe.get(t);if(!o||o.stopping||o.projectId!==r.id)throw new Error("no such export job");return o.snapshot}function de(){if(!de.monitor)try{const e=require("./cli/lib/telemetry");de.monitor=e.createExportMonitor({enrich(e){const r=oe.get(e),t=te.get(e);let o,i,a=t?.wait;for(const e of r?.cacheIds||[]){const r=_.sessions.get(e);r&&(o=r.stats,i=r.activity||i,r.wait&&(a={reason:r.wait.reason,elapsedMs:Math.max(0,Date.now()-r.wait.since)}))}return{stats:o,activity:i,wait:a,stderr:r?.stderrTail,detail:t?.detail,browser:t?.browser}}})}catch{de.monitor={stop(){},upsert(){},note(){},cancel:()=>!1,fail(){},complete(){},scan(){}}}return de.monitor}function pe(e,o){let i=0;const a=`/projects/${e.id}/media/`;for(const s of o?.media||[])try{if("string"!=typeof s?.src||!s.src.startsWith(a))continue;const o=decodeURIComponent(s.src.slice(a.length));if(!o||o.includes("..")||/[/\\]/.test(o))continue;i+=r.statSync(t.join(e.mediaDir,o)).size}catch{}return i}function le(e,r){if(!e||!/^[a-f0-9]{32}$/.test(e))return;const t=te.get(e);if(!t||!["complete","error","cancelled"].includes(t.state)){"error"===r.state&&(r={...r,error:`${r.error} (stage=${t?.state||"starting"}, frames=${t?.frames||0})`}),te.set(e,{...t,...r,updated:Date.now()});try{!function(e,r,t){const o=de();if("error"===t.state)return o.note(e,{stack:t.stack,clipIds:t.clipIds,browserGpu:t.browserGpu,timeSec:t.timeSec}),void o.fail(e,t.error,{stage:r?.state,frames:t.frames??r?.frames,totalFrames:t.totalFrames??r?.totalFrames,origin:"server"});"cancelled"!==t.state?"complete"!==t.state?o.upsert(e,{projectId:t.projectId||r?.projectId,state:t.state||r?.state,frames:t.frames??r?.frames,totalFrames:t.totalFrames??r?.totalFrames,detail:t.detail,engine:t.engine||r?.engine,browser:t.browser}):o.complete(e,{metrics:t.metrics,frames:t.frames??r?.frames,totalFrames:t.totalFrames??r?.totalFrames}):o.cancel(e)}(e,t,r)}catch{}if(["complete","error","cancelled"].includes(r.state)&&function(e){const r=oe.get(e);if(!r||r.stopping)return;r.stopping=!0,r.recordingUpload?.abort(),clearTimeout(r.timer);const t=[];for(const e of r.cacheIds||[])t.push(_.release(e,r.projectId));for(const[r,o]of re)o.requestId===e&&t.push(fe(r));r.child&&!r.childClosed&&r.child.kill();const o=()=>{r.cleanupDone=!0,ae(e)};t.some(Boolean)?Promise.allSettled(t).then(o):o()}(e),te.size>100){const e=[...te].sort((e,r)=>e[1].updated-r[1].updated).slice(0,20);for(const[r]of e)oe.has(r)||te.delete(r)}}}function me(e,a,s,n,c,d){const p=Date.now().toString(36)+Math.random().toString(36).slice(2,7),l=r.mkdtempSync(t.join(o.tmpdir(),"fablecut-")),m=t.join(l,"video.mp4"),f=i("ffmpeg",[...c?["-filter_threads","1"]:[],"-y","-f","image2pipe","-vcodec","mjpeg","-framerate",String(e),"-i","-","-vf","scale=in_range=full:in_color_matrix=bt601:out_range=tv:out_color_matrix=bt709","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-colorspace","bt709","-color_primaries","bt709","-color_trc","bt709","-color_range","tv",m],{stdio:["pipe","ignore","pipe"]});let u="";f.stderr.on("data",e=>{u=(u+e).slice(-2e3)});const h={controller:new AbortController,proc:f,dir:l,videoPath:m,name:(a||"export").replace(/[<>:"/\\|?*\x00-\x1f]+/g,"_").slice(0,120).trim()||"export",projectId:s,requestId:n,cacheId:c,totalFrames:Number.isSafeInteger(d)&&d>0?d:void 0,wav:null,frames:0,frameBytes:0,err:()=>u,done:new Promise(e=>f.on("close",e))};re.set(p,h);const g=e=>{h.cleaning||(le(n,{state:"error",projectId:s,error:e.message}),fe(p))};return f.on("error",g),f.stdin.on("error",g),f.once("exit",(e,r)=>{h.finalizing&&0===e||g(new Error(`ffmpeg exited (${r||e}): ${h.err()}`))}),le(n,{state:"rendering",projectId:s,frames:0,totalFrames:h.totalFrames}),p}function fe(e){const t=re.get(e);if(t)return t.cleaning||(t.controller.abort(),t.cleaning=(async()=>{await Promise.allSettled([c(t.proc,t.done),t.muxDone,t.cacheId&&_.release(t.cacheId,t.projectId)]),t.output&&!t.committed&&await r.promises.rm(t.output,{force:!0}),await r.promises.rm(t.dir,{recursive:!0,force:!0})})().catch(e=>console.error("Export cleanup failed:",e.message)).finally(()=>re.delete(e))),t.cleaning}function ue(e,o,i){r.stat(i,(a,s)=>{if(a||!s.isFile())return o.writeHead(404),void o.end("Not found");"HEAD"===e.method&&o.setHeader("ETag",`W/"${s.size}-${s.mtimeMs}"`);const n=U[t.extname(i).toLowerCase()]||"application/octet-stream",c=e.headers.range;if(c){const t=/bytes=(\d*)-(\d*)/.exec(c);let a=t&&t[1]?parseInt(t[1]):0,d=t&&t[2]?parseInt(t[2]):s.size-1;if(a=Math.min(a,s.size-1),d=Math.min(d,s.size-1),o.writeHead(206,{"Content-Type":n,"Accept-Ranges":"bytes","Content-Range":`bytes ${a}-${d}/${s.size}`,"Content-Length":d-a+1}),"HEAD"===e.method)return void o.end();r.createReadStream(i,{start:a,end:d}).pipe(o)}else{if(o.writeHead(200,{"Content-Type":n,"Content-Length":s.size,"Accept-Ranges":"bytes","Cache-Control":"no-cache"}),"HEAD"===e.method)return void o.end();r.createReadStream(i).pipe(o)}})}setInterval(()=>{for(const[e,r]of re)r.cacheId&&!_.sessions.has(r.cacheId)&&(le(r.requestId,{state:"error",projectId:r.projectId,error:"Optimized export session expired"}),fe(e))},15e3).unref();const he=e.createServer(async(e,o)=>{if(!function(e){if(!R(e.headers.host))return!1;const r=e.headers.origin;if(r)try{return R(new URL(r).host)}catch{return!1}return!0}(e))return void Y(o,403,{error:"forbidden: request must come from this machine (bad Host or Origin header)"});const i=new URL(e.url,"http://localhost"),a=decodeURIComponent(i.pathname);if(a.split(/[\\/]/).some(e=>e.startsWith(".")))return o.writeHead(403),void o.end();if("/api/status"===a&&"GET"===e.method)return void Y(o,200,{service:"fablecut",pid:process.pid,version:ge(),dataDir:r.realpathSync(b)});if("/api/export/active"===a&&"GET"===e.method){const e=ie(),r=i.searchParams.get("project");return void Y(o,200,{active:r?e.includes(r):e.length>0,projects:e})}if("/api/server/stop"===a&&"POST"===e.method){if(re.size>0||ie().length>0)return void Y(o,409,{error:"Export in progress"});const e=JSON.stringify({ok:!0,stopped:!0});return o.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-store","Content-Length":Buffer.byteLength(e)}),void o.end(e,()=>{Se(),he.close(()=>process.exit(0))})}if("/api/projects"===a&&"GET"===e.method)return void Y(o,200,O());if("/api/projects"===a&&"POST"===e.method){try{const r=JSON.parse((await K(e)).toString("utf8")||"{}"),t=String(r.name||"Untitled Project").trim()||"Untitled Project",{id:i}=h.create(t,r.id);B(i),J(null,"projects"),Y(o,201,{id:i,name:t})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/project"===a&&"GET"===e.method){try{const e=Q(i);Y(o,200,ce(i,e)||h.read(e.id))}catch(e){if("PROJECT_NOT_FOUND"!==e.code)try{require("./cli/lib/telemetry").reportError("ProjectReadFailed",e)}catch{}Y(o,"PROJECT_NOT_FOUND"===e.code?404:500,{error:String(e)})}return}if("/api/project"===a&&"PUT"===e.method){try{const r=Q(i),t=await K(e),a=JSON.parse(t.toString("utf8"));Y(o,200,{ok:!0,revision:h.save(r.id,a,"1"===i.searchParams.get("force")).revision})}catch(e){Y(o,e.status||400,{error:String(e),revision:e.revision})}return}if("/api/media"===a&&"GET"===e.method){try{const e=Q(i);Y(o,200,r.readdirSync(e.mediaDir).filter(o=>r.statSync(t.join(e.mediaDir,o)).isFile()).map(o=>({name:o,src:Z(e.id,o),size:r.statSync(t.join(e.mediaDir,o)).size})))}catch(e){Y(o,500,{error:String(e)})}return}if("/api/library"===a&&"GET"===e.method){const e=i.searchParams.get("dir");if(!I.includes(e))return void Y(o,400,{error:"dir must be one of "+I.join("|")});try{const i=t.join(j,e),a=[],s=(o,i)=>{for(const n of r.readdirSync(o)){const c=t.join(o,n),d=i?i+"/"+n:n,p=r.statSync(c);p.isDirectory()?s(c,d):a.push({name:n,rel:d,size:p.size,src:"/library/"+e+"/"+d.split("/").map(encodeURIComponent).join("/")})}};s(i,""),Y(o,200,a)}catch(e){Y(o,500,{error:String(e)})}return}if("/api/upload"===a&&"POST"===e.method){try{const a=Q(i);let s=function(e){return e.replace(/[^\w.\- ()\[\]]+/g,"_").slice(0,120)||"file"}(i.searchParams.get("name")||"upload.bin"),n=t.join(a.mediaDir,s),c=1;const p=t.extname(s),l=t.basename(s,p),m=await K(e);for(;;)try{r.writeFileSync(n,m,{flag:"wx"});break}catch(e){if("EEXIST"!==e.code)throw e;n=t.join(a.mediaDir,`${l}_${c++}${p}`)}await async function(e){if(!$||!ee.has(t.extname(e).toLowerCase()))return;const o=e+".fs"+t.extname(e);try{await d("ffmpeg",["-y","-i",e,"-c","copy","-movflags","+faststart",o]),r.rmSync(e),r.renameSync(o,e)}catch{try{r.rmSync(o)}catch{}}}(n),Y(o,200,{ok:!0,src:Z(a.id,t.basename(n))})}catch(e){Y(o,500,{error:String(e)})}return}if(a.startsWith("/api/export/cache/")){try{if(!$||!z)throw new Error("Optimized export needs ffmpeg and ffprobe");const r=Q(i),t=i.searchParams.get("id");if("/api/export/cache/begin"===a&&"POST"===e.method){const t=JSON.parse((await K(e)).toString()),a=ce(i,r),s=oe.get(i.searchParams.get("exportJob"));if(s&&!s.child)throw new Error("Export job has not started");const n=_.create(r,t.revision,a,{managed:!!s});s&&(s.cacheIds||=new Set).add(n),Y(o,200,{id:n})}else if("/api/export/cache/status"===a&&"GET"===e.method)Y(o,200,_.status(t,r.id));else if("/api/export/cache/block"===a&&"POST"===e.method){const i=JSON.parse((await K(e)).toString());Y(o,200,await _.block(t,r.id,i.clipId,i.block,{priority:i.priority,neededAt:i.neededAt,sequence:i.sequence}))}else if("/api/export/cache/retain"===a&&"POST"===e.method){const i=JSON.parse((await K(e)).toString());_.retain(t,r.id,i.sequence,i.blocks,i.current),Y(o,200,{ok:!0})}else if("/api/export/cache/wait"===a&&"POST"===e.method)_.reportWait(t,r.id,JSON.parse((await K(e)).toString())),Y(o,200,{ok:!0});else if("/api/export/cache/frame"===a&&"GET"===e.method)ue(e,o,_.frame(t,r.id,i.searchParams.get("key"),Number(i.searchParams.get("index"))));else if("/api/export/cache/drop"===a&&"POST"===e.method){const i=JSON.parse((await K(e)).toString());_.drop(t,r.id,i.clipIds),Y(o,200,{ok:!0})}else"/api/export/cache/release"===a&&"POST"===e.method?(await _.release(t,r.id),Y(o,200,{ok:!0})):Y(o,404,{error:"unknown cache operation"})}catch(e){Y(o,400,{error:String(e.message||e)})}return}if("/api/export/source-audio"===a&&"GET"===e.method){const e=new AbortController,t=()=>e.abort();let a;o.once("close",t);try{if(!$)throw Object.assign(new Error("Audio export needs ffmpeg"),{status:503});if(a=await C.prepare(Q(i),i.searchParams.get("src"),e.signal),e.signal.aborted)return;if(!a)return o.writeHead(204),void o.end();o.writeHead(200,{"Content-Type":"audio/wav","Cache-Control":"no-store"}),await S(r.createReadStream(a.file),o,{signal:e.signal})}catch(r){if(!e.signal.aborted&&(!r.status||r.status>=500))try{require("./cli/lib/telemetry").reportError("ExportAudioFailed",r)}catch{}o.destroyed||o.headersSent?o.destroyed||o.destroy():Y(o,r.status||400,{error:String(r.message||r)})}finally{o.removeListener("close",t),a&&await a.cleanup()}return}if(("/api/waveform"===a||"/api/audio-info"===a)&&"GET"===e.method){try{if(!$||!z)throw Object.assign(new Error("Waveforms need ffmpeg and ffprobe"),{status:503});const e=Q(i),r=i.searchParams.get("src");Y(o,200,"/api/waveform"===a?await A.peaks(e,r):{channels:await A.channels(e,r)})}catch(e){if(!e.status||e.status>=500)try{require("./cli/lib/telemetry").reportError("/api/waveform"===a?"WaveformFailed":"AudioInfoFailed",e)}catch{}Y(o,e.status||400,{error:String(e.message||e)})}return}if("/api/export/save"===a&&"POST"===e.method){const r=["127.0.0.1","::1","::ffff:127.0.0.1"].includes(e.socket.remoteAddress);let a=!1;try{a=new URL(e.headers.origin).origin===`http://${e.headers.host}`}catch{}if(!r||!a)return e.resume(),void Y(o,403,{error:"System folder selection requires a local, same-origin editor page",browserSave:!0});const s=new AbortController,n=()=>s.abort();o.once("close",n);try{const r=Q(i),a=i.searchParams.get("src");let n,c;if(a)n=await l(r,a),c=t.basename(n),e.resume();else if(c=i.searchParams.get("name"),!["video/mp4","video/webm"].includes(e.headers["content-type"]))throw Object.assign(new Error("Expected a completed video"),{status:400});const d=await f({source:n,name:c,stream:e,signal:s.signal});e.resume(),o.destroyed||Y(o,200,d)}catch(r){e.resume(),o.destroyed||Y(o,r.status||500,{error:r.message,browserSave:!0===r.browserSave||501===r.status})}finally{o.off("close",n)}return}if("/api/export/start"===a&&"POST"===e.method){try{const r=Q(i),t=JSON.parse((await K(e)).toString("utf8")||"{}"),a=t.engine||"optimized";if(!["fast","optimized","realtime"].includes(a))throw new Error("Unknown export engine");if("realtime"!==a&&(!$||"optimized"===a&&!z))throw new Error("Export needs ffmpeg"+("optimized"===a?" and ffprobe":""));if(void 0!==t.browser&&("string"!=typeof t.browser||!t.browser))throw new Error("browser must be a nonempty executable path");const s=void 0===t.timeoutSeconds?3600:t.timeoutSeconds;if(!Number.isFinite(s)||s<=0||s>86400)throw new Error("timeoutSeconds must be between 0 and 86400");const n=h.read(r.id);if(void 0!==t.revision&&t.revision!==n.revision)return void Y(o,409,{error:"Project changed; save/reload before exporting"});if(!n.clips?.length)throw new Error("Timeline is empty");const c=t.requestId??require("crypto").randomBytes(16).toString("hex");if("string"!=typeof c||!/^[a-f0-9]{32}$/.test(c))throw new Error("Invalid export request id");if(te.has(c))return void Y(o,409,{error:"Export request id already used"});oe.set(c,{projectId:r.id,snapshot:n,options:{engine:a,name:String(t.name||n.name||"export"),browser:t.browser,timeoutSeconds:s}});try{const o=require("./cli/lib/telemetry");de().upsert(c,{state:"queued",engine:a,projectId:r.id,project:o.summarizeExportProject(n,{mediaBytes:pe(r,n)}),startedAt:Date.now(),traceparent:e.headers.traceparent}),!0===t.fallback&&o.reportCustom({name:"export_ui_fallback",type:"export",properties:{engine:a,reason:"ui-fallback"}})}catch{}le(c,{state:"queued",projectId:r.id,engine:a}),se(),Y(o,202,{id:c,engine:a})}catch(e){try{const r=String(e.message||e);/Timeline is empty|Unknown export engine|timeoutSeconds|browser must|Invalid export request|already used/.test(r)||require("./cli/lib/telemetry").reportError("ExportStartFailed",e,{origin:"server"})}catch{}Y(o,400,{error:String(e.message||e)})}return}if("/api/export/cancel"===a&&"POST"===e.method){try{const e=Q(i),r=i.searchParams.get("id");if(te.get(r)?.projectId!==e.id)return void Y(o,404,{error:"no such export request"});le(r,{state:"cancelled",projectId:e.id}),Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/ffmpeg"===a&&"GET"===e.method)return void Y(o,200,{available:$,ffprobe:z});if("/api/export/status"===a&&"GET"===e.method){try{const e=Q(i),r=te.get(i.searchParams.get("id"));if(!r||r.projectId!==e.id)return void Y(o,404,{error:"no such export request"});let t;if(!["complete","error","cancelled","finalizing"].includes(r.state)){const e=oe.get(i.searchParams.get("id")),r=e?.cacheIds||[...re.values()].filter(e=>e.requestId===i.searchParams.get("id")).map(e=>e.cacheId);for(const e of r){const r=_.sessions.get(e)?.wait;if(r){t={reason:r.reason,elapsedMs:Math.max(0,Date.now()-r.since)};break}}}Y(o,200,{...r,...t?{wait:t}:{}})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/recording"===a&&"POST"===e.method){let a,s;try{const n=Q(i),c=i.searchParams.get("exportJob");if(s=oe.get(c),!s?.child||s.stopping||s.projectId!==n.id||"realtime"!==s.options.engine)return void Y(o,404,{error:"No active recording for this project"});if(s.recordingUpload)return void Y(o,409,{error:"Recording upload already started"});if(i.searchParams.has("frames")){const e=Number(i.searchParams.get("frames")),r=Number(i.searchParams.get("totalFrames"));if(!Number.isSafeInteger(e)||!Number.isSafeInteger(r)||e<0||r<=0||e>r)throw new Error("Invalid recording progress");le(c,{state:"rendering",frames:e,totalFrames:r})}else{const o=i.searchParams.get("name");if(!m(o)||!["video/mp4","video/webm"].includes(e.headers["content-type"]))throw new Error("Invalid recording");s.recordingUpload=new AbortController,le(c,{state:"finalizing"}),r.mkdirSync(n.exportsDir,{recursive:!0}),a=t.join(t.dirname(n.projectFile),".recording-"+c+".part");const d=r.createWriteStream(a,{flags:"wx"});if(await S(e,d,{signal:s.recordingUpload.signal}),s.recordingUpload.signal.throwIfAborted(),!d.bytesWritten)throw new Error("Empty recording");const p=X(n.exportsDir,o);r.renameSync(a,p),a=null,le(c,{state:"complete",src:"/projects/"+n.id+"/exports/"+encodeURIComponent(t.basename(p))})}Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:e.message})}finally{a&&r.rmSync(a,{force:!0})}return}if("/api/export/report"===a&&"POST"===e.method){try{const r=Q(i),t=JSON.parse((await K(e)).toString("utf8")||"{}");if(!0===t.diagnostic){try{const e=require("./cli/lib/telemetry");t.browserGpu&&e.noteBrowserGpu(t.browserGpu),de().note(t.requestId,{browserGpu:t.browserGpu,wait:t.wait,stack:t.stack,clipIds:t.clipIds,timeSec:t.timeSec}),Number.isFinite(Number(t.frame))&&de().upsert(t.requestId,{frames:Number(t.frame)}),t.error&&e.reportError("ExportPageError",t.error,{stage:t.stage||"browser",...Number.isFinite(Number(t.frame))?{frame:Number(t.frame)}:{}},{stack:t.stack,clipIds:t.clipIds,browserGpu:t.browserGpu})}catch{}return void Y(o,200,{ok:!0})}const a=te.get(t.requestId);a&&"error"===a.state||le(t.requestId,{state:"error",projectId:r.id,error:String(t.error||"browser export failed"),stack:t.stack,clipIds:t.clipIds,browserGpu:t.browserGpu,timeSec:t.timeSec,...Number.isFinite(Number(t.frame))?{frames:Number(t.frame)}:{}}),Y(o,200,{ok:!0})}catch(e){Y(o,400,{error:String(e)})}return}if("/api/export/begin"===a&&"POST"===e.method){if(!$)return void Y(o,400,{error:"ffmpeg not found on PATH"});try{const r=Q(i),t=JSON.parse((await K(e)).toString("utf8")||"{}"),a=te.get(t.requestId);if(a&&(["complete","error","cancelled"].includes(a.state)||a.projectId!==r.id))throw new Error("Export request is no longer active");"optimized"===t.engine&&_.get(t.cacheId,r.id),Y(o,200,{id:me(t.fps||30,t.name,r.id,t.requestId,"optimized"===t.engine?t.cacheId:void 0,t.totalFrames)})}catch(e){Y(o,500,{error:String(e)})}return}if("/api/export/frame"===a&&"POST"===e.method){const r=re.get(i.searchParams.get("id"));if(!r)return void Y(o,404,{error:"no such export session"});try{const t=await K(e);if(t.length<4||255!==t[0]||216!==t[1])throw new Error("export frame is not a valid JPEG");await n(r,t),r.frames++,r.frameBytes+=t.length,le(r.requestId,{state:"rendering",projectId:r.projectId,frames:r.frames,totalFrames:r.totalFrames}),Y(o,200,{ok:!0})}catch(e){le(r.requestId,{state:"error",projectId:r.projectId,error:e.message}),fe(i.searchParams.get("id")),Y(o,500,{error:String(e)})}return}if("/api/export/audio"===a&&"POST"===e.method){const a=re.get(i.searchParams.get("id"));if(!a)return void Y(o,404,{error:"no such export session"});try{a.wav=t.join(a.dir,"audio.wav"),r.writeFileSync(a.wav,await K(e)),Y(o,200,{ok:!0})}catch(e){Y(o,500,{error:String(e)})}return}if("/api/export/end"===a&&"POST"===e.method){const a=i.searchParams.get("id"),s=re.get(a);if(!s)return void Y(o,404,{error:"no such export session"});let n;try{if(i.searchParams.get("discard"))return await fe(a),void Y(o,200,{ok:!0});if(s.finalizing||s.cleaning)throw new Error("Export session is already ending");const c=Date.now(),p=await K(e),l=p.length?JSON.parse(p.toString()).metrics:void 0;if(!s.frames)throw new Error("export received no video frames");if(s.finalizing=!0,le(s.requestId,{state:"finalizing",projectId:s.projectId,frames:s.frames,totalFrames:s.totalFrames}),s.proc.stdin.end(),0!==await s.done)throw new Error("ffmpeg encode failed: "+s.err());if(s.controller.signal.aborted)throw new Error("export cancelled");const m=h.context(s.projectId);r.mkdirSync(m.exportsDir,{recursive:!0}),n=s.output=X(m.exportsDir,s.name.replace(/\.mp4$/i,"")+".mp4");const f=["-colorspace","bt709","-color_primaries","bt709","-color_trc","bt709","-color_range","tv"];if(s.wav&&r.existsSync(s.wav)?s.muxDone=d("ffmpeg",["-y","-i",s.videoPath,"-i",s.wav,"-c:v","copy","-c:a","aac","-b:a","192k",...s.cacheId?[]:["-shortest"],...f,"-movflags","+faststart",n],s.controller.signal):s.muxDone=d("ffmpeg",["-y","-i",s.videoPath,"-c","copy",...f,"-movflags","+faststart",n],s.controller.signal),await s.muxDone,s.controller.signal.aborted)throw new Error("export cancelled");l&&"object"==typeof l&&(l.phases||={},l.phases.finalizeMs=Date.now()-c,l.totalMs=(Number(l.totalMs)||0)+l.phases.finalizeMs,l.fps=s.frames/(l.totalMs/1e3),l.serverRssBytes=process.memoryUsage().rss),s.committed=!0,le(s.requestId,{state:"complete",projectId:m.id,metrics:l,src:"/projects/"+encodeURIComponent(m.id)+"/exports/"+encodeURIComponent(t.basename(n))}),await fe(a),Y(o,200,{ok:!0,metrics:l,src:"/projects/"+encodeURIComponent(m.id)+"/exports/"+encodeURIComponent(t.basename(n))})}catch(e){const r=`${String(e)} (frames=${s.frames}, bytes=${s.frameBytes})`;le(s.requestId,{state:"error",projectId:s.projectId,error:r}),await fe(a),Y(o,500,{error:r})}return}if("/api/analyze"===a&&"GET"===e.method){let e;try{e=Q(i)}catch(e){return void Y(o,400,{error:String(e)})}const a=decodeURIComponent(i.searchParams.get("src")||""),s=t.join(e.analysisDir,t.basename(a,t.extname(a))+".json");if(!a||!r.existsSync(s))return void Y(o,404,{error:"no cached analysis for that src — POST /api/analyze first"});try{Y(o,200,JSON.parse(r.readFileSync(s,"utf8")))}catch(e){Y(o,500,{error:String(e)})}return}if("/api/analyze"===a&&"POST"===e.method){if(!$)return void Y(o,400,{error:"ffmpeg not found on PATH"});try{const a=Q(i),s=JSON.parse((await K(e)).toString("utf8")||"{}"),n=t.basename(decodeURIComponent(s.src||"")),c=t.join(a.mediaDir,n);if(!n||!r.existsSync(c))return void Y(o,404,{error:"src must name an existing file under /media/"});const d=await u(c,{threshold:s.threshold,music:!1!==s.music,musicDir:a.mediaDir,srcUrl:Z(a.id,n)});d.music&&(d.music.src=Z(a.id,d.music.name)),r.writeFileSync(t.join(a.analysisDir,t.basename(n,t.extname(n))+".json"),JSON.stringify(d,null,2)),Y(o,200,d)}catch(e){try{require("./cli/lib/telemetry").reportError("AnalyzeFailed",e)}catch{}Y(o,500,{error:String(e)})}return}if("/api/events"===a){let r;try{r=Q(i)}catch(e){return void Y(o,400,{error:String(e)})}o.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-store",Connection:"keep-alive"}),o.write("data: hello\n\n");const t={res:o,projectId:r.id};return G.add(t),void e.on("close",()=>G.delete(t))}const s=/^\/projects\/([a-z0-9_-]+)\/(media|exports)\/(.+)$/.exec(a);if(s){try{const i=T(s[1]);if(!r.existsSync(i.projectFile))throw new Error("no such project");const a="media"===s[2]?i.mediaDir:i.exportsDir;ue(e,o,t.join(a,t.basename(s[3])))}catch{o.writeHead(404),o.end("Not found")}return}if(a.startsWith("/media/")||a.startsWith("/exports/")){try{const r=h.context(E),i=a.startsWith("/media/")?r.mediaDir:r.exportsDir;ue(e,o,t.join(i,t.basename(a)))}catch{o.writeHead(404),o.end("Not found")}return}if(a.startsWith("/library/")){const r=t.normalize(t.join(j,a.slice(9)));return r.startsWith(j+t.sep)?void ue(e,o,r):(o.writeHead(403),void o.end())}let c="/"===a?"/index.html":a;if(c=t.normalize(t.join(q,c)),!c.startsWith(q+t.sep))return o.writeHead(403),void o.end();ue(e,o,c)});function ge(){const e=process.env.FABLECUT_VERSION;return"string"==typeof e&&e.trim()?e.trim():""}function we(e){const r="0.0.0.0"===F?"127.0.0.1":"::"===F?"::1":F;return`http://${r.includes(":")&&!r.startsWith("[")?`[${r}]`:r}:${e}`}function ye(){return t.join(b,"server.json")}function Se(){try{JSON.parse(r.readFileSync(ye(),"utf8")).pid===process.pid&&r.rmSync(ye(),{force:!0})}catch{}}process.on("exit",Se);let ve=!1;function be(e,r){if(!ve){ve=!0;try{require("./cli/lib/telemetry").reportFatalSync(e,r,{command:"server"})}catch{}console.error(r),process.exit(1)}}process.on("uncaughtException",e=>{be("UncaughtException",e)}),process.on("unhandledRejection",e=>{be("UnhandledRejection",e instanceof Error?e:new Error(String(e)))});try{require("./cli/lib/telemetry").initTelemetry({role:"server",command:"server"})}catch{}const xe=function(){if("auto"===process.env.PORT)return{port:7777,fallback:!0};const e=Number(process.env.PORT||7777);return(!Number.isInteger(e)||e<0||e>65535)&&(console.error("Invalid PORT"),process.exit(1)),{port:e,fallback:"1"===process.env.FABLECUT_PORT_FALLBACK&&0!==e}}();!function e(t,o){const i=r=>{if(o&&r&&"EADDRINUSE"===r.code&&0!==t)e(0,!1);else{try{require("./cli/lib/telemetry").reportFatalSync("ServerListenFailed",r,{port:String(t)})}catch{}console.error(r&&r.message?r.message:r),process.exit(1)}};he.once("error",i),he.listen(t,F,()=>{he.removeListener("error",i);const e=he.address().port;!function(e){const t={pid:process.pid,host:F,port:e,url:we(e)+"/",version:ge(),dataDir:r.realpathSync(b),startedAt:(new Date).toISOString()},o=ye(),i=o+".tmp";r.writeFileSync(i,JSON.stringify(t)+"\n"),r.renameSync(i,o)}(e),console.log(`\n FableCut running → ${we(e)}/\n`),["127.0.0.1","localhost","::1"].includes(F)||console.log(` ⚠ WARNING: HOST=${F} exposes the editor (and its file APIs) to the network.\n`),console.log(` projects : ${x} (${O().length})`),console.log(` library : ${j} (${I.join(", ")})`),b!==v&&console.log(` app files : ${v}`),console.log(` ffmpeg : ${$?"found (fast export + faststart remux on)":"not found (real-time export only)"}\n`)})}(xe.port,xe.fallback);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tikagent/video-editor-cli",
3
- "version": "1.5.0-preview.1",
3
+ "version": "1.5.0-preview.2",
4
4
  "description": "Local video editing, on-demand preview, and headless",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "commonjs",
@@ -13,7 +13,6 @@
13
13
  },
14
14
  "files": [
15
15
  "dist/",
16
- "scripts/",
17
16
  "LICENSE",
18
17
  "THIRD-PARTY-NOTICES.md"
19
18
  ],
@@ -21,7 +20,7 @@
21
20
  "next": "node scripts/next-version.js",
22
21
  "sync-runtime": "node scripts/pack-runtime.js",
23
22
  "build": "node scripts/build-package.js",
24
- "prepare": "node scripts/build-package.js"
23
+ "prepare": "node -e \"const fs=require('node:fs');const {execFileSync}=require('node:child_process');if(fs.existsSync('lib/cli.js')){execFileSync(process.execPath,['scripts/build-package.js'],{stdio:'inherit'})}else{for(const file of ['dist/bin/tik-video-editor-cli.js','dist/runtime/server.js']){if(!fs.existsSync(file)){console.error('Incomplete CLI distribution: '+file);process.exit(1)}}}\""
25
24
  },
26
25
  "engines": {
27
26
  "node": ">=18"