From 3460aabd7badb08c8a76ee0ad3d48369e01d1c3f Mon Sep 17 00:00:00 2001 From: aceisace Date: Mon, 3 Oct 2022 02:58:07 +0200 Subject: [PATCH] Added (plain) text to display module --- inkycal/modules/__init__.py | 3 +- .../modules/inkycal_textfile_to_display.py | 117 ++++++++++++++++ .../tests/test_inkycal_textfile_to_display.py | 126 ++++++++++++++++++ 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 inkycal/modules/inkycal_textfile_to_display.py create mode 100644 inkycal/tests/test_inkycal_textfile_to_display.py diff --git a/inkycal/modules/__init__.py b/inkycal/modules/__init__.py index 00969b1..d473fe5 100755 --- a/inkycal/modules/__init__.py +++ b/inkycal/modules/__init__.py @@ -6,4 +6,5 @@ from .inkycal_todoist import Todoist from .inkycal_image import Inkyimage from .inkycal_jokes import Jokes from .inkycal_stocks import Stocks -from .inkycal_slideshow import Slideshow \ No newline at end of file +from .inkycal_slideshow import Slideshow +from .inkycal_textfile_to_display import TextToDisplay \ No newline at end of file diff --git a/inkycal/modules/inkycal_textfile_to_display.py b/inkycal/modules/inkycal_textfile_to_display.py new file mode 100644 index 0000000..a9bf98b --- /dev/null +++ b/inkycal/modules/inkycal_textfile_to_display.py @@ -0,0 +1,117 @@ +#!python3 +""" +Textfile module for InkyCal Project + +Reads data from a plain .txt file and renders it on the display. +If the content is too long, it will be truncated from the back until it fits + +Copyright by aceisace +""" +from inkycal.modules.template import inkycal_module +from inkycal.custom import * + +from urllib.request import urlopen + +logger = logging.getLogger(__name__) + + +class TextToDisplay(inkycal_module): + """TextToDisplay module + """ + + name = "Text module - Display text from a local file on the display" + + requires = { + "filepath": { + "label": "Please enter a filepath or URL pointing to a .txt file", + }, + } + + def __init__(self, config): + """Initialize inkycal_textfile_to_display module""" + + super().__init__(config) + + config = config['config'] + + # Check if all required parameters are present + for param in self.requires: + if param not in config: + raise Exception(f'config is missing {param}') + + # required parameters + self.filepath = config["filepath"] + + self.make_request = True if self.filepath.startswith("https://") else False + + + # give an OK message + print(f'{__name__} loaded') + + def _validate(self): + """Validate module-specific parameters""" + # ensure we only use a single file + assert (self.filepath and len(self.filepath) == 1) + + def generate_image(self): + """Generate image for this module""" + + # Define new image size with respect to padding + im_width = int(self.width - (2 * self.padding_left)) + im_height = int(self.height - (2 * self.padding_top)) + im_size = im_width, im_height + logger.info(f'Image size: {im_size}') + + # Create an image for black pixels and one for coloured pixels + im_black = Image.new('RGB', size=im_size, color='white') + im_colour = Image.new('RGB', size=im_size, color='white') + + # Check if internet is available + if internet_available(): + logger.info('Connection test passed') + else: + raise NetworkNotReachableError + + # Set some parameters for formatting feeds + line_spacing = 1 + line_height = self.font.getsize('hg')[1] + line_spacing + line_width = im_width + max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) + + # Calculate padding from top so the lines look centralised + spacing_top = int(im_height % line_height / 2) + + # Calculate line_positions + line_positions = [ + (0, spacing_top + _ * line_height) for _ in range(max_lines)] + + if self.make_request: + logger.info("Detected http path, making request") + file_content = urlopen(self.filepath).read().decode('utf-8') + else: + # Create list containing all lines + with open(self.filepath, 'r') as file: + file_content = file.read() + + fitted_content = text_wrap(file_content, font=self.font, max_width=im_width) + + # Trim down the list to the max number of lines + del fitted_content[max_lines:] + + # Write feeds on image + for index, line in enumerate(fitted_content): + write( + im_black, + line_positions[index], + (line_width, line_height), + line, + font=self.font, + alignment='left' + ) + + # return images + return im_black, im_colour + + +if __name__ == '__main__': + print(f'running {__name__} in standalone/debug mode') diff --git a/inkycal/tests/test_inkycal_textfile_to_display.py b/inkycal/tests/test_inkycal_textfile_to_display.py new file mode 100644 index 0000000..e826354 --- /dev/null +++ b/inkycal/tests/test_inkycal_textfile_to_display.py @@ -0,0 +1,126 @@ +#!python3 +import os +import unittest +from inkycal.modules import TextToDisplay as Module +from helper_functions import * + +environment = get_environment() + +# Set to True to preview images. Only works on Raspberry Pi OS with Desktop +use_preview = False + +file_path = None + +dummy_data = [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', ' Donec feugiat facilisis neque vel blandit.', + 'Integer viverra dolor risus.', ' Etiam neque tellus, sollicitudin at nisi a, mollis ornare enim.', + 'Quisque sed ante eu leo dictum sagittis quis nec nisi.', + 'Suspendisse id nulla dictum, sollicitudin urna id, iaculis elit.', + 'Nulla luctus pellentesque diam, ac consequat urna molestie vitae.', + 'Donec elementum turpis eget augue laoreet, nec maximus lacus malesuada.', '\n\nEtiam eu nunc mauris.', + 'Nullam aliquam tristique leo, at dignissim turpis sodales vitae.', + 'Aenean cursus laoreet neque, sit amet semper orci tincidunt et.', + 'Proin orci urna, efficitur malesuada mattis at, pretium commodo odio.', + 'Maecenas in ante id eros aliquam porttitor quis eget est.', + 'Duis ex urna, porta nec semper nec, dignissim eu urna.', ' Quisque eleifend non magna at rutrum.', + '\nSed at eros blandit, tempor quam et, mollis ante.', ' Etiam fringilla euismod gravida.', + 'Curabitur facilisis consectetur luctus.', + 'Integer lectus augue, convallis a consequat id, sollicitudin eget lorem.', + 'Curabitur tincidunt suscipit nibh quis mollis.', + 'Fusce cursus, orci ut maximus fringilla, velit mauris dictum est, sed ultricies ante orci viverra erat.', + 'Quisque pellentesque, mauris nec vulputate commodo, risus libero volutpat nibh, vel tristique mi neque id quam.', + '\nVivamus blandit, dolor ut interdum sagittis, arcu tortor finibus nibh, ornare convallis dui velit quis nunc.', + 'Sed turpis justo, pellentesque eu risus scelerisque, vestibulum vulputate magna.', + 'Vivamus tincidunt sollicitudin nisl, feugiat euismod nulla consequat ut.', + 'Praesent bibendum, sapien sit amet aliquet posuere, tellus purus porta lectus, ut volutpat purus tellus tempus est.', + 'Maecenas condimentum lobortis erat nec dignissim', ' Nulla molestie posuere est', + 'Proin ultrices, nisl id luctus lacinia, augue ipsum pharetra leo, quis commodo augue dui varius urna.', + 'Morbi ultrices turpis malesuada tellus fermentum vulputate.', + 'Aliquam viverra nulla aliquam viverra gravida.', ' Pellentesque eu viverra massa.', + 'Vestibulum id nisl vehicula, aliquet dui sed, venenatis eros.', + 'Nunc iaculis, neque vitae euismod viverra, nisl mauris luctus velit, a aliquam turpis erat fringilla libero.', + 'Ut ligula elit, lacinia convallis tempus interdum, finibus ut ex.', + 'Nulla efficitur ac ligula sit amet dignissim.', ' Donec sed mi et justo venenatis faucibus.', + 'Sed tincidunt nibh erat, in vestibulum purus consequat eget.', + '\nNulla iaculis volutpat orci id efficitur.', ' Vivamus vehicula sit amet augue tristique dignissim.', + 'Praesent eget nulla est.', ' Integer nec diam fermentum, convallis metus lacinia, lobortis mauris.', + 'Nulla venenatis metus fringilla, lacinia sem nec, pharetra sapien.', + 'Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.', + 'Duis facilisis sapien est, a elementum lorem maximus ut.' +] + +tests = [ + { + "position": 1, + "name": "TextToFile", + "config": { + "size": [500, 100], + "filepath": file_path, + "padding_x": 10, + "padding_y": 10, + "fontsize": 12, + "language": "en" + } + }, + { + "position": 1, + "name": "TextToFile", + "config": { + "size": [500, 400], + "filepath": file_path, + "padding_x": 10, + "padding_y": 10, + "fontsize": 12, + "language": "en" + } + }, +] + + +class TestTextToDisplay(unittest.TestCase): + def test_get_config(self): + print('getting data for web-ui...', end="") + Module.get_config() + print('OK') + + def test_generate_image(self): + delete_file_after_parse = False + + if not file_path: + delete_file_after_parse = True + print("Filepath does not exist. Creating dummy file") + + tmp_path = "tmp.txt" + with open(tmp_path, mode="w") as file: + file.writelines(dummy_data) + + # update tests with new temp path + for test in tests: + test["config"]["filepath"] = tmp_path + + else: + make_request = True if file_path.startswith("https://") else False + if not make_request and not os.path.exists(file_path): + raise FileNotFoundError("Your text file could not be found") + + for test in tests: + print(f'test {tests.index(test) + 1} generating image..') + module = Module(test) + im_black, im_colour = module.generate_image() + print('OK') + if use_preview and environment == 'Raspberry': + preview(merge(im_black, im_colour)) + im = merge(im_black, im_colour) + im.show() + + if delete_file_after_parse: + print("cleaning up temp file") + os.remove("tmp.txt") + + +if __name__ == '__main__': + logger = logging.getLogger() + logger.level = logging.DEBUG + logger.addHandler(logging.StreamHandler(sys.stdout)) + + unittest.main()