Code cleanup, adapting modules for new web-ui
This commit is contained in:
parent
035ce65f06
commit
b1c06b3add
@ -13,6 +13,5 @@ Inkycal is free to use for anyone (non-commercially) and open-source.
|
|||||||
It is mainly developed by `aceisace <https://github.com/aceisace>`_ and a few other developers
|
It is mainly developed by `aceisace <https://github.com/aceisace>`_ and a few other developers
|
||||||
in their free time.
|
in their free time.
|
||||||
|
|
||||||
Developing Inkycal requires a fairly large amount of coffee and the ePaper displays aren't free
|
Developing Inkycal requires a fairly large amount of coffee and free time. We work in our free time for offer you the best software we can write. Please consider a `DONATION <https://www.paypal.me/SaadNaseer>`_ to help keep this project
|
||||||
either. Please consider a `DONATION <https://www.paypal.me/SaadNaseer>`_ to help keep this project
|
|
||||||
well-maintained |:person_bowing:| .
|
well-maintained |:person_bowing:| .
|
||||||
|
@ -22,7 +22,7 @@ copyright = '2020, Ace Isace'
|
|||||||
author = 'Ace Isace'
|
author = 'Ace Isace'
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = '2.0.0beta'
|
release = '2.0.0'
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
@ -46,7 +46,7 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
#
|
#
|
||||||
html_theme = 'alabaster'
|
html_theme = 'classic'
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
@ -8,18 +8,6 @@ Inkycal
|
|||||||
:members:
|
:members:
|
||||||
|
|
||||||
..
|
..
|
||||||
inkycal settings
|
|
||||||
===================
|
|
||||||
.. module:: inkycal.config.settings_parser
|
|
||||||
.. autoclass:: Settings
|
|
||||||
:members:
|
|
||||||
|
|
||||||
inkycal layout
|
|
||||||
===================
|
|
||||||
.. module:: inkycal.config.layout
|
|
||||||
.. autoclass:: Layout
|
|
||||||
:members:
|
|
||||||
|
|
||||||
inkycal calendar
|
inkycal calendar
|
||||||
===================
|
===================
|
||||||
.. module:: inkycal.modules.inkycal_calendar
|
.. module:: inkycal.modules.inkycal_calendar
|
||||||
@ -32,10 +20,10 @@ Inkycal
|
|||||||
.. autoclass:: Agenda
|
.. autoclass:: Agenda
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
inkycal rss
|
inkycal feeds
|
||||||
===================
|
===================
|
||||||
.. module:: inkycal.modules.inkycal_rss
|
.. module:: inkycal.modules.inkycal_feeds
|
||||||
.. autoclass:: RSS
|
.. autoclass:: Feeds
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
inkycal weather
|
inkycal weather
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
# Settings and Layout
|
# Display class (for dirving E-Paper displays)
|
||||||
#from inkycal.config.layout import Layout
|
|
||||||
#from inkycal.config.settings_parser import Settings
|
|
||||||
from inkycal.display import Display
|
from inkycal.display import Display
|
||||||
|
|
||||||
# All supported inkycal_modules
|
# Default modules
|
||||||
import inkycal.modules.inkycal_agenda
|
import inkycal.modules.inkycal_agenda
|
||||||
import inkycal.modules.inkycal_calendar
|
import inkycal.modules.inkycal_calendar
|
||||||
import inkycal.modules.inkycal_weather
|
import inkycal.modules.inkycal_weather
|
||||||
import inkycal.modules.inkycal_feeds
|
import inkycal.modules.inkycal_feeds
|
||||||
|
import inkycal.modules.inkycal_todoist
|
||||||
# import inkycal.modules.inkycal_image
|
# import inkycal.modules.inkycal_image
|
||||||
# import inkycal.modules.inkycal_server
|
# import inkycal.modules.inkycal_server
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from .layout import Layout
|
|
@ -1,127 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Layout module for Inky-Calendar software.
|
|
||||||
Copyright by aceisace
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
filename = os.path.basename(__file__).split('.py')[0]
|
|
||||||
logger = logging.getLogger(filename)
|
|
||||||
logger.setLevel(level=logging.INFO)
|
|
||||||
|
|
||||||
class Layout:
|
|
||||||
"""Page layout handling"""
|
|
||||||
|
|
||||||
def __init__(self, model=None, width=None, height=None,
|
|
||||||
supports_colour=False, use_info_section=True):
|
|
||||||
"""Initialize parameters for specified epaper model
|
|
||||||
Use model parameter to specify display OR
|
|
||||||
Crate a custom display with given width and height"""
|
|
||||||
|
|
||||||
if (model != None) and (width == None) and (height == None):
|
|
||||||
display_dimensions = {
|
|
||||||
'9_in_7': (1200, 825),
|
|
||||||
'epd_7_in_5_v2_colour': (800, 480),
|
|
||||||
'epd_7_in_5_v2': (800, 480),
|
|
||||||
'epd_7_in_5_colour': (640, 384),
|
|
||||||
'epd_7_in_5': (640, 384),
|
|
||||||
'epd_5_in_83_colour': (600, 448),
|
|
||||||
'epd_5_in_83': (600, 448),
|
|
||||||
'epd_4_in_2_colour': (400, 300),
|
|
||||||
'epd_4_in_2': (400, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.display_height, self.display_width = display_dimensions[model]
|
|
||||||
|
|
||||||
# if 'colour' was found in the display name, set supports_colour to True
|
|
||||||
if 'colour' in model:
|
|
||||||
self.supports_colour = True
|
|
||||||
else:
|
|
||||||
self.supports_colour = False
|
|
||||||
|
|
||||||
# If a custom width and height was specified, use those values instead
|
|
||||||
elif width and height:
|
|
||||||
self.display_height = width
|
|
||||||
self.display_width = height
|
|
||||||
self.supports_colour = supports_colour
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise Exception("Can't create a layout without given sizes")
|
|
||||||
|
|
||||||
# If the info section should be used, reduce the canvas size to 95%
|
|
||||||
if not isinstance(use_info_section, bool):
|
|
||||||
raise ValueError('use_info_section should be a boolean (True/False)')
|
|
||||||
|
|
||||||
if use_info_section == True:
|
|
||||||
self.display_height = int(self.display_height*0.95)
|
|
||||||
|
|
||||||
self.display_size = self.display_width, self.display_height
|
|
||||||
|
|
||||||
self.top_section_width = self.display_width
|
|
||||||
self.middle_section_width = self.display_width
|
|
||||||
self.bottom_section_width = self.display_width
|
|
||||||
self.create_sections()
|
|
||||||
|
|
||||||
def create_sections(self, top_section=0.10, middle_section=0.65,
|
|
||||||
bottom_section=0.25):
|
|
||||||
"""Allocate fixed percentage height for top and middle section
|
|
||||||
e.g. 0.2 = 20% (Leave empty for default values)
|
|
||||||
Set top/bottom_section to 0 to allocate more space for the middle section
|
|
||||||
"""
|
|
||||||
scale = lambda percentage: round(percentage * self.display_height)
|
|
||||||
|
|
||||||
if top_section == 0 or bottom_section == 0:
|
|
||||||
if top_section == 0:
|
|
||||||
self.top_section_height = 0
|
|
||||||
|
|
||||||
if bottom_section == 0:
|
|
||||||
self.bottom_section_height = 0
|
|
||||||
|
|
||||||
self.middle_section_height = scale(1 - top_section - bottom_section)
|
|
||||||
else:
|
|
||||||
if top_section + middle_section + bottom_section > 1.0:
|
|
||||||
print('All percentages should add up to max 100%, not more!')
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.top_section_height = scale(top_section)
|
|
||||||
self.middle_section_height = scale(middle_section)
|
|
||||||
self.bottom_section_height = (self.display_height -
|
|
||||||
self.top_section_height - self.middle_section_height)
|
|
||||||
|
|
||||||
logger.debug(('top-section size: {} x {} px'.format(
|
|
||||||
self.top_section_width, self.top_section_height)))
|
|
||||||
logger.debug(('middle-section size: {} x {} px'.format(
|
|
||||||
self.middle_section_width, self.middle_section_height)))
|
|
||||||
logger.debug(('bottom-section size: {} x {} px'.format(
|
|
||||||
self.bottom_section_width, self.bottom_section_height)))
|
|
||||||
|
|
||||||
|
|
||||||
def get_size(self, section):
|
|
||||||
"""Enter top/middle/bottom to get the size of the section as a tuple:
|
|
||||||
(width, height)"""
|
|
||||||
|
|
||||||
if section not in ['top','middle','bottom']:
|
|
||||||
raise Exception('Invalid section: ', section)
|
|
||||||
else:
|
|
||||||
if section == 'top':
|
|
||||||
size = (self.top_section_width, self.top_section_height)
|
|
||||||
elif section == 'middle':
|
|
||||||
size = (self.middle_section_width, self.middle_section_height)
|
|
||||||
elif section == 'bottom':
|
|
||||||
size = (self.bottom_section_width, self.bottom_section_height)
|
|
||||||
return size
|
|
||||||
|
|
||||||
## def set_info_section(self, value):
|
|
||||||
## """Should a small info section be showed """
|
|
||||||
## if not isinstance(value, bool):
|
|
||||||
## raise ValueError('value has to bee a boolean: True/False')
|
|
||||||
## self.info_section = value
|
|
||||||
## logger.info(('show info section: {}').format(value))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print('running {0} in standalone/debug mode'.format(
|
|
||||||
os.path.basename(__file__).split('.py')[0]))
|
|
@ -27,8 +27,6 @@ class Display:
|
|||||||
driver = import_module(driver_path)
|
driver = import_module(driver_path)
|
||||||
self._epaper = driver.EPD()
|
self._epaper = driver.EPD()
|
||||||
self.model_name = epaper_model
|
self.model_name = epaper_model
|
||||||
#self.height = driver.EPD_HEIGHT
|
|
||||||
#self.width = driver.EPD_WIDTH
|
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception('This module is not supported. Check your spellings?')
|
raise Exception('This module is not supported. Check your spellings?')
|
||||||
|
@ -1 +1 @@
|
|||||||
1
|
0
|
||||||
|
@ -52,7 +52,6 @@ class Inkycal:
|
|||||||
with open(settings_path) as file:
|
with open(settings_path) as file:
|
||||||
settings = json.load(file)
|
settings = json.load(file)
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
#print(self.settings)
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print('No settings file found in specified location')
|
print('No settings file found in specified location')
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from .inkycal_agenda import Agenda
|
from .inkycal_agenda import Agenda
|
||||||
from .inkycal_calendar import Calendar
|
from .inkycal_calendar import Calendar
|
||||||
from .inkycal_weather import Weather
|
from .inkycal_weather import Weather
|
||||||
from .inkycal_feeds import RSS
|
from .inkycal_feeds import Feeds
|
||||||
|
from .inkycal_todoist import Todoist
|
||||||
#from .inkycal_image import Image
|
#from .inkycal_image import Image
|
||||||
|
#from .inkycal_server import Server
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Developer module template for Inkycal Project
|
Module template for Inky-Calendar Project
|
||||||
|
|
||||||
|
Create your own module with this template
|
||||||
|
|
||||||
Copyright by aceisace
|
Copyright by aceisace
|
||||||
"""
|
"""
|
||||||
@ -77,6 +79,9 @@ class Simple(inkycal_module):
|
|||||||
# Initialise this module via the inkycal_module template (required)
|
# Initialise this module via the inkycal_module template (required)
|
||||||
super().__init__(section_size, section_config)
|
super().__init__(section_size, section_config)
|
||||||
|
|
||||||
|
# module name (required)
|
||||||
|
self.name = self.__class__.__name__
|
||||||
|
|
||||||
# module specific parameters (optional)
|
# module specific parameters (optional)
|
||||||
self.do_something = True
|
self.do_something = True
|
||||||
|
|
@ -14,7 +14,7 @@ import arrow
|
|||||||
|
|
||||||
filename = os.path.basename(__file__).split('.py')[0]
|
filename = os.path.basename(__file__).split('.py')[0]
|
||||||
logger = logging.getLogger(filename)
|
logger = logging.getLogger(filename)
|
||||||
logger.setLevel(level=logging.INFO)
|
logger.setLevel(level=logging.ERROR)
|
||||||
|
|
||||||
class Agenda(inkycal_module):
|
class Agenda(inkycal_module):
|
||||||
"""Agenda class
|
"""Agenda class
|
||||||
|
@ -12,7 +12,7 @@ import arrow
|
|||||||
|
|
||||||
filename = os.path.basename(__file__).split('.py')[0]
|
filename = os.path.basename(__file__).split('.py')[0]
|
||||||
logger = logging.getLogger(filename)
|
logger = logging.getLogger(filename)
|
||||||
logger.setLevel(level=logging.DEBUG)
|
logger.setLevel(level=logging.ERROR)
|
||||||
|
|
||||||
class Calendar(inkycal_module):
|
class Calendar(inkycal_module):
|
||||||
"""Calendar class
|
"""Calendar class
|
||||||
|
@ -18,9 +18,9 @@ except ImportError:
|
|||||||
|
|
||||||
filename = os.path.basename(__file__).split('.py')[0]
|
filename = os.path.basename(__file__).split('.py')[0]
|
||||||
logger = logging.getLogger(filename)
|
logger = logging.getLogger(filename)
|
||||||
logger.setLevel(level=logging.INFO)
|
logger.setLevel(level=logging.ERROR)
|
||||||
|
|
||||||
class RSS(inkycal_module):
|
class Feeds(inkycal_module):
|
||||||
"""RSS class
|
"""RSS class
|
||||||
parses rss/atom feeds from given urls
|
parses rss/atom feeds from given urls
|
||||||
"""
|
"""
|
||||||
|
@ -1,32 +1,309 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Image module for inkycal Project
|
Image module for Inkycal Project
|
||||||
Copyright by aceisace
|
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
|
from PIL import ImageOps
|
||||||
import requests
|
import requests
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
"""----------------------------------------------------------------"""
|
filename = os.path.basename(__file__).split('.py')[0]
|
||||||
#path = 'https://github.com/aceisace/Inky-Calendar/raw/master/Gallery/Inky-Calendar-logo.png'
|
logger = logging.getLogger(filename)
|
||||||
#path ='/home/pi/Inky-Calendar/images/canvas.png'
|
logger.setLevel(level=logging.ERROR)
|
||||||
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?
|
|
||||||
"""----------------------------------------------------------------"""
|
|
||||||
|
|
||||||
# First determine dimensions
|
class Inkyimage(inkycal_module):
|
||||||
if mode == 'horizontal':
|
"""Image class
|
||||||
display_width, display_height == display_height, display_width
|
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, config):
|
||||||
|
"""Initialize inkycal_rss module"""
|
||||||
|
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
|
config = config['config']
|
||||||
|
|
||||||
|
# required parameters
|
||||||
|
for param in self.requires:
|
||||||
|
if not param in 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()
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
@ -1,27 +1,32 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from inkycal.modules import Agenda
|
from inkycal.modules import Agenda as Module
|
||||||
|
|
||||||
agenda = Agenda(
|
test = {
|
||||||
#size
|
"position": 1,
|
||||||
(400,400),
|
"name": "Agenda",
|
||||||
|
"config": {
|
||||||
# common config
|
"size": [880,100],
|
||||||
{
|
"ical_urls": "https://www.officeholidays.com/ics-fed/usa", "ical_files": "",
|
||||||
'language': 'en',
|
"date_format": "ddd D MMM",
|
||||||
'units': 'metric',
|
"time_format": "HH:mm",
|
||||||
'hours': 24,
|
"padding_x": 10,
|
||||||
# module-specific config
|
"padding_y": 10,
|
||||||
'week_starts_on': 'Monday',
|
"fontsize": 12,
|
||||||
'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']
|
"language": "en"
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
class inkycal_agenda_test(unittest.TestCase):
|
module = Module(test)
|
||||||
|
|
||||||
|
class module_test(unittest.TestCase):
|
||||||
|
def test_get_config(self):
|
||||||
|
print('getting data for web-ui')
|
||||||
|
module.get_config()
|
||||||
|
|
||||||
def test_generate_image(self):
|
def test_generate_image(self):
|
||||||
print('testing image generation')
|
print('testing image generation')
|
||||||
agenda.generate_image()
|
module.generate_image()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from inkycal.modules import Calendar
|
from inkycal.modules import Calendar as Module
|
||||||
|
|
||||||
calendar = Calendar(
|
test = {
|
||||||
#size
|
"position": 2,
|
||||||
(400,400),
|
"name": "Calendar",
|
||||||
|
"config": {
|
||||||
# common config
|
"size": [880,343],
|
||||||
{
|
"week_starts_on": "Monday",
|
||||||
'language': 'en',
|
"show_events": "True",
|
||||||
'units': 'metric',
|
"ical_urls": "https://www.officeholidays.com/ics-fed/usa",
|
||||||
'hours': 24,
|
"ical_files": "",
|
||||||
# module-specific config
|
"date_format": "D MMM",
|
||||||
'week_starts_on': 'Monday',
|
"time_format": "HH:mm",
|
||||||
'ical_urls': ['https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics']
|
"padding_x": 10,
|
||||||
|
"padding_y": 10,
|
||||||
|
"fontsize": 12,
|
||||||
|
"language": "en"
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
module = Module(test)
|
||||||
|
|
||||||
|
class module_test(unittest.TestCase):
|
||||||
|
def test_get_config(self):
|
||||||
|
print('getting data for web-ui')
|
||||||
|
module.get_config()
|
||||||
|
|
||||||
class inkycal_calendar_test(unittest.TestCase):
|
|
||||||
def test_generate_image(self):
|
def test_generate_image(self):
|
||||||
print('testing image generation')
|
print('testing image generation')
|
||||||
calendar.generate_image()
|
module.generate_image()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
30
inkycal/tests/inkycal_feeds_test.py
Normal file
30
inkycal/tests/inkycal_feeds_test.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import unittest
|
||||||
|
from inkycal.modules import Feeds as Module
|
||||||
|
|
||||||
|
test = {
|
||||||
|
"position": 1,
|
||||||
|
"name": "Feeds",
|
||||||
|
"config": {
|
||||||
|
"size": [400,100],
|
||||||
|
"feed_urls": "http://feeds.bbci.co.uk/news/world/rss.xml#",
|
||||||
|
"shuffle_feeds": "True",
|
||||||
|
"padding_x": 10,
|
||||||
|
"padding_y": 10,
|
||||||
|
"fontsize": 12,
|
||||||
|
"language": "en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module = Module(test)
|
||||||
|
|
||||||
|
class module_test(unittest.TestCase):
|
||||||
|
def test_get_config(self):
|
||||||
|
print('getting data for web-ui')
|
||||||
|
module.get_config()
|
||||||
|
|
||||||
|
def test_generate_image(self):
|
||||||
|
print('testing image generation')
|
||||||
|
module.generate_image()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -1,26 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from inkycal.modules import RSS
|
|
||||||
|
|
||||||
rss = RSS(
|
|
||||||
#size
|
|
||||||
(400,400),
|
|
||||||
|
|
||||||
# common onfig
|
|
||||||
{
|
|
||||||
'language': 'en',
|
|
||||||
'units': 'metric',
|
|
||||||
'hours': 24,
|
|
||||||
# module-specific config
|
|
||||||
'rss_urls': ['http://feeds.bbci.co.uk/news/world/rss.xml#']
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
class inkycal_rss_test(unittest.TestCase):
|
|
||||||
def test_generate_image(self):
|
|
||||||
print('testing image generation')
|
|
||||||
rss.generate_image()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
2
inkycal/tests/inkycal_todo.py
Normal file
2
inkycal/tests/inkycal_todo.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import unittest
|
||||||
|
from inkycal.modules import Todoist as Module
|
@ -5,5 +5,5 @@ recurring-ical-events==0.1.17b0 # parse recurring events
|
|||||||
feedparser==5.2.1 # parse RSS-feeds
|
feedparser==5.2.1 # parse RSS-feeds
|
||||||
numpy>=1.18.2 # image pre-processing #pre-installed on Raspbian, omitting
|
numpy>=1.18.2 # image pre-processing #pre-installed on Raspbian, omitting
|
||||||
arrow>=0.15.6 # time operations
|
arrow>=0.15.6 # time operations
|
||||||
jsmin>=2.2.2 # parsing settings.jsonc file
|
Flask==1.1.2 # webserver
|
||||||
# flask, flask-wtf
|
Flask-WTF==0.14.3 # webforms
|
||||||
|
@ -58,7 +58,7 @@ def inkycal_config():
|
|||||||
|
|
||||||
# display size
|
# display size
|
||||||
display_size = Display.get_display_size(model)
|
display_size = Display.get_display_size(model)
|
||||||
width, height = display_size[0], display_size[1]
|
width, height = int(display_size[0]), int(display_size[1])
|
||||||
|
|
||||||
|
|
||||||
# loop over the modules, add their config data based on user selection, merge the common_settings into each module's config
|
# loop over the modules, add their config data based on user selection, merge the common_settings into each module's config
|
||||||
@ -66,12 +66,14 @@ def inkycal_config():
|
|||||||
conf = {}
|
conf = {}
|
||||||
module = 'module'+str(i)
|
module = 'module'+str(i)
|
||||||
if request.form.get(module) != "None":
|
if request.form.get(module) != "None":
|
||||||
#conf = {"position":i , "name": request.form.get(module), "height": int(request.form.get(module+'_height')), "config":{}}
|
|
||||||
conf = {"position":i , "name": request.form.get(module), "size": (width, int(height*int(request.form.get(module+'_height')) /100)), "config":{}}
|
conf = {"position":i , "name": request.form.get(module), "config":{}}
|
||||||
|
|
||||||
for modules in settings:
|
for modules in settings:
|
||||||
if modules['name'] == request.form.get(module):
|
if modules['name'] == request.form.get(module):
|
||||||
|
|
||||||
|
conf['config']['size'] = (width, int(height*int(request.form.get(module+'_height')) /100))
|
||||||
|
|
||||||
# Add required fields to the config of the module in question
|
# Add required fields to the config of the module in question
|
||||||
if 'requires' in modules:
|
if 'requires' in modules:
|
||||||
for key in modules['requires']:
|
for key in modules['requires']:
|
||||||
@ -90,12 +92,12 @@ def inkycal_config():
|
|||||||
conf['config'][key] = None
|
conf['config'][key] = None
|
||||||
|
|
||||||
# update the config dictionary
|
# update the config dictionary
|
||||||
conf.update(common_settings)
|
conf["config"].update(common_settings)
|
||||||
template['modules'].append(conf)
|
template['modules'].append(conf)
|
||||||
|
|
||||||
# Send the data back to the server side in json dumps and convert the response to a downloadable settings.json file
|
# Send the data back to the server side in json dumps and convert the response to a downloadable settings.json file
|
||||||
try:
|
try:
|
||||||
user_settings = json.dumps(template, indent=4).encode('utf-8')
|
user_settings = json.dumps(template, indent=4).replace('null', '""').encode('utf-8')
|
||||||
response = Response(user_settings, mimetype="application/json", direct_passthrough=True)
|
response = Response(user_settings, mimetype="application/json", direct_passthrough=True)
|
||||||
response.headers['Content-Disposition'] = 'attachment; filename=settings.json'
|
response.headers['Content-Disposition'] = 'attachment; filename=settings.json'
|
||||||
|
|
||||||
|
5
setup.py
5
setup.py
@ -1,7 +1,7 @@
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
__project__ = "inkycal"
|
__project__ = "inkycal"
|
||||||
__version__ = "2.0.0beta"
|
__version__ = "2.0.0"
|
||||||
__description__ = "Python3 software for syncing icalendar events, weather and news on selected E-Paper displays"
|
__description__ = "Python3 software for syncing icalendar events, weather and news on selected E-Paper displays"
|
||||||
__packages__ = ["inkycal"]
|
__packages__ = ["inkycal"]
|
||||||
__author__ = "aceisace"
|
__author__ = "aceisace"
|
||||||
@ -15,7 +15,8 @@ __install_requires__ = ['pyowm==2.10.0', # weather
|
|||||||
'feedparser==5.2.1', # parse RSS-feeds
|
'feedparser==5.2.1', # parse RSS-feeds
|
||||||
'numpy>=1.18.2', # image pre-processing
|
'numpy>=1.18.2', # image pre-processing
|
||||||
'arrow>=0.15.6', # time handling
|
'arrow>=0.15.6', # time handling
|
||||||
'jsmin>=2.2.2' # Parsing jsonc file
|
'Flask==1.1.2' # webserver
|
||||||
|
'Flask-WTF==0.14.3' # webforms
|
||||||
]
|
]
|
||||||
|
|
||||||
__classifiers__ = [
|
__classifiers__ = [
|
||||||
|
Loading…
Reference in New Issue
Block a user