flaskdockernew / capp.py
MySafeCode's picture
Rename app.py to capp.py
be4effa verified
Raw
History Blame Contribute Delete
13.5 kB
#!/usr/bin/env python3
# shared_memory_working.py - Working shared memory
import pygame
import numpy as np
import time
import threading
import os
from flask import Flask, Response
# ===== 1. SETUP =====
os.environ['SDL_VIDEODRIVER'] = 'dummy'
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
PORT = int(os.getenv('PORT', 7860))
WIDTH, HEIGHT = 400, 300
print(f"πŸš€ Starting on port {PORT}")
# ===== 2. SHARED MEMORY WITH DEFAULT DATA =====
# Create initial frame data immediately (BEFORE any threads start)
initial_frame = np.zeros((HEIGHT, WIDTH, 3), dtype=np.uint8)
initial_frame[:, :] = [100, 150, 200] # Light blue
initial_bytes = initial_frame.tobytes()
shared_state = {
"raw_pixels": initial_bytes, # ALWAYS has data from the start
"frame_count": 0,
"streaming": True,
"lock": threading.Lock(),
"ready": True # Mark as ready immediately
}
# ===== 3. FLASK APP =====
app = Flask(__name__)
# ===== 4. SIMPLE HTML WITH VISIBLE TEST =====
HTML = '''<!DOCTYPE html>
<html>
<head>
<title>Shared Memory WORKING</title>
<style>
body { margin: 0; padding: 20px; background: #111; color: white; font-family: Arial; }
h1 { color: #4CAF50; text-align: center; }
.test-area {
text-align: center;
margin: 30px 0;
padding: 20px;
background: #222;
border-radius: 10px;
}
#streamCanvas {
border: 5px solid #4CAF50;
background: black;
display: block;
margin: 20px auto;
}
.stats {
background: #333;
padding: 15px;
border-radius: 8px;
display: inline-block;
margin: 10px;
}
.success { color: #4CAF50; font-weight: bold; }
.error { color: #f44336; font-weight: bold; }
</style>
</head>
<body>
<h1>🎯 SHARED MEMORY TEST - MUST SHOW IMAGE</h1>
<div class="test-area">
<h2>Canvas should show colors below:</h2>
<canvas id="streamCanvas" width="400" height="300"></canvas>
<div class="stats">
<div>Server Status: <span id="serverStatus" class="success">Connected</span></div>
<div>Frames Received: <span id="framesReceived">0</span></div>
<div>Last Update: <span id="lastUpdate">Never</span></div>
</div>
<div style="margin-top: 20px;">
<button onclick="testConnection()">Test Connection</button>
<button onclick="forceRefresh()">Force Refresh</button>
</div>
</div>
<div style="text-align: center; color: #888; margin-top: 30px;">
If you see a colored canvas above, it's working!<br>
If it's black, check browser console (F12) for errors.
</div>
<script>
const canvas = document.getElementById('streamCanvas');
const ctx = canvas.getContext('2d');
let receivedFrames = 0;
// Initialize with a test pattern immediately
function drawTestPattern() {
const testImage = new ImageData(400, 300);
const data = testImage.data;
// Create a red/gradient test pattern
for (let y = 0; y < 300; y++) {
for (let x = 0; x < 400; x++) {
const i = (y * 400 + x) * 4;
data[i] = x % 256; // Red gradient
data[i + 1] = y % 256; // Green gradient
data[i + 2] = 128; // Constant blue
data[i + 3] = 255; // Alpha
}
}
ctx.putImageData(testImage, 0, 0);
console.log('βœ… Drawn test pattern');
}
// Draw test pattern initially
drawTestPattern();
function updateFrame() {
const startTime = Date.now();
fetch('/frame')
.then(response => {
console.log('Response status:', response.status, 'type:', response.headers.get('content-type'));
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.arrayBuffer();
})
.then(buffer => {
receivedFrames++;
const now = new Date().toLocaleTimeString();
// Update stats
document.getElementById('framesReceived').textContent = receivedFrames;
document.getElementById('lastUpdate').textContent = now;
// Check buffer size
console.log(`πŸ“¦ Received ${buffer.byteLength} bytes`);
if (buffer.byteLength === 0) {
console.warn('⚠️ Empty buffer received');
return;
}
// Create ImageData
const imageData = new ImageData(400, 300);
const rgba = imageData.data;
// Convert RGB to RGBA
const rgb = new Uint8Array(buffer);
// Verify size
const expectedSize = 400 * 300 * 3;
if (rgb.length !== expectedSize) {
console.error(`❌ Wrong size: ${rgb.length} bytes, expected ${expectedSize}`);
return;
}
// Fast conversion
for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) {
rgba[i] = rgb[j];
rgba[i + 1] = rgb[j + 1];
rgba[i + 2] = rgb[j + 2];
rgba[i + 3] = 255;
}
// Draw to canvas
ctx.putImageData(imageData, 0, 0);
const latency = Date.now() - startTime;
console.log(`βœ… Frame ${receivedFrames} drawn, latency: ${latency}ms`);
})
.catch(error => {
console.error('❌ Frame fetch error:', error);
document.getElementById('serverStatus').textContent = 'Error: ' + error.message;
document.getElementById('serverStatus').className = 'error';
// Draw error pattern
const errorImage = new ImageData(400, 300);
const data = errorImage.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = 100; // R
data[i + 1] = 0; // G
data[i + 2] = 0; // B
data[i + 3] = 255; // A
}
ctx.putImageData(errorImage, 0, 0);
});
}
function testConnection() {
console.log('πŸ” Testing connection...');
fetch('/test')
.then(r => r.json())
.then(data => {
console.log('Server test response:', data);
alert('Server OK: ' + JSON.stringify(data));
})
.catch(err => {
console.error('Connection test failed:', err);
alert('Connection failed: ' + err);
});
}
function forceRefresh() {
console.log('πŸ”„ Forcing refresh...');
updateFrame();
}
// Start updating every second (1 FPS for testing)
setInterval(updateFrame, 1000);
// Initial update
setTimeout(updateFrame, 500);
console.log('🟒 Page loaded and ready');
</script>
</body>
</html>'''
@app.route('/')
def index():
return HTML
@app.route('/test')
def test():
"""Simple test endpoint"""
with shared_state['lock']:
return {
'status': 'ok',
'frame_count': shared_state['frame_count'],
'data_size': len(shared_state['raw_pixels']) if shared_state['raw_pixels'] else 0,
'time': time.time()
}
@app.route('/frame')
def get_frame():
"""Return frame data - ALWAYS returns data"""
with shared_state['lock']:
# ALWAYS return data, even if it's the initial frame
data = shared_state['raw_pixels']
frame_num = shared_state['frame_count']
print(f"πŸ“€ Serving frame {frame_num}, {len(data) if data else 0} bytes")
if data is None:
# This should never happen, but just in case
print("⚠️ WARNING: No frame data, sending fallback")
fallback = np.ones((HEIGHT, WIDTH, 3), dtype=np.uint8) * [255, 0, 0] # Red
data = fallback.tobytes()
return Response(
data,
mimetype='application/octet-stream',
headers={
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Content-Type': 'application/octet-stream',
'X-Frame-Number': str(frame_num)
}
)
# ===== 5. RENDER THREAD - UPDATES SHARED MEMORY =====
def render_thread():
"""Update the shared memory with new frames"""
print("🎬 Starting render thread...")
try:
pygame.init()
print("βœ… PyGame initialized")
except Exception as e:
print(f"❌ PyGame init failed: {e}")
# Continue anyway - we already have initial frame
# Create surface
surface = pygame.Surface((WIDTH, HEIGHT))
# Get pixel array
pixels_3d = pygame.surfarray.pixels3d(surface)
# Simple animation
x, y = WIDTH // 2, HEIGHT // 2
dx, dy = 3, 2
color_cycle = 0
while shared_state['streaming']:
try:
# Clear with gradient
for y_pos in range(HEIGHT):
gradient = 50 + int(y_pos / HEIGHT * 100)
pixels_3d[y_pos, :, 0] = gradient # Red
pixels_3d[y_pos, :, 1] = gradient // 2 # Green
pixels_3d[y_pos, :, 2] = 100 # Blue
# Update position
x += dx
y += dy
if x < 20 or x > WIDTH - 20:
dx = -dx
color_cycle = (color_cycle + 1) % 3
if y < 20 or y > HEIGHT - 20:
dy = -dy
color_cycle = (color_cycle + 1) % 3
# Draw ball
colors = [[255, 100, 100], [100, 255, 100], [100, 100, 255]]
current_color = colors[color_cycle]
# Simple circle drawing
for dy_circle in range(-20, 21):
for dx_circle in range(-20, 21):
if dx_circle*dx_circle + dy_circle*dy_circle <= 20*20:
px = x + dx_circle
py = y + dy_circle
if 0 <= px < WIDTH and 0 <= py < HEIGHT:
pixels_3d[py, px] = current_color
# Add text
font = pygame.font.Font(None, 24)
with shared_state['lock']:
frame_num = shared_state['frame_count']
time_text = font.render(time.strftime("%H:%M:%S"), True, (255, 255, 255))
frame_text = font.render(f"Frame: {frame_num}", True, (255, 255, 255))
# Blit text
surface.blit(time_text, (10, 10))
surface.blit(frame_text, (10, 40))
# Convert to bytes
raw_bytes = pixels_3d.tobytes()
# Update shared memory
with shared_state['lock']:
shared_state['raw_pixels'] = raw_bytes
shared_state['frame_count'] += 1
if frame_num % 30 == 0:
print(f"πŸ“Έ Rendered frame {frame_num}")
time.sleep(1/30) # 30 FPS
except Exception as e:
print(f"⚠️ Render error: {e}")
time.sleep(0.1)
print("πŸ›‘ Render thread stopped")
# ===== 6. MAIN =====
def main():
print("="*60)
print("🎯 SHARED MEMORY SERVER STARTING")
print("="*60)
print(f"πŸ“‘ Port: {PORT}")
print(f"🎨 Frame size: {WIDTH}x{HEIGHT}x3 = {WIDTH*HEIGHT*3} bytes")
print(f"πŸ“Š Initial data ready: {len(shared_state['raw_pixels'])} bytes")
print("="*60)
# Start render thread
renderer = threading.Thread(target=render_thread, daemon=True)
renderer.start()
# Give renderer time to start
time.sleep(0.5)
print("🌐 Starting Flask server...")
try:
app.run(
host='0.0.0.0',
port=PORT,
debug=False,
threaded=True,
use_reloader=False
)
except Exception as e:
print(f"❌ Server error: {e}")
finally:
shared_state['streaming'] = False
renderer.join(timeout=1)
print("βœ… Server stopped")
if __name__ == "__main__":
main()