Files
qobuz-streamrip-tools/scripts/Convert-FlacToCD.ps1
T
2026-08-09 12:06:46 +03:00

510 lines
13 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#requires -Version 5.1
<#
Конвертация всех FLAC-файлов в CD-качество:
16 бит / 44,1 кГц / стерео.
Скрипт:
- рекурсивно ищет все *.flac;
- пропускает файлы, которые уже 16 бит / 44100 Гц / стерео;
- сохраняет теги и встроенную обложку;
- проверяет результат через ffprobe;
- после успешной проверки заменяет исходный файл;
- показывает прогресс;
- в конце выводит список файлов с ошибками.
#>
$ProjectRoot = Split-Path -Parent $PSScriptRoot
$ToolsFolder = Join-Path $ProjectRoot "tools"
$RootFolder = Join-Path $ProjectRoot "downloads"
$ErrorActionPreference = "Stop"
function Find-Tool {
param(
[Parameter(Mandatory = $true)]
[string]$CommandName,
[Parameter(Mandatory = $true)]
[string[]]$LocalNames
)
foreach ($LocalName in $LocalNames) {
$LocalPath = Join-Path $ToolsFolder $LocalName
if (Test-Path -LiteralPath $LocalPath -PathType Leaf) {
return (Resolve-Path -LiteralPath $LocalPath).Path
}
}
$Command = Get-Command $CommandName -ErrorAction SilentlyContinue
if ($Command) {
return $Command.Source
}
return $null
}
$FfmpegPath = Find-Tool -CommandName "ffmpeg.exe" -LocalNames @("ffmpeg.exe")
$FfprobePath = Find-Tool -CommandName "ffprobe.exe" -LocalNames @("ffprobe.exe")
try {
$Host.UI.RawUI.WindowTitle = "FLAC -> CD quality"
}
catch {
}
function Wait-BeforeExit {
Write-Host ""
[void](Read-Host "Нажмите Enter для выхода")
}
function Get-FlacAudioInfo {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$probeOutput = & $FfprobePath `
-v error `
-select_streams a:0 `
-show_entries "stream=codec_name,sample_rate,channels,bits_per_sample,bits_per_raw_sample,sample_fmt" `
-of json `
-- `
$Path 2>&1
if ($LASTEXITCODE -ne 0) {
throw "ffprobe не смог прочитать файл:`n$probeOutput"
}
$probeData = $probeOutput | ConvertFrom-Json
if (
-not $probeData.streams -or
$probeData.streams.Count -lt 1
) {
throw "Аудиопоток не найден."
}
$stream = $probeData.streams[0]
$bitDepth = 0
if (
$null -ne $stream.bits_per_raw_sample -and
[string]$stream.bits_per_raw_sample -ne "" -and
[int]$stream.bits_per_raw_sample -gt 0
) {
$bitDepth = [int]$stream.bits_per_raw_sample
}
elseif (
$null -ne $stream.bits_per_sample -and
[string]$stream.bits_per_sample -ne "" -and
[int]$stream.bits_per_sample -gt 0
) {
$bitDepth = [int]$stream.bits_per_sample
}
elseif ([string]$stream.sample_fmt -match "s16") {
$bitDepth = 16
}
elseif ([string]$stream.sample_fmt -match "s32") {
$bitDepth = 24
}
[PSCustomObject]@{
Codec = [string]$stream.codec_name
SampleRate = [int]$stream.sample_rate
Channels = [int]$stream.channels
BitDepth = [int]$bitDepth
SampleFmt = [string]$stream.sample_fmt
}
}
Clear-Host
Write-Host "FLAC -> CD quality" -ForegroundColor Cyan
Write-Host "16 бит / 44,1 кГц / стерео"
Write-Host "Папка: $RootFolder"
Write-Host ""
if (
-not (
Test-Path `
-LiteralPath $RootFolder `
-PathType Container
)
) {
Write-Host "ОШИБКА: папка не найдена:" -ForegroundColor Red
Write-Host $RootFolder -ForegroundColor Red
Wait-BeforeExit
exit 1
}
if (-not $FfmpegPath) {
Write-Host "ОШИБКА: ffmpeg.exe не найден." -ForegroundColor Red
Write-Host "Проверены папка tools и системный PATH."
Wait-BeforeExit
exit 1
}
if (-not $FfprobePath) {
Write-Host "ОШИБКА: ffprobe.exe не найден." -ForegroundColor Red
Write-Host "Проверены папка tools и системный PATH."
Wait-BeforeExit
exit 1
}
$files = @(
Get-ChildItem `
-LiteralPath $RootFolder `
-Filter "*.flac" `
-File `
-Recurse |
Where-Object {
$_.Name -notlike "*.__cd_temp__.flac"
} |
Sort-Object FullName
)
$total = $files.Count
if ($total -eq 0) {
Write-Host "FLAC-файлы не найдены." `
-ForegroundColor Yellow
Wait-BeforeExit
exit 0
}
$converted = 0
$skipped = 0
$failed = 0
$index = 0
$startTime = Get-Date
$failedFiles = [System.Collections.Generic.List[object]]::new()
foreach ($file in $files) {
$index++
$percent = [int][Math]::Floor(
(($index - 1) / $total) * 100
)
Write-Progress `
-Activity "Конвертация FLAC в CD-качество" `
-Status (
"Файл {0} из {1}: {2}" -f `
$index,
$total,
$file.Name
) `
-CurrentOperation $file.DirectoryName `
-PercentComplete $percent
Write-Host ""
Write-Host (
"[{0}/{1}] {2}" -f `
$index,
$total,
$file.FullName
) -ForegroundColor White
$tempFile = Join-Path `
-Path $file.DirectoryName `
-ChildPath (
$file.BaseName +
".__cd_temp__.flac"
)
$backupFile = Join-Path `
-Path $file.DirectoryName `
-ChildPath (
$file.Name +
".__original_backup__"
)
try {
if (Test-Path -LiteralPath $tempFile) {
Remove-Item `
-LiteralPath $tempFile `
-Force
}
if (Test-Path -LiteralPath $backupFile) {
Remove-Item `
-LiteralPath $backupFile `
-Force
}
$sourceInfo = Get-FlacAudioInfo `
-Path $file.FullName
Write-Host (
"Исходник: {0} бит, {1} Гц, {2} канал(а)" -f `
$sourceInfo.BitDepth,
$sourceInfo.SampleRate,
$sourceInfo.Channels
)
$alreadyCdQuality = (
$sourceInfo.BitDepth -eq 16 -and
$sourceInfo.SampleRate -eq 44100 -and
$sourceInfo.Channels -eq 2
)
if ($alreadyCdQuality) {
$skipped++
Write-Host `
"Уже CD-качество — пропущено." `
-ForegroundColor Green
continue
}
Write-Host "Конвертация..." `
-ForegroundColor Yellow
$ffmpegOutput = & $FfmpegPath `
-hide_banner `
-loglevel error `
-y `
-i `
$file.FullName `
-map 0:a:0 `
-map "0:v?" `
-map_metadata 0 `
-c:a flac `
-sample_fmt s16 `
-ar 44100 `
-ac 2 `
-compression_level 8 `
-c:v copy `
-- `
$tempFile 2>&1
if ($LASTEXITCODE -ne 0) {
throw "ffmpeg завершился с ошибкой:`n$ffmpegOutput"
}
if (
-not (
Test-Path `
-LiteralPath $tempFile `
-PathType Leaf
)
) {
throw "Временный файл не был создан."
}
$tempFileInfo = Get-Item `
-LiteralPath $tempFile
if ($tempFileInfo.Length -le 0) {
throw "Создан пустой временный файл."
}
$resultInfo = Get-FlacAudioInfo `
-Path $tempFile
$validResult = (
$resultInfo.BitDepth -eq 16 -and
$resultInfo.SampleRate -eq 44100 -and
$resultInfo.Channels -eq 2
)
if (-not $validResult) {
throw (
"Проверка результата не пройдена. " +
"Получено: {0} бит, {1} Гц, " +
"{2} канал(а)." -f `
$resultInfo.BitDepth,
$resultInfo.SampleRate,
$resultInfo.Channels
)
}
$originalCreationTime = $file.CreationTime
$originalLastWriteTime = $file.LastWriteTime
Move-Item `
-LiteralPath $file.FullName `
-Destination $backupFile
try {
Move-Item `
-LiteralPath $tempFile `
-Destination $file.FullName
$newFile = Get-Item `
-LiteralPath $file.FullName
$newFile.CreationTime = $originalCreationTime
$newFile.LastWriteTime = $originalLastWriteTime
Remove-Item `
-LiteralPath $backupFile `
-Force
}
catch {
if (
Test-Path `
-LiteralPath $file.FullName
) {
Remove-Item `
-LiteralPath $file.FullName `
-Force `
-ErrorAction SilentlyContinue
}
if (
Test-Path `
-LiteralPath $backupFile
) {
Move-Item `
-LiteralPath $backupFile `
-Destination $file.FullName `
-ErrorAction SilentlyContinue
}
throw
}
$converted++
Write-Host `
"Готово — исходный файл заменён." `
-ForegroundColor Cyan
}
catch {
$failed++
$errorMessage = $_.Exception.Message
$failedFiles.Add(
[PSCustomObject]@{
Name = $file.BaseName
FileName = $file.Name
FullName = $file.FullName
Directory = $file.DirectoryName
Error = $errorMessage
}
)
Write-Host "ОШИБКА:" `
-ForegroundColor Red
Write-Host $errorMessage `
-ForegroundColor Red
if (
Test-Path `
-LiteralPath $tempFile
) {
Remove-Item `
-LiteralPath $tempFile `
-Force `
-ErrorAction SilentlyContinue
}
if (
(Test-Path -LiteralPath $backupFile) -and
-not (
Test-Path `
-LiteralPath $file.FullName
)
) {
Move-Item `
-LiteralPath $backupFile `
-Destination $file.FullName `
-ErrorAction SilentlyContinue
}
}
}
Write-Progress `
-Activity "Конвертация FLAC в CD-качество" `
-Completed
$elapsed = (Get-Date) - $startTime
Write-Host ""
Write-Host "========================================" `
-ForegroundColor DarkGray
Write-Host "Обработка завершена" `
-ForegroundColor Cyan
Write-Host (
"Всего файлов: {0}" -f $total
)
Write-Host (
"Конвертировано: {0}" -f $converted
) -ForegroundColor Cyan
Write-Host (
"Пропущено (уже CD): {0}" -f $skipped
) -ForegroundColor Green
if ($failed -gt 0) {
Write-Host (
"Ошибок: {0}" -f $failed
) -ForegroundColor Red
}
else {
Write-Host "Ошибок: 0" `
-ForegroundColor Gray
}
Write-Host (
"Время: {0:hh\:mm\:ss}" -f $elapsed
)
Write-Host "========================================" `
-ForegroundColor DarkGray
if ($failedFiles.Count -gt 0) {
Write-Host ""
Write-Host "ТРЕКИ С ОШИБКАМИ:" `
-ForegroundColor Red
Write-Host "========================================" `
-ForegroundColor DarkGray
$failedNumber = 0
foreach ($failedFile in $failedFiles) {
$failedNumber++
Write-Host (
"{0}. {1}" -f `
$failedNumber,
$failedFile.Name
) -ForegroundColor Red
Write-Host (
" Файл: {0}" -f `
$failedFile.FullName
) -ForegroundColor DarkGray
Write-Host (
" Ошибка: {0}" -f `
$failedFile.Error
) -ForegroundColor DarkYellow
Write-Host ""
}
Write-Host "========================================" `
-ForegroundColor DarkGray
}
else {
Write-Host ""
Write-Host "Все файлы обработаны без ошибок." `
-ForegroundColor Green
}
Wait-BeforeExit