Output ffmpeg normal
Anda, seperti saya, pernah mendengar tentang ffmpeg, tetapi takut menggunakannya. Hormati orang-orang seperti itu, seluruh program ditulis dalam C (si, no # dan ++).
Terlepas dari fungsionalitas program yang sangat tinggi, mengerikan, verbose besar, argumen yang tidak nyaman, default yang aneh, kurangnya sintaksis otomatis dan tak kenal ampun, ditambah dengan kesalahan yang tidak selalu mendetail dan dapat dipahami oleh pengguna, membuat program yang luar biasa ini menjadi tidak nyaman.
Saya tidak menemukan cmdlet yang sudah jadi di Internet untuk berinteraksi dengan ffmpeg, jadi mari selesaikan apa yang perlu ditingkatkan dan lakukan semuanya sehingga tidak memalukan untuk mempublikasikannya di PowershellGallery.
Membuat benda untuk pipa
class VideoFile {
$InputFileLiteralPath
$OutFileLiteralPath
$Arguments
}
Semuanya dimulai dengan sebuah objek. Program FFmpeg cukup sederhana, yang perlu kita ketahui adalah di mana kita bekerja, bagaimana kita bekerja dengannya dan di mana kita meletakkan semuanya.
Mulai, proses, akhiri
Di blok mulai, Anda tidak bisa bekerja dengan argumen yang diterima dengan cara apa pun, yaitu, Anda tidak bisa langsung menggabungkan string dengan argumen, di blok mulai semua parameter adalah nol.
Namun, di sini Anda dapat memuat file yang dapat dieksekusi, mengimpor modul yang diperlukan dan menginisialisasi penghitung untuk semua file yang akan diproses, bekerja dengan konstanta dan variabel sistem.
Pikirkan konstruksi Begin-Process sebagai foreach, di mana begin dieksekusi sebelum fungsi dipanggil dan parameter ditetapkan, dan End dieksekusi terakhir setelah foreach.
Beginilah tampilan kode jika tidak ada konstruksi Begin, Process, End. Ini adalah contoh kode yang buruk, Anda tidak boleh menulisnya.
# begin
$InputColection = Get-ChildItem -Path C:\file.txt
function Invoke-FunctionName {
param (
$i
)
# process
$InputColection | ForEach-Object {
$buffer = $_ | ConvertTo-Json
}
# end
return $buffer
}
Invoke-FunctionName -i $InputColection
Apa yang harus diletakkan di blok Begin?
Penghitung, buat jalur ke file yang dapat dieksekusi dan buat salam. Beginilah tampilan blok Begin bagi saya:
begin {
$PathToModule = Split-Path (Get-Module -ListAvailable ConvertTo-MP4).Path
$FfmpegPath = Join-Path (Split-Path $PathToModule) "ffmpeg"
$Exec = (Join-Path -Path $FfmpegPath -ChildPath "ffmpeg.exe")
$OutputArray = @()
$yesToAll = $false
$noToAll = $false
$Location = Get-Location
}
Saya ingin menarik perhatian Anda ke garis, ini adalah peretasan kehidupan nyata:
$PathToModule = Split-Path (Get-Module -ListAvailable ConvertTo-MP4).Path
Menggunakan Get-Module, kami mendapatkan jalur ke folder dengan modul, dan Split-Path mengambil nilai input dan mengembalikan folder satu tingkat di bawah. Dengan demikian, Anda dapat menyimpan file yang dapat dieksekusi di sebelah folder modul, tetapi tidak di folder itu sendiri.
Seperti ini:
PSffmpeg/
βββ ConvertTo-MP4/
β βββ ConvertTo-MP4.psm1
β βββ ConvertTo-MP4.psd1
β βββ Readme.md
βββ ffmpeg/
βββ ffmpeg.exe
βββ ffplay.exe
βββ ffprobe.exe
Dan dengan bantuan Split-Path, Anda dapat mengatur gaya hingga ke tingkat di bawah.
Set-Location ( Get-Location | Split-Path )
Bagaimana cara membuat blok Param yang benar?
Segera setelah Mulai, ada Proses bersama dengan blok Param. Blok Param itu sendiri menahan pemeriksaan nol, dan memvalidasi argumen. Contoh:
Validasi Daftar:
[ValidateSet("libx264", "libx265")]
$Encoder
Semuanya sederhana di sini. Jika nilainya tidak terlihat seperti dalam daftar, maka False dikembalikan dan kemudian pengecualian dilemparkan.
Validasi rentang:
[ValidateRange(0, 51)]
[UInt16]$Quality = 21
Anda dapat memvalidasi pada rentang dengan menentukan angka dari dan ke. Crf ffmpeg mendukung angka dari 0 hingga 51, jadi kisaran ini ditentukan di sini.
Validasi dengan skrip:
[ValidateScript( { $_ -match "(?:(?:([01]?\d|2[0-3]):)?([0-5]?\d):)?([0-5]?\d)" })]
[timespan]$TrimStart
Masukan kompleks dapat divalidasi dengan tetap atau seluruh skrip. Hal utama adalah bahwa skrip validasi mengembalikan true atau false.
Mendukung Proses dan kekuatan
Jadi, Anda perlu mengenkode ulang file dengan codec yang berbeda, tetapi dengan nama yang sama. Antarmuka ffmpeg klasik meminta pengguna menekan y / N untuk menimpa file. Dan untuk setiap file.
Pilihan terbaik adalah Ya standar untuk semua, Ya, Tidak, Tidak untuk semua.
Saya memilih "Ya untuk semua" dan Anda dapat menulis ulang file dalam batch dan ffmpeg tidak akan berhenti dan bertanya lagi apakah Anda ingin mengganti file ini atau tidak.
function ConvertTo-WEBM {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'high')]
param (
#
[switch]$Force
)
Beginilah tampilan blok Param telanjang dari orang yang sehat. Dengan SupportsShouldProcess, fungsi tersebut dapat bertanya sebelum melakukan tindakan destruktif, dan sakelar gaya sepenuhnya mengabaikannya.
Dalam kasus kami, kami bekerja dengan file video dan sebelum menimpa file tersebut, kami ingin memastikan bahwa pengguna memahami apa yang dilakukan fungsi tersebut.
# Jika parameter Force ditentukan, maka semua file akan ditimpa secara diam-diam
if ($ Force) {
$ continue = $ true
$ yesToAll = $ true
}
$Verb = "Overwrite file: " + $Arguments.OutFileLiteralPath # , ShouldContinue
# , .
if (Test-Path $Arguments.OutFileLiteralPath) {
# , ,
$continue = $PSCmdlet.ShouldContinue($OutFileLiteralPath, $Verb, [ref]$yesToAll, [ref]$noToAll)
# - , , ,
if ($continue) {
Start-Process $Exec -ArgumentList $Arguments.Arguments -NoNewWindow -Wait
}
# -
else {
break
}
}
# ,
else {
Start-Process $Exec -ArgumentList $Arguments.Arguments -NoNewWindow -Wait
}
Membuat pipa biasa
Dalam gaya fungsional, pipa normal akan terlihat seperti ini:
function New-FfmpegArgs {
$VideoFile = $InputObject
| Join-InputFileLiterallPath
| Join-Preset -Preset $Preset
| Join-ConstantRateFactor -ConstantRateFactor $Quality
| Join-VideoScale -Height $Height -Width $Width
| Join-Loglevel -VerboseEnabled $PSCmdlet.MyInvocation.BoundParameters["Verbose"]
| Join-Trim -TrimStart $TrimStart -TrimEnd $TrimEnd -FfmpegPath "C:\Users\nneeo\Documents\lib.Scripts\PSffmpeg\ConvertTo-WEBM\ffmpeg\" -SourceVideoPath ([IO.Path]::GetFullPath($InputObject))
| Join-Codec -Encoder $Encoder -FfmpegPath "C:\Users\nneeo\Documents\lib.Scripts\PSffmpeg\ConvertTo-WEBM\ffmpeg\" -SourceVideoPath ([IO.Path]::GetFullPath($InputObject))
| Join-OutFileLiterallPath -OutFileLiteralPath $OutFileLiteralPath -SourceVideoPath ([IO.Path]::GetFullPath($InputObject))
return $VideoFile
}
Tapi ini mengerikan, semuanya terlihat seperti mie, tidak bisakah kamu benar-benar membuat semuanya lebih bersih?
Tentu saja Anda bisa, tetapi Anda perlu menggunakan fungsi bertingkat untuk ini. Mereka dapat melihat deklarasi variabel di fungsi induk, yang sangat memudahkan. Berikut contohnya:
function Invoke-FunctionName {
$ParentVar = "Hello"
function Invoke-NetstedFunctionName {
Write-Host $ParentVar
}
Invoke-NetstedFunctionName
}
Tetapi pada saat yang sama, jika Anda memiliki banyak fungsi yang sama, Anda harus menyalin dan menempelkan kode yang sama ke setiap fungsi setiap saat. Dalam kasus ConvertTo-Mp4, ConvertTo-Webp, dll. lebih mudah dilakukan seperti yang saya lakukan.
Jika saya menggunakan fungsi bersarang, akan terlihat seperti ini:
$VideoFile = $InputObject
| Join-InputFileLiterallPath
| Join-Preset
| Join-ConstantRateFactor
| Join-VideoScale
| Join-Loglevel
| Join-Trim
| Join-Codec
| Join-OutFileLiterallPath
Tetapi sekali lagi, ini sangat mengurangi pertukaran kode.
Membuat fungsi normal
Kita perlu membuat argumen untuk ffmpeg.exe, dan untuk ini tidak ada yang lebih baik dari pipeline. Betapa saya menyukai saluran pipa!
Alih-alih interpolasi atau pembuat string, kami menggunakan pipa yang dapat mengoreksi argumen atau menulis kesalahan yang relevan. Anda melihat pipa itu sendiri di atas.
Sekarang tentang seperti apa fungsi pipeline paling keren itu :
1. Measure-VideoResolution
function Measure-VideoResolution {
param (
$SourceVideoPath,
$FfmpegPath
)
Set-Location $FfmpegPath
.\ffprobe.exe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 $SourceVideoPath | ForEach-Object {
return $_
}
}
h265 menyimpan bitrate mulai dari 1080 dan lebih tinggi, pada resolusi video yang lebih rendah itu tidak terlalu penting, oleh karena itu, untuk encoding video besar, Anda harus menentukan h265 sebagai default.
Return in Foreach-Object terlihat sangat aneh. Tapi tidak ada yang bisa Anda lakukan. FFmpeg menulis semuanya ke stdout dan ini adalah cara termudah untuk mengekstrak nilai dari program tersebut. Gunakan trik ini jika Anda perlu menarik sesuatu dari stdout. Jangan gunakan Start-Process, untuk menarik stdout Anda perlu memanggil file yang dapat dieksekusi secara langsung seperti pada contoh ini.
Tidak mungkin memanggil executable di sepanjang jalur lengkap dan mendapatkan stdout dengan cara lain. Anda harus secara khusus pergi ke folder dengan file yang dapat dieksekusi dan menyebutnya dengan nama dari sana. Untuk ini, di blok Begin, skrip mengingat jalur dari mana ia dimulai, sehingga setelah menyelesaikan pekerjaannya tidak mengganggu pengguna.
begin {
$Location = Get-Location
}
Fungsi ini akan terlihat bagus sebagai cmdlet terpisah, ini akan berguna, tetapi untuk masa depan.
2. Bergabung-VideoScale
function Join-VideoScale {
param(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[SupportsWildcards()]
[psobject]$InputObject,
$Height,
$Width
)
switch ($true) {
($null -eq $Height -and $null -eq $Width) {
return $InputObject
}
($null -ne $Height -and $null -ne $Width) {
$InputObject.Arguments += " -vf scale=" + $Width + ":" + $Height
return $InputObject
}
($null -ne $Height) {
$InputObject.Arguments += " -vf scale=" + $Height + ":-2"
return $InputObject
}
($null -ne $Width) {
$InputObject.Arguments += " -vf scale=" + "-2:" + $Width
return $InputObject
}
}
}
Salah satu lelucon favorit saya adalah tombol luar dalam. Tidak ada pola yang cocok di Powershell, tetapi konstruksi seperti itu menggantikannya, untuk sebagian besar.
Fungsi yang akan dijalankan ada di dalam tanda kurung. Dan jika hasil dari fungsi ini sama dengan kondisi pada sakelar, maka blok skrip dieksekusi di dalamnya.
3. Gabung-Potong
function Join-Trim {
param(
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[SupportsWildcards()]
[psobject]$InputObject,
$TrimStart,
$TrimEnd,
$FfmpegPath,
$SourceVideoPath
)
if ($null -ne $TrimStart) {
$TrimStart = [timespan]::Parse($TrimStart)
}
if ($null -ne $TrimEnd) {
$TrimEnd = [timespan]::Parse($TrimEnd)
}
if ($TrimStart -gt $TrimEnd -and $null -ne $TrimEnd) {
Write-Error "TrimStart can not be equal to TrimEnd" -Category InvalidArgument
break
}
if ($TrimStart -ge $TrimEnd -and $null -ne $TrimEnd) {
Write-Error "TrimStart can not be greater than TrimEnd" -Category InvalidArgument
break
}
$ActualVideoLenght = Measure-VideoLenght -SourceVideoPath $SourceVideoPath -FfmpegPath $FfmpegPath
if ($TrimStart -gt $ActualVideoLenght) {
Write-Error "TrimStart can not be greater than video lenght" -Category InvalidArgument
break
}
if ($TrimEnd -gt $ActualVideoLenght) {
Write-Error "TrimEnd can not be greater than video lenght" -Category InvalidArgument
break
}
switch ($true) {
($null -eq $TrimStart -and $null -eq $TrimEnd) {
return $InputObject
}
($null -ne $TrimStart -and $null -ne $TrimEnd) {
$ss = " -ss " + ("{0:hh\:mm\:ss}" -f $TrimStart)
$to = " -to " + ("{0:hh\:mm\:ss}" -f $TrimEnd)
$InputObject.Arguments += $ss + $to
return $InputObject
}
($null -ne $TrimStart) {
$ss = " -ss " + ("{0:hh\:mm\:ss}" -f $TrimStart)
$InputObject.Arguments += $ss
return $InputObject
}
($null -ne $TrimEnd) {
$to = " -to " + ("{0:hh\:mm\:ss}" -f $TrimEnd)
$InputObject.Arguments += $to
return $InputObject
}
}
}
Fitur terbesar di pipeline. Fungsi yang ditulis dengan benar harus menunjukkan kepada pengguna tentang kesalahan, Anda harus mengasapi kode seperti ini.
Untuk kesederhanaan, diputuskan untuk tidak merangkum jalur ke file yang dapat dieksekusi di kelas, itulah sebabnya fungsi mengambil begitu banyak argumen.
Menampilkan objek baru
Agar skrip ini dapat disematkan di pipeline lain, Anda harus membuatnya agar mengembalikan sesuatu. Kami memiliki InputObject yang diambil dari Get-ChildItem, tetapi bidang Nama hanya-baca, Anda tidak bisa begitu saja mengubah nama file.
Untuk membuat keluaran dari perintah terlihat seperti keluaran sistem, Anda perlu menyimpan nama dari objek yang dikodekan dan menggunakan Get-Chilitem untuk menambahkannya ke larik dan menampilkannya.
1. Di blok Begin, nyatakan sebuah array
begin {
$OutputArray = @()
}
2. Di blok Proses, masukkan file yang dikodekan:
Jangan lupa tentang pemeriksaan nol, bahkan dalam pemrograman fungsional mereka diperlukan.
process {
if (Test-Path $Arguments.OutFileLiteralPath) {
$OutputArray += Get-Item -Path $Arguments.OutFileLiteralPath
}
}
3. Di blok End, kembalikan larik yang dihasilkan
end {
return $OutputArray
}
Hore, selesaikan blok akhir, saatnya menggunakan skrip dengan benar.
Kami menggunakan skrip
Contoh # 1
Perintah ini akan memilih semua file dalam folder, mengonversinya ke format mp4 dan segera mengirim file-file ini ke drive jaringan.
Get-ChildItem | ConvertTo-MP4 -Width 320 -Preset Veryslow | Copy-Item βDestination '\\local.smb.server\videofiles'
Contoh # 2
Mari kita mengodekan ulang semua video game kita di folder tertentu, dan menghapus sumbernya.
ConvertTo-MP4 -Path "C:\Users\Administrator\Videos\Escape From Tarkov\" | Remove-Item -Exclude $_
Contoh # 3
Menyandikan semua file dari folder dan memindahkan file baru ke folder lain.
Get-ChildItem | ConvertTo-WEBM | Move-Item -Destination D:\OtherFolder
Kesimpulan
Jadi kami memperbaiki ffmpeg, sepertinya kami tidak melewatkan sesuatu yang penting. Tapi ada apa, ffmpeg tidak bisa digunakan tanpa shell biasa?
Ternyata iya.
Namun masih banyak pekerjaan yang harus diselesaikan. Akan berguna untuk memiliki cmdlet seperti Measure-videoLenght sebagai modul, yang mengembalikan durasi video dalam bentuk Jangka Waktu, dengan bantuan mereka akan mungkin untuk menyederhanakan pipa dan membuat kode lebih ringkas.
Namun, Anda perlu membuat perintah ConvertTo-Webp dan semuanya dalam semangat ini. Juga perlu membuat folder untuk pengguna, jika tidak ada, secara rekursif. Dan memeriksa akses baca dan tulis akan menyenangkan juga.
Sementara itu, ikuti proyek di github .