fix(rework): long-op timeout, dedup query, clearer error, logging
Review follow-ups (safe fixes): - pythonRestTemplate bean (10min read timeout) for /transcribe and /render so ffmpeg encoding / Whisper don't hit the shared 120s timeout; resolved by bean name so existing restTemplate injections are unaffected - getScriptData: fetch the script row once instead of 3 separate queries - renderTrimmed "no segments" now IllegalArgumentException (400 + visible message) instead of IllegalStateException swallowed as generic 500 - ProductionService: System.out.println -> log.info (3x) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8178d45209
commit
fa6342f97e
@ -34,6 +34,7 @@ public class ChannelService {
|
||||
|
||||
private final ChannelRepository channelRepository;
|
||||
private final RestTemplate restTemplate;
|
||||
private final RestTemplate pythonRestTemplate; // 전사/렌더 등 장시간 호출용(긴 read timeout). 빈 이름으로 구분 주입.
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final ChannelVideoRepository channelVideoRepository;
|
||||
@ -410,7 +411,7 @@ public class ChannelService {
|
||||
}
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, request, String.class);
|
||||
ResponseEntity<String> response = pythonRestTemplate.postForEntity(apiUrl, request, String.class);
|
||||
|
||||
if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
|
||||
throw new RuntimeException("Transcribe API failed with status: " + response.getStatusCode());
|
||||
@ -479,7 +480,8 @@ public class ChannelService {
|
||||
public byte[] renderTrimmed(Long channelVideoId, MultipartFile file, double pad, double minGap, double speed) {
|
||||
List<ScriptSegment> segments = getSegments(channelVideoId);
|
||||
if (segments.isEmpty()) {
|
||||
throw new IllegalStateException("세그먼트가 없습니다. 먼저 전사를 실행하세요.");
|
||||
// 사용자에게 이유가 보이도록 400(IllegalArgumentException → GlobalExceptionHandler 가 메시지 노출).
|
||||
throw new IllegalArgumentException("세그먼트가 없습니다. 먼저 영상 업로드·전사를 실행하세요.");
|
||||
}
|
||||
KeepIntervalPlanner.Plan plan = KeepIntervalPlanner.plan(segments, pad, minGap);
|
||||
|
||||
@ -494,7 +496,7 @@ public class ChannelService {
|
||||
body.add("speed", String.valueOf(speed));
|
||||
|
||||
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<byte[]> response = restTemplate.postForEntity(apiUrl, request, byte[].class);
|
||||
ResponseEntity<byte[]> response = pythonRestTemplate.postForEntity(apiUrl, request, byte[].class);
|
||||
if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) {
|
||||
throw new RuntimeException("Render API failed with status: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
@ -91,17 +91,16 @@ public class ChannelVideoCurationService {
|
||||
/** 스크립트 전체(평문 + 영상 싱크 세그먼트) 조회. 에디터의 세그먼트 리스트용. */
|
||||
public Map<String, Object> getScriptData(Long videoId) {
|
||||
ChannelVideo v = find(videoId);
|
||||
String transcript = channelVideoScriptRepository.findFirstByVideoIdOrderByIdDesc(v.getVideoId())
|
||||
.map(ChannelVideoScript::getTranscript)
|
||||
ChannelVideoScript script = channelVideoScriptRepository
|
||||
.findFirstByVideoIdOrderByIdDesc(v.getVideoId())
|
||||
.orElse(null);
|
||||
String language = channelVideoScriptRepository.findFirstByVideoIdOrderByIdDesc(v.getVideoId())
|
||||
.map(ChannelVideoScript::getLanguage)
|
||||
.orElse(null);
|
||||
List<ScriptSegment> segments = channelService.getSegments(v.getId());
|
||||
String transcript = (script != null) ? script.getTranscript() : null;
|
||||
List<ScriptSegment> segments = (script != null)
|
||||
? channelService.parseSegments(script.getSegmentsJson()) : List.of();
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("hasScript", transcript != null);
|
||||
result.put("language", language);
|
||||
result.put("language", (script != null) ? script.getLanguage() : null);
|
||||
result.put("transcript", transcript == null ? "" : transcript);
|
||||
result.put("segments", segments);
|
||||
return result;
|
||||
|
||||
@ -226,7 +226,7 @@ public class ProductionService {
|
||||
String oldSummary = text.substring(oldStart + oldSummaryMarker.length(), newStart).trim();
|
||||
String newSummary = text.substring(newStart + newSummaryMarker.length()).trim();
|
||||
|
||||
System.out.println("Saving summaries for video " + videoId);
|
||||
log.info("Saving summaries for video {}", videoId);
|
||||
|
||||
script.setOldScriptSummary(oldSummary);
|
||||
script.setNewScriptSummary(newSummary);
|
||||
@ -270,7 +270,7 @@ public class ProductionService {
|
||||
// 1. Fetch content using API
|
||||
String text = readGoogleDoc(docId);
|
||||
|
||||
System.out.println("Saving final script for video " + videoId);
|
||||
log.info("Saving final script for video {}", videoId);
|
||||
|
||||
// 2. Save to DB
|
||||
script.setFinalScript(text);
|
||||
@ -307,7 +307,7 @@ public class ProductionService {
|
||||
// 1. Fetch content using API
|
||||
String text = readGoogleDoc(docId);
|
||||
|
||||
System.out.println("Saving opening script for video " + videoId);
|
||||
log.info("Saving opening script for video {}", videoId);
|
||||
|
||||
// 2. Save to DB
|
||||
script.setOpeningScript(text);
|
||||
|
||||
@ -21,4 +21,17 @@ public class RestTemplateConfig {
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Python 마이크로서비스(Whisper 전사 /transcribe, ffmpeg 렌더 /render)용 RestTemplate.
|
||||
* 전사·인코딩은 수십 초~수 분이 걸릴 수 있어 read timeout 을 길게 둔다.
|
||||
* (주입 시 빈 이름 {@code pythonRestTemplate} 로 구분 — 다른 곳의 restTemplate 주입에는 영향 없음)
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate pythonRestTemplate(RestTemplateBuilder builder) {
|
||||
return builder
|
||||
.connectTimeout(Duration.ofSeconds(20))
|
||||
.readTimeout(Duration.ofMinutes(10))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user