Merge branch 'main' into weather_scaling
This commit is contained in:
@@ -8,3 +8,5 @@ from .inkycal_jokes import Jokes
|
||||
from .inkycal_stocks import Stocks
|
||||
from .inkycal_slideshow import Slideshow
|
||||
from .inkycal_textfile_to_display import TextToDisplay
|
||||
from .inkycal_webshot import Webshot
|
||||
from .inkycal_xkcd import Xkcd
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal iCalendar parsing module
|
||||
Copyright by aceinnolab
|
||||
@@ -119,7 +117,7 @@ class iCalendar:
|
||||
|
||||
events = (
|
||||
{
|
||||
'title': events.get('SUMMARY').lstrip(),
|
||||
'title': events.get('SUMMARY').lstrip() if events.get('SUMMARY') else "",
|
||||
|
||||
'begin': arrow.get(events.get('DTSTART').dt).to(timezone) if (
|
||||
arrow.get(events.get('dtstart').dt).format('HH:mm') != '00:00')
|
||||
|
@@ -83,12 +83,11 @@ class Inkyimage:
|
||||
|
||||
@staticmethod
|
||||
def preview(image):
|
||||
""""Previews an 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')
|
||||
"""Previews an image on gpicview (only works on Rapsbian with Desktop)."""
|
||||
path = '~/temp'
|
||||
image.save(path + '/temp.png')
|
||||
os.system("gpicview " + path + '/temp.png')
|
||||
os.system('rm ' + path + '/temp.png')
|
||||
|
||||
def _image_loaded(self):
|
||||
"""returns True if image was loaded"""
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Agenda Module
|
||||
Copyright by aceinnolab
|
||||
@@ -98,7 +96,9 @@ class Agenda(inkycal_module):
|
||||
|
||||
# Calculate the max number of lines that can fit on the image
|
||||
line_spacing = 1
|
||||
line_height = int(self.font.getsize('hg')[1]) + line_spacing
|
||||
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = im_height // line_height
|
||||
logger.debug(f'max lines: {max_lines}')
|
||||
@@ -109,9 +109,11 @@ class Agenda(inkycal_module):
|
||||
|
||||
# Create a list of dates for the next days
|
||||
agenda_events = [
|
||||
{'begin': today.shift(days=+_),
|
||||
'title': today.shift(days=+_).format(
|
||||
self.date_format, locale=self.language)}
|
||||
{
|
||||
'begin': today.shift(days=+_),
|
||||
'title': today.shift(days=+_).format(
|
||||
self.date_format, locale=self.language)
|
||||
}
|
||||
for _ in range(max_lines)]
|
||||
|
||||
# Load icalendar from config
|
||||
@@ -133,9 +135,9 @@ class Agenda(inkycal_module):
|
||||
# parser.show_events()
|
||||
|
||||
# Set the width for date, time and event titles
|
||||
date_width = int(max([self.font.getsize(
|
||||
dates['begin'].format(self.date_format, locale=self.language))[0]
|
||||
for dates in agenda_events]) * 1.2)
|
||||
date_width = int(max([self.font.getlength(
|
||||
dates['begin'].format(self.date_format, locale=self.language))
|
||||
for dates in agenda_events]) * 1.2)
|
||||
logger.debug(f'date_width: {date_width}')
|
||||
|
||||
# Calculate positions for each line
|
||||
@@ -147,9 +149,10 @@ class Agenda(inkycal_module):
|
||||
logger.info('Managed to parse events from urls')
|
||||
|
||||
# Find out how much space the event times take
|
||||
time_width = int(max([self.font.getsize(
|
||||
events['begin'].format(self.time_format, locale=self.language))[0]
|
||||
for events in upcoming_events]) * 1.2)
|
||||
|
||||
time_width = int(max([self.font.getlength(
|
||||
events['begin'].format(self.time_format, locale=self.language))
|
||||
for events in upcoming_events]) * 1.2)
|
||||
logger.debug(f'time_width: {time_width}')
|
||||
|
||||
# Calculate x-pos for time
|
||||
@@ -224,7 +227,3 @@ class Agenda(inkycal_module):
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Calendar Module
|
||||
Copyright by aceinnolab
|
||||
@@ -110,7 +108,8 @@ class Calendar(inkycal_module):
|
||||
|
||||
# Allocate space for month-names, weekdays etc.
|
||||
month_name_height = int(im_height * 0.10)
|
||||
weekdays_height = int(self.font.getsize('hg')[1] * 1.25)
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25)
|
||||
logger.debug(f"month_name_height: {month_name_height}")
|
||||
logger.debug(f"weekdays_height: {weekdays_height}")
|
||||
|
||||
@@ -182,15 +181,15 @@ class Calendar(inkycal_module):
|
||||
]
|
||||
logger.debug(f'weekday names: {weekday_names}')
|
||||
|
||||
for idx, weekday in enumerate(weekday_pos):
|
||||
for index, weekday in enumerate(weekday_pos):
|
||||
write(
|
||||
im_black,
|
||||
weekday,
|
||||
(icon_width, weekdays_height),
|
||||
weekday_names[idx],
|
||||
weekday_names[index],
|
||||
font=self.font,
|
||||
autofit=True,
|
||||
fill_height=1.0,
|
||||
fill_height=0.9,
|
||||
)
|
||||
|
||||
# Create a calendar template and flatten (remove nestings)
|
||||
@@ -207,6 +206,10 @@ class Calendar(inkycal_module):
|
||||
# remove zeros from calendar since they are not required
|
||||
calendar_flat = [num for num in calendar_flat if num != 0]
|
||||
|
||||
# ensure all numbers have the same size
|
||||
fontsize_numbers = int(min(icon_width, icon_height) * 0.5)
|
||||
number_font = ImageFont.truetype(self.font.path, fontsize_numbers)
|
||||
|
||||
# Add the numbers on the correct positions
|
||||
for number in calendar_flat:
|
||||
if number != int(now.day):
|
||||
@@ -215,9 +218,7 @@ class Calendar(inkycal_module):
|
||||
grid[number],
|
||||
(icon_width, icon_height),
|
||||
str(number),
|
||||
font=self.num_font,
|
||||
fill_height=0.5,
|
||||
fill_width=0.5,
|
||||
font=number_font,
|
||||
)
|
||||
|
||||
# Draw a red/black circle with the current day of month in white
|
||||
@@ -262,10 +263,10 @@ class Calendar(inkycal_module):
|
||||
from inkycal.modules.ical_parser import iCalendar
|
||||
|
||||
# find out how many lines can fit at max in the event section
|
||||
line_spacing = 0
|
||||
max_event_lines = events_height // (
|
||||
self.font.getsize('hg')[1] + line_spacing
|
||||
)
|
||||
line_spacing = 2
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
max_event_lines = events_height // (line_height + line_spacing)
|
||||
|
||||
# generate list of coordinates for each line
|
||||
events_offset = im_height - events_height
|
||||
@@ -293,14 +294,27 @@ 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 = []
|
||||
|
||||
# find out on which days of this month events are taking place
|
||||
days_with_events = [
|
||||
int(events['begin'].format('D')) for events in month_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)
|
||||
end = arrow.get(event['end'].date(), tzinfo=self.timezone)
|
||||
|
||||
# Use arrow's range function for generating dates
|
||||
for day in arrow.Arrow.range('day', start, end):
|
||||
day_num = int(day.format('D')) # get day number using arrow's format method
|
||||
if day_num not in days_with_events:
|
||||
days_with_events.append(day_num)
|
||||
|
||||
# remove duplicates (more than one event in a single day)
|
||||
list(set(days_with_events)).sort()
|
||||
days_with_events = sorted(set(days_with_events))
|
||||
self._days_with_events = days_with_events
|
||||
|
||||
# Draw a border with specified parameters around days with events
|
||||
@@ -329,31 +343,18 @@ class Calendar(inkycal_module):
|
||||
# Find out how much space (width) the date format requires
|
||||
lang = self.language
|
||||
|
||||
date_width = int(
|
||||
max(
|
||||
(
|
||||
self.font.getsize(
|
||||
events['begin'].format(self.date_format, locale=lang)
|
||||
)[0]
|
||||
for events in upcoming_events
|
||||
)
|
||||
)
|
||||
* 1.1
|
||||
date_width = int(max((
|
||||
self.font.getlength(events['begin'].format(self.date_format, locale=lang))
|
||||
for events in upcoming_events))* 1.1
|
||||
)
|
||||
|
||||
time_width = int(
|
||||
max(
|
||||
(
|
||||
self.font.getsize(
|
||||
events['begin'].format(self.time_format, locale=lang)
|
||||
)[0]
|
||||
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
|
||||
)
|
||||
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
|
||||
event_width_s = im_width - date_width - time_width
|
||||
event_width_l = im_width - date_width
|
||||
@@ -365,7 +366,13 @@ class Calendar(inkycal_module):
|
||||
cursor = 0
|
||||
for event in upcoming_events:
|
||||
if cursor < len(event_lines):
|
||||
the_name = event['title']
|
||||
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)
|
||||
the_name = f"{event['title']} ({days_translation})"
|
||||
else:
|
||||
the_name = event['title']
|
||||
the_date = event['begin'].format(self.date_format, locale=lang)
|
||||
the_time = event['begin'].format(self.time_format, locale=lang)
|
||||
# logger.debug(f"name:{the_name} date:{the_date} time:{the_time}")
|
||||
@@ -411,19 +418,16 @@ class Calendar(inkycal_module):
|
||||
cursor += 1
|
||||
else:
|
||||
symbol = '- '
|
||||
while self.font.getsize(symbol)[0] < im_width * 0.9:
|
||||
|
||||
while self.font.getlength(symbol) < im_width * 0.9:
|
||||
symbol += ' -'
|
||||
write(
|
||||
im_black,
|
||||
event_lines[0],
|
||||
(im_width, self.font.getsize(symbol)[1]),
|
||||
(im_width, line_height),
|
||||
symbol,
|
||||
font=self.font,
|
||||
)
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Feeds module for InkyCal Project
|
||||
Copyright by aceinnolab
|
||||
@@ -91,9 +89,11 @@ class Feeds(inkycal_module):
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -149,7 +149,3 @@ class Feeds(inkycal_module):
|
||||
|
||||
# return images
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
iCanHazDadJoke module for InkyCal Project
|
||||
Special thanks to Erik Fredericks (@efredericks) for the template!
|
||||
@@ -54,10 +52,11 @@ class Jokes(inkycal_module):
|
||||
raise NetworkNotReachableError
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
line_spacing = 5
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
@@ -97,7 +96,3 @@ class Jokes(inkycal_module):
|
||||
|
||||
# Return images for black and colour channels
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!python3
|
||||
"""
|
||||
Stocks Module for Inkycal Project
|
||||
|
||||
@@ -10,26 +9,18 @@ Version 0.1: Migration to Inkycal 2.0.0b
|
||||
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from inkycal.custom import write, internet_available
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from matplotlib import pyplot
|
||||
|
||||
try:
|
||||
import yfinance as yf
|
||||
except ImportError:
|
||||
print('yfinance is not installed! Please install with:')
|
||||
print('pip3 install yfinance')
|
||||
from inkycal.custom import write, internet_available
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.image as mpimg
|
||||
except ImportError:
|
||||
print('matplotlib is not installed! Please install with:')
|
||||
print('pip3 install matplotlib')
|
||||
import yfinance as yf
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.image as mpimg
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -79,26 +70,24 @@ class Stocks(inkycal_module):
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Create tmp path
|
||||
tmpPath = '/tmp/inkycal_stocks/'
|
||||
tmpPath = 'temp/'
|
||||
|
||||
try:
|
||||
if not os.path.exists(tmpPath):
|
||||
os.mkdir(tmpPath)
|
||||
print(f"Successfully created tmp directory {tmpPath} ")
|
||||
except OSError:
|
||||
print(f"Creation of tmp directory {tmpPath} failed")
|
||||
if not os.path.exists(tmpPath):
|
||||
print(f"Creating tmp directory {tmpPath}")
|
||||
os.mkdir(tmpPath)
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available() == True:
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = (im_height // (line_height + line_spacing))
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
@@ -211,7 +200,7 @@ class Stocks(inkycal_module):
|
||||
else:
|
||||
parsed_tickers_colour.append("")
|
||||
|
||||
if (_ < len(tickerCount)):
|
||||
if _ < len(tickerCount):
|
||||
parsed_tickers.append("")
|
||||
parsed_tickers_colour.append("")
|
||||
|
||||
@@ -232,9 +221,10 @@ class Stocks(inkycal_module):
|
||||
logger.info(f'chartSpace is...{im_width} {im_height}')
|
||||
logger.info(f'open chart ...{chartPath}')
|
||||
chartImage = Image.open(chartPath)
|
||||
chartImage.thumbnail((im_width / 4, line_height * 4), Image.BICUBIC)
|
||||
chartImage.thumbnail((int(im_width / 4), int(line_height * 4)), Image.BICUBIC)
|
||||
pyplot.close()
|
||||
|
||||
chartPasteX = im_width - (chartImage.width)
|
||||
chartPasteX = im_width - chartImage.width
|
||||
chartPasteY = line_height * 5 * _
|
||||
logger.info(f'pasting chart image with index {_} to...{chartPasteX} {chartPasteY}')
|
||||
|
||||
@@ -265,6 +255,3 @@ class Stocks(inkycal_module):
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('running module in standalone/debug mode')
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!python3
|
||||
"""
|
||||
Textfile module for InkyCal Project
|
||||
|
||||
@@ -7,44 +6,29 @@ If the content is too long, it will be truncated from the back until it fits
|
||||
|
||||
Copyright by aceinnolab
|
||||
"""
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from inkycal.custom import *
|
||||
|
||||
from urllib.request import urlopen
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TextToDisplay(inkycal_module):
|
||||
"""TextToDisplay module
|
||||
"""TextToDisplay module - Display text from a local file on the display
|
||||
"""
|
||||
|
||||
name = "Text module - Display text from a local file on the display"
|
||||
|
||||
requires = {
|
||||
"filepath": {
|
||||
"label": "Please enter a filepath or URL pointing to a .txt file",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
"""Initialize inkycal_textfile_to_display module"""
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
# Check if all required parameters are present
|
||||
for param in self.requires:
|
||||
if param not in config:
|
||||
raise Exception(f'config is missing {param}')
|
||||
|
||||
# required parameters
|
||||
self.filepath = config["filepath"]
|
||||
|
||||
self.make_request = True if self.filepath.startswith("https://") else False
|
||||
|
||||
|
||||
# give an OK message
|
||||
print(f'{__name__} loaded')
|
||||
|
||||
@@ -66,17 +50,12 @@ class TextToDisplay(inkycal_module):
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise NetworkNotReachableError
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
line_spacing = 4
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = im_height // line_height
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -87,6 +66,11 @@ class TextToDisplay(inkycal_module):
|
||||
|
||||
if self.make_request:
|
||||
logger.info("Detected http path, making request")
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise NetworkNotReachableError
|
||||
file_content = urlopen(self.filepath).read().decode('utf-8')
|
||||
else:
|
||||
# Create list containing all lines
|
||||
@@ -111,7 +95,3 @@ class TextToDisplay(inkycal_module):
|
||||
|
||||
# return images
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -1,5 +1,3 @@
|
||||
#!python3
|
||||
|
||||
"""
|
||||
Inkycal Todoist Module
|
||||
Copyright by aceinnolab
|
||||
@@ -86,9 +84,10 @@ class Todoist(inkycal_module):
|
||||
|
||||
# Set some parameters for formatting todos
|
||||
line_spacing = 1
|
||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
||||
max_lines = im_height // line_height
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
@@ -197,7 +196,3 @@ class Todoist(inkycal_module):
|
||||
|
||||
# return the images ready for the display
|
||||
return im_black, im_colour
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'running {__name__} in standalone/debug mode')
|
||||
|
@@ -12,7 +12,7 @@ import math
|
||||
import decimal
|
||||
import arrow
|
||||
|
||||
from pyowm.owm import OWM
|
||||
from inkycal.custom import OpenWeatherMap
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -95,7 +95,7 @@ class Weather(inkycal_module):
|
||||
self.use_beaufort = config['use_beaufort']
|
||||
|
||||
# additional configuration
|
||||
self.owm = OWM(self.api_key).weather_manager()
|
||||
self.owm = OpenWeatherMap(api_key=self.api_key, city_id=self.location, units=config['units'])
|
||||
self.timezone = get_system_tz()
|
||||
self.locale = config['language']
|
||||
self.weatherfont = ImageFont.truetype(
|
||||
@@ -104,6 +104,42 @@ class Weather(inkycal_module):
|
||||
# give an OK message
|
||||
print(f"{__name__} loaded")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def mps_to_beaufort(meters_per_second:float) -> int:
|
||||
"""Map meters per second to the beaufort scale.
|
||||
|
||||
Args:
|
||||
meters_per_second:
|
||||
float representing meters per seconds
|
||||
|
||||
Returns:
|
||||
an integer of the beaufort scale mapping the input
|
||||
"""
|
||||
thresholds = [0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.7, 24.5, 28.4]
|
||||
return next((i for i, threshold in enumerate(thresholds) if meters_per_second < threshold), 11)
|
||||
|
||||
@staticmethod
|
||||
def mps_to_mph(meters_per_second:float) -> float:
|
||||
"""Map meters per second to miles per hour, rounded to one decimal place.
|
||||
|
||||
Args:
|
||||
meters_per_second:
|
||||
float representing meters per seconds.
|
||||
|
||||
Returns:
|
||||
float representing the input value in miles per hour.
|
||||
"""
|
||||
# 1 m/s is approximately equal to 2.23694 mph
|
||||
miles_per_hour = meters_per_second * 2.23694
|
||||
return round(miles_per_hour, 1)
|
||||
|
||||
@staticmethod
|
||||
def celsius_to_fahrenheit(celsius:int or float):
|
||||
"""Converts the given temperate from degrees Celsius to Fahrenheit."""
|
||||
fahrenheit = (celsius * 9 / 5) + 32
|
||||
return fahrenheit
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
@@ -124,7 +160,11 @@ class Weather(inkycal_module):
|
||||
raise NetworkNotReachableError
|
||||
|
||||
def get_moon_phase():
|
||||
"""Calculate the current (approximate) moon phase"""
|
||||
"""Calculate the current (approximate) moon phase
|
||||
|
||||
Returns:
|
||||
The corresponding moonphase-icon.
|
||||
"""
|
||||
|
||||
dec = decimal.Decimal
|
||||
diff = now - arrow.get(2001, 1, 1)
|
||||
@@ -154,7 +194,7 @@ class Weather(inkycal_module):
|
||||
return answer
|
||||
|
||||
# Lookup-table for weather icons and weather codes
|
||||
weathericons = {
|
||||
weather_icons = {
|
||||
'01d': '\uf00d',
|
||||
'02d': '\uf002',
|
||||
'03d': '\uf013',
|
||||
@@ -227,26 +267,26 @@ class Weather(inkycal_module):
|
||||
# Increase fontsize to fit specified height and width of text box
|
||||
size = 8
|
||||
font = ImageFont.truetype(font.path, size)
|
||||
text_width, text_height = font.getsize(text)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
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.getsize(text)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
text_width, text_height = font.getsize(text)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
# Align text to desired position
|
||||
x = int((box_width / 2) - (text_width / 2))
|
||||
y = int((box_height / 2) - (text_height / 2) - (icon_size_correction[icon] * size) / 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)
|
||||
|
||||
if rotation != None:
|
||||
if rotation:
|
||||
space.rotate(rotation, expand=True)
|
||||
|
||||
# Update only region with text (add text with transparent background)
|
||||
@@ -350,14 +390,9 @@ class Weather(inkycal_module):
|
||||
temp_fc4 = (col7, row3)
|
||||
|
||||
# Create current-weather and weather-forecast objects
|
||||
if self.location.isdigit():
|
||||
logging.debug('looking up location by ID')
|
||||
weather = self.owm.weather_at_id(int(self.location)).weather
|
||||
forecast = self.owm.forecast_at_id(int(self.location), '3h')
|
||||
else:
|
||||
logging.debug('looking up location by string')
|
||||
weather = self.owm.weather_at_place(self.location).weather
|
||||
forecast = self.owm.forecast_at_place(self.location, '3h')
|
||||
logging.debug('looking up location by ID')
|
||||
weather = self.owm.get_current_weather()
|
||||
forecast = self.owm.get_weather_forecast()
|
||||
|
||||
# Set decimals
|
||||
dec_temp = None if self.round_temperature == True else 1
|
||||
@@ -369,12 +404,14 @@ class Weather(inkycal_module):
|
||||
elif self.units == 'imperial':
|
||||
temp_unit = 'fahrenheit'
|
||||
|
||||
logging.debug(f'temperature unit: {temp_unit}')
|
||||
logging.debug(f'temperature unit: {self.units}')
|
||||
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
|
||||
|
||||
# Get current time
|
||||
now = arrow.utcnow()
|
||||
|
||||
fc_data = {}
|
||||
|
||||
if self.forecast_interval == 'hourly':
|
||||
|
||||
logger.debug("getting hourly forecasts")
|
||||
@@ -386,21 +423,22 @@ class Weather(inkycal_module):
|
||||
else:
|
||||
hour_gap = 3
|
||||
|
||||
# Create timings for hourly forcasts
|
||||
# Create timings for hourly forecasts
|
||||
forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour')
|
||||
for _ in range(0, 12, 3)]
|
||||
|
||||
# Create forecast objects for given timings
|
||||
forecasts = [forecast.get_weather_at(forecast_time.datetime) for
|
||||
forecast_time in forecast_timings]
|
||||
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in forecast_timings]
|
||||
|
||||
# Add forecast-data to fc_data dictionary
|
||||
fc_data = {}
|
||||
for forecast in forecasts:
|
||||
temp = '{}°'.format(round(
|
||||
forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
||||
if self.units == "metric":
|
||||
temp = f"{round(weather['main']['temp'], ndigits=dec_temp)}°C"
|
||||
else:
|
||||
temp = f"{round(self.celsius_to_fahrenheit(weather['weather']['main']['temp']), ndigits=dec_temp)}°F"
|
||||
|
||||
icon = forecast.weather_icon_name
|
||||
icon = forecast["weather"][0]["icon"]
|
||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||
'temp': temp,
|
||||
'icon': icon,
|
||||
@@ -412,38 +450,35 @@ class Weather(inkycal_module):
|
||||
|
||||
logger.debug("getting daily forecasts")
|
||||
|
||||
def calculate_forecast(days_from_today):
|
||||
def calculate_forecast(days_from_today) -> dict:
|
||||
"""Get temperature range and most frequent icon code for forecast
|
||||
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
|
||||
"""
|
||||
|
||||
# Create a list containing time-objects for every 3rd hour of the day
|
||||
time_range = list(arrow.Arrow.range('hour',
|
||||
now.shift(days=days_from_today).floor('day'),
|
||||
now.shift(days=days_from_today).ceil('day')
|
||||
))[::3]
|
||||
time_range = list(
|
||||
arrow.Arrow.range('hour',
|
||||
now.shift(days=days_from_today).floor('day'),now.shift(days=days_from_today).ceil('day')
|
||||
))[::3]
|
||||
|
||||
# Get forecasts for each time-object
|
||||
forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range]
|
||||
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in time_range]
|
||||
|
||||
# Get all temperatures for this day
|
||||
daily_temp = [round(_.temperature(unit=temp_unit)['temp'],
|
||||
ndigits=dec_temp) for _ in forecasts]
|
||||
daily_temp = [round(_["main"]["temp"]) for _ in forecasts]
|
||||
# Calculate min. and max. temp for this day
|
||||
temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°'
|
||||
temp_range = f'{min(daily_temp)}°/{max(daily_temp)}°'
|
||||
|
||||
# Get all weather icon codes for this day
|
||||
daily_icons = [_.weather_icon_name for _ in forecasts]
|
||||
daily_icons = [_["weather"][0]["icon"] for _ in forecasts]
|
||||
# Find most common element from all weather icon codes
|
||||
status = max(set(daily_icons), key=daily_icons.count)
|
||||
|
||||
weekday = now.shift(days=days_from_today).format('ddd', locale=
|
||||
self.locale)
|
||||
weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale)
|
||||
return {'temp': temp_range, 'icon': status, 'stamp': weekday}
|
||||
|
||||
forecasts = [calculate_forecast(days) for days in range(1, 5)]
|
||||
|
||||
fc_data = {}
|
||||
for forecast in forecasts:
|
||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||
'temp': forecast['temp'],
|
||||
@@ -455,13 +490,15 @@ class Weather(inkycal_module):
|
||||
logger.debug((key, val))
|
||||
|
||||
# Get some current weather details
|
||||
temperature = '{}°'.format(round(
|
||||
weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
||||
if dec_temp != 0:
|
||||
temperature = f"{round(weather['main']['temp'])}°"
|
||||
else:
|
||||
temperature = f"{round(weather['main']['temp'],ndigits=dec_temp)}°"
|
||||
|
||||
weather_icon = weather.weather_icon_name
|
||||
humidity = str(weather.humidity)
|
||||
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone)
|
||||
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone)
|
||||
weather_icon = weather["weather"][0]["icon"]
|
||||
humidity = str(weather["main"]["humidity"])
|
||||
sunrise_raw = arrow.get(weather["sys"]["sunrise"]).to(self.timezone)
|
||||
sunset_raw = arrow.get(weather["sys"]["sunset"]).to(self.timezone)
|
||||
|
||||
logger.debug(f'weather_icon: {weather_icon}')
|
||||
|
||||
@@ -469,33 +506,29 @@ class Weather(inkycal_module):
|
||||
logger.debug('using 12 hour format for sunrise/sunset')
|
||||
sunrise = sunrise_raw.format('h:mm a')
|
||||
sunset = sunset_raw.format('h:mm a')
|
||||
|
||||
elif self.hour_format == 24:
|
||||
else:
|
||||
# 24 hours format
|
||||
logger.debug('using 24 hour format for sunrise/sunset')
|
||||
sunrise = sunrise_raw.format('H:mm')
|
||||
sunset = sunset_raw.format('H:mm')
|
||||
|
||||
# Format the windspeed to user preference
|
||||
# Format the wind-speed to user preference
|
||||
if self.use_beaufort:
|
||||
logger.debug("using beaufort for wind")
|
||||
wind = str(weather.wind(unit='beaufort')['speed'])
|
||||
|
||||
wind = str(self.mps_to_beaufort(weather["wind"]["speed"]))
|
||||
else:
|
||||
|
||||
if self.units == 'metric':
|
||||
logging.debug('getting windspeed in metric unit')
|
||||
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s'
|
||||
logging.debug('getting wind speed in meters per second')
|
||||
wind = f"{weather['wind']['speed']} m/s"
|
||||
else:
|
||||
logging.debug('getting wind speed in imperial unit')
|
||||
wind = f"{self.mps_to_mph(weather['wind']['speed'])} miles/h"
|
||||
|
||||
elif self.units == 'imperial':
|
||||
logging.debug('getting windspeed in imperial unit')
|
||||
wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
|
||||
|
||||
dec = decimal.Decimal
|
||||
moonphase = get_moon_phase()
|
||||
moon_phase = get_moon_phase()
|
||||
|
||||
# Fill weather details in col 1 (current weather icon)
|
||||
draw_icon(im_colour, weather_icon_pos, (col_width, im_height),
|
||||
weathericons[weather_icon])
|
||||
weather_icons[weather_icon])
|
||||
|
||||
# Fill weather details in col 2 (temp, humidity, wind)
|
||||
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
|
||||
@@ -521,7 +554,7 @@ class Weather(inkycal_module):
|
||||
wind, font=self.font)
|
||||
|
||||
# Fill weather details in col 3 (moonphase, sunrise, sunset)
|
||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase)
|
||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moon_phase)
|
||||
|
||||
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
|
||||
write(im_black, sunrise_time_pos, (col_width - icon_small, row_height),
|
||||
@@ -535,7 +568,7 @@ class Weather(inkycal_module):
|
||||
for pos in range(1, len(fc_data) + 1):
|
||||
stamp = fc_data[f'fc{pos}']['stamp']
|
||||
|
||||
icon = weathericons[fc_data[f'fc{pos}']['icon']]
|
||||
icon = weather_icons[fc_data[f'fc{pos}']['icon']]
|
||||
temp = fc_data[f'fc{pos}']['temp']
|
||||
|
||||
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
|
||||
@@ -548,7 +581,7 @@ class Weather(inkycal_module):
|
||||
border_h = row3 + row_height
|
||||
border_w = col_width - 3 # leave 3 pixels gap
|
||||
|
||||
# Add borders around each sub-section
|
||||
# Add borders around each subsection
|
||||
draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
|
||||
shrinkage=(0, 0))
|
||||
|
||||
|
164
inkycal/modules/inkycal_webshot.py
Normal file
164
inkycal/modules/inkycal_webshot.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
Webshot module for Inkycal
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
|
||||
from htmlwebshot import WebShot
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.inky_image import Inkyimage as Images
|
||||
from inkycal.modules.template import inkycal_module
|
||||
from tests import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Webshot(inkycal_module):
|
||||
name = "Webshot - Displays screenshots of webpages"
|
||||
|
||||
# required parameters
|
||||
requires = {
|
||||
|
||||
"url": {
|
||||
"label": "Please enter the url",
|
||||
},
|
||||
"palette": {
|
||||
"label": "Which color palette should be used for the webshots?",
|
||||
"options": ["bw", "bwr", "bwy"]
|
||||
}
|
||||
}
|
||||
|
||||
optional = {
|
||||
|
||||
"crop_x": {
|
||||
"label": "Please enter the crop x-position",
|
||||
},
|
||||
"crop_y": {
|
||||
"label": "Please enter the crop y-position",
|
||||
},
|
||||
"crop_w": {
|
||||
"label": "Please enter the crop width",
|
||||
},
|
||||
"crop_h": {
|
||||
"label": "Please enter the crop height",
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
self.url = config['url']
|
||||
self.palette = config['palette']
|
||||
|
||||
if "crop_h" in config and isinstance(config["crop_h"], str):
|
||||
self.crop_h = int(config["crop_h"])
|
||||
else:
|
||||
self.crop_h = 2000
|
||||
|
||||
if "crop_w" in config and isinstance(config["crop_w"], str):
|
||||
self.crop_w = int(config["crop_w"])
|
||||
else:
|
||||
self.crop_w = 2000
|
||||
|
||||
if "crop_x" in config and isinstance(config["crop_x"], str):
|
||||
self.crop_x = int(config["crop_x"])
|
||||
else:
|
||||
self.crop_x = 0
|
||||
|
||||
if "crop_y" in config and isinstance(config["crop_y"], str):
|
||||
self.crop_y = int(config["crop_y"])
|
||||
else:
|
||||
self.crop_y = 0
|
||||
|
||||
# give an OK message
|
||||
print(f'Inkycal webshot loaded')
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
# Create tmp path
|
||||
tmpFolder = Config.TEMP_PATH
|
||||
|
||||
if not os.path.exists(tmpFolder):
|
||||
print(f"Creating tmp directory {tmpFolder}")
|
||||
os.mkdir(tmpFolder)
|
||||
|
||||
# Define new image size with respect to padding
|
||||
im_width = int(self.width - (2 * self.padding_left))
|
||||
im_height = int(self.height - (2 * self.padding_top))
|
||||
im_size = im_width, im_height
|
||||
logger.info('image size: {} x {} px'.format(im_width, im_height))
|
||||
|
||||
# Create an image for black pixels and one for coloured pixels (required)
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
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.params = {
|
||||
"--crop-x": self.crop_x,
|
||||
"--crop-y": self.crop_y,
|
||||
"--crop-w": self.crop_w,
|
||||
"--crop-h": self.crop_h,
|
||||
}
|
||||
|
||||
logger.info(f'getting webshot from {self.url}...')
|
||||
|
||||
try:
|
||||
shot.create_pic(url=self.url, output=f"{tmpFolder}/webshot.png")
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print("If you have not already installed wkhtmltopdf, please use: sudo apt-get install wkhtmltopdf. See here for more details: https://github.com/1Danish-00/htmlwebshot/")
|
||||
raise Exception('Could not get webshot :/')
|
||||
|
||||
|
||||
logger.info(f'got webshot...')
|
||||
|
||||
webshotSpaceBlack = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
webshotSpaceColour = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
|
||||
im = Images()
|
||||
im.load(f'{tmpFolder}/webshot.png')
|
||||
im.remove_alpha()
|
||||
|
||||
imageAspectRatio = im_width / im_height
|
||||
webshotAspectRatio = im.image.width / im.image.height
|
||||
|
||||
if webshotAspectRatio > imageAspectRatio:
|
||||
imageScale = im_width / im.image.width
|
||||
else:
|
||||
imageScale = im_height / im.image.height
|
||||
|
||||
webshotHeight = int(im.image.height * imageScale)
|
||||
|
||||
im.resize(width=int(im.image.width * imageScale), height=webshotHeight)
|
||||
|
||||
im_webshot_black, im_webshot_colour = im.to_palette(self.palette)
|
||||
|
||||
webshotCenterPosY = int((im_height / 2) - (im.image.height / 2))
|
||||
|
||||
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)
|
||||
|
||||
im.clear()
|
||||
logger.info(f'added webshot image')
|
||||
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
202
inkycal/modules/inkycal_xkcd.py
Normal file
202
inkycal/modules/inkycal_xkcd.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Inkycal XKCD module
|
||||
by https://github.com/worstface
|
||||
"""
|
||||
|
||||
import xkcd
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.inky_image import Inkyimage as Images
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Xkcd(inkycal_module):
|
||||
name = "xkcd - Displays comics from xkcd.com by Randall Munroe"
|
||||
|
||||
# required parameters
|
||||
requires = {
|
||||
|
||||
"mode": {
|
||||
"label": "Please select the mode",
|
||||
"options": ["latest", "random"],
|
||||
"default": "latest"
|
||||
},
|
||||
"palette": {
|
||||
"label": "Which color palette should be used for the comic images?",
|
||||
"options": ["bw", "bwr", "bwy"]
|
||||
},
|
||||
"alt": {
|
||||
"label": "Would you like to add the alt text below the comic? If XKCD is not the only module you are showing, I recommend setting this to 'no'",
|
||||
"options": ["yes", "no"],
|
||||
"default": "no"
|
||||
},
|
||||
"filter": {
|
||||
"label": "Would you like to add a scaling filter? If the is far too big to be shown in the space you've allotted for it, the module will try to find another image for you. This only applies in random mode. If XKCD is not the only module you are showing, I recommend setting this to 'no'.",
|
||||
"options": ["yes", "no"],
|
||||
"default": "no"
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
|
||||
super().__init__(config)
|
||||
|
||||
config = config['config']
|
||||
|
||||
self.mode = config['mode']
|
||||
self.palette = config['palette']
|
||||
self.alt = config['alt']
|
||||
self.scale_filter = config['filter']
|
||||
|
||||
# give an OK message
|
||||
print(f'Inkycal XKCD loaded')
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
# Create tmp path
|
||||
tmpPath = f"{top_level}/temp"
|
||||
|
||||
if not os.path.exists(tmpPath):
|
||||
os.mkdir(tmpPath)
|
||||
|
||||
# Define new image size with respect to padding
|
||||
im_width = int(self.width - (2 * self.padding_left))
|
||||
im_height = int(self.height - (2 * self.padding_top))
|
||||
im_size = im_width, im_height
|
||||
logger.info('image size: {} x {} px'.format(im_width, im_height))
|
||||
|
||||
# Create an image for black pixels and one for coloured pixels (required)
|
||||
im_black = Image.new('RGB', size=im_size, color='white')
|
||||
im_colour = Image.new('RGB', size=im_size, color='white')
|
||||
|
||||
# Check if internet is available
|
||||
if internet_available():
|
||||
logger.info('Connection test passed')
|
||||
else:
|
||||
raise Exception('Network could not be reached :/')
|
||||
|
||||
# Set some parameters for formatting feeds
|
||||
line_spacing = 1
|
||||
text_bbox = self.font.getbbox("hg")
|
||||
line_height = text_bbox[3] + line_spacing
|
||||
line_width = im_width
|
||||
max_lines = im_height // (line_height + line_spacing)
|
||||
|
||||
logger.debug(f"max_lines: {max_lines}")
|
||||
|
||||
# Calculate padding from top so the lines look centralised
|
||||
spacing_top = int(im_height % line_height / 2)
|
||||
|
||||
# Calculate line_positions
|
||||
line_positions = [(0, spacing_top + _ * line_height) for _ in range(max_lines)]
|
||||
|
||||
logger.debug(f'line positions: {line_positions}')
|
||||
|
||||
logger.info(f'getting xkcd comic...')
|
||||
|
||||
if self.mode == 'random':
|
||||
if self.scale_filter == 'no':
|
||||
xkcdComic = xkcd.getRandomComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
else:
|
||||
perc = (2.1, 0.4)
|
||||
url = "test variable, not a real comic"
|
||||
while max(perc) > 1.75:
|
||||
print("looking for another comic, old comic was: ", perc, url)
|
||||
xkcdComic = xkcd.getRandomComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
actual_size = Image.open(tmpPath + '/xkcdComic.png').size
|
||||
perc = (actual_size[0] / im_width, actual_size[1] / im_height)
|
||||
url = xkcdComic.getImageLink()
|
||||
print("found one! perc: ", perc, url)
|
||||
else:
|
||||
xkcdComic = xkcd.getLatestComic()
|
||||
xkcdComic.download(output=tmpPath, outputFile='xkcdComic.png')
|
||||
|
||||
logger.info(f'got xkcd comic...')
|
||||
title_lines = []
|
||||
title_lines.append(xkcdComic.getTitle())
|
||||
|
||||
altOffset = int(line_height * 1)
|
||||
|
||||
if self.alt == "yes":
|
||||
alt_text = xkcdComic.getAltText() # get the alt text, too (I break it up into multiple lines later on)
|
||||
|
||||
# break up the alt text into lines
|
||||
alt_lines = []
|
||||
current_line = ""
|
||||
for _ in alt_text.split(" "):
|
||||
# this breaks up the alt_text into words and creates each line by adding
|
||||
# one word at a time until the line is longer than the width of the module
|
||||
# then it appends the line to the alt_lines array and starts testing a new line
|
||||
# with the next word
|
||||
text_bbox = self.font.getbbox(current_line + _ + " ")
|
||||
|
||||
if text_bbox[2] < im_width:
|
||||
current_line = current_line + _ + " "
|
||||
else:
|
||||
alt_lines.append(current_line)
|
||||
current_line = _ + " "
|
||||
alt_lines.append(
|
||||
current_line) # this adds the last line to the array (or the only line, if the alt text is really short)
|
||||
altHeight = int(line_height * len(alt_lines)) + altOffset
|
||||
else:
|
||||
altHeight = 0 # this is added so that I don't need to add more "if alt is yes" conditionals when centering below. Now the centering code will work regardless of whether they want alttext or not
|
||||
|
||||
comicSpaceBlack = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
comicSpaceColour = Image.new('RGBA', (im_width, im_height), (255, 255, 255, 255))
|
||||
|
||||
im = Images()
|
||||
im.load(f"{tmpPath}/xkcdComic.png")
|
||||
im.remove_alpha()
|
||||
|
||||
imageAspectRatio = im_width / im_height
|
||||
comicAspectRatio = im.image.width / im.image.height
|
||||
|
||||
if comicAspectRatio > imageAspectRatio:
|
||||
imageScale = im_width / im.image.width
|
||||
else:
|
||||
imageScale = im_height / im.image.height
|
||||
|
||||
comicHeight = int(im.image.height * imageScale)
|
||||
|
||||
headerHeight = int(line_height * 3 / 2)
|
||||
|
||||
if comicHeight + (headerHeight + altHeight) > im_height:
|
||||
comicHeight -= (headerHeight + altHeight)
|
||||
|
||||
im.resize(width=int(im.image.width * imageScale), height=comicHeight)
|
||||
|
||||
im_comic_black, im_comic_colour = im.to_palette(self.palette)
|
||||
|
||||
headerCenterPosY = int((im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2))
|
||||
comicCenterPosY = int((im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2) + headerHeight)
|
||||
altCenterPosY = int(
|
||||
(im_height / 2) - ((im.image.height + headerHeight + altHeight) / 2) + headerHeight + im.image.height)
|
||||
|
||||
centerPosX = int((im_width / 2) - (im.image.width / 2))
|
||||
|
||||
comicSpaceBlack.paste(im_comic_black, (centerPosX, comicCenterPosY))
|
||||
im_black.paste(comicSpaceBlack)
|
||||
|
||||
comicSpaceColour.paste(im_comic_colour, (centerPosX, comicCenterPosY))
|
||||
im_colour.paste(comicSpaceColour)
|
||||
|
||||
im.clear()
|
||||
logger.info(f'added comic image')
|
||||
|
||||
# Write the title on the black image
|
||||
write(im_black, (0, headerCenterPosY), (line_width, line_height),
|
||||
title_lines[0], font=self.font, alignment='center')
|
||||
|
||||
if self.alt == "yes":
|
||||
# write alt_text
|
||||
for _ in range(len(alt_lines)):
|
||||
write(im_black, (0, altCenterPosY + _ * line_height + altOffset), (line_width, line_height),
|
||||
alt_lines[_], font=self.font, alignment='left')
|
||||
|
||||
# Save image of black and colour channel in image-folder
|
||||
return im_black, im_colour
|
Reference in New Issue
Block a user