diff --git a/docs/inkycal.html b/docs/inkycal.html index 2166d2b..e098b4e 100644 --- a/docs/inkycal.html +++ b/docs/inkycal.html @@ -232,14 +232,14 @@ which the given font should be scaled to.

-inkycal.custom.functions.draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1))
+inkycal.custom.functions.draw_border(image: <module 'PIL.Image' from '/home/runner/work/Inkycal/Inkycal/venv/lib/python3.11/site-packages/PIL/Image.py'>, xy: ~typing.Tuple[int, int], size: ~typing.Tuple[int, int], radius: int = 5, thickness: int = 1, shrinkage: ~typing.Tuple[int, int] = (0.1, 0.1)) None

Draws a border at given coordinates.

Args:
  • image: The image on which the border should be drawn (usually im_black or -im_colour.

  • +im_colour).

  • xy: Tuple representing the top-left corner of the border e.g. (32, 100) -where 32 is the x co-ordinate and 100 is the y-coordinate.

  • +where 32 is the x-coordinate and 100 is the y-coordinate.

  • size: Size of the border as a tuple -> (width, height).

  • radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.

  • thickness: Thickness of the border in pixels.

  • @@ -288,14 +288,14 @@ printed fonts of this function:

    The extracted timezone can be used to show the local time instead of UTC. e.g.

    >>> import arrow
     >>> print(arrow.now()) # returns non-timezone-aware time
    ->>> print(arrow.now(tz=get_system_tz()) # prints timezone aware time.
    +>>> print(arrow.now(tz=get_system_tz())) # prints timezone aware time.
     
-inkycal.custom.functions.internet_available()
+inkycal.custom.functions.internet_available() bool

checks if the internet is available.

Attempts to connect to google.com with a timeout of 5 seconds to check if the network can be reached.

@@ -315,7 +315,7 @@ if the network can be reached.

-inkycal.custom.functions.text_wrap(text, font=None, max_width=None)
+inkycal.custom.functions.text_wrap(text: str, font=None, max_width=None)

Splits a very long text into smaller parts

Splits a long text to smaller lines which can fit in a line with max_width. Uses a Font object for more accurate calculations.

@@ -334,7 +334,7 @@ splitting the text into the next chunk.

-inkycal.custom.functions.write(image, xy, box_size, text, font=None, **kwargs)
+inkycal.custom.functions.write(image: <module 'PIL.Image' from '/home/runner/work/Inkycal/Inkycal/venv/lib/python3.11/site-packages/PIL/Image.py'>, xy: ~typing.Tuple[int, int], box_size: ~typing.Tuple[int, int], text: str, font=None, **kwargs)

Writes text on an image.

Writes given text at given position on the specified image.

diff --git a/fonts/MaterialIcons/MaterialIcons.ttf b/fonts/MaterialIcons/MaterialIcons.ttf new file mode 100644 index 0000000..9d09b0f Binary files /dev/null and b/fonts/MaterialIcons/MaterialIcons.ttf differ diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py index 206a2ec..2c39b76 100644 --- a/inkycal/custom/functions.py +++ b/inkycal/custom/functions.py @@ -8,9 +8,9 @@ import logging import os import time import traceback +from typing import Tuple import arrow -import PIL import requests import tzlocal from PIL import Image @@ -73,7 +73,7 @@ def get_system_tz() -> str: >>> import arrow >>> print(arrow.now()) # returns non-timezone-aware time - >>> print(arrow.now(tz=get_system_tz()) # prints timezone aware time. + >>> print(arrow.now(tz=get_system_tz())) # prints timezone aware time. """ try: local_tz = tzlocal.get_localzone().key @@ -111,7 +111,7 @@ def auto_fontsize(font, max_height): return font -def write(image, xy, box_size, text, font=None, **kwargs): +def write(image: Image, xy: Tuple[int, int], box_size: Tuple[int, int], text: str, font=None, **kwargs): """Writes text on an image. Writes given text at given position on the specified image. @@ -161,7 +161,7 @@ def write(image, xy, box_size, text, font=None, **kwargs): text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) while text_width < int(box_width * fill_width) and text_height < int(box_height * fill_height): size += 1 @@ -169,12 +169,12 @@ def write(image, xy, box_size, text, font=None, **kwargs): text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) # Truncate text if text is too long, so it can fit inside the box if (text_width, text_height) > (box_width, box_height): @@ -184,7 +184,7 @@ def write(image, xy, box_size, text, font=None, **kwargs): text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) logger.debug(text) # Align text to desired position @@ -195,10 +195,13 @@ def write(image, xy, box_size, text, font=None, **kwargs): elif alignment == "right": x = int(box_width - text_width) + # Vertical centering + y = int((box_height / 2) - (text_height / 2)) + # Draw the text in the text-box draw = ImageDraw.Draw(image) space = Image.new('RGBA', (box_width, box_height)) - ImageDraw.Draw(space).text((x, 0), text, fill=colour, font=font) + ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) # Uncomment following two lines, comment out above two lines to show # red text-box with white text (debugging purposes) @@ -213,7 +216,7 @@ def write(image, xy, box_size, text, font=None, **kwargs): image.paste(space, xy, space) -def text_wrap(text, font=None, max_width=None): +def text_wrap(text: str, font=None, max_width=None): """Splits a very long text into smaller parts Splits a long text to smaller lines which can fit in a line with max_width. @@ -249,7 +252,7 @@ def text_wrap(text, font=None, max_width=None): return lines -def internet_available(): +def internet_available() -> bool: """checks if the internet is available. Attempts to connect to google.com with a timeout of 5 seconds to check @@ -274,15 +277,16 @@ def internet_available(): return False -def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): +def draw_border(image: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int = 5, thickness: int = 1, + shrinkage: Tuple[int, int] = (0.1, 0.1)) -> None: """Draws a border at given coordinates. Args: - image: The image on which the border should be drawn (usually im_black or - im_colour. + im_colour). - xy: Tuple representing the top-left corner of the border e.g. (32, 100) - where 32 is the x co-ordinate and 100 is the y-coordinate. + where 32 is the x-coordinate and 100 is the y-coordinate. - size: Size of the border as a tuple -> (width, height). @@ -320,6 +324,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height) c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height) + # Draw lines and arcs, creating a square with round corners draw = ImageDraw.Draw(image) draw.line((p1, p2), fill=colour, width=thickness) @@ -334,7 +339,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): draw.arc((c7, c8), 90, 180, fill=colour, width=thickness) -def draw_border_2(im: PIL.Image, xy: tuple, size: tuple, radius: int): +def draw_border_2(im: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int): draw = ImageDraw.Draw(im) x, y = xy diff --git a/inkycal/custom/openweathermap_wrapper.py b/inkycal/custom/openweathermap_wrapper.py index 6cd4405..779c5bf 100644 --- a/inkycal/custom/openweathermap_wrapper.py +++ b/inkycal/custom/openweathermap_wrapper.py @@ -41,18 +41,9 @@ def get_json_from_url(request_url): class OpenWeatherMap: - def __init__( - self, - api_key: str, - city_id: int = None, - lat: float = None, - lon: float = None, - api_version: API_VERSIONS = "2.5", - temp_unit: TEMP_UNITS = "celsius", - wind_unit: WIND_UNITS = "meters_sec", - language: str = "en", - tz_name: str = "UTC", - ) -> None: + def __init__(self, api_key: str, city_id: int = None, lat: float = None, lon: float = None, + api_version: API_VERSIONS = "2.5", temp_unit: TEMP_UNITS = "celsius", + wind_unit: WIND_UNITS = "meters_sec", language: str = "en", tz_name: str = "UTC") -> None: self.api_key = api_key self.temp_unit = temp_unit self.wind_unit = wind_unit @@ -106,7 +97,7 @@ class OpenWeatherMap: current_weather["temp_feels_like"] = self.get_converted_temperature(current_data["main"]["feels_like"]) current_weather["min_temp"] = self.get_converted_temperature(current_data["main"]["temp_min"]) current_weather["max_temp"] = self.get_converted_temperature(current_data["main"]["temp_max"]) - current_weather["humidity"] = current_data["main"]["humidity"] # OWM Unit: % rH + current_weather["humidity"] = current_data["main"]["humidity"] # OWM Unit: % rH current_weather["wind"] = self.get_converted_windspeed( current_data["wind"]["speed"] ) # OWM Unit Default: meter/sec, Metric: meter/sec @@ -161,10 +152,10 @@ class OpenWeatherMap: forecast["wind"]["speed"] ), # OWM Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour "wind_gust": self.get_converted_windspeed(forecast["wind"]["gust"]), - "pressure": forecast["main"]["pressure"], # OWM Unit: hPa - "humidity": forecast["main"]["humidity"], # OWM Unit: % rH + "pressure": forecast["main"]["pressure"], # OWM Unit: hPa + "humidity": forecast["main"]["humidity"], # OWM Unit: % rH "precip_probability": forecast["pop"] - * 100.0, # OWM value is unitless, directly converting to % scale + * 100.0, # OWM value is unitless, directly converting to % scale "icon": forecast["weather"][0]["icon"], "datetime": datetime.fromtimestamp(forecast["dt"], tz=self.tz_zone), } @@ -187,7 +178,7 @@ class OpenWeatherMap: :return: Forecast dictionary """ - # Make sure hourly forecasts are up to date + # Make sure hourly forecasts are up-to-date _ = self.get_weather_forecast() # Calculate the start and end times for the specified number of days from now @@ -207,7 +198,7 @@ class OpenWeatherMap: ] # In case the next available forecast is already for the next day, use that one for the less than 3 remaining hours of today - if forecasts == []: + if not forecasts: forecasts.append(self.hourly_forecasts[0]) # Get rain and temperatures for that day diff --git a/inkycal/modules/inkycal_agenda.py b/inkycal/modules/inkycal_agenda.py index a91f93b..1508a66 100755 --- a/inkycal/modules/inkycal_agenda.py +++ b/inkycal/modules/inkycal_agenda.py @@ -75,6 +75,8 @@ class Agenda(inkycal_module): # Additional config self.timezone = get_system_tz() + self.icon_font = ImageFont.truetype(fonts['MaterialIcons'], size=self.fontsize) + # give an OK message logger.debug(f'{__name__} loaded') @@ -201,10 +203,10 @@ class Agenda(inkycal_module): write(im_black, (x_time, line_pos[cursor][1]), (time_width, line_height), time, font=self.font, alignment='right') - if parser.all_day(_): + else: write(im_black, (x_time, line_pos[cursor][1]), - (time_width, line_height), "all day", - font=self.font, alignment='right') + (time_width, line_height), "\ue878", + font=self.icon_font, alignment='right') write(im_black, (x_event, line_pos[cursor][1]), (event_width, line_height), diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py index 0f947cd..146c272 100755 --- a/inkycal/modules/inkycal_calendar.py +++ b/inkycal/modules/inkycal_calendar.py @@ -6,16 +6,16 @@ Copyright by aceinnolab # pylint: disable=logging-fstring-interpolation import calendar as cal -import arrow -from inkycal.modules.template import inkycal_module + from inkycal.custom import * +from inkycal.modules.template import inkycal_module logger = logging.getLogger(__name__) class Calendar(inkycal_module): """Calendar class - Create monthly calendar and show events from given icalendars + Create monthly calendar and show events from given iCalendars """ name = "Calendar - Show monthly calendar with events from iCalendars" @@ -39,12 +39,12 @@ class Calendar(inkycal_module): }, "date_format": { "label": "Use an arrow-supported token for custom date formatting " - + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", + + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", "default": "D MMM", }, "time_format": { "label": "Use an arrow-supported token for custom time formatting " - + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", + + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", "default": "HH:mm", }, } @@ -61,7 +61,7 @@ class Calendar(inkycal_module): self._days_with_events = None # optional parameters - self.weekstart = config['week_starts_on'] + self.week_start = config['week_starts_on'] self.show_events = config['show_events'] self.date_format = config["date_format"] self.time_format = config['time_format'] @@ -109,7 +109,7 @@ class Calendar(inkycal_module): # Allocate space for month-names, weekdays etc. month_name_height = int(im_height * 0.10) text_bbox_height = self.font.getbbox("hg") - weekdays_height = int((text_bbox_height[3] - text_bbox_height[1]) * 1.25) + weekdays_height = int((abs(text_bbox_height[3]) + abs(text_bbox_height[1])) * 1.25) logger.debug(f"month_name_height: {month_name_height}") logger.debug(f"weekdays_height: {weekdays_height}") @@ -117,7 +117,7 @@ class Calendar(inkycal_module): logger.debug("Allocating space for events") calendar_height = int(im_height * 0.6) events_height = ( - im_height - month_name_height - weekdays_height - calendar_height + im_height - month_name_height - weekdays_height - calendar_height ) logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') logger.debug(f'events-section size: {im_width} x {events_height} px') @@ -156,13 +156,13 @@ class Calendar(inkycal_module): now = arrow.now(tz=self.timezone) - # Set weekstart of calendar to specified weekstart - if self.weekstart == "Monday": + # Set week-start of calendar to specified week-start + if self.week_start == "Monday": cal.setfirstweekday(cal.MONDAY) - weekstart = now.shift(days=-now.weekday()) + week_start = now.shift(days=-now.weekday()) else: cal.setfirstweekday(cal.SUNDAY) - weekstart = now.shift(days=-now.isoweekday()) + week_start = now.shift(days=-now.isoweekday()) # Write the name of current month write( @@ -174,9 +174,9 @@ class Calendar(inkycal_module): autofit=True, ) - # Set up weeknames in local language and add to main section + # Set up week-names in local language and add to main section weekday_names = [ - weekstart.shift(days=+_).format('ddd', locale=self.language) + week_start.shift(days=+_).format('ddd', locale=self.language) for _ in range(7) ] logger.debug(f'weekday names: {weekday_names}') @@ -192,7 +192,7 @@ class Calendar(inkycal_module): fill_height=0.9, ) - # Create a calendar template and flatten (remove nestings) + # Create a calendar template and flatten (remove nesting) calendar_flat = self.flatten(cal.monthcalendar(now.year, now.month)) # logger.debug(f" calendar_flat: {calendar_flat}") @@ -281,7 +281,7 @@ class Calendar(inkycal_module): month_start = arrow.get(now.floor('month')) month_end = arrow.get(now.ceil('month')) - # fetch events from given icalendars + # fetch events from given iCalendars self.ical = iCalendar() parser = self.ical @@ -294,14 +294,12 @@ class Calendar(inkycal_module): month_events = parser.get_events(month_start, month_end, self.timezone) parser.sort() self.month_events = month_events - + # Initialize days_with_events as an empty list days_with_events = [] # Handle multi-day events by adding all days between start and end for event in month_events: - start_date = event['begin'].date() - end_date = event['end'].date() # Convert start and end dates to arrow objects with timezone start = arrow.get(event['begin'].date(), tzinfo=self.timezone) @@ -324,9 +322,7 @@ class Calendar(inkycal_module): im_colour, grid[days], (icon_width, icon_height), - radius=6, - thickness=1, - shrinkage=(0, 0), + radius=6 ) # Filter upcoming events until 4 weeks in the future @@ -345,13 +341,13 @@ class Calendar(inkycal_module): date_width = int(max(( self.font.getlength(events['begin'].format(self.date_format, locale=lang)) - for events in upcoming_events))* 1.1 - ) + for events in upcoming_events)) * 1.1 + ) time_width = int(max(( self.font.getlength(events['begin'].format(self.time_format, locale=lang)) - for events in upcoming_events))* 1.1 - ) + for events in upcoming_events)) * 1.1 + ) text_bbox_height = self.font.getbbox("hg") line_height = text_bbox_height[3] + line_spacing @@ -369,7 +365,8 @@ class Calendar(inkycal_module): event_duration = (event['end'] - event['begin']).days if event_duration > 1: # Format the duration using Arrow's localization - days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, locale=lang) + days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, + locale=lang) the_name = f"{event['title']} ({days_translation})" else: the_name = event['title'] diff --git a/inkycal/modules/inkycal_weather.py b/inkycal/modules/inkycal_weather.py index 8bfe2c2..2c32b0b 100644 --- a/inkycal/modules/inkycal_weather.py +++ b/inkycal/modules/inkycal_weather.py @@ -2,12 +2,12 @@ Inkycal weather module Copyright by aceinnolab """ - -import arrow import decimal import logging import math +from typing import Tuple +import arrow from PIL import Image from PIL import ImageDraw from PIL import ImageFont @@ -51,7 +51,7 @@ class Weather(inkycal_module): "options": [True, False], }, - "round_windspeed": { + "round_wind_speed": { "label": "Round windspeed?", "options": [True, False], }, @@ -89,7 +89,7 @@ class Weather(inkycal_module): # Check if all required parameters are present for param in self.requires: - if not param in config: + if param not in config: raise Exception(f'config is missing {param}') # required parameters @@ -98,15 +98,15 @@ class Weather(inkycal_module): # optional parameters self.round_temperature = config['round_temperature'] - self.round_windspeed = config['round_windspeed'] + self.round_wind_speed = config['round_windspeed'] self.forecast_interval = config['forecast_interval'] self.hour_format = int(config['hour_format']) if config['units'] == "imperial": self.temp_unit = "fahrenheit" else: self.temp_unit = "celsius" - - if config['use_beaufort'] == True: + + if config['use_beaufort']: self.wind_unit = "beaufort" elif config['units'] == "imperial": self.wind_unit = "miles_hour" @@ -116,17 +116,17 @@ class Weather(inkycal_module): # additional configuration self.owm = OpenWeatherMap( - api_key=self.api_key, - city_id=self.location, - wind_unit=self.wind_unit, + api_key=self.api_key, + city_id=self.location, + wind_unit=self.wind_unit, temp_unit=self.temp_unit, - language=self.locale, + language=self.locale, tz_name=self.timezone - ) - + ) + self.weatherfont = ImageFont.truetype( fonts['weathericons-regular-webfont'], size=self.fontsize) - + if self.wind_unit == "beaufort": self.windDispUnit = "bft" elif self.wind_unit == "knots": @@ -145,8 +145,6 @@ class Weather(inkycal_module): # give an OK message logger.debug(f"{__name__} loaded") - - def generate_image(self): """Generate image for this module""" @@ -191,7 +189,7 @@ class Weather(inkycal_module): 7: '\uf0ae' }[int(index) & 7] - def is_negative(temp:str): + def is_negative(temp: str): """Check if temp is below freezing point of water (0°C/32°F) returns True if temp below freezing point, else False""" answer = False @@ -224,12 +222,19 @@ class Weather(inkycal_module): '50n': '\uf023' } - def draw_icon(image, xy, box_size, icon, rotation=None): - """Custom function to add icons of weather font on image - image = on which image should the text be added? - xy = xy-coordinates as tuple -> (x,y) - box_size = size of text-box -> (width,height) - icon = icon-unicode, looks this up in weathericons dictionary + def draw_icon(image: Image, xy: Tuple[int, int], box_size: Tuple[int, int], icon: str, rotation=None): + """Custom function to add icons of weather font on the image. + + Args: + - image: + the image on which image should the text be added + - xy: + coordinates as tuple -> (x,y) + - box_size: + size of text-box -> (width,height) + - icon: + icon-unicode, looks this up in weather-icons dictionary + """ icon_size_correction = { @@ -264,7 +269,6 @@ class Weather(inkycal_module): '\uf0a0': 0, '\uf0a3': 0, '\uf0a7': 0, - '\uf0aa': 0, '\uf0ae': 0 } @@ -278,8 +282,7 @@ class Weather(inkycal_module): font = ImageFont.truetype(font.path, size) text_width, text_height = font.getbbox(text)[2:] - while (text_width < int(box_width * 0.9) and - text_height < int(box_height * 0.9)): + while text_width < int(box_width * 0.9) and text_height < int(box_height * 0.9): size += 1 font = ImageFont.truetype(font.path, size) text_width, text_height = font.getbbox(text)[2:] @@ -290,8 +293,6 @@ class Weather(inkycal_module): x = int((box_width / 2) - (text_width / 2)) y = int((box_height / 2) - (text_height / 2)) - # Draw the text in the text-box - draw = ImageDraw.Draw(image) space = Image.new('RGBA', (box_width, box_height)) ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) @@ -350,17 +351,17 @@ class Weather(inkycal_module): row3 = row2 + line_gap + row_height # Draw lines on each row and border - ############################################################################ - ## draw = ImageDraw.Draw(im_black) - ## draw.line((0, 0, im_width, 0), fill='red') - ## draw.line((0, im_height-1, im_width, im_height-1), fill='red') - ## draw.line((0, row1, im_width, row1), fill='black') - ## draw.line((0, row1+row_height, im_width, row1+row_height), fill='black') - ## draw.line((0, row2, im_width, row2), fill='black') - ## draw.line((0, row2+row_height, im_width, row2+row_height), fill='black') - ## draw.line((0, row3, im_width, row3), fill='black') - ## draw.line((0, row3+row_height, im_width, row3+row_height), fill='black') - ############################################################################ + ########################################################################### + # draw = ImageDraw.Draw(im_black) + # draw.line((0, 0, im_width, 0), fill='red') + # draw.line((0, im_height-1, im_width, im_height-1), fill='red') + # draw.line((0, row1, im_width, row1), fill='black') + # draw.line((0, row1+row_height, im_width, row1+row_height), fill='black') + # draw.line((0, row2, im_width, row2), fill='black') + # draw.line((0, row2+row_height, im_width, row2+row_height), fill='black') + # draw.line((0, row3, im_width, row3), fill='black') + # draw.line((0, row3+row_height, im_width, row3+row_height), fill='black') + ########################################################################### # Positions for current weather details weather_icon_pos = (col1, 0) @@ -379,24 +380,24 @@ class Weather(inkycal_module): sunset_time_pos = (col3 + icon_small, row3) # Positions for forecast 1 - stamp_fc1 = (col4, row1) - icon_fc1 = (col4, row1 + row_height) - temp_fc1 = (col4, row3) + stamp_fc1 = (col4, row1) # noqa + icon_fc1 = (col4, row1 + row_height) # noqa + temp_fc1 = (col4, row3) # noqa # Positions for forecast 2 - stamp_fc2 = (col5, row1) - icon_fc2 = (col5, row1 + row_height) - temp_fc2 = (col5, row3) + stamp_fc2 = (col5, row1) # noqa + icon_fc2 = (col5, row1 + row_height) # noqa + temp_fc2 = (col5, row3) # noqa # Positions for forecast 3 - stamp_fc3 = (col6, row1) - icon_fc3 = (col6, row1 + row_height) - temp_fc3 = (col6, row3) + stamp_fc3 = (col6, row1) # noqa + icon_fc3 = (col6, row1 + row_height) # noqa + temp_fc3 = (col6, row3) # noqa # Positions for forecast 4 - stamp_fc4 = (col7, row1) - icon_fc4 = (col7, row1 + row_height) - temp_fc4 = (col7, row3) + stamp_fc4 = (col7, row1) # noqa + icon_fc4 = (col7, row1 + row_height) # noqa + temp_fc4 = (col7, row3) # noqa # Create current-weather and weather-forecast objects logging.debug('looking up location by ID') @@ -405,7 +406,7 @@ class Weather(inkycal_module): # Set decimals dec_temp = 0 if self.round_temperature == True else 1 - dec_wind = 0 if self.round_windspeed == True else 1 + dec_wind = 0 if self.round_wind_speed == True else 1 logging.debug(f'temperature unit: {self.temp_unit}') logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') @@ -425,7 +426,8 @@ class Weather(inkycal_module): fc_data['fc' + str(index + 1)] = { 'temp': f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}", 'icon': forecast["icon"], - 'stamp': forecast["datetime"].strftime("%I %p" if self.hour_format == 12 else "%H:%M")} + 'stamp': forecast["datetime"].strftime("%I %p" if self.hour_format == 12 else "%H:%M") + } elif self.forecast_interval == 'daily': @@ -434,7 +436,7 @@ class Weather(inkycal_module): daily_forecasts = [self.owm.get_forecast_for_day(days) for days in range(1, 5)] for index, forecast in enumerate(daily_forecasts): - fc_data['fc' + str(index +1)] = { + fc_data['fc' + str(index + 1)] = { 'temp': f'{forecast["temp_min"]:.{dec_temp}f}{self.tempDispUnit}/{forecast["temp_max"]:.{dec_temp}f}{self.tempDispUnit}', 'icon': forecast['icon'], 'stamp': forecast['datetime'].strftime("%A") @@ -514,6 +516,9 @@ class Weather(inkycal_module): # Add the forecast data to the correct places for pos in range(1, len(fc_data) + 1): stamp = fc_data[f'fc{pos}']['stamp'] + # check if we're using daily forecasts + if "day" in stamp: + stamp = arrow.get(fc_data[f'fc{pos}']['stamp'], "dddd").format("dddd", locale="de") icon = weather_icons[fc_data[f'fc{pos}']['icon']] temp = fc_data[f'fc{pos}']['temp'] diff --git a/inkycal/modules/inkycal_webshot.py b/inkycal/modules/inkycal_webshot.py index ae49dee..5f72351 100644 --- a/inkycal/modules/inkycal_webshot.py +++ b/inkycal/modules/inkycal_webshot.py @@ -41,7 +41,10 @@ class Webshot(inkycal_module): }, "crop_h": { "label": "Please enter the crop height", - } + }, + "rotation": { + "label": "Please enter the rotation. Must be either 0, 90, 180 or 270", + }, } def __init__(self, config): @@ -73,6 +76,12 @@ class Webshot(inkycal_module): else: self.crop_y = 0 + self.rotation = 0 + if "rotation" in config: + self.rotation = int(config["rotation"]) + if self.rotation not in [0, 90, 180, 270]: + raise Exception("Rotation must be either 0, 90, 180 or 270") + # give an OK message logger.debug(f'Inkycal webshot loaded') @@ -106,7 +115,7 @@ class Webshot(inkycal_module): logger.info( f'preparing webshot from {self.url}... cropH{self.crop_h} cropW{self.crop_w} cropX{self.crop_x} cropY{self.crop_y}') - shot = WebShot() + shot = WebShot(size=(im_height, im_width)) shot.params = { "--crop-x": self.crop_x, @@ -152,11 +161,21 @@ class Webshot(inkycal_module): centerPosX = int((im_width / 2) - (im.image.width / 2)) - webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY)) - im_black.paste(webshotSpaceBlack) - webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY)) - im_colour.paste(webshotSpaceColour) + if self.rotation != 0: + webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY)) + im_black.paste(webshotSpaceBlack) + im_black = im_black.rotate(self.rotation, expand=True) + + webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY)) + im_colour.paste(webshotSpaceColour) + im_colour = im_colour.rotate(self.rotation, expand=True) + else: + webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY)) + im_black.paste(webshotSpaceBlack) + + webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY)) + im_colour.paste(webshotSpaceColour) im.clear() logger.info(f'added webshot image') diff --git a/requirements.txt b/requirements.txt index 4554902..75ea61b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ python-dotenv==1.0.1 pytz==2024.1 PyYAML==6.0.1 recurring-ical-events==2.1.2 -requests==2.31.0 +requests==2.32.0 sgmllib3k==1.0.0 six==1.16.0 soupsieve==2.5 @@ -46,7 +46,7 @@ types-python-dateutil==2.8.19.20240106 typing_extensions==4.9.0 tzdata==2024.1 tzlocal==5.2 -urllib3==2.2.0 +urllib3==2.2.2 virtualenv==20.25.0 webencodings==0.5.1 x-wr-timezone==0.0.6 diff --git a/tests/test_functions.py b/tests/test_functions.py index b624978..0d8a6dd 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,12 +1,22 @@ """ Test the functions in the functions module. """ +import unittest + from PIL import Image, ImageFont -from inkycal.custom import write, fonts + +from inkycal.custom import write, fonts, get_system_tz -def test_write(): - im = Image.new("RGB", (500, 200), "white") - font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size = 40) - write(im, (125,75), (250, 50), "Hello World", font) - # im.show() +class TestIcalendar(unittest.TestCase): + + def test_write(self): + im = Image.new("RGB", (500, 200), "white") + font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size=40) + write(im, (125, 75), (250, 50), "Hello World", font) + # im.show() + + def test_get_system_tz(self): + tz = get_system_tz() + assert isinstance(tz, str) + diff --git a/tests/test_inkycal_agenda.py b/tests/test_inkycal_agenda.py index 642f654..685bb86 100755 --- a/tests/test_inkycal_agenda.py +++ b/tests/test_inkycal_agenda.py @@ -37,7 +37,7 @@ tests = [ "size": [500, 800], "ical_urls": sample_url, "ical_files": None, - "date_format": "ddd D MMM", + "date_format": "DD.MMMM YYYY", "time_format": "HH:mm", "padding_x": 10, "padding_y": 10, diff --git a/tests/test_inkycal_calendar.py b/tests/test_inkycal_calendar.py index cb28b9a..434d5d8 100755 --- a/tests/test_inkycal_calendar.py +++ b/tests/test_inkycal_calendar.py @@ -20,7 +20,7 @@ tests = [ { "name": "Calendar", "config": { - "size": [500, 500], + "size": [500, 600], "week_starts_on": "Monday", "show_events": True, "ical_urls": sample_url, diff --git a/tests/test_inkycal_weather.py b/tests/test_inkycal_weather.py index bcc50ce..2325616 100755 --- a/tests/test_inkycal_weather.py +++ b/tests/test_inkycal_weather.py @@ -34,7 +34,7 @@ tests = [ "padding_x": 10, "padding_y": 10, "fontsize": 12, - "language": "en" + "language": "de" } }, { diff --git a/tests/test_inkycal_webshot.py b/tests/test_inkycal_webshot.py index b86c965..073a437 100755 --- a/tests/test_inkycal_webshot.py +++ b/tests/test_inkycal_webshot.py @@ -16,33 +16,13 @@ preview = Inkyimage.preview merge = Inkyimage.merge tests = [ - { - "position": 1, - "name": "Webshot", - "config": { - "size": [400, 100], - "url": "https://github.com", - "palette": "bwr", - "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" - } - }, { "position": 1, "name": "Webshot", "config": { "size": [400, 200], - "url": "https://github.com", - "palette": "bwy", - "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" - } - }, - { - "position": 1, - "name": "Webshot", - "config": { - "size": [400, 300], - "url": "https://github.com", - "palette": "bw", + "url": "https://aceinnolab.com", + "palette": "bwr", "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" } }, @@ -51,8 +31,31 @@ tests = [ "name": "Webshot", "config": { "size": [400, 400], - "url": "https://github.com", + "url": "https://aceinnolab.com", + "palette": "bwy", + "rotation": 0, + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } + }, + { + "position": 1, + "name": "Webshot", + "config": { + "size": [400, 600], + "url": "https://aceinnolab.com", + "palette": "bw", + "rotation": 90, + "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" + } + }, + { + "position": 1, + "name": "Webshot", + "config": { + "size": [400, 800], + "url": "https://aceinnolab.com", "palette": "bwr", + "rotation": 180, "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" } }