MySafeCode commited on
Commit
c1ecc31
·
verified ·
1 Parent(s): 3a405e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -66
app.py CHANGED
@@ -13,11 +13,12 @@ WIDTH, HEIGHT = 400, 300
13
  shared = {
14
  "frame_count": 0,
15
  "streaming": True,
16
- "x_position": 200, # Track circle position
17
- "current_filename": None
18
  }
19
 
20
  def game_loop():
 
21
  screen = pygame.Surface((WIDTH, HEIGHT))
22
  x, y = 200, 150
23
  speed = 5
@@ -30,28 +31,27 @@ def game_loop():
30
  if x < 30 or x > WIDTH-30:
31
  speed *= -1
32
 
33
- # Draw
34
  screen.fill((25, 25, 45))
35
  pygame.draw.circle(screen, (255, 80, 80), (int(x), int(y)), 20)
 
36
 
37
- # Create UNIQUE filename with position
38
- shared["frame_count"] += 1
39
- filename = f"frame_{shared['frame_count']}_x{x}.jpg"
40
 
41
- # Save directly with PyGame (fastest)
42
- pygame.image.save(screen, filename)
 
43
 
44
- # Update shared state
 
45
  shared["x_position"] = x
46
- shared["current_filename"] = filename
47
 
48
- # Clean old files (keep last 5 only)
49
- all_files = sorted([f for f in os.listdir('.') if f.startswith('frame_')])
50
- for old_file in all_files[:-5]:
51
- try:
52
- os.remove(old_file)
53
- except:
54
- pass
55
 
56
  # Control FPS
57
  elapsed = time.time() - start
@@ -62,50 +62,60 @@ app = Flask(__name__)
62
 
63
  HTML = '''<html>
64
  <body style="background:#0f172a; color:white; padding:20px; text-align:center;">
65
- <h1>🎮 PyGame Stream</h1>
66
 
67
- <!-- Image loads from unique URL each time -->
68
  <img id="stream"
69
  width="400"
70
  height="300"
71
- style="border:3px solid #60a5fa;"
72
- src="/get_frame">
73
 
74
- <div style="margin-top:20px; background:#1e293b; padding:15px; border-radius:10px; display:inline-block;">
75
- Frame: <span id="count">0</span> |
76
- Position: <span id="pos">(200,150)</span> |
77
- File: <span id="file">none</span>
78
  </div>
79
 
80
  <script>
81
- let lastFrame = 0;
 
82
 
83
  function updateStream() {
84
- // Get fresh frame with timestamp
85
  const img = document.getElementById('stream');
86
- img.src = '/get_frame?t=' + Date.now();
87
 
88
- // Get stats
89
- fetch('/stats?t=' + Date.now())
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  .then(r => r.json())
91
  .then(data => {
92
- if (data.frame_count > lastFrame) {
93
- document.getElementById('count').textContent = data.frame_count;
94
- document.getElementById('pos').textContent = `(${data.x},150)`;
95
- document.getElementById('file').textContent = data.filename;
96
- lastFrame = data.frame_count;
97
-
98
- // Visual feedback
99
- img.style.borderColor = '#10b981';
100
- setTimeout(() => {
101
- img.style.borderColor = '#60a5fa';
102
- }, 100);
103
- }
104
  });
105
  }
106
 
107
- // Fast polling - 50ms (20 FPS)
108
- setInterval(updateStream, 50);
 
 
 
 
 
 
 
 
109
  </script>
110
  </body>
111
  </html>'''
@@ -115,25 +125,20 @@ def index():
115
  return render_template_string(HTML)
116
 
117
  @app.route('/stats')
118
- def get_stats():
119
- """Return current game state"""
120
  return {
121
  'frame_count': shared['frame_count'],
122
  'x': shared['x_position'],
123
- 'filename': shared['current_filename'] or 'frame_0.jpg'
124
  }
125
 
126
- @app.route('/get_frame')
127
- def get_frame():
128
- """Serve the LATEST frame file"""
129
- filename = shared.get('current_filename', 'frame_0.jpg')
130
-
131
- if os.path.exists(filename):
132
- with open(filename, 'rb') as f:
133
- data = f.read()
134
-
135
  return Response(
136
- data,
137
  mimetype='image/jpeg',
138
  headers={
139
  'Cache-Control': 'no-cache, no-store, must-revalidate',
@@ -143,29 +148,42 @@ def get_frame():
143
  }
144
  )
145
 
146
- # Fallback: generate a simple frame
147
  screen = pygame.Surface((WIDTH, HEIGHT))
148
  screen.fill((25, 25, 45))
149
  pygame.draw.circle(screen, (255, 80, 80), (200, 150), 20)
150
 
 
 
151
  buf = BytesIO()
152
- pygame.image.save(screen, buf, format='JPEG')
153
- buf.seek(0)
154
 
155
  return Response(buf.getvalue(), mimetype='image/jpeg')
156
 
157
  if __name__ == "__main__":
 
 
158
  # Create initial frame
159
  screen = pygame.Surface((WIDTH, HEIGHT))
160
  screen.fill((25, 25, 45))
161
  pygame.draw.circle(screen, (255, 80, 80), (200, 150), 20)
162
- pygame.image.save(screen, "frame_0.jpg")
163
- shared["current_filename"] = "frame_0.jpg"
164
-
165
- print("✅ Created initial frame_0.jpg")
 
166
 
167
  # Start game loop
168
- threading.Thread(target=game_loop, daemon=True).start()
 
 
 
 
169
 
170
  # Start server
171
- app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
 
 
 
 
 
 
13
  shared = {
14
  "frame_count": 0,
15
  "streaming": True,
16
+ "x_position": 200,
17
+ "last_frame_bytes": None # Store frame as bytes in memory
18
  }
19
 
20
  def game_loop():
21
+ """PyGame draws, PIL converts to web-ready JPEG"""
22
  screen = pygame.Surface((WIDTH, HEIGHT))
23
  x, y = 200, 150
24
  speed = 5
 
31
  if x < 30 or x > WIDTH-30:
32
  speed *= -1
33
 
34
+ # PyGame: Draw to surface
35
  screen.fill((25, 25, 45))
36
  pygame.draw.circle(screen, (255, 80, 80), (int(x), int(y)), 20)
37
+ pygame.draw.circle(screen, (255, 255, 255), (int(x), int(y)), 20, 2)
38
 
39
+ # Convert PyGame surface to PIL Image
40
+ frame_str = pygame.image.tostring(screen, 'RGB')
41
+ img = Image.frombytes('RGB', (WIDTH, HEIGHT), frame_str)
42
 
43
+ # PIL: Optimize for web (small, fast JPEG)
44
+ buf = BytesIO()
45
+ img.save(buf, format='JPEG', quality=85, optimize=True)
46
 
47
+ # Store frame in memory (no filesystem!)
48
+ shared["frame_count"] += 1
49
  shared["x_position"] = x
50
+ shared["last_frame_bytes"] = buf.getvalue()
51
 
52
+ # Log occasionally
53
+ if shared["frame_count"] % 10 == 0:
54
+ print(f"Frame {shared['frame_count']}: X={x}")
 
 
 
 
55
 
56
  # Control FPS
57
  elapsed = time.time() - start
 
62
 
63
  HTML = '''<html>
64
  <body style="background:#0f172a; color:white; padding:20px; text-align:center;">
65
+ <h1>🎮 PyGame + PIL Stream</h1>
66
 
67
+ <!-- Image auto-updates via JavaScript -->
68
  <img id="stream"
69
  width="400"
70
  height="300"
71
+ style="border:3px solid #60a5fa; background:black;">
 
72
 
73
+ <div style="margin:20px auto; background:#1e293b; padding:15px; border-radius:10px; width:400px;">
74
+ <div>Frame: <span id="count">0</span></div>
75
+ <div>Position: X=<span id="pos">200</span></div>
76
+ <div>FPS: <span id="fps">0</span></div>
77
  </div>
78
 
79
  <script>
80
+ let frameCounter = 0;
81
+ let lastUpdate = Date.now();
82
 
83
  function updateStream() {
 
84
  const img = document.getElementById('stream');
85
+ const timestamp = Date.now();
86
 
87
+ // Force fresh image load
88
+ img.src = `/frame?t=${timestamp}`;
89
+
90
+ // Update stats
91
+ frameCounter++;
92
+ const now = Date.now();
93
+ if (now - lastUpdate >= 1000) {
94
+ const fps = frameCounter / ((now - lastUpdate) / 1000);
95
+ document.getElementById('fps').textContent = fps.toFixed(1);
96
+ frameCounter = 0;
97
+ lastUpdate = now;
98
+ }
99
+
100
+ // Get latest position
101
+ fetch(`/stats?t=${timestamp}`)
102
  .then(r => r.json())
103
  .then(data => {
104
+ document.getElementById('count').textContent = data.frame_count;
105
+ document.getElementById('pos').textContent = data.x;
 
 
 
 
 
 
 
 
 
 
106
  });
107
  }
108
 
109
+ // Update every 33ms (~30 FPS)
110
+ setInterval(updateStream, 33);
111
+
112
+ // Image load events for visual feedback
113
+ document.getElementById('stream').onload = function() {
114
+ this.style.borderColor = '#10b981';
115
+ setTimeout(() => this.style.borderColor = '#60a5fa', 100);
116
+ };
117
+
118
+ console.log('Stream started');
119
  </script>
120
  </body>
121
  </html>'''
 
125
  return render_template_string(HTML)
126
 
127
  @app.route('/stats')
128
+ def stats():
129
+ """Return game state as JSON"""
130
  return {
131
  'frame_count': shared['frame_count'],
132
  'x': shared['x_position'],
133
+ 'time': time.time()
134
  }
135
 
136
+ @app.route('/frame')
137
+ def frame():
138
+ """Serve the latest frame as JPEG"""
139
+ if shared["last_frame_bytes"]:
 
 
 
 
 
140
  return Response(
141
+ shared["last_frame_bytes"],
142
  mimetype='image/jpeg',
143
  headers={
144
  'Cache-Control': 'no-cache, no-store, must-revalidate',
 
148
  }
149
  )
150
 
151
+ # Create a default frame if none exists yet
152
  screen = pygame.Surface((WIDTH, HEIGHT))
153
  screen.fill((25, 25, 45))
154
  pygame.draw.circle(screen, (255, 80, 80), (200, 150), 20)
155
 
156
+ frame_str = pygame.image.tostring(screen, 'RGB')
157
+ img = Image.frombytes('RGB', (WIDTH, HEIGHT), frame_str)
158
  buf = BytesIO()
159
+ img.save(buf, format='JPEG')
 
160
 
161
  return Response(buf.getvalue(), mimetype='image/jpeg')
162
 
163
  if __name__ == "__main__":
164
+ print("🚀 Starting PyGame + PIL stream...")
165
+
166
  # Create initial frame
167
  screen = pygame.Surface((WIDTH, HEIGHT))
168
  screen.fill((25, 25, 45))
169
  pygame.draw.circle(screen, (255, 80, 80), (200, 150), 20)
170
+ frame_str = pygame.image.tostring(screen, 'RGB')
171
+ img = Image.frombytes('RGB', (WIDTH, HEIGHT), frame_str)
172
+ buf = BytesIO()
173
+ img.save(buf, format='JPEG')
174
+ shared["last_frame_bytes"] = buf.getvalue()
175
 
176
  # Start game loop
177
+ thread = threading.Thread(target=game_loop, daemon=True)
178
+ thread.start()
179
+
180
+ # Wait a moment for first update
181
+ time.sleep(0.5)
182
 
183
  # Start server
184
+ app.run(
185
+ host='0.0.0.0',
186
+ port=int(os.environ.get('PORT', 7860)),
187
+ debug=False,
188
+ threaded=True
189
+ )