diff --git a/.gitignore b/.gitignore index ac24778..5d8ee74 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ /Calendar/CalendarEvent.pyc /Calendar/design_exported_old.png /Calendar/settings_dev.py -/.vscode \ No newline at end of file +/Calendar/settings_pers.py +/.vscode +/Calendar/images/ \ No newline at end of file diff --git a/Calendar/Assets.py b/Calendar/Assets.py index bb8ffff..72783d1 100644 --- a/Calendar/Assets.py +++ b/Calendar/Assets.py @@ -46,4 +46,50 @@ colors = { "hl" : "red", "fg" : "black", "bg" : "white" -} \ No newline at end of file +} + +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", +] \ No newline at end of file diff --git a/Calendar/DesignEntity.py b/Calendar/DesignEntity.py index 9b8d9ef..c84f093 100644 --- a/Calendar/DesignEntity.py +++ b/Calendar/DesignEntity.py @@ -17,7 +17,7 @@ class DesignEntity (object): def __init_image__ (self, color = colors["bg"]): 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): if self.__finished_image__ is False: diff --git a/Calendar/E-Paper.py b/Calendar/E-Paper.py index 19db1a1..051fad5 100644 --- a/Calendar/E-Paper.py +++ b/Calendar/E-Paper.py @@ -19,6 +19,7 @@ from DayListPanel import DayListPanel from DayViewPanel import DayViewPanel from MonthViewPanel import MonthViewPanel from AgendaListPanel import AgendaListPanel +from ImageFramePanel import ImageFramePanel import OwmForecasts import IcalEvents import RssParserPosts @@ -52,7 +53,8 @@ available_panels = { "month-overview" : MonthOvPanel, "day-view" : DayViewPanel, "agenda-list" : AgendaListPanel, - "month-view" : MonthViewPanel + "month-view" : MonthViewPanel, + "image-frame" : ImageFramePanel } loop_timer = LoopTimer(update_interval, run_on_hour=True) diff --git a/Calendar/EpdAdapter.py b/Calendar/EpdAdapter.py index 3f1037b..a1a52a8 100644 --- a/Calendar/EpdAdapter.py +++ b/Calendar/EpdAdapter.py @@ -71,7 +71,7 @@ class EpdAdapter (DisplayAdapter): print('Converting image to data and sending it to the display') 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)) # Powering off the E-Paper until the next loop diff --git a/Calendar/ImageDesign.py b/Calendar/ImageDesign.py new file mode 100644 index 0000000..8323bec --- /dev/null +++ b/Calendar/ImageDesign.py @@ -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 \ No newline at end of file diff --git a/Calendar/ImageFramePanel.py b/Calendar/ImageFramePanel.py new file mode 100644 index 0000000..ac5b267 --- /dev/null +++ b/Calendar/ImageFramePanel.py @@ -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 diff --git a/Calendar/settings.py.sample b/Calendar/settings.py.sample index 46e0db6..63ec04a 100644 --- a/Calendar/settings.py.sample +++ b/Calendar/settings.py.sample @@ -1,26 +1,20 @@ """ To quickly get started, fill in the following details:""" ical_urls = [ - "https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics" ] highlighted_ical_urls = [ ] rss_feeds = [ - "http://feeds.bbci.co.uk/news/world/rss.xml#" ] crypto_coins = [ - "bitcoin", - "litecoin", - "ethereum", - "binancecoin" ] api_key = "" owm_paid_subscription = False -location = "Julich, DE" +location = "Berlin, DE" week_starts_on = "Monday" display_colours = "bwr" language = "en" @@ -34,11 +28,13 @@ update_interval = 60 font_size = 14 # does not affect every text font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold 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 "info-area" : "rss", # empty, events, rss, crypto "highlight-event-days" : True, - "weather-info" : True + "weather-info" : True, + "image-folder" : "", + "overlay-image" : "" # Size must be 384x640px with default display } diff --git a/README.md b/README.md index 5fa6c30..e2aad54 100644 --- a/README.md +++ b/README.md @@ -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) -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). @@ -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 * Syncronise events from any online calendar (like google, yahoo, etc.) * Get live weather data (including temperature, humidity, etc.) using openweathermap api +* Only show a slideshow of images ## 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) @@ -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. | | `"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). | +| `"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 | Parameter | Description |