Skip to content

Commit

Permalink
Add ascii player
Browse files Browse the repository at this point in the history
  • Loading branch information
yorevs committed Jan 10, 2025
1 parent 9d6b2c3 commit b25aa51
Showing 1 changed file with 146 additions and 0 deletions.
146 changes: 146 additions & 0 deletions src/demo/devel/animated_ascii.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import atexit
import shutil
import signal
import threading
from os.path import dirname
import os
from pathlib import Path
from threading import Thread
from typing import Optional, List

import pause
from PIL import Image
from askai.core.component.audio_player import player
from clitt.core.term.cursor import cursor
from hspylib.core.tools.commons import sysout
from hspylib.modules.application.exit_status import ExitStatus
from clitt.core.term.terminal import terminal, Terminal

PALETTES = {
1: " .:-=+*#%@",
2: " .'`\^\",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
3: " ▁▂▃▄▅▆▇█▊",
4: " ░▒▓█▓▒░▒▓",
}

DEFAULT_PALETTE = PALETTES[3]


VIDEO_DIR: Path = Path("/Users/hjunior/GIT-Repository/GitHub/askai/assets/videos")
if not VIDEO_DIR.exists():
VIDEO_DIR.mkdir(parents=True, exist_ok=True)

DATA_PATH: Path = Path(os.path.join(dirname(__file__), 'AscVideos'))
if not DATA_PATH.exists():
DATA_PATH.mkdir(parents=True, exist_ok=True)


def frame_to_ascii(
frame_path: str,
width: int = 80,
palette: str = DEFAULT_PALETTE,
reverse: bool = True
) -> str:
"""TODO"""
num_chars = len(palette if not reverse else palette[::-1])
img = Image.open(frame_path).convert("L")
aspect_ratio = img.height / img.width
new_height = int(width * aspect_ratio * 0.55)
img = img.resize((width, new_height), resample=Image.BILINEAR)
pixels = list(img.getdata())
ascii_str = "".join(palette[min(pixel * num_chars // 256, num_chars - 1)] for pixel in pixels)
ascii_lines = [ascii_str[i:i + width] for i in range(0, len(ascii_str), width)]

return "\n".join(ascii_lines)


def get_frames(frames_path: Path) -> list[str]:
"""TODO"""
ascii_frames: list[str] = []
for frame_file in sorted(os.listdir(frames_path)):
frame_path: str = os.path.join(frames_path, frame_file)
ascii_frame = frame_to_ascii(frame_path)
ascii_frames.append(ascii_frame)

return ascii_frames


def extract_audio_and_video_frames(video_path: Path) -> Optional[tuple[Path, List[str]]]:
"""
Extracts audio and video frames from the given video path.
Returns a tuple of (extracted_audio_path, list_of_frame_paths) if successful,
otherwise returns None on failure.
"""
video_name, _ = os.path.splitext(os.path.basename(video_path))

frame_dir: Path = Path(os.path.join(DATA_PATH, video_name, 'frames'))
audio_dir: Path = Path(os.path.join(DATA_PATH, video_name, 'audio'))

audio_path: Path = Path(os.path.join(audio_dir, "audio.mp3"))

# If output directory doesn't exist, perform extraction
if not frame_dir.exists():
frame_dir.mkdir(parents=True, exist_ok=True)
audio_dir.mkdir(parents=True, exist_ok=True)

# Extract frames
frame_command = f'ffmpeg -i "{video_path}" -vf "fps=10" "{frame_dir}/frame%04d.png"'
_, _, exit_code = terminal.shell_exec(frame_command, shell=True)
if exit_code != ExitStatus.SUCCESS:
return None

# Extract audio
audio_command = f'ffmpeg -i "{video_path}" -q:a 0 -map a "{audio_path}"'
_, _, exit_code = terminal.shell_exec(audio_command, shell=True)
if exit_code != ExitStatus.SUCCESS:
return None

return audio_path, get_frames(frame_dir)


def play_ascii_frames(ascii_frames: list[str], delay_ms: int = 90) -> None:
for f in ascii_frames:
max_y, max_x = shutil.get_terminal_size()
# Print ASCII frame line by line without exceeding window bounds
sysout("%HOM%")
for line in f.splitlines()[:max_y]:
cursor.write(f"{line}%EL0%%EOL%")
pause.milliseconds(delay_ms)


def play_audio(audio_path: str) -> Thread:
thread = threading.Thread(target=player.play_audio_file, args=(audio_path,))
thread.daemon = True # Optional: makes the thread exit when the main program ends
thread.start()
return thread


def setup_terminal():
Terminal.alternate_screen(True)
Terminal.clear()
Terminal.set_show_cursor(False)
signal.signal(signal.SIGINT, cleanup)
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGABRT, cleanup)
atexit.register(cleanup)


def cleanup():
Terminal.clear()
Terminal.alternate_screen(False)
Terminal.set_show_cursor(True)


def play_video(video_name: str) -> None:
# Assuming VIDEO_DIR and extract_video_frames are defined elsewhere in your code
setup_terminal()
video_path: Path = Path(os.path.join(VIDEO_DIR, video_name))
audio_path, video = extract_audio_and_video_frames(video_path)
tha = play_audio(audio_path)
play_ascii_frames(video)
tha.join()
cleanup()


if __name__ == '__main__':
play_video("AskAI-Trailer.mp4")

0 comments on commit b25aa51

Please sign in to comment.