-
-
Notifications
You must be signed in to change notification settings - Fork 139
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
1 parent
e00a1fe
commit b837215
Showing
15 changed files
with
820 additions
and
137 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 |
---|---|---|
@@ -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) |
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
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 |
---|---|---|
@@ -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 = '' | ||
|
||
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,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.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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,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() |
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,6 @@ | ||
from . import ascii_color_strategy as pas | ||
|
||
STRATEGIES = { | ||
"default": pas.AsciiColorStrategy(), | ||
"ascii-color": pas.AsciiColorStrategy() | ||
} |
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,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 | ||
|
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,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") | ||
|
Oops, something went wrong.