Spaces:
Sleeping
Sleeping
| import pygame | |
| import time | |
| import threading | |
| import os | |
| from flask import Flask, Response, render_template_string | |
| from io import BytesIO | |
| from PIL import Image | |
| os.environ['SDL_VIDEODRIVER'] = 'dummy' | |
| pygame.init() | |
| WIDTH, HEIGHT = 400, 300 | |
| shared = { | |
| "frame_count": 0, | |
| "streaming": True, | |
| "last_filename": None | |
| } | |
| def moving_circle_loop(): | |
| screen = pygame.Surface((WIDTH, HEIGHT)) | |
| circle_x, circle_y = WIDTH//2, HEIGHT//2 | |
| speed_x, speed_y = 5, 4 | |
| while shared["streaming"]: | |
| start_time = time.time() | |
| # Move circle | |
| circle_x += speed_x | |
| circle_y += speed_y | |
| if circle_x < 30 or circle_x > WIDTH-30: speed_x *= -1 | |
| if circle_y < 30 or circle_y > HEIGHT-30: speed_y *= -1 | |
| # Draw | |
| screen.fill((25, 25, 45)) | |
| pygame.draw.circle(screen, (255, 80, 80), (int(circle_x), int(circle_y)), 20) | |
| # Create unique filename | |
| shared["frame_count"] += 1 | |
| filename = f"frame_{shared['frame_count'] % 100:02d}.jpg" | |
| # Save to file | |
| frame_str = pygame.image.tostring(screen, 'RGB') | |
| img = Image.frombytes('RGB', (WIDTH, HEIGHT), frame_str) | |
| img.save(filename, quality=90, optimize=True) | |
| # Clean old file | |
| if shared["last_filename"] and os.path.exists(shared["last_filename"]): | |
| os.remove(shared["last_filename"]) | |
| shared["last_filename"] = filename | |
| # Console log | |
| if shared["frame_count"] % 10 == 0: | |
| print(f"Frame {shared['frame_count']}: {filename} | Pos: ({int(circle_x)},{int(circle_y)})") | |
| # Control FPS | |
| elapsed = time.time() - start_time | |
| if elapsed < 1.0/20: | |
| time.sleep(1.0/20 - elapsed) | |
| app = Flask(__name__) | |
| HTML = '''<html> | |
| <head> | |
| <style> | |
| body { background:#0f172a; color:white; text-align:center; padding:20px; } | |
| #streamImg { border:3px solid #60a5fa; width:400px; height:300px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🌀 Moving Circle</h1> | |
| <img id="streamImg" src=""> | |
| <p>Frame: <span id="count">0</span></p> | |
| <script> | |
| function updateStream() { | |
| const img = document.getElementById('streamImg'); | |
| fetch('/current_frame') | |
| .then(r => r.json()) | |
| .then(data => { | |
| if (data.filename) { | |
| const freshUrl = `/${data.filename}?t=${Date.now()}`; | |
| img.src = freshUrl; | |
| document.getElementById('count').textContent = data.frame_count; | |
| } | |
| }); | |
| } | |
| // Poll every 50ms (20 FPS) | |
| setInterval(updateStream, 50); | |
| updateStream(); | |
| </script> | |
| </body> | |
| </html>''' | |
| def index(): | |
| return render_template_string(HTML) | |
| def current_frame(): | |
| return { | |
| 'frame_count': shared['frame_count'], | |
| 'filename': shared.get('last_filename') | |
| } | |
| def serve_frame(filename): | |
| if os.path.exists(filename): | |
| with open(filename, 'rb') as f: | |
| frame_bytes = f.read() | |
| return Response( | |
| frame_bytes, | |
| mimetype='image/jpeg', | |
| headers={ | |
| 'Cache-Control': 'no-cache, no-store, must-revalidate', | |
| 'Pragma': 'no-cache', | |
| 'Expires': '0' | |
| } | |
| ) | |
| # No fallback - just 404 | |
| return Response(status=404) | |
| if __name__ == "__main__": | |
| # Start animation thread | |
| thread = threading.Thread(target=moving_circle_loop, daemon=True) | |
| thread.start() | |
| # Start server | |
| app.run( | |
| host='0.0.0.0', | |
| port=int(os.environ.get('PORT', 7860)), | |
| debug=False, | |
| threaded=True | |
| ) |