ASCILINE/stream_server.py

131 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
stream_server.py
================
Video to ASCII çekirdek motorunu HTTP/WebSocket üzerinden web'e yayınlar.
Bağımlılıklar: pip install fastapi uvicorn websockets
"""
import asyncio
import numpy as np
import cv2
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
import uvicorn
import os
from websockets.exceptions import ConnectionClosed
# Mevcut motoru import ediyoruz (ascii_video_player2.py)
from ascii_video_player2 import VideoDecoder, AsciiMapper
app = FastAPI()
# Serve static files (style.css, app.js) from the project directory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
app.mount("/static", StaticFiles(directory=BASE_DIR), name="static")
def get_html_content():
html_path = os.path.join(os.path.dirname(__file__), "index.html")
with open(html_path, "r", encoding="utf-8") as f:
return f.read()
@app.get("/")
async def root():
"""İstemciye Frontend (HTML/JS/CSS) dosyasını sunar."""
return HTMLResponse(get_html_content())
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
"""
İstemci bağlandığında videoyu decode etmeye başlar,
AsciiMapper ile saf ASCII'ye çevirir ve WebSockets üzerinden gönderir.
"""
await websocket.accept()
video_path = getattr(app.state, "video_path", "video.mp4")
render_mode = getattr(app.state, "render_mode", 1)
cols = getattr(app.state, "cols", 200)
rows = getattr(app.state, "rows", 80)
try:
decoder = VideoDecoder(video_path, cols, rows)
except FileNotFoundError:
await websocket.send_text("Hata: Video dosyası bulunamadı!")
await websocket.close()
return
mapper = AsciiMapper()
fps = decoder.fps
frame_t = 1.0 / fps
# Karakter → byte kodu lookup tablosu (binary format için)
char_byte_lut = np.array([ord(c) for c in mapper._lut], dtype=np.uint8)
# Kuantizasyon seviyesini bir kere belirle (render_mode sabit)
qb = {5: 0, 4: 2, 3: 3, 2: 5}.get(render_mode, 0)
# İstemciye meta bilgilerini yolla (cols/rows grid oluşturmak için)
await websocket.send_text(f"INIT:{fps}:{render_mode}:{cols}:{rows}")
try:
# Decoder iteratörü her kare için (gray, bgr) döndürüyor
# Binary frame buffer'ı önceden ayır (GC baskısını azalt)
frame_buf = np.empty((rows, cols, 4), dtype=np.uint8) if render_mode > 1 else None
for gray_frame, bgr_frame in decoder:
t0 = asyncio.get_event_loop().time()
# Ortak: yoğunluk → karakter indeksi
indices = np.floor_divide(gray_frame, max(1, 256 // mapper._n))
np.clip(indices, 0, mapper._n - 1, out=indices)
if render_mode == 1:
# --- SAF ASCII DÖNÜŞÜMÜ (text) ---
char_matrix = mapper._lut[indices]
lines = [''.join(row) for row in char_matrix]
await websocket.send_text('\n'.join(lines))
else:
# --- RENKLİ BINARY DÖNÜŞÜMÜ (numpy, sıfır Python döngüsü) ---
H, W = gray_frame.shape
char_codes = char_byte_lut[indices] # (H,W) uint8
rgb = bgr_frame[:, :, ::-1] # BGR → RGB
if qb > 0:
rgb = (rgb >> qb) << qb
# [char, R, G, B] interleaved binary frame
frame_buf[:, :, 0] = char_codes
frame_buf[:, :, 1:] = rgb
await websocket.send_bytes(frame_buf.tobytes())
elapsed = asyncio.get_event_loop().time() - t0
wait = frame_t - elapsed
if wait > 0:
await asyncio.sleep(wait)
except (WebSocketDisconnect, ConnectionClosed):
print("İstemci yayından ayrıldı.")
finally:
decoder.release()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Gerçek Zamanlı ASCII Web Sunucusu")
parser.add_argument("video", help="Yayınlanacak video dosyası", default="video.mp4", nargs='?')
parser.add_argument("--port", type=int, default=8000, help="Sunucu portu")
parser.add_argument("--mode", type=int, choices=[1, 2, 3, 4, 5], default=1, help="Render Modu: 1=B&W, 2=512renk, 3=32K, 4=262K, 5=16M Ultra")
parser.add_argument("--cols", type=int, default=200, help="Terminal kolon genişliği")
parser.add_argument("--rows", type=int, default=80, help="Terminal satır yüksekliği")
args = parser.parse_args()
# Argümanları global olarak state içine kaydet
app.state.video_path = args.video
app.state.render_mode = args.mode
app.state.cols = args.cols
app.state.rows = args.rows
print(f"[{args.video}] yayınlanmaya hazır. Mod: {args.mode}, Çöz: {args.cols}x{args.rows}")
print(f"Sunucu başlatılıyor... Lütfen tarayıcınızdan http://localhost:{args.port} adresine gidin.")
uvicorn.run(app, host="0.0.0.0", port=args.port, ws_ping_interval=None, ws_ping_timeout=None)