@tikagent/video-editor-cli 0.0.0-stage → 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.
- package/LICENSE +82 -0
- package/THIRD-PARTY-NOTICES.md +21 -0
- package/dist/bin/tik-video-editor-cli.js +2 -0
- package/dist/lib/asr.js +1 -0
- package/dist/lib/audio.js +1 -0
- package/dist/lib/auth.js +1 -0
- package/dist/lib/browser-zip.js +1 -0
- package/dist/lib/browser.js +1 -0
- package/dist/lib/cli.js +1 -0
- package/dist/lib/doctor.js +1 -0
- package/dist/lib/download.js +1 -0
- package/dist/lib/export-progress.js +1 -0
- package/dist/lib/import-video.js +1 -0
- package/dist/lib/local.js +1 -0
- package/dist/lib/media.js +1 -0
- package/dist/lib/operation.js +1 -0
- package/dist/lib/otlp.js +1 -0
- package/dist/lib/preset.js +1 -0
- package/dist/lib/processes.js +1 -0
- package/dist/lib/self.js +1 -0
- package/dist/lib/service-urls.js +1 -0
- package/dist/lib/storage.js +1 -0
- package/dist/lib/telemetry.js +1 -0
- package/dist/runtime/LICENSE +21 -0
- package/dist/runtime/analyze.js +1 -0
- package/dist/runtime/app.js +1 -0
- package/dist/runtime/cli/lib/browser-zip.js +1 -0
- package/dist/runtime/cli/lib/browser.js +1 -0
- package/dist/runtime/cli/lib/otlp.js +1 -0
- package/dist/runtime/cli/lib/processes.js +1 -0
- package/dist/runtime/cli/lib/telemetry.js +1 -0
- package/dist/runtime/export-audio.js +1 -0
- package/dist/runtime/export-cache.js +1 -0
- package/dist/runtime/export-encoder.js +1 -0
- package/dist/runtime/export-save.js +1 -0
- package/dist/runtime/export-worker.js +1 -0
- package/dist/runtime/favicon.svg +14 -0
- package/dist/runtime/i18n.js +1 -0
- package/dist/runtime/icons/favicon-32.png +0 -0
- package/dist/runtime/icons/tik-logo.png +0 -0
- package/dist/runtime/index.html +320 -0
- package/dist/runtime/library/README.md +12 -0
- package/dist/runtime/library/elements/bokeh-drift.svg +19 -0
- package/dist/runtime/library/elements/light-leak-cool.svg +16 -0
- package/dist/runtime/library/elements/light-leak-warm.svg +22 -0
- package/dist/runtime/library/elements/vhs-scanlines.svg +18 -0
- package/dist/runtime/library/elements/vignette-frame.svg +14 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/344/270/255/351/273/221.woff2 +0 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/345/270/270/350/247/204.woff2 +0 -0
- package/dist/runtime/library/fonts//351/230/277/351/207/214/345/267/264/345/267/264/346/231/256/346/203/240/344/275/223-/347/262/227/344/275/223.woff2 +0 -0
- package/dist/runtime/library/sfx/README.md +13 -0
- package/dist/runtime/library/svg/arrow-bounce.svg +18 -0
- package/dist/runtime/library/svg/arrow-scribble.svg +13 -0
- package/dist/runtime/library/svg/burst-lines.svg +30 -0
- package/dist/runtime/library/svg/checkmark-success.svg +19 -0
- package/dist/runtime/library/svg/confetti-burst.svg +23 -0
- package/dist/runtime/library/svg/countdown-ring.svg +92 -0
- package/dist/runtime/library/svg/equalizer.svg +18 -0
- package/dist/runtime/library/svg/glow-orbs.svg +29 -0
- package/dist/runtime/library/svg/heart-pop.svg +26 -0
- package/dist/runtime/library/svg/loading-dots.svg +16 -0
- package/dist/runtime/library/svg/location-pin.svg +25 -0
- package/dist/runtime/library/svg/lower-third.svg +17 -0
- package/dist/runtime/library/svg/new-badge.svg +18 -0
- package/dist/runtime/library/svg/progress-bar.svg +16 -0
- package/dist/runtime/library/svg/radial-pulse.svg +19 -0
- package/dist/runtime/library/svg/rating-stars.svg +25 -0
- package/dist/runtime/library/svg/rec-indicator.svg +41 -0
- package/dist/runtime/library/svg/sparkles.svg +17 -0
- package/dist/runtime/library/svg/speech-bubble.svg +23 -0
- package/dist/runtime/library/svg/subscribe-bell.svg +29 -0
- package/dist/runtime/library/svg/subscribe-pill.svg +22 -0
- package/dist/runtime/library/svg/swipe-up.svg +46 -0
- package/dist/runtime/library/svg/underline-swoosh.svg +15 -0
- package/dist/runtime/manifest.json +40 -0
- package/dist/runtime/meter-worklet.js +1 -0
- package/dist/runtime/optimized-encoder-worker.js +1 -0
- package/dist/runtime/optimized-export.js +1 -0
- package/dist/runtime/paths.js +1 -0
- package/dist/runtime/project-store.js +1 -0
- package/dist/runtime/ruler-worker.js +1 -0
- package/dist/runtime/server.js +1 -0
- package/dist/runtime/style.css +2126 -0
- package/dist/runtime/timeline.js +1 -0
- package/dist/runtime/waveform.js +1 -0
- package/package.json +36 -4
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("fs"),r=require("path"),i=__dirname,n=process.env.FABLECUT_DATA_DIR?r.resolve(process.env.FABLECUT_DATA_DIR):i,o=n!==i,t=process.env.FABLECUT_PROJECTS_DIR?r.resolve(process.env.FABLECUT_PROJECTS_DIR):r.join(n,"projects"),s=r.join(n,"library"),c=["sfx","elements","svg","fonts"],a="default";function d(r){try{return e.lstatSync(r),!0}catch(e){if("ENOENT"===e.code||"ENOTDIR"===e.code)return!1;throw e}}function l({dataDir:e=n,projectsDir:i=t}={}){const o=r.join(e,"projects");return{projectsDir:i,legacyDir:("win32"===process.platform?r.resolve(o).toLowerCase()===r.resolve(i).toLowerCase():r.resolve(o)===r.resolve(i))?null:o,hiddenDir:r.join(e,".legacy-projects-hidden")}}function j(i,n){i=u(i);const{legacyDir:o,hiddenDir:t}=l(n);if(!o||!d(r.join(o,i)))return;const s=r.join(t,i);d(s)||e.mkdirSync(s,{recursive:!0})}function u(e){const r=String(e||a).trim().toLowerCase();if(!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(r))throw new Error("project id must be 1-64 lowercase letters, numbers, _ or -");return r}function y(e=a,i){e=u(e);const{projectsDir:n,legacyDir:o,hiddenDir:t}=l(i);let s=r.join(n,e);return d(s)?j(e,i):o&&!d(r.join(t,e))&&d(r.join(o,e))&&(s=r.join(o,e)),{id:e,dir:s,projectFile:r.join(s,"project.json"),mediaDir:r.join(s,"media"),exportsDir:r.join(s,"exports"),analysisDir:r.join(s,"analysis")}}function f(r=a,i){const n=y(r);for(const r of[n.dir,n.mediaDir,n.exportsDir,n.analysisDir])e.mkdirSync(r,{recursive:!0});return e.existsSync(n.projectFile)||e.writeFileSync(n.projectFile,JSON.stringify(function(e="Untitled Project"){return{name:e,width:1280,height:720,fps:30,revision:0,media:[],clips:[]}}(i),null,2)),n}function p(r){return r&&d(r)?e.readdirSync(r,{withFileTypes:!0}).filter(e=>e.isDirectory()&&/^[a-z0-9][a-z0-9_-]{0,63}$/.test(e.name)).map(e=>e.name):[]}function m(e){const{legacyDir:i}=l(e);return p(i).map(r=>y(r,e)).filter(e=>e.dir===r.join(i,e.id))}function D(){return[...new Set([...p(t),...m().map(e=>e.id)])].map(r=>{const i=y(r);let n={};try{n=JSON.parse(e.readFileSync(i.projectFile,"utf8").replace(/^\uFEFF/,""))}catch{}let o=0;try{o=e.statSync(i.projectFile).mtimeMs}catch{}return{id:r,name:n.name||r,revision:n.revision||0,modified:o}}).sort((e,r)=>r.modified-e.modified||e.id.localeCompare(r.id))}module.exports={APP_DIR:i,DATA_DIR:n,SPLIT:o,PROJECTS_DIR:t,LIBRARY_DIR:s,LIBRARY_SUBDIRS:c,DEFAULT_PROJECT_ID:a,normalizeProjectId:u,projectPaths:y,ensureProject:f,listProjects:D,ensureDirs:function(){e.mkdirSync(n,{recursive:!0}),e.mkdirSync(t,{recursive:!0}),function(){if(!e.existsSync(r.join(n,"project.json")))return;const o=["project.json","media","exports","analysis"].map(e=>[r.join(n,e),e]).filter(([r])=>e.existsSync(r));if(!o.length)return;const t=y(a);e.mkdirSync(t.dir,{recursive:!0});for(const[i,n]of o){const o=r.join(t.dir,n);e.existsSync(o)||e.renameSync(i,o)}if(n===i)for(const i of["media","exports"]){const o=r.join(n,i);e.mkdirSync(o,{recursive:!0}),e.closeSync(e.openSync(r.join(o,".gitkeep"),"a"))}}();for(const i of c)e.mkdirSync(r.join(s,i),{recursive:!0});!function(){if(!o)return;const n=r.join(i,"library");if(!e.existsSync(n))return;const t=(i,n)=>{e.mkdirSync(n,{recursive:!0});for(const o of e.readdirSync(i,{withFileTypes:!0})){const s=r.join(i,o.name),c=r.join(n,o.name);o.isDirectory()?t(s,c):e.existsSync(c)||e.copyFileSync(s,c)}};t(n,s)}(),D().length||f(a)},projectRoots:l,hideLegacyProject:j,legacyProjectPaths:m};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let cv=null,g=null;function fmt(t,o){t=Math.max(0,t);const l=Math.floor(t/60),e=Math.floor(t%60),n=Math.floor(t%1*(o||30)),g=t=>String(t).padStart(2,"0");return`${g(l)}:${g(e)}:${g(n)}`}function draw(t){if(!g)return;const{w:o,h:l,dpr:e,sl:n,pps:i,markers:a,inPoint:f,outPoint:c,time:s,fps:r,duration:h}=t,d=Math.round(o*e),m=Math.round(l*e);cv.width===d&&cv.height===m||(cv.width=d,cv.height=m),g.setTransform(e,0,0,e,0,0),g.clearRect(0,0,o,l);const T=([.1,.25,.5,1,2,5,10,15,30,60,120,300].find(t=>t*i>=70)||600)/5,u=Math.max(0,Math.floor(n/i/T));g.strokeStyle="#4a4a55",g.fillStyle="#9a9aa6",g.font="10px Consolas, monospace",g.beginPath();for(let t=u;t*T*i<n+o&&t*T<=h;t++){const o=t*T,e=Math.round(o*i-n)+.5,a=t%5==0;g.moveTo(e,a?8:17),g.lineTo(e,l),a&&g.fillText(fmt(Math.round(1e3*o)/1e3,r).slice(0,5),e+4,12)}if(g.stroke(),null!=f&&null!=c&&c>f){const t=f*i-n,e=c*i-n;g.fillStyle="#00000055",t>0&&g.fillRect(0,0,Math.min(o,t),l),e<o&&g.fillRect(Math.max(0,e),0,o-Math.max(0,e),l)}for(const t of a||[]){const e=t.t*i-n;e<-6||e>o+6||(g.fillStyle="#ffd166",g.beginPath(),g.moveTo(e,l-9),g.lineTo(e+4,l-5),g.lineTo(e,l-1),g.lineTo(e-4,l-5),g.closePath(),g.fill())}const v=l-1,p=v-.75*(l-4),M=(p+v)/2;if(g.globalCompositeOperation="difference",null!=f){const t=f*i-n;t>=-10&&t<=o+10&&(g.fillStyle="#5eead4",g.beginPath(),g.moveTo(t,p),g.lineTo(t,v),g.lineTo(t+8,M),g.closePath(),g.fill())}if(null!=c){const t=c*i-n;t>=-10&&t<=o+10&&(g.fillStyle="#fb923c",g.beginPath(),g.moveTo(t,p),g.lineTo(t,v),g.lineTo(t-8,M),g.closePath(),g.fill())}g.globalCompositeOperation="source-over";const P=s*i-n;P>=-8&&P<=o+8&&(g.fillStyle="#ff4d6a",g.beginPath(),g.moveTo(P-6,12),g.lineTo(P+6,12),g.lineTo(P+6,19),g.lineTo(P,25),g.lineTo(P-6,19),g.closePath(),g.fill())}self.onmessage=t=>{const o=t.data;o&&("init"===o.type?(cv=o.canvas,g=cv.getContext("2d")):"draw"===o.type&&draw(o))};
|
|
@@ -0,0 +1 @@
|
|
|
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);
|