diff --git a/.gitignore b/.gitignore index 7318d72..1ed19ad 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ lib/ share/ lib64 pyvenv.cfg +*.m4 +*.h +*.guess +*.log +*.in diff --git a/inkycal/__init__.py b/inkycal/__init__.py index edbacf6..91d44d5 100644 --- a/inkycal/__init__.py +++ b/inkycal/__init__.py @@ -9,8 +9,9 @@ import inkycal.modules.inkycal_feeds import inkycal.modules.inkycal_todoist import inkycal.modules.inkycal_image import inkycal.modules.inkycal_jokes -import inkycal.modules.inkycal_stocks +import inkycal.modules.inkycal_slideshow # import inkycal.modules.inkycal_server # Main file -from inkycal.main import Inkycal \ No newline at end of file +from inkycal.main import Inkycal +import inkycal.modules.inkycal_stocks diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py index 3eded54..cac626d 100644 --- a/inkycal/custom/functions.py +++ b/inkycal/custom/functions.py @@ -35,8 +35,6 @@ for path,dirs,files in os.walk(fonts_location): name = filename.split('.ttf')[0] fonts[name] = os.path.join(path, filename) -# del name, filename, files - available_fonts = [key for key,values in fonts.items()] def get_fonts(): diff --git a/inkycal/display/display.py b/inkycal/display/display.py index ff53476..8cd0a2e 100644 --- a/inkycal/display/display.py +++ b/inkycal/display/display.py @@ -123,8 +123,10 @@ class Display: epaper = self._epaper epaper.init() - white = Image.new('1', (epaper.width, epaper.height), 'white') - black = Image.new('1', (epaper.width, epaper.height), 'black') + display_size = self.get_display_size(self.model_name) + + white = Image.new('1', display_size, 'white') + black = Image.new('1', display_size, 'black') print('----------Started calibration of ePaper display----------') if self.supports_colour == True: @@ -136,7 +138,7 @@ class Display: epaper.display(epaper.getbuffer(white), epaper.getbuffer(black)) print('white...') epaper.display(epaper.getbuffer(white), epaper.getbuffer(white)) - print('Cycle {0} of {1} complete'.format(_+1, cycles)) + print(f'Cycle {_+1} of {cycles} complete') if self.supports_colour == False: for _ in range(cycles): @@ -145,7 +147,7 @@ class Display: epaper.display(epaper.getbuffer(black)) print('white...') epaper.display(epaper.getbuffer(white)), - print('Cycle {0} of {1} complete'.format(_+1, cycles)) + print(f'Cycle {_+1} of {cycles} complete') print('-----------Calibration complete----------') epaper.sleep() diff --git a/inkycal/main.py b/inkycal/main.py index 832bd8e..6a9212b 100644 --- a/inkycal/main.py +++ b/inkycal/main.py @@ -11,6 +11,7 @@ from inkycal.custom import * import os import traceback import logging +from logging.handlers import RotatingFileHandler import arrow import time import json @@ -28,18 +29,36 @@ except ImportError: 'run: pip3 install numpy \nIf you are on Raspberry Pi ' 'remove numpy: pip3 uninstall numpy \nThen try again.') -logging.basicConfig( - level = logging.INFO, #DEBUG > #INFO > #ERROR > #WARNING > #CRITICAL - format='%(name)s -> %(levelname)s -> %(asctime)s -> %(message)s', - datefmt='%d-%m-%Y %H:%M') +# (i): Logging shows logs above a threshold level. +# e.g. logging.DEBUG will show all from DEBUG until CRITICAL +# e.g. logging.ERROR will show from ERROR until CRITICAL +# #DEBUG > #INFO > #ERROR > #WARNING > #CRITICAL + +# On the console, set a logger to show only important logs +# (level ERROR or higher) +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.ERROR) + +# Save all logs to a file, which contains much more detailed output +logging.basicConfig( + level = logging.DEBUG, + format='%(asctime)s | %(name)s | %(levelname)s: %(message)s', + datefmt='%d.%m.%Y %H:%M', + handlers=[ + + stream_handler, # add stream handler from above + + RotatingFileHandler( # log to a file too + f'{top_level}/logs/inkycal.log', # file to log + maxBytes=2097152, # 2MB max filesize + backupCount=5 # create max 5 log files + ) + ] + ) -logger = logging.getLogger('inykcal main') # TODO: fix issue with non-render mode requiring SPI -# TODO: fix info section not updating after a calibration -# TODO: add function to add/remove third party modules # TODO: autostart -> supervisor? -# TODO: logging to files class Inkycal: """Inkycal main class @@ -129,7 +148,7 @@ class Inkycal: # If a module was not found, print an error message except ImportError: - print('Could not find module: "{module}". Please try to import manually') + print(f'Could not find module: "{module}". Please try to import manually') # If something unexpected happened, show the error message except Exception as e: @@ -236,7 +255,7 @@ class Inkycal: errors = [] # store module numbers in here # short info for info-section - self.info = f"{runtime.format('D MMM @ HH:mm')} " + self.info = f"{arrow.now(tz=get_system_tz()).format('D MMM @ HH:mm')} " for number in range(1, self._module_number): @@ -257,6 +276,7 @@ class Inkycal: print('Error!') print(traceback.format_exc()) self.info += f"module {number}: Error! " + logging.error(f'Exception in module {number}:', exc_info=True) if errors: print('Error/s in modules:',*errors) @@ -657,4 +677,4 @@ class Inkycal: print(f"Your module '{filename}' with class '{classname}' was removed.") if __name__ == '__main__': - print('running {0} in standalone/debug mode'.format('inkycal main')) + print(f'running inkycal main in standalone/debug mode') diff --git a/inkycal/modules/__init__.py b/inkycal/modules/__init__.py index 0a3921b..7ea2de5 100644 --- a/inkycal/modules/__init__.py +++ b/inkycal/modules/__init__.py @@ -5,5 +5,6 @@ from .inkycal_feeds import Feeds 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 #from .inkycal_server import Inkyserver -from .inkycal_stocks import Stocks \ No newline at end of file diff --git a/inkycal/modules/ical_parser.py b/inkycal/modules/ical_parser.py index 6186e97..05fb46d 100644 --- a/inkycal/modules/ical_parser.py +++ b/inkycal/modules/ical_parser.py @@ -61,7 +61,7 @@ class iCalendar: else: ical = [auth_ical(url, username, password)] else: - raise Exception ("Input: '{}' is not a string or list!".format(url)) + raise Exception (f"Input: '{url}' is not a string or list!") def auth_ical(url, uname, passwd): @@ -84,14 +84,14 @@ class iCalendar: example: 'path1' (single file) OR ['path1', 'path2'] (multiple files) returns a list of iCalendars as string (raw) """ - if type(url) == list: + if type(filepath) == list: ical = (Calendar.from_ical(open(path)) for path in filepath) - elif type(url) == str: + elif type(filepath) == str: ical = (Calendar.from_ical(open(path))) else: - raise Exception ("Input: '{}' is not a string or list!".format(url)) + raise Exception (f"Input: '{filepath}' is not a string or list!") - self.icalendars += icals + self.icalendars += ical logger.info('loaded iCalendars from filepaths') def get_events(self, timeline_start, timeline_end, timezone=None): @@ -210,4 +210,4 @@ class iCalendar: if __name__ == '__main__': - print('running {0} in standalone mode'.format(filename)) + print(f'running {filename} in standalone mode') diff --git a/inkycal/modules/inky_image.py b/inkycal/modules/inky_image.py new file mode 100644 index 0000000..257e22b --- /dev/null +++ b/inkycal/modules/inky_image.py @@ -0,0 +1,246 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Custom image class for Inkycal Project +Takes care of handling images. Made to be used by other modules to handle +images. + +Copyright by aceisace +""" + +from PIL import Image, ImageOps +import requests +import numpy +import os +import logging + +filename = os.path.basename(__file__).split('.py')[0] +logger = logging.getLogger(filename) + +class Inkyimage: + """Inkyimage class + + missing documentation, lazy devs :/ + """ + + def __init__(self, image=None): + """Initialize Inkyimage module""" + + # no image initially + self.image = image + + # give an OK message + print(f'{filename} loaded') + + def load(self, path): + """loads an image from a URL or filepath. + + Args: + - path:The full path or url of the image file + e.g. `https://sample.com/logo.png` or `/home/pi/Downloads/nice_pic.png` + + Raises: + - FileNotFoundError: This Exception is raised when the file could not be + found. + - OSError: A OSError is raised when the URL doesn't point to the correct + file-format, i.e. is not an image + - TypeError: if the URLS doesn't start with htpp + """ + # Try to open the image if it exists and is an image file + try: + if path.startswith('http'): + logger.debug('loading image from URL') + image = Image.open(requests.get(path, stream=True).raw) + else: + logger.info('loading image from local path') + image = Image.open(path) + except FileNotFoundError: + raise ('Your file could not be found. Please check the filepath') + except OSError: + raise ('Please check if the path points to an image file.') + + logger.debug(f'width: {image.width}, height: {image.height}') + + image.convert(mode='RGBA') #convert to a more suitable format + self.image = image + print('loaded Image') + + def clear(self): + """Removes currently saved image if present""" + if self.image: + self.image = None + print('cleared') + + def _preview(self): + """Preview the image on gpicview (only works on Rapsbian with Desktop)""" + if self._image_loaded(): + path = '/home/pi/Desktop/' + self.image.save(path+'temp.png') + os.system("gpicview "+path+'temp.png') + os.system('rm '+path+'temp.png') + + @staticmethod + def preview(image): + """"Previews an image on gpicview (only works on Rapsbian with Desktop) + + + """ + path = '/home/pi/Desktop/' + image.save(path+'temp.png') + os.system("gpicview "+path+'temp.png') + os.system('rm '+path+'temp.png') + + def _image_loaded(self): + """returns True if image was loaded""" + if self.image: + return True + else: + print('image not loaded') + return False + + def flip(self, angle): + """Flips the image by the given angle. + + Args: + - angle:->int. A multiple of 90, e.g. 90, 180, 270, 360. + """ + if self._image_loaded(): + + image = self.image + if not angle % 90 == 0: + print('Angle must be a multiple of 90') + return + + image = image.rotate(angle, expand = True) + self.image = image + print(f'flipped image by {angle} degrees') + + def autoflip(self, layout): + """flips the image automatically to the given layout. + + Args: + - layout:-> str. Choose `horizontal` or `vertical`. + + Checks the image's width and height. + + In horizontal mode, the image is flipped if the image height is greater + than the image width. + + In vertical mode, the image is flipped if the image width is greater + than the image height. + """ + if self._image_loaded(): + + image = self.image + if layout == 'horizontal': + if (image.height > image.width): + print('image width greater than image height, flipping') + image = image.rotate(90, expand=True) + + elif layout == 'vertical': + if (image.width > image.height): + print('image width greater than image height, flipping') + image = image.rotate(90, expand=True) + else: + print('layout not supported') + return + self.image = image + + def remove_alpha(self): + """Removes transparency if image has transparency. + + Checks if an image has an alpha band and replaces the transparency with + white pixels. + """ + if self._image_loaded(): + image = self.image + + if len(image.getbands()) == 4: + print('has alpha') + logger.debug('removing transparency') + bg = Image.new('RGBA', (image.width, image.height), 'white') + im = Image.alpha_composite(bg, image) + + self.image.paste(im, (0,0)) + print('removed alpha') + + def resize(self, width=None, height=None): + """Resize an image to desired width or height""" + if self._image_loaded(): + + if width == None and height == None: + print('no height of width specified') + return + + image = self.image + + if width: + initial_width = image.width + wpercent = (width/float(image.width)) + hsize = int((float(image.height)*float(wpercent))) + image = image.resize((width, hsize), Image.ANTIALIAS) + logger.debug(f"resized image from {initial_width} to {image.width}") + self.image = image + + if height: + initial_height = image.height + hpercent = (height / float(image.height)) + wsize = int(float(image.width) * float(hpercent)) + image = image.resize((wsize, height), Image.ANTIALIAS) + logger.debug(f"resized image from {initial_height} to {image.height}") + self.image = image + + def to_mono(self): + """Converts image to pure balck-white image (1-bit). + + retrns 1-bit image + + """ + if self._image_loaded(): + image = self.image + + image = image.convert('1', dither=True) + return image + + + def to_colour(self): + """Maps image colours to 3 colours. + """ + if self._image_loaded(): + image = self.image.convert('RGB') + + # Create a simple palette + pal = [255,255,255, 0,0,0, 255,0,0, 255,255,255] + + # Map each pixel of the opened image to the Palette + palette_im = Image.new('P', (3,1)) + palette_im.putpalette(pal * 64) + quantized_im = image.quantize(palette=palette_im) + quantized_im.convert('RGB') + + # Create a buffer for coloured pixels + buffer1 = numpy.array(quantized_im.convert('RGB')) + r1,g1,b1 = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] + + # Create a buffer for black pixels + buffer2 = numpy.array(quantized_im.convert('RGB')) + r2,g2,b2 = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] + + # re-construct image from coloured-pixels buffer + buffer2[numpy.logical_and(r2 == 0, b2 == 0)] = [255,255,255] # black->white + buffer2[numpy.logical_and(r2 == 255, b2 == 0)] = [0,0,0] #red->black + im_colour = Image.fromarray(buffer2) + + # re-construct image from black pixels buffer + buffer1[numpy.logical_and(r1 == 255, b1 == 0)] = [255,255,255] + im_black = Image.fromarray(buffer1) + + return im_black, im_colour + + +if __name__ == '__main__': + print(f'running {filename} in standalone/debug mode') + +a = Inkyimage() +a.load('https://pngimg.com/uploads/pokemon/pokemon_PNG148.png') diff --git a/inkycal/modules/inkycal_agenda.py b/inkycal/modules/inkycal_agenda.py index 169911d..f55a446 100644 --- a/inkycal/modules/inkycal_agenda.py +++ b/inkycal/modules/inkycal_agenda.py @@ -82,25 +82,7 @@ class Agenda(inkycal_module): self.timezone = get_system_tz() # give an OK message - print('{0} loaded'.format(filename)) - - def _validate(self): - """Validate module-specific parameters""" - - if not isinstance(self.date_format, str): - print('date_format has to be an arrow-compatible token') - - if not isinstance(self.time_format, str): - print('time_format has to be an arrow-compatible token') - - if not isinstance(self.language, str): - print('language has to be a string: "en" ') - - if not isinstance(self.ical_urls, list): - print('ical_urls has to be a list ["url1", "url2"] ') - - if not isinstance(self.ical_files, list): - print('ical_files has to be a list ["path1", "path2"] ') + print(f'{filename} loaded') def generate_image(self): """Generate image for this module""" @@ -110,7 +92,7 @@ class Agenda(inkycal_module): im_height = int(self.height - (2 * self.padding_top)) im_size = im_width, im_height - logger.info('Image size: {0}'.format(im_size)) + 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') @@ -121,7 +103,7 @@ class Agenda(inkycal_module): line_height = int(self.font.getsize('hg')[1]) + line_spacing line_width = im_width max_lines = im_height // line_height - logger.debug(('max lines:',max_lines)) + logger.debug(f'max lines: {max_lines}') # Create timeline for agenda now = arrow.now() @@ -156,11 +138,11 @@ class Agenda(inkycal_module): date_width = int(max([self.font.getsize( dates['begin'].format(self.date_format, locale=self.language))[0] for dates in agenda_events]) * 1.2) - logger.debug(('date_width:', date_width)) + logger.debug(f'date_width: {date_width}') # Calculate positions for each line line_pos = [(0, int(line * line_height)) for line in range(max_lines)] - logger.debug(('line_pos:', line_pos)) + logger.debug(f'line_pos: {line_pos}') # Check if any events were filtered if upcoming_events: @@ -170,19 +152,19 @@ class Agenda(inkycal_module): time_width = int(max([self.font.getsize( events['begin'].format(self.time_format, locale=self.language))[0] for events in upcoming_events]) * 1.2) - logger.debug(('time_width:', time_width)) + logger.debug(f'time_width: {time_width}') # Calculate x-pos for time x_time = date_width - logger.debug(('x-time:', x_time)) + logger.debug(f'x-time: {x_time}') # Find out how much space is left for event titles event_width = im_width - time_width - date_width - logger.debug(('width for events:', event_width)) + logger.debug(f'width for events: {event_width}') # Calculate x-pos for event titles x_event = date_width + time_width - logger.debug(('x-event:', x_event)) + logger.debug(f'x-event: {x_event}') # Merge list of dates and list of events agenda_events += upcoming_events @@ -247,4 +229,4 @@ class Agenda(inkycal_module): return im_black, im_colour if __name__ == '__main__': - print('running {0} in standalone mode'.format(filename)) + print(f'running {filename} in standalone mode') diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py index e05b3ca..7f6141a 100644 --- a/inkycal/modules/inkycal_calendar.py +++ b/inkycal/modules/inkycal_calendar.py @@ -85,7 +85,7 @@ class Calendar(inkycal_module): fonts['NotoSans-SemiCondensed'], size = self.fontsize) # give an OK message - print('{0} loaded'.format(filename)) + print(f'{filename} loaded') def generate_image(self): """Generate image for this module""" @@ -95,7 +95,7 @@ class Calendar(inkycal_module): im_height = int(self.height - (2 * self.padding_top)) im_size = im_width, im_height - logger.info('Image size: {0}'.format(im_size)) + 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') @@ -104,8 +104,8 @@ class Calendar(inkycal_module): # Allocate space for month-names, weekdays etc. month_name_height = int(im_height * 0.1) weekdays_height = int(im_height * 0.05) - logger.debug((f"month_name_height: {month_name_height}")) - logger.debug((f"weekdays_height: {weekdays_height}")) + logger.debug(f"month_name_height: {month_name_height}") + logger.debug(f"weekdays_height: {weekdays_height}") if self.show_events == True: @@ -129,8 +129,8 @@ class Calendar(inkycal_module): x_spacing_calendar = int((im_width % calendar_cols) / 2) y_spacing_calendar = int((im_height % calendar_rows) / 2) - logger.debug((f"x_spacing_calendar: {x_spacing_calendar}")) - logger.debug((f"y_spacing_calendar :{y_spacing_calendar}")) + logger.debug(f"x_spacing_calendar: {x_spacing_calendar}") + logger.debug(f"y_spacing_calendar :{y_spacing_calendar}") # Calculate positions for days of month grid_start_y = (month_name_height + weekdays_height + y_spacing_calendar) @@ -160,7 +160,7 @@ class Calendar(inkycal_module): # Set up weeknames in local language and add to main section weekday_names = [weekstart.shift(days=+_).format('ddd',locale=self.language) for _ in range(7)] - logger.debug('weekday names: {}'.format(weekday_names)) + logger.debug(f'weekday names: {weekday_names}') for _ in range(len(weekday_pos)): write( @@ -333,4 +333,4 @@ class Calendar(inkycal_module): return im_black, im_colour if __name__ == '__main__': - print('running {0} in standalone mode'.format(filename)) + print(f'running {filename} in standalone mode') diff --git a/inkycal/modules/inkycal_feeds.py b/inkycal/modules/inkycal_feeds.py index 393252d..c312656 100644 --- a/inkycal/modules/inkycal_feeds.py +++ b/inkycal/modules/inkycal_feeds.py @@ -53,7 +53,7 @@ class Feeds(inkycal_module): # Check if all required parameters are present for param in self.requires: if not param in config: - raise Exception('config is missing {}'.format(param)) + raise Exception(f'config is missing {param}') # required parameters if config["feed_urls"] and isinstance(config['feed_urls'], str): @@ -65,7 +65,7 @@ class Feeds(inkycal_module): self.shuffle_feeds = config["shuffle_feeds"] # give an OK message - print('{0} loaded'.format(filename)) + print(f'{filename} loaded') def _validate(self): """Validate module-specific parameters""" @@ -81,7 +81,7 @@ class Feeds(inkycal_module): 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('image size: {} x {} px'.format(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') @@ -111,7 +111,7 @@ class Feeds(inkycal_module): for feeds in self.feed_urls: text = feedparser.parse(feeds) for posts in text.entries: - parsed_feeds.append('•{0}: {1}'.format(posts.title, posts.summary)) + parsed_feeds.append(f'•{posts.title}: {posts.summary}') self._parsed_feeds = parsed_feeds @@ -151,4 +151,4 @@ class Feeds(inkycal_module): return im_black, im_colour if __name__ == '__main__': - print('running {0} in standalone/debug mode'.format(filename)) + print(f'running {filename} in standalone/debug mode') diff --git a/inkycal/modules/inkycal_image.py b/inkycal/modules/inkycal_image.py index 8e346a5..040606f 100644 --- a/inkycal/modules/inkycal_image.py +++ b/inkycal/modules/inkycal_image.py @@ -9,55 +9,46 @@ Copyright by aceisace from inkycal.modules.template import inkycal_module from inkycal.custom import * -from PIL import ImageOps -import requests -import numpy +from inkycal.modules.inky_image import Inkyimage as Images filename = os.path.basename(__file__).split('.py')[0] logger = logging.getLogger(filename) class Inkyimage(inkycal_module): - """Image class - display an image from a given path or URL + """Displays an image from URL or local path """ name = "Inykcal Image - show an image from a URL or local path" requires = { - 'path': { - "label":"Please enter the full path of the image file (local or URL)", - } + + "path":{ + "label":"Path to a local folder, e.g. /home/pi/Desktop/images. " + "Only PNG and JPG/JPEG images are used for the slideshow." + }, - } + "use_colour": { + "label":"Does the display support colour?", + "options": [True, False] + } + + } optional = { + + "autoflip":{ + "label":"Should the image be flipped automatically?", + "options": [True, False] + }, - 'rotation':{ - "label":"Specify the angle to rotate the image. Default is 0", - "options": [0, 90, 180, 270, 360, "auto"], - "default":0, - }, - - 'layout':{ - "label":"How should the image be displayed on the display? Default is auto", - "options": ['fill', 'center', 'fit', 'auto'], - "default": "auto" - }, - - 'colours':{ - "label":"Specify the colours of your panel. Choose between bw (black and white), bwr (black, white and red) or bwy (black, white and yellow)", - "options": ['bw', 'bwr', 'bwy'], - "default": "bw" + "orientation":{ + "label": "Please select the desired orientation", + "options": ["vertical", "horizontal"] + } } - } - - - # TODO: thorough testing and code cleanup - # TODO: presentation mode (cycle through images in folder) - def __init__(self, config): - """Initialize inkycal_rss module""" + """Initialize module""" super().__init__(config) @@ -66,257 +57,56 @@ class Inkyimage(inkycal_module): # required parameters for param in self.requires: if not param in config: - raise Exception('config is missing {}'.format(param)) + raise Exception(f'config is missing {param}') # optional parameters - self.image_path = self.config['path'] - - self.rotation = self.config['rotation'] - self.layout = self.config['layout'] - self.colours = self.config['colours'] + self.path = config['path'] + self.use_colour = config['use_colour'] + self.autoflip = config['autoflip'] + self.orientation = config['orientation'] # give an OK message - print('{0} loaded'.format(self.name)) + print(f'{filename} loaded') - def _validate(self): - """Validate module-specific parameters""" - - # Validate image_path - if not isinstance(self.image_path, str): - print( - 'image_path has to be a string: "URL1" or "/home/pi/Desktop/im.png"') - - # Validate layout - if not isinstance(self.layout, str): - print('layout has to be a string') def generate_image(self): """Generate image for this module""" # Define new image size with respect to padding - im_width = self.width - im_height = self.height + 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('image size: {} x {} px'.format(im_width, im_height)) - logger.info('image path: {}'.format(self.image_path)) - logger.info('colors: {}'.format(self.colours)) - # Try to open the image if it exists and is an image file - try: - if self.image_path.startswith('http'): - logger.debug('identified url') - self.image = Image.open(requests.get(self.image_path, stream=True).raw) - else: - logger.info('identified local path') - self.image = Image.open(self.image_path) - except FileNotFoundError: - raise ('Your file could not be found. Please check the filepath') - except OSError: - raise ('Please check if the path points to an image file.') + logger.info(f'Image size: {im_size}') - logger.debug(('image-width:', self.image.width)) - logger.debug(('image-height:', self.image.height)) + # initialize custom image class + im = Images() - # 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') + # use the image at the first index + im.load(self.path) - # do the required operations - self._remove_alpha() - self._to_layout() - black, colour = self._map_colours(self.colours) + # Remove background if present + im.remove_alpha() - # paste the images on the canvas - im_black.paste(black, (self.x, self.y)) - im_colour.paste(colour, (self.x, self.y)) + # if autoflip was enabled, flip the image + if self.autoflip == True: + im.autoflip(self.orientation) - # Save images of black and colour channel in image-folder - im_black.save(images+self.name+'.png', 'PNG') - im_colour.save(images+self.name+'_colour.png', 'PNG') + # resize the image so it can fit on the epaper + im.resize( width=im_width, height=im_height ) + + # convert images according to given settings + if self.use_colour == False: + im_black = im.to_mono() + im_colour = Image.new('RGB', size = im_black.size, color = 'white') + else: + im_black, im_colour = im.to_colour() + + # with the images now send, clear the current image + im.clear() # return images - return black, colour - - def _rotate(self, angle=None): - """Rotate the image to a given angle - angle must be one of :[0, 90, 180, 270, 360, 'auto'] - """ - im = self.image - if angle == None: - angle = self.rotation - - # Check if angle is supported - # if angle not in self._allowed_rotation: - # print('invalid angle provided, setting to fallback: 0 deg') - # angle = 0 - - # Autoflip the image if angle == 'auto' - if angle == 'auto': - if (im.width > self.height) and (im.width < self.height): - print('display vertical, image horizontal -> flipping image') - image = im.rotate(90, expand=True) - if (im.width < self.height) and (im.width > self.height): - print('display horizontal, image vertical -> flipping image') - image = im.rotate(90, expand=True) - # if not auto, flip to specified angle - else: - image = im.rotate(angle, expand = True) - self.image = image - - def _fit_width(self, width=None): - """Resize an image to desired width""" - im = self.image - if width == None: width = self.width - - logger.debug(('resizing width from', im.width, 'to')) - wpercent = (width/float(im.width)) - hsize = int((float(im.height)*float(wpercent))) - image = im.resize((width, hsize), Image.ANTIALIAS) - logger.debug(image.width) - self.image = image - - def _fit_height(self, height=None): - """Resize an image to desired height""" - im = self.image - if height == None: height = self.height - - logger.debug(('resizing height from', im.height, 'to')) - hpercent = (height / float(im.height)) - wsize = int(float(im.width) * float(hpercent)) - image = im.resize((wsize, height), Image.ANTIALIAS) - logger.debug(image.height) - self.image = image - - def _to_layout(self, mode=None): - """Adjust the image to suit the layout - mode can be center, fit or fill""" - - im = self.image - if mode == None: mode = self.layout - - # if mode not in self._allowed_layout: - # print('{} is not supported. Should be one of {}'.format( - # mode, self._allowed_layout)) - # print('setting layout to fallback: centre') - # mode = 'center' - - # If mode is center, just center the image - if mode == 'center': - pass - - # if mode is fit, adjust height of the image while keeping ascept-ratio - if mode == 'fit': - self._fit_height() - - # if mode is fill, enlargen or shrink the image to fit width - if mode == 'fill': - self._fit_width() - - # in auto mode, flip image automatically and fit both height and width - if mode == 'auto': - - # Check if width is bigger than height and rotate by 90 deg if true - if im.width > im.height: - self._rotate(90) - - # fit both height and width - self._fit_height() - self._fit_width() - - if self.image.width > self.width: - x = int( (self.image.width - self.width) / 2) - else: - x = int( (self.width - self.image.width) / 2) - - if self.image.height > self.height: - y = int( (self.image.height - self.height) / 2) - else: - y = int( (self.height - self.image.height) / 2) - - self.x, self.y = x, y - - def _remove_alpha(self): - im = self.image - - if len(im.getbands()) == 4: - logger.debug('removing transparency') - bg = Image.new('RGBA', (im.width, im.height), 'white') - im = Image.alpha_composite(bg, im) - self.image.paste(im, (0,0)) - - def _map_colours(self, colours = None): - """Map image colours to display-supported colours """ - im = self.image.convert('RGB') - - if colours == 'bw': - - # For black-white images, use monochrome dithering - im_black = im.convert('1', dither=True) - im_colour = None - - elif colours == 'bwr': - # For black-white-red images, create corresponding palette - pal = [255,255,255, 0,0,0, 255,0,0, 255,255,255] - - elif colours == 'bwy': - # For black-white-yellow images, create corresponding palette""" - pal = [255,255,255, 0,0,0, 255,255,0, 255,255,255] - else: - logger.info('Unrecognized colors: {}, falling back to black and white'.format(colours)) - # Fallback to black-white images, use monochrome dithering - im_black = im.convert('1', dither=True) - im_colour = None - - # Map each pixel of the opened image to the Palette - if colours == 'bwr' or colours == 'bwy': - palette_im = Image.new('P', (3,1)) - palette_im.putpalette(pal * 64) - quantized_im = im.quantize(palette=palette_im) - quantized_im.convert('RGB') - - # Create buffer for coloured pixels - buffer1 = numpy.array(quantized_im.convert('RGB')) - r1,g1,b1 = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] - - # Create buffer for black pixels - buffer2 = numpy.array(quantized_im.convert('RGB')) - r2,g2,b2 = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] - - if colours == 'bwr': - # Create image for only red pixels - buffer2[numpy.logical_and(r2 == 0, b2 == 0)] = [255,255,255] # black->white - buffer2[numpy.logical_and(r2 == 255, b2 == 0)] = [0,0,0] #red->black - im_colour = Image.fromarray(buffer2) - - # Create image for only black pixels - buffer1[numpy.logical_and(r1 == 255, b1 == 0)] = [255,255,255] - im_black = Image.fromarray(buffer1) - - if colours == 'bwy': - # Create image for only yellow pixels - buffer2[numpy.logical_and(r2 == 0, b2 == 0)] = [255,255,255] # black->white - buffer2[numpy.logical_and(g2 == 255, b2 == 0)] = [0,0,0] #yellow -> black - im_colour = Image.fromarray(buffer2) - - # Create image for only black pixels - buffer1[numpy.logical_and(g1 == 255, b1 == 0)] = [255,255,255] - im_black = Image.fromarray(buffer1) - return im_black, im_colour - @staticmethod - def save(image, path): - im = self.image - im.save(path, 'PNG') - - @staticmethod - def _show(image): - """Preview the image on gpicview (only works on Rapsbian with Desktop)""" - path = '/home/pi/Desktop/' - image.save(path+'temp.png') - os.system("gpicview "+path+'temp.png') - os.system('rm '+path+'temp.png') - if __name__ == '__main__': - print('running {0} in standalone/debug mode'.format(filename)) + print(f'running {filename} in standalone/debug mode') diff --git a/inkycal/modules/inkycal_jokes.py b/inkycal/modules/inkycal_jokes.py index 339a3da..7fb0abe 100644 --- a/inkycal/modules/inkycal_jokes.py +++ b/inkycal/modules/inkycal_jokes.py @@ -33,7 +33,7 @@ class Jokes(inkycal_module): config = config['config'] # give an OK message - print('{0} loaded'.format(filename)) + print(f'{filename} loaded') def generate_image(self): """Generate image for this module""" @@ -42,7 +42,7 @@ class Jokes(inkycal_module): 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('image size: {} x {} px'.format(im_width, im_height)) + logger.info(f'image size: {im_width} x {im_height} px') # Create an image for black pixels and one for coloured pixels im_black = Image.new('RGB', size = im_size, color = 'white') @@ -76,7 +76,7 @@ class Jokes(inkycal_module): header = {"accept": "text/plain"} response = requests.get(url, headers=header) response.encoding = 'utf-8' # Change encoding to UTF-8 - joke = response.text + joke = response.text.rstrip() # use to remove newlines logger.debug(f"joke: {joke}") # wrap text in case joke is too large @@ -87,13 +87,18 @@ class Jokes(inkycal_module): if len(wrapped) > max_lines: logger.error("Ohoh, Joke is too large for given space, please consider " "increasing the size for this module") - logger.error("Removing lines in reverse order") - wrapped = wrapped[:max_lines] - # Write feeds on image + # Write the joke on the image for _ in range(len(wrapped)): + if _+1 > max_lines: + logger.error('Ran out of lines for this joke :/') + break write(im_black, line_positions[_], (line_width, line_height), wrapped[_], font = self.font, alignment= 'left') # Save image of black and colour channel in image-folder return im_black, im_colour + + +if __name__ == '__main__': + print(f'running {filename} in standalone/debug mode') diff --git a/inkycal/modules/inkycal_slideshow.py b/inkycal/modules/inkycal_slideshow.py new file mode 100644 index 0000000..89f215d --- /dev/null +++ b/inkycal/modules/inkycal_slideshow.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Image module for Inkycal Project +Copyright by aceisace +""" +import glob + +from inkycal.modules.template import inkycal_module +from inkycal.custom import * + +# PIL has a class named Image, use alias for Inkyimage -> Images +from inkycal.modules.inky_image import Inkyimage as Images + +filename = os.path.basename(__file__).split('.py')[0] +logger = logging.getLogger(filename) + +class Slideshow(inkycal_module): + """Cycles through images in a local image folder + """ + name = "Slideshow - cycle through images from a local folder" + + requires = { + + "path":{ + "label":"Path to a local folder, e.g. /home/pi/Desktop/images. " + "Only PNG and JPG/JPEG images are used for the slideshow." + }, + + "use_colour": { + "label":"Does the display support colour?", + "options": [True, False] + } + + } + + optional = { + + "autoflip":{ + "label":"Should the image be flipped automatically?", + "options": [True, False] + }, + + "orientation":{ + "label": "Please select the desired orientation", + "options": ["vertical", "horizontal"] + } + } + + def __init__(self, config): + """Initialize module""" + + super().__init__(config) + + config = config['config'] + + # required parameters + for param in self.requires: + if not param in config: + raise Exception(f'config is missing {param}') + + # optional parameters + self.path = config['path'] + self.use_colour = config['use_colour'] + self.autoflip = config['autoflip'] + self.orientation = config['orientation'] + + # Get the full path of all png/jpg/jpeg images in the given folder + all_files = glob.glob(f'{self.path}/*') + self.images = [i for i in all_files + if i.split('.')[-1].lower() in ('jpg', 'jpeg', 'png')] + + if not self.images: + logger.error('No images found in the given folder, please ' + 'double check your path!') + raise Exception('No images found in the given folder path :/') + + # set a 'first run' signal + self._first_run = True + + # give an OK message + print(f'{filename} loaded') + + 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}') + + # rotates list items by 1 index + def rotate(somelist): + return somelist[1:] + somelist[:1] + + # Switch to the next image if this is not the first run + if self._first_run == True: + self._first_run = False + else: + self.images = rotate(self.images) + + # initialize custom image class + im = Images() + + # use the image at the first index + im.load(self.images[0]) + + # Remove background if present + im.remove_alpha() + + # if autoflip was enabled, flip the image + if self.autoflip == True: + im.autoflip(self.orientation) + + # resize the image so it can fit on the epaper + im.resize( width=im_width, height=im_height ) + + # convert images according to given settings + if self.use_colour == False: + im_black = im.to_mono() + im_colour = Image.new('RGB', size = im_black.size, color = 'white') + else: + im_black, im_colour = im.to_colour() + + # with the images now send, clear the current image + im.clear() + + # return images + return im_black, im_colour + +if __name__ == '__main__': + print(f'running {filename} in standalone/debug mode') diff --git a/inkycal/modules/inkycal_stocks.py b/inkycal/modules/inkycal_stocks.py index 0d20ae2..9aca534 100644 --- a/inkycal/modules/inkycal_stocks.py +++ b/inkycal/modules/inkycal_stocks.py @@ -60,7 +60,7 @@ class Stocks(inkycal_module): 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('image size: {} x {} px'.format(im_width, im_height)) + logger.info(f'Image size: {im_size}') # Create an image for black pixels and one for coloured pixels (required) im_black = Image.new('RGB', size = im_size, color = 'white') diff --git a/inkycal/modules/inkycal_todoist.py b/inkycal/modules/inkycal_todoist.py index 116d136..c1deeb6 100644 --- a/inkycal/modules/inkycal_todoist.py +++ b/inkycal/modules/inkycal_todoist.py @@ -34,7 +34,7 @@ class Todoist(inkycal_module): optional = { 'project_filter': { "label":"Show Todos only from following project (separated by a comma). Leave empty to show "+ - "todos from all projects", + "todos from all projects", } } @@ -48,7 +48,7 @@ class Todoist(inkycal_module): # Check if all required parameters are present for param in self.requires: if not param in config: - raise Exception('config is missing {}'.format(param)) + raise Exception(f'config is missing {param}') # module specific parameters self.api_key = config['api_key'] @@ -63,7 +63,7 @@ class Todoist(inkycal_module): self._api.sync() # give an OK message - print('{0} loaded'.format(self.name)) + print(f'{filename} loaded') def _validate(self): """Validate module-specific parameters""" @@ -77,7 +77,7 @@ class Todoist(inkycal_module): 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('image size: {} x {} px'.format(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') @@ -196,4 +196,4 @@ class Todoist(inkycal_module): return im_black, im_colour if __name__ == '__main__': - print('running {0} in standalone/debug mode'.format(filename)) + print(f'running {filename} in standalone/debug mode') diff --git a/inkycal/modules/inkycal_weather.py b/inkycal/modules/inkycal_weather.py index 1913fb5..1464dc5 100644 --- a/inkycal/modules/inkycal_weather.py +++ b/inkycal/modules/inkycal_weather.py @@ -90,7 +90,7 @@ class Weather(inkycal_module): # Check if all required parameters are present for param in self.requires: if not param in config: - raise Exception('config is missing {}'.format(param)) + raise Exception(f'config is missing {param}') # required parameters self.api_key = config['api_key'] @@ -107,7 +107,7 @@ class Weather(inkycal_module): # additional configuration self.owm = OWM(self.api_key).weather_manager() self.timezone = get_system_tz() - self.locale = sys_locale()[0] + self.locale = config['language'] self.weatherfont = ImageFont.truetype( fonts['weathericons-regular-webfont'], size = self.fontsize) @@ -146,7 +146,7 @@ class Weather(inkycal_module): 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('image size: {} x {} px'.format(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') @@ -391,7 +391,7 @@ class Weather(inkycal_module): daily_temp = [round(_.temperature(unit=temp_unit)['temp'], ndigits=dec_temp) for _ in forecasts] # Calculate min. and max. temp for this day - temp_range = '{}°/{}°'.format(max(daily_temp), min(daily_temp)) + temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°' # Get all weather icon codes for this day @@ -417,7 +417,9 @@ class Weather(inkycal_module): logger.debug((key,val)) # Get some current weather details - temperature = '{}°'.format(weather.temperature(unit=temp_unit)['temp']) + temperature = '{}°'.format(round( + weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp)) + weather_icon = weather.weather_icon_name humidity = str(weather.humidity) sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone) @@ -510,4 +512,4 @@ class Weather(inkycal_module): return im_black, im_colour if __name__ == '__main__': - print('running {0} in standalone mode'.format(filename)) + print(f'running {filename} in standalone mode') diff --git a/inkycal/modules/template.py b/inkycal/modules/template.py index 87ebd0d..1756268 100644 --- a/inkycal/modules/template.py +++ b/inkycal/modules/template.py @@ -44,9 +44,9 @@ class inkycal_module(metaclass=abc.ABCMeta): self.fontsize = value else: setattr(self, key, value) - print("set '{}' to '{}'".format(key,value)) + print(f"set '{key}' to '{value}'") else: - print('{0} does not exist'.format(key)) + print(f'{key} does not exist') pass # Check if validation has been implemented @@ -70,12 +70,12 @@ class inkycal_module(metaclass=abc.ABCMeta): if hasattr(cls, 'requires'): for each in cls.requires: if not "label" in cls.requires[each]: - raise Exception("no label found for {}".format(each)) + raise Exception(f"no label found for {each}") if hasattr(cls, 'optional'): for each in cls.optional: if not "label" in cls.optional[each]: - raise Exception("no label found for {}".format(each)) + raise Exception(f"no label found for {each}") conf = { "name": cls.__name__, diff --git a/inkycal/tests/inkycal_feeds_test.py b/inkycal/tests/inkycal_feeds_test.py index 7fdf216..28255c6 100644 --- a/inkycal/tests/inkycal_feeds_test.py +++ b/inkycal/tests/inkycal_feeds_test.py @@ -9,10 +9,7 @@ tests = [ "size": [400,100], "feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#", "shuffle_feeds": True, - "padding_x": 10, - "padding_y": 10, - "fontsize": 12, - "language": "en" + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" } }, { @@ -22,10 +19,7 @@ tests = [ "size": [400,100], "feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#", "shuffle_feeds": False, - "padding_x": 10, - "padding_y": 10, - "fontsize": 12, - "language": "en" + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" } }, ] diff --git a/inkycal/tests/inkycal_image_test.py b/inkycal/tests/inkycal_image_test.py index 0a99797..87cc987 100644 --- a/inkycal/tests/inkycal_image_test.py +++ b/inkycal/tests/inkycal_image_test.py @@ -1,21 +1,93 @@ import unittest from inkycal.modules import Inkyimage as Module +from inkycal.custom import top_level + +test_path = f'{top_level}/Gallery/coffee.png' tests = [ { "position": 1, "name": "Inkyimage", "config": { - "size": [528,880], - "path": "https://cdn.britannica.com/s:700x500/84/73184-004-E5A450B5/Sunflower-field-Fargo-North-Dakota.jpg", - "rotation": "0", - "layout": "fill", - "padding_x": 0, - "padding_y": 0, - "fontsize": 12, - "language": "en", - "colours": "bwr" - } + "size": [400,200], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [800,500], + "path": test_path, + "use_colour": False, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": False, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "horizontal", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [500, 800], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 0, "padding_y": 0, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Inkyimage", + "config": { + "size": [500, 800], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 20, "padding_y": 20, "fontsize": 12, "language": "en" + } }, ] diff --git a/inkycal/tests/inkycal_jokes_test.py b/inkycal/tests/inkycal_jokes_test.py index 5967165..c653b5d 100644 --- a/inkycal/tests/inkycal_jokes_test.py +++ b/inkycal/tests/inkycal_jokes_test.py @@ -7,10 +7,23 @@ tests = [ "name": "Jokes", "config": { "size": [300, 60], - "padding_x": 10, - "padding_y": 10, - "fontsize": 12, - "language": "en" + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Jokes", + "config": { + "size": [300, 30], + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Jokes", + "config": { + "size": [100, 800], + "padding_x": 10, "padding_y": 10, "fontsize": 18, "language": "en" } }, ] diff --git a/inkycal/tests/inkycal_slideshow_test.py b/inkycal/tests/inkycal_slideshow_test.py new file mode 100644 index 0000000..b03433f --- /dev/null +++ b/inkycal/tests/inkycal_slideshow_test.py @@ -0,0 +1,108 @@ +import unittest +from inkycal.modules import Slideshow as Module +from inkycal.custom import top_level + +test_path = f'{top_level}/Gallery' + +tests = [ +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [400,200], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [800,500], + "path": test_path, + "use_colour": False, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": False, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [400,100], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "horizontal", + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [500, 800], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 0, "padding_y": 0, "fontsize": 12, "language": "en" + } +}, +{ + "position": 1, + "name": "Slideshow", + "config": { + "size": [500, 800], + "path": test_path, + "use_colour": True, + "autoflip": True, + "orientation": "vertical", + "padding_x": 20, "padding_y": 20, "fontsize": 12, "language": "en" + } +}, +] + +class module_test(unittest.TestCase): + def test_get_config(self): + print('getting data for web-ui...', end = "") + Module.get_config() + print('OK') + + def test_generate_image(self): + for test in tests: + print(f'test {tests.index(test)+1} generating image..') + module = Module(test) + module.generate_image() + print('OK') + +if __name__ == '__main__': + unittest.main() diff --git a/logs/dummy.txt.txt b/logs/dummy.txt.txt new file mode 100644 index 0000000..e69de29