@bylqwjc/media-editor-server 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/adapters/express.cjs +1 -0
- package/dist/adapters/express.d.ts +30 -0
- package/dist/adapters/express.js +1 -0
- package/dist/adapters/web.cjs +1 -0
- package/dist/adapters/web.d.ts +15 -0
- package/dist/adapters/web.js +1 -0
- package/dist/handler-DL4Inp7o.d.ts +47 -0
- package/dist/index.cjs +4 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +4 -0
- package/node_modules/@media-editor/core/LICENSE +21 -0
- package/node_modules/@media-editor/core/README.md +14 -0
- package/node_modules/@media-editor/core/dist/browser.cjs +1 -0
- package/node_modules/@media-editor/core/dist/browser.d.ts +100 -0
- package/node_modules/@media-editor/core/dist/browser.js +3 -0
- package/node_modules/@media-editor/core/dist/chunk-MGKYVYOH.js +1 -0
- package/node_modules/@media-editor/core/dist/chunk-USY6UDGL.js +1 -0
- package/node_modules/@media-editor/core/dist/chunk-XCBM7P7N.js +1 -0
- package/node_modules/@media-editor/core/dist/engine.cjs +1 -0
- package/node_modules/@media-editor/core/dist/engine.d.ts +359 -0
- package/node_modules/@media-editor/core/dist/engine.js +1 -0
- package/node_modules/@media-editor/core/dist/filerobot.cjs +1 -0
- package/node_modules/@media-editor/core/dist/filerobot.d.ts +88 -0
- package/node_modules/@media-editor/core/dist/filerobot.js +1 -0
- package/node_modules/@media-editor/core/dist/index.cjs +1 -0
- package/node_modules/@media-editor/core/dist/index.d.ts +264 -0
- package/node_modules/@media-editor/core/dist/index.js +1 -0
- package/node_modules/@media-editor/core/dist/platform-presets-B-o4C6uY.d.ts +80 -0
- package/node_modules/@media-editor/core/dist/state-B85vkf7_.d.ts +1027 -0
- package/node_modules/@media-editor/core/node_modules/zod/LICENSE +21 -0
- package/node_modules/@media-editor/core/node_modules/zod/README.md +208 -0
- package/node_modules/@media-editor/core/node_modules/zod/index.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/index.d.cts +4 -0
- package/node_modules/@media-editor/core/node_modules/zod/index.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/package.json +76 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/ZodError.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/ZodError.d.cts +164 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/ZodError.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/errors.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/errors.d.cts +5 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/errors.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/external.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/external.d.cts +6 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/external.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/enumUtil.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/enumUtil.d.cts +8 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/enumUtil.js +0 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/errorUtil.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/errorUtil.d.cts +9 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/errorUtil.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/parseUtil.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/parseUtil.d.cts +78 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/parseUtil.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/partialUtil.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/partialUtil.d.cts +8 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/partialUtil.js +0 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/typeAliases.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/typeAliases.d.cts +2 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/typeAliases.js +0 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/util.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/util.d.cts +85 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/helpers/util.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/index.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/index.d.cts +4 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/index.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/locales/en.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/locales/en.d.cts +3 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/locales/en.js +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/standard-schema.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/standard-schema.d.cts +102 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/standard-schema.js +0 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/types.cjs +1 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/types.d.cts +1031 -0
- package/node_modules/@media-editor/core/node_modules/zod/v3/types.js +1 -0
- package/node_modules/@media-editor/core/package.json +42 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 media-editor-sdk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @media-editor/server
|
|
2
|
+
|
|
3
|
+
Node 侧视频导出和封面抽取处理器。内置 Web 标准适配器和 Express 适配器,依赖 `ffmpeg-static`。
|
|
4
|
+
|
|
5
|
+
## Next.js App Router
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createVideoExportHandler } from '@media-editor/server';
|
|
9
|
+
import { toWebVideoHandler } from '@media-editor/server/web';
|
|
10
|
+
|
|
11
|
+
export const runtime = 'nodejs';
|
|
12
|
+
export const maxDuration = 300;
|
|
13
|
+
|
|
14
|
+
export const POST = toWebVideoHandler(createVideoExportHandler());
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Express
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import express from 'express';
|
|
21
|
+
import multer from 'multer';
|
|
22
|
+
import { createPosterHandler, createVideoExportHandler } from '@media-editor/server';
|
|
23
|
+
import { expressPoster, expressVideoExport } from '@media-editor/server/express';
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
const upload = multer();
|
|
27
|
+
|
|
28
|
+
app.post('/api/editor/video', upload.any(), expressVideoExport(createVideoExportHandler()));
|
|
29
|
+
app.post('/api/editor/poster', upload.any(), expressPoster(createPosterHandler()));
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
接口失败时会返回稳定错误契约:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "error": "BAD_REQUEST", "message": "..." }
|
|
36
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var f=Object.defineProperty,y=Object.getOwnPropertyDescriptor,c=Object.getOwnPropertyNames,d=Object.prototype.hasOwnProperty,m=(t,e)=>{for(var o in e)f(t,o,{get:e[o],enumerable:!0})},b=(t,e,o,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of c(e))!d.call(t,r)&&r!==o&&f(t,r,{get:()=>e[r],enumerable:!(s=y(e,r))||s.enumerable});return t},_=t=>b(f({},"__esModule",{value:!0}),t),p={};m(p,{expressPoster:()=>g,expressVideoExport:()=>v}),module.exports=_(p);function u(t){return Array.isArray(t.files)?t.files:t.files&&typeof t.files=="object"?Object.values(t.files).flat():[]}function l(t,e){const o=[];for(let s=0;;s++){const r=t.find(n=>n.fieldname===`${e}_${s}`);if(!r)break;o.push({type:r.mimetype,bytes:r.buffer})}return o}function a(t,e){if(e.ok){t.setHeader("Content-Type",e.contentType),t.setHeader("Content-Disposition",`attachment; filename="${e.filename}"`),t.setHeader("Cache-Control","no-store"),t.send(e.bytes);return}t.status(e.status).json({error:e.error,message:e.message})}function v(t){return async(e,o)=>{const s=u(e),r=s.find(i=>i.fieldname==="overlay");if(r&&r.mimetype!=="image/png"){a(o,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay \u5FC5\u987B\u662F PNG"});return}const n={videos:l(s,"video"),audios:l(s,"audio"),overlay:r?.buffer,editStateRaw:typeof e.body?.editState=="string"?e.body.editState:""};a(o,await t.handle(n))}}function g(t){return async(e,o)=>{const r=u(e).find(i=>i.fieldname==="file"),n=e.body?.time;if(!r||typeof n!="string"){a(o,{ok:!1,status:400,error:"BAD_REQUEST",message:"\u7F3A\u5C11 file \u6216 time"});return}a(o,await t.handle({type:r.mimetype,bytes:r.buffer},Number(n)))}}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { P as PosterHandler, V as VideoExportHandler } from '../handler-DL4Inp7o.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Express 适配器。用法:
|
|
5
|
+
* import multer from 'multer';
|
|
6
|
+
* import { createVideoExportHandler } from '@media-editor/server';
|
|
7
|
+
* import { expressVideoExport } from '@media-editor/server/express';
|
|
8
|
+
* app.post('/api/editor/video', multer().any(), expressVideoExport(createVideoExportHandler()));
|
|
9
|
+
* multer().any() 把所有字段放进 req.files(含 fieldname/mimetype/buffer),文本字段进 req.body。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface MulterFile {
|
|
13
|
+
fieldname: string;
|
|
14
|
+
mimetype: string;
|
|
15
|
+
buffer: Buffer;
|
|
16
|
+
}
|
|
17
|
+
interface ReqLike {
|
|
18
|
+
files?: MulterFile[] | Record<string, MulterFile[]>;
|
|
19
|
+
body?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
interface ResLike {
|
|
22
|
+
status(code: number): ResLike;
|
|
23
|
+
json(body: unknown): void;
|
|
24
|
+
setHeader(k: string, v: string): void;
|
|
25
|
+
send(body: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
declare function expressVideoExport(handler: VideoExportHandler): (req: ReqLike, res: ResLike) => Promise<void>;
|
|
28
|
+
declare function expressPoster(handler: PosterHandler): (req: ReqLike, res: ResLike) => Promise<void>;
|
|
29
|
+
|
|
30
|
+
export { expressPoster, expressVideoExport };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(e){return Array.isArray(e.files)?e.files:e.files&&typeof e.files=="object"?Object.values(e.files).flat():[]}function l(e,t){const o=[];for(let i=0;;i++){const s=e.find(a=>a.fieldname===`${t}_${i}`);if(!s)break;o.push({type:s.mimetype,bytes:s.buffer})}return o}function r(e,t){if(t.ok){e.setHeader("Content-Type",t.contentType),e.setHeader("Content-Disposition",`attachment; filename="${t.filename}"`),e.setHeader("Cache-Control","no-store"),e.send(t.bytes);return}e.status(t.status).json({error:t.error,message:t.message})}function m(e){return async(t,o)=>{const i=u(t),s=i.find(n=>n.fieldname==="overlay"),a=i.find(n=>n.fieldname==="videoCover"),f=l(i,"textOverlay");if(s&&s.mimetype!=="image/png"){r(o,{ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay \u5FC5\u987B\u662F PNG"});return}if(f.some(n=>n.type!=="image/png")){r(o,{ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay \u5FC5\u987B\u662F PNG"});return}if(a&&!/^image\/(?:png|jpe?g)$/.test(a.mimetype)){r(o,{ok:!1,status:415,error:"BAD_COVER",message:"videoCover must be PNG or JPG"});return}const y={videos:l(i,"video"),audios:l(i,"audio"),overlay:s?.buffer,videoCover:a?.buffer,textOverlays:f.map(n=>n.bytes),editStateRaw:typeof t.body?.editState=="string"?t.body.editState:""};r(o,await e.handle(y))}}function d(e){return async(t,o)=>{const s=u(t).find(f=>f.fieldname==="file"),a=t.body?.time;if(!s||typeof a!="string"){r(o,{ok:!1,status:400,error:"BAD_REQUEST",message:"\u7F3A\u5C11 file \u6216 time"});return}r(o,await e.handle({type:s.mimetype,bytes:s.buffer},Number(a)))}}export{d as expressPoster,m as expressVideoExport};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var p=Object.defineProperty,w=Object.getOwnPropertyDescriptor,b=Object.getOwnPropertyNames,v=Object.prototype.hasOwnProperty,O=(e,r)=>{for(var t in r)p(e,t,{get:r[t],enumerable:!0})},P=(e,r,t,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of b(r))!v.call(e,o)&&o!==t&&p(e,o,{get:()=>r[o],enumerable:!(a=w(r,o))||a.enumerable});return e},h=e=>P(p({},"__esModule",{value:!0}),e),d={};O(d,{toWebPosterHandler:()=>D,toWebVideoHandler:()=>B}),module.exports=h(d);var g=16;function f(e){return typeof e=="object"&&e!==null&&typeof e.type=="string"&&typeof e.arrayBuffer=="function"}async function c(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 B(e){return async r=>{let t;try{t=await r.formData()}catch{return s({ok:!1,status:400,error:"BAD_REQUEST",message:"\u8BF7\u6C42\u4F53\u89E3\u6790\u5931\u8D25"})}const a=[];for(let n=0;n<g;n++){const i=t.get(`video_${n}`);if(!f(i))break;a.push(await c(i))}const o=[];for(let n=0;n<g;n++){const i=t.get(`audio_${n}`);if(!f(i))break;o.push(await c(i))}const l=t.get("editState"),y=t.get("overlay"),u=f(y)?y:void 0,_=u?Buffer.from(await u.arrayBuffer()):void 0;if(u&&u.type!=="image/png")return s({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay \u5FC5\u987B\u662F PNG"});const m={videos:a,audios:o,overlay:_,editStateRaw:typeof l=="string"?l:""};return s(await e.handle(m))}}function D(e){return async r=>{let t;try{t=await r.formData()}catch{return s({ok:!1,status:400,error:"BAD_REQUEST",message:"\u8BF7\u6C42\u4F53\u89E3\u6790\u5931\u8D25"})}const a=t.get("file"),o=t.get("time");return!f(a)||typeof o!="string"?s({ok:!1,status:400,error:"BAD_REQUEST",message:"\u7F3A\u5C11 file \u6216 time"}):s(await e.handle(await c(a),Number(o)))}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { P as PosterHandler, V as VideoExportHandler } from '../handler-DL4Inp7o.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Web 标准适配器(Next.js App Router 路由 / 任何接收 Request 返回 Response 的运行时)。
|
|
5
|
+
* 用法(Next):
|
|
6
|
+
* import { createVideoExportHandler } from '@media-editor/server';
|
|
7
|
+
* import { toWebVideoHandler } from '@media-editor/server/web';
|
|
8
|
+
* export const runtime = 'nodejs'; export const maxDuration = 300;
|
|
9
|
+
* export const POST = toWebVideoHandler(createVideoExportHandler());
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare function toWebVideoHandler(handler: VideoExportHandler): (request: Request) => Promise<Response>;
|
|
13
|
+
declare function toWebPosterHandler(handler: PosterHandler): (request: Request) => Promise<Response>;
|
|
14
|
+
|
|
15
|
+
export { toWebPosterHandler, toWebVideoHandler };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var y=16;function s(e){return typeof e=="object"&&e!==null&&typeof e.type=="string"&&typeof e.arrayBuffer=="function"}async function c(e){return{type:e.type,bytes:Buffer.from(await e.arrayBuffer())}}function a(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 C(e){return async l=>{let t;try{t=await l.formData()}catch{return a({ok:!1,status:400,error:"BAD_REQUEST",message:"\u8BF7\u6C42\u4F53\u89E3\u6790\u5931\u8D25"})}const n=[];for(let r=0;r<y;r++){const o=t.get(`video_${r}`);if(!s(o))break;n.push(await c(o))}const i=[];for(let r=0;r<y;r++){const o=t.get(`audio_${r}`);if(!s(o))break;i.push(await c(o))}const p=t.get("editState"),g=t.get("overlay"),m=t.get("videoCover"),f=s(g)?g:void 0,u=s(m)?m:void 0,v=f?Buffer.from(await f.arrayBuffer()):void 0,B=u?Buffer.from(await u.arrayBuffer()):void 0,d=[];for(let r=0;r<y;r++){const o=t.get(`textOverlay_${r}`);if(!s(o))break;if(o.type!=="image/png")return a({ok:!1,status:415,error:"BAD_OVERLAY",message:"textOverlay \u5FC5\u987B\u662F PNG"});d.push(Buffer.from(await o.arrayBuffer()))}if(f&&f.type!=="image/png")return a({ok:!1,status:415,error:"BAD_OVERLAY",message:"overlay \u5FC5\u987B\u662F PNG"});if(u&&!/^image\/(?:png|jpe?g)$/.test(u.type))return a({ok:!1,status:415,error:"BAD_COVER",message:"videoCover \u5FC5\u987B\u662F PNG \u6216 JPG"});const w={videos:n,audios:i,overlay:v,videoCover:B,textOverlays:d,editStateRaw:typeof p=="string"?p:""};return a(await e.handle(w))}}function F(e){return async l=>{let t;try{t=await l.formData()}catch{return a({ok:!1,status:400,error:"BAD_REQUEST",message:"\u8BF7\u6C42\u4F53\u89E3\u6790\u5931\u8D25"})}const n=t.get("file"),i=t.get("time");return!s(n)||typeof i!="string"?a({ok:!1,status:400,error:"BAD_REQUEST",message:"\u7F3A\u5C11 file \u6216 time"}):a(await e.handle(await c(n),Number(i)))}}export{F as toWebPosterHandler,C as toWebVideoHandler};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface ExportLimits {
|
|
2
|
+
maxVideos: number;
|
|
3
|
+
maxAudios: number;
|
|
4
|
+
maxVideoBytes: number;
|
|
5
|
+
maxTotalVideoBytes: number;
|
|
6
|
+
maxAudioBytes: number;
|
|
7
|
+
maxOverlayBytes: number;
|
|
8
|
+
}
|
|
9
|
+
declare const DEFAULT_LIMITS: ExportLimits;
|
|
10
|
+
/** 一个上传文件的最小抽象(适配器从各自 multipart 解析后喂入) */
|
|
11
|
+
interface UploadFile {
|
|
12
|
+
type: string;
|
|
13
|
+
bytes: Buffer;
|
|
14
|
+
}
|
|
15
|
+
interface ParsedVideoExport {
|
|
16
|
+
videos: UploadFile[];
|
|
17
|
+
audios: UploadFile[];
|
|
18
|
+
overlay?: Buffer;
|
|
19
|
+
editStateRaw: string;
|
|
20
|
+
}
|
|
21
|
+
type ExportOk = {
|
|
22
|
+
ok: true;
|
|
23
|
+
bytes: Buffer;
|
|
24
|
+
contentType: string;
|
|
25
|
+
filename: string;
|
|
26
|
+
};
|
|
27
|
+
type ExportErr = {
|
|
28
|
+
ok: false;
|
|
29
|
+
status: number;
|
|
30
|
+
error: string;
|
|
31
|
+
message: string;
|
|
32
|
+
};
|
|
33
|
+
type ExportResult = ExportOk | ExportErr;
|
|
34
|
+
interface VideoExportHandler {
|
|
35
|
+
handle(parsed: ParsedVideoExport): Promise<ExportResult>;
|
|
36
|
+
}
|
|
37
|
+
declare function createVideoExportHandler(opts?: {
|
|
38
|
+
limits?: Partial<ExportLimits>;
|
|
39
|
+
}): VideoExportHandler;
|
|
40
|
+
interface PosterHandler {
|
|
41
|
+
handle(file: UploadFile, time: number): Promise<ExportResult>;
|
|
42
|
+
}
|
|
43
|
+
declare function createPosterHandler(opts?: {
|
|
44
|
+
maxBytes?: number;
|
|
45
|
+
}): PosterHandler;
|
|
46
|
+
|
|
47
|
+
export { DEFAULT_LIMITS as D, type ExportErr as E, type PosterHandler as P, type UploadFile as U, type VideoExportHandler as V, type ExportLimits as a, type ExportOk as b, type ExportResult as c, type ParsedVideoExport as d, createPosterHandler as e, createVideoExportHandler as f };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";var N=Object.create;var I=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var K=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of U(t))!J.call(e,o)&&o!==r&&I(e,o,{get:()=>t[o],enumerable:!(i=G(t,o))||i.enumerable});return e};var W=(e,t,r)=>(r=e!=null?N(z(e)):{},K(t||!e||!e.__esModule?I(r,"default",{value:e,enumerable:!0}):r,e));var Q=Object.create,P=Object.defineProperty,X=Object.getOwnPropertyDescriptor,Z=Object.getOwnPropertyNames,ee=Object.getPrototypeOf,te=Object.prototype.hasOwnProperty,ue=(e,t)=>{for(var r in t)P(e,r,{get:t[r],enumerable:!0})},L=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Z(t))!te.call(e,o)&&o!==r&&P(e,o,{get:()=>t[o],enumerable:!(i=X(t,o))||i.enumerable});return e},re=(e,t,r)=>(r=e!=null?Q(ee(e)):{},L(t||!e||!e.__esModule?P(r,"default",{value:e,enumerable:!0}):r,e)),oe=e=>L(P({},"__esModule",{value:!0}),e),R={};ue(R,{DEFAULT_LIMITS:()=>H,composeVideo:()=>fe,createPosterHandler:()=>he,createVideoExportHandler:()=>ve,extractAudio:()=>pe,probeHasAudio:()=>$,probeVideoDuration:()=>M,runVideoEdit:()=>V}),module.exports=oe(R);var ie=require("@media-editor/core"),k=require("@media-editor/core"),S=re(require("ffmpeg-static"),1),T=require("child_process"),m=require("fs/promises"),ne=require("fs"),ae=require("os"),d=require("path"),se={};async function ce(){const e=[];S.default&&typeof S.default=="string"&&e.push(S.default);try{const{createRequire:t}=await import("module"),i=t(se.url).resolve("ffmpeg-static/package.json"),o=i.substring(0,i.lastIndexOf("/"));e.push((0,d.join)(o,"ffmpeg"))}catch{}e.push((0,d.join)(process.cwd(),"node_modules","ffmpeg-static","ffmpeg"),(0,d.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,m.access)(t,ne.constants.X_OK),t}catch{}return null}var b;async function O(){return b!==void 0||(b=await ce(),b||console.warn("[ffmpeg-runner] ffmpeg-static \u4E8C\u8FDB\u5236\u672A\u627E\u5230 (\u5C1D\u8BD5\u4E86\u591A\u4E2A fallback \u8DEF\u5F84)")),b}async function x({args:e,timeoutMs:t=18e4}){const r=await O();if(!r)throw new Error("ffmpeg-static \u4E8C\u8FDB\u5236\u7F3A\u5931 \u2014 \u8BF7\u68C0\u67E5 node_modules/ffmpeg-static/ffmpeg \u662F\u5426\u5B58\u5728");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((i,o)=>{const s=(0,T.spawn)(r,e,{stdio:["ignore","pipe","pipe"]});let n="",f=!1;const a=setTimeout(()=>{f=!0,s.kill("SIGKILL"),o(new Error(`ffmpeg \u8D85\u65F6 (${t}ms)`))},t);s.stderr.on("data",u=>{n+=u.toString(),n.length>8192&&(n=n.slice(-8192))}),s.on("close",u=>{if(clearTimeout(a),f)return;const p=n.slice(-2500);console.log(`[ffmpeg-runner] exit=${u}, stderr tail:
|
|
2
|
+
${p}
|
|
3
|
+
---end---`),u===0?i():o(new Error(`ffmpeg \u9000\u51FA\u7801 ${u}: ${p.slice(-800)}`))}),s.on("error",u=>{clearTimeout(a),o(u)})})}async function B(e){const t=await(0,m.mkdtemp)((0,d.join)((0,ae.tmpdir)(),"vtrans-"));try{return await e(t)}finally{(0,m.rm)(t,{recursive:!0,force:!0}).catch(()=>{})}}async function $(e){const t=await O();return t?B(async r=>{const i=(0,d.join)(r,"probe.mp4");return await(0,m.writeFile)(i,e),new Promise(o=>{const s=(0,T.spawn)(t,["-hide_banner","-i",i],{stdio:["ignore","ignore","pipe"]});let n="";const f=setTimeout(()=>{s.kill("SIGKILL"),o(!1)},15e3);s.stderr.on("data",a=>{n+=a.toString(),n.length>16384&&(n=n.slice(-16384))}),s.on("close",()=>{clearTimeout(f),o(/Stream #[^\n]*Audio:/.test(n))}),s.on("error",()=>{clearTimeout(f),o(!1)})})}):!1}async function pe(e){return B(async t=>{const r=(0,d.join)(t,"in.mp4"),i=(0,d.join)(t,"audio.mp3");return await(0,m.writeFile)(r,e),await x({args:["-y","-i",r,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",i],timeoutMs:6e4}),(0,m.readFile)(i)})}var le=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function fe(e){const r=(await M(e.videoBytes)).durationSeconds;return B(async i=>{const o=(0,d.join)(i,"in.mp4"),s=(0,d.join)(i,"out.mp4");await(0,m.writeFile)(o,e.videoBytes);const n=["-i",o];let f=-1,a=-1;if(e.newAudioBytes){const g=(0,d.join)(i,"new.mp3");await(0,m.writeFile)(g,e.newAudioBytes),f=(n.length-0)/2,n.push("-i",g)}if(e.bgmAudioBytes){const g=(0,d.join)(i,"bgm.mp3");await(0,m.writeFile)(g,e.bgmAudioBytes),a=n.length/2,n.push("-i",g)}const u=e.srtContent&&e.srtContent.trim().length>0;let p=null;u&&(p=(0,d.join)(i,"subs.srt"),await(0,m.writeFile)(p,e.srtContent));const v=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${r.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${v}, srt=${e.srtContent?.length??0}chars`);const y=le.replace(/,/g,"\\,"),h=[];let _,D=!1;u&&p?(h.push(`[0:v]subtitles=${p}:force_style=${y}[v]`),_="[v]",D=!0):_="0:v:0";const A=[];f>=0&&A.push({idx:f,isBgm:!1}),a>=0&&A.push({idx:a,isBgm:!0});let c=null;if(A.length===1)c=`${A[0].idx}:a:0`;else if(A.length>=2){const g=[];for(const w of A)w.isBgm?(h.push(`[${w.idx}:a]volume=${v}[bgm]`),g.push("[bgm]")):g.push(`[${w.idx}:a]`);h.push(`${g.join("")}amix=inputs=${g.length}:duration=first:dropout_transition=0[a]`),c="[a]"}const F=["-y",...n];h.length>0&&F.push("-filter_complex",h.join(";")),F.push("-map",_),c&&F.push("-map",c),D?F.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):F.push("-c:v","copy"),F.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",r.toFixed(3)),F.push(s),await x({args:F,timeoutMs:18e4});const C=c?await O():null;if(C)try{const g=await new Promise(w=>{const E=(0,T.spawn)(C,["-i",s],{stdio:["ignore","pipe","pipe"]});let j="";E.stderr.on("data",Y=>j+=Y.toString()),E.on("close",()=>w(j))});if(!/Audio:/.test(g))throw console.error(`[ffmpeg-runner] \u4EA7\u7269\u7F3A\u5C11\u97F3\u8F68,ffmpeg \u6D41\u6982\u89C8:
|
|
4
|
+
`,g.slice(-2e3)),new Error("\u5408\u6210\u5931\u8D25: \u8F93\u51FA mp4 \u7F3A\u5C11\u97F3\u8F68 (\u68C0\u67E5 filter_complex \u8BED\u6CD5)");console.log("[ffmpeg-runner] composeVideo \u5B8C\u6210,\u5DF2\u542B\u97F3\u89C6\u9891\u53CC\u6D41")}catch(g){if(g instanceof Error&&g.message.includes("\u5408\u6210\u5931\u8D25"))throw g}return(0,m.readFile)(s)})}async function M(e){return B(async t=>{const r=(0,d.join)(t,"in.mp4");await(0,m.writeFile)(r,e);const i=await O();if(!i)throw new Error("ffmpeg-static \u4E8C\u8FDB\u5236\u7F3A\u5931");return new Promise((o,s)=>{const n=(0,T.spawn)(i,["-i",r],{stdio:["ignore","pipe","pipe"]});let f="";n.stderr.on("data",a=>f+=a.toString()),n.on("close",()=>{const a=f.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!a)return s(new Error("\u65E0\u6CD5\u89E3\u6790\u89C6\u9891\u65F6\u957F"));const u=parseInt(a[1]),p=parseInt(a[2]),v=parseFloat(a[3]);o({durationSeconds:u*3600+p*60+v})}),n.on("error",s)})})}async function V(e,t,r="mp4",i=[],o=24e4){return B(async s=>{const n=(0,d.join)(s,"input.mp4"),f=(0,d.join)(s,`output.${r}`);await(0,m.writeFile)(n,e);const a={};for(const u of i){const p=u.name.replace(/[^a-zA-Z0-9._-]/g,"_"),v=(0,d.join)(s,p);await(0,m.writeFile)(v,u.bytes),a[u.name]=v}return await x({args:t(n,f,a),timeoutMs:o}),(0,m.readFile)(f)})}function de(e){const t=String(e??"").toLowerCase();return t==="mp4"||t==="mov"}function me(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 ge(e,t,r="mp4",i=[],o,s=24e4){return B(async n=>{const f=(0,d.join)(n,"input.mp4"),a=(0,d.join)(n,`output.${r}`),u=(0,d.join)(n,"video-cover.png"),p=(0,d.join)(n,`output-with-cover.${r}`);await(0,m.writeFile)(f,e);const v={};for(const y of i){const h=y.name.replace(/[^a-zA-Z0-9._-]/g,"_"),_=(0,d.join)(n,h);await(0,m.writeFile)(_,y.bytes),v[y.name]=_}return await x({args:t(f,a,v),timeoutMs:s}),!de(r)||!o?.length?(0,m.readFile)(a):(await(0,m.writeFile)(u,o),await x({args:me(a,u,p),timeoutMs:s}),(0,m.readFile)(p))})}var H={maxVideos:6,maxAudios:3,maxVideoBytes:100*1024*1024,maxTotalVideoBytes:300*1024*1024,maxAudioBytes:30*1024*1024,maxOverlayBytes:20*1024*1024},l=(e,t,r)=>({ok:!1,status:e,error:t,message:r});function q(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 Fe(e){if(!e?.poster?.useAsVideoCover)return;const r=(typeof e.poster.preview=="string"?e.poster.preview:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);if(r)return Buffer.from(r[1],"base64")}function ve(e){const t={...H,...e?.limits};return{async handle(r){const{videos:i,audios:o,overlay:s,videoCover:n,editStateRaw:f}=r;if(i.length>t.maxVideos)return l(413,"TOO_MANY",`\u89C6\u9891\u6570\u8D85\u8FC7 ${t.maxVideos}`);for(let u=0;u<i.length;u++){if(!/^video\//.test(i[u].type))return l(415,"BAD_TYPE",`video_${u} \u4E0D\u662F\u89C6\u9891\u6587\u4EF6`);if(i[u].bytes.length>t.maxVideoBytes)return l(413,"TOO_LARGE",`\u89C6\u9891 ${u+1} \u8D85\u8FC7 100MB`)}if(!i.length)return l(400,"BAD_REQUEST","\u7F3A\u5C11\u89C6\u9891\u6587\u4EF6(video_0)");if(i.reduce((u,p)=>u+p.bytes.length,0)>t.maxTotalVideoBytes)return l(413,"TOO_LARGE","\u89C6\u9891\u603B\u5927\u5C0F\u8D85\u8FC7 300MB,\u8BF7\u51CF\u5C11\u7D20\u6750\u6216\u538B\u7F29");if(o.length>t.maxAudios)return l(413,"TOO_MANY",`\u97F3\u9891\u6570\u8D85\u8FC7 ${t.maxAudios}`);for(let u=0;u<o.length;u++){const p=/^audio\//.test(o[u].type),v=/^video\//.test(o[u].type);if(!p&&!v)return l(415,"BAD_TYPE",`audio_${u} \u4E0D\u662F\u97F3\u9891\u6216\u89C6\u9891\u6587\u4EF6`);if(o[u].bytes.length>(v?t.maxVideoBytes:t.maxAudioBytes))return l(413,"TOO_LARGE",`\u97F3\u9891 ${u+1} \u8D85\u8FC7 ${v?"100MB":"30MB"}`)}if(typeof f!="string")return l(400,"BAD_REQUEST","\u7F3A\u5C11 editState");if(s&&s.length>t.maxOverlayBytes)return l(413,"OVERLAY_TOO_LARGE","overlay \u8FC7\u5927");if(n&&n.length>t.maxOverlayBytes)return l(413,"COVER_TOO_LARGE","videoCover \u8FC7\u5927");let a;try{a=ie.VideoEditState.parse(JSON.parse(f))}catch{return l(400,"BAD_STATE","\u7F16\u8F91\u53C2\u6570\u4E0D\u5408\u6CD5")}if(a.clips.some(u=>u.srcIdx>=i.length))return l(400,"BAD_STATE","clips.srcIdx \u8D85\u51FA\u4E0A\u4F20\u89C6\u9891\u6570");if((a.audioClips??[]).some(u=>u.srcIdx>=o.length))return l(400,"BAD_STATE","audioClips.srcIdx \u8D85\u51FA\u4E0A\u4F20\u97F3\u9891\u6570");try{const u=i.map(c=>c.bytes),p=o.map(c=>c.bytes),v=[...new Set((a.audioClips??[]).map(c=>c.srcIdx))];for(const c of v)if(/^video\//.test(o[c].type)&&!await $(p[c]))return l(400,"BAD_TYPE",`\u97F3\u9891 ${c+1} \u662F\u89C6\u9891\u6587\u4EF6\u4F46\u6CA1\u6709\u97F3\u8F68,\u65E0\u6CD5\u63D0\u53D6\u58F0\u97F3`);const y=i.map(()=>!1);if(a.keepAudio){const c=[...new Set(a.clips.map(F=>F.srcIdx))];await Promise.all(c.map(async F=>{y[F]=await $(u[F])})),!y.some(Boolean)&&!(a.audioClips??[]).length&&(a={...a,keepAudio:!1})}const h=[];for(let c=1;c<u.length;c++)h.push({name:`video_${c}.mp4`,bytes:u[c]});p.forEach((c,F)=>h.push({name:`audio_${F}.${q(o[F].type)}`,bytes:c})),s&&h.push({name:"overlay.png",bytes:s});const _=n?.length?n:Fe(a),D=(c,F,C)=>{const g={videoPaths:[c,...u.slice(1).map((w,E)=>C[`video_${E+1}.mp4`])],videoHasAudio:y,audioPaths:p.map((w,E)=>C[`audio_${E}.${q(o[E].type)}`]),overlayPath:s?C["overlay.png"]:void 0};return(0,k.buildFfmpegArgs)(a,g,F)};return{ok:!0,bytes:_==null?await V(u[0],D,"mp4",h,28e4):await ge(u[0],D,"mp4",h,_,28e4),contentType:"video/mp4",filename:"edited.mp4"}}catch(u){return(u.message??"").includes("\u4E8C\u8FDB\u5236\u7F3A\u5931")?l(503,"SERVER_NOT_READY","\u670D\u52A1\u7AEF\u7F16\u7801\u7EC4\u4EF6\u672A\u5C31\u7EEA\uFF08ffmpeg \u7F3A\u5931\uFF09\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458"):l(502,"ENCODE_FAILED","\u89C6\u9891\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u6216\u6362\u66F4\u77ED\u7684\u89C6\u9891")}}}}function he(e){const t=e?.maxBytes??104857600;return{async handle(r,i){if(!/^video\//.test(r.type))return l(415,"BAD_TYPE","\u53EA\u652F\u6301\u89C6\u9891\u6587\u4EF6");if(r.bytes.length>t)return l(413,"TOO_LARGE","\u6587\u4EF6\u8D85\u8FC7 100MB");if(!isFinite(i)||i<0)return l(400,"BAD_TIME","time \u53C2\u6570\u4E0D\u5408\u6CD5");try{return{ok:!0,bytes:await V(r.bytes,(s,n)=>(0,k.buildPosterArgs)(s,i,n),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(o){return(o.message??"").includes("\u4E8C\u8FDB\u5236\u7F3A\u5931")?l(503,"SERVER_NOT_READY","\u670D\u52A1\u7AEF\u7F16\u7801\u7EC4\u4EF6\u672A\u5C31\u7EEA\uFF08ffmpeg \u7F3A\u5931\uFF09\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458"):l(502,"EXTRACT_FAILED","\u5C01\u9762\u62BD\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5")}}}}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ffmpeg-runner: 视频/音频处理工具
|
|
5
|
+
*
|
|
6
|
+
* 封装 ffmpeg-static 二进制 + child_process spawn,提供:
|
|
7
|
+
* - extractAudio(): 从视频抽 mp3 (16kHz mono,供 Whisper ASR)
|
|
8
|
+
* - composeReplaceAudio(): 用新音轨替换视频原音轨
|
|
9
|
+
* - composeBurnSubtitles(): 烧录 .srt 字幕到视频
|
|
10
|
+
*
|
|
11
|
+
* MVP V0:
|
|
12
|
+
* - 仅本地 Node 环境 (next dev / vercel functions if binary fits 50MB limit)
|
|
13
|
+
* - 失败 → 抛错(含 stderr 末尾 500 字),pipeline 捕获 → task.status='failed'
|
|
14
|
+
*
|
|
15
|
+
* V1 升级方向:
|
|
16
|
+
* - 部署到独立 worker (Docker + 真 ffmpeg) — 摆脱 50MB 函数包限制
|
|
17
|
+
* - 长视频 → 分片并发
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* 探测视频是否含音轨:`ffmpeg -i`(无输出文件)必然非零退出,但 stderr 会列出全部流,
|
|
21
|
+
* 只看有没有 "Audio:" 流行。多段导出的 [0:a]atrim 滤镜链在无音轨输入上会直接编码失败,
|
|
22
|
+
* 调用方据此降级 keepAudio —— 用户上传无声视频也能切段/重排/复制导出。
|
|
23
|
+
*/
|
|
24
|
+
declare function probeHasAudio(videoBytes: Buffer): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* 从视频抽出音轨,16kHz 单声道 mp3 (Whisper 友好格式)
|
|
27
|
+
*/
|
|
28
|
+
declare function extractAudio(videoBytes: Buffer): Promise<Buffer>;
|
|
29
|
+
interface ComposeOptions {
|
|
30
|
+
videoBytes: Buffer;
|
|
31
|
+
/** 新音轨字节 (TTS/配音, mp3/wav/aac) — 视频翻译用;视频生成/爆款复刻无此项 */
|
|
32
|
+
newAudioBytes?: Buffer;
|
|
33
|
+
/** 用户上传 BGM (mp3/wav/aac) — 与 newAudioBytes 混音,无新音轨则当主音轨 */
|
|
34
|
+
bgmAudioBytes?: Buffer;
|
|
35
|
+
/** BGM 音量 (0-1),默认 0.18 (垫在配音下面) */
|
|
36
|
+
bgmVolume?: number;
|
|
37
|
+
/** SRT 字幕内容 (空字符串 = 不烧字幕) */
|
|
38
|
+
srtContent?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function composeVideo(opts: ComposeOptions): Promise<Buffer>;
|
|
41
|
+
interface VideoProbe {
|
|
42
|
+
durationSeconds: number;
|
|
43
|
+
}
|
|
44
|
+
declare function probeVideoDuration(videoBytes: Buffer): Promise<VideoProbe>;
|
|
45
|
+
/**
|
|
46
|
+
* 通用视频编辑:写入入字节 → 跑给定 ffmpeg 参数(参数里的 input/output 用占位名)→ 读出产物。
|
|
47
|
+
* 复用模块内的 withTempDir/runFfmpeg;writeFile/readFile/join 已在文件顶部导入。
|
|
48
|
+
* @param argsFor buildFfmpegArgs(state, inputPath, outputPath) 这样的工厂
|
|
49
|
+
*/
|
|
50
|
+
declare function runVideoEdit(inputBytes: Buffer, argsFor: (inputPath: string, outputPath: string, extraPaths: Record<string, string>) => string[], outExt?: string,
|
|
51
|
+
/** 额外输入文件(其余视频源/音乐/overlay PNG):写进同一临时目录,路径经 extraPaths 传回 argsFor */
|
|
52
|
+
extraFiles?: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
bytes: Buffer;
|
|
55
|
+
}>,
|
|
56
|
+
/** 多源 concat 可能更慢,调用方可放宽(默认 240s 不变) */
|
|
57
|
+
timeoutMs?: number): Promise<Buffer>;
|
|
58
|
+
|
|
59
|
+
export { type ComposeOptions, type VideoProbe, composeVideo, extractAudio, probeHasAudio, probeVideoDuration, runVideoEdit };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{VideoEditState as M}from"@media-editor/core";import{buildFfmpegArgs as G,buildPosterArgs as H}from"@media-editor/core";import V from"ffmpeg-static";import{spawn as $}from"child_process";import{writeFile as h,readFile as b,mkdtemp as N,rm as j,access as z}from"fs/promises";import{constants as U}from"fs";import{tmpdir as q}from"os";import{join as d}from"path";async function K(){const e=[];V&&typeof V=="string"&&e.push(V);try{const{createRequire:t}=await import("module"),r=t(import.meta.url).resolve("ffmpeg-static/package.json"),i=r.substring(0,r.lastIndexOf("/"));e.push(d(i,"ffmpeg"))}catch{}e.push(d(process.cwd(),"node_modules","ffmpeg-static","ffmpeg"),d(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 z(t,U.X_OK),t}catch{}return null}var T;async function S(){return T!==void 0||(T=await K(),T||console.warn("[ffmpeg-runner] ffmpeg-static \u4E8C\u8FDB\u5236\u672A\u627E\u5230 (\u5C1D\u8BD5\u4E86\u591A\u4E2A fallback \u8DEF\u5F84)")),T}async function P({args:e,timeoutMs:t=18e4}){const o=await S();if(!o)throw new Error("ffmpeg-static \u4E8C\u8FDB\u5236\u7F3A\u5931 \u2014 \u8BF7\u68C0\u67E5 node_modules/ffmpeg-static/ffmpeg \u662F\u5426\u5B58\u5728");return console.log("[ffmpeg-runner] args:",JSON.stringify(e)),new Promise((r,i)=>{const s=$(o,e,{stdio:["ignore","pipe","pipe"]});let n="",l=!1;const f=setTimeout(()=>{l=!0,s.kill("SIGKILL"),i(new Error(`ffmpeg \u8D85\u65F6 (${t}ms)`))},t);s.stderr.on("data",a=>{n+=a.toString(),n.length>8192&&(n=n.slice(-8192))}),s.on("close",a=>{if(clearTimeout(f),l)return;const u=n.slice(-2500);console.log(`[ffmpeg-runner] exit=${a}, stderr tail:
|
|
2
|
+
${u}
|
|
3
|
+
---end---`),a===0?r():i(new Error(`ffmpeg \u9000\u51FA\u7801 ${a}: ${u.slice(-800)}`))}),s.on("error",a=>{clearTimeout(f),i(a)})})}async function _(e){const t=await N(d(q(),"vtrans-"));try{return await e(t)}finally{j(t,{recursive:!0,force:!0}).catch(()=>{})}}async function R(e){const t=await S();return t?_(async o=>{const r=d(o,"probe.mp4");return await h(r,e),new Promise(i=>{const s=$(t,["-hide_banner","-i",r],{stdio:["ignore","ignore","pipe"]});let n="";const l=setTimeout(()=>{s.kill("SIGKILL"),i(!1)},15e3);s.stderr.on("data",f=>{n+=f.toString(),n.length>16384&&(n=n.slice(-16384))}),s.on("close",()=>{clearTimeout(l),i(/Stream #[^\n]*Audio:/.test(n))}),s.on("error",()=>{clearTimeout(l),i(!1)})})}):!1}async function W(e){return _(async t=>{const o=d(t,"in.mp4"),r=d(t,"audio.mp3");return await h(o,e),await P({args:["-y","-i",o,"-vn","-acodec","libmp3lame","-ar","16000","-ac","1","-b:a","64k",r],timeoutMs:6e4}),b(r)})}var J=["Fontsize=14","PrimaryColour=&H00FFFFFF","OutlineColour=&H00000000","BackColour=&H80000000","BorderStyle=1","Outline=2","Shadow=0","Alignment=2","MarginV=80","Bold=1"].join(",");async function Q(e){const o=(await L(e.videoBytes)).durationSeconds;return _(async r=>{const i=d(r,"in.mp4"),s=d(r,"out.mp4");await h(i,e.videoBytes);const n=["-i",i];let l=-1,f=-1;if(e.newAudioBytes){const c=d(r,"new.mp3");await h(c,e.newAudioBytes),l=(n.length-0)/2,n.push("-i",c)}if(e.bgmAudioBytes){const c=d(r,"bgm.mp3");await h(c,e.bgmAudioBytes),f=n.length/2,n.push("-i",c)}const a=e.srtContent&&e.srtContent.trim().length>0;let u=null;a&&(u=d(r,"subs.srt"),await h(u,e.srtContent));const g=e.bgmVolume??.18;console.log(`[ffmpeg-runner] composeVideo: video=${e.videoBytes.length}B (${o.toFixed(2)}s), newAudio=${e.newAudioBytes?.length??0}B, bgm=${e.bgmAudioBytes?.length??0}B@${g}, srt=${e.srtContent?.length??0}chars`);const y=J.replace(/,/g,"\\,"),v=[];let F,D=!1;a&&u?(v.push(`[0:v]subtitles=${u}:force_style=${y}[v]`),F="[v]",D=!0):F="0:v:0";const E=[];l>=0&&E.push({idx:l,isBgm:!1}),f>=0&&E.push({idx:f,isBgm:!0});let w=null;if(E.length===1)w=`${E[0].idx}:a:0`;else if(E.length>=2){const c=[];for(const A of E)A.isBgm?(v.push(`[${A.idx}:a]volume=${g}[bgm]`),c.push("[bgm]")):c.push(`[${A.idx}:a]`);v.push(`${c.join("")}amix=inputs=${c.length}:duration=first:dropout_transition=0[a]`),w="[a]"}const C=["-y",...n];v.length>0&&C.push("-filter_complex",v.join(";")),C.push("-map",F),w&&C.push("-map",w),D?C.push("-c:v","libx264","-preset","fast","-crf","22","-pix_fmt","yuv420p"):C.push("-c:v","copy"),C.push("-c:a","aac","-b:a","128k","-ar","44100","-ac","2","-movflags","+faststart","-t",o.toFixed(3)),C.push(s),await P({args:C,timeoutMs:18e4});const p=w?await S():null;if(p)try{const c=await new Promise(A=>{const O=$(p,["-i",s],{stdio:["ignore","pipe","pipe"]});let x="";O.stderr.on("data",B=>x+=B.toString()),O.on("close",()=>A(x))});if(!/Audio:/.test(c))throw console.error(`[ffmpeg-runner] \u4EA7\u7269\u7F3A\u5C11\u97F3\u8F68,ffmpeg \u6D41\u6982\u89C8:
|
|
4
|
+
`,c.slice(-2e3)),new Error("\u5408\u6210\u5931\u8D25: \u8F93\u51FA mp4 \u7F3A\u5C11\u97F3\u8F68 (\u68C0\u67E5 filter_complex \u8BED\u6CD5)");console.log("[ffmpeg-runner] composeVideo \u5B8C\u6210,\u5DF2\u542B\u97F3\u89C6\u9891\u53CC\u6D41")}catch(c){if(c instanceof Error&&c.message.includes("\u5408\u6210\u5931\u8D25"))throw c}return b(s)})}async function L(e){return _(async t=>{const o=d(t,"in.mp4");await h(o,e);const r=await S();if(!r)throw new Error("ffmpeg-static \u4E8C\u8FDB\u5236\u7F3A\u5931");return new Promise((i,s)=>{const n=$(r,["-i",o],{stdio:["ignore","pipe","pipe"]});let l="";n.stderr.on("data",f=>l+=f.toString()),n.on("close",()=>{const f=l.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);if(!f)return s(new Error("\u65E0\u6CD5\u89E3\u6790\u89C6\u9891\u65F6\u957F"));const a=parseInt(f[1]),u=parseInt(f[2]),g=parseFloat(f[3]);i({durationSeconds:a*3600+u*60+g})}),n.on("error",s)})})}async function I(e,t,o="mp4",r=[],i=24e4){return _(async s=>{const n=d(s,"input.mp4"),l=d(s,`output.${o}`);await h(n,e);const f={};for(const a of r){const u=a.name.replace(/[^a-zA-Z0-9._-]/g,"_"),g=d(s,u);await h(g,a.bytes),f[a.name]=g}return await P({args:t(n,l,f),timeoutMs:i}),b(l)})}function X(e){const t=String(e??"").toLowerCase();return t==="mp4"||t==="mov"}function Z(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 ee(e,t,o="mp4",r=[],i,s=24e4){return _(async n=>{const l=d(n,"input.mp4"),f=d(n,`output.${o}`),a=d(n,"video-cover.png"),u=d(n,`output-with-cover.${o}`);await h(l,e);const g={};for(const y of r){const v=y.name.replace(/[^a-zA-Z0-9._-]/g,"_"),F=d(n,v);await h(F,y.bytes),g[y.name]=F}return await P({args:t(l,f,g),timeoutMs:s}),!X(o)||!i?.length?b(f):(await h(a,i),await P({args:Z(f,a,u),timeoutMs:s}),b(u))})}var k={maxVideos:6,maxAudios:3,maxVideoBytes:100*1024*1024,maxTotalVideoBytes:300*1024*1024,maxAudioBytes:30*1024*1024,maxOverlayBytes:20*1024*1024},m=(e,t,o)=>({ok:!1,status:e,error:t,message:o});function Y(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 te(e){const t=String(e??"mp4").toLowerCase();return t==="mov"||t==="mxf"?t:"mp4"}function ue(e){return e==="mov"?"video/quicktime":e==="mxf"?"application/mxf":"video/mp4"}function re(e){if(!e?.poster?.useAsVideoCover)return;const o=(typeof e.poster.preview=="string"?e.poster.preview:"").match(/^data:image\/(?:png|jpe?g);base64,([a-z0-9+/=]+)$/i);if(o)return Buffer.from(o[1],"base64")}function ne(e){const t={...k,...e?.limits};return{async handle(o){const{videos:r,audios:i,overlay:s,videoCover:n,textOverlays:l=[],editStateRaw:f}=o;if(r.length>t.maxVideos)return m(413,"TOO_MANY",`\u89C6\u9891\u6570\u8D85\u8FC7 ${t.maxVideos}`);for(let u=0;u<r.length;u++){if(!/^video\//.test(r[u].type))return m(415,"BAD_TYPE",`video_${u} \u4E0D\u662F\u89C6\u9891\u6587\u4EF6`);if(r[u].bytes.length>t.maxVideoBytes)return m(413,"TOO_LARGE",`\u89C6\u9891 ${u+1} \u8D85\u8FC7 100MB`)}if(!r.length)return m(400,"BAD_REQUEST","\u7F3A\u5C11\u89C6\u9891\u6587\u4EF6(video_0)");if(r.reduce((u,g)=>u+g.bytes.length,0)>t.maxTotalVideoBytes)return m(413,"TOO_LARGE","\u89C6\u9891\u603B\u5927\u5C0F\u8D85\u8FC7 300MB,\u8BF7\u51CF\u5C11\u7D20\u6750\u6216\u538B\u7F29");if(i.length>t.maxAudios)return m(413,"TOO_MANY",`\u97F3\u9891\u6570\u8D85\u8FC7 ${t.maxAudios}`);for(let u=0;u<i.length;u++){const g=/^audio\//.test(i[u].type),y=/^video\//.test(i[u].type);if(!g&&!y)return m(415,"BAD_TYPE",`audio_${u} \u4E0D\u662F\u97F3\u9891\u6216\u89C6\u9891\u6587\u4EF6`);if(i[u].bytes.length>(y?t.maxVideoBytes:t.maxAudioBytes))return m(413,"TOO_LARGE",`\u97F3\u9891 ${u+1} \u8D85\u8FC7 ${y?"100MB":"30MB"}`)}if(typeof f!="string")return m(400,"BAD_REQUEST","\u7F3A\u5C11 editState");if(s&&s.length>t.maxOverlayBytes)return m(413,"OVERLAY_TOO_LARGE","overlay \u8FC7\u5927");if(n&&n.length>t.maxOverlayBytes)return m(413,"COVER_TOO_LARGE","videoCover \u8FC7\u5927");if(l.some(u=>u.length>t.maxOverlayBytes))return m(413,"OVERLAY_TOO_LARGE","textOverlay \u8FC7\u5927");let a;try{a=M.parse(JSON.parse(f))}catch{return m(400,"BAD_STATE","\u7F16\u8F91\u53C2\u6570\u4E0D\u5408\u6CD5")}if(a.clips.some(u=>u.srcIdx>=r.length))return m(400,"BAD_STATE","clips.srcIdx \u8D85\u51FA\u4E0A\u4F20\u89C6\u9891\u6570");if((a.audioClips??[]).some(u=>u.srcIdx>=i.length))return m(400,"BAD_STATE","audioClips.srcIdx \u8D85\u51FA\u4E0A\u4F20\u97F3\u9891\u6570");try{const u=r.map(p=>p.bytes),g=i.map(p=>p.bytes),y=[...new Set((a.audioClips??[]).map(p=>p.srcIdx))];for(const p of y)if(/^video\//.test(i[p].type)&&!await R(g[p]))return m(400,"BAD_TYPE",`\u97F3\u9891 ${p+1} \u662F\u89C6\u9891\u6587\u4EF6\u4F46\u6CA1\u6709\u97F3\u8F68,\u65E0\u6CD5\u63D0\u53D6\u58F0\u97F3`);const v=r.map(()=>!1);if(a.keepAudio){const p=[...new Set(a.clips.map(c=>c.srcIdx))];await Promise.all(p.map(async c=>{v[c]=await R(u[c])})),!v.some(Boolean)&&!(a.audioClips??[]).length&&(a={...a,keepAudio:!1})}const F=[];for(let p=1;p<u.length;p++)F.push({name:`video_${p}.mp4`,bytes:u[p]});g.forEach((p,c)=>F.push({name:`audio_${c}.${Y(i[c].type)}`,bytes:p})),s&&F.push({name:"overlay.png",bytes:s}),l.forEach((p,c)=>F.push({name:`textOverlay_${c}.png`,bytes:p}));const D=te(a.exportSettings?.format),E=n?.length?n:re(a),w=(p,c,A)=>{const O={videoPaths:[p,...u.slice(1).map((x,B)=>A[`video_${B+1}.mp4`])],videoHasAudio:v,audioPaths:g.map((x,B)=>A[`audio_${B}.${Y(i[B].type)}`]),overlayPath:s?A["overlay.png"]:void 0,textOverlayPaths:l.map((x,B)=>A[`textOverlay_${B}.png`]).filter(Boolean)};return G(a,O,c)};return{ok:!0,bytes:E==null?await I(u[0],w,D,F,28e4):await ee(u[0],w,D,F,E,28e4),contentType:ue(D),filename:`edited.${D}`}}catch(u){return(u.message??"").includes("\u4E8C\u8FDB\u5236\u7F3A\u5931")?m(503,"SERVER_NOT_READY","\u670D\u52A1\u7AEF\u7F16\u7801\u7EC4\u4EF6\u672A\u5C31\u7EEA\uFF08ffmpeg \u7F3A\u5931\uFF09\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458"):m(502,"ENCODE_FAILED","\u89C6\u9891\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u6216\u6362\u66F4\u77ED\u7684\u89C6\u9891")}}}}function oe(e){const t=e?.maxBytes??104857600;return{async handle(o,r){if(!/^video\//.test(o.type))return m(415,"BAD_TYPE","\u53EA\u652F\u6301\u89C6\u9891\u6587\u4EF6");if(o.bytes.length>t)return m(413,"TOO_LARGE","\u6587\u4EF6\u8D85\u8FC7 100MB");if(!isFinite(r)||r<0)return m(400,"BAD_TIME","time \u53C2\u6570\u4E0D\u5408\u6CD5");try{return{ok:!0,bytes:await I(o.bytes,(s,n)=>H(s,r,n),"jpg"),contentType:"image/jpeg",filename:"poster.jpg"}}catch(i){return(i.message??"").includes("\u4E8C\u8FDB\u5236\u7F3A\u5931")?m(503,"SERVER_NOT_READY","\u670D\u52A1\u7AEF\u7F16\u7801\u7EC4\u4EF6\u672A\u5C31\u7EEA\uFF08ffmpeg \u7F3A\u5931\uFF09\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458"):m(502,"EXTRACT_FAILED","\u5C01\u9762\u62BD\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5")}}}}export{k as DEFAULT_LIMITS,Q as composeVideo,oe as createPosterHandler,ne as createVideoExportHandler,W as extractAudio,R as probeHasAudio,L as probeVideoDuration,I as runVideoEdit};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 media-editor-sdk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @media-editor/core
|
|
2
|
+
|
|
3
|
+
框架无关核心包。包含视频编辑状态、时间轴、导出参数、滤镜、平台比例、图片编辑器配置工厂和浏览器 canvas 工具。
|
|
4
|
+
|
|
5
|
+
## 子路径
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { defaultVideoEditState } from '@media-editor/core';
|
|
9
|
+
import { VideoEditorEngine } from '@media-editor/core/engine';
|
|
10
|
+
import { buildVideoExportFormData } from '@media-editor/core/browser';
|
|
11
|
+
import { createImageEditorConfig } from '@media-editor/core/filerobot';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`@media-editor/core` 不依赖 React 或 Vue,适合服务端和自定义 UI 复用。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var v=Math.PI/180;function y(e,t,a){e.strokeStyle=t,e.lineWidth=a,e.lineCap="round",e.lineJoin="round"}function j(e,t,a,n,r,i){const o=Math.atan2(r-a,n-t),c=Math.max(12,i*3.5);e.beginPath(),e.moveTo(n,r),e.lineTo(n-c*Math.cos(o-Math.PI/7),r-c*Math.sin(o-Math.PI/7)),e.moveTo(n,r),e.lineTo(n-c*Math.cos(o+Math.PI/7),r-c*Math.sin(o+Math.PI/7)),e.stroke()}function b(e){if(!Number.isFinite(e))return 0;let t=e%360;return t>180&&(t-=360),t<-180&&(t+=360),Math.abs(t)<.001?0:t}function g(e){return b(e.rotation??0)}function S(e){switch(e.type){case"pen":{let t=1/0,a=1/0,n=-1/0,r=-1/0;for(let i=0;i<e.points.length;i+=2)t=Math.min(t,e.points[i]),n=Math.max(n,e.points[i]),a=Math.min(a,e.points[i+1]),r=Math.max(r,e.points[i+1]);return{x:t,y:a,w:n-t,h:r-a}}case"line":case"arrow":return{x:Math.min(e.x1,e.x2),y:Math.min(e.y1,e.y2),w:Math.abs(e.x2-e.x1),h:Math.abs(e.y2-e.y1)};case"rect":case"ellipse":case"sticker":return{x:e.x,y:e.y,w:e.w,h:e.h};case"text":return{x:e.x,y:e.y,w:e.text.length*e.fontSize*.6,h:e.fontSize*1.2}}}function M(e){const t=S(e);return{x:t.x+t.w/2,y:t.y+t.h/2}}function A(e,t,a,n,r){const i=r*v,o=Math.cos(i),c=Math.sin(i),h=e-a,s=t-n;return{x:a+h*o-s*c,y:n+h*c+s*o}}function F(e,t,a,n,r){return{x:a+(e-a)*r,y:n+(t-n)*r}}function T(e,t,a){const n=g(t);if(n){const r=M(t);e.save(),e.translate(r.x,r.y),e.rotate(n*v),e.translate(-r.x,-r.y),O(e,t,a),e.restore();return}O(e,t,a)}function O(e,t,a){switch(t.type){case"pen":{y(e,t.color,t.width),e.beginPath(),e.moveTo(t.points[0],t.points[1]);for(let n=2;n<t.points.length;n+=2)e.lineTo(t.points[n],t.points[n+1]);e.stroke();break}case"line":case"arrow":{y(e,t.color,t.width),e.beginPath(),e.moveTo(t.x1,t.y1),e.lineTo(t.x2,t.y2),e.stroke(),t.type==="arrow"&&j(e,t.x1,t.y1,t.x2,t.y2,t.width);break}case"rect":{y(e,t.color,t.width),e.strokeRect(t.x,t.y,t.w,t.h);break}case"ellipse":{y(e,t.color,t.width),e.beginPath(),e.ellipse(t.x+t.w/2,t.y+t.h/2,Math.abs(t.w/2),Math.abs(t.h/2),0,0,Math.PI*2),e.stroke();break}case"text":{e.fillStyle=t.color,e.font=`bold ${t.fontSize}px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif`,e.textBaseline="top",e.strokeStyle="rgba(0,0,0,0.45)",e.lineWidth=Math.max(2,t.fontSize/16),e.lineJoin="round",e.strokeText(t.text,t.x,t.y),e.fillText(t.text,t.x,t.y);break}case"sticker":{if(t.emoji)e.font=`${Math.min(t.w,t.h)}px -apple-system, "Apple Color Emoji", "Segoe UI Emoji", sans-serif`,e.textBaseline="top",e.fillText(t.emoji,t.x,t.y);else if(t.src){const n=a?.get(t.src);n&&e.drawImage(n,t.x,t.y,t.w,t.h)}break}}}function x(e,t,a){for(const n of t)T(e,n,a)}async function P(e){const t=new Map,a=[...new Set(e.filter(n=>n.type==="sticker"&&n.src).map(n=>n.src))];return await Promise.all(a.map(n=>new Promise(r=>{const i=new Image;i.onload=()=>{t.set(n,i),r()},i.onerror=()=>r(),i.src=n}))),t}async function E(e,t,a){if(!e?.length)return null;const n=document.createElement("canvas");n.width=t,n.height=a;const r=n.getContext("2d");if(!r)return null;const i=await P(e);return x(r,e,i),new Promise(o=>n.toBlob(c=>o(c),"image/png"))}function $(e,t,a,n=12){for(let r=e.length-1;r>=0;r--){const i=e[r];if(D(i,t,a,n))return r}return-1}function V(e,t,a,n,r){return Math.abs(e-a)<=r&&Math.abs(t-n)<=r}function C(e,t,a,n,r,i,o){const c=r-a,h=i-n,s=c*c+h*h||1,u=Math.max(0,Math.min(1,((e-a)*c+(t-n)*h)/s)),l=a+u*c,p=n+u*h;return(e-l)**2+(t-p)**2<=o*o}function D(e,t,a,n){const r=g(e);if(r){const i=M(e),o=A(t,a,i.x,i.y,-r);return k(e,o.x,o.y,n)}return k(e,t,a,n)}function k(e,t,a,n){switch(e.type){case"pen":{for(let r=0;r+3<e.points.length;r+=2)if(C(t,a,e.points[r],e.points[r+1],e.points[r+2],e.points[r+3],n+e.width/2))return!0;return!1}case"line":case"arrow":return C(t,a,e.x1,e.y1,e.x2,e.y2,n+e.width/2);case"rect":case"ellipse":return t>=e.x-n&&t<=e.x+e.w+n&&a>=e.y-n&&a<=e.y+e.h+n;case"text":return t>=e.x-n&&t<=e.x+e.text.length*e.fontSize*.6+n&&a>=e.y-n&&a<=e.y+e.fontSize+n;case"sticker":return V(t,a,e.x+e.w/2,e.y+e.h/2,Math.max(e.w,e.h)/2+n)}}function _(e,t,a){const n=Math.max(8,a);if(!(e>0)||!(t>0))return{w:n,h:n};const r=n/Math.max(e,t);return{w:Math.max(8,Math.round(e*r)),h:Math.max(8,Math.round(t*r))}}function N(e,t){const a=Math.max(.05,t),n=M(e);switch(e.type){case"pen":return{...e,points:e.points.map((r,i)=>i%2===0?n.x+(r-n.x)*a:n.y+(r-n.y)*a)};case"line":case"arrow":{const r=F(e.x1,e.y1,n.x,n.y,a),i=F(e.x2,e.y2,n.x,n.y,a);return{...e,x1:r.x,y1:r.y,x2:i.x,y2:i.y}}case"rect":case"ellipse":case"sticker":return{...e,w:Math.max(8,e.w*a),h:Math.max(8,e.h*a)};case"text":return{...e,fontSize:Math.min(512,Math.max(8,e.fontSize*a))};default:return e}}function U(e,t){return{...e,rotation:b(t)}}function Y(e,t,a){switch(e.type){case"pen":{const n=e.points.map((r,i)=>i%2===0?r+t:r+a);return{...e,points:n}}case"line":case"arrow":return{...e,x1:e.x1+t,y1:e.y1+a,x2:e.x2+t,y2:e.y2+a};case"rect":case"ellipse":case"sticker":return{...e,x:e.x+t,y:e.y+a};case"text":return{...e,x:e.x+t,y:e.y+a}}}var I=Math.PI/180;function B(e,t,a){const n=J(a);if(n===0||n===180)return{w:e,h:t};if(n===90||n===270)return{w:t,h:e};const r=n*I;return{w:Math.round(Math.abs(e*Math.cos(r))+Math.abs(t*Math.sin(r))),h:Math.round(Math.abs(e*Math.sin(r))+Math.abs(t*Math.cos(r)))}}function J(e){return(e%360+360)%360}function f(e){const t=Math.round(e);return t-t%2}function z(e,t){let a=e.w,n=a/t;return n>e.h&&(n=e.h,a=n*t),{x:Math.round((e.w-a)/2),y:Math.round((e.h-n)/2),width:Math.round(a),height:Math.round(n)}}function L(e,t,a){const n={x:0,y:0,width:e.width,height:e.height},r={x:0,y:0,width:t.w,height:t.h};return a==="cover"?{src:z({w:e.width,h:e.height},t.w/t.h),dst:r}:a==="contain"?{src:n,dst:z({w:t.w,h:t.h},e.width/e.height)}:{src:n,dst:r}}function R(e,t){if(t.scale)return{w:f(t.scale.width),h:f(t.scale.height)};if(t.crop)return{w:f(t.crop.width),h:f(t.crop.height)};const a=B(e.w,e.h,t.rotation);return{w:f(a.w),h:f(a.h)}}function X(e){const t=String(e??"").match(/^data:(image\/(?:png|jpe?g));base64,([a-z0-9+/=]+)$/i);if(!t)return null;const a=atob(t[2]),n=new Uint8Array(a.length);for(let r=0;r<a.length;r++)n[r]=a.charCodeAt(r);return new Blob([n],{type:t[1].toLowerCase()})}async function q(e,t,a=[]){const n=e.getExportPlan(),r=new FormData;n.videoSrcOrder.forEach((s,u)=>{const l=t[s];l&&r.append(`video_${u}`,l,l.name||`video_${u}.mp4`)}),n.audioSrcOrder.forEach((s,u)=>{const l=a[s];l&&r.append(`audio_${u}`,l,l.name||`audio_${u}`)});const i=R(n.state.canvas,n.state),o=await E(n.state.overlays,i.w,i.h);o&&r.append("overlay",o,"overlay.png");const c=n.state.poster?.useAsVideoCover?X(n.state.poster.preview):null;c&&r.append("videoCover",c,"video-cover.png");const h=n.state.poster?.preview?{...n.state,poster:{...n.state.poster,preview:void 0}}:n.state;return r.append("editState",JSON.stringify(h)),r}var G=[{id:"bw",label:"\u9ED1\u767D",vf:"hue=s=0",css:"grayscale(1)"},{id:"sepia",label:"\u590D\u53E4",vf:"colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131",css:"sepia(0.85)"},{id:"warm",label:"\u6696\u9633",vf:"colorbalance=rs=.15:gs=.04:bs=-.15",css:"sepia(0.25) saturate(1.25) brightness(1.03)"},{id:"cool",label:"\u51B7\u8C03",vf:"colorbalance=rs=-.14:bs=.16",css:"hue-rotate(12deg) saturate(0.95) brightness(1.02)"},{id:"vivid",label:"\u9C9C\u8273",vf:"eq=saturation=1.45:contrast=1.08",css:"saturate(1.45) contrast(1.08)"}];function H(e){return e?G.find(t=>t.id===e):void 0}function W(e,t){const a=H(t)?.css??"";return[`brightness(${1+e.brightness})`,`contrast(${1+e.contrast})`,`saturate(${1+e.saturation})`,a].filter(Boolean).join(" ")}function K(e,t,a,n,r,i=1,o,c=!0){const h=R(n,r);e.width=Math.max(2,Math.round(h.w*i)),e.height=Math.max(2,Math.round(h.h*i));const s=e.getContext("2d");if(!s)return;const u=B(n.w,n.h,r.rotation),l=r.crop??{x:0,y:0,width:u.w,height:u.h},p=r.scale?.fit??"force",{src:w,dst:d}=L({width:l.width,height:l.height},h,p);p==="contain"&&(s.fillStyle="#000",s.fillRect(0,0,e.width,e.height)),s.filter=W(r.filters,r.filterPreset),s.save(),p==="contain"&&(s.beginPath(),s.rect(i*d.x,i*d.y,i*d.width,i*d.height),s.clip()),s.translate(i*d.x,i*d.y),s.scale(i*d.width/w.width,i*d.height/w.height),s.translate(-(l.x+w.x),-(l.y+w.y)),s.translate(u.w/2,u.h/2),s.rotate(r.rotation*I),s.scale(r.flipX?-1:1,r.flipY?-1:1);const m=L({width:a.w,height:a.h},{w:n.w,h:n.h},"cover");s.drawImage(t,m.src.x,m.src.y,m.src.width,m.src.height,-n.w/2,-n.h/2,n.w,n.h),s.restore(),s.filter="none",c&&r.overlays?.length&&(s.setTransform(i,0,0,i,0,0),x(s,r.overlays,o),s.setTransform(1,0,0,1,0,0))}function Q(e,t){return new Promise(a=>{const n=()=>{e.removeEventListener("seeked",n),a()};e.addEventListener("seeked",n),e.currentTime=Math.min(Math.max(0,t),e.duration||t)})}async function Z(e,t,a=160){const n=e.duration;if(!n||!isFinite(n)||t<=0)return[];const r=e.videoWidth||16,i=e.videoHeight||9,o=Math.min(a,r),c=Math.max(1,Math.round(o*i/r)),h=document.createElement("canvas");h.width=o,h.height=c;const s=h.getContext("2d");if(!s)return[];const u=[];for(let l=0;l<t;l++)await Q(e,n*(l+.5)/t),s.drawImage(e,0,0,o,c),u.push(h.toDataURL("image/jpeg",.6));return u}function ee(e,t={}){const a=t.mode??"overlay",n=document.createElement("button");n.type="button",n.className=["me-fullscreen-toggle",t.className].filter(Boolean).join(" "),n.dataset.meFullscreenToggle="true",n.dataset.mode=a,n.title="\u5168\u5C4F / \u9000\u51FA\u5168\u5C4F",n.style.cssText=[a==="overlay"?"position:absolute;left:8px;bottom:8px;z-index:30;":"position:static;","width:32px;height:32px;padding:0;","display:inline-flex;align-items:center;justify-content:center;","border:1px solid var(--me-border,#26262b);border-radius:6px;","background:var(--me-chip,#1c1c20);color:var(--me-fg,#e7e7ee);","cursor:pointer;font-size:15px;line-height:1;opacity:0.9;font:inherit;"].join(""),e.classList.add("me-fullscreen-host");const r=()=>{const c=document.fullscreenElement===e;n.textContent=c?"\xD7":"\u26F6",n.setAttribute("aria-label",c?"\u9000\u51FA\u5168\u5C4F":"\u5168\u5C4F"),e.classList.toggle("me-is-fullscreen",c),e.dataset.meFullscreen=c?"true":"false"};r();const i=()=>r(),o=()=>{(document.fullscreenElement===e?document.exitFullscreen?.():e.requestFullscreen?.())?.catch?.(()=>{})};return n.addEventListener("click",o),document.addEventListener("fullscreenchange",i),{button:n,dispose:()=>{document.removeEventListener("fullscreenchange",i),n.removeEventListener("click",o),n.remove(),e.classList.remove("me-fullscreen-host","me-is-fullscreen"),delete e.dataset.meFullscreen}}}exports.buildVideoExportFormData=q,exports.createFullscreenToggle=ee,exports.drawOverlay=T,exports.drawOverlays=x,exports.drawVideoFrame=K,exports.fitStickerBox=_,exports.generateThumbnails=Z,exports.getOverlayRotation=g,exports.hitTestOverlay=$,exports.loadOverlayImages=P,exports.overlayBBox=S,exports.renderOverlaysToPng=E,exports.rotateOverlay=U,exports.scaleOverlay=N,exports.translateOverlay=Y;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { O as OverlayT, V as VideoEditStateT } from './state-B85vkf7_.js';
|
|
2
|
+
import { VideoEditorEngine } from './engine.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* editor-core/overlay-render —— 标注/贴纸绘制(浏览器环境,Canvas API;零框架依赖)。
|
|
7
|
+
* 交互画布与导出 PNG 共用 drawOverlays,保证所见即所得。
|
|
8
|
+
* 坐标系 = 最终输出空间(旋转/裁剪/缩放后),导出 PNG 即按输出分辨率渲染。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare function getOverlayRotation(o: OverlayT): number;
|
|
12
|
+
declare function overlayBBox(o: OverlayT): {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
w: number;
|
|
16
|
+
h: number;
|
|
17
|
+
};
|
|
18
|
+
/** 单个对象绘制;sticker 的位图经 imageCache 提供(加载由调用方负责) */
|
|
19
|
+
declare function drawOverlay(ctx: CanvasRenderingContext2D, o: OverlayT, imageCache?: Map<string, HTMLImageElement>): void;
|
|
20
|
+
declare function drawOverlays(ctx: CanvasRenderingContext2D, overlays: OverlayT[], imageCache?: Map<string, HTMLImageElement>): void;
|
|
21
|
+
/** sticker 位图预加载(dataURL → HTMLImageElement) */
|
|
22
|
+
declare function loadOverlayImages(overlays: OverlayT[]): Promise<Map<string, HTMLImageElement>>;
|
|
23
|
+
/** 导出:把 overlays 渲染成输出分辨率的透明 PNG Blob(无 overlay 返回 null) */
|
|
24
|
+
declare function renderOverlaysToPng(overlays: OverlayT[] | undefined, width: number, height: number): Promise<Blob | null>;
|
|
25
|
+
/** 命中检测(选中/拖动用):返回最上层命中的 overlay 下标,无命中 -1 */
|
|
26
|
+
declare function hitTestOverlay(overlays: OverlayT[], x: number, y: number, tolerance?: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* 贴纸图片等比落盒:长边 = boxSide、短边按原始宽高比,下限 8px。
|
|
29
|
+
* 上传任意比例图片不再被压成正方形(drawImage 是拉伸语义,入库 w/h 必须等比)。
|
|
30
|
+
*/
|
|
31
|
+
declare function fitStickerBox(natW: number, natH: number, boxSide: number): {
|
|
32
|
+
w: number;
|
|
33
|
+
h: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 缩放 overlay(选中后拖右下角把手):以对象左上角为锚,按 ratio 等比。
|
|
37
|
+
* sticker/rect/ellipse 改 w/h;text 改 fontSize;pen/line/arrow 不支持(原样返回)。
|
|
38
|
+
*/
|
|
39
|
+
declare function scaleOverlay(o: OverlayT, ratio: number): OverlayT;
|
|
40
|
+
/** 旋转 overlay:以对象 bbox 中心为视觉锚点;坐标本身不变,绘制/命中检测按 rotation 处理。 */
|
|
41
|
+
declare function rotateOverlay(o: OverlayT, deg: number): OverlayT;
|
|
42
|
+
/** 平移 overlay(拖动选中对象) */
|
|
43
|
+
declare function translateOverlay(o: OverlayT, dx: number, dy: number): OverlayT;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 把引擎导出计划 + 浏览器端 File 组装成服务端约定的 FormData
|
|
47
|
+
* (video_0../audio_0../overlay/editState),供 @media-editor/client.exportVideo 直接发。
|
|
48
|
+
* 触碰 DOM(overlay → canvas PNG),故归 @media-editor/core/browser,React/Vue 共用。
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param videoFiles 以「原始 srcIdx」为下标的视频 File 数组(引擎按 videoSrcOrder 取并重排)
|
|
53
|
+
* @param audioFiles 同上(音频)
|
|
54
|
+
*/
|
|
55
|
+
declare function buildVideoExportFormData(engine: VideoEditorEngine, videoFiles: ReadonlyArray<File | undefined>, audioFiles?: ReadonlyArray<File | undefined>): Promise<FormData>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 帧合成器(框架无关 canvas)—— 把「源帧 → 输出画布 + 完整效果链 + overlays」绘到 canvas。
|
|
59
|
+
* 从 SocialEcho VideoFramePreview.drawFinalFrame 抽出,供实时预览与封面导出复用。
|
|
60
|
+
*
|
|
61
|
+
* 效果链与 ffmpeg 导出严格同构:源→画布 cover 归一 → flips → rotate → crop → scale + 调色/滤镜 css。
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/** drawImage 可接受的源(video / image / canvas) */
|
|
65
|
+
type FrameSource = CanvasImageSource;
|
|
66
|
+
/**
|
|
67
|
+
* 把一帧合成绘到 canvas(物理分辨率 = 输出尺寸 × k)。
|
|
68
|
+
* @param srcDims 活动源原始分辨率(逐片段可能不同)
|
|
69
|
+
* @param canvasDims 输出画布(= 首素材分辨率),全局效果链的「源 dims」
|
|
70
|
+
* @param k 物理像素倍率(预览用 ≤1 控带宽,封面导出用 1 全分辨率)
|
|
71
|
+
* @param imageCache 贴纸图片缓存(loadOverlayImages 产出)
|
|
72
|
+
*/
|
|
73
|
+
declare function drawVideoFrame(canvas: HTMLCanvasElement, source: FrameSource, srcDims: {
|
|
74
|
+
w: number;
|
|
75
|
+
h: number;
|
|
76
|
+
}, canvasDims: {
|
|
77
|
+
w: number;
|
|
78
|
+
h: number;
|
|
79
|
+
}, state: VideoEditStateT, k?: number, imageCache?: Map<string, HTMLImageElement>, withOverlays?: boolean): void;
|
|
80
|
+
/**
|
|
81
|
+
* 抽 count 张等距缩略图(dataURL,jpeg)。供时间轴缩略图条 / 封面条。
|
|
82
|
+
* 传入**专用离屏 video 元素**(勿用主预览源,seek 会打断播放)。
|
|
83
|
+
*/
|
|
84
|
+
declare function generateThumbnails(videoEl: HTMLVideoElement, count: number, maxW?: number): Promise<string[]>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 全屏切换按钮(框架无关 DOM)。点按对 host 进/出全屏;监听 fullscreenchange 切图标和 host 状态类。
|
|
88
|
+
* 图片/视频 mount 共用:默认可作为覆盖按钮,也可放进宿主自己的返回工具栏。
|
|
89
|
+
*/
|
|
90
|
+
interface FullscreenToggle {
|
|
91
|
+
button: HTMLButtonElement;
|
|
92
|
+
dispose: () => void;
|
|
93
|
+
}
|
|
94
|
+
interface FullscreenToggleOptions {
|
|
95
|
+
mode?: 'overlay' | 'inline';
|
|
96
|
+
className?: string;
|
|
97
|
+
}
|
|
98
|
+
declare function createFullscreenToggle(host: HTMLElement, options?: FullscreenToggleOptions): FullscreenToggle;
|
|
99
|
+
|
|
100
|
+
export { type FrameSource, type FullscreenToggle, type FullscreenToggleOptions, buildVideoExportFormData, createFullscreenToggle, drawOverlay, drawOverlays, drawVideoFrame, fitStickerBox, generateThumbnails, getOverlayRotation, hitTestOverlay, loadOverlayImages, overlayBBox, renderOverlaysToPng, rotateOverlay, scaleOverlay, translateOverlay };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{outputSize as G,rotatedSize as Tt,fitRects as it,buildFilterCss as at,DEG2RAD as St,findFilterPreset as Ct}from"./chunk-USY6UDGL.js";var st=Math.PI/180;function D(t,e,r){t.strokeStyle=e,t.lineWidth=r,t.lineCap="round",t.lineJoin="round"}function Ft(t,e,r,n,i,a){const c=Math.atan2(i-r,n-e),o=Math.max(12,a*3.5);t.beginPath(),t.moveTo(n,i),t.lineTo(n-o*Math.cos(c-Math.PI/7),i-o*Math.sin(c-Math.PI/7)),t.moveTo(n,i),t.lineTo(n-o*Math.cos(c+Math.PI/7),i-o*Math.sin(c+Math.PI/7)),t.stroke()}function ot(t){if(!Number.isFinite(t))return 0;let e=t%360;return e>180&&(e-=360),e<-180&&(e+=360),Math.abs(e)<.001?0:e}var At=.84;function Nt(t){const e=Math.max(8,Number(t?.w)||0),r=Math.max(8,Number(t?.h)||0),n=Math.max(8,Math.min(e,r)*At);return{x:(Number(t?.x)||0)+(e-n)/2,y:(Number(t?.y)||0)+(r-n)/2,size:n}}var V=null;function Ot(){return typeof document>"u"?null:(V??(V=document.createElement("canvas")),V.getContext("2d"))}function ct(t){return Math.min(512,Math.max(8,Number(t.fontSize)||24))}function kt(t,e){return Number.isFinite(t.paddingX)?Math.max(0,t.paddingX):Math.max(8,Math.round(e*.18))}function Pt(t,e){return Number.isFinite(t.paddingY)?Math.max(0,t.paddingY):Math.max(6,Math.round(e*.14))}function Et(t,e){return(Number.isFinite(t.lineHeight)?Math.max(.9,t.lineHeight):1.1)*e}function Wt(t){return t.textAlign==="center"||t.textAlign==="right"?t.textAlign:"left"}function It(t){const e=ct(t),r=t.fontStyle||"normal",n=t.fontWeight||"700",i=t.fontFamily||'-apple-system, "PingFang SC", "Microsoft YaHei", sans-serif';return`${r} ${n} ${e}px ${i}`}function zt(t,e){const r=t==null?"":String(t);switch(e){case"uppercase":return r.toUpperCase();case"lowercase":return r.toLowerCase();case"capitalize":return r.toLowerCase().replace(/(^|\s)(\S)/g,(n,i,a)=>`${i}${a.toUpperCase()}`);default:return r}}function Bt(t,e){const n=zt(t,e).replace(/\r\n/g,`
|
|
2
|
+
`).split(`
|
|
3
|
+
`);return n.length?n:[""]}function X(t){return Number.isFinite(t)&&t>=0}function Lt(t){if(!J(t))return null;const e=X(t.startAt)?Number(t.startAt):null,r=X(t.endAt)?Number(t.endAt):null;if(e==null&&r==null)return null;const n=Math.max(0,e??0),i=Math.max(n,r??n);return{start:n,end:i}}function lt(t){const e=Lt(t);return!!e&&e.end>e.start}function J(t){return t?.type==="pen"||t?.type==="line"||t?.type==="arrow"||t?.type==="rect"||t?.type==="ellipse"||t?.type==="text"||t?.type==="sticker"}function Yt(t){if(!J(t)&&t?.type!=="filter"&&t?.type!=="adjust")return null;const e=X(t.startAt)?Number(t.startAt):null,r=X(t.endAt)?Number(t.endAt):null;if(e==null&&r==null)return null;const n=Math.max(0,e??0),i=Math.max(n,r??n);return{start:n,end:i}}function jt(t,e){const r=Yt(t);return r?Number.isFinite(e)?e>=r.start&&e<=r.end:!1:!0}function Xt(t,e){return t?.length?Number.isFinite(e)?t.filter(r=>jt(r,e)):t:[]}function ut(t){return t?.type==="text"||t?.type==="sticker"||t?.type==="filter"||t?.type==="adjust"}function ht(t){return Number.isFinite(t?.timelineTrack)?Math.max(0,Math.round(Number(t.timelineTrack))):0}function dt(t){return X(t?.startAt)?Number(t.startAt):0}function q(t){const e=ct(t),r=kt(t,e),n=Pt(t,e),i=Et(t,e),a=Bt(t.text,t.textTransform),c=Ot(),o=It(t);c&&(c.font=o);const f=a.map(x=>{const y=x===""?" ":x;return c?c.measureText(y).width:Math.max(1,y.length)*e*.6}),s=f.length?Math.max(...f):e*.6,w=Math.max(e,a.length*i),b=Math.max(Number.isFinite(t.width)?t.width:0,s+r*2),m=w+n*2;return{font:o,fontSize:e,paddingX:r,paddingY:n,lineHeight:i,lines:a,widths:f,boxWidth:b,boxHeight:m,align:Wt(t)}}function Rt(t,e,r){return e.align==="center"?t.x+e.boxWidth/2-r/2:e.align==="right"?t.x+e.boxWidth-e.paddingX-r:t.x+e.paddingX}function $t(t,e,r,n,i,a){if(!e.textDecoration||a<=0)return;const c=r.fontSize,o=String(e.textDecoration).includes("underline"),f=String(e.textDecoration).includes("line-through");if(!(!o&&!f)){if(t.save(),t.strokeStyle=e.color||"#fff",t.lineWidth=Math.max(1,c/18),o){const s=i+c+Math.max(1,c*.08);t.beginPath(),t.moveTo(n,s),t.lineTo(n+a,s),t.stroke()}if(f){const s=i+c*.56;t.beginPath(),t.moveTo(n,s),t.lineTo(n+a,s),t.stroke()}t.restore()}}function K(t){return ot(t.rotation??0)}function ft(t){switch(t.type){case"pen":{let e=1/0,r=1/0,n=-1/0,i=-1/0;for(let a=0;a<t.points.length;a+=2)e=Math.min(e,t.points[a]),n=Math.max(n,t.points[a]),r=Math.min(r,t.points[a+1]),i=Math.max(i,t.points[a+1]);return{x:e,y:r,w:n-e,h:i-r}}case"line":case"arrow":return{x:Math.min(t.x1,t.x2),y:Math.min(t.y1,t.y2),w:Math.abs(t.x2-t.x1),h:Math.abs(t.y2-t.y1)};case"rect":case"ellipse":case"sticker":return{x:t.x,y:t.y,w:t.w,h:t.h};case"text":{const e=q(t);return{x:t.x,y:t.y,w:e.boxWidth,h:e.boxHeight}}}}function Q(t){const e=ft(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Ht(t,e,r,n,i){const a=i*st,c=Math.cos(a),o=Math.sin(a),f=t-r,s=e-n;return{x:r+f*c-s*o,y:n+f*o+s*c}}function mt(t,e,r,n,i){return{x:r+(t-r)*i,y:n+(e-n)*i}}function Z(t,e,r){const n=K(e);if(n){const i=Q(e);t.save(),t.translate(i.x,i.y),t.rotate(n*st),t.translate(-i.x,-i.y),pt(t,e,r),t.restore();return}pt(t,e,r)}function pt(t,e,r){switch(e.type){case"pen":{D(t,e.color,e.width),t.beginPath(),t.moveTo(e.points[0],e.points[1]);for(let n=2;n<e.points.length;n+=2)t.lineTo(e.points[n],e.points[n+1]);t.stroke();break}case"line":case"arrow":{D(t,e.color,e.width),t.beginPath(),t.moveTo(e.x1,e.y1),t.lineTo(e.x2,e.y2),t.stroke(),e.type==="arrow"&&Ft(t,e.x1,e.y1,e.x2,e.y2,e.width);break}case"rect":{D(t,e.color,e.width),t.strokeRect(e.x,e.y,e.w,e.h);break}case"ellipse":{D(t,e.color,e.width),t.beginPath(),t.ellipse(e.x+e.w/2,e.y+e.h/2,Math.abs(e.w/2),Math.abs(e.h/2),0,0,Math.PI*2),t.stroke();break}case"text":{const n=q(e),i=Number.isFinite(e.opacity)?Math.min(1,Math.max(0,e.opacity/100)):1,a=Number.isFinite(e.borderWidth)?Math.max(0,e.borderWidth):0,c=e.autoOutline!==!1;t.save(),t.globalAlpha*=i,e.backgroundColor&&e.backgroundColor!=="transparent"&&(t.fillStyle=e.backgroundColor,t.fillRect(e.x,e.y,n.boxWidth,n.boxHeight)),t.fillStyle=e.color||"#fff",t.font=n.font,t.textBaseline="top",t.lineJoin="round";const o=e.boxShadow;o&&(t.shadowColor=o.color||"transparent",t.shadowOffsetX=o.x||0,t.shadowOffsetY=o.y||0,t.shadowBlur=o.blur||0),n.lines.forEach((f,s)=>{const w=n.widths[s]??0,b=Rt(e,n,w),m=e.y+n.paddingY+s*n.lineHeight;(a>0||c)&&(t.strokeStyle=a>0?e.borderColor||"#000":e.strokeColor||"rgba(0,0,0,0.45)",t.lineWidth=a>0?a:Math.max(1,n.fontSize/18),t.strokeText(f,b,m)),t.fillText(f,b,m),$t(t,e,n,b,m,w)}),t.restore();break}case"sticker":{if(e.emoji){const n=Nt(e);t.save(),t.font=`${n.size}px -apple-system, "Apple Color Emoji", "Segoe UI Emoji", sans-serif`,t.textAlign="center",t.textBaseline="middle",t.fillText(e.emoji,n.x+n.size/2,n.y+n.size/2),t.restore()}else if(e.src){const n=r?.get(e.src);n&&t.drawImage(n,e.x,e.y,e.w,e.h)}break}}}function tt(t,e,r){for(const n of e)Z(t,n,r)}async function gt(t){const e=new Map,r=[...new Set(t.filter(n=>n.type==="sticker"&&n.src).map(n=>n.src))];return await Promise.all(r.map(n=>new Promise(i=>{const a=new Image;a.onload=()=>{e.set(n,a),i()},a.onerror=()=>i(),a.src=n}))),e}async function et(t,e,r){if(!t?.length)return null;const n=document.createElement("canvas");n.width=e,n.height=r;const i=n.getContext("2d");if(!i)return null;const a=await gt(t);return tt(i,t,a),new Promise(c=>n.toBlob(o=>c(o),"image/png"))}function Dt(t,e,r,n=12){for(let i=t.length-1;i>=0;i--){const a=t[i];if(Ut(a,e,r,n))return i}return-1}function _t(t,e,r,n,i){return Math.abs(t-r)<=i&&Math.abs(e-n)<=i}function yt(t,e,r,n,i,a,c){const o=i-r,f=a-n,s=o*o+f*f||1,w=Math.max(0,Math.min(1,((t-r)*o+(e-n)*f)/s)),b=r+w*o,m=n+w*f;return(t-b)**2+(e-m)**2<=c*c}function Ut(t,e,r,n){const i=K(t);if(i){const a=Q(t),c=Ht(e,r,a.x,a.y,-i);return wt(t,c.x,c.y,n)}return wt(t,e,r,n)}function wt(t,e,r,n){switch(t.type){case"pen":{for(let i=0;i+3<t.points.length;i+=2)if(yt(e,r,t.points[i],t.points[i+1],t.points[i+2],t.points[i+3],n+t.width/2))return!0;return!1}case"line":case"arrow":return yt(e,r,t.x1,t.y1,t.x2,t.y2,n+t.width/2);case"rect":case"ellipse":return e>=t.x-n&&e<=t.x+t.w+n&&r>=t.y-n&&r<=t.y+t.h+n;case"text":{const i=q(t);return e>=t.x-n&&e<=t.x+i.boxWidth+n&&r>=t.y-n&&r<=t.y+i.boxHeight+n}case"sticker":return _t(e,r,t.x+t.w/2,t.y+t.h/2,Math.max(t.w,t.h)/2+n)}}function Gt(t,e,r){const n=Math.max(8,r);if(!(t>0)||!(e>0))return{w:n,h:n};const i=n/Math.max(t,e);return{w:Math.max(8,Math.round(t*i)),h:Math.max(8,Math.round(e*i))}}function Vt(t,e){const r=Math.max(.05,e),n=Q(t);switch(t.type){case"pen":return{...t,points:t.points.map((i,a)=>a%2===0?n.x+(i-n.x)*r:n.y+(i-n.y)*r)};case"line":case"arrow":{const i=mt(t.x1,t.y1,n.x,n.y,r),a=mt(t.x2,t.y2,n.x,n.y,r);return{...t,x1:i.x,y1:i.y,x2:a.x,y2:a.y}}case"rect":case"ellipse":case"sticker":return{...t,w:Math.max(8,t.w*r),h:Math.max(8,t.h*r)};case"text":return{...t,fontSize:Math.min(512,Math.max(8,t.fontSize*r)),width:Number.isFinite(t.width)?Math.max(80,t.width*r):t.width,paddingX:Number.isFinite(t.paddingX)?Math.max(0,t.paddingX*r):t.paddingX,paddingY:Number.isFinite(t.paddingY)?Math.max(0,t.paddingY*r):t.paddingY,borderWidth:Number.isFinite(t.borderWidth)?Math.max(0,t.borderWidth*r):t.borderWidth,boxShadow:t.boxShadow?{...t.boxShadow,x:(t.boxShadow.x||0)*r,y:(t.boxShadow.y||0)*r,blur:(t.boxShadow.blur||0)*r}:t.boxShadow};default:return t}}function Jt(t,e){return{...t,rotation:ot(e)}}function qt(t,e,r){switch(t.type){case"pen":{const n=t.points.map((i,a)=>a%2===0?i+e:i+r);return{...t,points:n}}case"line":case"arrow":return{...t,x1:t.x1+e,y1:t.y1+r,x2:t.x2+e,y2:t.y2+r};case"rect":case"ellipse":case"sticker":return{...t,x:t.x+e,y:t.y+r};case"text":return{...t,x:t.x+e,y:t.y+r}}}function Y(t,e){const r=Math.min(8192,Math.max(2,Math.round(Number(t)||e)));return r%2===0?r:r-1}function ce(t){const e=G(t.canvas,t),r=t?.exportSettings??null;if(!r)return e;const n=Number.isFinite(Number(r.width))&&Number(r.width)>0,i=Number.isFinite(Number(r.height))&&Number(r.height)>0;if(!n&&!i)return e;const a=e.w>0&&e.h>0?e.w/e.h:1;if(n&&i)return{w:Y(r.width,e.w),h:Y(r.height,e.h)};if(n){const o=Y(r.width,e.w);return{w:o,h:Y(o/a,e.h)}}const c=Y(r.height,e.h);return{w:Y(c*a,e.w),h:c}}function Kt(t){const e=String(t??"").match(/^data:(image\/(?:png|jpe?g));base64,([a-z0-9+/=]+)$/i);if(!e)return null;const r=atob(e[2]),n=new Uint8Array(r.length);for(let i=0;i<r.length;i++)n[i]=r.charCodeAt(i);return new Blob([n],{type:e[1].toLowerCase()})}async function Qt(t,e,r=[]){const n=t.getExportPlan(),i=new FormData;n.videoSrcOrder.forEach((m,x)=>{const y=e[m];y&&i.append(`video_${x}`,y,y.name||`video_${x}.mp4`)}),n.audioSrcOrder.forEach((m,x)=>{const y=r[m];y&&i.append(`audio_${x}`,y,y.name||`audio_${x}`)});const a=G(n.state.canvas,n.state),c=n.state.overlays??[],o=c.filter(m=>lt(m)),f=c.filter(m=>!lt(m)&&J(m)),s=await et(f,a.w,a.h);s&&i.append("overlay",s,"overlay.png");for(let m=0;m<o.length;m++){const x=await et([o[m]],a.w,a.h);x&&i.append(`textOverlay_${m}`,x,`text-overlay-${m}.png`)}const w=n.state.poster?.useAsVideoCover?Kt(n.state.poster.preview):null;w&&i.append("videoCover",w,"video-cover.png");const b=n.state.poster?.preview?{...n.state,poster:{...n.state.poster,preview:void 0}}:n.state;return i.append("editState",JSON.stringify(b)),i}function F(t){return Math.max(-1,Math.min(1,Number(t)||0))}function j(t){return Math.max(0,Math.min(1,Number(t)||0))}function T(t){return Math.max(0,Math.min(1,t))}function N(t){return Math.max(0,Math.min(255,t))}function W(t,e,r){if(t===e)return r<t?0:1;const n=Math.max(0,Math.min(1,(r-t)/(e-t)));return n*n*(3-2*n)}function nt(t){const e=W(.08,.4,t),r=1-W(.6,.92,t);return T(e*r*2.2)}function bt(t,e,r=0){const n=Math.sin(t*12.9898+e*78.233+r*37.719)*43758.5453;return n-Math.floor(n)}var I=null;function Zt(t,e){return typeof document>"u"?null:(I??(I=document.createElement("canvas")),I.width!==t&&(I.width=t),I.height!==e&&(I.height=e),I)}function xt(t,e,r,n){if(!n||n==="none")return;const i=Zt(e,r),a=i?.getContext("2d");!i||!a||(a.setTransform(1,0,0,1,0,0),a.clearRect(0,0,e,r),a.drawImage(t.canvas,0,0,e,r),t.save(),t.setTransform(1,0,0,1,0,0),t.clearRect(0,0,e,r),t.filter=n,t.drawImage(i,0,0,e,r),t.restore(),t.filter="none")}function te(t,e,r,n){const i=Ct(n?.filterPreset)?.css??"";xt(t,e,r,i)}function ee(t,e,r,n){xt(t,e,r,at(n??{},"")),Mt(t,e,r,n??{})}function ne(t={}){return["temperature","tint","vibrance","gamma","highlights","shadows","whites","blacks","lightSense"].some(r=>Math.abs(F(t[r]))>1e-4)?!0:["sharpen","clarity","grain","fade","vignette"].some(r=>j(t[r])>1e-4)}function Mt(t,e,r,n={}){if(!ne(n))return;let i;try{i=t.getImageData(0,0,e,r)}catch{return}const a=F(n.temperature),c=F(n.tint),o=F(n.vibrance),f=F(n.gamma),s=F(n.highlights),w=F(n.shadows),b=F(n.whites),m=F(n.blacks),x=F(n.lightSense),y=j(n.sharpen),O=j(n.clarity),z=j(n.grain),k=j(n.fade),R=j(n.vignette),_=1+f*(f>0?1:.5),M=f===0?1:1/Math.max(.01,_),P=a*.18,$=a*.02,H=-a*.18,rt=c*.1,vt=-c*.12,g=i.data;for(let u=0;u<g.length;u+=4){let d=g[u]/255,p=g[u+1]/255,h=g[u+2]/255;if((a!==0||c!==0)&&(d=T(d+P+rt),p=T(p+$+vt),h=T(h+H+rt)),o!==0){const l=Math.max(d,p,h),S=Math.min(d,p,h),C=l-S,v=(d+p+h)/3,A=o>0?o*(1-C):o*(.35+C*.65);d=T(v+(d-v)*(1+A)),p=T(v+(p-v)*(1+A)),h=T(v+(h-v)*(1+A))}if(f!==0&&(d=T(Math.pow(d,M)),p=T(Math.pow(p,M)),h=T(Math.pow(h,M))),s!==0||w!==0||b!==0||m!==0||x!==0||k!==0){const l=.2126*d+.7152*p+.0722*h,S=1-W(.18,.62,l),C=1-W(.04,.32,l),v=W(.38,.84,l),A=W(.7,.98,l),B=nt(l),E=w*S*.28+s*v*.24+m*C*.2+b*A*.2+x*B*.18+k*(.12+S*.06);if(d=T(d+E),p=T(p+E),h=T(h+E),k>0){const U=(d+p+h)/3,L=k*.16;d=T(d*(1-L)+U*L),p=T(p*(1-L)+U*L),h=T(h*(1-L)+U*L)}}g[u]=Math.round(d*255),g[u+1]=Math.round(p*255),g[u+2]=Math.round(h*255)}if(y>1e-4||O>1e-4){const u=new Uint8ClampedArray(g),d=e*4;for(let p=1;p<r-1;p++)for(let h=1;h<e-1;h++){const l=p*d+h*4,S=(u[l-4]+u[l+4]+u[l-d]+u[l+d]+u[l]*4)/8,C=(u[l-3]+u[l+5]+u[l-d+1]+u[l+d+1]+u[l+1]*4)/8,v=(u[l-2]+u[l+6]+u[l-d+2]+u[l+d+2]+u[l+2]*4)/8,A=(.2126*u[l]+.7152*u[l+1]+.0722*u[l+2])/255,B=Math.min(1.8,y*1.45+O*.9*nt(A));g[l]=N(u[l]+(u[l]-S)*B),g[l+1]=N(u[l+1]+(u[l+1]-C)*B),g[l+2]=N(u[l+2]+(u[l+2]-v)*B)}}if(z>1e-4){const u=z*26;for(let d=0;d<r;d++)for(let p=0;p<e;p++){const h=(d*e+p)*4,l=(.2126*g[h]+.7152*g[h+1]+.0722*g[h+2])/255,S=.4+nt(l)*.9,C=(bt(p,d)-.5)*u*S,v=(bt(p,d,1)-.5)*u*.22*z;g[h]=N(g[h]+C+v),g[h+1]=N(g[h+1]+C*.88),g[h+2]=N(g[h+2]+C-v)}}if(R>1e-4){const u=(e-1)/2,d=(r-1)/2,p=Math.sqrt(u*u+d*d)||1;for(let h=0;h<r;h++)for(let l=0;l<e;l++){const S=(h*e+l)*4,C=l-u,v=h-d,A=Math.sqrt(C*C+v*v)/p,E=1-W(.32,1,A)*R*.62;g[S]=N(g[S]*E),g[S+1]=N(g[S+1]*E),g[S+2]=N(g[S+2]*E)}}t.putImageData(i,0,0)}function re(t,e,r,n,i,a=1,c,o=!0){const f=G(n,i);t.width=Math.max(2,Math.round(f.w*a)),t.height=Math.max(2,Math.round(f.h*a));const s=t.getContext("2d");if(!s)return;const w=Tt(n.w,n.h,i.rotation),b=i.crop??{x:0,y:0,width:w.w,height:w.h},m=i.scale?.fit??"force",{src:x,dst:y}=it({width:b.width,height:b.height},f,m);m==="contain"&&(s.fillStyle="#000",s.fillRect(0,0,t.width,t.height)),s.filter=at(i.filters,i.filterPreset),s.save(),m==="contain"&&(s.beginPath(),s.rect(a*y.x,a*y.y,a*y.width,a*y.height),s.clip()),s.translate(a*y.x,a*y.y),s.scale(a*y.width/x.width,a*y.height/x.height),s.translate(-(b.x+x.x),-(b.y+x.y)),s.translate(w.w/2,w.h/2),s.rotate(i.rotation*St),s.scale(i.flipX?-1:1,i.flipY?-1:1);const O=it({width:r.w,height:r.h},{w:n.w,h:n.h},"cover");if(s.drawImage(e,O.src.x,O.src.y,O.src.width,O.src.height,-n.w/2,-n.h/2,n.w,n.h),s.restore(),s.filter="none",Mt(s,t.width,t.height,i.filters),o&&i.overlays?.length){const z=Number.isFinite(i?.__mePreviewTimelineTime)?Number(i.__mePreviewTimelineTime):null,k=z==null?i.overlays:Xt(i.overlays,z),R=k.map((M,P)=>({overlay:M,index:P})).filter(M=>ut(M.overlay)).sort((M,P)=>{const $=ht(P.overlay)-ht(M.overlay);if($!==0)return $;const H=dt(M.overlay)-dt(P.overlay);return Math.abs(H)>1e-6?H:M.index-P.index}),_=k.filter(M=>!ut(M));s.setTransform(a,0,0,a,0,0),R.forEach(M=>{if(M.overlay?.type==="filter"){s.setTransform(1,0,0,1,0,0),te(s,t.width,t.height,M.overlay),s.setTransform(a,0,0,a,0,0);return}if(M.overlay?.type==="adjust"){s.setTransform(1,0,0,1,0,0),ee(s,t.width,t.height,M.overlay),s.setTransform(a,0,0,a,0,0);return}Z(s,M.overlay,c)}),tt(s,_,c),s.setTransform(1,0,0,1,0,0)}}function ie(t,e){return new Promise(r=>{const n=()=>{t.removeEventListener("seeked",n),r()};t.addEventListener("seeked",n),t.currentTime=Math.min(Math.max(0,e),t.duration||e)})}async function ae(t,e,r=160){const n=t.duration;if(!n||!isFinite(n)||e<=0)return[];const i=t.videoWidth||16,a=t.videoHeight||9,c=Math.min(r,i),o=Math.max(1,Math.round(c*a/i)),f=document.createElement("canvas");f.width=c,f.height=o;const s=f.getContext("2d");if(!s)return[];const w=[];for(let b=0;b<e;b++)await ie(t,n*(b+.5)/e),s.drawImage(t,0,0,c,o),w.push(f.toDataURL("image/jpeg",.6));return w}function se(t,e={}){const r=e.mode??"overlay",n=document.createElement("button");n.type="button",n.className=["me-fullscreen-toggle",e.className].filter(Boolean).join(" "),n.dataset.meFullscreenToggle="true",n.dataset.mode=r,n.title="\u5168\u5C4F / \u9000\u51FA\u5168\u5C4F",n.style.cssText=[r==="overlay"?"position:absolute;left:8px;bottom:8px;z-index:30;":"position:static;","width:32px;height:32px;padding:0;","display:inline-flex;align-items:center;justify-content:center;","border:1px solid var(--me-border,#26262b);border-radius:6px;","background:var(--me-chip,#1c1c20);color:var(--me-fg,#e7e7ee);","cursor:pointer;font-size:15px;line-height:1;opacity:0.9;font:inherit;"].join(""),t.classList.add("me-fullscreen-host");const i=()=>{const o=document.fullscreenElement===t;n.textContent=o?"\xD7":"\u26F6",n.setAttribute("aria-label",o?"\u9000\u51FA\u5168\u5C4F":"\u5168\u5C4F"),t.classList.toggle("me-is-fullscreen",o),t.dataset.meFullscreen=o?"true":"false"};i();const a=()=>i(),c=()=>{(document.fullscreenElement===t?document.exitFullscreen?.():t.requestFullscreen?.())?.catch?.(()=>{})};return n.addEventListener("click",c),document.addEventListener("fullscreenchange",a),{button:n,dispose:()=>{document.removeEventListener("fullscreenchange",a),n.removeEventListener("click",c),n.remove(),t.classList.remove("me-fullscreen-host","me-is-fullscreen"),delete t.dataset.meFullscreen}}}export{Qt as buildVideoExportFormData,se as createFullscreenToggle,Z as drawOverlay,tt as drawOverlays,re as drawVideoFrame,Gt as fitStickerBox,ae as generateThumbnails,K as getOverlayRotation,Dt as hitTestOverlay,gt as loadOverlayImages,ft as overlayBBox,et as renderOverlaysToPng,Jt as rotateOverlay,Vt as scaleOverlay,qt as translateOverlay};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{z as t}from"zod";var d={color:t.string().max(32),width:t.number().positive().max(200)},m={rotation:t.number().min(-180).max(180).optional()},l={startAt:t.number().min(0).optional(),endAt:t.number().min(0).optional(),timelineTrack:t.number().int().min(0).max(99).optional()},f=t.discriminatedUnion("type",[t.object({type:t.literal("pen"),points:t.array(t.number()).min(4),...d,...l,...m}),t.object({type:t.literal("line"),x1:t.number(),y1:t.number(),x2:t.number(),y2:t.number(),...d,...l,...m}),t.object({type:t.literal("arrow"),x1:t.number(),y1:t.number(),x2:t.number(),y2:t.number(),...d,...l,...m}),t.object({type:t.literal("rect"),x:t.number(),y:t.number(),w:t.number(),h:t.number(),...d,...l,...m}),t.object({type:t.literal("ellipse"),x:t.number(),y:t.number(),w:t.number(),h:t.number(),...d,...l,...m}),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(),...m}),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(),...m}),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(),...l,...m}),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),...l,...m})]),x=t.object({srcIdx:t.number().int().min(0).max(7),in:t.number().min(0),out:t.number().min(0)}),h=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)}),M=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)}),I=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(x).min(1).max(50),audioClips:t.array(h).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(f).max(200).optional(),transitions:t.array(M).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:I}).superRefine((e,i)=>{e.clips.forEach((n,a)=>{n.out<=n.in&&i.addIssue({code:t.ZodIssueCode.custom,message:`clips[${a}].out \u5FC5\u987B\u5927\u4E8E in`,path:["clips",a,"out"]})}),e.audioClips?.forEach((n,a)=>{n.out<=n.in&&i.addIssue({code:t.ZodIssueCode.custom,message:`audioClips[${a}].out \u5FC5\u987B\u5927\u4E8E in`,path:["audioClips",a,"out"]})}),(e.canvas.w%2!==0||e.canvas.h%2!==0)&&i.addIssue({code:t.ZodIssueCode.custom,message:"canvas \u5BBD\u9AD8\u5FC5\u987B\u4E3A\u5076\u6570",path:["canvas"]}),e.exportSettings?.width!=null&&e.exportSettings.width%2!==0&&i.addIssue({code:t.ZodIssueCode.custom,message:"exportSettings.width \u5FC5\u987B\u4E3A\u5076\u6570",path:["exportSettings","width"]}),e.exportSettings?.height!=null&&e.exportSettings.height%2!==0&&i.addIssue({code:t.ZodIssueCode.custom,message:"exportSettings.height \u5FC5\u987B\u4E3A\u5076\u6570",path:["exportSettings","height"]})});function j(){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 o=.1;function c(e){return e.out-e.in}function g(e){return e.reduce((i,n)=>i+c(n),0)}function k(e,i){let n=0;for(let a=0;a<i&&a<e.length;a++)n+=c(e[a]);return n}function v(e,i){if(!e.length||i<0)return null;let n=0;for(let a=0;a<e.length;a++){const r=c(e[a]);if(i<n+r)return{idx:a,srcTime:e[a].in+(i-n)};n+=r}if(Math.abs(i-n)<1e-9||i<=n){const a=e.length-1;return{idx:a,srcTime:e[a].out}}return null}function E(e,i){const n=v(e,i);if(!n)return e;const a=e[n.idx];return n.srcTime<a.in+o||n.srcTime>a.out-o?e:[...e.slice(0,n.idx),{...a,out:n.srcTime},{...a,in:n.srcTime},...e.slice(n.idx+1)]}function T(e,i){return e.length<=1||!e[i]?e:e.filter((n,a)=>a!==i)}function N(e,i){const n=e[i];return n?[...e.slice(0,i+1),{...n},...e.slice(i+1)]:e}function B(e,i,n){if(i===n||!e[i]||n<0||n>=e.length)return e;const a=[...e],[r]=a.splice(i,1);return a.splice(n,0,r),a}function D(e,i,n,a,r){if(!e[i])return e;const u=e.map(s=>({...s}));return n==="in"?u[i].in=Math.min(Math.max(a,0),u[i].out-o):u[i].out=Math.max(Math.min(a,r),u[i].in+o),u}function F(e,i,n){const a=Math.max(Math.min(e.out,i),o),r=Math.min(Math.max(e.in,0),a-o),b=Math.min(Math.max(e.startAt,0),Math.max(0,n-o)),u=Math.max(o,a-r),s=Math.min(Math.max(Number(e.speed)||1,.5),2),A=Number.isInteger(e.track)&&e.track>=0?e.track:0,p=Math.min(Math.max(Number(e.fadeIn)||0,0),u),C=Math.min(Math.max(Number(e.fadeOut)||0,0),Math.max(0,u-p));return{...e,track:A,in:r,out:a,startAt:b,speed:s,fadeIn:p,fadeOut:C}}function O(e=[]){return(e??[]).reduce((i,n)=>{if(n?.type!=="text"&&n?.type!=="sticker")return i;const a=Number.isFinite(n?.startAt)?Math.max(0,Number(n.startAt)):0,r=Number.isFinite(n?.endAt)?Math.max(a,Number(n.endAt)):a;return Math.max(i,r)},0)}function P(e,i=[],n=[]){const a=g(e),r=i.reduce((u,s)=>Math.max(u,s.startAt+(s.out-s.in)),0),b=O(n);return Math.max(a,r,b)}function Z(e){return[...new Set(e.map(i=>i.srcIdx))].sort((i,n)=>i-n)}function R(e,i){const n=new Map(i.map((a,r)=>[a,r]));return e.map(a=>({...a,srcIdx:n.get(a.srcIdx)??0}))}var V=100;function H(e){return{past:[],present:e,future:[]}}function X(e,i){if(i===e.present)return e;const n=[...e.past,e.present];return n.length>V&&n.shift(),{past:n,present:i,future:[]}}function _(e,i){return i===e.present?e:{...e,present:i}}function y(e){return e.past.length>0}function w(e){return e.future.length>0}function z(e){return y(e)?{past:e.past.slice(0,-1),present:e.past[e.past.length-1],future:[e.present,...e.future]}:e}function K(e){if(!w(e))return e;const[i,...n]=e.future;return{past:[...e.past,e.present],present:i,future:n}}export{h as AudioClip,x as Clip,o as MIN_CLIP,f as Overlay,S as VideoEditState,w as canRedo,y as canUndo,F as clampAudioClip,c as clipDuration,k as clipStartsAt,P as contentDuration,H as createHistory,j as defaultVideoEditState,N as duplicateClip,v as locateOutput,X as pushHistory,K as redo,R as remapSrcIndices,T as removeClip,B as reorderClips,_ as replacePresent,E as splitAtOutput,g as totalDuration,D as trimClipEdge,z as undo,Z as usedVideoSrcIndices};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=Math.PI/180;function c(t,h,n){const r=o(n);if(r===0||r===180)return{w:t,h};if(r===90||r===270)return{w:h,h:t};const a=r*s;return{w:Math.round(Math.abs(t*Math.cos(a))+Math.abs(h*Math.sin(a))),h:Math.round(Math.abs(t*Math.sin(a))+Math.abs(h*Math.cos(a)))}}function o(t){return(t%360+360)%360}function i(t){const h=Math.round(t);return h-h%2}function w(t,h,n=40){const r=Math.min(Math.max(n,t.width),h.w),a=Math.min(Math.max(n,t.height),h.h),l=Math.min(Math.max(0,t.x),h.w-r),M=Math.min(Math.max(0,t.y),h.h-a);return{x:Math.round(l),y:Math.round(M),width:Math.round(r),height:Math.round(a)}}function e(t,h){let n=t.w,r=n/h;return r>t.h&&(r=t.h,n=r*h),{x:Math.round((t.w-n)/2),y:Math.round((t.h-r)/2),width:Math.round(n),height:Math.round(r)}}function f(t,h,n){const r={x:0,y:0,width:t.width,height:t.height},a={x:0,y:0,width:h.w,height:h.h};return n==="cover"?{src:e({w:t.width,h:t.height},h.w/h.h),dst:a}:n==="contain"?{src:r,dst:e({w:h.w,h:h.h},t.width/t.height)}:{src:r,dst:a}}function g(t,h){if(h.scale)return{w:i(h.scale.width),h:i(h.scale.height)};if(h.crop)return{w:i(h.crop.width),h:i(h.crop.height)};const n=c(t.w,t.h,h.rotation);return{w:i(n.w),h:i(n.h)}}var u=[{id:"bw",label:"\u9ED1\u767D",vf:"hue=s=0",css:"grayscale(1)"},{id:"sepia",label:"\u590D\u53E4",vf:"colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131",css:"sepia(0.85)"},{id:"warm",label:"\u6696\u9633",vf:"colorbalance=rs=.15:gs=.04:bs=-.15",css:"sepia(0.25) saturate(1.25) brightness(1.03)"},{id:"cool",label:"\u51B7\u8C03",vf:"colorbalance=rs=-.14:bs=.16",css:"hue-rotate(12deg) saturate(0.95) brightness(1.02)"},{id:"vivid",label:"\u9C9C\u8273",vf:"eq=saturation=1.45:contrast=1.08",css:"saturate(1.45) contrast(1.08)"}];function d(t){return t?u.find(h=>h.id===t):void 0}function v(t,h){const n=d(h)?.css??"";return[`brightness(${1+t.brightness})`,`contrast(${1+t.contrast})`,`saturate(${1+t.saturation})`,n].filter(Boolean).join(" ")}export{s as DEG2RAD,u as FILTER_PRESETS,v as buildFilterCss,e as centeredMaxRect,w as clampRect,i as evenize,d as findFilterPreset,f as fitRects,o as normalizeDeg,g as outputSize,c as rotatedSize};
|