diff --git a/app/controller/RecordController.py b/app/controller/RecordController.py index 1de9ea7..b7b5d66 100644 --- a/app/controller/RecordController.py +++ b/app/controller/RecordController.py @@ -1,16 +1,23 @@ import os +import subprocess +import time from typing import List import requests -from boto3 import client + from fastapi import APIRouter, Request, UploadFile, File, Form +from boto3 import client +from pydub import AudioSegment +from pydub.playback import play + +from app import s3Service +from app.dto.BasicTTSRequestDto import BasicTTSRequestDto +from app.dto.FirstTTSRequestDto import FirstTTSRequestDto +from app.dto.ExtraTTSRequestDto import ExtraTTSRequestDto -from app.dto.ScheduleSpeakRequestDto import ScheduleSpeakRequestDto -from app.dto.ScheduleTTSRequestDto import ScheduleTTSRequestDto -from app.service.elevenLabs import text_to_speech_file_save_AWS -from app.service.gpt import ChatgptAPI -from app.service.s3Service import download_from_s3 -from app.utils import play_file + +from app.elevenLabs import add_voice, text_to_speech_file_save_AWS, get_voice, delete_all_voice, text_to_speech_file +from app.s3Service import download_from_s3_links, download_from_s3 router = APIRouter( prefix="/api/fastapi", @@ -30,18 +37,6 @@ ) -async def save_local_file(file: UploadFile) -> str: - """하나의 파일을 로컬에 저장하고 경로를 반환합니다.""" - audio_dir = "./audio" - if not os.path.exists(audio_dir): - os.makedirs(audio_dir) - - local_file_path = os.path.join(audio_dir, file.filename) - with open(local_file_path, "wb") as f: - f.write(await file.read()) - return local_file_path - - async def save_local_files(files: List[UploadFile]) -> list: """업로드된 파일을 로컬에 저장하고 파일 경로를 반환합니다.""" audio_dir = "./audio" @@ -56,76 +51,82 @@ async def save_local_files(files: List[UploadFile]) -> list: return local_file_path_list -# 첫 로그인 시 목소리 녹음 api +# 첫 로그인 시 1분 목소리 녹음 api @router.post("/voices") -async def getVoice(request: Request, user_id: int = Form(...), file: UploadFile = File(...)): - token = request.headers.get("Authorization").split(" ")[1] - local_file_path = await save_local_file(file) - name = str(user_id) - # voice_id = add_voice(name=name, local_file_paths=[local_file_path]) - print(name) +async def getVoice(request: Request, files: List[UploadFile] = File(...)): + # token = request.headers.get("Authorization").split(" ")[1] + local_file_path_list = await save_local_files(files) + name = 'yjg' + voice_id = add_voice(name=name, local_file_paths=local_file_path_list) + print(voice_id) # voice_url = s3Service.upload_to_s3(local_file_path) - os.remove(local_file_path) + # os.remove(local_file_path) - send_user_voice_id_to_spring(token=token, voice_id=yjg_voice_id) + # send_user_voice_file_to_spring(token=token, voice_url=voice_url) - -@router.post("/schedules") -async def schedule_tts(request: Request, schedules: ScheduleTTSRequestDto): +@router.post("/save/basic-tts") +async def save_S3_basic_tts(request: Request, firstTTSRequestDtoList: FirstTTSRequestDto): # token = request.headers.get("Authorization").split(" ")[1] - voice_id = schedules.voice_id - - prompt = ChatgptAPI(schedules.schedule_text, schedules.alias) - - # schedule_dict: {"저녁": "엄마~ 저녁 잘 챙겨 먹었어?", "운동": "오늘 운동했어? 건강 챙겨~!"} - schedule_dict = prompt.get_schedule_json() + # text가 어떤형식으로 올지 몰라서 일단 그대로 내보낸다고 가정 (변환시 지피티 사용) # TTS 처리 (MP3 파일 생성 후 s3 저장) response = { - schedules.schedule_id[i]: { - "keyword": schedules.schedule_text[i], # 키워드 직접 저장 - "text": schedule_dict.get(schedules.schedule_text[i], ""), # 문장은 GPT 결과에서 매핑 - "url": text_to_speech_file_save_AWS( - schedule_dict.get(schedules.schedule_text[i], ""), - yjg_voice_id - ) - } - for i in range(len(schedules.schedule_id)) + firstTTSRequestDtoList.basic_schedule_id[i]: text_to_speech_file_save_AWS(firstTTSRequestDtoList.basic_schedule_text[i], + yjg_voice_id) + for i in range(len(firstTTSRequestDtoList.basic_schedule_id)) } + return response -@router.post("/schedules-speak") -async def speak_schedule(request: Request, scheduleSpeakRequestDto: ScheduleSpeakRequestDto): +@router.post("/basic-tts") +async def speak_schedule_tts(request: Request, basicTTSRequestDto: BasicTTSRequestDto): # token = request.headers.get("Authorization").split(" ")[1] - local_file_path = download_from_s3(scheduleSpeakRequestDto.schedule_voice_Url) + local_file_path = download_from_s3(basicTTSRequestDto.schedule_voice_Url) print(f"Downloaded file path: {local_file_path}") - # target_time에 맞춰서 TTS 파일 재생 - play_file.play_at_target_time(scheduleSpeakRequestDto.target_time, local_file_path) + # 블루투스 헤드셋 또는 기본 스피커로 출력 + os.system("pactl list sinks | grep 'bluez_sink'") # 블루투스 출력 장치 확인 + os.system("pactl set-default-sink `pactl list sinks short | grep bluez_sink | awk '{print $2}'`") # 기본 출력 변경 - return {"message": "TTS completed and played on speaker"} + # 로컬 파일을 직접 재생 + subprocess.run(["mpg321", local_file_path]) + return {"message": "TTS completed and played on Bluetooth headset or speaker"} -def send_user_voice_file_to_spring(token: str, voice_url: str): - headers = { - "Authorization": f"Bearer {token}" - } - data = { - "voiceUrl": voice_url - } - requests.post("http://localhost:8080/api/spring/records/voices", headers=headers, json=data) - # requests.post("https://peachmentor.com/api/spring/records/voices", headers=headers, json=data) +@router.post("/extra-tts") +async def speak_schedule_tts(request: Rlocalhostquest, extraTTSRequestDto: ExtraTTSRequestDto): + # token = request.headers.get("Authorization").split(" ")[1] + schedule_text = extraTTSRequestDto.schedule_text + + #진짜 실제로 쓸 코드 + local_file_path = text_to_speech_file(schedule_text, yjg_voice_id) + + # 테스트하면서 AWS에 올려놓으려고 남긴 코드 + url = text_to_speech_file_save_AWS(schedule_text, yjg_voice_id) + local_file_path = download_from_s3(url) + + # local_file_path = os.getcwd()+"/test_audio/test8.mp3" # test + # 블루투스 헤드셋 또는 기본 스피커로 출력 + os.system("pactl list sinks | grep 'bluez_sink'") # 블루투스 출력 장치 확인 + os.system("pactl set-default-sink `pactl list sinks short | grep bluez_sink | awk '{print $2}'`") # 기본 출력 변경 -def send_user_voice_id_to_spring(token: str, voice_id: str): + # 로컬 파일을 직접 재생 + subprocess.run(["/usr/bin/mpg321", local_file_path]) + # subprocess.run(["ffplay", "-nodisp", "-autoexit", local_file_path], + # stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # 윈도우용 + return {"message": "TTS completed and played on Bluetooth headset or speaker"} + + +def send_user_voice_file_to_spring(token: str, voice_url: str): headers = { "Authorization": f"Bearer {token}" } data = { - "voiceId": voice_id + "voiceUrl": voice_url } - requests.post("http://localhost:8080/api/spring/records/voices", headers=headers, json=data) + requests.post("http://springboot:8080/api/spring/records/voices", headers=headers, json=data) # requests.post("https://peachmentor.com/api/spring/records/voices", headers=headers, json=data) @@ -137,7 +138,7 @@ def send_user_speech_file_to_spring(token: str, before_audio_link: str, answerId "beforeAudioLink": before_audio_link, "answerId": answerId } - requests.post("http://localhost:8080/api/spring/records/speeches", headers=headers, json=data) + requests.post("http://springboot:8080/api/spring/records/speeches", headers=headers, json=data) # requests.post("https://peachmentor.com/api/spring/records/speeches", headers=headers, json=data) @@ -145,7 +146,7 @@ def receive_self_feedback(token: str) -> str: headers = { "Authorization": f"Bearer {token}" } - response = requests.get("http://localhost:8080/api/spring/self-feedbacks/latest-feedbacks", headers=headers) + response = requests.get("http://springboot:8080/api/spring/self-feedbacks/latest-feedbacks", headers=headers) # response = requests.get("https://peachmentor.com/api/spring/self-feedbacks/latest-feedbacks", headers=headers) feedback_data = response.json().get('result', {}) @@ -165,7 +166,7 @@ def send_statistics_to_spring(token: str, gantourCount: int, silentTime: float, "silentTime": silentTime, "answerId": answerId } - requests.post("http://localhost:8080/api/spring/statistics", headers=headers, json=data) + requests.post("http://springboot:8080/api/spring/statistics", headers=headers, json=data) # requests.post("https://peachmentor.com/api/spring/statistics", headers=headers, json=data) # # 질문 답변에 대한 insight 제공 api @@ -232,4 +233,4 @@ def send_statistics_to_spring(token: str, gantourCount: int, silentTime: float, # analysis_gpt = AnalysisAssistant(questions=analysisRequestDto.questions, beforeScripts=analysisRequestDto.beforeScripts) # analysis = analysis_gpt.get_analysis() # -# return {"analysisText": analysis} # 데이터를 JSON 객체로 감쌈 +# return {"analysisText": analysis} # 데이터를 JSON 객체로 감쌈 \ No newline at end of file diff --git a/audio/99ef70a1-0fe0-4b82-af32-9e32cbea7800.mp3 b/audio/99ef70a1-0fe0-4b82-af32-9e32cbea7800.mp3 new file mode 100644 index 0000000..c21adb1 Binary files /dev/null and b/audio/99ef70a1-0fe0-4b82-af32-9e32cbea7800.mp3 differ diff --git a/audio/d9da92b8-6a16-4886-bee1-2222a98a8cf3.mp3 b/audio/d9da92b8-6a16-4886-bee1-2222a98a8cf3.mp3 new file mode 100644 index 0000000..c21adb1 Binary files /dev/null and b/audio/d9da92b8-6a16-4886-bee1-2222a98a8cf3.mp3 differ diff --git a/audio/test8.mp3 b/audio/test8.mp3 new file mode 100644 index 0000000..93287b8 Binary files /dev/null and b/audio/test8.mp3 differ diff --git a/main.py b/main.py index c8e4812..25789d4 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,71 @@ +import RPi.GPIO as GPIO import time +from datetime import datetime -import uvicorn +# ─────────────────────────────── +# PIR 센서 관련 +# ─────────────────────────────── +PIR_PIN = 17 # GPIO17 +def detect_motion(): + GPIO.setmode(GPIO.BCM) + GPIO.setup(PIR_PIN, GPIO.IN) + + print("PIR 센서 디버깅 시작 (Ctrl+C 종료)") + prev_state = None + + try: + while True: + signal = GPIO.input(PIR_PIN) + + if signal != prev_state: + timestamp = datetime.now().strftime("%H:%M:%S") + state_str = "감지됨 (HIGH)" if signal else " 없음 (LOW)" + print(f"[{timestamp}] 상태 변경 ▶ {state_str}") + prev_state = signal + + time.sleep(0.1) + except KeyboardInterrupt: + print("⛔ 종료 중...") + GPIO.cleanup() + + +# ─────────────────────────────── +# DHT11 센서 관련 (5회 재시도 버전) +# ─────────────────────────────── +import adafruit_dht +import board + +def read_dht11(): + print("🌡️ DHT11 센서 측정 시작...") + dhtDevice = adafruit_dht.DHT11(board.D4) # GPIO4 (멀티보드 IO4) + + for i in range(5): # 최대 5번 재시도 + try: + print(f"📡 시도 {i + 1} ...") + temperature = dhtDevice.temperature + humidity = dhtDevice.humidity + + if temperature is not None and humidity is not None: + print(f"✅ 온도: {temperature}°C") + print(f"✅ 습도: {humidity}%") + break + else: + print("⚠️ 센서로부터 데이터를 읽을 수 없습니다.") + except RuntimeError as error: + print(f"⚠️ 에러 발생: {error.args[0]}") + except Exception as error: + print(f"❌ 심각한 오류: {error}") + break + time.sleep(2) # 재시도 간 간격 + + # 종료 함수는 비활성화 (라이브러리 오류 방지) + # dhtDevice.exit() + + +# ─────────────────────────────── +# 메인 함수 +# ─────────────────────────────── if __name__ == "__main__": - uvicorn.run( - app="app.main:app", - host="localhost", - # host="0.0.0.0", - port=8000, - ) \ No newline at end of file + # detect_motion() # PIR 센서 테스트 시 주석 해제 + read_dht11() # 현재는 DHT11만 테스트