Revert "Inititial commit for release v2.0.0"
This reverts commit 5fa6102c0d.
			
			
This commit is contained in:
		| @@ -211,11 +211,4 @@ class iCalendar: | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone mode'.format(filename)) | ||||
|  | ||||
|   a = iCalendar() | ||||
|   now = arrow.now() | ||||
|   a.load_url('https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics') | ||||
|   a.load_url('https://calendar.yahoo.com/saadnaseer63/37435f792ecb221cdd169d06a518b30f/ycal.ics?id=1670') | ||||
|   a.get_events(now, now.shift(weeks=2), a.get_system_tz()) | ||||
|   a.show_events() | ||||
|   print('running {0} in standalone mode'.format(filename)) | ||||
| @@ -16,60 +16,35 @@ filename = os.path.basename(__file__).split('.py')[0] | ||||
| logger = logging.getLogger(filename) | ||||
| logger.setLevel(level=logging.ERROR) | ||||
|  | ||||
|  | ||||
| class Agenda(inkycal_module): | ||||
|   """Agenda class | ||||
|   Create agenda and show events from given icalendars | ||||
|   """ | ||||
|  | ||||
|   name = "Inkycal Agenda" | ||||
|  | ||||
|   requires = { | ||||
|     "ical_urls" : { | ||||
|       "label":"iCalendar URL/s, separate multiple ones with a comma", | ||||
|       }, | ||||
|  | ||||
|     } | ||||
|  | ||||
|   optional = { | ||||
|     "ical_files" : { | ||||
|       "label":"iCalendar filepaths, separated with a comma", | ||||
|       "default":[] | ||||
|       }, | ||||
|  | ||||
|     "date_format":{ | ||||
|       "label":"Use an arrow-supported token for custom date formatting "+ | ||||
|       "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. ddd D MMM", | ||||
|       "default": "ddd D MMM", | ||||
|       }, | ||||
|  | ||||
|     "time_format":{ | ||||
|       "label":"Use an arrow-supported token for custom time formatting "+ | ||||
|       "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", | ||||
|       }, | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_agenda module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     for param in self.equires: | ||||
|     # Module specific parameters | ||||
|     required = ['week_starts_on', 'ical_urls'] | ||||
|     for param in required: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # module specific parameters | ||||
|     self.date_format = self.config['date_format'] | ||||
|     self.time_format = self.config['time_format'] | ||||
|     self.language = self.config['language'] | ||||
|     self.ical_urls = self.config['ical_urls'] | ||||
|     self.ical_files = self.config['ical_files'] | ||||
|     # class name | ||||
|     self.name = self.__class__.__name__ | ||||
|  | ||||
|     # module specific parameters | ||||
|     self.date_format = 'ddd D MMM' | ||||
|     self.time_format = "HH:mm" | ||||
|     self.language = self.config['language'] | ||||
|     self.timezone = get_system_tz() | ||||
|     self.ical_urls = self.config['ical_urls'] | ||||
|     self.ical_files = [] | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(filename)) | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   def _validate(self): | ||||
|     """Validate module-specific parameters""" | ||||
| @@ -216,6 +191,7 @@ class Agenda(inkycal_module): | ||||
|  | ||||
|     # If no events were found, write only dates and lines | ||||
|     else: | ||||
|       line_pos = [(0, int(line * line_height)) for line in range(max_lines)] | ||||
|       cursor = 0 | ||||
|       for _ in agenda_events: | ||||
|         title = _['title'] | ||||
| @@ -230,8 +206,9 @@ class Agenda(inkycal_module): | ||||
|  | ||||
|       logger.info('no events found') | ||||
|  | ||||
|     # return the images ready for the display | ||||
|     return im_black, im_colour | ||||
|     # Save image of black and colour channel in image-folder | ||||
|     im_black.save(images+self.name+'.png') | ||||
|     im_colour.save(images+self.name+'_colour.png') | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone mode'.format(filename)) | ||||
|   | ||||
| @@ -19,74 +19,42 @@ class Calendar(inkycal_module): | ||||
|   Create monthly calendar and show events from given icalendars | ||||
|   """ | ||||
|  | ||||
|   name = "Inkycal Calendar" | ||||
|  | ||||
|   optional = { | ||||
|      | ||||
|     "week_starts_on" : { | ||||
|       "label":"When does your week start? (default=Monday)", | ||||
|       "options": ["Monday", "Sunday"], | ||||
|       "default": "Monday" | ||||
|       }, | ||||
|  | ||||
|     "show_events" : { | ||||
|       "label":"Show parsed events? (default = True)", | ||||
|       "options": [True, False], | ||||
|       "default": True | ||||
|       }, | ||||
|  | ||||
|     "ical_urls" : { | ||||
|       "label":"iCalendar URL/s, separate multiple ones with a comma", | ||||
|       "default":[] | ||||
|       }, | ||||
|  | ||||
|     "ical_files" : { | ||||
|       "label":"iCalendar filepaths, separated with a comma", | ||||
|       "default":[] | ||||
|       }, | ||||
|      | ||||
|     "date_format":{ | ||||
|       "label":"Use an arrow-supported token for custom date formatting "+ | ||||
|       "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", | ||||
|       "default": "D MMM", | ||||
|       }, | ||||
|  | ||||
|     "time_format":{ | ||||
|       "label":"Use an arrow-supported token for custom time formatting "+ | ||||
|       "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", | ||||
|       "default": "HH:mm" | ||||
|       }, | ||||
|      | ||||
|     } | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_calendar module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # Module specific parameters | ||||
|     required = ['week_starts_on'] | ||||
|     for param in required: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # module name | ||||
|     self.name = self.__class__.__name__ | ||||
|  | ||||
|     # module specific parameters | ||||
|     self.num_font = ImageFont.truetype( | ||||
|       fonts['NotoSans-SemiCondensed'], size = self.fontsize) | ||||
|     self.weekstart = self.config['week_starts_on'] | ||||
|     self.show_events = self.config['show_events'] | ||||
|     self.date_format = self.config["date_format"] | ||||
|     self.time_format = self.config['time_format'] | ||||
|     self.show_events = True | ||||
|     self.date_format = 'D MMM' | ||||
|     self.time_format = "HH:mm" | ||||
|     self.language = self.config['language'] | ||||
|  | ||||
|     self.timezone = get_system_tz() | ||||
|     self.ical_urls = self.config['ical_urls'] | ||||
|     self.ical_files = self.config['ical_files'] | ||||
|     self.ical_files = [] | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(filename)) | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   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_x)) | ||||
|     im_height = int(self.height - (2 * self.padding_y)) | ||||
|     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||
|     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||
|     im_size = im_width, im_height | ||||
|  | ||||
|     logger.info('Image size: {0}'.format(im_size)) | ||||
| @@ -112,7 +80,15 @@ class Calendar(inkycal_module): | ||||
|         im_width, calendar_height)) | ||||
|  | ||||
|     # Create grid and calculate icon sizes | ||||
|     calendar_rows, calendar_cols = 6, 7 | ||||
|     now = arrow.now(tz = self.timezone) | ||||
|     monthstart = now.span('month')[0].weekday() | ||||
|     monthdays = now.ceil('month').day | ||||
|  | ||||
|     if monthstart > 4 and monthdays == 31: | ||||
|         calendar_rows, calendar_cols = 7, 7 | ||||
|     else: | ||||
|         calendar_rows, calendar_cols = 6, 7 | ||||
|  | ||||
|     icon_width = im_width // calendar_cols | ||||
|     icon_height = calendar_height // calendar_rows | ||||
|  | ||||
| @@ -130,8 +106,6 @@ class Calendar(inkycal_module): | ||||
|     weekday_pos = [(grid_start_x + icon_width*_, month_name_height) for _ in | ||||
|                    range(calendar_cols)] | ||||
|  | ||||
|     now = arrow.now(tz = self.timezone) | ||||
|  | ||||
|     # Set weekstart of calendar to specified weekstart | ||||
|     if self.weekstart == "Monday": | ||||
|       cal.setfirstweekday(cal.MONDAY) | ||||
| @@ -309,8 +283,9 @@ class Calendar(inkycal_module): | ||||
|               (im_width, self.font.getsize(symbol)[1]), symbol, | ||||
|               font = self.font) | ||||
|  | ||||
|     # return the images ready for the display | ||||
|     return im_black, im_colour | ||||
|     # Save image of black and colour channel in image-folder | ||||
|     im_black.save(images+self.name+'.png') | ||||
|     im_colour.save(images+self.name+'_colour.png') | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone mode'.format(filename)) | ||||
|   | ||||
| @@ -1,32 +1,305 @@ | ||||
| #!/usr/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| Image module for inkycal Project | ||||
| Image module for Inkycal Project | ||||
| Copyright by aceisace | ||||
| Development satge: Beta | ||||
| """ | ||||
|  | ||||
| from os import path | ||||
| from inkycal.modules.template import inkycal_module | ||||
| from inkycal.custom import * | ||||
|  | ||||
| from PIL import ImageOps | ||||
| import requests | ||||
| import numpy | ||||
|  | ||||
| """----------------------------------------------------------------""" | ||||
| #path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png' | ||||
| #path  ='/home/pi/Inky-Calendar/images/canvas.png' | ||||
| path      = inkycal_image_path | ||||
| path_body = inkycal_image_path_body | ||||
| mode = 'auto'         # 'horizontal' # 'vertical' # 'auto' | ||||
| upside_down = False    # Flip image by 180 deg (upside-down) | ||||
| alignment = 'center'  # top_center, top_left, center_left, bottom_right etc. | ||||
| colours = 'bwr'       # bwr # bwy # bw | ||||
| render = True         # show image on E-Paper? | ||||
| """----------------------------------------------------------------""" | ||||
| filename = os.path.basename(__file__).split('.py')[0] | ||||
| logger = logging.getLogger(filename) | ||||
| logger.setLevel(level=logging.DEBUG) | ||||
|  | ||||
| # First determine dimensions | ||||
| if mode == 'horizontal': | ||||
|   display_width, display_height == display_height, display_width | ||||
| class Inkyimage(inkycal_module): | ||||
|   """Image class | ||||
|   display an image from a given path or URL | ||||
|   """ | ||||
|   _allowed_layout = ['fill', 'center', 'fit', 'auto'] | ||||
|   _allowed_rotation = [0, 90, 180, 270, 360, 'auto'] | ||||
|   _allowed_colours = ['bw', 'bwr', 'bwy'] | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_rss module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # Module specific parameters | ||||
|     required = ['path'] | ||||
|     for param in required: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # module name | ||||
|     self.name = self.__class__.__name__ | ||||
|  | ||||
|     # module specific parameters | ||||
|     self.image_path = self.config['path'] | ||||
|  | ||||
|     self.rotation = 0 #0 #90 # 180 # 270 # auto | ||||
|     self.layout = 'fill' # centre # fit # auto | ||||
|     self.colours = 'bw' #grab from settings file? | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   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) or ( | ||||
|       self.layout not in self._allowed_layout): | ||||
|       print('layout has to be one of the following:', self._allowed_layout) | ||||
|  | ||||
|     # Validate rotation angle | ||||
|     if self.rotation not in self._allowed_rotation: | ||||
|       print('rotation has to be one of the following:', self._allowed_rotation) | ||||
|  | ||||
|     # Validate colours | ||||
|     if not isinstance(self.colours, str) or ( | ||||
|       self.colours not in self._allowed_colours): | ||||
|       print('colour has to be one of the following:', self._allowed_colours) | ||||
|  | ||||
|   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_size = im_width, im_height | ||||
|     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||
|  | ||||
|     # 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.debug(('image-width:', self.image.width)) | ||||
|     logger.debug(('image-height:', self.image.height)) | ||||
|  | ||||
|     # 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') | ||||
|  | ||||
|     # do the required operations | ||||
|     self._remove_alpha() | ||||
|     self._to_layout() | ||||
|     black, colour = self._map_colours() | ||||
|  | ||||
|     # paste the imaeges on the canvas | ||||
|     im_black.paste(black, (self.x, self.y)) | ||||
|     if colour != None: | ||||
|       im_colour.paste(colour, (self.x, self.y)) | ||||
|  | ||||
|     # Save image of black and colour channel in image-folder | ||||
|     im_black.save(images+self.name+'.png', 'PNG') | ||||
|     if colour != None: | ||||
|       im_colour.save(images+self.name+'_colour.png', 'PNG') | ||||
|  | ||||
|   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 == None: colours = self.colours | ||||
|  | ||||
|     if colours not in self._allowed_colours: | ||||
|       print('invalid colour: "{}", has to be one of: {}'.format( | ||||
|         colours, self._allowed_colours)) | ||||
|       print('setting to fallback: bw') | ||||
|       colours = 'bw' | ||||
|  | ||||
|     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] | ||||
|  | ||||
|     # 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): | ||||
|     im = self.image | ||||
|     im.save('/home/pi/Desktop/test.png', '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)) | ||||
|  | ||||
| ##  a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"}) | ||||
| ##  a.generate_image() | ||||
|  | ||||
|      | ||||
| print('Done') | ||||
|   | ||||
| @@ -1,305 +0,0 @@ | ||||
| #!/usr/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| Image module for Inkycal Project | ||||
| Copyright by aceisace | ||||
| """ | ||||
|  | ||||
| from inkycal.modules.template import inkycal_module | ||||
| from inkycal.custom import * | ||||
|  | ||||
| from PIL import ImageOps | ||||
| import requests | ||||
| import numpy | ||||
|  | ||||
| filename = os.path.basename(__file__).split('.py')[0] | ||||
| logger = logging.getLogger(filename) | ||||
| logger.setLevel(level=logging.ERROR) | ||||
|  | ||||
| class Inkyimage(inkycal_module): | ||||
|   """Image class | ||||
|   display an image from a given path or URL | ||||
|   """ | ||||
|  | ||||
|   name = "Inykcal Image - show an image from a URL or local path" | ||||
|    | ||||
|   requires = { | ||||
|   'path': { | ||||
|     "label":"Please enter the path of the image file (local or URL)", | ||||
|     } | ||||
|  | ||||
|   } | ||||
|  | ||||
|   optional = { | ||||
|   '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" | ||||
|   } | ||||
|  | ||||
|   } | ||||
|  | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_rss module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # required parameters | ||||
|     for param in self.requires: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # optional parameters | ||||
|     self.image_path = self.config['path'] | ||||
|  | ||||
|     self.rotation = self.config['rotation'] | ||||
|     self.layout = self.config['layout'] | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   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_size = im_width, im_height | ||||
|     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||
|  | ||||
|     # 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.debug(('image-width:', self.image.width)) | ||||
|     logger.debug(('image-height:', self.image.height)) | ||||
|  | ||||
|     # 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') | ||||
|  | ||||
|     # do the required operations | ||||
|     self._remove_alpha() | ||||
|     self._to_layout() | ||||
|     black, colour = self._map_colours() | ||||
|  | ||||
|     # paste the images on the canvas | ||||
|     im_black.paste(black, (self.x, self.y)) | ||||
|     im_colour.paste(colour, (self.x, self.y)) | ||||
|  | ||||
|     # 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') | ||||
|  | ||||
|   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] | ||||
|  | ||||
|     # 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)) | ||||
|  | ||||
|   #a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"}) | ||||
|   #a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"}) | ||||
|   a = Inkyimage((480, 800), {'path': "/home/pi/Desktop/im/IMG_0475.JPG"}) | ||||
|   a.generate_image() | ||||
|    | ||||
|  | ||||
| @@ -2,7 +2,7 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| RSS module for inkyCal Project | ||||
| RSS module for Inky-Calendar Project | ||||
| Copyright by aceisace | ||||
| """ | ||||
|  | ||||
| @@ -25,48 +25,28 @@ class RSS(inkycal_module): | ||||
|   parses rss/atom feeds from given urls | ||||
|   """ | ||||
|  | ||||
|   name = "Inkycal RSS / Atom" | ||||
|  | ||||
|   requires = { | ||||
|     "rss_urls" : { | ||||
|       "label":"Please enter ATOM or RSS feed URL/s, separated by a comma", | ||||
|       }, | ||||
|  | ||||
|     } | ||||
|  | ||||
|   optional = { | ||||
|      | ||||
|     "shuffle_feeds": { | ||||
|       "label": "Should the parsed RSS feeds be shuffled? (default=True)", | ||||
|       "options": [True, False], | ||||
|       "default": True | ||||
|       }, | ||||
|  | ||||
|     } | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_rss module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # Check if required parameters are available in config | ||||
|     for param in self.requires: | ||||
|     # Module specific parameters | ||||
|     required = ['rss_urls'] | ||||
|     for param in required: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # parse required config | ||||
|     self.rss_urls = self.config["rss_urls"].split(",") | ||||
|     # module name | ||||
|     self.name = self.__class__.__name__ | ||||
|  | ||||
|     # parse optional config | ||||
|     self.shuffle_feeds = self.config["shuffle_feeds"] | ||||
|                     | ||||
|     # module specific parameters | ||||
|     self.shuffle_feeds = True | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(filename)) | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   def _validate(self): | ||||
|     """Validate module-specific parameters""" | ||||
|  | ||||
|     if not isinstance(self.shuffle_feeds, bool): | ||||
|       print('shuffle_feeds has to be a boolean: True/False') | ||||
|  | ||||
| @@ -75,8 +55,8 @@ class RSS(inkycal_module): | ||||
|     """Generate image for this module""" | ||||
|  | ||||
|     # Define new image size with respect to padding | ||||
|     im_width = int(self.width - ( 2 * self.padding_x)) | ||||
|     im_height = int(self.height - (2 * self.padding_y)) | ||||
|     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||
|     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||
|     im_size = im_width, im_height | ||||
|     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||
|  | ||||
| @@ -90,6 +70,7 @@ class RSS(inkycal_module): | ||||
|     else: | ||||
|       raise Exception('Network could not be reached :/') | ||||
|  | ||||
|  | ||||
|     # Set some parameters for formatting rss feeds | ||||
|     line_spacing = 1 | ||||
|     line_height = self.font.getsize('hg')[1] + line_spacing | ||||
| @@ -105,7 +86,7 @@ class RSS(inkycal_module): | ||||
|  | ||||
|     # Create list containing all rss-feeds from all rss-feed urls | ||||
|     parsed_feeds = [] | ||||
|     for feeds in self.rss_urls: | ||||
|     for feeds in self.config['rss_urls']: | ||||
|       text = feedparser.parse(feeds) | ||||
|       for posts in text.entries: | ||||
|         parsed_feeds.append('•{0}: {1}'.format(posts.title, posts.summary)) | ||||
| @@ -146,8 +127,8 @@ class RSS(inkycal_module): | ||||
|     del filtered_feeds, parsed_feeds, wrapped, counter, text | ||||
|  | ||||
|     # Save image of black and colour channel in image-folder | ||||
|     return im_black, im_colour | ||||
|     im_black.save(images+self.name+'.png', 'PNG') | ||||
|     im_colour.save(images+self.name+'_colour.png', 'PNG') | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone/debug mode'.format(filename)) | ||||
|   print(RSS.get_config()) | ||||
|   | ||||
| @@ -1,41 +0,0 @@ | ||||
| #!/usr/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
| Image Server module for Inkycal project | ||||
| For use with Robert Sierre's inkycal web-service | ||||
|  | ||||
| Copyright by aceisace | ||||
| """ | ||||
|  | ||||
| from os import path | ||||
| from PIL import ImageOps | ||||
| import requests | ||||
| import numpy | ||||
|  | ||||
| """----------------------------------------------------------------""" | ||||
| #path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png' | ||||
| #path  ='/home/pi/Inky-Calendar/images/canvas.png' | ||||
| path      = inkycal_image_path | ||||
| path_body = inkycal_image_path_body | ||||
| mode = 'auto'         # 'horizontal' # 'vertical' # 'auto' | ||||
| upside_down = False    # Flip image by 180 deg (upside-down) | ||||
| alignment = 'center'  # top_center, top_left, center_left, bottom_right etc. | ||||
| colours = 'bwr'       # bwr # bwy # bw | ||||
| render = True         # show image on E-Paper? | ||||
| """----------------------------------------------------------------""" | ||||
|  | ||||
|  | ||||
| path = path.replace('{model}', model).replace('{width}',str(display_width)).replace('{height}',str(display_height)) | ||||
| print(path) | ||||
|  | ||||
| try: | ||||
|   # POST request, passing path_body in the body | ||||
|   im = Image.open(requests.post(path, json=path_body, stream=True).raw) | ||||
|    | ||||
| except FileNotFoundError: | ||||
|   raise Exception('Your file could not be found. Please check the path to your file.') | ||||
|  | ||||
| except OSError: | ||||
|   raise Exception('Please check if the path points to an image file.') | ||||
|  | ||||
|  | ||||
| @@ -20,157 +20,14 @@ logger = logging.getLogger(filename) | ||||
| logger.setLevel(level=logging.ERROR) | ||||
|  | ||||
|  | ||||
| class Todoist(inkycal_module): | ||||
|   """Todoist api class | ||||
|   parses todo's from api-key | ||||
|   """ | ||||
| api = todoist.TodoistAPI('your api key') | ||||
| api.sync() | ||||
|  | ||||
|   name = "Inkycal Todoist" | ||||
|  | ||||
|   requires = { | ||||
|     'api_key': { | ||||
|       "label":"Please enter your Todoist API-key", | ||||
|       }, | ||||
|   } | ||||
|  | ||||
|   optional = { | ||||
|     'project_filter': { | ||||
|       "label":"Show Todos only from following project (separated by a comma). Leave empty to show "+ | ||||
|       "todos from all projects", | ||||
|       "default": [] | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_rss module""" | ||||
|  | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # Module specific parameters | ||||
|     for param in self.requires: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
| # Print name of author | ||||
| print(api.state['user']['full_name']+'\n') | ||||
|  | ||||
|  | ||||
|     # module specific parameters | ||||
|     self.api_key = self.config['api_key'] | ||||
|     self.project_filter = self.config['project_filter']# only show todos from these projects | ||||
| tasks = (task.data for task in api.state['items']) | ||||
|  | ||||
|     self._api = todoist.TodoistAPI(self.config['api_key']) | ||||
|     self._api.sync() | ||||
|  | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   def _validate(self): | ||||
|     """Validate module-specific parameters""" | ||||
|     if not isinstance(self.api_key, str): | ||||
|       print('api_key has to be a string: "Yourtopsecretkey123" ') | ||||
|  | ||||
|   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_x)) | ||||
|     im_height = int(self.height - (2 * self.padding_y)) | ||||
|     im_size = im_width, im_height | ||||
|     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||
|  | ||||
|     # 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() == True: | ||||
|       logger.info('Connection test passed') | ||||
|     else: | ||||
|       raise Exception('Network could not be reached :/') | ||||
|  | ||||
|     # Set some parameters for formatting todos | ||||
|     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)] | ||||
|  | ||||
| #------------------------------------------------------------------------## | ||||
|     # Get all projects by name and id | ||||
|     all_projects = {project['name']: project['id'] | ||||
|                     for project in self._api.projects.all()} | ||||
|  | ||||
|     # Check if project from filter could be found | ||||
|     if self.project_filter: | ||||
|       for project in self.project_filter: | ||||
|         if project not in all_projects: | ||||
|           print('Could not find a project named {}'.format(project)) | ||||
|           self.project_filter.remove(project) | ||||
|  | ||||
|     # function for extracting project names from tasks | ||||
|     get_project_name = lambda task: (self._api.projects.get_data( | ||||
|                                      task['project_id'])['project']['name']) | ||||
|  | ||||
|     # If the filter is empty, parse all tasks which are not yet done | ||||
|     if self.project_filter: | ||||
|       tasks = (task.data for task in self._api.state['items'] | ||||
|                if (task['checked'] == 0) and | ||||
|                (get_project_name(task) in self.project_filter)) | ||||
|  | ||||
|     # If filter is not empty, parse undone tasks in only those projects | ||||
|     else: | ||||
|       tasks = (task.data for task in self._api.state['items'] if | ||||
|                (task['checked'] == 0)) | ||||
|  | ||||
|     # Simplify the tasks for faster processing | ||||
|     simplified = [{'name':task['content'], | ||||
|                    'due':task['due'], | ||||
|                    'priority':task['priority'], | ||||
|                    'project_id':task['project_id']} | ||||
|                   for task in tasks] | ||||
|  | ||||
|     # Group tasks by project name | ||||
|     grouped = {} | ||||
|  | ||||
|     if self.project_filter: | ||||
|       for project in self.project_filter: | ||||
|         project_id = all_projects[project] | ||||
|         grouped[ project ] = [ | ||||
|           task for task in simplified if task['project_id'] == project_id] | ||||
|     else: | ||||
|       for project in all_projects: | ||||
|         project_id = all_projects[project] | ||||
|         grouped[ project ] = [ | ||||
|           task for task in simplified if task['project_id'] == project_id] | ||||
|  | ||||
|     # Print tasks sorted by groups | ||||
|     for project, tasks in grouped.items(): | ||||
|       print('*', project) | ||||
|       for task in tasks: | ||||
|         print('• {} {}'.format( | ||||
|           task['due']['string'] if task['due'] != None else '', task['name'])) | ||||
|  | ||||
|  | ||||
| ##    # Write rss-feeds on image | ||||
| ##    for _ in range(len(filtered_feeds)): | ||||
| ##      write(im_black, line_positions[_], (line_width, line_height), | ||||
| ##            filtered_feeds[_], font = self.font, alignment= 'left') | ||||
|  | ||||
|  | ||||
|  | ||||
|     # Cleanup --------------------------- | ||||
|     # del grouped, parsed_feeds, wrapped, counter, text | ||||
|  | ||||
|     # return the images ready for the display | ||||
|     return im_black, im_colour | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone/debug mode'.format(filename)) | ||||
|   config = {'api_key':'4e166367dcafdd60e6a9f4cbed598d578bf2c359'} | ||||
|   size = (480, 100) | ||||
|   a = Todoist(size, config) | ||||
|   b,c = a.generate_image() | ||||
| for _ in tasks: | ||||
|   print('task: {} is {}'.format(_['content'], 'done' if _['checked'] == 1 else 'not done')) | ||||
|   | ||||
| @@ -26,60 +26,6 @@ class Weather(inkycal_module): | ||||
|   """Weather class | ||||
|   parses weather details from openweathermap | ||||
|   """ | ||||
|   #TODO: automatic setup of pyowm by location id if location is numeric | ||||
|  | ||||
|   name = "Inkycal Weather (openweathermap)" | ||||
|  | ||||
|   requires = { | ||||
|  | ||||
|     "api_key" : { | ||||
|       "label":"Please enter openweathermap api-key. You can create one for free on openweathermap", | ||||
|     }, | ||||
|      | ||||
|     "location": { | ||||
|       "label":"Please enter your location in the following format: City, Country-Code" | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   optional = { | ||||
|      | ||||
|     "round_temperature": { | ||||
|       "label":"Round temperature to the nearest degree?", | ||||
|       "options": [True, False], | ||||
|       "default" : True | ||||
|       }, | ||||
|  | ||||
|     "round_windspeed": { | ||||
|       "label":"Round windspeed?", | ||||
|       "options": [True, False], | ||||
|       "default": True | ||||
|       }, | ||||
|  | ||||
|     "forecast_interval": { | ||||
|       "label":"Please select the forecast interval", | ||||
|       "options": ["daily", "hourly"], | ||||
|       "default": "daily" | ||||
|       }, | ||||
|  | ||||
|     "units": { | ||||
|       "label": "Which units should be used?", | ||||
|       "options": ["metric", "imperial"], | ||||
|       "default": "metric" | ||||
|       }, | ||||
|  | ||||
|     "hour_format": { | ||||
|       "label": "Which hour format do you prefer?", | ||||
|       "options": [12, 24], | ||||
|       "default": 24 | ||||
|       }, | ||||
|  | ||||
|     "use_beaufort": { | ||||
|       "label": "Use beaufort scale for windspeed?", | ||||
|       "options": [True, False], | ||||
|       "default": True | ||||
|       }, | ||||
|      | ||||
|     } | ||||
|  | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize inkycal_weather module""" | ||||
| @@ -87,36 +33,35 @@ class Weather(inkycal_module): | ||||
|     super().__init__(section_size, section_config) | ||||
|  | ||||
|     # Module specific parameters | ||||
|     for param in self.requires: | ||||
|     required = ['api_key','location'] | ||||
|     for param in required: | ||||
|       if not param in section_config: | ||||
|         raise Exception('config is missing {}'.format(param)) | ||||
|  | ||||
|     # required parameters | ||||
|     self.location = self.config['location'] | ||||
|     self.api_key = self.config['api_key'] | ||||
|     # module name | ||||
|     self.name = self.__class__.__name__ | ||||
|  | ||||
|     # optional parameters | ||||
|     self.round_temperature = self.config['round_temperature'] | ||||
|     self.round_windspeed = self.config['round_windspeed'] | ||||
|     self.forecast_interval = self.config['forecast_interval'] | ||||
|     # module specific parameters | ||||
|     self.owm = pyowm.OWM(self.config['api_key']) | ||||
|     self.units = self.config['units'] | ||||
|     self.hour_format = self.config['hour_format'] | ||||
|     self.use_beaufort = self.config['use_beaufort'] | ||||
|     self.hour_format = self.config['hours'] | ||||
|     self.timezone = get_system_tz() | ||||
|     self.round_temperature = True | ||||
|     self.round_windspeed = True | ||||
|     self.use_beaufort = True | ||||
|     self.forecast_interval = 'daily' # daily # hourly | ||||
|     self.locale = sys_locale()[0] | ||||
|     self.weatherfont = ImageFont.truetype(fonts['weathericons-regular-webfont'], | ||||
|                                           size = self.fontsize) | ||||
|  | ||||
|     #self.owm = pyowm.OWM(self.config['api_key']) | ||||
|     # give an OK message | ||||
|     print('{0} loaded'.format(filename)) | ||||
|     print('{0} loaded'.format(self.name)) | ||||
|  | ||||
|   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_x)) | ||||
|     im_height = int(self.height - (2 * self.padding_y)) | ||||
|     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||
|     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||
|     im_size = im_width, im_height | ||||
|     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||
|  | ||||
| @@ -477,8 +422,9 @@ class Weather(inkycal_module): | ||||
|     draw_border(im_black, (col6, row1), (col_width, im_height)) | ||||
|     draw_border(im_black, (col7, row1), (col_width, im_height)) | ||||
|  | ||||
|     # return the images ready for the display | ||||
|     return im_black, im_colour | ||||
|   # Save image 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") | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   print('running {0} in standalone mode'.format(filename)) | ||||
|   | ||||
| @@ -10,16 +10,14 @@ class inkycal_module(metaclass=abc.ABCMeta): | ||||
|       callable(subclass.generate_image) or | ||||
|       NotImplemented) | ||||
|  | ||||
|   def __init__(self, section_config): | ||||
|   def __init__(self, section_size, section_config): | ||||
|     # Initializes base module | ||||
|     # sets properties shared amongst all sections | ||||
|     self.config = section_config | ||||
|     self.width, self.height = section_config['size'] | ||||
|  | ||||
|     self.padding_left = self.padding_right = self.config["padding_x"] | ||||
|     self.padding_top = self.padding_bottom = self.config["padding_y"] | ||||
|  | ||||
|     self.fontsize = self.config["fontsize"] | ||||
|     self.width, self.height = section_size | ||||
|     self.fontsize = 12 | ||||
|     self.margin_x = 0.02 | ||||
|     self.margin_y = 0.05 | ||||
|     self.font = ImageFont.truetype( | ||||
|       fonts['NotoSans-SemiCondensed'], size = self.fontsize) | ||||
|  | ||||
| @@ -58,33 +56,3 @@ class inkycal_module(metaclass=abc.ABCMeta): | ||||
|     # Generate image for this module with specified parameters | ||||
|     raise NotImplementedError( | ||||
|       'The developers were too lazy to implement this function') | ||||
|  | ||||
|   @classmethod | ||||
|   def get_config(cls): | ||||
|     # Get the config of this module for the web-ui | ||||
|     # Do not change | ||||
|     try: | ||||
|  | ||||
|       if hasattr(cls, 'requires'): | ||||
|         for each in cls.requires: | ||||
|           if not "label" in cls.requires[each]: | ||||
|             raise Exception("no label found for {}".format(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)) | ||||
|  | ||||
|       conf = { | ||||
|         "name": cls.__name__, | ||||
|         "name_str": cls.name, | ||||
|         "requires": cls.requires if hasattr(cls, 'requires') else {}, | ||||
|         "optional": cls.optional if hasattr(cls, 'optional') else {}, | ||||
|         } | ||||
|       return conf | ||||
|     except: | ||||
|       raise Exception( | ||||
|         'Ohoh, something went wrong while trying to get the config of this module') | ||||
|  | ||||
|  | ||||
|    | ||||
|   | ||||
| @@ -60,21 +60,9 @@ class Simple(inkycal_module): | ||||
|   Explain what this module does... | ||||
|   """ | ||||
|  | ||||
|   # name is the name that will be shown on the web-ui | ||||
|   # may be same or different to the class name (Do not remove this) | ||||
|   name = "My own module" | ||||
|  | ||||
|   # create a dictionary that specifies what your module absolutely needs | ||||
|   # to run correctly | ||||
|   # Use the following format -> "key" : "info about this key for web-ui" | ||||
|   # You can add as many required entries as you like | ||||
|   requires = { | ||||
|     "module_parameter" : "Short info about this parameter, shown on the web-ui", | ||||
|     } | ||||
|  | ||||
|   # Initialise the class (do not remove) | ||||
|   def __init__(self, section_size, section_config): | ||||
|     """Initialize your module module""" | ||||
|     """Initialize inkycal_rss module""" | ||||
|  | ||||
|     # Initialise this module via the inkycal_module template (required) | ||||
|     super().__init__(section_size, section_config) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user