Last active
April 18, 2026 07:15
-
-
Save jult/27230b590fedcd737ce8945277a17e82 to your computer and use it in GitHub Desktop.
powershell 7+ script to normalize & downsample FLAC 192kHz to 96 kHz (for stems compatibility of denon equipment)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Resample FLAC 192kHz→96kHz + Peak Normalize to 0dBFS. | |
| Uses FFmpeg + soxr, max FLAC compression, metadata preservation. | |
| PowerShell 7+ required. | |
| .NOTES | |
| - Two-pass process per file: volumedetect → resample+normalize | |
| - Parallel execution maxing out CPU/RAM | |
| - All tags, cover art, and metadata preserved | |
| #> | |
| # ==================== USER CONFIGURATION ==================== | |
| # ⚠️ UPDATE THESE PATHS TO MATCH YOUR SYSTEM ⚠️ | |
| $FFmpegPath = "P:\Portable\pz\tools\FFmpeg64\ffmpeg.exe" # Your FFmpeg path | |
| $MetaflacPath = "P:\Portable\flac\tools\metaflac.exe" # Your metaflac path | |
| $SourceDir = "J:\audio\!OUT\stemsfail\192kc" # Folder with 192kHz FLACs | |
| $DestDir = "J:\xff\@utput" # Output folder | |
| $Threads = 26 # Parallel jobs (logical cores) | |
| # Soxr resample filter (audiophile-grade) | |
| $ResampleFilter = "aresample=96000:resampler=soxr:precision=33:cheby=1" | |
| # ============================================================ | |
| # Validate executables | |
| @{ FFmpeg = $FFmpegPath; metaflac = $MetaflacPath }.GetEnumerator() | ForEach-Object { | |
| if (!(Test-Path -LiteralPath $_.Value)) { | |
| throw "$($_.Key) not found at: $($_.Value)`nPlease update the paths at the top of this script." | |
| } | |
| } | |
| # Create output directory | |
| if (!(Test-Path -LiteralPath $DestDir)) { | |
| New-Item -ItemType Directory -LiteralPath $DestDir | Out-Null | |
| } | |
| Write-Host "`n========================================" | |
| Write-Host " FLAC 192kHz → 96kHz + Peak Normalize (0 dBFS)" | |
| Write-Host " Quality: soxr precision=33, cheby=1" | |
| Write-Host " Compression: FLAC level 8 (smallest lossless)" | |
| Write-Host " Threads: $Threads" | |
| Write-Host "========================================`n" | |
| # Process files in parallel | |
| Get-ChildItem -LiteralPath $SourceDir -Filter "*.flac" -File | ForEach-Object -Parallel { | |
| $File = $_ | |
| $TempFile = Join-Path $env:TEMP "FFmpeg_$($_.BaseName).flac" | |
| $TagsFile = Join-Path $env:TEMP "FFmpeg_$($_.BaseName).tags" | |
| $OutputFile = Join-Path $using:DestDir $File.Name | |
| $ffmpeg = $using:FFmpegPath | |
| $metaflac = $using:MetaflacPath | |
| $resFilter = $using:ResampleFilter | |
| try { | |
| # 1️⃣ Export Vorbis comments/tags | |
| & $metaflac "--export-tags-to=$TagsFile" $File.FullName 2>$null | |
| # 2️⃣ PASS 1: Analyze peak level with volumedetect | |
| $analyzeLog = & $ffmpeg -nostdin -i $File.FullName -af "volumedetect" -f null -y - 2>&1 | |
| $peakMatch = [regex]::Match(($analyzeLog -join "`n"), 'max_volume:\s*(-?\d+(?:\.\d+)?)\s*dB') | |
| $gain = 0.0 | |
| if ($peakMatch.Success) { | |
| $peak = [double]$peakMatch.Groups[1].Value | |
| if ($peak -lt 0) { $gain = -$peak } # Boost quiet files to 0 dBFS | |
| } | |
| # 3️⃣ PASS 2: Resample + Normalize + Encode in one go | |
| $afChain = if ($gain -gt 0) { "$resFilter,volume=${gain}dB" } else { $resFilter } | |
| $result = & $ffmpeg -nostdin -y -i $File.FullName ` | |
| -af $afChain ` | |
| -c:a flac ` | |
| -compression_level 8 ` | |
| -map 0 ` | |
| -map_metadata 0 ` | |
| $TempFile ` | |
| 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "FFmpeg encode failed: $($result -join "`n")" | |
| } | |
| # 4️⃣ Verify output sample rate | |
| $probeOutput = & $ffmpeg -nostdin -i $TempFile -hide_banner 2>&1 | |
| $rateMatch = [regex]::Match(($probeOutput -join "`n"), '(\d+)\s*Hz') | |
| $actualRate = $rateMatch.Groups[1].Value | |
| if ([string]::IsNullOrWhiteSpace($actualRate) -or $actualRate -ne "96000") { | |
| throw "Verification failed: output sample rate is $actualRate Hz (expected 96000)" | |
| } | |
| # 5️⃣ Restore metadata tags | |
| if (Test-Path -LiteralPath $TagsFile) { | |
| & $metaflac "--import-tags-from=$TagsFile" $TempFile 2>$null | |
| Remove-Item -LiteralPath $TagsFile -Force -ErrorAction SilentlyContinue | |
| } | |
| # 6️⃣ Move verified file to destination | |
| Move-Item -Force -LiteralPath $TempFile -Destination $OutputFile | |
| $gainLabel = if ($gain -gt 0) { "[+${gain}dB]" } else { "[0dB]" } | |
| Write-Host "[✅ SUCCESS] $gainLabel $($File.Name)" | |
| } catch { | |
| Remove-Item -LiteralPath $TempFile, $TagsFile -Force -ErrorAction SilentlyContinue | |
| Write-Host "[❌ FAILED] $($File.Name) | $($_.Exception.Message)" | |
| } | |
| } -ThrottleLimit $Threads | |
| Write-Host "`n========================================" | |
| Write-Host " All done! Output saved to: $DestDir" | |
| Write-Host "========================================" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment