Релиз v1.0.0
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
#requires -Version 5.1
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Корень проекта определяется независимо от текущей папки PowerShell.
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$ToolsDir = Join-Path $ProjectRoot "tools"
|
||||
$OutputDir = Join-Path $ProjectRoot "output"
|
||||
|
||||
if (-not (Test-Path $OutputDir)) {
|
||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||
}
|
||||
|
||||
function Find-Tool {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
|
||||
[string[]]$LocalNames = @()
|
||||
)
|
||||
|
||||
foreach ($localName in $LocalNames) {
|
||||
$localPath = Join-Path $ToolsDir $localName
|
||||
if (Test-Path $localPath -PathType Leaf) {
|
||||
return (Resolve-Path $localPath).Path
|
||||
}
|
||||
}
|
||||
|
||||
$command = Get-Command $Name -ErrorAction SilentlyContinue
|
||||
if ($command) {
|
||||
return $command.Source
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Require-Tool {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
|
||||
[string[]]$LocalNames = @(),
|
||||
|
||||
[string]$InstallHint = ""
|
||||
)
|
||||
|
||||
$tool = Find-Tool -Name $Name -LocalNames $LocalNames
|
||||
|
||||
if (-not $tool) {
|
||||
Write-Host ""
|
||||
Write-Host "ОШИБКА: не найдена зависимость '$Name'." -ForegroundColor Red
|
||||
Write-Host "Проверены:"
|
||||
Write-Host " 1. Папка: $ToolsDir"
|
||||
Write-Host " 2. Системная переменная PATH"
|
||||
|
||||
if ($InstallHint) {
|
||||
Write-Host ""
|
||||
Write-Host $InstallHint
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Read-Host "Нажмите Enter для выхода"
|
||||
exit 1
|
||||
}
|
||||
|
||||
return $tool
|
||||
}
|
||||
|
||||
$YtDlp = Require-Tool `
|
||||
-Name "yt-dlp" `
|
||||
-LocalNames @("yt-dlp.exe", "yt-dlp.cmd", "yt-dlp.ps1") `
|
||||
-InstallHint "Скачать yt-dlp: https://github.com/yt-dlp/yt-dlp/releases/latest"
|
||||
|
||||
$Ffmpeg = Require-Tool `
|
||||
-Name "ffmpeg" `
|
||||
-LocalNames @("ffmpeg.exe") `
|
||||
-InstallHint "Скачать FFmpeg: https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z"
|
||||
|
||||
$VotCli = Require-Tool `
|
||||
-Name "vot-cli" `
|
||||
-LocalNames @("vot-cli.cmd", "vot-cli.exe", "vot-cli.ps1") `
|
||||
-InstallHint "Установка vot-cli: npm install -g vot-cli"
|
||||
|
||||
$Node = Require-Tool `
|
||||
-Name "node" `
|
||||
-LocalNames @("node.exe") `
|
||||
-InstallHint "Установите Node.js: https://nodejs.org/"
|
||||
|
||||
$Npm = Require-Tool `
|
||||
-Name "npm" `
|
||||
-LocalNames @("npm.cmd", "npm.exe", "npm.ps1") `
|
||||
-InstallHint "npm устанавливается вместе с Node.js."
|
||||
|
||||
$FfmpegDir = Split-Path -Parent $Ffmpeg
|
||||
|
||||
function Get-SafeFileName {
|
||||
param([string]$Name)
|
||||
|
||||
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
|
||||
foreach ($char in $invalidChars) {
|
||||
$Name = $Name.Replace([string]$char, "_")
|
||||
}
|
||||
|
||||
return $Name.Trim()
|
||||
}
|
||||
|
||||
function Process-Video {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$VideoLink,
|
||||
|
||||
[double]$OriginalSoundRatio = 0.2
|
||||
)
|
||||
|
||||
$TempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("youtube-tools-" + [Guid]::NewGuid().ToString("N"))
|
||||
$TempVideoDir = Join-Path $TempRoot "video"
|
||||
$TempAudioDir = Join-Path $TempRoot "audio"
|
||||
|
||||
New-Item -ItemType Directory -Path $TempVideoDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $TempAudioDir -Force | Out-Null
|
||||
|
||||
try {
|
||||
Write-Host ""
|
||||
Write-Host "Получение названия видео..."
|
||||
|
||||
$Title = (& $YtDlp "--get-title" "--no-playlist" $VideoLink | Select-Object -First 1)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Title)) {
|
||||
$Title = "YouTubeVideo"
|
||||
}
|
||||
|
||||
$SafeTitle = Get-SafeFileName $Title
|
||||
$TempVideoTemplate = Join-Path $TempVideoDir "%(title)s.%(ext)s"
|
||||
|
||||
Write-Host "Скачивание исходного видео..."
|
||||
|
||||
& $YtDlp `
|
||||
"--no-playlist" `
|
||||
"-f" "bv*+ba/b" `
|
||||
"--ffmpeg-location" $FfmpegDir `
|
||||
"-o" $TempVideoTemplate `
|
||||
$VideoLink
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "yt-dlp завершил работу с кодом $LASTEXITCODE."
|
||||
}
|
||||
|
||||
$VideoFile = Get-ChildItem -Path $TempVideoDir -File |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $VideoFile) {
|
||||
throw "Не найден скачанный видеофайл."
|
||||
}
|
||||
|
||||
Write-Host "Получение переведённой аудиодорожки..."
|
||||
|
||||
& $VotCli $VideoLink "--output" $TempAudioDir
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "vot-cli завершил работу с кодом $LASTEXITCODE."
|
||||
}
|
||||
|
||||
$AudioFile = Get-ChildItem -Path $TempAudioDir -File |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $AudioFile) {
|
||||
throw "vot-cli не создал аудиофайл."
|
||||
}
|
||||
|
||||
$OutputFile = Join-Path $OutputDir ($SafeTitle + "_перевод.mp4")
|
||||
|
||||
Write-Host "Смешивание оригинального звука и перевода..."
|
||||
|
||||
$Filter = "[0:a]volume=$OriginalSoundRatio[original];[original][1:a]amix=inputs=2:duration=longest[audio_out]"
|
||||
|
||||
& $Ffmpeg `
|
||||
"-hide_banner" `
|
||||
"-i" $VideoFile.FullName `
|
||||
"-i" $AudioFile.FullName `
|
||||
"-c:v" "copy" `
|
||||
"-c:a" "aac" `
|
||||
"-b:a" "128k" `
|
||||
"-filter_complex" $Filter `
|
||||
"-map" "0:v:0" `
|
||||
"-map" "[audio_out]" `
|
||||
"-movflags" "+faststart" `
|
||||
"-y" `
|
||||
$OutputFile
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "FFmpeg завершил работу с кодом $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Готово: $OutputFile" -ForegroundColor Green
|
||||
}
|
||||
finally {
|
||||
if (Test-Path $TempRoot) {
|
||||
Remove-Item -Path $TempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "============================================================"
|
||||
Write-Host " СКАЧИВАНИЕ YOUTUBE С ЗАКАДРОВЫМ ПЕРЕВОДОМ"
|
||||
Write-Host "============================================================"
|
||||
Write-Host "Результат: $OutputDir"
|
||||
Write-Host ""
|
||||
|
||||
$VideoLinksInput = Read-Host "Введите ссылку или несколько ссылок через запятую"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($VideoLinksInput)) {
|
||||
Write-Host ""
|
||||
Write-Host "ОШИБКА: ссылки не введены." -ForegroundColor Red
|
||||
Read-Host "Нажмите Enter для выхода"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$VideoLinks = $VideoLinksInput -split "," |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
|
||||
foreach ($VideoLink in $VideoLinks) {
|
||||
Write-Host ""
|
||||
Write-Host "------------------------------------------------------------"
|
||||
Write-Host "Обработка: $VideoLink"
|
||||
Write-Host "------------------------------------------------------------"
|
||||
|
||||
try {
|
||||
Process-Video -VideoLink $VideoLink -OriginalSoundRatio 0.2
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "ОШИБКА: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Обработка завершена."
|
||||
Write-Host "Результаты находятся в: $OutputDir"
|
||||
Write-Host ""
|
||||
Read-Host "Нажмите Enter для выхода"
|
||||
Reference in New Issue
Block a user