Skip to content

Commit

Permalink
being more idiomatic
Browse files Browse the repository at this point in the history
  • Loading branch information
joelibaceta committed Dec 3, 2018
1 parent e00a1fe commit b837215
Show file tree
Hide file tree
Showing 15 changed files with 820 additions and 137 deletions.
549 changes: 549 additions & 0 deletions .pylintrc

Large diffs are not rendered by default.

20 changes: 15 additions & 5 deletions bin/video-to-ascii
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
#!/usr/bin/env python
#/usr/bin/env python

import sys
import video_to_ascii
import argparse
from video_to_ascii import player

filename = sys.argv[1:][0]
CLI_DESC = ("It is a simple python package to play videos in the terminal"
"using colored characters as pixels or other usefull outputs")

video_to_ascii.play(filename)
EPILOG = ("\033[1;37mThanks for trying video-to-ascii!\033[0m 👍")

PARSER = argparse.ArgumentParser(prog='video-to-ascii', description=CLI_DESC, epilog=EPILOG)
PARSER.add_argument('-f', '--file', type=str, dest='file', help='input video file', action='store', required=True)
PARSER.add_argument('--strategy', default='ascii-color', type=str, dest='strategy',
choices=["ascii-color", "ascii"], help='choose an strategy to render the output', action='store')

ARGS = PARSER.parse_args()

player.play(ARGS.file, strategy=ARGS.strategy)
15 changes: 13 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,31 @@

setup(
name="video_to_ascii",
version="1.1.5",
version="1.1.6",
author="Joel Ibaceta",
author_email="mail@joelibaceta.com",
description="A simple tool to play a video using ascii characters",
license='MIT',
description="It is a simple python package to play videos in the terminal",
long_description="A simple tool to play a video using ascii characters using colored characters as pixels or other usefull outputs",
url="https://github.com/joelibaceta/video-to-ascii",
project_urls={
'Source': 'https://github.com/joelibaceta/video-to-ascii',
'Tracker': 'https://github.com/joelibaceta/video-to-ascii/issues'
},
packages=find_packages(),
include_package_data=True,
install_requires=[
'opencv-python', 'xtermcolor'
],
classifiers=[
"Programming Language :: Python :: 3",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Topic :: Video",
"Topic :: Ascii",
"Topic :: OpenCV",
],
keywords='video ascii terminal opencv',
scripts=['bin/video-to-ascii'],
)
48 changes: 0 additions & 48 deletions video_to_ascii/__init__.py
Original file line number Diff line number Diff line change
@@ -1,48 +0,0 @@
import os
import cv2
import sys
from .image_processor import processor
from .video_processor import engine
import time

def play(filename):

fps = 30
time_delta = 1./fps
str = ''
reader, frames_count = engine.load_video_frames(filename)

while(reader.isOpened()):
t0 = time.clock()
rows, columns = os.popen('stty size', 'r').read().split()

width = reader.get(cv2.CAP_PROP_FRAME_WIDTH)
height = reader.get(cv2.CAP_PROP_FRAME_HEIGHT)

REDUCTION_PERCENT = (float(rows)-1) / height * 100

reduced_width = int(width * REDUCTION_PERCENT / 100)
reduced_height = int(height * REDUCTION_PERCENT / 100)

fill_left_with_blank = max(int(columns) - (reduced_width * 2), 0)
max_width = min((int(columns)/2), (reduced_width))

# read frame by frame
ret, frame = reader.read()
frame = engine.rescale_frame(frame, percent=REDUCTION_PERCENT)

for j in range(reduced_height):
for i in range(max_width):
pixel = frame[j][i]
ascii_char = processor.pixel_to_ascii(pixel)
str += ascii_char
str += (" " * fill_left_with_blank)
str += "\r\n"

t1 = time.clock()
delta = time_delta - (t1 - t0)
if (delta > 0):
time.sleep(delta)
sys.stdout.write( str )
str = ''

67 changes: 67 additions & 0 deletions video_to_ascii/image_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from xtermcolor import colorize
import colorsys

CHARS_LIGHT = ['@', '#', '$', '=', '*', '!', ';', ':', '~', '-', ',', '.', ' ', ' ']
CHARS_FILLED = ['█', '▓', '▒', '░', '□']

def brightness_to_ascii(i, density=0):
"""
Get an apropiate char of brighnes from a rgb color
"""
chars_collection = CHARS_LIGHT
if density == 1:
chars_collection = CHARS_FILLED
max, min = len(chars_collection), 0
index = int((i-min) / (max - min))
return chars_collection[index]

def colorize_char(char, ansi_color):
"""
Get an apropiate char of brighnes from a rgb color
"""
str_colorized = colorize(char, ansi=ansi_color)
return str_colorized

def pixel_to_ascii(pixel, colored=True):
"""
Convert a pixel to char
"""
char = ''
if colored:
b, g, r = pixel[0], pixel[1], pixel[2]
h, s, v = colorsys.rgb_to_hsv(float(r), float(g), float(b))
s = s + 0.3 if s + 0.3 < 1.0 else 1.0
r, g, b = colorsys.hsv_to_rgb(h, s, v)
bright = rgb_to_brightness(pixel)
char = brightness_to_ascii(bright)
ansi_color = rgb_to_ansi(r, g, b)
char = colorize(char*2, ansi=ansi_color)
else:
bright = rgb_to_brightness(pixel)
char = brightness_to_ascii(bright, 0)
return char

def rgb_to_brightness(rgb):
"""
Calc a brighness factor according to rgb color
"""
return int((rgb[0] + rgb[1] + rgb[2]) / 3)

def rgb_to_ansi(r, g, b):
"""
Convert an rgb color to ansi color
"""
r, g, b = int(r), int(g), int(b)
if (r == g & g == b):
if (r < 8):
return int(16)
if (r > 248):
return int(230)
return int(round(((r - 8) / 247) * 24) + 232)
r_in_ansi_range = int(round(float(r) / 51))
g_in_ansi_range = int(round(float(g) / 51))
b_in_ansi_range = int(round(float(b) / 51))
ansi = 16 + (36 * r_in_ansi_range) + (6 * g_in_ansi_range) + b_in_ansi_range
return int(ansi)


Empty file.
34 changes: 0 additions & 34 deletions video_to_ascii/image_processor/color.py

This file was deleted.

34 changes: 0 additions & 34 deletions video_to_ascii/image_processor/processor.py

This file was deleted.

13 changes: 13 additions & 0 deletions video_to_ascii/player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""This module contains a set of functions to use the video_to_ascii from cli"""

from . import video_engine as ve

def play(filename, **kwargs):
"""
Play a video from a file by default using ascii chars in terminal
"""
engine = ve.VideoEngine()
if "strategy" in kwargs:
engine.set_strategy(kwargs["strategy"])
engine.load_video_from_file(filename)
engine.play()
6 changes: 6 additions & 0 deletions video_to_ascii/render_strategy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import ascii_color_strategy as pas

STRATEGIES = {
"default": pas.AsciiColorStrategy(),
"ascii-color": pas.AsciiColorStrategy()
}
109 changes: 109 additions & 0 deletions video_to_ascii/render_strategy/ascii_color_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
This module contains a class AsciiColorStrategy, o process video frames and build an ascii output
"""

import time
import sys
import os
import cv2

from . import render_strategy as re
from .. import image_processor as ipe

class AsciiColorStrategy(re.RenderStrategy):
"""Print each frame in the terminal using ascii characters"""

def convert_frame_pixels_to_ascii(self, frame):
"""
Replace all pixeles with colored chars and return the resulting string
This method iterates each pixel of one video frame
respecting the dimensions of the printing area
to truncate the width if necessary
and use the pixel_to_ascii method to convert one pixel
into a character with the appropriate color.
Finally joins the set of chars in a string ready to print.
Args:
frame: a single video frame
dimensions: an array with the printing area dimensions
in pixels [rows, cols]
Returns:
str: The resulting set of colored chars as a unique string
"""
_, cols = os.popen('stty size', 'r').read().split()
h, w, _ = frame.shape
printing_width = int(min(int(cols), (w*2))/2)
padding_right = max(int(cols) - (w*2), 0)
str = ''
for j in range(h):
for i in range(printing_width):
pixel = frame[j][i]
colored_ascii_char = ipe.pixel_to_ascii(pixel)
str += colored_ascii_char
str += (" " * padding_right)
str += "\r\n"
return str

def render(self, vc):
"""
Iterate each video frame to print a set of ascii chars
This method read each video frame from a opencv video capture
resizing the frame and truncate the width if necessary to
print correcly the final string builded with the method
convert_frame_pixels_to_ascii.
Finally each final string is printed correctly, if the process
was done too fast will sleep the necessary time to comply
with the fps expected (30 fps by default).
Args:
vc: An OpenCV video capture
"""

v_width = vc.get(cv2.CAP_PROP_FRAME_WIDTH)
v_height = vc.get(cv2.CAP_PROP_FRAME_HEIGHT)

fps = 30
time_delta = 1./fps
# read each frame
while(vc.isOpened()):
t0 = time.clock()
_ret, frame = vc.read()
# scale each frame according to terminal dimensions
resized_frame = self.resize_frame(frame)
# convert frame pixels to colored string
str = self.convert_frame_pixels_to_ascii(resized_frame)
# sleep if the process was too fast
t1 = time.clock()
delta = time_delta - (t1 - t0)
if (delta > 0):
time.sleep(delta)
# Print the final string
sys.stdout.write(str)

def resize_frame(self, frame):
"""
Resize a frame to meet the terminal dimensions
Calculating the output terminal dimensions (cols, rows),
we can to get a reduction factor to resize the frame
according to the height of the terminal mainly
to print each frame at a time, using all the available rows
Args:
frame: Frame to resize
Returns:
A resized frame
"""
h, w, _ = frame.shape
rows, _ = os.popen('stty size', 'r').read().split()
reduction_factor = (float(rows)-1) / h * 100
reduced_width = int(w * reduction_factor / 100)
reduced_height = int(h * reduction_factor / 100)
dimension = (reduced_width, reduced_height)
resized_frame = cv2.resize(frame, dimension, interpolation=cv2.INTER_AREA)
return resized_frame

14 changes: 14 additions & 0 deletions video_to_ascii/render_strategy/render_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""This module has an abstract class for render strategies"""

from abc import ABC, abstractmethod

class RenderStrategy(ABC):
"""An abstract class to guide about how to implement a render_strategy class"""

@abstractmethod
def render(self, vc):
"""
This method must be implemented with the necessary instructions to process the video frames and generate the output
"""
raise Exception("NotImplementedException")

Loading

0 comments on commit b837215

Please sign in to comment.