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; 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 }; }