Релиз v1.0.0
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal
|
||||
|
||||
set "STREAMRIP_DIR=%APPDATA%\streamrip"
|
||||
set "DOWNLOADS_DB=%STREAMRIP_DIR%\downloads.db"
|
||||
set "FAILED_DB=%STREAMRIP_DIR%\failed_downloads.db"
|
||||
|
||||
echo.
|
||||
echo ============================================
|
||||
echo ОЧИСТКА БАЗЫ STREAMRIP
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
if exist "%DOWNLOADS_DB%" (
|
||||
del /q "%DOWNLOADS_DB%" >nul 2>&1
|
||||
if exist "%DOWNLOADS_DB%" (
|
||||
echo ОШИБКА: не удалось удалить downloads.db
|
||||
) else (
|
||||
echo downloads.db удалён.
|
||||
)
|
||||
) else (
|
||||
echo downloads.db не найден — пропущено.
|
||||
)
|
||||
|
||||
if exist "%FAILED_DB%" (
|
||||
del /q "%FAILED_DB%" >nul 2>&1
|
||||
if exist "%FAILED_DB%" (
|
||||
echo ОШИБКА: не удалось удалить failed_downloads.db
|
||||
) else (
|
||||
echo failed_downloads.db удалён.
|
||||
)
|
||||
) else (
|
||||
echo failed_downloads.db не найден — пропущено.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Готово.
|
||||
echo.
|
||||
pause
|
||||
@@ -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
|
||||
@@ -0,0 +1,93 @@
|
||||
#requires -Version 5.1
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$RootPath = Join-Path $ProjectRoot "downloads"
|
||||
$Separator = " - "
|
||||
|
||||
try {
|
||||
$Host.UI.RawUI.WindowTitle = "Группировка альбомов по исполнителям"
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
function Wait-BeforeExit {
|
||||
Write-Host ""
|
||||
[void](Read-Host "Нажмите Enter для выхода")
|
||||
}
|
||||
|
||||
Clear-Host
|
||||
|
||||
Write-Host "Группировка альбомов по исполнителям" -ForegroundColor Cyan
|
||||
Write-Host "Папка: $RootPath"
|
||||
Write-Host ""
|
||||
|
||||
if (-not (Test-Path -LiteralPath $RootPath -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $RootPath -Force | Out-Null
|
||||
}
|
||||
|
||||
$AlbumFolders = Get-ChildItem -LiteralPath $RootPath -Directory |
|
||||
Where-Object {
|
||||
$_.Name.Contains($Separator)
|
||||
} |
|
||||
ForEach-Object {
|
||||
$Artist = ($_.Name -split [regex]::Escape($Separator), 2)[0].Trim()
|
||||
|
||||
[PSCustomObject]@{
|
||||
Artist = $Artist
|
||||
Folder = $_
|
||||
}
|
||||
}
|
||||
|
||||
$ArtistGroups = $AlbumFolders | Group-Object -Property Artist
|
||||
|
||||
$Moved = 0
|
||||
$Skipped = 0
|
||||
|
||||
foreach ($Group in $ArtistGroups) {
|
||||
# Папка исполнителя создаётся только при наличии двух и более альбомов.
|
||||
if ($Group.Count -lt 2) {
|
||||
continue
|
||||
}
|
||||
|
||||
$ArtistName = $Group.Name
|
||||
$ArtistPath = Join-Path -Path $RootPath -ChildPath $ArtistName
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ArtistPath -PathType Container)) {
|
||||
New-Item -Path $ArtistPath -ItemType Directory | Out-Null
|
||||
Write-Host "Создана папка: $ArtistName" -ForegroundColor Green
|
||||
}
|
||||
|
||||
foreach ($Album in $Group.Group) {
|
||||
$SourcePath = $Album.Folder.FullName
|
||||
$DestinationPath = Join-Path -Path $ArtistPath -ChildPath $Album.Folder.Name
|
||||
|
||||
if (Test-Path -LiteralPath $DestinationPath) {
|
||||
Write-Warning "Папка уже существует, пропущено: $DestinationPath"
|
||||
$Skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
Move-Item `
|
||||
-LiteralPath $SourcePath `
|
||||
-Destination $ArtistPath `
|
||||
-ErrorAction Stop
|
||||
|
||||
Write-Host "Перемещено: $($Album.Folder.Name) -> $ArtistName" -ForegroundColor Cyan
|
||||
$Moved++
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Не удалось переместить '$SourcePath': $($_.Exception.Message)"
|
||||
$Skipped++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Обработка завершена." -ForegroundColor Yellow
|
||||
Write-Host "Перемещено: $Moved"
|
||||
Write-Host "Пропущено: $Skipped"
|
||||
|
||||
Wait-BeforeExit
|
||||
Reference in New Issue
Block a user