Ahmet Firat Gamsiz commited on
Commit
ff5f2a0
1 Parent(s): c9d6947
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. Narrator.jpg +0 -0
  3. Opening.mp4 +3 -0
  4. app.py +153 -0
  5. requirements.txt +8 -0
  6. utils.py +165 -0
.gitattributes CHANGED
@@ -32,3 +32,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
32
  *.zip filter=lfs diff=lfs merge=lfs -text
33
  *.zst filter=lfs diff=lfs merge=lfs -text
34
  *tfevents* filter=lfs diff=lfs merge=lfs -text
35
+ Opening.mp4 filter=lfs diff=lfs merge=lfs -text
Narrator.jpg ADDED
Opening.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:856ea80ee310b8ef402bbdf53c426f6395e3bcf29adee5e0605b0736eea129a8
3
+ size 1100428
app.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import gradio as gr
4
+ import utils
5
+ from utils import ChatCompletion #my customly built helpers to hold commonly used data models
6
+ from random import choice
7
+ from time import sleep
8
+ from uuid import uuid4
9
+ chat = ChatCompletion()
10
+ error_code = 0
11
+
12
+ images = {"Narrator": "Narrator.jpg"}
13
+ dubs = {"Narrator": "Josh"}
14
+ males = ["Adam", "Antoni", "Arnold", "Sam"]
15
+ females = ["Bella", "Domi", "Elli", "Rachel"]
16
+
17
+ im_gen_msg = f"""Stable Diffusion is an AI art generation model similar to DALLE-2.
18
+ Below is a list of prompts that can be used to generate images with Stable Diffusion:
19
+
20
+ - pirate, concept art, deep focus, fantasy, intricate, highly detailed, digital painting, artstation, matte, sharp focus, illustration, art by magali villeneuve, chippy, ryan yee, rk post, clint cearley, daniel ljunggren, zoltan boros, gabor szikszai, howard lyon, steve argyle, winona nelson
21
+ - a fantasy style portrait painting of rachel lane / alison brie hybrid in the style of francois boucher oil painting unreal 5 daz. rpg portrait, extremely detailed artgerm greg rutkowski alphonse mucha greg hildebrandt tim hildebrandt
22
+ - athena, greek goddess, claudia black, art by artgerm and greg rutkowski and magali villeneuve, bronze greek armor, owl crown, d & d, fantasy, intricate, portrait, highly detailed, headshot, digital painting, trending on artstation, concept art, sharp focus, illustration
23
+ - closeup portrait shot of a large strong female biomechanic woman in a scenic scifi environment, intricate, elegant, highly detailed, centered, digital painting, artstation, concept art, smooth, sharp focus, warframe, illustration, thomas kinkade, tomasz alen kopera, peter mohrbacher, donato giancola, leyendecker, boris vallejo
24
+
25
+ Follow the structure of the example prompts."""
26
+
27
+
28
+ start_msg = """Greetings, traveler from far-off worlds. I am Korkut, your humble guide through the boundless expanse of the universe. In this game, we shall embark on a journey like no other, delving deep into the mysteries of a sci-fi realm filled with endless wonders and unfathomable dangers.
29
+ As the Game Master, it is my honor to lead you on this epic quest, where we shall explore uncharted territories, encounter strange creatures and civilizations, and uncover hidden treasures beyond imagination.
30
+ But be warned, for the challenges that lie ahead are not for the faint of heart. Only the bravest and most cunning among us will survive. We shall be tested at every turn, and our fates will be decided by the choices we make.
31
+ So, dear friend, I ask you to take up your sword, your laser gun, or whatever weapon you prefer, and prepare to face the unknown. But first, let us know each other. What is your name, brave adventurer?"""
32
+
33
+
34
+ def chat_handler(hist):
35
+ global error_code
36
+ reply = chat.single_response(hist)
37
+
38
+ if reply == "" or reply == -1:
39
+ raise gr.Error("Chatbot response is empty. Please reload the page. If the issue repeats please contact me.")
40
+ sleep(20) #TODO REMOVE/REDUCE
41
+
42
+ hist.append({"role": "assistant", "content": reply})
43
+ speaker = re.findall(r"\[([A-Za-z0-9\s]+)\]:", reply)
44
+ dialog = re.findall(r"\[[A-Za-z\s0-9]+\]:([^\[]*)", reply)
45
+ results = list(zip(speaker, dialog))
46
+ speaker_set = set(speaker).difference(set(images))
47
+ if(len(speaker_set)>=1):
48
+ hists = []
49
+ for speaker in speaker_set:
50
+ gender = chat.decide_gender(speaker)
51
+ if gender == "male":
52
+ dub = choice(males)
53
+ dubs[speaker] = dub
54
+ else:
55
+ dub = choice(females)
56
+ dubs[speaker] = dub
57
+
58
+ tmp = []
59
+ tmp.append({"role": "assistant", "content": reply})
60
+ tmp.append({"role": "user", "content": im_gen_msg + f" Generate a prompt for {speaker}'s appearance. If you don't have enough information then generate an arbitrary prompt. Keep the prompt shorter than 70 words."})
61
+ hists.append(tmp)
62
+ tmp = []
63
+
64
+ replies = chat.multi_response(hists)
65
+ if -1 in replies:
66
+ raise gr.Error("Image generation failed. Please reload the page. If the issue repeats please contact me.")
67
+ images.update(dict(zip(speaker_set, replies)))
68
+
69
+ for speaker in speaker_set:
70
+ reply = images[speaker]
71
+ img_path = chat.generate_image(reply, speaker)
72
+ images[speaker] = img_path
73
+
74
+ out_clip = []
75
+ text = ""
76
+ for result in results:
77
+ if result[1].strip()=="": continue
78
+ text += "**" + result[0] + ":** " + result[1].strip() + "\n"
79
+ dub = dubs[result[0]]
80
+ audio_name = utils.generate_audio(dub, result[1].strip())
81
+ if audio_name == -1:
82
+ error_code = -1
83
+ raise gr.Error("Audio generation failed.Please contact me.")
84
+ out_clip.append([result[0], audio_name, images[result[0]]])
85
+ out_path = "video" + str(uuid4()) + ".mp4"
86
+ try:
87
+ utils.generate_video(out_clip, out_path)
88
+ except Exception as e:
89
+ error_code = -1
90
+ print("Error:" + str(e))
91
+ raise gr.Error("Video generation failed. Please contact me.")
92
+ return out_path, text, hist
93
+
94
+
95
+ def btn_1(text,history, hist):
96
+ if error_code == -1:
97
+ return {button1: gr.update(interactive=False), button2: gr.update(visible=False),textbox: gr.update(value="System error! Please contant me."), chatbot: gr.update(value=history), state: hist}
98
+
99
+ user_name = utils.get_user_name(chat, text)
100
+
101
+ if user_name == -1:
102
+ return {button1: gr.update(interactive=False), button2: gr.update(visible=False),textbox: gr.update(value="This name is not appropriate! Please restart the game and choose an appropriate name."), chatbot: gr.update(value=history), state: hist}
103
+ if user_name == -2:
104
+ return {button1: gr.update(interactive=False), button2: gr.update(visible=False),textbox: gr.update(value="This name is invalid! Please restart the game and choose a valid name."), chatbot: gr.update(value=history), state: hist}
105
+
106
+ history += [(None,text)]
107
+ system_message = f"""You are a game master. You are going to create a DND game takes place in sci-fi setting. You use a mysterious, exciting and interesting language. The story you are telling must be compelling. Since I am the only one playing with you, you need to refer me as "you" when you are talking about my character's actions. You should use a lot dialogs in your story. Other characters can refer me as {user_name}. Dialogs should be formatted like
108
+ "[Person1 Name]: Speech
109
+ [Person2 Name]: Speech"
110
+ Also when there is no dialog and you are narrating story the you should output like "[Narrator]: Story".
111
+ Overall an example output should look like the following
112
+ "[Narrator]: You wake up in a deserted planet.
113
+ [Stranger]: Wake up! Who are you?
114
+ [Narrator]: What are you going to do?"
115
+ You mustn't generate my dialogs. Instead at the end of your message you should ask what I will do or say and your next message you should shape the story according to my response."""
116
+ hist = [{"role": "system", "content": system_message}, {"role": "user", "content": "Start"}]
117
+ try:
118
+ out_path, text, hist = chat_handler(hist)
119
+ except gr.Error as e:
120
+ return {button1: gr.update(interactive=False), button2: gr.update(visible=False),textbox: gr.update(value=str(e)), chatbot: gr.update(value=history), state: hist}
121
+
122
+ history += [((out_path,None),None),(text,None)]
123
+ return {button1: gr.update(visible=False), button2: gr.update(visible=True), textbox: gr.update(label="What will you do?",value=""), chatbot: gr.update(value=history), state: hist}
124
+
125
+ def btn_2(text, history, hist):
126
+ if error_code == -1:
127
+ return {button2: gr.update(interactive=False), textbox: gr.update(value="System error! Please contant me."), chatbot: gr.update(value=history), state:hist}
128
+
129
+ if not chat.safety_check(text):
130
+ return {button2: gr.update(interactive=False), textbox: gr.update(value="This message is inappropriate! Please restart the game and use an appropriate language."), chatbot: gr.update(value=history), state:hist}
131
+
132
+ hist.append({"role": "user", "content": text})
133
+
134
+ try:
135
+ out_path, text, hist = chat_handler(hist)
136
+ except gr.Error as e:
137
+ return {button2: gr.update(interactive=False), textbox: gr.update(value=str(e)), chatbot: gr.update(value=history), state: hist}
138
+
139
+ history += [((out_path,None),None),(text,None)]
140
+ return {textbox: gr.update(value=""), chatbot: gr.update(value=history), state: hist}
141
+
142
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
143
+ state = gr.State([])
144
+ gr.Markdown("# KORKUT: A Spacetime Odyssey")
145
+ history = [(("./Opening.mp4",None),None),(start_msg, None)]
146
+ chatbot = gr.Chatbot(history, elem_id="chatbot", starts_with="bot").style(height=600)
147
+ textbox = gr.Textbox(lines=3, label="What is your name, brave adventurer?")
148
+ button1 = gr.Button(value="Submit")
149
+ button2 = gr.Button(value="Submit", visible=False)
150
+ button2.click(btn_2, [textbox, chatbot, state],[textbox, chatbot, state, button2])
151
+ button1.click(btn_1 ,[textbox, chatbot, state],[button1, button2, textbox, chatbot, state])
152
+ demo.queue().launch(share=True)
153
+
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ openai
2
+ dotenv
3
+ httpx
4
+ asyncio
5
+ aiometer
6
+ elevenlabs
7
+ moviepy
8
+ uuid
utils.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pip install python-dotenv first install this package to be able to call custom env variables from .env
2
+ # don't forget to call the load_dotenv() function to initialize the getenv() method of os module
3
+
4
+ import openai
5
+ import os
6
+ from dotenv import load_dotenv
7
+ import httpx
8
+ import asyncio
9
+ import aiometer
10
+ from functools import partial
11
+ from elevenlabs import generate, save, set_api_key
12
+ from base64 import b64decode
13
+ import re
14
+ from moviepy.editor import ImageClip, AudioFileClip, CompositeVideoClip, concatenate_videoclips, concatenate_audioclips, TextClip, CompositeAudioClip
15
+ from random import choice
16
+ from uuid import uuid4
17
+
18
+ TIMEOUT = 300
19
+ RATE_LIMIT = 0.05 # x requests per minute
20
+
21
+ # Load environment variables from the .env file
22
+ load_dotenv()
23
+
24
+ set_api_key(os.getenv("elevenlabs_api_key"))
25
+ openai.api_key = os.getenv("openai_api_key")
26
+
27
+ class ChatCompletion:
28
+ def __init__(self, temperature=0.8,):
29
+ self.model = "gpt-3.5-turbo",
30
+ self.temperature = temperature
31
+ self.total_tokens = 0
32
+
33
+
34
+ def single_response(self, hist):
35
+ response = openai.ChatCompletion.create(
36
+ model= "gpt-3.5-turbo",
37
+ messages=hist)
38
+ try:
39
+ print("Tokens used: " + str(response["usage"]["total_tokens"]))
40
+ self.total_tokens += response["usage"]["total_tokens"]
41
+ except:
42
+ print("Error: " + str(response["error"]["message"]))
43
+ return -1
44
+ return response["choices"][0]["message"]["content"]
45
+
46
+ async def _async_response(self,payload):
47
+ async with httpx.AsyncClient() as client:
48
+ return await client.post(
49
+ url="https://api.openai.com/v1/chat/completions",
50
+ json=payload,
51
+ headers={"content_type": "application/json", "Authorization": f"Bearer {openai.api_key}"},
52
+ timeout=TIMEOUT,
53
+ )
54
+
55
+ async def _request(self, hist):
56
+ response = await self._async_response({
57
+ "model": "gpt-3.5-turbo",
58
+ "messages": hist,
59
+ })
60
+ try:
61
+ print("Tokens used: " + str(response.json()["usage"]["total_tokens"]))
62
+ self.total_tokens += response.json()["usage"]["total_tokens"]
63
+ reply = response.json()["choices"][0]["message"]["content"]
64
+ return reply
65
+ except:
66
+ print("Error: " + str(response.json()["error"]["message"]))
67
+ return -1
68
+
69
+ async def _multi_response(self, hists):
70
+ return await aiometer.run_all(
71
+ [partial(self._request, hist) for hist in hists],
72
+ max_per_second = RATE_LIMIT
73
+ )
74
+
75
+ def multi_response(self, hists):
76
+ return asyncio.run(self._multi_response(hists))
77
+
78
+ def safety_check(self, message):
79
+ if len(message) > 2000:
80
+ return False
81
+ else:
82
+ return True
83
+ # else:
84
+ # text = f"""Just answer with "yes" or "no". Is the following message appropriate in DND game context?
85
+
86
+ # {message}"""
87
+ # hist = [{"role": "user", "content": text}]
88
+ # response = self.single_response(hist).lower()
89
+ # if(response=="no." or response=="no"):
90
+ # return False
91
+ # else:
92
+ # return True
93
+
94
+ def decide_gender(self, message):
95
+ return choice(["male","female"])
96
+ # text = f"""Only reply with "male" or "female". Select a gender for {message}. If unknown or both just arbitrarily select one gender."""
97
+ # hist = [{"role": "user", "content": text}]
98
+ # response = self.single_response(hist).lower()
99
+ # match = re.search(r"female", response)
100
+ # if match:
101
+ # return "female"
102
+ # return "male"
103
+
104
+ def generate_image(self, desc, speaker):
105
+ response = openai.Image.create(
106
+ prompt=desc,
107
+ n=1,
108
+ size="256x256",
109
+ response_format = "b64_json"
110
+ )
111
+ image_b64 = response["data"][0]["b64_json"]
112
+ with open(f"{speaker}.png","wb") as img:
113
+ img.write(b64decode(image_b64))
114
+ return f"{speaker}.png"
115
+
116
+ def _str_check(message):
117
+ unwanted = re.findall(r"[^A-Za-z\s0-9]", message)
118
+ if len(unwanted) > 0:
119
+ return False
120
+ return True
121
+
122
+ def generate_audio(speaker, message):
123
+ try:
124
+ audio = generate(
125
+ text=message,
126
+ voice=speaker,
127
+ model="eleven_monolingual_v1"
128
+ )
129
+ except Exception as e:
130
+ print("Error:" + str(e))
131
+ return -1
132
+ file_name = speaker + str(uuid4()) + ".wav"
133
+ save(audio, file_name)
134
+ return file_name
135
+
136
+
137
+ def get_user_name(chat, user_name):
138
+ if not chat.safety_check(f"My name is {user_name}"):
139
+ print("Inappropriate name.")
140
+ return -1
141
+ if not _str_check(user_name):
142
+ print("Invalid name.")
143
+ return -2
144
+ return user_name
145
+
146
+ def generate_video(triples,output_path):
147
+ video_clips = []
148
+ audio_clips = []
149
+ for _, audio_path, image_path in triples:
150
+ image = ImageClip(image_path)
151
+ audio = AudioFileClip(audio_path)
152
+ duration = audio.duration
153
+ image = image.set_duration(duration)
154
+ #txt_clip = TextClip(text, fontsize=24, color='white', stroke_width=3).set_pos(('left', 'top'))
155
+ video = CompositeVideoClip([image])#, txt_clip])
156
+ video = video.set_audio(audio)
157
+ video_clips.append(video)
158
+ audio_clips.append(audio)
159
+
160
+ final_video = concatenate_videoclips(video_clips, method="compose")
161
+ final_audio = concatenate_audioclips(audio_clips)
162
+ final_video = final_video.set_audio(final_audio)
163
+ final_video.write_videofile(output_path, fps=24, verbose=False, logger=None)
164
+ for _, audio_path, _ in triples:
165
+ os.remove(audio_path)