mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-17 22:35:13 +02:00
Initial commit: ASCILINE Engine - Modular & Optimized
This commit is contained in:
commit
7cd84b657b
7 changed files with 1016 additions and 0 deletions
131
stream_server.py
Normal file
131
stream_server.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
"""
|
||||
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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue