Skip to content

Commit

Permalink
Merge pull request #78 from rimgosu/main
Browse files Browse the repository at this point in the history
feat: naver voice
  • Loading branch information
rimgosu authored Feb 21, 2024
2 parents a6e4e11 + 6095fe6 commit aa63dbb
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 9 deletions.
3 changes: 3 additions & 0 deletions app/api/api_v1/endpoints/gree.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import uuid

from app.schemas.greeFileDto import GreeFileSchema
from app.services.gree_update_service import update_gree_voice_type
from app.services.image_service import create_image, check_image_status, upload_images_to_azure
from app.services.upload_service import upload_file_to_azure, upload_greefile_to_azure, \
upload_yaml_to_azure_blob, upload_gif_to_azure_blob
Expand Down Expand Up @@ -107,6 +108,8 @@ async def update_gree(gree_id: int, gree_update: GreeUpdate, current_user: Membe
if not gree:
raise HTTPException(status_code=404, detail="Gree not found")

await update_gree_voice_type(db, gree_id, gree_update)

updated_gree = await crud_update_gree(db, gree_id, gree_update)
return {"message": "Gree updated successfully"}

Expand Down
28 changes: 22 additions & 6 deletions app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,25 @@ class EmotionTypeEnum(PyEnum):

@unique
class VoiceTypeEnum(PyEnum):
ALLOY = 'alloy'
ECHO = 'echo'
FABLE = 'fable'
ONYX = 'onyx'
NOVA = 'nova'
SHIMMER = 'shimmer'
NWOOF = 'nwoof'
NJONGHYUN = 'njonghyun'
VDONGHYUN = 'vdonghyun'
NMAMMON = 'nmammon'
NGARAM = 'ngaram'
NMEOW = 'nmeow'
NIHYUN = 'nihyun'
VYUNA = 'vyuna'
VHYERI = 'vhyeri'
NHAJUN = 'nhajun'
NJAEWOOK = 'njaewook'
NJOONYOUNG = 'njoonyoung'
VDAESEONG = 'vdaeseong'
VIAN = 'vian'
NKYUWON = 'nkyuwon'
VDAIN = 'vdain'
NDAIN = 'ndain'
NMINSEO = 'nminseo'
NBORA = 'nbora'
NJIWON = 'njiwon'
VGOEUN = 'vgoeun'
NTIFFANY = 'ntiffany'
2 changes: 1 addition & 1 deletion app/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Gree(Base):
status = Column(Enum(StatusEnum))
isFavorite = Column(Boolean, default=False)
# 그리는 프롬프트 엔지니어링을 통해 다음과 같이 TTS의 목소리가 결정되어야한다.
voice_type = Column(Enum(VoiceTypeEnum), default=VoiceTypeEnum.ALLOY)
voice_type = Column(Enum(VoiceTypeEnum), default=VoiceTypeEnum.NWOOF)
register_at = Column(DateTime, nullable=False, default=datetime.now())

member = relationship("Member", back_populates="gree")
Expand Down
53 changes: 53 additions & 0 deletions app/services/gree_update_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from sqlalchemy import update
from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.models import Gree
from app.schemas.greeDto import GreeUpdate
import random # 랜덤 모듈 import

async def update_gree_voice_type(db: AsyncSession, gree_id: int, gree_update: GreeUpdate):
# MBTI의 첫 글자(E/I)에 따라 활기찬(E) 또는 차분한(I) 결정
personality_type = 'E' if gree_update.prompt_mbti and gree_update.prompt_mbti.startswith('E') else 'I'

# 성별에 따른 분류
gender = gree_update.prompt_gender.upper() if gree_update.prompt_gender else 'N' # Default to 'N' if None

# 나이에 따른 분류
if gree_update.prompt_age is not None:
if gree_update.prompt_age <= 11:
age_group = '어린이'
elif 11 < gree_update.prompt_age <= 17:
age_group = '청소년'
else:
age_group = '청년'
else:
age_group = None

# VoiceTypeEnum에서 적절한 값을 찾기
voice_type = None
# 나이 그룹별로 분류된 VoiceTypeEnum 매핑
voice_type_mapping = {
('E', '남자', '어린이'): ['nwoof'],
('E', '남자', '청소년'): ['njonghyun'],
('E', '남자', '청년'): ['vdonghyun', 'nmammon'],
('E', '여자', '어린이'): ['ngaram', 'nmeow'],
('E', '여자', '청소년'): ['nihyun'],
('E', '여자', '청년'): ['vyuna', 'vhyeri'],
('I', '남자', '어린이'): ['nhajun'],
('I', '남자', '청소년'): ['njaewook', 'njoonyoung'],
('I', '남자', '청년'): ['vdaeseong', 'vian', 'nkyuwon'],
('I', '여자', '어린이'): ['vdain', 'ndain'],
('I', '여자', '청소년'): ['nminseo', 'nbora', 'njiwon'],
('I', '여자', '청년'): ['vgoeun', 'ntiffany'],
}

if age_group:
voice_options = voice_type_mapping.get((personality_type, gender, age_group), [])
if voice_options:
voice_type = random.choice(voice_options) # 선택지 중 하나를 랜덤으로 선택

if voice_type:
# gree 객체의 voice_type 업데이트
stmt = update(Gree).where(Gree.id == gree_id).values(voice_type=voice_type)
await db.execute(stmt)
await db.commit()
39 changes: 37 additions & 2 deletions app/services/voice_service.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import urllib.request
import os
import uuid
import aiohttp
Expand Down Expand Up @@ -97,7 +98,9 @@ async def chat_with_openai_service(db: AsyncSession, chat_request: ChatRequestDt
)
gree_talk= completion.choices[0].message.content

mp3_path = await text_to_speech(gree.voice_type, gree_talk)
print(f'gree_talk = {gree_talk}')

mp3_path = await text_to_speech_naver(gree.voice_type, gree_talk)
voice_url_azure= await upload_mp3_azure(mp3_path)

createGptLogDto = CreateGreeTalkLogDto(
Expand Down Expand Up @@ -147,7 +150,39 @@ async def text_to_speech(voice_type: VoiceTypeEnum, gree_talk: str) -> str:
return mp3_path
else:
return "Error: 응답 상태가 200이 아닙니다."


async def text_to_speech_naver(voice_type: VoiceTypeEnum, text: str) -> str:
client_id = os.getenv('NAVER_CLIENT_ID') # 환경변수에서 클라이언트 ID를 가져옵니다.
client_secret = os.getenv('NAVER_CLIENT_SECRET') # 환경변수에서 클라이언트 비밀을 가져옵니다.
encText = urllib.parse.quote(text)
data = f"speaker={voice_type.value}&volume=0&speed=0&pitch=0&format=mp3&text=" + encText
url = "https://naveropenapi.apigw.ntruss.com/tts-premium/v1/tts"


headers = {
"X-NCP-APIGW-API-KEY-ID": client_id,
"X-NCP-APIGW-API-KEY": client_secret,
"Content-Type": "application/x-www-form-urlencoded"
}

async with aiohttp.ClientSession() as session:
async with session.post(url, data=data.encode('utf-8'), headers=headers) as response:
if response.status == 200:
content = await response.read()

# 지정된 경로에 디렉토리가 없으면 생성
dir_path = '/temp/voice_temp/'
if not os.path.exists(dir_path):
os.makedirs(dir_path)

# 파일 저장 경로 변경
mp3_path = os.path.join(dir_path, f'{voice_type.value}_speech.mp3')

with open(mp3_path, 'wb') as f:
f.write(content)
return mp3_path
else:
return "Error: 응답 상태가 200이 아닙니다."

async def upload_mp3_azure(mp3_path: str) -> str:
try:
Expand Down

0 comments on commit aa63dbb

Please sign in to comment.