- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Enthält download_smd.py (Capture/Crop/OCR + Parallel-Worker), die Tkinter-GUI, tag_scores.py (Besetzung/Tonumfang via Audiveris+music21) und Hilfsskripte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
| .gitignore | ||
| download_smd.py | ||
| download_smd_gui.py | ||
| inspect_noteflight.py | ||
| README.md | ||
| requirements.txt | ||
| sheetmusicdirect-downloader.desktop | ||
| tag_scores.py | ||
SheetMusicDirect Downloader
Lädt Noten von sheetmusicdirect.com per Selenium-Screenshots, baut daraus ein PDF und optional einen Tesseract-Textlayer sowie MusicXML (Audiveris).
Voraussetzung: Du hast die Rechte am Material (z.B. Abo, gekauftes Stück, eigene Veröffentlichungen). Sheet-Music-Direct verbietet den Download außerhalb der eigenen App — dieses Tool ist für die persönliche, autorisierte Nutzung gedacht.
Komponenten
| Datei | Zweck |
|---|---|
download_smd.py |
CLI-Hauptscript (Selenium + PDF + OCR/OMR) |
download_smd_gui.py |
Tkinter-GUI als Wrapper um das CLI-Script |
sheetmusicdirect-downloader.desktop |
KDE-Menüeintrag |
requirements.txt |
Python-Dependencies |
.venv/ |
Python venv (lokal angelegt) |
.chrome-profile/ |
Persistentes Chromium-Profil (Cookies/Login) |
downloads/ |
Ausgabe-Verzeichnis pro Stück |
Installation
# Python-Dependencies
python -m venv .venv
.venv/bin/pip install -r requirements.txt
# System-Tools (Arch / CachyOS)
sudo pacman -S tk tesseract tesseract-data-deu tesseract-data-eng \
chromium libnotify
paru -S audiveris # optional, nur für MusicXML
# KDE-Menüeintrag
cp sheetmusicdirect-downloader.desktop ~/.local/share/applications/
tk ist für Tkinter (GUI). libnotify für Desktop-Notifications. audiveris ist optional — wenn nicht installiert, wird MusicXML-Export einfach übersprungen.
Verwendung
GUI
.venv/bin/python download_smd_gui.py
Oder über KDE-Menü („Multimedia → SheetMusicDirect Downloader").
Felder ausfüllen → Start → im Browser einloggen → „Login fertig → Weiter" klicken (Button wird aktiv, sobald das Script soweit ist). Live-Log unten zeigt Fortschritt. KDE-Notification kommt bei Cloudflare-Check und am Ende des Laufs.
Im Abschnitt Verhalten steuert „Bereits vorhandene Downloads überspringen" (default an), ob Stücke mit bereits existierender PDF erneut gezogen werden — praktisch zum Wiederaufsetzen abgebrochener Batch-Läufe.
CLI
Einzelnes Stück:
.venv/bin/python download_smd.py --no-omr \
--url "https://www.sheetmusicdirect.com/de-DE/se/ID_No/1255185/Product.aspx"
Ganze Kategorie/Suchergebnis (Batch):
.venv/bin/python download_smd.py --no-omr --list-url \
"https://www.sheetmusicdirect.com/de-DE/Search.aspx?query=Film+Choir&arr=108&page=1"
Ordnername wird automatisch aus <h1> / <title> der Produktseite abgeleitet (alles vor „VON/BY/BEI…" abgeschnitten, all-caps → Title-Case).
Workflow
Single-Mode
- Browser geht auf die Produktseite
- Du loggst dich ein und öffnest den Viewer (Vorschau / Anzeigen)
- Enter / „Weiter"
- Script navigiert direkt zur iframe-
src(Top-Frame, mehr Auflösung) - Bei Cloudflare-Wand → Pause + Desktop-Notification; nach 2 Min Auto-Resume, sobald die Challenge passiv durchgegangen ist
- Screenshot-Loop bis letzte Seite (
button.pager.page-rightdisabled oder zwei identische Bilder) - Pro Seite: Trim Footer → Auto-Crop → Margin
- PDF zusammenbauen → Tesseract-Layer → ggf. Audiveris-MusicXML
Batch-Mode (--list-url)
- Browser geht auf Listen-URL
- Login + Enter
- URL-Cache prüfen: liegt unter
downloads/.url-cache/<hash>.json(Hash der list-url). Wenn vorhanden → Schritt 4 überspringen. Mit--refresh-listerzwingt man das Neusammeln collect_product_urls()sammelt alleProduct.aspx-Links inkl. Pagination (URL?page=Nerhöhen, fallback auf next-Button / infinite scroll), Ergebnis wird in den Cache geschrieben- Für jede URL:
download_one()wie Single-Mode - Schon vorhandene PDFs werden übersprungen (idempotent, default; via
--no-skip-existingdeaktivierbar) → Abgebrochene Läufe sind so wiederaufnehmbar: Cache liefert die URL-Liste, Skip-Existing überspringt schon fertige Stücke - Stücke mit nur Noteflight-Player werden über den Noteflight-Pfad capturt (SVG-Screenshot pro Seite, Pfeiltasten-Navigation)
- Cloudflare-Pausen sind autonom überbrückbar → Batch-Lauf braucht keine Anwesenheit nach dem initialen Login
Wichtige CLI-Optionen
| Flag | Default | Wirkung |
|---|---|---|
--url URL |
— | Produktseite direkt öffnen (Single-Mode) |
--list-url URL |
— | Batch-Mode über Kategorie/Suchergebnis |
--max-list-pages N |
0 | Hartes Limit für Pagination-Iterationen; 0 = unbegrenzt (komplette Kategorie). Positiver Wert = Sperre (z.B. 200 ≈ ~5000 Songs) |
--margin N |
120 | Weißer Rand um jede Seite (Pixel) |
--viewport B,H,DPR |
2400,1800,2 | CDP-Render-Viewport (unabh. vom OS-Fenster) |
--window B,H |
1600,1200 | OS-Browserfenster-Startgröße |
--scale N |
2.0 | devicePixelRatio (Schärfe ↔ RAM) |
--no-crop |
— | Auto-Crop deaktivieren |
--no-trim-footer |
— | DRM-Subscription-Footer NICHT wegcroppen |
--no-hide |
— | Pager/Footer nicht verstecken (Debug) |
--no-ocr |
— | Tesseract-Textlayer überspringen |
--no-omr |
— | Audiveris-Export überspringen |
--no-skip-existing |
— | Bereits heruntergeladene Stücke nicht überspringen (default: skippen) |
--refresh-list |
— | URL-Cache ignorieren und Produkt-URLs neu sammeln (default: gecached aus downloads/.url-cache/) |
--no-stealth |
— | undetected-chromedriver deaktivieren, Standard-Selenium nutzen (default: Stealth an) |
--inspect |
— | DOM nach Login analysieren, Selektor-Kandidaten ausgeben |
Bildverarbeitung
Pro Screenshot in dieser Reihenfolge:
trim_drm_footer()— SMD brennt eine ZeileSheetMusicDirect.com Monthly Subscription for <Name>…direkt ins PDF. Heuristik: nach „Hintergrund-Lücke" in den unteren 400px suchen, oberhalb croppen.auto_crop()— pro Spalte/Zeile zählen, wieviele Pixel < 200 (dark) sind. Ränder ohne Inhalt werden weggeschnitten. Histogram-basiert, robust gegen Einzelpixel-Rauschen am Rand.add_margin()— weißer Rand drumherum (Default 120px).
Anti-Sabotage-Mechanik
SMD/ng2-pdf-viewer hat eingebaute Stolpersteine. Lösungen:
- Pager-Pfeile am Bildrand → per JS
opacity: 0versteckt (klickbar bleibt). Selenium-Klick umgangen viaarguments[0].click(), weilelement_to_be_clickableopacity:0 als „nicht klickbar" wertet. - Subscription-Footer im DOM → per Pattern-Match auf Klassennamen und Textinhalt versteckt.
- Subscription im PDF eingebrannt → durch
trim_drm_footer()aus dem Bild entfernt. - Kleiner iframe-Slot → Viewer wird direkt aufgerufen statt im iframe (Top-Frame, voller Browserrender).
- Render-Auflösung an OS-Fenstergröße gekoppelt → CDP
Emulation.setDeviceMetricsOverridevirtualisiert den Viewport auf 2400×1800 @ DPR 2 (4800×3600 px Screenshots) unabhängig vom echten Fenster. - Cloudflare-Wand bei zu vielen schnellen Requests → wird per DOM-/Text-Marker erkannt, Viewport-Override temporär aufgehoben (damit du die CF-Buttons siehst), Script pausiert + sendet KDE-Notification.
wait_for_cloudflare()pollt parallel auf stdin (User-Enter) und alle 5 s auf die Challenge selbst — sobald die Challenge weg ist und ≥ 120 s vergangen sind, läuft das Script autonom weiter (>>> Auto-Resume nach …s). Damit braucht ein Batch-Lauf keine ständige Anwesenheit; CF-Passive-Checks gehen i. d. R. von selbst durch. Bei manuellem Enter wird der Wait sofort beendet. - Häufige CF-Re-Challenges (alle ~10 Min) → standardmäßig nutzt der Driver
undetected-chromedriver: gepatchter Chromedriver ohnecdc_*-Marker, kein--test-type=webdriver-Flag,navigator.webdriver = false. SMD/Cloudflare flaggt die Session dadurch weniger aggressiv,cf_clearance-Cookie hält länger. Bei Bedarf via--no-stealthdeaktivierbar (Fallback auf Standard-Selenium). - Network-Errors / unvollständige Page-Loads →
navigate_with_retry()checkt nach jedemdriver.get()ob die Seite sauber kam (chrome-error://, ERR_*-Titel, fehlende SMD-Chrome, Stylesheet-Count). Bei Bruch reload mit Backoff (1 → 2 → 4 s, 3 Versuche). Nach CF-Pass nochmal Health-Check, falls SMD die echte Seite nicht sauber nachlädt.
Unterstützte Viewer
| Viewer | iframe-ID | Status |
|---|---|---|
| ng2-pdf-viewer (tools.sheetmusic.direct) | staticScoreViewer |
✅ voll unterstützt |
| Halleonard PDF-Preview | ifConsumerPreview |
✅ unterstützt (gleiche Engine) |
| Noteflight (interaktiv) | performanceProduct |
✅ unterstützt — SVG-Screenshot pro Seite, Pfeiltasten-Navigation, Print im JWT serverseitig deaktiviert (disablePrinting:true), daher kein freier PDF-Pfad |
Verzeichnisstruktur eines Ergebnisses
downloads/
└── Take Me Home, Country Roads (Arr. Roger Emerson)/
├── pages/
│ ├── page_001.png # gecropt + Margin
│ └── …
├── Take Me Home….pdf # Roh-PDF aus Bildern
├── Take Me Home….ocr.pdf # mit Tesseract-Textlayer
└── musicxml/ # Audiveris-Output (optional)
└── *.xml
Troubleshooting
| Symptom | Ursache / Fix |
|---|---|
TimeoutException beim ersten Capture |
Selektoren stimmen nicht — mit --inspect DOM analysieren |
| Nur eine Seite wird gespeichert | Next-Klick geht nicht durch → _dump_pager_state() läuft automatisch, Output prüfen |
| Crop entfernt nichts | Hintergrund zu dunkel oder Subscription-Text quer übers Bild → --no-trim-footer testweise aus |
| Bilder pixelig nach Fensterresize | --viewport aktivieren (Default tut das schon) |
| CF-Button nicht klickbar | Viewport-Override muss aufgehoben werden → passiert automatisch via wait_for_cloudflare() |
| Audiveris-Fehler „Could not export" | Erkennungsproblem bei komplexer Notation, nicht der Installation — .omr ist trotzdem da, manuell in Audiveris-GUI nachbearbeiten |
| GUI: Strg+A markiert nichts | Tk bindet das nicht von selbst — ist in der GUI manuell registriert |
Architektur-Hinweise
- Selenium 4 + Chromedriver (matched gegen Chromium). Persistentes Profil in
.chrome-profile/, damit Cookies erhalten bleiben. - CDP über
driver.execute_cdp_cmd()für Viewport-Override und Cleanup. element.screenshot()statt ganzer Viewport — schneidet den iframe-Container präzise aus.- GUI-Subprocess wird mit
python -u(unbuffered) gestartet. Stdout wird line-iterated und intk.Textgepiped. Stdin wird per"\n"getriggert für die Login-/CF-Pausen. Die GUI erkennt Auto-Resume per Log-Pattern (Auto-Resume nach/Enter empfangen) und deaktiviert den Weiter-Button wieder, damit ein nachträglicher Klick keinen spätereninput()-Aufruf abkürzt. - CF-Wait nicht-blockierend via
select.select([sys.stdin], …, timeout)— funktioniert sowohl mit TTY als auch Pipe-stdin (GUI-Modus), beide nehmen den gleichen Code-Pfad. - Notifications via
notify-send(libnotify) — KDE-konfigurierbar unter Anwendungsname „SheetMusicDirect Downloader".
Lizenz / Haftung
Dieses Tool ist ein persönliches Hilfsmittel. Verwende es nur für Inhalte, an denen du die Rechte hast (eigene Käufe, eigene Veröffentlichungen, autorisierte Backups). Massendownload-Aktionen, Weiterverteilung von Inhalten Dritter und Umgehung von Schutzmaßnahmen können gegen SMDs Nutzungsbedingungen und/oder Urheberrecht verstoßen.