-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
148 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |