четверг, 13 августа 2026 г.

Project, Convert-FlacToMp3

Project, Convert-FlacToMp3

------------------------------------------------------------------------

D:\Projects\VS02\2606\FlacToMp3\Giga\FlacToMp3

D:\Projects\VS02\2606\FlacToMp3\Giga\FlacToMp3\Pws

-----------------------------------------------------------------------

https://gsffmpeg.blogspot.com/

https://gsmainprojects.blogspot.com/2026/07/install-winget-giga-ffmpeg-flac-mp3.html

https://gsffmpeg.blogspot.com/2026/07/install-winget-giga-ffmpeg-flac-mp3.html

https://gsffmpeg.blogspot.com/2026/07/ffmpeg-install-download.html

https://gsffmpeg.blogspot.com/2026/07/ffmpeg.html

-----------------------------------------------------------------------------------

https://giga.chat/link/gcsQTYxzeF

https://giga.chat/link/gcsKiQAIiI

-----------------------------------------------------------------------------------

Чтобы запустить скрипт в PowerShell, когда он и файлы находятся в одной папке, используй одну из этих команд:

powershell
.\Convert-FlacToMp3_v2.ps1

или (если хочешь явно указать путь):

powershell
.\Convert-FlacToMp3_v2.ps1 -Path .

Важные нюансы

  1. Точка и слэш (.\) — обязательны. PowerShell не запускает скрипты из текущей директории без указания пути, даже если они там лежат. Без .\ будет ошибка вроде «не является внутренней или внешней командой».
  2. Политика выполнения у тебя уже настроена для текущего процесса (Bypass), так что скрипт должен запуститься без дополнительных вопросов.
  3. Если скрипт ожидает параметры (например, путь к файлам), проверь его заголовок или комментарии — возможно, он сам берёт файлы из текущей папки, если параметр не передан.

Если скажешь, какие параметры принимает твой скрипт (можно глянуть через Get-Help .\Convert-FlacToMp3_v2.ps1 или показать фрагмент кода), подскажу точную строку вызова.

  • Я

    Хорошо, что я храню свои старые версии. ----------------- # Полный код скрипта Convert-FlacToMp3.ps1 (Финальная версия) # Включает: логирование (в Килобайтах), защиту от повторного запуска, # проверку битрейта итогового файла через ffprobe и финальную статистику. $logFile = "ConversionLog.txt" function Write-ConversionLog { param ( [string]$Status, [string]$InputName, [string]$InputSizeStr, [string]$OutputName, [string]$OutputSizeStr, [string]$ElapsedString, [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" # Функция форматирования размера. Возвращает строку вида "422 664.00 KB" function Format-FileSize { param ([long]$size) if ($size -eq -1) { return "N/A" } return ("{0:N2} KB" -f ($size / 1KB)) } $logEntry = @( "$timestamp [$Status]", "In : $InputName", " Size: $InputSizeStr", "Out: $OutputName", " Size: $OutputSizeStr", "Msg: $Message", "Time : $ElapsedString", "----------------------------------------" ) -join "`r`n" Add-Content -Path $logFile -Value $logEntry -Encoding UTF8 } Write-Host "Поиск FLAC-файлов в текущем каталоге..." $flacFiles = Get-ChildItem -Filter *.flac -File if ($flacFiles.Count -eq 0) { Write-Warning "Файлы .flac не найдены." exit } # Счетчики для итоговой статистики $total = $flacFiles.Count $okCount = 0 $errCount = 0 $skipCount = 0 foreach ($file in $flacFiles) { $inputPath = $file.FullName $outputPath = $file.BaseName + ".mp3" # Подготовка данных входного файла (ТЕПЕРЬ В КБ) $inSizeStr = "{0:N2} KB" -f ($file.Length / 1KB) $outSizeBytes = -1 # --- БЛОК ПРОПУСКА СУЩЕСТВУЮЩИХ ФАЙЛОВ --- if ((Test-Path $outputPath) -and ((Get-Item $outputPath).Length -gt 0)) { $existingSize = "{0:N2} KB" -f ((Get-Item $outputPath).Length / 1KB) Write-Host "Skip: $($file.Name)" Write-ConversionLog -Status "SKIP" ` -InputName $file.Name ` -InputSizeStr $inSizeStr ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeStr $existingSize ` -ElapsedString "00:00.00" ` -Message "Файл уже существует." $skipCount++ continue } # ========================================== $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { ffmpeg -hide_banner -nostats -i $inputPath -map_metadata 0 -codec:a libmp3lame -b:a 320k $outputPath if ($LASTEXITCODE -eq 0) { $stopwatch.Stop() # --- БЛОК ПРОВЕРКИ ДОСТУПНОСТИ И РАЗМЕРА OUT --- $maxRetries = 10 $retryDelayMs = 200 $fileReady = $false for ($i = 1; $i -le $maxRetries; $i++) { try { $stream = [System.IO.File]::Open($outputPath, 'Open', 'Read', 'None') $outSizeBytes = $stream.Length $stream.Close() $fileReady = $true break } catch { Start-Sleep -Milliseconds $retryDelayMs } } # ========================================== # Форматируем строку размера Out (ТЕПЕРЬ В КБ) $finalOutSizeStr = "N/A" if ($outSizeBytes -ge 0) { $calcSize = $outSizeBytes / 1KB $finalOutSizeStr = "{0:N2} KB" -f $calcSize } # --- БЛОК ПРОВЕРКИ БИТРЕЙТА (через ffprobe) --- [int]$actualBitrate = 0 # Значение по умолчанию на случай сбоя ffprobe try { $rawProbe = & ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 $outputPath 2>$null if ($rawProbe -and $rawProbe.Trim() -ne 'N/A') { [int]$actualBitrate = [math]::Round([long]$rawProbe / 1000) } else { # Резервный расчет: Размер (байты) * 8 / Длительность (сек) $durationSec = (& ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $outputPath 2>$null) if ($durationSec -and $outSizeBytes -gt 0) { $calcBps = ([double]$outSizeBytes * 8) / [double]$durationSec [int]$actualBitrate = [math]::Round($calcBps / 1000) } } } catch { # Оставляем actualBitrate равным 0, если ffprobe выдал ошибку } if ($actualBitrate -ne 320) { $msgSuffix = " ВНИМАНИЕ: Битрейт $actualBitrate kbps!" Write-Host "Warn: Bitrate is $actualBitrate kbps for $($file.Name)" -ForegroundColor Yellow Write-ConversionLog -Status "OK-WARN" ` -InputName $file.Name ` -InputSizeStr $inSizeStr ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeStr $finalOutSizeStr ` -ElapsedString ("{0:mm\:ss\.ff}" -f $stopwatch.Elapsed) ` -Message ("Конвертация успешна." + $msgSuffix) } else { Write-Host "Ok : $($file.Name)" Write-ConversionLog -Status "OK" ` -InputName $file.Name ` -InputSizeStr $inSizeStr ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeStr $finalOutSizeStr ` -ElapsedString ("{0:mm\:ss\.ff}" -f $stopwatch.Elapsed) ` -Message "Конвертация успешна." } $okCount++ } else { $stopwatch.Stop() throw "FFmpeg вернул код ошибки: $LASTEXITCODE" } } catch { $stopwatch.Stop() # Попытка считать размер даже при ошибке конвертации $errorOutSize = -1 for ($i = 1; $i -le $maxRetries; $i++) { try { $s=[System.IO.File]::Open($outputPath,'Open','Read','None'); $errorOutSize=$s.Length; $s.Close(); $fileReady=$true; break } catch { Start-Sleep -Milliseconds $retryDelayMs } } $finalOutSizeStr = "N/A" if ($errorOutSize -ge 0) { $calcErrSize = $errorOutSize / 1KB $finalOutSizeStr = "{0:N2} KB" -f $calcErrSize } Write-Host "Err: $($file.Name) - $_" -ForegroundColor Red Write-ConversionLog -Status "ERROR" ` -InputName $file.Name ` -InputSizeStr $inSizeStr ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeStr $finalOutSizeStr ` -ElapsedString ("{0:mm\:ss\.ff}" -f $stopwatch.Elapsed) ` -Message $_.Exception.Message $errCount++ } } # --- ИТОГОВАЯ СТАТИСТИКА --- Write-Host "`n================== ПРОТОКОЛ ЗАВЕРШЕН ==================" Write-Host "Всего обработано файлов: $total" Write-Host "Успешно конвертировано: $okCount" Write-Host "Пропущено (существовали): $skipCount" Write-Host "Ошибок: $errCount" Write-Host "=======================================================" Write-Host "Подробный отчет сохранен в '$logFile'" -------------------- Старая версия сработала Вспоминаю, что начал писать новую версию и что-то недописал Новая вот такая - недописанная. Что-там нового, а то я уже забыл ----------- # Convert-FlacToMp3_v2_compat.ps1 # Версия для старых систем (Windows PowerShell 5.1), заменяющая 'using' на .Dispose() param ( [string]$LogFile = "ConversionLog.txt", [string]$ffmpegPath = "ffmpeg", [string]$ffprobePath = "ffprobe" ) function Write-ConversionLog { param ( [string]$Status, [string]$InputName, [long]$InputSizeBytes, [string]$OutputName, [long]$OutputSizeBytes, [timespan]$Elapsed, [string]$Message ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" function Format-BytesAsKb { param ([long]$size) if ($size -eq -1) { return "N/A" } return ("{0:N2} KB" -f ($size / 1KB)) } $logEntry = @( "$timestamp [$Status]", "In : $InputName", " Size: $(Format-BytesAsKb $InputSizeBytes)", "Out: $OutputName", " Size: $(Format-BytesAsKb $OutputSizeBytes)", "Msg: $Message", "Time : {0:mm\:ss\.ff}" -f $Elapsed, "----------------------------------------" ) -join "`r`n" Add-Content -Path $LogFile -Value $logEntry -Encoding UTF8 } function Wait-FileReady { param ( [string]$Path, [int]$MaxRetries = 10, [int]$DelayMs = 200 ) for ($i = 1; $i -le $MaxRetries; $i++) { $stream = $null try { $stream = [System.IO.File]::Open($Path, 'Open', 'Read', 'None') $length = $stream.Length return $length } catch { Start-Sleep -Milliseconds $DelayMs } finally { if ($stream) { $stream.Dispose() } } } throw "Файл '$Path' не освободился после $MaxRetries попыток." } function Convert-SingleFlacToMp3 { param ( [System.IO.FileInfo]$File, [string]$ffmpegPath, [string]$ffprobePath, [ref]$OkCount, [ref]$ErrCount, [ref]$SkipCount ) $inputPath = $File.FullName $outputPath = Join-Path $File.DirectoryName ("$($File.BaseName).mp3") $inSizeBytes = $File.Length # Блок пропуска существующих файлов if ((Test-Path $outputPath)) { $existingItem = Get-Item $outputPath if ($existingItem.Length -gt 0) { Write-Host "Skip: $($File.Name)" Write-ConversionLog -Status "SKIP" ` -InputName $File.Name ` -InputSizeBytes $inSizeBytes ` -OutputName $existingItem.Name ` -OutputSizeBytes $existingItem.Length ` -Elapsed ([TimeSpan]::Zero) ` -Message "Файл уже существует." $SkipCount.Value++ return } } $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() try { & $ffmpegPath -hide_banner -nostats -i $inputPath -map_metadata 0 -codec:a libmp3lame -b:a 320k $outputPath if ($LASTEXITCODE -eq 0) { $stopwatch.Stop() $outputSizeBytes = Wait-FileReady -Path $outputPath [int]$actualBitrate = 0 try { $rawProbe = & $ffprobePath -v error -select_streams a:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 $outputPath 2>$null if ($rawProbe -and $rawProbe.Trim() -ne 'N/A') { [int]$actualBitrate = [math]::Round([long]$rawProbe / 1000) } else { $durationSecStr = & $ffprobePath -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $outputPath 2>$null if ($durationSecStr -and $outputSizeBytes -gt 0) { $calcBps = ([double]$outputSizeBytes * 8) / [double]$durationSecStr [int]$actualBitrate = [math]::Round($calcBps / 1000) } } } catch {} $statusType = "OK" $msgSuffix = "" if ($actualBitrate -lt 315 -or $actualBitrate -gt 325) { $statusType = "OK-WARN" $msgSuffix = " ВНИМАНИЕ: Битрейт $actualBitrate kbps!" Write-Host "Warn: Bitrate is $actualBitrate kbps for $($File.Name)" -ForegroundColor Yellow } else { Write-Host "Ok : $($File.Name)" } Write-ConversionLog -Status $statusType ` -InputName $File.Name ` -InputSizeBytes $inSizeBytes ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeBytes $outputSizeBytes ` -Elapsed $stopwatch.Elapsed ` -Message ("Конвертация успешна." + $msgSuffix) $OkCount.Value++ } else { $stopwatch.Stop() throw "FFmpeg вернул код ошибки: $LASTEXITCODE" } } catch { $stopwatch.Stop() $errorOutSize = -1 try { $errorOutSize = Wait-FileReady -Path $outputPath } catch {} Write-Host "Err: $($File.Name) - $_" -ForegroundColor Red Write-ConversionLog -Status "ERROR" ` -InputName $File.Name ` -InputSizeBytes $inSizeBytes ` -OutputName (Split-Path $outputPath -Leaf) ` -OutputSizeBytes $errorOutSize ` -Elapsed $stopwatch.Elapsed ` -Message $_.Exception.Message $ErrCount.Value++ } } # --- ОСНОВНОЙ БЛОК --- Clear-Host Write-Host "Поиск FLAC-файлов (включая подпапки)..." $flacFiles = Get-ChildItem -Filter *.flac -File -Recurse -ErrorAction SilentlyContinue if (-not $flacFiles) { Write-Warning "Файлы .flac не найдены." exit } [int]$total = $flacFiles.Count [int]$okCount = 0 [int]$errCount = 0 [int]$skipCount = 0 foreach ($file in $flacFiles) { Convert-SingleFlacToMp3 -File $file ` -ffmpegPath $ffmpegPath ` -ffprobePath $ffprobePath ` -OkCount ([ref]$okCount) ` -ErrCount ([ref]$errCount) ` -SkipCount ([ref]$skipCount) } Write-Host "`n================== ПРОТОКОЛ ЗАВЕРШЕН ==================" Write-Host "Всего обработано файлов: $total" Write-Host "Успешно конвертировано: $okCount" Write-Host "Пропущено (существовали): $skipCount" Write-Host "Ошибок: $errCount" Write-Host "=======================================================" Write-Host "Подробный отчет сохранен в '$LogFile'" ---------- В этой версии на using ругается PS D:\trnt\HeavyMetal\Dio\HolyDiver\TM1> .\Convert-FlacToMp3_v2.ps1 ParserError: D:\trnt\HeavyMetal\Dio\HolyDiver\TM1\Convert-FlacToMp3_v2.ps1:52 Line | 52 | using ($stream = [System.IO.File]::Open($Path, 'Open', 'R … | ~ | Missing using directive PS D:\trnt\HeavyMetal\Dio\HolyDiver\TM1> .\Convert-FlacToMp3_v2_01.ps1 ParserError: D:\trnt\HeavyMetal\Dio\HolyDiver\TM1\Convert-FlacToMp3_v2_01.ps1:52 Line | 52 | using ($stream = [System.IO.File]::Open($Path, 'Open', 'R … | ~ | Missing using directive
  • Алиса

    Проблема в том, что ключевое слово using (в стиле C#) не работает в PowerShell — оно появилось в C# для гарантированного вызова Dispose(), а в PowerShell его нет.

    У тебя в коде (в версии v2) как раз и стоит эта строчка:

    powershell
    using ($stream = [System.IO.File]::Open(...))
    

    Отсюда и ошибка Missing using directive.


    Что нового в твоих версиях

    В первой (полной) версии (Convert-FlacToMp3.ps1)

    • Логирование в КБ (а не в байтах).
    • Защита от повторного запуска: если .mp3 уже есть и не пустой — пропускает.
    • Проверка битрейта через ffprobe, а если он не сработал — резервный расчёт по размеру и длительности.
    • Обработка того, что файл может быть ещё заблокирован (цикл с Open + Sleep).
    • Красивая статистика в конце.

    Во второй (v2_compat.ps1)

    • Добавлены параметры: можно явно указать пути к ffmpeg и ffprobe.
    • Рекурсивный поиск FLAC-файлов (включая подпапки): -Recurse.
    • Вынесена логика ожидания освобождения файла в отдельную функцию Wait-FileReady.
    • Вместо using используется try/finally с ручным .Dispose().

    Именно эта вторая версия и должна работать на Windows PowerShell 5.1, но из‑за using она ломается.


    Как исправить ошибку в v2

    Тебе нужно заменить блок с using на try/finally. В твоём файле найди функцию, где есть using ($stream = ...) (примерно строка 52), и замени её на такой шаблон:

    powershell
    $stream = $null
    try {
        $stream = [System.IO.File]::Open($Path, 'Open', 'Read', 'None')
        $length = $stream.Length
        return $length
    }
    catch {
        throw $_
    }
    finally {
        if ($stream) {
            $stream.Dispose()
        }
    }
    

    Это как раз то, что делает твой v2_compat — он уже написан правильно.


    Какой скрипт запускать прямо сейчас

    Учитывая, что у тебя:

    • файлы в одной папке (без подпапок),
    • ffmpeg и ffprobe уже в PATH,
    • нужна простая конвертация с логом и проверкой битрейта,

    лучше всего запустить первую версию (Convert-FlacToMp3.ps1). Она не содержит using, работает на PowerShell 5.1 и делает всё, что тебе нужно.

    Команда:

    powershell
    .\Convert-FlacToMp3.ps1
    

    А v2_compat оставь как «универсальную» версию для случаев, когда:

    • нужно искать FLAC-файлы рекурсивно,
    • ffmpeg лежит не в PATH, а в конкретной папке.

    Важные проверки перед запуском

    1. FFmpeg и FFprobe должны быть установлены и доступны в PATH. Проверь:

      powershell
      ffmpeg -version
      ffprobe -version
      

      Если выдаст версию — всё ок. Если «не является внутренней командой» — нужно добавить путь к ним в переменную среды PATH или указать в скрипте явно (тогда лучше v2_compat с параметром -ffmpegPath).

    2. Политика выполнения. У тебя уже стоит Bypass для текущего процесса — этого достаточно.

    3. Права на запись. Скрипт будет создавать .mp3 и дописывать ConversionLog.txt в текущей папке — убедись, что папка не только для чтения.

    Если хочешь, могу взять твой код v2 и полностью переписать его без using, чтобы он был максимально совместим и чист. Что выбираешь? 😊

  • Я

    Я уже с семеркой работаю PS D:\trnt\HeavyMetal\Dio\HolyDiver\TM1> $PSVersionTable.PSVersion Major Minor Patch PreReleaseLabel BuildLabel ----- ----- ----- --------------- ---------- 7 6 4
  • Алиса

    Отлично, PowerShell 7.6.4 — это даже лучше: он стабильнее и быстрее, чем 5.1. Но ключевое тут не версия, а то, что using (в стиле C#) в PowerShell всё равно не работает — ни в 5.1, ни в 7.x. Ошибка из-за него будет везде.


    Что делать прямо сейчас

    У тебя есть два рабочих варианта.

    Вариант 1 (самый простой) — запусти первую версию

    Твой скрипт Convert-FlacToMp3.ps1 (тот, что без using, с ручным циклом и Dispose() через finally) уже полностью совместим с PowerShell 7 и делает всё нужное: ищет FLAC в текущей папке, пропускает готовые MP3, проверяет битрейт, пишет лог.

    Просто выполни:

    powershell
    .\Convert-FlacToMp3.ps1
    

    Никаких параметров не нужно, если ffmpeg и ffprobe уже в PATH.


    Вариант 2 — если хочешь именно «v2» (с рекурсией и параметрами)

    Тогда бери Convert-FlacToMp3_v2_compat.ps1, потому что в нём нет using: там уже правильно сделано через try/finally и .Dispose().

    Запуск:

    powershell
    .\Convert-FlacToMp3_v2_compat.ps1
    

    Если ffmpeg не в PATH, укажи путь явно:

    powershell
    .\Convert-FlacToMp3_v2_compat.ps1 -ffmpegPath "C:\Путь\К\ffmpeg.exe" -ffprobePath "C:\Путь\К\ffprobe.exe"
    

    Как быстро проверить, что всё готово

    Выполни эти две команды в той же папке:

    powershell
    ffmpeg -version
    ffprobe -version
    
    • Если видишь версию — можно сразу запускать скрипт.
    • Если пишет «не является внутренней командой» — значит, ffmpeg не добавлен в PATH. Тогда используй Вариант 2 с явным указанием путей.

    Важное про твои предпочтения (в духе «не люблю кашу из компонентов»)

    Твой текущий подход здесь идеален:

    • Отдельный скрипт вместо «всё в одном монстре».
    • Чёткое разделение ответственности: скрипт только конвертирует и логирует, ffmpeg/ffprobe делают саму кодировку.
    • Минимум зависимостей: никаких лишних библиотек.

    Это как раз тот случай, когда «три малых компонента» (скрипт + ffmpeg + ffprobe) работают лучше, чем одна огромная штука.


    Если хочешь, могу взять твой «сломанный» v2 с using и выдать тебе сразу исправленную версию (полностью готовую к запуску в PS 7), где всё переписано на try/finally. Или, если первая версия устраивает — можно вообще ничего не править и просто запустить. Что ближе? 😊

  • Комментариев нет:

    Отправить комментарий