48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const fraction = z.number().finite().min(0).max(1);
|
|
export const videoTimeRangeSchema = z
|
|
.object({ start: fraction, end: fraction })
|
|
.refine((range) => range.end > range.start, "结束位置必须晚于开始位置");
|
|
export type VideoTimeRange = z.infer<typeof videoTimeRangeSchema>;
|
|
|
|
export const videoProcessSchema = z.discriminatedUnion("operation", [
|
|
z.object({
|
|
operation: z.literal("crop"),
|
|
url: z.string().url(),
|
|
rect: z
|
|
.object({
|
|
x: fraction,
|
|
y: fraction,
|
|
width: fraction.positive(),
|
|
height: fraction.positive(),
|
|
})
|
|
.refine(
|
|
(rect) => rect.x + rect.width <= 1.001 && rect.y + rect.height <= 1.001,
|
|
"裁剪区域超出画面",
|
|
),
|
|
}),
|
|
z.object({ operation: z.literal("audio-video"), url: z.string().url() }),
|
|
z.object({
|
|
operation: z.literal("frame"),
|
|
url: z.string().url(),
|
|
kind: z.enum(["first", "last", "current"]),
|
|
time: z.number().finite().min(0).default(0),
|
|
}),
|
|
z.object({
|
|
operation: z.literal("slow"),
|
|
url: z.string().url(),
|
|
factor: z.union([z.literal(2), z.literal(3), z.literal(5)]),
|
|
}),
|
|
z.object({ operation: z.literal("trim"), url: z.string().url(), range: videoTimeRangeSchema }),
|
|
]);
|
|
|
|
export function toVideoTimeRange(range: VideoTimeRange, duration: number) {
|
|
if (!videoTimeRangeSchema.safeParse(range).success || !Number.isFinite(duration) || duration <= 0)
|
|
throw new Error("剪辑范围无效");
|
|
const start = range.start * duration;
|
|
const end = range.end * duration;
|
|
if (end - start < 0.1) throw new Error("剪辑时长不能少于 0.1 秒");
|
|
return { start, end, duration: end - start };
|
|
}
|