diff --git a/src/main/java/com/swyp/picke/domain/scenario/service/ScenarioAudioPipelineService.java b/src/main/java/com/swyp/picke/domain/scenario/service/ScenarioAudioPipelineService.java index 26a3e94b..6b4946fa 100644 --- a/src/main/java/com/swyp/picke/domain/scenario/service/ScenarioAudioPipelineService.java +++ b/src/main/java/com/swyp/picke/domain/scenario/service/ScenarioAudioPipelineService.java @@ -16,6 +16,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import java.io.File; import java.util.*; @@ -86,6 +88,8 @@ public void generateAndMergeAudioAsync(Long scenarioId) { // 경로 탐색 List> paths = PathFinder.findAllPaths(scenario.getNodes()); List pathDurations = new ArrayList<>(); + // 새 병합 파일은 매번 다른 키로 올라가므로, 기존 병합 파일은 커밋 후 정리한다. + Set previousMergedAudios = new HashSet<>(scenario.getAudios().values()); log.info("--- [2단계] 경로 탐색 완료. 총 {}개의 통합 경로 발견 ---", paths.size()); for (int i = 0; i < paths.size(); i++) { @@ -98,6 +102,9 @@ public void generateAndMergeAudioAsync(Long scenarioId) { scenario.updateStatus(ScenarioStatus.PUBLISHED); scenarioRepository.saveAndFlush(scenario); + previousMergedAudios.removeAll(scenario.getAudios().values()); + deleteMergedAudiosAfterCommit(previousMergedAudios); + cleanUpFiles(ttsCache, silence); if (!pathDurations.isEmpty()) { // 인터랙티브는 A/B 중 긴 경로를, 일반형은 하나뿐인 경로 시간을 최종 시간으로 사용합니다. @@ -162,7 +169,8 @@ private int processPathAndMerge(Scenario scenario, List path, Map< } File merged = ffmpegService.mergeAudioFiles(segments); - String s3Key = FileCategory.SCENARIO.getPath() + "/" + scenario.getId() + "/" + type + ".mp3"; + // 재생성 시 URL이 바뀌어야 앱/플레이어 캐시에 이전 오디오가 남지 않는다. + String s3Key = FileCategory.SCENARIO.getPath() + "/" + scenario.getId() + "/" + type + "_" + UUID.randomUUID() + ".mp3"; String url = s3UploadService.uploadFile(s3Key, merged); log.info("[S3 업로드 완료] {} 오디오 주소: {}", type, url); @@ -181,6 +189,30 @@ private AudioPathType determineType(List path, boolean isInteracti return AudioPathType.COMMON; } + private void deleteMergedAudiosAfterCommit(Set mergedAudioUrls) { + if (mergedAudioUrls.isEmpty()) return; + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + deleteMergedAudios(mergedAudioUrls); + return; + } + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + deleteMergedAudios(mergedAudioUrls); + } + }); + } + + private void deleteMergedAudios(Set mergedAudioUrls) { + for (String mergedAudioUrl : mergedAudioUrls) { + try { + s3UploadService.deleteFile(mergedAudioUrl); + } catch (Exception e) { + log.warn("[S3 정리 실패] 이전 병합 오디오: {}", mergedAudioUrl, e); + } + } + } + private void cleanUpFiles(Map cache, File silence) { cache.values().forEach(File::delete); if (silence != null) silence.delete();