#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