MySafeCode commited on
Commit
632d781
Β·
verified Β·
1 Parent(s): a29c634

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -107
app.py CHANGED
@@ -1,13 +1,13 @@
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
8
  import os
9
  import io
10
- from flask import Flask, Response, render_template_string
 
11
 
12
  # ===== 1. SETUP =====
13
  os.environ['SDL_VIDEODRIVER'] = 'dummy'
@@ -27,7 +27,9 @@ shared = {
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 =====
@@ -40,7 +42,7 @@ 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; }
@@ -51,31 +53,29 @@ HTML = '''
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">
@@ -85,25 +85,24 @@ HTML = '''
85
  <div class="stat-row"><span>Capture FPS:</span><span class="stat-value" id="captureFps">0.0</span></div>
86
  <div class="stat-row"><span>Total Frames:</span><span class="stat-value" id="totalFrames">0</span></div>
87
  <div class="stat-row"><span>Stream FPS:</span><span class="stat-value" id="streamFps">0.0</span></div>
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
 
96
  <script>
97
  let streamFrameCount = 0;
98
  let lastStreamUpdate = Date.now();
99
- let lastFrameTimestamp = 0;
 
100
 
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++;
@@ -114,24 +113,45 @@ HTML = '''
114
  lastStreamUpdate = now;
115
  }
116
 
117
- // Get stats
118
- fetch('/stats')
119
  .then(r => r.json())
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>
@@ -142,131 +162,210 @@ HTML = '''
142
  def index():
143
  return render_template_string(HTML)
144
 
145
- @app.route('/stats')
146
- def stats():
 
147
  return {
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',
164
  headers={
165
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
 
166
  'Pragma': 'no-cache',
167
- 'Expires': '0'
 
 
 
 
 
 
 
 
 
 
 
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
 
 
215
  if circle_x - circle_r < 0 or circle_x + circle_r > WIDTH:
216
  speed_x *= -1
 
 
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
@@ -289,41 +388,44 @@ def numpy_capture_loop():
289
  time.sleep(target_time - elapsed)
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:
 
 
 
 
327
  app.run(
328
  host='0.0.0.0',
329
  port=PORT,
@@ -331,15 +433,13 @@ def main():
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__":
345
  main()
 
1
  #!/usr/bin/env python3
2
+ # FIXED VERSION: Flask not updating images
3
  import pygame
 
4
  import time
5
  import threading
6
  import base64
7
  import os
8
  import io
9
+ import random
10
+ from flask import Flask, Response, render_template_string, request
11
 
12
  # ===== 1. SETUP =====
13
  os.environ['SDL_VIDEODRIVER'] = 'dummy'
 
27
  "capture_fps": 0,
28
  "streaming": True,
29
  "last_frame_time": time.time(),
30
+ "frames_this_second": 0,
31
+ "frame_id": 0, # Add frame ID to track updates
32
+ "frame_timestamps": {} # Store timestamps for each frame
33
  }
34
 
35
  # ===== 2. CREATE FLASK APP =====
 
42
  <head>
43
  <meta charset="UTF-8">
44
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
45
+ <title>PyGame Streaming - NO CACHE</title>
46
  <style>
47
  body { font-family: Arial; background: #0f172a; color: white; padding: 20px; margin: 0; }
48
  .container { max-width: 800px; margin: 0 auto; text-align: center; }
 
53
  margin: 20px auto;
54
  border: 3px solid #60a5fa;
55
  background: #000;
 
56
  }
57
  #streamImg {
58
  width: 100%;
59
  height: 100%;
60
  object-fit: contain;
 
61
  }
62
  .stats {
 
63
  background: #1e293b;
64
  padding: 20px;
65
  border-radius: 10px;
66
+ margin: 20px auto;
67
+ display: inline-block;
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
+ .warning { color: #f59e0b; background: rgba(245, 158, 11, 0.1); padding: 10px; border-radius: 5px; margin: 10px 0; }
73
  </style>
74
  </head>
75
  <body>
76
  <div class="container">
77
+ <h1>PyGame Streaming - NO CACHE MODE</h1>
78
+ <div class="warning">Flask caching disabled - should update every frame</div>
79
 
80
  <div class="image-container">
81
  <img id="streamImg" src="/stream" alt="Live Stream">
 
85
  <div class="stat-row"><span>Capture FPS:</span><span class="stat-value" id="captureFps">0.0</span></div>
86
  <div class="stat-row"><span>Total Frames:</span><span class="stat-value" id="totalFrames">0</span></div>
87
  <div class="stat-row"><span>Stream FPS:</span><span class="stat-value" id="streamFps">0.0</span></div>
88
+ <div class="stat-row"><span>Frame ID:</span><span class="stat-value" id="frameId">0</span></div>
89
+ <div class="stat-row"><span>Cache Status:</span><span class="stat-value" id="cacheStatus">Unknown</span></div>
 
 
 
90
  </div>
91
  </div>
92
 
93
  <script>
94
  let streamFrameCount = 0;
95
  let lastStreamUpdate = Date.now();
96
+ let lastFrameId = 0;
97
+ let cacheMissCount = 0;
98
 
99
  function updateStream() {
100
  const now = Date.now();
101
  const img = document.getElementById('streamImg');
102
 
103
+ // CRITICAL: Add random parameter to prevent caching
104
+ const randomParam = Math.random().toString(36).substring(7);
105
+ img.src = '/stream?nocache=' + now + '&rand=' + randomParam;
106
 
107
  // Calculate stream FPS
108
  streamFrameCount++;
 
113
  lastStreamUpdate = now;
114
  }
115
 
116
+ // Get frame info
117
+ fetch('/frame_info')
118
  .then(r => r.json())
119
  .then(data => {
120
  document.getElementById('captureFps').textContent = data.capture_fps.toFixed(1);
121
  document.getElementById('totalFrames').textContent = data.frame_count;
122
+ document.getElementById('frameId').textContent = data.frame_id;
123
 
124
+ // Check if frame actually updated
125
+ if (lastFrameId > 0 && data.frame_id === lastFrameId) {
126
+ cacheMissCount++;
127
+ document.getElementById('cacheStatus').textContent = 'STUCK! Same frame: ' + cacheMissCount;
128
+ document.getElementById('cacheStatus').style.color = '#ef4444';
129
+ } else {
130
+ cacheMissCount = 0;
131
+ document.getElementById('cacheStatus').textContent = 'UPDATING βœ“';
132
+ document.getElementById('cacheStatus').style.color = '#10b981';
133
  }
134
+ lastFrameId = data.frame_id;
135
+ })
136
+ .catch(err => {
137
+ console.error('Fetch error:', err);
138
  });
139
+
140
+ // Debug: Check if image actually loaded new data
141
+ img.onload = function() {
142
+ console.log('Image loaded at:', new Date().toISOString());
143
+ };
144
+
145
+ img.onerror = function() {
146
+ console.error('Image failed to load');
147
+ };
148
  }
149
 
150
+ // Poll aggressively
151
+ setInterval(updateStream, 33); // ~30 FPS
152
  updateStream();
153
+
154
+ console.log('NO-CACHE streaming client started');
155
  </script>
156
  </body>
157
  </html>
 
162
  def index():
163
  return render_template_string(HTML)
164
 
165
+ @app.route('/frame_info')
166
+ def frame_info():
167
+ """Return frame information including unique frame ID."""
168
  return {
169
  'frame_count': shared['frame_count'],
170
  'capture_fps': shared['capture_fps'],
171
+ 'frame_id': shared['frame_id'],
172
  'timestamp': time.time(),
173
  'has_frame': shared['latest_frame'] is not None
174
  }
175
 
176
  @app.route('/stream')
177
  def stream():
178
+ """Stream endpoint - COMPLETELY DISABLED CACHE."""
179
+ # Get request timestamp for debugging
180
+ request_time = time.time()
181
+ client_timestamp = request.args.get('nocache', '0')
182
+
183
  if shared['latest_frame']:
184
  try:
185
+ # Decode the frame
186
  image_data = base64.b64decode(shared['latest_frame'])
187
+
188
+ # Create response with ALL cache-disabling headers
189
+ response = Response(
190
  image_data,
191
  mimetype='image/jpeg',
192
  headers={
193
+ # Disable ALL caching
194
+ 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
195
  'Pragma': 'no-cache',
196
+ 'Expires': '0',
197
+ 'X-Accel-Expires': '0',
198
+
199
+ # Add unique headers to prevent caching
200
+ 'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime()),
201
+ 'ETag': f'"{shared["frame_id"]}-{time.time()}"',
202
+
203
+ # Debug info
204
+ 'X-Frame-ID': str(shared['frame_id']),
205
+ 'X-Frame-Count': str(shared['frame_count']),
206
+ 'X-Request-Time': str(request_time),
207
+ 'X-Client-Timestamp': client_timestamp
208
  }
209
  )
210
+
211
+ # Force immediate flush
212
+ response.direct_passthrough = True
213
+
214
+ print(f"βœ… Serving frame {shared['frame_id']} to client {client_timestamp[:10]}...")
215
+ return response
216
+
217
  except Exception as e:
218
+ print(f"❌ Stream error: {e}")
219
+ # Return error image
220
+ return Response(
221
+ b'ERROR',
222
+ mimetype='text/plain',
223
+ headers={'Cache-Control': 'no-cache'}
224
+ )
225
 
226
+ # No frame available - return loading image
227
+ print("⚠️ No frame available yet")
228
+ loading_img = create_loading_image(shared['frame_id'])
229
+ return Response(
230
+ loading_img,
231
+ mimetype='image/png',
232
+ headers={'Cache-Control': 'no-cache'}
233
+ )
234
 
235
+ def create_loading_image(frame_id):
236
+ """Create a simple loading image with frame info."""
237
+ import pygame as pg_local
238
+ surface = pg_local.Surface((STREAM_WIDTH, STREAM_HEIGHT))
239
+ surface.fill((40, 40, 60))
240
+
241
+ # Draw loading text
242
+ font = pg_local.font.Font(None, 24)
243
+ text1 = font.render(f"Loading... Frame {frame_id}", True, (255, 255, 255))
244
+ text2 = font.render(f"Time: {time.strftime('%H:%M:%S')}", True, (200, 200, 255))
245
+
246
+ surface.blit(text1, (STREAM_WIDTH//2 - text1.get_width()//2, STREAM_HEIGHT//2 - 20))
247
+ surface.blit(text2, (STREAM_WIDTH//2 - text2.get_width()//2, STREAM_HEIGHT//2 + 10))
248
+
249
+ # Save to buffer
250
+ buffer = io.BytesIO()
251
+ pg_local.image.save(surface, buffer)
252
+ buffer.seek(0)
253
+ return buffer.getvalue()
254
+
255
+ # ===== 5. CAPTURE LOOP - WITH VISIBLE CHANGES =====
256
+ def capture_loop():
257
+ """Capture loop with VISIBLE changes each frame."""
258
 
259
  try:
260
  pygame.init()
261
+ print("βœ… PyGame initialized")
262
  except Exception as e:
263
  print(f"❌ PyGame init error: {e}")
264
  return
265
 
266
+ # Create surface
267
  screen = pygame.Surface((WIDTH, HEIGHT))
268
 
269
+ # Animation with CHANGING colors and positions
270
+ colors = [
271
+ (255, 80, 80), # Red
272
+ (80, 255, 80), # Green
273
+ (80, 80, 255), # Blue
274
+ (255, 255, 80), # Yellow
275
+ (255, 80, 255), # Magenta
276
+ (80, 255, 255), # Cyan
277
+ ]
278
 
 
279
  circle_x, circle_y = WIDTH // 2, HEIGHT // 2
280
  circle_r = 30
281
+ speed_x, speed_y = 5, 4
282
+ color_index = 0
283
+ pulse_size = 0
284
+ pulse_dir = 1
 
 
285
 
286
  fps_update_time = time.time()
287
  frames_since_update = 0
288
 
289
+ print("🎬 Starting animation with visible changes every frame...")
290
+
291
  try:
292
  while shared["streaming"]:
293
  start_time = time.time()
294
 
295
+ # === UPDATE ANIMATION - MAKE CHANGES VISIBLE ===
296
  circle_x += speed_x
297
  circle_y += speed_y
298
 
299
+ # Bounce
300
  if circle_x - circle_r < 0 or circle_x + circle_r > WIDTH:
301
  speed_x *= -1
302
+ color_index = (color_index + 1) % len(colors)
303
+
304
  if circle_y - circle_r < 0 or circle_y + circle_r > HEIGHT:
305
  speed_y *= -1
306
+ color_index = (color_index + 1) % len(colors)
307
+
308
+ # Pulse effect
309
+ pulse_size += pulse_dir * 0.5
310
+ if pulse_size > 10 or pulse_size < 0:
311
+ pulse_dir *= -1
312
 
313
+ # === DRAW FRAME ===
314
+ # Background with gradient
315
+ for y in range(HEIGHT):
316
+ color_val = 25 + int(y / HEIGHT * 30)
317
+ pygame.draw.line(screen, (color_val, color_val, 65), (0, y), (WIDTH, y))
318
 
319
+ # Draw bouncing circle with current color
320
+ current_color = colors[color_index]
321
+ pygame.draw.circle(screen, current_color, (int(circle_x), int(circle_y)), int(circle_r + pulse_size))
322
+ pygame.draw.circle(screen, (255, 255, 255), (int(circle_x), int(circle_y)), int(circle_r + pulse_size), 3)
323
 
324
+ # Draw trail
325
+ for i in range(1, 6):
326
+ trail_x = circle_x - speed_x * i * 0.2
327
+ trail_y = circle_y - speed_y * i * 0.2
328
+ trail_alpha = 255 - i * 40
329
+ trail_color = (current_color[0], current_color[1], current_color[2], trail_alpha)
330
+ pygame.draw.circle(screen, trail_color[:3], (int(trail_x), int(trail_y)), circle_r - i * 2)
331
 
332
+ # Draw frame info
333
+ font = pygame.font.Font(None, 32)
 
334
 
335
+ # Frame counter with changing color
336
+ counter_color = (100 + (shared['frame_count'] % 155), 255, 100)
337
+ frame_text = font.render(f"Frame: {shared['frame_count']}", True, counter_color)
338
+ screen.blit(frame_text, (10, 10))
 
 
 
 
339
 
340
+ # Position info
341
+ pos_text = font.render(f"Pos: {int(circle_x)},{int(circle_y)}", True, (200, 200, 255))
342
+ screen.blit(pos_text, (10, 50))
343
 
344
+ # Color info
345
+ color_text = font.render(f"Color: {color_index}", True, current_color)
346
+ screen.blit(color_text, (10, 90))
 
347
 
348
+ # Time
349
+ time_text = font.render(time.strftime("%H:%M:%S"), True, (255, 255, 200))
350
+ screen.blit(time_text, (WIDTH - 150, 10))
351
 
352
+ # === SAVE FRAME ===
353
+ # Scale
354
  scaled = pygame.transform.smoothscale(screen, (STREAM_WIDTH, STREAM_HEIGHT))
355
 
356
+ # Save to buffer
357
  buffer = io.BytesIO()
358
+ pygame.image.save(scaled, buffer)
359
  buffer.seek(0)
 
 
360
  image_bytes = buffer.getvalue()
361
+
362
+ # Update shared state
363
  shared["latest_frame"] = base64.b64encode(image_bytes).decode('utf-8')
364
+ shared["frame_id"] += 1 # Increment frame ID
365
+
366
+ # Log every 30 frames (1 second at 30 FPS)
367
+ if shared['frame_count'] % 30 == 0:
368
+ print(f"πŸ“Έ Frame {shared['frame_count']}: Pos({int(circle_x)},{int(circle_y)}) Color:{color_index}")
369
 
370
  # === UPDATE COUNTERS ===
371
  shared["frame_count"] += 1
 
388
  time.sleep(target_time - elapsed)
389
 
390
  except Exception as e:
391
+ print(f"❌ Capture error: {e}")
392
  import traceback
393
  traceback.print_exc()
394
 
395
  pygame.quit()
396
+ print("πŸ›‘ Capture loop stopped")
397
 
398
  # ===== 6. MAIN FUNCTION =====
399
  def main():
400
  print("=" * 60)
401
+ print("πŸš€ PyGame Streaming - FLASK CACHE FIX")
402
  print("=" * 60)
403
  print(f"Port: {PORT}")
404
+ print(f"Cache: DISABLED (force fresh frames)")
405
+ print(f"Changes: Color, position, pulse, trail effects")
 
406
  print("=" * 60)
407
 
408
+ # Start capture
409
+ print("Starting capture thread...")
410
+ capture_thread = threading.Thread(target=capture_loop, daemon=True)
411
  capture_thread.start()
412
 
413
  # Wait for first frame
414
  print("Waiting for first frame...")
415
+ for i in range(50):
416
  if shared['latest_frame']:
417
+ print(f"βœ… First frame ready!")
418
  break
419
  time.sleep(0.1)
420
 
421
+ print(f"Starting Flask on port {PORT}...")
422
  print("=" * 60)
423
 
424
  try:
425
+ # Disable Flask caching completely
426
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
427
+ app.config['TEMPLATES_AUTO_RELOAD'] = True
428
+
429
  app.run(
430
  host='0.0.0.0',
431
  port=PORT,
 
433
  threaded=True,
434
  use_reloader=False
435
  )
 
 
436
  except Exception as e:
437
+ print(f"Server error: {e}")
438
  finally:
439
  shared['streaming'] = False
440
  capture_thread.join(timeout=2)
441
+ print(f"\n🎬 Final: {shared['frame_count']} frames @ {shared['capture_fps']:.1f} FPS")
442
+ print("βœ… Done")
443
 
444
  if __name__ == "__main__":
445
  main()