@bylqwjc/media-editor-server 1.0.25 → 1.0.26

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.25",
3
+ "version": "1.0.26",
4
4
  "description": "媒体编辑器框架无关内核:状态/时间轴/ffmpeg 参数/几何/标注渲染/VideoEditorEngine/图片编辑器纯配置工厂(零 React)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +1 @@
1
- "use strict";var __defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,__export=(e,r)=>{for(var t in r)__defProp(e,t,{get:r[t],enumerable:!0})},__copyProps=(e,r,t,o)=>{if(r&&"object"==typeof r||"function"==typeof r)for(let s of __getOwnPropNames(r))__hasOwnProp.call(e,s)||s===t||__defProp(e,s,{get:()=>r[s],enumerable:!(o=__getOwnPropDesc(r,s))||o.enumerable});return e},__toCommonJS=e=>__copyProps(__defProp({},"__esModule",{value:!0}),e),express_exports={};__export(express_exports,{expressPoster:()=>expressPoster,expressVideoExport:()=>expressVideoExport}),module.exports=__toCommonJS(express_exports);var MAX_SLOTS=16;function filesArray(e){return Array.isArray(e.files)?e.files:e.files&&"object"==typeof e.files?Object.values(e.files).flat():[]}function collect(e,r){const t=[];for(let o=0;;o++){const s=e.find(e=>e.fieldname===`${r}_${o}`);if(!s)break;t.push({type:s.mimetype,bytes:s.buffer})}return t}function collectSlots(e,r){const t=[];for(let o=0;o<MAX_SLOTS;o++){const s=e.find(e=>e.fieldname===`${r}_${o}`);s&&(t[o]={type:s.mimetype,bytes:s.buffer})}return t}function bodyString(e,r){const t=e?.[r];return"string"==typeof t?t||void 0:Array.isArray(t)&&"string"==typeof t[0]&&t[0]||void 0}function bodyNumber(e,r){const t=bodyString(e,r);if(!t)return;const o=Number(t);return Number.isFinite(o)?o:void 0}function collectRemoteVideos(e){const r=[];for(let t=0;t<MAX_SLOTS;t++){const o=bodyString(e,`videoUrl_${t}`);o&&(r[t]={url:o,name:bodyString(e,`videoName_${t}`),type:bodyString(e,`videoType_${t}`),size:bodyNumber(e,`videoSize_${t}`)})}return r}function reply(e,r){if(r.ok)return e.setHeader("Content-Type",r.contentType),e.setHeader("Content-Disposition",`attachment; filename="${r.filename}"`),e.setHeader("Cache-Control","no-store"),void e.send(r.bytes);console.error("[media-editor-server] export rejected",{status:r.status,error:r.error,message:r.message}),e.status(r.status).json({error:r.error,message:r.message})}function fail(e,r,t){console.error(`[media-editor-server] ${r} failed`,t),e.headersSent||e.status(500).json({error:"INTERNAL_ERROR",message:t instanceof Error&&t.message?t.message:"导出服务异常"})}function expressVideoExport(e){return async(r,t)=>{try{const o=filesArray(r),s=o.find(e=>"overlay"===e.fieldname);if(s&&"image/png"!==s.mimetype)return void reply(t,{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(t,{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(t,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const a={videos:collectSlots(o,"video"),videoUrls:collectRemoteVideos(r.body),audios:collect(o,"audio"),overlay:s?.buffer,videoCover:i?.buffer,textOverlays:n.map(e=>e.bytes),editStateRaw:"string"==typeof r.body?.editState?r.body.editState:""};reply(t,await e.handle(a))}catch(e){fail(t,"video export",e)}}}function expressPoster(e){return async(r,t)=>{try{const o=filesArray(r).find(e=>"file"===e.fieldname),s=r.body?.time;if(!o||"string"!=typeof s)return void reply(t,{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"});reply(t,await e.handle({type:o.mimetype,bytes:o.buffer},Number(s)))}catch(e){fail(t,"poster export",e)}}}
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={};__export(express_exports,{expressPoster:()=>expressPoster,expressVideoExport:()=>expressVideoExport}),module.exports=__toCommonJS(express_exports);var MAX_SLOTS=16;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 collectSlots(e,t){const r=[];for(let o=0;o<MAX_SLOTS;o++){const s=e.find(e=>e.fieldname===`${t}_${o}`);s&&(r[o]={type:s.mimetype,bytes:s.buffer})}return r}function bodyString(e,t){const r=e?.[t];return"string"==typeof r?r||void 0:Array.isArray(r)&&"string"==typeof r[0]&&r[0]||void 0}function bodyNumber(e,t){const r=bodyString(e,t);if(!r)return;const o=Number(r);return Number.isFinite(o)?o:void 0}function collectRemoteVideos(e){const t=[];for(let r=0;r<MAX_SLOTS;r++){const o=bodyString(e,`videoUrl_${r}`);o&&(t[r]={url:o,name:bodyString(e,`videoName_${r}`),type:bodyString(e,`videoType_${r}`),size:bodyNumber(e,`videoSize_${r}`)})}return t}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:collectSlots(o,"video"),videoUrls:collectRemoteVideos(t.body),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"})}}
@@ -1,4 +1,4 @@
1
- import { P as PosterHandler, V as VideoExportHandler } from '../handler-B8iJZVfU.js';
1
+ import { P as PosterHandler, V as VideoExportHandler } from '../handler-BiOIAlrh.js';
2
2
 
3
3
  /**
4
4
  * Express 适配器。用法:
@@ -19,7 +19,6 @@ interface ReqLike {
19
19
  body?: Record<string, unknown>;
20
20
  }
21
21
  interface ResLike {
22
- headersSent?: boolean;
23
22
  status(code: number): ResLike;
24
23
  json(body: unknown): void;
25
24
  setHeader(k: string, v: string): void;
@@ -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 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 r(e,t){const r=[];for(let o=0;o<16;o++){const s=e.find(e=>e.fieldname===`${t}_${o}`);s&&(r[o]={type:s.mimetype,bytes:s.buffer})}return r}function o(e,t){const r=e?.[t];return"string"==typeof r?r||void 0:Array.isArray(r)&&"string"==typeof r[0]&&r[0]||void 0}function s(e,t){const r=o(e,t);if(!r)return;const s=Number(r);return Number.isFinite(s)?s:void 0}function i(e){const t=[];for(let r=0;r<16;r++){const i=o(e,`videoUrl_${r}`);i&&(t[r]={url:i,name:o(e,`videoName_${r}`),type:o(e,`videoType_${r}`),size:s(e,`videoSize_${r}`)})}return t}function n(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);console.error("[media-editor-server] export rejected",{status:t.status,error:t.error,message:t.message}),e.status(t.status).json({error:t.error,message:t.message})}function a(e,t,r){console.error(`[media-editor-server] ${t} failed`,r),e.headersSent||e.status(500).json({error:"INTERNAL_ERROR",message:r instanceof Error&&r.message?r.message:"导出服务异常"})}function f(o){return async(s,f)=>{try{const a=e(s),d=a.find(e=>"overlay"===e.fieldname);if(d&&"image/png"!==d.mimetype)return void n(f,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const u=t(a,"textOverlay");if(u.some(e=>"image/png"!==e.type))return void n(f,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay must be PNG"});const m=a.find(e=>"videoCover"===e.fieldname);if(m&&!/^image\/(?:png|jpe?g)$/.test(m.mimetype))return void n(f,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const y={videos:r(a,"video"),videoUrls:i(s.body),audios:t(a,"audio"),overlay:d?.buffer,videoCover:m?.buffer,textOverlays:u.map(e=>e.bytes),editStateRaw:"string"==typeof s.body?.editState?s.body.editState:""};n(f,await o.handle(y))}catch(e){a(f,"video export",e)}}}function d(t){return async(r,o)=>{try{const s=e(r).find(e=>"file"===e.fieldname),i=r.body?.time;if(!s||"string"!=typeof i)return void n(o,{ok:!1,status:400,error:"BAD_REQUEST",message:"缺少 file 或 time"});n(o,await t.handle({type:s.mimetype,bytes:s.buffer},Number(i)))}catch(e){a(o,"poster export",e)}}}export{d as expressPoster,f 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 o=0;;o++){const i=e.find(e=>e.fieldname===`${t}_${o}`);if(!i)break;r.push({type:i.mimetype,bytes:i.buffer})}return r}function r(e,t){const r=[];for(let o=0;o<16;o++){const i=e.find(e=>e.fieldname===`${t}_${o}`);i&&(r[o]={type:i.mimetype,bytes:i.buffer})}return r}function o(e,t){const r=e?.[t];return"string"==typeof r?r||void 0:Array.isArray(r)&&"string"==typeof r[0]&&r[0]||void 0}function i(e,t){const r=o(e,t);if(!r)return;const i=Number(r);return Number.isFinite(i)?i:void 0}function n(e){const t=[];for(let r=0;r<16;r++){const n=o(e,`videoUrl_${r}`);n&&(t[r]={url:n,name:o(e,`videoName_${r}`),type:o(e,`videoType_${r}`),size:i(e,`videoSize_${r}`)})}return t}function s(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 a(o){return async(i,a)=>{const f=e(i),u=f.find(e=>"overlay"===e.fieldname);if(u&&"image/png"!==u.mimetype)return void s(a,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay 必须是 PNG"});const d=t(f,"textOverlay");if(d.some(e=>"image/png"!==e.type))return void s(a,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay must be PNG"});const y=f.find(e=>"videoCover"===e.fieldname);if(y&&!/^image\/(?:png|jpe?g)$/.test(y.mimetype))return void s(a,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPEG"});const m={videos:r(f,"video"),videoUrls:n(i.body),audios:t(f,"audio"),overlay:u?.buffer,videoCover:y?.buffer,textOverlays:d.map(e=>e.bytes),editStateRaw:"string"==typeof i.body?.editState?i.body.editState:""};s(a,await o.handle(m))}}function f(t){return async(r,o)=>{const i=e(r).find(e=>"file"===e.fieldname),n=r.body?.time;s(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{f as expressPoster,a as expressVideoExport};
@@ -1,4 +1,4 @@
1
- import { P as PosterHandler, V as VideoExportHandler } from '../handler-B8iJZVfU.js';
1
+ import { P as PosterHandler, V as VideoExportHandler } from '../handler-BiOIAlrh.js';
2
2
 
3
3
  /**
4
4
  * Web 标准适配器(Next.js App Router 路由 / 任何接收 Request 返回 Response 的运行时)。
@@ -45,7 +45,6 @@ interface VideoExportHandler {
45
45
  }
46
46
  declare function createVideoExportHandler(opts?: {
47
47
  limits?: Partial<ExportLimits>;
48
- remoteVideoTimeoutMs?: number;
49
48
  }): VideoExportHandler;
50
49
  interface PosterHandler {
51
50
  handle(file: UploadFile, time: number): Promise<ExportResult>;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
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 u=SUBTITLE_STYLE.replace(/,/g,"\\,"),l=[];let d,_=!1;p&&m?(l.push(`[0:v]subtitles=${m}:force_style=${u}[v]`),d="[v]",_=!0):d="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?(l.push(`[${t.idx}:a]volume=${c}[bgm]`),e.push("[bgm]")):e.push(`[${t.idx}:a]`);l.push(`${e.join("")}amix=inputs=${e.length}:duration=first:dropout_transition=0[a]`),g="[a]"}const h=["-y",...n];l.length>0&&h.push("-filter_complex",l.join(";")),h.push("-map",d),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 u=(0,import_node_path.join)(s,`video-cover.${coverFileExtFromBytes(i)}`);return await(0,import_promises.writeFile)(u,i),await runFfmpeg({args:coverConcatArgs(p,u,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 isExportErr(e){return"object"==typeof e&&null!==e&&"ok"in e&&!1===e.ok}var DEFAULT_REMOTE_VIDEO_TIMEOUT_MS=6e5,MIN_REMOTE_VIDEO_TIMEOUT_MS=12e4,REMOTE_VIDEO_TIMEOUT_BYTES_PER_MS=512;function remoteVideoTimeoutMs(e,t,r){if(null!=r)return Math.max(1,r);const o=e.size&&e.size>0?Math.min(e.size,t):t,i=Math.ceil(o/REMOTE_VIDEO_TIMEOUT_BYTES_PER_MS)+3e4;return Math.max(MIN_REMOTE_VIDEO_TIMEOUT_MS,Math.min(DEFAULT_REMOTE_VIDEO_TIMEOUT_MS,i))}function inferVideoTypeFromUrl(e){const t=(()=>{try{return new URL(e).pathname.toLowerCase()}catch{return e.toLowerCase()}})();return t.endsWith(".mov")?"video/quicktime":t.endsWith(".webm")?"video/webm":t.endsWith(".mkv")?"video/x-matroska":t.endsWith(".mxf")?"application/mxf":t.endsWith(".avi")?"video/x-msvideo":"video/mp4"}function resolveRemoteVideoType(e,t){if(e.type?.startsWith("video/")||"application/mxf"===e.type)return e.type;const r=t?.split(";")[0]?.trim().toLowerCase()??"";return r.startsWith("video/")||"application/mxf"===r||"application/octet-stream"!==r&&r?r:inferVideoTypeFromUrl(e.url)}async function downloadRemoteVideo(e,t,r){let o;try{o=new URL(e.url)}catch{return err(400,"BAD_REMOTE_URL","videoUrl 不是合法 URL")}if("http:"!==o.protocol&&"https:"!==o.protocol)return err(400,"BAD_REMOTE_URL","videoUrl 只支持 http/https");if(null!=e.size&&e.size>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);const i=new AbortController,n=setTimeout(()=>i.abort(),remoteVideoTimeoutMs(e,t,r));try{const r=await fetch(o,{signal:i.signal});if(!r.ok)return err(502,"REMOTE_FETCH_FAILED",`远程视频下载失败(${r.status})`);if(Number(r.headers.get("content-length")??0)>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);const n=[];let s=0;if(r.body){const e=r.body.getReader();for(;;){const{done:r,value:o}=await e.read();if(r)break;if(s+=o.byteLength,s>t)return await e.cancel().catch(()=>{}),err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);n.push(Buffer.from(o))}}else{const e=Buffer.from(await r.arrayBuffer());if(e.length>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);n.push(e),s=e.length}return s<=0?err(502,"REMOTE_FETCH_FAILED","远程视频为空"):{type:resolveRemoteVideoType(e,r.headers.get("content-type")),bytes:Buffer.concat(n,s)}}catch(e){const t="AbortError"===e.name?"远程视频下载超时":"远程视频下载失败";return err(502,"REMOTE_FETCH_FAILED",t)}finally{clearTimeout(n)}}async function resolveVideoInputs(e,t,r,o){const i=[],n=Math.max(e.length,t?.length??0);for(let s=0;s<n;s++){const n=e[s];if(n){i[s]=n;continue}const a=t?.[s];if(!a?.url)continue;const p=await downloadRemoteVideo(a,r,o);if(isExportErr(p))return p;i[s]=p}return i.filter(Boolean)}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(r){const{videos:o,videoUrls:i,audios:n,overlay:s,videoCover:a,textOverlays:p=[],editStateRaw:m}=r,c=await resolveVideoInputs(o,i,t.maxVideoBytes,e?.remoteVideoTimeoutMs);if(isExportErr(c))return c;const u=c;if(u.length>t.maxVideos)return err(413,"TOO_MANY",`视频数超过 ${t.maxVideos}`);for(let e=0;e<u.length;e++){if(!/^video\//.test(u[e].type))return err(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(u[e].bytes.length>t.maxVideoBytes)return err(413,"TOO_LARGE",`视频 ${e+1} 超过 ${limitLabel(t.maxVideoBytes)}`)}if(!u.length)return err(400,"BAD_REQUEST","缺少视频文件(video_0)");if(u.reduce((e,t)=>e+t.bytes.length,0)>t.maxTotalVideoBytes)return err(413,"TOO_LARGE",`视频总大小超过 ${limitLabel(t.maxTotalVideoBytes)},请减少素材或压缩`);if(n.length>t.maxAudios)return err(413,"TOO_MANY",`音频数超过 ${t.maxAudios}`);for(let e=0;e<n.length;e++){const r=/^audio\//.test(n[e].type),o=/^video\//.test(n[e].type);if(!r&&!o)return err(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(n[e].bytes.length>(o?t.maxVideoBytes:t.maxAudioBytes))return err(413,"TOO_LARGE",`音频 ${e+1} 超过 ${limitLabel(o?t.maxVideoBytes:t.maxAudioBytes)}`)}if("string"!=typeof m)return err(400,"BAD_REQUEST","缺少 editState");if(s&&s.length>t.maxOverlayBytes)return err(413,"OVERLAY_TOO_LARGE","overlay 过大");if(p.some(e=>e.length>t.maxOverlayBytes))return err(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(a&&a.length>t.maxOverlayBytes)return err(413,"COVER_TOO_LARGE","videoCover too large");let l;try{l=import_core.VideoEditState.parse(JSON.parse(m))}catch{return err(400,"BAD_STATE","编辑参数不合法")}if(l.clips.some(e=>e.srcIdx>=u.length))return err(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((l.audioClips??[]).some(e=>e.srcIdx>=n.length))return err(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=u.map(e=>e.bytes),t=n.map(e=>e.bytes),r=new Map,o=e=>{let t=r.get(e);return t||(t=probeHasAudio(e),r.set(e,t)),t},i=[...new Set((l.audioClips??[]).map(e=>e.srcIdx))];for(const e of i)if(/^video\//.test(n[e].type)&&!await o(t[e]))return err(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const m=u.map(()=>!1);if(l.keepAudio){const t=[...new Set(l.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{m[t]=await o(e[t])})),m.some(Boolean)||(l.audioClips??[]).length||(l={...l,keepAudio:!1})}const c=[];for(let t=1;t<e.length;t++)c.push({name:`video_${t}.mp4`,bytes:e[t]});t.forEach((e,t)=>c.push({name:`audio_${t}.${audioExt(n[t].type)}`,bytes:e})),s&&c.push({name:"overlay.png",bytes:s}),p.forEach((e,t)=>c.push({name:`textOverlay_${t}.png`,bytes:e}));const d=exportFormat(l),_=a?.length?a:posterPreviewBytes(l),f=(r,o,i)=>{const a={videoPaths:[r,...e.slice(1).map((e,t)=>i[`video_${t+1}.mp4`])],videoHasAudio:m,audioPaths:t.map((e,t)=>i[`audio_${t}.${audioExt(n[t].type)}`]),overlayPath:s?i["overlay.png"]:void 0,textOverlayPaths:p.map((e,t)=>i[`textOverlay_${t}.png`]).filter(Boolean),overlayLayerPaths:p.map((e,t)=>i[`textOverlay_${t}.png`]).filter(Boolean)};return(0,import_core2.buildFfmpegArgs)(l,a,o)};return{ok:!0,bytes:_?.length?await runVideoEditWithCover(e[0],f,d,c,_,28e4):await runVideoEdit(e[0],f,d,c,28e4),contentType:videoContentType(d),filename:`edited.${d}`}}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","封面抽取失败,请重试")}}}}
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 l=SUBTITLE_STYLE.replace(/,/g,"\\,"),u=[];let d,f=!1;p&&m?(u.push(`[0:v]subtitles=${m}:force_style=${l}[v]`),d="[v]",f=!0):d="0:v:0";const _=[];s>=0&&_.push({idx:s,isBgm:!1}),a>=0&&_.push({idx:a,isBgm:!0});let g=null;if(1===_.length)g=`${_[0].idx}:a:0`;else if(_.length>=2){const e=[];for(const t of _)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",d),g&&h.push("-map",g),f?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 l=(0,import_node_path.join)(s,`video-cover.${coverFileExtFromBytes(i)}`);return await(0,import_promises.writeFile)(l,i),await runFfmpeg({args:coverConcatArgs(p,l,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 isExportErr(e){return"object"==typeof e&&null!==e&&"ok"in e&&!1===e.ok}var REMOTE_VIDEO_TIMEOUT_MS=12e4;function inferVideoTypeFromUrl(e){const t=(()=>{try{return new URL(e).pathname.toLowerCase()}catch{return e.toLowerCase()}})();return t.endsWith(".mov")?"video/quicktime":t.endsWith(".webm")?"video/webm":t.endsWith(".mkv")?"video/x-matroska":t.endsWith(".mxf")?"application/mxf":t.endsWith(".avi")?"video/x-msvideo":"video/mp4"}function resolveRemoteVideoType(e,t){if(e.type?.startsWith("video/")||"application/mxf"===e.type)return e.type;const r=t?.split(";")[0]?.trim().toLowerCase()??"";return r.startsWith("video/")||"application/mxf"===r||"application/octet-stream"!==r&&r?r:inferVideoTypeFromUrl(e.url)}async function downloadRemoteVideo(e,t){let r;try{r=new URL(e.url)}catch{return err(400,"BAD_REMOTE_URL","videoUrl 不是合法 URL")}if("http:"!==r.protocol&&"https:"!==r.protocol)return err(400,"BAD_REMOTE_URL","videoUrl 只支持 http/https");if(null!=e.size&&e.size>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);const o=new AbortController,i=setTimeout(()=>o.abort(),REMOTE_VIDEO_TIMEOUT_MS);try{const i=await fetch(r,{signal:o.signal});if(!i.ok)return err(502,"REMOTE_FETCH_FAILED",`远程视频下载失败(${i.status})`);if(Number(i.headers.get("content-length")??0)>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);const n=[];let s=0;if(i.body){const e=i.body.getReader();for(;;){const{done:r,value:o}=await e.read();if(r)break;if(s+=o.byteLength,s>t)return await e.cancel().catch(()=>{}),err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);n.push(Buffer.from(o))}}else{const e=Buffer.from(await i.arrayBuffer());if(e.length>t)return err(413,"TOO_LARGE",`视频超过 ${limitLabel(t)}`);n.push(e),s=e.length}return s<=0?err(502,"REMOTE_FETCH_FAILED","远程视频为空"):{type:resolveRemoteVideoType(e,i.headers.get("content-type")),bytes:Buffer.concat(n,s)}}catch(e){const t="AbortError"===e.name?"远程视频下载超时":"远程视频下载失败";return err(502,"REMOTE_FETCH_FAILED",t)}finally{clearTimeout(i)}}async function resolveVideoInputs(e,t,r){const o=[],i=Math.max(e.length,t?.length??0);for(let n=0;n<i;n++){const i=e[n];if(i){o[n]=i;continue}const s=t?.[n];if(!s?.url)continue;const a=await downloadRemoteVideo(s,r);if(isExportErr(a))return a;o[n]=a}return o.filter(Boolean)}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,videoUrls:o,audios:i,overlay:n,videoCover:s,textOverlays:a=[],editStateRaw:p}=e,m=await resolveVideoInputs(r,o,t.maxVideoBytes);if(isExportErr(m))return m;const c=m;if(c.length>t.maxVideos)return err(413,"TOO_MANY",`视频数超过 ${t.maxVideos}`);for(let e=0;e<c.length;e++){if(!/^video\//.test(c[e].type))return err(415,"BAD_TYPE",`video_${e} 不是视频文件`);if(c[e].bytes.length>t.maxVideoBytes)return err(413,"TOO_LARGE",`视频 ${e+1} 超过 ${limitLabel(t.maxVideoBytes)}`)}if(!c.length)return err(400,"BAD_REQUEST","缺少视频文件(video_0)");if(c.reduce((e,t)=>e+t.bytes.length,0)>t.maxTotalVideoBytes)return err(413,"TOO_LARGE",`视频总大小超过 ${limitLabel(t.maxTotalVideoBytes)},请减少素材或压缩`);if(i.length>t.maxAudios)return err(413,"TOO_MANY",`音频数超过 ${t.maxAudios}`);for(let e=0;e<i.length;e++){const r=/^audio\//.test(i[e].type),o=/^video\//.test(i[e].type);if(!r&&!o)return err(415,"BAD_TYPE",`audio_${e} 不是音频或视频文件`);if(i[e].bytes.length>(o?t.maxVideoBytes:t.maxAudioBytes))return err(413,"TOO_LARGE",`音频 ${e+1} 超过 ${limitLabel(o?t.maxVideoBytes:t.maxAudioBytes)}`)}if("string"!=typeof p)return err(400,"BAD_REQUEST","缺少 editState");if(n&&n.length>t.maxOverlayBytes)return err(413,"OVERLAY_TOO_LARGE","overlay 过大");if(a.some(e=>e.length>t.maxOverlayBytes))return err(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(s&&s.length>t.maxOverlayBytes)return err(413,"COVER_TOO_LARGE","videoCover too large");let l;try{l=import_core.VideoEditState.parse(JSON.parse(p))}catch{return err(400,"BAD_STATE","编辑参数不合法")}if(l.clips.some(e=>e.srcIdx>=c.length))return err(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((l.audioClips??[]).some(e=>e.srcIdx>=i.length))return err(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const e=c.map(e=>e.bytes),t=i.map(e=>e.bytes),r=new Map,o=e=>{let t=r.get(e);return t||(t=probeHasAudio(e),r.set(e,t)),t},p=[...new Set((l.audioClips??[]).map(e=>e.srcIdx))];for(const e of p)if(/^video\//.test(i[e].type)&&!await o(t[e]))return err(400,"BAD_TYPE",`音频 ${e+1} 是视频文件但没有音轨,无法提取声音`);const m=c.map(()=>!1);if(l.keepAudio){const t=[...new Set(l.clips.map(e=>e.srcIdx))];await Promise.all(t.map(async t=>{m[t]=await o(e[t])})),m.some(Boolean)||(l.audioClips??[]).length||(l={...l,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(i[t].type)}`,bytes:e})),n&&u.push({name:"overlay.png",bytes:n}),a.forEach((e,t)=>u.push({name:`textOverlay_${t}.png`,bytes:e}));const d=exportFormat(l),f=s?.length?s:posterPreviewBytes(l),_=(r,o,s)=>{const p={videoPaths:[r,...e.slice(1).map((e,t)=>s[`video_${t+1}.mp4`])],videoHasAudio:m,audioPaths:t.map((e,t)=>s[`audio_${t}.${audioExt(i[t].type)}`]),overlayPath:n?s["overlay.png"]:void 0,textOverlayPaths:a.map((e,t)=>s[`textOverlay_${t}.png`]).filter(Boolean),overlayLayerPaths:a.map((e,t)=>s[`textOverlay_${t}.png`]).filter(Boolean)};return(0,import_core2.buildFfmpegArgs)(l,p,o)};return{ok:!0,bytes:f?.length?await runVideoEditWithCover(e[0],_,d,u,f,28e4):await runVideoEdit(e[0],_,d,u,28e4),contentType:videoContentType(d),filename:`edited.${d}`}}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, R as RemoteUploadSource, U as UploadFile, V as VideoExportHandler, e as createPosterHandler, f as createVideoExportHandler } from './handler-B8iJZVfU.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, R as RemoteUploadSource, U as UploadFile, V as VideoExportHandler, e as createPosterHandler, f as createVideoExportHandler } from './handler-BiOIAlrh.js';
2
2
 
3
3
  /**
4
4
  * ffmpeg-runner: 视频/音频处理工具
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{VideoEditState as t}from"../core/dist/index.js";import{buildFfmpegArgs as e,buildPosterArgs as n}from"../core/dist/index.js";import{spawn as o}from"child_process";import{writeFile as r,readFile as i,mkdtemp as s,rm as a,access as c}from"fs/promises";import{constants as u}from"fs";import{tmpdir as m}from"os";import{dirname as l,join as p}from"path";var f;async function d(){return void 0!==f||(f=await async function(){const t=[],e=e=>{t.push(e),e.toLowerCase().endsWith(".exe")||t.push(`${e}.exe`)};try{const t=(await import("ffmpeg-static")).default;"string"==typeof t&&e(t)}catch{}try{const{createRequire:t}=await import("module"),n=t(import.meta.url).resolve("ffmpeg-static/package.json"),o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),r=o>=0?n.substring(0,o):n;e(p(r,"ffmpeg"))}catch{}(()=>{let t=process.cwd();for(;;){e(p(t,"node_modules","ffmpeg-static","ffmpeg")),e(p(t,"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));const n=l(t);if(n===t)break;t=n}})();for(const e of t)if(e&&!e.startsWith("/ROOT/"))try{return await c(e,u.X_OK),e}catch{}return null}())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),f}async function g({args:t,timeoutMs:e=18e4}){const n=await d();if(!n)throw new Error("ffmpeg-static 二进制缺失 — 请检查 node_modules/ffmpeg-static/ffmpeg 是否存在");return console.log("[ffmpeg-runner] args:",JSON.stringify(t)),new Promise((r,i)=>{const s=o(n,t,{stdio:["ignore","pipe","pipe"]});let a="",c=!1;const u=setTimeout(()=>{c=!0,s.kill("SIGKILL"),i(new Error(`ffmpeg 超时 (${e}ms)`))},e);s.stderr.on("data",t=>{a+=t.toString(),a.length>8192&&(a=a.slice(-8192))}),s.on("close",t=>{if(clearTimeout(u),c)return;const e=a.slice(-2500);console.log(`[ffmpeg-runner] exit=${t}, stderr tail:\n${e}\n---end---`),0===t?r():i(new Error(`ffmpeg 退出码 ${t}: ${e.slice(-800)}`))}),s.on("error",t=>{clearTimeout(u),i(t)})})}async function y(t){const e=await s(p(m(),"vtrans-"));try{return await t(e)}finally{a(e,{recursive:!0,force:!0}).catch(()=>{})}}async function h(t){const e=await d();return!!e&&y(async n=>{const i=p(n,"probe.mp4");return await r(i,t),new Promise(t=>{const n=o(e,["-hide_banner","-i",i],{stdio:["ignore","ignore","pipe"]});let r="";const s=setTimeout(()=>{n.kill("SIGKILL"),t(!1)},15e3);n.stderr.on("data",t=>{r+=t.toString(),r.length>16384&&(r=r.slice(-16384))}),n.on("close",()=>{clearTimeout(s),t(/Stream #[^\n]*Audio:/.test(r))}),n.on("error",()=>{clearTimeout(s),t(!1)})})})}async function v(t){return y(async e=>{const n=p(e,"in.mp4"),o=p(e,"audio.mp3");return await r(n,t),await g({args:["-y","-i",n,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",o],timeoutMs:6e4}),i(o)})}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(t){const e=(await _(t.videoBytes)).durationSeconds;return y(async n=>{const s=p(n,"in.mp4"),a=p(n,"out.mp4");await r(s,t.videoBytes);const c=["-i",s];let u=-1,m=-1;if(t.newAudioBytes){const e=p(n,"new.mp3");await r(e,t.newAudioBytes),u=(c.length-0)/2,c.push("-i",e)}if(t.bgmAudioBytes){const e=p(n,"bgm.mp3");await r(e,t.bgmAudioBytes),m=c.length/2,c.push("-i",e)}const l=t.srtContent&&t.srtContent.trim().length>0;let f=null;l&&(f=p(n,"subs.srt"),await r(f,t.srtContent));const y=t.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${t.videoBytes.length}B (${e.toFixed(2)}s), newAudio=${t.newAudioBytes?.length??0}B, bgm=${t.bgmAudioBytes?.length??0}B@${y}, srt=${t.srtContent?.length??0}chars`);const h=w.replace(/,/g,"\\,"),v=[];let x,_=!1;l&&f?(v.push(`[0:v]subtitles=${f}:force_style=${h}[v]`),x="[v]",_=!0):x="0:v:0";const A=[];u>=0&&A.push({idx:u,isBgm:!1}),m>=0&&A.push({idx:m,isBgm:!0});let E=null;if(1===A.length)E=`${A[0].idx}:a:0`;else if(A.length>=2){const t=[];for(const e of A)e.isBgm?(v.push(`[${e.idx}:a]volume=${y}[bgm]`),t.push("[bgm]")):t.push(`[${e.idx}:a]`);v.push(`${t.join("")}amix=inputs=${t.length}:duration=first:dropout_transition=0[a]`),E="[a]"}const T=["-y",...c];v.length>0&&T.push("-filter_complex",v.join(";")),T.push("-map",x),E&&T.push("-map",E),_?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",e.toFixed(3)),T.push(a),await g({args:T,timeoutMs:18e4});const B=E?await d():null;if(B)try{const t=await new Promise(t=>{const e=o(B,["-i",a],{stdio:["ignore","pipe","pipe"]});let n="";e.stderr.on("data",t=>n+=t.toString()),e.on("close",()=>t(n))});if(!/Audio:/.test(t))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",t.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(t){if(t instanceof Error&&t.message.includes("合成失败"))throw t}return i(a)})}async function _(t){return y(async e=>{const n=p(e,"in.mp4");await r(n,t);const i=await d();if(!i)throw new Error("ffmpeg-static 二进制缺失");return new Promise((t,e)=>{const r=o(i,["-i",n],{stdio:["ignore","pipe","pipe"]});let s="";r.stderr.on("data",t=>s+=t.toString()),r.on("close",()=>{const n=s.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!n)return e(new Error("无法解析视频时长"));const o=parseInt(n[1]),r=parseInt(n[2]),i=parseFloat(n[3]);t({durationSeconds:3600*o+60*r+i})}),r.on("error",e)})})}async function A(t,e,n="mp4",o=[],s=24e4){return y(async a=>{const c=p(a,"input.mp4"),u=p(a,`output.${n}`);await r(c,t);const m={};for(const t of o){const e=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),n=p(a,e);await r(n,t.bytes),m[t.name]=n}return await g({args:e(c,u,m),timeoutMs:s}),i(u)})}function E(t,e,n){return["-y","-fflags","+genpts","-loop","1","-t","0.04","-i",e,"-i",t,"-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",n]}async function T(t,e,n="mp4",o=[],s,a=24e4){return y(async c=>{const u=p(c,"input.mp4"),m=p(c,`output.${n}`),l=p(c,`output-with-cover.${n}`);await r(u,t);const f={};for(const t of o){const e=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),n=p(c,e);await r(n,t.bytes),f[t.name]=n}if(await g({args:e(u,m,f),timeoutMs:a}),!s?.length||!function(t){const e=String(t||"").toLowerCase();return"mp4"===e||"mov"===e}(n))return i(m);const d=p(c,"video-cover."+((y=s).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,s),await g({args:E(m,d,l),timeoutMs:a}),i(l)})}var B=1048576,O=t=>`${Math.round(t/B)}MB`,b={maxVideos:6,maxAudios:3,maxVideoBytes:512*B,maxTotalVideoBytes:1024*B,maxAudioBytes:30*B,maxOverlayBytes:20*B},$=(t,e,n)=>({ok:!1,status:t,error:e,message:n});function R(t){return"object"==typeof t&&null!==t&&"ok"in t&&!1===t.ok}function L(t,e){if(t.type?.startsWith("video/")||"application/mxf"===t.type)return t.type;const n=e?.split(";")[0]?.trim().toLowerCase()??"";return n.startsWith("video/")||"application/mxf"===n||"application/octet-stream"!==n&&n?n:function(t){const e=(()=>{try{return new URL(t).pathname.toLowerCase()}catch{return t.toLowerCase()}})();return e.endsWith(".mov")?"video/quicktime":e.endsWith(".webm")?"video/webm":e.endsWith(".mkv")?"video/x-matroska":e.endsWith(".mxf")?"application/mxf":e.endsWith(".avi")?"video/x-msvideo":"video/mp4"}(t.url)}async function S(t,e,n){let o;try{o=new URL(t.url)}catch{return $(400,"BAD_REMOTE_URL","videoUrl 不是合法 URL")}if("http:"!==o.protocol&&"https:"!==o.protocol)return $(400,"BAD_REMOTE_URL","videoUrl 只支持 http/https");if(null!=t.size&&t.size>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);const r=new AbortController,i=setTimeout(()=>r.abort(),function(t,e,n){if(null!=n)return Math.max(1,n);const o=t.size&&t.size>0?Math.min(t.size,e):e,r=Math.ceil(o/512)+3e4;return Math.max(12e4,Math.min(6e5,r))}(t,e,n));try{const n=await fetch(o,{signal:r.signal});if(!n.ok)return $(502,"REMOTE_FETCH_FAILED",`远程视频下载失败(${n.status})`);if(Number(n.headers.get("content-length")??0)>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);const i=[];let s=0;if(n.body){const t=n.body.getReader();for(;;){const{done:n,value:o}=await t.read();if(n)break;if(s+=o.byteLength,s>e)return await t.cancel().catch(()=>{}),$(413,"TOO_LARGE",`视频超过 ${O(e)}`);i.push(Buffer.from(o))}}else{const t=Buffer.from(await n.arrayBuffer());if(t.length>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);i.push(t),s=t.length}return s<=0?$(502,"REMOTE_FETCH_FAILED","远程视频为空"):{type:L(t,n.headers.get("content-type")),bytes:Buffer.concat(i,s)}}catch(t){const e="AbortError"===t.name?"远程视频下载超时":"远程视频下载失败";return $(502,"REMOTE_FETCH_FAILED",e)}finally{clearTimeout(i)}}function C(t){return t.startsWith("video/")?"mp4":t.includes("mpeg")||t.includes("mp3")?"mp3":t.includes("wav")?"wav":t.includes("aac")?"aac":t.includes("ogg")?"ogg":t.includes("mp4")||t.includes("m4a")?"m4a":t.includes("flac")?"flac":"bin"}function M(n){const o={...b,...n?.limits};return{async handle(r){const{videos:i,videoUrls:s,audios:a,overlay:c,videoCover:u,textOverlays:m=[],editStateRaw:l}=r,p=await async function(t,e,n,o){const r=[],i=Math.max(t.length,e?.length??0);for(let s=0;s<i;s++){const i=t[s];if(i){r[s]=i;continue}const a=e?.[s];if(!a?.url)continue;const c=await S(a,n,o);if(R(c))return c;r[s]=c}return r.filter(Boolean)}(i,s,o.maxVideoBytes,n?.remoteVideoTimeoutMs);if(R(p))return p;const f=p;if(f.length>o.maxVideos)return $(413,"TOO_MANY",`视频数超过 ${o.maxVideos}`);for(let t=0;t<f.length;t++){if(!/^video\//.test(f[t].type))return $(415,"BAD_TYPE",`video_${t} 不是视频文件`);if(f[t].bytes.length>o.maxVideoBytes)return $(413,"TOO_LARGE",`视频 ${t+1} 超过 ${O(o.maxVideoBytes)}`)}if(!f.length)return $(400,"BAD_REQUEST","缺少视频文件(video_0)");if(f.reduce((t,e)=>t+e.bytes.length,0)>o.maxTotalVideoBytes)return $(413,"TOO_LARGE",`视频总大小超过 ${O(o.maxTotalVideoBytes)},请减少素材或压缩`);if(a.length>o.maxAudios)return $(413,"TOO_MANY",`音频数超过 ${o.maxAudios}`);for(let t=0;t<a.length;t++){const e=/^audio\//.test(a[t].type),n=/^video\//.test(a[t].type);if(!e&&!n)return $(415,"BAD_TYPE",`audio_${t} 不是音频或视频文件`);if(a[t].bytes.length>(n?o.maxVideoBytes:o.maxAudioBytes))return $(413,"TOO_LARGE",`音频 ${t+1} 超过 ${O(n?o.maxVideoBytes:o.maxAudioBytes)}`)}if("string"!=typeof l)return $(400,"BAD_REQUEST","缺少 editState");if(c&&c.length>o.maxOverlayBytes)return $(413,"OVERLAY_TOO_LARGE","overlay 过大");if(m.some(t=>t.length>o.maxOverlayBytes))return $(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(u&&u.length>o.maxOverlayBytes)return $(413,"COVER_TOO_LARGE","videoCover too large");let d;try{d=t.parse(JSON.parse(l))}catch{return $(400,"BAD_STATE","编辑参数不合法")}if(d.clips.some(t=>t.srcIdx>=f.length))return $(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((d.audioClips??[]).some(t=>t.srcIdx>=a.length))return $(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const t=f.map(t=>t.bytes),n=a.map(t=>t.bytes),o=new Map,r=t=>{let e=o.get(t);return e||(e=h(t),o.set(t,e)),e},i=[...new Set((d.audioClips??[]).map(t=>t.srcIdx))];for(const t of i)if(/^video\//.test(a[t].type)&&!await r(n[t]))return $(400,"BAD_TYPE",`音频 ${t+1} 是视频文件但没有音轨,无法提取声音`);const s=f.map(()=>!1);if(d.keepAudio){const e=[...new Set(d.clips.map(t=>t.srcIdx))];await Promise.all(e.map(async e=>{s[e]=await r(t[e])})),s.some(Boolean)||(d.audioClips??[]).length||(d={...d,keepAudio:!1})}const l=[];for(let e=1;e<t.length;e++)l.push({name:`video_${e}.mp4`,bytes:t[e]});n.forEach((t,e)=>l.push({name:`audio_${e}.${C(a[e].type)}`,bytes:t})),c&&l.push({name:"overlay.png",bytes:c}),m.forEach((t,e)=>l.push({name:`textOverlay_${e}.png`,bytes:t}));const p=function(t){const e=String(t.exportSettings?.format??"mp4").toLowerCase();return"mov"===e||"mxf"===e?e:"mp4"}(d),y=u?.length?u:function(t){const e=t.poster;if(!e?.useAsVideoCover)return;const n=("string"==typeof e.preview?e.preview:"string"==typeof e.src?e.src:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return n?Buffer.from(n[1],"base64"):void 0}(d),v=(o,r,i)=>{const u={videoPaths:[o,...t.slice(1).map((t,e)=>i[`video_${e+1}.mp4`])],videoHasAudio:s,audioPaths:n.map((t,e)=>i[`audio_${e}.${C(a[e].type)}`]),overlayPath:c?i["overlay.png"]:void 0,textOverlayPaths:m.map((t,e)=>i[`textOverlay_${e}.png`]).filter(Boolean),overlayLayerPaths:m.map((t,e)=>i[`textOverlay_${e}.png`]).filter(Boolean)};return e(d,u,r)};return{ok:!0,bytes:y?.length?await T(t[0],v,p,l,y,28e4):await A(t[0],v,p,l,28e4),contentType:(g=p,"mov"===g?"video/quicktime":"mxf"===g?"application/mxf":"video/mp4"),filename:`edited.${p}`}}catch(t){return(t.message??"").includes("二进制缺失")?$(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):$(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}var g}}}function V(t){const e=t?.maxBytes??b.maxVideoBytes;return{async handle(t,o){if(!/^video\//.test(t.type))return $(415,"BAD_TYPE","只支持视频文件");if(t.bytes.length>e)return $(413,"TOO_LARGE",`文件超过 ${O(e)}`);if(!isFinite(o)||o<0)return $(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await A(t.bytes,(t,e)=>n(t,o,e),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(t){return(t.message??"").includes("二进制缺失")?$(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):$(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}export{b as DEFAULT_LIMITS,x as composeVideo,V as createPosterHandler,M as createVideoExportHandler,v as extractAudio,h as probeHasAudio,_ as probeVideoDuration,A as runVideoEdit,T as runVideoEditWithCover};
1
+ import{VideoEditState as t}from"../core/dist/index.js";import{buildFfmpegArgs as e,buildPosterArgs as o}from"../core/dist/index.js";import{spawn as n}from"child_process";import{writeFile as r,readFile as s,mkdtemp as i,rm as a,access as c}from"fs/promises";import{constants as u}from"fs";import{tmpdir as m}from"os";import{dirname as l,join as p}from"path";var f;async function d(){return void 0!==f||(f=await async function(){const t=[],e=e=>{t.push(e),e.toLowerCase().endsWith(".exe")||t.push(`${e}.exe`)};try{const t=(await import("ffmpeg-static")).default;"string"==typeof t&&e(t)}catch{}try{const{createRequire:t}=await import("module"),o=t(import.meta.url).resolve("ffmpeg-static/package.json"),n=Math.max(o.lastIndexOf("/"),o.lastIndexOf("\\")),r=n>=0?o.substring(0,n):o;e(p(r,"ffmpeg"))}catch{}(()=>{let t=process.cwd();for(;;){e(p(t,"node_modules","ffmpeg-static","ffmpeg")),e(p(t,"node_modules",".pnpm","ffmpeg-static@5.3.0","node_modules","ffmpeg-static","ffmpeg"));const o=l(t);if(o===t)break;t=o}})();for(const e of t)if(e&&!e.startsWith("/ROOT/"))try{return await c(e,u.X_OK),e}catch{}return null}())||console.warn("[ffmpeg-runner] ffmpeg-static 二进制未找到 (尝试了多个 fallback 路径)"),f}async function g({args:t,timeoutMs:e=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(t)),new Promise((r,s)=>{const i=n(o,t,{stdio:["ignore","pipe","pipe"]});let a="",c=!1;const u=setTimeout(()=>{c=!0,i.kill("SIGKILL"),s(new Error(`ffmpeg 超时 (${e}ms)`))},e);i.stderr.on("data",t=>{a+=t.toString(),a.length>8192&&(a=a.slice(-8192))}),i.on("close",t=>{if(clearTimeout(u),c)return;const e=a.slice(-2500);console.log(`[ffmpeg-runner] exit=${t}, stderr tail:\n${e}\n---end---`),0===t?r():s(new Error(`ffmpeg 退出码 ${t}: ${e.slice(-800)}`))}),i.on("error",t=>{clearTimeout(u),s(t)})})}async function y(t){const e=await i(p(m(),"vtrans-"));try{return await t(e)}finally{a(e,{recursive:!0,force:!0}).catch(()=>{})}}async function h(t){const e=await d();return!!e&&y(async o=>{const s=p(o,"probe.mp4");return await r(s,t),new Promise(t=>{const o=n(e,["-hide_banner","-i",s],{stdio:["ignore","ignore","pipe"]});let r="";const i=setTimeout(()=>{o.kill("SIGKILL"),t(!1)},15e3);o.stderr.on("data",t=>{r+=t.toString(),r.length>16384&&(r=r.slice(-16384))}),o.on("close",()=>{clearTimeout(i),t(/Stream #[^\n]*Audio:/.test(r))}),o.on("error",()=>{clearTimeout(i),t(!1)})})})}async function v(t){return y(async e=>{const o=p(e,"in.mp4"),n=p(e,"audio.mp3");return await r(o,t),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 _(t){const e=(await x(t.videoBytes)).durationSeconds;return y(async o=>{const i=p(o,"in.mp4"),a=p(o,"out.mp4");await r(i,t.videoBytes);const c=["-i",i];let u=-1,m=-1;if(t.newAudioBytes){const e=p(o,"new.mp3");await r(e,t.newAudioBytes),u=(c.length-0)/2,c.push("-i",e)}if(t.bgmAudioBytes){const e=p(o,"bgm.mp3");await r(e,t.bgmAudioBytes),m=c.length/2,c.push("-i",e)}const l=t.srtContent&&t.srtContent.trim().length>0;let f=null;l&&(f=p(o,"subs.srt"),await r(f,t.srtContent));const y=t.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${t.videoBytes.length}B (${e.toFixed(2)}s), newAudio=${t.newAudioBytes?.length??0}B, bgm=${t.bgmAudioBytes?.length??0}B@${y}, srt=${t.srtContent?.length??0}chars`);const h=w.replace(/,/g,"\\,"),v=[];let _,x=!1;l&&f?(v.push(`[0:v]subtitles=${f}:force_style=${h}[v]`),_="[v]",x=!0):_="0:v:0";const A=[];u>=0&&A.push({idx:u,isBgm:!1}),m>=0&&A.push({idx:m,isBgm:!0});let E=null;if(1===A.length)E=`${A[0].idx}:a:0`;else if(A.length>=2){const t=[];for(const e of A)e.isBgm?(v.push(`[${e.idx}:a]volume=${y}[bgm]`),t.push("[bgm]")):t.push(`[${e.idx}:a]`);v.push(`${t.join("")}amix=inputs=${t.length}:duration=first:dropout_transition=0[a]`),E="[a]"}const T=["-y",...c];v.length>0&&T.push("-filter_complex",v.join(";")),T.push("-map",_),E&&T.push("-map",E),x?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",e.toFixed(3)),T.push(a),await g({args:T,timeoutMs:18e4});const B=E?await d():null;if(B)try{const t=await new Promise(t=>{const e=n(B,["-i",a],{stdio:["ignore","pipe","pipe"]});let o="";e.stderr.on("data",t=>o+=t.toString()),e.on("close",()=>t(o))});if(!/Audio:/.test(t))throw console.error("[ffmpeg-runner] 产物缺少音轨,ffmpeg 流概览:\n",t.slice(-2e3)),new Error("合成失败: 输出 mp4 缺少音轨 (检查 filter_complex 语法)");console.log("[ffmpeg-runner] composeVideo 完成,已含音视频双流")}catch(t){if(t instanceof Error&&t.message.includes("合成失败"))throw t}return s(a)})}async function x(t){return y(async e=>{const o=p(e,"in.mp4");await r(o,t);const s=await d();if(!s)throw new Error("ffmpeg-static 二进制缺失");return new Promise((t,e)=>{const r=n(s,["-i",o],{stdio:["ignore","pipe","pipe"]});let i="";r.stderr.on("data",t=>i+=t.toString()),r.on("close",()=>{const o=i.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!o)return e(new Error("无法解析视频时长"));const n=parseInt(o[1]),r=parseInt(o[2]),s=parseFloat(o[3]);t({durationSeconds:3600*n+60*r+s})}),r.on("error",e)})})}async function A(t,e,o="mp4",n=[],i=24e4){return y(async a=>{const c=p(a,"input.mp4"),u=p(a,`output.${o}`);await r(c,t);const m={};for(const t of n){const e=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=p(a,e);await r(o,t.bytes),m[t.name]=o}return await g({args:e(c,u,m),timeoutMs:i}),s(u)})}function E(t,e,o){return["-y","-fflags","+genpts","-loop","1","-t","0.04","-i",e,"-i",t,"-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(t,e,o="mp4",n=[],i,a=24e4){return y(async c=>{const u=p(c,"input.mp4"),m=p(c,`output.${o}`),l=p(c,`output-with-cover.${o}`);await r(u,t);const f={};for(const t of n){const e=t.name.replace(/[^a-zA-Z0-9._-]/g,"_"),o=p(c,e);await r(o,t.bytes),f[t.name]=o}if(await g({args:e(u,m,f),timeoutMs:a}),!i?.length||!function(t){const e=String(t||"").toLowerCase();return"mp4"===e||"mov"===e}(o))return s(m);const d=p(c,"video-cover."+((y=i).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,i),await g({args:E(m,d,l),timeoutMs:a}),s(l)})}var B=1048576,O=t=>`${Math.round(t/B)}MB`,b={maxVideos:6,maxAudios:3,maxVideoBytes:512*B,maxTotalVideoBytes:1024*B,maxAudioBytes:30*B,maxOverlayBytes:20*B},$=(t,e,o)=>({ok:!1,status:t,error:e,message:o});function R(t){return"object"==typeof t&&null!==t&&"ok"in t&&!1===t.ok}function L(t,e){if(t.type?.startsWith("video/")||"application/mxf"===t.type)return t.type;const o=e?.split(";")[0]?.trim().toLowerCase()??"";return o.startsWith("video/")||"application/mxf"===o||"application/octet-stream"!==o&&o?o:function(t){const e=(()=>{try{return new URL(t).pathname.toLowerCase()}catch{return t.toLowerCase()}})();return e.endsWith(".mov")?"video/quicktime":e.endsWith(".webm")?"video/webm":e.endsWith(".mkv")?"video/x-matroska":e.endsWith(".mxf")?"application/mxf":e.endsWith(".avi")?"video/x-msvideo":"video/mp4"}(t.url)}async function S(t,e){let o;try{o=new URL(t.url)}catch{return $(400,"BAD_REMOTE_URL","videoUrl 不是合法 URL")}if("http:"!==o.protocol&&"https:"!==o.protocol)return $(400,"BAD_REMOTE_URL","videoUrl 只支持 http/https");if(null!=t.size&&t.size>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);const n=new AbortController,r=setTimeout(()=>n.abort(),12e4);try{const r=await fetch(o,{signal:n.signal});if(!r.ok)return $(502,"REMOTE_FETCH_FAILED",`远程视频下载失败(${r.status})`);if(Number(r.headers.get("content-length")??0)>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);const s=[];let i=0;if(r.body){const t=r.body.getReader();for(;;){const{done:o,value:n}=await t.read();if(o)break;if(i+=n.byteLength,i>e)return await t.cancel().catch(()=>{}),$(413,"TOO_LARGE",`视频超过 ${O(e)}`);s.push(Buffer.from(n))}}else{const t=Buffer.from(await r.arrayBuffer());if(t.length>e)return $(413,"TOO_LARGE",`视频超过 ${O(e)}`);s.push(t),i=t.length}return i<=0?$(502,"REMOTE_FETCH_FAILED","远程视频为空"):{type:L(t,r.headers.get("content-type")),bytes:Buffer.concat(s,i)}}catch(t){const e="AbortError"===t.name?"远程视频下载超时":"远程视频下载失败";return $(502,"REMOTE_FETCH_FAILED",e)}finally{clearTimeout(r)}}function C(t){return t.startsWith("video/")?"mp4":t.includes("mpeg")||t.includes("mp3")?"mp3":t.includes("wav")?"wav":t.includes("aac")?"aac":t.includes("ogg")?"ogg":t.includes("mp4")||t.includes("m4a")?"m4a":t.includes("flac")?"flac":"bin"}function V(o){const n={...b,...o?.limits};return{async handle(o){const{videos:r,videoUrls:s,audios:i,overlay:a,videoCover:c,textOverlays:u=[],editStateRaw:m}=o,l=await async function(t,e,o){const n=[],r=Math.max(t.length,e?.length??0);for(let s=0;s<r;s++){const r=t[s];if(r){n[s]=r;continue}const i=e?.[s];if(!i?.url)continue;const a=await S(i,o);if(R(a))return a;n[s]=a}return n.filter(Boolean)}(r,s,n.maxVideoBytes);if(R(l))return l;const p=l;if(p.length>n.maxVideos)return $(413,"TOO_MANY",`视频数超过 ${n.maxVideos}`);for(let t=0;t<p.length;t++){if(!/^video\//.test(p[t].type))return $(415,"BAD_TYPE",`video_${t} 不是视频文件`);if(p[t].bytes.length>n.maxVideoBytes)return $(413,"TOO_LARGE",`视频 ${t+1} 超过 ${O(n.maxVideoBytes)}`)}if(!p.length)return $(400,"BAD_REQUEST","缺少视频文件(video_0)");if(p.reduce((t,e)=>t+e.bytes.length,0)>n.maxTotalVideoBytes)return $(413,"TOO_LARGE",`视频总大小超过 ${O(n.maxTotalVideoBytes)},请减少素材或压缩`);if(i.length>n.maxAudios)return $(413,"TOO_MANY",`音频数超过 ${n.maxAudios}`);for(let t=0;t<i.length;t++){const e=/^audio\//.test(i[t].type),o=/^video\//.test(i[t].type);if(!e&&!o)return $(415,"BAD_TYPE",`audio_${t} 不是音频或视频文件`);if(i[t].bytes.length>(o?n.maxVideoBytes:n.maxAudioBytes))return $(413,"TOO_LARGE",`音频 ${t+1} 超过 ${O(o?n.maxVideoBytes:n.maxAudioBytes)}`)}if("string"!=typeof m)return $(400,"BAD_REQUEST","缺少 editState");if(a&&a.length>n.maxOverlayBytes)return $(413,"OVERLAY_TOO_LARGE","overlay 过大");if(u.some(t=>t.length>n.maxOverlayBytes))return $(413,"OVERLAY_TOO_LARGE","textOverlay 过大");if(c&&c.length>n.maxOverlayBytes)return $(413,"COVER_TOO_LARGE","videoCover too large");let f;try{f=t.parse(JSON.parse(m))}catch{return $(400,"BAD_STATE","编辑参数不合法")}if(f.clips.some(t=>t.srcIdx>=p.length))return $(400,"BAD_STATE","clips.srcIdx 超出上传视频数");if((f.audioClips??[]).some(t=>t.srcIdx>=i.length))return $(400,"BAD_STATE","audioClips.srcIdx 超出上传音频数");try{const t=p.map(t=>t.bytes),o=i.map(t=>t.bytes),n=new Map,r=t=>{let e=n.get(t);return e||(e=h(t),n.set(t,e)),e},s=[...new Set((f.audioClips??[]).map(t=>t.srcIdx))];for(const t of s)if(/^video\//.test(i[t].type)&&!await r(o[t]))return $(400,"BAD_TYPE",`音频 ${t+1} 是视频文件但没有音轨,无法提取声音`);const m=p.map(()=>!1);if(f.keepAudio){const e=[...new Set(f.clips.map(t=>t.srcIdx))];await Promise.all(e.map(async e=>{m[e]=await r(t[e])})),m.some(Boolean)||(f.audioClips??[]).length||(f={...f,keepAudio:!1})}const l=[];for(let e=1;e<t.length;e++)l.push({name:`video_${e}.mp4`,bytes:t[e]});o.forEach((t,e)=>l.push({name:`audio_${e}.${C(i[e].type)}`,bytes:t})),a&&l.push({name:"overlay.png",bytes:a}),u.forEach((t,e)=>l.push({name:`textOverlay_${e}.png`,bytes:t}));const g=function(t){const e=String(t.exportSettings?.format??"mp4").toLowerCase();return"mov"===e||"mxf"===e?e:"mp4"}(f),y=c?.length?c:function(t){const e=t.poster;if(!e?.useAsVideoCover)return;const o=("string"==typeof e.preview?e.preview:"string"==typeof e.src?e.src:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);return o?Buffer.from(o[1],"base64"):void 0}(f),v=(n,r,s)=>{const c={videoPaths:[n,...t.slice(1).map((t,e)=>s[`video_${e+1}.mp4`])],videoHasAudio:m,audioPaths:o.map((t,e)=>s[`audio_${e}.${C(i[e].type)}`]),overlayPath:a?s["overlay.png"]:void 0,textOverlayPaths:u.map((t,e)=>s[`textOverlay_${e}.png`]).filter(Boolean),overlayLayerPaths:u.map((t,e)=>s[`textOverlay_${e}.png`]).filter(Boolean)};return e(f,c,r)};return{ok:!0,bytes:y?.length?await T(t[0],v,g,l,y,28e4):await A(t[0],v,g,l,28e4),contentType:(d=g,"mov"===d?"video/quicktime":"mxf"===d?"application/mxf":"video/mp4"),filename:`edited.${g}`}}catch(t){return(t.message??"").includes("二进制缺失")?$(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):$(502,"ENCODE_FAILED","视频处理失败,请重试或换更短的视频")}var d}}}function k(t){const e=t?.maxBytes??b.maxVideoBytes;return{async handle(t,n){if(!/^video\//.test(t.type))return $(415,"BAD_TYPE","只支持视频文件");if(t.bytes.length>e)return $(413,"TOO_LARGE",`文件超过 ${O(e)}`);if(!isFinite(n)||n<0)return $(400,"BAD_TIME","time 参数不合法");try{return{ok:!0,bytes:await A(t.bytes,(t,e)=>o(t,n,e),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(t){return(t.message??"").includes("二进制缺失")?$(503,"SERVER_NOT_READY","服务端编码组件未就绪(ffmpeg 缺失),请联系管理员"):$(502,"EXTRACT_FAILED","封面抽取失败,请重试")}}}}export{b as DEFAULT_LIMITS,_ as composeVideo,k as createPosterHandler,V as createVideoExportHandler,v as extractAudio,h as probeHasAudio,x 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.25",
3
+ "version": "1.0.26",
4
4
  "description": "媒体编辑器服务端:框架无关的视频导出/封面处理器 + Web(Next)/Express 适配器(Node + ffmpeg-static)",
5
5
  "type": "module",
6
6
  "license": "MIT",