commit
81b2ea2405
@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y git
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# CMD om de container niet direct te stoppen
|
||||||
|
CMD ["sleep", "infinity"]
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "Multi-Python DevContainer",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/python:1": {
|
||||||
|
"version": "3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"ms-python.python",
|
||||||
|
"ms-python.vscode-pylance",
|
||||||
|
"ms-python.flake8", // Voeg Flake8 toe voor linting
|
||||||
|
"visualstudioexptteam.vscodeintellicode"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"python.linting.enabled": true,
|
||||||
|
"python.linting.flake8Enabled": true,
|
||||||
|
"python.linting.pylintEnabled": false, // Als je Flake8 wilt gebruiken, zet Pylint uit
|
||||||
|
"python.linting.lintOnSave": true, // Lint bij opslaan
|
||||||
|
"python.linting.flake8Args": [
|
||||||
|
"--max-line-length=88" // Optionele linting instelling voor max lijnlengte
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postCreateCommand": "find /workspace -type f -name 'requirements.txt' -exec pip install --no-cache-dir -r {} \\;",
|
||||||
|
"remoteUser": "root",
|
||||||
|
"mounts": [
|
||||||
|
"source=${localWorkspaceFolder},target=/workspace,type=bind"
|
||||||
|
],
|
||||||
|
"runArgs": ["--entrypoint", "bash"],
|
||||||
|
"settings": {
|
||||||
|
"python.pythonPath": "/usr/local/bin/python"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
-Open deze in devcontainer om te ontwikkelen(CTRL+SH+O, open in devcontainer)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,30 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Geef het absolute pad op naar je .env bestand
|
||||||
|
env_path = "./.env" # Pas dit pad aan!
|
||||||
|
|
||||||
|
# Laad de .env bestand expliciet
|
||||||
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_config():
|
||||||
|
"""Haalt de database configuratie op uit de .env bestand."""
|
||||||
|
return {
|
||||||
|
"host": os.getenv("DB_HOST"),
|
||||||
|
"user": os.getenv("DB_USER"),
|
||||||
|
"password": os.getenv("DB_PASSWORD"),
|
||||||
|
"database": os.getenv("DB_NAME"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_mqtt_config():
|
||||||
|
"""Haalt de MQTT configuratie op uit de .env bestand."""
|
||||||
|
return {
|
||||||
|
"broker": os.getenv("MQTT_BROKER"),
|
||||||
|
"port": int(os.getenv("MQTT_PORT")),
|
||||||
|
"topic_price": os.getenv("MQTT_TOPIC_PRICE"),
|
||||||
|
"topic_costs": os.getenv("MQTT_TOPIC_ELCOST"),
|
||||||
|
"username": os.getenv("MQTT_USER"),
|
||||||
|
"password": os.getenv("MQTT_PASSWORD"),
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import time
|
||||||
|
from db import Database
|
||||||
|
from mqtt_publisher import MQTTPublisher
|
||||||
|
from logger import setup_logger
|
||||||
|
from config import get_db_config, get_mqtt_config
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
# Laad de configuratie uit het .env bestand
|
||||||
|
load_dotenv()
|
||||||
|
print("Configuratie geladen.")
|
||||||
|
logger = setup_logger()
|
||||||
|
logger.debug("Logger ingesteld.")
|
||||||
|
|
||||||
|
# Verkrijg de database configuratie
|
||||||
|
db_config = get_db_config()
|
||||||
|
logger.debug(f"Database configuratie geladen: {db_config}")
|
||||||
|
|
||||||
|
# Verkrijg de MQTT configuratie
|
||||||
|
mqtt_config = get_mqtt_config()
|
||||||
|
logger.debug(f"MQTT configuratie geladen: {mqtt_config}")
|
||||||
|
|
||||||
|
# Maak een verbinding met de database
|
||||||
|
db = Database(db_config, logger)
|
||||||
|
logger.debug(
|
||||||
|
"db initialiseerd...")
|
||||||
|
mqtt_publisher = MQTTPublisher(mqtt_config, logger)
|
||||||
|
logger.debug(
|
||||||
|
"mqtt_publisher initialiseerd...")
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Start met het ophalen en publiceren van de elektriciteitsprijs...")
|
||||||
|
|
||||||
|
# Periodieke publicatie in een loop (elke 30 minuten)
|
||||||
|
while True:
|
||||||
|
# Haal de prijs op uit de database
|
||||||
|
price = db.fetch_current_price()
|
||||||
|
if price is not None:
|
||||||
|
logger.debug(f"Huidige prijs opgehaald: {price} EUR/kWh")
|
||||||
|
mqtt_publisher.publish(price, mqtt_config['topic_price'])
|
||||||
|
logger.debug(
|
||||||
|
f"Prijs gepubliceerd naar MQTT-topic {mqtt_config['topic_price']}: {price}")
|
||||||
|
else:
|
||||||
|
logger.warning("Geen prijs gevonden voor het huidige uur.")
|
||||||
|
total_costs_today = db.fetch_daily_costs()
|
||||||
|
if total_costs_today is not None:
|
||||||
|
rounded_costs = round(total_costs_today, 2)
|
||||||
|
# Afgerond op 2 decimalen en met euroteken
|
||||||
|
formatted_costs = f"€{rounded_costs:.2f}"
|
||||||
|
logger.debug(f"Huidige kosten voor vandaag: {formatted_costs}")
|
||||||
|
mqtt_publisher.publish(
|
||||||
|
formatted_costs, mqtt_config['topic_costs'])
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Huidige kosten voor vandaag: {formatted_costs} EUR")
|
||||||
|
|
||||||
|
mqtt_publisher.publish(
|
||||||
|
formatted_costs, mqtt_config['topic_costs'])
|
||||||
|
logger.debug(
|
||||||
|
f"Kosten gepubliceerd naar MQTT-topic {mqtt_config['topic_costs']}: {formatted_costs}")
|
||||||
|
else:
|
||||||
|
logger.warning("Geen kosten gevonden voor de huidige dag.")
|
||||||
|
|
||||||
|
# Wacht 30 minuten (1800 seconden) voor de volgende publicatie
|
||||||
|
print("Wachten op de volgende publicatie...")
|
||||||
|
logger.debug("Wachten op de volgende publicatie...")
|
||||||
|
time.sleep(60)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fout in main loop: {e}")
|
||||||
|
exit(1) # Stop de applicatie als er een fout optreedt
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import mysql.connector
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self, config, logger):
|
||||||
|
self.config = config
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def fetch_current_price(self):
|
||||||
|
"""Haalt de huidige elektriciteitsprijs op uit de database."""
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**self.config)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
query = """
|
||||||
|
SELECT price_fr
|
||||||
|
FROM daillyprices
|
||||||
|
WHERE DATE(timestamp) = CURRENT_DATE
|
||||||
|
AND HOUR(timestamp) = HOUR(NOW());
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
result = cursor.fetchone()
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
return result[0] if result else None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Database fout: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_daily_costs(self):
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**self.config)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
query = """
|
||||||
|
SELECT SUM(el_costs) AS total_costs_today
|
||||||
|
FROM energy_costs
|
||||||
|
WHERE DATE(starttime) = CURDATE();
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
result = cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return result[0] # De eerste kolom is de som van de kosten
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(
|
||||||
|
f"Fout bij het ophalen van de dagelijkse kosten: {e}")
|
||||||
|
return None
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,41 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Laad .env bestand
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
"""Stelt de logger in op basis van de configuratie in de .env bestand."""
|
||||||
|
log_level = os.getenv("LOG_LEVEL", "INFO").upper(
|
||||||
|
) # Standaard naar INFO als het niet is ingesteld
|
||||||
|
|
||||||
|
# Maak de logger aan
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
# Zet het logniveau op basis van de waarde uit de .env
|
||||||
|
log_levels = {
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"CRITICAL": logging.CRITICAL
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gebruik het juiste logniveau
|
||||||
|
# Standaard naar INFO als het onbekend is
|
||||||
|
logger.setLevel(log_levels.get(log_level, logging.INFO))
|
||||||
|
|
||||||
|
# Maak een console handler
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(log_levels.get(log_level, logging.INFO))
|
||||||
|
|
||||||
|
# Maak een formatter
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Voeg de handler toe aan de logger
|
||||||
|
logger.addHandler(ch)
|
||||||
|
|
||||||
|
return logger
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTPublisher:
|
||||||
|
def __init__(self, config, logger):
|
||||||
|
self.broker = config["broker"]
|
||||||
|
self.port = config["port"]
|
||||||
|
self.username = config["username"]
|
||||||
|
self.password = config["password"]
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def publish(self, message, topic):
|
||||||
|
"""Publiceert een bericht naar het MQTT-topic."""
|
||||||
|
try:
|
||||||
|
self.logger.debug(f"Verbinden met MQTT broker {self.broker}...")
|
||||||
|
client = mqtt.Client()
|
||||||
|
# Authenticatie toevoegen
|
||||||
|
client.username_pw_set(self.username, self.password)
|
||||||
|
client.connect(self.broker, self.port, 60)
|
||||||
|
self.logger.debug(
|
||||||
|
f"Verbonden met MQTT broker op poort {self.port}.")
|
||||||
|
|
||||||
|
# Zet het bericht om naar een string als het geen string is
|
||||||
|
# Zorg ervoor dat het bericht een string is
|
||||||
|
message_str = str(message)
|
||||||
|
|
||||||
|
self.logger.debug(f"Publiceren naar MQTT-topic: {topic}")
|
||||||
|
# Publiceer de string versie van het bericht
|
||||||
|
client.publish(topic, message_str)
|
||||||
|
self.logger.debug(
|
||||||
|
f"Bericht gepubliceerd naar {topic}: {message_str}")
|
||||||
|
|
||||||
|
client.disconnect()
|
||||||
|
self.logger.debug("Verbinding met MQTT broker gesloten.")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"MQTT fout: {e}")
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
requests
|
||||||
|
mysql-connector-python
|
||||||
|
python-dotenv
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
paho-mqtt
|
||||||
|
mysql-connector-python
|
||||||
|
python-dotenv
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import requests
|
||||||
|
import mysql.connector
|
||||||
|
from mysql.connector import Error
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
# Configuratievariabelen
|
||||||
|
ENERVER_API_URL = "https://enever.nl/api/stroomprijs_laatste30dagen.php" # API URL
|
||||||
|
ENERVER_API_TOKEN = "10d856e7b6b63a4d4e554c10b00e6b0d" # Vervang met je token
|
||||||
|
|
||||||
|
MYSQL_CONFIG = {
|
||||||
|
"host": "localhost",
|
||||||
|
"user": "root",
|
||||||
|
"password": "<your_password>",
|
||||||
|
"database": "energieprijzen",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_enever_prices():
|
||||||
|
"""Haal stroomprijsgegevens op van de EneVer API."""
|
||||||
|
params = {"token": ENERVER_API_TOKEN}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(ENERVER_API_URL, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json() # Verwacht JSON als antwoord
|
||||||
|
except requests.RequestException as e:
|
||||||
|
print(f"Fout bij ophalen EneVer-prijzen: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def store_prices_to_db(prices):
|
||||||
|
"""Sla de relevante stroomprijzen op in een MySQL-database."""
|
||||||
|
try:
|
||||||
|
connection = mysql.connector.connect(**MYSQL_CONFIG)
|
||||||
|
if connection.is_connected():
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Zorg dat de tabel bestaat
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS stroomprijzen (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
timestamp DATETIME,
|
||||||
|
price_ti DECIMAL(10, 5)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Voeg de prijzen toe
|
||||||
|
for price_entry in prices:
|
||||||
|
timestamp = datetime.datetime.strptime(
|
||||||
|
price_entry["datum"], "%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
price_ti = float(price_entry["prijsTI"])
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO stroomprijzen (timestamp, price_ti)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
""",
|
||||||
|
(timestamp, price_ti),
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
print("Relevante prijzen succesvol opgeslagen in de database.")
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
print(f"Fout bij verbinden met de database: {e}")
|
||||||
|
finally:
|
||||||
|
if connection.is_connected():
|
||||||
|
cursor.close()
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Prijzen ophalen van EneVer API
|
||||||
|
enever_prices = fetch_enever_prices()
|
||||||
|
# if enever_prices:
|
||||||
|
# # Prijzen opslaan in de database
|
||||||
|
# store_prices_to_db(enever_prices)
|
||||||
|
# else:
|
||||||
|
# print("Geen prijzen beschikbaar om op te slaan.")
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
2025-01-30 08:11:07,499 - qrcodegen.py - Script gestart voor platform: spotify. CSV bestand: nummers.csv, Output PDF: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:11:07,572 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:07,695 - qrcodegen.py - URL voor 'Bohemian Rhapsody - Queen' gevonden.
|
||||||
|
2025-01-30 08:11:07,770 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:08,043 - qrcodegen.py - URL voor 'Roller Coaster - Danny Vera' gevonden.
|
||||||
|
2025-01-30 08:11:08,110 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:08,231 - qrcodegen.py - URL voor 'Hotel California - Eagles' gevonden.
|
||||||
|
2025-01-30 08:11:08,312 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:08,437 - qrcodegen.py - URL voor 'Piano Man - Billy Joel' gevonden.
|
||||||
|
2025-01-30 08:11:08,518 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:08,694 - qrcodegen.py - URL voor 'Fix You - Coldplay' gevonden.
|
||||||
|
2025-01-30 08:11:08,774 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:08,920 - qrcodegen.py - URL voor 'Stairway To Heaven - Led Zeppelin' gevonden.
|
||||||
|
2025-01-30 08:11:08,995 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:09,110 - qrcodegen.py - URL voor 'Black - Pearl Jam' gevonden.
|
||||||
|
2025-01-30 08:11:09,188 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:09,328 - qrcodegen.py - URL voor 'Avond - Boudewijn de Groot' gevonden.
|
||||||
|
2025-01-30 08:11:09,401 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:09,525 - qrcodegen.py - URL voor 'Nothing Else Matters (Albumversie) - Metallica' gevonden.
|
||||||
|
2025-01-30 08:11:09,595 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:11:09,715 - qrcodegen.py - URL voor 'Love Of My Life - Queen' gevonden.
|
||||||
|
2025-01-30 08:11:09,729 - qrcodegen.py - PDF succesvol gegenereerd: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:12:59,529 - qrcodegen.py - Script gestart voor platform: spotify. CSV bestand: nummers.csv, Output PDF: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:12:59,597 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:12:59,709 - qrcodegen.py - URL voor 'Bohemian Rhapsody - Queen' gevonden.
|
||||||
|
2025-01-30 08:12:59,806 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:12:59,978 - qrcodegen.py - URL voor 'Roller Coaster - Danny Vera' gevonden.
|
||||||
|
2025-01-30 08:13:00,061 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:00,180 - qrcodegen.py - URL voor 'Hotel California - Eagles' gevonden.
|
||||||
|
2025-01-30 08:13:00,256 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:00,380 - qrcodegen.py - URL voor 'Piano Man - Billy Joel' gevonden.
|
||||||
|
2025-01-30 08:13:00,449 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:00,572 - qrcodegen.py - URL voor 'Fix You - Coldplay' gevonden.
|
||||||
|
2025-01-30 08:13:00,644 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:00,756 - qrcodegen.py - URL voor 'Stairway To Heaven - Led Zeppelin' gevonden.
|
||||||
|
2025-01-30 08:13:00,816 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:00,964 - qrcodegen.py - URL voor 'Black - Pearl Jam' gevonden.
|
||||||
|
2025-01-30 08:13:01,040 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:01,167 - qrcodegen.py - URL voor 'Avond - Boudewijn de Groot' gevonden.
|
||||||
|
2025-01-30 08:13:01,253 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:01,371 - qrcodegen.py - URL voor 'Nothing Else Matters (Albumversie) - Metallica' gevonden.
|
||||||
|
2025-01-30 08:13:01,458 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:01,614 - qrcodegen.py - URL voor 'Love Of My Life - Queen' gevonden.
|
||||||
|
2025-01-30 08:13:01,638 - qrcodegen.py - PDF succesvol gegenereerd: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:13:52,844 - qrcodegen.py - Script gestart voor platform: spotify. CSV bestand: nummers.csv, Output PDF: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:13:52,936 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:53,050 - qrcodegen.py - URL voor 'Bohemian Rhapsody - Queen' gevonden.
|
||||||
|
2025-01-30 08:13:53,136 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:53,262 - qrcodegen.py - URL voor 'Roller Coaster - Danny Vera' gevonden.
|
||||||
|
2025-01-30 08:13:53,353 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:53,491 - qrcodegen.py - URL voor 'Hotel California - Eagles' gevonden.
|
||||||
|
2025-01-30 08:13:53,562 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:53,678 - qrcodegen.py - URL voor 'Piano Man - Billy Joel' gevonden.
|
||||||
|
2025-01-30 08:13:53,750 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:53,904 - qrcodegen.py - URL voor 'Fix You - Coldplay' gevonden.
|
||||||
|
2025-01-30 08:13:53,977 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:54,103 - qrcodegen.py - URL voor 'Stairway To Heaven - Led Zeppelin' gevonden.
|
||||||
|
2025-01-30 08:13:54,175 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:54,350 - qrcodegen.py - URL voor 'Black - Pearl Jam' gevonden.
|
||||||
|
2025-01-30 08:13:54,429 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:54,540 - qrcodegen.py - URL voor 'Avond - Boudewijn de Groot' gevonden.
|
||||||
|
2025-01-30 08:13:54,612 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:54,739 - qrcodegen.py - URL voor 'Nothing Else Matters (Albumversie) - Metallica' gevonden.
|
||||||
|
2025-01-30 08:13:54,812 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:13:54,976 - qrcodegen.py - URL voor 'Love Of My Life - Queen' gevonden.
|
||||||
|
2025-01-30 08:13:54,999 - qrcodegen.py - PDF succesvol gegenereerd: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:14:55,877 - qrcodegen.py - Script gestart voor platform: spotify. CSV bestand: nummers.csv, Output PDF: nummers_met_qr.pdf
|
||||||
|
2025-01-30 08:14:55,941 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:56,058 - qrcodegen.py - URL voor 'Bohemian Rhapsody - Queen' gevonden.
|
||||||
|
2025-01-30 08:14:56,146 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:56,264 - qrcodegen.py - URL voor 'Roller Coaster - Danny Vera' gevonden.
|
||||||
|
2025-01-30 08:14:56,342 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:56,454 - qrcodegen.py - URL voor 'Hotel California - Eagles' gevonden.
|
||||||
|
2025-01-30 08:14:56,519 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:56,698 - qrcodegen.py - URL voor 'Piano Man - Billy Joel' gevonden.
|
||||||
|
2025-01-30 08:14:56,775 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:56,896 - qrcodegen.py - URL voor 'Fix You - Coldplay' gevonden.
|
||||||
|
2025-01-30 08:14:56,983 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:57,122 - qrcodegen.py - URL voor 'Stairway To Heaven - Led Zeppelin' gevonden.
|
||||||
|
2025-01-30 08:14:57,196 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:57,330 - qrcodegen.py - URL voor 'Black - Pearl Jam' gevonden.
|
||||||
|
2025-01-30 08:14:57,408 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:57,532 - qrcodegen.py - URL voor 'Avond - Boudewijn de Groot' gevonden.
|
||||||
|
2025-01-30 08:14:57,613 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:57,740 - qrcodegen.py - URL voor 'Nothing Else Matters (Albumversie) - Metallica' gevonden.
|
||||||
|
2025-01-30 08:14:57,828 - qrcodegen.py - Succesvol Spotify toegangstoken verkregen.
|
||||||
|
2025-01-30 08:14:57,973 - qrcodegen.py - URL voor 'Love Of My Life - Queen' gevonden.
|
||||||
|
2025-01-30 08:14:57,998 - qrcodegen.py - PDF succesvol gegenereerd: nummers_met_qr.pdf
|
||||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,190 @@
|
|||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import urllib.parse
|
||||||
|
import pandas as pd
|
||||||
|
import qrcode
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
# Geef het absolute pad op naar je .env bestand
|
||||||
|
env_path = "./.env" # Pas dit pad aan!
|
||||||
|
|
||||||
|
# Laad de .env bestand expliciet
|
||||||
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
# Controleer of de variabelen goed zijn geladen
|
||||||
|
CLIENT_ID = os.getenv('SPOTIFY_CLIENT_ID')
|
||||||
|
CLIENT_SECRET = os.getenv('SPOTIFY_CLIENT_SECRET')
|
||||||
|
|
||||||
|
|
||||||
|
def get_spotify_access_token():
|
||||||
|
|
||||||
|
auth_url = "https://accounts.spotify.com/api/token"
|
||||||
|
auth_data = {
|
||||||
|
'grant_type': 'client_credentials'
|
||||||
|
}
|
||||||
|
auth_headers = {
|
||||||
|
'Authorization': f"Basic {base64.b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode('utf-8')).decode('utf-8')}"
|
||||||
|
}
|
||||||
|
response = requests.post(auth_url, data=auth_data, headers=auth_headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
access_token = response.json().get('access_token')
|
||||||
|
return access_token
|
||||||
|
else:
|
||||||
|
print("Fout bij het verkrijgen van Spotify toegangstoken")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Functie om de Spotify track link te verkrijgen
|
||||||
|
|
||||||
|
|
||||||
|
def get_spotify_track_url(title, artist):
|
||||||
|
access_token = get_spotify_access_token()
|
||||||
|
if access_token is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Encodeer de titel en artiest voor de zoekopdracht
|
||||||
|
query = f"{title} {artist}"
|
||||||
|
encoded_query = urllib.parse.quote_plus(query)
|
||||||
|
|
||||||
|
# Voer de zoekopdracht uit via de Spotify API
|
||||||
|
search_url = f"https://api.spotify.com/v1/search?q={encoded_query}&type=track&limit=1"
|
||||||
|
search_headers = {
|
||||||
|
'Authorization': f"Bearer {access_token}"
|
||||||
|
}
|
||||||
|
response = requests.get(search_url, headers=search_headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
results = response.json()
|
||||||
|
tracks = results.get('tracks', {}).get('items', [])
|
||||||
|
|
||||||
|
if tracks:
|
||||||
|
track_url = tracks[0]['external_urls']['spotify']
|
||||||
|
return track_url
|
||||||
|
print(f"Fout bij het zoeken naar track voor '{title} - {artist}'")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Functie om QR-codes te genereren met titels
|
||||||
|
|
||||||
|
|
||||||
|
def generate_qr_codes_with_titles(csv_file, output_folder, position_column, title_column, artist_column, year_column):
|
||||||
|
# Maak de outputmap aan als die nog niet bestaat
|
||||||
|
os.makedirs(output_folder, exist_ok=True)
|
||||||
|
|
||||||
|
# Lees het CSV-bestand
|
||||||
|
try:
|
||||||
|
data = pd.read_csv(csv_file, delimiter=";")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fout bij het lezen van de CSV: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Controleer of de benodigde kolommen bestaan
|
||||||
|
if position_column not in data.columns or title_column not in data.columns or artist_column not in data.columns or year_column not in data.columns:
|
||||||
|
print(
|
||||||
|
f"Kolommen '{position_column}', '{title_column}', '{artist_column}' en/of '{year_column}' niet gevonden in CSV-bestand.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Loop door de rijen in de CSV
|
||||||
|
for index, row in data.iterrows():
|
||||||
|
position = row[position_column]
|
||||||
|
title = row[title_column]
|
||||||
|
artist = row[artist_column]
|
||||||
|
year = row[year_column]
|
||||||
|
|
||||||
|
# Verkrijg de Spotify link voor het nummer
|
||||||
|
url = get_spotify_track_url(title, artist)
|
||||||
|
|
||||||
|
if url:
|
||||||
|
# Print de URL in de console
|
||||||
|
print(f"Genereerde URL voor '{title} - {artist}': {url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Maak QR-code
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
|
||||||
|
qr.add_data(url)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_img = qr.make_image(
|
||||||
|
fill='black', back_color='white').convert("RGB")
|
||||||
|
|
||||||
|
# Maak een nieuwe afbeelding met ruimte voor de titel en URL
|
||||||
|
title_height = 50 # Hoogte voor de titeltekst
|
||||||
|
url_height = 30 # Hoogte voor de URL
|
||||||
|
total_width = qr_img.size[0]
|
||||||
|
total_height = qr_img.size[1] + title_height + url_height
|
||||||
|
|
||||||
|
img_with_title_and_url = Image.new(
|
||||||
|
"RGB", (total_width, total_height), "white")
|
||||||
|
draw = ImageDraw.Draw(img_with_title_and_url)
|
||||||
|
|
||||||
|
# Voeg de titel toe boven de QR-code
|
||||||
|
font_size = 20
|
||||||
|
try:
|
||||||
|
# Probeer een systeemfont te laden (je kunt dit aanpassen aan je systeem)
|
||||||
|
font = ImageFont.truetype("arial.ttf", font_size)
|
||||||
|
except:
|
||||||
|
# Gebruik een standaardfont als `arial.ttf` niet beschikbaar is
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# Gebruik textbbox() om de breedte en hoogte van de tekst te berekenen
|
||||||
|
text_bbox = draw.textbbox((0, 0), title, font=font)
|
||||||
|
text_width = text_bbox[2] - \
|
||||||
|
text_bbox[0] # breedte van de tekst
|
||||||
|
text_height = text_bbox[3] - \
|
||||||
|
text_bbox[1] # hoogte van de tekst
|
||||||
|
|
||||||
|
# Bereken de positie om de titel in het midden te plaatsen
|
||||||
|
text_x = (total_width - text_width) // 2
|
||||||
|
text_y = (title_height - text_height) // 2
|
||||||
|
draw.text((text_x, text_y), title, fill="black", font=font)
|
||||||
|
|
||||||
|
# Voeg de QR-code toe onder de titel
|
||||||
|
img_with_title_and_url.paste(qr_img, (0, title_height))
|
||||||
|
|
||||||
|
# Voeg de URL onder de QR-code toe
|
||||||
|
url_font_size = 15 # Kleinere tekst voor de URL
|
||||||
|
try:
|
||||||
|
# Probeer een systeemfont te laden voor de URL
|
||||||
|
url_font = ImageFont.truetype("arial.ttf", url_font_size)
|
||||||
|
except:
|
||||||
|
# Gebruik een standaardfont als `arial.ttf` niet beschikbaar is
|
||||||
|
url_font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# Gebruik textbbox() om de breedte van de URL te berekenen
|
||||||
|
url_bbox = draw.textbbox((0, 0), url, font=url_font)
|
||||||
|
url_width = url_bbox[2] - url_bbox[0] # breedte van de URL
|
||||||
|
url_height = url_bbox[3] - url_bbox[1] # hoogte van de URL
|
||||||
|
|
||||||
|
# Bereken de positie om de URL in het midden te plaatsen
|
||||||
|
url_x = (total_width - url_width) // 2
|
||||||
|
url_y = total_height - url_height - 10 # 10 pixels van de onderkant
|
||||||
|
draw.text((url_x, url_y), url, fill="black", font=url_font)
|
||||||
|
|
||||||
|
# Maak de bestandsnaam: positie_titel.png
|
||||||
|
filename = f"{position}_{title}.png"
|
||||||
|
# Verwijder ongewenste tekens uit de bestandsnaam (zoals slashes, dubbele punten, enz.)
|
||||||
|
filename = filename.replace("/", "_").replace(":", "_")
|
||||||
|
|
||||||
|
# Sla de afbeelding op
|
||||||
|
img_with_title_and_url.save(
|
||||||
|
os.path.join(output_folder, filename))
|
||||||
|
print(
|
||||||
|
f"QR-code met titel '{title}' gegenereerd als '{filename}'")
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
f"Fout bij het genereren van QR-code voor '{title}': {e}")
|
||||||
|
else:
|
||||||
|
print(f"Geen Spotify link gevonden voor '{title} - {artist}'")
|
||||||
|
|
||||||
|
|
||||||
|
# Voorbeeldgebruik
|
||||||
|
csv_file = "nummers.csv" # Vervang door je eigen CSV-bestand
|
||||||
|
output_folder = "qr_codes_with_titles" # Map waar QR-codes worden opgeslagen
|
||||||
|
position_column = "positie" # De kolomnaam in de CSV met de posities
|
||||||
|
title_column = "titel" # De kolomnaam in de CSV met de titels
|
||||||
|
artist_column = "artiest" # De kolomnaam in de CSV met de artiesten
|
||||||
|
year_column = "jaar" # De kolomnaam in de CSV met de jaren
|
||||||
|
|
||||||
|
generate_qr_codes_with_titles(
|
||||||
|
csv_file, output_folder, position_column, title_column, artist_column, year_column)
|
||||||
@ -0,0 +1,164 @@
|
|||||||
|
import pandas as pd
|
||||||
|
import qrcode
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
# Geef het absolute pad op naar je .env bestand
|
||||||
|
env_path = "./.env" # Pas dit pad aan!
|
||||||
|
|
||||||
|
# Laad de .env bestand expliciet
|
||||||
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
# Controleer of de variabelen goed zijn geladen
|
||||||
|
YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY')
|
||||||
|
|
||||||
|
|
||||||
|
# Functie om de YouTube video link te verkrijgen
|
||||||
|
|
||||||
|
|
||||||
|
def get_youtube_video_url(title, artist):
|
||||||
|
# Encodeer de titel en artiest voor de zoekopdracht
|
||||||
|
query = f"{title} {artist}"
|
||||||
|
encoded_query = urllib.parse.quote_plus(query)
|
||||||
|
|
||||||
|
# Zoek naar de video op YouTube via de API
|
||||||
|
search_url = f"https://www.googleapis.com/youtube/v3/search?part=snippet&q={encoded_query}&key={YOUTUBE_API_KEY}&maxResults=1"
|
||||||
|
response = requests.get(search_url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
results = response.json()
|
||||||
|
items = results.get('items', [])
|
||||||
|
|
||||||
|
if items:
|
||||||
|
# Haal de video-ID op van het zoekresultaat
|
||||||
|
video_id = items[0]['id'].get('videoId')
|
||||||
|
if video_id:
|
||||||
|
video_url = f"https://www.youtube.com/watch?v={video_id}"
|
||||||
|
return video_url
|
||||||
|
print(f"Fout bij het zoeken naar video voor '{title} - {artist}'")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_qr_codes_with_titles(csv_file, output_folder, position_column, title_column, artist_column, year_column):
|
||||||
|
# Maak de outputmap aan als die nog niet bestaat
|
||||||
|
os.makedirs(output_folder, exist_ok=True)
|
||||||
|
|
||||||
|
# Lees het CSV-bestand
|
||||||
|
try:
|
||||||
|
data = pd.read_csv(csv_file, delimiter=";")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fout bij het lezen van de CSV: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Controleer of de benodigde kolommen bestaan
|
||||||
|
if position_column not in data.columns or title_column not in data.columns or artist_column not in data.columns or year_column not in data.columns:
|
||||||
|
print(
|
||||||
|
f"Kolommen '{position_column}', '{title_column}', '{artist_column}' en/of '{year_column}' niet gevonden in CSV-bestand.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Loop door de rijen in de CSV
|
||||||
|
for index, row in data.iterrows():
|
||||||
|
position = row[position_column]
|
||||||
|
title = row[title_column]
|
||||||
|
artist = row[artist_column]
|
||||||
|
year = row[year_column]
|
||||||
|
|
||||||
|
# Verkrijg de YouTube link voor het nummer
|
||||||
|
url = get_youtube_video_url(title, artist)
|
||||||
|
|
||||||
|
if url:
|
||||||
|
# Print de URL in de console
|
||||||
|
print(f"Genereerde URL voor '{title} - {artist}': {url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Maak QR-code
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
|
||||||
|
qr.add_data(url)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_img = qr.make_image(
|
||||||
|
fill='black', back_color='white').convert("RGB")
|
||||||
|
|
||||||
|
# Maak een nieuwe afbeelding met ruimte voor de titel en URL
|
||||||
|
title_height = 50 # Hoogte voor de titeltekst
|
||||||
|
url_height = 30 # Hoogte voor de URL
|
||||||
|
total_width = qr_img.size[0]
|
||||||
|
total_height = qr_img.size[1] + title_height + url_height
|
||||||
|
|
||||||
|
img_with_title_and_url = Image.new(
|
||||||
|
"RGB", (total_width, total_height), "white")
|
||||||
|
draw = ImageDraw.Draw(img_with_title_and_url)
|
||||||
|
|
||||||
|
# Voeg de titel toe boven de QR-code
|
||||||
|
font_size = 20
|
||||||
|
try:
|
||||||
|
# Probeer een systeemfont te laden (je kunt dit aanpassen aan je systeem)
|
||||||
|
font = ImageFont.truetype("arial.ttf", font_size)
|
||||||
|
except:
|
||||||
|
# Gebruik een standaardfont als `arial.ttf` niet beschikbaar is
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# Gebruik textbbox() om de breedte en hoogte van de tekst te berekenen
|
||||||
|
text_bbox = draw.textbbox((0, 0), title, font=font)
|
||||||
|
text_width = text_bbox[2] - \
|
||||||
|
text_bbox[0] # breedte van de tekst
|
||||||
|
text_height = text_bbox[3] - \
|
||||||
|
text_bbox[1] # hoogte van de tekst
|
||||||
|
|
||||||
|
# Bereken de positie om de titel in het midden te plaatsen
|
||||||
|
text_x = (total_width - text_width) // 2
|
||||||
|
text_y = (title_height - text_height) // 2
|
||||||
|
draw.text((text_x, text_y), title, fill="black", font=font)
|
||||||
|
|
||||||
|
# Voeg de QR-code toe onder de titel
|
||||||
|
img_with_title_and_url.paste(qr_img, (0, title_height))
|
||||||
|
|
||||||
|
# Voeg de URL onder de QR-code toe
|
||||||
|
url_font_size = 15 # Kleinere tekst voor de URL
|
||||||
|
try:
|
||||||
|
# Probeer een systeemfont te laden voor de URL
|
||||||
|
url_font = ImageFont.truetype("arial.ttf", url_font_size)
|
||||||
|
except:
|
||||||
|
# Gebruik een standaardfont als `arial.ttf` niet beschikbaar is
|
||||||
|
url_font = ImageFont.load_default()
|
||||||
|
|
||||||
|
# Gebruik textbbox() om de breedte van de URL te berekenen
|
||||||
|
url_bbox = draw.textbbox((0, 0), url, font=url_font)
|
||||||
|
url_width = url_bbox[2] - url_bbox[0] # breedte van de URL
|
||||||
|
url_height = url_bbox[3] - url_bbox[1] # hoogte van de URL
|
||||||
|
|
||||||
|
# Bereken de positie om de URL in het midden te plaatsen
|
||||||
|
url_x = (total_width - url_width) // 2
|
||||||
|
url_y = total_height - url_height - 10 # 10 pixels van de onderkant
|
||||||
|
draw.text((url_x, url_y), url, fill="black", font=url_font)
|
||||||
|
|
||||||
|
# Maak de bestandsnaam: positie_titel.png
|
||||||
|
filename = f"{position}_{title}.png"
|
||||||
|
# Verwijder ongewenste tekens uit de bestandsnaam (zoals slashes, dubbele punten, enz.)
|
||||||
|
filename = filename.replace("/", "_").replace(":", "_")
|
||||||
|
|
||||||
|
# Sla de afbeelding op
|
||||||
|
img_with_title_and_url.save(
|
||||||
|
os.path.join(output_folder, filename))
|
||||||
|
print(
|
||||||
|
f"QR-code met titel '{title}' gegenereerd als '{filename}'")
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
f"Fout bij het genereren van QR-code voor '{title}': {e}")
|
||||||
|
else:
|
||||||
|
print(f"Geen YouTube link gevonden voor '{title} - {artist}'")
|
||||||
|
|
||||||
|
|
||||||
|
# Voorbeeldgebruik
|
||||||
|
csv_file = "nummers.csv" # Vervang door je eigen CSV-bestand
|
||||||
|
output_folder = "qr_codes_with_titles" # Map waar QR-codes worden opgeslagen
|
||||||
|
position_column = "positie" # De kolomnaam in de CSV met de posities
|
||||||
|
title_column = "titel" # De kolomnaam in de CSV met de titels
|
||||||
|
artist_column = "artiest" # De kolomnaam in de CSV met de artiesten
|
||||||
|
year_column = "jaar" # De kolomnaam in de CSV met de jaren
|
||||||
|
|
||||||
|
generate_qr_codes_with_titles(
|
||||||
|
csv_file, output_folder, position_column, title_column, artist_column, year_column)
|
||||||
@ -0,0 +1,173 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import base64
|
||||||
|
import requests
|
||||||
|
import urllib.parse
|
||||||
|
import pandas as pd
|
||||||
|
import qrcode
|
||||||
|
import logging
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
|
||||||
|
# Geef het absolute pad op naar je .env bestand
|
||||||
|
env_path = "./.env" # Pas dit pad aan!
|
||||||
|
|
||||||
|
# Laad de .env bestand expliciet
|
||||||
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
# Haal API-sleutels op
|
||||||
|
CLIENT_ID = os.getenv('SPOTIFY_CLIENT_ID')
|
||||||
|
CLIENT_SECRET = os.getenv('SPOTIFY_CLIENT_SECRET')
|
||||||
|
YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY')
|
||||||
|
|
||||||
|
# Configureren van logging
|
||||||
|
logging.basicConfig(filename="log.txt", level=logging.INFO,
|
||||||
|
format="%(asctime)s - %(message)s")
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(message):
|
||||||
|
logging.info(f"{sys.argv[0]} - {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_spotify_access_token():
|
||||||
|
auth_url = "https://accounts.spotify.com/api/token"
|
||||||
|
auth_data = {'grant_type': 'client_credentials'}
|
||||||
|
auth_headers = {
|
||||||
|
'Authorization': f"Basic {base64.b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode('utf-8')).decode('utf-8')}"
|
||||||
|
}
|
||||||
|
response = requests.post(auth_url, data=auth_data, headers=auth_headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
log_message(f"Succesvol Spotify toegangstoken verkregen.")
|
||||||
|
return response.json().get('access_token')
|
||||||
|
else:
|
||||||
|
log_message(
|
||||||
|
f"Fout bij het verkrijgen van Spotify toegangstoken: {response.status_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_spotify_track_url(title, artist):
|
||||||
|
access_token = get_spotify_access_token()
|
||||||
|
if not access_token:
|
||||||
|
log_message(
|
||||||
|
f"Geen toegangstoken voor Spotify beschikbaar voor {title} - {artist}.")
|
||||||
|
return None
|
||||||
|
query = urllib.parse.quote_plus(f"{title} {artist}")
|
||||||
|
search_url = f"https://api.spotify.com/v1/search?q={query}&type=track&limit=1"
|
||||||
|
headers = {'Authorization': f"Bearer {access_token}"}
|
||||||
|
response = requests.get(search_url, headers=headers)
|
||||||
|
tracks = response.json().get('tracks', {}).get(
|
||||||
|
'items', []) if response.status_code == 200 else []
|
||||||
|
if tracks:
|
||||||
|
log_message(f"URL voor '{title} - {artist}' gevonden.")
|
||||||
|
return tracks[0]['external_urls']['spotify']
|
||||||
|
else:
|
||||||
|
log_message(f"Geen URL gevonden voor '{title} - {artist}' op Spotify.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_youtube_video_url(title, artist):
|
||||||
|
query = urllib.parse.quote_plus(f"{title} {artist}")
|
||||||
|
search_url = f"https://www.googleapis.com/youtube/v3/search?part=snippet&q={query}&key={YOUTUBE_API_KEY}&maxResults=1&type=video"
|
||||||
|
response = requests.get(search_url)
|
||||||
|
items = response.json().get('items', []) if response.status_code == 200 else []
|
||||||
|
if items and 'videoId' in items[0]['id']:
|
||||||
|
log_message(f"URL voor '{title} - {artist}' gevonden op YouTube.")
|
||||||
|
return f"https://www.youtube.com/watch?v={items[0]['id']['videoId']}"
|
||||||
|
else:
|
||||||
|
log_message(f"Geen URL gevonden voor '{title} - {artist}' op YouTube.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_qr_codes_and_pdf(csv_file, output_pdf, position_col, title_col, artist_col, year_col, platform):
|
||||||
|
data = pd.read_csv(csv_file, delimiter=";")
|
||||||
|
c = canvas.Canvas(output_pdf)
|
||||||
|
|
||||||
|
for _, row in data.iterrows():
|
||||||
|
position, title, artist, year = row[position_col], row[title_col], row[artist_col], row[year_col]
|
||||||
|
url = get_spotify_track_url(
|
||||||
|
title, artist) if platform == "spotify" else get_youtube_video_url(title, artist)
|
||||||
|
|
||||||
|
if url:
|
||||||
|
# Genereer QR-code
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
|
||||||
|
qr.add_data(url)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_img = qr.make_image(
|
||||||
|
fill='black', back_color='white').convert("RGB")
|
||||||
|
|
||||||
|
# Sla de QR-code op als afbeelding
|
||||||
|
qr_image_path = f"temp_qr.png"
|
||||||
|
qr_img.save(qr_image_path)
|
||||||
|
|
||||||
|
# Haal de afmetingen van de QR-code op
|
||||||
|
qr_width, qr_height = qr_img.size
|
||||||
|
|
||||||
|
# Pas de pagina grootte aan
|
||||||
|
page_width = qr_width
|
||||||
|
page_height = qr_height + 100 # Voeg extra ruimte bovenaan de pagina voor tekst
|
||||||
|
|
||||||
|
# Stel de pagina grootte in
|
||||||
|
c.setPageSize((page_width, page_height))
|
||||||
|
|
||||||
|
# Voeg de QR-code toe aan de PDF (Pagina 1)
|
||||||
|
c.drawImage(qr_image_path, 0, page_height - qr_height,
|
||||||
|
width=qr_width, height=qr_height)
|
||||||
|
c.showPage() # Maak een nieuwe pagina
|
||||||
|
|
||||||
|
# Bereken de breedte van de tekst
|
||||||
|
text_artist = f"Artiest: {artist}"
|
||||||
|
text_title = f"Titel: {title}"
|
||||||
|
text_year = f"Jaar: {year}"
|
||||||
|
text_url = f"Link: {url}"
|
||||||
|
|
||||||
|
# Stel het lettertype in en meet de tekstgrootte
|
||||||
|
c.setFont("Helvetica", 12)
|
||||||
|
|
||||||
|
text_artist_width = c.stringWidth(text_artist, "Helvetica", 12)
|
||||||
|
text_title_width = c.stringWidth(text_title, "Helvetica", 12)
|
||||||
|
text_year_width = c.stringWidth(text_year, "Helvetica", 12)
|
||||||
|
text_url_width = c.stringWidth(text_url, "Helvetica", 12)
|
||||||
|
|
||||||
|
# Bereken de X-coördinaten voor gecentreerde tekst
|
||||||
|
x_artist = (page_width - text_artist_width) / 2
|
||||||
|
x_title = (page_width - text_title_width) / 2
|
||||||
|
x_year = (page_width - text_year_width) / 2
|
||||||
|
x_url = (page_width - text_url_width) / 2
|
||||||
|
|
||||||
|
# Verhoog de Y-positie voor de tekst zodat deze niet onder de QR-code valt
|
||||||
|
y_start = page_height - qr_height - 30 # Verhoog naar boven
|
||||||
|
|
||||||
|
# Voeg de teksten toe aan de PDF (Pagina 2)
|
||||||
|
c.drawString(x_artist, y_start, text_artist)
|
||||||
|
c.drawString(x_title, y_start - 20, text_title)
|
||||||
|
c.drawString(x_year, y_start - 40, text_year)
|
||||||
|
c.drawString(x_url, y_start - 60, text_url)
|
||||||
|
|
||||||
|
c.showPage() # Maak een nieuwe pagina voor de volgende nummer
|
||||||
|
|
||||||
|
os.remove(qr_image_path) # Verwijder tijdelijke QR-code afbeelding
|
||||||
|
else:
|
||||||
|
log_message(f"Geen link gevonden voor '{title} - {artist}'.")
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
log_message(f"PDF succesvol gegenereerd: {output_pdf}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) != 2 or sys.argv[1] not in ["spotify", "youtube"]:
|
||||||
|
log_message("Gebruik: python scriptnaam.py <mode>")
|
||||||
|
log_message("<mode> moet 'spotify' of 'youtube' zijn.")
|
||||||
|
print("Gebruik: python scriptnaam.py <mode>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
platform = sys.argv[1]
|
||||||
|
csv_file = "nummers.csv"
|
||||||
|
output_pdf = "nummers_met_qr.pdf"
|
||||||
|
position_col, title_col, artist_col, year_col = "positie", "titel", "artiest", "jaar"
|
||||||
|
|
||||||
|
log_message(
|
||||||
|
f"Script gestart voor platform: {platform}. CSV bestand: {csv_file}, Output PDF: {output_pdf}")
|
||||||
|
generate_qr_codes_and_pdf(csv_file, output_pdf, position_col,
|
||||||
|
title_col, artist_col, year_col, platform)
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
pandas
|
||||||
|
qrcode[pil]
|
||||||
|
Pillow
|
||||||
|
reportlab
|
||||||
|
requests
|
||||||
|
python-dotenv
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
pandas
|
||||||
|
matplotlib
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
requests
|
||||||
|
mysql-connector-python
|
||||||
|
python-dotenv
|
||||||
@ -0,0 +1,132 @@
|
|||||||
|
import requests
|
||||||
|
import mysql.connector
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Laad .env bestand
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Configuratie
|
||||||
|
GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY")
|
||||||
|
WEATHER_API_KEY = os.getenv("WEATHER_API_KEY")
|
||||||
|
HOME_ADDRESS = os.getenv("HOME_ADDRESS")
|
||||||
|
WORK_ADDRESS = os.getenv("WORK_ADDRESS")
|
||||||
|
|
||||||
|
LOG_FILE = "log.txt"
|
||||||
|
SCRIPT_NAME = "LogTravelTime.py"
|
||||||
|
|
||||||
|
db_config = {
|
||||||
|
"host": os.getenv("DB_HOST"),
|
||||||
|
"user": os.getenv("DB_USER"),
|
||||||
|
"password": os.getenv("DB_PASSWORD"),
|
||||||
|
"database": os.getenv("DB_NAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(message):
|
||||||
|
with open(LOG_FILE, "a") as log_file:
|
||||||
|
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
log_file.write(f"[{timestamp}] {SCRIPT_NAME}: {message}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def create_table():
|
||||||
|
connection = mysql.connector.connect(**db_config)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS travel_times (
|
||||||
|
utc_datetime CHAR(16),
|
||||||
|
origin VARCHAR(255),
|
||||||
|
destination VARCHAR(255),
|
||||||
|
travel_time INT,
|
||||||
|
direction ENUM('heen', 'terug'),
|
||||||
|
mode ENUM('driving', 'bicycling', 'transit'),
|
||||||
|
temperature FLOAT,
|
||||||
|
weather VARCHAR(255),
|
||||||
|
PRIMARY KEY (utc_datetime, mode, direction)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_weather():
|
||||||
|
url = f"https://api.openweathermap.org/data/2.5/weather?q=Amsterdam&appid={WEATHER_API_KEY}&units=metric"
|
||||||
|
response = requests.get(url)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data["cod"] == 200:
|
||||||
|
temperature = data["main"]["temp"]
|
||||||
|
weather = data["weather"][0]["description"]
|
||||||
|
return temperature, weather
|
||||||
|
else:
|
||||||
|
log_message("Error fetching weather data")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_travel_time(origin, destination, mode):
|
||||||
|
url = "https://maps.googleapis.com/maps/api/distancematrix/json"
|
||||||
|
params = {
|
||||||
|
"origins": origin,
|
||||||
|
"destinations": destination,
|
||||||
|
"mode": mode,
|
||||||
|
"departure_time": "now",
|
||||||
|
"traffic_model": "best_guess",
|
||||||
|
"key": GOOGLE_MAPS_API_KEY
|
||||||
|
}
|
||||||
|
response = requests.get(url, params=params)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data["status"] == "OK":
|
||||||
|
if mode == "driving":
|
||||||
|
duration = data["rows"][0]["elements"][0]["duration_in_traffic"]["value"]
|
||||||
|
else:
|
||||||
|
duration = data["rows"][0]["elements"][0]["duration"]["value"]
|
||||||
|
return duration // 60 # Minuten
|
||||||
|
else:
|
||||||
|
log_message(f"Error fetching travel time for {mode}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def save_travel_time(origin, destination, travel_time, direction, mode, temperature, weather):
|
||||||
|
connection = mysql.connector.connect(**db_config)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
query = """
|
||||||
|
INSERT INTO travel_times (utc_datetime, origin, destination, travel_time, direction, mode, temperature, weather)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE travel_time = VALUES(travel_time), direction = VALUES(direction), temperature = VALUES(temperature), weather = VALUES(weather)
|
||||||
|
"""
|
||||||
|
utc_now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
|
||||||
|
cursor.execute(query, (utc_now, origin, destination,
|
||||||
|
travel_time, direction, mode, temperature, weather))
|
||||||
|
connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
connection.close()
|
||||||
|
log_message(
|
||||||
|
f"Saved travel time for {mode} {direction}: {travel_time} minutes, weather: {weather}, temp: {temperature}°C")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
create_table()
|
||||||
|
temperature, weather = get_weather()
|
||||||
|
|
||||||
|
if temperature is None or weather is None:
|
||||||
|
log_message("Skipping travel time logging due to missing weather data")
|
||||||
|
return
|
||||||
|
|
||||||
|
for mode in ["driving", "bicycling", "transit"]:
|
||||||
|
travel_time_morning = get_travel_time(HOME_ADDRESS, WORK_ADDRESS, mode)
|
||||||
|
if travel_time_morning:
|
||||||
|
save_travel_time(HOME_ADDRESS, WORK_ADDRESS,
|
||||||
|
travel_time_morning, "heen", mode, temperature, weather)
|
||||||
|
|
||||||
|
travel_time_evening = get_travel_time(WORK_ADDRESS, HOME_ADDRESS, mode)
|
||||||
|
if travel_time_evening:
|
||||||
|
save_travel_time(WORK_ADDRESS, HOME_ADDRESS,
|
||||||
|
travel_time_evening, "terug", mode, temperature, weather)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
flask
|
||||||
|
numpy
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
from flask import Flask, jsonify
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def hello_world():
|
||||||
|
# Genereer een willekeurige matrix van 3x3 met NumPy
|
||||||
|
matrix = np.random.rand(3, 3).tolist()
|
||||||
|
|
||||||
|
# Maak een bericht met de matrix
|
||||||
|
message = {
|
||||||
|
"message": "Welkom bij de Flask API!",
|
||||||
|
"random_matrix": matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify(message)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export TZ=Europe/Amsterdam
|
||||||
|
#SNAP_BASE="/mnt/hgfs/Disk2/UniFi-Snaps"
|
||||||
|
SNAP_BASE="/files/img"
|
||||||
|
OUT_DIR="$SNAP_BASE/timelapse"
|
||||||
|
DATE_EXT=`date '+%F %H%M'`
|
||||||
|
VERBOSE=1
|
||||||
|
|
||||||
|
declare -A CAMS
|
||||||
|
|
||||||
|
CAMS["Front Garden"]="http://192.168.10.23/snap.jpeg"
|
||||||
|
CAMS["Frontdoor"]="http://192.168.1.131/snap.jpeg"
|
||||||
|
CAMS["Driveway"]="http://192.1.1.112/snap.jpeg"
|
||||||
|
CAMS["Back Garden"]="http://192.168.1.134/snap.jpeg"
|
||||||
|
|
||||||
|
# If we are in a terminal, be verbose.
|
||||||
|
# if [[ -z $VERBOSE && -t 1 ]]; then
|
||||||
|
# VERBOSE=1
|
||||||
|
# fi
|
||||||
|
|
||||||
|
|
||||||
|
log()
|
||||||
|
{
|
||||||
|
if [ ! -z $VERBOSE ]; then echo "$@"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
logerr()
|
||||||
|
{
|
||||||
|
echo "$@" 1>&2;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDir()
|
||||||
|
{
|
||||||
|
if [ ! -d "$1" ]; then
|
||||||
|
mkdir "$1"
|
||||||
|
# check error here
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
getSnap() {
|
||||||
|
|
||||||
|
snapDir="$SNAP_BASE/$1"
|
||||||
|
if [ ! -d "$snapDir" ]; then
|
||||||
|
mkdir -p "$snapDir"
|
||||||
|
# check error here
|
||||||
|
fi
|
||||||
|
|
||||||
|
snapFile="$snapDir/$1 - $DATE_EXT.jpg"
|
||||||
|
|
||||||
|
log savingSnap "$2" to "$snapFile"
|
||||||
|
|
||||||
|
wget --quiet -O "$snapFile" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
createMovie()
|
||||||
|
{
|
||||||
|
snapDir="$SNAP_BASE/$1"
|
||||||
|
snapTemp="$snapDir/temp-$DATE_EXT"
|
||||||
|
snapFileList="$snapDir/temp-$DATE_EXT/files.list"
|
||||||
|
|
||||||
|
if [ ! -d "$snapDir" ]; then
|
||||||
|
logedd "Error : No media files in '$snapDir'"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
createDir "$snapTemp"
|
||||||
|
|
||||||
|
if [ "$2" = "today" ]; then
|
||||||
|
log "Creating video of $1 from today's images"
|
||||||
|
ls "$snapDir/"*`date '+%F'`*.jpg | sort > "$snapFileList"
|
||||||
|
elif [ "$2" = "yesterday" ]; then
|
||||||
|
log "Creating video of $1 from yesterday's images"
|
||||||
|
ls "$snapDir/"*`date '+%F' -d "1 day ago"`*.jpg | sort > "$snapFileList"
|
||||||
|
elif [ "$2" = "file" ]; then
|
||||||
|
if [ ! -f "$3" ]; then
|
||||||
|
logerr "ERROR file '$3' not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "Creating video of $1 from images in $3"
|
||||||
|
cp "$3" "$snapFileList"
|
||||||
|
else
|
||||||
|
log "Creating video of $1 from all images"
|
||||||
|
`ls "$snapDir/"*.jpg | sort > "$snapFileList"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# need to chance current dir so links work over network mounts
|
||||||
|
cwd=`pwd`
|
||||||
|
cd "$snapTemp"
|
||||||
|
x=1
|
||||||
|
#for file in $snapSearch; do
|
||||||
|
while IFS= read -r file; do
|
||||||
|
counter=$(printf %06d $x)
|
||||||
|
ln -s "../`basename "$file"`" "./$counter.jpg"
|
||||||
|
x=$(($x+1))
|
||||||
|
done < "$snapFileList"
|
||||||
|
#done
|
||||||
|
|
||||||
|
if [ $x -eq 1 ]; then
|
||||||
|
logerr "ERROR no files found"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
createDir "$OUT_DIR"
|
||||||
|
outfile="$OUT_DIR/$1 - $DATE_EXT.mp4"
|
||||||
|
|
||||||
|
ffmpeg -r 15 -start_number 1 -i "$snapTemp/"%06d.jpg -c:v libx264 -preset slow -crf 18 -c:a copy -pix_fmt yuv420p "$outfile" -hide_banner -loglevel panic
|
||||||
|
|
||||||
|
log "Created $outfile"
|
||||||
|
|
||||||
|
cd $cwd
|
||||||
|
rm -rf "$snapTemp"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
savesnap)
|
||||||
|
for ((i = 2; i <= $#; i++ )); do
|
||||||
|
if [ -z "${CAMS[${!i}]}" ]; then
|
||||||
|
logerr "ERROR, can't find camera '${!i}'"
|
||||||
|
else
|
||||||
|
getSnap "${!i}" "${CAMS[${!i}]}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
createvideo)
|
||||||
|
createMovie "${2}" "${3}" "${4}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
logerr "Bad Args use :-"
|
||||||
|
logerr "$0 savesnap \"camera name\""
|
||||||
|
logerr "$0 createvideo \"camera name\" today"
|
||||||
|
logerr "options (today|yesterday|all|filename)"
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,101 @@
|
|||||||
|
import mysql.connector
|
||||||
|
from mysql.connector import Error
|
||||||
|
|
||||||
|
|
||||||
|
def create_db_connection(config, logger):
|
||||||
|
"""
|
||||||
|
Maakt verbinding met de MySQL-database.
|
||||||
|
:param config: Configuratie voor databaseverbinding.
|
||||||
|
:param logger: Logger object voor loggen.
|
||||||
|
:return: De databaseverbinding
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
connection = mysql.connector.connect(
|
||||||
|
host=config['host'],
|
||||||
|
port=config['port'],
|
||||||
|
user=config['user'],
|
||||||
|
password=config['password'],
|
||||||
|
database=config['database']
|
||||||
|
)
|
||||||
|
if connection.is_connected():
|
||||||
|
logger.debug("Successfully connected to the database.")
|
||||||
|
return connection
|
||||||
|
except Error as e:
|
||||||
|
logger.error(f"Error while connecting to MySQL: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def create_table_if_not_exists(cursor, logger):
|
||||||
|
"""
|
||||||
|
Maakt de tabel aan in de database als deze nog niet bestaat.
|
||||||
|
:param cursor: De database cursor
|
||||||
|
:param logger: Logger object voor loggen.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# SQL query om de tabel aan te maken
|
||||||
|
create_table_query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS weather_forecast (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
utc DATETIME UNIQUE NOT NULL,
|
||||||
|
temperature FLOAT NOT NULL,
|
||||||
|
weather_description VARCHAR(255) NOT NULL,
|
||||||
|
wind_speed FLOAT NOT NULL,
|
||||||
|
rain FLOAT DEFAULT 0,
|
||||||
|
solar_performance FLOAT DEFAULT 0,
|
||||||
|
sunrise DATETIME NOT NULL,
|
||||||
|
sunset DATETIME NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
cursor.execute(create_table_query)
|
||||||
|
logger.debug("Weather forecast table is ready (created or exists).")
|
||||||
|
except Error as e:
|
||||||
|
logger.error(f"Error creating table: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
def insert_or_update_forecast_data(cursor, utc_dt, temp, weather, wind_speed, rain, solar_performance, sunrise, sunset, logger):
|
||||||
|
"""
|
||||||
|
Voegt een record toe aan de tabel of werkt het bij als het record al bestaat.
|
||||||
|
:param cursor: De database cursor
|
||||||
|
:param utc_dt: UTC-tijd van de voorspelling
|
||||||
|
:param temp: Temperatuur
|
||||||
|
:param weather: Weersomstandigheden
|
||||||
|
:param wind_speed: Windsnelheid
|
||||||
|
:param rain: Hoeveelheid regen
|
||||||
|
:param solar_performance: Schatting van de zonneprestaties
|
||||||
|
:param sunrise: Tijd van zonsopgang
|
||||||
|
:param sunset: Tijd van zonsondergang
|
||||||
|
:param logger: Logger object voor loggen.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Check of het record al bestaat
|
||||||
|
check_query = """
|
||||||
|
SELECT id FROM weather_forecast WHERE utc = %s;
|
||||||
|
"""
|
||||||
|
cursor.execute(check_query, (utc_dt,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# Als het record bestaat, werk het dan bij
|
||||||
|
update_query = """
|
||||||
|
UPDATE weather_forecast
|
||||||
|
SET temperature = %s, weather_description = %s, wind_speed = %s, rain = %s, solar_performance = %s, sunrise = %s, sunset = %s
|
||||||
|
WHERE utc = %s;
|
||||||
|
"""
|
||||||
|
cursor.execute(update_query, (temp, weather, wind_speed,
|
||||||
|
rain, solar_performance, sunrise, sunset, utc_dt))
|
||||||
|
logger.debug(f"Updated existing record for {utc_dt}.")
|
||||||
|
else:
|
||||||
|
# Als het record niet bestaat, voeg het dan toe
|
||||||
|
insert_query = """
|
||||||
|
INSERT INTO weather_forecast (utc, temperature, weather_description, wind_speed, rain, solar_performance, sunrise, sunset)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s);
|
||||||
|
"""
|
||||||
|
cursor.execute(insert_query, (utc_dt, temp, weather,
|
||||||
|
wind_speed, rain, solar_performance, sunrise, sunset))
|
||||||
|
logger.debug(f"Inserted new record for {utc_dt}.")
|
||||||
|
|
||||||
|
except Error as e:
|
||||||
|
logger.error(f"Error inserting or updating data for {utc_dt}: {e}")
|
||||||
|
raise e
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
import requests
|
||||||
|
import datetime
|
||||||
|
import mysql.connector
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Laad de .env bestand
|
||||||
|
print("Loading environment variables from .env...")
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Haal de API-sleutel uit de omgevingsvariabelen
|
||||||
|
api_key = os.getenv('OPENWEATHERMAP_API_KEY')
|
||||||
|
# Haal de MySQL-gegevens uit de omgevingsvariabelen
|
||||||
|
db_host = os.getenv('DB_HOST')
|
||||||
|
db_user = os.getenv('DB_USER')
|
||||||
|
db_password = os.getenv('DB_PASSWORD')
|
||||||
|
db_database = os.getenv('DB_DATABASE')
|
||||||
|
# Haal de locatie uit de omgevingsvariabelen
|
||||||
|
location = os.getenv('LOCATION')
|
||||||
|
|
||||||
|
print(f"Using OpenWeatherMap API key: {api_key}")
|
||||||
|
print(f"Using location: {location}")
|
||||||
|
|
||||||
|
# URL van de OpenWeatherMap API voor de forecast
|
||||||
|
url = f'http://api.openweathermap.org/data/2.5/forecast?q={location}&units=metric&cnt=7&appid={api_key}'
|
||||||
|
|
||||||
|
print(f"Fetching weather forecast from OpenWeatherMap for {location}...")
|
||||||
|
|
||||||
|
# Haal de data op
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("Data successfully fetched from OpenWeatherMap API.")
|
||||||
|
data = response.json()
|
||||||
|
else:
|
||||||
|
print(f"Error fetching data: {response.status_code}")
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# Verbinding maken met MySQL-database
|
||||||
|
print("Connecting to MySQL database...")
|
||||||
|
db_connection = mysql.connector.connect(
|
||||||
|
host=db_host,
|
||||||
|
user=db_user,
|
||||||
|
password=db_password,
|
||||||
|
database=db_database
|
||||||
|
)
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
|
||||||
|
# Maak de tabel aan als deze nog niet bestaat
|
||||||
|
print("Ensuring the forecast_data table exists...")
|
||||||
|
create_table_query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS forecast_data (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
utc_datetime DATETIME UNIQUE,
|
||||||
|
temperature FLOAT,
|
||||||
|
weather_description VARCHAR(255),
|
||||||
|
wind_speed FLOAT,
|
||||||
|
rain FLOAT
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
cursor.execute(create_table_query)
|
||||||
|
print("Table `forecast_data` is ready.")
|
||||||
|
|
||||||
|
# Toon de forecast en sla deze op in de database
|
||||||
|
print(f"Processing forecast data for {len(data.get('list', []))} entries...")
|
||||||
|
|
||||||
|
for entry in data.get('list', []):
|
||||||
|
# Converteer de tijd naar UTC
|
||||||
|
utc_dt = datetime.datetime.utcfromtimestamp(entry['dt'])
|
||||||
|
temp = entry['main']['temp']
|
||||||
|
weather = entry['weather'][0]['description']
|
||||||
|
wind_speed = entry['wind']['speed']
|
||||||
|
# Regensomstandigheden (indien aanwezig)
|
||||||
|
rain = entry.get('rain', {}).get('3h', 0)
|
||||||
|
|
||||||
|
# Print de data
|
||||||
|
print(f"\nProcessing data for {utc_dt.strftime('%Y-%m-%d %H:%M:%S')} UTC:")
|
||||||
|
print(f"Temperature: {temp}°C")
|
||||||
|
print(f"Weather: {weather.capitalize()}")
|
||||||
|
print(f"Wind Speed: {wind_speed} m/s")
|
||||||
|
print(f"Rain: {rain} mm")
|
||||||
|
|
||||||
|
# Voeg de data toe aan de database of update als het record al bestaat
|
||||||
|
query = """
|
||||||
|
INSERT INTO forecast_data (utc_datetime, temperature, weather_description, wind_speed, rain)
|
||||||
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
temperature = VALUES(temperature),
|
||||||
|
weather_description = VALUES(weather_description),
|
||||||
|
wind_speed = VALUES(wind_speed),
|
||||||
|
rain = VALUES(rain);
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"Executing query to insert/update data for {utc_dt.strftime('%Y-%m-%d %H:%M:%S')}...")
|
||||||
|
cursor.execute(query, (utc_dt, temp, weather, wind_speed, rain))
|
||||||
|
print(
|
||||||
|
f"Data for {utc_dt.strftime('%Y-%m-%d %H:%M:%S')} successfully inserted/updated.")
|
||||||
|
|
||||||
|
# Commit de veranderingen en sluit de verbinding
|
||||||
|
print("Committing changes to the database...")
|
||||||
|
db_connection.commit()
|
||||||
|
print("Changes committed successfully.")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
db_connection.close()
|
||||||
|
print("Database connection closed.")
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logger(app_name, log_level=logging.DEBUG, log_format='%(asctime)s - %(levelname)s - %(message)s'):
|
||||||
|
# Naam van de app wordt hier dynamisch ingesteld
|
||||||
|
logger = logging.getLogger(app_name)
|
||||||
|
logger.setLevel(log_level)
|
||||||
|
|
||||||
|
ch = logging.StreamHandler() # Loggen naar de console
|
||||||
|
ch.setLevel(log_level)
|
||||||
|
|
||||||
|
# Formatter wordt hier als parameter gegeven
|
||||||
|
formatter = logging.Formatter(log_format)
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger.addHandler(ch)
|
||||||
|
return logger
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from weather import fetch_weather_data, estimate_solar_performance, get_sun_times
|
||||||
|
from database import create_db_connection, create_table_if_not_exists, insert_or_update_forecast_data
|
||||||
|
from logger import setup_logger, logging
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
# Initialisatie van variabelen en instellingen
|
||||||
|
def initialize_app():
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app_name = "weather_forecast_app"
|
||||||
|
log_format = '%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
|
||||||
|
logger = setup_logger(
|
||||||
|
app_name, log_level=logging.INFO, log_format=log_format)
|
||||||
|
logger.info("Starting weather forecast application...")
|
||||||
|
|
||||||
|
API_KEY = os.getenv('OPENWEATHER_API_KEY')
|
||||||
|
LOCATION = os.getenv('LOCATION', 'Rijswijk')
|
||||||
|
|
||||||
|
db_config = {
|
||||||
|
'host': os.getenv('DB_HOST'),
|
||||||
|
'port': os.getenv('DB_PORT'),
|
||||||
|
'user': os.getenv('DB_USER'),
|
||||||
|
'password': os.getenv('DB_PASSWORD'),
|
||||||
|
'database': os.getenv('DB_NAME')
|
||||||
|
}
|
||||||
|
|
||||||
|
return API_KEY, LOCATION, db_config, logger
|
||||||
|
|
||||||
|
|
||||||
|
# Database functionaliteit
|
||||||
|
def handle_database_operations(db_config, logger):
|
||||||
|
try:
|
||||||
|
db_connection = create_db_connection(db_config, logger)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to connect to the database: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
cursor = db_connection.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_table_if_not_exists(cursor, logger)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating table: {e}")
|
||||||
|
db_connection.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
return db_connection, cursor
|
||||||
|
|
||||||
|
|
||||||
|
# Weather functionaliteit
|
||||||
|
def fetch_and_process_weather_data(API_KEY, LOCATION, logger):
|
||||||
|
try:
|
||||||
|
logger.info(
|
||||||
|
f"Fetching weather forecast from OpenWeatherMap for {LOCATION}...")
|
||||||
|
data = fetch_weather_data(API_KEY, LOCATION, logger)
|
||||||
|
# pprint(data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching weather data: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
# Hoofdproces: het verwerken van de weersvoorspellingen en opslaan in de database
|
||||||
|
def process_forecasts(data, cursor, logger, location):
|
||||||
|
logger.info(
|
||||||
|
f"Processing forecast data for {len(data.get('list', []))} entries...")
|
||||||
|
|
||||||
|
for entry in data.get('list', []):
|
||||||
|
utc_dt = datetime.fromtimestamp(entry['dt'], tz=timezone.utc)
|
||||||
|
temp = entry['main']['temp']
|
||||||
|
weather = entry['weather'][0]['description']
|
||||||
|
wind_speed = entry['wind']['speed']
|
||||||
|
rain = entry.get('rain', {}).get('3h', 0)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Processing entry at {utc_dt}: Temp={temp}, Weather={weather}, Wind={wind_speed}, Rain={rain}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Haal de sunrise en sunset tijden op per forecast entry
|
||||||
|
sunrise, sunset = get_sun_times(utc_dt, location, logger=logger)
|
||||||
|
|
||||||
|
solar_performance = estimate_solar_performance(
|
||||||
|
entry, utc_dt, location, sunrise, sunset, logger)
|
||||||
|
logger.debug(f"Estimated solar performance: {solar_performance}%")
|
||||||
|
|
||||||
|
insert_or_update_forecast_data(
|
||||||
|
cursor, utc_dt, temp, weather, wind_speed, rain, solar_performance, sunrise, sunset, logger)
|
||||||
|
|
||||||
|
|
||||||
|
# Hoofdfunctie
|
||||||
|
def main():
|
||||||
|
API_KEY, LOCATION, db_config, logger = initialize_app()
|
||||||
|
|
||||||
|
try:
|
||||||
|
db_connection, cursor = handle_database_operations(db_config, logger)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during database operations: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = fetch_and_process_weather_data(
|
||||||
|
API_KEY, LOCATION, logger)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching weather data: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
process_forecasts(data, cursor, logger, LOCATION)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing forecasts: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Committing changes to the database...")
|
||||||
|
db_connection.commit()
|
||||||
|
logger.info("Changes committed successfully.")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
db_connection.close()
|
||||||
|
logger.info("Database connection closed.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
astral
|
||||||
|
requests
|
||||||
|
python-dotenv
|
||||||
|
mysql-connector-python
|
||||||
@ -0,0 +1,119 @@
|
|||||||
|
from astral import LocationInfo
|
||||||
|
from astral.sun import sun
|
||||||
|
import pytz
|
||||||
|
from datetime import datetime
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_weather_data(api_key, location, logger):
|
||||||
|
"""
|
||||||
|
Haalt de weersvoorspelling op van OpenWeatherMap.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- api_key: API sleutel voor OpenWeatherMap
|
||||||
|
- location: Locatienaam (stad)
|
||||||
|
- logger: Logger instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- JSON data van de weersvoorspelling
|
||||||
|
"""
|
||||||
|
base_url = "https://api.openweathermap.org/data/2.5/forecast"
|
||||||
|
params = {
|
||||||
|
'q': location,
|
||||||
|
'appid': api_key,
|
||||||
|
'units': 'metric'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(base_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
logger.info(f"Weather data fetched successfully for {location}.")
|
||||||
|
return response.json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Error fetching weather data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_sun_times(utc_datetime, location_name="Rijswijk", country="Netherlands", logger=None):
|
||||||
|
"""
|
||||||
|
Haalt de zonsopgang en zonsondergang op voor een bepaalde UTC tijd en locatie.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- sunrise, sunset (beide datetime objects in UTC).
|
||||||
|
"""
|
||||||
|
location = LocationInfo(location_name, country,
|
||||||
|
"Europe/Amsterdam", 52.0345, 4.3186)
|
||||||
|
|
||||||
|
# Bereken zon-op/zon-onder tijden met Astral
|
||||||
|
sun_times = sun(location.observer,
|
||||||
|
date=utc_datetime.date(), tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
# Overtuig dat het datetime-objecten zijn
|
||||||
|
sunrise = sun_times["sunrise"]
|
||||||
|
sunset = sun_times["sunset"]
|
||||||
|
|
||||||
|
if isinstance(sunrise, str): # Als het toch een string is, fix het
|
||||||
|
sunrise = datetime.fromisoformat(sunrise).replace(tzinfo=pytz.utc)
|
||||||
|
if isinstance(sunset, str):
|
||||||
|
sunset = datetime.fromisoformat(sunset).replace(tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.debug(
|
||||||
|
f"For {utc_datetime.date()} in {location_name}: Sunrise at {sunrise} , Sunset at {sunset} ")
|
||||||
|
|
||||||
|
return sunrise, sunset
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_solar_performance(entry, utc_dt, location, sunrise, sunset, logger):
|
||||||
|
"""
|
||||||
|
Schat de zonne-energieproductie op basis van de zonshoogte en weersfactoren.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- entry: JSON entry met de weerdata
|
||||||
|
- utc_dt: Tijdstip van de voorspelling (UTC)
|
||||||
|
- location: Naam van de locatie voor berekening van zonsopgang/zonsondergang
|
||||||
|
- logger: Logger voor debugging
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- Een percentage (0-100) dat de zonne-energieproductie schat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 🚀 Zorg ervoor dat sunrise en sunset datetime-objecten zijn
|
||||||
|
if isinstance(sunrise, str):
|
||||||
|
sunrise = datetime.fromisoformat(sunrise).replace(tzinfo=pytz.utc)
|
||||||
|
if isinstance(sunset, str):
|
||||||
|
sunset = datetime.fromisoformat(sunset).replace(tzinfo=pytz.utc)
|
||||||
|
# Temperatuur en bewolking ophalen
|
||||||
|
temperature = entry["main"]["temp"]
|
||||||
|
cloudiness = entry["clouds"]["all"] # % bewolking
|
||||||
|
weather_desc = entry["weather"][0]["description"]
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logger.debug(
|
||||||
|
f"UTC: {utc_dt}, Temp: {temperature}, Clouds: {cloudiness}%, Weather: {weather_desc}")
|
||||||
|
|
||||||
|
# Stap 1: Bereken een initiële waarde op basis van zonshoogte (factor van 0 tot 1)
|
||||||
|
if utc_dt < sunrise or utc_dt > sunset:
|
||||||
|
sun_factor = 0.0 # Nacht
|
||||||
|
elif utc_dt == sunrise or utc_dt == sunset:
|
||||||
|
sun_factor = 0.1 # 10% bij zonsopkomst/ondergang
|
||||||
|
else:
|
||||||
|
# Bereken de relatieve tijd binnen de zonnedag (0 = zonsopgang, 1 = zonsondergang)
|
||||||
|
day_fraction = (utc_dt - sunrise) / (sunset - sunrise)
|
||||||
|
# Gebruik een parabolische curve voor maximale intensiteit op het midden van de dag
|
||||||
|
sun_factor = -4 * (day_fraction - 0.5) ** 2 + 1
|
||||||
|
|
||||||
|
sun_factor = max(0, min(sun_factor, 1)) # Beperken tot 0-1
|
||||||
|
|
||||||
|
# Stap 2: Pas weerfactoren toe (bewolking beïnvloedt zonne-intensiteit)
|
||||||
|
# Omrekenen van % naar 0-1 schaal
|
||||||
|
weather_factor = (100 - cloudiness) / 100
|
||||||
|
|
||||||
|
# Totale zonneprestaties berekenen
|
||||||
|
solar_performance = sun_factor * weather_factor * 100 # Omzetten naar percentage
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logger.debug(
|
||||||
|
f"Sun factor: {sun_factor}, Weather factor: {weather_factor}, Solar performance: {solar_performance:.2f}%")
|
||||||
|
|
||||||
|
return round(solar_performance, 2)
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
from flask import Flask, render_template
|
||||||
|
from modules import database
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Laad omgevingsvariabelen vanuit .env bestand
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Configuratie voor database en logging uit omgevingsvariabelen
|
||||||
|
config = {
|
||||||
|
'user': os.getenv('DB_USER'),
|
||||||
|
'password': os.getenv('DB_PASSWORD'),
|
||||||
|
'host': os.getenv('DB_HOST'),
|
||||||
|
'database': os.getenv('DB_NAME')
|
||||||
|
}
|
||||||
|
logger = app.logger # Flask loggers
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_windkracht(wind_speed):
|
||||||
|
"""Berekent de windkracht op basis van de wind_snelheid (m/s)."""
|
||||||
|
if wind_speed <= 0.2:
|
||||||
|
return "Windstil", 0
|
||||||
|
elif wind_speed <= 1.5:
|
||||||
|
return "Zwak", 1
|
||||||
|
elif wind_speed <= 3.3:
|
||||||
|
return "Matig", 2
|
||||||
|
elif wind_speed <= 5.4:
|
||||||
|
return "Frisse wind", 3
|
||||||
|
elif wind_speed <= 7.9:
|
||||||
|
return "Harde wind", 4
|
||||||
|
elif wind_speed <= 10.7:
|
||||||
|
return "Stormachtig", 5
|
||||||
|
else:
|
||||||
|
return "Storm", 6
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
# Maak een database-object (pas de naam aan naar Database)
|
||||||
|
db = database.Database(config, logger) # Veranderd naar database.Database
|
||||||
|
|
||||||
|
# Haal de weerdata op uit de database
|
||||||
|
weather_data = db.get_weather_data()
|
||||||
|
|
||||||
|
# Verwerk de windkracht op basis van de gemiddelde wind_speed
|
||||||
|
for entry in weather_data:
|
||||||
|
wind_speed = entry['avg_wind_speed']
|
||||||
|
|
||||||
|
if wind_speed is not None:
|
||||||
|
entry['windkracht_tekst'], entry['windkracht_num'] = calculate_windkracht(
|
||||||
|
wind_speed)
|
||||||
|
else:
|
||||||
|
entry['windkracht_tekst'] = "Gegevens niet beschikbaar"
|
||||||
|
entry['windkracht_num'] = None
|
||||||
|
|
||||||
|
# Voeg de dag van de week toe (bijvoorbeeld 'Maandag', 'Dinsdag', etc.)
|
||||||
|
# Gebruik direct strftime op een datetime object
|
||||||
|
entry['dag_van_de_week'] = entry['date'].strftime('%A')
|
||||||
|
|
||||||
|
# Geef de verwerkte data door aan de template
|
||||||
|
return render_template('index.html', weather_data=weather_data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
Binary file not shown.
@ -0,0 +1,38 @@
|
|||||||
|
import mysql.connector
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self, config, logger):
|
||||||
|
self.config = config
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Maakt een databaseverbinding."""
|
||||||
|
try:
|
||||||
|
conn = mysql.connector.connect(**self.config)
|
||||||
|
return conn
|
||||||
|
except mysql.connector.Error as err:
|
||||||
|
self.logger.error(f"Database connectiefout: {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_weather_data(self):
|
||||||
|
"""Haalt de gemiddelde weergegevens per dag op."""
|
||||||
|
conn = self.connect()
|
||||||
|
if not conn:
|
||||||
|
return []
|
||||||
|
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT DATE(utc) as date,
|
||||||
|
AVG(temperature) as avg_temp,
|
||||||
|
AVG(solar_performance) as avg_solar,
|
||||||
|
AVG(wind_speed) as avg_wind_speed,
|
||||||
|
GROUP_CONCAT(DISTINCT weather_description) as descriptions
|
||||||
|
FROM weather_forecast
|
||||||
|
GROUP BY DATE(utc)
|
||||||
|
ORDER BY date ASC
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
data = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return data
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
/* Algemene body-stijlen */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Weer-container */
|
||||||
|
.weather-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Weer-box styling */
|
||||||
|
.weather-box {
|
||||||
|
background-color: white;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover-effect voor weer-boxen */
|
||||||
|
.weather-box:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Windkracht styling */
|
||||||
|
.weather-box p {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toevoegen van zonprestatie-kleur (voorbeeld) */
|
||||||
|
.low-solar {
|
||||||
|
border-color: blue;
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medium-solar {
|
||||||
|
border-color: green;
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.high-solar {
|
||||||
|
border-color: orange;
|
||||||
|
background-color: lightyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.very-high-solar {
|
||||||
|
border-color: yellow;
|
||||||
|
background-color: lightgoldenrodyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specifieke styling voor de dagen van de week */
|
||||||
|
.weather-box h2 {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling voor de zonnetjes (per zonne-rating) */
|
||||||
|
.sun-rating {
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alternatief voor een duidelijker onderscheid tussen de dagen */
|
||||||
|
.weather-box:nth-child(odd) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="nl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Weervoorspelling</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Weervoorspelling per dag</h1>
|
||||||
|
|
||||||
|
<div class="weather-container">
|
||||||
|
{% for day in weather_data %}
|
||||||
|
<div class="weather-box
|
||||||
|
{% if day.avg_solar <= 20 %}
|
||||||
|
low-solar
|
||||||
|
{% elif day.avg_solar <= 50 %}
|
||||||
|
medium-solar
|
||||||
|
{% elif day.avg_solar <= 75 %}
|
||||||
|
high-solar
|
||||||
|
{% else %}
|
||||||
|
very-high-solar
|
||||||
|
{% endif %}
|
||||||
|
">
|
||||||
|
<h2>{{ day.dag_van_de_week }} - {{ day.date }}</h2>
|
||||||
|
<p><strong>Gemiddelde temperatuur:</strong> {{ '%.1f' % day.avg_temp }} °C</p>
|
||||||
|
<p><strong>Weersomstandigheden:</strong> {{ day.descriptions }}</p>
|
||||||
|
<p><strong>Windkracht:</strong> {{ day.windkracht_tekst }} (Windkracht {{ day.windkracht_num }})</p>
|
||||||
|
|
||||||
|
<!-- Zonprestatie (afhankelijk van de solar performance) -->
|
||||||
|
<div class="sun-rating">
|
||||||
|
{% if day.avg_solar <= 20 %}
|
||||||
|
🌑
|
||||||
|
{% elif day.avg_solar <= 50 %}
|
||||||
|
🌒
|
||||||
|
{% elif day.avg_solar <= 75 %}
|
||||||
|
🌞🌞
|
||||||
|
{% else %}
|
||||||
|
🌞🌞🌞
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import sys, os
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# list file in directory
|
||||||
|
CheckDir(path)
|
||||||
|
|
||||||
|
|
||||||
|
dryrun = False
|
||||||
|
|
||||||
|
|
||||||
|
def CheckDir(path):
|
||||||
|
# if dir has nfo file
|
||||||
|
|
||||||
|
path2 = os.path.join(path, "")
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for name in dirs:
|
||||||
|
# print(name)
|
||||||
|
orgpath = os.path.join(root, name)
|
||||||
|
fullpath = orgpath
|
||||||
|
if "Unknown" in name:
|
||||||
|
# name = name.replace("Ai Iijima", baseinfo["actress"])
|
||||||
|
if not dryrun:
|
||||||
|
newpath = os.path.join(root, name)
|
||||||
|
os.rename(orgpath, newpath)
|
||||||
|
fullpath = newpath
|
||||||
|
# print("orgpath= {0}, newpath={1}".format(orgpath, newpath))
|
||||||
|
# print("name= {0}, path={1}".format(name, subdir))
|
||||||
|
procesSubdir(fullpath)
|
||||||
|
|
||||||
|
|
||||||
|
rootdir = "/Volumes/Docker/Syncthing/config/Sync"
|
||||||
|
path = os.path.join(rootdir, "manualsort")
|
||||||
|
baseinfo = {}
|
||||||
|
baseinfo["actress"] = "@Unknown"
|
||||||
|
baseinfo["altname"] = "Ai Iijima"
|
||||||
|
|
||||||
|
|
||||||
|
def procesSubdir(path):
|
||||||
|
hasnfo = False
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for name in files:
|
||||||
|
fullpath = os.path.join(root, name)
|
||||||
|
if ".nfo" in name:
|
||||||
|
hasnfo = True
|
||||||
|
setnfofile(root, fullpath)
|
||||||
|
|
||||||
|
# create nfo file withe same filename as mediafile\
|
||||||
|
|
||||||
|
|
||||||
|
# setnfofile
|
||||||
|
|
||||||
|
|
||||||
|
def setnfofile(root, nfofile):
|
||||||
|
#
|
||||||
|
# # add baseinfo into nfo file
|
||||||
|
|
||||||
|
root, basename = os.path.split(root)
|
||||||
|
# print(basename)
|
||||||
|
# print("root= {0} basename={1}".format(root, basename))
|
||||||
|
|
||||||
|
tree = ET.parse(nfofile)
|
||||||
|
xmlroot = tree.getroot()
|
||||||
|
for title in xmlroot.findall("title"):
|
||||||
|
title.text = basename
|
||||||
|
|
||||||
|
# Create a new element
|
||||||
|
new_element = ET.Element("tag")
|
||||||
|
new_element.text = "uncensored"
|
||||||
|
# tag = xmlroot.findall("tag")
|
||||||
|
xmlroot.append(new_element)
|
||||||
|
new_element = ET.Element("tag")
|
||||||
|
new_element.text = "subtitled"
|
||||||
|
# tag = xmlroot.findall("tag")
|
||||||
|
xmlroot.append(new_element)
|
||||||
|
|
||||||
|
if not dryrun:
|
||||||
|
print("writing")
|
||||||
|
tree.write(nfofile)
|
||||||
|
|
||||||
|
# for actor in xmlroot.findall("actor"):
|
||||||
|
# name = actor.find("name")
|
||||||
|
# if name.text == "ai iijima":
|
||||||
|
# name.text = baseinfo["actress"]
|
||||||
|
|
||||||
|
# altname = actor.find("altname")
|
||||||
|
# altname.text = baseinfo["altname"]
|
||||||
|
# print(ET.tostring(actor))
|
||||||
|
|
||||||
|
# for actor in xmlroot.findall("actor"):
|
||||||
|
# name = actor.find("name")
|
||||||
|
# if name.text == "Unknown":
|
||||||
|
# name.text = baseinfo["actress"]
|
||||||
|
|
||||||
|
# altname = actor.find("altname")
|
||||||
|
# altname.text = baseinfo["altname"]
|
||||||
|
# # print(ET.tostring(actor))
|
||||||
|
|
||||||
|
# if not dryrun:
|
||||||
|
# tree.write(nfofile)
|
||||||
|
# print(xmlroot)
|
||||||
|
|
||||||
|
# for root, dirs, files in os.walk(root):
|
||||||
|
# for name in dirs:
|
||||||
|
# print(name)
|
||||||
|
# print(root)
|
||||||
|
|
||||||
|
|
||||||
|
# def tryscrape():
|
||||||
|
|
||||||
|
|
||||||
|
# def getcover():
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in new issue