mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-14 22:25:13 +02:00
docs: Update README with strategic vision, translate comments to English, add custom MIT License
This commit is contained in:
parent
7cd84b657b
commit
95a3029679
4 changed files with 155 additions and 109 deletions
26
LICENSE
Normal file
26
LICENSE
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
MIT License (with Anti-Advertisement Restriction)
|
||||
|
||||
Copyright (c) 2026 YusufB5 (ASCILINE)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
ANTI-ADVERTISEMENT RESTRICTION:
|
||||
The permission granted by this license explicitly EXCLUDES the right to use this software, in whole or in part, for the purpose of serving, delivering, or displaying digital advertisements, sponsored content, or any form of commercial marketing to end-users. Any such use immediately terminates this license.
|
||||
30
README.md
30
README.md
|
|
@ -1,10 +1,24 @@
|
|||
# 🌌 ASCILINE Engine
|
||||
|
||||
**ASCILINE** is a high-performance, real-time ASCII video rendering engine for the web. It streams video frames from a Python backend directly into a web browser at **60 FPS** using binary WebSockets and HTML5 Canvas.
|
||||
**ASCILINE** is a high-performance, real-time ASCII video rendering engine. **Our core objective is to transform the web into a highly dynamic and interactive typographic canvas.** By moving away from traditional video players, ASCILINE streams visual data from a Python backend directly into the browser at **60 FPS** as raw, manipulable text.
|
||||
|
||||
 <!-- Replace with your actual GIF -->
|
||||
<p align="center">
|
||||
<img src="https://github.com/user-attachments/assets/cc38d219-b4d2-4873-82dc-2abb179b5665" width="600" alt="Animation" />
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://github.com/user-attachments/assets/6bd7f5c0-81de-49fe-ba0d-9a8872ec8ae3" width="600" alt="Animation-after" />
|
||||
<br>
|
||||
<sub><i>* Showcases rendered using Mode 3 (32K Colors)</i></sub>
|
||||
</p>
|
||||
|
||||
## 🚀 Key Features
|
||||
## 🎯 Strategic Vision & Core Capabilities
|
||||
|
||||
1. **Pure Typographic Manipulation**: The visual stream is not a standard media file—it's raw HTML/Canvas text. This makes the impossible possible: you can apply real-time CSS filters (neon glows, text shadows) to a playing video, dynamically manipulate colors, or let users literally copy a moving visual element with their cursor.
|
||||
2. **Local AI & LLM Ready**: By reducing complex pixel streams into structured logical strings, ASCILINE acts as a perfect bridge for AI. Instead of feeding heavy computer vision models, lightweight text blocks can be fed directly to Local LLMs. Analyzing visual changes becomes as simple as taking a "diff" between two text strings.
|
||||
3. **Ultra-Low Bandwidth & IoT Compatibility**: Standard codecs (H.264/VP9) choke microcontrollers and weak networks. ASCILINE processes the heavy lifting once on the backend, streaming only a few kilobytes of String packets per second via WebSockets. It enables zero-latency live streams on satellite connections, embedded systems, and extreme low-bandwidth environments.
|
||||
4. **Bypassing Browser Constraints**: Modern browsers aggressively throttle autoplay videos, and ad-blockers restrict traditional media frames. To the browser, ASCILINE is simply "JavaScript updating text on a page." This circumvents traditional restrictions, allowing for immediate, unblockable visual streams.
|
||||
|
||||
## 🚀 Technical Features
|
||||
|
||||
- **Real-Time Streaming**: Low-latency video-to-ASCII conversion.
|
||||
- **High Performance**: Uses **HTML5 Canvas** for rendering instead of heavy DOM elements, enabling 60 FPS playback.
|
||||
|
|
@ -63,5 +77,11 @@ The engine supports different fidelity levels via the `--mode` flag:
|
|||
python stream_server.py --mode 5 --cols 240 --rows 100
|
||||
```
|
||||
|
||||
## 📜 License
|
||||
MIT License. Feel free to use and abuse for your own ASCII adventures!
|
||||
## 📜 License & Ethical Guardrails
|
||||
|
||||
**MIT License (with Anti-Ad Restriction)**
|
||||
|
||||
ASCILINE is distributed under the MIT License, but with a strict ethical guardrail.
|
||||
Because this engine bypasses standard browser constraints and ad-blockers (by rendering pure text instead of video), we strictly prohibit its use by ad-networks to serve unblockable advertisements.
|
||||
|
||||
See the [LICENSE](LICENSE) file for the full text, which includes the **ANTI-ADVERTISEMENT RESTRICTION** clause.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"""
|
||||
ascii_video_player.py
|
||||
=====================
|
||||
Modüler, renkli (True Color / 24-bit ANSI), sıfır titremeli ASCII video oynatıcı.
|
||||
Modular, True Color (24-bit ANSI), zero-flicker ASCII video player.
|
||||
|
||||
- VideoDecoder : Video → (gray, color) kare çifti üretir.
|
||||
- AsciiMapper : Gri matris → ASCII karakter + ANSI True Color kodu → String.
|
||||
- TerminalRenderer: Ana döngü, FPS kontrolü, yön tespiti, render.
|
||||
- VideoDecoder : Produces (gray, color) frame pairs from video.
|
||||
- AsciiMapper : Gray matrix -> ASCII character + ANSI True Color code -> String.
|
||||
- TerminalRenderer: Main loop, FPS control, orientation detection, rendering.
|
||||
|
||||
Bağımlılıklar:
|
||||
Dependencies:
|
||||
pip install opencv-python numpy
|
||||
"""
|
||||
|
||||
|
|
@ -18,20 +18,20 @@ import numpy as np
|
|||
import cv2
|
||||
import os
|
||||
|
||||
# PowerShell/CMD (Windows) üzerinde ANSI renk kodlarını aktif etmek için:
|
||||
# Enable ANSI color codes on PowerShell/CMD (Windows):
|
||||
os.system("")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# MODÜL 1 ─ VideoDecoder
|
||||
# MODULE 1 ─ VideoDecoder
|
||||
# ─────────────────────────────────────────────
|
||||
class VideoDecoder:
|
||||
"""
|
||||
Video dosyasını açar ve her kare için (gray, bgr) çifti üretir.
|
||||
Opens the video file and yields (gray, bgr) pair for each frame.
|
||||
|
||||
Renkli render için hem gri (karakter seçimi) hem de
|
||||
orijinal BGR (renk örnekleme) matrisine ihtiyaç var.
|
||||
İkisi de aynı resize işleminden geçer → boyut tutarlılığı garantili.
|
||||
For color rendering, both gray (for character selection) and
|
||||
original BGR (for color sampling) matrices are needed.
|
||||
Both undergo the same resize operation -> size consistency guaranteed.
|
||||
"""
|
||||
|
||||
def __init__(self, path: str, cols: int, rows: int) -> None:
|
||||
|
|
@ -58,7 +58,7 @@ class VideoDecoder:
|
|||
|
||||
small = cv2.resize(frame, self._size, interpolation=cv2.INTER_LINEAR)
|
||||
gray = cv2.cvtColor(small, cv2.COLOR_BGR2GRAY)
|
||||
return gray, small # small = küçültülmüş BGR karesi
|
||||
return gray, small # small = downscaled BGR frame
|
||||
|
||||
def release(self):
|
||||
self._cap.release()
|
||||
|
|
@ -68,81 +68,81 @@ class VideoDecoder:
|
|||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# MODÜL 2 ─ AsciiMapper
|
||||
# MODULE 2 ─ AsciiMapper
|
||||
# ─────────────────────────────────────────────
|
||||
class AsciiMapper:
|
||||
"""
|
||||
Gri + BGR matrisini ANSI True Color kodlarıyla renklendirilmiş
|
||||
ASCII çerçeve dizisine dönüştürür.
|
||||
Converts Gray + BGR matrix into a string of ASCII characters
|
||||
colored with ANSI True Color codes.
|
||||
|
||||
── True Color ANSI Formatı ─────────────────────────────────────────────
|
||||
\033[38;2;R;G;Bm{karakter}\033[0m
|
||||
└─ ön plan rengi ─────────┘
|
||||
── True Color ANSI Format ─────────────────────────────────────────────
|
||||
\033[38;2;R;G;Bm{character}\033[0m
|
||||
└─ foreground color ───────┘
|
||||
|
||||
── Renk Kuantizasyonu (Performans Optimizasyonu) ───────────────────────
|
||||
Her piksel için ayrı bir escape kodu üretmek yerine renk değerleri
|
||||
6-bit'e indirilir (>> 2 << 2, 64 seviye/kanal).
|
||||
Bu sayede ardışık aynı renkli pikseller tek bir escape koduyla
|
||||
temsil edilir → string boyutu ve stdout.write yükü azalır.
|
||||
Gözle algılanabilir renk kaybı olmaz (16M → ~262K renk).
|
||||
── Color Quantization (Performance Optimization) ───────────────────────
|
||||
Instead of generating a separate escape code for every pixel, color values
|
||||
are downsampled to 6-bit (>> 2 << 2, 64 levels/channel).
|
||||
This allows consecutive pixels with the same color to share a single escape code
|
||||
-> reduces string size and stdout.write overhead.
|
||||
There is no visually perceptible loss of color (16M -> ~262K colors).
|
||||
|
||||
── RLE (Run-Length Encoding) ───────────────────────────────────────────
|
||||
Aynı renkteki ardışık karakterler için escape kodu tekrar yazılmaz;
|
||||
yalnızca renk değiştiğinde yeni kod eklenir.
|
||||
Tipik bir karede %40-60 oranında string küçülmesi sağlar.
|
||||
The escape code is not repeated for consecutive characters of the same color;
|
||||
a new code is appended only when the color changes.
|
||||
This provides a 40-60% reduction in string size for a typical frame.
|
||||
"""
|
||||
|
||||
DEFAULT_PALETTE = list(
|
||||
" `.-':_,^=;><+!rc*/z?sLTv)J7(|Fi{C}fI31tlu[neoZ5Yxjya]2ESwqkP6h9d4VpOGbUAKXHm8RD#$Bg0MNWQ%&@"
|
||||
)
|
||||
|
||||
# ANSI sıfırlama + satır başı
|
||||
# ANSI reset + carriage return
|
||||
_RESET = "\033[0m"
|
||||
|
||||
def __init__(self, palette: list[str] | None = None, quantize_bits: int = 0) -> None:
|
||||
"""
|
||||
:param palette: Karakter listesi (None → 93 seviyeli varsayılan)
|
||||
:param quantize_bits: Renk kuantizasyonu için sağdan kaydırma miktarı.
|
||||
2 → 64 seviye/kanal (hızlı),
|
||||
0 → tam 8-bit (en yüksek kalite, varsayılan).
|
||||
:param palette: Character list (None -> 93 level default)
|
||||
:param quantize_bits: Right bit shift amount for color quantization.
|
||||
2 -> 64 levels/channel (fast),
|
||||
0 -> full 8-bit (highest quality, default).
|
||||
"""
|
||||
p = palette or self.DEFAULT_PALETTE
|
||||
self._n = len(p)
|
||||
self._lut = np.array(p, dtype='U1')
|
||||
self._qb = quantize_bits # kuantizasyon bit kaydırma miktarı
|
||||
self._qb = quantize_bits # quantization bit shift amount
|
||||
|
||||
def convert(self, gray: np.ndarray, bgr: np.ndarray) -> str:
|
||||
"""
|
||||
Her piksel için:
|
||||
1. Gri değeri → ASCII karakter (yoğunluk LUT)
|
||||
2. BGR rengi → ANSI True Color escape kodu (kuantize + RLE)
|
||||
For each pixel:
|
||||
1. Gray value -> ASCII character (intensity LUT)
|
||||
2. BGR color -> ANSI True Color escape code (quantized + RLE)
|
||||
|
||||
:param gray: shape=(H,W) uint8 gri matris
|
||||
:param bgr: shape=(H,W,3) uint8 BGR renk matrisi
|
||||
:return: Terminale doğrudan yazılabilecek renkli ASCII dizesi
|
||||
:param gray: shape=(H,W) uint8 gray matrix
|
||||
:param bgr: shape=(H,W,3) uint8 BGR color matrix
|
||||
:return: Colored ASCII string ready to be written directly to the terminal
|
||||
"""
|
||||
H, W = gray.shape
|
||||
|
||||
# ── Adım 1: Piksel yoğunluğu → karakter indeksi ──────────────────
|
||||
# ── Step 1: Pixel intensity -> character index ──────────────────
|
||||
indices = np.floor_divide(gray, max(1, 256 // self._n))
|
||||
np.clip(indices, 0, self._n - 1, out=indices)
|
||||
char_matrix = self._lut[indices] # shape=(H,W), dtype='U1'
|
||||
|
||||
# ── Adım 2: Renk kuantizasyonu ────────────────────────────────────
|
||||
# BGR → RGB sıralaması (ANSI kodu R,G,B sırasında)
|
||||
rgb = bgr[:, :, ::-1] # BGR → RGB view, kopyasız
|
||||
# ── Step 2: Color quantization ────────────────────────────────────
|
||||
# BGR -> RGB order (ANSI code is in R,G,B order)
|
||||
rgb = bgr[:, :, ::-1] # BGR -> RGB view, no copy
|
||||
|
||||
if self._qb > 0:
|
||||
# Düşük bitleri sıfırla → renk hassasiyetini düşür, hızı artır
|
||||
# Zero out the lower bits -> reduce color precision, increase speed
|
||||
qb = self._qb
|
||||
rgb = (rgb >> qb) << qb # örn. qb=2: 0b11111100 maskeleme
|
||||
rgb = (rgb >> qb) << qb # e.g., qb=2: 0b11111100 masking
|
||||
|
||||
# ── Adım 3: RLE ile renkli string birleştirme ─────────────────────
|
||||
# Saf NumPy ile RLE yapılamadığından bu kısım Python döngüsüdür.
|
||||
# Ancak satır başına yalnızca renk değişimlerinde escape kodu yazılır;
|
||||
# tekrarlanan renkler için döngü yükü minimize edilir.
|
||||
# ── Step 3: RLE and colored string construction ─────────────────────
|
||||
# Since RLE cannot be done with pure NumPy, this part uses a Python loop.
|
||||
# However, the escape code is only written when the color changes per row;
|
||||
# loop overhead is minimized for repeated colors.
|
||||
lines = []
|
||||
prev_r = prev_g = prev_b = -1 # önceki renk (ilk piksel hep farklı)
|
||||
prev_r = prev_g = prev_b = -1 # previous color (first pixel is always different)
|
||||
|
||||
for row_idx in range(H):
|
||||
row_chars = char_matrix[row_idx] # shape=(W,) char array
|
||||
|
|
@ -154,7 +154,7 @@ class AsciiMapper:
|
|||
int(row_colors[col_idx, 1]), \
|
||||
int(row_colors[col_idx, 2])
|
||||
|
||||
# RLE: sadece renk değişince yeni escape kodu ekle
|
||||
# RLE: only add a new escape code if the color changes
|
||||
if r != prev_r or g != prev_g or b != prev_b:
|
||||
buf.append(f"\033[38;2;{r};{g};{b}m")
|
||||
prev_r, prev_g, prev_b = r, g, b
|
||||
|
|
@ -167,27 +167,27 @@ class AsciiMapper:
|
|||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# MODÜL 3 ─ TerminalRenderer
|
||||
# MODULE 3 ─ TerminalRenderer
|
||||
# ─────────────────────────────────────────────
|
||||
class TerminalRenderer:
|
||||
"""
|
||||
VideoDecoder → AsciiMapper → stdout akışını yönetir.
|
||||
Manages the flow: VideoDecoder -> AsciiMapper -> stdout.
|
||||
|
||||
Ek özellikler (renkli sürüm):
|
||||
- Başlangıçta terminal arka planını siyaha alır (\033[40m)
|
||||
→ renkli karakterler daha belirgin görünür.
|
||||
- Her kare sonunda \033[0m ile renk sıfırlanır
|
||||
→ sonraki terminal komutları etkilenmez.
|
||||
Additional features (colored version):
|
||||
- Sets terminal background to black initially (\033[40m)
|
||||
-> colored characters appear more prominent.
|
||||
- Resets color with \033[0m at the end of each frame
|
||||
-> prevents affecting subsequent terminal commands.
|
||||
"""
|
||||
|
||||
_CURSOR_HOME = "\033[H"
|
||||
_HIDE_CURSOR = "\033[?25l"
|
||||
_SHOW_CURSOR = "\033[?25h"
|
||||
_BLACK_BG = "\033[40m" # siyah arka plan — kontrast için
|
||||
_BLACK_BG = "\033[40m" # black background — for contrast
|
||||
_RESET_ALL = "\033[0m"
|
||||
_CLEAR_SCREEN = "\033[2J"
|
||||
|
||||
CHAR_RATIO = 0.45 # terminal karakter en-boy oranı düzeltmesi
|
||||
CHAR_RATIO = 0.45 # terminal character aspect ratio correction
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -196,22 +196,22 @@ class TerminalRenderer:
|
|||
quantize_bits: int = 0,
|
||||
) -> None:
|
||||
"""
|
||||
:param path: Video dosyası yolu
|
||||
:param palette: Özel karakter paleti (None → 93 seviyeli)
|
||||
:param quantize_bits: Renk kuantizasyonu (0=tam kalite, 2=hızlı)
|
||||
:param path: Path to video file
|
||||
:param palette: Custom character palette (None -> 93 levels)
|
||||
:param quantize_bits: Color quantization (0=full quality, 2=fast)
|
||||
"""
|
||||
# ── Video meta bilgisi ────────────────────────────────────────────
|
||||
# ── Video metadata ────────────────────────────────────────────
|
||||
_probe = VideoDecoder(path, 2, 2)
|
||||
vid_w, vid_h = _probe.vid_w, _probe.vid_h
|
||||
src_fps = _probe.fps
|
||||
_probe.release()
|
||||
|
||||
# ── Terminal boyutları ────────────────────────────────────────────
|
||||
# ── Terminal dimensions ────────────────────────────────────────────
|
||||
term = shutil.get_terminal_size(fallback=(220, 50))
|
||||
t_cols = term.columns
|
||||
t_lines = term.lines - 2
|
||||
|
||||
# ── Yön tespiti & en-boy oranı korumalı boyutlandırma ─────────────
|
||||
# ── Orientation detection & aspect-ratio-preserving resizing ─────────────
|
||||
orientation = "portrait" if vid_h > vid_w else "landscape"
|
||||
aspect = vid_h / vid_w
|
||||
|
||||
|
|
@ -228,16 +228,16 @@ class TerminalRenderer:
|
|||
cols = t_cols
|
||||
rows = max(1, int(cols * aspect * self.CHAR_RATIO))
|
||||
|
||||
# ── Bilgi ekranı ──────────────────────────────────────────────────
|
||||
# ── Info screen ──────────────────────────────────────────────────
|
||||
print(self._CLEAR_SCREEN)
|
||||
print(
|
||||
f"\033[1m[ASCII Player — True Color]\033[0m\n"
|
||||
f" Yön : {orientation.upper()}\n"
|
||||
f" Video : {vid_w}×{vid_h}\n"
|
||||
f" ASCII : {cols}×{rows} karakter\n"
|
||||
f" FPS : {src_fps:.1f}\n"
|
||||
f" Kuantizasyon: {2**(8-quantize_bits)} seviye/kanal\n"
|
||||
f" Çıkış : Ctrl+C\n"
|
||||
f" Orientation : {orientation.upper()}\n"
|
||||
f" Video : {vid_w}x{vid_h}\n"
|
||||
f" ASCII : {cols}x{rows} characters\n"
|
||||
f" FPS : {src_fps:.1f}\n"
|
||||
f" Quantization: {2**(8-quantize_bits)} levels/channel\n"
|
||||
f" Exit : Ctrl+C\n"
|
||||
)
|
||||
time.sleep(2.0)
|
||||
|
||||
|
|
@ -247,7 +247,7 @@ class TerminalRenderer:
|
|||
self._frame_t = 1.0 / self._fps
|
||||
|
||||
def play(self) -> None:
|
||||
"""Ana oynatma döngüsü."""
|
||||
"""Main playback loop."""
|
||||
stdout = sys.stdout
|
||||
|
||||
stdout.write(self._HIDE_CURSOR + self._BLACK_BG)
|
||||
|
|
@ -276,20 +276,20 @@ class TerminalRenderer:
|
|||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# GİRİŞ NOKTASI
|
||||
# ENTRY POINT
|
||||
# ─────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="True Color ANSI ASCII video oynatıcı — sıfır titreme"
|
||||
description="True Color ANSI ASCII video player — zero flicker"
|
||||
)
|
||||
parser.add_argument("video",
|
||||
help="Video dosyası yolu (MP4, AVI, MKV …)")
|
||||
help="Path to video file (MP4, AVI, MKV ...)")
|
||||
parser.add_argument("--palette", default=None,
|
||||
help="Özel karakter paleti, boşlukla ayrılmış")
|
||||
help="Custom character palette, space-separated")
|
||||
parser.add_argument("--quality", type=int, choices=[0, 1, 2, 3], default=0,
|
||||
help="Renk kalitesi: 0=maksimum kalite, 3=maksimum hız (varsayılan: 0)")
|
||||
help="Color quality: 0=max quality, 3=max speed (default: 0)")
|
||||
args = parser.parse_args()
|
||||
|
||||
custom_palette = args.palette.split() if args.palette else None
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
"""
|
||||
stream_server.py
|
||||
================
|
||||
Video to ASCII çekirdek motorunu HTTP/WebSocket üzerinden web'e yayınlar.
|
||||
Bağımlılıklar: pip install fastapi uvicorn websockets
|
||||
Streams the core Video-to-ASCII engine to the web via HTTP/WebSocket.
|
||||
Dependencies: pip install fastapi uvicorn websockets
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
|
@ -15,7 +15,7 @@ import uvicorn
|
|||
import os
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
|
||||
# Mevcut motoru import ediyoruz (ascii_video_player2.py)
|
||||
# Import the existing engine (ascii_video_player2.py)
|
||||
from ascii_video_player2 import VideoDecoder, AsciiMapper
|
||||
|
||||
app = FastAPI()
|
||||
|
|
@ -31,14 +31,14 @@ def get_html_content():
|
|||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""İstemciye Frontend (HTML/JS/CSS) dosyasını sunar."""
|
||||
"""Serves the Frontend (HTML/JS/CSS) file to the client."""
|
||||
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.
|
||||
Starts decoding the video when a client connects,
|
||||
converts to pure ASCII using AsciiMapper and sends via WebSockets.
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||
try:
|
||||
decoder = VideoDecoder(video_path, cols, rows)
|
||||
except FileNotFoundError:
|
||||
await websocket.send_text("Hata: Video dosyası bulunamadı!")
|
||||
await websocket.send_text("Error: Video file not found!")
|
||||
await websocket.close()
|
||||
return
|
||||
|
||||
|
|
@ -58,34 +58,34 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||
fps = decoder.fps
|
||||
frame_t = 1.0 / fps
|
||||
|
||||
# Karakter → byte kodu lookup tablosu (binary format için)
|
||||
# Character -> byte code lookup table (for binary format)
|
||||
char_byte_lut = np.array([ord(c) for c in mapper._lut], dtype=np.uint8)
|
||||
|
||||
# Kuantizasyon seviyesini bir kere belirle (render_mode sabit)
|
||||
# Set the quantization level once (render_mode is fixed)
|
||||
qb = {5: 0, 4: 2, 3: 3, 2: 5}.get(render_mode, 0)
|
||||
|
||||
# İstemciye meta bilgilerini yolla (cols/rows grid oluşturmak için)
|
||||
# Send meta information to the client (to create cols/rows grid)
|
||||
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)
|
||||
# Decoder iterator yields (gray, bgr) for each frame
|
||||
# Pre-allocate binary frame buffer (reduces GC pressure)
|
||||
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
|
||||
# Common: intensity -> character index
|
||||
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) ---
|
||||
# --- PURE ASCII CONVERSION (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ü) ---
|
||||
# --- COLOR BINARY CONVERSION (numpy, zero Python loops) ---
|
||||
H, W = gray_frame.shape
|
||||
char_codes = char_byte_lut[indices] # (H,W) uint8
|
||||
|
||||
|
|
@ -105,27 +105,27 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||
await asyncio.sleep(wait)
|
||||
|
||||
except (WebSocketDisconnect, ConnectionClosed):
|
||||
print("İstemci yayından ayrıldı.")
|
||||
print("Client disconnected from the stream.")
|
||||
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")
|
||||
parser = argparse.ArgumentParser(description="Real-Time ASCII Web Server")
|
||||
parser.add_argument("video", help="Video file to be streamed", default="video.mp4", nargs='?')
|
||||
parser.add_argument("--port", type=int, default=8000, help="Server port")
|
||||
parser.add_argument("--mode", type=int, choices=[1, 2, 3, 4, 5], default=1, help="Render Mode: 1=B&W, 2=512colors, 3=32K, 4=262K, 5=16M Ultra")
|
||||
parser.add_argument("--cols", type=int, default=200, help="Terminal column width")
|
||||
parser.add_argument("--rows", type=int, default=80, help="Terminal row height")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Argümanları global olarak state içine kaydet
|
||||
# Save arguments globally into the state
|
||||
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.")
|
||||
print(f"[{args.video}] ready to stream. Mode: {args.mode}, Res: {args.cols}x{args.rows}")
|
||||
print(f"Starting server... Please go to http://localhost:{args.port} in your browser.")
|
||||
|
||||
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