Files
TrueGrowth/packages/drawnix/src/utils/segment-plan.ts

126 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 视频分段时长计算算法
*
* 根据目标时长和模型可用的 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];
}