Skip to content

Commit

Permalink
Merge pull request #10 from Fairy-Taless/feature/#3-voice
Browse files Browse the repository at this point in the history
✨ FEAT. ElevenLabs 음성 생성 API 기능
  • Loading branch information
junhaa authored Apr 10, 2024
2 parents 71501ad + 3589961 commit 25ffc1b
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 12 deletions.
2 changes: 2 additions & 0 deletions src/main/java/fairytale/tbd/FairytaleApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class FairytaleApplication {

public static void main(String[] args) {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/fairytale/tbd/domain/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@


import fairytale.tbd.domain.user.enums.Gender;
import fairytale.tbd.domain.voice.entity.Voice;
import fairytale.tbd.global.entity.BaseEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -41,4 +45,14 @@ public class User extends BaseEntity {
@Column(name = "username", nullable = false)
private String username;

@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Voice voice;


// 연관관계 편의 메서드
public void setVoice(Voice voice){
this.voice = voice;
voice.setUser(this);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fairytale.tbd.domain.user.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

Expand All @@ -9,4 +11,6 @@
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByUsername(String username);

Optional<User> findById(Long userId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fairytale.tbd.domain.voice.converter;

import fairytale.tbd.domain.voice.entity.Voice;
import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO;

public class VoiceConverter {

public static VoiceResponseDTO.AddVoiceResultDTO toAddVoiceResult(Voice voice){
return VoiceResponseDTO.AddVoiceResultDTO.builder()
.voiceId(voice.getId())
.createdAt(voice.getCreatedAt())
.build();
}

public static Voice toVoice(String keyId){
return Voice.builder()
.keyId(keyId)
.build();
}
}
45 changes: 45 additions & 0 deletions src/main/java/fairytale/tbd/domain/voice/entity/Voice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package fairytale.tbd.domain.voice.entity;

import fairytale.tbd.domain.user.entity.User;
import fairytale.tbd.global.entity.BaseEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Voice extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "voice_id")
private Long id;

@Column(name = "voice_key_id", nullable = false)
private String keyId;


@OneToOne
@JoinColumn(name = "user_id")
private User user;


// 연관 관계 편의 메소드
public void setUser(User user){
this.user = user;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package fairytale.tbd.domain.voice.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import fairytale.tbd.domain.voice.entity.Voice;

@Repository
public interface VoiceRepository extends JpaRepository<Voice, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fairytale.tbd.domain.voice.service;

import fairytale.tbd.domain.voice.entity.Voice;
import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO;

public interface VoiceCommandService {
Voice uploadVoice(VoiceRequestDTO.AddVoiceDTO request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package fairytale.tbd.domain.voice.service;

import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import net.andrewcpu.elevenlabs.ElevenLabs;

import fairytale.tbd.domain.user.entity.User;
import fairytale.tbd.domain.user.repository.UserRepository;
import fairytale.tbd.domain.voice.converter.VoiceConverter;
import fairytale.tbd.domain.voice.entity.Voice;
import fairytale.tbd.domain.voice.repository.VoiceRepository;
import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO;
import fairytale.tbd.global.elevenlabs.ElevenlabsManager;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class VoiceCommandServiceImpl implements VoiceCommandService{

private static final Logger LOGGER = LogManager.getLogger(VoiceCommandServiceImpl.class);

private final ElevenlabsManager elevenlabsManager;
private final UserRepository userRepository;
private final VoiceRepository voiceRepository;

/**
* ElevenLabs Voice 추가
* @param request MultiPartFile sample 사용자 녹음 파일
*/

@Transactional
@Override
public Voice uploadVoice(VoiceRequestDTO.AddVoiceDTO request) {

Optional<User> userOptional = userRepository.findById(6L);
User user = userOptional.get();

// TODO username session에서 가져오기
String keyId = elevenlabsManager.addVoice("test", request.getSample());

Voice voice = VoiceConverter.toVoice(keyId);
user.setVoice(voice);
voiceRepository.save(voice);

return voice;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fairytale.tbd.domain.voice.web.controller;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import fairytale.tbd.domain.voice.converter.VoiceConverter;
import fairytale.tbd.domain.voice.entity.Voice;
import fairytale.tbd.domain.voice.service.VoiceCommandService;
import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO;
import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO;
import fairytale.tbd.global.response.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/voice")
public class VoiceRestController {

private static final Logger LOGGER = LogManager.getLogger(VoiceRestController.class);
private final VoiceCommandService voiceCommandService;

@PostMapping("")
public ApiResponse<VoiceResponseDTO.AddVoiceResultDTO> addVoice(@Valid @ModelAttribute VoiceRequestDTO.AddVoiceDTO request){
// TODO 이미 존재하는 Voice 인지 검증
LOGGER.info("request = {}", request);
Voice voice = voiceCommandService.uploadVoice(request);
return ApiResponse.onSuccess(VoiceConverter.toAddVoiceResult(voice));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fairytale.tbd.domain.voice.web.dto;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;

public class VoiceRequestDTO {
@Getter
@Setter
public static class AddVoiceDTO{
//파일 형식 검증
@NotNull
MultipartFile sample;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package fairytale.tbd.domain.voice.web.dto;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

public class VoiceResponseDTO {
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class AddVoiceResultDTO{
private Long voiceId;
private LocalDateTime createdAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.FileOutputStream;
import java.io.InputStream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -18,6 +20,7 @@

import fairytale.tbd.global.elevenlabs.exception.FileConvertException;
import fairytale.tbd.global.enums.statuscode.ErrorStatus;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand All @@ -31,6 +34,8 @@
@RequiredArgsConstructor
public class ElevenlabsManager {

private static final Logger LOGGER = LogManager.getLogger(ElevenlabsManager.class);

@Value("${voice.elevenlabs.apikey}")
private String apiKey;

Expand All @@ -43,16 +48,20 @@ public class ElevenlabsManager {
@Value("${voice.elevenlabs.voice_setting.use_speaker_boost}")
private boolean useSpeakerBoost;

@PostConstruct
public void init(){
ElevenLabs.setApiKey(apiKey);
ElevenLabs.setDefaultModel("eleven_multilingual_v2");
}


/**
* EleventLabs TTS 변환
* @param text 음성 TTS 변환 할 내용
* @param voiceId voice 고유 값
* @return 생성된 .mpga 파일
*/

public File elevenLabsTTS(String text, String voiceId){
ElevenLabs.setApiKey(apiKey);
ElevenLabs.setDefaultModel("eleven_multilingual_v2");
return SpeechGenerationBuilder.textToSpeech()
.file()
.setText(text)
Expand All @@ -70,16 +79,17 @@ public File elevenLabsTTS(String text, String voiceId){
* @return voice 고유번호
*/
public String addVoice(String userName, MultipartFile sample) {
File file = convertMultipartFileToFile(sample);
VoiceBuilder builder = new VoiceBuilder();
builder.withName(userName);
builder.withFile(convertMultipartFileToFile(sample));
builder.withFile(file);
builder.withDescription("the emotional voice of the main character of a children's book");
builder.withLabel("language", "ko");
Voice voice = builder.create();
return voice.getVoiceId();
}


// TODO IOException 예외 처리 => 500

/**
* MultiPartFile -> File 변환
Expand All @@ -91,13 +101,8 @@ public static File convertMultipartFileToFile(MultipartFile multipartFile) {
File file = new File(System.getProperty("java.io.tmpdir") + "/" + multipartFile.getOriginalFilename());

// MultipartFile의 내용을 파일에 쓰기
try (InputStream inputStream = multipartFile.getInputStream();
FileOutputStream outputStream = new FileOutputStream(file)) {
int read;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
try {
multipartFile.transferTo(file);
}
catch (Exception e){
e.printStackTrace();
Expand Down

0 comments on commit 25ffc1b

Please sign in to comment.