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/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/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_send_msg.py b/Unity/socket_client.py similarity index 100% rename from Unity/socket_send_msg.py rename to Unity/socket_client.py diff --git a/Unity/socketServer.py b/Unity/socket_server.py similarity index 100% rename from Unity/socketServer.py rename to Unity/socket_server.py 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 0000000..a032377 Binary files /dev/null and b/first_response.wav differ