docs: Update README with strategic vision, translate comments to English, add custom MIT License

This commit is contained in:
YusufB5 2026-05-03 13:49:22 +03:00
parent 7cd84b657b
commit 95a3029679
4 changed files with 155 additions and 109 deletions

26
LICENSE Normal file
View 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.

View file

@ -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.
![ASCILINE Showcase](https://via.placeholder.com/800x450.png?text=Add+Your+Amazing+ASCII+GIF+Here) <!-- 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.

View file

@ -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

View file

@ -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)