Created
April 10, 2022 10:03
-
-
Save Starbuck5/4c9e868c79d7c934a1e911dc78713c15 to your computer and use it in GitHub Desktop.
ASCII Video Player
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
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Relies on pygame, numpy, and opencv (pip install opencv-python).