MySafeCode commited on
Commit
4ac5872
·
verified ·
1 Parent(s): ce14a3f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +14 -367
app.py CHANGED
@@ -1,373 +1,20 @@
1
- import pygame
2
- import numpy as np
3
- from flask import Flask, Response, render_template_string
4
- from flask_sock import Sock
5
- import time
6
- import os
7
- import cv2
8
- import threading
9
- import json
10
 
11
- # Initialize Pygame headlessly
12
- os.environ['SDL_VIDEODRIVER'] = 'dummy'
13
- pygame.init()
 
 
 
 
14
 
15
- try:
16
- pygame.mixer.init(frequency=44100, size=-16, channels=2)
17
- print("✅ Audio mixer initialized")
18
- except Exception as e:
19
- print(f"⚠️ Audio mixer not available: {e}")
20
 
21
- app = Flask(__name__)
22
- sock = Sock(app)
23
 
24
- class ShaderRenderer:
25
- def __init__(self, width=640, height=480):
26
- self.width = width
27
- self.height = height
28
- self.mouse_x = width // 2
29
- self.mouse_y = height // 2
30
- self.start_time = time.time()
31
- self.surface = pygame.Surface((width, height))
32
- self.frame_count = 0
33
- self.last_frame_time = time.time()
34
- self.fps = 0
35
- self.button_clicked = False
36
- self.sound_source = 'none'
37
-
38
- def set_mouse(self, x, y):
39
- self.mouse_x = max(0, min(self.width, x))
40
- self.mouse_y = max(0, min(self.height, y))
41
-
42
- def handle_click(self, x, y):
43
- button_rect = pygame.Rect(self.width-200, 120, 180, 40)
44
- if button_rect.collidepoint(x, y):
45
- self.button_clicked = not self.button_clicked
46
- return True
47
- return False
48
-
49
- def render_frame(self):
50
- t = time.time() - self.start_time
51
-
52
- # Calculate FPS
53
- self.frame_count += 1
54
- if time.time() - self.last_frame_time > 1.0:
55
- self.fps = self.frame_count
56
- self.frame_count = 0
57
- self.last_frame_time = time.time()
58
-
59
- # Clear
60
- self.surface.fill((20, 20, 30))
61
- font = pygame.font.Font(None, 24)
62
-
63
- # Draw TOP marker
64
- pygame.draw.rect(self.surface, (255, 100, 100), (10, 10, 100, 30))
65
- text = font.render("TOP", True, (255, 255, 255))
66
- self.surface.blit(text, (20, 15))
67
-
68
- # Draw BOTTOM marker
69
- pygame.draw.rect(self.surface, (100, 255, 100), (10, self.height-40, 100, 30))
70
- text = font.render("BOTTOM", True, (0, 0, 0))
71
- self.surface.blit(text, (20, self.height-35))
72
-
73
- # Draw CLOCK
74
- current_time = time.time()
75
- seconds = int(current_time) % 60
76
- hundredths = int((current_time * 100) % 100)
77
- time_str = f"{seconds:02d}.{hundredths:02d}s"
78
- clock_text = font.render(time_str, True, (0, 255, 255))
79
- self.surface.blit(clock_text, (self.width-150, 40))
80
-
81
- # Draw BUTTON
82
- button_rect = pygame.Rect(self.width-200, 120, 180, 40)
83
- mouse_over = button_rect.collidepoint(self.mouse_x, self.mouse_y)
84
-
85
- if self.button_clicked:
86
- button_color = (0, 200, 0)
87
- elif mouse_over:
88
- button_color = (100, 100, 200)
89
- else:
90
- button_color = (80, 80, 80)
91
-
92
- pygame.draw.rect(self.surface, button_color, button_rect)
93
- pygame.draw.rect(self.surface, (200, 200, 200), button_rect, 2)
94
-
95
- btn_text = "✅ CLICKED!" if self.button_clicked else "🔘 CLICK ME"
96
- text_surf = font.render(btn_text, True, (255, 255, 255))
97
- text_rect = text_surf.get_rect(center=button_rect.center)
98
- self.surface.blit(text_surf, text_rect)
99
-
100
- # Draw circle
101
- circle_size = 30 + int(20 * np.sin(t * 2))
102
- if self.sound_source == 'pygame':
103
- color = (100, 255, 100)
104
- elif self.sound_source == 'browser':
105
- color = (100, 100, 255)
106
- else:
107
- color = (255, 100, 100)
108
-
109
- pygame.draw.circle(self.surface, color,
110
- (self.mouse_x, self.mouse_y), circle_size)
111
-
112
- # Draw grid
113
- for x in range(0, self.width, 50):
114
- alpha = int(40 + 20 * np.sin(x * 0.1 + t))
115
- pygame.draw.line(self.surface, (alpha, alpha, 50),
116
- (x, 0), (x, self.height))
117
- for y in range(0, self.height, 50):
118
- alpha = int(40 + 20 * np.cos(y * 0.1 + t))
119
- pygame.draw.line(self.surface, (alpha, alpha, 50),
120
- (0, y), (self.width, y))
121
-
122
- # FPS counter
123
- fps_text = font.render(f"FPS: {self.fps}", True, (255, 255, 0))
124
- self.surface.blit(fps_text, (self.width-150, self.height-60))
125
-
126
- return pygame.image.tostring(self.surface, 'RGB')
127
-
128
- def get_frame_jpeg(self, quality=70):
129
- frame = self.render_frame()
130
- img = np.frombuffer(frame, dtype=np.uint8).reshape((self.height, self.width, 3))
131
- img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
132
- _, jpeg = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, quality])
133
- return jpeg.tobytes()
134
 
135
- renderer = ShaderRenderer()
136
 
137
- # WebSocket for all interactions
138
- @sock.route('/ws')
139
- def websocket(ws):
140
- """Single WebSocket connection for all interaction"""
141
- while True:
142
- try:
143
- message = ws.receive()
144
- if not message:
145
- continue
146
-
147
- data = json.loads(message)
148
-
149
- if data['type'] == 'mouse':
150
- renderer.set_mouse(data['x'], data['y'])
151
-
152
- elif data['type'] == 'click':
153
- renderer.handle_click(data['x'], data['y'])
154
- # Broadcast button state to all clients? Optional
155
-
156
- elif data['type'] == 'sound':
157
- renderer.sound_source = data['source']
158
-
159
- except:
160
- break
161
-
162
- # MJPEG stream (one long connection, zero API calls)
163
- @app.route('/video.mjpeg')
164
- def video_mjpeg():
165
- def generate():
166
- while True:
167
- frame = renderer.get_frame_jpeg()
168
- yield (b'--frame\r\n'
169
- b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
170
- time.sleep(1/30)
171
-
172
- return Response(
173
- generate(),
174
- mimetype='multipart/x-mixed-replace; boundary=frame'
175
- )
176
-
177
- @app.route('/')
178
- def index():
179
- return render_template_string('''
180
- <!DOCTYPE html>
181
- <html>
182
- <head>
183
- <title>🎮 Pygame + WebSocket</title>
184
- <style>
185
- body {
186
- margin: 0;
187
- background: #0a0a0a;
188
- color: white;
189
- font-family: 'Segoe UI', Arial, sans-serif;
190
- display: flex;
191
- justify-content: center;
192
- align-items: center;
193
- min-height: 100vh;
194
- }
195
- .container {
196
- max-width: 900px;
197
- padding: 20px;
198
- text-align: center;
199
- }
200
- h1 { color: #4CAF50; }
201
- .video-container {
202
- background: #000;
203
- border-radius: 12px;
204
- padding: 5px;
205
- margin: 20px 0;
206
- position: relative;
207
- }
208
- #mjpegImg {
209
- width: 100%;
210
- max-width: 640px;
211
- height: auto;
212
- border-radius: 8px;
213
- cursor: crosshair;
214
- }
215
- .mouse-coords {
216
- position: absolute;
217
- bottom: 10px;
218
- left: 10px;
219
- background: rgba(0,0,0,0.7);
220
- color: #4CAF50;
221
- padding: 5px 10px;
222
- border-radius: 20px;
223
- font-family: monospace;
224
- }
225
- .controls {
226
- background: #1a1a1a;
227
- border-radius: 12px;
228
- padding: 20px;
229
- margin-top: 20px;
230
- }
231
- .sound-buttons {
232
- display: flex;
233
- gap: 10px;
234
- justify-content: center;
235
- margin: 20px 0;
236
- }
237
- button {
238
- background: #333;
239
- color: white;
240
- border: none;
241
- padding: 12px 24px;
242
- border-radius: 8px;
243
- cursor: pointer;
244
- font-weight: bold;
245
- }
246
- button.active {
247
- background: #4CAF50;
248
- box-shadow: 0 0 20px #4CAF50;
249
- }
250
- .status {
251
- display: flex;
252
- justify-content: space-around;
253
- margin-top: 15px;
254
- padding: 10px;
255
- background: #222;
256
- border-radius: 8px;
257
- }
258
- .badge {
259
- padding: 5px 10px;
260
- border-radius: 20px;
261
- background: #333;
262
- }
263
- .badge.green { background: #4CAF50; }
264
- </style>
265
- </head>
266
- <body>
267
- <div class="container">
268
- <h1>🎮 Pygame + WebSocket (Zero API)</h1>
269
-
270
- <div class="video-container">
271
- <img id="mjpegImg" src="/video.mjpeg" crossorigin="anonymous">
272
- <div id="mouseCoords" class="mouse-coords">X: 320, Y: 240</div>
273
- </div>
274
-
275
- <div class="controls">
276
- <h3>🔊 Sound Source</h3>
277
- <div class="sound-buttons">
278
- <button id="btnNone" onclick="setSound('none')" class="active">🔇 None</button>
279
- <button id="btnPygame" onclick="setSound('pygame')">🎮 Pygame</button>
280
- <button id="btnBrowser" onclick="setSound('browser')">🌐 Browser</button>
281
- </div>
282
-
283
- <div class="status">
284
- <div>Connection: <span id="wsStatus" class="badge green">🟢 Connected</span></div>
285
- <div>API Calls: <span class="badge">0</span></div>
286
- </div>
287
-
288
- <p style="color: #666; font-size: 12px; margin-top: 15px;">
289
- ⚡ Zero API polling • All interaction via WebSocket • MJPEG stream
290
- </p>
291
- </div>
292
- </div>
293
-
294
- <audio id="browserAudio" loop style="display:none;">
295
- <source src="/static/sound.mp3" type="audio/mpeg">
296
- </audio>
297
-
298
- <script>
299
- const img = document.getElementById('mjpegImg');
300
- const browserAudio = document.getElementById('browserAudio');
301
-
302
- // WebSocket connection (single socket for everything)
303
- const ws = new WebSocket((location.protocol === 'https:' ? 'wss:' : 'ws:') + '//' + window.location.host + '/ws');
304
-
305
- ws.onopen = () => document.getElementById('wsStatus').innerHTML = '🟢 Connected';
306
- ws.onclose = () => document.getElementById('wsStatus').innerHTML = '🔴 Disconnected';
307
-
308
- // Mouse tracking - send via WebSocket
309
- let mouseTimer;
310
- img.addEventListener('mousemove', (e) => {
311
- const rect = img.getBoundingClientRect();
312
- const x = Math.round((e.clientX - rect.left) * 640 / rect.width);
313
- const y = Math.round((e.clientY - rect.top) * 480 / rect.height);
314
-
315
- document.getElementById('mouseCoords').innerHTML = `X: ${x}, Y: ${y}`;
316
-
317
- // Throttle to 30fps
318
- if (mouseTimer) clearTimeout(mouseTimer);
319
- mouseTimer = setTimeout(() => {
320
- ws.send(JSON.stringify({
321
- type: 'mouse',
322
- x: x,
323
- y: y
324
- }));
325
- }, 33);
326
- });
327
-
328
- // Click handling - send via WebSocket
329
- img.addEventListener('click', (e) => {
330
- const rect = img.getBoundingClientRect();
331
- const x = Math.round((e.clientX - rect.left) * 640 / rect.width);
332
- const y = Math.round((e.clientY - rect.top) * 480 / rect.height);
333
-
334
- ws.send(JSON.stringify({
335
- type: 'click',
336
- x: x,
337
- y: y
338
- }));
339
- });
340
-
341
- // Sound handling
342
- function setSound(source) {
343
- document.getElementById('btnNone').className = source === 'none' ? 'active' : '';
344
- document.getElementById('btnPygame').className = source === 'pygame' ? 'active' : '';
345
- document.getElementById('btnBrowser').className = source === 'browser' ? 'active' : '';
346
-
347
- if (source === 'browser') {
348
- browserAudio.play().catch(e => console.log('Audio error:', e));
349
- } else {
350
- browserAudio.pause();
351
- browserAudio.currentTime = 0;
352
- }
353
-
354
- ws.send(JSON.stringify({
355
- type: 'sound',
356
- source: source
357
- }));
358
- }
359
- </script>
360
- </body>
361
- </html>
362
- ''')
363
-
364
- @app.route('/static/sound.mp3')
365
- def serve_sound():
366
- if os.path.exists('sound.mp3'):
367
- with open('sound.mp3', 'rb') as f:
368
- return Response(f.read(), mimetype='audio/mpeg')
369
- return 'Sound not found', 404
370
-
371
- if __name__ == '__main__':
372
- port = int(os.environ.get('PORT', 7860))
373
- app.run(host='0.0.0.0', port=port, debug=False, threaded=True)
 
1
+ FROM python:3.9-slim
 
 
 
 
 
 
 
 
2
 
3
+ RUN apt-get update && apt-get install -y \
4
+ libsdl2-mixer-2.0-0 \
5
+ libsdl2-image-2.0-0 \
6
+ libsdl2-2.0-0 \
7
+ libsdl2-ttf-2.0-0 \
8
+ ffmpeg \
9
+ && rm -rf /var/lib/apt/lists/*
10
 
11
+ WORKDIR /app
 
 
 
 
12
 
13
+ COPY requirements.txt .
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
 
16
+ COPY . .
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ ENV PORT=7860
19
 
20
+ CMD ["python", "app.py"]