GitLab CI commited on
Commit
6831f1f
Β·
1 Parent(s): 726ec90

Update game build from GitLab CI

Browse files
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, Pipe, connection
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[dict]:
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], action_queue: Queue[str]):
10
  super().__init__()
11
  self.audio_queue = audio_queue
12
- self.action_queue = 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
- # Add a global queue for actions
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
- data = request.get_json()
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  # Validate the incoming data
85
- if not data or "audio_chunk" not in data:
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(data["audio_chunk"])
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, action_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