MySafeCode commited on
Commit
a29c634
Β·
verified Β·
1 Parent(s): 5de2ee4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -158
app.py CHANGED
@@ -1,6 +1,7 @@
1
  #!/usr/bin/env python3
2
- # reliable_30fps_fixed.py - DEBUG VERSION
3
  import pygame
 
4
  import time
5
  import threading
6
  import base64
@@ -26,39 +27,58 @@ shared = {
26
  "capture_fps": 0,
27
  "streaming": True,
28
  "last_frame_time": time.time(),
29
- "frames_this_second": 0,
30
- "debug_info": ""
31
  }
32
 
33
  # ===== 2. CREATE FLASK APP =====
34
  app = Flask(__name__)
35
 
36
- # ===== 3. HTML TEMPLATE WITH DEBUG INFO =====
37
  HTML = '''
38
  <!DOCTYPE html>
39
  <html>
40
  <head>
41
  <meta charset="UTF-8">
42
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
43
- <title>PyGame + Flask Streaming - DEBUG</title>
44
  <style>
45
  body { font-family: Arial; background: #0f172a; color: white; padding: 20px; margin: 0; }
46
  .container { max-width: 800px; margin: 0 auto; text-align: center; }
47
  h1 { color: #60a5fa; }
48
- .debug { background: #1e293b; padding: 15px; margin: 20px 0; border-radius: 8px; text-align: left; }
49
- .image-container { width: 400px; height: 300px; margin: 20px auto; border: 3px solid #60a5fa; background: #000; }
50
- #streamImg { width: 100%; height: 100%; object-fit: contain; }
51
- .stats { display: inline-block; background: #1e293b; padding: 20px; border-radius: 10px; margin: 10px; }
52
- .stat-row { margin: 10px 0; display: flex; justify-content: space-between; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  .stat-value { font-weight: bold; color: #60a5fa; }
 
54
  </style>
55
  </head>
56
  <body>
57
  <div class="container">
58
- <h1>PyGame + Flask Streaming - DEBUG MODE</h1>
 
59
 
60
  <div class="image-container">
61
- <img id="streamImg" src="/stream" alt="Live Stream" onerror="console.error('Image failed to load')">
62
  </div>
63
 
64
  <div class="stats">
@@ -68,16 +88,8 @@ HTML = '''
68
  <div class="stat-row"><span>Latency:</span><span class="stat-value" id="latency">-- ms</span></div>
69
  </div>
70
 
71
- <div class="debug">
72
- <h3>Debug Info:</h3>
73
- <div id="debugInfo">Waiting for debug info...</div>
74
- <div>Image URL: <span id="imageUrl"></span></div>
75
- <div>Last frame time: <span id="lastFrameTime"></span></div>
76
- </div>
77
-
78
- <div style="margin-top: 20px;">
79
- <button onclick="testImage()">Test Image Load</button>
80
- <button onclick="reloadPage()">Reload Page</button>
81
  </div>
82
  </div>
83
 
@@ -89,13 +101,11 @@ HTML = '''
89
  function updateStream() {
90
  const now = Date.now();
91
  const img = document.getElementById('streamImg');
92
- const timestamp = now;
93
 
94
- // Update image with timestamp
95
- img.src = '/stream?_=' + timestamp;
96
- document.getElementById('imageUrl').textContent = img.src;
97
 
98
- // Calculate FPS
99
  streamFrameCount++;
100
  if (now - lastStreamUpdate >= 1000) {
101
  const streamFps = streamFrameCount / ((now - lastStreamUpdate) / 1000);
@@ -110,37 +120,18 @@ HTML = '''
110
  .then(data => {
111
  document.getElementById('captureFps').textContent = data.capture_fps.toFixed(1);
112
  document.getElementById('totalFrames').textContent = data.frame_count;
113
- document.getElementById('debugInfo').textContent = data.debug_info || 'No debug info';
114
- document.getElementById('lastFrameTime').textContent = new Date(data.timestamp * 1000).toLocaleTimeString();
115
 
116
  if (lastFrameTimestamp > 0) {
117
  const latency = now - (data.timestamp * 1000);
118
  document.getElementById('latency').textContent = Math.round(latency) + ' ms';
119
  }
120
  lastFrameTimestamp = data.timestamp * 1000;
121
- })
122
- .catch(err => {
123
- console.error('Stats fetch error:', err);
124
  });
125
  }
126
 
127
- function testImage() {
128
- const img = document.getElementById('streamImg');
129
- console.log('Testing image load...');
130
- img.onload = () => console.log('βœ… Image loaded successfully');
131
- img.onerror = () => console.log('❌ Image failed to load');
132
- img.src = '/stream?_=' + Date.now();
133
- }
134
-
135
- function reloadPage() {
136
- location.reload();
137
- }
138
-
139
- // Start polling
140
  setInterval(updateStream, 33);
141
  updateStream();
142
-
143
- console.log('DEBUG: Streaming client started');
144
  </script>
145
  </body>
146
  </html>
@@ -157,34 +148,16 @@ def stats():
157
  'frame_count': shared['frame_count'],
158
  'capture_fps': shared['capture_fps'],
159
  'timestamp': time.time(),
160
- 'has_frame': shared['latest_frame'] is not None,
161
- 'debug_info': shared['debug_info'],
162
- 'frame_size': len(shared['latest_frame']) if shared['latest_frame'] else 0
163
  }
164
 
165
  @app.route('/stream')
166
  def stream():
167
- """Stream endpoint - FIXED VERSION"""
168
  if shared['latest_frame']:
169
  try:
170
- # Debug: Check frame size
171
- frame_size = len(shared['latest_frame'])
172
- print(f"DEBUG: Serving frame {shared['frame_count']}, size: {frame_size} bytes")
173
-
174
- # Decode base64
175
  image_data = base64.b64decode(shared['latest_frame'])
176
-
177
- # Check if it's valid image data
178
- if len(image_data) < 100: # Too small for a valid JPEG
179
- print(f"WARNING: Image data too small: {len(image_data)} bytes")
180
- # Return a test image
181
- return Response(
182
- b'Test image',
183
- mimetype='text/plain',
184
- headers={'Cache-Control': 'no-cache'}
185
- )
186
-
187
- # Return the image
188
  return Response(
189
  image_data,
190
  mimetype='image/jpeg',
@@ -195,66 +168,47 @@ def stream():
195
  }
196
  )
197
  except Exception as e:
198
- print(f"ERROR in /stream: {e}")
199
- shared['debug_info'] = f"Stream error: {str(e)}"
200
 
201
- # Return a simple test image
202
- print("WARNING: No frame available, sending test pattern")
203
- import random
204
- test_image = create_test_image()
205
- return Response(
206
- test_image,
207
- mimetype='image/png',
208
- headers={'Cache-Control': 'no-cache'}
209
- )
210
 
211
- def create_test_image():
212
- """Create a simple test image to verify streaming works."""
213
- from PIL import Image, ImageDraw
214
- import io as imageio
215
-
216
- # Create a simple test image
217
- img = Image.new('RGB', (400, 300), color='blue')
218
- draw = ImageDraw.Draw(img)
219
- draw.rectangle([50, 50, 350, 250], fill='red')
220
- draw.text((150, 140), 'TEST', fill='white')
221
- draw.text((120, 160), 'PyGame Stream', fill='white')
222
-
223
- # Save to bytes
224
- buffer = imageio.BytesIO()
225
- img.save(buffer, format='PNG')
226
- return buffer.getvalue()
227
-
228
- # ===== 5. FIXED CAPTURE LOOP =====
229
- def simple_capture_loop():
230
- """CAPTURE LOOP WITH FIXED IMAGE SAVING"""
231
 
232
  try:
233
  pygame.init()
234
- print("βœ… PyGame initialized")
235
  except Exception as e:
236
  print(f"❌ PyGame init error: {e}")
237
- shared['debug_info'] = f"PyGame init error: {e}"
238
  return
239
 
240
  # Create surfaces
241
  screen = pygame.Surface((WIDTH, HEIGHT))
242
 
243
- # Animation
244
- circle_x, circle_y = WIDTH//2, HEIGHT//2
 
 
 
 
245
  circle_r = 30
246
  speed_x, speed_y = 4, 3
247
 
 
 
 
 
 
248
  fps_update_time = time.time()
249
  frames_since_update = 0
250
- frame_num = 0
251
 
252
  try:
253
  while shared["streaming"]:
254
  start_time = time.time()
255
- frame_num += 1
256
 
257
- # Update position
258
  circle_x += speed_x
259
  circle_y += speed_y
260
 
@@ -263,54 +217,58 @@ def simple_capture_loop():
263
  if circle_y - circle_r < 0 or circle_y + circle_r > HEIGHT:
264
  speed_y *= -1
265
 
266
- # Draw
267
- screen.fill((25, 25, 45))
 
 
268
  pygame.draw.circle(screen, (255, 80, 80), (int(circle_x), int(circle_y)), circle_r)
269
  pygame.draw.circle(screen, (255, 255, 255), (int(circle_x), int(circle_y)), circle_r, 2)
270
 
271
- # Add frame number text
272
  font = pygame.font.Font(None, 36)
273
- text = font.render(f"Frame: {frame_num}", True, (100, 255, 100))
274
  screen.blit(text, (10, 10))
275
 
276
- # === CRITICAL FIX: Save image properly ===
277
- try:
278
- # Scale the image
279
- scaled = pygame.transform.smoothscale(screen, (STREAM_WIDTH, STREAM_HEIGHT))
280
-
281
- # Save to memory buffer
282
- buffer = io.BytesIO()
283
-
284
- # FIX 1: Use save_extended for better control
285
- pygame.image.save_extended(scaled, buffer)
286
-
287
- # Get the bytes
288
- buffer.seek(0)
289
- image_bytes = buffer.getvalue()
290
-
291
- # FIX 2: Debug output
292
- if frame_num % 30 == 0: # Every second at 30 FPS
293
- print(f"Frame {frame_num}: Saved {len(image_bytes)} bytes")
294
-
295
- # FIX 3: Encode to base64
296
- encoded = base64.b64encode(image_bytes).decode('utf-8')
297
-
298
- # FIX 4: Update shared state
299
- shared["latest_frame"] = encoded
300
- shared["debug_info"] = f"Frame {frame_num}: {len(encoded)} chars"
301
-
302
- except Exception as e:
303
- print(f"❌ Image save error on frame {frame_num}: {e}")
304
- shared['debug_info'] = f"Save error: {e}"
305
- # Create a simple fallback frame
306
- fallback = pygame.Surface((STREAM_WIDTH, STREAM_HEIGHT))
307
- fallback.fill((255, 0, 0) if frame_num % 2 == 0 else (0, 0, 255))
308
- buffer = io.BytesIO()
309
- pygame.image.save(fallback, buffer)
310
- buffer.seek(0)
311
- shared["latest_frame"] = base64.b64encode(buffer.getvalue()).decode('utf-8')
 
 
312
 
313
- # Update counters
314
  shared["frame_count"] += 1
315
  frames_since_update += 1
316
  shared["frames_this_second"] += 1
@@ -324,7 +282,7 @@ def simple_capture_loop():
324
 
325
  shared["last_frame_time"] = current_time
326
 
327
- # Control frame rate
328
  elapsed = time.time() - start_time
329
  target_time = 1.0 / TARGET_FPS
330
  if elapsed < target_time:
@@ -332,31 +290,37 @@ def simple_capture_loop():
332
 
333
  except Exception as e:
334
  print(f"❌ Capture loop error: {e}")
335
- shared['debug_info'] = f"Capture error: {e}"
 
336
 
337
  pygame.quit()
338
- print("πŸ›‘ Capture loop stopped")
339
 
340
  # ===== 6. MAIN FUNCTION =====
341
  def main():
342
  print("=" * 60)
343
- print("πŸš€ PyGame + Flask Streaming - DEBUG VERSION")
344
  print("=" * 60)
345
  print(f"Port: {PORT}")
346
- print(f"Resolution: {WIDTH}x{HEIGHT} β†’ {STREAM_WIDTH}x{STREAM_HEIGHT}")
347
  print(f"Target FPS: {TARGET_FPS}")
 
348
  print("=" * 60)
349
 
350
  # Start capture thread
351
- print("Starting capture thread...")
352
- capture_thread = threading.Thread(target=simple_capture_loop, daemon=True)
353
  capture_thread.start()
354
 
355
- # Wait a bit for initialization
356
- time.sleep(1)
 
 
 
 
 
357
 
358
  print(f"Starting Flask server on port {PORT}...")
359
- print(f"Open: https://YOUR_SPACE.hf.space")
360
  print("=" * 60)
361
 
362
  try:
@@ -367,11 +331,14 @@ def main():
367
  threaded=True,
368
  use_reloader=False
369
  )
 
 
370
  except Exception as e:
371
- print(f"Server error: {e}")
372
  finally:
373
  shared['streaming'] = False
374
  capture_thread.join(timeout=2)
 
375
  print("βœ… Shutdown complete")
376
 
377
  if __name__ == "__main__":
 
1
  #!/usr/bin/env python3
2
+ # reliable_30fps_numpy.py - PyGame + Flask with NumPy surface arrays
3
  import pygame
4
+ import numpy as np
5
  import time
6
  import threading
7
  import base64
 
27
  "capture_fps": 0,
28
  "streaming": True,
29
  "last_frame_time": time.time(),
30
+ "frames_this_second": 0
 
31
  }
32
 
33
  # ===== 2. CREATE FLASK APP =====
34
  app = Flask(__name__)
35
 
36
+ # ===== 3. HTML TEMPLATE =====
37
  HTML = '''
38
  <!DOCTYPE html>
39
  <html>
40
  <head>
41
  <meta charset="UTF-8">
42
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
43
+ <title>PyGame + NumPy Streaming</title>
44
  <style>
45
  body { font-family: Arial; background: #0f172a; color: white; padding: 20px; margin: 0; }
46
  .container { max-width: 800px; margin: 0 auto; text-align: center; }
47
  h1 { color: #60a5fa; }
48
+ .image-container {
49
+ width: 400px;
50
+ height: 300px;
51
+ margin: 20px auto;
52
+ border: 3px solid #60a5fa;
53
+ background: #000;
54
+ overflow: hidden;
55
+ }
56
+ #streamImg {
57
+ width: 100%;
58
+ height: 100%;
59
+ object-fit: contain;
60
+ image-rendering: pixelated;
61
+ }
62
+ .stats {
63
+ display: inline-block;
64
+ background: #1e293b;
65
+ padding: 20px;
66
+ border-radius: 10px;
67
+ margin: 10px;
68
+ text-align: left;
69
+ }
70
+ .stat-row { margin: 10px 0; display: flex; justify-content: space-between; min-width: 200px; }
71
  .stat-value { font-weight: bold; color: #60a5fa; }
72
+ .info { color: #94a3b8; margin-top: 20px; font-size: 0.9em; }
73
  </style>
74
  </head>
75
  <body>
76
  <div class="container">
77
+ <h1>PyGame + NumPy Surface Array Streaming</h1>
78
+ <p class="info">Using NumPy arrays for pixel manipulation</p>
79
 
80
  <div class="image-container">
81
+ <img id="streamImg" src="/stream" alt="Live Stream">
82
  </div>
83
 
84
  <div class="stats">
 
88
  <div class="stat-row"><span>Latency:</span><span class="stat-value" id="latency">-- ms</span></div>
89
  </div>
90
 
91
+ <div class="info">
92
+ Rendering with NumPy arrays β€’ Port: ''' + str(PORT) + ''' β€’ Target: 30 FPS
 
 
 
 
 
 
 
 
93
  </div>
94
  </div>
95
 
 
101
  function updateStream() {
102
  const now = Date.now();
103
  const img = document.getElementById('streamImg');
 
104
 
105
+ // Update image with anti-cache timestamp
106
+ img.src = '/stream?_=' + now;
 
107
 
108
+ // Calculate stream FPS
109
  streamFrameCount++;
110
  if (now - lastStreamUpdate >= 1000) {
111
  const streamFps = streamFrameCount / ((now - lastStreamUpdate) / 1000);
 
120
  .then(data => {
121
  document.getElementById('captureFps').textContent = data.capture_fps.toFixed(1);
122
  document.getElementById('totalFrames').textContent = data.frame_count;
 
 
123
 
124
  if (lastFrameTimestamp > 0) {
125
  const latency = now - (data.timestamp * 1000);
126
  document.getElementById('latency').textContent = Math.round(latency) + ' ms';
127
  }
128
  lastFrameTimestamp = data.timestamp * 1000;
 
 
 
129
  });
130
  }
131
 
132
+ // Start polling at 30 FPS
 
 
 
 
 
 
 
 
 
 
 
 
133
  setInterval(updateStream, 33);
134
  updateStream();
 
 
135
  </script>
136
  </body>
137
  </html>
 
148
  'frame_count': shared['frame_count'],
149
  'capture_fps': shared['capture_fps'],
150
  'timestamp': time.time(),
151
+ 'has_frame': shared['latest_frame'] is not None
 
 
152
  }
153
 
154
  @app.route('/stream')
155
  def stream():
156
+ """Stream endpoint - returns the latest NumPy/surface frame."""
157
  if shared['latest_frame']:
158
  try:
159
+ # Decode base64 and serve
 
 
 
 
160
  image_data = base64.b64decode(shared['latest_frame'])
 
 
 
 
 
 
 
 
 
 
 
 
161
  return Response(
162
  image_data,
163
  mimetype='image/jpeg',
 
168
  }
169
  )
170
  except Exception as e:
171
+ print(f"Stream error: {e}")
 
172
 
173
+ # Return empty response if no frame
174
+ return Response(b'', mimetype='image/jpeg')
 
 
 
 
 
 
 
175
 
176
+ # ===== 5. NUMPY SURFACE CAPTURE LOOP =====
177
+ def numpy_capture_loop():
178
+ """Capture loop using NumPy arrays for pixel manipulation."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  try:
181
  pygame.init()
182
+ print("βœ… PyGame initialized for NumPy rendering")
183
  except Exception as e:
184
  print(f"❌ PyGame init error: {e}")
 
185
  return
186
 
187
  # Create surfaces
188
  screen = pygame.Surface((WIDTH, HEIGHT))
189
 
190
+ # Create NumPy array for direct pixel access (optional, for advanced effects)
191
+ # This creates a 3D array: [height, width, 3] for RGB
192
+ pixel_array = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
193
+
194
+ # Animation variables
195
+ circle_x, circle_y = WIDTH // 2, HEIGHT // 2
196
  circle_r = 30
197
  speed_x, speed_y = 4, 3
198
 
199
+ # For gradient effect using NumPy
200
+ x_coords = np.arange(WIDTH)
201
+ y_coords = np.arange(HEIGHT)
202
+ X, Y = np.meshgrid(x_coords, y_coords)
203
+
204
  fps_update_time = time.time()
205
  frames_since_update = 0
 
206
 
207
  try:
208
  while shared["streaming"]:
209
  start_time = time.time()
 
210
 
211
+ # === UPDATE POSITION ===
212
  circle_x += speed_x
213
  circle_y += speed_y
214
 
 
217
  if circle_y - circle_r < 0 or circle_y + circle_r > HEIGHT:
218
  speed_y *= -1
219
 
220
+ # === METHOD 1: Traditional PyGame drawing (simpler) ===
221
+ screen.fill((25, 25, 45)) # Dark blue background
222
+
223
+ # Draw bouncing circle
224
  pygame.draw.circle(screen, (255, 80, 80), (int(circle_x), int(circle_y)), circle_r)
225
  pygame.draw.circle(screen, (255, 255, 255), (int(circle_x), int(circle_y)), circle_r, 2)
226
 
227
+ # Draw frame counter
228
  font = pygame.font.Font(None, 36)
229
+ text = font.render(f"Frame: {shared['frame_count']}", True, (100, 255, 100))
230
  screen.blit(text, (10, 10))
231
 
232
+ # Draw FPS
233
+ fps_text = font.render(f"FPS: {shared['capture_fps']:.1f}", True, (100, 255, 100))
234
+ screen.blit(fps_text, (10, 50))
235
+
236
+ # === OPTIONAL: METHOD 2: NumPy pixel manipulation ===
237
+ # Uncomment this for advanced NumPy effects
238
+ """
239
+ # Clear with gradient using NumPy
240
+ gradient = 25 + (X * 0.03 + Y * 0.02).astype(np.uint8)
241
+ pixel_array[:, :, 0] = gradient # Red channel
242
+ pixel_array[:, :, 1] = gradient // 2 # Green channel
243
+ pixel_array[:, :, 2] = 45 # Blue channel
244
+
245
+ # Draw circle using NumPy (distance formula)
246
+ distance = np.sqrt((X - circle_x)**2 + (Y - circle_y)**2)
247
+ circle_mask = distance < circle_r
248
+
249
+ # Set circle pixels to red
250
+ pixel_array[circle_mask, 0] = 255 # Red
251
+ pixel_array[circle_mask, 1] = 80 # Green
252
+ pixel_array[circle_mask, 2] = 80 # Blue
253
+
254
+ # Convert NumPy array to PyGame surface
255
+ pygame.surfarray.blit_array(screen, pixel_array)
256
+ """
257
+
258
+ # === SCALE AND SAVE FRAME ===
259
+ # Scale down for streaming
260
+ scaled = pygame.transform.smoothscale(screen, (STREAM_WIDTH, STREAM_HEIGHT))
261
+
262
+ # Save to memory buffer
263
+ buffer = io.BytesIO()
264
+ pygame.image.save(scaled, buffer, 'JPEG')
265
+ buffer.seek(0)
266
+
267
+ # Encode to base64 for sharing
268
+ image_bytes = buffer.getvalue()
269
+ shared["latest_frame"] = base64.b64encode(image_bytes).decode('utf-8')
270
 
271
+ # === UPDATE COUNTERS ===
272
  shared["frame_count"] += 1
273
  frames_since_update += 1
274
  shared["frames_this_second"] += 1
 
282
 
283
  shared["last_frame_time"] = current_time
284
 
285
+ # === FRAME RATE CONTROL ===
286
  elapsed = time.time() - start_time
287
  target_time = 1.0 / TARGET_FPS
288
  if elapsed < target_time:
 
290
 
291
  except Exception as e:
292
  print(f"❌ Capture loop error: {e}")
293
+ import traceback
294
+ traceback.print_exc()
295
 
296
  pygame.quit()
297
+ print("πŸ›‘ NumPy capture loop stopped")
298
 
299
  # ===== 6. MAIN FUNCTION =====
300
  def main():
301
  print("=" * 60)
302
+ print("πŸš€ PyGame + NumPy Surface Array Streaming")
303
  print("=" * 60)
304
  print(f"Port: {PORT}")
305
+ print(f"Render: {WIDTH}x{HEIGHT} β†’ Stream: {STREAM_WIDTH}x{STREAM_HEIGHT}")
306
  print(f"Target FPS: {TARGET_FPS}")
307
+ print("Using NumPy for pixel manipulation")
308
  print("=" * 60)
309
 
310
  # Start capture thread
311
+ print("Starting NumPy capture thread...")
312
+ capture_thread = threading.Thread(target=numpy_capture_loop, daemon=True)
313
  capture_thread.start()
314
 
315
+ # Wait for first frame
316
+ print("Waiting for first frame...")
317
+ for i in range(30):
318
+ if shared['latest_frame']:
319
+ print(f"βœ… First frame ready after {i*0.1:.1f}s")
320
+ break
321
+ time.sleep(0.1)
322
 
323
  print(f"Starting Flask server on port {PORT}...")
 
324
  print("=" * 60)
325
 
326
  try:
 
331
  threaded=True,
332
  use_reloader=False
333
  )
334
+ except KeyboardInterrupt:
335
+ print("\nπŸ‘‹ Interrupted")
336
  except Exception as e:
337
+ print(f"❌ Server error: {e}")
338
  finally:
339
  shared['streaming'] = False
340
  capture_thread.join(timeout=2)
341
+ print(f"\nπŸ“Š Final: {shared['frame_count']} frames @ {shared['capture_fps']:.1f} FPS")
342
  print("βœ… Shutdown complete")
343
 
344
  if __name__ == "__main__":