Skip to content

Instantly share code, notes, and snippets.

@Starbuck5
Created April 10, 2022 10:03
Show Gist options
  • Save Starbuck5/4c9e868c79d7c934a1e911dc78713c15 to your computer and use it in GitHub Desktop.
Save Starbuck5/4c9e868c79d7c934a1e911dc78713c15 to your computer and use it in GitHub Desktop.
ASCII Video Player
import cv2
import pygame
import numpy
import functools
# WHAT IS THIS?
# This script is a simple ascii video player made in Python / pygame
# It allows you to drop in videos, or to use the webcam
pygame.init()
select_size = 1280, 720
screen = pygame.display.set_mode(select_size)
clock = pygame.time.Clock()
fps = 140
font = pygame.font.Font("freesansbold.ttf", 10)
display_font = pygame.font.SysFont("Arial", 48)
chars = " .`':il=|fLnvhckeabCUwgAB$NWM@"
thresholds = [0, 6, 12, 18, 27, 39, 48, 54, 60, 69, 75, 81]
thresholds += [90, 96, 102, 111, 117, 123, 132, 138, 144, 153]
thresholds += [159, 165, 171, 180, 186, 213, 243, 249]
characters = [font.render(char, False, "white") for char in chars]
cmw = max(surf.get_width() for surf in characters)
cmh = max(surf.get_height() for surf in characters)
for i in range(len(characters)):
s = pygame.Surface((cmw, cmh))
s.blit(characters[i], (0, 0))
characters[i] = s
cx = cmw
cy = cmh
thresholds = [t * 3 for t in thresholds]
thresholds = list(enumerate(thresholds))
conv = dict()
for x in range(766):
# I know this is horribly inefficient but it only runs once
thresholds.sort(key=lambda y: abs(y[1] - x))
conv[x] = characters[thresholds[0][0]]
vcapture = False
cam = False
select = True
locations = []
def select_option(webcam, vname):
if webcam and vname:
raise ValueError("Pick one!")
if vname:
# frames are in BGR
vcapture = cv2.VideoCapture(vname)
width = int(vcapture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(vcapture.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = int(vcapture.get(cv2.CAP_PROP_FPS)) # frame locked at video FPS
if webcam:
import pygame.camera
pygame.camera.init()
cameras = pygame.camera.list_cameras()
if len(cameras) == 0:
raise ValueError("No webcams detected")
cam = pygame.camera.Camera(cameras[0], (1280, 720))
cam.set_controls(hflip=True) # so it doesn't look too weird
cam.start()
width, height = cam.get_size()
fps = 140 # reasonable FPS limit, only refreshes on new webcam frames
locations.clear()
x = 0
y = 0
while y < height - cy // 2:
while x < width - cx // 2:
locations.append((x + cx // 2, y + cy // 2))
x += cx
y += cy
x = 0
if vname:
return fps, (width, height), vcapture
if webcam:
return fps, (width, height), cam
@functools.lru_cache(maxsize=10000)
def convert(x):
return conv[sum(x)]
running = True
while running:
refresh = False
if vcapture:
frame = vcapture.read()[1]
if frame is None:
select = True
pygame.display.set_mode(select_size)
vcapture = False
fps = 140
else:
frame = numpy.fliplr(frame)
frame = numpy.rot90(frame)
refresh = True
if cam:
if cam.query_image():
frame = pygame.surfarray.pixels3d(cam.get_image())
refresh = True
if select:
screen.fill("lightgreen")
pygame.draw.rect(screen, "springgreen3", screen.get_rect(width=110))
pygame.draw.rect(
screen, "springgreen3", screen.get_rect(width=110, right=screen.get_width())
)
text = display_font.render("ASCII Video Player", True, "black")
screen.blit(text, text.get_rect(midtop=(1280 / 2, 50)))
text = display_font.render(
"To select a video, drag it into the window", True, "black"
)
screen.blit(text, text.get_rect(midtop=(1280 / 2, 200)))
text = display_font.render("To select the webcam, press 0", True, "black")
screen.blit(text, text.get_rect(midtop=(1280 / 2, 300)))
pygame.display.flip()
if refresh:
# screen.blits + generator expression + caching was the most efficient
# way I found to do this
screen.blits(
((convert(tuple(frame[x][y])), (x, y)) for x, y in locations),
doreturn=False,
)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.DROPFILE:
fps, size, vcapture = select_option(False, event.file)
pygame.display.set_mode(size)
select = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_0:
fps, size, cam = select_option(True, False)
pygame.display.set_mode(size)
select = False
if event.key == pygame.K_ESCAPE:
select = True
pygame.display.set_mode(select_size)
vcapture = False
if cam:
cam.stop()
cam = False
fps = 140
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 6:
select = True
pygame.display.set_mode(select_size)
vcapture = False
if cam:
cam.stop()
cam = False
fps = 140
clock.tick(fps)
pygame.display.set_caption(
f"ASCII Video Player ({round(clock.get_fps(), 1)} / {fps} FPS)"
)
if cam:
cam.stop()
pygame.quit()
@Starbuck5
Copy link
Author

Relies on pygame, numpy, and opencv (pip install opencv-python).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment