According to OpenAPI documentation this is a sample code to generate Speech from Text:
from pathlib import Path
from openai import OpenAI
client = OpenAI()
speech_file_path = Path(__file__).parent / "speech.mp3"
response = client.audio.speech.create(
model="tts-1",
voice="alloy",
input="Today is a wonderful day to build something people love!"
)
response.stream_to_file(speech_file_path)
To execute this sample I have to install openai first:
pip install openai
To play the mp3-file I have to install ffmpeg first:
sudo apt install ffmpeg
Create mp3 and play it:
# run sample code
python sample.py
# play soundfile
ffplay speech.mp3
Play MP3 with Python
Install pygame:
pip install pygame
from pathlib import Path
import pygame
def play_mp3(file_path):
pygame.mixer.init()
pygame.mixer.music.load(file_path)
pygame.mixer.music.play()
# Keep the program running while the music plays
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
# Usage
speech_file_path = Path(__file__).parent / "speech.mp3"
play_mp3(speech_file_path)
python playmp3.py
Read Heise Article
from dotenv import load_dotenv
from pathlib import Path
from openai import OpenAI
import selenium.webdriver as webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import pygame
def scrape_website(website):
print("Launching chrome browser...")
service = Service()
options = Options()
options.headless = True # Headless-Modus aktivieren, um den Browser unsichtbar zu machen
driver = webdriver.Chrome(service=service, options=options)
try:
driver.get(website)
print("Page loaded...")
html = driver.page_source
return html
finally:
driver.quit()
def split_dom_content(dom_content, max_length=6000):
return [
dom_content[i : i + max_length] for i in range(0, len(dom_content), max_length)
]
def scrape_heise_website(website):
html = scrape_website(website)
# BeautifulSoup zum Parsen des HTML-Codes verwenden
soup = BeautifulSoup(html, 'html.parser')
# Artikel-Header und -Inhalt extrahieren
# Der Header ist oft in einem <h1>-Tag zu finden
header_title = soup.find('h1', {'class': 'a-article-header__title'}).get_text().strip()
header_lead = soup.find('p', {'class': 'a-article-header__lead'}).get_text().strip()
# Der eigentliche Artikelinhalt befindet sich oft in einem <div>-Tag mit der Klasse 'article-content'
article_div = soup.find('div', {'class': 'article-content'})
paragraphs = article_div.find_all('p') if article_div else []
# 'redakteurskuerzel' entfernen
for para in paragraphs:
spans_to_remove = para.find_all('span', {'class': 'redakteurskuerzel'})
for span in spans_to_remove:
span.decompose() # Entfernt den Tag vollständig aus dem Baum
article_content = "\n".join([para.get_text().strip() for para in paragraphs])
return article_content
# Header und Artikelinhalt ausgeben
#result = "Header Title:" + header_title + "\nHeader Lead:" + header_lead + "\nContent:" + article_content
#return result
def article_to_mp3(article_content):
client = OpenAI()
speech_file_path = Path(__file__).parent / "speech.mp3"
response = client.audio.speech.create(
model="tts-1",
voice="alloy",
input=article_content
)
response.stream_to_file(speech_file_path)
def play_mp3():
speech_file_path = Path(__file__).parent / "speech.mp3"
pygame.mixer.init()
pygame.mixer.music.load(speech_file_path)
pygame.mixer.music.play()
# Keep the program running while the music plays
while pygame.mixer.music.get_busy():
pygame.time.Clock().tick(10)
# .env-Datei laden#
load_dotenv()
article_content = scrape_heise_website("https://www.heise.de/news/Streit-ueber-Kosten-Meta-kappt-Leitungen-zur-Telekom-9953162.html")
article_to_mp3(article_content)
play_mp3()
Open WSL Terminal, switch into the folder for my Web Scraping project and create a virtual environment first.
# Python virtual Environment
## Install
### On Debian/Ubuntu systems, you need to install the python3-venv package
sudo apt install python3.10-venv -y
python3 -m venv ai
## Activate
source ai/bin/activate
Visual Code
Open Visual Code IDE with code .
Change Python Interpreter to the one of virtual Environment
When working in a Terminal window in VS then the virtual environment has also to be activated in this terminal window: source ai/bin/activate
Requirements
I put all required external libraries and their specific versions my project relies on in a seperate file: requirements.txt. In Python projects this is considered best practice.
selenium
Installation:
pip install -r requirements.txt
Selenium
Selenium is a powerful automation tool for web browsers. It allows you to control web browsers programmatically, simulating user interactions like clicking buttons, filling out forms, and navigating between pages. This makes it ideal for tasks such as web testing, web scraping, and browser automation.
As of Selenium 4.6, Selenium downloads the correct driver for you. You shouldn’t need to do anything. If you are using the latest version of Selenium and you are getting an error, please turn on logging and file a bug report with that information. Quelle
So installation of Google Chrome and Google Chrome Webdriver is not required anymore. But I had to install some additional libraries on WSL:
sudo apt install libnss3 libgbm1 libasound2
Exkurs: Google Chrome
To find missing libraries I downloaded Google Chrome and tried to start it until all missing libraries were installed.
## Google Chrome
wget https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/linux64/chrome-linux64.zip
unzip chrome-linux64.zip
mv chrome-linux64 chrome
## Google Chrome Webdriver
wget https://storage.googleapis.com/chrome-for-testing-public/129.0.6668.70/linux64/chromedriver-linux64.zip
unzip chromedriver-linux64.zip
mv chromedriver-linux64 chromedriver
cp chromedriver/chromedriver chrome/chromedriver
cd chrome
./chromedriver
Scrape a single page
import selenium.webdriver as webdriver
from selenium.webdriver.chrome.service import Service
def scrape_website(website):
print("Launching chrome browser...")
driver= webdriver.Chrome()
try:
driver.get(website)
print("Page loaded...")
html = driver.page_source
return html
finally:
driver.quit()
print(scrape_website("https://www.selenium.dev/"))
python scrape.py
Scape a Heise News Article
Extract Article Header, Article Lead and Article itself:
import selenium.webdriver as webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
def scrape_website(website):
print("Launching chrome browser...")
service = Service()
options = Options()
options.headless = True # Headless-Modus aktivieren, um den Browser unsichtbar zu machen
driver = webdriver.Chrome(service=service, options=options)
try:
driver.get(website)
print("Page loaded...")
html = driver.page_source
return html
finally:
driver.quit()
def split_dom_content(dom_content, max_length=6000):
return [
dom_content[i : i + max_length] for i in range(0, len(dom_content), max_length)
]
def scrape_heise_website(website):
html = scrape_website(website)
# BeautifulSoup zum Parsen des HTML-Codes verwenden
soup = BeautifulSoup(html, 'html.parser')
# Artikel-Header und -Inhalt extrahieren
# Der Header ist oft in einem <h1>-Tag zu finden
header_title = soup.find('h1', {'class': 'a-article-header__title'}).get_text().strip()
header_lead = soup.find('p', {'class': 'a-article-header__lead'}).get_text().strip()
# Der eigentliche Artikelinhalt befindet sich oft in einem <div>-Tag mit der Klasse 'article-content'
article_div = soup.find('div', {'class': 'article-content'})
paragraphs = article_div.find_all('p') if article_div else []
# 'redakteurskuerzel' entfernen
for para in paragraphs:
spans_to_remove = para.find_all('span', {'class': 'redakteurskuerzel'})
for span in spans_to_remove:
span.decompose() # Entfernt den Tag vollständig aus dem Baum
article_content = "\n".join([para.get_text().strip() for para in paragraphs])
# Header und Artikelinhalt ausgeben
result = "Header Title:" + header_title + "\nHeader Lead:" + header_lead + "\nContent:" + article_content
return result
result = scrape_heise_website("https://www.heise.de/news/Streit-ueber-Kosten-Meta-kappt-Leitungen-zur-Telekom-9953162.html")
print(result)
Um mich in die Technik einzuarbeiten habe ich mir ein paar Bilder von Pixabay heruntergeladen, ein Projekt auf GitHub angelegt und dann schrittweise ein PDF mit Bildern erzeugt:
Wie man erkennen kann, passen die Bilder nicht in die Zelle sondern stehen oben über.
Ich vermute einen Bug und habe einen Issue eröffnet.
Es muss das Formular mit der Bestell-Nr. 224 (CITES Bescheinigung; im Shop unter Verschiedene Vordrucke, Artenschutz) des Wilhelm Köhler Verlags bedruckt werden.
Wir benötigen zwei PDFs: Eines mit dem Formular und den Formularfeldern und eines nur mit den Formularfeldern, um es auf das Formular zu drucken.
Der Verlag bietet leider keine PDFs an, daher habe ich mir angesehen, ob ich mit vertretbarem Aufwand das Formular nachbauen kann.
Zur PDF Bearbeitung verwende ich Adobe Acrobat Pro.
Der perfekte Scan als Vorlage
Formular als PDF scannen. Anschließend auf einem zweiten Formular ausdrucken und neu scannen, bis Ausdruck und Formular übereinstimmen.
Dabei ist es wichtig, die "Tatsächliche Größe" beim Druck auszuwählen. Voreingestellt ist "Übergroße Seiten verkleinern", was das Druckergebnis verzerrt.
Formularfelder setzen
Das PDF mit dem Scan in Adobe Acrobat Pro öffnen. Aus den Tools "Formular vorbereiten" auswählen:
Adobe konnte keine Formularfelder identifizieren:
Wir setzen die Felder per Hand, und richten sie anschließend aus, zB mehrere Textfelder am linken bzw. rechten Rand auswählen und in den Eigenschaften unter Position Links, bzw. Rechts, gleich setzen, Breite anpassen etc.
Einige Felder können mehrzeilig sein, das wird in den Eigenschaften unter Optionen aktiviert.
Bei den Kontrollkästchen wird die Randfarbe auf "Keine Farbe" gesetzt und der Kontrollkästchenstil auf "Kreuz".
Alle Formularelemente sind im Text Schriftgrad: "Auto"
Das so bearbeitete PDF wird mit Daten befüllt und von mehreren Mitarbeitern auf Formulare gedruckt, bis die Positionierung der Felder verifiziert ist. Dabei ist wichtig, die "Tatsächliche Größe" beim Druck auszuwählen.
Bei einigen Ausdrucken war grade im unteren Bereich eine leichte Verschiebung zu sehen. Vermutlich war das Formular nicht immer 100%ig grade eingelegt, aber bei ganz genauer Betrachtung stellte sich auch heraus, dass der Scan ganz leicht schief ist.
Scan to PDF
Dass im Hintergrund ein gescanntes Formular verwendet wird, gefällt mir nicht so gut, zB sieht es an einigen Stellen "fleckig" aus, was von dem besonderen Papier des Formulars herrührt. Daher habe ich den Versuch gestartet, aus dem Scan ein PDF zu generieren.
Der erste Versuch schlug grundlegend fehl, da das Programm nicht mit den hochkant stehenden Schriften (unten links, "Bestell-Nr. 224" etc.) zurecht kommt. Daher muss der Scan vorbereitet werden, indem diese Schrift entfernt wird, zumal wir sie auch nicht benötigen.
Scan vorbereiten
Mit dem Tool PDF24 Creator habe ich das Bild aus dem PDF extrahiert und eine TIFF-Datei erhalten.
Die Datei öffne ich mit Paint.net, füge eine weitere Ebene hinzu, die in den Hintergrund kommt, diese wird weiß aufgefüllt.
Die Schrift wird entfernt.
Mit dem Zauberstab-Tool wird so viel wie möglich entfernt, da einiges "Rauschen" im Hintergrund ist. Dazu stelle ich die Toleranz auf einen niedrigen Wert und aktiviere "Globale Auswahl" in der oberen Leiste. Dies ermöglicht es dem Zauberstab, alle passenden Pixel im gesamten Bild auszuwählen, nicht nur zusammenhängende Bereiche.
Als png speichern.
Scan to Docx
In Adobe Acrobat Pro ein PDF aus der png-Datei erstellen.
Aus dem Alle Tools Menu Scan & OCR auswählen. Gescannte Datei verbessern. Und dann Text erkennen -> In dieser Datei.
Speichern unter: Konvertieren in "Windows Word (*.docx)"
Docx to PDF
In Adobe Acrobat Pro ein PDF aus der docx-Datei erstellen.
Der erste Versuch sah bei mir schon ganz gut aus:
Fazit
Leider war es dann so, dass ich bei späteren Versuchen schlechtere Ergebnisse erzielt habe. Warum die Ergebnisse teilweise sehr voneinander abweichen konnte ich nicht herausfinden.
Das Vorgehen, um von einem analogen Formular einen digitalen Klon zu erzeugen, funktioniert grundsätzlich. Allerdings war es auch sehr zeitaufwändig und am Ende frustrierend, da ich nicht nachvollziehen konnte, warum auf einmal die PDFs immer schlechter wurden.
Jetzt ist erstmal Ende meiner Motivation und des Budgets.
Für ein Projekt musste ich ein PDF erzeugen und habe das dann mit PDFBox umgesetzt.
Um mich in die Technik einzuarbeiten habe ich mir ein paar Bilder von Pixabay heruntergeladen, ein Projekt auf GitHub angelegt und dann schrittweise ein PDF mit Bildern erzeugt:
In my projects I use Maven as dependency managment system.
In the past I updated versions of libraries manually in pom.xml. But as projects grow, this becomes more and more annoying and time consuming. So I decided to give it a try to do this automatically.
I could not update the version of the plugins when the version information is hardcoded in the plugin section. So I used properties for the plugin versions.
API-Key: You can get an API key from OpenAI. But you need at least to pay 5$, what I did. Alternativley you can use demo as API key for limited testing.
Model-Name: Here are the OpenAI Models. gpt-3.5-turbo is default. Hint: It is not working, if there is a " "(space/blank) after the model-name.
I had stored my OpenAI-API-key as GitHub secret, so the key is available as environment variable in my Codespace. Therefore I changed the configuration:
No code change needed, it works with the same code as for ChatGPT.
Everything is changed by configuration.
Antrophic Claude
Extension
./mvnw quarkus:add-extension -Dextensions='quarkus-langchain4j-anthropic'
[ERROR] ❗ Nothing installed because keyword(s) 'quarkus-langchain4j-anthropic' were not matched in the catalog.
It did not work with the maven executable. Need to add dependency manually to pom.xml, see documentation:
However, Quarkus is ignoring my resourcefriendly choice, as I can see in the Logs: "Preloading model llama3" 🤷♂️ UPDATE: For Ollama it is model-id, not model-name!
API-key: You can generate an API-key in Mistral AI Console. But you are required to have a Abonnement, which I do not have. Therefore nor API-key for me.
Model: mistral-tiny is default one
Code
Also no change.
But could not test, because I do not have an API-key.
Groq
I like Groq but unfortunately there is no LangChain4j support yet.
We can not just delete the workshop folder, because GIT information are inside this folder.
Workaround:
cd /workspaces/workshop/
mvn io.quarkus.platform:quarkus-maven-plugin:3.10.2:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=workshop
mv workshop/* .
ls -lisah workshop/
# hidden files/directory still there
mv workshop/.* .
ls -lisah workshop/
# now everthing has moved
rmdir workshop
Stage, commit and push to repository:
Open GreetingResource, this will force Visual Code to ask to install Extension Pack for Java:
Go to Explorer → Java Projects → Import Projects
It takes a minute or two to show our workshop project:
Start Quarkus
./mvnw quarkus:dev
In Ports Port 5005 is automatically added, but not Port 8080.
We need to add Port 8080 manually.
Click on the Globus Icon and Quarkus welcome page opens:
# Ich muss vermutlich nur den zweiten Befehl ausführen?
# ./mvnw install -Dnative
./mvnw package -Dnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
Open another terminal and see how much codespace machine is sweating:
htop
Run:
target/workshop-1.0.0-SNAPSHOT-runner
The project generation has provided a Dockerfile.native-micro in the src/main/docker directory.
# build
docker build -f src/main/docker/Dockerfile.native-micro -t deringo/workshop .
# run
docker run -i --rm -p 8080:8080 deringo/workshop
WebSockets are a protocol that provides full-duplex communication channels over a single, long-lived connection. They are designed for real-time, event-driven web applications and allow for low-latency communication between a client (typically a web browser) and a server. Here are some key points about WebSockets:
Full-Duplex Communication: Unlike HTTP, which is request-response based, WebSockets allow for two-way communication where both client and server can send and receive messages independently of each other.
Persistent Connection: WebSocket connections are persistent, meaning they remain open as long as both the client and server agree to keep the connection alive. This reduces the overhead associated with establishing new connections.
Low Latency: WebSockets are ideal for scenarios requiring real-time updates because they reduce the latency associated with polling or long-polling techniques.
Protocol: WebSockets are established by upgrading an HTTP/HTTPS connection using a WebSocket handshake, switching the protocol from HTTP to WebSocket.
Use Cases: Common use cases include live chat applications, real-time notifications, collaborative editing, online gaming, and any application requiring real-time data updates.
Node.js in Codespace
I want to test WebSockets with Node.js in GitHub Codespace.
# install LTS
nvm install --lts
Installing latest LTS version.
Now using node v20.14.0 (npm v10.7.0)
# check version
nvm ls
# check node version
node --version
v20.14.0
Some npm commands
# Show installed Nodes
nvm ls
# Show available versions
nvm ls-remote
# Install latest version
nvm install node
# Install LTS version
nvm install --lts
# Install a specific version (list available -> example 16.20.2)
nvm install 16.20.2
# Use a specific version
nvm use 16.20.2
# Show npm version
npm --version
Simple Website with Node.js
To serve an HTML page using Node.js, we can use the built-in http module.
Create an HTML file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js HTML Server</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a simple HTML page served by Node.js.</p>
</body>
</html>
To create a Node.js server that provides both an HTTP server for serving an HTML page and a WebSocket server for real-time communication, we can use the ws library for WebSockets.
When setting up a WebSocket connection from the client-side script within the HTML file, the WebSocket URL must match the address and port where the WebSocket server is running. This URL should include the WebSocket protocol (ws:// or wss:// for secure connections)
Adjustment:
const socket = new WebSocket('wss://symmetrical-disco-g454xqrq9pqvfw6pr-3000.app.github.dev/:3000');
OK, this works, so make it a little more dynamic:
const socket = new WebSocket('wss://' + location.host + '/:3000');
Test WebSocket from terminal
Unfortunately, curl and httpie do not natively support WebSocket protocols. To test WebSocket connections using command-line we can use wscat, which is specifically designed for WebSocket communication.