126 lines
3.3 KiB
TypeScript
126 lines
3.3 KiB
TypeScript
/**
|
||
* 视频分段时长计算算法
|
||
*
|
||
* 根据目标时长和模型可用的 duration 选项,计算最优分段方案。
|
||
* 供 ScriptPage、GeneratePage、long-video.ts 三处复用。
|
||
*/
|
||
|
||
import type { DurationOption } from '../types/video.types';
|
||
|
||
/** 分段计划结果 */
|
||
export interface SegmentPlan {
|
||
/** 每段的时长(秒)数组,长度等于段数 */
|
||
segments: number[];
|
||
/** 实际总时长 = sum(segments) */
|
||
actualTotal: number;
|
||
/** 是否为固定时长模型 */
|
||
isFixed: boolean;
|
||
/** 目标时长与实际时长的差值(actualTotal - targetDuration),≥ 0 */
|
||
overflow: number;
|
||
}
|
||
|
||
/** 默认兜底时长 */
|
||
const FALLBACK_DURATION = 8;
|
||
|
||
/**
|
||
* 计算分段方案
|
||
*
|
||
* 策略:
|
||
* - 固定时长模型(options.length === 1):
|
||
* segmentCount = ceil(targetDur / fixedDur)
|
||
* actualTotal = segmentCount × fixedDur
|
||
*
|
||
* - 可变时长模型(options.length > 1):
|
||
* 1. 若 targetDur ≤ maxDur → 单段,选 >= targetDur 的最小选项
|
||
* 2. 否则用 maxDur 贪心填充,余数从 options 中选最近 ≥ 选项
|
||
*/
|
||
export function computeSegmentPlan(
|
||
targetDuration: number,
|
||
durationOptions: DurationOption[]
|
||
): SegmentPlan {
|
||
const options = durationOptions
|
||
.map(o => parseInt(o.value, 10))
|
||
.filter(n => !isNaN(n) && n > 0)
|
||
.sort((a, b) => a - b);
|
||
|
||
// 兜底:无有效选项
|
||
if (options.length === 0) {
|
||
const count = Math.max(1, Math.ceil(targetDuration / FALLBACK_DURATION));
|
||
const total = count * FALLBACK_DURATION;
|
||
return {
|
||
segments: Array(count).fill(FALLBACK_DURATION),
|
||
actualTotal: total,
|
||
isFixed: true,
|
||
overflow: total - targetDuration,
|
||
};
|
||
}
|
||
|
||
const isFixed = options.length === 1;
|
||
const maxDur = options[options.length - 1];
|
||
|
||
if (isFixed) {
|
||
const fixedDur = options[0];
|
||
const count = Math.max(1, Math.ceil(targetDuration / fixedDur));
|
||
const total = count * fixedDur;
|
||
return {
|
||
segments: Array(count).fill(fixedDur),
|
||
actualTotal: total,
|
||
isFixed: true,
|
||
overflow: total - targetDuration,
|
||
};
|
||
}
|
||
|
||
// 可变时长:单段可覆盖
|
||
if (targetDuration <= maxDur) {
|
||
const best = options.find(d => d >= targetDuration) ?? maxDur;
|
||
return {
|
||
segments: [best],
|
||
actualTotal: best,
|
||
isFixed: false,
|
||
overflow: best - targetDuration,
|
||
};
|
||
}
|
||
|
||
// 可变时长:多段贪心
|
||
const segments: number[] = [];
|
||
let remaining = targetDuration;
|
||
|
||
while (remaining > maxDur) {
|
||
segments.push(maxDur);
|
||
remaining -= maxDur;
|
||
}
|
||
|
||
// 余数:找最近的 >= remaining 选项
|
||
const tail = options.find(d => d >= remaining) ?? maxDur;
|
||
segments.push(tail);
|
||
|
||
const actualTotal = segments.reduce((a, b) => a + b, 0);
|
||
return {
|
||
segments,
|
||
actualTotal,
|
||
isFixed: false,
|
||
overflow: actualTotal - targetDuration,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 为单个 shot 选最合适的模型时长
|
||
*
|
||
* 找 >= targetDur 的最小选项,没有则取最大值
|
||
*/
|
||
export function findBestDuration(
|
||
targetDur: number,
|
||
durationOptions: DurationOption[]
|
||
): number {
|
||
const options = durationOptions
|
||
.map(o => parseInt(o.value, 10))
|
||
.filter(n => !isNaN(n) && n > 0)
|
||
.sort((a, b) => a - b);
|
||
|
||
if (options.length === 0) {
|
||
return Math.min(Math.max(targetDur, 5), 10);
|
||
}
|
||
|
||
return options.find(d => d >= targetDur) ?? options[options.length - 1];
|
||
}
|