Релиз v1.0.0
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user