From 48a036a4195c706f0e099a69483b9fc97f568939 Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:49:47 +0900 Subject: [PATCH 1/7] =?UTF-8?q?Style=20:=20Issue=20=EB=B0=8F=20PR=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ISSUE_TEMPLATE/\342\234\205-feature.md" | 20 ++++++++++++++ .../ISSUE_TEMPLATE/\360\237\220\233-bug.md" | 27 +++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 10 +++++++ 3 files changed, 57 insertions(+) create mode 100644 ".github/ISSUE_TEMPLATE/\342\234\205-feature.md" create mode 100644 ".github/ISSUE_TEMPLATE/\360\237\220\233-bug.md" create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git "a/.github/ISSUE_TEMPLATE/\342\234\205-feature.md" "b/.github/ISSUE_TEMPLATE/\342\234\205-feature.md" new file mode 100644 index 0000000..3e69819 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\342\234\205-feature.md" @@ -0,0 +1,20 @@ +--- +name: "✅ FEATURE" +about: Feature 작업 사항을 입력해주세요. +title: '' +labels: '' +assignees: '' + +--- + +### Description +> 설명을 작성해주세요. + +### In Progress +> 작업사항들을 작성해주세요. +- [ ] todo1 +- [ ] todo2 +- [ ] todo3 + +### ETC +> 기타사항 diff --git "a/.github/ISSUE_TEMPLATE/\360\237\220\233-bug.md" "b/.github/ISSUE_TEMPLATE/\360\237\220\233-bug.md" new file mode 100644 index 0000000..da77c94 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\360\237\220\233-bug.md" @@ -0,0 +1,27 @@ +--- +name: "\U0001F41B BUG" +about: bug 발생 시 작성해주세요. +title: '' +labels: '' +assignees: '' + +--- + +### Describe the bug +> 버그에 대해 설명해주세요. + +### To Reproduce +> 버그 발생 과정을 기술하세요. +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +### Expected behavior +> 본래 작동할 것이라 예상했던 구현 결과에 대해 설명하세요. + +### Screenshots +> 화면 첨부파일 + +### Additional context(Optional) +>발생한 문제에 대한 추가사항 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6e0fd3d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ +## ☑️ Describe your changes +- 작업 내용을 적어주세요 +> ex. - CORS 허용 범위를 (/**) URL 전체로 변경한다. + +## 📷 Screenshot +- 관련 스크린샷 + +## 🔗 Issue number and link +- 이슈 번호를 등록해주세요 +> closed {#이슈번호} \ No newline at end of file From 760202b5ef6d281f28ffaf7fbc2216fde61e9e5c Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:51:04 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Feat=20:=20TTS=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20DTO=20=EC=83=9D=EC=84=B1=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../balpyo/ai/dto/EstimateRequestDTO.java | 17 ++++++++++++++ .../java/site/balpyo/ai/dto/PollyDTO.java | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/site/balpyo/ai/dto/EstimateRequestDTO.java create mode 100644 src/main/java/site/balpyo/ai/dto/PollyDTO.java diff --git a/src/main/java/site/balpyo/ai/dto/EstimateRequestDTO.java b/src/main/java/site/balpyo/ai/dto/EstimateRequestDTO.java new file mode 100644 index 0000000..9375fcf --- /dev/null +++ b/src/main/java/site/balpyo/ai/dto/EstimateRequestDTO.java @@ -0,0 +1,17 @@ +package site.balpyo.ai.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author dongheonlee + */ +@Data +public class EstimateRequestDTO { + + // 소요시간을 계산할 입력 텍스트 + @NotBlank(message = "text는 비어 있을 수 없습니다.") + private String text; + +} diff --git a/src/main/java/site/balpyo/ai/dto/PollyDTO.java b/src/main/java/site/balpyo/ai/dto/PollyDTO.java new file mode 100644 index 0000000..368eaac --- /dev/null +++ b/src/main/java/site/balpyo/ai/dto/PollyDTO.java @@ -0,0 +1,23 @@ +package site.balpyo.ai.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * @author dongheonlee + */ +@Data +public class PollyDTO { + + // 음성으로 변환할 텍스트 + @NotBlank(message = "text는 비어 있을 수 없습니다.") + private String text; + + // 빠르기 조절 [-2, -1, 0, 1, 2] + @NotBlank(message = "speed는 비어 있을 수 없습니다.") + private int speed; + + @NotBlank(message = "balpyoSecretKey는 비어 있을 수 없습니다.") + private String balpyoAPIKey; +} From 65632f11fd77273d00bfaddd201348b684b868a2 Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:51:55 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Feat=20:=20=EC=9E=85=EB=A0=A5=EB=90=9C=20?= =?UTF-8?q?=ED=85=8D=EC=8A=A4=ED=8A=B8=EC=9D=98=20=EC=86=8C=EC=9A=94?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EB=B9=A0?= =?UTF-8?q?=EB=A5=B4=EA=B8=B0=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=9D=8C?= =?UTF-8?q?=EC=84=B1=ED=8C=8C=EC=9D=BC=20=EB=B3=80=ED=99=98=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../site/balpyo/ai/service/PollyService.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/main/java/site/balpyo/ai/service/PollyService.java diff --git a/src/main/java/site/balpyo/ai/service/PollyService.java b/src/main/java/site/balpyo/ai/service/PollyService.java new file mode 100644 index 0000000..3f46bf3 --- /dev/null +++ b/src/main/java/site/balpyo/ai/service/PollyService.java @@ -0,0 +1,104 @@ +package site.balpyo.ai.service; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.polly.AmazonPolly; +import com.amazonaws.services.polly.AmazonPollyClient; +import com.amazonaws.services.polly.model.*; +import org.springframework.stereotype.Service; +import site.balpyo.ai.dto.PollyDTO; + +import java.io.InputStream; + +/** + * @author dongheonlee + */ +@Service +public class PollyService { + + /** + * 입력된 텍스트의 예상 음성 파일 재생 시간을 계산하여 반환한다. + * + * @param text 입력 텍스트 + * @return 예상 음성 파일 재생 시간 (초) + */ + public int estimateSpeechDuration(String text) { + // 예상 소요 시간 계산을 위한 변수 + int totalDuration = 0; + + // 입력 텍스트에서 "\n"과 "."을 제외한 문자열 추출 + String cleanedText = text.replaceAll("[\\n.]", ""); + + // 입력 텍스트를 한 음절씩 분리하여 처리 + for (char c : cleanedText.toCharArray()) { + // 띄어쓰기나 쉼표가 있을 때마다 한 번씩 숨을 쉬는 시간 추가 + if (c == ' ' || c == ',') { + totalDuration += 1000; // 1초의 숨쉬는 시간으로 가정 + } else { + int durationPerSyllable = 150; // 음절당 150ms로 설정 + totalDuration += durationPerSyllable; + } + } + + // 소요 시간을 초로 변환하여 반환 + return totalDuration / 1000; // ms를 초로 변환 + } + + + /** + * 입력된 텍스트와 선택된 빠르기에 따라 음성파일으로 변환하여 반환한다. + * + * @param pollyDTO + * @return mp3 오디오 파일 + */ + public InputStream synthesizeSpeech(PollyDTO pollyDTO) { + + String inputText = pollyDTO.getText(); + int speed = pollyDTO.getSpeed(); + + // Amazon Polly 클라이언트 생성 + AmazonPolly amazonPolly = AmazonPollyClient.builder() + .withRegion(Regions.AP_NORTHEAST_2) // 서울 리전 + .withCredentials(new DefaultAWSCredentialsProviderChain()) + .build(); + + // 빠르기 계산 + float relativeSpeed = calculateRelativeSpeed(speed); + + // SynthesizeSpeechRequest 생성 + SynthesizeSpeechRequest synthesizeSpeechRequest = new SynthesizeSpeechRequest() + .withText(inputText) + .withOutputFormat(OutputFormat.Mp3) // MP3 형식 + .withVoiceId(VoiceId.Seoyeon) // 한국어 음성 변환 보이스 + .withTextType("ssml") // SSML 형식 사용 -> 태그와 rate로 설정 가능 + .withText("" + inputText + ""); + + // 텍스트를 음성으로 변환하여 InputStream으로 반환 + SynthesizeSpeechResult synthesizeSpeechResult = amazonPolly.synthesizeSpeech(synthesizeSpeechRequest); + return synthesizeSpeechResult.getAudioStream(); + } + + + /** + * mp3 audio 생성 시, 빠르기 설정 메소드 + */ + private static float calculateRelativeSpeed(int speed) { + // 기본 속도 + float baseSpeed = 1.0f; + + switch (speed) { + case -2: + return baseSpeed * 0.5f; + case -1: + return baseSpeed * 0.8f; + case 1: + return baseSpeed * 1.2f; + case 2: + return baseSpeed * 1.5f; + default: + return baseSpeed; + } + } + + +} \ No newline at end of file From 86175f18eae59627adea1de6bc4bf7dc901307c7 Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:52:13 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat=20:=20AWS=20polly=EB=A5=BC=20=ED=99=9C?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20tts=20=EA=B5=AC=ED=98=84=20controller=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../balpyo/ai/controller/PollyController.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/main/java/site/balpyo/ai/controller/PollyController.java diff --git a/src/main/java/site/balpyo/ai/controller/PollyController.java b/src/main/java/site/balpyo/ai/controller/PollyController.java new file mode 100644 index 0000000..e921934 --- /dev/null +++ b/src/main/java/site/balpyo/ai/controller/PollyController.java @@ -0,0 +1,97 @@ +package site.balpyo.ai.controller; + +import com.amazonaws.util.IOUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +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 site.balpyo.ai.dto.PollyDTO; +import site.balpyo.ai.service.PollyService; +import site.balpyo.common.dto.CommonResponse; +import site.balpyo.common.dto.ErrorEnum; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * @author dongheonlee + * AWS polly를 활용한 tts 구현 컨트롤러 + */ +@RestController +@Slf4j +@RequiredArgsConstructor +@RequestMapping("/api/polly") +public class PollyController { + + @Value("${secrets.BALPYO_API_KEY}") //TODO :: 임시 api 시크릿 키 구현 (차후 로그인 연동시 삭제예정) + public String BALPYO_API_KEY; + + private final PollyService pollyService; + + /** + * @param pollyDTO + * @return 호출 시, 요청정보에 따른 mp3 음성파일을 반환(audioBytes)한다. + */ + @PostMapping("/generateAudio") + public ResponseEntity synthesizeText(@RequestBody PollyDTO pollyDTO) { + + if (!BALPYO_API_KEY.equals(pollyDTO.getBalpyoAPIKey())) { + return CommonResponse.error(ErrorEnum.BALPYO_API_KEY_ERROR); + } + + try { + // Amazon Polly와 통합하여 텍스트를 음성으로 변환 + InputStream audioStream = pollyService.synthesizeSpeech(pollyDTO); + + // InputStream을 byte 배열로 변환 + byte[] audioBytes = IOUtils.toByteArray(audioStream); + + // MP3 파일을 클라이언트에게 반환 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("testAudio", "speech.mp3"); + + return ResponseEntity.ok() + .headers(headers) + .body(audioBytes); + + } catch (IOException e) { + e.printStackTrace(); + return CommonResponse.error(ErrorEnum.INTERNAL_SERVER_ERROR); + } + } + + + /** + * 입력한 대본을 음성 파일 변환 시, 음절당 기준값에 따라 계산된 소요시간을 반환한다. + * @param pollyDTO + * @return + */ + @PostMapping("/estimateDuration") + public ResponseEntity estimateSpeechDuration(@RequestBody PollyDTO pollyDTO) { + + if (!BALPYO_API_KEY.equals(pollyDTO.getBalpyoAPIKey())) { + return CommonResponse.error(ErrorEnum.BALPYO_API_KEY_ERROR); + } + + String inputText = pollyDTO.getText(); + + try { + int durationInSeconds = pollyService.estimateSpeechDuration(inputText); + + + return CommonResponse.success(durationInSeconds); + + } catch (Exception e) { + e.printStackTrace(); + return CommonResponse.error(ErrorEnum.INTERNAL_SERVER_ERROR); + } + } +} From bff13217acadeb410706d6b4a8113ac92ceca882 Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:52:59 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Feat=20:=20=EC=84=9C=EB=B2=84=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20ErrorEnum=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/site/balpyo/common/dto/ErrorEnum.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/site/balpyo/common/dto/ErrorEnum.java b/src/main/java/site/balpyo/common/dto/ErrorEnum.java index 2e8b8ea..18359a3 100644 --- a/src/main/java/site/balpyo/common/dto/ErrorEnum.java +++ b/src/main/java/site/balpyo/common/dto/ErrorEnum.java @@ -1,7 +1,6 @@ package site.balpyo.common.dto; import lombok.AllArgsConstructor; -import lombok.Data; import lombok.Getter; @Getter @@ -13,7 +12,11 @@ public enum ErrorEnum { GPT_API_KEY_MISSING("8002", "GPT API 키 누락."), //9000 - client 계열 에러 - BALPYO_API_KEY_ERROR("9000", "BALPYO_API_KEY를 다시 확인해주세요."); + BALPYO_API_KEY_ERROR("9000", "BALPYO_API_KEY를 다시 확인해주세요."), + + // 5000 - 내부 서버 에러 + INTERNAL_SERVER_ERROR("5000", "내부 서버 오류가 발생했습니다."); + private final String code; private final String message; From 5034f5fd79ba5ae483a28fbf3d6fcbf94166172e Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:53:29 +0900 Subject: [PATCH 6/7] =?UTF-8?q?Chore=20:=20aws=20polly=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index c34aee5..6e53a7d 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.1' + // https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-polly + implementation 'com.amazonaws:aws-java-sdk-polly:1.12.681' + } From 59ae59c3ea1ed73c52e1b7d9fc4ca9dbdc8bf26d Mon Sep 17 00:00:00 2001 From: sirius506775 Date: Mon, 18 Mar 2024 00:54:09 +0900 Subject: [PATCH 7/7] =?UTF-8?q?Feat=20:=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C,=20Prompt=EC=97=90?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/site/balpyo/ai/service/AIGenerateUtils.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/site/balpyo/ai/service/AIGenerateUtils.java b/src/main/java/site/balpyo/ai/service/AIGenerateUtils.java index b22c5c5..dd52315 100644 --- a/src/main/java/site/balpyo/ai/service/AIGenerateUtils.java +++ b/src/main/java/site/balpyo/ai/service/AIGenerateUtils.java @@ -23,6 +23,7 @@ public String createPromptString(String topic, String keywords , Integer sec){ "\n" + "I want you to act as a presenter specialized in "+ topic +" My first request is for you to generate a script:\n" + "\n" + + "Make a script by calculating 150ms per syllable except spaces and commas" + "Here's some context:\n" + "Topic - " + topic +"\n" + "Keywords - " + keywords +"\n" + @@ -51,6 +52,14 @@ public ResponseEntity requestGPTTextGeneration(String prompt, float tempera } - +// public ResponseEntity<>Map> requestGPTTextToSpeech(){ +// +// HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); +// +// RestTemplate restTemplate = new RestTemplate(); +// ResponseEntity response = restTemplate.postForEntity(ENDPOINT, requestEntity, Map.class); +// +// return new response; +// } }