package com.hlab.yanalyst.domain.channel;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 말(speech) 세그먼트로부터 "남길 구간(keep)"과 "잘라낼 구간(remove)"을 계산하는 순수 유틸.
*
*
오디오 무음(silencedetect)이 아니라 Whisper 세그먼트 기반으로 "아무도 말하지 않는 구간"을
* 잘라 영상을 타이트하게 만든다(배경음악이 깔려 있어도 동작).
*
*
* - {@code pad}: 각 말 구간 앞뒤로 남길 여백(초). 말 잘림 방지.
* - {@code minGap}: 이 값 이하의 짧은 간격은 끊지 않고 이어 붙인다(잦은 컷 방지).
*
*
* {@code remove} 는 첫 keep 시작 전(leading)과 keep 사이 간격만 포함한다(마지막 keep 이후
* 꼬리는 어떤 자막에도 영향을 주지 않으므로 재매핑 대상이 아니며, 영상 컷은 keep 만으로 충분).
*/
public final class KeepIntervalPlanner {
private KeepIntervalPlanner() {
}
public record Plan(List keep, List remove) {
}
public static Plan plan(List segments, double pad, double minGap) {
if (segments == null || segments.isEmpty()) {
return new Plan(List.of(), List.of());
}
// 1) 패딩 적용(시작 0 클램프)
List padded = new ArrayList<>(segments.size());
for (ScriptSegment s : segments) {
padded.add(new TimeInterval(Math.max(0, s.start() - pad), s.end() + pad));
}
padded.sort(Comparator.comparingDouble(TimeInterval::start));
// 2) 겹치거나 간격이 minGap 이하면 병합 → keep
List keep = new ArrayList<>();
double curStart = padded.get(0).start();
double curEnd = padded.get(0).end();
for (int i = 1; i < padded.size(); i++) {
TimeInterval iv = padded.get(i);
if (iv.start() - curEnd <= minGap) {
curEnd = Math.max(curEnd, iv.end());
} else {
keep.add(new TimeInterval(curStart, curEnd));
curStart = iv.start();
curEnd = iv.end();
}
}
keep.add(new TimeInterval(curStart, curEnd));
// 3) remove = [0, firstKeepStart) + keep 사이 간격
List remove = new ArrayList<>();
if (keep.get(0).start() > 1e-9) {
remove.add(new TimeInterval(0.0, keep.get(0).start()));
}
for (int i = 1; i < keep.size(); i++) {
remove.add(new TimeInterval(keep.get(i - 1).end(), keep.get(i).start()));
}
return new Plan(keep, remove);
}
/** keep 구간 총 길이 = 무음 제거 후 예상 영상 길이(배속 전). */
public static double keptDuration(Plan plan) {
double total = 0;
for (TimeInterval iv : plan.keep()) {
total += iv.end() - iv.start();
}
return total;
}
}