Initial TrueGrowth source import
This commit is contained in:
125
packages/drawnix/src/utils/segment-plan.ts
Normal file
125
packages/drawnix/src/utils/segment-plan.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 视频分段时长计算算法
|
||||
*
|
||||
* 根据目标时长和模型可用的 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];
|
||||
}
|
||||
Reference in New Issue
Block a user