@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 r=require("fs"),e=require("path"),{Transform:t,Readable:n}=require("stream"),{pipeline:o}=require("stream/promises"),{createInflateRaw:a}=require("zlib"),s=Array.from({length:256},(r,e)=>{for(let r=0;r<8;r++)e=1&e?3988292384^e>>>1:e>>>1;return e>>>0});function i(r,e=4294967295){for(const t of r)e=s[255&(e^t)]^e>>>8;return e>>>0}function w(r,t){const n=e.relative(r,t);return""!==n&&".."!==n&&!n.startsWith(".."+e.sep)&&!e.isAbsolute(n)}module.exports={extractZip:async function(s,c){const d=await r.promises.open(s,"r"),l=e.resolve(c),f=[],I=new Set;try{const{size:h}=await d.stat();async function E(r,e){if(r<0||e<0||r+e>h)throw new Error("Truncated browser ZIP");const t=Buffer.alloc(e);let n=0;for(;n<e;){const{bytesRead:o}=await d.read(t,n,e-n,r+n);if(!o)throw new Error("Truncated browser ZIP");n+=o}return t}const u=await E(Math.max(0,h-65557),Math.min(h,65557));let m=u.length-22;for(;m>=0&&(101010256!==u.readUInt32LE(m)||m+22+u.readUInt16LE(m+20)!==u.length);)m--;if(m<0)throw new Error("Invalid browser ZIP directory");const p=u.readUInt16LE(m+10),U=u.readUInt32LE(m+12),L=u.readUInt32LE(m+16);if(u.readUInt32LE(m+4)||u.readUInt16LE(m+8)!==p||65535===p||U>16777216)throw new Error("Unsupported browser ZIP format");const b=await E(L,U);let y=0,Z=0;for(let g=0;g<p;g++){if(y+46>b.length||33639248!==b.readUInt32LE(y))throw new Error("Invalid browser ZIP entry");const v=b.readUInt16LE(y+8),k=b.readUInt16LE(y+10),x=b.readUInt32LE(y+16),q=b.readUInt32LE(y+20),z=b.readUInt32LE(y+24),B=b.readUInt16LE(y+28),R=b.readUInt16LE(y+30),S=b.readUInt16LE(y+32),T=b.readUInt32LE(y+38)>>>16,A=b.readUInt32LE(y+42),W=y+46+B+R+S;if(W>b.length||1&v||![0,8].includes(k)||4294967295===q||4294967295===A)throw new Error("Unsupported browser ZIP entry");const M=b.subarray(y+46,y+46+B).toString("utf8");y=W;const F=e.resolve(l,M);if(!M||/[\\:\0]/.test(M)||M.split("/").some(r=>".."===r)||!w(l,F)||I.has(F))throw new Error("Unsafe browser ZIP path: "+M);if(I.add(F),Z+=z,Z>3*1024**3)throw new Error("Browser ZIP exceeds extraction limit");const j=61440&T;if(j&&![16384,32768,40960].includes(j))throw new Error("Unsupported browser ZIP file type");if(M.endsWith("/")){if(z||j&&16384!==j)throw new Error("Invalid browser ZIP directory entry");await r.promises.mkdir(F,{recursive:!0});continue}const C=await E(A,30);if(67324752!==C.readUInt32LE(0)||C.readUInt16LE(8)!==k)throw new Error("Invalid browser ZIP local entry");const D=A+30+C.readUInt16LE(26)+C.readUInt16LE(28);if(D+q>L)throw new Error("Truncated browser ZIP payload");if(40960===j&&z>4096)throw new Error("Invalid browser ZIP symlink");let G=0,H=4294967295;const J=new t({transform(r,e,t){if(G+=r.length,G>z)return t(new Error("Browser ZIP size mismatch"));H=i(r,H),t(null,r)}});await r.promises.mkdir(e.dirname(F),{recursive:!0});const K=[q?r.createReadStream(s,{start:D,end:D+q-1}):n.from([])];if(8===k&&K.push(a()),K.push(J,r.createWriteStream(F,{flags:"wx",mode:73&T?493:420})),await o(...K),G!==z||(4294967295^H)>>>0!==x)throw new Error("Browser ZIP checksum mismatch: "+M);if(40960===j){const N=await r.promises.readFile(F,"utf8");if(!N||/[\\:\0]/.test(N)||e.isAbsolute(N)||!w(l,e.resolve(e.dirname(F),N)))throw new Error("Unsafe browser ZIP symlink: "+M);f.push({target:F,link:N})}}if(y!==b.length)throw new Error("Invalid browser ZIP directory size");for(const{target:O,link:Q}of f)await r.promises.unlink(O),await r.promises.symlink(Q,O);const P=await r.promises.realpath(l);for(const{target:V}of f)if(!w(P,await r.promises.realpath(V)))throw new Error("Unsafe browser ZIP symlink chain")}finally{await d.close()}},crc32:i};
|
|
@@ -0,0 +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})}}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:fs"),r=require("node:os"),t=require("node:path"),o=require("node:child_process"),n=()=>t.join(r.homedir(),".tik-video-editor-cli-processes"),i=()=>"1"===process.env.TIK_VIDEO_EDITOR_MANAGED,s=e=>new Promise(r=>setTimeout(r,e));function c(){try{e.rmdirSync(n())}catch{}}function p(e){let r;if("win32"===process.platform){const n=t.join(process.env.SystemRoot||"C:\\Windows","System32","WindowsPowerShell","v1.0","powershell.exe"),i=`Get-CimInstance Win32_Process${e?` -Filter 'ProcessId=${Number(e)}'`:""} | Select-Object ProcessId,ParentProcessId,@{n='Birth';e={$_.CreationDate.ToUniversalTime().Ticks.ToString()}} | ConvertTo-Json -Compress`;if(r=o.spawnSync(n,["-NoProfile","-NonInteractive","-Command",i],{encoding:"utf8",windowsHide:!0,timeout:1e4,maxBuffer:8<<20}),r.error||0!==r.status)throw new Error("Cannot inspect CLI processes: "+(r.error?.message||r.stderr));const s=JSON.parse(r.stdout.trim()||"[]");return new Map([].concat(s).filter(Boolean).map(e=>[e.ProcessId,{pid:e.ProcessId,ppid:e.ParentProcessId,birth:e.Birth,group:0}]))}if(r=o.spawnSync("/bin/ps",[...e?["-p",String(e)]:["-ax"],"-o","pid=,ppid=,pgid=,stat=,lstart="],{encoding:"utf8",env:{...process.env,LC_ALL:"C",TZ:"UTC"},timeout:5e3,maxBuffer:8<<20}),r.error||0!==r.status&&(!e||1!==r.status||r.stdout.trim()))throw new Error("Cannot inspect CLI processes: "+(r.error?.message||r.stderr));const n=new Map;for(const e of r.stdout.trim().split("\n")){const r=e.trim().match(/^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);if(!r||r[4].includes("Z"))continue;const[,t,o,i,,s]=r;n.set(+t,{pid:+t,ppid:+o,group:+i,birth:s.trim()})}return n}const d=(e,r)=>!!r&&e.pid===r.pid&&e.birth===r.birth;function a(r){try{return JSON.parse(e.readFileSync(r,"utf8"))}catch(e){if("ENOENT"===e.code||e instanceof SyntaxError)return null;throw e}}function f(r){const o=t.join(n(),r),i=a(o);if(!i)return null;let s;try{s=p(i.pid).get(i.pid)}catch{return i}return d(i,s)?i:(e.rmSync(o,{force:!0}),null)}function u(){if(f("shutdown.json"))throw new Error("CLI shutdown is in progress; retry after it finishes");const e=f("migration.json");if(e&&e.pid!==process.pid)throw new Error("CLI storage migration is in progress; retry after it finishes")}function l(e){try{return process.kill(e,0),!0}catch{return!1}}function w(r,o=!1){const i=p(r).get(r);if(!i)return null;i.group=o&&i.group===r?r:0,e.mkdirSync(n(),{recursive:!0,mode:448});const s=t.join(n(),`${r}.json`),c=`${s}.${process.pid}.tmp`;try{e.writeFileSync(c,JSON.stringify(i),{mode:384})}catch(r){if("ENOENT"!==r.code)throw r;e.mkdirSync(n(),{recursive:!0,mode:448}),e.writeFileSync(c,JSON.stringify(i),{mode:384})}return e.renameSync(c,s),{record:i,file:s}}function h(e){const r={...e};return"win32"===process.platform&&(r.windowsHide=!0),r}function g(r,t,n){const s=h(t[n]);if(!i()){const e=t.slice();return e[n]=s,o[r](...e)}u(),"win32"!==process.platform&&(s.detached=!0),t[n]=s;const c=o[r](...t);let d;try{c.pid&&(d=w(c.pid,!0))}catch(e){throw c.kill("SIGKILL"),e}return c.once("close",()=>{if(d)try{const r=p();d.record.group&&[...r.values()].some(e=>e.group===d.record.group)||e.rmSync(d.file,{force:!0})}catch{}}),c}function m(r,o,i){for(const i of e.readdirSync(n())){if(!/^\d+\.json$/.test(i))continue;const s=t.join(n(),i),c=a(s);if(!c||!Number.isInteger(c.pid)||c.pid<=1||"string"!=typeof c.birth)continue;const p=r.get(c.pid);if(d(c,p))o.set(c.pid,p);else if(p){e.rmSync(s,{force:!0});continue}if(c.group===c.pid)for(const e of r.values())e.group===c.group&&o.set(e.pid,e);p||[...r.values()].some(e=>c.group&&e.group===c.group)||e.rmSync(s,{force:!0})}let s;do{s=!1;for(const e of r.values())o.has(e.pid)||i.has(e.pid)||!d(o.get(e.ppid)||{},r.get(e.ppid))||(o.set(e.pid,e),s=!0)}while(s);for(const e of i)o.delete(e);return[...o.values()].filter(e=>d(e,r.get(e.pid)))}module.exports={registerCurrent:function(){u();const r=w(process.pid,i());process.env.TIK_VIDEO_EDITOR_MANAGED="1",process.once("exit",()=>{if(r)try{r.record.group&&[...p().values()].some(e=>e.pid!==process.pid&&e.group===r.record.group)||e.rmSync(r.file,{force:!0}),c()}catch{}}),u()},shutdown:async function(){e.mkdirSync(n(),{recursive:!0,mode:448});const r=t.join(n(),"shutdown.json"),i=p(),f=i.get(process.pid),u=a(r);if(u&&d(u,i.get(u.pid)))throw new Error("CLI shutdown is already in progress");u&&e.rmSync(r,{force:!0}),e.writeFileSync(r,JSON.stringify(f),{flag:"wx",mode:384});const l=new Map,w=new Set,h=new Set,g=new Map,S=new Set([process.pid]);let y=i.get(process.ppid);for(;y&&!S.has(y.pid);)S.add(y.pid),y=i.get(y.ppid);try{if("win32"!==process.platform)for(;;){const e=m(p(),l,S).filter(e=>!g.has(e.pid));if(!e.length)break;for(const r of e)try{process.kill(r.pid,"SIGSTOP"),g.set(r.pid,r)}catch(e){if("ESRCH"!==e.code)throw e}}const e=Date.now()+3e3;let r,t="win32"===process.platform?i:null;do{r=m(t||p(),l,S),t=null;for(const e of r.reverse())if(!w.has(e.pid))try{process.kill(e.pid,"SIGTERM"),w.add(e.pid),g.has(e.pid)&&(process.kill(e.pid,"SIGCONT"),g.delete(e.pid))}catch(e){if("ESRCH"!==e.code)throw e}if(!r.length)break;await s(100)}while(Date.now()<e);if(r.length){const e=Date.now()+3e3;do{r=m(p(),l,S);for(const e of r.reverse())try{if("win32"===process.platform){const r=o.spawnSync("taskkill.exe",["/PID",String(e.pid),"/T","/F"],{encoding:"utf8",windowsHide:!0,timeout:5e3});if(r.error)throw r.error}else process.kill(e.pid,"SIGKILL");w.add(e.pid),h.add(e.pid)}catch(e){if("ESRCH"!==e.code)throw e}if(!r.length)break;await s(100)}while(Date.now()<e)}const n=r.length?m(p(),l,S).map(e=>e.pid):[];return{ok:0===n.length,stopped:[...w],forced:[...h],remaining:n}}finally{for(const e of g.values())try{d(e,p(e.pid).get(e.pid))&&process.kill(e.pid,"SIGCONT")}catch{}e.rmSync(r,{force:!0}),c()}},spawn:(e,r,t)=>g("spawn",[e,r,t],2),fork:(e,r,t)=>g("fork",[e,r,t],2),execFile:(e,r,t,o)=>g("execFile",[e,r,t,o],2),spawnSync:function(e,r,t){return o.spawnSync(e,r,h(t))},listManaged:function(){return e.existsSync(n())?m(p(),new Map,new Set):[]},descendantPids:function(e){const r=new Set;if(!e)return r;const t=p();r.add(e);let o=!0;for(;o;){o=!1;for(const e of t.values())r.has(e.ppid)&&!r.has(e.pid)&&(r.add(e.pid),o=!0)}return r},extraManaged:function(r){const o=r||new Set;return function(){if(!e.existsSync(n()))return[];const r=p(),o=[];for(const i of e.readdirSync(n())){if(!/^\d+\.json$/.test(i))continue;const e=a(t.join(n(),i)),s=e&&r.get(e.pid);e&&d(e,s)&&o.push(e.pid)}return o}().filter(e=>e!==process.pid&&!o.has(e))},beginMigration:function(){u(),e.mkdirSync(n(),{recursive:!0,mode:448});const r=t.join(n(),"migration.json"),o=p(process.pid).get(process.pid);if(!o)throw new Error("Cannot record the migration process");const i=a(r);if(i&&i.pid!==process.pid&&f("migration.json"))throw new Error("CLI storage migration is in progress; retry after it finishes");return e.writeFileSync(r,JSON.stringify(o),{mode:384}),()=>{try{e.rmSync(r,{force:!0})}catch{}}},migrationOwnedByOther:function(){const e=f("migration.json");return!(!e||e.pid===process.pid)},stopPids:async function(e){const r=[...new Set(e)].filter(e=>e&&e!==process.pid&&l(e));for(const e of r)try{process.kill(e,"SIGTERM")}catch{}const t=Date.now()+2e3;for(;Date.now()<t&&r.some(l);)await s(100);for(const e of r)if(l(e))try{process.kill(e,"SIGKILL")}catch{}}};
|
|
@@ -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({})}};
|
|
@@ -0,0 +1 @@
|
|
|
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}}};
|
|
@@ -0,0 +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)||(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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const{spawn:e}=require("./cli/lib/processes");module.exports={writeFrame:function(e,r){const{proc:o,controller:n}=e,t=o.stdin;return new Promise((i,l)=>{let s=!1;const c=e=>{s||(s=!0,t.removeListener("error",a),t.removeListener("close",d),o.removeListener("exit",d),o.removeListener("error",a),n.signal.removeEventListener("abort",u),e?l(e):i())},a=e=>c(e),d=()=>c(new Error("ffmpeg closed during frame upload: "+e.err())),u=()=>c(new Error("export cancelled"));if(t.once("error",a),t.once("close",d),o.once("exit",d),o.once("error",a),n.signal.addEventListener("abort",u,{once:!0}),n.signal.aborted)return u();if(null!==o.exitCode||null!==o.signalCode||t.destroyed||t.writableEnded)return d();try{t.write(r,e=>c(e))}catch(e){c(e)}})},stopProcess:async function(e,r){let o;null===e.exitCode&&null===e.signalCode&&(e.kill(),o=setTimeout(()=>e.kill("SIGKILL"),3e3));try{await r}finally{clearTimeout(o)}},run:function(r,o,n){return new Promise((t,i)=>{if(n?.aborted)return i(new Error("export cancelled"));const l=e(r,o,{stdio:["ignore","ignore","pipe"]});let s,c,a="";const d=()=>{s||=new Error("export cancelled"),l.kill(),c=setTimeout(()=>l.kill("SIGKILL"),3e3)};n?.addEventListener("abort",d,{once:!0}),l.stderr.on("data",e=>{a=(a+e).slice(-2e3)}),l.on("error",e=>{s||=e}),l.once("close",e=>{clearTimeout(c),n?.removeEventListener("abort",d),s||0!==e?i(s||new Error(a||r+" failed")):t()})})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("node:fs"),r=require("node:path"),{pipeline:o}=require("node:stream/promises"),{execFile:t}=require("./cli/lib/processes");function n(e,r=400){return Object.assign(new Error(e),{status:r})}function i(e){return"string"==typeof e&&!/[<>:"/\\|?*\x00-\x1f]/.test(e)&&/\.(mp4|webm)$/i.test(e)&&!/^(con|prn|aux|nul|com[1-9]|lpt[1-9])\./i.test(e)}function s({signal:e,timeoutMs:o=12e4}={}){let i,s;if("win32"===process.platform){i=r.join(process.env.SystemRoot||"C:\\Windows","System32/WindowsPowerShell/v1.0/powershell.exe");const e="\n$ErrorActionPreference = 'Stop'\n[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)\nAdd-Type -AssemblyName System.Windows.Forms\n[System.Windows.Forms.Application]::EnableVisualStyles()\n$owner = New-Object System.Windows.Forms.Form\n$owner.TopMost = $true\n$owner.ShowInTaskbar = $false\n$owner.StartPosition = 'CenterScreen'\n$owner.Size = New-Object System.Drawing.Size(1, 1)\n$owner.Opacity = 0\n$dialog = New-Object System.Windows.Forms.FolderBrowserDialog\n$dialog.Description = '选择成片保存文件夹'\n$dialog.ShowNewFolderButton = $true\ntry {\n # 隐藏启动的进程需要先显示并激活父窗口,选择框才能正常置前。\n $owner.Show()\n $owner.Activate()\n if ($dialog.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {\n [Console]::Write($dialog.SelectedPath)\n }\n} finally { $dialog.Dispose(); $owner.Dispose() }\n";s=["-NoProfile","-STA","-EncodedCommand",Buffer.from(e,"utf16le").toString("base64")]}else if("darwin"===process.platform)i="/usr/bin/osascript",s=["-e",'try\nactivate\nreturn POSIX path of (choose folder with prompt "选择成片保存文件夹")\non error number -128\nreturn ""\nend try'];else{if("linux"!==process.platform)return Promise.reject(n("System folder selection is unavailable on this platform",501));i="zenity",s=["--file-selection","--directory","--title=选择成片保存文件夹"]}return new Promise((r,n)=>{t(i,s,{windowsHide:!0,encoding:"utf8",maxBuffer:65536,signal:e,timeout:o},(e,o)=>{if("linux"!==process.platform||1!==e?.code||e.killed)return e?("AbortError"!==e.name&&(e.browserSave=!0),void n(e)):void r(o.replace(/[\r\n]+$/,"")||null);r(null)})})}module.exports={chooseExportDirectory:s,createExportSaver:function({pickDirectory:t=s}={}){let a=!1;return async function({source:s,stream:l,name:c,signal:d}){if(a)throw n("A save dialog is already open",409);if(!i(c))throw n("Invalid export filename");a=!0;let p,m,w=!1;try{d?.throwIfAborted();const n=await t({signal:d});if(d?.throwIfAborted(),!n)return{saved:!1,cancelled:!0};const i=r.extname(c),a=r.basename(c,i);for(let o=0;;o++){m=r.join(n,o?`${a}_${o}${i}`:c);try{p=await e.promises.open(m,"wx");break}catch(e){if("EEXIST"!==e.code)throw e}}return await o(s?e.createReadStream(s):l,p.createWriteStream(),{signal:d}),w=!0,{saved:!0,name:r.basename(m)}}finally{try{p&&(await p.close(),w||await e.promises.rm(m,{force:!0}))}finally{a=!1}}}},resolveExportSource:async function(o,t){const s=`/projects/${o.id}/exports/`,a="string"==typeof t&&t.startsWith(s)?t.slice(s.length):"default"===o.id&&"string"==typeof t&&t.startsWith("/exports/")?t.slice(9):"";let l;try{l=decodeURIComponent(a)}catch{throw n("Invalid export source")}if(!i(l))throw n("Invalid export source");const c=await e.promises.realpath(o.exportsDir),d=await e.promises.realpath(r.join(c,l));if(r.dirname(d)!==c||!(await e.promises.stat(d)).isFile())throw n("Invalid export source");return d},validVideoName:i};
|
|
@@ -0,0 +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,{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()}});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#7b6cff"/>
|
|
5
|
+
<stop offset="1" stop-color="#4f8cff"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="512" height="512" rx="112" fill="url(#g)"/>
|
|
9
|
+
<!-- play triangle sliced by a cut (the "Cut" in FableCut) -->
|
|
10
|
+
<g fill="#fff">
|
|
11
|
+
<path d="M188 146 L244 176 L244 336 L188 366 Z"/>
|
|
12
|
+
<path d="M268 189 L392 256 L268 323 Z"/>
|
|
13
|
+
</g>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";!function(e){const t={"Thumbnails {loaded}/{total}":"缩略图 {loaded}/{total}","{failed} thumbnails unavailable":"{failed} 张缩略图无法加载","Project not found":"项目不存在","Project could not be loaded":"项目加载失败","Invalid project data":"工程数据格式无效","Invalid project link. Open the project again from its original entry.":"项目链接无效,请从原入口重新打开项目。","Project request failed ({status})":"工程读取请求失败({status})","Project ID: {id}":"项目 ID:{id}","This project is not in the current or legacy storage folder. It may have been moved or deleted. Restore the project folder, then retry.":"当前及旧存储目录中均未找到此工程,可能已被移动或删除。请恢复工程文件夹后重试。","The project file could not be read or the editor service is unavailable. Check the file or service, then retry.":"工程文件无法读取,或编辑器服务暂时不可用。请检查文件或服务后重试。","Error details":"错误详情","Retry loading":"重新加载","Media missing":"媒体丢失","Media unreadable":"媒体无法读取","Check again":"重新检测","The source file was deleted or moved. Restore it to its original location, then retry.":"源文件已被删除或移动,请恢复到原位置后重新检测。","The source file could not be loaded. Check the file or connection, then retry.":"源文件无法加载,请检查文件或网络连接后重新检测。","Snapping on (N)":"吸附已开启(N)","Snapping off (N)":"吸附已关闭(N)","Zoom out (-)":"缩小时间轴(-)","Zoom in (+)":"放大时间轴(+)","Timeline zoom":"时间轴缩放","Insert before {clip}":"插到「{clip}」之前","Insert after {clip}":"插到「{clip}」之后","Move to {track} · {seconds}s":"移到{track} · {seconds}秒","Moves together":"一起移动","Moves together: {tracks}":"同时移动:{tracks}","Adjusted to an available position":"位置受限,已预览可用落点","Drop on a matching track":"请拖入同类型轨道或轨道间隙","Trimmed {seconds}s":"裁掉 {seconds} 秒","Restored {seconds}s":"恢复 {seconds} 秒","Duration {seconds}s":"剩余时长 {seconds} 秒","Following clips move together":"后续片段随裁剪联动","Linked media stays in sync":"关联音视频保持同步","Source or clip limit reached":"已到裁剪边界(素材范围/最短时长/重叠限制)","Clip actions":"片段操作","Timeline actions":"时间轴操作","Selected {count} clips · linked media follows":"已选 {count} 个片段 · 编辑包含关联音视频","Shared gap on enabled tracks":"闭合各启用轨道的公共空隙","No shared gap: {track} contains a clip":"没有公共空隙:{track}此处有片段","Move the playhead inside the clip first":"请先将播放头移到片段内部","Go to clip start":"跳到此片段片头","Zoom to selected clips":"放大所选片段","Split at playhead":"在播放头处分割","Trim start to playhead":"裁片头到播放头(当前片段)","Trim end to playhead":"裁片尾到播放头(当前片段)","Delete selected clips":"删除所选片段","Close shared gap":"闭合公共空隙","Timeline changed. Open the menu again.":"时间轴已更新,请重新打开菜单。","+ New track":"+ 新建轨道","Place here":"放置到此处","Drop media here to create a track":"拖入素材以创建轨道","+ Adjust":"+ 调整层","+ Import":"+ 导入","+ Text":"+ 文字","+click bin":"+单击素材区","+click clip":"+单击片段","+wheel":"+滚轮","AI bg remove":"AI 去背景","Add / remove from selection":"加入/移出选区","Add / remove marker at playhead":"在播放头处添加/移除标记","Add text at the playhead":"在播放头处添加文字","Add an adjustment layer (grades everything below it)":"添加调整图层(影响下方所有图层)","Add at playhead":"添加到播放头位置","Add transition at playhead (in / out by half)":"在播放头处添加转场(按前后半段选择入场/出场)","Adjustment layer":"调整图层",Align:"水平对齐",Animation:"动画",Assets:"素材","Assets panel":"素材面板","Inspector panel":"属性面板","Connection status":"连接状态","Settings button":"设置入口","Project switcher":"项目切换","Layout controls":"布局切换","Monitor toolbar":"预览工具栏","Audio meter":"音量表","Go to start / end":"跳至开头/末尾","Fit timeline":"适应时间轴","Clip {number}":"片段{number}","Visible modules":"显示模块","Hidden modules keep their functions and shortcuts.":"隐藏模块仍保留原有功能和快捷键。","Audio / Time":"音频/时间","Audio Hold — while paused, loop one frame of audio at the playhead. Play/Stop turns it off.":"音频保持:暂停时循环播放播放头处一帧音频。播放/停止会关闭此功能。","Audio hold":"音频保持","Audio {number}":"音频{number}","Video {number}":"视频{number}",Undo:"撤销",Redo:"重做","Undo (Ctrl/Cmd+Z)":"撤销(Ctrl/Cmd+Z)","Redo (Ctrl/Cmd+Shift+Z)":"重做(Ctrl/Cmd+Shift+Z)","Back 1 frame (←)":"后退一帧(←)","Background removal unavailable — couldn't load MediaPipe (offline?). Using chroma key still works.":"去背景不可用:无法加载 MediaPipe,请检查网络。仍可使用色度抠像。","Bg color":"背景色",Blend:"混合模式",Bold:"粗体","Box W/H":"文本框宽/高","Buffering…":"缓冲中…","Cached video frames · {count} compatibility fallbacks · You can switch tabs.":"使用缓存帧 · {count} 个片段使用兼容模式 · 可切换标签页。",Cancel:"取消","Canvas aspect preset":"画布宽高比预设",Channel:"声道","Channel {number}":"声道 {number}","Choose…":"选择…","Clear IN / OUT marker":"清除入点/出点标记","Clear keyframes":"清除关键帧","Click to switch RMS / LUFS / Peak":"单击切换 RMS/LUFS/峰值","Click: keyframe graph · Ctrl-click: reset":"单击:关键帧曲线 · Ctrl+单击:重置","Clip — {kind}":"片段 — {kind}",Close:"关闭","Close gap":"闭合空隙","Close gap at playhead (enabled tracks)":"闭合播放头处的空隙(已启用轨道)","Close gap at playhead (⇧G)":"闭合播放头处的空隙(⇧G)","Close graph":"关闭曲线","Closed {seconds}s gap":"已闭合 {seconds} 秒空隙",Collapse:"折叠",Color:"颜色","Compact tracks (no thumbnails)":"紧凑轨道(无缩略图)",Content:"内容","Couldn't create project: ":"无法创建项目:","Couldn't import — unsupported or unreadable file type":"无法导入:文件类型不受支持或无法读取","Couldn't play ":"无法播放:","Create a project":"创建项目","Crop L/R %":"左右裁剪 %","Crop T/B %":"上下裁剪 %","Ctrl-click to reset":"Ctrl+单击重置","Custom · {fps} fps":"自定义 · {fps} fps","Custom · {width}×{height}":"自定义 · {width}×{height}","Cycle RMS → LUFS → Peak":"循环切换 RMS → LUFS → 峰值",Delete:"删除","Delete folder (keeps media)":"删除文件夹(保留素材)","Delete selected (Del)":"删除所选片段(Del)","Delete selected clip(s), or clear focused transition":"删除所选片段,或清除当前转场",Direction:"文字方向","Disable chroma key":"关闭色度抠像","Disable gradient":"关闭渐变","Disable track":"禁用轨道","Drag empty area":"拖动空白区域","Drag to adjust duration":"拖动调整时长","Drag to resize · double-click to reset":"拖动调整大小 · 双击重置","Drop files into":"将文件放入","Drop here to move to root":"拖到此处移至根目录","Drop video, audio or images here":"将视频、音频或图片拖到此处",Elements:"装饰素材","Enable track":"启用轨道","Encoding…":"编码中…",Expand:"展开",Export:"导出","Export preview":"成片预览","Export progress":"导出进度","Play exported video":"播放成片","Video duration {seconds}s":"成片时长 {seconds} 秒","Preparing video…":"正在准备视频…","Rendering video…":"正在合成视频…","Rendering clip {current}/{total}":"正在合成第 {current}/{total} 个片段"," · About {seconds}s remaining":" · 预计剩余 {seconds} 秒","Finalizing video…":"正在封装视频,即将完成…","Video complete. Your file is ready.":"合成完成,文件已就绪",Save:"保存","Saving…":"保存中…","Choose a folder to save your video.":"请选择成片保存文件夹","Saved: {name}":"已保存:{name}","Save cancelled. Click Save to choose a folder again.":"已取消保存,可点击「保存」重新选择文件夹","Unable to save. Click Save to try again.":"保存失败,请点击「保存」重试","Click Save to choose a location in your browser.":"请点击「保存」,通过浏览器选择保存位置","Check your browser downloads to confirm the file was saved.":"已请求浏览器下载,请在下载列表中确认保存结果","Retry export":"重试导出","Cancel this export?":"确定取消导出?","The video is still exporting. Cancelling will stop this task.":"视频仍在导出中,取消后将停止本次任务。","Continue export":"继续导出","Confirm cancellation":"确定取消","Quick export":"快捷合成","Quick export in progress. You can switch chats.":"正在后台快捷合成,可以切换聊天","Background recording failed. You can record in this page while it stays open.":"后台录制失败,可在当前页面录制。录制期间请保持页面在前台。","Record in this page":"在当前页面录制","Recording failed. Keep this page open to try again.":"当前页面录制失败,可重新录制。录制期间请保持页面在前台。","Export interrupted. Try quick export to export again.":"合成任务中断,可尝试快捷合成重新导出","Quick export in progress. Keep this page open and in the foreground.":"快捷合成中,请保持当前页面,不要关闭窗口或切换页面","The exported file is unavailable. Please retry.":"未获取到成片文件,请重试。","Unable to check export progress. Please retry.":"暂时无法查询导出进度,请重试。","Unable to preview. Open the downloaded video to watch it.":"暂时无法预览,请打开已下载的视频观看。","Export failed: ":"导出失败:","Exporting…":"导出中…","Exporting… {percent}%":"导出中… {percent}%","Tik — Video Editor":"Tik — 视频编辑器",Family:"字体","CLI Fast (ffmpeg)":"CLI 快速(ffmpeg)","CLI Optimized (ffmpeg + frame cache)":"CLI 优化(ffmpeg + 帧缓存)","Fast (ffmpeg)":"快速(ffmpeg)","Filter / Color":"滤镜/调色","Find gap":"查找空隙","Find gap (G)":"查找空隙(G)","Find next shared gap (wraps; respects IN/OUT)":"查找下一处共同空隙(循环查找,遵循入点/出点)",Fit:"适应","Flip H":"水平翻转","Flip V":"垂直翻转",Font:"字体","Font size":"字号","Forward 1 frame (→)":"前进一帧(→)","Queued for export…":"正在排队,等待导出…","Starting background export…":"正在启动后台导出…","CLI renders in a separate process. Closing this page does not stop export.":"CLI 在独立进程中渲染,关闭此页面后仍继续导出。","CLI pre-decodes and caches frames in a separate process. You can close this page.":"CLI 在独立进程中预解码并缓存视频帧,关闭此页面后仍继续导出。","CLI renders frames in a separate process with ffmpeg. You can close this page.":"CLI 在独立进程中逐帧渲染并交由 ffmpeg 编码,关闭此页面后仍继续导出。","Frame-accurate ffmpeg encode. Keeps rendering if you switch tabs.":"使用 ffmpeg 逐帧编码,切换标签页后仍继续渲染。","Glow color":"发光颜色","Glow uses the text color":"发光使用文字颜色","Go to end (End)":"跳到结尾(End)","Go to previous / next keyframe":"跳到上一/下一关键帧","Go to start (Home)":"跳到开头(Home)","Google font":"Google 字体","Google fonts":"Google 字体","Gradient bottom color":"渐变底部颜色","Height in px (0 = no box — hug content)":"高度(像素);0 表示按内容自适应","IN and OUT must be at different times":"入点与出点不能相同","IN must be before OUT":"入点必须早于出点","Import media files":"导入素材文件","Imported {added}, skipped {skipped}":"已导入 {added} 个,跳过 {skipped} 个","Imported, but cannot replace {kind} with {replacement}":"已导入,但无法用{replacement}替换{kind}片段",In:"入场",Inspector:"属性","Interface language":"界面语言",Italic:"斜体","Jump to start / end":"跳到开头/结尾","Key color":"抠像颜色","Keyboard shortcuts":"键盘快捷键","Keying / Cut-out":"抠像/去背景",L:"L",Language:"语言","Large tracks":"大轨道",Layout:"布局",Left:"左声道","Length (s)":"时长(秒)","Library fonts":"本地字体库",Limit:"限区播放","Limit (toolbar)":"限区播放(工具栏)","Limit playback to IN/OUT (Home/End jump to markers; playhead outside = override)":"限制在入点/出点之间播放(Home/End 跳到标记;播放头在范围外时不限制)","Link timeline and Project bin selection":"联动时间线与项目素材选择","Linked {count} audio channels from {name}":"已关联 {name} 的 {count} 个声道",Load:"加载",'Loading Google font "{name}"…':"正在加载 Google 字体“{name}”…",M:"M","Marquee-select clips":"框选片段","Max size":"最大字号","Media replaced":"已替换素材","Media replaced — trimmed to fit shorter source":"已替换素材,并按较短素材裁剪","Media used by this project — right-click for New folder":"本项目使用的素材,右键可新建文件夹","MediaRecorder is not supported in this browser.":"当前浏览器不支持 MediaRecorder。","Medium tracks":"中等轨道","Missing media":"素材缺失","Mixing audio…":"混音中…","Mode: {mode} — click to switch":"模式:{mode} — 单击切换","Motion FX":"运动特效","Move playhead over the selected clip":"请将播放头移到所选片段上",Name:"名称","Needs the server + ffmpeg and ffprobe on PATH.":"需要运行本地服务,且 PATH 中包含 ffmpeg 和 ffprobe。","Needs the server + ffmpeg on PATH.":"需要运行本地服务,且 PATH 中包含 ffmpeg。","New folder":"新建文件夹","New subfolder":"新建子文件夹","No assets yet.":"暂无素材。","No clips on the timeline use this media":"时间线上没有使用此素材的片段","No enabled tracks":"没有已启用的轨道","No gap on {track} — disable the track or move the playhead":"轨道 {track} 没有空隙,请禁用该轨道或移动播放头","No gaps found":"未找到空隙","No gaps in IN/OUT range":"入点/出点范围内没有空隙","No keyframes":"没有关键帧","No next keyframe":"没有下一关键帧","No other {kind} in bin":"素材区没有其他{kind}素材","No previous keyframe":"没有上一关键帧","Nothing to close at playhead":"播放头处没有可闭合的空隙","Nothing to split at IN/OUT":"入点/出点处没有可分割的片段","Nothing to trim":"没有可裁剪的片段","OUT must be after IN":"出点必须晚于入点","Open project":"打开项目","Optimized (ffmpeg + frame cache)":"优化(ffmpeg + 帧缓存)","Optimized export complete: {seconds}s · {hits} cache hits":"优化导出完成:{seconds} 秒 · 命中缓存 {hits} 次","Optimized export failed: ":"优化导出失败:",Out:"出场","Overlapping audio":"重叠音频","Overlay art from library/elements":"来自 library/elements 的叠加素材","Pick a style — hover to preview it live":"选择样式,悬停可实时预览","Play / Pause (Space)":"播放/暂停(空格)","Play / pause":"播放/暂停","Play stops at OUT; Home/End use IN/OUT":"播放到出点停止;Home/End 跳到入点/出点","Plays the timeline once and records it. Keep the tab focused.":"播放并录制一遍时间线,请保持当前标签页处于前台。","Position X":"位置 X","Position Y":"位置 Y","Pre-decodes video frames and caches them. First export needs preparation; you can switch tabs.":"预解码并缓存视频帧,首次导出需要准备,可切换标签页。","Pre-decoding video and caching frames. You can switch tabs.":"正在预解码视频并缓存帧,可切换标签页。","Preferences are stored in this browser only.":"偏好设置仅保存在当前浏览器中。","Preparing source frames…":"准备素材帧…","Preparing the next part of your video. Export will continue automatically.":"正在准备下一段视频,完成后将自动继续导出。","Preparing video frames…":"准备视频帧…",Preset:"预设",Preview:"预览","Preview speed — click to cycle 1×/1.5×/2×/4×. While playing: L faster · J slower · K play/pause and reset to 1×. Preview only, not export.":"预览速度:单击切换 1×/1.5×/2×/4×。播放时 L 加速、J 减速、K 播放/暂停并恢复 1×。仅影响预览。","Program Monitor":"预览监视器",Project:"项目",Media:"媒体","Project frame rate":"项目帧率","Project name":"项目名称","Project was updated externally — your last change may need redoing.":"项目已被外部更新,可能需要重新应用刚才的修改。","Random style":"随机样式","Realtime (in-browser)":"实时(浏览器内录制)","Remove (and its clips)":"移除素材及其片段","Rendering frames → ffmpeg. You can switch tabs; export continues.":"正在渲染并交由 ffmpeg 编码,切换标签页后仍继续导出。","Rendering your sequence in real time. Keep this tab focused.":"正在实时渲染时间线,请保持当前标签页处于前台。","Rendering… {percent}%":"渲染中… {percent}%","Replace this clip's media — keeps position, trim, keyframes and effects":"替换片段素材,保留位置、裁剪、关键帧和特效","Reset monitor view to fit stage":"重置预览缩放以适应窗口","Restore default layout":"恢复默认布局",Right:"右声道",S:"S",SVG:"SVG","Scale to fit":"缩放以适应","Select a clip first":"请先选择片段","Select a clip to edit its":"选择片段以编辑","Select a clip to zoom to":"请选择要缩放显示的片段","Select all / deselect":"全选/取消选择","Selecting a timeline clip highlights its media in Project, and the reverse. Off by default.":"选择时间线片段时高亮项目中的对应素材,反之亦然。默认关闭。",Selection:"选择","Set IN / OUT work-area marker":"设置工作区入点/出点标记","Set IN and OUT markers first (I / O)":"请先设置入点和出点标记(I / O)","Set an IN or OUT marker first (I / O)":"请先设置入点或出点标记(I / O)","Set keyframe at playhead":"在播放头处设置关键帧",Settings:"设置","Show / hide keyframe graph":"显示/隐藏关键帧曲线",Shuffle:"随机","Shuttle preview —":"变速预览 —",Snap:"吸附","Sound FX":"音效","Sound effects from library/sfx":"来自 library/sfx 的音效",Source:"素材源","Split at playhead (S)":"在播放头处分割(S)","Split clip(s) at playhead":"在播放头处分割片段","Split clips at IN/OUT markers":"在入点/出点标记处分割片段","Start (s)":"开始(秒)","Step 1 frame (⇧ = 1 second)":"前进/后退一帧(⇧ = 一秒)","Stroke col.":"描边颜色",System:"系统字体",Text:"文本","Text style":"文本样式","Then drag media onto the timeline below.":"然后将素材拖到下方时间线上。","Timeline is empty — add some clips first.":"时间线为空,请先添加片段。","Timeline track height":"时间线轨道高度","Toggle safe-area guides":"显示/隐藏安全区域参考线","Toggle snapping":"开关吸附","Toggle snapping (N)":"开关吸附(N)","Track {tracks} is disabled and will be omitted from the export.":"轨道 {tracks} 已禁用,不会包含在导出中。","Tracks {tracks} are disabled and will be omitted from the export.":"轨道 {tracks} 已禁用,不会包含在导出中。",Transform:"变换",Transition:"转场",Trim:"裁剪","Trim clips to IN/OUT (remove heads/tails)":"按入点/出点裁剪片段(移除首尾)","Trim clips to IN/OUT (⇧T)":"按入点/出点裁剪片段(⇧T)","Trim selected in / out to playhead":"将所选片段的入点/出点裁至播放头","Type any Google Font name…":"输入 Google 字体名称…","Undo / redo":"撤销/重做","Unsupported file type: ":"不支持的文件类型:","Untitled Project":"未命名项目",Uppercase:"全部大写","V-align":"垂直对齐","Vector animations from library/svg":"来自 library/svg 的矢量动画","Video preview: {name}: {error}":"视频预览:{name}:{error}",Weight:"字重","Width in px (0 = no box — hug content)":"宽度(像素);0 表示按内容自适应",Zoom:"缩放","Zoom timeline":"缩放时间线","Zoom timeline at cursor":"以光标为中心缩放时间线","Zoom timeline to fit":"缩放以显示完整时间线","Zoom timeline to fit (⇧Z)":"缩放以显示完整时间线(⇧Z)","Zoom to IN–OUT work area":"缩放以显示入点至出点的工作区","Zoom to selected clip(s)":"缩放以显示所选片段","faster /":"加速/",flat:"纯色","loading timed out":"加载超时",off:"关闭","or click":"或单击","play/pause + reset to 1×":"播放/暂停并恢复 1×","slower (1× · 1.5× · 2× · 4×);":"减速(1× · 1.5× · 2× · 4×);","transform, effects & audio.":"变换、特效与音频。","{count} clips selected — drag moves them together, Del deletes all. Fields below edit the primary (white-outlined) clip.":"已选 {count} 个片段,拖动可一起移动,Del 删除全部。下方属性仅编辑主选片段(白色边框)。","{name} has fewer channels — removed {count} linked audio clips":"{name} 的声道更少,已移除 {count} 个关联音频片段","{name}: added {tracks} audio tracks and linked {channels} channels":"{name}:已添加 {tracks} 条音轨并关联 {channels} 个声道","{name}: {count} audio channels, only {max} channels supported — extra channels dropped":"{name}:共 {count} 个声道,单个素材最多支持 {max} 个声道,已舍弃多余声道","— this list live-updates.":"— 列表会自动更新。","⊡ Layout":"⊡ 布局","▦ Safe":"▦ 安全区","⚪ local session":"⚪ 本地会话","✂ Split":"✂ 分割","⬇ Export":"⬇ 导出","+ Project":"+ 项目","📂 Browse file…":"📂 浏览文件…","🟢 connected":"🟢 已连接",video:"视频",audio:"音频",image:"图片",svg:"矢量动画",text:"文字",adjust:"调整层",none:"无",fade:"淡入淡出","slide-left":"向左滑入","slide-right":"向右滑入","slide-up":"向上滑入","slide-down":"向下滑入",zoom:"缩放",wipe:"向左擦除","wipe-right":"向右擦除","wipe-up":"向上擦除","wipe-down":"向下擦除",iris:"圆形展开",spin:"旋转",blur:"模糊",whip:"甩镜",glitch:"故障",pop:"弹出",typewriter:"打字机","word-pop":"逐词弹出","word-slide":"逐词滑入",karaoke:"卡拉 OK","letter-pop":"逐字弹出",wave:"波浪",bounce:"弹跳",shake:"抖动","clip-reveal":"裁切揭示","zoom-in":"放大入场","font-cut":"字体切换","rise-mask":"遮罩上升",normal:"正常",multiply:"正片叠底",screen:"滤色",overlay:"叠加",lighter:"加亮","soft-light":"柔光","hard-light":"强光","color-dodge":"颜色减淡",darken:"变暗",lighten:"变亮",difference:"差值",contain:"完整显示",cover:"填满画布",stretch:"拉伸",left:"左对齐",center:"居中",right:"右对齐",justify:"两端对齐",top:"顶部",middle:"中部",bottom:"底部",auto:"自动",ltr:"从左到右",rtl:"从右到左",cinematic:"电影感","teal-orange":"青橙",noir:"黑白电影",vintage:"复古",faded:"褪色",warm:"暖色",cold:"冷色",dreamy:"梦幻",retro:"怀旧","bw-soft":"柔和黑白",cyberpunk:"赛博朋克",sunset:"日落",midnight:"午夜",Plain:"简约",Impact:"冲击",Elegant:"优雅","Kinetic cut":"动态切换",Neon:"霓虹",Handwritten:"手写","Serif drop":"衬线落入",Subtitle:"字幕","Bold rise":"粗体上升",Luxury:"典雅",Scale:"缩放",Rotation:"旋转",Opacity:"不透明度",Brightness:"亮度",Contrast:"对比度",Saturation:"饱和度",Hue:"色相",Temperature:"色温",Tint:"色调",Blur:"模糊",Grayscale:"灰度",Sepia:"褐色",Invert:"反相",Vignette:"暗角",CornerRadius:"圆角",Shake:"抖动",ShakeSpeed:"抖动速度",RgbSplit:"色差分离",Grain:"颗粒",ChromaTolerance:"抠像容差",ChromaSoftness:"边缘柔化",Volume:"音量",Speed:"速度",LetterSpacing:"字距",LineHeight:"行距",StrokeWidth:"描边宽度",BgOpacity:"背景不透明度",TextShadow:"文字阴影",Glow:"发光",WordRate:"逐词间隔",FontSize:"字号","Pos X":"位置 X","Pos Y":"位置 Y",Bright:"亮度",Sat:"饱和度",Gray:"灰度",Temp:"色温",Radius:"圆角",RGB:"RGB",Size:"字号",Track:"字距","9:16 · Reel 1080×1920":"9:16 · 竖屏 1080×1920","4:5 · IG 1080×1350":"4:5 · 社交媒体 1080×1350"};let o="zh-CN";function a(e){return"en"===e?"en":"zh-CN"}function i(e,a={}){const i="zh-CN"===o&&Object.hasOwn(t,e)?t[e]:e;return String(i).replace(/\{(\w+)\}/g,(e,t)=>Object.hasOwn(a,t)?String(a[t]):e)}const r={text:i,normalize:a,setLanguage:function(e){o=a(e)},apply:function(e){e.documentElement.lang=o;for(const t of e.querySelectorAll("[data-i18n]"))t.textContent=i(t.getAttribute("data-i18n"));for(const t of["title","aria-label","placeholder"])for(const o of e.querySelectorAll(`[data-i18n-${t}]`))o.setAttribute(t,i(o.getAttribute(`data-i18n-${t}`)))},messages:t,get language(){return o}};"undefined"!=typeof module&&module.exports?module.exports=r:e.FableCutI18n=r}(globalThis);
|
|
Binary file
|
|
Binary file
|