Implementation of interface (template) for all modules
- Correct setup of logging - all inkycal-modules inherit from the given template - Added basic, optional validation - more code cleanups - fixed a few minor bugs
This commit is contained in:
		| @@ -1,47 +1,40 @@ | |||||||
| from importlib import import_module | from inkycal.config import settings | ||||||
|  |  | ||||||
| from inkycal.config import settings, layout |  | ||||||
|  |  | ||||||
| ##modules = settings.which_modules() | f = '/home/pi/Desktop/settings.json' | ||||||
| ##for module in modules: |  | ||||||
| ##  if module == 'inkycal_rss': |  | ||||||
| ##    module = import_module('inkycal.modules.'+module) |  | ||||||
| ##    #import_module('modules.'+module) |  | ||||||
| ##print(module) |  | ||||||
|  |  | ||||||
| settings_file = "/home/pi/Desktop/Inkycal/inkycal/config/settings.json" | settings = settings(f) | ||||||
|  |  | ||||||
| class inkycal: | specified_modules = settings.active_modules() | ||||||
|   """Main class for inkycal | for module in specified_modules: | ||||||
|   """ |   try: | ||||||
|      |      | ||||||
|   def __init__(self, settings_file_path): |     if module == 'inkycal_weather': | ||||||
|     """Load settings file from path""" |       from inkycal import weather | ||||||
|  |       conf = settings.get_config(module) | ||||||
|  |       weather = weather(conf['size'], conf['config']) | ||||||
|  |  | ||||||
|     # Load settings file |     if module == 'inkycal_rss': | ||||||
|     self.settings = settings(settings_file_path) |       from inkycal import rss | ||||||
|     self.model = self.settings.model |       conf = settings.get_config(module) | ||||||
|  |       rss = rss(conf['size'], conf['config']) | ||||||
|  |  | ||||||
|   def create_canvas(self): |     if module == 'inkycal_image': | ||||||
|     """Create a canvas with same size as the specified model""" |       from inkycal import image | ||||||
|  |       conf = settings.get_config(module) | ||||||
|  |       image = image(conf['size'], conf['config']) | ||||||
|  |  | ||||||
|     self.layout = layout(model=self.model) |     if module == 'inkycal_calendar': | ||||||
|  |       from inkycal import calendar | ||||||
|  |       conf = settings.get_config(module) | ||||||
|  |       calendar = calendar(conf['size'], conf['config']) | ||||||
|  |  | ||||||
|   def create_custom_canvas(self, width=None, height=None, |     if module == 'inkycal_agenda': | ||||||
|                            supports_colour=False): |       from inkycal import agenda | ||||||
|     """Create a custom canvas by specifying height and width""" |       conf = settings.get_config(module) | ||||||
|  |       agenda = agenda(conf['size'], conf['config']) | ||||||
|     self.layout = layout(model=model, width=width, height=height, |  | ||||||
|                supports_colour=supports_colour) |  | ||||||
|  |  | ||||||
|   def create_sections(self): |  | ||||||
|     """Create sections with default sizes""" |  | ||||||
|     self.layout.create_sections() |  | ||||||
|  |  | ||||||
|   def create_custom_sections(self, top_section=0.10, middle_section=0.65, |  | ||||||
|                       bottom_section=0.25): |  | ||||||
|     """Create custom-sized sections in the canvas""" |  | ||||||
|     self.layout.create_sections(top_section=top_section, |  | ||||||
|         middle_section=middle_section, |  | ||||||
|         bottom_section=bottom_section) |  | ||||||
|  |  | ||||||
|  |   except ImportError: | ||||||
|  |     print( | ||||||
|  |       'Could not find module: "{}". Please try to import manually.'.format( | ||||||
|  |       module)) | ||||||
|   | |||||||
| @@ -1 +1,5 @@ | |||||||
| from .Inkycal import inkycal | from inkycal.modules.inkycal_agenda import agenda | ||||||
|  | from inkycal.modules.inkycal_calendar import calendar | ||||||
|  | from inkycal.modules.inkycal_weather import weather | ||||||
|  | from inkycal.modules.inkycal_rss import rss | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,2 @@ | |||||||
| from .settings_parser import settings | from .settings_parser import settings | ||||||
| print('loaded settings') |  | ||||||
| from .layout import layout | from .layout import layout | ||||||
| print('loaded layout') |  | ||||||
|   | |||||||
| @@ -6,22 +6,21 @@ Copyright by aceisace | |||||||
| """ | """ | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|  | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
| class layout: | class layout: | ||||||
|   """Page layout handling""" |   """Page layout handling""" | ||||||
|  |  | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.DEBUG) |  | ||||||
|  |  | ||||||
|   def __init__(self, model=None, width=None, height=None, |   def __init__(self, model=None, width=None, height=None, | ||||||
|                supports_colour=False): |                supports_colour=False): | ||||||
|     """Initialize parameters for specified epaper model |     """Initialize parameters for specified epaper model | ||||||
|     Use model parameter to specify display OR |     Use model parameter to specify display OR | ||||||
|     Crate a custom display with given width and height""" |     Crate a custom display with given width and height""" | ||||||
|  |  | ||||||
|     self.background_colour = 'white' |  | ||||||
|     self.text_colour = 'black' |  | ||||||
|  |  | ||||||
|     if (model != None) and (width == None) and (height == None): |     if (model != None) and (width == None) and (height == None): | ||||||
|       display_dimensions = { |       display_dimensions = { | ||||||
|       'epd_7_in_5_v2_colour': (800, 400), |       'epd_7_in_5_v2_colour': (800, 400), | ||||||
| @@ -78,15 +77,15 @@ class layout: | |||||||
|       self.bottom_section_height = (self.display_height - |       self.bottom_section_height = (self.display_height - | ||||||
|         self.top_section_height - self.middle_section_height) |         self.top_section_height - self.middle_section_height) | ||||||
|  |  | ||||||
|     logging.debug('top-section size: {} x {} px'.format( |     logger.debug('top-section size: {} x {} px'.format( | ||||||
|       self.top_section_width, self.top_section_height)) |       self.top_section_width, self.top_section_height)) | ||||||
|     logging.debug('middle-section size: {} x {} px'.format( |     logger.debug('middle-section size: {} x {} px'.format( | ||||||
|       self.middle_section_width, self.middle_section_height)) |       self.middle_section_width, self.middle_section_height)) | ||||||
|     logging.debug('bottom-section size: {} x {} px'.format( |     logger.debug('bottom-section size: {} x {} px'.format( | ||||||
|       self.bottom_section_width, self.bottom_section_height)) |       self.bottom_section_width, self.bottom_section_height)) | ||||||
|  |  | ||||||
|  |  | ||||||
|   def get_section_size(self, section): |   def get_size(self, section): | ||||||
|     """Enter top/middle/bottom to get the size of the section as a tuple: |     """Enter top/middle/bottom to get the size of the section as a tuple: | ||||||
|     (width, height)""" |     (width, height)""" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ Copyright by aceisace | |||||||
| """ | """ | ||||||
| import json | import json | ||||||
| import os | import os | ||||||
|  | from inkycal.config.layout import layout | ||||||
|  |  | ||||||
| class settings: | class settings: | ||||||
|   """Load and validate settings from the settings file""" |   """Load and validate settings from the settings file""" | ||||||
| @@ -25,7 +26,6 @@ class settings: | |||||||
|   def __init__(self, settings_file_path): |   def __init__(self, settings_file_path): | ||||||
|     """Load settings from path (folder or settings.json file)""" |     """Load settings from path (folder or settings.json file)""" | ||||||
|     try: |     try: | ||||||
|       # If |  | ||||||
|       if settings_file_path.endswith('settings.json'): |       if settings_file_path.endswith('settings.json'): | ||||||
|         folder = settings_file_path.split('/settings.json')[0] |         folder = settings_file_path.split('/settings.json')[0] | ||||||
|       else: |       else: | ||||||
| @@ -39,14 +39,23 @@ class settings: | |||||||
|     except FileNotFoundError: |     except FileNotFoundError: | ||||||
|       print('No settings file found in specified location') |       print('No settings file found in specified location') | ||||||
|  |  | ||||||
|  |     # Validate the settings | ||||||
|     self._validate() |     self._validate() | ||||||
|  |  | ||||||
|  |     # Get the height-percentages of the modules | ||||||
|  |     heights = [_['height']/100 for _ in self._settings['panels']] | ||||||
|  |  | ||||||
|  |     self.layout = layout(model=self.model) | ||||||
|  |     self.layout.create_sections(top_section= heights[0], | ||||||
|  |                            middle_section=heights[1], | ||||||
|  |                            bottom_section=heights[2]) | ||||||
|  |  | ||||||
|   def _validate(self): |   def _validate(self): | ||||||
|     """Validate the basic config""" |     """Validate the basic config""" | ||||||
|     settings = self._settings |     settings = self._settings | ||||||
|  |  | ||||||
|     required =  ['language', 'units', 'hours', 'model', 'calibration_hours', |     required =  ['language', 'units', 'hours', 'model', 'calibration_hours'] | ||||||
|             'display_orientation'] |             #'display_orientation'] | ||||||
|  |  | ||||||
|     # Check if all required settings exist |     # Check if all required settings exist | ||||||
|     for param in required: |     for param in required: | ||||||
| @@ -60,7 +69,7 @@ class settings: | |||||||
|     self.hours = settings['hours'] |     self.hours = settings['hours'] | ||||||
|     self.model = settings['model'] |     self.model = settings['model'] | ||||||
|     self.calibration_hours = settings['calibration_hours'] |     self.calibration_hours = settings['calibration_hours'] | ||||||
|     self.display_orientation = settings['display_orientation'] |     #self.display_orientation = settings['display_orientation'] | ||||||
|  |  | ||||||
|     # Validate the parameters |     # Validate the parameters | ||||||
|     if (not isinstance(self.language, str) or self.language not in |     if (not isinstance(self.language, str) or self.language not in | ||||||
| @@ -87,30 +96,31 @@ class settings: | |||||||
|       print('calibration_hours not supported, switching to fallback, [0,12,18]') |       print('calibration_hours not supported, switching to fallback, [0,12,18]') | ||||||
|       self.calibration_hours = [0,12,18] |       self.calibration_hours = [0,12,18] | ||||||
|  |  | ||||||
|     if (not isinstance(self.display_orientation, str) or self.display_orientation not in | ##    if (not isinstance(self.display_orientation, str) or self.display_orientation not in | ||||||
|         self._supported_display_orientation): | ##        self._supported_display_orientation): | ||||||
|       print('display orientation not supported, switching to fallback, normal') | ##      print('display orientation not supported, switching to fallback, normal') | ||||||
|       self.display_orientation = 'normal' | ##      self.display_orientation = 'normal' | ||||||
|  |  | ||||||
|     print('Settings file loaded') |     print('Settings file OK!') | ||||||
|  |  | ||||||
|   def _active_modules(self): |   def active_modules(self): | ||||||
|     modules = [section['type'] for section in self._settings['panels']] |     modules = [section['type'] for section in self._settings['panels']] | ||||||
|     return modules |     return modules | ||||||
|  |  | ||||||
|   def get_config(self, module_name): |   def get_config(self, module_name): | ||||||
|     """Ge the config of this module""" |     """Ge the config of this module (size, config)""" | ||||||
|     if module_name not in self._active_modules(): |     if module_name not in self.active_modules(): | ||||||
|       print('No config is available for this module') |       print('No config is available for this module') | ||||||
|     else: |     else: | ||||||
|       for section in self._settings['panels']: |       for section in self._settings['panels']: | ||||||
|         if section['type'] == module_name: |         if section['type'] == module_name: | ||||||
|           config = section['config'] |           config = section['config'] | ||||||
|     return config |           size = self.layout.get_size(self.get_position(module_name)) | ||||||
|  |     return {'size':size, 'config':config} | ||||||
|  |  | ||||||
|   def get_position(self, module_name): |   def get_position(self, module_name): | ||||||
|     """Get the position of this module's image on the display""" |     """Get the position of this module's image on the display""" | ||||||
|     if module_name not in self._active_modules(): |     if module_name not in self.active_modules(): | ||||||
|       print('No position is available for this module') |       print('No position is available for this module') | ||||||
|     else: |     else: | ||||||
|       for section in self._settings['panels']: |       for section in self._settings['panels']: | ||||||
|   | |||||||
| @@ -107,11 +107,11 @@ def write(image, xy, box_size, text, font=None, **kwargs): | |||||||
|  |  | ||||||
|   # Truncate text if text is too long so it can fit inside the box |   # Truncate text if text is too long so it can fit inside the box | ||||||
|   if (text_width, text_height) > (box_width, box_height): |   if (text_width, text_height) > (box_width, box_height): | ||||||
|     logging.debug('text too big for space, truncating now...') |     logging.debug(('truncating {}'.format(text))) | ||||||
|     while (text_width, text_height) > (box_width, box_height): |     while (text_width, text_height) > (box_width, box_height): | ||||||
|       text=text[0:-1] |       text=text[0:-1] | ||||||
|       text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] |       text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1] | ||||||
|     logging.debug(('truncated text:', text)) |     logging.debug((text)) | ||||||
|  |  | ||||||
|   # Align text to desired position |   # Align text to desired position | ||||||
|   if alignment == "center" or None: |   if alignment == "center" or None: | ||||||
| @@ -178,19 +178,24 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)): | |||||||
|   """ |   """ | ||||||
|    |    | ||||||
|   colour='black' |   colour='black' | ||||||
|  |    | ||||||
|   # size from function paramter |   # size from function paramter | ||||||
|   width, height = size[0]*(1-shrinkage[0]), size[1]*(1-shrinkage[1]) |   width, height = int(size[0]*(1-shrinkage[0])), int(size[1]*(1-shrinkage[1])) | ||||||
|  |  | ||||||
|  |   # shift cursor to move rectangle to center | ||||||
|   offset_x, offset_y = int((size[0] - width)/2), int((size[1]- height)/2) |   offset_x, offset_y = int((size[0] - width)/2), int((size[1]- height)/2) | ||||||
|    |    | ||||||
|   x, y, diameter = xy[0]+offset_x, xy[1]+offset_y, radius*2 |   x, y, diameter = xy[0]+offset_x, xy[1]+offset_y, radius*2 | ||||||
|   # lenght of rectangle size |   # lenght of rectangle size | ||||||
|   a,b = (width - diameter), (height-diameter) |   a,b = (width - diameter), (height-diameter) | ||||||
|  |  | ||||||
|   # Set coordinates for round square |   # Set coordinates for staright lines | ||||||
|   p1, p2 = (x+radius, y), (x+radius+a, y) |   p1, p2 = (x+radius, y), (x+radius+a, y) | ||||||
|   p3, p4 = (x+width, y+radius), (x+width, y+radius+b) |   p3, p4 = (x+width, y+radius), (x+width, y+radius+b) | ||||||
|   p5, p6 = (p2[0], y+height), (p1[0], y+height) |   p5, p6 = (p2[0], y+height), (p1[0], y+height) | ||||||
|   p7, p8  = (x, p4[1]), (x,p3[1]) |   p7, p8  = (x, p4[1]), (x,p3[1]) | ||||||
|  |   if radius != 0: | ||||||
|  |     # Set coordinates for arcs | ||||||
|     c1, c2 = (x,y), (x+diameter, y+diameter) |     c1, c2 = (x,y), (x+diameter, y+diameter) | ||||||
|     c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter) |     c3, c4 = ((x+width)-diameter, y), (x+width, y+diameter) | ||||||
|     c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height) |     c5, c6 = ((x+width)-diameter, (y+height)-diameter), (x+width, y+height) | ||||||
| @@ -198,11 +203,12 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)): | |||||||
|  |  | ||||||
|   # Draw lines and arcs, creating a square with round corners |   # Draw lines and arcs, creating a square with round corners | ||||||
|   draw = ImageDraw.Draw(image) |   draw = ImageDraw.Draw(image) | ||||||
|  |  | ||||||
|   draw.line( (p1, p2) , fill=colour, width = thickness) |   draw.line( (p1, p2) , fill=colour, width = thickness) | ||||||
|   draw.line( (p3, p4) , fill=colour, width = thickness) |   draw.line( (p3, p4) , fill=colour, width = thickness) | ||||||
|   draw.line( (p5, p6) , fill=colour, width = thickness) |   draw.line( (p5, p6) , fill=colour, width = thickness) | ||||||
|   draw.line( (p7, p8) , fill=colour, width = thickness) |   draw.line( (p7, p8) , fill=colour, width = thickness) | ||||||
|  |  | ||||||
|  |   if radius != 0: | ||||||
|     draw.arc(  (c1, c2) , 180, 270, fill=colour, width=thickness) |     draw.arc(  (c1, c2) , 180, 270, fill=colour, width=thickness) | ||||||
|     draw.arc(  (c3, c4) , 270, 360, fill=colour, width=thickness) |     draw.arc(  (c3, c4) , 270, 360, fill=colour, width=thickness) | ||||||
|     draw.arc(  (c5, c6) , 0, 90, fill=colour, width=thickness) |     draw.arc(  (c5, c6) , 0, 90, fill=colour, width=thickness) | ||||||
| @@ -230,18 +236,6 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)): | |||||||
| ##  return image | ##  return image | ||||||
|  |  | ||||||
|  |  | ||||||
| """Not required anymore?""" |  | ||||||
| ##def fix_ical(ical_url): |  | ||||||
| ##  """Use iCalendars in compatability mode (without alarms)""" |  | ||||||
| ##  ical = str(urlopen(ical_url).read().decode()) |  | ||||||
| ##  beginAlarmIndex = 0 |  | ||||||
| ##  while beginAlarmIndex >= 0: |  | ||||||
| ##    beginAlarmIndex = ical.find('BEGIN:VALARM') |  | ||||||
| ##    if beginAlarmIndex >= 0: |  | ||||||
| ##      endAlarmIndex = ical.find('END:VALARM') |  | ||||||
| ##      ical = ical[:beginAlarmIndex] + ical[endAlarmIndex+12:] |  | ||||||
| ##  return ical |  | ||||||
|  |  | ||||||
|  |  | ||||||
| """Not required anymore?""" | """Not required anymore?""" | ||||||
| ##def image_cleanup(): | ##def image_cleanup(): | ||||||
| @@ -296,7 +290,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1,0.1)): | |||||||
| ##  with open(path+'release.txt','r') as file: | ##  with open(path+'release.txt','r') as file: | ||||||
| ##    lines = file.readlines() | ##    lines = file.readlines() | ||||||
| ##    installed_release = lines[0].rstrip() | ##    installed_release = lines[0].rstrip() | ||||||
| ## |  | ||||||
| ##  temp = subp.check_output(['curl','-s','https://github.com/aceisace/Inky-Calendar/releases/latest']) | ##  temp = subp.check_output(['curl','-s','https://github.com/aceisace/Inky-Calendar/releases/latest']) | ||||||
| ##  latest_release_url = str(temp).split('"')[1] | ##  latest_release_url = str(temp).split('"')[1] | ||||||
| ##  latest_release = latest_release_url.split('/tag/')[1] | ##  latest_release = latest_release_url.split('/tag/')[1] | ||||||
|   | |||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | ##from .inkycal_agenda import agenda | ||||||
|  | ##from .inkycal_calendar import calendar | ||||||
|  | ##from .inkycal_weather import weather | ||||||
|  | ##from .inkycal_rss import rss | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import arrow | |||||||
| from urllib.request import urlopen | from urllib.request import urlopen | ||||||
| import logging | import logging | ||||||
| import time # timezone, timing speed of execution | import time # timezone, timing speed of execution | ||||||
|  | import os | ||||||
|  |  | ||||||
| try: | try: | ||||||
|   import recurring_ical_events |   import recurring_ical_events | ||||||
| @@ -30,20 +31,15 @@ except ModuleNotFoundError: | |||||||
|   print('icalendar library could not be found. Please install this with:') |   print('icalendar library could not be found. Please install this with:') | ||||||
|   print('pip3 install icalendar') |   print('pip3 install icalendar') | ||||||
|  |  | ||||||
| urls = [ |  | ||||||
|   # Default calendar | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|   'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics', | logger = logging.getLogger(filename) | ||||||
|   # inkycal debug calendar | logger.setLevel(level=logging.INFO) | ||||||
|   'https://calendar.google.com/calendar/ical/6nqv871neid5l0t7hgk6jgr24c%40group.calendar.google.com/private-c9ab692c99fb55360cbbc28bf8dedb3a/basic.ics' |  | ||||||
|   ] |  | ||||||
|  |  | ||||||
| class icalendar: | class icalendar: | ||||||
|   """iCalendar parsing moudule for inkycal. |   """iCalendar parsing moudule for inkycal. | ||||||
|   Parses events from given iCalendar URLs / paths""" |   Parses events from given iCalendar URLs / paths""" | ||||||
|  |  | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.DEBUG) |  | ||||||
|  |  | ||||||
|   def __init__(self): |   def __init__(self): | ||||||
|     self.icalendars = [] |     self.icalendars = [] | ||||||
|     self.parsed_events = [] |     self.parsed_events = [] | ||||||
| @@ -82,7 +78,7 @@ class icalendar: | |||||||
|  |  | ||||||
|     # Add the parsed icalendar/s to the self.icalendars list |     # Add the parsed icalendar/s to the self.icalendars list | ||||||
|     if ical: self.icalendars += ical |     if ical: self.icalendars += ical | ||||||
|     logging.info('loaded iCalendars from URLs') |     logger.info('loaded iCalendars from URLs') | ||||||
|  |  | ||||||
|   def load_from_file(self, filepath): |   def load_from_file(self, filepath): | ||||||
|     """Input a string or list of strings containing valid iCalendar filepaths |     """Input a string or list of strings containing valid iCalendar filepaths | ||||||
| @@ -97,7 +93,7 @@ class icalendar: | |||||||
|       raise Exception ("Input: '{}' is not a string or list!".format(url)) |       raise Exception ("Input: '{}' is not a string or list!".format(url)) | ||||||
|  |  | ||||||
|     self.icalendars += icals |     self.icalendars += icals | ||||||
|     logging.info('loaded iCalendars from filepaths') |     logger.info('loaded iCalendars from filepaths') | ||||||
|  |  | ||||||
|   def get_events(self, timeline_start, timeline_end, timezone=None): |   def get_events(self, timeline_start, timeline_end, timezone=None): | ||||||
|     """Input an arrow (time) object for: |     """Input an arrow (time) object for: | ||||||
| @@ -148,8 +144,6 @@ class icalendar: | |||||||
|         if arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00' else 'UTC') |         if arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00' else 'UTC') | ||||||
|       } for ical in recurring_events for events in ical] |       } for ical in recurring_events for events in ical] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # if any recurring events were found, add them to parsed_events |     # if any recurring events were found, add them to parsed_events | ||||||
|     if re_events: self.parsed_events += re_events |     if re_events: self.parsed_events += re_events | ||||||
|  |  | ||||||
| @@ -159,9 +153,9 @@ class icalendar: | |||||||
|     return self.parsed_events |     return self.parsed_events | ||||||
|  |  | ||||||
|   def sort(self): |   def sort(self): | ||||||
|     """Sort all parsed events""" |     """Sort all parsed events in order of beginning time""" | ||||||
|     if not self.parsed_events: |     if not self.parsed_events: | ||||||
|       logging.debug('no events found to be sorted') |       logger.debug('no events found to be sorted') | ||||||
|     else: |     else: | ||||||
|       by_date = lambda event: event['begin'] |       by_date = lambda event: event['begin'] | ||||||
|       self.parsed_events.sort(key=by_date) |       self.parsed_events.sort(key=by_date) | ||||||
| @@ -208,7 +202,7 @@ class icalendar: | |||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     if not self.parsed_events: |     if not self.parsed_events: | ||||||
|       logging.debug('no events found to be shown') |       logger.debug('no events found to be shown') | ||||||
|     else: |     else: | ||||||
|       line_width = max(len(_['title']) for _ in self.parsed_events) |       line_width = max(len(_['title']) for _ in self.parsed_events) | ||||||
|       for events in self.parsed_events: |       for events in self.parsed_events: | ||||||
| @@ -217,6 +211,18 @@ class icalendar: | |||||||
|         print('{0} {1} | {2} | {3}'.format( |         print('{0} {1} | {2} | {3}'.format( | ||||||
|           title, ' ' * (line_width - len(title)), begin, end)) |           title, ' ' * (line_width - len(title)), begin, end)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |   print('running {0} in standalone mode'.format(filename)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | urls = [ | ||||||
|  |   # Default calendar | ||||||
|  |   'https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics', | ||||||
|  |   # inkycal debug calendar | ||||||
|  |   'https://calendar.google.com/calendar/ical/6nqv871neid5l0t7hgk6jgr24c%40group.calendar.google.com/private-c9ab692c99fb55360cbbc28bf8dedb3a/basic.ics' | ||||||
|  |   ] | ||||||
|  |  | ||||||
| ##a = icalendar() | ##a = icalendar() | ||||||
| ##a.load_url(urls) | ##a.load_url(urls) | ||||||
| ##a.get_events(arrow.now(), arrow.now().shift(weeks=4), timezone = a.get_system_tz()) | ##a.get_events(arrow.now(), arrow.now().shift(weeks=4), timezone = a.get_system_tz()) | ||||||
|   | |||||||
| @@ -5,85 +5,74 @@ Agenda module for Inky-Calendar Project | |||||||
| Copyright by aceisace | Copyright by aceisace | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | from inkycal.modules.template import inkycal_module | ||||||
| from inkycal.custom import * | from inkycal.custom import * | ||||||
| import calendar as cal |  | ||||||
| import arrow |  | ||||||
| from inkycal.modules.ical_parser import icalendar | from inkycal.modules.ical_parser import icalendar | ||||||
|  |  | ||||||
| size = (400, 520) | import calendar as cal | ||||||
| config = {'week_starts_on': 'Monday', 'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']} | import arrow | ||||||
|  |  | ||||||
|  | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|  | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
|  |  | ||||||
| class agenda: | class agenda(inkycal_module): | ||||||
|   """Agenda class |   """Agenda class | ||||||
|   Create agenda and show events from given icalendars |   Create agenda and show events from given icalendars | ||||||
|   """ |   """ | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.DEBUG) |  | ||||||
|  |  | ||||||
|   def __init__(self, section_size, section_config): |   def __init__(self, section_size, section_config): | ||||||
|     """Initialize inkycal_agenda module""" |     """Initialize inkycal_agenda module""" | ||||||
|     self.name = os.path.basename(__file__).split('.py')[0] |  | ||||||
|     self.config = section_config |  | ||||||
|     self.width, self.height = section_size |  | ||||||
|     self.background_colour =  'white' |  | ||||||
|     self.font_colour = 'black' |  | ||||||
|     self.fontsize = 12 |  | ||||||
|     self.font = ImageFont.truetype( |  | ||||||
|       fonts['NotoSans-SemiCondensed'], size = self.fontsize) |  | ||||||
|     self.padding_x = 0.02 #rename to margin? |  | ||||||
|     self.padding_y = 0.05 |  | ||||||
|  |  | ||||||
|     # Section specific config |     super().__init__(section_size, section_config) | ||||||
|     # Format for formatting dates |     # 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 name | ||||||
|  |     self.name = filename | ||||||
|  |  | ||||||
|  |     # module specific parameters | ||||||
|     self.date_format = 'ddd D MMM' |     self.date_format = 'ddd D MMM' | ||||||
|     # Fromat for formatting event timings |     self.time_format = "HH:mm" | ||||||
|     self.time_format = "HH:mm" #use auto for 24/12 hour format? |     self.language = 'en' | ||||||
|     self.language = 'en' # Grab from settings file? |  | ||||||
|     self.timezone = get_system_tz() |     self.timezone = get_system_tz() | ||||||
|     # urls of icalendars |  | ||||||
|     self.ical_urls = config['ical_urls'] |     self.ical_urls = config['ical_urls'] | ||||||
|     # filepaths of icalendar files |  | ||||||
|     self.ical_files = [] |     self.ical_files = [] | ||||||
|  |  | ||||||
|  |     # give an OK message | ||||||
|     print('{0} loaded'.format(self.name)) |     print('{0} loaded'.format(self.name)) | ||||||
|  |  | ||||||
|   def set(self, **kwargs): |   def _validate(self): | ||||||
|     """Manually set some parameters of this module""" |     """Validate module-specific parameters""" | ||||||
|  |     if not isinstance(self.date_format, str): | ||||||
|     for key, value in kwargs.items(): |       print('date_format has to be an arrow-compatible token') | ||||||
|       if key in self.__dict__: |     if not isinstance(self.time_format, str): | ||||||
|         setattr(self, key, value) |       print('time_format has to be an arrow-compatible token') | ||||||
|       else: |     if not isinstance(self.language, str): | ||||||
|         print('{0} does not exist'.format(key)) |       print('language has to be a string: "en" ') | ||||||
|         pass |     if not isinstance(self.timezone, str): | ||||||
|  |       print('The timezone has bo be a string.') | ||||||
|   def get(self, **kwargs): |     if not isinstance(self.ical_urls, list): | ||||||
|     """Manually get some parameters of this module""" |       print('ical_urls has to be a list ["url1", "url2"] ') | ||||||
|  |     if not isinstance(self.ical_files, list): | ||||||
|     for key, value in kwargs.items(): |       print('ical_files has to be a list ["path1", "path2"] ') | ||||||
|       if key in self.__dict__: |  | ||||||
|         getattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get_options(self): |  | ||||||
|     """Get all options which can be changed""" |  | ||||||
|  |  | ||||||
|     return self.__dict__ |  | ||||||
|  |  | ||||||
|   def generate_image(self): |   def generate_image(self): | ||||||
|     """Generate image for this module""" |     """Generate image for this module""" | ||||||
|  |  | ||||||
|     # Define new image size with respect to padding |     # Define new image size with respect to padding | ||||||
|     im_width = int(self.width - (self.width * 2 * self.padding_x)) |     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||||
|     im_height = int(self.height - (self.height * 2 * self.padding_y)) |     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||||
|     im_size = im_width, im_height |     im_size = im_width, im_height | ||||||
|  |  | ||||||
|     logging.info('Image size: {0}'.format(im_size)) |     logger.info('Image size: {0}'.format(im_size)) | ||||||
|  |  | ||||||
|     # Create an image for black pixels and one for coloured pixels |     # Create an image for black pixels and one for coloured pixels | ||||||
|     im_black = Image.new('RGB', size = im_size, color = self.background_colour) |     im_black = Image.new('RGB', size = im_size, color = 'white') | ||||||
|     im_colour = Image.new('RGB', size = im_size, color = 'white') |     im_colour = Image.new('RGB', size = im_size, color = 'white') | ||||||
|  |  | ||||||
|     # Calculate the max number of lines that can fit on the image |     # Calculate the max number of lines that can fit on the image | ||||||
| @@ -91,7 +80,7 @@ class agenda: | |||||||
|     line_height = int(self.font.getsize('hg')[1]) + line_spacing |     line_height = int(self.font.getsize('hg')[1]) + line_spacing | ||||||
|     line_width = im_width |     line_width = im_width | ||||||
|     max_lines = im_height // line_height |     max_lines = im_height // line_height | ||||||
|     logging.debug(('max lines:',max_lines)) |     logger.debug(('max lines:',max_lines)) | ||||||
|  |  | ||||||
|     # Create timeline for agenda |     # Create timeline for agenda | ||||||
|     now = arrow.now() |     now = arrow.now() | ||||||
| @@ -122,7 +111,7 @@ class agenda: | |||||||
|     date_width = int(max([self.font.getsize( |     date_width = int(max([self.font.getsize( | ||||||
|           dates['begin'].format(self.date_format, locale=self.language))[0] |           dates['begin'].format(self.date_format, locale=self.language))[0] | ||||||
|           for dates in agenda_events]) * 1.2) |           for dates in agenda_events]) * 1.2) | ||||||
|     logging.debug(('date_width:', date_width)) |     logger.debug(('date_width:', date_width)) | ||||||
|  |  | ||||||
|     # Check if any events were filtered |     # Check if any events were filtered | ||||||
|     if upcoming_events: |     if upcoming_events: | ||||||
| @@ -131,23 +120,23 @@ class agenda: | |||||||
|       time_width = int(max([self.font.getsize( |       time_width = int(max([self.font.getsize( | ||||||
|           events['begin'].format(self.time_format, locale=self.language))[0] |           events['begin'].format(self.time_format, locale=self.language))[0] | ||||||
|           for events in upcoming_events]) * 1.2) |           for events in upcoming_events]) * 1.2) | ||||||
|       logging.debug(('time_width:', time_width)) |       logger.debug(('time_width:', time_width)) | ||||||
|  |  | ||||||
|       # Calculate x-pos for time |       # Calculate x-pos for time | ||||||
|       x_time = date_width |       x_time = date_width | ||||||
|       logging.debug(('x-time:', x_time)) |       logger.debug(('x-time:', x_time)) | ||||||
|  |  | ||||||
|       # Find out how much space is left for event titles |       # Find out how much space is left for event titles | ||||||
|       event_width = im_width - time_width - date_width |       event_width = im_width - time_width - date_width | ||||||
|       logging.debug(('width for events:', event_width)) |       logger.debug(('width for events:', event_width)) | ||||||
|  |  | ||||||
|       # Calculate x-pos for event titles |       # Calculate x-pos for event titles | ||||||
|       x_event = date_width + time_width |       x_event = date_width + time_width | ||||||
|       logging.debug(('x-event:', x_event)) |       logger.debug(('x-event:', x_event)) | ||||||
|  |  | ||||||
|       # Calculate positions for each line |       # Calculate positions for each line | ||||||
|       line_pos = [(0, int(line * line_height)) for line in range(max_lines)] |       line_pos = [(0, int(line * line_height)) for line in range(max_lines)] | ||||||
|       logging.debug(('line_pos:', line_pos)) |       logger.debug(('line_pos:', line_pos)) | ||||||
|  |  | ||||||
|       # Merge list of dates and list of events |       # Merge list of dates and list of events | ||||||
|       agenda_events += upcoming_events |       agenda_events += upcoming_events | ||||||
| @@ -159,7 +148,7 @@ class agenda: | |||||||
|       # Delete more entries than can be displayed (max lines) |       # Delete more entries than can be displayed (max lines) | ||||||
|       del agenda_events[max_lines:] |       del agenda_events[max_lines:] | ||||||
|  |  | ||||||
|       #print(agenda_events) |       self._agenda_events = agenda_events | ||||||
|  |  | ||||||
|       cursor = 0 |       cursor = 0 | ||||||
|       for _ in agenda_events: |       for _ in agenda_events: | ||||||
| @@ -205,19 +194,15 @@ class agenda: | |||||||
|  |  | ||||||
|         cursor += 1 |         cursor += 1 | ||||||
|  |  | ||||||
|       logging.info('no events found') |       logger.info('no events found') | ||||||
|  |  | ||||||
| ############################################################################ |  | ||||||
| # Exception handling |  | ||||||
| ############################################################################ |  | ||||||
|  |  | ||||||
|     # Save image of black and colour channel in image-folder |     # Save image of black and colour channel in image-folder | ||||||
|     im_black.save(images+self.name+'.png') |     im_black.save(images+self.name+'.png') | ||||||
|     im_colour.save(images+self.name+'_colour.png') |     im_colour.save(images+self.name+'_colour.png') | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   print('running {0} in standalone mode'.format( |   print('running {0} in standalone mode'.format(filename)) | ||||||
|     os.path.basename(__file__).split('.py')[0])) |  | ||||||
|  |  | ||||||
|   # remove below line later! | ##size = (400, 520) | ||||||
|   a = agenda(size, config).generate_image() | ##config = {'week_starts_on': 'Monday', 'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']} | ||||||
|  | ##a = agenda(size, config).generate_image() | ||||||
|   | |||||||
| @@ -4,81 +4,62 @@ | |||||||
| Calendar module for Inky-Calendar Project | Calendar module for Inky-Calendar Project | ||||||
| Copyright by aceisace | Copyright by aceisace | ||||||
| """ | """ | ||||||
|  | from inkycal.modules.template import inkycal_module | ||||||
| from inkycal.custom import * | from inkycal.custom import * | ||||||
|  |  | ||||||
| import calendar as cal | import calendar as cal | ||||||
| import arrow | import arrow | ||||||
|  |  | ||||||
| size = (400, 520) | filename = os.path.basename(__file__).split('.py')[0] | ||||||
| config = {'week_starts_on': 'Monday', 'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']} | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
|  | class calendar(inkycal_module): | ||||||
| class calendar: |  | ||||||
|   """Calendar class |   """Calendar class | ||||||
|   Create monthly calendar and show events from given icalendars |   Create monthly calendar and show events from given icalendars | ||||||
|   """ |   """ | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.DEBUG) |  | ||||||
|  |  | ||||||
|   def __init__(self, section_size, section_config): |   def __init__(self, section_size, section_config): | ||||||
|     """Initialize inkycal_calendar module""" |     """Initialize inkycal_calendar module""" | ||||||
|  |  | ||||||
|     self.name = os.path.basename(__file__).split('.py')[0] |     super().__init__(section_size, section_config) | ||||||
|     self.config = section_config |  | ||||||
|     self.width, self.height = section_size |     # Module specific parameters | ||||||
|     self.fontsize = 12 |     required = ['week_starts_on'] | ||||||
|     self.font = ImageFont.truetype( |     for param in required: | ||||||
|       fonts['NotoSans-SemiCondensed'], size = self.fontsize) |       if not param in section_config: | ||||||
|     self.padding_x = 0.02 |         raise Exception('config is missing {}'.format(param)) | ||||||
|     self.padding_y = 0.05 |  | ||||||
|  |     # module name | ||||||
|  |     self.name = filename | ||||||
|  |  | ||||||
|  |     # module specific parameters | ||||||
|  |     self.shuffle_feeds = True | ||||||
|  |  | ||||||
|     self.num_font = ImageFont.truetype( |     self.num_font = ImageFont.truetype( | ||||||
|       fonts['NotoSans-SemiCondensed'], size = self.fontsize) |       fonts['NotoSans-SemiCondensed'], size = self.fontsize) | ||||||
|     self.weekstart = 'Monday' |     self.weekstart = self.config['week_starts_on'] | ||||||
|     self.show_events = True |     self.show_events = True | ||||||
|     self.date_format = 'D MMM' # used for dates  |     self.date_format = 'D MMM' # used for dates  | ||||||
|     self.time_format = "HH:mm" # used for timings |     self.time_format = "HH:mm" # used for timings | ||||||
|     self.language = 'en' # Grab from settings file? |     self.language = 'en' # Grab from settings file? | ||||||
|      |      | ||||||
|     self.timezone = get_system_tz() |     self.timezone = get_system_tz() | ||||||
|     self.ical_urls = config['ical_urls'] |     self.ical_urls = self.config['ical_urls'] | ||||||
|     self.ical_files = [] |     self.ical_files = [] | ||||||
|  |  | ||||||
|  |     # give an OK message | ||||||
|     print('{0} loaded'.format(self.name)) |     print('{0} loaded'.format(self.name)) | ||||||
|  |  | ||||||
|   def set(self, **kwargs): |  | ||||||
|     """Manually set some parameters of this module""" |  | ||||||
|  |  | ||||||
|     for key, value in kwargs.items(): |  | ||||||
|       if key in self.__dict__: |  | ||||||
|         setattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get(self, **kwargs): |  | ||||||
|     """Manually get some parameters of this module""" |  | ||||||
|  |  | ||||||
|     for key, value in kwargs.items(): |  | ||||||
|       if key in self.__dict__: |  | ||||||
|         getattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get_options(self): |  | ||||||
|     """Get all options which can be changed""" |  | ||||||
|  |  | ||||||
|     return self.__dict__ |  | ||||||
|  |  | ||||||
|   def generate_image(self): |   def generate_image(self): | ||||||
|     """Generate image for this module""" |     """Generate image for this module""" | ||||||
|  |  | ||||||
|     # Define new image size with respect to padding |     # Define new image size with respect to padding | ||||||
|     im_width = int(self.width - (self.width * 2 * self.padding_x)) |     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||||
|     im_height = int(self.height - (self.height * 2 * self.padding_y)) |     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||||
|     im_size = im_width, im_height |     im_size = im_width, im_height | ||||||
|  |  | ||||||
|     logging.info('Image size: {0}'.format(im_size)) |     logger.info('Image size: {0}'.format(im_size)) | ||||||
|  |  | ||||||
|     # Create an image for black pixels and one for coloured pixels |     # Create an image for black pixels and one for coloured pixels | ||||||
|     im_black = Image.new('RGB', size = im_size, color = 'white') |     im_black = Image.new('RGB', size = im_size, color = 'white') | ||||||
| @@ -91,13 +72,13 @@ class calendar: | |||||||
|     if self.show_events == True: |     if self.show_events == True: | ||||||
|       calendar_height = int(self.height*0.6) |       calendar_height = int(self.height*0.6) | ||||||
|       events_height = int(self.height*0.25) |       events_height = int(self.height*0.25) | ||||||
|       logging.debug('calendar-section size: {0} x {1} px'.format( |       logger.debug('calendar-section size: {0} x {1} px'.format( | ||||||
|         im_width, calendar_height)) |         im_width, calendar_height)) | ||||||
|       logging.debug('events-section size: {0} x {1} px'.format( |       logger.debug('events-section size: {0} x {1} px'.format( | ||||||
|         im_width, events_height)) |         im_width, events_height)) | ||||||
|     else: |     else: | ||||||
|       calendar_height = self.height - month_name_height - weekday_height |       calendar_height = self.height - month_name_height - weekday_height | ||||||
|       logging.debug('calendar-section size: {0} x {1} px'.format( |       logger.debug('calendar-section size: {0} x {1} px'.format( | ||||||
|         im_width, calendar_height)) |         im_width, calendar_height)) | ||||||
|  |  | ||||||
|     # Create grid and calculate icon sizes |     # Create grid and calculate icon sizes | ||||||
| @@ -141,7 +122,7 @@ class calendar: | |||||||
|     # Set up weeknames in local language and add to main section |     # Set up weeknames in local language and add to main section | ||||||
|     weekday_names = [weekstart.shift(days=+_).format('ddd',locale=self.language) |     weekday_names = [weekstart.shift(days=+_).format('ddd',locale=self.language) | ||||||
|       for _ in range(7)] |       for _ in range(7)] | ||||||
|     logging.debug('weekday names: {}'.format(weekday_names)) |     logger.debug('weekday names: {}'.format(weekday_names)) | ||||||
|  |  | ||||||
|     for _ in range(len(weekday_pos)): |     for _ in range(len(weekday_pos)): | ||||||
|       write( |       write( | ||||||
| @@ -300,8 +281,10 @@ class calendar: | |||||||
|     im_colour.save(images+self.name+'_colour.png') |     im_colour.save(images+self.name+'_colour.png') | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   print('running {0} in standalone mode'.format( |   print('running {0} in standalone mode'.format(filename)) | ||||||
|     os.path.basename(__file__).split('.py')[0])) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ##size = (400, 520) | ||||||
|  | ##config = {'week_starts_on': 'Monday', 'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']} | ||||||
| ##a = calendar(size, config) | ##a = calendar(size, config) | ||||||
| ##a.generate_image() | ##a.generate_image() | ||||||
|   | |||||||
| @@ -1,81 +1,64 @@ | |||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| """ | """ | ||||||
| RSS module for Inky-Calendar Project | RSS module for Inky-Calendar Project | ||||||
| Copyright by aceisace | Copyright by aceisace | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | from inkycal.modules.template import inkycal_module | ||||||
| from inkycal.custom import * | from inkycal.custom import * | ||||||
| from random import shuffle |  | ||||||
|  |  | ||||||
|  | from random import shuffle | ||||||
| try: | try: | ||||||
|   import feedparser |   import feedparser | ||||||
| except ImportError: | except ImportError: | ||||||
|   print('feedparser is not installed! Please install with:') |   print('feedparser is not installed! Please install with:') | ||||||
|   print('pip3 install feedparser') |   print('pip3 install feedparser') | ||||||
|  |  | ||||||
|  | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|  | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
| # Debug Data (not for production use!) | class rss(inkycal_module): | ||||||
| size = (384, 160) |  | ||||||
| config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']} |  | ||||||
| #config = {'rss_urls': ['http://www.tagesschau.de/xml/atom/']} |  | ||||||
| #https://www.tagesschau.de/xml/rss2 |  | ||||||
|  |  | ||||||
| class rss: |  | ||||||
|   """RSS class |   """RSS class | ||||||
|   parses rss feeds from given urls |   parses rss feeds from given urls | ||||||
|   """ |   """ | ||||||
|  |  | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.DEBUG) |  | ||||||
|  |  | ||||||
|   def __init__(self, section_size, section_config): |   def __init__(self, section_size, section_config): | ||||||
|     """Initialize inkycal_rss module""" |     """Initialize inkycal_rss module""" | ||||||
|      |      | ||||||
|     self.name = os.path.basename(__file__).split('.py')[0] |     super().__init__(section_size, section_config) | ||||||
|     self.config = section_config |  | ||||||
|     self.width, self.height = section_size |  | ||||||
|     self.fontsize = 12 |  | ||||||
|     self.padding_x = 0.02 |  | ||||||
|     self.padding_y = 0.05 |  | ||||||
|     self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], |  | ||||||
|                                    size = self.fontsize) |  | ||||||
|  |  | ||||||
|     # module specifc config |     # Module specific parameters | ||||||
|  |     required = ['rss_urls'] | ||||||
|  |     for param in required: | ||||||
|  |       if not param in section_config: | ||||||
|  |         raise Exception('config is missing {}'.format(param)) | ||||||
|  |  | ||||||
|  |     # module name | ||||||
|  |     self.name = filename | ||||||
|  |  | ||||||
|  |     # module specific parameters | ||||||
|     self.shuffle_feeds = True |     self.shuffle_feeds = True | ||||||
|  |  | ||||||
|  |     # give an OK message | ||||||
|     print('{0} loaded'.format(self.name)) |     print('{0} loaded'.format(self.name)) | ||||||
|  |  | ||||||
|   def set(self, **kwargs): |   def _validate(self): | ||||||
|     """Manually set some parameters of this module""" |     """Validate module-specific parameters""" | ||||||
|     for key, value in kwargs.items(): |     if not isinstance(self.shuffle_feeds, bool): | ||||||
|       if key in self.__dict__: |       print('shuffle_feeds has to be a boolean: True/False') | ||||||
|         setattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get(self, **kwargs): |  | ||||||
|     """Manually get some parameters of this module""" |  | ||||||
|     for key, value in kwargs.items(): |  | ||||||
|       if key in self.__dict__: |  | ||||||
|         getattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get_options(self): |  | ||||||
|     """Get all options which can be changed""" |  | ||||||
|     return self.__dict__ |  | ||||||
|  |  | ||||||
|   def generate_image(self): |   def generate_image(self): | ||||||
|     """Generate image for this module""" |     """Generate image for this module""" | ||||||
|  |  | ||||||
|     # Define new image size with respect to padding |     # Define new image size with respect to padding | ||||||
|     im_width = int(self.width - (self.width * 2 * self.padding_x)) |     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||||
|     im_height = int(self.height - (self.height * 2 * self.padding_y)) |     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||||
|     im_size = im_width, im_height |     im_size = im_width, im_height | ||||||
|     logging.info('image size: {} x {} px'.format(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 |     # Create an image for black pixels and one for coloured pixels | ||||||
|     im_black = Image.new('RGB', size = im_size, color = 'white') |     im_black = Image.new('RGB', size = im_size, color = 'white') | ||||||
| @@ -83,7 +66,7 @@ class rss: | |||||||
|  |  | ||||||
|     # Check if internet is available |     # Check if internet is available | ||||||
|     if internet_available() == True: |     if internet_available() == True: | ||||||
|       logging.info('Connection test passed') |       logger.info('Connection test passed') | ||||||
|     else: |     else: | ||||||
|       raise Exception('Network could not be reached :/') |       raise Exception('Network could not be reached :/') | ||||||
|  |  | ||||||
| @@ -101,8 +84,6 @@ class rss: | |||||||
|     line_positions = [ |     line_positions = [ | ||||||
|       (0, spacing_top + _ * line_height ) for _ in range(max_lines)] |       (0, spacing_top + _ * line_height ) for _ in range(max_lines)] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Create list containing all rss-feeds from all rss-feed urls |     # Create list containing all rss-feeds from all rss-feed urls | ||||||
|     parsed_feeds = [] |     parsed_feeds = [] | ||||||
|     for feeds in self.config['rss_urls']: |     for feeds in self.config['rss_urls']: | ||||||
| @@ -149,9 +130,20 @@ class rss: | |||||||
|     im_black.save(images+self.name+'.png', 'PNG') |     im_black.save(images+self.name+'.png', 'PNG') | ||||||
|     im_colour.save(images+self.name+'_colour.png', 'PNG') |     im_colour.save(images+self.name+'_colour.png', 'PNG') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ##def main(): | ||||||
|  | ##  print('Main got executed just now~~~~') | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   print('running {0} in standalone mode'.format( |   print('running {0} in standalone/debug mode'.format(filename)) | ||||||
|     os.path.basename(__file__).split('.py')[0])) | ##else: | ||||||
|  | ##  print(filename, 'imported') | ||||||
|  | ##  main() | ||||||
|    |    | ||||||
| ##a = rss(size, config) | ##a = rss(size, config) | ||||||
| ##a.generate_image() | ##a.generate_image() | ||||||
|  | ##size = (384, 160) | ||||||
|  | ##config = {'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']} | ||||||
|  | #config = {'rss_urls': ['http://www.tagesschau.de/xml/atom/']} | ||||||
|  | #https://www.tagesschau.de/xml/rss2 -> problematic feed | ||||||
|   | |||||||
| @@ -4,46 +4,43 @@ | |||||||
| Weather module for Inky-Calendar software. | Weather module for Inky-Calendar software. | ||||||
| Copyright by aceisace | Copyright by aceisace | ||||||
| """ | """ | ||||||
|  | from inkycal.modules.template import inkycal_module | ||||||
| from inkycal.custom import * | from inkycal.custom import * | ||||||
|  |  | ||||||
| import math, decimal | import math, decimal | ||||||
| import arrow | import arrow | ||||||
| from locale import getdefaultlocale as sys_locale | from locale import getdefaultlocale as sys_locale | ||||||
|  |  | ||||||
| try: | try: | ||||||
|   import pyowm |   import pyowm | ||||||
| except ImportError: | except ImportError: | ||||||
|   print('pyowm is not installed! Please install with:') |   print('pyowm is not installed! Please install with:') | ||||||
|   print('pip3 install pyowm') |   print('pip3 install pyowm') | ||||||
|  |  | ||||||
|  | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|  | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
| # Debug Data (not for production use!) | class weather(inkycal_module): | ||||||
| config = {'api_key': 'secret', 'location': 'Stuttgart, DE'} |  | ||||||
| size = (384,80) |  | ||||||
|  |  | ||||||
| class weather: |  | ||||||
|   """weather class |   """weather class | ||||||
|   parses weather details from openweathermap |   parses weather details from openweathermap | ||||||
|   """ |   """ | ||||||
|  |  | ||||||
|   logger = logging.getLogger(__name__) |  | ||||||
|   logging.basicConfig(level=logging.INFO) |  | ||||||
|  |  | ||||||
|   def __init__(self, section_size, section_config): |   def __init__(self, section_size, section_config): | ||||||
|     """Initialize inkycal_weather module""" |     """Initialize inkycal_weather module""" | ||||||
|     self.name = os.path.basename(__file__).split('.py')[0] |  | ||||||
|     self.config = section_config |  | ||||||
|     self.width, self.height = section_size |  | ||||||
|     self.background_colour =  'white' |  | ||||||
|     self.font_colour = 'black' |  | ||||||
|     self.fontsize = 12 |  | ||||||
|     self.font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], |  | ||||||
|                                    size = self.fontsize) |  | ||||||
|     self.padding_x = 0.02 |  | ||||||
|     self.padding_y = 0.05 |  | ||||||
|      |      | ||||||
|     # Weather-specfic options |     super().__init__(section_size, section_config) | ||||||
|     self.owm = pyowm.OWM(config['api_key']) |  | ||||||
|  |     # Module specific parameters | ||||||
|  |     required = ['api_key','location'] | ||||||
|  |     for param in required: | ||||||
|  |       if not param in section_config: | ||||||
|  |         raise Exception('config is missing {}'.format(param)) | ||||||
|  |  | ||||||
|  |     # module name | ||||||
|  |     self.name = filename | ||||||
|  |  | ||||||
|  |     # module specific parameters | ||||||
|  |     self.owm = pyowm.OWM(self.config['api_key']) | ||||||
|     self.units = 'metric' # metric # imperial |     self.units = 'metric' # metric # imperial | ||||||
|     self.hour_format = '24' # 12 #24 |     self.hour_format = '24' # 12 #24 | ||||||
|     self.timezone = get_system_tz() |     self.timezone = get_system_tz() | ||||||
| @@ -54,52 +51,25 @@ class weather: | |||||||
|     self.locale = sys_locale()[0] |     self.locale = sys_locale()[0] | ||||||
|     self.weatherfont = ImageFont.truetype(fonts['weathericons-regular-webfont'], |     self.weatherfont = ImageFont.truetype(fonts['weathericons-regular-webfont'], | ||||||
|                                           size = self.fontsize) |                                           size = self.fontsize) | ||||||
|  |     # give an OK message | ||||||
|     print('{0} loaded'.format(self.name)) |     print('{0} loaded'.format(self.name)) | ||||||
|  |  | ||||||
|   def set(self, **kwargs): |  | ||||||
|     """Manually set some parameters of this module""" |  | ||||||
|  |  | ||||||
|     for key, value in kwargs.items(): |  | ||||||
|       if key in self.__dict__: |  | ||||||
|         setattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|   def get(self, **kwargs): |  | ||||||
|     """Manually get some parameters of this module""" |  | ||||||
|  |  | ||||||
|     for key, value in kwargs.items(): |  | ||||||
|       if key in self.__dict__: |  | ||||||
|         getattr(self, key, value) |  | ||||||
|       else: |  | ||||||
|         print('{0} does not exist'.format(key)) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   def get_options(self): |  | ||||||
|     """Get all options which can be changed""" |  | ||||||
|  |  | ||||||
|     return self.__dict__ |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   def generate_image(self): |   def generate_image(self): | ||||||
|     """Generate image for this module""" |     """Generate image for this module""" | ||||||
|  |  | ||||||
|     # Define new image size with respect to padding |     # Define new image size with respect to padding | ||||||
|     im_width = int(self.width - (self.width * 2 * self.padding_x)) |     im_width = int(self.width - (self.width * 2 * self.margin_x)) | ||||||
|     im_height = int(self.height - (self.height * 2 * self.padding_y)) |     im_height = int(self.height - (self.height * 2 * self.margin_y)) | ||||||
|     im_size = im_width, im_height |     im_size = im_width, im_height | ||||||
|     logging.info('image size: {} x {} px'.format(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 |     # Create an image for black pixels and one for coloured pixels | ||||||
|     im_black = Image.new('RGB', size = im_size, color = self.background_colour) |     im_black = Image.new('RGB', size = im_size, color = 'white') | ||||||
|     im_colour = Image.new('RGB', size = im_size, color = 'white') |     im_colour = Image.new('RGB', size = im_size, color = 'white') | ||||||
|  |  | ||||||
|     # Check if internet is available |     # Check if internet is available | ||||||
|     if internet_available() == True: |     if internet_available() == True: | ||||||
|       logging.info('Connection test passed') |       logger.info('Connection test passed') | ||||||
|     else: |     else: | ||||||
|       raise Exception('Network could not be reached :(') |       raise Exception('Network could not be reached :(') | ||||||
|  |  | ||||||
| @@ -353,7 +323,7 @@ class weather: | |||||||
|           } |           } | ||||||
|  |  | ||||||
|     for key,val in fc_data.items(): |     for key,val in fc_data.items(): | ||||||
|       logging.info((key,val)) |       logger.info((key,val)) | ||||||
|  |  | ||||||
|     # Get some current weather details |     # Get some current weather details | ||||||
|     temperature = '{}°'.format(weather.get_temperature(unit=temp_unit)['temp']) |     temperature = '{}°'.format(weather.get_temperature(unit=temp_unit)['temp']) | ||||||
| @@ -450,16 +420,16 @@ class weather: | |||||||
|     draw_border(im_black, (col6, row1), (col_width, im_height)) |     draw_border(im_black, (col6, row1), (col_width, im_height)) | ||||||
|     draw_border(im_black, (col7, row1), (col_width, im_height)) |     draw_border(im_black, (col7, row1), (col_width, im_height)) | ||||||
|  |  | ||||||
| ############################################################################## |  | ||||||
| #        Error Handling |  | ||||||
| ############################################################################## |  | ||||||
|  |  | ||||||
|   # Save image of black and colour channel in image-folder |   # Save image of black and colour channel in image-folder | ||||||
|     im_black.save(images+self.name+'.png', "PNG") |     im_black.save(images+self.name+'.png', "PNG") | ||||||
|     im_colour.save(images+self.name+'_colour.png', "PNG") |     im_colour.save(images+self.name+'_colour.png', "PNG") | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   print('running {0} in standalone mode'.format( |   print('running {0} in standalone mode'.format(filename)) | ||||||
|     os.path.basename(__file__).split('.py')[0])) |  | ||||||
|   a = weather(size, config) |  | ||||||
|   a.generate_image() | ##config = {'api_key': 'secret', 'location': 'Stuttgart, DE'} | ||||||
|  | ##size = (384,80) | ||||||
|  | ##a = weather(size, config) | ||||||
|  | ##a.generate_image() | ||||||
|  | # Debug Data (not for production use!) | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								inkycal/modules/template.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								inkycal/modules/template.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | import abc | ||||||
|  | from inkycal.custom import * | ||||||
|  |  | ||||||
|  | class inkycal_module(metaclass=abc.ABCMeta): | ||||||
|  |   """Generic base class for inykcal modules""" | ||||||
|  |  | ||||||
|  |   @classmethod | ||||||
|  |   def __subclasshook__(cls, subclass): | ||||||
|  |     return (hasattr(subclass, 'generate_image') and | ||||||
|  |       callable(subclass.generate_image) or | ||||||
|  |       NotImplemented) | ||||||
|  |  | ||||||
|  |   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_size | ||||||
|  |     self.fontsize = 12 | ||||||
|  |     self.margin_x = 0.02 | ||||||
|  |     self.margin_y = 0.05 | ||||||
|  |     self.font = ImageFont.truetype( | ||||||
|  |       fonts['NotoSans-SemiCondensed'], size = self.fontsize) | ||||||
|  |  | ||||||
|  |   def set(self, help=False, **kwargs): | ||||||
|  |     """Set attributes of class, e.g. class.set(key=value) | ||||||
|  |     see that can be changed by setting help to True | ||||||
|  |     """ | ||||||
|  |     lst = dir(self).copy() | ||||||
|  |     options = [_ for _ in lst if not _.startswith('_')] | ||||||
|  |     if 'logger' in options: options.remove('logger') | ||||||
|  |  | ||||||
|  |     if help == True: | ||||||
|  |       print('The following can be configured:') | ||||||
|  |       print(options) | ||||||
|  |  | ||||||
|  |     for key, value in kwargs.items(): | ||||||
|  |       if key in options: | ||||||
|  |         setattr(self, key, value) | ||||||
|  |         print("set '{}' to '{}'".format(key,value)) | ||||||
|  |       else: | ||||||
|  |         print('{0} does not exist'.format(key)) | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     # Check if validation has been implemented | ||||||
|  |     try: | ||||||
|  |       self._validate() | ||||||
|  |     except AttributeError: | ||||||
|  |       print('no validation implemented') | ||||||
|  |  | ||||||
|  |   @abc.abstractmethod | ||||||
|  |   def generate_image(self): | ||||||
|  |     # Generate image for this module with specified parameters | ||||||
|  |     raise NotImplementedError( | ||||||
|  |       'The developers were too lazy to implement this function') | ||||||
							
								
								
									
										137
									
								
								inkycal/modules/test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								inkycal/modules/test.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | #!/usr/bin/python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | """ | ||||||
|  | Module template for Inky-Calendar Project | ||||||
|  |  | ||||||
|  | Create your own module with this template | ||||||
|  |  | ||||||
|  | Copyright by aceisace | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                           Required imports (do not remove) | ||||||
|  | ############################################################################# | ||||||
|  | # Required for setting up this module | ||||||
|  | from inkycal.modules.template import inkycal_module | ||||||
|  | from inkycal.custom import * | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                           Built-in library imports | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  | # Built-in libraries go here | ||||||
|  | from random import shuffle | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                         External library imports | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  | # For external libraries, which require installing, | ||||||
|  | # use try...except ImportError to check if it has been installed | ||||||
|  | # If it is not found, print a short message on how to install this dependency | ||||||
|  | try: | ||||||
|  |   import feedparser | ||||||
|  | except ImportError: | ||||||
|  |   print('feedparser is not installed! Please install with:') | ||||||
|  |   print('pip3 install feedparser') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                         Filename + logging (do not remove) | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  | # Get the name of this file, set up logging for this filename | ||||||
|  | filename = os.path.basename(__file__).split('.py')[0] | ||||||
|  | logger = logging.getLogger(filename) | ||||||
|  | logger.setLevel(level=logging.INFO) | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                         Class setup | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  | class simple(inkycal_module): | ||||||
|  |   """ Simple Class | ||||||
|  |   Explain what this module does... | ||||||
|  |   """ | ||||||
|  |  | ||||||
|  |   # Initialise the class (do not remove) | ||||||
|  |   def __init__(self, section_size, section_config): | ||||||
|  |     """Initialize inkycal_rss module""" | ||||||
|  |  | ||||||
|  |     # Initialise this module via the inkycal_module template (required) | ||||||
|  |     super().__init__(section_size, section_config) | ||||||
|  |  | ||||||
|  |     # module name (required) | ||||||
|  |     self.name = filename | ||||||
|  |  | ||||||
|  |     # module specific parameters (optional) | ||||||
|  |     self.do_something = True | ||||||
|  |  | ||||||
|  |     # give an OK message (optional) | ||||||
|  |     print('{0} loaded'.format(self.name)) | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                 Validation of module specific parameters                  # | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  |   def _validate(self): | ||||||
|  |     """Validate module-specific parameters""" | ||||||
|  |     # Check the type of module-specific parameters | ||||||
|  |     # This function is optional, but very useful for debugging. | ||||||
|  |  | ||||||
|  |     # Here, we are checking if do_something (from init) is True/False | ||||||
|  |     if not isinstance(self.do_something, bool): | ||||||
|  |       print('do_something has to be a boolean: True/False') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ############################################################################# | ||||||
|  | #                       Generating the image                                # | ||||||
|  | ############################################################################# | ||||||
|  |  | ||||||
|  |   def generate_image(self): | ||||||
|  |     """Generate image for this module""" | ||||||
|  |  | ||||||
|  |     # Define new image size with respect to padding (required) | ||||||
|  |     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 | ||||||
|  |  | ||||||
|  |     # Use logger.info(), logger.debug(), logger.warning() to display | ||||||
|  |     # useful information for the developer | ||||||
|  |     logger.info('image size: {} x {} px'.format(im_width, im_height)) | ||||||
|  |  | ||||||
|  |     # Create an image for black pixels and one for coloured pixels (required) | ||||||
|  |     im_black = Image.new('RGB', size = im_size, color = 'white') | ||||||
|  |     im_colour = Image.new('RGB', size = im_size, color = 'white') | ||||||
|  |  | ||||||
|  |     ################################################################# | ||||||
|  |  | ||||||
|  |     #                    Your code goes here                        # | ||||||
|  |      | ||||||
|  |     # Write/Draw something on the image | ||||||
|  |  | ||||||
|  |     #   You can use these custom functions to help you create the image: | ||||||
|  |     # - write()               -> write text on the image | ||||||
|  |     # - get_fonts()           -> see which fonts are available | ||||||
|  |     # - get_system_tz()       -> Get the system's current timezone | ||||||
|  |     # - auto_fontsize()       -> Scale the fontsize to the provided height | ||||||
|  |     # - textwrap()            -> Split a paragraph into smaller lines | ||||||
|  |     # - internet_available()  -> Check if internet is available | ||||||
|  |     # - draw_border()         -> Draw a border around the specified area | ||||||
|  |  | ||||||
|  |     # If these aren't enough, take a look at python Pillow (imaging library)'s | ||||||
|  |     # documentation. | ||||||
|  |  | ||||||
|  |      | ||||||
|  |     ################################################################# | ||||||
|  |  | ||||||
|  |     # 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') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Check if the module is being run by itself | ||||||
|  | if __name__ == '__main__': | ||||||
|  |   print('running {0} in standalone mode'.format(filename)) | ||||||
		Reference in New Issue
	
	Block a user