From 1f1bc480d3bd65f1d8e27c7c25cc9a64adff7699 Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Sat, 9 Apr 2022 13:29:07 +0530 Subject: [PATCH 1/5] added color divisions --- generate.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/generate.py b/generate.py index e6e2698..aa96583 100644 --- a/generate.py +++ b/generate.py @@ -1,6 +1,6 @@ import sys from PIL import Image -import numpy +import numpy as np import time import cv2 import pysrt @@ -47,7 +47,7 @@ def subtitle_show(subs, tstamp_ms): def get_pixel_matrix(image): """Function to get image from the media file and change its dimensions to fit the terminal, then turning it into pixel matrix.""" - image = image.convert("RGB") + image = image.convert("HSV") # current row and column size definitions ac_row, ac_col = image.size @@ -67,9 +67,7 @@ def get_color_matrix(pixels): for row in pixels: color_matrix_row = [] for p in row: - r = round(p[0]/255) - g = round(p[1]/255) - b = round(p[2]/255) + clr = p[0] cNum = 0 if r + g + b != 3: if r == 1 and g == 1: @@ -192,6 +190,17 @@ def read_media(vidfile, option): curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(6, curses.COLOR_BLUE, curses.COLOR_BLACK) + +# defining hsv color limits +# somewhat magic numbers, obtained by opening a color palette and observing the hue at which a color can be described as green +# same procedure for all numbers +gLowerHue = 85 +gUpperHue = 160 +rLowerHue = 340 +rUpperHue = 16 +bLowerHue = 215 +bUpperHue = 255 + if len(sys.argv) == 3: beginX = 0; beginY = 0 height = curses.LINES; width = curses.COLS From c8729e517f46afc6b08be0f7dcf4c497e152a7b2 Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Thu, 28 Apr 2022 11:43:40 +0530 Subject: [PATCH 2/5] Completed HSV based ASCII art generation --- generate.py | 55 ++++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/generate.py b/generate.py index aa96583..dc27f5e 100644 --- a/generate.py +++ b/generate.py @@ -8,7 +8,7 @@ ASCII_CHAR_ARRAY = (" .:-=+*#%@", " .,:ilwW", " ▏▁░▂▖▃▍▐▒▀▞▚▌▅▆▊▓▇▉█", " `^|1aUBN", " .`!?xyWN") -ASCII_CHARS = ASCII_CHAR_ARRAY[2] +ASCII_CHARS = ASCII_CHAR_ARRAY[0] MAX_PIXEL_VALUE = 255 def vid_render(st_matrix, st, ed, option): @@ -67,21 +67,23 @@ def get_color_matrix(pixels): for row in pixels: color_matrix_row = [] for p in row: - clr = p[0] + # Convert values to percentages and normalize + hue = (p[0] / 255) * 179 + sat = (p[1] / 255) * 100 cNum = 0 - if r + g + b != 3: - if r == 1 and g == 1: - cNum = 1 - elif r == 1 and b == 1: - cNum = 2 - elif g == 1 and b == 1: - cNum = 3 - elif r == 1: + if sat >= 30: + if (0 <= hue and hue < rUpperHue) or (hue <= 180 and hue > rLowerHue): cNum = 4 - elif g == 1: + if hue <= gUpperHue and hue > gLowerHue: cNum = 5 - elif b == 1: + if hue <= gLowerHue and hue > rUpperHue: + cNum = 1 + if hue <= bUpperHue and hue > bLowerHue: cNum = 6 + if hue <= bLowerHue and hue > gUpperHue: + cNum = 3 + if hue <= rLowerHue and hue > bUpperHue: + cNum = 2 color_matrix_row.append(cNum) color_matrix.append(color_matrix_row) return color_matrix @@ -96,14 +98,15 @@ def get_intensity_matrix(pixels, option): intensity_matrix_row = [] for p in row: intensity = 0 - if option == 1: - intensity = ((p[0] + p[1] + p[2]) / 3.0) - elif option == 2: - intensity = (max(p[0], p[1], p[2]) + min(p[0], p[1], p[2])) / 2 - elif option == 3: - intensity = (0.299 * p[0] * p[0] + 0.587 * p[1] * p[1] + 0.114 * p[2] * p[2]) ** 0.5 - else: - raise Exception("Unrecognised intensity option: %d" % option) + intensity = p[2] + # if option == 1: + # intensity = ((p[0] + p[1] + p[2]) / 3.0) + # elif option == 2: + # intensity = (max(p[0], p[1], p[2]) + min(p[0], p[1], p[2])) / 2 + # elif option == 3: + # intensity = (0.299 * p[0] * p[0] + 0.587 * p[1] * p[1] + 0.114 * p[2] * p[2]) ** 0.5 + # else: + # raise Exception("Unrecognised intensity option: %d" % option) intensity_matrix_row.append(intensity) intensity_matrix.append(intensity_matrix_row) @@ -194,12 +197,12 @@ def read_media(vidfile, option): # defining hsv color limits # somewhat magic numbers, obtained by opening a color palette and observing the hue at which a color can be described as green # same procedure for all numbers -gLowerHue = 85 -gUpperHue = 160 -rLowerHue = 340 -rUpperHue = 16 -bLowerHue = 215 -bUpperHue = 255 +gLowerHue = 36 +gUpperHue = 80 +rLowerHue = 159 +rUpperHue = 14 +bLowerHue = 104 +bUpperHue = 138 if len(sys.argv) == 3: beginX = 0; beginY = 0 From c0c5e091811b06f99c897ff7ba50be4cee25f5e1 Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Tue, 3 May 2022 17:27:31 +0530 Subject: [PATCH 3/5] Code optimization --- generate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/generate.py b/generate.py index dc27f5e..5f79005 100644 --- a/generate.py +++ b/generate.py @@ -1,8 +1,7 @@ import sys from PIL import Image -import numpy as np import time -import cv2 +from cv2 import cv2 import pysrt import curses @@ -15,7 +14,7 @@ def vid_render(st_matrix, st, ed, option): media.addstr(0, 1, "Video Playback") pixels = [st_matrix[i][:] for i in range (st, ed)] # CONFIG OPTION - intensity measure - intensity_matrix = get_intensity_matrix(pixels, 3) + intensity_matrix = get_intensity_matrix(pixels) intensity_matrix = normalize_intensity_matrix(intensity_matrix) color_matrix = get_color_matrix(pixels) @@ -88,7 +87,7 @@ def get_color_matrix(pixels): color_matrix.append(color_matrix_row) return color_matrix -def get_intensity_matrix(pixels, option): +def get_intensity_matrix(pixels): """Function to set the measure of brightness to be used depending upon the option, choose between three measures namely luminance, lightness and average pixel values From 591774eb54d59c3a104552049b4605c5df8f3bdc Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Fri, 26 Aug 2022 15:27:28 +0530 Subject: [PATCH 4/5] routine: --- generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate.py b/generate.py index 5f79005..ac9e7f4 100644 --- a/generate.py +++ b/generate.py @@ -7,7 +7,7 @@ ASCII_CHAR_ARRAY = (" .:-=+*#%@", " .,:ilwW", " ▏▁░▂▖▃▍▐▒▀▞▚▌▅▆▊▓▇▉█", " `^|1aUBN", " .`!?xyWN") -ASCII_CHARS = ASCII_CHAR_ARRAY[0] +ASCII_CHARS = ASCII_CHAR_ARRAY[3] MAX_PIXEL_VALUE = 255 def vid_render(st_matrix, st, ed, option): From bd81d45952ecb13d81482c32dd3d6a78eb23e43e Mon Sep 17 00:00:00 2001 From: Vishesh-dd4723 Date: Fri, 7 Oct 2022 17:01:27 +0530 Subject: [PATCH 5/5] Code Reformat --- generate.py | 423 ++++++++++++++++++++++------------------------------ 1 file changed, 177 insertions(+), 246 deletions(-) diff --git a/generate.py b/generate.py index b38dbd0..114b293 100644 --- a/generate.py +++ b/generate.py @@ -1,251 +1,182 @@ import sys from PIL import Image -import numpy import time -import cv2 +from cv2 import cv2 import pysrt import curses - -ASCII_CHAR_ARRAY = ( - " .:-=+*#%@", - " .,:ilwW", - " ▏▁░▂▖▃▍▐▒▀▞▚▌▅▆▊▓▇▉█", - " `^|1aUBN", - " .`!?xyWN", -) - -ASCII_CHARS = ASCII_CHAR_ARRAY[3] -MAX_PIXEL_VALUE = 255 - - -def vid_render(st_matrix, st, ed, option): - media.addstr(0, 1, "Video Playback") - pixels = [st_matrix[i][:] for i in range(st, ed)] - # CONFIG OPTION - intensity measure - intensity_matrix = get_intensity_matrix(pixels, 3) - intensity_matrix = normalize_intensity_matrix(intensity_matrix) - color_matrix = get_color_matrix(pixels) - - for i in range(len(intensity_matrix)): - offset = 1 - for j in range(len(intensity_matrix[0])): - intensity = intensity_matrix[i][j] - symbol_index = int(intensity / MAX_PIXEL_VALUE * len(ASCII_CHARS)) - 1 - symbol_index = symbol_index + 1 if symbol_index < 0 else symbol_index - asciiStr = ( - ASCII_CHARS[symbol_index] - + ASCII_CHARS[symbol_index] - + ASCII_CHARS[symbol_index] - ) - if option == 1: - color = color_matrix[i][j] - media.addstr(i + 1, offset, asciiStr, curses.color_pair(color)) - else: - media.addstr(i + 1, offset, asciiStr, curses.color_pair(0)) - offset += 3 - - media.refresh() - - -def subtitle_show(subs, tstamp_ms): - """Function to get subtitles of current frame and display them""" - parts = subs.slice( - starts_before={"milliseconds": int(tstamp_ms)}, - ends_after={"milliseconds": int(tstamp_ms)}, - ) - captions.addstr(0, 1, "Captions") - captions.move(1, 1) - for part in parts: - captions.addstr(part.text, curses.A_BOLD) - - captions.refresh() - - -def get_pixel_matrix(image): - """Function to get image from the media file and change its dimensions to fit the terminal, then turning it into pixel matrix.""" - image = image.convert("RGB") - - # current row and column size definitions - ac_row, ac_col = image.size - # d1 and d2 are the width and height of image resp - size = media.getmaxyx() - d2 = min((size[0] - 2) - 3, int((ac_col * (size[1] - 2)) / ac_row)) - d1 = min(int((size[1] - 2) / 3), int((ac_row * d2) / ac_col)) - - # set image to determined d1 and column size - im = image.resize((d1, d2)) - pixels = list(im.getdata()) - return [pixels[i : i + im.width] for i in range(0, len(pixels), im.width)] - - -def get_color_matrix(pixels): - """Function to get the colour codes (ANSI escape sequences) from RGB values of pixel.""" - color_matrix = [] - for row in pixels: - color_matrix_row = [] - for p in row: - r = round(p[0] / 255) - g = round(p[1] / 255) - b = round(p[2] / 255) - cNum = 0 - if r + g + b != 3: - if r == 1 and g == 1: - cNum = 1 - elif r == 1 and b == 1: - cNum = 2 - elif g == 1 and b == 1: - cNum = 3 - elif r == 1: - cNum = 4 - elif g == 1: - cNum = 5 - elif b == 1: - cNum = 6 - color_matrix_row.append(cNum) - color_matrix.append(color_matrix_row) - return color_matrix - - -def get_intensity_matrix(pixels, option): - """Function to set the measure of brightness to be used depending upon the - option, choose between three measures namely luminance, - lightness and average pixel values - """ - intensity_matrix = [] - for row in pixels: - intensity_matrix_row = [] - for p in row: - intensity = 0 - if option == 1: - intensity = (p[0] + p[1] + p[2]) / 3.0 - elif option == 2: - intensity = (max(p[0], p[1], p[2]) + min(p[0], p[1], p[2])) / 2 - elif option == 3: - intensity = ( - 0.299 * p[0] * p[0] + 0.587 * p[1] * p[1] + 0.114 * p[2] * p[2] - ) ** 0.5 - else: - raise Exception("Unrecognised intensity option: %d" % option) - intensity_matrix_row.append(intensity) - intensity_matrix.append(intensity_matrix_row) - - return intensity_matrix - - -def normalize_intensity_matrix(intensity_matrix): - """Function to normalize the intensity matrix so that values fall between acceptable limits.""" - normalized_intensity_matrix = [] - max_pixel = max(map(max, intensity_matrix)) - min_pixel = min(map(min, intensity_matrix)) - for row in intensity_matrix: - rescaled_row = [] - for p in row: - denm = float(max_pixel - min_pixel) - if denm == 0: - denm = 1 - r = MAX_PIXEL_VALUE * (p - min_pixel) / denm - rescaled_row.append(r) - normalized_intensity_matrix.append(rescaled_row) - - return normalized_intensity_matrix - - -def print_from_image(filename, option): - """Function to take in an image & use its RGB values to decide upon an ASCII character - to represent it. This ASCII character will be based upon the brightness - measure calculated - - Manager function. - """ - try: - with Image.open(filename) as image: - pixels = get_pixel_matrix(image) - vid_render(pixels, 0, len(pixels), option) - except OSError: - print("Could not open image file!") - - -def read_media_sub(vidfile, subfile, option): - """Function to read the media file and pass on data to rendering functions frame by frame.""" - vidcap = cv2.VideoCapture(vidfile) - subs = pysrt.open(subfile) - fps = vidcap.get(cv2.CAP_PROP_FPS) - while vidcap.isOpened(): - # read frames from the image - success, image = vidcap.read() - if not success: - break - dur = time.process_time() - cv2.imwrite("./data/frame.jpg", image) - print_from_image("./data/frame.jpg", option) - subtitle_show(subs, vidcap.get(cv2.CAP_PROP_POS_MSEC)) - dur = time.process_time() - dur - dur *= 1000 - if round(1000 / fps - dur) >= 0: - curses.napms(round(1000 / fps - dur)) - vidcap.release() - cv2.destroyAllWindows() - - -def read_media(vidfile, option): - """Function to read the media file and pass on data to rendering functions frame by frame.""" - vidcap = cv2.VideoCapture(vidfile) - fps = vidcap.get(cv2.CAP_PROP_FPS) - while vidcap.isOpened(): - # read frames from the image - success, image = vidcap.read() - if not success: - break - dur = time.process_time() - cv2.imwrite("./data/frame.jpg", image) - print_from_image("./data/frame.jpg", option) - dur = time.process_time() - dur - dur *= 1000 - if round(1000 / fps - dur) >= 0: - curses.napms(round(1000 / fps - dur)) - vidcap.release() - cv2.destroyAllWindows() - - -stdscr = curses.initscr() -curses.noecho() -curses.cbreak() -curses.start_color() -curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK) -curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) -curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK) -curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK) -curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) -curses.init_pair(6, curses.COLOR_BLUE, curses.COLOR_BLACK) -if len(sys.argv) == 3: - beginX = 0 - beginY = 0 - height = curses.LINES - width = curses.COLS - media = curses.newwin(height - 1, width - 1, beginY, beginX) - media.border(0, 0, 0, 0, 0, 0, 0, 0) - vidfile = sys.argv[1] - colored_output = int(sys.argv[2]) - read_media(vidfile, colored_output) -else: - beginX = 0 - beginY = 0 - height = curses.LINES - 5 - width = curses.COLS - media = curses.newwin(height, width, beginY, beginX) - media.border(0, 0, 0, 0, 0, 0, 0, 0) - beginX = 0 - beginY = curses.LINES - 5 - height = 5 - width = curses.COLS - captions = curses.newwin(height, width, beginY, beginX) - captions.border(0, 0, 0, 0, 0, 0, 0, 0) - vidfile = sys.argv[1] - subfile = sys.argv[2] - colored_output = int(sys.argv[3]) - read_media_sub(vidfile, subfile, colored_output) - -media.getch() -curses.nocbreak() -curses.echo() -curses.endwin() +import numpy as np + +class AMP(): + def __init__(self, chars_id=3, rLH=159, rUH=14, gLH=36, gUH=80, bLH=104, bUH=138): + ASCII_CHAR_ARRAY = (" .:-=+*#%@", " .,:ilwW", " ▏▁░▂▖▃▍▐▒▀▞▚▌▅▆▊▓▇▉█", " `^|1aUBN", " .`!?xyWN") + + self.ASCII_CHARS = ASCII_CHAR_ARRAY[chars_id] + self.MAX_PIXEL_VALUE = 255 + curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_MAGENTA, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(5, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(6, curses.COLOR_BLUE, curses.COLOR_BLACK) + + # defining hsv color limits + # somewhat magic numbers, obtained by opening a color palette and observing the hue at which a color can be described as green + # same procedure for all numbers + self.gLowerHue = gLH + self.gUpperHue = gUH + self.rLowerHue = rLH + self.rUpperHue = rUH + self.bLowerHue = bLH + self.bUpperHue = bUH + + + def vid_render(self, pixels, coloring = True): + """Function to merge and render the ASCII output to the terminal. + @param pixels - Pixel matrix of an image + @param coloring - Option to switch between color and bnw output. + """ + self.media.addstr(0, 1, "Video Playback") + intensity_matrix = pixels[:, :, 2] + intensity_matrix = self.normalize_intensity_matrix(intensity_matrix) + color_matrix = self.get_color_matrix(pixels) + + for i in range(len(intensity_matrix)): + offset = 1 + for j in range(len(intensity_matrix[0])): + symbol_index = int(intensity_matrix[i][j] / self.MAX_PIXEL_VALUE * len(self.ASCII_CHARS)) - 1 + symbol_index += int(symbol_index < 0) + asciiStr = self.ASCII_CHARS[symbol_index] * 3 + self.media.addstr(i + 1, offset, asciiStr, curses.color_pair(color_matrix[i][j] if coloring else 0)) + offset += 3 + + self.media.refresh() + + def subtitle_show(self, subs, tstamp_ms): + """Function to get subtitles of current frame and display them""" + self.captions.clear() + self.captions.border(' ', ' ', 0, 0, ' ', ' ', ' ', ' ') + parts = subs.slice(starts_before={'milliseconds': int(tstamp_ms)}, ends_after={'milliseconds': int(tstamp_ms)}) + self.captions.addstr(0, 1, "Captions") + self.captions.move(1, 0) + for part in parts: + self.captions.addstr(part.text, curses.A_BOLD) + + self.captions.refresh() + + + def get_pixel_matrix(self, image): + """Function to get image from the self.media file and change its dimensions to fit the terminal, then turning it into pixel matrix.""" + image = image.convert("HSV") + + # current row and column size definitions + ac_row, ac_col = image.size + # d1 and d2 are the width and height of image resp + size = self.media.getmaxyx() + d2 = min((size[0] - 2) - 3, int((ac_col * (size[1] - 2)) / ac_row)) + d1 = min(int((size[1] - 2) / 3), int((ac_row * d2) / ac_col)) + + # set image to determined d1 and column size + im = image.resize((d1, d2)) + pixels = np.reshape(im.getdata(), (d2, d1, 3)) + with open('error.txt', 'a') as f: + print(pixels.shape, file=f) + return pixels + + + def get_color_matrix(self, pixels): + """Function to get the colour codes (ANSI escape sequences) from RGB values of pixel.""" + color_matrix = [] + for row in pixels: + color_matrix_row = [] + for p in row: + # Convert values to percentages and normalize + hue = (p[0] / 255) * 179 + sat = (p[1] / 255) * 100 + cNum = 0 + if sat >= 30: + if (0 <= hue and hue < self.rUpperHue) or (hue <= 180 and hue > self.rLowerHue): + cNum = 4 + if hue <= self.gUpperHue and hue > self.gLowerHue: + cNum = 5 + if hue <= self.gLowerHue and hue > self.rUpperHue: + cNum = 1 + if hue <= self.bUpperHue and hue > self.bLowerHue: + cNum = 6 + if hue <= self.bLowerHue and hue > self.gUpperHue: + cNum = 3 + if hue <= self.rLowerHue and hue > self.bUpperHue: + cNum = 2 + color_matrix_row.append(cNum) + color_matrix.append(color_matrix_row) + return np.array(color_matrix) + + + def normalize_intensity_matrix(self, intensity_matrix): + """Function to normalize the intensity matrix so that values fall between acceptable limits.""" + maxval, minval = intensity_matrix.max(), intensity_matrix.min() + if (maxval != minval): intensity_matrix = (intensity_matrix - minval) * self.MAX_PIXEL_VALUE / maxval + return intensity_matrix + + + def print_from_image(self, filename, coloring): + """Function to take in an image & use its RGB values to decide upon an ASCII character + to represent it. This ASCII character will be based upon the brightness + measure calculated + + Manager function. + """ + try: + with Image.open(filename) as image: + pixels = self.get_pixel_matrix(image) + self.vid_render(pixels, coloring) + except OSError: + print("Could not open image file!") + + + def read_media_sub(self, vidfile, coloring, subfile=''): + """Function to read the self.media file and pass on data to rendering functions frame by frame.""" + vidcap = cv2.VideoCapture(vidfile) + if subfile: subs = pysrt.open(subfile,encoding='latin-1') + fps = vidcap.get(cv2.CAP_PROP_FPS) + while vidcap.isOpened(): + # read frames from the image + success, image = vidcap.read() + if not success: break + dur = time.process_time() + cv2.imwrite("./data/frame.jpg", image) + self.print_from_image("./data/frame.jpg", coloring) + if subfile: self.subtitle_show(subs, vidcap.get(cv2.CAP_PROP_POS_MSEC)) + dur = (time.process_time() - dur) * 1000 + if (round(1000/fps - dur) >= 0): curses.napms(round(1000/fps - dur)) + vidcap.release() + cv2.destroyAllWindows() + + + def main(self, argv): + beginX = 0; beginY = 0 + vidfile = argv[1] + colored_output = int(argv[-1]) + + if len(argv) == 3: height = curses.LINES; width = curses.COLS + else: height = curses.LINES - 5; width = curses.COLS + + self.media = curses.newwin(height, width, beginY, beginX) + self.media.border(0, 0, 0, 0, 0, 0, 0, 0) + + if len(argv) == 3: subfile = '' + else: + beginX = 0; beginY = curses.LINES - 5 + height = 5; width = curses.COLS + self.captions = curses.newwin(height, width, beginY, beginX) + self.captions.border(0, 0, 0, 0, 0, 0, 0, 0) + subfile = argv[2] + + self.read_media_sub(vidfile,colored_output,subfile) + + self.media.getch() + +def run(stdscr): + obj = AMP() + obj.main(sys.argv) + +curses.wrapper(run) \ No newline at end of file