mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-17 22:35:13 +02:00
Add 480p, 720p, and 1080p resolution presets
Added `--res` argument to both `stream_server.py` and `ascii_video_player2.py` to easily set higher quality/density ASCII and pixel rendering. The `res` argument maps `480p`, `720p`, and `1080p` presets to appropriate column sizes (854, 1280, and 1920). Updated `README.md` to document the new `--res` flag.
This commit is contained in:
parent
88b261eae9
commit
720dccb149
6 changed files with 83 additions and 12 deletions
17
README.md
17
README.md
|
|
@ -162,13 +162,20 @@ The engine supports different fidelity levels via the `--mode` flag:
|
|||
python stream_server.py --mode 5 --cols 240 --rows 100
|
||||
```
|
||||
### 📐 Resolution & Auto-Scaling
|
||||
By default, you only need to specify the width (`--cols`). ASCILINE will automatically calculate the correct `--rows` based on the source video's aspect ratio to prevent stretching.
|
||||
You can easily control the output quality by using the `--res` flag, which provides convenient presets that automatically set the optimal width for higher quality/density output:
|
||||
- `--res 480p` (Maps to 854 columns)
|
||||
- `--res 720p` (Maps to 1280 columns)
|
||||
- `--res 1080p` (Maps to 1920 columns)
|
||||
|
||||
Alternatively, you can manually specify the width (`--cols`). ASCILINE will automatically calculate the correct `--rows` based on the source video's aspect ratio to prevent stretching.
|
||||
|
||||
- **ASCII Mode Recommended:** `--cols 200` to `--cols 240` (Best balance of text detail and cinematic 30 FPS performance).
|
||||
- **Pixel Mode Recommended:** `--cols 600` to `--cols 900` (Provides near-HD visual quality. Performance heavily depends on your machine's CPU/VRAM).
|
||||
- > **Smart Defaults:** If you do not specify a `--cols` value, ASCILINE automatically defaults to `450` when Pixel Mode is enabled, and `200` for standard ASCII text mode.
|
||||
- > ⚠️ **Hardware Limits & A/V Sync:** If you push the `--cols` too high for your specific hardware (e.g., `1350` on a laptop vs a gaming desktop), the Python backend won't be able to encode and send the massive frames fast enough. When the video stream lags behind the audio, you will experience A/V desync (audio finishing early). If this happens, simply lower your `--cols` value!
|
||||
- > **Smart Defaults:** If you do not specify `--res` or `--cols`, ASCILINE automatically defaults to `450` when Pixel Mode is enabled, and `200` for standard ASCII text mode.
|
||||
- > ⚠️ **Hardware Limits & A/V Sync:** Higher resolutions (like 720p or 1080p) will output incredibly dense and highly detailed ASCII art and pixels, but they require a very fast CPU to process frames in real-time. If you push the resolution too high for your specific hardware (e.g., `--res 1080p` on a standard laptop), the Python backend won't be able to encode and send the massive frames fast enough. When the video stream lags behind the audio, you will experience A/V desync (audio finishing early). If this happens, simply select a lower resolution or lower your `--cols` value!
|
||||
```bash
|
||||
python stream_server.py video.mp4 --mode 5 --res 720p
|
||||
# OR manually specify width
|
||||
python stream_server.py video.mp4 --mode 5 --cols 240
|
||||
# Terminal will show: [AUTO] 1920x1080 → grid 240x67
|
||||
```
|
||||
|
|
@ -189,11 +196,11 @@ python stream_server.py video.mp4 --cols 220 --vol 3 # Loud
|
|||
```
|
||||
|
||||
### Playlist Format (`playlist.json`)
|
||||
Each entry can override the global `--mode`, `--pixel`, `--vol`, and `--cols` defaults:
|
||||
Each entry can override the global `--mode`, `--pixel`, `--vol`, `--cols`, and `--res` defaults:
|
||||
```json
|
||||
[
|
||||
{ "video": "intro.mp4", "mode": 1, "vol": 1 },
|
||||
{ "video": "main.mp4", "mode": 5, "pixel": true, "vol": 3, "cols": 520 },
|
||||
{ "video": "main.mp4", "mode": 5, "pixel": true, "vol": 3, "res": "720p" },
|
||||
{ "video": "outro.mp4", "mode": 3, "vol": 2, "cols": 240 }
|
||||
]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -322,16 +322,26 @@ if __name__ == "__main__":
|
|||
help="Color quality: 0=max quality, 3=max speed (default: 0)")
|
||||
parser.add_argument("-c", "--cols", type=int, default=0,
|
||||
help="Fixed grid width. If 0, auto-fits to terminal (default: 0)")
|
||||
parser.add_argument("--res", type=str, choices=["480p", "720p", "1080p"], default=None,
|
||||
help="Resolution preset (overrides --cols)")
|
||||
args = parser.parse_args()
|
||||
|
||||
custom_palette = args.palette.split() if args.palette else None
|
||||
|
||||
# Map resolution to cols
|
||||
res_cols = None
|
||||
if args.res:
|
||||
res_map = {"480p": 854, "720p": 1280, "1080p": 1920}
|
||||
res_cols = res_map.get(args.res.lower())
|
||||
|
||||
final_cols = res_cols if res_cols is not None else args.cols
|
||||
|
||||
try:
|
||||
renderer = TerminalRenderer(
|
||||
path = args.video,
|
||||
palette = custom_palette,
|
||||
quantize_bits = args.quality,
|
||||
cols = args.cols,
|
||||
cols = final_cols,
|
||||
)
|
||||
renderer.play()
|
||||
except FileNotFoundError as e:
|
||||
|
|
|
|||
1
run_server.py
Normal file
1
run_server.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from multiprocessing import Process; import uvicorn; uvicorn.run('stream_server:app', host='127.0.0.1', port=8000, log_level='info')
|
||||
19
stream_server.log
Normal file
19
stream_server.log
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[36m
|
||||
_ ____ ____ ___ _ ___ _ _ _____
|
||||
/ \ / ___| / ___|_ _| | |_ _| \ | | ____|
|
||||
/ _ \ \___ \| | | || | | || \| | _|
|
||||
/ ___ \ ___) | |___ | || |___ | || |\ | |___
|
||||
/_/ \_\____/ \____|___|_____|___|_| \_|_____|
|
||||
[0m
|
||||
[1;37m═══════════════════════════════════════════════════════[0m
|
||||
[32m▶[0m [1mQueue[0m : 1 video(s)
|
||||
[32m▶[0m [1mLoop[0m : OFF
|
||||
[32m▶[0m [1mResolution[0m: 200x(auto)
|
||||
[32m▶[0m [1mDefault[0m : mode=1 | pixel=OFF | vol=1
|
||||
[1;37m───────────────────────────────────────────────────────[0m
|
||||
1. [36mvideo.mp4[0m (mode=1 vol=1)
|
||||
[1;37m═══════════════════════════════════════════════════════[0m
|
||||
|
||||
[1;32m🚀 Server live →[0m [4;36mhttp://localhost:8000[0m
|
||||
|
||||
[90mType [36m/help[90m for available commands.[0m
|
||||
|
|
@ -41,6 +41,15 @@ def get_video_dimensions(path: str) -> tuple[int, int]:
|
|||
return w, h
|
||||
|
||||
|
||||
def get_cols_from_res(res: str) -> int | None:
|
||||
"""Returns the preset width for a given resolution string."""
|
||||
res_map = {
|
||||
"480p": 854,
|
||||
"720p": 1280,
|
||||
"1080p": 1920
|
||||
}
|
||||
return res_map.get(res.lower() if res else None)
|
||||
|
||||
def calc_auto_rows(cols: int, vid_w: int, vid_h: int, pixel_mode: bool) -> int:
|
||||
"""
|
||||
Calculate rows from video aspect ratio.
|
||||
|
|
@ -114,6 +123,11 @@ def load_folder(folder_path: str, default_mode: int, default_vol: int) -> list[d
|
|||
# Filesystem order (no sort applied)
|
||||
return entries
|
||||
|
||||
class MockArgs:
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def build_queue(args) -> list[dict]:
|
||||
"""
|
||||
Builds the video queue based on argument priority:
|
||||
|
|
@ -131,24 +145,36 @@ def build_queue(args) -> list[dict]:
|
|||
item.setdefault("pixel", args.pixel)
|
||||
|
||||
is_pixel = item.get("pixel", False)
|
||||
default_cols = args.cols if args.cols is not None else (450 if is_pixel else 200)
|
||||
|
||||
item_res = item.get("res", args.res)
|
||||
res_cols = get_cols_from_res(item_res)
|
||||
|
||||
if res_cols is not None:
|
||||
default_cols = res_cols
|
||||
else:
|
||||
default_cols = args.cols if args.cols is not None else (450 if is_pixel else 200)
|
||||
|
||||
item.setdefault("cols", default_cols)
|
||||
item.setdefault("rows", args.rows)
|
||||
return items
|
||||
|
||||
res_cols = get_cols_from_res(args.res)
|
||||
if res_cols is not None:
|
||||
global_default_cols = res_cols
|
||||
else:
|
||||
global_default_cols = args.cols if args.cols is not None else (450 if args.pixel else 200)
|
||||
|
||||
if args.folder:
|
||||
print(f"[FOLDER] Scanning: {args.folder}")
|
||||
items = load_folder(args.folder, args.mode, args.vol)
|
||||
default_cols = args.cols if args.cols is not None else (450 if args.pixel else 200)
|
||||
for item in items:
|
||||
item["pixel"] = args.pixel
|
||||
item["cols"] = default_cols
|
||||
item["cols"] = global_default_cols
|
||||
item["rows"] = args.rows
|
||||
return items
|
||||
|
||||
# Legacy: single video argument
|
||||
default_cols = args.cols if args.cols is not None else (450 if args.pixel else 200)
|
||||
return [{"video": resolve_video_path(args.video), "mode": args.mode, "vol": args.vol, "pixel": args.pixel, "cols": default_cols, "rows": args.rows}]
|
||||
return [{"video": resolve_video_path(args.video), "mode": args.mode, "vol": args.vol, "pixel": args.pixel, "cols": global_default_cols, "rows": args.rows}]
|
||||
|
||||
|
||||
# ── APP STATE ──────────────────────────────────────────────
|
||||
|
|
@ -501,6 +527,7 @@ HELP_TEXT = "\033[1;37m" + """
|
|||
║ \033[32m--pixel\033[1;37m Pixel block mode (with mode 2-5) ║
|
||||
║ \033[32m--cols\033[1;37m \033[35mN\033[1;37m Grid columns (default: 200) ║
|
||||
║ \033[32m--rows\033[1;37m \033[35mN\033[1;37m Grid rows (default: auto) ║
|
||||
║ \033[32m--res\033[1;37m \033[35mR\033[1;37m Resolution preset (480p, 720p, 1080p)║
|
||||
║ ║
|
||||
║ \033[33m─── Playback ───\033[1;37m ║
|
||||
║ \033[32m--vol\033[1;37m \033[35m0-5\033[1;37m Volume (0=mute, 1=normal, 5=2x) ║
|
||||
|
|
@ -609,6 +636,7 @@ if __name__ == "__main__":
|
|||
)
|
||||
render.add_argument("--cols", type=int, default=None, help="Grid columns (default: 200 for text, 450 for pixel)")
|
||||
render.add_argument("--rows", type=int, default=0, help="Grid rows (default: auto from video aspect ratio)")
|
||||
render.add_argument("--res", type=str, choices=["480p", "720p", "1080p"], default=None, help="Resolution preset (overrides --cols)")
|
||||
|
||||
# ── Playback ──
|
||||
playback = parser.add_argument_group('\033[33mPlayback\033[0m')
|
||||
|
|
@ -651,7 +679,13 @@ if __name__ == "__main__":
|
|||
app.state.loop = args.loop
|
||||
app.state.tolerance = {"lossless": 0, "high": 4, "balanced": 8, "low": 16}[args.quality]
|
||||
app.state.debug = args.debug
|
||||
global_default_cols = args.cols if args.cols is not None else (450 if args.pixel else 200)
|
||||
|
||||
res_cols = get_cols_from_res(args.res)
|
||||
if res_cols is not None:
|
||||
global_default_cols = res_cols
|
||||
else:
|
||||
global_default_cols = args.cols if args.cols is not None else (450 if args.pixel else 200)
|
||||
|
||||
app.state.cols = global_default_cols
|
||||
app.state.rows = args.rows
|
||||
|
||||
|
|
|
|||
0
test.txt
Normal file
0
test.txt
Normal file
Loading…
Add table
Add a link
Reference in a new issue