ㅁ 개요
O 프로그램 소개
- 이번 글은 이전글([프로젝트] 파이썬 음성으로 네이버 캘린더에 자동으로 일정 등록하는 chatGPT plugin 만들기 - 4.이전 글의 정보로 API를 이용하여 네이버 캘린더에 일정 추가하기)에 이은 6번째 마지막 글로 이전글의 내용들을 종합하여 chatGPT에게 음성으로 대화 및 네이버 캘린더에에 일정을 추가하는 방법에 대해 설명합니다.
자세한 내용은 아래 이전글 구글 캘린더에 자동으로 일정 등록하는 chatGPT plugin 만들기편을 참고하시기 바랍니다.
[프로젝트] 파이썬 음성으로 구글 캘린더에 자동으로 일정 등록하는 chatGPT plugin 만들기 - 4.챗GPT에게 음성으로 대화 및 구글 캘린더 일정 추가/삭제하기
O 완성된 프로그램 실행 화면
1.아래와 같이 프로그램을 실행하면 챗GPT가 음성으로 노란색으로 안내(질문)하고, 사용자가 음성으로 빨간색으로 답변(요청)합니다.
chatGPT가 등록한 일정에 대한 시간과 내용을 안내해 주고 지피티 모드를 종료하고 대기모드로 돌아갑니다.
2. 네이버 캘린더에서 확인 결과 위에서 음성으로 등록 요청한 내용이 잘 등록된 것을 확인 할 수 있습니다.
ㅁ 세부 내용
O 완성된 소스
소스 : 6.voice_add_cal_C.py
# 네이버 API 예제 - 캘린더 일정 추가하기
import os
import urllib.request
import random
from config import *
import datetime
from datetime import datetime, timedelta
import time
import speech_recognition as sr
from gtts import gTTS
import os
import playsound
from translate import Translator
import openai # pip install openai
from config import *
openai.api_key = OPENAI_API_KEY
def textToVoice(text):
tts = gTTS(text=text, lang='ko')
filename='voice.mp3'
tts.save(filename) # 파일을 만들고,
playsound.playsound(filename) # 해당 음성파일을 실행(즉, 음성을 말함)
os.remove(filename)
def voiceToText():
r = sr.Recognizer()
with sr.Microphone() as source:
print("say something: ")
audio = r.listen(source)
said = " "
try:
said = r.recognize_google(audio, language="ko-KR")
print("Your speech thinks like: ", said)
except Exception as e:
print("Exception: " + str(e))
return said
def create_event(start_time, end_time, summary, location=None, description=None):
header = "Bearer " + token # Bearer 다음에 공백 추가
url = "https://openapi.naver.com/calendar/createSchedule.json"
calSum = urllib.parse.quote("[제목] : "+summary)
calDes = urllib.parse.quote("[상세] : "+description)
calLoc = urllib.parse.quote("[장소] : "+location)
# uid = token[1:16] # UUID 생성 (임시로 일단 토큰값을 잘라서 사용)
# UID값을 만들기 위해 토큰의 15자리로 랜덤하게 만듦
uid = ''.join(random.choices(token, k=15))
print(uid,"++++++++++++++")
scheduleIcalString = "BEGIN:VCALENDAR\n"
scheduleIcalString += "VERSION:2.0\n"
scheduleIcalString += "PRODID:Naver Calendar\n"
scheduleIcalString += "CALSCALE:GREGORIAN\n"
scheduleIcalString += "BEGIN:VTIMEZONE\n"
scheduleIcalString += "TZID:Asia/Seoul\n"
scheduleIcalString += "BEGIN:STANDARD\n"
scheduleIcalString += "DTSTART:19700101T000000\n"
scheduleIcalString += "TZNAME:GMT%2B09:00\n"
scheduleIcalString += "TZOFFSETFROM:%2B0900\n"
scheduleIcalString += "TZOFFSETTO:%2B0900\n"
scheduleIcalString += "END:STANDARD\n"
scheduleIcalString += "END:VTIMEZONE\n"
scheduleIcalString += "BEGIN:VEVENT\n"
scheduleIcalString += "SEQUENCE:0\n"
scheduleIcalString += "CLASS:PUBLIC\n"
scheduleIcalString += "TRANSP:OPAQUE\n"
scheduleIcalString += "UID:" + uid + "\n" # 일정 고유 아이디
# scheduleIcalString += "DTSTART;TZID=Asia/Seoul:20161116T190000\n" # 시작 일시
# scheduleIcalString += "DTEND;TZID=Asia/Seoul:20161116T193000\n" # 종료 일시
scheduleIcalString += "DTSTART;TZID=Asia/Seoul:"+start_time+"\n" # 시작 일시
scheduleIcalString += "DTEND;TZID=Asia/Seoul:"+end_time+"\n" # 종료 일시
scheduleIcalString += "SUMMARY:" + calSum + " \n" # 일정 제목
scheduleIcalString += "DESCRIPTION:" + calDes + " \n" # 일정 상세 내용
scheduleIcalString += "LOCATION:" + calLoc + " \n" # 장소
#scheduleIcalString += "RRULE:FREQ=YEARLY;BYDAY=FR;INTERVAL=1;UNTIL=20201231\n" + # 일정 반복시 설정
scheduleIcalString += "ORGANIZER;CN=관리자:mailto:admin@sample.com\n" # 일정 만든 사람
scheduleIcalString += "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=admin:mailto:user1@sample.com\n" # 참석자
scheduleIcalString += "CREATED:20230509T190000Z\n" # 일정 생성시각
scheduleIcalString += "LAST-MODIFIED:20230509T190000Z\n" # 일정 수정시각
scheduleIcalString += "DTSTAMP:20230509T190000Z\n" # 일정 타임스탬프
scheduleIcalString += "END:VEVENT\n"
scheduleIcalString += "END:VCALENDAR"
print(scheduleIcalString)
data = "calendarId=defaultCalendarId&scheduleIcalString=" + scheduleIcalString
request = urllib.request.Request(url, data=data.encode("utf-8"))
print(request)
request.add_header("Authorization", header)
response = urllib.request.urlopen(request)
rescode = response.getcode()
if(rescode==200):
response_body = response.read()
print(response_body.decode('utf-8'))
# {"result":"success","code":200,"returnValue":{"calendarId":"17061888","processType":"create","icalUid":"AAANXhz98LbVaB"}}
else:
print("Error Code:" + rescode)
def register_schedule():
my_res2 = "yes"
if my_res2 == "yes": #---------------------------------(1)
# print("네이버캘린더 플러그인을 설치하였습니다.")
# textToVoice("네이버캘린더 플러그인을 설치하였습니다.")
print("일정등록을 위한 몇가지 질문을 드리겠습니다.")
textToVoice("일정등록을 위한 몇가지 질문을 드리겠습니다.")
print("등록일자를 알려주세요. 예를 들어 '2023년 4월 20일 오후 7시'인 경우 '2023 0420 19' 라고만 말씀해 주세요.")
textToVoice("등록일자를 알려주세요. 예를 들어 '2023년 4월 20일 오후 7시'인 경우 '이공이삼 공사이공 일구' 라고만 말씀해 주세요.")
my_res_date = voiceToText()
print("등록할 일정의 내용을 말씀해 주세요.")
textToVoice("등록할 일정의 내용을 말씀해 주세요.")
my_res_cont = voiceToText() # 생일
print(my_res_date, " 날짜에", my_res_cont, " 내용으로 등록하겠습니다. 내용이 맞으면 yes, 틀리면 no라고 말씀해 주세요.") ##########################
textToVoice(str(my_res_date)+"과"+ str(my_res_cont)+" 으로 등록하겠습니다. 내용이 맞으면 yes, 틀리면 no라고 말씀해 주세요.") ##########################
my_res_yesno = voiceToText()
if my_res_yesno == "yes":
try:
# 일정 등록 예시
# my_res_date = "2 0 2 3 0 4 2 0 1 9"
my_res_date = my_res_date.replace(" ","").strip()
# print(my_res_date)
year = my_res_date[:4]
# print(year)
month = my_res_date[4:6]
day = my_res_date[6:8]
hour = my_res_date[8:10]
time_obj = datetime.strptime(my_res_date, "%Y%m%d%H")
time_obj += timedelta(hours=1)
time_str_formatted = time_obj.strftime("%Y%m%d%H%M%S")
# print(time_str_formatted) # 출력 결과: "01:00"
hour2 = str(time_str_formatted)[8:10]
# print(hour2)
minute = "00"
seconed = "00"
# start_time = datetime(int(year), int(month), int(day), int(hour), int(minute), int(seconed))
# end_time = datetime(int(year), int(month), int(day), int(hour2), int(minute), int(seconed))
start_time = year+month+day+"T"+hour+minute+seconed
print(start_time)
end_time = year+month+day+"T"+hour2+minute+seconed
summary = my_res_cont
location = ' '
description = my_res_cont
create_event(start_time, end_time, summary, location, description)
print("네이버캘린더에 홍길동과 점심약속으로 2023년 4월 20일 오후 7시로 등록되었습니다.") ##########################
textToVoice("네이버캘린더에 홍길동과 점심약속으로 2023년 4월 20일 오후 7시로 등록되었습니다.") ##########################
print("지피티모드를 종료하고 대기모드로 돌아갑니다. 제가 필요하면 대기모드에서 '지피티'라고 불러주세요.")
textToVoice("지피티모드를 종료하고 대기모드로 돌아갑니다. 제가 필요하면 대기모드에서 '지피티'라고 불러주세요.")
# break
except:
print(f'An error occurred')
textToVoice("에러가 발생하였습니다. 지피티 모드를 종료합니다.")
# break
else:
print("내용이 잘못 입력되었습니다. 다시 등록하려면 yes, 여기서 나가시려면 no라고 대답해 주세요.")
textToVoice("내용이 잘못 입력되었습니다. 다시 등록하려면 yes, 여기서 나가시려면 no라고 대답해 주세요.")
my_res_yesno = voiceToText()
if my_res_yesno == "yes":
register_schedule() # -------------------------------- 위의 (1)번으로 돌아감
pass
else:
# break
pass
content = ''
for_break = True
while True:
if for_break == False: # 이중 루프를 빠져나가기 위한 변수 설정임
break
print("챗지피티와 대화하려면 '지피티'라고 불러주시고, 대화를 종료하려면 '굿바이'라고 말씀해 주세요.")
textToVoice("챗지피티와 대화하려면 '지피티'라고 불러주시고, 대화를 종료하려면 '굿바이'라고 말씀해 주세요.")
my_res = voiceToText()
if '굿바이' in my_res:
textToVoice("chatGPT를 종료합니다.")
print("chatGPT를 종료합니다.")
break
if "GPT" in my_res:
print("지피티 모드 입니다.")
textToVoice("지피티 모드 입니다.")
while True:
#현재 시점 이후 30초 경과 알아내기
now = datetime.now()
delta = timedelta(seconds=5)
after_30sec = now + delta
print("네. 주인님. 말씀하세요.")
textToVoice("네. 주인님. 말씀하세요.")
my_res1 = voiceToText() # 일정 등록 좀 해줘 (키워드: 일정)
if '굿바이' in my_res1:
textToVoice("chatGPT를 종료합니다.")
print("chatGPT를 종료합니다.")
for_break = False
break
now = datetime.now()
if my_res1 == " " and (now > after_30sec):
print("5초 동안 아무 움직임이 없어 지피티모드를 종료합니다.(대기(리슨)모드로 돌아갑니다.)")
textToVoice("5초 동안 아무 움직임이 없어 지피티모드를 종료합니다.(대기모드로 돌아 갑니다.)")
time.sleep(1)
break
elif "일정" in my_res1:
print("네이버캘린더에 일정을 추가할 수 있습니다. 일정을 등록하기 위해서는 네이버캘린더 플러그인을 설치해야 합니다. \
설치할까요? yes, no로 답변해 주세요.")
textToVoice("네이버캘린더에 일정을 추가할 수 있습니다. 일정을 등록하기 위해서는 네이버캘린더 플러그인을 설치해야 합니다. \
설치할까요? yes, no로 답변해 주세요.")
my_res2 = voiceToText()
if my_res2 == "yes" or my_res2 == "예스": #---------------------------------(1)
print("네이버캘린더 플러그인을 설치하였습니다.")
textToVoice("네이버캘린더 플러그인을 설치하였습니다.")
print("일정등록을 위한 몇가지 질문을 드리겠습니다.")
textToVoice("일정등록을 위한 몇가지 질문을 드리겠습니다.")
print("등록일자를 알려주세요. 예를 들어 '2023년 4월 20일 오후 7시'인 경우 '2023 0420 19' 라고만 말씀해 주세요.")
textToVoice("등록일자를 알려주세요. 예를 들어 '2023년 4월 20일 오후 7시'인 경우 '이공이삼 공사이공 일구' 라고만 말씀해 주세요.")
my_res_date = voiceToText()
# print(my_res_date, type(my_res_date)) ##########################
# time.sleep(10)
print("등록할 일정의 내용을 말씀해 주세요.")
textToVoice("등록할 일정의 내용을 말씀해 주세요.")
my_res_cont = voiceToText() # 생일
print(my_res_date, "과", my_res_cont, " 으로 등록하겠습니다. 내용이 맞으면 yes, 틀리면 no라고 말씀해 주세요.") ##########################
textToVoice(str(my_res_date)+"과"+ str(my_res_cont)+" 으로 등록하겠습니다. 내용이 맞으면 yes, 틀리면 no라고 말씀해 주세요.") ##########################
my_res_yesno = voiceToText()
if my_res_yesno == "yes" or my_res_yesno == "예스":
try:
# 일정 등록 예시
# my_res_date = "2 0 2 3 0 4 2 0 1 9"
my_res_date = my_res_date.replace(" ","").strip()
# print(my_res_date)
year = my_res_date[:4]
# print(year)
month = my_res_date[4:6]
day = my_res_date[6:8]
hour = my_res_date[8:10]
time_obj = datetime.strptime(my_res_date, "%Y%m%d%H")
time_obj += timedelta(hours=1)
time_str_formatted = time_obj.strftime("%Y%m%d%H%M%S")
# print(time_str_formatted) # 출력 결과: "01:00"
hour2 = str(time_str_formatted)[8:10]
# print(hour2)
minute = "00"
seconed = "00"
# start_time = datetime(int(year), int(month), int(day), int(hour), int(minute), int(seconed))
# end_time = datetime(int(year), int(month), int(day), int(hour2), int(minute), int(seconed))
start_time = year+month+day+"T"+hour+minute+seconed
print(start_time)
end_time = year+month+day+"T"+hour2+minute+seconed
summary = my_res_cont
location = ' '
description = my_res_cont
create_event(start_time, end_time, summary, location, description)
print("네이버캘린더에 "+year+" 년 " + month+" 월 "+ day+" 일 "+ hour+" 에서 "+ hour2 + " 시 사이로 " +summary+" 의 내용으로 등록되었습니다.") ##########################
textToVoice("네이버캘린더에 "+year+" 년 " + month+" 월 "+ day+" 일 "+ hour+" 에서 "+ hour2 + " 시 사이로 " +summary+" 의 내용으로 등록되었습니다.") ##########################
print("지피티모드를 종료하고 대기모드로 돌아갑니다. 제가 필요하면 대기모드에서 '지피티'라고 불러주세요.")
textToVoice("지피티모드를 종료하고 대기모드로 돌아갑니다. 제가 필요하면 대기모드에서 '지피티'라고 불러주세요.")
break
except :
print(f'에러가 발생하였습니다. 등록일자가 잘못된것 같습니다. 지피티 모드를 종료합니다.: ')
textToVoice("에러가 발생하였습니다. 등록일자가 잘못된것 같습니다. 지피티 모드를 종료합니다.")
break
else:
print("내용이 잘못 입력되었습니다. 다시 등록하려면 yes, 여기서 나가시려면 no라고 대답해 주세요.")
textToVoice("내용이 잘못 입력되었습니다. 다시 등록하려면 yes, 여기서 나가시려면 no라고 대답해 주세요.")
my_res_yesno = voiceToText()
if my_res_yesno == "yes":
register_schedule() # -------------------------------- 위의 (1)번으로 돌아감
pass
else:
break
else:
print("네이버캘린더 플러그인을 설치하지 않았습니다.")
textToVoice("네이버캘린더 플러그인을 설치하지 않았습니다.")
print("지피티모드를 종료하고 대기모드로 돌아갑니다.")
textToVoice("지피티모드를 종료하고 대기모드로 돌아갑니다.")
break
else:
prompt = my_res1
try:
messages = [
{'role': 'system', 'content': 'You are a helpful assistant.'},
{'role': 'user', 'content': content},
]
messages.append({'role': 'assistant', 'content': msg })
messages.append({'role': 'user', 'content': prompt })
except:
messages = [
{'role': 'system', 'content': 'You are a helpful assistant.'},
{'role': 'user', 'content': prompt},
]
response = openai.ChatCompletion.create(
model='gpt-3.5-turbo',
messages=messages
)
# print(response)
print("--------------------------------")
print(str(response['choices'][0]['message']['content']).strip())
msg = str(response['choices'][0]['message']['content']).strip()
print("--------------------------------")
textToVoice(msg)
# time.sleep(1)
content = content + msg
else:
print("대기모드 입니다.")
textToVoice("대기모드 입니다.")
time.sleep(1)
O 주요 내용
1. 아래 소스코드에 대해서 간략히 설명합니다.
(대부분 이전 글에서 설명한 내용이며, 코드내 주석을 달았으므로 참고하시기 바랍니다.)
1. 관련 모듈을 임포트 합니다.
2. 안내를 위해 텍스트를 음성으로 변환해주는 함수와 chatGPT에게 음성명령을 내릴때 사용하는 voiceToText()함수를 정의합니다.
3. 네이버 캘린터 API 사용을 위해 일정 추가 함수를 만들어 줍니다.
4.여기서는 음성으로 캘린더에 등록해야 하므로 음성을 입력받고 처리해 주는 반복적인 기능을 처리하기 위해 아래와 같이 함수로 만들었습니다.
5.사용자의 음성명령 입력값이 옳지 않거나, 이상한 경우 지피티 모드를 종료하거나 다시 입력 받도록 합니다.
아래와 같이 다시 입력 받도록 본인(재귀함수) 함수를 다시 호출 하고 있습니다.
7. while 문 내에 while문이 하나 더 있습니다. 따라서 2번째 while문을 빠져 나가기 위해서는 break 가 두번 나와야 하므로 꼼수로 for_break = False가 되는 경우 다시 루프를 빠져 나갈 수 있도록 해주었습니다.(이중루프 탈출)
그리고 첫번재 while문에서는 대기모드와 지피티모드를 선택할 수 있게 하였고,
지피티모드로 진입하면 두번재 while문에서는 chatGPT와 대화하거나, '일정' 키워드가 입력되면 플러그인을 사용할 수 있게 하였습니다.
추가로 지피티모드에서 5초 동안 아무런 말을 하지 않으면 지피티모드를 빠져나가도록 설정해 주었습니다.
8. 위에서 설명드린 지피티 모드에서 '굿바이'라고 말하면 대기모드로 들어가게 되는데, 즉 종료되지 않는데 여기서 for_break = False로 설정하여 if조건(한번 더 break)에 의하여 대기모드에서도 빠져나가 종료되도록 설정하였습니다.
대화내용 속에 '일정' 키워드가 존재하면 구글 캘린더에 일정을 추가할 수 있도록 안내해 줍니다.
일정 추가를 위해 사용자로부터 등록일시와 내용을 확인합니다.
9. 음성으로 입력받은 등록일시를 형식(년,월,일, 시,분,초)에 맞게 가공해 줍니다.
10. 입력값에 에러가 없으면 정상적으로 등록 후 안내 메시지를 들려줍니다.
만일 에러가 발생한 경우 지피티 모드를 종료하거나, 다시 음성입력을 받을 수 있도록 안내해 줍니다.
11. 특정 키워드가 아닌 일반적인 질문에는 chatGPT3.5가 대답을 할 수 있도록 아래와 같이 코딩해 줍니다.
(chatGPT3.5 사용 시 적지만 과금이 되니 주의 바랍니다.)
ㅁ 정리
O 우리가 배운 내용
1. 기본적으로 chatGPT와 대화하면서, 특정 키워드('일정', '에약', '결제' 등)가 나오면 플러그인 기능을 활용할 수 있도록 해주었습니다.
1.1 기본 기능 : chatGPT와 대화
1.2 플러그인 기능 : 대화속에 "일정" 키워드가 나오면 구글캘린더에 일정을 추가할 수 있도록 안내
2. 대기 모드와 지피티(대화) 모드로 구분하였습니다.
>기본적으로 chatGPT가 명령을 기다리고 있다가 특정 단어("지피티")가 나오면 그때부터 대화 및 플러그인 기능을 이용할 수 있게 하였습니다.
2.1 대기(리슨) 모드 : 아무런 행위("챗GPT를 부르는 등의 행위")가 없으면 계속 리슨하고 있는 상태(예: 1초 간격으로 지피티가 자신을 부르는지를 확인하기 위하여 리슨->대화(없음)->리슨->대화(없음) 모드를 반복하는 행위)
2.2 지피티(대화) 모드 : 대기모드에서 '지피티'라고 말하면 지피티모드로 진입하게 되며, 여기서 chatGPT와 대화 및 특정 키워드로 플러그인을 사용할 수 있게 됨
오늘은 여기까지이며, 댓글과 하트는 제가 이글을 지속할 수 있게 해주는 힘이 됩니다.
위의 내용이 유익하셨다면, 댓글과 하트 부탁드립니다.
감사합니다.
※ 추가적인 정보는 아래 유튜브 영상에서 해당 내용을 더욱 자세히 보실 수 있습니다.