From b5c0c8eb2cacb34f54cfd43b606d54033421f2ca Mon Sep 17 00:00:00 2001 From: yoshidaR3n Date: Sat, 9 Nov 2024 00:52:12 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E3=81=AB=E3=82=88?= =?UTF-8?q?=E3=82=8B=E3=82=AB=E3=83=AC=E3=83=B3=E3=83=80=E3=83=BC=E3=81=B8?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0=E3=83=BB=E5=8F=82=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- voice_to_llm/test.py | 182 +++++++++++++++++++++++++++++++++++++++++ voice_to_llm/uniuni.py | 67 +++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 voice_to_llm/test.py create mode 100644 voice_to_llm/uniuni.py diff --git a/voice_to_llm/test.py b/voice_to_llm/test.py new file mode 100644 index 0000000..2166665 --- /dev/null +++ b/voice_to_llm/test.py @@ -0,0 +1,182 @@ +import speech_recognition as sr +import openai +import requests +import pygame +import json +from datetime import datetime, timezone + +from uniuni import get_events,register_event + +# ウェイクワードの設定 +WAKE_WORD = "あいり" # 任意のウェイクワードに変更可能 + +# OpenAI APIキーを設定 +openai.api_key = "OPENAI_API_KEY" + +get_function_description={ + "name":"get_events", + "description":"カレンダーを参照して今日の予定を取得する", + "parameters": { + "type": "object", + "properties": { + "schedule": { + "type": "string", + "description": "直近の予定" + } + }, + "required": ["schedule"] + } + } + +register_function_description={ + "name":"register_event", + "description":f"予定を登録する,現在時刻は{datetime.now().isoformat()}", + "parameters": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "description": "登録する予定の内容" + }, + "start":{ + "type": "string", + "description": "予定の開始時間(例:2024-11-17T09:00:00+09:00)" + }, + "end":{ + "type": "string", + "description": "予定の終了時間(例:2024-11-17T13:00:00+09:00)" + } + }, + "required": ["summary", "start", "end"] + } +} + +def get_openai_response(prompt, model="gpt-4o-mini"): + try: + response = openai.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, + {"role": "user", "content": prompt} + ], + functions=[get_function_description, register_function_description], + function_call="auto" + ) + # 応答テキストを抽出 + # ChatGPTの応答から関数呼び出しの情報を取得 + message = response.choices[0].message + + # 関数がget_eventsの場合、関数を実行 + + if message.function_call is not None and message.function_call.name == "get_events": + print("get_eventsが選択されました") + #args = json.loads(message.function_call.arguments) + result = get_events() + # 関数の結果をChatGPTに送信して応答を作成 + follow_up_response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, + {"role": "user", "content": prompt}, + message, + {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} + ] + ) + print(follow_up_response.choices[0].message.content) + return follow_up_response.choices[0].message.content + + if message.function_call is not None and message.function_call.name == "register_event": + print("register_eventが選択されました") + args = json.loads(message.function_call.arguments) + print(args.get("summary"),args.get("start"),args.get("end")) + result = register_event(args.get("summary"),args.get("start"),args.get("end")) + # 関数の結果をChatGPTに送信して応答を作成 + follow_up_response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, + {"role": "user", "content": prompt}, + message, + {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} + ] + ) + print(follow_up_response.choices[0].message.content) + return follow_up_response.choices[0].message.content + + return response.choices[0].message.content + + except Exception as e: + print(f"OpenAI APIでエラーが発生しました エラー:{e}") + return None + +#ここにLLMからの応答データを入れる +def speak_zunda(text): + # 音声合成用クエリを作成 + query_payload = {'text': text, 'speaker': 3} + query_res = requests.post("http://127.0.0.1:50021/audio_query", params=query_payload) + #query_res.raise_for_status() + audio_query = query_res.json() + + # 音声合成を実行して音声データを生成 + synthesis_res = requests.post( + "http://127.0.0.1:50021/synthesis", #ローカルでやるとき + params={'speaker': 3}, + json=audio_query + ) + + #synthesis_res.raise_for_status() + + # 音声ファイルを保存して再生(絶対パス) + with open("C:\\WorkPlace\\test\\test.wav", "wb") as f: + f.write(synthesis_res.content) + + pygame.mixer.init() + pygame.mixer.music.load("C:\\WorkPlace\\test\\test.wav") + pygame.mixer.music.play() + +def detect_wake_word(): + recognizer = sr.Recognizer() + microphone = sr.Microphone() + + print("音声を取得しています...") + + with microphone as source: + audio = recognizer.listen(source) # 音声を取得 + try: + # Google Speech Recognition APIで音声をテキストに変換 + transcription = recognizer.recognize_google(audio,language='ja-JP') + print(f"Recognized text: {transcription}") + + # ウェイクワードが音声に含まれているかをチェック + if WAKE_WORD in transcription: + print("ウェイクワードが検出されました!") + remove_str="あいり" + transcription = transcription.replace(remove_str, "") + user_prompt = transcription + + # OpenAIからの応答を取得 + response = get_openai_response(user_prompt) + if response: + print("応答:", response) + speak_zunda(response) + else: + print("応答を取得できませんでした。") + return True + else: + print("Wake word not detected.") + return False + + except sr.UnknownValueError: + # 音声が理解できなかった場合 + print("音声を認識できませんでした") + return False + + except sr.RequestError as e: + # APIリクエストのエラー処理 + print(f"Could not request results; {e}") + return False + +while 1: + detect_wake_word() + + diff --git a/voice_to_llm/uniuni.py b/voice_to_llm/uniuni.py new file mode 100644 index 0000000..d58315b --- /dev/null +++ b/voice_to_llm/uniuni.py @@ -0,0 +1,67 @@ +from datetime import datetime, timezone +import os +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build + +# Google Calendar APIのスコープ(読み書き権限) +SCOPES = ["https://www.googleapis.com/auth/calendar"] +def get_calendar_service(): + creds = None + # 既存のトークンファイルがあれば読み込む(トークンは認証情報を保存するためのもの) + if os.path.exists("token.json"): + creds = Credentials.from_authorized_user_file("token.json", SCOPES) + # 有効な認証情報がない場合、再認証を実行 + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + # トークンの期限が切れている場合、リフレッシュ + creds.refresh(Request()) + else: + # 初回認証、もしくはトークンがない場合に認証フローを開始 + flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) + creds = flow.run_local_server(port=0) + # 認証情報をtoken.jsonに保存して次回以降再利用できるようにする + with open("token.json", "w") as token: + token.write(creds.to_json()) + # Google Calendar APIサービスのインスタンスを作成 + service = build("calendar", "v3", credentials=creds) + return service + +#カレンダーに予定登録 +def register_event(summary:str, start:str, end:str): + service=get_calendar_service() + # イベントの詳細情報 + event = { + 'summary': summary, # イベントのタイトル + 'start': { # イベント開始日時(日本時間) + 'dateTime': start, + 'timeZone': 'Asia/Tokyo' + }, + 'end': { # イベント終了日時(日本時間) + 'dateTime': end, + 'timeZone': 'Asia/Tokyo' + }, + } + # イベントをGoogleカレンダーに追加 + created_event = service.events().insert(calendarId='primary', body=event).execute() + print('Event created: %s' % created_event.get('htmlLink')) # 作成したイベントのリンクを出力 + return "登録成功" + + +def get_events(): + service = get_calendar_service() + # 今から1週間分のイベントを取得する + now = datetime.now().isoformat() + 'Z' + events_result = service.events().list( + calendarId='primary', timeMin=now, + maxResults= 3, singleEvents=True, + orderBy='startTime').execute() + events = events_result.get('items', []) + if not events: + print('イベントが取得できませんでした') + return "イベントが取得できませんでした" + for event in events: + scedule = event['start'].get('dateTime', event['start'].get('date')) + print(scedule, event['summary']) + return events From 06a128ec62905f480a8947ccbcc44168d91c5189 Mon Sep 17 00:00:00 2001 From: Yoshida Ren Date: Sat, 9 Nov 2024 21:29:17 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E5=90=8D=E7=A7=B0=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=A8=E4=B8=8D=E8=A6=81=E3=81=AA=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AE=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Unity/call_function.py | 63 -------------- Unity/socketServer.py | 31 ------- Unity/socket_send_msg.py | 8 -- voice_to_llm/test.py | 182 --------------------------------------- voice_to_llm/uniuni.py | 67 -------------- 5 files changed, 351 deletions(-) delete mode 100644 Unity/call_function.py delete mode 100644 Unity/socketServer.py delete mode 100644 Unity/socket_send_msg.py delete mode 100644 voice_to_llm/test.py delete mode 100644 voice_to_llm/uniuni.py diff --git a/Unity/call_function.py b/Unity/call_function.py deleted file mode 100644 index 7279af4..0000000 --- a/Unity/call_function.py +++ /dev/null @@ -1,63 +0,0 @@ -from dotenv import load_dotenv -import os -import openai -import json - -load_dotenv() -OPEN_AI_API=os.environ.get("OPEN_AI_API") -openai.api_key = OPEN_AI_API - -#仮の関数 -def play_anim(anim_id:str): - print(anim_id) - return - - -#call functionで呼び出される関数を説明 -function_description={ - "name":"play_anim", - "description":"Unity上のアニメーションを実行する", - "parameters": { - "type": "object", - "properties": { - "anim_id": { - "type": "string", - "description": """Unity上で実行されるアニメーションID {嬉しい:grad, 悲しい:sad, 怒り:angry}""" - } - }, - "required": ["anim_id"] - } -} - - -response = openai.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content": "入力されてた文章から感情を読み取り、適切なアニメーションIDを選択します"}, - {"role": "user", "content": "いい加減起きなさい"} - ], - functions=[function_description], - function_call={"name":"play_anim"} -) - -# ChatGPTの応答から関数呼び出しの情報を取得 -message = response.choices[0].message -print(message.function_call.arguments) - -# 関数がplay_animの場合、関数を実行 -if message.function_call.name == "play_anim": - print("play_animが選択されました") - args = json.loads(message.function_call.arguments) - result = play_anim(args["anim_id"]) - - # # 関数の結果をChatGPTに送信して応答を作成 - # follow_up_response = openai.ChatCompletion.create( - # model="gpt-4o-mini", - # messages=[ - # {"role": "user", "content": "東京の天気を教えてください。"}, - # message, - # {"role": "function", "name": "get_weather", "content": json.dumps(result)} - # ] - # ) - - # print(follow_up_response["choices"][0]["message"]["content"]) diff --git a/Unity/socketServer.py b/Unity/socketServer.py deleted file mode 100644 index 30b6043..0000000 --- a/Unity/socketServer.py +++ /dev/null @@ -1,31 +0,0 @@ -import socket - -def getString_socket(): - # ソケットを作成 - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - # バインドするアドレスとポート - # host = socket.gethostbyname(socket.gethostname()) # localhost - host = "0.0.0.0" - port = 65432 - server_socket.bind((host, port)) - - # クライアントからの接続を待機 - server_socket.listen(1) - print("クライアントからの接続を待っています...") - - # 接続を受け入れる - client_socket, addr = server_socket.accept() - print(f"接続を受け入れました: {addr}") - - # データを受信 - data = client_socket.recv(1024) - print(f"受信したデータ: {data.decode('utf-8')}") - - # ソケットを閉じる - client_socket.close() - server_socket.close() - - return data.decode('utf-8') - -#getString_socket() diff --git a/Unity/socket_send_msg.py b/Unity/socket_send_msg.py deleted file mode 100644 index 02a7482..0000000 --- a/Unity/socket_send_msg.py +++ /dev/null @@ -1,8 +0,0 @@ -import socket -import threading - -# クライアント接続して、文字を送って接続解除 -def start_client_sendString(message, host="10.0.0.192", port=65432): - client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - client_socket.connect((host, port)) - client_socket.sendall(message.encode('utf-8')) \ No newline at end of file diff --git a/voice_to_llm/test.py b/voice_to_llm/test.py deleted file mode 100644 index 2166665..0000000 --- a/voice_to_llm/test.py +++ /dev/null @@ -1,182 +0,0 @@ -import speech_recognition as sr -import openai -import requests -import pygame -import json -from datetime import datetime, timezone - -from uniuni import get_events,register_event - -# ウェイクワードの設定 -WAKE_WORD = "あいり" # 任意のウェイクワードに変更可能 - -# OpenAI APIキーを設定 -openai.api_key = "OPENAI_API_KEY" - -get_function_description={ - "name":"get_events", - "description":"カレンダーを参照して今日の予定を取得する", - "parameters": { - "type": "object", - "properties": { - "schedule": { - "type": "string", - "description": "直近の予定" - } - }, - "required": ["schedule"] - } - } - -register_function_description={ - "name":"register_event", - "description":f"予定を登録する,現在時刻は{datetime.now().isoformat()}", - "parameters": { - "type": "object", - "properties": { - "summary": { - "type": "string", - "description": "登録する予定の内容" - }, - "start":{ - "type": "string", - "description": "予定の開始時間(例:2024-11-17T09:00:00+09:00)" - }, - "end":{ - "type": "string", - "description": "予定の終了時間(例:2024-11-17T13:00:00+09:00)" - } - }, - "required": ["summary", "start", "end"] - } -} - -def get_openai_response(prompt, model="gpt-4o-mini"): - try: - response = openai.chat.completions.create( - model=model, - messages=[ - {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, - {"role": "user", "content": prompt} - ], - functions=[get_function_description, register_function_description], - function_call="auto" - ) - # 応答テキストを抽出 - # ChatGPTの応答から関数呼び出しの情報を取得 - message = response.choices[0].message - - # 関数がget_eventsの場合、関数を実行 - - if message.function_call is not None and message.function_call.name == "get_events": - print("get_eventsが選択されました") - #args = json.loads(message.function_call.arguments) - result = get_events() - # 関数の結果をChatGPTに送信して応答を作成 - follow_up_response = openai.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, - {"role": "user", "content": prompt}, - message, - {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} - ] - ) - print(follow_up_response.choices[0].message.content) - return follow_up_response.choices[0].message.content - - if message.function_call is not None and message.function_call.name == "register_event": - print("register_eventが選択されました") - args = json.loads(message.function_call.arguments) - print(args.get("summary"),args.get("start"),args.get("end")) - result = register_event(args.get("summary"),args.get("start"),args.get("end")) - # 関数の結果をChatGPTに送信して応答を作成 - follow_up_response = openai.chat.completions.create( - model="gpt-4o-mini", - messages=[ - {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, - {"role": "user", "content": prompt}, - message, - {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} - ] - ) - print(follow_up_response.choices[0].message.content) - return follow_up_response.choices[0].message.content - - return response.choices[0].message.content - - except Exception as e: - print(f"OpenAI APIでエラーが発生しました エラー:{e}") - return None - -#ここにLLMからの応答データを入れる -def speak_zunda(text): - # 音声合成用クエリを作成 - query_payload = {'text': text, 'speaker': 3} - query_res = requests.post("http://127.0.0.1:50021/audio_query", params=query_payload) - #query_res.raise_for_status() - audio_query = query_res.json() - - # 音声合成を実行して音声データを生成 - synthesis_res = requests.post( - "http://127.0.0.1:50021/synthesis", #ローカルでやるとき - params={'speaker': 3}, - json=audio_query - ) - - #synthesis_res.raise_for_status() - - # 音声ファイルを保存して再生(絶対パス) - with open("C:\\WorkPlace\\test\\test.wav", "wb") as f: - f.write(synthesis_res.content) - - pygame.mixer.init() - pygame.mixer.music.load("C:\\WorkPlace\\test\\test.wav") - pygame.mixer.music.play() - -def detect_wake_word(): - recognizer = sr.Recognizer() - microphone = sr.Microphone() - - print("音声を取得しています...") - - with microphone as source: - audio = recognizer.listen(source) # 音声を取得 - try: - # Google Speech Recognition APIで音声をテキストに変換 - transcription = recognizer.recognize_google(audio,language='ja-JP') - print(f"Recognized text: {transcription}") - - # ウェイクワードが音声に含まれているかをチェック - if WAKE_WORD in transcription: - print("ウェイクワードが検出されました!") - remove_str="あいり" - transcription = transcription.replace(remove_str, "") - user_prompt = transcription - - # OpenAIからの応答を取得 - response = get_openai_response(user_prompt) - if response: - print("応答:", response) - speak_zunda(response) - else: - print("応答を取得できませんでした。") - return True - else: - print("Wake word not detected.") - return False - - except sr.UnknownValueError: - # 音声が理解できなかった場合 - print("音声を認識できませんでした") - return False - - except sr.RequestError as e: - # APIリクエストのエラー処理 - print(f"Could not request results; {e}") - return False - -while 1: - detect_wake_word() - - diff --git a/voice_to_llm/uniuni.py b/voice_to_llm/uniuni.py deleted file mode 100644 index d58315b..0000000 --- a/voice_to_llm/uniuni.py +++ /dev/null @@ -1,67 +0,0 @@ -from datetime import datetime, timezone -import os -from google.auth.transport.requests import Request -from google.oauth2.credentials import Credentials -from google_auth_oauthlib.flow import InstalledAppFlow -from googleapiclient.discovery import build - -# Google Calendar APIのスコープ(読み書き権限) -SCOPES = ["https://www.googleapis.com/auth/calendar"] -def get_calendar_service(): - creds = None - # 既存のトークンファイルがあれば読み込む(トークンは認証情報を保存するためのもの) - if os.path.exists("token.json"): - creds = Credentials.from_authorized_user_file("token.json", SCOPES) - # 有効な認証情報がない場合、再認証を実行 - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - # トークンの期限が切れている場合、リフレッシュ - creds.refresh(Request()) - else: - # 初回認証、もしくはトークンがない場合に認証フローを開始 - flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) - creds = flow.run_local_server(port=0) - # 認証情報をtoken.jsonに保存して次回以降再利用できるようにする - with open("token.json", "w") as token: - token.write(creds.to_json()) - # Google Calendar APIサービスのインスタンスを作成 - service = build("calendar", "v3", credentials=creds) - return service - -#カレンダーに予定登録 -def register_event(summary:str, start:str, end:str): - service=get_calendar_service() - # イベントの詳細情報 - event = { - 'summary': summary, # イベントのタイトル - 'start': { # イベント開始日時(日本時間) - 'dateTime': start, - 'timeZone': 'Asia/Tokyo' - }, - 'end': { # イベント終了日時(日本時間) - 'dateTime': end, - 'timeZone': 'Asia/Tokyo' - }, - } - # イベントをGoogleカレンダーに追加 - created_event = service.events().insert(calendarId='primary', body=event).execute() - print('Event created: %s' % created_event.get('htmlLink')) # 作成したイベントのリンクを出力 - return "登録成功" - - -def get_events(): - service = get_calendar_service() - # 今から1週間分のイベントを取得する - now = datetime.now().isoformat() + 'Z' - events_result = service.events().list( - calendarId='primary', timeMin=now, - maxResults= 3, singleEvents=True, - orderBy='startTime').execute() - events = events_result.get('items', []) - if not events: - print('イベントが取得できませんでした') - return "イベントが取得できませんでした" - for event in events: - scedule = event['start'].get('dateTime', event['start'].get('date')) - print(scedule, event['summary']) - return events From 6b1a0f7b69801cd9665225aa511a2152c259f2e7 Mon Sep 17 00:00:00 2001 From: Yoshida Ren Date: Sat, 9 Nov 2024 21:31:05 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=E3=82=A6=E3=82=A7=E3=82=A4=E3=82=AF?= =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=83=89=E6=A9=9F=E8=83=BD=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=A8google=E3=82=AB=E3=83=AC=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=BC=E3=81=B8=E3=81=AE=E4=BA=88=E5=AE=9A=E7=99=BB=E9=8C=B2?= =?UTF-8?q?/=E4=BA=88=E5=AE=9A=E5=8F=82=E7=85=A7=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Unity/.gitignore | 2 + Unity/create_voice.py | 53 +++++++---- Unity/google_api.py | 70 +++++++++++++++ Unity/socket_client.py | 8 ++ Unity/socket_server.py | 31 +++++++ Unity/wake_word.py | 194 +++++++++++++++++++++++++++++++++++++++++ first_response.wav | Bin 0 -> 53292 bytes 7 files changed, 339 insertions(+), 19 deletions(-) create mode 100644 Unity/google_api.py create mode 100644 Unity/socket_client.py create mode 100644 Unity/socket_server.py create mode 100644 Unity/wake_word.py create mode 100644 first_response.wav diff --git a/Unity/.gitignore b/Unity/.gitignore index 4c49bd7..c610a7c 100644 --- a/Unity/.gitignore +++ b/Unity/.gitignore @@ -1 +1,3 @@ .env +credentials.json +token.json \ No newline at end of file diff --git a/Unity/create_voice.py b/Unity/create_voice.py index eceb281..b61806a 100644 --- a/Unity/create_voice.py +++ b/Unity/create_voice.py @@ -9,19 +9,48 @@ # 親ディレクトリをPythonのパスに追加 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # from sd_2410.audio.register_time import register_and_responce -from socketServer import getString_socket +from sd_2410.Unity.socket_server import getString_socket # ずんだもんの話者ID speaker_id = 3 #speaker_id = 67 -while 1: +# while 1: +# #ここにLLMからの応答データを入れる +# text = getString_socket() + +# # 音声合成用クエリを作成 +# query_payload = {'text': text, 'speaker': speaker_id} +# query_res = requests.post("http://127.0.0.1:50021/audio_query", params=query_payload) +# query_res.raise_for_status() +# audio_query = query_res.json() + +# # 音声合成を実行して音声データを生成 +# synthesis_res = requests.post( +# "http://127.0.0.1:50021/synthesis", #ローカルでやるとき +# params={'speaker': speaker_id}, +# json=audio_query +# ) + +# synthesis_res.raise_for_status() + +# # 音声ファイルを保存して再生(絶対パス) +# with open("C:/Users/renta/Joyman/Assets/Audio/bando.wav", "wb") as f: +# f.write(synthesis_res.content) + +# # 文字ファイルを保存(絶対パス) +# with open("C:/Users/renta/Joyman/Assets/Text/responce.txt", "w",encoding="utf-8") as f: +# f.write(text) + +# print("音声・文字ファイルが生成されました!") + + +def create_voice(text): #ここにLLMからの応答データを入れる - text = getString_socket() + #text = "おはようございますですわ" # 音声合成用クエリを作成 query_payload = {'text': text, 'speaker': speaker_id} query_res = requests.post("http://127.0.0.1:50021/audio_query", params=query_payload) - #query_res = requests.post("http://10.0.0.194:50021/audio_query", params=query_payload) #VPN接続して query_res.raise_for_status() audio_query = query_res.json() @@ -32,24 +61,10 @@ json=audio_query ) - # synthesis_res = requests.post( - # "http://10.0.0.194:50021/synthesis", #リクエスト投げるときはyotchiのPCのIPアドレスにしてください - # params={'speaker': speaker_id}, - # json=audio_query - # ) - synthesis_res.raise_for_status() - # # 音声ファイルを保存して再生 - # with open("sd_2410/Unity/Assets/resouce/zundamon_voice.wav", "wb") as f: - # f.write(synthesis_res.content) - - # # 文字ファイルを保存 - # with open("sd_2410/Unity/Assets/resouce/responce.txt", "w",encoding="utf-8") as f: - # f.write(text) - # 音声ファイルを保存して再生(絶対パス) - with open("C:/Users/renta/Joyman/Assets/Audio/bando.wav", "wb") as f: + with open("C:/Users/renta/Joyman/Assets/Audio/abando.wav", "wb") as f: f.write(synthesis_res.content) # 文字ファイルを保存(絶対パス) diff --git a/Unity/google_api.py b/Unity/google_api.py new file mode 100644 index 0000000..d46fd5f --- /dev/null +++ b/Unity/google_api.py @@ -0,0 +1,70 @@ +from datetime import datetime, timezone +import os +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build + +# Google Calendar APIのスコープ(読み書き権限) +SCOPES = ["https://www.googleapis.com/auth/calendar"] +TOKEN_PATH=f"{os.path.dirname(os.path.abspath(__file__))}/token.json" +CRED_PATH=f"{os.path.dirname(os.path.abspath(__file__))}/credentials.json" + +def get_calendar_service(): + creds = None + # 既存のトークンファイルがあれば読み込む(トークンは認証情報を保存するためのもの) + if os.path.exists(TOKEN_PATH): + creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES) + # 有効な認証情報がない場合、再認証を実行 + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + # トークンの期限が切れている場合、リフレッシュ + creds.refresh(Request()) + else: + # 初回認証、もしくはトークンがない場合に認証フローを開始 + flow = InstalledAppFlow.from_client_secrets_file(CRED_PATH, SCOPES) + creds = flow.run_local_server(port=0) + # 認証情報をtoken.jsonに保存して次回以降再利用できるようにする + with open(TOKEN_PATH, "w") as token: + token.write(creds.to_json()) + # Google Calendar APIサービスのインスタンスを作成 + service = build("calendar", "v3", credentials=creds) + return service + +#カレンダーに予定登録 +def register_event(summary:str, start:str, end:str): + service=get_calendar_service() + # イベントの詳細情報 + event = { + 'summary': summary, # イベントのタイトル + 'start': { # イベント開始日時(日本時間) + 'dateTime': start, + 'timeZone': 'Asia/Tokyo' + }, + 'end': { # イベント終了日時(日本時間) + 'dateTime': end, + 'timeZone': 'Asia/Tokyo' + }, + } + # イベントをGoogleカレンダーに追加 + created_event = service.events().insert(calendarId='primary', body=event).execute() + print('Event created: %s' % created_event.get('htmlLink')) # 作成したイベントのリンクを出力 + return "登録成功" + + +def get_events(): + service = get_calendar_service() + # 今から1週間分のイベントを取得する + now = datetime.now().isoformat() + 'Z' + events_result = service.events().list( + calendarId='primary', timeMin=now, + maxResults= 3, singleEvents=True, + orderBy='startTime').execute() + events = events_result.get('items', []) + if not events: + print('イベントが取得できませんでした') + return "イベントが取得できませんでした" + for event in events: + scedule = event['start'].get('dateTime', event['start'].get('date')) + print(scedule, event['summary']) + return events diff --git a/Unity/socket_client.py b/Unity/socket_client.py new file mode 100644 index 0000000..02a7482 --- /dev/null +++ b/Unity/socket_client.py @@ -0,0 +1,8 @@ +import socket +import threading + +# クライアント接続して、文字を送って接続解除 +def start_client_sendString(message, host="10.0.0.192", port=65432): + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.connect((host, port)) + client_socket.sendall(message.encode('utf-8')) \ No newline at end of file diff --git a/Unity/socket_server.py b/Unity/socket_server.py new file mode 100644 index 0000000..30b6043 --- /dev/null +++ b/Unity/socket_server.py @@ -0,0 +1,31 @@ +import socket + +def getString_socket(): + # ソケットを作成 + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # バインドするアドレスとポート + # host = socket.gethostbyname(socket.gethostname()) # localhost + host = "0.0.0.0" + port = 65432 + server_socket.bind((host, port)) + + # クライアントからの接続を待機 + server_socket.listen(1) + print("クライアントからの接続を待っています...") + + # 接続を受け入れる + client_socket, addr = server_socket.accept() + print(f"接続を受け入れました: {addr}") + + # データを受信 + data = client_socket.recv(1024) + print(f"受信したデータ: {data.decode('utf-8')}") + + # ソケットを閉じる + client_socket.close() + server_socket.close() + + return data.decode('utf-8') + +#getString_socket() diff --git a/Unity/wake_word.py b/Unity/wake_word.py new file mode 100644 index 0000000..57d4d24 --- /dev/null +++ b/Unity/wake_word.py @@ -0,0 +1,194 @@ +import speech_recognition as sr +import openai +#import pygame +import json +from datetime import datetime +import dotenv +import os + +from google_api import get_events, register_event +from create_voice import create_voice + +dotenv.load_dotenv() +OPEN_AI_API=os.environ.get("OPEN_AI_API") +# OpenAI APIキーを設定 +openai.api_key = OPEN_AI_API + +WAKE_WORD = "ずんだ" # 任意のウェイクワードに変更可能 + +get_function_description={ + "name":"get_events", + "description":"カレンダーを参照して今日の予定を取得する", + "parameters": { + "type": "object", + "properties": { + "schedule": { + "type": "string", + "description": "直近の予定" + } + }, + "required": ["schedule"] + } + } + +register_function_description={ + "name":"register_event", + "description":f"予定を登録する,現在時刻は{datetime.now().isoformat()}", + "parameters": { + "type": "object", + "properties": { + "summary": { + "type": "string", + "description": "登録する予定の内容" + }, + "start":{ + "type": "string", + "description": "予定の開始時間(例:2024-11-17T09:00:00+09:00)" + }, + "end":{ + "type": "string", + "description": "予定の終了時間(例:2024-11-17T13:00:00+09:00)" + } + }, + "required": ["summary", "start", "end"] + } +} + +def get_openai_response(prompt, model="gpt-4o-mini"): + try: + response = openai.chat.completions.create( + model=model, + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, + {"role": "user", "content": prompt} + ], + functions=[get_function_description, register_function_description], + function_call="auto" + ) + # 応答テキストを抽出 + # ChatGPTの応答から関数呼び出しの情報を取得 + message = response.choices[0].message + + # 関数がget_eventsの場合、関数を実行 + + if message.function_call is not None and message.function_call.name == "get_events": + print("get_eventsが選択されました") + #args = json.loads(message.function_call.arguments) + result = get_events() + # 関数の結果をChatGPTに送信して応答を作成 + follow_up_response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。カレンダーへのリンクは応答に含めないでください。100字以内"}, + {"role": "user", "content": prompt}, + message, + {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} + ] + ) + print(follow_up_response.choices[0].message.content) + return follow_up_response.choices[0].message.content + + if message.function_call is not None and message.function_call.name == "register_event": + print("register_eventが選択されました") + args = json.loads(message.function_call.arguments) + print(args.get("summary"),args.get("start"),args.get("end")) + result = register_event(args.get("summary"),args.get("start"),args.get("end")) + # 関数の結果をChatGPTに送信して応答を作成 + follow_up_response = openai.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content":"生意気な口調で「なのだ」口調でしゃべって。100字以内"}, + {"role": "user", "content": prompt}, + message, + {"role": "function", "name": "get_event", "content":json.dumps({"schedule": result})} + ] + ) + print(follow_up_response.choices[0].message.content) + return follow_up_response.choices[0].message.content + + return response.choices[0].message.content + + except Exception as e: + print(f"OpenAI APIでエラーが発生しました エラー:{e}") + return None + +#Unityが無い環境はこっち使ってください +# #ここにLLMからの応答データを入れる +# def speak_zunda(text): +# # 音声合成用クエリを作成 +# query_payload = {'text': text, 'speaker': 3} +# query_res = requests.post("http://127.0.0.1:50021/audio_query", params=query_payload) +# #query_res.raise_for_status() +# audio_query = query_res.json() + +# # 音声合成を実行して音声データを生成 +# synthesis_res = requests.post( +# "http://127.0.0.1:50021/synthesis", #ローカルでやるとき +# params={'speaker': 3}, +# json=audio_query +# ) + +# #synthesis_res.raise_for_status() + +# # 音声ファイルを保存して再生(絶対パス) +# with open("C:\\WorkPlace\\test\\test.wav", "wb") as f: +# f.write(synthesis_res.content) + +# pygame.mixer.init() +# pygame.mixer.music.load("C:\\WorkPlace\\test\\test.wav") +# pygame.mixer.music.play() + +def detect_wake_word(): + recognizer = sr.Recognizer() + microphone = sr.Microphone() + + print("音声を取得しています...") + + with microphone as source: + audio = recognizer.listen(source) # 音声を取得 + try: + # Google Speech Recognition APIで音声をテキストに変換 + transcription = recognizer.recognize_google(audio,language='ja-JP') + print(f"Recognized text: {transcription}") + + # ウェイクワードが音声に含まれているかをチェック + if WAKE_WORD in transcription: + print("ウェイクワードが検出されました!") + # 音声ファイルを保存して再生(絶対パス) + # コピー元ファイルをバイナリで読み込み、コピー先ファイルに書き込む + with open( "sd_2410/first_response.wav", "rb") as src_file: + data = src_file.read() + with open("C:/Users/renta/Joyman/Assets/Audio/abando.wav", "wb") as dest_file: + dest_file.write(data) + # 文字ファイルを保存(絶対パス) + with open("C:/Users/renta/Joyman/Assets/Text/responce.txt", "w",encoding="utf-8") as f: + f.write("どうしたのだ?") + + remove_str=WAKE_WORD + transcription = transcription.replace(remove_str, "") + user_prompt = transcription + + # OpenAIからの応答を取得 + response = get_openai_response(user_prompt) + if response: + create_voice(response) + else: + print("応答を取得できませんでした。") + return True + else: + return False + + except sr.UnknownValueError: + # 音声が理解できなかった場合 + print("音声を認識できませんでした") + return False + + except sr.RequestError as e: + # APIリクエストのエラー処理 + print(f"APIエラー") + return False + +while 1: + detect_wake_word() + + diff --git a/first_response.wav b/first_response.wav new file mode 100644 index 0000000000000000000000000000000000000000..a0323771829a125ae9d55f6c246d75efb2fdbdb5 GIT binary patch literal 53292 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 Y|KDeviQQ)j@lP>|3K>hSy~BV01&zo_N&o-= literal 0 HcmV?d00001