feat: [sound-logo] 아이 목소리 옵션 + 말하기 속도 조절 추가
- 스타일 옵션에 어린이(5~7세), 초등학생(8~12세), 청소년(13~18세) 추가 - 말하기 속도 슬라이더 추가 (매우느리게~매우빠르게 5단계) - 속도와 스타일을 TTS 프롬프트 지시문으로 조합하여 Gemini API에 전달 - 음성 목록 여성/남성/중성 순서로 정렬
This commit is contained in:
@@ -487,6 +487,7 @@ public function soundLogoTts(Request $request): JsonResponse
|
||||
'text' => 'required|string|max:200',
|
||||
'voice_name' => 'nullable|string|max:30',
|
||||
'voice_style' => 'nullable|string|max:100',
|
||||
'voice_speed' => 'nullable|integer|min:1|max:5',
|
||||
]);
|
||||
|
||||
$apiKey = config('services.gemini.api_key');
|
||||
@@ -498,13 +499,29 @@ public function soundLogoTts(Request $request): JsonResponse
|
||||
|
||||
$voiceName = $request->voice_name ?: 'Kore';
|
||||
$voiceStyle = $request->voice_style ?: '';
|
||||
$voiceSpeed = $request->voice_speed ?: 3;
|
||||
|
||||
// 속도 지시문 매핑
|
||||
$speedDirectives = [
|
||||
1 => '아주 천천히 또박또박 말해주세요.',
|
||||
2 => '조금 느린 속도로 말해주세요.',
|
||||
3 => '', // 보통 — 지시 없음
|
||||
4 => '조금 빠른 속도로 말해주세요.',
|
||||
5 => '아주 빠른 속도로 말해주세요.',
|
||||
];
|
||||
|
||||
// TTS에 전달할 텍스트 구성
|
||||
$ttsText = $request->text;
|
||||
|
||||
// 스타일 지시가 있으면 프롬프트로 감싸기
|
||||
$directives = [];
|
||||
if ($voiceStyle) {
|
||||
$ttsText = "{$voiceStyle}: {$ttsText}";
|
||||
$directives[] = $voiceStyle;
|
||||
}
|
||||
if (! empty($speedDirectives[$voiceSpeed])) {
|
||||
$directives[] = $speedDirectives[$voiceSpeed];
|
||||
}
|
||||
|
||||
$ttsText = $request->text;
|
||||
if (! empty($directives)) {
|
||||
$ttsText = implode(' ', $directives)." \n\n{$ttsText}";
|
||||
}
|
||||
|
||||
// 짧은 텍스트는 TTS 모델이 텍스트 생성으로 인식할 수 있으므로 발화 컨텍스트 추가
|
||||
|
||||
@@ -369,6 +369,16 @@
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<!-- 말하기 속도 -->
|
||||
<div class="sl-param" style="margin-bottom: 8px;">
|
||||
<div class="sl-param-label">
|
||||
<span><i class="ri-speed-line"></i> 말하기 속도</span>
|
||||
<span x-text="voiceSpeedLabels[voiceSpeed - 1]" style="font-size: 10px;"></span>
|
||||
</div>
|
||||
<input type="range" class="sl-slider" min="1" max="5" step="1"
|
||||
x-model.number="voiceSpeed">
|
||||
</div>
|
||||
|
||||
<button class="sl-btn sm primary" style="width:100%;" @click="generateVoice()" :disabled="voiceLoading || !voiceText.trim()">
|
||||
<template x-if="!voiceLoading">
|
||||
<span><i class="ri-mic-line"></i> 음성 생성</span>
|
||||
@@ -1068,23 +1078,24 @@ function soundLogo() {
|
||||
voiceBuffer: null,
|
||||
voiceName: 'Kore',
|
||||
voiceStyle: '',
|
||||
voiceSpeed: 3, // 1~5 (1=매우느림, 3=보통, 5=매우빠름)
|
||||
voiceOptions: [
|
||||
{ value: 'Kore', label: '코어', desc: '단정하고 명확한', gender: '여성' },
|
||||
{ value: 'Aoede', label: '아오이데', desc: '산뜻하고 가벼운', gender: '여성' },
|
||||
{ value: 'Leda', label: '레다', desc: '젊고 발랄한', gender: '여성' },
|
||||
{ value: 'Achernar', label: '아케르나르', desc: '부드럽고 섬세한', gender: '여성' },
|
||||
{ value: 'Sulafat', label: '술라파트', desc: '따뜻하고 포근한', gender: '여성' },
|
||||
{ value: 'Sadachbia', label: '사다키비아', desc: '활기차고 생동감', gender: '여성' },
|
||||
{ value: 'Vindemiatrix', label: '빈데미아트릭스', desc: '부드럽고 차분한', gender: '여성' },
|
||||
{ value: 'Puck', label: '퍽', desc: '밝고 활발한', gender: '남성' },
|
||||
{ value: 'Charon', label: '카론', desc: '정보적이고 안정적', gender: '남성' },
|
||||
{ value: 'Fenrir', label: '펜리르', desc: '에너지 넘치는', gender: '남성' },
|
||||
{ value: 'Orus', label: '오루스', desc: '무게감 있는', gender: '남성' },
|
||||
{ value: 'Perseus', label: '페르세우스', desc: '차분하고 침착한', gender: '남성' },
|
||||
{ value: 'Zephyr', label: '제피르', desc: '밝고 경쾌한', gender: '중성' },
|
||||
{ value: 'Achernar', label: '아케르나르', desc: '부드럽고 섬세한', gender: '여성' },
|
||||
{ value: 'Gacrux', label: '가크룩스', desc: '성숙하고 중후한', gender: '남성' },
|
||||
{ value: 'Sulafat', label: '술라파트', desc: '따뜻하고 포근한', gender: '여성' },
|
||||
{ value: 'Achird', label: '아키르드', desc: '친근하고 다정한', gender: '남성' },
|
||||
{ value: 'Vindemiatrix', label: '빈데미아트릭스', desc: '부드럽고 차분한', gender: '여성' },
|
||||
{ value: 'Sadachbia', label: '사다키비아', desc: '활기차고 생동감', gender: '여성' },
|
||||
{ value: 'Algieba', label: '알기에바', desc: '매끄럽고 세련된', gender: '남성' },
|
||||
{ value: 'Zephyr', label: '제피르', desc: '밝고 경쾌한', gender: '중성' },
|
||||
],
|
||||
voiceStyleOptions: [
|
||||
{ value: '', label: '기본 스타일' },
|
||||
@@ -1094,9 +1105,11 @@ function soundLogo() {
|
||||
{ value: '힘 있고 당당하게', label: '힘 있고 당당하게' },
|
||||
{ value: '귀엽고 사랑스럽게', label: '귀엽고 사랑스럽게' },
|
||||
{ value: '진지하고 전문적으로', label: '진지하고 전문적으로' },
|
||||
{ value: '느리고 또박또박', label: '느리고 또박또박' },
|
||||
{ value: '빠르고 신나게', label: '빠르고 신나게' },
|
||||
{ value: '어린 아이의 목소리로', label: '어린 아이 (5~7세)' },
|
||||
{ value: '초등학생 아이의 밝은 목소리로', label: '초등학생 (8~12세)' },
|
||||
{ value: '10대 청소년의 목소리로', label: '청소년 (13~18세)' },
|
||||
],
|
||||
voiceSpeedLabels: ['매우 느리게', '느리게', '보통', '빠르게', '매우 빠르게'],
|
||||
|
||||
aiQuickPrompts: [
|
||||
'밝고 미래적인 IT 기업 로고',
|
||||
@@ -1669,6 +1682,7 @@ function soundLogo() {
|
||||
text: this.voiceText,
|
||||
voice_name: this.voiceName,
|
||||
voice_style: this.voiceStyle,
|
||||
voice_speed: this.voiceSpeed,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user