From 43ba68f67645c136dfd8e2f1c7319b59e84537a1 Mon Sep 17 00:00:00 2001 From: Mengmansen <150459022+Mengmansen@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:31:30 +0200 Subject: [PATCH 1/3] Improve audio collect script for mac compatibility --- docs/audio_collect_command.md | 122 +++++++++++++++++++ scripts/audio_collect.ps1 | 221 ++++++++++++++++++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 docs/audio_collect_command.md create mode 100644 scripts/audio_collect.ps1 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/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 +} From d782babfb5912ff949896a74a5d2c9b69d20a6df Mon Sep 17 00:00:00 2001 From: Mengmansen <150459022+Mengmansen@users.noreply.github.com> Date: Tue, 11 Nov 2025 05:00:07 +0100 Subject: [PATCH 2/3] Improve platform detection in audio collector script --- scripts/audio_collect.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/audio_collect.ps1 b/scripts/audio_collect.ps1 index 8543c925c..95ef719bf 100644 --- a/scripts/audio_collect.ps1 +++ b/scripts/audio_collect.ps1 @@ -183,11 +183,23 @@ function Add-SearchRoot { } } +function Get-PlatformInfo { + $osx = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX) + $linux = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux) + + return [pscustomobject]@{ + IsMacOS = $osx + IsLinux = $linux + } +} + +$platformInfo = Get-PlatformInfo + Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root } | ForEach-Object { Add-SearchRoot -Path $_.Root } -if ($IsMacOS -or $IsLinux) { +if ($platformInfo.IsMacOS -or $platformInfo.IsLinux) { $volumeRoot = '/Volumes' if (Test-Path -LiteralPath $volumeRoot) { Get-ChildItem -LiteralPath $volumeRoot -Directory -ErrorAction SilentlyContinue | From 9efe13408f2b97d545f2107b4a8e9f7946c4a890 Mon Sep 17 00:00:00 2001 From: Mengmansen <150459022+Mengmansen@users.noreply.github.com> Date: Thu, 13 Nov 2025 04:16:16 +0100 Subject: [PATCH 3/3] Create python-package.yml --- .github/workflows/python-package.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 000000000..e56abb630 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python package + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest