package com.hlab.yanalyst.domain.channel;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 무음 구간 제거에 따른 자막 타임스탬프 재매핑(순수 유틸).
*
*
무음을 잘라낸 뒤 남는 구간을 이어붙이면 전체 타임라인이 앞으로 당겨진다.
* 원본 시각 {@code t} 는 그 앞에 제거된 무음 길이만큼 빼서 새 시각으로 매핑된다:
* {@code f(t) = t - (t 이전에 제거된 무음 총량)}.
*
*
배속은 여기서 적용하지 않는다. 재매핑은 "무음 제거 후, 배속 전" 타임라인을 만들고,
* 배속은 {@link SrtFormatter}(÷speed)와 ffmpeg(atempo) 단계에서 일관되게 적용한다.
*/
public final class TimelineRemapper {
private TimelineRemapper() {
}
public static List remap(List segments, List silences) {
if (segments == null || segments.isEmpty()) {
return List.of();
}
List merged = merge(silences);
List out = new ArrayList<>(segments.size());
for (ScriptSegment seg : segments) {
double ns = mapPoint(seg.start(), merged);
double ne = mapPoint(seg.end(), merged);
if (ne < ns) {
ne = ns;
}
out.add(new ScriptSegment(ns, ne, seg.text()));
}
return out;
}
/** 원본 길이에서 (병합된) 무음 총량을 뺀 새 전체 길이. */
public static double newDuration(double originalDuration, List silences) {
double removed = 0;
for (TimeInterval s : merge(silences)) {
double a = Math.max(0, s.start());
double b = Math.min(originalDuration, s.end());
if (b > a) {
removed += b - a;
}
}
return Math.max(0, originalDuration - removed);
}
/** f(t) = t - (t 이전에 제거된 무음 총량). 무음 내부의 점은 그 무음 시작으로 클램프된다. */
private static double mapPoint(double t, List merged) {
double removed = 0;
for (TimeInterval s : merged) {
if (s.end() <= t) {
removed += s.end() - s.start();
} else if (s.start() < t) {
removed += t - s.start(); // t 가 무음 내부 → 경계로 클램프
break;
} else {
break; // 정렬되어 있으니 이후는 t 이후
}
}
return t - removed;
}
/** 겹치거나 정렬 안 된 구간을 정렬·병합한다. */
private static List merge(List silences) {
if (silences == null || silences.isEmpty()) {
return List.of();
}
List sorted = new ArrayList<>(silences);
sorted.sort(Comparator.comparingDouble(TimeInterval::start));
List merged = new ArrayList<>();
double curStart = sorted.get(0).start();
double curEnd = sorted.get(0).end();
for (int i = 1; i < sorted.size(); i++) {
TimeInterval iv = sorted.get(i);
if (iv.start() <= curEnd) {
curEnd = Math.max(curEnd, iv.end());
} else {
merged.add(new TimeInterval(curStart, curEnd));
curStart = iv.start();
curEnd = iv.end();
}
}
merged.add(new TimeInterval(curStart, curEnd));
return merged;
}
}