Generating pretty images for each talks.
This commit is contained in:
parent
561239bfa5
commit
0df5876fbd
|
@ -4,15 +4,39 @@ import argparse
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
from html2image import Html2Image
|
||||||
|
|
||||||
|
|
||||||
|
def generate_file(output_path, title, author, type, date, salle):
|
||||||
|
environment = Environment(loader=FileSystemLoader("template/"))
|
||||||
|
template = environment.get_template("template.html")
|
||||||
|
|
||||||
|
rend = template.render(
|
||||||
|
title=title, author=author, type=type, date=date, salle=salle
|
||||||
|
)
|
||||||
|
|
||||||
|
hti = Html2Image()
|
||||||
|
hti.load_file("template/illustration_1.png")
|
||||||
|
hti.load_file("template/logo_pyconfr_1.png")
|
||||||
|
hti.screenshot(
|
||||||
|
html_str=rend,
|
||||||
|
css_file="template/styles.css",
|
||||||
|
save_as=output_path,
|
||||||
|
size=(1200, 675),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def convert_datetime(val):
|
def convert_datetime(val):
|
||||||
"""Convert ISO 8601 datetime to datetime.datetime object."""
|
"""Convert ISO 8601 datetime to datetime.datetime object."""
|
||||||
return datetime.fromisoformat(val.decode())
|
return datetime.fromisoformat(val.decode() + "+00:00")
|
||||||
|
|
||||||
|
|
||||||
sqlite3.register_converter("datetime", convert_datetime)
|
sqlite3.register_converter("datetime", convert_datetime)
|
||||||
|
@ -64,7 +88,8 @@ def get_talks(django_site_id=4):
|
||||||
with con:
|
with con:
|
||||||
res = con.execute(
|
res = con.execute(
|
||||||
f"""
|
f"""
|
||||||
SELECT cfp_talk.start_date,
|
SELECT cfp_talk.id,
|
||||||
|
cfp_talk.start_date,
|
||||||
cfp_room.name room,
|
cfp_room.name room,
|
||||||
GROUP_CONCAT(cfp_participant.name, x'00') participant,
|
GROUP_CONCAT(cfp_participant.name, x'00') participant,
|
||||||
GROUP_CONCAT(cfp_participant.mastodon, x'00') mastodon,
|
GROUP_CONCAT(cfp_participant.mastodon, x'00') mastodon,
|
||||||
|
@ -116,12 +141,28 @@ def live_toot(talks, mastodon: Mastodon, ahead_days=0):
|
||||||
for talk in talks:
|
for talk in talks:
|
||||||
if talk.category == "Sprint":
|
if talk.category == "Sprint":
|
||||||
continue # We don't toot sprints.
|
continue # We don't toot sprints.
|
||||||
delta = talk.start_date - datetime.now() - timedelta(days=ahead_days)
|
delta = (
|
||||||
|
talk.start_date - datetime.now(timezone.utc) - timedelta(days=ahead_days)
|
||||||
|
)
|
||||||
if delta.total_seconds() > 10:
|
if delta.total_seconds() > 10:
|
||||||
logging.info("Waiting %s for %s to start.", delta, talk.title)
|
logging.info("Waiting %s for %s to start.", delta, talk.title)
|
||||||
sleep(delta.total_seconds() - 2) # Wait for the talk to start.
|
sleep(delta.total_seconds() - 2) # Wait for the talk to start.
|
||||||
sleep(2) # This is just a fool guard so we can Ctrl-C easily anytime.
|
sleep(2) # This is just a fool guard so we can Ctrl-C easily anytime.
|
||||||
mastodon.toot(toot_for(talk, ahead_days))
|
png_name = f"talk-{talk.id}.png"
|
||||||
|
with suppress(FileNotFoundError):
|
||||||
|
Path(png_name).unlink()
|
||||||
|
generate_file(
|
||||||
|
output_path=png_name,
|
||||||
|
title=talk.title,
|
||||||
|
author=", ".join(talk.participants),
|
||||||
|
type=talk.category.split()[0],
|
||||||
|
date=talk.start_date.astimezone(ZoneInfo("Europe/Paris")).strftime(
|
||||||
|
"%A %d %B %HH%M"
|
||||||
|
),
|
||||||
|
salle="Salle " + talk.room.split("/")[0],
|
||||||
|
)
|
||||||
|
media = mastodon.media_post("rend.png")
|
||||||
|
mastodon.status_post(toot_for(talk, ahead_days), media_ids=[media])
|
||||||
|
|
||||||
|
|
||||||
class DryRunMastodon:
|
class DryRunMastodon:
|
||||||
|
@ -136,6 +177,12 @@ class DryRunMastodon:
|
||||||
def toot(self, message):
|
def toot(self, message):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
|
def media_post(self, *args, **kwargs):
|
||||||
|
print("Would upload media to Mastodon.")
|
||||||
|
|
||||||
|
def status_post(self, *args, **kwargs):
|
||||||
|
print("Would tooting", args, kwargs)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
Mastodon.py
|
Mastodon.py
|
||||||
|
Jinja2==3.1.2
|
||||||
|
html2image==2.0.1
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 644 KiB |
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,133 @@
|
||||||
|
.1_3 {
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.e1_3 {
|
||||||
|
background-color:rgba(255, 255, 255, 1);
|
||||||
|
width:1200px;
|
||||||
|
height:675px;
|
||||||
|
}
|
||||||
|
.e1_4 {
|
||||||
|
width:1200px;
|
||||||
|
height:675px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:0px;
|
||||||
|
}
|
||||||
|
.e4_34 {
|
||||||
|
width:1200px;
|
||||||
|
height:675px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:0px;
|
||||||
|
background-image:url(illustration_1.png);
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-size:cover;
|
||||||
|
}
|
||||||
|
.e1_8 {
|
||||||
|
background-color:rgba(22.4910007417202, 61.18752770125866, 69.30900290608406, 1);
|
||||||
|
width:975px;
|
||||||
|
height:309px;
|
||||||
|
position:absolute;
|
||||||
|
left:127px;
|
||||||
|
top:138px;
|
||||||
|
border-top-left-radius:16px;
|
||||||
|
border-top-right-radius:16px;
|
||||||
|
border-bottom-left-radius:16px;
|
||||||
|
border-bottom-right-radius:16px;
|
||||||
|
}
|
||||||
|
.e1_9 {
|
||||||
|
color:rgba(183.60000729560852, 242.1246188879013, 255, 1);
|
||||||
|
width:943px;
|
||||||
|
height:79px;
|
||||||
|
position:absolute;
|
||||||
|
left:16px;
|
||||||
|
top:8px;
|
||||||
|
font-family:Fira Sans;
|
||||||
|
text-align:center;
|
||||||
|
font-size:64px;
|
||||||
|
letter-spacing:5;
|
||||||
|
}
|
||||||
|
.e1_10 {
|
||||||
|
width:943px;
|
||||||
|
height:198px;
|
||||||
|
position:absolute;
|
||||||
|
left:16px;
|
||||||
|
top:95px;
|
||||||
|
}
|
||||||
|
.e1_11 {
|
||||||
|
color:rgba(255, 255, 255, 1);
|
||||||
|
width:943px;
|
||||||
|
height:154px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:0px;
|
||||||
|
font-family:Fira Sans;
|
||||||
|
text-align:center;
|
||||||
|
font-size:42px;
|
||||||
|
letter-spacing:0;
|
||||||
|
}
|
||||||
|
.e1_12 {
|
||||||
|
color:rgba(255, 255, 255, 1);
|
||||||
|
width:943px;
|
||||||
|
height:44px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:154px;
|
||||||
|
font-family:Fira Sans;
|
||||||
|
text-align:center;
|
||||||
|
font-size:30px;
|
||||||
|
letter-spacing:0;
|
||||||
|
}
|
||||||
|
.e1_13 {
|
||||||
|
background-color:rgba(250.00000029802322, 249.00000035762787, 247.00000047683716, 1);
|
||||||
|
width:739px;
|
||||||
|
height:115px;
|
||||||
|
position:absolute;
|
||||||
|
left:231px;
|
||||||
|
top:493px;
|
||||||
|
border-top-left-radius:16px;
|
||||||
|
border-top-right-radius:16px;
|
||||||
|
border-bottom-left-radius:16px;
|
||||||
|
border-bottom-right-radius:16px;
|
||||||
|
}
|
||||||
|
.e1_15 {
|
||||||
|
width:515px;
|
||||||
|
height:83px;
|
||||||
|
position:absolute;
|
||||||
|
left:208px;
|
||||||
|
top:16px;
|
||||||
|
}
|
||||||
|
.e1_16 {
|
||||||
|
color:rgba(22.000000588595867, 61.00000016391277, 69.00000348687172, 1);
|
||||||
|
width:515px;
|
||||||
|
height:38px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:0px;
|
||||||
|
font-family:Fira Sans;
|
||||||
|
text-align:center;
|
||||||
|
font-size:44px;
|
||||||
|
letter-spacing:-2;
|
||||||
|
}
|
||||||
|
.e1_17 {
|
||||||
|
color:rgba(22.000000588595867, 61.00000016391277, 69.00000348687172, 1);
|
||||||
|
width:512px;
|
||||||
|
height:27px;
|
||||||
|
position:absolute;
|
||||||
|
left:0px;
|
||||||
|
top:56px;
|
||||||
|
font-family:Fira Sans;
|
||||||
|
text-align:center;
|
||||||
|
font-size:26px;
|
||||||
|
letter-spacing:-1.5;
|
||||||
|
}
|
||||||
|
.e4_35 {
|
||||||
|
width:176px;
|
||||||
|
height:83px;
|
||||||
|
position:absolute;
|
||||||
|
left:16px;
|
||||||
|
top:16px;
|
||||||
|
background-image:url(logo_pyconfr_1.png);
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-size:cover;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>Html Generated</title>
|
||||||
|
<meta name="description" content="Figma htmlGenerator">
|
||||||
|
<meta name="author" content="htmlGenerator">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Fira+Sans&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*
|
||||||
|
Figma Background for illustrative/preview purposes only.
|
||||||
|
You can remove this style tag with no consequence
|
||||||
|
*/
|
||||||
|
body {background: #E5E5E5; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class=e1_3><div class=e1_4><div class="e4_34"></div></div><div class=e1_8><span class="e1_9">{{type}}</span><div class=e1_10><span class="e1_11">{{title}}</span><span class="e1_12">{{type}} de {{author}}</span></div></div><div class=e1_13><div class=e1_15><span class="e1_16">{{date}}</span><span class="e1_17">Université de Bordeaux · {{salle}}</span></div><div class="e4_35"></div></div></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
$fira sans-font : "Fira Sans"
|
Loading…
Reference in New Issue