From 293864d52616aa70102514de7c620a739689193b Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Sun, 10 Apr 2022 13:28:29 +0530 Subject: [PATCH 1/6] changed default character options --- generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate.py b/generate.py index e6e2698..8b0b9c6 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[3] MAX_PIXEL_VALUE = 255 def vid_render(st_matrix, st, ed, option): From f9cf70ab1ab17aa57067f3f280916249dbb4de6c Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Fri, 27 May 2022 22:15:02 +0530 Subject: [PATCH 2/6] Reformatted code --- generate.py | 81 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 25 deletions(-) diff --git a/generate.py b/generate.py index 8b0b9c6..b38dbd0 100644 --- a/generate.py +++ b/generate.py @@ -6,14 +6,21 @@ import pysrt import curses -ASCII_CHAR_ARRAY = (" .:-=+*#%@", " .,:ilwW", " ▏▁░▂▖▃▍▐▒▀▞▚▌▅▆▊▓▇▉█", " `^|1aUBN", " .`!?xyWN") +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)] + 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) @@ -25,7 +32,11 @@ def vid_render(st_matrix, st, ed, option): 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] + 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)) @@ -35,9 +46,13 @@ def vid_render(st_matrix, st, ed, option): 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)}) + 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: @@ -45,10 +60,11 @@ def subtitle_show(subs, tstamp_ms): 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 @@ -59,7 +75,8 @@ def get_pixel_matrix(image): # 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)] + 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.""" @@ -67,9 +84,9 @@ 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) + 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: @@ -88,6 +105,7 @@ def get_color_matrix(pixels): 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, @@ -99,11 +117,13 @@ def get_intensity_matrix(pixels, option): for p in row: intensity = 0 if option == 1: - intensity = ((p[0] + p[1] + p[2]) / 3.0) + 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 + 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) @@ -111,6 +131,7 @@ def get_intensity_matrix(pixels, option): 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 = [] @@ -128,6 +149,7 @@ def normalize_intensity_matrix(intensity_matrix): 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 @@ -139,9 +161,10 @@ def print_from_image(filename, option): with Image.open(filename) as image: pixels = get_pixel_matrix(image) vid_render(pixels, 0, len(pixels), option) - except OSError: + 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) @@ -156,13 +179,14 @@ def read_media_sub(vidfile, subfile, option): 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 = time.process_time() - dur dur *= 1000 - if (round(1000/fps - dur) >= 0): - curses.napms(round(1000/fps - dur)) + 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) @@ -175,13 +199,14 @@ def read_media(vidfile, option): dur = time.process_time() cv2.imwrite("./data/frame.jpg", image) print_from_image("./data/frame.jpg", option) - dur = (time.process_time() - dur) + dur = time.process_time() - dur dur *= 1000 - if (round(1000/fps - dur) >= 0): - curses.napms(round(1000/fps - dur)) + if round(1000 / fps - dur) >= 0: + curses.napms(round(1000 / fps - dur)) vidcap.release() cv2.destroyAllWindows() + stdscr = curses.initscr() curses.noecho() curses.cbreak() @@ -193,20 +218,26 @@ def read_media(vidfile, option): 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 + 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 + 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 + 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] @@ -217,4 +248,4 @@ def read_media(vidfile, option): media.getch() curses.nocbreak() curses.echo() -curses.endwin() \ No newline at end of file +curses.endwin() From 76ddea98e0020bc3a1202c7def19d806b3249924 Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Thu, 11 Aug 2022 10:32:13 +0530 Subject: [PATCH 3/6] updated metadata --- CONTRIBUTING.md | 3 +++ README.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 44ec6ac..9ef543a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,9 @@ # Contribution guidelines + Thank you for showing your interest in the repository and the idea. I am grateful to be working with fellow developers. ## Raising Issues + *Issues may be raised only upon problems raised while using the main and tc-version branches.* Note that alpha branch code is highly unstable and is expected to break, kindly do not raise issues regarding it. @@ -16,6 +18,7 @@ When raising an issue, try your best to include the following information: If some infromation is not available to you, you should mention that too. ## Responding to issues + *When contributing, you can contribute to any branch. The issues page will give you a list of issues you can resolve in the main branch.* diff --git a/README.md b/README.md index 91c4866..0c138b8 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,13 @@ A python program that creates ASCII graphics from images and videos. It can also play videos with subtitle support (given a .srt file)! :scream: # 🍎 Motivation + You have seen Music Players, Stackoverflow surfers, Hacker News portals etc. in the terminal, so it is the logical next step 😅. Besides, the terminal makes almost everything appear x10 times more cool. # ⚗️ Dependencies ## Language and Packages + The program runs using python3 The following python packages are used in the program: - pysrt @@ -26,6 +28,7 @@ All POSIX compliant terminals should work well. If you use windows, and the prog https://user-images.githubusercontent.com/55488899/161427699-1d606858-4a3d-4490-b21e-ecd4ac56a83b.mp4 # 🛠️ Usage + Navigate to the directory of the python script and run the following command ```shell python generate.py $VIDEO_FILENAME $SUBTITLE_FILENAME $OPTION @@ -37,6 +40,7 @@ python generate.py $VIDEO_FILENAME $OPTION Here `$VIDEO_FILENAME` and `$SUBTITLE_FILENAME` are the full path to the files and `$OPTION` takes values **0 for black and white output** and **1 for true color output** (see if your terminal supports true color before enabling) # 📝 TODO and Future Plans + - [x] Support 3-bit RGB (8-colors) - [x] Support true color (24-bit RGB) *visit tc-version branch* - [ ] Support automatic resizing From 2a4f2fb8a3c7d7d9907c4c594a3676292eb927ad Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Wed, 5 Oct 2022 18:20:56 +0530 Subject: [PATCH 4/6] Updated contributing guidelines --- CONTRIBUTING.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ef543a..1f718da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,8 @@ Thank you for showing your interest in the repository and the idea. I am gratefu ## Raising Issues -*Issues may be raised only upon problems raised while using the main and tc-version branches.* - -Note that alpha branch code is highly unstable and is expected to break, kindly do not raise issues regarding it. When raising an issue, try your best to include the following information: + - Which branch were you using when things broke? - What was your input? - What was the throwback/error message received, if any? From 4df7938630be2f0f7cf4615faaed5df76c2f5ffe Mon Sep 17 00:00:00 2001 From: Pratyush Kumar Date: Wed, 5 Oct 2022 18:27:53 +0530 Subject: [PATCH 5/6] Updated readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0c138b8..512269d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ You have seen Music Players, Stackoverflow surfers, Hacker News portals etc. in The program runs using python3 The following python packages are used in the program: + - pysrt - opencv-python - Pillow From bd81d45952ecb13d81482c32dd3d6a78eb23e43e Mon Sep 17 00:00:00 2001 From: Vishesh-dd4723 Date: Fri, 7 Oct 2022 17:01:27 +0530 Subject: [PATCH 6/6] 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