Archime commited on
Commit
9f80a29
·
1 Parent(s): 5c8a18f
Files changed (2) hide show
  1. app.py +58 -20
  2. app/session_utils.py +36 -3
app.py CHANGED
@@ -9,7 +9,9 @@ import os
9
  import json
10
  import spaces
11
  from app.utils import generate_coturn_config
 
12
  from app.session_utils import (
 
13
  generate_session_id,
14
  register_session,
15
  unregister_session,
@@ -20,7 +22,7 @@ from app.session_utils import (
20
  reset_active_sessions,
21
  )
22
 
23
- # Reset active sessions at startup
24
  reset_active_sessions()
25
 
26
  EXAMPLE_FILES = ["data/bonjour.wav", "data/bonjour2.wav"]
@@ -44,7 +46,7 @@ def read_and_stream_audio(filepath_to_stream: str, session_id: str):
44
 
45
  clear_stop_flag(session_id)
46
  register_session(session_id, filepath_to_stream)
47
- progress_path = f"/tmp/progress_{session_id}.json"
48
 
49
  try:
50
  segment = AudioSegment.from_file(filepath_to_stream)
@@ -61,10 +63,25 @@ def read_and_stream_audio(filepath_to_stream: str, session_id: str):
61
  iter_start = time.perf_counter()
62
  logging.debug(f"[{session_id}] Sending chunk {i}/{total_chunks}...")
63
 
64
- progress_data = {"value": i / total_chunks, "text": f"Streaming... ({i}/{total_chunks})"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  with open(progress_path, "w") as f:
66
  json.dump(progress_data, f)
67
 
 
68
  output_chunk = (
69
  chunk.frame_rate,
70
  np.array(chunk.get_array_of_samples()).reshape(1, -1),
@@ -75,7 +92,7 @@ def read_and_stream_audio(filepath_to_stream: str, session_id: str):
75
  time.sleep(max((chunk_ms / 1000.0) - (process_ms / 1000.0) - 0.1, 0.01))
76
 
77
  with open(progress_path, "w") as f:
78
- json.dump({"value": 1.0, "text": "Streaming completed ✅"}, f)
79
 
80
  logging.info(f"[{session_id}] Stream completed successfully.")
81
 
@@ -100,21 +117,26 @@ def stop_streaming(session_id: str):
100
 
101
 
102
  def get_session_progress(session_id: str):
103
- """Read progress data from /tmp/progress_<session_id>.json."""
104
- progress_path = f"/tmp/progress_{session_id}.json"
105
  if not os.path.exists(progress_path):
106
- return 0.0, ""
107
  try:
108
  with open(progress_path, "r") as f:
109
  data = json.load(f)
110
- return data.get("value", 0.0), data.get("text", "")
 
 
111
  except Exception as e:
112
  logging.error(f"[{session_id}] Progress read error: {e}")
113
- return 0.0, ""
114
 
115
 
116
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
117
- gr.Markdown("## 🎧 WebRTC Audio Streamer (Multi-user)\nEach user controls their own audio stream with live progress.")
 
 
 
118
 
119
  session_id = gr.State(value=generate_session_id)
120
  active_filepath = gr.State(value=DEFAULT_FILE)
@@ -130,16 +152,21 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
130
  )
131
 
132
  progress_bar = gr.Slider(
133
- label="Streaming Progress",
134
  minimum=0,
135
- maximum=1,
136
  value=0,
137
- step=0.01,
138
  interactive=False,
139
  visible=False,
140
  )
141
 
142
- progress_text = gr.Textbox(label="Stream Status", interactive=False, visible=False)
 
 
 
 
 
143
 
144
  with gr.Row():
145
  with gr.Column(scale=1, min_width=0):
@@ -169,11 +196,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
169
  stop_button: gr.Button(interactive=True),
170
  main_audio: gr.Audio(visible=False),
171
  progress_bar: gr.Slider(value=0, visible=True),
172
- progress_text: gr.Textbox(value="Streaming started...", visible=False),
173
  }
174
 
175
  def stop_streaming_ui(session_id):
176
- logging.debug(f"[{session_id}] UI: Stop clicked → restoring controls.")
177
  return {
178
  start_button: gr.Button(interactive=True),
179
  stop_button: gr.Button(interactive=False),
@@ -185,7 +212,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
185
  visible=True,
186
  ),
187
  progress_bar: gr.Slider(value=0, visible=False),
188
- progress_text: gr.Textbox(value="", visible=False),
189
  }
190
 
191
  ui_components = [start_button, stop_button, main_audio, progress_bar, progress_text]
@@ -199,9 +226,20 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
199
  concurrency_limit=20,
200
  )
201
 
202
- start_button.click(fn=start_streaming_ui, inputs=[session_id], outputs=ui_components)
203
- stop_button.click(fn=stop_streaming, inputs=[session_id], outputs=[webrtc_stream]).then(
204
- fn=stop_streaming_ui, inputs=[session_id], outputs=ui_components
 
 
 
 
 
 
 
 
 
 
 
205
  )
206
 
207
  with gr.Accordion("📊 Active Sessions", open=False):
 
9
  import json
10
  import spaces
11
  from app.utils import generate_coturn_config
12
+
13
  from app.session_utils import (
14
+ TMP_DIR,
15
  generate_session_id,
16
  register_session,
17
  unregister_session,
 
22
  reset_active_sessions,
23
  )
24
 
25
+ # Reset sessions at startup
26
  reset_active_sessions()
27
 
28
  EXAMPLE_FILES = ["data/bonjour.wav", "data/bonjour2.wav"]
 
46
 
47
  clear_stop_flag(session_id)
48
  register_session(session_id, filepath_to_stream)
49
+ progress_path = os.path.join(TMP_DIR, f"progress_{session_id}.json")
50
 
51
  try:
52
  segment = AudioSegment.from_file(filepath_to_stream)
 
63
  iter_start = time.perf_counter()
64
  logging.debug(f"[{session_id}] Sending chunk {i}/{total_chunks}...")
65
 
66
+ # Compute elapsed time (hh:mm:ss)
67
+ elapsed_s = i * (chunk_ms / 1000)
68
+ hours, remainder = divmod(int(elapsed_s), 3600)
69
+ minutes, seconds = divmod(remainder, 60)
70
+ elapsed_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
71
+
72
+ # Compute percentage
73
+ percent = round((i / total_chunks) * 100, 2)
74
+
75
+ # Save progress info
76
+ progress_data = {
77
+ "value": percent,
78
+ "elapsed": elapsed_str,
79
+ "text": f"Streaming... {elapsed_str} ({percent}%)"
80
+ }
81
  with open(progress_path, "w") as f:
82
  json.dump(progress_data, f)
83
 
84
+ # Stream chunk
85
  output_chunk = (
86
  chunk.frame_rate,
87
  np.array(chunk.get_array_of_samples()).reshape(1, -1),
 
92
  time.sleep(max((chunk_ms / 1000.0) - (process_ms / 1000.0) - 0.1, 0.01))
93
 
94
  with open(progress_path, "w") as f:
95
+ json.dump({"value": 100.0, "elapsed": elapsed_str, "text": "Streaming completed ✅"}, f)
96
 
97
  logging.info(f"[{session_id}] Stream completed successfully.")
98
 
 
117
 
118
 
119
  def get_session_progress(session_id: str):
120
+ """Read streaming progress and return slider position + elapsed time."""
121
+ progress_path = os.path.join(TMP_DIR, f"progress_{session_id}.json")
122
  if not os.path.exists(progress_path):
123
+ return 0.0, "00:00:00"
124
  try:
125
  with open(progress_path, "r") as f:
126
  data = json.load(f)
127
+ value = data.get("value", 0.0)
128
+ elapsed = data.get("elapsed", "00:00:00")
129
+ return value, elapsed
130
  except Exception as e:
131
  logging.error(f"[{session_id}] Progress read error: {e}")
132
+ return 0.0, "00:00:00"
133
 
134
 
135
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
136
+ gr.Markdown(
137
+ "## 🎧 WebRTC Audio Streamer (Multi-user)\n"
138
+ "Each user controls their own audio stream with elapsed time and percentage progress."
139
+ )
140
 
141
  session_id = gr.State(value=generate_session_id)
142
  active_filepath = gr.State(value=DEFAULT_FILE)
 
152
  )
153
 
154
  progress_bar = gr.Slider(
155
+ label="Streaming Progress (%)",
156
  minimum=0,
157
+ maximum=100,
158
  value=0,
159
+ step=0.1,
160
  interactive=False,
161
  visible=False,
162
  )
163
 
164
+ progress_text = gr.Textbox(
165
+ label="Elapsed Time (hh:mm:ss)",
166
+ interactive=False,
167
+ visible=False,
168
+ show_label=False
169
+ )
170
 
171
  with gr.Row():
172
  with gr.Column(scale=1, min_width=0):
 
196
  stop_button: gr.Button(interactive=True),
197
  main_audio: gr.Audio(visible=False),
198
  progress_bar: gr.Slider(value=0, visible=True),
199
+ progress_text: gr.Textbox(value="00:00:00", visible=True),
200
  }
201
 
202
  def stop_streaming_ui(session_id):
203
+ logging.debug(f"[{session_id}] UI: Stop clicked or finished → restoring controls.")
204
  return {
205
  start_button: gr.Button(interactive=True),
206
  stop_button: gr.Button(interactive=False),
 
212
  visible=True,
213
  ),
214
  progress_bar: gr.Slider(value=0, visible=False),
215
+ progress_text: gr.Textbox(value="00:00:00", visible=False),
216
  }
217
 
218
  ui_components = [start_button, stop_button, main_audio, progress_bar, progress_text]
 
226
  concurrency_limit=20,
227
  )
228
 
229
+ start_button.click(
230
+ fn=start_streaming_ui,
231
+ inputs=[session_id],
232
+ outputs=ui_components,
233
+ )
234
+
235
+ stop_button.click(
236
+ fn=stop_streaming,
237
+ inputs=[session_id],
238
+ outputs=[webrtc_stream],
239
+ ).then(
240
+ fn=stop_streaming_ui,
241
+ inputs=[session_id],
242
+ outputs=ui_components,
243
  )
244
 
245
  with gr.Accordion("📊 Active Sessions", open=False):
app/session_utils.py CHANGED
@@ -4,17 +4,48 @@ import uuid
4
  from datetime import datetime
5
  from app.logger_config import logger as logging
6
 
7
- ACTIVE_SESSIONS_FILE = "/tmp/active_sessions.json"
 
 
 
 
 
 
 
 
 
8
 
9
 
10
  def reset_active_sessions():
11
- """Removes or clears the active sessions file at startup."""
 
 
12
  try:
 
13
  if os.path.exists(ACTIVE_SESSIONS_FILE):
14
  os.remove(ACTIVE_SESSIONS_FILE)
15
  logging.info("Active sessions file reset at startup.")
16
  else:
17
  logging.debug("No active sessions found to reset.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  except Exception as e:
19
  logging.error(f"Error resetting active sessions: {e}")
20
 
@@ -28,6 +59,7 @@ def generate_session_id() -> str:
28
 
29
  def register_session(session_id: str, filepath: str):
30
  """Registers a new session."""
 
31
  data = {}
32
  if os.path.exists(ACTIVE_SESSIONS_FILE):
33
  with open(ACTIVE_SESSIONS_FILE, "r") as f:
@@ -92,7 +124,8 @@ def get_active_sessions():
92
 
93
  def stop_file_path(session_id: str) -> str:
94
  """Returns the stop-flag file path for a given session."""
95
- return f"/tmp/stream_stop_flag_{session_id}.txt"
 
96
 
97
 
98
  def create_stop_flag(session_id: str):
 
4
  from datetime import datetime
5
  from app.logger_config import logger as logging
6
 
7
+ TMP_DIR = "/tmp/canary_aed_streaming"
8
+ ACTIVE_SESSIONS_FILE = os.path.join(TMP_DIR, "active_sessions.json")
9
+
10
+
11
+ def ensure_tmp_dir():
12
+ """Ensures the base temporary directory exists."""
13
+ try:
14
+ os.makedirs(TMP_DIR, exist_ok=True)
15
+ except Exception as e:
16
+ logging.error(f"Failed to create tmp directory {TMP_DIR}: {e}")
17
 
18
 
19
  def reset_active_sessions():
20
+ """Removes all temporary session files at startup."""
21
+ ensure_tmp_dir()
22
+
23
  try:
24
+ # Remove active sessions file
25
  if os.path.exists(ACTIVE_SESSIONS_FILE):
26
  os.remove(ACTIVE_SESSIONS_FILE)
27
  logging.info("Active sessions file reset at startup.")
28
  else:
29
  logging.debug("No active sessions found to reset.")
30
+
31
+ # Clean up old progress files
32
+ for f in os.listdir(TMP_DIR):
33
+ if f.startswith("progress_") and f.endswith(".json"):
34
+ try:
35
+ os.remove(os.path.join(TMP_DIR, f))
36
+ logging.debug(f"Removed leftover progress file: {f}")
37
+ except Exception as e:
38
+ logging.warning(f"Failed to remove progress file {f}: {e}")
39
+
40
+ # Clean up old stop flag files
41
+ for f in os.listdir(TMP_DIR):
42
+ if f.startswith("stream_stop_flag_") and f.endswith(".txt"):
43
+ try:
44
+ os.remove(os.path.join(TMP_DIR, f))
45
+ logging.debug(f"Removed leftover stop flag file: {f}")
46
+ except Exception as e:
47
+ logging.warning(f"Failed to remove stop flag file {f}: {e}")
48
+
49
  except Exception as e:
50
  logging.error(f"Error resetting active sessions: {e}")
51
 
 
59
 
60
  def register_session(session_id: str, filepath: str):
61
  """Registers a new session."""
62
+ ensure_tmp_dir()
63
  data = {}
64
  if os.path.exists(ACTIVE_SESSIONS_FILE):
65
  with open(ACTIVE_SESSIONS_FILE, "r") as f:
 
124
 
125
  def stop_file_path(session_id: str) -> str:
126
  """Returns the stop-flag file path for a given session."""
127
+ ensure_tmp_dir()
128
+ return os.path.join(TMP_DIR, f"stream_stop_flag_{session_id}.txt")
129
 
130
 
131
  def create_stop_flag(session_id: str):