From 81b2ea2405f7b776acf9bac382f8084c04bd8a71 Mon Sep 17 00:00:00 2001 From: "peter.fong" Date: Tue, 4 Feb 2025 11:41:45 +0000 Subject: [PATCH] Eerste commit --- .devcontainer/Dockerfile | 8 + .devcontainer/devcontainer.json | 39 + README.md | 1 + energie/__pycache__/config.cpython-39.pyc | Bin 0 -> 920 bytes energie/__pycache__/db.cpython-39.pyc | Bin 0 -> 1736 bytes energie/__pycache__/logger.cpython-39.pyc | Bin 0 -> 840 bytes .../__pycache__/mqtt_publisher.cpython-39.pyc | Bin 0 -> 1392 bytes energie/config.py | 30 + energie/currentprice.py | 76 + energie/db.py | 47 + energie/energieprijzen 2024.csv | 8349 +++++++++++++++++ energie/logger.py | 41 + energie/mqtt_publisher.py | 36 + energie/requirements copy.txt | 3 + energie/requirements.txt | 3 + energie/stroomprijzen.py | 82 + energie/utilitycosts.py | 0 qrcodegen/log.txt | 88 + qrcodegen/nummers.csv | 11 + qrcodegen/nummers_met_qr.pdf | 459 + qrcodegen/qrcodegen-spotify.py | 190 + qrcodegen/qrcodegen-youtube.py | 164 + qrcodegen/qrcodegen.py | 173 + ...ts (conflicted copy 2025-01-30 090325).txt | 6 + qrcodegen/requirements.txt | 2 + reistijd/requirements.txt | 3 + reistijd/traveltime.py | 132 + test/requirements.txt | 2 + test/test.py | 22 + timelapse/timelapse.script.sh | 141 + .../__pycache__/database.cpython-39.pyc | Bin 0 -> 3563 bytes .../__pycache__/logger.cpython-39.pyc | Bin 0 -> 561 bytes .../__pycache__/weather.cpython-39.pyc | Bin 0 -> 3115 bytes weather forecast/database.py | 101 + weather forecast/getforecast.py | 106 + weather forecast/logger.py | 17 + weather forecast/main.py | 129 + weather forecast/requirements.txt | 4 + weather forecast/weather.py | 119 + weer_app/main.py | 67 + .../__pycache__/database.cpython-39.pyc | Bin 0 -> 1555 bytes weer_app/modules/database.py | 38 + weer_app/static/style.css | 73 + weer_app/templates/index.html | 47 + xml tagger/xmltag.py | 117 + 45 files changed, 10926 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 README.md create mode 100644 energie/__pycache__/config.cpython-39.pyc create mode 100644 energie/__pycache__/db.cpython-39.pyc create mode 100644 energie/__pycache__/logger.cpython-39.pyc create mode 100644 energie/__pycache__/mqtt_publisher.cpython-39.pyc create mode 100644 energie/config.py create mode 100644 energie/currentprice.py create mode 100644 energie/db.py create mode 100644 energie/energieprijzen 2024.csv create mode 100644 energie/logger.py create mode 100644 energie/mqtt_publisher.py create mode 100644 energie/requirements copy.txt create mode 100644 energie/requirements.txt create mode 100644 energie/stroomprijzen.py create mode 100644 energie/utilitycosts.py create mode 100644 qrcodegen/log.txt create mode 100644 qrcodegen/nummers.csv create mode 100644 qrcodegen/nummers_met_qr.pdf create mode 100644 qrcodegen/qrcodegen-spotify.py create mode 100644 qrcodegen/qrcodegen-youtube.py create mode 100644 qrcodegen/qrcodegen.py create mode 100644 qrcodegen/requirements (conflicted copy 2025-01-30 090325).txt create mode 100644 qrcodegen/requirements.txt create mode 100644 reistijd/requirements.txt create mode 100644 reistijd/traveltime.py create mode 100644 test/requirements.txt create mode 100644 test/test.py create mode 100644 timelapse/timelapse.script.sh create mode 100644 weather forecast/__pycache__/database.cpython-39.pyc create mode 100644 weather forecast/__pycache__/logger.cpython-39.pyc create mode 100644 weather forecast/__pycache__/weather.cpython-39.pyc create mode 100644 weather forecast/database.py create mode 100644 weather forecast/getforecast.py create mode 100644 weather forecast/logger.py create mode 100644 weather forecast/main.py create mode 100644 weather forecast/requirements.txt create mode 100644 weather forecast/weather.py create mode 100644 weer_app/main.py create mode 100644 weer_app/modules/__pycache__/database.cpython-39.pyc create mode 100644 weer_app/modules/database.py create mode 100644 weer_app/static/style.css create mode 100644 weer_app/templates/index.html create mode 100644 xml tagger/xmltag.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..6dba749 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -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"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..e65ec9c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -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" + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..96e32bc --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +-Open deze in devcontainer om te ontwikkelen(CTRL+SH+O, open in devcontainer) diff --git a/energie/__pycache__/config.cpython-39.pyc b/energie/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e0de4b84119cdee9cd26fbc7959f819c97e9897 GIT binary patch literal 920 zcma)*O>fgM7{}uzO|zBqCUMURDVIh!}s+(b}Z|M4tARj!2!JDIdsG# zsIU@k?E;l{Vk3(DbTqE#Z()wa0F7ZekY>S|&75YD6rgat~)hQSzDPfXUS1Yd< z^}uGjxZ6Ak`tXXU&`IknB4~|_wKvw;Sa*#`>&AsGyw!_ij0+hM8W1e;3=0}$th&hO zOO9nu16Bvi+|>1^!Hf!tE7D8agQN6#G>wh(ema~a9mtFir_=M%WMHyqho{5PRqiDd zQn^b(x$weY5x;e7@;!`w3*qvi~~`m^f7!YP@hO{Sv(8Q~lsndcYRxxZ-okTf)3 zf^$dH-txR|$nFp|mDYx^2ZY-Xm^|9a4Bz&(BF%WCNw=B0Q6==0H{)ypLbx^KQgtM&^DG658QlgMs3nxI z<*IN=c?-@TI=l^rYbeUeEBQyH#?wUA15LX8DCLIr6G)2t&{@FxUPBxCxBM|=LeH$I ryyjAK{kJ4OT!6I3^3q6R#{F|6|5eGzvY@XK%ra`i`i|@Pj=%pKZkg-- literal 0 HcmV?d00001 diff --git a/energie/__pycache__/db.cpython-39.pyc b/energie/__pycache__/db.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..907574155fc39f9263b563788a0543fa31456196 GIT binary patch literal 1736 zcmbW1O>Y}T7{_P!&1<)5Nht`bBF%wA_G0%~(E<&LON6L{>_is6tTsDiXYKW_Gc#N2 zmCXTq;D9)A1tdq}(yzcr;3Mpn6JG%#!Sk#gaN=-bR`Z`{UY=*?x(C7ur!nc#j|n%p`I2z+Yoljy+996xJM@!Qh`OE(s62tJ-USih z%L#gL<0a{t+~gLN7Pq+rrOhi4$t~RdKnEv*5+VNmBB71Z_8_a*KxTACd@?gYAu4#% zmS+@Bm?r&bP&jcq7zpW^g{4H?m*_4nkXJ;)LdhQ=Hoi#ZNKJxJsK!KSAshQ?n2klE zRig`Cjfsq&2U;{ne(qu@7MxR7vMuz6k^gBAIGrF+O$sD$7ZqAH_Ju}ZKg~cu$XtkiM zoD-Or1F&Et?fl_E5X72s!A`S?M+3n`EJj*JVWdT*6>QB=v2?<+s2s&tCsxmYsj(}m z)BLo#cf`OY6n%N`$C7OX(9?cj8i3wYV$=XCOS%!62ih3Ql|2iBN3jS zh0)|qH0`rY$Z6q7p|V&PRFt$Xr2+$r*UQj8{80j`ez{{+=umHg5W}E%sY@-V28!Pu zdJA%X|6)<~Yux`E&~G4Q{>lFU)RG@9fkYU)@N%Fg4*|={^s6;2-9?p~SFi+V0ZZ`U zrBz}HHP340KVCX81Z_oE=lI! z#7T|lt*9$eypEr|@f4_GC()3d3Jq7}G>ApQo(BouCLRn#91TZGun|mEB!E{<;_*p* z6yyz*@~J`?;6nyYXa^yBzcTn&tSp#Exd9rwsRttIu2B`ZWgs!l#5etjfj2d9! zQ1>s($nRZ2V=X|-bMToA)xF5sSYrXI4)4S zzHy2{Cj}{KN-+}j3&My~?%bf%;rIl_?k|Wz!x)>5qGo)|Yb~nT#2W983xWsK1yVP_ zG+JPeIa!hgS)yyC>B4~v#7DSMkm+Qxlz+B399hXlJ(&Gf`CRUN41GECV#&GNe|<%Yiq zq-oiv^C~j|*axctB>Y^osw?fzrCqvg**1IyJ~qFh;BDF`S_)Rafe8vo6&%*GWLkr` z36B4?ka2$N6uW5v700VCjkn%d8s3|fA(M|LIKRvE9myw9`FA(@RLe`%u!&Gf0~5&P zpq@-iQE8Q&0+ci|pD`_x%ko^;b)gbb37O?0S=Z<^pI2Q?oAKHZKw9-27$RGa?>PAO z`{RY%)W_R2#32oddvYhKCskVzfEbM`wCrk_<%hH!1yoAnokSy3WKI>ppSjL HNj>@(9r55= literal 0 HcmV?d00001 diff --git a/energie/__pycache__/mqtt_publisher.cpython-39.pyc b/energie/__pycache__/mqtt_publisher.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bf1360a8ada173f6dd34aedae78091fc58524a4 GIT binary patch literal 1392 zcmZuxPmkO*6t^8GlbxB}uC}6rszQQr=54Vpr6WU!LFpJpW$O{rxF{_4n`Fy!HwC6AxFH z2Zx8S&0QdZ2wIYgGD1nkR+N%2iD1HeNrd-;tvo@$BMbf-EXi?(^y*@8cnI6v0V2tY z3bJB?3igtKrzbePx$q%E;P$`%_VMFyHtVu5XHsWa#BL#vp&r3D9|P&Apzo26B*?Lz zqriDR$AIHK=XDHhxFC?gKX$=dt8=LxZimG-s_uJtM2nOF~Zs?tuiZdES25VifKV`D9g{qw|wN`o&=n&fCCY3ZM&E?APy9skK", + "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.") diff --git a/energie/utilitycosts.py b/energie/utilitycosts.py new file mode 100644 index 0000000..e69de29 diff --git a/qrcodegen/log.txt b/qrcodegen/log.txt new file mode 100644 index 0000000..7ff5423 --- /dev/null +++ b/qrcodegen/log.txt @@ -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 diff --git a/qrcodegen/nummers.csv b/qrcodegen/nummers.csv new file mode 100644 index 0000000..924ae00 --- /dev/null +++ b/qrcodegen/nummers.csv @@ -0,0 +1,11 @@ +positie;titel;artiest;jaar; +1;Bohemian Rhapsody;Queen;1975; +2;Roller Coaster;Danny Vera;2019; +3;Hotel California;Eagles;1977; +4;Piano Man;Billy Joel;1973; +5;Fix You;Coldplay;2005; +6;Stairway To Heaven;Led Zeppelin;1971; +7;Black;Pearl Jam;1992; +8;Avond;Boudewijn de Groot;1997; +9;Nothing Else Matters (Albumversie);Metallica;1992; +10;Love Of My Life;Queen;1975; \ No newline at end of file diff --git a/qrcodegen/nummers_met_qr.pdf b/qrcodegen/nummers_met_qr.pdf new file mode 100644 index 0000000..bbf28af --- /dev/null +++ b/qrcodegen/nummers_met_qr.pdf @@ -0,0 +1,459 @@ +%PDF-1.3 +%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com +1 0 obj +<< +/F1 2 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 370 /Length 6777 /Subtype /Image + /Type /XObject /Width 370 +>> +stream +Gb"/b0sUsA%)[,c9.+SVSaW9B8BF7QMhH9RrO8"h8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;R6(1*D0[V2Y1J`-'^!e$dMa!DVt1cqJrp10.SHM>a\l:Xtk"o^5p^/S'?=`5tU1\m"0;DM;.cQPePphk0"*(.T^3uS?#?=^@.ihj^p;U<_u])1:uILA`EE3+/LVLS,'@AP00'C"m7?K\*s2BIC\%TQPG`aY08LX=.-RHhacqSc"=\kTVQU>G/:`3`9mFc8mcHoo48(OPq00Kc;c3/htL"EEjM/e.k%B%R9ad717;fCaaEPMq;'nSk0?kEZ`]WT->)D-BLBo>4fcBc]k?aa'fYH)R:B])D-BLBo>4fcBc]k?aa'fYH)R:B]<%dpr,aNVl);G\9OE/e*\\BOj.9IaRbk#*WVNB"F;/fS'XI;gMJ1,F!N'U2',>U,RE0(V7dP7!_G\Pro.,7^l=b6F;Y+.PoVbfjqNUbIeA2UdA[*/sV%:LkBL(ZGC,/#A6pX1Gb@i6#"9R20.C.1@KiOWKO`>,lVSspU:-5A9lT:*BJ/$mERZ<)%bmCh[p#+S8=bfj^3n);FUZD$Vb>]G,Z\X)p2L+'t_l2RkEkARjU4PXbOd\C:DFM)Mjsen&@K'pM2bCq.smRY`r40@j]Eh\DAFqeWdkrL/R$\1cEEA)A#c7f.#3e,>_l2RkEkARjU4PXbOd\C:DFM)Mjsen&@K'pM2bCq.smRY`r40@j]Eh\DAFqeWdkrL/R$\1cEEA)A#c7f.#3e,>_l2RkEkARjU4PXbOd\C:BpjkIjEl.anhfnk+Nh1sbZI?_I:c(CQJGMdhYIG[QZgY)MXM1#'RLYMQOPeN^5T:O&IhgN"_hacqcg&0N[I.uHPg\6,\^7EN@S]H$4ICO^_8*Sd]r7QpSpYCVZpO/:?S^!dtaEQNi\:*7)l.anhfnk+Nh1sbZI?_I:c(CQJGMf]qbjUE^jl4k)9$&IP5N5SO\94uQo#^fj\5Y(uh5ccf1B/ROQ&Al@.OC_2s+j>0imte8aX?[Ei_m=sFg"^6ao%40<%`(EX-@ues!'.]Md,p*-(iE]LeHjhf"F\t-hBR^;Rs;[YEFb.gUQp)2O1T"oeL04l`6]NSVYJ>Xa;pjZIeb)B7oNNT_+e2bLN_A23kK!oeL04l`6]NSVYJ>Xa;pjZIeb)B7oNNT_+e2bLN_A23kK!oeL04l`6]NSVYJ>Xa;pjZIeb)B7oNNT_+e2bLN_A23kK!oeL04l`6]NSVYJ>Xa;pjZIeb)B7oNNT_+e2bLN_A23kK!oeL04l`6]NSVYJ>Xa;pjZIeb)B7oNNT_.&qi[XO,RFcY..qDuDj]TFM.+?0phjIh=EKtg'bj2l'/%?L/gA'elo4obW19ZdbI@"RM=//G.hgB'c%E2?0'f)u]nuUbaP\K0In'%u/\B;HNk4N#O'jQ_RD1(rGq)&jfR9.JB^<j]S$CTLiGuelAb-^]`GNRLR>\.4boD1)c$Ecjd2ST"4>lKdnP+1S$bO&;9G,=213EO@0,o*n5+,WX-QFefZ=*`JaMOW^4.=7=ZgtmH--Fm:I^>*&VAQL@!Q?/nhqG9;]6DO.mf[o[k.Q&;'m>Ub#b<8?>Y-mlK-m_7I_j^hLCDO,@?lr?r'HhX5$jCYt"EdO(:cBY\:8]C)QLLQF9jmL=ZP>bS*]_mc@\nHPIl>G&csi]EspMU<1WmS1AK\_SjYNmX9^HiA+1cRCSZ*`U-ZmR+]HlDt#)Yk(tE@pN!FH]a>E/]=@SEhLs'7d:DBGc5!fi@:EgbpV+J5E1&)BbhUCPiq=iqbj=?G2g>ugo0ee1HT>0_?A46(?/5;]Do-(WBJOW^kDJ#p0JTnlHf*aU\BGU\B("bcEI/EIAp/1_)nZKDH(rF)]ppTjYJS6%0(+.?B:`Bfb&$.-F`HPl5&T8m`b!S@L!3#h=Vk`-kMu:7CKr2l=3,4=RWWW4I6MQ+^W'j5FbS%/Y#uQRhWNqmrG,Xb?!#*`jI142k']F2hnR0BdWL8f7]ol1BAVh3pAJc3-=']oCuh%-ETV[5DVr4KA^Y^TbXSa<(MDUfj7`Fe>UE`XaDBq+F`HPl5&T8m`b!S@L!3#h=Vk`-kMu:7CGJ%c^?d&MdFia;X8BL%Eg\;LGZNa#AbK-FCioBArT_,"]j0]V.%V0`<1/.L1)ld4B>;$]CG2$",)U":/lcLjL!3#!hq>4$le9NnSquB/JP29]=!=otfm!.jqmuKAR4"_RipkkSe\b7R=,f*)kDCi^UgUa0:-r.MJ!)FGuhX0QOPQIAm^SMqC6GBp3B]Q/!g\tGuhX0QOPQIAm^SMqC6GBp3B]Q/!g\tGuhX0QOPQIAm^SMqC6GB\18s\F*kINM)MlW/gb1R_7AR>/*1>[0/ku?#.N4'`69hn=58"r]Zfd)AgcQ^aOZKA5^$@V-FH/+1#I?KUs%ZP.1pHX0=hm"0;tI94S7gg4lAU()"DMi!j-E3?_>8pQiZbKWeh_ctTMB4#Vt\"R>Mhck&fIq(>@QORj$`9SqAg\2Igk20:c_.A8R47W/>k4ZIlQh`R@F)]gjp.i97P.?!Z_.EV*eJqgP\@iQZY@MP.>B$![@l4LX%;tXQlRSCXpD.#rhcS.>AV8`!nbPl?p:UWrq);>qE7s1C:;"<(\UAD4Ac@\>**bD3]b@TQA8^:/E1)2c2$]HWDXYO0/",/d(0p"Z=P\Po7be0X7U.E\Y:Hr[YOXH8l8?(%-tG6q;K7%hR9b6+Qap)!_-K>MZOrO]g[!#KlDqSAV0WU=$4<2\\5_6i=(YN@j^g'pCN*?OaDAekiqBr=]D=!1F/`Ju]7N@],;'_?f9P%>=*^3,obuU#'J^VIWOTXEbc\\&b?V(!i3&`7fn`;?m<)u`oi5>\;S<9Y"bk/i>UjWp/$l:[nhQQs27P.bA2`FFEI1uZ?%&!)\Q%\jh"suehO!!?iNhEWZnF5%l8=h>`JGgk#/n4Xmb?C7]sEE/fUr_q_E5l?K9U\EdAt^?4N;2=/123\Q#bPlT7,?O_ZV*bD*(F^pQc!CPeS&<%0%FamqG'e(jP%f9ZJfQ*0/Js[2/[giV`/Y`j`&ml2;4Cb99fhH8pQgt2:4m*,As[mY-7Ee7(=`q06=*>g;n*Km<-8<0%E?/R>n_i0"AX/jb[qX:gW9^9sg&9Lth6Il`7Z\&ag"Kb?V'(2DQ4kHX1T=Q'Wl8>sWb(9/Y*dAg;2s$a-](njKu<-nfY@-JHOW`9h]`oi5U*<"-1eja,m!H1b0/\K$="fW.OF]#]d3^IqT@"=ZO0q^="5Nf^:bfnmeY)Lhm3Yp,?H?@o=hma!Ufg,bMTW5>U*<"-1eja,m!H1b0/\K$="fW.OF]#]d3^IqT@"=ZO0q^="5Nf^:bfnmeY)Lhm3Yp,?H?@o=hma!Ufg,bMTW5>U*<"-1eja,m!H1b0/\K$="fW.OF]#]p[[Q;GuFhE$dAJIP$g8o00Z7\DV1B-8T"X/X7jX5.E,Qi,!?Z1LGS_Pq2Gi\1>)ICiC^W?[aSl/+Ys7/%FMRE$dAJIP$g8o00Z7\DV1B-8T"X/X7jX5.E,Qi,!?Z1LGS_Pq2Gi\1>)ICiC^W?[aSl/+Ys7/%FMRE$dAJIP$g8o00Z7\DV1B-8T"X/X7jX4so!&?2o7pT1KMoUO@JFR:nt0bg#Q3al.!ADf6s9D((g_fmP2->`JIEc"*OX,r7KlcQn:HdD!;^9XLL(k%=@UjD[!1[l9M-2O)G@lopUQY"&<^AhqbgP*qfqB9L0_B]%Z?-JD9OF0ka;Ek!%TgRH;Q)bO^[G+V>9=/11j1RVoDair#t1WeTjZu9k0MN+qql%I:)YM!mfFhtV7%F2:HB'.]]\GEep>eL@)=WoTmCGoFG?#ZrQKCc1+RJA]ZY.cMJ=0KGc*(S%PXi&iN#Aq&!ZX02oVeSB,q'f%S[B#E0N"m>/.36qr"R:!;ZlKa%Cobs?5L!7cEfl6V,_7=k$cEfiLk1=FZqRYTi]a!Y&J]l7piq@E!b0(d-^75Wb<@6LL\LT)7>U+,>2ljU8iTeV>i)4.'h3Ya*1_VLQjJhkM,iq@E!b0(d-^75Wb<@6LL\LT)7>U+,>2ljU8iTeV>i)4.'h3Ya*1982Ji_j@Uc-4DJ29J=liOXUrX2$UP;J#:@bfmm\b!`Mn\q2]<^bt+LAa[gebeZ0Z7'Y^^JhjCB2<%MFm`iu"_)9A'Z/N9G1982Ji_j@Uc-4DJ29J=liOXUrX2$UP;J#:@bfmm\b!`Mn\q2]<^bt+LAa[gebeZ0Z7'Y^^JhjCB2<%MFm`iu"_)9A'Z/N9G1982Ji_j@Uc-4DJ29J=liOXUrX2$UP;J#:@bfmm\b%.4U,r>:4E%`^iWAl[1\mF766lVQO#5>ETP):KjYNt:u9XKrXD/_-Y.&f0]qmp+ja$^l7hZih\Js)!9Z`i]VI8%#1q/(((.k%AV1AeXhhT&#'RGS5CZ@\6s,I=T>jaBh0Onij50.SHM3&:BB?J`^j)k#RfpTfC6MBfI2lfEOg[#t0$E%`^iWAl[1\mF766lVQO#5>ETP):KjYNt:u.'>=&qI[WoHX1uup`k?_rT_-KNj_FEN>??rUJ9L7p4l,qQ?Ha^*V55Q8mm[8[T2q+%mdJ%H_rI+AsmbKZ&V/N4]=/56;]))$MZZYs8g"3`XcP2VXRBBeS^1Zcsn$B3;n"(sVI5>0>)L]r_e_b"Hj5)pt;#4e8E]*]bXfK4_^;dj5VP*S+FQI904^2!;T.Qn(;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PYt+&V3FkM.A`8,8h$U$;G&C7PVi@A0@"u&~>endstream +endobj +4 0 obj +<< +/Contents 27 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Contents 28 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +6 0 obj +<< +/Contents 29 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +7 0 obj +<< +/Contents 30 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/Contents 31 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +9 0 obj +<< +/Contents 32 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +10 0 obj +<< +/Contents 33 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +11 0 obj +<< +/Contents 34 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +12 0 obj +<< +/Contents 35 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +13 0 obj +<< +/Contents 36 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +14 0 obj +<< +/Contents 37 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +15 0 obj +<< +/Contents 38 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +16 0 obj +<< +/Contents 39 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +17 0 obj +<< +/Contents 40 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +18 0 obj +<< +/Contents 41 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +19 0 obj +<< +/Contents 42 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +20 0 obj +<< +/Contents 43 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +21 0 obj +<< +/Contents 44 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +22 0 obj +<< +/Contents 45 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.efd6002a9b88bceedc48b31a13f9ac3c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +23 0 obj +<< +/Contents 46 0 R /MediaBox [ 0 0 370 470 ] /Parent 26 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +24 0 obj +<< +/PageMode /UseNone /Pages 26 0 R /Type /Catalog +>> +endobj +25 0 obj +<< +/Author (anonymous) /CreationDate (D:20250130081455+00'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20250130081455+00'00') /Producer (ReportLab PDF Library - www.reportlab.com) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +26 0 obj +<< +/Count 20 /Kids [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R + 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R ] /Type /Pages +>> +endobj +27 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +28 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 254 +>> +stream +Gas2AYti1j'LhbF`>rXt4-/#clE(f'e1(Q"7L1n2\?[QgUVmT&l\PlL67TRp)m8$XQ]R3#n(Y4b>(^`"F:3Ku#pu<\D"R(dSphA!$6+/%ChT6#dr-)q`NVjA-TC`0R=X1hWD0<\Dj^jh]g0f=U&i4M.MXj"EQ-?fK1(5Q~>endstream +endobj +29 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +30 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 250 +>> +stream +Gas2@4`A1k(kd*^`Jn^>4)]^bmB-"j-m&P;KiBr\1,L/Z,MNV)oi)AfV$_=m%B&AL6Lb`cmCEPb%/EYk'"eHD`>#u')c(A!n8f2q`endstream +endobj +31 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +32 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 246 +>> +stream +Gas2@9oHkB(khAWME1+m+mGKg1H2EN&W_K+LXKqG+":osq!Y]U*Yq$ZR"8\;+[iBpme$SH2#)Ve!kJQS/;??`d[N0YrEW6i1.aL3#^X;,ed'sR6(p'#^6q3ZJ"XQJ),W(:O?=(k6dmP*h&7\9Dd2BpCL3Z*b0gF`JY%hj$<1QhMfls+(9_^bTC(8[>XOlj`OVJ,Oj0't[_8s"%6Sl6GF\S285V"oS#l>6pYjURa#sBGO3K>&E)0.~>endstream +endobj +33 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +34 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 245 +>> +stream +Gas3+98gY@'F!F.^Z$fp7FGrj9=+6#-pD5T&_r(<0ss_J)hcM?+13^*7(I7PQh)4aE'JY\endstream +endobj +35 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +36 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 241 +>> +stream +Gas2@6&WT6(e!ilME/6hP'h+FBl:)\M"KUl<'5oh@VfgPRg&cb.pEO9-j+N-jun=+L]4o!JSu!h&L`145S8pIF/4-Q@Aem4,%=u2\U\QP?1d!>K3)0dk1DQRp'#l!T"RAq9+o;XdajbE6>!D\\DK5uN*.\DNl9Y=?6KkH^@S[5`1ZXkgInYfo"H%WkH6@t~>endstream +endobj +37 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +38 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 256 +>> +stream +Gas2@9hWAh'Lhcp'mFB(R)s-XRb:]G8;Vrn;!Mp8?g62i)3_nbIr@%o6rYeo`T<[Y8C!pTFq&E4cmo1B_-9a512r4gT)>qDQ\+@bWlb`]!cDt^m%]mj)V">0B*gj6^7Bj__6]aZ'e2AIo9!Q*j^@QhXm_>E"R^4Rp0IZlo0O0PdLfeigD`shddQXY.R<%%p?g^E(Ln;@em4A<%AA]/HW%"^FR6-alhp$39qL4m7jfGd)u#^ODsuI?2^TUKAPt#~>endstream +endobj +39 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +40 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 235 +>> +stream +Gas2AYmu@>(kd+*i^S'(q14->P=,Vli6W;])Zm92Kg6I2?6Tm!"EVZoQga>JhCNu"Bn967B*@e!C/DurOJ!lh=6Q$.,Qo]@@@iIsh78!!S9KDSA7OLW;bsL]/9f#0b/I@>U:-808=Z%&lohl>k4UZnbmRn%9Tf~>endstream +endobj +41 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +42 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 249 +>> +stream +Gas2@4`A1k(kd*^`Jq6WSJO@phLYl0D'n=t0hMj3&3D.T&c]Q[@6dAVPAW?CcOloOd'`0TQtu1;JS*85OCT!8`'R7+lu]i^,*jSodKmG'[-3-"6Vs0IT>mDghOuZ+p"kH_kWSS8UE9jjAr&r_gL-$K`NOL11Ho5=$o.=F`q`SE[(IjS)j"9~>endstream +endobj +43 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +44 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 274 +>> +stream +Gas2@9htgF(khAUME-9DG.Y2p[Pp`";&7'C-c;d^71.k3CgT(!H\q()(bq4J-Bjif+M10lH//H.$bd5b3U?c51'q/nBd`rQiYs%O`jA4'_)@\+#7S,J<[WaLOtm>-+.DD2W*k?$6'WOi4@>Z[U^)]X_2A2s%(hm1aU'Fl4dmM\iQX3Y%Bnl!01qtlUXZs?bEhEJ'6J)E'W"BeW(q#~>endstream +endobj +45 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 129 +>> +stream +Gap(;.h7(`&-`$(igg'mB`lendstream +endobj +46 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 252 +>> +stream +Gas2A92!/f'SZ;['k_5dbiCZ(B8-;$QB,_m=i=qGE>WWhmhA9#)/oY.>IGNt0JX,4urR%_hk!/2EKN:-0U=U7<3No-no8EBh6o;U1qYr+;'djOr3Wi)l+6oW=>E!FfZu0'H-0g:PVlSPDA+7q^R'p997aBR4_7`~>endstream +endobj +xref +0 47 +0000000000 65535 f +0000000073 00000 n +0000000104 00000 n +0000000211 00000 n +0000007179 00000 n +0000007437 00000 n +0000007632 00000 n +0000007890 00000 n +0000008085 00000 n +0000008343 00000 n +0000008538 00000 n +0000008797 00000 n +0000008993 00000 n +0000009252 00000 n +0000009448 00000 n +0000009707 00000 n +0000009903 00000 n +0000010162 00000 n +0000010358 00000 n +0000010617 00000 n +0000010813 00000 n +0000011072 00000 n +0000011268 00000 n +0000011527 00000 n +0000011723 00000 n +0000011793 00000 n +0000012090 00000 n +0000012282 00000 n +0000012502 00000 n +0000012847 00000 n +0000013067 00000 n +0000013408 00000 n +0000013628 00000 n +0000013965 00000 n +0000014185 00000 n +0000014521 00000 n +0000014741 00000 n +0000015073 00000 n +0000015293 00000 n +0000015640 00000 n +0000015860 00000 n +0000016186 00000 n +0000016406 00000 n +0000016746 00000 n +0000016966 00000 n +0000017331 00000 n +0000017551 00000 n +trailer +<< +/ID +[] +% ReportLab generated PDF document -- digest (http://www.reportlab.com) + +/Info 25 0 R +/Root 24 0 R +/Size 47 +>> +startxref +17894 +%%EOF diff --git a/qrcodegen/qrcodegen-spotify.py b/qrcodegen/qrcodegen-spotify.py new file mode 100644 index 0000000..8ecba3c --- /dev/null +++ b/qrcodegen/qrcodegen-spotify.py @@ -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) diff --git a/qrcodegen/qrcodegen-youtube.py b/qrcodegen/qrcodegen-youtube.py new file mode 100644 index 0000000..303e712 --- /dev/null +++ b/qrcodegen/qrcodegen-youtube.py @@ -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) diff --git a/qrcodegen/qrcodegen.py b/qrcodegen/qrcodegen.py new file mode 100644 index 0000000..a1397bb --- /dev/null +++ b/qrcodegen/qrcodegen.py @@ -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 ") + log_message(" moet 'spotify' of 'youtube' zijn.") + print("Gebruik: python scriptnaam.py ") + 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) diff --git a/qrcodegen/requirements (conflicted copy 2025-01-30 090325).txt b/qrcodegen/requirements (conflicted copy 2025-01-30 090325).txt new file mode 100644 index 0000000..958b3c4 --- /dev/null +++ b/qrcodegen/requirements (conflicted copy 2025-01-30 090325).txt @@ -0,0 +1,6 @@ +pandas +qrcode[pil] +Pillow +reportlab +requests +python-dotenv diff --git a/qrcodegen/requirements.txt b/qrcodegen/requirements.txt new file mode 100644 index 0000000..5d56fdd --- /dev/null +++ b/qrcodegen/requirements.txt @@ -0,0 +1,2 @@ +pandas +matplotlib diff --git a/reistijd/requirements.txt b/reistijd/requirements.txt new file mode 100644 index 0000000..c96b2df --- /dev/null +++ b/reistijd/requirements.txt @@ -0,0 +1,3 @@ +requests +mysql-connector-python +python-dotenv \ No newline at end of file diff --git a/reistijd/traveltime.py b/reistijd/traveltime.py new file mode 100644 index 0000000..17619f1 --- /dev/null +++ b/reistijd/traveltime.py @@ -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() diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..60126c1 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,2 @@ +flask +numpy diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..4d0818d --- /dev/null +++ b/test/test.py @@ -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) diff --git a/timelapse/timelapse.script.sh b/timelapse/timelapse.script.sh new file mode 100644 index 0000000..dde2baa --- /dev/null +++ b/timelapse/timelapse.script.sh @@ -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 + + diff --git a/weather forecast/__pycache__/database.cpython-39.pyc b/weather forecast/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08604479e9f29412e0b92e5936c66958473d9b7c GIT binary patch literal 3563 zcmbtW&5zqe6d!-&W7BO)p%jV=8YDg%Y1yqRDnWpXu91)S79KCC@$(Na93S4R{N@gh(`2pP+ubjQiQusN~8?; z0#UlCw)7`hp>|D43I=07v&C|=ONWWrd>#J$8yKR^0PQRLY7E<4LNKaw)a0nkv2Y2I z;&+8ZHCB(b$B1pj$`Ne&SW!@{?w~H(LEZ;C3JTMQ3Odvj6k*UPl5(v5fF7w*Py1Tg zL3a>YI@BI&UM0O>MOWbsxLk>Tu3)&fEbDH74=CGpJmPpmJf;CA6mLztU$ovMR$%Q~ zoUTgU`ov<^7~8(L=M3w3UEV`x1rEgrzR&PX{dl~!GVv1(eb{k(VOx_@%@qG(wUyf6DZ0Y)p5I4N($pCzvuRs zvW0w>l(K2E?C`;S{iHzXZa8EDAW4A=u+;RV2y#eiQcN?HR5E?X_Y#HvgxGmd|GT{g z;oapE3;bT2(15bF4c`vO)C>694v5w!%sH?Ex^{QG7x=!**X~mbqG5Q?XVkWMur^E0 z>SUT+uo;Nyfb7ovgZ)IB_+=PSNmYu+zkiCQvQmLxRe3>SFM<4YkoQ3vT_y^^7aaj$ zB^;F_g~(Y3U3F^apsNw>DbUq3=*qi z#hyRJo&(6w?X;3_0&YvZ5O26M^$AZj1Yr_uX6Pn4cO zDKLn`F;pFy;w#xE7}$?KaCpFLtI@@jY@|q%jIa?5qbDW$fZ8Dx{TZk($scSPW|BQ5 zfLRqD`!8liEq5E-3n`nj7lehNkaTzvX31*{dd}X!^MgU^hp2uo7DGi<)vKc9Wzk=R zH@0k}LIxK!QY5^Qo$WW6MBhTuS7gywVr5Tx42=_>l*j1sOnio!L_J)V(&bo<9ZeL4&D}4J`)9pGv-&laId# z1a*Jv5*reNgF&i2+x;@-JwbPOyQ_!Im#&vC%-^CiSGy<38HPM9y~bPW^d z30f?;GB?RnaVD7`lH7X*OI}T7Co-R@WGOv<5`-vB7CbpEC=p3?xi^6x3luL2L!4v= zbKq*6D67%03u`soU87@)ezrZ2<+$xZ;#TrOKQ&%>IS)0Bas{6V5MHHlkrN23w&2F? zMyqFZ@fDHM)BiaVm$AOugpR>PupYgik288ZC2L2@MG_$RP$Wjo#W#rf?81>M_`AR^ z06+H2do-{|zITGf&#{+5o)ptkPjmr-q?FZaQaKJj(Zvxb=T8Kml(OKHVp1kY#|Z)6%i8)TNV;~R)LKLpqLTQHz1Gz_h(lvQ2T z3(HF7-=ca!)Ae+AQLQQ$74|0R)r$Rd@{4&j`_lCpE5rM=uZgcqCc2H(&2=u>!({s^ Ye?0a{=+YZN(TEGvva+m|Un#%-A0J}o8UO$Q literal 0 HcmV?d00001 diff --git a/weather forecast/__pycache__/logger.cpython-39.pyc b/weather forecast/__pycache__/logger.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bac04f20cc539aa3c881230561228f705076439 GIT binary patch literal 561 zcmYjP%}xR_5N^9aEE`cDzylWyd(eFWV~nD5AY6zs;X;~iS&;opTOgX{=4+V1(O2r# zlMmp!x(GcqR3 zV)ip05{3sTaNa?I`cFZtO=YB}DGy}xq}ggGe8H29{>3SmlE#pYOz~fH0hss-mh6I4 zXpKv>!7Hn@)}+K6j16aP(8@0D9mXyRs2t#&(lIK~yQRHF&=~FqHC;>cIG$#)uE$*U z0CS-|3D7jB8v`XcO}jK>2_)(_xk#x}0QW%iANiEAA5q|Hj~2zq3^uGo^-(pa_6;0Y z=e27tg(4)H==zp}ny*Og!+f8;aG8slENH}KSa8UMojjVSJX12fhg4XI>4GXA&eE~U z^F)SAPSu2qCe-2)l`7m`a9TW8jn0eFmaxqu9)d%}$3F3}Z<*c4!jOR_2yEKvTwmVZ aR#er|jyo`U?<~#PJmKf?F_1JzJnI`dOqbRG literal 0 HcmV?d00001 diff --git a/weather forecast/__pycache__/weather.cpython-39.pyc b/weather forecast/__pycache__/weather.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc31fe2f05ee687a436a47352d1cdaef5ca5e9c4 GIT binary patch literal 3115 zcmaJ@O>f-B8Rn2&lKUZ9Uyfy`cG(XXT0qvS26|ZpwFB2d6EsR-xe&_Bg5u1sxZ;w` z%&Z+PxM=~U$6O1vjsodYAV5#O73eQvF9CYexu+T})MtjfisicHg2VZEXXc%Ep67iw zTv%urc$Rgt<%QmB=BgHw$M|j)~?ZBJjc35o0P3zWKk&NEPX%iXJ}huobKT3 z(NHoOk65H!nLjQn%JD~d#cxnhGPMd)z$t2dX-)0PeQ69yVHGyDzc8n@qIPPY8N?{e zQ;XIL^1`6biFJm9V8lK%_KaP_HZZ4lG$F>+A;#nmY#r(r&I{wjRF-?juMFxQL zw;Fod#~}|#Okzq`x9|C39`_E}QP+R>j>E%8$wwec;~Aq^IKSVHGn$1%ww@yH5kkR|;1 zGDb`9NjwzK~NnsDo!Z@|g z3~3K*pqZs;Mr$`gAAB8rUFj5#bdOE`RC=fN!Z{zcR?MM>H$T$HVox+XDJ>YJe9!sJ8)^OA-WTtk)*LLh^szHX(IaowQ^ zsR+d|vd?lb7eN2z(?`CHhg6wY_snJ-eT^bWP#i$o1T1mW(%;x;G3L+nm7nboStJE` zrq*n4RfJi3Jm%m@=iQM21ZX%K{NQi@{A_4{@Nn?g+REC4_m&^F&C*t@l#ZMzWR~u1 ztJ0~q`kL_TxRQze9-N=pYKO@}*Waxi@{=5Uoo125Ybj6Vag z*U&8O{76npYb>L(5sP_VKnj+eD^QdReV&bCkpYOKP?j!dc>)}lHOltKgSHp6Fjrwv zXX;>K&W~P-q!zeQHcll!;tR@wmU5tugY;BZ7yK>Nc|#S7CRG6?!loc=@6zrry2STU z7_LK{3)^-`QZ6=#MzbXeaCt{U5DEyv z(3Z8KLkVzc0qejwM&Hq+_LWgviOfxdS{mn2Jk%BDUGxL<)Wz$e?Lk=FHzBt0ASj+j zEd=V&5WfSn>_eM>Nf)N|w2PK?Xo^4SUX%ZsE~58`s&^fJsq$@O+Mw5p23=Y==<=$u zX3&)z#dJairz^+iu{o{N)xw3vD(ss@y=cO2b8y+uLoGFaA$Zg$jC=I`}$JT-u{BPD@*|Q64x^l8q^3EliTqok;=5 zeHmqwG7<4uE7+ulKy>|yx9LB{G}IcK{v$mV_14`@f3Fhlu0NPFX7GpKtLHBd&i{3B zaWU)u=E1qjA?Ip`bBbcpLXPpPTg6f&h4tva#^U;^jv`)I z{%XWXldSh&rdmz6;Foc2p0rw|MV2g^+(JrQA`Ud#v6jp=bIER5E%dLegmxb@+7@5I zhp(d8X*U7~f2Met=&T+zs>T+Mp)-6HJRMM_h($ literal 0 HcmV?d00001 diff --git a/weather forecast/database.py b/weather forecast/database.py new file mode 100644 index 0000000..c6b52e2 --- /dev/null +++ b/weather forecast/database.py @@ -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 diff --git a/weather forecast/getforecast.py b/weather forecast/getforecast.py new file mode 100644 index 0000000..3ee60ba --- /dev/null +++ b/weather forecast/getforecast.py @@ -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.") diff --git a/weather forecast/logger.py b/weather forecast/logger.py new file mode 100644 index 0000000..d9a379b --- /dev/null +++ b/weather forecast/logger.py @@ -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 diff --git a/weather forecast/main.py b/weather forecast/main.py new file mode 100644 index 0000000..3e5d2e1 --- /dev/null +++ b/weather forecast/main.py @@ -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() diff --git a/weather forecast/requirements.txt b/weather forecast/requirements.txt new file mode 100644 index 0000000..a475f22 --- /dev/null +++ b/weather forecast/requirements.txt @@ -0,0 +1,4 @@ +astral +requests +python-dotenv +mysql-connector-python diff --git a/weather forecast/weather.py b/weather forecast/weather.py new file mode 100644 index 0000000..f4256d8 --- /dev/null +++ b/weather forecast/weather.py @@ -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) diff --git a/weer_app/main.py b/weer_app/main.py new file mode 100644 index 0000000..8bbc788 --- /dev/null +++ b/weer_app/main.py @@ -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) diff --git a/weer_app/modules/__pycache__/database.cpython-39.pyc b/weer_app/modules/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8afb2256f5414d70e40d74aa8a293a438affbcbd GIT binary patch literal 1555 zcmZ`(&1>UE6rUMMj$#s;E?sCB3Oe)f$ z8@Uv^^w!=>X-S}${!6;{)PJF;z9)agUb-XZ>CL>adB1t{~C6Z z4ht7gaheB66jMBALw?MZP}W&XsZ}mDDjpJ zEoCVi|F&{e4gZeXK~HY!c4RJJNdo~zUk&2PTN|f2KvM96`K%B~(JNZ=UPF|%AWcS5 zSk~e+3_&-n(l!u}G?{D6kp0DU4S)akTGy$*FqsU%v@*y+w>oJs8AFnr))|UgnU1bx z4y}vvC{NSaw5|cPFSD#QPSqqvGqpTuGnywke3G5X!FyX}G3IT~~iCwb8Dq#?lJp|_>Bv^aWke3-)+1IT=$ zq0*LDp-+47w=iiewU0)lRF7p6+}Tx<_Zwd0b^9g*Q0qu{TmNr%e0uWjH-7u1*FG8? zcAoVI&w7~NHOTxmz6k{;&{0NcEHb@Yo2`7mJUw~7w!&jWAWgmvNfpWa;^iH7TOLkM zJKa+f6Pn&pzrB_=oT{3z15C7`;)FL4OmY~yBgli-GLB0ph*JZoNadsViBKmgoy!U6 zX=$TR`A&tF2O;GCI#E=(hZcqi6E3&8E%vxwuX7i_*(d9web=hjs8NeZ8sGjKR#o%M4w^a!@ VoK!x^Q|W%bIK>lcNrrs5)_)Drb|3%% literal 0 HcmV?d00001 diff --git a/weer_app/modules/database.py b/weer_app/modules/database.py new file mode 100644 index 0000000..230bcba --- /dev/null +++ b/weer_app/modules/database.py @@ -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 diff --git a/weer_app/static/style.css b/weer_app/static/style.css new file mode 100644 index 0000000..aa23579 --- /dev/null +++ b/weer_app/static/style.css @@ -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; +} diff --git a/weer_app/templates/index.html b/weer_app/templates/index.html new file mode 100644 index 0000000..67b7ccf --- /dev/null +++ b/weer_app/templates/index.html @@ -0,0 +1,47 @@ + + + + + + Weervoorspelling + + + +

Weervoorspelling per dag

+ +
+ {% for day in weather_data %} +
+

{{ day.dag_van_de_week }} - {{ day.date }}

+

Gemiddelde temperatuur: {{ '%.1f' % day.avg_temp }} °C

+

Weersomstandigheden: {{ day.descriptions }}

+

Windkracht: {{ day.windkracht_tekst }} (Windkracht {{ day.windkracht_num }})

+ + +
+ {% if day.avg_solar <= 20 %} + 🌑 + {% elif day.avg_solar <= 50 %} + 🌒 + {% elif day.avg_solar <= 75 %} + 🌞🌞 + {% else %} + 🌞🌞🌞 + {% endif %} +
+
+ {% endfor %} +
+ + + diff --git a/xml tagger/xmltag.py b/xml tagger/xmltag.py new file mode 100644 index 0000000..6a0414e --- /dev/null +++ b/xml tagger/xmltag.py @@ -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()