Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.fourformance.tts_vc_web.common.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 알 수 없는 속성을 무시
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class SSEController {
/**
* 클라이언트의 SSE 구독 요청을 처리 (새 연결 생성)
*/
@PostMapping("/{clientId}")
@GetMapping(value = "/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribe(@PathVariable Long clientId) {
return sseEmitterService.subscribe(clientId);
}
Expand All @@ -27,24 +27,27 @@ public SseEmitter subscribe(@PathVariable Long clientId) {
* 서버에서 특정 클라이언트로 데이터 전송 (디버그/테스트용)
*/
@PostMapping("/{clientId}/messages")
public void sendMessageToClient(@PathVariable Long clientId, @RequestBody String message) {
public ResponseEntity<String> sendMessageToClient(@PathVariable Long clientId, @RequestBody String message) {
sseEmitterService.sendToClient(clientId, message);
return ResponseEntity.ok("Message sent to clientId: " + clientId);
}

/**
* 특정 클라이언트 연결 종료
*/
@DeleteMapping("/{clientId}")
public void disconnect(@PathVariable Long clientId) {
public ResponseEntity<String> disconnect(@PathVariable Long clientId) {
sseEmitterService.disconnect(clientId);
return ResponseEntity.ok("Disconnected clientId: " + clientId);
}

/**
* 모든 연결 종료
*/
@DeleteMapping
public void disconnectAll() {
public ResponseEntity<String> disconnectAll() {
sseEmitterService.disconnectAll();
return ResponseEntity.ok("All clients disconnected");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ public SseEmitter subscribe(Long clientId) {
log.info("Subscribing client with ID: " + clientId);
SseEmitter emitter = createEmitter(clientId);

// 초기 메시지 전송
sendToClient(clientId, "Connection established for clientId: " + clientId);
log.info("SSE subscription completed for clientId: " + clientId);
try {
// 초기 메시지 전송
emitter.send(SseEmitter.event()
.id(String.valueOf(clientId))
.name("connection")
.data("Connection established for clientId: " + clientId));
log.info("SSE subscription completed for clientId: " + clientId);
} catch (IOException exception) {
log.warning("Failed to send initial message to clientId: " + clientId);
emitterRepository.deleteById(clientId); // 초기 전송 실패 시 Emitter 삭제
}

return emitter;
}
Expand Down Expand Up @@ -61,27 +69,29 @@ private SseEmitter createEmitter(Long clientId) {
/**
* 클라이언트에게 데이터 전송
*/
public void sendToClient(Long memberId, Object data) {

public void sendToClient(Long clientId, Object data) {
if (clientId == null) {
throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND);
}
if (data == null) {
data = "No data available";
}

if(memberId == null) { throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND); }


SseEmitter emitter = emitterRepository.get(memberId);
SseEmitter emitter = emitterRepository.get(clientId);
if (emitter != null) {
try {
log.info("Sending data to memberId: " + memberId);
emitter.send(SseEmitter.event().id(String.valueOf(memberId)).name("taskUpdate").data(data));
log.info("Sending data to clientId: " + clientId);
emitter.send(SseEmitter.event()
.id(String.valueOf(clientId))
.name("taskUpdate")
.data(data));
} catch (IOException exception) {
log.warning("Failed to send data to memberId: " + memberId + ", removing emitter.");
emitterRepository.deleteById(memberId);
log.warning("Failed to send data to clientId: " + clientId + ", removing emitter.");
emitterRepository.deleteById(clientId);
emitter.completeWithError(exception);
}
} else {
log.warning("No active SSE connection for memberId: " + memberId);
log.warning("No active SSE connection for clientId: " + clientId);
}
}

Expand All @@ -94,6 +104,8 @@ public void disconnect(Long clientId) {
emitter.complete();
emitterRepository.deleteById(clientId);
log.info("Disconnected clientId: " + clientId);
} else {
log.warning("No active SSE connection for clientId: " + clientId);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ public class ConcatService_TaskJob {
private final S3Service s3Service; // S3 연동 서비스
private final AudioProcessingService audioProcessingService; // 오디오 처리 서비스
private final ConcatProjectRepository concatProjectRepository; // 프로젝트 관련 저장소
private final ConcatStatusHistoryRepository concatStatusHistoryRepository; // 프로젝트 이력 관련 저장소
private final ConcatDetailRepository concatDetailRepository; // 디테일 관련 저장소
private final MemberRepository memberRepository; // 멤버 관련 저장소
private final Environment environment; // Environment 주입
private final VCService_team_multi vcService;
private final ConcatService_team_api concatService;
private final TaskRepository taskRepository;
private final ObjectMapper objectMapper;
private final TaskProducer taskProducer;
Expand Down Expand Up @@ -93,58 +91,6 @@ public void initialize() {
setupFFmpeg();
}

// @Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRED)
// public void enqueueConcatTask(ConcatRequestDto concatReqDto, List<MultipartFile> files, Long memberId){
//
// // 1. 유효성 검증: 요청 데이터 및 상세 데이터 확인
// if (concatReqDto == null ||
// concatReqDto.getConcatRequestDetails() == null ||
// concatReqDto.getConcatRequestDetails().isEmpty()) {
// throw new BusinessException(ErrorCode.INVALID_REQUEST_DATA); // 커스텀 예외 발생
// }
//
// // 2. 파일 수와 요청 DTO의 상세 정보 수가 동일한지 확인
// List<ConcatRequestDetailDto> details = concatReqDto.getConcatRequestDetails();
// if (details.size() != files.size()) {
// throw new BusinessException(ErrorCode.INVALID_REQUEST_DATA);
// }
//
// // 3. 요청 DTO의 각 상세 항목에 업로드된 파일 매핑
// for (int i = 0; i < details.size(); i++) {
// ConcatRequestDetailDto detail = details.get(i);
// MultipartFile file = vcService.findMultipartFileByName(files, detail.getLocalFileName());
//
// detail.setSourceAudio(file);
// }
//
// // 프로젝트 저장
// ConcatProject concatProject = saveOrUpdateProject(concatReqDto, memberId);
//
// // 디테일 저장
// for (ConcatRequestDetailDto detail : details) {
// MemberAudioMeta memberAudioMeta = uploadConcatDetailSourceAudio(detail, concatProject);
// saveOrUpdateDetail(detail, concatProject,memberAudioMeta);
// }
//
// // 프로젝트 ID로 연관된 concat 디테일 조회
// List<ConcatDetail> concatDetails = concatDetailRepository.findByConcatProject_Id(concatProject.getId());
//
// // 6. ConcatProject와 ConcatDetail 객체를 ConcatMsgDto로 변환
// ConcatMsgDto msgDto = createConcatMsgDto(concatProject, concatDetails, memberId);
// System.out.println("===========================================msgDto.toString() = " + msgDto.toString());
//
// // 문자열 json으로 변환
// String taskData = convertToJson(msgDto);
//
// // Task 생성 및 저장
// Task task = Task.createTask(concatProject, ProjectType.CONCAT, taskData);
// taskRepository.save(task);
//
// // 메시지 생성 및 RabbitMQ에 전송
// msgDto.setTaskId(task.getId());
// taskProducer.sendTask("AUDIO_CONCAT", msgDto);
// }

private String convertToJson(ConcatMsgDto msgDto) {
try {
return objectMapper.writeValueAsString(msgDto);
Expand Down Expand Up @@ -197,46 +143,6 @@ private void setupFFmpeg() {
}
}

/**
* 오디오 파일 병합 프로세스 수행
*
* @param concatRequestDto 요청 데이터 DTO
* @return 병합 결과 DTO
*/
public ConcatResponseDto convertAllConcatDetails(ConcatRequestDto concatRequestDto, Long memberId) {
LOGGER.info("convertAllConcatDetails 호출: " + concatRequestDto);

// 1. 프로젝트 생성 또는 업데이트
ConcatProject concatProject = saveOrUpdateProject(concatRequestDto, memberId);

// 2. 응답 DTO 생성 및 초기화
ConcatResponseDto concatResponseDto = initializeResponseDto(concatProject);

try {
// 3. 각 요청 디테일 처리
List<ConcatResponseDetailDto> responseDetails = processRequestDetails(concatRequestDto, concatProject);

// 4. 병합된 오디오 생성 및 S3 업로드
String mergedFileUrl = mergeAudioFilesAndUploadToS3(responseDetails, uploadDir, memberId, concatProject.getId());

// 응답 DTO에 데이터 설정
concatResponseDto.setOutputConcatAudios(Collections.singletonList(mergedFileUrl));
concatResponseDto.setConcatResponseDetails(responseDetails);

// 성공 시 ConcatStatusHistory 저장
saveConcatStatusHistory(concatProject, ConcatStatusConst.SUCCESS);

return concatResponseDto;

} catch (Exception e) {
LOGGER.severe("오류 발생: " + e.getMessage());

// 실패 시 ConcatStatusHistory 저장
saveConcatStatusHistory(concatProject, ConcatStatusConst.FAILURE);

throw e;
}
}

/**
* 요청 디테일을 처리하여 응답 디테일 리스트를 반환
Expand Down Expand Up @@ -273,55 +179,6 @@ private List<ConcatResponseDetailDto> processRequestDetails(ConcatRequestDto con
return responseDetails;
}

/**
* 오디오 파일 병합 및 S3 업로드
*/
public String mergeAudioFilesAndUploadToS3(List<ConcatResponseDetailDto> audioDetails, String uploadDir, Long userId, Long projectId) {
List<String> savedFilePaths = new ArrayList<>();
List<String> silenceFilePaths = new ArrayList<>();
String mergedFilePath = null;

try {
// 1. 체크된 파일 필터링
List<ConcatResponseDetailDto> filteredDetails = audioDetails.stream()
.filter(ConcatResponseDetailDto::isChecked)
.collect(Collectors.toList());

if (filteredDetails.isEmpty()) {
LOGGER.severe("병합할 파일이 없습니다.");

// 실패 시 ConcatStatusHistory 저장
saveConcatStatusHistory(concatProjectRepository.findById(projectId).orElseThrow(() -> new BusinessException(ErrorCode.NOT_EXISTS_PROJECT)), ConcatStatusConst.FAILURE);

throw new BusinessException(ErrorCode.NO_FILES_TO_MERGE);
}

// 2. S3에서 파일 다운로드 및 침묵 파일 생성
for (ConcatResponseDetailDto detail : filteredDetails) {
if (detail.getAudioUrl() != null) {
String savedFilePath = s3Service.downloadFileFromS3(detail.getAudioUrl(), uploadDir);
savedFilePaths.add(savedFilePath);

String silenceFilePath = audioProcessingService.createSilenceFile(detail.getEndSilence().longValue(), uploadDir);
if (silenceFilePath != null) silenceFilePaths.add(silenceFilePath);
} else {
LOGGER.warning("Audio URL이 없습니다. Detail ID: " + detail.getId());
}
}

// 3. 병합된 파일 생성
mergedFilePath = audioProcessingService.mergeAudioFilesWithSilence(savedFilePaths, silenceFilePaths, uploadDir);

// 4. 병합된 파일을 S3에 업로드 후 URL 반환
return s3Service.uploadConcatSaveFile(audioProcessingService.convertToMultipartFile(mergedFilePath), userId, projectId);

} catch (IOException e) {
throw new BusinessException(ErrorCode.FILE_PROCESSING_ERROR);
} finally {
// 5. 임시 파일 삭제
cleanupTemporaryFiles(savedFilePaths, silenceFilePaths, mergedFilePath);
}
}

/**
* 프로젝트 생성 또는 업데이트
Expand Down Expand Up @@ -427,17 +284,6 @@ private MemberAudioMeta uploadConcatDetailSourceAudio(ConcatRequestDetailDto det
return memberAudioMetas.get(0);
}

/**
* 응답 DTO 초기화
*/
private ConcatResponseDto initializeResponseDto(ConcatProject concatProject) {
return ConcatResponseDto.builder()
.projectId(concatProject.getId())
.projectName(concatProject.getProjectName())
.globalFrontSilenceLength(concatProject.getGlobalFrontSilenceLength())
.globalTotalSilenceLength(concatProject.getGlobalTotalSilenceLength())
.build();
}

/**
* 응답 디테일 DTO 생성
Expand All @@ -464,13 +310,6 @@ private void cleanupTemporaryFiles(List<String> savedFilePaths, List<String> sil
}
}

/**
* ConcatStatusHistory 저장
*/
private void saveConcatStatusHistory(ConcatProject concatProject, ConcatStatusConst status) {
ConcatStatusHistory concatStatusHistory = ConcatStatusHistory.createConcatStatusHistory(concatProject, status);
concatStatusHistoryRepository.save(concatStatusHistory);
}

//---------------------------------------------------------------------
@Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRED)
Expand Down Expand Up @@ -525,4 +364,56 @@ private void enqueueConcatTaskMessage(ConcatProject concatProject, Long memberId
taskProducer.sendTask("AUDIO_CONCAT", msgDto);
}

// @Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRED)
// public void enqueueConcatTask(ConcatRequestDto concatReqDto, List<MultipartFile> files, Long memberId){
//
// // 1. 유효성 검증: 요청 데이터 및 상세 데이터 확인
// if (concatReqDto == null ||
// concatReqDto.getConcatRequestDetails() == null ||
// concatReqDto.getConcatRequestDetails().isEmpty()) {
// throw new BusinessException(ErrorCode.INVALID_REQUEST_DATA); // 커스텀 예외 발생
// }
//
// // 2. 파일 수와 요청 DTO의 상세 정보 수가 동일한지 확인
// List<ConcatRequestDetailDto> details = concatReqDto.getConcatRequestDetails();
// if (details.size() != files.size()) {
// throw new BusinessException(ErrorCode.INVALID_REQUEST_DATA);
// }
//
// // 3. 요청 DTO의 각 상세 항목에 업로드된 파일 매핑
// for (int i = 0; i < details.size(); i++) {
// ConcatRequestDetailDto detail = details.get(i);
// MultipartFile file = vcService.findMultipartFileByName(files, detail.getLocalFileName());
//
// detail.setSourceAudio(file);
// }
//
// // 프로젝트 저장
// ConcatProject concatProject = saveOrUpdateProject(concatReqDto, memberId);
//
// // 디테일 저장
// for (ConcatRequestDetailDto detail : details) {
// MemberAudioMeta memberAudioMeta = uploadConcatDetailSourceAudio(detail, concatProject);
// saveOrUpdateDetail(detail, concatProject,memberAudioMeta);
// }
//
// // 프로젝트 ID로 연관된 concat 디테일 조회
// List<ConcatDetail> concatDetails = concatDetailRepository.findByConcatProject_Id(concatProject.getId());
//
// // 6. ConcatProject와 ConcatDetail 객체를 ConcatMsgDto로 변환
// ConcatMsgDto msgDto = createConcatMsgDto(concatProject, concatDetails, memberId);
// System.out.println("===========================================msgDto.toString() = " + msgDto.toString());
//
// // 문자열 json으로 변환
// String taskData = convertToJson(msgDto);
//
// // Task 생성 및 저장
// Task task = Task.createTask(concatProject, ProjectType.CONCAT, taskData);
// taskRepository.save(task);
//
// // 메시지 생성 및 RabbitMQ에 전송
// msgDto.setTaskId(task.getId());
// taskProducer.sendTask("AUDIO_CONCAT", msgDto);
// }

}
Loading
Loading