Pragmatische Technik aus der Praxis

Kategorie: Allgemeines

  • Wie ich mir Spotify Daily Drive zurückgeholt habe – mit einem kleinen Open‑Source‑Hack

    Spotify hat Daily Drive am 17. März 2026 beerdigt.
    Keine automatisch aktualisierte Mischung aus Nachrichten, Podcasts und Musik mehr.
    Einfach weg. [1]

    Also: Alternative suchen.
    Gefunden: dailydrive von patdeg – ein kleines Node.js‑Tool, das genau das macht, was Spotify uns weggenommen hat:
    Deine eigene Daily‑Drive‑Playlist bauen. Voll automatisiert. [1]


    Was dailydrive macht

    Das Projekt rekreiert im Prinzip das alte „Your Daily Drive“:

    • zieht die neuesten Episoden deiner Lieblings‑Podcasts
    • mischt Musik dazu (Top‑Tracks, Genres, Playlists – frei konfigurierbar)
    • ordnet alles nach einem Muster (z.B. 1 Podcast, 4 Songs, wiederholen)
    • überschreibt eine definierte Spotify‑Playlist mit diesem Mix
    • läuft automatisiert per cron oder systemd auf einem Linux‑Kistchen (Raspberry Pi, Server, altes Notebook – egal) [1]

    Oder kurz:
    Du stellst einmal alles ein, ab dann baut eine kleine JavaScript‑App jeden Tag deine persönliche „Drive“-Playlist. [1]


    Voraussetzungen

    Bevor du loslegst, brauchst du:

    • Spotify Premium (Pflicht für Dev‑Apps seit Feb 2026, plus Limit von 5 Usern pro App) [1]
    • einen Spotify Developer Account
    • irgendeine Linux‑Kiste (Raspberry Pi reicht völlig)
    • minimalen Komfort mit Terminal / SSH

    Installation – in kurz

    1. Repo klonen
    git clone https://github.com/patdeg/dailydrive.git
    cd dailydrive
    Code-Sprache: PHP (php)
    1. Installer ausführen
    chmod +x install.sh
    ./install.sh
    

    Der Installer:

    • installiert Node.js (falls nötig)
    • zieht die Dependencies
    • legt eine config.yaml an (bzw. Vorlage) [1]

    Spotify‑App anlegen

    Damit das Tool deine Playlists anfassen darf, braucht es eine eigene Spotify‑App. [1]

    1. Geh ins Spotify Developer Dashboard.
    2. Erstelle eine neue App:
      • Name: z.B. Daily Drive
      • Beschreibung: „Personal Daily Drive playlist tool“
      • Redirect URI: http://127.0.0.1:8888/callback (wichtig: 127.0.0.1, nicht localhost) [1]
      • Häkchen bei Web API und Web Playback SDK
    3. Unter Settings → User Management:
      • deine Spotify‑Mail hinzufügen (sonst 403‑Fehler beim Schreiben der Playlist) [1]
    4. Client ID und Client Secret merken.

    Playlist vorbereiten

    1. In Spotify eine neue leere Playlist anlegen, z.B. „My Daily Drive“.
    2. Rechtsklick → Teilen → Link zur Playlist kopieren.
    3. Aus dem Link den Playlist‑ID‑Teil rausziehen – das, was nach /playlist/ kommt. [1]

    config.yaml bearbeiten

    Das ist das Herzstück. Hier legst du fest:

    • welche Playlist gefüllt wird
    • welche Podcasts du hören willst
    • woher die Musik kommt
    • in welchem Muster alles gemischt wird [1]

    Du startest mit der Vorlage:

    cp config.example.yaml config.yaml
    nano config.yaml
    Code-Sprache: CSS (css)

    Wichtige Punkte:

    • Spotify‑App‑Daten eintragen:
      • Client ID
      • Client Secret
    • Playlist ID eintragen
    • Podcasts definieren (IDs, Anzahl Episoden, optional position: first für News am Anfang) [1]
    • Music‑Sources konfigurieren:
      • Top‑Tracks
      • Genres
      • Playlists

    Beispiel für Top‑Tracks:

    music:
      top_tracks:
        enabled: true
        time_range: "short_term"  # ~4 Wochen
        count: 30
    Code-Sprache: PHP (php)

    Beispiel für Genre‑Discovery:

    music:
      genres:
        - dance pop
        - pop rock
        - electropop
    

    Wenn du Top‑Tracks und Genres gleichzeitig verwendest, splittet das Script automatisch 50/50:
    Halb Komfortzone, halb Entdeckung. [1]


    Podcast‑IDs und Playlist‑IDs finden

    • Podcast:
      • Show in Spotify öffnen
      • … → Teilen → Link zur Show kopieren
      • Der Teil nach /show/ ist die ID [1]
    • Playlist:
      • … → Teilen → Link zur Playlist kopieren
      • Der Teil nach /playlist/ ist die ID [1]

    Mix‑Pattern: Wie der Flow klingt

    Das Muster stellt P für „Podcast Episode“ und M für „Song“ dar. [1]

    Beispiele:

    • PMMM → 1 Podcast, 3 Songs, wiederholen
    • PMMMM → 1 Podcast, 4 Songs, wiederholen
    • PMMPMMM → 1 Podcast, 2 Songs, dann 1 Podcast, 3 Songs, wiederholen
    • PM → abwechselnd Podcast / Song
    • MMMPMMMM → 3 Songs, 1 Podcast, 4 Songs, wiederholen

    Zusätzlich kannst du Podcasts mit position: first vorne anpinnen, z.B. News‑Shorts:

    podcasts:
      - name: "NPR News Now"
        id: "6BRSvIBNQnB68GuoXJRCnQ"
        episodes: 1
        position: first
    Code-Sprache: JavaScript (javascript)

    Die laufen dann immer zuerst, danach greift das Pattern. [1]


    Einmalige Authentifizierung

    npm run setup
    
    • Script startet einen kleinen Webserver auf 127.0.0.1:8888
    • Browser geht auf, du loggst dich bei Spotify ein
    • Token wird lokal in .spotify-token.json gespeichert [1]

    Kein Monitor / Headless‑Server?
    SSH‑Trick:

    ssh -L 8888:127.0.0.1:8888 user@dein-server
    npm run setup
    Code-Sprache: CSS (css)

    Dann die URL im lokalen Browser öffnen, der Port wird getunnelt. [1]


    Erster Lauf: Playlist bauen

    npm start
    

    Danach in Spotify deine Ziel‑Playlist öffnen:
    Da sollte jetzt dein neuer Daily‑Mix drin liegen – Podcasts und Songs im definierten Pattern. [1]

    Wenn du nur testen willst:

    npm test
    

    Das zeigt dir, welche Inhalte ins Playlist‑Update gehen würden, ohne tatsächlich die Playlist zu überschreiben. [1]


    Automatisieren: damit du nie wieder dran denken musst

    Variante A: Cronjob

    crontab -e
    

    Eintrag für 4:00 und 16:00 Uhr:

    0 4,16 * * * cd /home/$USER/dailydrive && /usr/bin/node index.js >> /tmp/dailydrive.log 2>&1
    Code-Sprache: JavaScript (javascript)

    Variante B: systemd Timer (robuster, schöner) [1]

    sudo cp systemd/dailydrive.service /etc/systemd/system/[email protected]
    sudo cp systemd/dailydrive.timer   /etc/systemd/system/[email protected]
    sudo systemctl daemon-reload
    
    sudo systemctl enable dailydrive@YOUR_USERNAME.timer
    sudo systemctl start  dailydrive@YOUR_USERNAME.timer
    

    Zeitplan anpassen:

    sudo systemctl edit dailydrive@YOUR_USERNAME.timer
    Code-Sprache: CSS (css)

    Standard: 4:00 und 16:00 Uhr. [1]


    Nice Extra: Taste‑Profile per KI

    dailydrive kann deine Genres automatisch aus deinen Top‑Tracks ableiten.
    Dafür nutzt es ein LLM über Demeterics. [1]

    • Demeterics‑Key holen
    • in .env eintragen:
    DEMETERICS_API_KEY=dmt_dein_key_oder_dmt_key;sk-dein-openai-key
    

    Dann:

    npm run taste
    

    Das Skript analysiert deine Top‑Tracks und baut passende Genre‑Tags für deine config.yaml. [1]


    Stabilität & Technik‑Nerdkram

    Unter der Haube passiert:

    • Zugriff auf die Spotify Web API über spotify-web-api-node
    • OAuth 2.0 Authorization Code Flow
    • Tokens werden automatisch aktualisiert und lokal gespeichert
    • Podcast‑Episoden: GET /v1/shows/{id}/episodes
    • Musik:
      • GET /v1/me/top/tracks
      • GET /v1/search für Genres
      • GET /v1/playlists/{id}/tracks
    • Playlist‑Update über PUT /v1/playlists/{id}/items (neuer Endpunkt seit den API‑Änderungen 2026) [1]

    Damit das Ding dir nicht während des Hörens ständig die Playlist umsortiert, gibt es ein State‑Caching in state.json.
    Wenn sich keine neuen Podcast‑Episoden ergeben, wird das Update einfach übersprungen. [1]


    Typische Fehler & schnelle Fixes

    • „Not authenticated!“
      npm run setup noch mal ausführen. [1]
    • config.yaml not found
      cp config.example.yaml config.yaml und bearbeiten. [1]
    • Token abgelaufen
      → ebenfalls npm run setup. [1]
    • 403 Forbidden beim Playlist‑Update
      → deine eigene Mail im Spotify‑Dev‑Dashboard unter „User Management“ eintragen. [1]
    • Playlist bleibt leer
      npm test checken: liefern die Podcasts / Playlists überhaupt Inhalte? IDs prüfen. [1]

    Was besser ist als das alte Spotify Daily Drive

    Spotify hat dir früher „irgendwas“ gemischt.
    Jetzt bestimmst du:

    • welche Podcasts drin sind (und wie viele Episoden)
    • wo sie im Mix landen (News zuerst, Long‑Form später)
    • welche Musikquellen genutzt werden
    • wie das Pattern aussieht
    • wann die Playlist aktualisiert wird [1]

    Du bist nicht mehr von einem Launenschalter im Spotify‑Backend abhängig.
    Wenn Spotify wieder eine Funktion killt – egal. Du baust dir die halt selber.


    Fazit

    Wenn du Daily Drive vermisst und keine Lust hast, jeden Tag manuell News‑Podcasts und Musik zusammenzuklicken:

    • dailydrive ist leichtgewichtig
    • schnell eingerichtet
    • komplett unter deiner Kontrolle
    • und läuft auf praktisch jeder Linux‑Kiste im Hintergrund [1]

    Du hast dir das Ding installiert, es „läuft genial“ – exakt dafür ist das Projekt gebaut.
    Einmal sauber konfigurieren, dann einfach ins Auto steigen, Play drücken und die Maschine machen lassen.

    So muss das.

  • OM-1 Stacking Pipeline v4:

    Jetzt mit moderner Web-UI und macOS App

    15. März 2026 • Oliver Kümmel


    TL;DR

    Die OM-1 Macro Focus Stacking Pipeline ist jetzt als native macOS App verfügbar! Statt Terminal-Befehlen gibt’s jetzt eine schicke Web-Oberfläche mit Live-Preview, Echtzeit-Progress und One-Click-Installation.

    → Download auf GitHub


    Von der Kommandozeile zur App

    Im letzten Artikel hab ich gezeigt, wie man mit Python-Scripts automatisch Focus-Stacks aus OM-1 RAW-Serien erstellt. Das funktionierte, war aber… sagen wir mal „entwicklerfreundlich“. 😅

    Das Problem:

    • Terminal-Befehle eintippen
    • Python-Environment aufsetzen
    • Kein visuelles Feedback
    • Für Nicht-Nerds eher abschreckend

    Die Lösung: v4.0 🚀


    Was ist neu?

    🎨 Moderne Web-Oberfläche

    Statt schwarzem Terminal-Fenster gibt’s jetzt eine responsive Web-UI mit:

    • Thumbnail-Vorschau aller erkannten Serien
    • Interaktive Auswahl per Checkbox
    • Live-Progress mit Echtzeit-Logs
    • Statistiken (Zeit, Erfolgsrate, etc.)

    Screenshot: (hier würde ein Screenshot der Web-UI hin)

    📦 Native macOS App

    Die Pipeline ist jetzt eine echte .app:

    # Früher:
    cd ~/Projects/stacking
    source venv/bin/activate
    python3 macro_stacking.py --watch /Volumes/SD-CARD
    
    # Jetzt:
    # Doppelklick auf "OM-1 Stacking Pipeline.app" → fertig!Code-Sprache: PHP (php)

    Installation:

    1. DMG herunterladen
    2. App nach /Applications ziehen
    3. Doppelklick → Browser öffnet sich automatisch

    ⚡ Performance-Optimierungen

    Smart Image Handling:

    • Nutzt OOC JPEGs wenn vorhanden (keine Konvertierung!)
    • Konvertiert RAW nur bei Bedarf
    • Thumbnail-Caching (beim 2. Mal instant)

    Vorher/Nachher:

    Vorher (v3): 10 Bilder → ~90 Sekunden
    Jetzt (v4):  10 Bilder → ~15 Sekunden ⚡

    🔍 Bessere Helicon-Integration

    Helicon Focus wird jetzt direkt per CLI angesteuert:

    # Konfiguration in ~/.stacking_config.yaml
    helicon_method: C      # Pyramid (beste Qualität)
    helicon_radius: 8      # Feintuning möglich
    helicon_smoothing: 4   # Übergänge optimierenCode-Sprache: PHP (php)

    Tipp: Method C dauert länger, liefert aber deutlich bessere Ergebnisse als die alte focus-stack-Lösung!


    Der neue Workflow

    1. SD-Karte einlegen

    Die App erkennt automatisch alle gemounteten SD-Karten mit DCIM-Ordner.

    Screenshot: (SD-Karten-Auswahl)

    2. Serien reviewen

    Für jede erkannte Serie wird ein Thumbnail generiert (aus dem ersten Bild):

    • Anzahl der Bilder
    • Zeitraum der Aufnahme
    • Dauer der Serie

    Screenshot: (Serien-Grid mit Thumbnails)

    3. Auswählen & Stacken

    • Checkboxen zum Auswählen
    • „Select All“ / „Select None“ Buttons
    • Live-Log zeigt jeden Schritt:
    [21:35:42]Serie 1: 12 images
    [21:35:43]Serie 1: Prepared 12 images (8 OOC JPG, 4 converted)
    [21:35:44]Serie 1: Stacking with Helicon Focus (Method C)...
    [21:36:07]Serie 1: Stack created in 23.4sstack_20260315_213542.jpgCode-Sprache: CSS (css)

    Screenshot: (Live-Progress-View)

    4. Fertig!

    Stacks landen in ~/Pictures/Stacked/ mit:

    • EXIF-Metadaten (Datum, Anzahl Bilder, Software)
    • Sinnvollem Dateinamen (stack_YYYYMMDD_HHMMSS.jpg)
    • macOS-Notification bei Fertigstellung

    Technische Details

    Architektur

    ┌─────────────────────────────────────┐
    │   Web-UI (HTML/CSS/JS)              │
    │   - Thumbnails                       │
    │   - Live Updates via WebSocket      │
    └──────────────┬──────────────────────┘
                   │
    ┌──────────────▼──────────────────────┐
    │   Flask + SocketIO Backend          │
    │   - Series Detection                 │
    │   - Image Processing                 │
    │   - Progress Tracking                │
    └──────────────┬──────────────────────┘
                   │
    ┌──────────────▼──────────────────────┐
    │   Processing Pipeline                │
    │   - ImageMagick (RAW→JPG)           │
    │   - Helicon Focus (Stacking)        │
    │   - exiftool (Metadata)             │
    └─────────────────────────────────────┘

    py2app für macOS Apps

    Die App wird mit py2app gebaut:

    # setup.py
    setup(
        app=['macro_stacking_web_v4.1.py'],
        options={'py2app': {
            'packages': ['flask', 'flask_socketio', 'PIL'],
            'iconfile': 'resources/app_icon.icns',
            'plist': {
                'CFBundleName': 'OM-1 Stacking Pipeline',
                'LSApplicationCategoryType': 'public.app-category.photography',
            }
        }}
    )Code-Sprache: PHP (php)

    Build:

    python3 setup.py py2appCode-Sprache: CSS (css)

    Ergebnis: Standalone-App (~50MB) mit embedded Python + Dependencies.

    Thumbnail-Caching

    Thumbnails werden gecacht mit Hash aus:

    • Dateiname
    • Dateigröße
    • Modification Time
    cache_key = f"{filename}_{size}_{mtime}"
    cache_hash = hashlib.md5(cache_key.encode()).hexdigest()[:16]
    cache_path = ~/.stacking_cache/thumbnails/{cache_hash}.jpgCode-Sprache: JavaScript (javascript)

    Vorteil: Beim 2. Öffnen der gleichen SD-Karte sind Thumbnails instant verfügbar!


    Vergleich: v3 vs. v4

    Featurev3.0 (CLI/Tkinter)v4.0 (Web + App)
    InstallationPython, pip, venvDMG → Drag & Drop
    InterfaceTerminal / TkinterWeb-Browser
    ThumbnailsLangsam, kein CacheSchnell + Cache
    ProgressText-OutputLive-Log + Bar
    BedienungKeyboardPoint & Click
    PortabilitätPython nötigStandalone App
    Updatesgit pullAuto-Update (geplant)

    Praxis-Beispiel: Pilz-Makro

    Setup:

    • OM-1 + M.Zuiko 60mmMacro
    • Focus Bracketing: 15 Bilder, Step 3
    • Freihand
    • RAW + JPG

    Workflow:

    1. Shooting: 30 Sekunden (15 Aufnahmen)
    2. SD-Karte einlegen: 5 Sekunden
    3. App starten: 2 Sekunden (Browser öffnet)
    4. Serie auswählen: 3 Sekunden
    5. Stacking: 18 Sekunden (Helicon Method C)
    6. Fertig! Stack in Lightroom importieren

    Gesamt: ~1 Minute vom Shooting zum fertigen Stack! 🚀

    Vorher/Nachher-Vergleich:

    Einzelbild (mittlere Fokusebene):

    • Schärfebereich: ~2mm
    • Vorder-/Hintergrund unscharf

    Gestacktes Bild:

    • Schärfebereich: ~15mm
    • Durchgehend scharf von vorne bis hinten
    • Keine Ghosting-Artefakte (dank Helicon Method C)

    Installation & Setup

    Voraussetzungen

    # Homebrew Tools
    brew install imagemagick exiftool
    
    # Helicon Focus (Trial oder Lizenz)
    # Download: https://www.heliconsoft.comCode-Sprache: PHP (php)

    App installieren

    1. Download: GitHub Releases
    2. DMG öffnen und App nach /Applications ziehen
    3. Erste Ausführung: Rechtsklick → „Öffnen“ (macOS Gatekeeper)
    4. Browser öffnet sich automatisch

    Konfiguration

    Config-Datei: ~/.stacking_config.yaml

    # Wichtigste Settings:
    output_dir: ~/Pictures/Stacked
    helicon_method: C
    helicon_radius: 8
    time_threshold: 30  # Sekunden zwischen Bildern
    min_images: 3       # Mindestanzahl pro SerieCode-Sprache: PHP (php)

    Tipp: Für schnellere Tests helicon_method: A nutzen!


    Tipps & Tricks

    Optimales Shooting

    Focus Bracketing Settings (OM-1):

    Modus:           Focus BKT
    Anzahl:          10-30 (je nach Motiv)
    Fokus-Schritt:   3-5 (kleiner = mehr Überlappung)
    Intervall:       0s (so schnell wie möglich)Code-Sprache: HTTP (http)

    Kamera-Settings:

    • RAW + JPG aktivieren → schnellere Thumbnails!
    • IS einschalten (auch auf Stativ)
    • Selbstauslöser 2s oder Fernauslöser
    • Manuelle Belichtung (keine Änderungen zwischen Frames)

    Performance-Tuning

    Schneller Workflow:

    jpg_converter: imagemagick  # Schneller als dcraw
    output_format: jpg          # Schneller als TIFF
    helicon_method: A           # Für PreviewsCode-Sprache: PHP (php)

    Beste Qualität:

    helicon_method: C           # Pyramid-Methode
    output_quality: 95          # Hohe JPG-Qualität
    output_format: tiff         # Für weitere BearbeitungCode-Sprache: PHP (php)

    Troubleshooting

    Problem: Thumbnails werden nicht generiert

    # Check ImageMagick
    magick --version
    
    # Test RAW-Konvertierung
    magick P3150001.ORF -resize 200x133 test.jpgCode-Sprache: CSS (css)

    Problem: Helicon nicht gefunden

    # Pfad prüfen
    ls /Applications/HeliconFocus.app/Contents/MacOS/HeliconFocus
    
    # In Config anpassen
    nano ~/.stacking_config.yamlCode-Sprache: PHP (php)

    Problem: Serien werden nicht erkannt

    # Schwellwert erhöhen
    time_threshold: 60  # Statt 30
    min_images: 2       # Statt 3Code-Sprache: PHP (php)

    Ausblick: Was kommt noch?

    Geplante Features

    v4.2 (Q2 2026):

    • [ ] Auto-Update Funktion
    • [ ] Presets für verschiedene Workflows
    • [ ] Batch-Export zu Lightroom/Capture One
    • [ ] GPU-Beschleunigung für RAW-Konvertierung

    v5.0 (Q3 2026):

    • [ ] Windows/Linux Support
    • [ ] Cloud-Processing Option
    • [ ] AI-basierte Fokus-Optimierung
    • [ ] Plugin-System für andere Stacker

    Community-Features

    Gewünscht von Usern:

    • Depth-Map Export für 3D-Modelle
    • Vergleichs-Ansicht (Einzelbild vs. Stack)
    • Direkter Upload zu Flickr/500px
    • Mobile App (iOS/Android)

    → Feature-Requests auf GitHub willkommen!


    Fazit

    Die OM-1 Stacking Pipeline v4 macht Focus-Stacking endlich zugänglich und schnell:

    Keine Terminal-Kenntnisse mehr nötig
    Visuelles Feedback in Echtzeit
    Deutlich schneller durch Smart Processing
    Bessere Qualität mit Helicon Focus
    Native macOS App statt Script-Sammlung

    Von der SD-Karte zum fertigen Stack in unter 1 Minute! 🚀


    Download & Links

    Lizenz: MIT (Open Source, kostenlos)


    Feedback & Kontakt

    Fragen? Probleme? Feature-Wünsche?

    Pull Requests willkommen! 🤝


    Beispiel-Galerie

    Hier würden gestackte Beispielbilder hin – falls du welche hast, kann ich die Beschreibungen schreiben!

    Beispiel 1: Pilz-Makro

    • Kamera: OM-1 + M.Zuiko 90mm f/3.5
    • Bilder: 15 Frames, Step 3
    • Stacking: Helicon Method C
    • Zeit: 18 Sekunden

    Beispiel 2: Insekten-Detail

    • Kamera: OM-1 + M.Zuiko 60mm f/2.8
    • Bilder: 30 Frames, Step 2
    • Stacking: Helicon Method C
    • Zeit: 35 Sekunden

    Happy Stacking! 📸🔬

    Dieser Artikel ist auch auf GitHub verfügbar.

  • Automatisierter Makro-Stacking-Workflow für die OM-1: Von der SD-Karte zum fertigen Bild

    Wie ich meinen Makro-Fotografie-Workflow mit Python, LibRaw und focus-stack vollständig automatisiert habe


    TL;DR

    SD-Karte einstecken, Script starten, fertig. Automatisches Focus-Stacking für Makro-Fotografie mit der OM-1: Das System erkennt Bildserien, konvertiert RAW-Files, stackt sie und gibt perfekt scharfe Makro-Aufnahmen aus. Alles Open Source, läuft auf macOS.

    Tech-Stack: Python • LibRaw • focus-stack • exiftool • YAML


    Das Problem: Makro-Stacking ist mühsam

    Makro-Fotografie hat ein fundamentales Problem: Schärfentiefe. Bei extremen Vergrößerungen liegt nur ein winziger Bereich im Fokus. Die Lösung? Focus-Stacking – mehrere Bilder mit unterschiedlichen Fokusebenen zu einem komplett scharfen Bild kombinieren.

    Das manuelle Ritual

    1. 📸 Fotos machen (10-50 Bilder pro Motiv)
    2. 💾 SD-Karte in den Rechner
    3. 📁 Bilder sortieren (welche gehören zusammen?)
    4. 🔄 RAW-Files konvertieren (ORF → TIFF)
    5. 🔬 In Stacking-Software laden
    6. ⏳ Warten…
    7. 💾 Ergebnis speichern
    8. 🔁 Für jede Serie wiederholen

    Das nervt. Besonders wenn man 5-10 Serien pro Session hat.


    Die Vision: Komplette Automation

    Was wäre, wenn das alles automatisch passiert?

    SD-Karte einstecken
        ↓
    Script starten
        ↓
    ☕ Kaffee trinken
        ↓
    Fertige Bilder im Output-Ordner
    

    Genau das habe ich gebaut.


    Das Setup

    Hardware

    • Kamera: OM System OM-1 (funktioniert mit allen ORF-Kameras)
    • Computer: Mac (Intel oder Apple Silicon)
    • SD-Kartenleser: Beliebig

    Software-Stack

    ToolZweckWarum?
    Python 3.xOrchestrierungFlexibel, gut dokumentiert
    LibRawRAW-KonvertierungUnterstützt OM-1 (dcraw ist zu alt!)
    focus-stackStacking-EngineOpen Source, gute Makro-Qualität
    exiftoolEXIF-DatenSerien-Erkennung via Timestamp
    PyYAMLConfig-ParsingLesbare Konfiguration mit Kommentaren

    Optional:

    • GPS-Logger (iPhone) für GPS-Tagging

    Architektur: Wie funktioniert’s?

    1. SD-Karten-Erkennung

    Das Script durchsucht /Volumes/ nach gemounteten Volumes mit DCIM-Ordner:

    def find_sd_cards(self):
        volumes_path = Path("/Volumes")
        sd_cards = []
        
        for volume in volumes_path.iterdir():
            dcim_path = volume / "DCIM"
            if dcim_path.exists() and dcim_path.is_dir():
                orf_count = len(list(dcim_path.rglob("*.ORF")))
                sd_cards.append({
                    'name': volume.name,
                    'dcim': dcim_path,
                    'orf_count': orf_count
                })
        
        return sd_cards
    Code-Sprache: PHP (php)

    Drei Modi:

    • ask – Interaktive Auswahl bei mehreren Karten
    • first – Erste Karte automatisch nehmen
    • manual – Fester Pfad aus Config

    2. Serien-Erkennung via EXIF-Timestamps

    Makro-Serien erkennt man daran, dass Bilder zeitlich nah beieinander liegen:

    def find_series(self, images):
        threshold = timedelta(seconds=self.config['time_threshold'])
        
        for img, timestamp in sorted_images:
            if last_time and (timestamp - last_time) > threshold:
                # Neue Serie!
                series.append(current_series)
                current_series = [img]
            else:
                current_series.append(img)
    Code-Sprache: PHP (php)

    Beispiel:

    • Bilder um 10:15:00, 10:15:02, 10:15:04 → Serie 1
    • Bilder um 10:20:00, 10:20:02 → Serie 2 (mehr als 30s Pause)

    Warum nicht Dateinamen?

    • Kamera resettet Nummern
    • Mehrere Sessions durcheinander
    • EXIF-Timestamp ist immer korrekt

    3. RAW-Konvertierung: Das dcraw-Problem

    Problem: Die OM-1 kam 2022 raus. Das klassische dcraw (letztes Update: 2018) kennt das ORF-Format der OM-1 nicht richtig.

    Symptom: Unexpected end of file oder Cannot use camera white balance

    Lösung: LibRaw – der aktive Fork von dcraw mit Support für neue Kameras.

    # OM-1 ORF zu 16-bit TIFF
    dcraw_emu -6 -T -w -o 1 -q 3 image.ORF
    Code-Sprache: CSS (css)

    Wichtig: Das -6 Flag für 16-bit Output!

    Bei 8-bit verliert man zu viel Farbtiefe. Das Stacking-Ergebnis wird verrauscht und sieht aus wie ein schlechter Instagram-Filter.

    Quirk: dcraw_emu erstellt image.ORF.tiff (nicht image.tiff) – das Script benennt automatisch um.


    4. Focus-Stacking mit focus-stack

    Warum focus-stack?

    Featurefocus-stackHelicon FocusZerene Stacker
    PreisKostenlos~$200~$300
    CLI✅ Ja⚠️ Eingeschränkt⚠️ Eingeschränkt
    Qualität⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
    Automation✅ Perfekt❌ Schwierig❌ Schwierig

    Für Automation ist eine echte CLI essentiell. focus-stack liefert.

    Optimale Parameter für Makro:

    focus-stack \
      --output=result.jpg \
      --consistency=3 \      # Alignment-Strenge (2-5)
      --denoise=0.5 \        # Leichte Rauschunterdrückung
      *.tiff
    Code-Sprache: PHP (php)

    Parameter-Tuning:

    • consistency=2 → Schnell, tolerant, mehr Artefakte
    • consistency=5 → Langsam, streng, weniger Fehler
    • denoise=0 → Maximale Schärfe, mehr Rauschen
    • denoise=2 → Starke Glättung, weniger Detail

    5. Workflow-Orchestrierung

    Das Python-Script koordiniert alle Schritte:

    def stack_series(self, images, output_name):
        # 1. Bilder kopieren (SD-Karte → /tmp)
        copied_images = self.copy_images(images)
        
        # 2. RAW konvertieren (ORF → 16-bit TIFF)
        tiff_files = self.convert_raw(copied_images)
        
        # 3. Stacking
        self.focus_stack(tiff_files, output_name)
        
        # 4. Cleanup
        self.cleanup_temp()
    Code-Sprache: PHP (php)

    Warum erst kopieren?

    dcraw/LibRaw hat Probleme beim direkten Lesen von SD-Karten (USB-Latenz, Dateisystem-Quirks). Lokale Kopie ist stabiler und schneller.


    Die Config: YAML statt JSON

    Anfangs hatte ich JSON mit _comment_*-Keys. Das war hässlich.

    Jetzt: YAML mit echten Kommentaren!

    # ═══════════════════════════════════════════
    # OM-1 Makro-Stacking Pipeline
    # ═══════════════════════════════════════════
    
    # SD-Karten-Modus
    # 'ask' = immer fragen
    # 'first' = erste nehmen
    # 'manual' = fester Pfad
    sd_card_mode: ask
    
    # Serien-Erkennung
    # Max. Sekunden zwischen Bildern einer Serie
    time_threshold: 30
    
    # Mindestanzahl Bilder pro Serie
    min_images: 3
    
    # Stacking-Software
    stacker: focus-stack
    stacker_binary: ~/bin/focus-stack/focus-stack.app/Contents/MacOS/focus-stack
    
    # focus-stack Parameter
    consistency: 3      # 2-5, höher = strenger
    denoise: 0.5        # 0-2, Rauschunterdrückung
    
    # RAW-Konvertierung
    convert_raw: true
    raw_converter: libraw
    
    # Output
    output_dir: ~/Pictures/Stacked
    output_format: jpg
    output_quality: 95
    Code-Sprache: YAML (yaml)

    Viel lesbarer als JSON!


    Installation

    Schritt 1: Dependencies

    # Homebrew (falls noch nicht installiert)
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    
    # Tools installieren
    brew install python3 exiftool libraw
    
    # Python-Dependencies
    pip3 install pyyaml
    Code-Sprache: PHP (php)

    Schritt 2: focus-stack

    # Download von GitHub Releases
    cd ~/Downloads
    # https://github.com/PetteriAimonen/focus-stack/releases
    # → focus-stack-macos.zip herunterladen
    
    # Entpacken und installieren
    unzip focus-stack-*.zip
    mkdir -p ~/bin
    mv focus-stack ~/bin/
    chmod +x ~/bin/focus-stack
    
    # Testen
    ~/bin/focus-stack --help
    Code-Sprache: PHP (php)

    Schritt 3: Script einrichten

    # Ordner erstellen
    mkdir -p ~/scripts
    
    # Script erstellen
    nano ~/scripts/macro_stacking.py
    # (Code einfügen – siehe unten)
    
    # Ausführbar machen
    chmod +x ~/scripts/macro_stacking.py
    Code-Sprache: PHP (php)

    Schritt 4: Erste Ausführung

    # Erstellt Config-Datei
    python3 ~/scripts/macro_stacking.py
    
    # Config anpassen
    nano ~/.stacking_config.yaml
    Code-Sprache: PHP (php)

    Das komplette Script

    Das Script ist modular aufgebaut und ca. 250 Zeilen Python:

    #!/usr/bin/env python3
    """
    Automatischer Makro-Stacking-Workflow für OM-1 auf macOS
    """
    
    import os
    import subprocess
    import shutil
    from pathlib import Path
    from datetime import datetime, timedelta
    
    class OM1StackingPipeline:
        
        def __init__(self, config_path="~/.stacking_config.yaml"):
            self.config = self.load_config(config_path)
            self.output_dir = Path(self.config['output_dir']).expanduser()
            self.temp_dir = Path(self.config['temp_dir']).expanduser()
            self.stacker_bin = Path(self.config['stacker_binary']).expanduser()
            
            self.output_dir.mkdir(parents=True, exist_ok=True)
            self.temp_dir.mkdir(parents=True, exist_ok=True)
        
        def load_config(self, path):
            """Config laden oder Default erstellen"""
            config_path = Path(path).expanduser()
            
            if not config_path.exists():
                # Default-Config mit Kommentaren erstellen
                default_config = """# OM-1 Makro-Stacking Pipeline
    
    sd_card_mode: ask
    time_threshold: 30
    min_images: 3
    
    stacker: focus-stack
    stacker_binary: ~/bin/focus-stack
    
    consistency: 3
    denoise: 0.5
    
    convert_raw: true
    raw_converter: libraw
    
    output_dir: ~/Pictures/Stacked
    output_format: jpg
    output_quality: 95
    """
                config_path.parent.mkdir(parents=True, exist_ok=True)
                with open(config_path, 'w') as f:
                    f.write(default_config)
                
                import yaml
                return yaml.safe_load(default_config)
            
            import yaml
            with open(config_path) as f:
                return yaml.safe_load(f)
        
        def find_sd_cards(self):
            """SD-Karten mit DCIM-Ordner finden"""
            volumes_path = Path("/Volumes")
            sd_cards = []
            
            for volume in volumes_path.iterdir():
                if not volume.is_dir():
                    continue
                
                # System-Volumes überspringen
                if volume.name in ['Macintosh HD', 'Preboot', 'Recovery', 'VM', 'Data']:
                    continue
                
                dcim_path = volume / "DCIM"
                if dcim_path.exists():
                    orf_count = len(list(dcim_path.rglob("*.ORF")))
                    sd_cards.append({
                        'name': volume.name,
                        'dcim': dcim_path,
                        'orf_count': orf_count
                    })
            
            return sd_cards
        
        def select_sd_card(self, sd_cards):
            """SD-Karte auswählen"""
            mode = self.config.get('sd_card_mode', 'ask')
            
            if mode == 'first':
                return sd_cards[0]
            elif mode == 'ask':
                print("\n📸 SD-Karten gefunden:\n")
                for i, card in enumerate(sd_cards, 1):
                    print(f"  [{i}] {card['name']} ({card['orf_count']} ORF-Files)")
                
                choice = input("\nWelche SD-Karte? [1]: ").strip() or "1"
                return sd_cards[int(choice) - 1]
        
        def find_series(self, images):
            """Bilder in Serien gruppieren via EXIF-Timestamp"""
            # EXIF-Daten auslesen
            images_with_time = []
            for img in images:
                timestamp = self.get_exif_data(img)
                if timestamp:
                    images_with_time.append((img, timestamp))
            
            # Nach Zeit sortieren
            images_with_time.sort(key=lambda x: x[1])
            
            # Serien erkennen
            series = []
            current_series = []
            last_time = None
            threshold = timedelta(seconds=self.config['time_threshold'])
            
            for img, timestamp in images_with_time:
                if last_time and (timestamp - last_time) > threshold:
                    if len(current_series) >= self.config['min_images']:
                        series.append(current_series)
                    current_series = [img]
                else:
                    current_series.append(img)
                last_time = timestamp
            
            if len(current_series) >= self.config['min_images']:
                series.append(current_series)
            
            return series
        
        def convert_raw(self, orf_file, output_dir):
            """ORF zu 16-bit TIFF konvertieren"""
            subprocess.run([
                'dcraw_emu',
                '-6',  # 16-bit!
                '-T',  # TIFF
                '-w',  # Camera WB
                '-o', '1',  # sRGB
                '-q', '3',  # Best quality
                orf_file.name
            ], cwd=str(output_dir), check=True, capture_output=True)
            
            # Umbenennen: image.ORF.tiff → image.tiff
            output_raw = output_dir / f"{orf_file.name}.tiff"
            output = output_dir / f"{orf_file.stem}.tiff"
            output_raw.rename(output)
            
            return output
        
        def stack_series(self, images, output_name):
            """Serie stacken"""
            temp_dir = self.temp_dir / output_name
            temp_dir.mkdir(parents=True, exist_ok=True)
            
            print(f"  📸 Stacke {len(images)} Bilder...")
            
            # 1. Kopieren
            copied = [shutil.copy2(img, temp_dir / img.name) for img in images]
            
            # 2. Konvertieren
            print(f"  🔄 Konvertiere RAW...")
            converted = []
            for i, img in enumerate([Path(p) for p in copied], 1):
                print(f"    [{i}/{len(copied)}] {img.name}", end=" ")
                try:
                    tiff = self.convert_raw(img, temp_dir)
                    converted.append(tiff)
                    print("✓")
                except:
                    print("✗")
            
            # 3. Stacking
            print(f"  🔬 Stacking...")
            output_file = self.output_dir / f"{output_name}.{self.config['output_format']}"
            
            subprocess.run([
                str(self.stacker_bin),
                f'--output={output_file}',
                f'--consistency={self.config["consistency"]}',
                f'--denoise={self.config["denoise"]}',
                *[str(img) for img in converted]
            ], check=True, capture_output=True)
            
            # 4. Cleanup
            shutil.rmtree(temp_dir)
            
            return output_file
        
        def run(self):
            """Haupt-Pipeline"""
            print("╔═══════════════════════════════════════════╗")
            print("║  OM-1 Makro-Stacking Pipeline            ║")
            print("╚═══════════════════════════════════════════╝")
            
            # SD-Karte finden
            print("\n🔍 Suche SD-Karten...")
            sd_cards = self.find_sd_cards()
            
            if not sd_cards:
                print("❌ Keine SD-Karte gefunden!")
                return
            
            selected = self.select_sd_card(sd_cards)
            
            # Bilder finden
            images = list(selected['dcim'].rglob("*.ORF"))
            print(f"\n✓ Gefunden: {len(images)} Bilder")
            
            # Serien erkennen
            series = self.find_series(images)
            print(f"✓ Erkannt: {len(series)} Serien\n")
            
            # Stacking
            success = 0
            for i, imgs in enumerate(series, 1):
                timestamp = self.get_exif_data(imgs[0])
                series_name = f"series_{timestamp.strftime('%Y%m%d_%H%M%S')}"
                
                print(f"━━━ Serie {i}/{len(series)}: {series_name} ━━━")
                
                try:
                    output = self.stack_series(imgs, series_name)
                    print(f"  ✓ Fertig: {output.name}\n")
                    success += 1
                except Exception as e:
                    print(f"  ❌ Fehler: {e}\n")
            
            print(f"╔═══════════════════════════════════════════╗")
            print(f"║  ✓ {success}/{len(series)} Serien gestackt! ║")
            print(f"╚═══════════════════════════════════════════╝")
    
    if __name__ == "__main__":
        try:
            pipeline = OM1StackingPipeline()
            pipeline.run()
        except KeyboardInterrupt:
            print("\n⚠️  Abgebrochen")
    Code-Sprache: Python (python)

    Vollständiges Script: Download auf GitHub 


    Usage

    Standard-Workflow

    # 1. SD-Karte einstecken
    # 2. Terminal öffnen
    # 3. Script starten
    python3 ~/scripts/macro_stacking.py
    Code-Sprache: PHP (php)

    Output:

    ╔═══════════════════════════════════════════╗
    ║  OM-1 Makro-Stacking Pipeline            ║
    ╚═══════════════════════════════════════════╝
    
    🔍 Suche SD-Karten...
    
    📸 SD-Karten gefunden:
    
      [1] untitled (42 ORF-Files)
    
    Welche SD-Karte? [1]: 
    
    ✓ Gefunden: 42 Bilder
    
    📊 Analysiere Bilder...
    ✓ Erkannt: 3 Serien
    
    ━━━ Serie 1/3: series_20260314_083628 ━━━
      📸 Stacke 16 Bilder...
      🔄 Konvertiere RAW...
        [1/16] P3141806.ORF ✓
        [2/16] P3141807.ORF ✓
        ...
      🔬 Stacking...
      ✓ Fertig: series_20260314_083628.jpg
    
    ━━━ Serie 2/3: series_20260314_091522 ━━━
      ...
    
    ╔═══════════════════════════════════════════╗
    ║  ✓ 3/3 Serien gestackt!                   ║
    ╚═══════════════════════════════════════════╝
    

    Mehrere SD-Karten

    Das Script zeigt alle verfügbaren Karten:

    📸 SD-Karten gefunden:
    
      [1] untitled (42 ORF-Files)
      [2] EOS_DIGITAL (18 ORF-Files)
    
    Welche SD-Karte? [1]: 2
    
    ✓ Gewählt: EOS_DIGITAL
    Code-Sprache: CSS (css)

    Automatischer Modus

    Für wiederkehrende Workflows:

    # Config: ~/.stacking_config.yaml
    sd_card_mode: first  # Nimmt automatisch erste Karte
    Code-Sprache: PHP (php)
    python3 ~/scripts/macro_stacking.py
    # Keine Interaktion nötig!
    Code-Sprache: PHP (php)

    Erweiterte Features

    GPS-Tagging

    Für Außenaufnahmen (oder Makro im Feld):

    1. GPS-Track aufzeichnen:

    • iPhone: GPX Tracker, Trails, Gaia GPS
    • Android: GPS Logger, OsmAnd
    • Track als .gpx exportieren

    2. Config anpassen:

    gpx_file: ~/Downloads/track_20260314.gpx
    Code-Sprache: JavaScript (javascript)

    3. Script läuft:

    🌍 Füge GPS-Daten hinzu: track_20260314.gpxGPS-Daten hinzugefügt
    Code-Sprache: CSS (css)

    Alle Bilder bekommen GPS-Koordinaten basierend auf Aufnahmezeit.

    Funktioniert auch für Nicht-Makro-Fotos!


    Debug-Modus

    Für Troubleshooting:

    debug: true
    keep_temp: true
    Code-Sprache: JavaScript (javascript)

    Behält konvertierte TIFF-Files in /tmp/stacking/ zum Inspizieren.

    Nützlich wenn:

    • Stacking-Qualität schlecht ist
    • Alignment fehlschlägt
    • Konvertierung Probleme macht

    Parameter-Tuning

    Für schwierige Motive:

    consistency: 5      # Sehr streng (weniger Fehler)
    denoise: 0          # Kein Denoise (maximale Schärfe)
    Code-Sprache: PHP (php)

    Für schnelles Stacking:

    consistency: 2      # Toleranter (schneller)
    denoise: 1.5        # Mehr Glättung
    Code-Sprache: PHP (php)

    Mein Sweet-Spot:

    consistency: 3
    denoise: 0.5
    Code-Sprache: HTTP (http)

    Troubleshooting

    „Keine SD-Karte gefunden“

    Check:

    ls /Volumes/
    ls /Volumes/*/DCIM/
    

    Mögliche Ursachen:

    • SD-Karte nicht gemountet
    • Kein DCIM-Ordner
    • Permissions-Problem

    Fix:

    # Config: Manuellen Pfad nutzen
    sd_card_mode: manual
    watch_dir: /Volumes/MEINE_KARTE/DCIM
    Code-Sprache: PHP (php)

    „dcraw_emu: command not found“

    # LibRaw installieren
    brew install libraw
    
    # Checken
    which dcraw_emu
    dcraw_emu --version
    Code-Sprache: PHP (php)

    „focus-stack crashed“

    Mögliche Ursachen:

    • Zu wenig RAM (braucht ~2GB pro Serie bei 20MP)
    • Korrupte TIFF-Files
    • Bilder zu unterschiedlich (Bewegung, Wind)

    Debug:

    keep_temp: true
    Code-Sprache: JavaScript (javascript)

    Dann TIFFs in /tmp/stacking/ manuell mit Vorschau öffnen und prüfen.


    Schlechte Stacking-Qualität

    Checklist:

    1. ✅ 16-bit TIFF? (Check mit file image.tiff)
    2. ✅ Genug Bilder? (min. 5-10 für Makro)
    3. ✅ Stativ benutzt? (Bewegung killt Alignment)
    4. ✅ Fokus-Schritte klein genug? (Overlap wichtig!)
    5. ✅ consistency Parameter anpassen

    Häufigster Fehler: 8-bit TIFF statt 16-bit → verrauschtes Ergebnis


    „Unexpected end of file“ bei dcraw_emu

    Ursache: Bilder werden direkt von SD-Karte gelesen (USB-Latenz).

    Fix: Script kopiert Bilder erst nach /tmp – sollte nicht mehr auftreten.

    Falls doch:

    # Anderes Temp-Verzeichnis
    temp_dir: ~/tmp/stacking
    Code-Sprache: PHP (php)

    Performance

    Benchmark (M1 MacBook Pro, 20MP ORF-Files):

    SerieBilderRAW → TIFFStackingTotal
    Klein712s8s20s
    Mittel1628s22s50s
    Groß3258s65s2m 3s

    Bottlenecks:

    • RAW-Konvertierung: ~1.5-2s pro Bild
    • Stacking: ~1-2s pro Bild (abhängig von Auflösung)
    • I/O: SSD vs. HDD macht massiven Unterschied

    Optimierungen:

    • ✅ SSD für /tmp/stacking (2x schneller)
    • ✅ 16GB+ RAM (verhindert Swapping)
    • ⏳ TODO: Parallele Verarbeitung (mehrere Serien gleichzeitig)

    Lessons Learned

    1. dcraw ist tot, lang lebe LibRaw

    dcraw (2018) kennt moderne Kameras nicht. LibRaw ist Pflicht für neue Kameras wie die OM-1.

    Symptom: Unexpected end of fileCannot use camera white balance

    Lösung: brew install librawdcraw_emu nutzen


    2. 16-bit ist nicht optional

    8-bit TIFF sieht nach Stacking katastrophal aus:

    • Verrauscht
    • Banding in Farbverläufen
    • Verlust von Highlight/Shadow-Detail

    16-bit ist bei Makro mit hohem ISO essentiell.

    Vergleich:

    • 8-bit: 256 Helligkeitsstufen pro Kanal
    • 16-bit: 65.536 Helligkeitsstufen pro Kanal

    Bei 10+ Bildern macht das einen massiven Unterschied.


    3. Serien-Erkennung via EXIF > Dateinamen

    Anfangs wollte ich nach Dateinamen gruppieren (P3141806.ORFP3141807.ORF, …).

    Probleme:

    • Kamera resettet Nummern
    • Mehrere Sessions durcheinander
    • Lücken in Sequenzen

    EXIF-Timestamp ist immer korrekt und funktioniert über Sessions hinweg.


    4. YAML > JSON für Configs

    JSON ohne Kommentare ist unleserlich. YAML mit echten Kommentaren ist so viel besser.

    Vorher (JSON):

    {
      "_comment_consistency": "2-5, höher = strenger",
      "consistency": 3
    }
    Code-Sprache: JSON / JSON mit Kommentaren (json)

    Nachher (YAML):

    # Consistency-Level (2-5, höher = strenger)
    consistency: 3
    Code-Sprache: PHP (php)

    Kein Vergleich.


    5. Kopieren vor Konvertieren

    dcraw/LibRaw hat Probleme beim direkten Lesen von SD-Karten:

    • USB-Latenz
    • Dateisystem-Quirks (exFAT)
    • Timeouts bei langsamen Karten

    Lösung: Bilder erst nach /tmp kopieren (schnell, lokal, zuverlässig).


    Mögliche Erweiterungen

    TODO-Liste:

    •  Parallele Verarbeitung (mehrere Serien gleichzeitig)
    •  Hazel-Integration (automatisch bei SD-Karte einstecken)
    •  Web-UI (Flask-Dashboard mit Live-Progress)
    •  Qualitäts-Check (verwackelte Bilder aussortieren via Sharpness-Analyse)
    •  Cloud-Backup (gestackte Bilder automatisch auf NAS/Cloud)
    •  Helicon Focus Support (bessere Qualität, aber kommerziell)
    •  Linux/Windows Support (aktuell nur macOS)
    •  Batch-Reprocessing (alte Serien mit neuen Parametern neu stacken)

    Alternative Ansätze

    Warum nicht…

    …Lightroom/Photoshop?

    • ❌ Nicht automatisierbar
    • ❌ Teuer (Abo-Modell: 12€/Monat)
    • ❌ Closed Source
    • ❌ Erfordert manuelle Interaktion

    …Helicon Focus?

    • ✅ Beste Qualität
    • ❌ Kommerziell (~$200)
    • ❌ CLI eingeschränkt
    • ❌ Für Hobby-Workflow overkill

    …Zerene Stacker?

    • ✅ Auch sehr gut
    • ❌ Teuer (~$300)
    • ❌ Ähnliche CLI-Probleme
    • ❌ Keine gute Automation

    …shinestacker?

    • ✅ Kann RAW direkt
    • ✅ Open Source
    • ❌ Nur Python-API (keine CLI)
    • ❌ Komplizierter zu integrieren

    …Alles manuell?

    • ✅ Funktioniert
    • ❌ Zeitverschwendung
    • ❌ Fehleranfällig
    • ❌ Nicht reproduzierbar
    • ❌ Nervt nach der dritten Serie

    Fazit

    Mit diesem Setup ist Makro-Stacking endlich schmerzfrei:

    ✅ Keine manuelle Sortierung mehr
    ✅ Keine RAW-Konvertierung mehr
    ✅ Kein Klicken durch GUIs mehr
    ✅ Reproduzierbar und konsistent
    ✅ Open Source und kostenlos

    Einmal eingerichtet läuft es vollautomatisch.

    Der neue Workflow:

    1. 📸 Fotos machen
    2. 🚀 Script starten
    3. ☕ Kaffee trinken
    4. ✅ Fertig

    Genau so sollte es sein.


    Download & Links

    Code:

    Tools:

    Hardware:


    Weitere Artikel in dieser Serie


    Fragen? Verbesserungen? Bugs?

    Hinterlasst Kommentare oder schreibt mir: E-Mail

    Happy Stacking! 🔬📸✨

  • Mein Paperless-NGX Backup-Konzept: Automatisiert, sicher und zuverlässig

    TL;DR

    Automatisches Backup-System für Paperless-NGX mit täglichen Backups, wöchentlicher Rotation und optionalem Offsite-Transfer. Alles läuft via Cron-Job und einem simplen Bash-Script.

    Warum überhaupt Backups?

    Paperless-NGX ist großartig – alle Dokumente digitalisiert, durchsuchbar und ordentlich organisiert. Aber was passiert, wenn die Festplatte den Geist aufgibt, ein Update schiefgeht oder man sich aus Versehen die Datenbank zerschießt? Genau: Game Over. 

    Deshalb habe ich mir ein Backup-Konzept gebaut, das automatisch läuft und mich nachts ruhig schlafen lässt.

    Das Konzept

    Die Anforderungen

    • Automatisch: Kein manuelles Eingreifen nötig
    • Vollständig: Datenbank, Medien und Konfiguration
    • Versioniert: Mehrere Backup-Generationen
    • Offsite-fähig: Optional auf externen Speicher (NAS, Cloud, etc.)
    • Ressourcenschonend: Läuft nachts, wenn eh nichts los ist

    Die Backup-Strategie

    Ich setze auf ein 3-2-1-Prinzip (oder zumindest so nah wie möglich dran):

    • 3 Kopien der Daten (Produktiv + 2 Backups)
    • 2 verschiedene Medien (lokale Festplatte + NAS/Cloud)
    • 1 Offsite-Kopie (außerhalb des Servers)

    Backup-Rotation

    • Täglich: Die letzten 7 Tage
    • Wöchentlich: Die letzten 4 Wochen
    • Optional: Monatliche Backups für längere Aufbewahrung

    Das Backup-Script

    Das Script macht folgendes:

    1. Stoppt Paperless-Container (optional, für konsistente Backups)
    2. Exportiert die PostgreSQL-Datenbank
    3. Packt alles zusammen: DB-Dump, Media-Files, Config
    4. Erstellt ein komprimiertes Archiv mit Timestamp
    5. Startet Paperless wieder
    6. Löscht alte Backups nach Rotation-Schema
    7. Optional: Sync zu externem Speicher
    #!/bin/bash
    
    ###############################################################################
    # Paperless-NGX Backup Script
    # Erstellt vollständige Backups inkl. Datenbank, Medien und Konfiguration
    ###############################################################################
    
    # Konfiguration
    PAPERLESS_DIR="/opt/paperless-ngx"
    BACKUP_DIR="/mnt/backups/paperless"
    COMPOSE_FILE="$PAPERLESS_DIR/docker-compose.yml"
    RETENTION_DAYS=7
    RETENTION_WEEKS=4
    
    # Optional: Offsite Backup (NAS, rsync, rclone, etc.)
    OFFSITE_ENABLED=false
    OFFSITE_DEST="/mnt/nas/backups/paperless"
    
    # Timestamp für Backup
    TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
    BACKUP_NAME="paperless_backup_$TIMESTAMP"
    TEMP_DIR="/tmp/$BACKUP_NAME"
    
    # Logging
    LOG_FILE="$BACKUP_DIR/backup.log"
    
    log() {
        echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
    }
    
    # Backup-Verzeichnis erstellen
    mkdir -p "$BACKUP_DIR"
    mkdir -p "$TEMP_DIR"
    
    log "=== Paperless-NGX Backup gestartet ==="
    
    # 1. Container stoppen (optional - für konsistente Backups)
    log "Stoppe Paperless-Container..."
    cd "$PAPERLESS_DIR" || exit 1
    docker compose -f "$COMPOSE_FILE" stop webserver
    
    # 2. Datenbank exportieren
    log "Exportiere PostgreSQL-Datenbank..."
    docker compose -f "$COMPOSE_FILE" exec -T db pg_dump -U paperless paperless > "$TEMP_DIR/database.sql"
    
    if [ $? -eq 0 ]; then
        log "Datenbank-Export erfolgreich"
    else
        log "FEHLER: Datenbank-Export fehlgeschlagen!"
        docker compose -f "$COMPOSE_FILE" start webserver
        exit 1
    fi
    
    # 3. Media-Files kopieren
    log "Kopiere Media-Files..."
    cp -r "$PAPERLESS_DIR/media" "$TEMP_DIR/"
    cp -r "$PAPERLESS_DIR/data" "$TEMP_DIR/"
    
    # 4. Konfiguration sichern
    log "Sichere Konfiguration..."
    cp "$PAPERLESS_DIR/docker-compose.yml" "$TEMP_DIR/"
    cp "$PAPERLESS_DIR/.env" "$TEMP_DIR/" 2>/dev/null || log "Keine .env Datei gefunden"
    
    # 5. Container wieder starten
    log "Starte Paperless-Container..."
    docker compose -f "$COMPOSE_FILE" start webserver
    
    # 6. Backup komprimieren
    log "Komprimiere Backup..."
    cd /tmp || exit 1
    tar -czf "$BACKUP_DIR/$BACKUP_NAME.tar.gz" "$BACKUP_NAME"
    
    if [ $? -eq 0 ]; then
        log "Backup erfolgreich erstellt: $BACKUP_NAME.tar.gz"
        BACKUP_SIZE=$(du -h "$BACKUP_DIR/$BACKUP_NAME.tar.gz" | cut -f1)
        log "Backup-Größe: $BACKUP_SIZE"
    else
        log "FEHLER: Backup-Komprimierung fehlgeschlagen!"
        exit 1
    fi
    
    # Temp-Verzeichnis aufräumen
    rm -rf "$TEMP_DIR"
    
    # 7. Alte Backups löschen (Rotation)
    log "Lösche alte Backups..."
    
    # Täglich: Behalte letzte 7 Tage
    find "$BACKUP_DIR" -name "paperless_backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete
    
    # Optional: Wöchentliche Backups behalten
    # Hier könnte man zusätzliche Logik einbauen für wöchentliche/monatliche Backups
    
    log "Alte Backups bereinigt"
    
    # 8. Optional: Offsite-Backup
    if [ "$OFFSITE_ENABLED" = true ]; then
        log "Starte Offsite-Backup..."
        
        mkdir -p "$OFFSITE_DEST"
        
        # Beispiel mit rsync (anpassen je nach Setup)
        rsync -avz --delete "$BACKUP_DIR/" "$OFFSITE_DEST/"
        
        # Alternativ mit rclone (z.B. für Cloud-Storage):
        # rclone sync "$BACKUP_DIR" "remote:paperless-backups"
        
        if [ $? -eq 0 ]; then
            log "Offsite-Backup erfolgreich"
        else
            log "WARNUNG: Offsite-Backup fehlgeschlagen!"
        fi
    fi
    
    # Backup-Statistik
    BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/paperless_backup_*.tar.gz 2>/dev/null | wc -l)
    log "Anzahl vorhandener Backups: $BACKUP_COUNT"
    
    log "=== Backup abgeschlossen ==="
    
    # Optional: Benachrichtigung senden (z.B. via ntfy, gotify, email, etc.)
    # curl -d "Paperless Backup erfolgreich: $BACKUP_SIZE" ntfy.sh/mein-paperless-backup
    Code-Sprache: PHP (php)

    Der Cron-Job

    Um das Backup automatisch laufen zu lassen, richte ich einen Cron-Job ein:

    # Crontab bearbeiten
    sudo crontab -e
    Code-Sprache: PHP (php)

    Eintrag für tägliches Backup um 3 Uhr nachts:

    # Paperless-NGX Backup - täglich um 3:00 Uhr
    0 3 * * * /opt/paperless-ngx/scripts/backup.sh >> /mnt/backups/paperless/backup.log 2>&1
    Code-Sprache: PHP (php)

    Alternative Cron-Schedules

    # Alle 6 Stunden
    0 */6 * * * /opt/paperless-ngx/scripts/backup.sh
    
    # Jeden Sonntag um 2 Uhr (wöchentlich)
    0 2 * * 0 /opt/paperless-ngx/scripts/backup.sh
    
    # Werktags um 1 Uhr nachts
    0 1 * * 1-5 /opt/paperless-ngx/scripts/backup.sh
    Code-Sprache: PHP (php)

    Installation & Setup

    1. Script vorbereiten

    # Verzeichnis für Scripts erstellen
    sudo mkdir -p /opt/paperless-ngx/scripts
    
    # Script erstellen
    sudo nano /opt/paperless-ngx/scripts/backup.sh
    
    # Script ausfü
    Code-Sprache: PHP (php)
  • AI Office. Wie ich mein Dokumentenarchiv mit Paperless und KI erweitert habe

    Papier ist selten das eigentliche Problem. Das Problem ist das Wiederfinden.

    Rechnungen. Verträge. Bescheide. Sitzungsunterlagen. Alles wird heute zwar digital abgelegt, aber die klassische Dateisuche bleibt mühsam. Man erinnert sich selten an Dateinamen. Man erinnert sich an Inhalte.

    Genau hier setzt mein Projekt an. Ein persönliches AI Office.

    Die Basis bildet Paperless ngx. Darauf läuft eine zusätzliche KI Ebene, die Dokumente automatisch analysiert, zusammenfasst, verschlagwortet und für semantische Suche vorbereitet. Alles läuft auf eigener Infrastruktur.


    Ziel
    Dokumente nicht mehr suchen. Dokumente fragen.


    Architektur

    Das System besteht aus wenigen klar getrennten Komponenten.

    Scanner oder Upload
    → Paperless ngx
    → Dokumentexport über API
    → KI Verarbeitung
    → Vektor Datenbank
    → RAG API
    → Fragen und Antworten

    Das bedeutet konkret

    Paperless übernimmt

    OCR
    Dokumentenverwaltung
    Originaldateien
    Metadaten

    Die KI übernimmt

    Zusammenfassungen
    automatische Tags
    semantische Suche
    Fragen über Dokumente

    Das Ergebnis ist ein Dokumentenarchiv, das nicht nur speichert, sondern Inhalte versteht.


    Paperless als Grundlage

    Paperless ngx ist ein Dokumentenmanagementsystem für selbstgehostete Umgebungen. Dokumente werden automatisch importiert, per OCR erkannt und archiviert.

    Ein typisches Docker Setup sieht zum Beispiel so aus.

    docker compose

    version: "3.8"
    
    services:
    
      redis:
        image: redis:7
        restart: unless-stopped
    
      db:
        image: postgres:15
        restart: unless-stopped
        environment:
          POSTGRES_DB: paperless
          POSTGRES_USER: paperless
          POSTGRES_PASSWORD: paperless
    
      webserver:
        image: ghcr.io/paperless-ngx/paperless-ngx:latest
        restart: unless-stopped
        depends_on:
          - redis
          - db
        ports:
          - 8000:8000
        environment:
          PAPERLESS_REDIS: redis://redis:6379
          PAPERLESS_DBHOST: db
          PAPERLESS_DBNAME: paperless
          PAPERLESS_DBUSER: paperless
          PAPERLESS_DBPASS: paperless
        volumes:
          - ./data:/usr/src/paperless/data
          - ./media:/usr/src/paperless/media
          - ./consume:/usr/src/paperless/consume
    Code-Sprache: JavaScript (javascript)

    Der consume Ordner ist dabei entscheidend. Alles was dort abgelegt wird, wird automatisch von Paperless importiert.

    Scanner, Mailregeln oder Automationen können Dokumente dort einfach ablegen.


    Zugriff auf Dokumentinhalte

    Für die KI Verarbeitung müssen die OCR Texte der Dokumente verfügbar sein. Paperless stellt dafür eine API bereit.

    Ein einfaches Python Beispiel zeigt, wie Dokumente abgefragt werden können.

    import requests
    
    URL = "http://paperless:8000/api/documents/"
    TOKEN = "API_TOKEN"
    
    headers = {
        "Authorization": f"Token {TOKEN}"
    }
    
    r = requests.get(URL, headers=headers)
    
    for doc in r.json()["results"]:
        print(doc["title"], doc["content"])
    Code-Sprache: JavaScript (javascript)

    Das Feld content enthält den vollständigen OCR Text eines Dokuments. Genau dieser Text wird später von der KI analysiert.


    Konfiguration der KI Umgebung

    Alle zentralen Einstellungen werden in einer Konfigurationsdatei zusammengefasst.

    config/settings.py

    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    PAPERLESS_URL = os.getenv("PAPERLESS_URL")
    PAPERLESS_TOKEN = os.getenv("PAPERLESS_TOKEN")
    
    OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
    
    CHROMA_PATH = os.getenv("CHROMA_PATH", "./vector_db")
    
    EMBEDDING_MODEL = "text-embedding-3-small"
    CHAT_MODEL = "gpt-4o-mini"
    Code-Sprache: JavaScript (javascript)

    Die sensiblen Werte liegen in einer .env Datei.

    Beispiel

    PAPERLESS_URL=http://paperless:8000
    PAPERLESS_TOKEN=
    
    OPENAI_API_KEY=
    
    CHROMA_PATH=./vector_db
    Code-Sprache: JavaScript (javascript)

    Dokumente für semantische Suche vorbereiten

    Der erste KI Schritt ist die Erstellung eines semantischen Indexes.

    Dabei werden Dokumente in kleinere Textabschnitte zerlegt und für jeden Abschnitt sogenannte Embeddings erzeugt. Diese numerischen Vektoren repräsentieren die Bedeutung des Textes.

    scripts/index_documents.py

    from openai import OpenAI
    import chromadb
    from scripts.export_documents import get_documents
    from config.settings import *
    
    client = OpenAI(api_key=OPENAI_API_KEY)
    
    chroma = chromadb.PersistentClient(path=CHROMA_PATH)
    
    collection = chroma.get_or_create_collection("documents")
    
    
    def split_text(text, size=500):
    
        words = text.split()
    
        for i in range(0, len(words), size):
            yield " ".join(words[i:i+size])
    
    
    def index_document(doc):
    
        text = doc["content"]
    
        chunks = list(split_text(text))
    
        for i, chunk in enumerate(chunks):
    
            emb = client.embeddings.create(
                model=EMBEDDING_MODEL,
                input=chunk
            )
    
            collection.add(
                ids=[f"{doc['id']}_{i}"],
                documents=[chunk],
                embeddings=[emb.data[0].embedding]
            )
    Code-Sprache: JavaScript (javascript)

    Die erzeugten Vektoren werden in einer lokalen Vektor Datenbank gespeichert.

    Ein einfacher Ordner genügt.

    mkdir vector_db
    

    Automatische Zusammenfassungen

    Beim Import eines neuen Dokuments kann automatisch eine Kurzfassung erzeugt werden.

    scripts/summarize.py

    from openai import OpenAI
    from config.settings import *
    
    client = OpenAI(api_key=OPENAI_API_KEY)
    
    
    def summarize(text):
    
        prompt = f"""
        Fasse dieses Dokument in drei Sätzen zusammen.
    
        {text}
        """
    
        r = client.chat.completions.create(
            model=CHAT_MODEL,
            messages=[{"role":"user","content":prompt}]
        )
    
        return r.choices[0].message.content
    Code-Sprache: PHP (php)

    Die Zusammenfassung kann später direkt im Dokument angezeigt werden.


    Automatisches Tagging

    Neben der Zusammenfassung werden auch Tags automatisch generiert.

    scripts/auto_tag.py

    from openai import OpenAI
    from config.settings import *
    
    client = OpenAI(api_key=OPENAI_API_KEY)
    
    def generate_tags(text):
    
        prompt = f"""
        Analysiere das Dokument und gib maximal fünf Tags zurück.
        Nur einzelne Wörter.
    
        {text}
        """
    
        r = client.chat.completions.create(
            model=CHAT_MODEL,
            messages=[{"role":"user","content":prompt}]
        )
    
        tags = r.choices[0].message.content
    
        return [t.strip() for t in tags.split("\n") if t.strip()]
    Code-Sprache: PHP (php)

    Diese Tags können anschließend automatisch über die Paperless API im Dokument gespeichert werden.

    Damit entsteht mit der Zeit ein sauber strukturiertes Archiv.


    Dokumente über Fragen durchsuchen

    Der interessanteste Teil ist die RAG API.

    RAG steht für Retrieval Augmented Generation.

    Zuerst werden passende Dokumentabschnitte gefunden.
    Dann erzeugt das Sprachmodell daraus eine Antwort.

    api/rag_api.py

    from fastapi import FastAPI
    import chromadb
    from openai import OpenAI
    from config.settings import *
    
    app = FastAPI()
    
    client = OpenAI(api_key=OPENAI_API_KEY)
    
    chroma = chromadb.PersistentClient(path=CHROMA_PATH)
    
    collection = chroma.get_collection("documents")
    
    
    @app.get("/ask")
    
    def ask(q: str):
    
        emb = client.embeddings.create(
            model=EMBEDDING_MODEL,
            input=q
        )
    
        results = collection.query(
            query_embeddings=[emb.data[0].embedding],
            n_results=5
        )
    
        context = "\n".join(results["documents"][0])
    
        prompt = f"""
        Nutze diese Dokumente um die Frage zu beantworten.
    
        {context}
    
        Frage: {q}
        """
    
        answer = client.chat.completions.create(
            model=CHAT_MODEL,
            messages=[{"role":"user","content":prompt}]
        )
    
        return {
            "answer": answer.choices[0].message.content
        }
    Code-Sprache: PHP (php)

    Die API kann anschließend gestartet werden.

    uvicorn api.rag_api:app --host 0.0.0.0 --port 9000
    Code-Sprache: CSS (css)

    Automatische Aktualisierung

    Der Dokumentindex sollte regelmäßig aktualisiert werden. Eine einfache Cron Aufgabe reicht aus.

    0 3 * * * python3 /srv/paperless_ai/scripts/index_documents.py
    

    Damit wird jede Nacht der semantische Index aktualisiert.


    Ergebnis

    Nach kurzer Zeit entsteht ein intelligentes Dokumentarchiv.

    Dokumente werden automatisch

    importiert
    analysiert
    zusammengefasst
    getaggt
    semantisch indexiert

    Danach können Fragen gestellt werden wie

    Welche Rechnungen habe ich vom Energieversorger
    Wann war die letzte Heizungswartung
    Welche Dokumente betreffen meine Gebäudeversicherung

    Die KI durchsucht automatisch alle Dokumente und liefert die Antwort.


    Fazit

    Paperless löst das Archivierungsproblem.

    KI löst das Informationsproblem.

    Die Kombination macht aus einem klassischen Dokumentenarchiv ein persönliches Wissenssystem.

    Dokumente liegen nicht mehr einfach nur im Speicher.
    Sie werden verstanden, strukturiert und jederzeit abrufbar.

    Ein echtes AI Office.

  • OpenClaw im Homelab: Ein (teures) Experiment mit autonomen AI-Agents

    Oder: Wie ich $10 bei Grok verbrannte und lernte, dass AI-Agents nichts für arme Homelabber sind


    TL;DR

    • ✅ OpenClaw installiert auf Proxmox CT
    • ❌ $10 bei Grok in wenigen Stunden verbrannt
    • ⚠️ Gemini Free Tier (1.500 req/Tag) reicht nicht für autonome Agents
    • 💡 Fazit: Für Homelab zu teuer, besser On-Demand nutzen

    Die Idee: Ein autonomer AI-Agent für mein Homelab

    Ich betreibe ein zweistufiges Proxmox-Cluster mit 15+ Services – von Paperless-NGX über OpenHAB bis hin zu einem kompletten AI Office Stack. Als ich von OpenClaw hörte (einem Open-Source autonomen AI-Agent), dachte ich: „Perfekt! Das könnte meine Infrastruktur noch smarter machen!“

    Die Vision:

    • Automatische Log-Analysen
    • Smart Home Automationen via Natural Language
    • Dokumenten-Zusammenfassungen aus Paperless
    • Proaktive System-Überwachung

    Klingt cool, oder? Spoiler: Ist es auch – aber teuer.


    Die Installation: Überraschend einfach

    OpenClaw lässt sich mit einem One-Liner installieren:

    curl -fsSL https://openclaw.ai/install.sh | bash
    Code-Sprache: JavaScript (javascript)

    Ich habe einen neuen LXC Container (CT 115) auf meinem stärkeren Proxmox-Node (pve2) erstellt:

    Specs:

    • CPU: 4 Cores (i5-7500)
    • RAM: 4 GB
    • Storage: 50 GB
    • OS: Ubuntu 24.04

    Die Installation lief durch, OpenClaw startete drei Prozesse:

    • openclaw-gateway – WebSocket Gateway
    • openclaw – Haupt-Agent
    • openclaw-models – LLM Integration

    Soweit, so gut! ✅


    Der erste Schock: Token-Verbrauch

    OpenClaw zeigte mir in der TUI:

    tokens 36k/1.0m (3%)
    

    36.000 Tokens pro Request?! Zum Vergleich: Ein normaler Chat mit ChatGPT verbraucht 100-500 Tokens.

    Warum so viel?

    OpenClaw sendet bei jedem Request:

    • Komplette Conversation History
    • Agent Memory
    • Workspace Context 
    • System Prompts
    • Subagent-Contexts

    Meine Config war schuld:

    {
      "maxConcurrent": 4,
      "subagents": {
        "maxConcurrent": 8
      },
      "compaction": {
        "mode": "safeguard"  // Behält viel Context
      }
    }
    Code-Sprache: JSON / JSON mit Kommentaren (json)

    Pro Task also potenziell 12 parallele Requests mit je 36k Tokens = 432k Tokens auf einmal! 💸


    Der zweite Schock: Die Grok-Rechnung

    Ich hatte während des Setups X.AI Grok 4 als LLM konfiguriert. Das Pricing:

    Input:  $5.00 / 1M tokens
    Output: $15.00 / 1M tokens
    Code-Sprache: HTTP (http)

    Nach ein paar Test-Sessions und etwas Debugging: $10 verbrannt.

    Die Rechnung:

    ~3-4 komplexe Agent-Tasks mit Subagents
    = ~40-50 Requests à 36k tokens
    = ~2M Input + 500k Output tokens
    = $10 + $7.50 = $17.50
    
    Mein Credit-Guthaben: $10 → aufgebraucht 😱
    

    Grok ist 28x teurer als Gemini Flash für die gleiche Arbeit!


    Der Umzug zu Gemini: Free Tier als Rettung?

    Ich schwenkte schnell auf Google Gemini 1.5 Flash um:

    Input:  $0.075 / 1M tokens (67x günstiger!)
    Output: $0.30 / 1M tokens (50x günstiger!)
    
    Free Tier: 1.500 Requests/Tag
    Code-Sprache: HTTP (http)

    Das klang nach der Lösung! Aber…

    Problem: Setup-Phase frisst das Limit

    Am ersten Tag mit OpenClaw:

    • Onboarding Wizard: ~500 Requests
    • Config-Tests: ~300 Requests 
    • Meine Experimente: ~700 Requests
    • Total: ~1.500 Requests = Limit erreicht ⚠️

    Und das war nur ein Tag mit aktivem Testen!


    Die Erkenntnis: Autonome Agents sind teuer

    Warum OpenClaw so viele Requests verbraucht:

    1. Subagents spawnen wie verrückt

    Ein Task: "Check my emails and summarize"
    
    Spawnt:
    ├─ Subagent 1: Email abrufen
    ├─ Subagent 2: Inhalte analysieren
    ├─ Subagent 3: Zusammenfassung erstellen
    └─ Subagent 4: Formatierung
    
    = 4-5 API Calls statt 1
    Code-Sprache: JavaScript (javascript)

    2. Lange Context History

    OpenClaw behält ALLES im Context:
    - Alle bisherigen Messages
    - Agent Memory
    - Workspace Files
    - System State
    
    → 36k tokens pro Request
    → Schnell teuer bei vielen Requests
    

    3. 24/7 Betrieb

    Autonome Agents laufen kontinuierlich:
    - Background Monitoring
    - Scheduled Tasks
    - Proaktive Checks
    - Auto-Responses
    
    → 2.000+ Requests/Tag
    → Free Tier reicht nicht
    

    Kostenvergleich: Was wäre wenn…?

    Szenario: 100 Requests/Tag (moderate Nutzung)

    ProviderModelMonatFaktor
    GoogleGemini Flash$0.811x
    OpenAIGPT-4o Mini$1.622x
    AnthropicHaiku$8.6411x
    X.AIGrok 4$22.5028x

    Mit meiner unoptimierter Config (36k tokens/Request):

    ProviderMonatJahr
    Gemini$17$204
    Grok$990$11.880 💀

    Grok wäre teurer als meine komplette Homelab-Stromrechnung!


    Optimierungsversuche

    Config-Tuning

    Ich versuchte, den Token-Verbrauch zu reduzieren:

    {
      "agents": {
        "defaults": {
          "compaction": {
            "mode": "aggressive",  // Statt "safeguard"
            "maxTokens": 8000
          },
          "maxConcurrent": 1,      // Statt 4
          "subagents": {
            "maxConcurrent": 1     // Statt 8
          }
        }
      }
    }
    Code-Sprache: JSON / JSON mit Kommentaren (json)

    Problem: OpenClaw überschrieb meine Config immer wieder!

    Die Lösung: Config-Commands nutzen statt manuelles Editieren:

    openclaw config set agents.defaults.maxConcurrent 1
    Code-Sprache: CSS (css)

    Aber selbst dann: OpenClaw hat keine granularen Token-Limits wie ich sie wollte.


    Die Alternativen-Recherche

    1. Lokales Ollama?

    Meine Hardware:

    • Intel i5-7500 (keine GPU)
    • 31 GB RAM

    Ollama Performance:

    • Llama 3.1 8B: 20-30 Sekunden pro Response 🐌
    • Bei Subagents: 60-90 Sekunden Wartezeit

    Fazit: ❌ Zu langsam für einen autonomen Agent

    2. Hugging Face Inference API?

    Free Tier:

    • ~30.000 tokens/Tag
    • ~100 req/Stunde

    Probleme:

    • ❌ Keine native OpenClaw-Integration
    • ❌ Schlechtere Tool-Use Fähigkeiten
    • ❌ Instabil (Shared Infrastructure)
    • ❌ Rate Limits zu niedrig

    Fazit: ⚠️ Zu viel Aufwand für schlechteres Ergebnis

    3. Gemini Free Tier richtig nutzen?

    Das könnte funktionieren, WENN:

    • ✅ Nur On-Demand statt 24/7
    • ✅ Optimierte Config (1+1 concurrent)
    • ✅ Web Search deaktiviert
    • ✅ Keine autonomen Background-Tasks

    Realistische Nutzung im Free Tier:

    Morgens:   10 Requests (Email-Check, News)
    Tagsüber:  20 Requests (Queries, Automationen)
    Abends:    10 Requests (Reports)
    ────────────────────────────────────
    Total:     40 Requests/Tag ✅
    
    → Unter 1.500 Limit
    → Kostenlos!
    

    Hardware-Überlegung: DS916+ statt Proxmox?

    Ich fragte mich: Sollte OpenClaw auf meiner Synology DS916+ laufen statt auf pve2?

    DS916+ Specs:

    • CPU: Celeron N3060 (2 Cores @ 1.6 GHz)
    • RAM: 8 GB
    • Storage: 16.8 TB (!)

    Pro:

    • ✅ Unbegrenzter Storage (vs. 157 GB auf pve2)
    • ✅ Läuft eh 24/7 (keine Extra-Stromkosten)
    • ✅ Native Backup-Integration

    Contra:

    • ❌ CPU 2x schwächer als pve2
    • ❌ Weniger RAM (8 vs. 31 GB)
    • ❌ HDD statt NVMe (10-50x langsamer)

    Fazit: Hybrid-Ansatz ist optimal:

    • Runtime auf pve2 (schnelle CPU/RAM)
    • Workspace/Logs auf DS916+ (viel Storage)

    Mein Fazit nach 24 Stunden

    Was ich gelernt habe:

    1. Autonome AI-Agents sind (noch) zu teuer für Homelab

    Für $10-30/Monat könnte ich:

    • Einen VPS mieten
    • Mehr Storage kaufen
    • Meine Stromrechnung bezahlen
    • 10 Monate Netflix haben

    2. Token-Verbrauch wird unterschätzt

    36k tokens/Request klingt abstrakt, aber:

    • Bei 100 Requests/Tag = 3.6M tokens
    • Bei Gemini: $1/Tag = $30/Monat
    • Bei Grok: $18/Tag = $540/Monat 💀

    3. Free Tiers sind für Testing, nicht Production

    Gemini Free (1.5k/Tag) reicht für:

    • ✅ Manuelle Queries
    • ✅ Gelegentliche Automationen
    • ❌ 24/7 autonome Agents

    4. OpenClaw ist cool, aber…

    …nicht für Homelab-Budgets optimiert:

    • Keine granularen Cost-Controls
    • Keine Token-Budgets pro Agent
    • Subagents spawnen unkontrolliert
    • Context-Compaction zu konservativ

    Was ich jetzt mache

    Option A: On-Demand statt 24/7

    Statt OpenClaw kontinuierlich laufen zu lassen:

    # Nur starten wenn ich es brauche:
    openclaw gateway
    
    # Spezifische Tasks:
    openclaw agent --message "Fasse Paperless-Dokumente zusammen"
    openclaw agent --message "Analysiere OpenHAB Logs"
    
    # Dann wieder stoppen:
    pkill -f openclaw
    Code-Sprache: PHP (php)

    Kosten: $0/Monat (im Free Tier)

    Option B: Einfach lassen

    • OpenClaw installiert lassen
    • Nicht aktiv nutzen
    • Falls ich mal Bock habe: Da
    • Kostet nichts wenn nicht genutzt

    Option C: Deinstallieren

    openclaw uninstall
    npm uninstall -g openclaw
    rm -rf ~/.openclaw
    pct destroy 115  # CT in Proxmox löschen
    Code-Sprache: PHP (php)

    Aktuell: Tendiere zu Option B – installiert lassen, aber nicht aktiv nutzen.


    Empfehlungen für andere Homelabber

    OpenClaw macht Sinn wenn:

    ✅ Du ein Business-Budget hast ($50-100/Monat OK)
    ✅ Du wirklich 24/7 Automationen brauchst
    ✅ Du bereit bist, Paid Tiers zu nutzen
    ✅ Du die Kosten monitoren und kontrollieren kannst 

    Besser für Homelab:

    ✅ Paperless-NGX + RAG (nur bezahlen wenn du fragst)
    ✅ OpenHAB Automationen (keine Cloud-Kosten)
    ✅ Cron-Jobs + Scripts (kostenlos, zuverlässig)
    ✅ Gemini Free für manuelle Queries (1.5k/Tag reicht) 


    Alternativen zu OpenClaw

    Wenn du AI im Homelab willst, aber nicht $30/Monat ausgeben:

    1. Paperless-NGX + LLM-Integration

    Kosten: $1-3/Monat
    Use Case: Dokumente durchsuchbar + AI-Queries
    On-Demand: Nur bezahlen wenn du fragst
    Code-Sprache: PHP (php)

    2. Home Assistant + AI Voice

    Kosten: $0 (lokal) oder $5/Monat (Cloud)
    Use Case: Smart Home Steuerung
    Besser integriert als OpenClaw
    Code-Sprache: PHP (php)

    3. n8n + AI Nodes

    Kosten: $0 (self-hosted)
    Use Case: Workflow-Automationen mit AI
    Mehr Kontrolle über API Calls
    Code-Sprache: PHP (php)

    4. Ollama für Batch-Jobs

    Kosten: $0 (nur Strom)
    Use Case: Nächtliche Analysen
    Egal ob 30 Sek oder 5 Min
    Code-Sprache: PHP (php)

    Die harte Wahrheit

    Autonome AI-Agents sind die Zukunft – aber noch nicht für Homelab-Budgets.

    Die Technologie ist da, aber:

    • Token-Preise sind für Business optimiert
    • Free Tiers sind zu limitiert für 24/7 Betrieb
    • Lokale LLMs (ohne GPU) sind zu langsam
    • Cost-Controls fehlen in den Tools

    Meine Prognose:

    • In 2-3 Jahren: Günstigere/effizientere Models
    • Bessere lokale LLMs (NPU-Support)
    • Tools mit eingebauten Cost-Controls
    • Dann wird es für Homelab interessant

    Bis dahin: On-Demand > Autonomous


    Ressourcen & Links


    Kommentare & Diskussion

    Habt ihr ähnliche Erfahrungen mit AI-Agents im Homelab? Wie managed ihr die Kosten?

    Teilt eure Setups in den Kommentaren! 👇


    Geschrieben am 13. März 2026 nach 24 Stunden OpenClaw-Experiment

    Tags: #homelab #ai #openclaw #llm #kostenoptimierung #proxmox #selfhosted