diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..8a269d29a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": {} +}"ghcr.io/robbert229/devcontainer-features/operator-sdk:1": {} diff --git a/docs/audio_collect_command.md b/docs/audio_collect_command.md new file mode 100644 index 000000000..c419ab52e --- /dev/null +++ b/docs/audio_collect_command.md @@ -0,0 +1,122 @@ +# Audio-Sammelskript für Windows und macOS + +Dieses Repository enthält das PowerShell-Skript [`scripts/audio_collect.ps1`](../scripts/audio_collect.ps1), +mit dem Sie alle verfügbaren Laufwerke nach Audiodateien durchsuchen und diese in einen +zentralen Ordner `Audio_Quelle` kopieren können. + +## Voraussetzungen + +* Windows 10 oder neuer (PowerShell 5.1 oder PowerShell 7) **oder** macOS mit PowerShell 7. +* Lesezugriff auf die gewünschten Laufwerke (lokal oder Netzwerk). +* Schreibrechte für den Zielordner `Audio_Quelle` (standardmäßig im Benutzerprofil). + +### PowerShell auf macOS installieren + +macOS bringt PowerShell nicht standardmäßig mit. Installieren Sie zunächst Homebrew +und anschließend PowerShell: + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +brew install --cask powershell +``` + +Starten Sie PowerShell anschließend über `pwsh` im Terminal. + +## Verwendung + +1. Öffnen Sie eine PowerShell-Sitzung mit ausreichenden Berechtigungen. +2. Navigieren Sie in das Verzeichnis dieses Repositories. +3. Führen Sie das Skript mit folgendem Befehl aus: + + ```powershell + pwsh ./scripts/audio_collect.ps1 + ``` + + > Hinweis: Unter Windows PowerShell genügt `./scripts/audio_collect.ps1`. + + Beim ersten Aufruf empfiehlt sich der Zusatz `-WhatIf`, um eine Vorschau der + Aktionen zu erhalten: + + ```powershell + pwsh ./scripts/audio_collect.ps1 -WhatIf + ``` + +## Optionen + +Das Skript akzeptiert optionale Parameter, die Sie bei Bedarf anpassen können: + +```powershell +pwsh ./scripts/audio_collect.ps1 -Destination "D:\\MeinOrdner" -Extensions '*.mp3','*.wav' +``` + +* `-Destination` – Zielordner, in den alle gefundenen Dateien kopiert oder + verlinkt werden. Standard ist `%USERPROFILE%\Audio_Quelle`. +* `-Extensions` – Liste erlaubter Dateierweiterungen. Standardmäßig werden `mp3`, + `wav`, `aiff`, `flac`, `aac`, `ogg`, `wma` und `m4a` durchsucht. +* `-TransferMode` – legt fest, ob Dateien kopiert (`Copy`, Standard) oder als + Hardlink abgelegt werden (`HardLink`). + +## Besonderheiten auf dem MacBook und externen Laufwerken + +* Das Skript kopiert Dateien – es verschiebt nichts. Damit entspricht es der + Anforderung „vom MacBook 2011 soll nur kopiert werden“. +* Der Zielordner wird automatisch von der Suche ausgenommen. Wird also z. B. + `-Destination "/Volumes/T5 EVO/Audio_Quelle"` angegeben, durchsucht das Skript + diesen Pfad nicht erneut. +* macOS-Laufwerke werden über `/Volumes` erkannt. Alle dort eingebundenen Netzwerk- + oder USB-Volumes, die Sie im Finder sehen, fließen in die Suche ein. +* Hardlinks sind nur möglich, wenn Quelle und Ziel auf demselben Dateisystem + liegen. Viele externe SSDs mit exFAT unterstützen keine Hardlinks – in diesem + Fall fällt das Skript automatisch auf Kopieren zurück. + +## iPad und iPhone einbinden + +Apple erlaubt keinen direkten Dateizugriff auf iOS-Geräte wie bei einem USB-Stick. +So binden Sie dennoch Audiodateien ein: + +1. Öffnen Sie den Finder, wählen Sie Ihr iPhone oder iPad aus und aktivieren Sie + unter „Dateifreigabe“ die gewünschten Apps. Kopieren Sie deren Dateien in einen + lokalen Ordner (z. B. `~/Music/Import`). +2. Alternativ können Sie Tools wie [ifuse](https://github.com/libimobiledevice/ifuse) + nutzen (`brew install ifuse`), um das Gerät als FUSE-Volume nach `/Volumes/` + einzubinden. Das Skript durchsucht das eingebundene Volume anschließend wie ein + gewöhnliches Laufwerk. + +Sobald die Dateien lokal oder auf einem gemounteten Volume liegen, sammelt das +Skript sie im Zielordner `Audio_Quelle`. + +## Funktionsweise + +* Alle Dateisystemlaufwerke (`Get-PSDrive -PSProvider FileSystem`) sowie unter + macOS erkannte Volumes (`/Volumes/...`) werden rekursiv durchsucht. +* Verzeichnisse, deren Pfad `Ableton` enthält, werden ignoriert. +* Zielpfade erhalten bei Bedarf ein numerisches Suffix (z. B. `Datei (1).wav`), + sodass bestehende Dateien nicht überschrieben werden – unabhängig davon, ob sie + kopiert oder verlinkt werden. +* Im Modus `HardLink` wird für Dateien auf demselben Laufwerk ein Hardlink erstellt. + Liegt Quelle oder Ziel auf unterschiedlichen Laufwerken, fällt das Skript automatisch + auf Kopieren zurück und informiert über den Grund. + +## Hardlinks vs. Kopieren + +Hardlinks sind besonders dann hilfreich, wenn Programme wie Traktor oder Apple Music +auf denselben Datenträger zugreifen sollen: Die Musikdatei bleibt nur einmal vorhanden, +alle Hardlinks weisen auf dieselbe physische Datei. Beachten Sie jedoch: + +* Hardlinks sind nur innerhalb desselben Laufwerks/Volumes möglich. +* Netzwerkshares oder externe Laufwerke mit anderem Laufwerksbuchstaben bzw. + anderem Volume-Namen werden deshalb automatisch kopiert. +* Für Hardlinks sind die gleichen Berechtigungen erforderlich wie für gewöhnliche + Dateien. + +Wenn Sie sicherstellen möchten, dass eine Software stets Zugriff auf eine unabhängige +Dateikopie hat (z. B. zur Archivierung oder für Backups), verwenden Sie den +Standardmodus `Copy`. + +## Fehlersuche + +* Stellen Sie sicher, dass Sie die Ausführungsrichtlinie für Skripte ggf. mit + `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` angepasst haben. +* Nutzen Sie den Parameter `-Extensions`, falls Sie zusätzliche Audioformate durchsuchen möchten. +* Verwenden Sie `-Destination`, um den Zielordner auf ein externes Laufwerk oder einen Netzwerkspeicher zu legen. + Der Ordner wird bei der Suche übersprungen, um Endlosschleifen zu verhindern. diff --git a/docs/prompt_cleaning_prompt.md b/docs/prompt_cleaning_prompt.md new file mode 100644 index 000000000..170a0a8e0 --- /dev/null +++ b/docs/prompt_cleaning_prompt.md @@ -0,0 +1,59 @@ +# Prompt-Verbesserung: Bereinigung von Audio-Titeln + +## Ursprünglicher Prompt +> Ich möchte eine Bereinigung des Namens und Befreiung von Sonderzeichen und Buchstabensalaten und Zahlen in der Bezeichnung von Audio-Titeln. Es soll eigentlich nur der Titel herausgeschält werden. Und jegliche Tonart und jegliches TrackID und TrackZählen, was im Laufe der Zeit der Datei hinzugefügt wurde, kann gelöscht werden. So dass der Dateiname wieder klar ist. Meistens Track und Interpret. + +## Empfohlene Optimierungen +1. **Ziel und gewünschtes Ergebnis präzisieren** – Definiere klar, dass der bereinigte Dateiname ausschließlich aus „Interpret – Titel“ bestehen soll und nenne Beispiele zulässiger bzw. unerwünschter Elemente. +2. **Formatierungs- und Transformationsregeln strukturieren** – Formuliere Schritt-für-Schritt-Regeln (z. B. Entfernen von Tonarten, Tracknummern und Sonderzeichen), damit das Modell deterministisch vorgehen kann. +3. **Kontext zu Eingabeformat und gewünschten Ausgaben liefern** – Beschreibe das ursprüngliche Dateinamenformat, nenne potentielle Störfaktoren und gib vor, wie die bereinigten Namen auszugeben sind (z. B. Liste, JSON, Tabelle), um die Antwortqualität zu erhöhen. +4. **Beispiele für Vorher/Nachher liefern** – Zeige repräsentative Beispiele, damit das Modell das gewünschte Muster schneller erfasst und konsistente Ergebnisse liefert. + +## Varianten +### Variante A – Fokus auf strukturierte Automatisierung +```text +Du arbeitest als Dateibenennungs-Assistent. Bereinige eine Liste von Audio-Dateinamen so, dass ausschließlich das Muster „Interpret – Titel“ übrigbleibt. + +Vorgehen: +1. Entferne Präfixe wie Tracknummern (z. B. `01`, `A3`), Disc- oder Mix-Angaben sowie Suffixe wie Tonarten (`in G minor`, `Am`) oder Zählungen (`TrackID`, `Extended Mix`, `Remastered 2010`). +2. Lösche Sonderzeichen, doppelte Leerzeichen und überflüssige Klammern; ersetze `_` oder `.` durch Leerzeichen. +3. Standardisiere die Schreibweise auf „Interpret – Titel“ (Interpret zuerst, dann Titel, jeweils mit korrekter Groß-/Kleinschreibung). Fehlt einer der beiden Bestandteile, gib „Unbekannt“ für das fehlende Element an. + +Eingabe: Eine Liste von rohen Dateinamen. +Ausgabe: Eine Markdown-Tabelle mit den Spalten „Originalname“ und „Bereinigter Titel“. + +Beispiele: +- `01_artist-name_track-title (Extended Mix).mp3` → `Artist Name – Track Title` +- `B2 The Band - Song Name in F# major.wav` → `The Band – Song Name` +``` + +### Variante B – Fokus auf kreative Datenaufbereitung +```text +Handle als kreativer Metadaten-Kurator für eine Musikbibliothek. Isoliere aus jedem gelieferten Dateinamen ausschließlich den reinen Songtitel inklusive Interpret im Format „Interpret – Titel“. + +Regeln: +- Entferne alles, was nicht direkt zu Interpret oder Songtitel gehört: Tonarten, BPM, Veröffentlichungsjahre, Mix-/Remaster-Hinweise, Track-IDs, Dateiendungen. +- Bereinige Sonderzeichen, setze Leerzeichen korrekt und konvertiere Unterstriche in Leerzeichen. +- Bewahre legitime Bindestriche oder Klammern, wenn sie Teil des Titels sind (z. B. „Love – Live Version“, „(Acoustic)“). + +Liefere das Ergebnis als nummerierte Liste im Format `1. Interpret – Titel`. + +Beispiel: `03-DJ_Test-Sunrise_in_Am (Remastered 2015).flac` wird zu `1. DJ Test – Sunrise`. +``` + +### Variante C – Fokus auf technische Skripterstellung +```text +Du hilfst beim Entwickeln eines Skripts zur Bereinigung von Audio-Dateinamen. Analysiere jeden gegebenen Dateinamen und gib strukturierte Anweisungen, wie er in das Format „Interpret – Titel“ konvertiert werden soll. + +Für jeden Eintrag soll die Antwort JSON-Objekte mit den Feldern enthalten: +- `original`: ursprünglicher Dateiname +- `steps`: Liste der bereinigungsschritte (z. B. „Remove track number prefix“, „Strip key signature“) +- `result`: finaler Name „Interpret – Titel“ + +Bereinigungsrichtlinien: +1. Entferne numerische Präfixe, Tonarten, Mix-/Versionstags, Track-IDs, BPM und Jahresangaben. +2. Löse Sonderzeichen ( `_`, `[]`, `()` etc.) auf, lasse jedoch Bindestriche zwischen Interpret und Titel bestehen. +3. Wenn der Interpret nicht eindeutig erkennbar ist, gib `"Unknown Artist"` zurück, behalte den Titel jedoch nach Möglichkeit bei. + +Füge zwei eigene Beispiele (Vorher/Nachher + Schritte) hinzu, bevor du die Nutzerliste bearbeitest. +``` diff --git a/scripts/audio_collect.ps1 b/scripts/audio_collect.ps1 new file mode 100644 index 000000000..8543c925c --- /dev/null +++ b/scripts/audio_collect.ps1 @@ -0,0 +1,221 @@ +<# +.SYNOPSIS + Durchsucht alle verfügbaren lokalen und Netzwerk-Laufwerke nach Audiodateien + und kopiert sie in einen zentralen Ordner "Audio_Quelle". + +.DESCRIPTION + - Alle Dateisystemlaufwerke (lokal und gemountete Netzwerkshares) werden rekursiv durchsucht. + - Audiodateien mit den gebräuchlichsten Erweiterungen werden berücksichtigt. + - Ordner, deren Pfad "Ableton" enthält, werden übersprungen. + - Bereits vorhandene Dateien werden nicht überschrieben; stattdessen wird + ein eindeutiger Dateiname erzeugt, sodass beide Versionen erhalten bleiben. + +.NOTES + Speichern Sie dieses Skript als audio_collect.ps1 und führen Sie es in einer + PowerShell-Sitzung mit ausreichenden Berechtigungen aus. +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [string]$Destination = (Join-Path -Path $env:USERPROFILE -ChildPath 'Audio_Quelle'), + + [string[]]$Extensions = @('*.mp3', '*.wav', '*.aiff', '*.flac', '*.aac', '*.ogg', '*.wma', '*.m4a'), + + [ValidateSet('Copy', 'HardLink')] + [string]$TransferMode = 'Copy' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $Destination)) { + New-Item -ItemType Directory -Path $Destination | Out-Null +} + +function Resolve-NormalizedPath { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + try { + $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop + return [System.IO.Path]::GetFullPath($resolved.ProviderPath) + } + catch { + return $null + } +} + +$destinationFullPath = Resolve-NormalizedPath -Path $Destination + +if (-not $destinationFullPath) { + throw "Zielpfad '$Destination' konnte nicht aufgelöst werden." +} + +function Test-IsDescendantPath { + param( + [Parameter(Mandatory)] + [string]$Candidate, + + [Parameter(Mandatory)] + [string]$Ancestor + ) + + try { + $candidateFull = [System.IO.Path]::GetFullPath($Candidate) + } + catch { + return $false + } + + $ancestorFull = $Ancestor + + if (-not $ancestorFull.EndsWith([System.IO.Path]::DirectorySeparatorChar)) { + $ancestorFull += [System.IO.Path]::DirectorySeparatorChar + } + + return $candidateFull.Equals($Ancestor, [System.StringComparison]::OrdinalIgnoreCase) -or + $candidateFull.StartsWith($ancestorFull, [System.StringComparison]::OrdinalIgnoreCase) +} + +function Test-IsAbletonPath { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + if (-not $Path) { + return $false + } + + return $Path -match '(?i)(?:^|[\\/])Ableton(?:$|[\\/])' +} + +function Get-UniqueTargetPath { + param( + [Parameter(Mandatory)] + [string]$DestinationDirectory, + + [Parameter(Mandatory)] + [string]$FileName + ) + + $baseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) + $extension = [System.IO.Path]::GetExtension($FileName) + $targetPath = Join-Path -Path $DestinationDirectory -ChildPath $FileName + $suffix = 1 + + while (Test-Path -LiteralPath $targetPath) { + $newName = "{0} ({1}){2}" -f $baseName, $suffix, $extension + $targetPath = Join-Path -Path $DestinationDirectory -ChildPath $newName + $suffix += 1 + } + + return $targetPath +} + +function Copy-AudioFile { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory)] + [System.IO.FileInfo]$Source, + + [Parameter(Mandatory)] + [string]$DestinationDirectory + ) + + $targetPath = Get-UniqueTargetPath -DestinationDirectory $DestinationDirectory -FileName $Source.Name + + if ($PSCmdlet.ShouldProcess($Source.FullName, "Kopieren nach $targetPath")) { + Copy-Item -LiteralPath $Source.FullName -Destination $targetPath + } +} + +function New-HardLinkOrCopy { + [CmdletBinding(SupportsShouldProcess = $true)] + param( + [Parameter(Mandatory)] + [System.IO.FileInfo]$Source, + + [Parameter(Mandatory)] + [string]$DestinationDirectory + ) + + $targetPath = Get-UniqueTargetPath -DestinationDirectory $DestinationDirectory -FileName $Source.Name + + $sourceRoot = [System.IO.Path]::GetPathRoot($Source.FullName) + $destinationRoot = [System.IO.Path]::GetPathRoot((Resolve-Path -LiteralPath $DestinationDirectory).Path) + + if ($sourceRoot -ieq $destinationRoot) { + if ($PSCmdlet.ShouldProcess($Source.FullName, "Hardlink nach $targetPath")) { + New-Item -ItemType HardLink -Path $targetPath -Value $Source.FullName | Out-Null + } + } + else { + Write-Warning "Hardlinks sind nur innerhalb desselben Laufwerks möglich. Datei wird kopiert: $($Source.FullName)" + if ($PSCmdlet.ShouldProcess($Source.FullName, "Kopieren nach $targetPath")) { + Copy-Item -LiteralPath $Source.FullName -Destination $targetPath + } + } +} + +$searchRoots = @() + +function Add-SearchRoot { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + $normalized = Resolve-NormalizedPath -Path $Path + + if (-not $normalized) { + return + } + + if (Test-IsDescendantPath -Candidate $normalized -Ancestor $destinationFullPath) { + return + } + + if (-not ($searchRoots | Where-Object { $_.Normalized -eq $normalized })) { + $searchRoots += [pscustomobject]@{ Original = $Path; Normalized = $normalized } + } +} + +Get-PSDrive -PSProvider FileSystem | + Where-Object { $_.Root } | + ForEach-Object { Add-SearchRoot -Path $_.Root } + +if ($IsMacOS -or $IsLinux) { + $volumeRoot = '/Volumes' + if (Test-Path -LiteralPath $volumeRoot) { + Get-ChildItem -LiteralPath $volumeRoot -Directory -ErrorAction SilentlyContinue | + ForEach-Object { Add-SearchRoot -Path $_.FullName } + } +} + +foreach ($root in $searchRoots) { + foreach ($pattern in $Extensions) { + Get-ChildItem -Path $root.Original -Filter $pattern -File -Recurse -ErrorAction SilentlyContinue | + Where-Object { + -not (Test-IsAbletonPath -Path $_.DirectoryName) -and + -not (Test-IsDescendantPath -Candidate $_.FullName -Ancestor $destinationFullPath) + } | + ForEach-Object { + if ($TransferMode -eq 'HardLink') { + New-HardLinkOrCopy -Source $_ -DestinationDirectory $Destination + } + else { + Copy-AudioFile -Source $_ -DestinationDirectory $Destination + } + } + } +} + +if ($TransferMode -eq 'HardLink') { + Write-Host "Audiodateien wurden nach '$Destination' verlinkt oder kopiert (falls Hardlink nicht möglich war)." -ForegroundColor Green +} +else { + Write-Host "Audiodateien wurden nach '$Destination' kopiert." -ForegroundColor Green +}