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 dab76aa
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions src/demo/devel/animated_ascii.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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: " %#*+=-:. ",
3: " ▁▂▃▄▅▆▇█",
4: " ░▒▓█",
5: " ▉▊▋▌▍▎▏",
}

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,
inverse: bool = False
) -> str:
"""TODO"""
num_chars = len(palette if not inverse 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.
"""
name, _ = os.path.splitext(os.path.basename(video_path))
frame_dir: Path = Path(os.path.join(DATA_PATH, name, 'frames'))
audio_dir: Path = Path(os.path.join(DATA_PATH, name, 'audio'))

# Define the expected audio output path
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

# If directory already existed, assume audio was extracted to the expected path
# (This assumes that the audio file exists if out_dir exists. Adjust logic if needed.)

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.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 dab76aa

Please sign in to comment.