diff --git a/src/main/java/com/hlab/yanalyst/domain/channel/ChannelVideoCurationService.java b/src/main/java/com/hlab/yanalyst/domain/channel/ChannelVideoCurationService.java index 99357cb..b796bb4 100644 --- a/src/main/java/com/hlab/yanalyst/domain/channel/ChannelVideoCurationService.java +++ b/src/main/java/com/hlab/yanalyst/domain/channel/ChannelVideoCurationService.java @@ -188,9 +188,13 @@ public class ChannelVideoCurationService { String source = scriptOpt.map(ChannelVideoScript::getLanguage).orElse(null); String tgt = (target == null || target.isBlank()) ? "ko" : target.trim(); boolean timeline = "timeline".equalsIgnoreCase(format); + boolean segmentsMode = "segments".equalsIgnoreCase(format); // SRT용: 세그먼트별 번역 배열 반환 - String translated; - if (timeline) { + Map result = new LinkedHashMap<>(); + result.put("source", source); + result.put("target", tgt); + + if (timeline || segmentsMode) { List segments = channelService.getSegments(v.getId()); if (segments.isEmpty()) { throw new IllegalArgumentException("타임라인(시간 싱크 세그먼트)이 없습니다. 먼저 원본 다운로드/업로드로 전사하세요."); @@ -198,26 +202,28 @@ public class ChannelVideoCurationService { List texts = segments.stream().map(ScriptSegment::text).toList(); List ko = translateService.translateBatch(texts, source, tgt); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < segments.size(); i++) { - String line = (i < ko.size()) ? ko.get(i) : ""; - sb.append('[').append(mmss(segments.get(i).start())).append("] ") - .append(line == null ? "" : line.trim()).append('\n'); + if (segmentsMode) { + // 입력 세그먼트와 1:1 정렬된 번역 배열(프론트가 기존 SRT 로직에 끼워 넣음) + result.put("format", "segments"); + result.put("texts", ko); + } else { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < segments.size(); i++) { + String line = (i < ko.size()) ? ko.get(i) : ""; + sb.append('[').append(mmss(segments.get(i).start())).append("] ") + .append(line == null ? "" : line.trim()).append('\n'); + } + result.put("format", "timeline"); + result.put("translatedText", sb.toString().stripTrailing()); } - translated = sb.toString().stripTrailing(); } else { String text = scriptOpt.map(ChannelVideoScript::getTranscript).orElse(null); if (text == null || text.isBlank()) { throw new IllegalArgumentException("번역할 원본 스크립트가 없습니다. 먼저 전사(원본 다운로드/업로드)하세요."); } - translated = translateService.translate(text, source, tgt); + result.put("format", "plain"); + result.put("translatedText", translateService.translate(text, source, tgt)); } - - Map result = new LinkedHashMap<>(); - result.put("translatedText", translated); - result.put("source", source); - result.put("target", tgt); - result.put("format", timeline ? "timeline" : "plain"); return result; } diff --git a/src/main/resources/templates/rework.html b/src/main/resources/templates/rework.html index 09815b2..da567e1 100644 --- a/src/main/resources/templates/rework.html +++ b/src/main/resources/templates/rework.html @@ -201,6 +201,9 @@ + @@ -407,6 +410,25 @@ document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); } + // 세그먼트를 한국어로 번역해(서버 일괄번역) 기존 타임라인(배속·말없는구간 제거 반영)에 끼워 SRT 생성. + async function exportKoSrt(){ + const btn = document.getElementById('koSrtBtn'); + const orig = btn.innerHTML; btn.disabled = true; btn.innerHTML = '번역 중…'; + try { + const r = await api(API + '/' + VIDEO_ID + '/translate?target=ko&format=segments', { method:'POST' }); + const ko = (r && r.texts) || []; + if(!ko.length){ alert('번역할 세그먼트가 없습니다. 먼저 전사하세요.'); return; } + // 현재 작업 세그먼트(원본/무음제거 후)와 1:1 정렬 → 텍스트만 한국어로 교체(타임코드·배속 그대로) + const segs = workingSegments(); + const koSegs = segs.map((sg, i) => ({ start: sg.start, end: sg.end, text: (ko[i] != null ? ko[i] : (sg.text || '')) })); + const blob = new Blob([buildSrt(koSegs, curSpeed())], {type:'application/x-subrip'}); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); a.download = 'script_ko_' + VIDEO_ID + '.srt'; + document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href); + } catch(e){ alert('한국어 SRT 실패: ' + e.message); } + finally { btn.disabled = false; btn.innerHTML = orig; if(window.lucide) lucide.createIcons(); } + } + // ===== 말 없는 구간 제거 ===== function onTrimToggle(){ if(document.getElementById('trimOn').checked){ previewTrim(); }