From 720dccb14981e4197b1c8c51f054246e62bb637a Mon Sep 17 00:00:00 2001 From: taisrisk <145309547+taisrisk@users.noreply.github.com> Date: Mon, 15 Jun 2026 15:13:03 +0000 Subject: [PATCH] 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. --- README.md | 17 +++++++++++----- ascii_video_player2.py | 12 ++++++++++- run_server.py | 1 + stream_server.log | 19 +++++++++++++++++ stream_server.py | 46 ++++++++++++++++++++++++++++++++++++------ test.txt | 0 6 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 run_server.py create mode 100644 stream_server.log create mode 100644 test.txt diff --git a/README.md b/README.md index ae5efad..cf90974 100644 --- a/README.md +++ b/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 } ] ``` diff --git a/ascii_video_player2.py b/ascii_video_player2.py index 141dfb7..673ad9f 100644 --- a/ascii_video_player2.py +++ b/ascii_video_player2.py @@ -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: diff --git a/run_server.py b/run_server.py new file mode 100644 index 0000000..15306c0 --- /dev/null +++ b/run_server.py @@ -0,0 +1 @@ +from multiprocessing import Process; import uvicorn; uvicorn.run('stream_server:app', host='127.0.0.1', port=8000, log_level='info') diff --git a/stream_server.log b/stream_server.log new file mode 100644 index 0000000..6f474f2 --- /dev/null +++ b/stream_server.log @@ -0,0 +1,19 @@ + + _ ____ ____ ___ _ ___ _ _ _____ + / \ / ___| / ___|_ _| | |_ _| \ | | ____| + / _ \ \___ \| | | || | | || \| | _| + / ___ \ ___) | |___ | || |___ | || |\ | |___ +/_/ \_\____/ \____|___|_____|___|_| \_|_____| + +═══════════════════════════════════════════════════════ + ▶ Queue : 1 video(s) + ▶ Loop : OFF + ▶ Resolution: 200x(auto) + ▶ Default : mode=1 | pixel=OFF | vol=1 +─────────────────────────────────────────────────────── + 1. video.mp4 (mode=1 vol=1) +═══════════════════════════════════════════════════════ + + 🚀 Server live → http://localhost:8000 + + Type /help for available commands. diff --git a/stream_server.py b/stream_server.py index 1a008b9..d3d4885 100644 --- a/stream_server.py +++ b/stream_server.py @@ -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 diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e69de29