Added image-frame design
This commit is contained in:
parent
6bda730c61
commit
3309dde202
9 changed files with 205 additions and 15 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -16,4 +16,6 @@
|
||||||
/Calendar/CalendarEvent.pyc
|
/Calendar/CalendarEvent.pyc
|
||||||
/Calendar/design_exported_old.png
|
/Calendar/design_exported_old.png
|
||||||
/Calendar/settings_dev.py
|
/Calendar/settings_dev.py
|
||||||
/.vscode
|
/Calendar/settings_pers.py
|
||||||
|
/.vscode
|
||||||
|
/Calendar/images/
|
|
@ -46,4 +46,50 @@ colors = {
|
||||||
"hl" : "red",
|
"hl" : "red",
|
||||||
"fg" : "black",
|
"fg" : "black",
|
||||||
"bg" : "white"
|
"bg" : "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supported_img_formats = [
|
||||||
|
"BMP",
|
||||||
|
"DIB",
|
||||||
|
"EPS",
|
||||||
|
"GIF",
|
||||||
|
"ICNS",
|
||||||
|
"ICO",
|
||||||
|
"IM",
|
||||||
|
"JPG",
|
||||||
|
"JPEG",
|
||||||
|
"J2K",
|
||||||
|
"J2P",
|
||||||
|
"JPX",
|
||||||
|
"MSP",
|
||||||
|
"PCX",
|
||||||
|
"PNG",
|
||||||
|
"PPM",
|
||||||
|
"SGI",
|
||||||
|
"SPI",
|
||||||
|
"TGA",
|
||||||
|
"TIFF",
|
||||||
|
"WEBP",
|
||||||
|
"XBM",
|
||||||
|
"BLP",
|
||||||
|
"CUR",
|
||||||
|
"DCX",
|
||||||
|
"DDS",
|
||||||
|
"FLI",
|
||||||
|
"FLC",
|
||||||
|
"FPX",
|
||||||
|
"FTEX",
|
||||||
|
"GBR",
|
||||||
|
"GD",
|
||||||
|
"IMT",
|
||||||
|
"IPTC",
|
||||||
|
"NAA",
|
||||||
|
"MCIDAS",
|
||||||
|
"MIC",
|
||||||
|
"MPO",
|
||||||
|
"PCD",
|
||||||
|
"PIXAR",
|
||||||
|
"PSD",
|
||||||
|
"WAL",
|
||||||
|
"XPM",
|
||||||
|
]
|
|
@ -17,7 +17,7 @@ class DesignEntity (object):
|
||||||
|
|
||||||
def __init_image__ (self, color = colors["bg"]):
|
def __init_image__ (self, color = colors["bg"]):
|
||||||
rounded_size = (int(self.size[0]),int(self.size[1]))
|
rounded_size = (int(self.size[0]),int(self.size[1]))
|
||||||
self.__image__ = Image.new('RGB', rounded_size, color=color)
|
self.__image__ = Image.new('RGBA', rounded_size, color=color)
|
||||||
|
|
||||||
def get_image (self):
|
def get_image (self):
|
||||||
if self.__finished_image__ is False:
|
if self.__finished_image__ is False:
|
||||||
|
|
|
@ -19,6 +19,7 @@ from DayListPanel import DayListPanel
|
||||||
from DayViewPanel import DayViewPanel
|
from DayViewPanel import DayViewPanel
|
||||||
from MonthViewPanel import MonthViewPanel
|
from MonthViewPanel import MonthViewPanel
|
||||||
from AgendaListPanel import AgendaListPanel
|
from AgendaListPanel import AgendaListPanel
|
||||||
|
from ImageFramePanel import ImageFramePanel
|
||||||
import OwmForecasts
|
import OwmForecasts
|
||||||
import IcalEvents
|
import IcalEvents
|
||||||
import RssParserPosts
|
import RssParserPosts
|
||||||
|
@ -52,7 +53,8 @@ available_panels = {
|
||||||
"month-overview" : MonthOvPanel,
|
"month-overview" : MonthOvPanel,
|
||||||
"day-view" : DayViewPanel,
|
"day-view" : DayViewPanel,
|
||||||
"agenda-list" : AgendaListPanel,
|
"agenda-list" : AgendaListPanel,
|
||||||
"month-view" : MonthViewPanel
|
"month-view" : MonthViewPanel,
|
||||||
|
"image-frame" : ImageFramePanel
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_timer = LoopTimer(update_interval, run_on_hour=True)
|
loop_timer = LoopTimer(update_interval, run_on_hour=True)
|
||||||
|
|
|
@ -71,7 +71,7 @@ class EpdAdapter (DisplayAdapter):
|
||||||
|
|
||||||
print('Converting image to data and sending it to the display')
|
print('Converting image to data and sending it to the display')
|
||||||
print('This may take a while...' + '\n')
|
print('This may take a while...' + '\n')
|
||||||
prepared_image = design.get_image().rotate(270, expand=1)
|
prepared_image = design.get_image().rotate(270, expand=1).convert("RGB")
|
||||||
self.display_frame(self.get_frame_buffer(prepared_image))
|
self.display_frame(self.get_frame_buffer(prepared_image))
|
||||||
|
|
||||||
# Powering off the E-Paper until the next loop
|
# Powering off the E-Paper until the next loop
|
||||||
|
|
83
Calendar/ImageDesign.py
Normal file
83
Calendar/ImageDesign.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from Assets import path as application_path
|
||||||
|
from PIL import Image, ExifTags
|
||||||
|
|
||||||
|
class ImageDesign (DesignEntity):
|
||||||
|
"""Creates a TableDesign filled with rss post
|
||||||
|
date and title"""
|
||||||
|
def __init__ (self, size, path, fill = "none", color="RGBA", dither=None): # fill: "none" : original size, "stretch" : strech to fill, "scale" : scale to fill, "border" : scale until one side touches border
|
||||||
|
super(ImageDesign, self).__init__(size)
|
||||||
|
self.set_path(path)
|
||||||
|
self.fill = fill
|
||||||
|
self.color = color
|
||||||
|
self.dither = dither
|
||||||
|
|
||||||
|
def set_path (self, path):
|
||||||
|
path = path.replace('\\', '/')
|
||||||
|
if path[0] != '/' and ':' not in path[0:3]:
|
||||||
|
path = application_path + '/' + path
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def __finish_image__ (self):
|
||||||
|
img = Image.open(self.path)
|
||||||
|
img = img.convert(self.color, dither=self.dither)
|
||||||
|
|
||||||
|
img = self.__fix_orientation__(img)
|
||||||
|
img = self.__resize_image__(img)
|
||||||
|
pos = self.__get_centered_position__(img)
|
||||||
|
|
||||||
|
self.__init_image__("#00000000")
|
||||||
|
self.draw(img, pos)
|
||||||
|
|
||||||
|
def __resize_image__(self, img):
|
||||||
|
if self.fill is "none":
|
||||||
|
return img
|
||||||
|
|
||||||
|
if self.fill is "stretch":
|
||||||
|
img = img.resize(self.size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
if self.fill is "scale":
|
||||||
|
size = self.size
|
||||||
|
img_proportions = img.width / img.height
|
||||||
|
if img_proportions < size[0] / size[1]:
|
||||||
|
size = (size[0], int(size[0] * (1 / img_proportions)))
|
||||||
|
else:
|
||||||
|
size = (int(size[1] * img_proportions), size[1])
|
||||||
|
img = img.resize(size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
if self.fill is "border":
|
||||||
|
size = self.size
|
||||||
|
img_proportions = img.width / img.height
|
||||||
|
if img_proportions < size[0] / size[1]:
|
||||||
|
size = (int(size[1] * img_proportions), size[1])
|
||||||
|
else:
|
||||||
|
size = (size[0], int(size[0] * (1 / img_proportions)))
|
||||||
|
img = img.resize(size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
return img
|
||||||
|
|
||||||
|
def __get_centered_position__(self, img):
|
||||||
|
screen_size = self.size
|
||||||
|
img_size = img.size
|
||||||
|
|
||||||
|
delta_size = [s - i for s, i in zip(screen_size, img_size)]
|
||||||
|
delta_center_pos = [s / 2 for s in delta_size]
|
||||||
|
|
||||||
|
return delta_center_pos
|
||||||
|
|
||||||
|
def __fix_orientation__(self, img):
|
||||||
|
if "parsed_exif" not in img.info.keys():
|
||||||
|
return img
|
||||||
|
|
||||||
|
for orientation in ExifTags.TAGS.keys():
|
||||||
|
if ExifTags.TAGS[orientation]=='Orientation':
|
||||||
|
break
|
||||||
|
exif=img.info["parsed_exif"]
|
||||||
|
|
||||||
|
if exif[orientation] == 3:
|
||||||
|
img = img.rotate(180, expand=True)
|
||||||
|
elif exif[orientation] == 6:
|
||||||
|
img = img.rotate(270, expand=True)
|
||||||
|
elif exif[orientation] == 8:
|
||||||
|
img = img.rotate(90, expand=True)
|
||||||
|
return img
|
58
Calendar/ImageFramePanel.py
Normal file
58
Calendar/ImageFramePanel.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from settings import general_settings
|
||||||
|
from Assets import supported_img_formats, path as application_path
|
||||||
|
from os import listdir
|
||||||
|
from os.path import isfile, join
|
||||||
|
from ImageDesign import ImageDesign
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
|
||||||
|
class ImageFramePanel (PanelDesign):
|
||||||
|
"""Converts the display into a digital frame and
|
||||||
|
shows a slide show of images, iterating on each update"""
|
||||||
|
def __init__ (self, size):
|
||||||
|
super(ImageFramePanel, self).__init__(size)
|
||||||
|
self.overlay_path = self.__complete_path__(general_settings["overlay-image"])
|
||||||
|
self.image_folder_path = self.__complete_path__(general_settings["image-folder"])
|
||||||
|
self.images = self.__extract_valid_img_paths__()
|
||||||
|
self.__first_render__()
|
||||||
|
|
||||||
|
def __extract_valid_img_paths__ (self):
|
||||||
|
images = []
|
||||||
|
for file in listdir(self.image_folder_path):
|
||||||
|
file_path = join(self.image_folder_path, file).replace('\\', '/')
|
||||||
|
if isfile(file_path) and self.overlay_path != file_path:
|
||||||
|
if file.split('.')[-1].upper() in supported_img_formats:
|
||||||
|
images.append(file_path)
|
||||||
|
return images
|
||||||
|
|
||||||
|
def __complete_path__(self, path):
|
||||||
|
path = path.replace('\\', '/')
|
||||||
|
if path[0] != '/' and ':' not in path[0:3]:
|
||||||
|
path = join(application_path, path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def __first_render__(self):
|
||||||
|
current_image = choice(self.images)
|
||||||
|
img = ImageDesign(self.size, current_image, fill="scale", color="1")
|
||||||
|
self.draw_design(img)
|
||||||
|
|
||||||
|
if self.overlay_path != "":
|
||||||
|
overlay = ImageDesign(self.size, self.overlay_path)
|
||||||
|
overlay.__finish_image__()
|
||||||
|
self.__image__.alpha_composite(overlay.__image__)
|
||||||
|
|
||||||
|
def add_weather (self, weather):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_calendar (self, calendar):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_rssfeed (self, rss):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_crypto (self, crypto):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_tasks (self, tasks):
|
||||||
|
pass
|
|
@ -1,26 +1,20 @@
|
||||||
""" To quickly get started, fill in the following details:"""
|
""" To quickly get started, fill in the following details:"""
|
||||||
|
|
||||||
ical_urls = [
|
ical_urls = [
|
||||||
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
highlighted_ical_urls = [
|
highlighted_ical_urls = [
|
||||||
]
|
]
|
||||||
|
|
||||||
rss_feeds = [
|
rss_feeds = [
|
||||||
"http://feeds.bbci.co.uk/news/world/rss.xml#"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
crypto_coins = [
|
crypto_coins = [
|
||||||
"bitcoin",
|
|
||||||
"litecoin",
|
|
||||||
"ethereum",
|
|
||||||
"binancecoin"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
api_key = ""
|
api_key = ""
|
||||||
owm_paid_subscription = False
|
owm_paid_subscription = False
|
||||||
location = "Julich, DE"
|
location = "Berlin, DE"
|
||||||
week_starts_on = "Monday"
|
week_starts_on = "Monday"
|
||||||
display_colours = "bwr"
|
display_colours = "bwr"
|
||||||
language = "en"
|
language = "en"
|
||||||
|
@ -34,11 +28,13 @@ update_interval = 60
|
||||||
font_size = 14 # does not affect every text
|
font_size = 14 # does not affect every text
|
||||||
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
|
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
|
||||||
line_thickness = 1 # 1-3 Thickness advised
|
line_thickness = 1 # 1-3 Thickness advised
|
||||||
choosen_design = "month-overview" # month-overview, day-list, day-view, agenda-list, month-view
|
choosen_design = "month-overview" # month-overview, day-list, day-view, agenda-list, month-view, image-frame
|
||||||
general_settings = { # General settings that designs may use
|
general_settings = { # General settings that designs may use
|
||||||
"info-area" : "rss", # empty, events, rss, crypto
|
"info-area" : "rss", # empty, events, rss, crypto
|
||||||
"highlight-event-days" : True,
|
"highlight-event-days" : True,
|
||||||
"weather-info" : True
|
"weather-info" : True,
|
||||||
|
"image-folder" : "",
|
||||||
|
"overlay-image" : "" # Size must be 384x640px with default display
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/0962be8b02e947e9aa4326e73b387e01)](https://app.codacy.com/app/m.giller.dev/E-Paper-Calendar?utm_source=github.com&utm_medium=referral&utm_content=mgfcf/E-Paper-Calendar&utm_campaign=Badge_Grade_Dashboard)
|
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/0962be8b02e947e9aa4326e73b387e01)](https://app.codacy.com/app/m.giller.dev/E-Paper-Calendar?utm_source=github.com&utm_medium=referral&utm_content=mgfcf/E-Paper-Calendar&utm_campaign=Badge_Grade_Dashboard)
|
||||||
|
|
||||||
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online.
|
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online. It can also be used as an image frame.
|
||||||
|
|
||||||
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
|
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E
|
||||||
* Optionally get RSS-Feed fetched and shown
|
* Optionally get RSS-Feed fetched and shown
|
||||||
* Syncronise events from any online calendar (like google, yahoo, etc.)
|
* Syncronise events from any online calendar (like google, yahoo, etc.)
|
||||||
* Get live weather data (including temperature, humidity, etc.) using openweathermap api
|
* Get live weather data (including temperature, humidity, etc.) using openweathermap api
|
||||||
|
* Only show a slideshow of images
|
||||||
|
|
||||||
## Hardware required
|
## Hardware required
|
||||||
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
|
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
|
||||||
|
@ -92,6 +93,8 @@ Once the packages are installed, navigate to the home directory, open 'E-Paper-M
|
||||||
| `"info-area"` | Defines the content type of an additionaly info area on the design. Can be one of `"rss"`, `"events"` or empty, to remove this area or keep it clean. |
|
| `"info-area"` | Defines the content type of an additionaly info area on the design. Can be one of `"rss"`, `"events"` or empty, to remove this area or keep it clean. |
|
||||||
| `"highlight-event-days"` | If set to `True`, days with events are highlighted in contrast to days without events. |
|
| `"highlight-event-days"` | If set to `True`, days with events are highlighted in contrast to days without events. |
|
||||||
| `"weather-info"` | If set to `False`, weather info areas disappear and make room for events/rss/etc. (depends on the design). |
|
| `"weather-info"` | If set to `False`, weather info areas disappear and make room for events/rss/etc. (depends on the design). |
|
||||||
|
| `"image-folder"` | Set a relative or absolute path to a folder containing images that you want to see fullscreen with the `"image-frame"` design activated. |
|
||||||
|
| `"overlay-image"` | Set a relative or absolute path to an image with the same size as the screen (default: 384x640px) to show some static information over every image shown in the `"image-frame"` design. If the overlay image is contained within the `"image-folder"`, it will not be included into the slideshow. |
|
||||||
|
|
||||||
### Debug
|
### Debug
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
|
|
Loading…
Reference in a new issue