Spaces:
Running
on
A10G
Running
on
A10G
GitLab CI
commited on
Commit
Β·
6831f1f
1
Parent(s):
726ec90
Update game build from GitLab CI
Browse files- server/ActionProcessor.py +56 -0
- server/AudioRecorder.py +3 -54
- server/AudioTranscriber.py +2 -2
- server/__main__.py +27 -12
- server/static/{index.apple-touch-icon.png β godot/index.apple-touch-icon.png} +0 -0
- server/static/{index.audio.worklet.js β godot/index.audio.worklet.js} +0 -0
- server/static/godot/index.html +248 -0
- server/static/{index.icon.png β godot/index.icon.png} +0 -0
- server/static/{index.js β godot/index.js} +0 -0
- server/static/godot/index.pck +0 -0
- server/static/{index.png β godot/index.png} +0 -0
- server/static/{index.wasm β godot/index.wasm} +0 -0
- server/static/{index.worker.js β godot/index.worker.js} +0 -0
- server/static/index.html +70 -245
- server/static/index.pck +0 -3
server/ActionProcessor.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from threading import Thread
|
2 |
+
from queue import Queue
|
3 |
+
from typing import Dict, Any
|
4 |
+
import json
|
5 |
+
import re
|
6 |
+
|
7 |
+
|
8 |
+
class ActionProcessor(Thread):
|
9 |
+
def __init__(self, text_queue: "Queue[str]", action_queue: "Queue[str]"):
|
10 |
+
super().__init__()
|
11 |
+
self.text_queue = text_queue
|
12 |
+
self.action_queue = action_queue
|
13 |
+
self.daemon = True # Thread will exit when main program exits
|
14 |
+
|
15 |
+
def process_text(self, text: str) -> Dict[str, Any] | None:
|
16 |
+
"""Convert text into an action if a complete command is detected."""
|
17 |
+
# Define command patterns
|
18 |
+
command_patterns = {
|
19 |
+
r"(?i)\b(stop|now)\b": "stop",
|
20 |
+
r"(?i)\b(come back|get back)\b": "return",
|
21 |
+
r"(?i)\b(easy)\b": "slow",
|
22 |
+
r"(?i)\b(stop drinking)\b": "pause_liquid",
|
23 |
+
r"(?i)\b(stop eating)\b": "pause_solid",
|
24 |
+
r"(?i)\b(look at me)\b": "look_at_me",
|
25 |
+
r"(?i)\b(look away)\b": "look_away",
|
26 |
+
r"(?i)\b(don't do that)\b": "stop",
|
27 |
+
}
|
28 |
+
|
29 |
+
# Check each pattern
|
30 |
+
for pattern, action_type in command_patterns.items():
|
31 |
+
match = re.search(pattern, text.lower())
|
32 |
+
if match:
|
33 |
+
return {"type": action_type}
|
34 |
+
|
35 |
+
return None
|
36 |
+
|
37 |
+
def run(self) -> None:
|
38 |
+
"""Main processing loop."""
|
39 |
+
while True:
|
40 |
+
try:
|
41 |
+
# Get text from queue, blocks until text is available
|
42 |
+
text = self.text_queue.get()
|
43 |
+
|
44 |
+
# Process the text into an action
|
45 |
+
action = self.process_text(text)
|
46 |
+
|
47 |
+
# If we got a valid action, add it to the action queue
|
48 |
+
if action:
|
49 |
+
self.action_queue.put(json.dumps(action))
|
50 |
+
|
51 |
+
# Mark the text as processed
|
52 |
+
self.text_queue.task_done()
|
53 |
+
|
54 |
+
except Exception as e:
|
55 |
+
print(f"Error processing text: {str(e)}")
|
56 |
+
continue
|
server/AudioRecorder.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1 |
-
from multiprocessing import Process,
|
2 |
-
from typing import Optional, List, Deque
|
3 |
from collections import deque
|
4 |
import pyaudio
|
5 |
-
import time
|
6 |
import io
|
7 |
import wave
|
8 |
|
@@ -24,7 +23,7 @@ class AudioRecorder:
|
|
24 |
self.input_device_index: Optional[int] = input_device_index
|
25 |
|
26 |
@staticmethod
|
27 |
-
def list_microphones() -> List[
|
28 |
"""List all available input devices with their properties"""
|
29 |
p = pyaudio.PyAudio()
|
30 |
devices = []
|
@@ -92,53 +91,3 @@ class AudioRecorder:
|
|
92 |
if self.recording_process:
|
93 |
self.recording_process.terminate()
|
94 |
self.recording_process = None
|
95 |
-
|
96 |
-
|
97 |
-
def transcription_process(input_pipe: connection.Connection) -> None:
|
98 |
-
from server.tts import AudioTranscriber # Import here to avoid thread locks
|
99 |
-
|
100 |
-
transcriber = AudioTranscriber()
|
101 |
-
|
102 |
-
while True:
|
103 |
-
try:
|
104 |
-
audio_data = input_pipe.recv()
|
105 |
-
audio_buffer = io.BytesIO(audio_data)
|
106 |
-
audio_buffer.seek(0)
|
107 |
-
|
108 |
-
segments, info = transcriber.transcribe_audio_bytes(
|
109 |
-
audio_buffer, language="fr"
|
110 |
-
)
|
111 |
-
transcriber.print_segments(segments)
|
112 |
-
except Exception as e:
|
113 |
-
print(f"Transcription error: {e}")
|
114 |
-
|
115 |
-
|
116 |
-
if __name__ == "__main__":
|
117 |
-
# List available microphones
|
118 |
-
microphones = AudioRecorder.list_microphones()
|
119 |
-
print("\nAvailable microphones:")
|
120 |
-
for i, device in enumerate(microphones):
|
121 |
-
print(f"{i}: {device['name']}")
|
122 |
-
|
123 |
-
# Ask for microphone selection
|
124 |
-
selected_index = int(input("\nSelect microphone index: "))
|
125 |
-
device_index = microphones[selected_index]["index"]
|
126 |
-
|
127 |
-
# Create pipe for communication between processes
|
128 |
-
recorder_conn, transcriber_conn = Pipe()
|
129 |
-
|
130 |
-
# Create and start transcription process
|
131 |
-
transcription_proc = Process(target=transcription_process, args=(transcriber_conn,))
|
132 |
-
transcription_proc.start()
|
133 |
-
|
134 |
-
# Create and start recorder with selected device
|
135 |
-
recorder = AudioRecorder(recorder_conn, input_device_index=device_index)
|
136 |
-
print("DΓ©but de l'enregistrement... Appuyez sur Ctrl+C pour arrΓͺter")
|
137 |
-
try:
|
138 |
-
recorder.start_recording()
|
139 |
-
while True:
|
140 |
-
time.sleep(1)
|
141 |
-
except KeyboardInterrupt:
|
142 |
-
recorder.stop_recording()
|
143 |
-
transcription_proc.terminate()
|
144 |
-
print("\nEnregistrement arrΓͺtΓ©")
|
|
|
1 |
+
from multiprocessing import Process, connection
|
2 |
+
from typing import Any, Dict, Optional, List, Deque
|
3 |
from collections import deque
|
4 |
import pyaudio
|
|
|
5 |
import io
|
6 |
import wave
|
7 |
|
|
|
23 |
self.input_device_index: Optional[int] = input_device_index
|
24 |
|
25 |
@staticmethod
|
26 |
+
def list_microphones() -> List[Dict[str, Any]]:
|
27 |
"""List all available input devices with their properties"""
|
28 |
p = pyaudio.PyAudio()
|
29 |
devices = []
|
|
|
91 |
if self.recording_process:
|
92 |
self.recording_process.terminate()
|
93 |
self.recording_process = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server/AudioTranscriber.py
CHANGED
@@ -6,10 +6,10 @@ from faster_whisper import WhisperModel
|
|
6 |
|
7 |
|
8 |
class AudioTranscriber(threading.Thread):
|
9 |
-
def __init__(self, audio_queue: Queue[io.BytesIO],
|
10 |
super().__init__()
|
11 |
self.audio_queue = audio_queue
|
12 |
-
self.action_queue =
|
13 |
self.daemon = True # Thread will exit when main program exits
|
14 |
|
15 |
self.transcriber = WhisperModel(
|
|
|
6 |
|
7 |
|
8 |
class AudioTranscriber(threading.Thread):
|
9 |
+
def __init__(self, audio_queue: "Queue[io.BytesIO]", text_queue: "Queue[str]"):
|
10 |
super().__init__()
|
11 |
self.audio_queue = audio_queue
|
12 |
+
self.action_queue = text_queue
|
13 |
self.daemon = True # Thread will exit when main program exits
|
14 |
|
15 |
self.transcriber = WhisperModel(
|
server/__main__.py
CHANGED
@@ -1,22 +1,22 @@
|
|
1 |
-
from ast import Tuple
|
2 |
import io
|
|
|
3 |
from flask import Flask, send_from_directory, jsonify, request, abort
|
4 |
import os
|
5 |
import gunicorn.app.base
|
6 |
from flask_cors import CORS
|
7 |
from multiprocessing import Queue
|
8 |
import base64
|
9 |
-
from typing import Any, Optional, List, Dict
|
10 |
from queue import Queue
|
11 |
|
12 |
from server.AudioTranscriber import AudioTranscriber
|
|
|
13 |
|
14 |
# Use a directory in the user's home folder for static files
|
15 |
-
STATIC_DIR = "/app/server/static"
|
16 |
|
17 |
-
# Add a global queue for audio processing
|
18 |
audio_queue: "Queue[io.BytesIO]" = Queue()
|
19 |
-
|
20 |
action_queue: "Queue[str]" = Queue()
|
21 |
|
22 |
app = Flask(__name__, static_folder=STATIC_DIR)
|
@@ -78,20 +78,31 @@ def get_data():
|
|
78 |
|
79 |
@app.route("/api/process", methods=["POST"])
|
80 |
def process_data():
|
|
|
81 |
try:
|
82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
# Validate the incoming data
|
85 |
-
if not
|
86 |
return (
|
87 |
jsonify({"error": "Missing audio_chunk in request", "status": "error"}),
|
88 |
400,
|
89 |
)
|
90 |
|
91 |
# Decode the base64 audio chunk
|
92 |
-
audio_chunk: Optional[bytes] = None
|
93 |
try:
|
94 |
-
audio_chunk = base64.b64decode(
|
95 |
except Exception as e:
|
96 |
return (
|
97 |
jsonify(
|
@@ -130,10 +141,10 @@ def get_actions() -> Tuple[Dict[str, Any], int]:
|
|
130 |
while not action_queue.empty():
|
131 |
try:
|
132 |
actions.append(action_queue.get_nowait())
|
133 |
-
except:
|
134 |
break
|
135 |
|
136 |
-
return jsonify({"actions": actions, "status": "success"}), 200
|
137 |
|
138 |
|
139 |
@app.route("/<path:path>")
|
@@ -167,9 +178,13 @@ if __name__ == "__main__":
|
|
167 |
os.makedirs(app.static_folder, exist_ok=True)
|
168 |
|
169 |
# Start the audio transcriber thread
|
170 |
-
transcriber = AudioTranscriber(audio_queue,
|
171 |
transcriber.start()
|
172 |
|
|
|
|
|
|
|
|
|
173 |
options: Any = {
|
174 |
"bind": "0.0.0.0:7860",
|
175 |
"workers": 3,
|
|
|
|
|
1 |
import io
|
2 |
+
import json
|
3 |
from flask import Flask, send_from_directory, jsonify, request, abort
|
4 |
import os
|
5 |
import gunicorn.app.base
|
6 |
from flask_cors import CORS
|
7 |
from multiprocessing import Queue
|
8 |
import base64
|
9 |
+
from typing import Any, Optional, List, Dict, Tuple
|
10 |
from queue import Queue
|
11 |
|
12 |
from server.AudioTranscriber import AudioTranscriber
|
13 |
+
from server.ActionProcessor import ActionProcessor
|
14 |
|
15 |
# Use a directory in the user's home folder for static files
|
16 |
+
STATIC_DIR = "/app/server/static" if os.getenv("DEBUG") != "true" else "./server"
|
17 |
|
|
|
18 |
audio_queue: "Queue[io.BytesIO]" = Queue()
|
19 |
+
text_queue: "Queue[str]" = Queue()
|
20 |
action_queue: "Queue[str]" = Queue()
|
21 |
|
22 |
app = Flask(__name__, static_folder=STATIC_DIR)
|
|
|
78 |
|
79 |
@app.route("/api/process", methods=["POST"])
|
80 |
def process_data():
|
81 |
+
print("Processing data")
|
82 |
try:
|
83 |
+
# Check content type
|
84 |
+
content_type = request.headers.get("Content-Type", "")
|
85 |
+
|
86 |
+
# Handle different content types
|
87 |
+
if "application/json" in content_type:
|
88 |
+
data = request.get_json()
|
89 |
+
audio_base64 = data.get("audio_chunk")
|
90 |
+
elif "multipart/form-data" in content_type:
|
91 |
+
audio_base64 = request.form.get("audio_chunk")
|
92 |
+
else:
|
93 |
+
# Try to get raw data
|
94 |
+
audio_base64 = request.get_data().decode("utf-8")
|
95 |
|
96 |
# Validate the incoming data
|
97 |
+
if not audio_base64:
|
98 |
return (
|
99 |
jsonify({"error": "Missing audio_chunk in request", "status": "error"}),
|
100 |
400,
|
101 |
)
|
102 |
|
103 |
# Decode the base64 audio chunk
|
|
|
104 |
try:
|
105 |
+
audio_chunk = base64.b64decode(audio_base64)
|
106 |
except Exception as e:
|
107 |
return (
|
108 |
jsonify(
|
|
|
141 |
while not action_queue.empty():
|
142 |
try:
|
143 |
actions.append(action_queue.get_nowait())
|
144 |
+
except Exception:
|
145 |
break
|
146 |
|
147 |
+
return jsonify({"actions": json.dumps(actions), "status": "success"}), 200
|
148 |
|
149 |
|
150 |
@app.route("/<path:path>")
|
|
|
178 |
os.makedirs(app.static_folder, exist_ok=True)
|
179 |
|
180 |
# Start the audio transcriber thread
|
181 |
+
transcriber = AudioTranscriber(audio_queue, text_queue)
|
182 |
transcriber.start()
|
183 |
|
184 |
+
# Start the action processor thread
|
185 |
+
action_processor = ActionProcessor(text_queue, action_queue)
|
186 |
+
action_processor.start()
|
187 |
+
|
188 |
options: Any = {
|
189 |
"bind": "0.0.0.0:7860",
|
190 |
"workers": 3,
|
server/static/{index.apple-touch-icon.png β godot/index.apple-touch-icon.png}
RENAMED
File without changes
|
server/static/{index.audio.worklet.js β godot/index.audio.worklet.js}
RENAMED
File without changes
|
server/static/godot/index.html
ADDED
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8">
|
5 |
+
<meta name="viewport" content="width=device-width, user-scalable=no">
|
6 |
+
<title>Magame</title>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
touch-action: none;
|
10 |
+
margin: 0;
|
11 |
+
border: 0 none;
|
12 |
+
padding: 0;
|
13 |
+
text-align: center;
|
14 |
+
background-color: black;
|
15 |
+
}
|
16 |
+
|
17 |
+
#canvas {
|
18 |
+
display: block;
|
19 |
+
margin: 0;
|
20 |
+
color: white;
|
21 |
+
}
|
22 |
+
|
23 |
+
#canvas:focus {
|
24 |
+
outline: none;
|
25 |
+
}
|
26 |
+
|
27 |
+
.godot {
|
28 |
+
font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif;
|
29 |
+
color: #e0e0e0;
|
30 |
+
background-color: #3b3943;
|
31 |
+
background-image: linear-gradient(to bottom, #403e48, #35333c);
|
32 |
+
border: 1px solid #45434e;
|
33 |
+
box-shadow: 0 0 1px 1px #2f2d35;
|
34 |
+
}
|
35 |
+
|
36 |
+
/* Status display */
|
37 |
+
|
38 |
+
#status {
|
39 |
+
position: absolute;
|
40 |
+
left: 0;
|
41 |
+
top: 0;
|
42 |
+
right: 0;
|
43 |
+
bottom: 0;
|
44 |
+
display: flex;
|
45 |
+
justify-content: center;
|
46 |
+
align-items: center;
|
47 |
+
/* don't consume click events - make children visible explicitly */
|
48 |
+
visibility: hidden;
|
49 |
+
}
|
50 |
+
|
51 |
+
#status-progress {
|
52 |
+
width: 366px;
|
53 |
+
height: 7px;
|
54 |
+
background-color: #38363A;
|
55 |
+
border: 1px solid #444246;
|
56 |
+
padding: 1px;
|
57 |
+
box-shadow: 0 0 2px 1px #1B1C22;
|
58 |
+
border-radius: 2px;
|
59 |
+
visibility: visible;
|
60 |
+
}
|
61 |
+
|
62 |
+
@media only screen and (orientation:portrait) {
|
63 |
+
#status-progress {
|
64 |
+
width: 61.8%;
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
#status-progress-inner {
|
69 |
+
height: 100%;
|
70 |
+
width: 0;
|
71 |
+
box-sizing: border-box;
|
72 |
+
transition: width 0.5s linear;
|
73 |
+
background-color: #202020;
|
74 |
+
border: 1px solid #222223;
|
75 |
+
box-shadow: 0 0 1px 1px #27282E;
|
76 |
+
border-radius: 3px;
|
77 |
+
}
|
78 |
+
|
79 |
+
#status-indeterminate {
|
80 |
+
height: 42px;
|
81 |
+
visibility: visible;
|
82 |
+
position: relative;
|
83 |
+
}
|
84 |
+
|
85 |
+
#status-indeterminate > div {
|
86 |
+
width: 4.5px;
|
87 |
+
height: 0;
|
88 |
+
border-style: solid;
|
89 |
+
border-width: 9px 3px 0 3px;
|
90 |
+
border-color: #2b2b2b transparent transparent transparent;
|
91 |
+
transform-origin: center 21px;
|
92 |
+
position: absolute;
|
93 |
+
}
|
94 |
+
|
95 |
+
#status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); }
|
96 |
+
#status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); }
|
97 |
+
#status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); }
|
98 |
+
#status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); }
|
99 |
+
#status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); }
|
100 |
+
#status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); }
|
101 |
+
#status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); }
|
102 |
+
#status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); }
|
103 |
+
|
104 |
+
#status-notice {
|
105 |
+
margin: 0 100px;
|
106 |
+
line-height: 1.3;
|
107 |
+
visibility: visible;
|
108 |
+
padding: 4px 6px;
|
109 |
+
visibility: visible;
|
110 |
+
}
|
111 |
+
</style>
|
112 |
+
<link id='-gd-engine-icon' rel='icon' type='image/png' href='index.icon.png' />
|
113 |
+
<link rel='apple-touch-icon' href='index.apple-touch-icon.png'/>
|
114 |
+
|
115 |
+
</head>
|
116 |
+
<body>
|
117 |
+
<canvas id="canvas">
|
118 |
+
HTML5 canvas appears to be unsupported in the current browser.<br >
|
119 |
+
Please try updating or use a different browser.
|
120 |
+
</canvas>
|
121 |
+
<div id="status">
|
122 |
+
<div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();">
|
123 |
+
<div id ="status-progress-inner"></div>
|
124 |
+
</div>
|
125 |
+
<div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();">
|
126 |
+
<div></div>
|
127 |
+
<div></div>
|
128 |
+
<div></div>
|
129 |
+
<div></div>
|
130 |
+
<div></div>
|
131 |
+
<div></div>
|
132 |
+
<div></div>
|
133 |
+
<div></div>
|
134 |
+
</div>
|
135 |
+
<div id="status-notice" class="godot" style="display: none;"></div>
|
136 |
+
</div>
|
137 |
+
|
138 |
+
<script src="index.js"></script>
|
139 |
+
<script>
|
140 |
+
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":5424,"index.wasm":35410474},"focusCanvas":true,"gdextensionLibs":[]};
|
141 |
+
const engine = new Engine(GODOT_CONFIG);
|
142 |
+
|
143 |
+
(function () {
|
144 |
+
const INDETERMINATE_STATUS_STEP_MS = 100;
|
145 |
+
const statusProgress = document.getElementById('status-progress');
|
146 |
+
const statusProgressInner = document.getElementById('status-progress-inner');
|
147 |
+
const statusIndeterminate = document.getElementById('status-indeterminate');
|
148 |
+
const statusNotice = document.getElementById('status-notice');
|
149 |
+
|
150 |
+
let initializing = true;
|
151 |
+
let statusMode = 'hidden';
|
152 |
+
|
153 |
+
let animationCallbacks = [];
|
154 |
+
function animate(time) {
|
155 |
+
animationCallbacks.forEach((callback) => callback(time));
|
156 |
+
requestAnimationFrame(animate);
|
157 |
+
}
|
158 |
+
requestAnimationFrame(animate);
|
159 |
+
|
160 |
+
function animateStatusIndeterminate(ms) {
|
161 |
+
const i = Math.floor((ms / INDETERMINATE_STATUS_STEP_MS) % 8);
|
162 |
+
if (statusIndeterminate.children[i].style.borderTopColor === '') {
|
163 |
+
Array.prototype.slice.call(statusIndeterminate.children).forEach((child) => {
|
164 |
+
child.style.borderTopColor = '';
|
165 |
+
});
|
166 |
+
statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
function setStatusMode(mode) {
|
171 |
+
if (statusMode === mode || !initializing) {
|
172 |
+
return;
|
173 |
+
}
|
174 |
+
[statusProgress, statusIndeterminate, statusNotice].forEach((elem) => {
|
175 |
+
elem.style.display = 'none';
|
176 |
+
});
|
177 |
+
animationCallbacks = animationCallbacks.filter(function (value) {
|
178 |
+
return (value !== animateStatusIndeterminate);
|
179 |
+
});
|
180 |
+
switch (mode) {
|
181 |
+
case 'progress':
|
182 |
+
statusProgress.style.display = 'block';
|
183 |
+
break;
|
184 |
+
case 'indeterminate':
|
185 |
+
statusIndeterminate.style.display = 'block';
|
186 |
+
animationCallbacks.push(animateStatusIndeterminate);
|
187 |
+
break;
|
188 |
+
case 'notice':
|
189 |
+
statusNotice.style.display = 'block';
|
190 |
+
break;
|
191 |
+
case 'hidden':
|
192 |
+
break;
|
193 |
+
default:
|
194 |
+
throw new Error('Invalid status mode');
|
195 |
+
}
|
196 |
+
statusMode = mode;
|
197 |
+
}
|
198 |
+
|
199 |
+
function setStatusNotice(text) {
|
200 |
+
while (statusNotice.lastChild) {
|
201 |
+
statusNotice.removeChild(statusNotice.lastChild);
|
202 |
+
}
|
203 |
+
const lines = text.split('\n');
|
204 |
+
lines.forEach((line) => {
|
205 |
+
statusNotice.appendChild(document.createTextNode(line));
|
206 |
+
statusNotice.appendChild(document.createElement('br'));
|
207 |
+
});
|
208 |
+
}
|
209 |
+
|
210 |
+
function displayFailureNotice(err) {
|
211 |
+
const msg = err.message || err;
|
212 |
+
console.error(msg);
|
213 |
+
setStatusNotice(msg);
|
214 |
+
setStatusMode('notice');
|
215 |
+
initializing = false;
|
216 |
+
}
|
217 |
+
|
218 |
+
const missing = Engine.getMissingFeatures();
|
219 |
+
if (missing.length !== 0) {
|
220 |
+
const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n';
|
221 |
+
displayFailureNotice(missingMsg + missing.join('\n'));
|
222 |
+
} else {
|
223 |
+
setStatusMode('indeterminate');
|
224 |
+
engine.startGame({
|
225 |
+
'onProgress': function (current, total) {
|
226 |
+
if (total > 0) {
|
227 |
+
statusProgressInner.style.width = `${(current / total) * 100}%`;
|
228 |
+
setStatusMode('progress');
|
229 |
+
if (current === total) {
|
230 |
+
// wait for progress bar animation
|
231 |
+
setTimeout(() => {
|
232 |
+
setStatusMode('indeterminate');
|
233 |
+
}, 500);
|
234 |
+
}
|
235 |
+
} else {
|
236 |
+
setStatusMode('indeterminate');
|
237 |
+
}
|
238 |
+
},
|
239 |
+
}).then(() => {
|
240 |
+
setStatusMode('hidden');
|
241 |
+
initializing = false;
|
242 |
+
}, displayFailureNotice);
|
243 |
+
}
|
244 |
+
}());
|
245 |
+
</script>
|
246 |
+
</body>
|
247 |
+
</html>
|
248 |
+
|
server/static/{index.icon.png β godot/index.icon.png}
RENAMED
File without changes
|
server/static/{index.js β godot/index.js}
RENAMED
File without changes
|
server/static/godot/index.pck
ADDED
Binary file (5.42 kB). View file
|
|
server/static/{index.png β godot/index.png}
RENAMED
File without changes
|
server/static/{index.wasm β godot/index.wasm}
RENAMED
File without changes
|
server/static/{index.worker.js β godot/index.worker.js}
RENAMED
File without changes
|
server/static/index.html
CHANGED
@@ -1,248 +1,73 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
<html lang="en">
|
3 |
-
<head>
|
4 |
-
<meta charset="utf-8">
|
5 |
-
<meta name="viewport" content="width=device-width, user-scalable=no">
|
6 |
-
<title>Magame</title>
|
7 |
-
<style>
|
8 |
-
body {
|
9 |
-
touch-action: none;
|
10 |
-
margin: 0;
|
11 |
-
border: 0 none;
|
12 |
-
padding: 0;
|
13 |
-
text-align: center;
|
14 |
-
background-color: black;
|
15 |
-
}
|
16 |
-
|
17 |
-
#canvas {
|
18 |
-
display: block;
|
19 |
-
margin: 0;
|
20 |
-
color: white;
|
21 |
-
}
|
22 |
-
|
23 |
-
#canvas:focus {
|
24 |
-
outline: none;
|
25 |
-
}
|
26 |
-
|
27 |
-
.godot {
|
28 |
-
font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif;
|
29 |
-
color: #e0e0e0;
|
30 |
-
background-color: #3b3943;
|
31 |
-
background-image: linear-gradient(to bottom, #403e48, #35333c);
|
32 |
-
border: 1px solid #45434e;
|
33 |
-
box-shadow: 0 0 1px 1px #2f2d35;
|
34 |
-
}
|
35 |
-
|
36 |
-
/* Status display */
|
37 |
-
|
38 |
-
#status {
|
39 |
-
position: absolute;
|
40 |
-
left: 0;
|
41 |
-
top: 0;
|
42 |
-
right: 0;
|
43 |
-
bottom: 0;
|
44 |
-
display: flex;
|
45 |
-
justify-content: center;
|
46 |
-
align-items: center;
|
47 |
-
/* don't consume click events - make children visible explicitly */
|
48 |
-
visibility: hidden;
|
49 |
-
}
|
50 |
-
|
51 |
-
#status-progress {
|
52 |
-
width: 366px;
|
53 |
-
height: 7px;
|
54 |
-
background-color: #38363A;
|
55 |
-
border: 1px solid #444246;
|
56 |
-
padding: 1px;
|
57 |
-
box-shadow: 0 0 2px 1px #1B1C22;
|
58 |
-
border-radius: 2px;
|
59 |
-
visibility: visible;
|
60 |
-
}
|
61 |
-
|
62 |
-
@media only screen and (orientation:portrait) {
|
63 |
-
#status-progress {
|
64 |
-
width: 61.8%;
|
65 |
-
}
|
66 |
-
}
|
67 |
-
|
68 |
-
#status-progress-inner {
|
69 |
-
height: 100%;
|
70 |
-
width: 0;
|
71 |
-
box-sizing: border-box;
|
72 |
-
transition: width 0.5s linear;
|
73 |
-
background-color: #202020;
|
74 |
-
border: 1px solid #222223;
|
75 |
-
box-shadow: 0 0 1px 1px #27282E;
|
76 |
-
border-radius: 3px;
|
77 |
-
}
|
78 |
-
|
79 |
-
#status-indeterminate {
|
80 |
-
height: 42px;
|
81 |
-
visibility: visible;
|
82 |
-
position: relative;
|
83 |
-
}
|
84 |
-
|
85 |
-
#status-indeterminate > div {
|
86 |
-
width: 4.5px;
|
87 |
-
height: 0;
|
88 |
-
border-style: solid;
|
89 |
-
border-width: 9px 3px 0 3px;
|
90 |
-
border-color: #2b2b2b transparent transparent transparent;
|
91 |
-
transform-origin: center 21px;
|
92 |
-
position: absolute;
|
93 |
-
}
|
94 |
-
|
95 |
-
#status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); }
|
96 |
-
#status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); }
|
97 |
-
#status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); }
|
98 |
-
#status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); }
|
99 |
-
#status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); }
|
100 |
-
#status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); }
|
101 |
-
#status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); }
|
102 |
-
#status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); }
|
103 |
-
|
104 |
-
#status-notice {
|
105 |
-
margin: 0 100px;
|
106 |
-
line-height: 1.3;
|
107 |
-
visibility: visible;
|
108 |
-
padding: 4px 6px;
|
109 |
-
visibility: visible;
|
110 |
-
}
|
111 |
-
</style>
|
112 |
-
<link id='-gd-engine-icon' rel='icon' type='image/png' href='index.icon.png' />
|
113 |
-
<link rel='apple-touch-icon' href='index.apple-touch-icon.png'/>
|
114 |
-
|
115 |
-
</head>
|
116 |
-
<body>
|
117 |
-
<canvas id="canvas">
|
118 |
-
HTML5 canvas appears to be unsupported in the current browser.<br >
|
119 |
-
Please try updating or use a different browser.
|
120 |
-
</canvas>
|
121 |
-
<div id="status">
|
122 |
-
<div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();">
|
123 |
-
<div id ="status-progress-inner"></div>
|
124 |
-
</div>
|
125 |
-
<div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();">
|
126 |
-
<div></div>
|
127 |
-
<div></div>
|
128 |
-
<div></div>
|
129 |
-
<div></div>
|
130 |
-
<div></div>
|
131 |
-
<div></div>
|
132 |
-
<div></div>
|
133 |
-
<div></div>
|
134 |
-
</div>
|
135 |
-
<div id="status-notice" class="godot" style="display: none;"></div>
|
136 |
-
</div>
|
137 |
-
|
138 |
-
<script src="index.js"></script>
|
139 |
-
<script>
|
140 |
-
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":5424,"index.wasm":35410474},"focusCanvas":true,"gdextensionLibs":[]};
|
141 |
-
const engine = new Engine(GODOT_CONFIG);
|
142 |
-
|
143 |
-
(function () {
|
144 |
-
const INDETERMINATE_STATUS_STEP_MS = 100;
|
145 |
-
const statusProgress = document.getElementById('status-progress');
|
146 |
-
const statusProgressInner = document.getElementById('status-progress-inner');
|
147 |
-
const statusIndeterminate = document.getElementById('status-indeterminate');
|
148 |
-
const statusNotice = document.getElementById('status-notice');
|
149 |
-
|
150 |
-
let initializing = true;
|
151 |
-
let statusMode = 'hidden';
|
152 |
-
|
153 |
-
let animationCallbacks = [];
|
154 |
-
function animate(time) {
|
155 |
-
animationCallbacks.forEach((callback) => callback(time));
|
156 |
-
requestAnimationFrame(animate);
|
157 |
-
}
|
158 |
-
requestAnimationFrame(animate);
|
159 |
-
|
160 |
-
function animateStatusIndeterminate(ms) {
|
161 |
-
const i = Math.floor((ms / INDETERMINATE_STATUS_STEP_MS) % 8);
|
162 |
-
if (statusIndeterminate.children[i].style.borderTopColor === '') {
|
163 |
-
Array.prototype.slice.call(statusIndeterminate.children).forEach((child) => {
|
164 |
-
child.style.borderTopColor = '';
|
165 |
-
});
|
166 |
-
statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';
|
167 |
-
}
|
168 |
-
}
|
169 |
-
|
170 |
-
function setStatusMode(mode) {
|
171 |
-
if (statusMode === mode || !initializing) {
|
172 |
-
return;
|
173 |
-
}
|
174 |
-
[statusProgress, statusIndeterminate, statusNotice].forEach((elem) => {
|
175 |
-
elem.style.display = 'none';
|
176 |
-
});
|
177 |
-
animationCallbacks = animationCallbacks.filter(function (value) {
|
178 |
-
return (value !== animateStatusIndeterminate);
|
179 |
-
});
|
180 |
-
switch (mode) {
|
181 |
-
case 'progress':
|
182 |
-
statusProgress.style.display = 'block';
|
183 |
-
break;
|
184 |
-
case 'indeterminate':
|
185 |
-
statusIndeterminate.style.display = 'block';
|
186 |
-
animationCallbacks.push(animateStatusIndeterminate);
|
187 |
-
break;
|
188 |
-
case 'notice':
|
189 |
-
statusNotice.style.display = 'block';
|
190 |
-
break;
|
191 |
-
case 'hidden':
|
192 |
-
break;
|
193 |
-
default:
|
194 |
-
throw new Error('Invalid status mode');
|
195 |
-
}
|
196 |
-
statusMode = mode;
|
197 |
-
}
|
198 |
-
|
199 |
-
function setStatusNotice(text) {
|
200 |
-
while (statusNotice.lastChild) {
|
201 |
-
statusNotice.removeChild(statusNotice.lastChild);
|
202 |
-
}
|
203 |
-
const lines = text.split('\n');
|
204 |
-
lines.forEach((line) => {
|
205 |
-
statusNotice.appendChild(document.createTextNode(line));
|
206 |
-
statusNotice.appendChild(document.createElement('br'));
|
207 |
-
});
|
208 |
-
}
|
209 |
-
|
210 |
-
function displayFailureNotice(err) {
|
211 |
-
const msg = err.message || err;
|
212 |
-
console.error(msg);
|
213 |
-
setStatusNotice(msg);
|
214 |
-
setStatusMode('notice');
|
215 |
-
initializing = false;
|
216 |
-
}
|
217 |
-
|
218 |
-
const missing = Engine.getMissingFeatures();
|
219 |
-
if (missing.length !== 0) {
|
220 |
-
const missingMsg = 'Error\nThe following features required to run Godot projects on the Web are missing:\n';
|
221 |
-
displayFailureNotice(missingMsg + missing.join('\n'));
|
222 |
-
} else {
|
223 |
-
setStatusMode('indeterminate');
|
224 |
-
engine.startGame({
|
225 |
-
'onProgress': function (current, total) {
|
226 |
-
if (total > 0) {
|
227 |
-
statusProgressInner.style.width = `${(current / total) * 100}%`;
|
228 |
-
setStatusMode('progress');
|
229 |
-
if (current === total) {
|
230 |
-
// wait for progress bar animation
|
231 |
-
setTimeout(() => {
|
232 |
-
setStatusMode('indeterminate');
|
233 |
-
}, 500);
|
234 |
-
}
|
235 |
-
} else {
|
236 |
-
setStatusMode('indeterminate');
|
237 |
-
}
|
238 |
-
},
|
239 |
-
}).then(() => {
|
240 |
-
setStatusMode('hidden');
|
241 |
-
initializing = false;
|
242 |
-
}, displayFailureNotice);
|
243 |
-
}
|
244 |
-
}());
|
245 |
-
</script>
|
246 |
-
</body>
|
247 |
-
</html>
|
248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<!DOCTYPE html>
|
2 |
<html lang="en">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
+
<head>
|
5 |
+
<meta charset="UTF-8">
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
+
<title>Godot Export with Audio Recording</title>
|
8 |
+
</head>
|
9 |
+
|
10 |
+
<body>
|
11 |
+
<h1>Godot Export with Microphone Recording</h1>
|
12 |
+
|
13 |
+
<!-- Iframe for the Godot export -->
|
14 |
+
<iframe src="godot/index.html" width="800" height="600" frameborder="0"></iframe>
|
15 |
+
|
16 |
+
<script>
|
17 |
+
// URL of the server to send audio chunks
|
18 |
+
const serverUrl = "./api/process"
|
19 |
+
|
20 |
+
// Check if browser supports audio recording
|
21 |
+
if (!navigator.mediaDevices?.getUserMedia) {
|
22 |
+
console.error('Your browser does not support audio recording.')
|
23 |
+
alert('Your browser does not support audio recording. Please try using a modern browser like Chrome, Firefox, or Edge.')
|
24 |
+
throw new Error('Audio recording not supported')
|
25 |
+
}
|
26 |
+
|
27 |
+
// Set up audio recording
|
28 |
+
navigator.mediaDevices.getUserMedia({ audio: true })
|
29 |
+
.then(stream => {
|
30 |
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
31 |
+
const mediaRecorder = new MediaRecorder(stream)
|
32 |
+
const audioChunks = []
|
33 |
+
|
34 |
+
mediaRecorder.ondataavailable = event => {
|
35 |
+
audioChunks.push(event.data)
|
36 |
+
}
|
37 |
+
|
38 |
+
mediaRecorder.onstop = () => {
|
39 |
+
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' })
|
40 |
+
audioChunks.length = 0 // Clear chunks after creating the Blob
|
41 |
+
|
42 |
+
// Send the audio chunk to the server
|
43 |
+
const formData = new FormData()
|
44 |
+
formData.append('audio', audioBlob)
|
45 |
+
|
46 |
+
fetch(serverUrl, {
|
47 |
+
method: 'POST',
|
48 |
+
body: formData
|
49 |
+
}).then(response => {
|
50 |
+
console.log('Audio chunk sent successfully')
|
51 |
+
}).catch(error => {
|
52 |
+
console.error('Error sending audio chunk:', error)
|
53 |
+
})
|
54 |
+
}
|
55 |
+
|
56 |
+
// Start recording in intervals
|
57 |
+
const chunkInterval = 300 // Chunk duration in milliseconds
|
58 |
+
setInterval(() => {
|
59 |
+
if (mediaRecorder.state === 'recording') {
|
60 |
+
mediaRecorder.stop()
|
61 |
+
mediaRecorder.start()
|
62 |
+
} else {
|
63 |
+
mediaRecorder.start()
|
64 |
+
}
|
65 |
+
}, chunkInterval)
|
66 |
+
})
|
67 |
+
.catch(error => {
|
68 |
+
console.error('Error accessing microphone:', error)
|
69 |
+
});
|
70 |
+
</script>
|
71 |
+
</body>
|
72 |
+
|
73 |
+
</html>
|
server/static/index.pck
DELETED
@@ -1,3 +0,0 @@
|
|
1 |
-
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:66a491c47db387a79e701d71d84c5c2d517649710b6b7a611f699de82955bcb6
|
3 |
-
size 5424
|
|
|
|
|
|
|
|