Skip to content

Commit

Permalink
Merge pull request #65 from tukcomCD2024/feat#56/Change-elevenlabs-to…
Browse files Browse the repository at this point in the history
…-WAS-server

Feat#56/change elevenlabs to was server
  • Loading branch information
JunRain2 authored Apr 10, 2024
2 parents cd00346 + 0423aaa commit a1c01d9
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 1 deletion.
5 changes: 5 additions & 0 deletions backend/memetory/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ dependencies {
// Swagger 적용
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'


// AWS SDK
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// test에 lombok 추가
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.security:spring-security-test'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.memetory.domain.voice.controller;

import com.example.memetory.domain.voice.dto.request.GenerateVoiceRequestDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;

import java.io.IOException;

@Tag(name = "Voice")
public interface VoiceApi {

@Operation(
summary = "목소리 추출",
description = "S3_URL에 있는 음성파일에서 목소리를 추출한다.",
security = {@SecurityRequirement(name = "access_token")}
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "목소리 추출!"
)
})
ResponseEntity<Object> register(
@Parameter(hidden = true) String email,
GenerateVoiceRequestDto generateVoiceRequestDto
) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.example.memetory.domain.voice.controller;

import com.example.memetory.domain.voice.dto.VoiceServiceDto;
import com.example.memetory.domain.voice.dto.request.GenerateVoiceRequestDto;
import com.example.memetory.domain.voice.dto.response.GenerateVoiceResponseDto;
import com.example.memetory.domain.voice.service.VoiceService;
import com.example.memetory.global.annotation.LoginMemberEmail;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
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 org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.io.*;


@RestController
@RequiredArgsConstructor
@RequestMapping("/voice")
public class VoiceController implements VoiceApi {

private final VoiceService voiceService;

@Value("${elevenlabs.api.url.add}")
private String apiUrl;

@Value("${elevenlabs.api.key}")
private String apiKey;

@PostMapping
@Override
public ResponseEntity<Object> register(@LoginMemberEmail String email, @RequestBody GenerateVoiceRequestDto generateVoiceRequestDto) throws IOException {

// s3 url을 받아서 음성파일을 가져오는 ServiceDto 로직 1개
VoiceServiceDto voiceServiceDtoS3 = generateVoiceRequestDto.toServiceDtoS3(email);
MultiValueMap<String, Object> formData = voiceService.generateVoice(voiceServiceDtoS3);

return WebClient
.create(apiUrl)
.post()
.header("xi-api-key", apiKey)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(formData))
.retrieve()
.bodyToMono(GenerateVoiceResponseDto.class)
.flatMap(response -> {
// elevenlabs API 호출이 완료 되면 실행할 로직
VoiceServiceDto voiceServiceDtoElevenlabs =
generateVoiceRequestDto.toServiceDtoElevenlabs(email, response.getElevenlabsVoiceId());
voiceService.register(voiceServiceDtoElevenlabs);
return Mono.just(ResponseEntity.status(HttpStatus.CREATED).build());
})
.onErrorResume(error -> {
System.out.println(("An error occurred while processing the request: {}" + error.getMessage()));
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
})
.block();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.memetory.domain.voice.dto;

import com.example.memetory.domain.member.entity.Member;
import com.example.memetory.domain.voice.entity.Voice;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class VoiceServiceDto {

private String email;
private String s3Key;
private String name;
private String description;
private String elevenlabsVoiceId;

public Voice toEntity(Member member) {
return Voice.builder()
.member(member)
.elevenlabsVoiceId(elevenlabsVoiceId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.memetory.domain.voice.dto.request;

import com.example.memetory.domain.voice.dto.VoiceServiceDto;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class GenerateVoiceRequestDto {
private String s3Key;
private String name;
private String description;

public VoiceServiceDto toServiceDtoS3(String email) {
return VoiceServiceDto.builder()
.email(email)
.s3Key(s3Key)
.name(name)
.description(description)
.build();
}

public VoiceServiceDto toServiceDtoElevenlabs(String email, String elevenlabsId) {
return VoiceServiceDto.builder()
.email(email)
.elevenlabsVoiceId(elevenlabsId)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.memetory.domain.voice.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;


@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class GenerateVoiceResponseDto {

@JsonProperty("voice_id")
private String elevenlabsVoiceId;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.memetory.domain.voice.entity;

import com.example.memetory.domain.member.entity.Member;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Voice {

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

@Column(name = "elevenlabs_voice_id")
private String elevenlabsVoiceId;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@Builder
public Voice(String elevenlabsVoiceId, Member member) {
this.elevenlabsVoiceId = elevenlabsVoiceId;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.memetory.domain.voice.repository;

import com.example.memetory.domain.voice.entity.Voice;
import org.springframework.data.jpa.repository.JpaRepository;

public interface VoiceRepository extends JpaRepository<Voice, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.example.memetory.domain.voice.service;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.S3Object;
import com.example.memetory.domain.member.entity.Member;
import com.example.memetory.domain.member.service.MemberService;
import com.example.memetory.domain.voice.dto.VoiceServiceDto;
import com.example.memetory.domain.voice.entity.Voice;
import com.example.memetory.domain.voice.repository.VoiceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.io.*;


@Service
@RequiredArgsConstructor
public class VoiceService {

private final VoiceRepository voiceRepository;
private final MemberService memberService;
private final AmazonS3Client amazonS3Client;

@Value("${cloud.aws.s3.bucket}")
private String bucketName;

@Transactional
public void register(VoiceServiceDto voiceServiceDto) {
Member foundMember = memberService.findByEmail(voiceServiceDto.getEmail());
Voice newVoice = voiceServiceDto.toEntity(foundMember);

voiceRepository.save(newVoice);
}

// 목소리 생성
public MultiValueMap<String, Object> generateVoice(VoiceServiceDto voiceServiceDto) throws IOException {
S3Object s3Object = getS3File(voiceServiceDto);

return createFormData(s3Object, voiceServiceDto);
}

// S3 파일 가져오기
private S3Object getS3File(VoiceServiceDto voiceServiceDto) {
return amazonS3Client.getObject(bucketName, voiceServiceDto.getS3Key());
}

// Elevenlabs로 보낼 formData 생성
private MultiValueMap<String, Object> createFormData(S3Object s3Object, VoiceServiceDto voiceServiceDto) throws IOException {
// S3Object로부터 데이터를 읽어오기 위한 InputStream 생성
InputStream inputStream = s3Object.getObjectContent();

// 데이터를 임시로 저장할 ByteArrayOutputStream 생성
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

// 입력 스트림에서 데이터를 읽어와서 ByteArrayOutputStream에 쓰기
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}

// ByteArrayOutputStream에 저장된 데이터를 byte 배열로 변환
byte[] data = byteArrayOutputStream.toByteArray();

// byte 배열을 파일 시스템 리소스로 변환
FileSystemResource fileSystemResource = createFileSystemResource(data, voiceServiceDto.getS3Key());

// Elevenlabs API 호출을 위한 폼 데이터 MultiValueMap 생성
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
formData.add("files", fileSystemResource);
formData.add("name", voiceServiceDto.getName());
formData.add("description", voiceServiceDto.getDescription());

// 입력 스트림 닫기
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}

// S3Object 닫기
if (s3Object != null) {
try {
s3Object.close();
} catch (IOException e) {
e.printStackTrace();
}
}

return formData;
}

private FileSystemResource createFileSystemResource(byte[] data, String s3Key) throws IOException {
// 임시 파일 생성
File tempFile = File.createTempFile(s3Key, "");
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
// 데이터를 파일에 쓰기
fos.write(data);
}
// 파일 시스템 리소스 생성하여 반환
return new FileSystemResource(tempFile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.memetory.global.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
2 changes: 1 addition & 1 deletion backend/memetory/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
spring:
profiles:
group:
"local" : "local, jwt"
"local" : "local, jwt, config"
active : local # "local" 을 실행

---
Expand Down

0 comments on commit a1c01d9

Please sign in to comment.