인터넷에 이런 밈이 있습니다.
화면이 있다면 배드애플을 틀어라 🍎
CPU, 계산기 등등 다양한 디스플레이로 해당 뮤비를 실행시키는 원리 자체는 비슷합니다.
이번에는 아스키 아트로 Bad Apple을 실행하도록 하겠습니다.
Bad Apple
니코동 전설의 영상입니다.
해당 영상을 보면서 나오는 특징은 다음과 같습니다.
- 영상 자체 흰색과 검정색으로 이루어져 있다 (흑백 실루엣 애니메이션)
해당 특징을 이용하여 흑과 백으로 구분하여 작성하면 됩니다.
영상을 프레임 단위로 쪼개자

영상을 아스키 아트로 바꾸는 방법은 OpenCV 라이브러리를 이용하는 방법입니다. 영상은 결국 사진의 연속입니다.
이를 이용하여 다음과 같이 진행합니다.
- 프레임 추출 : 영상에서 1장씩 프레임을 읽어옵니다.
- 흑백변환 : 이미 흑백영상이지만, 컴퓨터가 계산하기 쉽도록 명암값으로 변환합니다. (0~255)
- 리사이징 : 원본 영상을 그대로 콘솔에 출력하면 화면을 벗어납니다. 콘솔창 크기에 맞게 이미지 사이즈를 줄입니다.
한마디로 영상을 이미지화하고 이를 콘솔 크기로 변환하는 작업입니다.
영상을 실행하는 방법

위에서 사전 작업이 완료되었다면 텍스트를 변환하고 실행하면 영상이 됩니다.
- 텍스트 변환 : 현재 프레임을 아스키 문자열로 변환 및 출력합니다.
- 콘솔화면 초기화 : 이전 프레임을 콘솔화면으로 지웁니다.
- 출력 : 변환된 텍스트를 출력합니다.
- 프레임 속도 맞추기 : 원본 영상의 프레임 속도에 맞춰 대기 및 다음 프레임으로 넘어갑니다.
- 프레임 스킵 : 원본의 프레임을 못 따라가는 경우 다음 프레임을 건너뜁니다.
추가적인 디테일
- 아스키영상이 실행할 때 소리도 나오도록 수정
- 깜빡임 방지 코드 추가 ( '\033[H' ) - cls나 clear를 할 때 화면 전체를 매번 지우기 때문에 번쩍거림이 일어납니다. 이를 화면 맨 위 좌측(0,0)으로 이동시켜 깜빵이를 방지합니다.
- 싱크를 맞추는 로직 실행 - time.sleep(1/fps)로 실행이 된다면 CPU 부하에 따른 프레임이 밀리는 현상을 방지하기 위함입니다.
패키지 설치
pip install opencv-python pygame moviepy
영상을 쪼개고 실행하는 코드들의 패키지입니다. 설치하도록 합니다.
또한 영상을 쪼개야하므로 같은 디렉토리에 mp4파일을 다운로드 합니다.
소스코드
import cv2, pygame
import time, sys, os
from moviepy import VideoFileClip
ASCII_CHARS = [" ", ".", "-", "+", "*", "%", "@", "█"]
# 이미지프레임을 ASCII 규격으로 변경
def frame2Ascii(frame, new_width=100):
height, width = frame.shape
ratio = height / width / 1.65 # 16:9 비뮬 맞추기 위함
new_height = int(new_width * ratio)
resized_frame = cv2.resize(frame, (new_width, new_height))
ascii_frame = ""
for y in range(new_height):
for x in range(new_width):
pixel_value = resized_frame[y, x]
index = int((pixel_value / 255) * (len(ASCII_CHARS) - 1))
ascii_frame += ASCII_CHARS[index]
ascii_frame += "\n"
return ascii_frame
# 동영상 소리도 나오도록 오디오 임시 추출
def extractAudio(video_path, audio_path="audio.mp3"):
print("영상에서 오디오를 추출 중입니다...")
video = VideoFileClip(video_path)
video.audio.write_audiofile(audio_path, logger=None)
return True
def playBadApple(video_path):
audio_path = "audio.mp3"
# 1. 오디오 추출
if not extractAudio(video_path, audio_path):
return
# 2. 오디오 초기화 및 로드
pygame.mixer.init()
pygame.mixer.music.load(audio_path)
# 3. 영상 캡처 객체 생성
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
if fps == 0: fps = 30
# 터미널 화면 한 번 깨끗하게 지우기
os.system('cls' if os.name == 'nt' else 'clear')
# 4. 오디오 재생 시작!
pygame.mixer.music.play()
# 싱크를 맞추기 위한 시간 기록
start_time = time.time()
frame_count = 0
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame_count += 1
expected_time = frame_count / fps
current_time = time.time() - start_time
# 영상이 실제 시간보다 50ms 이상 뒤처지면 프레임 스킵
if current_time - expected_time > 0.05:
# 프레임 스킵!
continue
# 시간이 넉넉하다면 정상적으로 흑백 변환 및 아스키 작업 수행
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ascii_str = frame2Ascii(gray_frame, new_width=100)
sys.stdout.write('\033[H')
sys.stdout.write(ascii_str)
sys.stdout.flush()
# 화면에 뿌린 직후의 시간으로 대기 시간을 재계산
current_time_after_render = time.time() - start_time
sleep_time = expected_time - current_time_after_render
if sleep_time > 0:
time.sleep(sleep_time)
cap.release()
pygame.mixer.music.stop()
pygame.mixer.quit()
time.sleep(0.5)
if os.path.exists(audio_path):
os.remove(audio_path)
print("\nBad Apple!! 재생 완료.")
# --- 실행 부분 ---
playBadApple('bad_apple.mp4')
실행 화면
전체적인 싱크는 맞으나 원본 프레임과 조금씩 안 맞는 것은 사양 이슈가 발생했습니다. 이에따른 스킵과정입니다.
해당 영상 뿐만 아니라 다양한 영상에서도 위와 같이 진행할 수 있습니다.
'컴퓨터 > Python' 카테고리의 다른 글
| Python (30) - 텔레그램 챗봇 환경 만들기 (0) | 2026.02.20 |
|---|---|
| Python (29) - YOLOv5 모델을 이용한 애니메이션 이미지를 검출해보자 (0) | 2025.10.07 |
| Python (28) - 오버로딩(Overloading)과 오버라이딩(Overriding) (1) | 2025.05.04 |
| Python (27) - PC에서 유튜브 음악을 실행해보자 (pytubefix) (0) | 2025.03.10 |
| Python (26) - Flask를 활용하여 웹서버를 만들어보자 (0) | 2024.11.07 |