@bylqwjc/media-editor-server 1.0.22 → 1.0.24

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/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@media-editor/core",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "媒体编辑器框架无关内核:状态/时间轴/ffmpeg 参数/几何/标注渲染/VideoEditorEngine/图片编辑器纯配置工厂(零 React)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +1 @@
1
- "use strict";var a=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p=Object.getOwnPropertyNames,d=Object.prototype.hasOwnProperty,m=(e,t)=>{for(var r in t)a(e,r,{get:t[r],enumerable:!0})},b=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of p(t))!d.call(e,n)&&n!==r&&a(e,n,{get:()=>t[n],enumerable:!(o=c(t,n))||o.enumerable});return e},g=e=>b(a({},"__esModule",{value:!0}),e),f={};function u(e){return Array.isArray(e.files)?e.files:e.files&&"object"==typeof e.files?Object.values(e.files).flat():[]}function y(e,t){const r=[];for(let o=0;;o++){const n=e.find(e=>e.fieldname===`${t}_${o}`);if(!n)break;r.push({type:n.mimetype,bytes:n.buffer})}return r}function i(e,t){if(t.ok)return e.setHeader("Content-Type",t.contentType),e.setHeader("Content-Disposition",`attachment; filename="${t.filename}"`),e.setHeader("Cache-Control","no-store"),void e.send(t.bytes);e.status(t.status).json({error:t.error,message:t.message})}function v(e){return async(t,r)=>{const o=u(t),n=o.find(e=>"overlay"===e.fieldname);if(n&&"image/png"!==n.mimetype)return void i(r,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const s={videos:y(o,"video"),audios:y(o,"audio"),overlay:n?.buffer,editStateRaw:"string"==typeof t.body?.editState?t.body.editState:""};i(r,await e.handle(s))}}function O(e){return async(t,r)=>{const o=u(t).find(e=>"file"===e.fieldname),n=t.body?.time;i(r,o&&"string"==typeof n?await e.handle({type:o.mimetype,bytes:o.buffer},Number(n)):{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}m(f,{expressPoster:()=>O,expressVideoExport:()=>v}),module.exports=g(f);
1
+ "use strict";var __defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,__export=(e,t)=>{for(var r in t)__defProp(e,r,{get:t[r],enumerable:!0})},__copyProps=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of __getOwnPropNames(t))__hasOwnProp.call(e,s)||s===r||__defProp(e,s,{get:()=>t[s],enumerable:!(o=__getOwnPropDesc(t,s))||o.enumerable});return e},__toCommonJS=e=>__copyProps(__defProp({},"__esModule",{value:!0}),e),express_exports={};function filesArray(e){return Array.isArray(e.files)?e.files:e.files&&"object"==typeof e.files?Object.values(e.files).flat():[]}function collect(e,t){const r=[];for(let o=0;;o++){const s=e.find(e=>e.fieldname===`${t}_${o}`);if(!s)break;r.push({type:s.mimetype,bytes:s.buffer})}return r}function reply(e,t){if(t.ok)return e.setHeader("Content-Type",t.contentType),e.setHeader("Content-Disposition",`attachment; filename="${t.filename}"`),e.setHeader("Cache-Control","no-store"),void e.send(t.bytes);e.status(t.status).json({error:t.error,message:t.message})}function expressVideoExport(e){return async(t,r)=>{const o=filesArray(t),s=o.find(e=>"overlay"===e.fieldname);if(s&&"image/png"!==s.mimetype)return void reply(r,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const n=collect(o,"textOverlay");if(n.some(e=>"image/png"!==e.type))return void reply(r,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay must be PNG"});const i=o.find(e=>"videoCover"===e.fieldname);if(i&&!/^image\/(?:png|jpe?g)$/.test(i.mimetype))return void reply(r,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const a={videos:collect(o,"video"),audios:collect(o,"audio"),overlay:s?.buffer,videoCover:i?.buffer,textOverlays:n.map(e=>e.bytes),editStateRaw:"string"==typeof t.body?.editState?t.body.editState:""};reply(r,await e.handle(a))}}function expressPoster(e){return async(t,r)=>{const o=filesArray(t).find(e=>"file"===e.fieldname),s=t.body?.time;reply(r,o&&"string"==typeof s?await e.handle({type:o.mimetype,bytes:o.buffer},Number(s)):{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}__export(express_exports,{expressPoster:()=>expressPoster,expressVideoExport:()=>expressVideoExport}),module.exports=__toCommonJS(express_exports);
@@ -1,4 +1,4 @@
1
- import { P as PosterHandler, V as VideoExportHandler } from '../handler-DL4Inp7o.js';
1
+ import { P as PosterHandler, V as VideoExportHandler } from '../handler-BEalyBz9.js';
2
2
 
3
3
  /**
4
4
  * Express 适配器。用法:
@@ -1 +1 @@
1
- function e(e){return Array.isArray(e.files)?e.files:e.files&&"object"==typeof e.files?Object.values(e.files).flat():[]}function t(e,t){const r=[];for(let s=0;;s++){const o=e.find(e=>e.fieldname===`${t}_${s}`);if(!o)break;r.push({type:o.mimetype,bytes:o.buffer})}return r}function r(e,t){if(t.ok)return e.setHeader("Content-Type",t.contentType),e.setHeader("Content-Disposition",`attachment; filename="${t.filename}"`),e.setHeader("Cache-Control","no-store"),void e.send(t.bytes);e.status(t.status).json({error:t.error,message:t.message})}function s(s){return async(o,i)=>{const a=e(o),n=a.find(e=>"overlay"===e.fieldname),f=a.find(e=>"videoCover"===e.fieldname),d=t(a,"textOverlay");if(n&&"image/png"!==n.mimetype)return void r(i,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});if(d.some(e=>"image/png"!==e.type))return void r(i,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay 必须是 PNG"});if(f&&!/^image\/(?:png|jpe?g)$/.test(f.mimetype))return void r(i,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPG"});const m={videos:t(a,"video"),audios:t(a,"audio"),overlay:n?.buffer,videoCover:f?.buffer,textOverlays:d.map(e=>e.bytes),editStateRaw:"string"==typeof o.body?.editState?o.body.editState:""};r(i,await s.handle(m))}}function o(t){return async(s,o)=>{const i=e(s).find(e=>"file"===e.fieldname),a=s.body?.time;r(o,i&&"string"==typeof a?await t.handle({type:i.mimetype,bytes:i.buffer},Number(a)):{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}export{o as expressPoster,s as expressVideoExport};
1
+ function e(e){return Array.isArray(e.files)?e.files:e.files&&"object"==typeof e.files?Object.values(e.files).flat():[]}function t(e,t){const r=[];for(let s=0;;s++){const o=e.find(e=>e.fieldname===`${t}_${s}`);if(!o)break;r.push({type:o.mimetype,bytes:o.buffer})}return r}function r(e,t){if(t.ok)return e.setHeader("Content-Type",t.contentType),e.setHeader("Content-Disposition",`attachment; filename="${t.filename}"`),e.setHeader("Cache-Control","no-store"),void e.send(t.bytes);e.status(t.status).json({error:t.error,message:t.message})}function s(s){return async(o,i)=>{const n=e(o),a=n.find(e=>"overlay"===e.fieldname);if(a&&"image/png"!==a.mimetype)return void r(i,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const f=t(n,"textOverlay");if(f.some(e=>"image/png"!==e.type))return void r(i,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay must be PNG"});const d=n.find(e=>"videoCover"===e.fieldname);if(d&&!/^image\/(?:png|jpe?g)$/.test(d.mimetype))return void r(i,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const m={videos:t(n,"video"),audios:t(n,"audio"),overlay:a?.buffer,videoCover:d?.buffer,textOverlays:f.map(e=>e.bytes),editStateRaw:"string"==typeof o.body?.editState?o.body.editState:""};r(i,await s.handle(m))}}function o(t){return async(s,o)=>{const i=e(s).find(e=>"file"===e.fieldname),n=s.body?.time;r(o,i&&"string"==typeof n?await t.handle({type:i.mimetype,bytes:i.buffer},Number(n)):{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}export{o as expressPoster,s as expressVideoExport};
@@ -1 +1 @@
1
- "use strict";var c=Object.defineProperty,w=Object.getOwnPropertyDescriptor,B=Object.getOwnPropertyNames,h=Object.prototype.hasOwnProperty,v=(e,t)=>{for(var r in t)c(e,r,{get:t[r],enumerable:!0})},D=(e,t,r,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let o of B(t))!h.call(e,o)&&o!==r&&c(e,o,{get:()=>t[o],enumerable:!(a=w(t,o))||a.enumerable});return e},C=e=>D(c({},"__esModule",{value:!0}),e),g={};v(g,{toWebPosterHandler:()=>O,toWebVideoHandler:()=>E}),module.exports=C(g);var m=16;function f(e){return"object"==typeof e&&null!==e&&"string"==typeof e.type&&"function"==typeof e.arrayBuffer}async function y(e){return{type:e.type,bytes:Buffer.from(await e.arrayBuffer())}}function s(e){return e.ok?new Response(new Uint8Array(e.bytes),{status:200,headers:{"Content-Type":e.contentType,"Content-Disposition":`attachment; filename="${e.filename}"`,"Cache-Control":"no-store"}}):new Response(JSON.stringify({error:e.error,message:e.message}),{status:e.status,headers:{"Content-Type":"application/json"}})}function E(e){return async t=>{let r;try{r=await t.formData()}catch{return s({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const a=[];for(let e=0;e<m;e++){const t=r.get(`video_${e}`);if(!f(t))break;a.push(await y(t))}const o=[];for(let e=0;e<m;e++){const t=r.get(`audio_${e}`);if(!f(t))break;o.push(await y(t))}const n=r.get("editState"),i=r.get("overlay"),u=f(i)?i:void 0,c=u?Buffer.from(await u.arrayBuffer()):void 0;if(u&&"image/png"!==u.type)return s({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const p={videos:a,audios:o,overlay:c,editStateRaw:"string"==typeof n?n:""};return s(await e.handle(p))}}function O(e){return async t=>{let r;try{r=await t.formData()}catch{return s({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const a=r.get("file"),o=r.get("time");return f(a)&&"string"==typeof o?s(await e.handle(await y(a),Number(o))):s({ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}
1
+ "use strict";var __defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,__export=(e,t)=>{for(var o in t)__defProp(e,o,{get:t[o],enumerable:!0})},__copyProps=(e,t,o,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let a of __getOwnPropNames(t))__hasOwnProp.call(e,a)||a===o||__defProp(e,a,{get:()=>t[a],enumerable:!(r=__getOwnPropDesc(t,a))||r.enumerable});return e},__toCommonJS=e=>__copyProps(__defProp({},"__esModule",{value:!0}),e),web_exports={};__export(web_exports,{toWebPosterHandler:()=>toWebPosterHandler,toWebVideoHandler:()=>toWebVideoHandler}),module.exports=__toCommonJS(web_exports);var MAX_SLOTS=16;function isUploadFormFile(e){return"object"==typeof e&&null!==e&&"string"==typeof e.type&&"function"==typeof e.arrayBuffer}async function fileToUpload(e){return{type:e.type,bytes:Buffer.from(await e.arrayBuffer())}}function toResponse(e){return e.ok?new Response(new Uint8Array(e.bytes),{status:200,headers:{"Content-Type":e.contentType,"Content-Disposition":`attachment; filename="${e.filename}"`,"Cache-Control":"no-store"}}):new Response(JSON.stringify({error:e.error,message:e.message}),{status:e.status,headers:{"Content-Type":"application/json"}})}function toWebVideoHandler(e){return async t=>{let o;try{o=await t.formData()}catch{return toResponse({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const r=[];for(let e=0;e<MAX_SLOTS;e++){const t=o.get(`video_${e}`);if(!isUploadFormFile(t))break;r.push(await fileToUpload(t))}const a=[];for(let e=0;e<MAX_SLOTS;e++){const t=o.get(`audio_${e}`);if(!isUploadFormFile(t))break;a.push(await fileToUpload(t))}const s=o.get("editState"),n=o.get("overlay"),i=isUploadFormFile(n)?n:void 0,p=i?Buffer.from(await i.arrayBuffer()):void 0;if(i&&"image/png"!==i.type)return toResponse({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const f=o.get("videoCover"),l=isUploadFormFile(f)?f:void 0,u=l?Buffer.from(await l.arrayBuffer()):void 0;if(l&&!/^image\/(?:png|jpe?g)$/.test(l.type))return toResponse({ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const _=[];for(let e=0;e<MAX_SLOTS;e++){const t=o.get(`textOverlay_${e}`);if(!isUploadFormFile(t))break;if("image/png"!==t.type)return toResponse({ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay 必须是 PNG"});_.push(Buffer.from(await t.arrayBuffer()))}const d={videos:r,audios:a,overlay:p,videoCover:u,textOverlays:_,editStateRaw:"string"==typeof s?s:""};return toResponse(await e.handle(d))}}function toWebPosterHandler(e){return async t=>{let o;try{o=await t.formData()}catch{return toResponse({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const r=o.get("file"),a=o.get("time");return isUploadFormFile(r)&&"string"==typeof a?toResponse(await e.handle(await fileToUpload(r),Number(a))):toResponse({ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}
@@ -1,4 +1,4 @@
1
- import { P as PosterHandler, V as VideoExportHandler } from '../handler-DL4Inp7o.js';
1
+ import { P as PosterHandler, V as VideoExportHandler } from '../handler-BEalyBz9.js';
2
2
 
3
3
  /**
4
4
  * Web 标准适配器(Next.js App Router 路由 / 任何接收 Request 返回 Response 的运行时)。
@@ -1 +1 @@
1
- function e(e){return"object"==typeof e&&null!==e&&"string"==typeof e.type&&"function"==typeof e.arrayBuffer}async function t(e){return{type:e.type,bytes:Buffer.from(await e.arrayBuffer())}}function r(e){return e.ok?new Response(new Uint8Array(e.bytes),{status:200,headers:{"Content-Type":e.contentType,"Content-Disposition":`attachment; filename="${e.filename}"`,"Cache-Control":"no-store"}}):new Response(JSON.stringify({error:e.error,message:e.message}),{status:e.status,headers:{"Content-Type":"application/json"}})}function a(a){return async o=>{let s;try{s=await o.formData()}catch{return r({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const n=[];for(let r=0;r<16;r++){const a=s.get(`video_${r}`);if(!e(a))break;n.push(await t(a))}const i=[];for(let r=0;r<16;r++){const a=s.get(`audio_${r}`);if(!e(a))break;i.push(await t(a))}const f=s.get("editState"),u=s.get("overlay"),y=s.get("videoCover"),g=e(u)?u:void 0,p=e(y)?y:void 0,c=g?Buffer.from(await g.arrayBuffer()):void 0,l=p?Buffer.from(await p.arrayBuffer()):void 0,m=[];for(let t=0;t<16;t++){const a=s.get(`textOverlay_${t}`);if(!e(a))break;if("image/png"!==a.type)return r({ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay 必须是 PNG"});m.push(Buffer.from(await a.arrayBuffer()))}if(g&&"image/png"!==g.type)return r({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});if(p&&!/^image\/(?:png|jpe?g)$/.test(p.type))return r({ok:!1,status:415,error:"BAD_COVER",message:"videoCover 必须是 PNG 或 JPG"});const d={videos:n,audios:i,overlay:c,videoCover:l,textOverlays:m,editStateRaw:"string"==typeof f?f:""};return r(await a.handle(d))}}function o(a){return async o=>{let s;try{s=await o.formData()}catch{return r({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const n=s.get("file"),i=s.get("time");return e(n)&&"string"==typeof i?r(await a.handle(await t(n),Number(i))):r({ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}export{o as toWebPosterHandler,a as toWebVideoHandler};
1
+ function e(e){return"object"==typeof e&&null!==e&&"string"==typeof e.type&&"function"==typeof e.arrayBuffer}async function t(e){return{type:e.type,bytes:Buffer.from(await e.arrayBuffer())}}function r(e){return e.ok?new Response(new Uint8Array(e.bytes),{status:200,headers:{"Content-Type":e.contentType,"Content-Disposition":`attachment; filename="${e.filename}"`,"Cache-Control":"no-store"}}):new Response(JSON.stringify({error:e.error,message:e.message}),{status:e.status,headers:{"Content-Type":"application/json"}})}function a(a){return async o=>{let s;try{s=await o.formData()}catch{return r({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const n=[];for(let r=0;r<16;r++){const a=s.get(`video_${r}`);if(!e(a))break;n.push(await t(a))}const i=[];for(let r=0;r<16;r++){const a=s.get(`audio_${r}`);if(!e(a))break;i.push(await t(a))}const f=s.get("editState"),u=s.get("overlay"),y=e(u)?u:void 0,c=y?Buffer.from(await y.arrayBuffer()):void 0;if(y&&"image/png"!==y.type)return r({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const g=s.get("videoCover"),p=e(g)?g:void 0,m=p?Buffer.from(await p.arrayBuffer()):void 0;if(p&&!/^image\/(?:png|jpe?g)$/.test(p.type))return r({ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const l=[];for(let t=0;t<16;t++){const a=s.get(`textOverlay_${t}`);if(!e(a))break;if("image/png"!==a.type)return r({ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay 必须是 PNG"});l.push(Buffer.from(await a.arrayBuffer()))}const v={videos:n,audios:i,overlay:c,videoCover:m,textOverlays:l,editStateRaw:"string"==typeof f?f:""};return r(await a.handle(v))}}function o(a){return async o=>{let s;try{s=await o.formData()}catch{return r({ok:!1,status:400,error:"BAD_REQUEST",message:"请求体解析失败"})}const n=s.get("file"),i=s.get("time");return e(n)&&"string"==typeof i?r(await a.handle(await t(n),Number(i))):r({ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"})}}export{o as toWebPosterHandler,a as toWebVideoHandler};
@@ -16,6 +16,8 @@ interface ParsedVideoExport {
16
16
  videos: UploadFile[];
17
17
  audios: UploadFile[];
18
18
  overlay?: Buffer;
19
+ videoCover?: Buffer;
20
+ textOverlays?: Buffer[];
19
21
  editStateRaw: string;
20
22
  }
21
23
  type ExportOk = {
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var z=Object.create,V=Object.defineProperty,K=Object.getOwnPropertyDescriptor,U=Object.getOwnPropertyNames,J=Object.getPrototypeOf,Q=Object.prototype.hasOwnProperty,W=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of U(t))!Q.call(e,n)&&n!==r&&V(e,n,{get:()=>t[n],enumerable:!(o=K(t,n))||o.enumerable});return e},X=(e,t,r)=>(r=null!=e?z(J(e)):{},W(!t&&e&&e.__esModule?r:V(r,"default",{value:e,enumerable:!0}),e)),Z=Object.create,M=Object.defineProperty,ee=Object.getOwnPropertyDescriptor,te=Object.getOwnPropertyNames,ue=Object.getPrototypeOf,re=Object.prototype.hasOwnProperty,oe=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of te(t))!re.call(e,n)&&n!==r&&M(e,n,{get:()=>t[n],enumerable:!(o=ee(t,n))||o.enumerable});return e},ke=(e,t,r)=>(r=null!=e?Z(ue(e)):{},oe(!t&&e&&e.__esModule?r:M(r,"default",{value:e,enumerable:!0}),e)),ne=Object.create,R=Object.defineProperty,ie=Object.getOwnPropertyDescriptor,ae=Object.getOwnPropertyNames,se=Object.getPrototypeOf,ce=Object.prototype.hasOwnProperty,le=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of ae(t))!ce.call(e,n)&&n!==r&&R(e,n,{get:()=>t[n],enumerable:!(o=ie(t,n))||o.enumerable});return e},Ne=(e,t,r)=>(r=null!=e?ne(se(e)):{},le(!t&&e&&e.__esModule?r:R(r,"default",{value:e,enumerable:!0}),e)),pe=Object.create,I=Object.defineProperty,me=Object.getOwnPropertyDescriptor,fe=Object.getOwnPropertyNames,de=Object.getPrototypeOf,ge=Object.prototype.hasOwnProperty,ye=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of fe(t))!ge.call(e,n)&&n!==r&&I(e,n,{get:()=>t[n],enumerable:!(o=me(t,n))||o.enumerable});return e},qe=(e,t,r)=>(r=null!=e?pe(de(e)):{},ye(!t&&e&&e.__esModule?r:I(r,"default",{value:e,enumerable:!0}),e)),Fe=Object.create,D=Object.defineProperty,we=Object.getOwnPropertyDescriptor,ve=Object.getOwnPropertyNames,Ae=Object.getPrototypeOf,he=Object.prototype.hasOwnProperty,Ee=(e,t)=>{for(var r in t)D(e,r,{get:t[r],enumerable:!0})},L=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let n of ve(t))!he.call(e,n)&&n!==r&&D(e,n,{get:()=>t[n],enumerable:!(o=we(t,n))||o.enumerable});return e},be=(e,t,r)=>(r=null!=e?Fe(Ae(e)):{},L(!t&&e&&e.__esModule?r:D(r,"default",{value:e,enumerable:!0}),e)),Ce=e=>L(D({},"__esModule",{value:!0}),e),k={};Ee(k,{DEFAULT_LIMITS:()=>Y,composeVideo:()=>Pe,createPosterHandler:()=>Ie,createVideoExportHandler:()=>Re,extractAudio:()=>xe,probeHasAudio:()=>P,probeVideoDuration:()=>q,runVideoEdit:()=>$}),module.exports=Ce(k);var j,Oe=require("../core/dist/index.cjs"),N=require("../core/dist/index.cjs"),T=be(require("ffmpeg-static"),1),_=require("child_process"),d=require("fs/promises"),Be=require("fs"),De=require("os"),m=require("path"),_e={};async function je(){const e=[];T.default&&"string"==typeof T.default&&e.push(T.default);try{const{createRequire:t}=await import("module"),r=t(_e.url).resolve("ffmpeg-static/package.json"),o=r.substring(0,r.lastIndexOf("/"));e.push((0,m.join)(o,"ffmpeg"))}catch{}e.push((0,m.join)(process.cwd(),"node_modules","ffmpeg-static","ffmpeg"),(0,m.join)(process.cwd(),"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));for(const t of e)if(t&&!t.startsWith("/ROOT/"))try{return await(0,d.access)(t,Be.constants.X_OK),t}catch{}return null}async function x(){return void 0!==j||(j=await je())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),j}async function B({args:e,timeoutMs:t=18e4}){const r=await x();if(!r)throw new Error("ffmpeg-static 二进制缺失 — 请检查 node_modules/ffmpeg-static/ffmpeg 是否存在");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((o,n)=>{const i=(0,_.spawn)(r,e,{stdio:["ignore","pipe","pipe"]});let s="",a=!1;const c=setTimeout(()=>{a=!0,i.kill("SIGKILL"),n(new Error(`ffmpeg 超时 (${t}ms)`))},t);i.stderr.on("data",e=>{s+=e.toString(),s.length>8192&&(s=s.slice(-8192))}),i.on("close",e=>{if(clearTimeout(c),a)return;const t=s.slice(-2500);console.log(`[ffmpeg-runner] exit=${e}, stderr tail:\n${t}\n---end---`),0===e?o():n(new Error(`ffmpeg 退出码 ${e}: ${t.slice(-800)}`))}),i.on("error",e=>{clearTimeout(c),n(e)})})}async function O(e){const t=await(0,d.mkdtemp)((0,m.join)((0,De.tmpdir)(),"vtrans-"));try{return await e(t)}finally{(0,d.rm)(t,{recursive:!0,force:!0}).catch(()=>{})}}async function P(e){const t=await x();return!!t&&O(async r=>{const o=(0,m.join)(r,"probe.mp4");return await(0,d.writeFile)(o,e),new Promise(e=>{const r=(0,_.spawn)(t,["-hide_banner","-i",o],{stdio:["ignore","ignore","pipe"]});let n="";const i=setTimeout(()=>{r.kill("SIGKILL"),e(!1)},15e3);r.stderr.on("data",e=>{n+=e.toString(),n.length>16384&&(n=n.slice(-16384))}),r.on("close",()=>{clearTimeout(i),e(/Stream #[^\n]*Audio:/.test(n))}),r.on("error",()=>{clearTimeout(i),e(!1)})})})}async function xe(e){return O(async t=>{const r=(0,m.join)(t,"in.mp4"),o=(0,m.join)(t,"audio.mp3");return await(0,d.writeFile)(r,e),await B({args:["-y","-i",r,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",o],timeoutMs:6e4}),(0,d.readFile)(o)})}var Te=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function Pe(e){const t=(await q(e.videoBytes)).durationSeconds;return O(async r=>{const o=(0,m.join)(r,"in.mp4"),n=(0,m.join)(r,"out.mp4");await(0,d.writeFile)(o,e.videoBytes);const i=["-i",o];let s=-1,a=-1;if(e.newAudioBytes){const t=(0,m.join)(r,"new.mp3");await(0,d.writeFile)(t,e.newAudioBytes),s=(i.length-0)/2,i.push("-i",t)}if(e.bgmAudioBytes){const t=(0,m.join)(r,"bgm.mp3");await(0,d.writeFile)(t,e.bgmAudioBytes),a=i.length/2,i.push("-i",t)}const c=e.srtContent&&e.srtContent.trim().length>0;let l=null;c&&(l=(0,m.join)(r,"subs.srt"),await(0,d.writeFile)(l,e.srtContent));const u=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${t.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${u}, srt=${e.srtContent?.length??0}chars`);const p=Te.replace(/,/g,"\\,"),f=[];let g,y=!1;c&&l?(f.push(`[0:v]subtitles=${l}:force_style=${p}[v]`),g="[v]",y=!0):g="0:v:0";const w=[];s>=0&&w.push({idx:s,isBgm:!1}),a>=0&&w.push({idx:a,isBgm:!0});let b=null;if(1===w.length)b=`${w[0].idx}:a:0`;else if(w.length>=2){const e=[];for(const t of w)t.isBgm?(f.push(`[${t.idx}:a]volume=${u}[bgm]`),e.push("[bgm]")):e.push(`[${t.idx}:a]`);f.push(`${e.join("")}amix=inputs=${e.length}:duration=first:dropout_transition=0[a]`),b="[a]"}const O=["-y",...i];f.length>0&&O.push("-filter_complex",f.join(";")),O.push("-map",g),b&&O.push("-map",b),y?O.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):O.push("-c:v","copy"),O.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",t.toFixed(3)),O.push(n),await B({args:O,timeoutMs:18e4});const v=b?await x():null;if(v)try{const e=await new Promise(e=>{const t=(0,_.spawn)(v,["-i",n],{stdio:["ignore","pipe","pipe"]});let r="";t.stderr.on("data",e=>r+=e.toString()),t.on("close",()=>e(r))});if(!/Audio:/.test(e))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",e.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(e){if(e instanceof Error&&e.message.includes("合成失败"))throw e}return(0,d.readFile)(n)})}async function q(e){return O(async t=>{const r=(0,m.join)(t,"in.mp4");await(0,d.writeFile)(r,e);const o=await x();if(!o)throw new Error("ffmpeg-static 二进制缺失");return new Promise((e,t)=>{const n=(0,_.spawn)(o,["-i",r],{stdio:["ignore","pipe","pipe"]});let i="";n.stderr.on("data",e=>i+=e.toString()),n.on("close",()=>{const r=i.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!r)return t(new Error("无法解析视频时长"));const o=parseInt(r[1]),n=parseInt(r[2]),s=parseFloat(r[3]);e({durationSeconds:3600*o+60*n+s})}),n.on("error",t)})})}async function $(e,t,r="mp4",o=[],n=24e4){return O(async i=>{const s=(0,m.join)(i,"input.mp4"),a=(0,m.join)(i,`output.${r}`);await(0,d.writeFile)(s,e);const c={};for(const e of o){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),r=(0,m.join)(i,t);await(0,d.writeFile)(r,e.bytes),c[e.name]=r}return await B({args:t(s,a,c),timeoutMs:n}),(0,d.readFile)(a)})}function $e(e){const t=String(e??"").toLowerCase();return"mp4"===t||"mov"===t}function Se(e,t,r){return["-y","-loop","1","-t","0.04","-i",t,"-i",e,"-filter_complex","[0:v][1:v]scale2ref=w=main_w:h=main_h[vcover][vmainref];[vcover]setsar=1,fps=30,format=yuv420p[vcoverf];[vmainref]setpts=PTS-STARTPTS,fps=30,format=yuv420p[vmain];[vcoverf][vmain]concat=n=2:v=1:a=0[v]","-map","[v]","-map","1:a?","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-c:a","copy","-movflags","+faststart",r]}async function Ve(e,t,r="mp4",o=[],n,i=24e4){return O(async s=>{const a=(0,m.join)(s,"input.mp4"),c=(0,m.join)(s,`output.${r}`),l=(0,m.join)(s,"video-cover.png"),u=(0,m.join)(s,`output-with-cover.${r}`);await(0,d.writeFile)(a,e);const p={};for(const e of o){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),r=(0,m.join)(s,t);await(0,d.writeFile)(r,e.bytes),p[e.name]=r}return await B({args:t(a,c,p),timeoutMs:i}),$e(r)&&n?.length?(await(0,d.writeFile)(l,n),await B({args:Se(c,l,u),timeoutMs:i}),(0,d.readFile)(u)):(0,d.readFile)(c)})}var Y={maxVideos:6,maxAudios:3,maxVideoBytes:104857600,maxTotalVideoBytes:314572800,maxAudioBytes:31457280,maxOverlayBytes:20971520},l=(e,t,r)=>({ok:!1,status:e,error:t,message:r});function G(e){return e.startsWith("video/")?"mp4":e.includes("mpeg")||e.includes("mp3")?"mp3":e.includes("wav")?"wav":e.includes("aac")?"aac":e.includes("ogg")?"ogg":e.includes("mp4")||e.includes("m4a")?"m4a":e.includes("flac")?"flac":"bin"}function Me(e){if(!e?.poster?.useAsVideoCover)return;const t=("string"==typeof e.poster.preview?e.poster.preview:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return t?Buffer.from(t[1],"base64"):void 0}function Re(e){const t={...Y,...e?.limits};return{async handle(e){const{videos:r,audios:o,overlay:n,videoCover:i,editStateRaw:s}=e;if(r.length>t.maxVideos)return l(413,"TOO_MANY",`视频数超过 ${t.maxVideos}`);for(let e=0;e<r.length;e++){if(!/^video\//.test(r[e].type))return l(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(r[e].bytes.length>t.maxVideoBytes)return l(413,"TOO_LARGE",`视频 ${e+1} 超过 100MB`)}if(!r.length)return l(400,"BAD_REQUEST","缺少视频文件(video_0)");if(r.reduce((e,t)=>e+t.bytes.length,0)>t.maxTotalVideoBytes)return l(413,"TOO_LARGE","视频总大小超过 300MB,请减少素材或压缩");if(o.length>t.maxAudios)return l(413,"TOO_MANY",`音频数超过 ${t.maxAudios}`);for(let e=0;e<o.length;e++){const r=/^audio\//.test(o[e].type),n=/^video\//.test(o[e].type);if(!r&&!n)return l(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(o[e].bytes.length>(n?t.maxVideoBytes:t.maxAudioBytes))return l(413,"TOO_LARGE",`音频 ${e+1} 超过 ${n?"100MB":"30MB"}`)}if("string"!=typeof s)return l(400,"BAD_REQUEST","缺少 editState");if(n&&n.length>t.maxOverlayBytes)return l(413,"OVERLAY_TOO_LARGE","overlay 过大");if(i&&i.length>t.maxOverlayBytes)return l(413,"COVER_TOO_LARGE","videoCover 过大");let a;try{a=Oe.VideoEditState.parse(JSON.parse(s))}catch{return l(400,"BAD_STATE","编辑参数不合法")}if(a.clips.some(e=>e.srcIdx>=r.length))return l(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((a.audioClips??[]).some(e=>e.srcIdx>=o.length))return l(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=r.map(e=>e.bytes),t=o.map(e=>e.bytes),s=[...new Set((a.audioClips??[]).map(e=>e.srcIdx))];for(const e of s)if(/^video\//.test(o[e].type)&&!await P(t[e]))return l(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const c=r.map(()=>!1);if(a.keepAudio){const t=[...new Set(a.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{c[t]=await P(e[t])})),!c.some(Boolean)&&!(a.audioClips??[]).length&&(a={...a,keepAudio:!1})}const u=[];for(let t=1;t<e.length;t++)u.push({name:`video_${t}.mp4`,bytes:e[t]});t.forEach((e,t)=>u.push({name:`audio_${t}.${G(o[t].type)}`,bytes:e})),n&&u.push({name:"overlay.png",bytes:n});const p=i?.length?i:Me(a),m=(r,i,s)=>{const l={videoPaths:[r,...e.slice(1).map((e,t)=>s[`video_${t+1}.mp4`])],videoHasAudio:c,audioPaths:t.map((e,t)=>s[`audio_${t}.${G(o[t].type)}`]),overlayPath:n?s["overlay.png"]:void 0};return(0,N.buildFfmpegArgs)(a,l,i)};return{ok:!0,bytes:null==p?await $(e[0],m,"mp4",u,28e4):await Ve(e[0],m,"mp4",u,p,28e4),contentType:"video/mp4",filename:"edited.mp4"}}catch(e){return(e.message??"").includes("二进制缺失")?l(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):l(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}}}}function Ie(e){const t=e?.maxBytes??104857600;return{async handle(e,r){if(!/^video\//.test(e.type))return l(415,"BAD_TYPE","只支持视频文件");if(e.bytes.length>t)return l(413,"TOO_LARGE","文件超过 100MB");if(!isFinite(r)||r<0)return l(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await $(e.bytes,(e,t)=>(0,N.buildPosterArgs)(e,r,t),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(e){return(e.message??"").includes("二进制缺失")?l(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):l(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}
1
+ "use strict";var __create=Object.create,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty,__export=(e,t)=>{for(var r in t)__defProp(e,r,{get:t[r],enumerable:!0})},__copyProps=(e,t,r,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let i of __getOwnPropNames(t))__hasOwnProp.call(e,i)||i===r||__defProp(e,i,{get:()=>t[i],enumerable:!(o=__getOwnPropDesc(t,i))||o.enumerable});return e},__toESM=(e,t,r)=>(r=null!=e?__create(__getProtoOf(e)):{},__copyProps(!t&&e&&e.__esModule?r:__defProp(r,"default",{value:e,enumerable:!0}),e)),__toCommonJS=e=>__copyProps(__defProp({},"__esModule",{value:!0}),e),src_exports={};__export(src_exports,{DEFAULT_LIMITS:()=>DEFAULT_LIMITS,composeVideo:()=>composeVideo,createPosterHandler:()=>createPosterHandler,createVideoExportHandler:()=>createVideoExportHandler,extractAudio:()=>extractAudio,probeHasAudio:()=>probeHasAudio,probeVideoDuration:()=>probeVideoDuration,runVideoEdit:()=>runVideoEdit,runVideoEditWithCover:()=>runVideoEditWithCover}),module.exports=__toCommonJS(src_exports);var _resolvedPath,import_core=require("../core/dist/index.cjs"),import_core2=require("../core/dist/index.cjs"),import_node_child_process=require("child_process"),import_promises=require("fs/promises"),import_node_fs=require("fs"),import_node_os=require("os"),import_node_path=require("path"),import_meta={};async function resolveFfmpegPath(){const e=[],t=t=>{e.push(t),t.toLowerCase().endsWith(".exe")||e.push(`${t}.exe`)};try{const e=(await import("ffmpeg-static")).default;"string"==typeof e&&t(e)}catch{}try{const{createRequire:e}=await import("module"),r=e(import_meta.url).resolve("ffmpeg-static/package.json"),o=Math.max(r.lastIndexOf("/"),r.lastIndexOf("\\")),i=o>=0?r.substring(0,o):r;t((0,import_node_path.join)(i,"ffmpeg"))}catch{}(()=>{let e=process.cwd();for(;;){t((0,import_node_path.join)(e,"node_modules","ffmpeg-static","ffmpeg")),t((0,import_node_path.join)(e,"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));const r=(0,import_node_path.dirname)(e);if(r===e)break;e=r}})();for(const t of e)if(t&&!t.startsWith("/ROOT/"))try{return await(0,import_promises.access)(t,import_node_fs.constants.X_OK),t}catch{}return null}async function getFfmpegPath(){return void 0!==_resolvedPath||(_resolvedPath=await resolveFfmpegPath())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),_resolvedPath}async function runFfmpeg({args:e,timeoutMs:t=18e4}){const r=await getFfmpegPath();if(!r)throw new Error("ffmpeg-static 二进制缺失 — 请检查 node_modules/ffmpeg-static/ffmpeg 是否存在");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((o,i)=>{const n=(0,import_node_child_process.spawn)(r,e,{stdio:["ignore","pipe","pipe"]});let s="",a=!1;const p=setTimeout(()=>{a=!0,n.kill("SIGKILL"),i(new Error(`ffmpeg 超时 (${t}ms)`))},t);n.stderr.on("data",e=>{s+=e.toString(),s.length>8192&&(s=s.slice(-8192))}),n.on("close",e=>{if(clearTimeout(p),a)return;const t=s.slice(-2500);console.log(`[ffmpeg-runner] exit=${e}, stderr tail:\n${t}\n---end---`),0===e?o():i(new Error(`ffmpeg 退出码 ${e}: ${t.slice(-800)}`))}),n.on("error",e=>{clearTimeout(p),i(e)})})}async function withTempDir(e){const t=await(0,import_promises.mkdtemp)((0,import_node_path.join)((0,import_node_os.tmpdir)(),"vtrans-"));try{return await e(t)}finally{(0,import_promises.rm)(t,{recursive:!0,force:!0}).catch(()=>{})}}async function probeHasAudio(e){const t=await getFfmpegPath();return!!t&&withTempDir(async r=>{const o=(0,import_node_path.join)(r,"probe.mp4");return await(0,import_promises.writeFile)(o,e),new Promise(e=>{const r=(0,import_node_child_process.spawn)(t,["-hide_banner","-i",o],{stdio:["ignore","ignore","pipe"]});let i="";const n=setTimeout(()=>{r.kill("SIGKILL"),e(!1)},15e3);r.stderr.on("data",e=>{i+=e.toString(),i.length>16384&&(i=i.slice(-16384))}),r.on("close",()=>{clearTimeout(n),e(/Stream #[^\n]*Audio:/.test(i))}),r.on("error",()=>{clearTimeout(n),e(!1)})})})}async function extractAudio(e){return withTempDir(async t=>{const r=(0,import_node_path.join)(t,"in.mp4"),o=(0,import_node_path.join)(t,"audio.mp3");return await(0,import_promises.writeFile)(r,e),await runFfmpeg({args:["-y","-i",r,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",o],timeoutMs:6e4}),(0,import_promises.readFile)(o)})}var SUBTITLE_STYLE=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function composeVideo(e){const t=(await probeVideoDuration(e.videoBytes)).durationSeconds;return withTempDir(async r=>{const o=(0,import_node_path.join)(r,"in.mp4"),i=(0,import_node_path.join)(r,"out.mp4");await(0,import_promises.writeFile)(o,e.videoBytes);const n=["-i",o];let s=-1,a=-1;if(e.newAudioBytes){const t=(0,import_node_path.join)(r,"new.mp3");await(0,import_promises.writeFile)(t,e.newAudioBytes),s=(n.length-0)/2,n.push("-i",t)}if(e.bgmAudioBytes){const t=(0,import_node_path.join)(r,"bgm.mp3");await(0,import_promises.writeFile)(t,e.bgmAudioBytes),a=n.length/2,n.push("-i",t)}const p=e.srtContent&&e.srtContent.trim().length>0;let m=null;p&&(m=(0,import_node_path.join)(r,"subs.srt"),await(0,import_promises.writeFile)(m,e.srtContent));const c=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${t.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${c}, srt=${e.srtContent?.length??0}chars`);const d=SUBTITLE_STYLE.replace(/,/g,"\\,"),u=[];let l,_=!1;p&&m?(u.push(`[0:v]subtitles=${m}:force_style=${d}[v]`),l="[v]",_=!0):l="0:v:0";const f=[];s>=0&&f.push({idx:s,isBgm:!1}),a>=0&&f.push({idx:a,isBgm:!0});let g=null;if(1===f.length)g=`${f[0].idx}:a:0`;else if(f.length>=2){const e=[];for(const t of f)t.isBgm?(u.push(`[${t.idx}:a]volume=${c}[bgm]`),e.push("[bgm]")):e.push(`[${t.idx}:a]`);u.push(`${e.join("")}amix=inputs=${e.length}:duration=first:dropout_transition=0[a]`),g="[a]"}const h=["-y",...n];u.length>0&&h.push("-filter_complex",u.join(";")),h.push("-map",l),g&&h.push("-map",g),_?h.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):h.push("-c:v","copy"),h.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",t.toFixed(3)),h.push(i),await runFfmpeg({args:h,timeoutMs:18e4});const y=g?await getFfmpegPath():null;if(y)try{const e=await new Promise(e=>{const t=(0,import_node_child_process.spawn)(y,["-i",i],{stdio:["ignore","pipe","pipe"]});let r="";t.stderr.on("data",e=>r+=e.toString()),t.on("close",()=>e(r))});if(!/Audio:/.test(e))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",e.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(e){if(e instanceof Error&&e.message.includes("合成失败"))throw e}return(0,import_promises.readFile)(i)})}async function probeVideoDuration(e){return withTempDir(async t=>{const r=(0,import_node_path.join)(t,"in.mp4");await(0,import_promises.writeFile)(r,e);const o=await getFfmpegPath();if(!o)throw new Error("ffmpeg-static 二进制缺失");return new Promise((e,t)=>{const i=(0,import_node_child_process.spawn)(o,["-i",r],{stdio:["ignore","pipe","pipe"]});let n="";i.stderr.on("data",e=>n+=e.toString()),i.on("close",()=>{const r=n.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!r)return t(new Error("无法解析视频时长"));const o=parseInt(r[1]),i=parseInt(r[2]),s=parseFloat(r[3]);e({durationSeconds:3600*o+60*i+s})}),i.on("error",t)})})}async function runVideoEdit(e,t,r="mp4",o=[],i=24e4){return withTempDir(async n=>{const s=(0,import_node_path.join)(n,"input.mp4"),a=(0,import_node_path.join)(n,`output.${r}`);await(0,import_promises.writeFile)(s,e);const p={};for(const e of o){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),r=(0,import_node_path.join)(n,t);await(0,import_promises.writeFile)(r,e.bytes),p[e.name]=r}return await runFfmpeg({args:t(s,a,p),timeoutMs:i}),(0,import_promises.readFile)(a)})}function canMuxVideoCover(e){const t=String(e||"").toLowerCase();return"mp4"===t||"mov"===t}function coverFileExtFromBytes(e){return e.length>=8&&137===e[0]&&80===e[1]&&78===e[2]&&71===e[3]&&13===e[4]&&10===e[5]&&26===e[6]&&10===e[7]?"png":"jpg"}function coverConcatArgs(e,t,r){return["-y","-fflags","+genpts","-loop","1","-t","0.04","-i",t,"-i",e,"-filter_complex","[0:v][1:v]scale2ref=w=main_w:h=main_h[vcover][vmainref];[vcover]setsar=1,setpts=PTS-STARTPTS,fps=30,format=yuv420p[vcoverf];[vmainref]setsar=1,setpts=PTS-STARTPTS,fps=30,format=yuv420p[vmain];[vcoverf][vmain]concat=n=2:v=1:a=0[v]","-map","[v]","-map","1:a?","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k","-ar","44100","-ac","2","-movflags","+faststart",r]}async function runVideoEditWithCover(e,t,r="mp4",o=[],i,n=24e4){return withTempDir(async s=>{const a=(0,import_node_path.join)(s,"input.mp4"),p=(0,import_node_path.join)(s,`output.${r}`),m=(0,import_node_path.join)(s,`output-with-cover.${r}`);await(0,import_promises.writeFile)(a,e);const c={};for(const e of o){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),r=(0,import_node_path.join)(s,t);await(0,import_promises.writeFile)(r,e.bytes),c[e.name]=r}if(await runFfmpeg({args:t(a,p,c),timeoutMs:n}),!i?.length||!canMuxVideoCover(r))return(0,import_promises.readFile)(p);const d=(0,import_node_path.join)(s,`video-cover.${coverFileExtFromBytes(i)}`);return await(0,import_promises.writeFile)(d,i),await runFfmpeg({args:coverConcatArgs(p,d,m),timeoutMs:n}),(0,import_promises.readFile)(m)})}var MB=1048576,limitLabel=e=>`${Math.round(e/MB)}MB`,DEFAULT_LIMITS={maxVideos:6,maxAudios:3,maxVideoBytes:512*MB,maxTotalVideoBytes:1024*MB,maxAudioBytes:30*MB,maxOverlayBytes:20*MB},err=(e,t,r)=>({ok:!1,status:e,error:t,message:r});function audioExt(e){return e.startsWith("video/")?"mp4":e.includes("mpeg")||e.includes("mp3")?"mp3":e.includes("wav")?"wav":e.includes("aac")?"aac":e.includes("ogg")?"ogg":e.includes("mp4")||e.includes("m4a")?"m4a":e.includes("flac")?"flac":"bin"}function exportFormat(e){const t=String(e.exportSettings?.format??"mp4").toLowerCase();return"mov"===t||"mxf"===t?t:"mp4"}function videoContentType(e){return"mov"===e?"video/quicktime":"mxf"===e?"application/mxf":"video/mp4"}function posterPreviewBytes(e){const t=e.poster;if(!t?.useAsVideoCover)return;const r=("string"==typeof t.preview?t.preview:"string"==typeof t.src?t.src:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return r?Buffer.from(r[1],"base64"):void 0}function createVideoExportHandler(e){const t={...DEFAULT_LIMITS,...e?.limits};return{async handle(e){const{videos:r,audios:o,overlay:i,videoCover:n,textOverlays:s=[],editStateRaw:a}=e;if(r.length>t.maxVideos)return err(413,"TOO_MANY",`视频数超过 ${t.maxVideos}`);for(let e=0;e<r.length;e++){if(!/^video\//.test(r[e].type))return err(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(r[e].bytes.length>t.maxVideoBytes)return err(413,"TOO_LARGE",`视频 ${e+1} 超过 ${limitLabel(t.maxVideoBytes)}`)}if(!r.length)return err(400,"BAD_REQUEST","缺少视频文件(video_0)");if(r.reduce((e,t)=>e+t.bytes.length,0)>t.maxTotalVideoBytes)return err(413,"TOO_LARGE",`视频总大小超过 ${limitLabel(t.maxTotalVideoBytes)},请减少素材或压缩`);if(o.length>t.maxAudios)return err(413,"TOO_MANY",`音频数超过 ${t.maxAudios}`);for(let e=0;e<o.length;e++){const r=/^audio\//.test(o[e].type),i=/^video\//.test(o[e].type);if(!r&&!i)return err(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(o[e].bytes.length>(i?t.maxVideoBytes:t.maxAudioBytes))return err(413,"TOO_LARGE",`音频 ${e+1} 超过 ${limitLabel(i?t.maxVideoBytes:t.maxAudioBytes)}`)}if("string"!=typeof a)return err(400,"BAD_REQUEST","缺少 editState");if(i&&i.length>t.maxOverlayBytes)return err(413,"OVERLAY_TOO_LARGE","overlay 过大");if(s.some(e=>e.length>t.maxOverlayBytes))return err(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(n&&n.length>t.maxOverlayBytes)return err(413,"COVER_TOO_LARGE","videoCover too large");let p;try{p=import_core.VideoEditState.parse(JSON.parse(a))}catch{return err(400,"BAD_STATE","编辑参数不合法")}if(p.clips.some(e=>e.srcIdx>=r.length))return err(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((p.audioClips??[]).some(e=>e.srcIdx>=o.length))return err(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=r.map(e=>e.bytes),t=o.map(e=>e.bytes),a=new Map,m=e=>{let t=a.get(e);return t||(t=probeHasAudio(e),a.set(e,t)),t},c=[...new Set((p.audioClips??[]).map(e=>e.srcIdx))];for(const e of c)if(/^video\//.test(o[e].type)&&!await m(t[e]))return err(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const d=r.map(()=>!1);if(p.keepAudio){const t=[...new Set(p.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{d[t]=await m(e[t])})),d.some(Boolean)||(p.audioClips??[]).length||(p={...p,keepAudio:!1})}const u=[];for(let t=1;t<e.length;t++)u.push({name:`video_${t}.mp4`,bytes:e[t]});t.forEach((e,t)=>u.push({name:`audio_${t}.${audioExt(o[t].type)}`,bytes:e})),i&&u.push({name:"overlay.png",bytes:i}),s.forEach((e,t)=>u.push({name:`textOverlay_${t}.png`,bytes:e}));const l=exportFormat(p),_=n?.length?n:posterPreviewBytes(p),f=(r,n,a)=>{const m={videoPaths:[r,...e.slice(1).map((e,t)=>a[`video_${t+1}.mp4`])],videoHasAudio:d,audioPaths:t.map((e,t)=>a[`audio_${t}.${audioExt(o[t].type)}`]),overlayPath:i?a["overlay.png"]:void 0,textOverlayPaths:s.map((e,t)=>a[`textOverlay_${t}.png`]).filter(Boolean),overlayLayerPaths:s.map((e,t)=>a[`textOverlay_${t}.png`]).filter(Boolean)};return(0,import_core2.buildFfmpegArgs)(p,m,n)};return{ok:!0,bytes:_?.length?await runVideoEditWithCover(e[0],f,l,u,_,28e4):await runVideoEdit(e[0],f,l,u,28e4),contentType:videoContentType(l),filename:`edited.${l}`}}catch(e){return(e.message??"").includes("二进制缺失")?err(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):err(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}}}}function createPosterHandler(e){const t=e?.maxBytes??DEFAULT_LIMITS.maxVideoBytes;return{async handle(e,r){if(!/^video\//.test(e.type))return err(415,"BAD_TYPE","只支持视频文件");if(e.bytes.length>t)return err(413,"TOO_LARGE",`文件超过 ${limitLabel(t)}`);if(!isFinite(r)||r<0)return err(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await runVideoEdit(e.bytes,(e,t)=>(0,import_core2.buildPosterArgs)(e,r,t),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(e){return(e.message??"").includes("二进制缺失")?err(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):err(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { D as DEFAULT_LIMITS, E as ExportErr, a as ExportLimits, b as ExportOk, c as ExportResult, d as ParsedVideoExport, P as PosterHandler, U as UploadFile, V as VideoExportHandler, e as createPosterHandler, f as createVideoExportHandler } from './handler-DL4Inp7o.js';
1
+ export { D as DEFAULT_LIMITS, E as ExportErr, a as ExportLimits, b as ExportOk, c as ExportResult, d as ParsedVideoExport, P as PosterHandler, U as UploadFile, V as VideoExportHandler, e as createPosterHandler, f as createVideoExportHandler } from './handler-BEalyBz9.js';
2
2
 
3
3
  /**
4
4
  * ffmpeg-runner: 视频/音频处理工具
@@ -55,5 +55,9 @@ extraFiles?: Array<{
55
55
  }>,
56
56
  /** 多源 concat 可能更慢,调用方可放宽(默认 240s 不变) */
57
57
  timeoutMs?: number): Promise<Buffer>;
58
+ declare function runVideoEditWithCover(inputBytes: Buffer, argsFor: (inputPath: string, outputPath: string, extraPaths: Record<string, string>) => string[], outExt?: string, extraFiles?: Array<{
59
+ name: string;
60
+ bytes: Buffer;
61
+ }>, coverBytes?: Buffer, timeoutMs?: number): Promise<Buffer>;
58
62
 
59
- export { type ComposeOptions, type VideoProbe, composeVideo, extractAudio, probeHasAudio, probeVideoDuration, runVideoEdit };
63
+ export { type ComposeOptions, type VideoProbe, composeVideo, extractAudio, probeHasAudio, probeVideoDuration, runVideoEdit, runVideoEditWithCover };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{VideoEditState as e}from"../core/dist/index.js";import{buildFfmpegArgs as t,buildPosterArgs as o}from"../core/dist/index.js";import s from"ffmpeg-static";import{spawn as n}from"child_process";import{writeFile as r,readFile as i,mkdtemp as a,rm as c,access as u}from"fs/promises";import{constants as m}from"fs";import{tmpdir as p}from"os";import{join as l}from"path";var f;async function d(){return void 0!==f||(f=await async function(){const e=[];s&&"string"==typeof s&&e.push(s);try{const{createRequire:t}=await import("module"),o=t(import.meta.url).resolve("ffmpeg-static/package.json"),s=o.substring(0,o.lastIndexOf("/"));e.push(l(s,"ffmpeg"))}catch{}e.push(l(process.cwd(),"node_modules","ffmpeg-static","ffmpeg"),l(process.cwd(),"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));for(const t of e)if(t&&!t.startsWith("/ROOT/"))try{return await u(t,m.X_OK),t}catch{}return null}())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),f}async function g({args:e,timeoutMs:t=18e4}){const o=await d();if(!o)throw new Error("ffmpeg-static 二进制缺失 — 请检查 node_modules/ffmpeg-static/ffmpeg 是否存在");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((s,r)=>{const i=n(o,e,{stdio:["ignore","pipe","pipe"]});let a="",c=!1;const u=setTimeout(()=>{c=!0,i.kill("SIGKILL"),r(new Error(`ffmpeg 超时 (${t}ms)`))},t);i.stderr.on("data",e=>{a+=e.toString(),a.length>8192&&(a=a.slice(-8192))}),i.on("close",e=>{if(clearTimeout(u),c)return;const t=a.slice(-2500);console.log(`[ffmpeg-runner] exit=${e}, stderr tail:\n${t}\n---end---`),0===e?s():r(new Error(`ffmpeg 退出码 ${e}: ${t.slice(-800)}`))}),i.on("error",e=>{clearTimeout(u),r(e)})})}async function y(e){const t=await a(l(p(),"vtrans-"));try{return await e(t)}finally{c(t,{recursive:!0,force:!0}).catch(()=>{})}}async function v(e){const t=await d();return!!t&&y(async o=>{const s=l(o,"probe.mp4");return await r(s,e),new Promise(e=>{const o=n(t,["-hide_banner","-i",s],{stdio:["ignore","ignore","pipe"]});let r="";const i=setTimeout(()=>{o.kill("SIGKILL"),e(!1)},15e3);o.stderr.on("data",e=>{r+=e.toString(),r.length>16384&&(r=r.slice(-16384))}),o.on("close",()=>{clearTimeout(i),e(/Stream #[^\n]*Audio:/.test(r))}),o.on("error",()=>{clearTimeout(i),e(!1)})})})}async function h(e){return y(async t=>{const o=l(t,"in.mp4"),s=l(t,"audio.mp3");return await r(o,e),await g({args:["-y","-i",o,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",s],timeoutMs:6e4}),i(s)})}var w=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function _(e){const t=(await A(e.videoBytes)).durationSeconds;return y(async o=>{const s=l(o,"in.mp4"),a=l(o,"out.mp4");await r(s,e.videoBytes);const c=["-i",s];let u=-1,m=-1;if(e.newAudioBytes){const t=l(o,"new.mp3");await r(t,e.newAudioBytes),u=(c.length-0)/2,c.push("-i",t)}if(e.bgmAudioBytes){const t=l(o,"bgm.mp3");await r(t,e.bgmAudioBytes),m=c.length/2,c.push("-i",t)}const p=e.srtContent&&e.srtContent.trim().length>0;let f=null;p&&(f=l(o,"subs.srt"),await r(f,e.srtContent));const y=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${t.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${y}, srt=${e.srtContent?.length??0}chars`);const v=w.replace(/,/g,"\\,"),h=[];let _,A=!1;p&&f?(h.push(`[0:v]subtitles=${f}:force_style=${v}[v]`),_="[v]",A=!0):_="0:v:0";const x=[];u>=0&&x.push({idx:u,isBgm:!1}),m>=0&&x.push({idx:m,isBgm:!0});let B=null;if(1===x.length)B=`${x[0].idx}:a:0`;else if(x.length>=2){const e=[];for(const t of x)t.isBgm?(h.push(`[${t.idx}:a]volume=${y}[bgm]`),e.push("[bgm]")):e.push(`[${t.idx}:a]`);h.push(`${e.join("")}amix=inputs=${e.length}:duration=first:dropout_transition=0[a]`),B="[a]"}const E=["-y",...c];h.length>0&&E.push("-filter_complex",h.join(";")),E.push("-map",_),B&&E.push("-map",B),A?E.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):E.push("-c:v","copy"),E.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",t.toFixed(3)),E.push(a),await g({args:E,timeoutMs:18e4});const T=B?await d():null;if(T)try{const e=await new Promise(e=>{const t=n(T,["-i",a],{stdio:["ignore","pipe","pipe"]});let o="";t.stderr.on("data",e=>o+=e.toString()),t.on("close",()=>e(o))});if(!/Audio:/.test(e))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",e.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(e){if(e instanceof Error&&e.message.includes("合成失败"))throw e}return i(a)})}async function A(e){return y(async t=>{const o=l(t,"in.mp4");await r(o,e);const s=await d();if(!s)throw new Error("ffmpeg-static 二进制缺失");return new Promise((e,t)=>{const r=n(s,["-i",o],{stdio:["ignore","pipe","pipe"]});let i="";r.stderr.on("data",e=>i+=e.toString()),r.on("close",()=>{const o=i.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!o)return t(new Error("无法解析视频时长"));const s=parseInt(o[1]),n=parseInt(o[2]),r=parseFloat(o[3]);e({durationSeconds:3600*s+60*n+r})}),r.on("error",t)})})}async function x(e,t,o="mp4",s=[],n=24e4){return y(async a=>{const c=l(a,"input.mp4"),u=l(a,`output.${o}`);await r(c,e);const m={};for(const e of s){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=l(a,t);await r(o,e.bytes),m[e.name]=o}return await g({args:t(c,u,m),timeoutMs:n}),i(u)})}function B(e,t,o){return["-y","-loop","1","-t","0.04","-i",t,"-i",e,"-filter_complex","[0:v][1:v]scale2ref=w=main_w:h=main_h[vcover][vmainref];[vcover]setsar=1,fps=30,format=yuv420p[vcoverf];[vmainref]setpts=PTS-STARTPTS,fps=30,format=yuv420p[vmain];[vcoverf][vmain]concat=n=2:v=1:a=0[v]","-map","[v]","-map","1:a?","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-c:a","copy","-movflags","+faststart",o]}async function E(e,t,o="mp4",s=[],n,a=24e4){return y(async c=>{const u=l(c,"input.mp4"),m=l(c,`output.${o}`),p=l(c,"video-cover.png"),f=l(c,`output-with-cover.${o}`);await r(u,e);const d={};for(const e of s){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=l(c,t);await r(o,e.bytes),d[e.name]=o}return await g({args:t(u,m,d),timeoutMs:a}),function(e){const t=String(e??"").toLowerCase();return"mp4"===t||"mov"===t}(o)&&n?.length?(await r(p,n),await g({args:B(m,p,f),timeoutMs:a}),i(f)):i(m)})}var T={maxVideos:6,maxAudios:3,maxVideoBytes:104857600,maxTotalVideoBytes:314572800,maxAudioBytes:31457280,maxOverlayBytes:20971520},b=(e,t,o)=>({ok:!1,status:e,error:t,message:o});function O(e){return e.startsWith("video/")?"mp4":e.includes("mpeg")||e.includes("mp3")?"mp3":e.includes("wav")?"wav":e.includes("aac")?"aac":e.includes("ogg")?"ogg":e.includes("mp4")||e.includes("m4a")?"m4a":e.includes("flac")?"flac":"bin"}function $(e){return"mov"===e?"video/quicktime":"mxf"===e?"application/mxf":"video/mp4"}function S(o){const s={...T,...o?.limits};return{async handle(o){const{videos:n,audios:r,overlay:i,videoCover:a,textOverlays:c=[],editStateRaw:u}=o;if(n.length>s.maxVideos)return b(413,"TOO_MANY",`视频数超过 ${s.maxVideos}`);for(let e=0;e<n.length;e++){if(!/^video\//.test(n[e].type))return b(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(n[e].bytes.length>s.maxVideoBytes)return b(413,"TOO_LARGE",`视频 ${e+1} 超过 100MB`)}if(!n.length)return b(400,"BAD_REQUEST","缺少视频文件(video_0)");if(n.reduce((e,t)=>e+t.bytes.length,0)>s.maxTotalVideoBytes)return b(413,"TOO_LARGE","视频总大小超过 300MB,请减少素材或压缩");if(r.length>s.maxAudios)return b(413,"TOO_MANY",`音频数超过 ${s.maxAudios}`);for(let e=0;e<r.length;e++){const t=/^audio\//.test(r[e].type),o=/^video\//.test(r[e].type);if(!t&&!o)return b(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(r[e].bytes.length>(o?s.maxVideoBytes:s.maxAudioBytes))return b(413,"TOO_LARGE",`音频 ${e+1} 超过 ${o?"100MB":"30MB"}`)}if("string"!=typeof u)return b(400,"BAD_REQUEST","缺少 editState");if(i&&i.length>s.maxOverlayBytes)return b(413,"OVERLAY_TOO_LARGE","overlay 过大");if(a&&a.length>s.maxOverlayBytes)return b(413,"COVER_TOO_LARGE","videoCover 过大");if(c.some(e=>e.length>s.maxOverlayBytes))return b(413,"OVERLAY_TOO_LARGE","textOverlay 过大");let m;try{m=e.parse(JSON.parse(u))}catch{return b(400,"BAD_STATE","编辑参数不合法")}if(m.clips.some(e=>e.srcIdx>=n.length))return b(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((m.audioClips??[]).some(e=>e.srcIdx>=r.length))return b(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=n.map(e=>e.bytes),o=r.map(e=>e.bytes),s=[...new Set((m.audioClips??[]).map(e=>e.srcIdx))];for(const e of s)if(/^video\//.test(r[e].type)&&!await v(o[e]))return b(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const u=n.map(()=>!1);if(m.keepAudio){const t=[...new Set(m.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{u[t]=await v(e[t])})),!u.some(Boolean)&&!(m.audioClips??[]).length&&(m={...m,keepAudio:!1})}const p=[];for(let t=1;t<e.length;t++)p.push({name:`video_${t}.mp4`,bytes:e[t]});o.forEach((e,t)=>p.push({name:`audio_${t}.${O(r[t].type)}`,bytes:e})),i&&p.push({name:"overlay.png",bytes:i}),c.forEach((e,t)=>p.push({name:`textOverlay_${t}.png`,bytes:e}));const l=function(e){const t=String(e??"mp4").toLowerCase();return"mov"===t||"mxf"===t?t:"mp4"}(m.exportSettings?.format),f=a?.length?a:function(e){if(!e?.poster?.useAsVideoCover)return;const t=("string"==typeof e.poster.preview?e.poster.preview:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return t?Buffer.from(t[1],"base64"):void 0}(m),d=(s,n,a)=>{const p={videoPaths:[s,...e.slice(1).map((e,t)=>a[`video_${t+1}.mp4`])],videoHasAudio:u,audioPaths:o.map((e,t)=>a[`audio_${t}.${O(r[t].type)}`]),overlayPath:i?a["overlay.png"]:void 0,textOverlayPaths:c.map((e,t)=>a[`textOverlay_${t}.png`]).filter(Boolean)};return t(m,p,n)};return{ok:!0,bytes:null==f?await x(e[0],d,l,p,28e4):await E(e[0],d,l,p,f,28e4),contentType:$(l),filename:`edited.${l}`}}catch(e){return(e.message??"").includes("二进制缺失")?b(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):b(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}}}}function R(e){const t=e?.maxBytes??104857600;return{async handle(e,s){if(!/^video\//.test(e.type))return b(415,"BAD_TYPE","只支持视频文件");if(e.bytes.length>t)return b(413,"TOO_LARGE","文件超过 100MB");if(!isFinite(s)||s<0)return b(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await x(e.bytes,(e,t)=>o(e,s,t),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(e){return(e.message??"").includes("二进制缺失")?b(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):b(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}export{T as DEFAULT_LIMITS,_ as composeVideo,R as createPosterHandler,S as createVideoExportHandler,h as extractAudio,v as probeHasAudio,A as probeVideoDuration,x as runVideoEdit};
1
+ import{VideoEditState as e}from"../core/dist/index.js";import{buildFfmpegArgs as t,buildPosterArgs as o}from"../core/dist/index.js";import{spawn as n}from"child_process";import{writeFile as r,readFile as s,mkdtemp as a,rm as i,access as c}from"fs/promises";import{constants as m}from"fs";import{tmpdir as u}from"os";import{dirname as p,join as l}from"path";var f;async function d(){return void 0!==f||(f=await async function(){const e=[],t=t=>{e.push(t),t.toLowerCase().endsWith(".exe")||e.push(`${t}.exe`)};try{const e=(await import("ffmpeg-static")).default;"string"==typeof e&&t(e)}catch{}try{const{createRequire:e}=await import("module"),o=e(import.meta.url).resolve("ffmpeg-static/package.json"),n=Math.max(o.lastIndexOf("/"),o.lastIndexOf("\\")),r=n>=0?o.substring(0,n):o;t(l(r,"ffmpeg"))}catch{}(()=>{let e=process.cwd();for(;;){t(l(e,"node_modules","ffmpeg-static","ffmpeg")),t(l(e,"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));const o=p(e);if(o===e)break;e=o}})();for(const t of e)if(t&&!t.startsWith("/ROOT/"))try{return await c(t,m.X_OK),t}catch{}return null}())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),f}async function g({args:e,timeoutMs:t=18e4}){const o=await d();if(!o)throw new Error("ffmpeg-static 二进制缺失 — 请检查 node_modules/ffmpeg-static/ffmpeg 是否存在");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((r,s)=>{const a=n(o,e,{stdio:["ignore","pipe","pipe"]});let i="",c=!1;const m=setTimeout(()=>{c=!0,a.kill("SIGKILL"),s(new Error(`ffmpeg 超时 (${t}ms)`))},t);a.stderr.on("data",e=>{i+=e.toString(),i.length>8192&&(i=i.slice(-8192))}),a.on("close",e=>{if(clearTimeout(m),c)return;const t=i.slice(-2500);console.log(`[ffmpeg-runner] exit=${e}, stderr tail:\n${t}\n---end---`),0===e?r():s(new Error(`ffmpeg 退出码 ${e}: ${t.slice(-800)}`))}),a.on("error",e=>{clearTimeout(m),s(e)})})}async function y(e){const t=await a(l(u(),"vtrans-"));try{return await e(t)}finally{i(t,{recursive:!0,force:!0}).catch(()=>{})}}async function v(e){const t=await d();return!!t&&y(async o=>{const s=l(o,"probe.mp4");return await r(s,e),new Promise(e=>{const o=n(t,["-hide_banner","-i",s],{stdio:["ignore","ignore","pipe"]});let r="";const a=setTimeout(()=>{o.kill("SIGKILL"),e(!1)},15e3);o.stderr.on("data",e=>{r+=e.toString(),r.length>16384&&(r=r.slice(-16384))}),o.on("close",()=>{clearTimeout(a),e(/Stream #[^\n]*Audio:/.test(r))}),o.on("error",()=>{clearTimeout(a),e(!1)})})})}async function h(e){return y(async t=>{const o=l(t,"in.mp4"),n=l(t,"audio.mp3");return await r(o,e),await g({args:["-y","-i",o,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",n],timeoutMs:6e4}),s(n)})}var w=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function x(e){const t=(await _(e.videoBytes)).durationSeconds;return y(async o=>{const a=l(o,"in.mp4"),i=l(o,"out.mp4");await r(a,e.videoBytes);const c=["-i",a];let m=-1,u=-1;if(e.newAudioBytes){const t=l(o,"new.mp3");await r(t,e.newAudioBytes),m=(c.length-0)/2,c.push("-i",t)}if(e.bgmAudioBytes){const t=l(o,"bgm.mp3");await r(t,e.bgmAudioBytes),u=c.length/2,c.push("-i",t)}const p=e.srtContent&&e.srtContent.trim().length>0;let f=null;p&&(f=l(o,"subs.srt"),await r(f,e.srtContent));const y=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${t.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${y}, srt=${e.srtContent?.length??0}chars`);const v=w.replace(/,/g,"\\,"),h=[];let x,_=!1;p&&f?(h.push(`[0:v]subtitles=${f}:force_style=${v}[v]`),x="[v]",_=!0):x="0:v:0";const A=[];m>=0&&A.push({idx:m,isBgm:!1}),u>=0&&A.push({idx:u,isBgm:!0});let B=null;if(1===A.length)B=`${A[0].idx}:a:0`;else if(A.length>=2){const e=[];for(const t of A)t.isBgm?(h.push(`[${t.idx}:a]volume=${y}[bgm]`),e.push("[bgm]")):e.push(`[${t.idx}:a]`);h.push(`${e.join("")}amix=inputs=${e.length}:duration=first:dropout_transition=0[a]`),B="[a]"}const T=["-y",...c];h.length>0&&T.push("-filter_complex",h.join(";")),T.push("-map",x),B&&T.push("-map",B),_?T.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):T.push("-c:v","copy"),T.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",t.toFixed(3)),T.push(i),await g({args:T,timeoutMs:18e4});const $=B?await d():null;if($)try{const e=await new Promise(e=>{const t=n($,["-i",i],{stdio:["ignore","pipe","pipe"]});let o="";t.stderr.on("data",e=>o+=e.toString()),t.on("close",()=>e(o))});if(!/Audio:/.test(e))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",e.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(e){if(e instanceof Error&&e.message.includes("合成失败"))throw e}return s(i)})}async function _(e){return y(async t=>{const o=l(t,"in.mp4");await r(o,e);const s=await d();if(!s)throw new Error("ffmpeg-static 二进制缺失");return new Promise((e,t)=>{const r=n(s,["-i",o],{stdio:["ignore","pipe","pipe"]});let a="";r.stderr.on("data",e=>a+=e.toString()),r.on("close",()=>{const o=a.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!o)return t(new Error("无法解析视频时长"));const n=parseInt(o[1]),r=parseInt(o[2]),s=parseFloat(o[3]);e({durationSeconds:3600*n+60*r+s})}),r.on("error",t)})})}async function A(e,t,o="mp4",n=[],a=24e4){return y(async i=>{const c=l(i,"input.mp4"),m=l(i,`output.${o}`);await r(c,e);const u={};for(const e of n){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=l(i,t);await r(o,e.bytes),u[e.name]=o}return await g({args:t(c,m,u),timeoutMs:a}),s(m)})}function B(e,t,o){return["-y","-fflags","+genpts","-loop","1","-t","0.04","-i",t,"-i",e,"-filter_complex","[0:v][1:v]scale2ref=w=main_w:h=main_h[vcover][vmainref];[vcover]setsar=1,setpts=PTS-STARTPTS,fps=30,format=yuv420p[vcoverf];[vmainref]setsar=1,setpts=PTS-STARTPTS,fps=30,format=yuv420p[vmain];[vcoverf][vmain]concat=n=2:v=1:a=0[v]","-map","[v]","-map","1:a?","-c:v","libx264","-preset","fast","-crf","18","-pix_fmt","yuv420p","-c:a","aac","-b:a","192k","-ar","44100","-ac","2","-movflags","+faststart",o]}async function T(e,t,o="mp4",n=[],a,i=24e4){return y(async c=>{const m=l(c,"input.mp4"),u=l(c,`output.${o}`),p=l(c,`output-with-cover.${o}`);await r(m,e);const f={};for(const e of n){const t=e.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=l(c,t);await r(o,e.bytes),f[e.name]=o}if(await g({args:t(m,u,f),timeoutMs:i}),!a?.length||!function(e){const t=String(e||"").toLowerCase();return"mp4"===t||"mov"===t}(o))return s(u);const d=l(c,"video-cover."+((y=a).length>=8&&137===y[0]&&80===y[1]&&78===y[2]&&71===y[3]&&13===y[4]&&10===y[5]&&26===y[6]&&10===y[7]?"png":"jpg"));var y;return await r(d,a),await g({args:B(u,d,p),timeoutMs:i}),s(p)})}var $=1048576,O=e=>`${Math.round(e/$)}MB`,E={maxVideos:6,maxAudios:3,maxVideoBytes:512*$,maxTotalVideoBytes:1024*$,maxAudioBytes:30*$,maxOverlayBytes:20*$},b=(e,t,o)=>({ok:!1,status:e,error:t,message:o});function S(e){return e.startsWith("video/")?"mp4":e.includes("mpeg")||e.includes("mp3")?"mp3":e.includes("wav")?"wav":e.includes("aac")?"aac":e.includes("ogg")?"ogg":e.includes("mp4")||e.includes("m4a")?"m4a":e.includes("flac")?"flac":"bin"}function R(o){const n={...E,...o?.limits};return{async handle(o){const{videos:r,audios:s,overlay:a,videoCover:i,textOverlays:c=[],editStateRaw:m}=o;if(r.length>n.maxVideos)return b(413,"TOO_MANY",`视频数超过 ${n.maxVideos}`);for(let e=0;e<r.length;e++){if(!/^video\//.test(r[e].type))return b(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(r[e].bytes.length>n.maxVideoBytes)return b(413,"TOO_LARGE",`视频 ${e+1} 超过 ${O(n.maxVideoBytes)}`)}if(!r.length)return b(400,"BAD_REQUEST","缺少视频文件(video_0)");if(r.reduce((e,t)=>e+t.bytes.length,0)>n.maxTotalVideoBytes)return b(413,"TOO_LARGE",`视频总大小超过 ${O(n.maxTotalVideoBytes)},请减少素材或压缩`);if(s.length>n.maxAudios)return b(413,"TOO_MANY",`音频数超过 ${n.maxAudios}`);for(let e=0;e<s.length;e++){const t=/^audio\//.test(s[e].type),o=/^video\//.test(s[e].type);if(!t&&!o)return b(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(s[e].bytes.length>(o?n.maxVideoBytes:n.maxAudioBytes))return b(413,"TOO_LARGE",`音频 ${e+1} 超过 ${O(o?n.maxVideoBytes:n.maxAudioBytes)}`)}if("string"!=typeof m)return b(400,"BAD_REQUEST","缺少 editState");if(a&&a.length>n.maxOverlayBytes)return b(413,"OVERLAY_TOO_LARGE","overlay 过大");if(c.some(e=>e.length>n.maxOverlayBytes))return b(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(i&&i.length>n.maxOverlayBytes)return b(413,"COVER_TOO_LARGE","videoCover too large");let u;try{u=e.parse(JSON.parse(m))}catch{return b(400,"BAD_STATE","编辑参数不合法")}if(u.clips.some(e=>e.srcIdx>=r.length))return b(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((u.audioClips??[]).some(e=>e.srcIdx>=s.length))return b(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=r.map(e=>e.bytes),o=s.map(e=>e.bytes),n=new Map,m=e=>{let t=n.get(e);return t||(t=v(e),n.set(e,t)),t},l=[...new Set((u.audioClips??[]).map(e=>e.srcIdx))];for(const e of l)if(/^video\//.test(s[e].type)&&!await m(o[e]))return b(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const f=r.map(()=>!1);if(u.keepAudio){const t=[...new Set(u.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{f[t]=await m(e[t])})),f.some(Boolean)||(u.audioClips??[]).length||(u={...u,keepAudio:!1})}const d=[];for(let t=1;t<e.length;t++)d.push({name:`video_${t}.mp4`,bytes:e[t]});o.forEach((e,t)=>d.push({name:`audio_${t}.${S(s[t].type)}`,bytes:e})),a&&d.push({name:"overlay.png",bytes:a}),c.forEach((e,t)=>d.push({name:`textOverlay_${t}.png`,bytes:e}));const g=function(e){const t=String(e.exportSettings?.format??"mp4").toLowerCase();return"mov"===t||"mxf"===t?t:"mp4"}(u),y=i?.length?i:function(e){const t=e.poster;if(!t?.useAsVideoCover)return;const o=("string"==typeof t.preview?t.preview:"string"==typeof t.src?t.src:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return o?Buffer.from(o[1],"base64"):void 0}(u),h=(n,r,i)=>{const m={videoPaths:[n,...e.slice(1).map((e,t)=>i[`video_${t+1}.mp4`])],videoHasAudio:f,audioPaths:o.map((e,t)=>i[`audio_${t}.${S(s[t].type)}`]),overlayPath:a?i["overlay.png"]:void 0,textOverlayPaths:c.map((e,t)=>i[`textOverlay_${t}.png`]).filter(Boolean),overlayLayerPaths:c.map((e,t)=>i[`textOverlay_${t}.png`]).filter(Boolean)};return t(u,m,r)};return{ok:!0,bytes:y?.length?await T(e[0],h,g,d,y,28e4):await A(e[0],h,g,d,28e4),contentType:(p=g,"mov"===p?"video/quicktime":"mxf"===p?"application/mxf":"video/mp4"),filename:`edited.${g}`}}catch(e){return(e.message??"").includes("二进制缺失")?b(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):b(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}var p}}}function V(e){const t=e?.maxBytes??E.maxVideoBytes;return{async handle(e,n){if(!/^video\//.test(e.type))return b(415,"BAD_TYPE","只支持视频文件");if(e.bytes.length>t)return b(413,"TOO_LARGE",`文件超过 ${O(t)}`);if(!isFinite(n)||n<0)return b(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await A(e.bytes,(e,t)=>o(e,n,t),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(e){return(e.message??"").includes("二进制缺失")?b(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):b(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}export{E as DEFAULT_LIMITS,x as composeVideo,V as createPosterHandler,R as createVideoExportHandler,h as extractAudio,v as probeHasAudio,_ as probeVideoDuration,A as runVideoEdit,T as runVideoEditWithCover};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bylqwjc/media-editor-server",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "媒体编辑器服务端:框架无关的视频导出/封面处理器 + Web(Next)/Express 适配器(Node + ffmpeg-static)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- import{z as t}from"../zod/index.js";var e={color:t.string().max(32),width:t.number().positive().max(200)},n={rotation:t.number().min(-180).max(180).optional()},a={startAt:t.number().min(0).optional(),endAt:t.number().min(0).optional(),timelineTrack:t.number().int().min(0).max(99).optional()},i=t.discriminatedUnion("type",[t.object({type:t.literal("pen"),points:t.array(t.number()).min(4),...e,...a,...n}),t.object({type:t.literal("line"),x1:t.number(),y1:t.number(),x2:t.number(),y2:t.number(),...e,...a,...n}),t.object({type:t.literal("arrow"),x1:t.number(),y1:t.number(),x2:t.number(),y2:t.number(),...e,...a,...n}),t.object({type:t.literal("rect"),x:t.number(),y:t.number(),w:t.number(),h:t.number(),...e,...a,...n}),t.object({type:t.literal("ellipse"),x:t.number(),y:t.number(),w:t.number(),h:t.number(),...e,...a,...n}),t.object({type:t.literal("text"),x:t.number(),y:t.number(),text:t.string().max(500),color:t.string().max(32),fontSize:t.number().positive().max(512),startAt:t.number().min(0).optional(),endAt:t.number().min(0).optional(),timelineTrack:t.number().int().min(0).max(99).optional(),...n}),t.object({type:t.literal("sticker"),x:t.number().optional(),y:t.number().optional(),w:t.number().positive().optional(),h:t.number().positive().optional(),emoji:t.string().max(16).optional(),src:t.string().optional(),startAt:t.number().min(0).optional(),endAt:t.number().min(0).optional(),timelineTrack:t.number().int().min(0).max(99).optional(),...n}),t.object({type:t.literal("filter"),x:t.number().optional(),y:t.number().optional(),w:t.number().positive().optional(),h:t.number().positive().optional(),filterPreset:t.string().max(32).optional(),...a,...n}),t.object({type:t.literal("adjust"),x:t.number(),y:t.number(),w:t.number().positive(),h:t.number().positive(),brightness:t.number().min(-1).max(1).default(0),contrast:t.number().min(-1).max(1).default(0),saturation:t.number().min(-1).max(1).default(0),temperature:t.number().min(-1).max(1).default(0),tint:t.number().min(-1).max(1).default(0),vibrance:t.number().min(-1).max(1).default(0),gamma:t.number().min(-1).max(1).default(0),highlights:t.number().min(-1).max(1).default(0),shadows:t.number().min(-1).max(1).default(0),whites:t.number().min(-1).max(1).default(0),blacks:t.number().min(-1).max(1).default(0),lightSense:t.number().min(-1).max(1).default(0),sharpen:t.number().min(0).max(1).default(0),clarity:t.number().min(0).max(1).default(0),grain:t.number().min(0).max(1).default(0),fade:t.number().min(0).max(1).default(0),vignette:t.number().min(0).max(1).default(0),...a,...n})]),r=t.object({srcIdx:t.number().int().min(0).max(7),in:t.number().min(0),out:t.number().min(0)}),m=t.object({srcIdx:t.number().int().min(0).max(2),track:t.number().int().min(0).default(0),startAt:t.number().min(0),in:t.number().min(0),out:t.number().min(0),speed:t.number().min(.5).max(2).default(1),volume:t.number().min(0).max(2),fadeIn:t.number().min(0).default(0),fadeOut:t.number().min(0).default(0)}),u=t.object({type:t.enum(["none","fade","slide","wipe","flip","clockWipe","star","circle","rectangle"]).default("none"),duration:t.number().min(0).max(2).default(.4)}),o=t.object({fileName:t.string().max(120).optional(),format:t.enum(["mp4","mov","mxf"]).default("mp4"),width:t.number().int().min(2).max(8192).optional(),height:t.number().int().min(2).max(8192).optional(),frameRate:t.number().int().min(12).max(120).default(30),videoBitrateKbps:t.number().int().min(100).max(5e4).optional(),audioBitrateKbps:t.number().int().min(32).max(1024).optional(),audioCodec:t.enum(["aac","mp3"]).default("aac")}).optional(),s=t.object({clips:t.array(r).min(1).max(50),audioClips:t.array(m).max(8).default([]),canvas:t.object({w:t.number().int().min(2).max(4096),h:t.number().int().min(2).max(4096)}),crop:t.object({x:t.number().min(0),y:t.number().min(0),width:t.number().positive(),height:t.number().positive()}).optional(),scale:t.object({width:t.number().positive(),height:t.number().positive(),fit:t.enum(["contain","cover","force"])}).optional(),rotation:t.number().min(-180).max(180).default(0),flipX:t.boolean().default(!1),flipY:t.boolean().default(!1),filters:t.object({brightness:t.number().min(-1).max(1).default(0),contrast:t.number().min(-1).max(1).default(0),saturation:t.number().min(-1).max(1).default(0),temperature:t.number().min(-1).max(1).default(0),tint:t.number().min(-1).max(1).default(0),vibrance:t.number().min(-1).max(1).default(0),gamma:t.number().min(-1).max(1).default(0),highlights:t.number().min(-1).max(1).default(0),shadows:t.number().min(-1).max(1).default(0),whites:t.number().min(-1).max(1).default(0),blacks:t.number().min(-1).max(1).default(0),lightSense:t.number().min(-1).max(1).default(0),sharpen:t.number().min(0).max(1).default(0),clarity:t.number().min(0).max(1).default(0),grain:t.number().min(0).max(1).default(0),fade:t.number().min(0).max(1).default(0),vignette:t.number().min(0).max(1).default(0)}).default({brightness:0,contrast:0,saturation:0,temperature:0,tint:0,vibrance:0,gamma:0,highlights:0,shadows:0,whites:0,blacks:0,lightSense:0,sharpen:0,clarity:0,grain:0,fade:0,vignette:0}),filterPreset:t.string().max(32).optional(),overlays:t.array(i).max(200).optional(),transitions:t.array(u).max(49).default([]),keepAudio:t.boolean().default(!0),poster:t.object({time:t.number().min(0),useAsVideoCover:t.boolean().optional(),preview:t.string().optional()}).optional(),exportSettings:o}).superRefine((e,n)=>{e.clips.forEach((e,a)=>{e.out<=e.in&&n.addIssue({code:t.ZodIssueCode.custom,message:`clips[${a}].out 必须大于 in`,path:["clips",a,"out"]})}),e.audioClips?.forEach((e,a)=>{e.out<=e.in&&n.addIssue({code:t.ZodIssueCode.custom,message:`audioClips[${a}].out 必须大于 in`,path:["audioClips",a,"out"]})}),(e.canvas.w%2!=0||e.canvas.h%2!=0)&&n.addIssue({code:t.ZodIssueCode.custom,message:"canvas 宽高必须为偶数",path:["canvas"]}),null!=e.exportSettings?.width&&e.exportSettings.width%2!=0&&n.addIssue({code:t.ZodIssueCode.custom,message:"exportSettings.width 必须为偶数",path:["exportSettings","width"]}),null!=e.exportSettings?.height&&e.exportSettings.height%2!=0&&n.addIssue({code:t.ZodIssueCode.custom,message:"exportSettings.height 必须为偶数",path:["exportSettings","height"]})});function l(){return{clips:[],audioClips:[],canvas:{w:2,h:2},rotation:0,flipX:!1,flipY:!1,filters:{brightness:0,contrast:0,saturation:0,temperature:0,tint:0,vibrance:0,gamma:0,highlights:0,shadows:0,whites:0,blacks:0,lightSense:0,sharpen:0,clarity:0,grain:0,fade:0,vignette:0},transitions:[],keepAudio:!0}}var p=.1;function c(t){return t.out-t.in}function d(t){return t.reduce((t,e)=>t+c(e),0)}function b(t,e){let n=0;for(let a=0;a<e&&a<t.length;a++)n+=c(t[a]);return n}function f(t,e){if(!t.length||e<0)return null;let n=0;for(let a=0;a<t.length;a++){const i=c(t[a]);if(e<n+i)return{idx:a,srcTime:t[a].in+(e-n)};n+=i}if(Math.abs(e-n)<1e-9||e<=n){const e=t.length-1;return{idx:e,srcTime:t[e].out}}return null}function x(t,e){const n=f(t,e);if(!n)return t;const a=t[n.idx];return n.srcTime<a.in+p||n.srcTime>a.out-p?t:[...t.slice(0,n.idx),{...a,out:n.srcTime},{...a,in:n.srcTime},...t.slice(n.idx+1)]}function h(t,e){return t.length<=1||!t[e]?t:t.filter((t,n)=>n!==e)}function g(t,e){const n=t[e];return n?[...t.slice(0,e+1),{...n},...t.slice(e+1)]:t}function v(t,e,n){if(e===n||!t[e]||n<0||n>=t.length)return t;const a=[...t],[i]=a.splice(e,1);return a.splice(n,0,i),a}function y(t,e,n,a,i){if(!t[e])return t;const r=t.map(t=>({...t}));return"in"===n?r[e].in=Math.min(Math.max(a,0),r[e].out-p):r[e].out=Math.max(Math.min(a,i),r[e].in+p),r}function w(t,e,n){const a=Math.max(Math.min(t.out,e),p),i=Math.min(Math.max(t.in,0),a-p),r=Math.min(Math.max(t.startAt,0),Math.max(0,n-p)),m=Math.max(p,a-i),u=Math.min(Math.max(Number(t.speed)||1,.5),2),o=Number.isInteger(t.track)&&t.track>=0?t.track:0,s=Math.min(Math.max(Number(t.fadeIn)||0,0),m),l=Math.min(Math.max(Number(t.fadeOut)||0,0),Math.max(0,m-s));return{...t,track:o,in:i,out:a,startAt:r,speed:u,fadeIn:s,fadeOut:l}}function M(t,e=[],n=[]){const a=d(t),i=e.reduce((t,e)=>Math.max(t,e.startAt+(e.out-e.in)),0),r=function(t=[]){return(t??[]).reduce((t,e)=>{if("text"!==e?.type&&"sticker"!==e?.type)return t;const n=Number.isFinite(e?.startAt)?Math.max(0,Number(e.startAt)):0,a=Number.isFinite(e?.endAt)?Math.max(n,Number(e.endAt)):n;return Math.max(t,a)},0)}(n);return Math.max(a,i,r)}function I(t){return[...new Set(t.map(t=>t.srcIdx))].sort((t,e)=>t-e)}function j(t,e){const n=new Map(e.map((t,e)=>[t,e]));return t.map(t=>({...t,srcIdx:n.get(t.srcIdx)??0}))}function A(t){return{past:[],present:t,future:[]}}function C(t,e){if(e===t.present)return t;const n=[...t.past,t.present];return n.length>100&&n.shift(),{past:n,present:e,future:[]}}function S(t,e){return e===t.present?t:{...t,present:e}}function k(t){return t.past.length>0}function N(t){return t.future.length>0}function T(t){return k(t)?{past:t.past.slice(0,-1),present:t.past[t.past.length-1],future:[t.present,...t.future]}:t}function O(t){if(!N(t))return t;const[e,...n]=t.future;return{past:[...t.past,t.present],present:e,future:n}}export{m as AudioClip,r as Clip,p as MIN_CLIP,i as Overlay,s as VideoEditState,N as canRedo,k as canUndo,w as clampAudioClip,c as clipDuration,b as clipStartsAt,M as contentDuration,A as createHistory,l as defaultVideoEditState,g as duplicateClip,f as locateOutput,C as pushHistory,O as redo,j as remapSrcIndices,h as removeClip,v as reorderClips,S as replacePresent,x as splitAtOutput,d as totalDuration,y as trimClipEdge,T as undo,I as usedVideoSrcIndices};
@@ -1 +0,0 @@
1
- var t=Math.PI/180;function a(a,n,s){const r=h(s);if(0===r||180===r)return{w:a,h:n};if(90===r||270===r)return{w:n,h:a};const e=r*t;return{w:Math.round(Math.abs(a*Math.cos(e))+Math.abs(n*Math.sin(e))),h:Math.round(Math.abs(a*Math.sin(e))+Math.abs(n*Math.cos(e)))}}function h(t){return(t%360+360)%360}function n(t){const a=Math.round(t);return a-a%2}function s(t,a,h=40){const n=Math.min(Math.max(h,t.width),a.w),s=Math.min(Math.max(h,t.height),a.h),r=Math.min(Math.max(0,t.x),a.w-n),e=Math.min(Math.max(0,t.y),a.h-s);return{x:Math.round(r),y:Math.round(e),width:Math.round(n),height:Math.round(s)}}function r(t,a){let h=t.w,n=h/a;return n>t.h&&(n=t.h,h=n*a),{x:Math.round((t.w-h)/2),y:Math.round((t.h-n)/2),width:Math.round(h),height:Math.round(n)}}function e(t,a,h){const n={x:0,y:0,width:t.width,height:t.height},s={x:0,y:0,width:a.w,height:a.h};return"cover"===h?{src:r({w:t.width,h:t.height},a.w/a.h),dst:s}:"contain"===h?{src:n,dst:r({w:a.w,h:a.h},t.width/t.height)}:{src:n,dst:s}}function i(t,h){if(h.scale)return{w:n(h.scale.width),h:n(h.scale.height)};if(h.crop)return{w:n(h.crop.width),h:n(h.crop.height)};const s=a(t.w,t.h,h.rotation);return{w:n(s.w),h:n(s.h)}}var o=[{id:"bw",label:"黑白",vf:"hue=s=0",css:"grayscale(1)"},{id:"sepia",label:"复古",vf:"colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131",css:"sepia(0.85)"},{id:"warm",label:"暖阳",vf:"colorbalance=rs=.15:gs=.04:bs=-.15",css:"sepia(0.25) saturate(1.25) brightness(1.03)"},{id:"cool",label:"冷调",vf:"colorbalance=rs=-.14:bs=.16",css:"hue-rotate(12deg) saturate(0.95) brightness(1.02)"},{id:"vivid",label:"鲜艳",vf:"eq=saturation=1.45:contrast=1.08",css:"saturate(1.45) contrast(1.08)"}];function c(t){return t?o.find(a=>a.id===t):void 0}function u(t,a){const h=c(a)?.css??"";return[`brightness(${1+t.brightness})`,`contrast(${1+t.contrast})`,`saturate(${1+t.saturation})`,h].filter(Boolean).join(" ")}export{t as DEG2RAD,o as FILTER_PRESETS,u as buildFilterCss,r as centeredMaxRect,s as clampRect,n as evenize,c as findFilterPreset,e as fitRects,h as normalizeDeg,i as outputSize,a as rotatedSize};
@@ -1 +0,0 @@
1
- var e={"tool.trim":"剪辑","tool.crop":"裁剪","tool.tune":"调色","tool.filter":"滤镜","tool.annotate":"标注","tool.sticker":"贴纸","tool.resize":"比例","tune.brightness":"亮度","tune.contrast":"对比","tune.saturation":"饱和","tune.gamma":"Gamma","overlay.select":"选择","overlay.pen":"画笔","overlay.eraser":"橡皮","overlay.line":"直线","overlay.arrow":"箭头","overlay.rect":"矩形","overlay.ellipse":"椭圆","overlay.text":"文字","overlay.color":"线色","overlay.width":"线宽","overlay.copySelected":"复制","overlay.delSelected":"删除选中","overlay.editSelected":"编辑","overlay.pickImage":"选择图片","overlay.hint":"点 emoji/图片添加 → 画布上拖动摆位,拖右下角紫色把手缩放","overlay.textPlaceholder":"输入文字,回车确认","filter.none":"原图","filter.bw":"黑白","filter.sepia":"复古","filter.warm":"暖阳","filter.cool":"冷调","filter.vivid":"鲜艳","ratio.orig":"原始","ratio.free":"自由比例","ratio.9:16":"竖屏 9:16","ratio.16:9":"横屏 16:9","ratio.1:1":"方形 1:1","ratio.4:5":"竖图 4:5","act.fit":"适应","act.zoomFit":"缩放至适当大小","act.zoomTo":"缩放至 {value}%","act.split":"分割","act.copy":"复制","act.delete":"删除","act.restore":"还原","act.deleteMusic":"删除音乐","act.cancel":"取消","act.exportPoster":"导出封面","act.exporting":"导出中…","act.poster":"🖼 封面","act.again":"再次下载","act.saveDrive":"存网盘","act.changeImage":"← 换一张","act.changeVideo":"← 换一个","crop.rotateLeft":"↺ 向左旋转","crop.flipX":"⇋ 水平翻转","crop.flipY":"⇵ 垂直翻转","crop.rotateLeftShort":"左转","crop.flipXShort":"横翻","crop.flipYShort":"竖翻","tip.close":"关闭(换文件)","tip.undo":"撤销","tip.redo":"重做","tip.resetOpened":"重置到打开文件时","tip.previewZoom":"预览缩放比例","tip.previewZoomOut":"缩小预览","tip.previewZoomIn":"放大预览","tip.previewZoomFit":"适应预览","tip.previewPointer":"指针工具","tip.previewPan":"手工具:拖动画面","tip.poster":"预览并选择封面帧","tip.keepAudio":"原视频声音开关(与音乐混音;预览同步)","tip.split":"在播放头处把当前片段一分为二","tip.zoomOut":"缩小时间轴(Ctrl+滚轮)","tip.zoomIn":"放大时间轴(Ctrl+滚轮)","tip.fit":"缩放到适应宽度","tip.addVideo":"添加视频片段","tip.addAudio":"添加音乐(音频文件,或选含声音的视频自动提取音轨;与原声混音)","tip.dupClip":"复制该片段(插到其后)","tip.delClip":"删除该片段","tip.lockAspect":"锁定纵横比","tip.dblConfirm":"双击确认裁剪","err.notVideo":"请选择视频文件","err.maxVideo":"最多 {max} 个视频素材","err.videoLoad":"视频加载失败,请换一个文件","err.notAudio":"请选择音频文件(mp3/wav/m4a),或含声音的视频(自动提取音轨)","err.maxAudio":"最多 {max} 个音频素材","err.audioLoad":"音频加载失败,请换一个文件","err.processFailed":"处理失败","err.posterFailed":"封面渲染失败","err.canvas":"canvas 不可用","err.imageRead":"图片读取失败,请换一张","err.notImage":"请选择图片文件","err.fileType":"只支持图片或视频文件","err.saveDrive":"存网盘失败","msg.processing":"正在导出中…","msg.exportingHint":"请稍候,导出完成后会自动返回结果。","msg.done":"完成 ✓","msg.loading":"素材加载中…","msg.exported":"已导出:","msg.saving":"存网盘中…","msg.saved":"已存到网盘 ✓","lbl.ratioSection":"画面比例(裁剪,不变形)","lbl.dragToReposition":"↔ 拖动预览可调整裁剪位置","lbl.sizeSection":"导出尺寸(像素;比例不符时 cover 居中裁剪)","lbl.currentOut":"当前输出","lbl.zoomSize":"大小","lbl.noScale":"(未缩放)","lbl.coverNote":"· 比例不一致时居中裁剪(cover),不变形","lbl.audioTrackHint":"♪ 音频轨(右侧 + 添加音乐,与原声混音)","lbl.volume":"音量","lbl.volNote":"(预览最大 100%,导出按设定值)","lbl.music":"音乐","kbd.fit":"⇧ F","poster.title":"选择封面帧","panel.title":"媒体编辑器","panel.subtitle":"上传图片 / 视频,裁剪 · 滤镜 · 文字 · 导出","panel.pick":"点击选择图片或视频","panel.pickHint":"图片用 Filerobot · 视频可裁剪/静音/封面"},o={"简":"簡","体":"體","图":"圖","频":"頻","视":"視","辑":"輯","调":"調","滤":"濾","标":"標","注":"註","贴":"貼","选":"選","择":"擇","线":"線","箭":"箭","头":"頭","复":"複","删":"刪","除":"除","编":"編","边":"邊","输":"輸","入":"入","确":"確","认":"認","饱":"飽","对":"對","暖":"暖","阳":"陽","冷":"冷","纵":"縱","横":"橫","适":"適","应":"應","缩":"縮","实":"實","际":"際","导":"導","出":"出","封":"封","面":"面","换":"換","张":"張","预":"預","览":"覽","动":"動","声":"聲","开":"開","关":"關","与":"與","乐":"樂","混":"混","处":"處","载":"載","错":"錯","误":"誤","读":"讀","仅":"僅","终":"終","稍":"稍","候":"候","返回":"返回","当前":"目前","输出":"輸出","画":"畫","变":"變","音频":"音訊","音轨":"音軌","添加":"新增","删除":"刪除","保存":"儲存","加载":"載入","网盘":"網盤"},t=e=>{let t=e;for(const[e,a]of Object.entries(o).sort((e,o)=>o[0].length-e[0].length))t=t.replaceAll(e,a);return t},a={zh:e,en:{"tool.trim":"Trim","tool.crop":"Crop","tool.tune":"Tune","tool.filter":"Filters","tool.annotate":"Annotate","tool.sticker":"Stickers","tool.resize":"Ratio","tune.brightness":"Brightness","tune.contrast":"Contrast","tune.saturation":"Saturation","tune.gamma":"Gamma","overlay.select":"Select","overlay.pen":"Pen","overlay.eraser":"Eraser","overlay.line":"Line","overlay.arrow":"Arrow","overlay.rect":"Rect","overlay.ellipse":"Ellipse","overlay.text":"Text","overlay.color":"Color","overlay.width":"Width","overlay.copySelected":"Copy","overlay.delSelected":"Delete selected","overlay.editSelected":"Edit","overlay.pickImage":"Pick image","overlay.hint":"Tap emoji/image to add → drag to place, drag the purple corner handle to scale","overlay.textPlaceholder":"Type text, Enter to confirm","filter.none":"Original","filter.bw":"B&W","filter.sepia":"Sepia","filter.warm":"Warm","filter.cool":"Cool","filter.vivid":"Vivid","ratio.orig":"Original","ratio.free":"Free ratio","ratio.9:16":"Portrait 9:16","ratio.16:9":"Landscape 16:9","ratio.1:1":"Square 1:1","ratio.4:5":"Vertical 4:5","act.fit":"Fit","act.zoomFit":"Zoom to fit","act.zoomTo":"Zoom to {value}%","act.split":"Split","act.copy":"Copy","act.delete":"Delete","act.restore":"Reset","act.deleteMusic":"Remove music","act.cancel":"Cancel","act.exportPoster":"Export cover","act.exporting":"Exporting…","act.poster":"🖼 Cover","act.again":"Download again","act.saveDrive":"Save to drive","act.changeImage":"← Change image","act.changeVideo":"← Change file","crop.rotateLeft":"↺ Rotate left","crop.flipX":"⇋ Flip horizontal","crop.flipY":"⇵ Flip vertical","crop.rotateLeftShort":"Rot","crop.flipXShort":"Flip","crop.flipYShort":"Flip","tip.close":"Close (change file)","tip.undo":"Undo","tip.redo":"Redo","tip.resetOpened":"Reset to opened file","tip.previewZoom":"Preview zoom","tip.previewZoomOut":"Zoom out preview","tip.previewZoomIn":"Zoom in preview","tip.previewZoomFit":"Fit preview","tip.previewPointer":"Pointer tool","tip.previewPan":"Hand tool: pan canvas","tip.poster":"Preview and pick a cover frame","tip.keepAudio":"Original audio on/off (mixed with music; preview follows)","tip.split":"Split the current clip at the playhead","tip.zoomOut":"Zoom out timeline (Ctrl+wheel)","tip.zoomIn":"Zoom in timeline (Ctrl+wheel)","tip.fit":"Fit to width","tip.addVideo":"Add video clip","tip.addAudio":"Add music (audio file, or a video with sound to extract its track; mixed with original)","tip.dupClip":"Duplicate clip (insert after)","tip.delClip":"Delete clip","tip.lockAspect":"Lock aspect ratio","tip.dblConfirm":"Double-click to confirm crop","err.notVideo":"Please choose a video file","err.maxVideo":"Up to {max} video sources","err.videoLoad":"Failed to load video, try another file","err.notAudio":"Choose an audio file (mp3/wav/m4a), or a video with sound (audio auto-extracted)","err.maxAudio":"Up to {max} audio sources","err.audioLoad":"Failed to load audio, try another file","err.processFailed":"Processing failed","err.posterFailed":"Failed to render cover","err.canvas":"Canvas unavailable","err.imageRead":"Failed to read image, try another","err.notImage":"Please choose an image file","err.fileType":"Only image or video files are supported","err.saveDrive":"Failed to save to drive","msg.processing":"Exporting…","msg.exportingHint":"Please wait. The result will be returned automatically when export finishes.","msg.done":"Done ✓","msg.loading":"Loading media…","msg.exported":"Exported: ","msg.saving":"Saving…","msg.saved":"Saved to drive ✓","lbl.ratioSection":"Frame ratio (crop, no distortion)","lbl.dragToReposition":"↔ Drag the preview to reposition the crop","lbl.sizeSection":"Export size (px; cover center-crop when ratios differ)","lbl.currentOut":"Output","lbl.zoomSize":"Size","lbl.noScale":"(no scaling)","lbl.coverNote":"· center-cropped (cover) when ratios differ, no distortion","lbl.audioTrackHint":"♪ Audio track (+ on the right to add music, mixed with original)","lbl.volume":"Volume","lbl.volNote":"(preview caps at 100%, export uses the set value)","lbl.music":"Music","kbd.fit":"⇧ F","poster.title":"Pick a cover frame","panel.title":"Media Editor","panel.subtitle":"Upload an image / video — crop · filters · text · export","panel.pick":"Click to choose an image or video","panel.pickHint":"Images via Filerobot · videos: trim/mute/cover"},es:{"tool.trim":"Editar","tool.crop":"Recortar","tool.tune":"Ajustar color","tool.filter":"Filtros","tool.annotate":"Anotar","tool.sticker":"Stickers","tool.resize":"Proporción","tune.brightness":"Brillo","tune.contrast":"Contraste","tune.saturation":"Saturación","tune.gamma":"Gamma","overlay.select":"Seleccionar","overlay.pen":"Pincel","overlay.eraser":"Borrador","overlay.line":"Línea","overlay.arrow":"Flecha","overlay.rect":"Rectángulo","overlay.ellipse":"Elipse","overlay.text":"Texto","overlay.color":"Color","overlay.width":"Grosor","overlay.copySelected":"Copiar","overlay.delSelected":"Eliminar seleccionado","overlay.editSelected":"Editar","overlay.pickImage":"Elegir imagen","overlay.hint":"Toca un emoji/imagen para añadirlo → arrastra para colocarlo, arrastra el tirador morado para escalar","overlay.textPlaceholder":"Escribe texto, Enter para confirmar","filter.none":"Original","filter.bw":"Blanco y negro","filter.sepia":"Sepia","filter.warm":"Cálido","filter.cool":"Frío","filter.vivid":"Intenso","ratio.orig":"Original","ratio.free":"Libre","ratio.9:16":"Vertical 9:16","ratio.16:9":"Horizontal 16:9","ratio.1:1":"Cuadrado 1:1","ratio.4:5":"Vertical 4:5","act.fit":"Ajustar","act.zoomFit":"Ajustar a pantalla","act.zoomTo":"Zoom al {value}%","act.split":"Dividir","act.copy":"Copiar","act.delete":"Eliminar","act.restore":"Restablecer","act.deleteMusic":"Quitar música","act.cancel":"Cancelar","act.exportPoster":"Exportar portada","act.exporting":"Exportando…","act.poster":"🖼 Portada","act.again":"Descargar de nuevo","act.saveDrive":"Guardar en disco","act.changeImage":"← Cambiar imagen","act.changeVideo":"← Cambiar archivo","crop.rotateLeft":"↺ Girar a la izquierda","crop.flipX":"⇋ Voltear horizontal","crop.flipY":"⇵ Voltear vertical","crop.rotateLeftShort":"Giro","crop.flipXShort":"Flip","crop.flipYShort":"Flip","tip.close":"Cerrar (cambiar archivo)","tip.undo":"Deshacer","tip.redo":"Rehacer","tip.resetOpened":"Restablecer al archivo abierto","tip.previewZoom":"Zoom de vista previa","tip.previewZoomOut":"Reducir vista previa","tip.previewZoomIn":"Ampliar vista previa","tip.previewZoomFit":"Ajustar vista previa","tip.previewPointer":"Herramienta de puntero","tip.previewPan":"Mano: desplazar lienzo","tip.poster":"Previsualizar y elegir portada","tip.keepAudio":"Activar/desactivar audio original (mezclado con música; la vista previa lo refleja)","tip.split":"Dividir el clip actual en el cursor","tip.zoomOut":"Reducir línea de tiempo (Ctrl+rueda)","tip.zoomIn":"Ampliar línea de tiempo (Ctrl+rueda)","tip.fit":"Ajustar al ancho","tip.addVideo":"Añadir clip de vídeo","tip.addAudio":"Añadir música (archivo de audio o vídeo con sonido para extraer la pista; se mezcla con el original)","tip.dupClip":"Duplicar clip (insertar después)","tip.delClip":"Eliminar clip","tip.lockAspect":"Bloquear proporción","tip.dblConfirm":"Doble clic para confirmar recorte","err.notVideo":"Elige un archivo de vídeo","err.maxVideo":"Máximo {max} vídeos","err.videoLoad":"No se pudo cargar el vídeo, prueba con otro archivo","err.notAudio":"Elige un audio (mp3/wav/m4a) o un vídeo con sonido (se extrae automáticamente)","err.maxAudio":"Máximo {max} audios","err.audioLoad":"No se pudo cargar el audio, prueba con otro archivo","err.processFailed":"Error de procesamiento","err.posterFailed":"No se pudo renderizar la portada","err.canvas":"Canvas no disponible","err.imageRead":"No se pudo leer la imagen, prueba con otra","err.notImage":"Elige un archivo de imagen","err.fileType":"Solo se admiten archivos de imagen o vídeo","err.saveDrive":"No se pudo guardar en disco","msg.processing":"Exportando…","msg.exportingHint":"Espera un momento. El resultado se devolverá automáticamente al finalizar.","msg.done":"Listo ✓","msg.loading":"Cargando medios…","msg.exported":"Exportado: ","msg.saving":"Guardando…","msg.saved":"Guardado en disco ✓","lbl.ratioSection":"Proporción de imagen (recorte, sin deformar)","lbl.dragToReposition":"↔ Arrastra la vista previa para reposicionar el recorte","lbl.sizeSection":"Tamaño de exportación (px; recorte cover centrado si la proporción difiere)","lbl.currentOut":"Salida","lbl.zoomSize":"Tamaño","lbl.noScale":"(sin escala)","lbl.coverNote":"· recorte centrado (cover) cuando la proporción difiere, sin deformar","lbl.audioTrackHint":"♪ Pista de audio (+ a la derecha para añadir música, mezclada con el original)","lbl.volume":"Volumen","lbl.volNote":"(la vista previa limita a 100%, la exportación usa el valor definido)","lbl.music":"Música","kbd.fit":"⇧ F","poster.title":"Elegir portada","panel.title":"Editor multimedia","panel.subtitle":"Sube una imagen / vídeo — recorta · filtros · texto · exporta","panel.pick":"Haz clic para elegir una imagen o vídeo","panel.pickHint":"Imágenes con Filerobot · vídeos: cortar/silenciar/portada"},"zh-Hant":Object.fromEntries(Object.entries(e).map(([e,o])=>[e,t(o)]))};function i(e,o,t){let i=a[e]?.[o]??a.zh[o]??o;if(t)for(const[e,o]of Object.entries(t))i=i.replaceAll(`{${e}}`,String(o));return i}var r=[{platform:"tiktok",postType:"video",label:"TikTok Video",shortLabel:"TT·Video",ratio:"9:16",imageSize:"1440x2560",videoRatio:"9:16",videoResolution:"720p"},{platform:"instagram",postType:"post",label:"Instagram Post",shortLabel:"IG·Post",ratio:"1:1",imageSize:"2048x2048"},{platform:"instagram",postType:"reels",label:"Instagram Reels",shortLabel:"IG·Reels",ratio:"9:16",imageSize:"1440x2560",videoRatio:"9:16",videoResolution:"720p"},{platform:"instagram",postType:"story",label:"Instagram Story",shortLabel:"IG·Story",ratio:"9:16",imageSize:"1440x2560",videoRatio:"9:16",videoResolution:"720p"},{platform:"facebook",postType:"post",label:"Facebook Post",shortLabel:"FB·Post",ratio:"1:1",imageSize:"2048x2048"},{platform:"facebook",postType:"reels",label:"Facebook Reels",shortLabel:"FB·Reels",ratio:"9:16",imageSize:"1440x2560",videoRatio:"9:16",videoResolution:"720p"},{platform:"youtube",postType:"video",label:"YouTube Video",shortLabel:"YT·Video",ratio:"16:9",imageSize:"2560x1440",videoRatio:"16:9",videoResolution:"1080p"},{platform:"youtube",postType:"shorts",label:"YouTube Shorts",shortLabel:"YT·Shorts",ratio:"9:16",imageSize:"1440x2560",videoRatio:"9:16",videoResolution:"720p"},{platform:"pinterest",postType:"pin",label:"Pinterest Pin",shortLabel:"PIN",ratio:"2:3",imageSize:"1664x2496",videoRatio:"9:16",videoResolution:"720p"},{platform:"x",postType:"post",label:"X Post",shortLabel:"X·Post",ratio:"16:9",imageSize:"2560x1440",videoRatio:"16:9",videoResolution:"720p"},{platform:"linkedin",postType:"post",label:"LinkedIn Post",shortLabel:"LI·Post",ratio:"1:1",imageSize:"2048x2048"},{platform:"linkedin",postType:"video",label:"LinkedIn Video",shortLabel:"LI·Video",ratio:"16:9",imageSize:"2560x1440",videoRatio:"16:9",videoResolution:"1080p"},{platform:"threads",postType:"post",label:"Threads Post",shortLabel:"TH·Post",ratio:"1:1",imageSize:"2048x2048"},{platform:"telegram",postType:"post",label:"Telegram Post",shortLabel:"TG·Post",ratio:"1:1",imageSize:"2048x2048",videoRatio:"9:16",videoResolution:"720p"},{platform:"reddit",postType:"post",label:"Reddit Post",shortLabel:"RD·Post",ratio:"16:9",imageSize:"2560x1440",videoRatio:"16:9",videoResolution:"720p"}],l={"1:1":"方形 1:1","4:5":"竖图 4:5","3:4":"竖图 3:4","9:16":"竖屏 9:16","16:9":"横屏 16:9","2:3":"竖图 2:3","1.91:1":"横幅 1.91:1"};function p(e){const[o,t]=e.split(":").map(Number);return o&&t?o/t:1}function n(e){const o=new Set,t=[];for(const a of e)!a||o.has(a)||(o.add(a),t.push({ratio:a,value:p(a),label:l[a]??a}));return t}var s=n(["1:1","4:5","3:4","2:3","9:16","16:9","1.91:1"]),c=n(["9:16","16:9","1:1","4:5"]);function d(e=r){const o=new Set,t=[];for(const a of e){if(!a.videoRatio)continue;const e=`${a.platform}:${a.postType}:${a.videoRatio}`;if(o.has(e))continue;o.add(e);const i=a.label.split(" ").slice(0,-1).join(" ")||a.label;t.push({platform:a.platform,postType:a.postType,label:`${i} ${a.videoRatio}`,ratio:a.videoRatio,value:p(a.videoRatio)})}return t}var m=d();function v(e=r){const o=new Map;for(const t of e){const e=o.get(t.platform);(!e||"post"!==e.postType&&"post"===t.postType)&&o.set(t.platform,t)}return Array.from(o.values()).map(e=>{const[o,t]=e.imageSize.split("x").map(Number),a=e.label.split(" ").slice(0,-1).join(" ")||e.label;return{platform:e.platform,label:`${a} ${e.ratio}`,ratio:e.ratio,value:p(e.ratio),w:o,h:t}})}var u=v();function g(e,o){return e>=1?{w:2*Math.round(o*e/2),h:o}:{w:o,h:2*Math.round(o/e/2)}}function f(e=r){const o=new Set,t=[];for(const a of e){if(!a.videoRatio||!a.videoResolution)continue;const e="1080p"===a.videoResolution?1080:720,{w:i,h:r}=g(p(a.videoRatio),e),l=`${i}x${r}`;o.has(l)||(o.add(l),t.push({label:`${a.shortLabel} ${i}×${r}`,w:i,h:r}))}return t}var b=f();function h(e,o){if(e.isDuplicated)return{};if(0!==(e.rotation??0))return{};const t=o.adjustments?.rotation??0;return t?{rotation:-t}:{}}export{r as DEFAULT_PLATFORM_TARGETS,a as EDITOR_MESSAGES,u as IMAGE_PLATFORM_PRESETS,s as IMAGE_RATIO_PRESETS,m as VIDEO_PLATFORM_RATIO_PRESETS,c as VIDEO_RATIO_PRESETS,b as VIDEO_SIZE_PRESETS,v as buildImagePlatformPresets,d as buildVideoPlatformRatioPresets,f as buildVideoSizePresets,h as compensateAnnotationRotation,p as parseRatio,i as tEditor,t as toHant};