Problem: I need to pipe ffmpeg stream to a variable using PowerShell. The code below "works" (95% of the time), the problem is some Unicode characters do not exist when printed to the console which results in a malformed image.
Question: Is there a way to pipe bytes, decimal values, or even binary values instead of Unicode to console from ffmpeg???
clear-host
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$ffmpeg = "C:\ffmpeg\bin\ffmpeg.exe"
$input = "C:\Users\Jukari\Desktop\test\test1.mp4"
$output = "C:\Users\Jukari\Desktop\test\test1.bmp"
$screenshot = 3
[console]::OutputEncoding = New-Object System.Text.UnicodeEncoding
[string]$image = & cmd /u /c "chcp 65001 > NUL && $ffmpeg -i `"$input`" -hide_banner -loglevel error -ss $screenshot -vframes 1 -an -c:v bmp -f image2pipe - 2>&1" 2>&1 | Out-String -Stream
[console]::OutputEncoding = New-Object System.Text.UTF8Encoding
$bytes = [System.Text.Encoding]::Unicode.GetBytes($image)
$stream = new-object System.IO.MemoryStream(,$bytes)
$image_out = [system.drawing.Image]::FromStream($stream,1,1)
write-host $image_out.Height
write-host $image_out.Width
if(Test-Path -LiteralPath $output)
{
Remove-Item $output
}
$image_out.Save($output)
$image_out.Dispose()
Thank you in advance!
Related
Hi I have a script to automate some tasks, running in powershell core v.7.+.
In one of these scripts when I run the command inside the ps1 file and the special characters returned is encoded and I can't decode to right format, below the command used to do this and the return:
// the variable $script is my command its a ask-cli command to work in alexa
$model = pwsh -Command $script
/*
* when I running the script from here the special characters returned is these:
* "nächste",
* "nächstgelegene"
*/
But when I run the same command directly in the terminal the strings returned are:
/*
* "nächste",
* "nächstgelegene"
*/
I would like to know how can I run the command inside the file without encode the return.
I already tried some things as:
$encoding = [System.Text.Encoding]::Unicode
$model = pwsh -Command $script
Write-Output $model
$model = $encoding.GetBytes($model)
$model = $encoding.GetString($model)
But don't work as expected, I don't know more how I can to this, if someone could help me with this I appreciate too much.
Try returning the string as bytes and then decode it from the place you are calling the function pwsh. This would preserve it from any changes. What you're doing is converting it into bytes after receiving it then returning it to string.
Below my script:
(Get-Content "$currentPath\skill-package\skill.json" -Raw | ConvertFrom-Json).manifest.publishingInformation.locales.PSObject.Properties | ForEach-Object {
$lang = $_.Name
Write-Output "Profile: $profile skill-id: $skillId language: $lang"
$script = "ask smapi get-interaction-model -l $lang -s $skillId -p $profile -g $env"
Write-Output 'Running script'
Write-Warning $script
# $encoding = [System.Text.Encoding]::ASCII
$model = pwsh -Command $script
Write-Output $model
$model = $model
| ConvertFrom-Json -Depth 100
| Select-Object * -ExcludeProperty version
| ConvertTo-Json -Depth 100
# out-file "$file$lang.json" -InputObject $model -Encoding ascii
Write-Output "New model saved locally $file$lang.json"
}
Write-Warning 'Finished!!!'
(Get-Content "$currentPath\skill-package\skill.json" -Raw | ConvertFrom-Json).manifest.publishingInformation.locales.PSObject.Properties | ForEach-Object {
$lang = $_.Name
Write-Output "Profile: $profile skill-id: $skillId language: $lang"
$script = "`$command = ask smapi get-interaction-model -l $lang -s $skillId -p $profile -g $env;`$command = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes(Invoke-Expression `$command));`$command"
Write-Output 'Running script'
Write-Warning $script
# $encoding = [System.Text.Encoding]::ASCII
$model = pwsh -Command $script
$model = Text.Encoding::Unicode.GetString([Convert]::FromBase64String($model))
Write-Output $model
$model = $model
| ConvertFrom-Json -Depth 100
| Select-Object * -ExcludeProperty version
| ConvertTo-Json -Depth 100
# out-file "$file$lang.json" -InputObject $model -Encoding ascii
Write-Output "New model saved locally $file$lang.json"
}
Write-Warning 'Finished!!!'
After many researches, I could find something more easiest to solve my problem.
Powershell has by default a different output encoder used in these cases, and the only thing I need to do it's change it.
I used the command:
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
I find this question explaining how this work and this help a lot, for more question please check this answer.
The following script demonstrates an issue I'm experiencing after moving from PowerShell v4 to v5.1. I make extensive use of Start-Process redirecting StandardError to a file, if this file contains data after execution I can read the file to see what the issue was.
However, with v5.1 when the launched PowerShell process starts, it makes use of progress output which is on the error stream, so this is redirected to the error file. In previous versions of PowerShell this did not happen, if I use Command rather than EncodedCommand it works as expected.
I believe this is a bug but it would be useful if someone could confirm or suggest a workaround?
The output to the script below is as follows:
PowerShell v4
No Errors
hello world!
PowerShell v5.1
WARNING:
SourceId Record
1 parent = -1 id = 0 act = Preparing modules for first use. stat = cur = pct = -1 sec = -1 type = Completed
hello world!
hello world!
Demo Script
Note: this will create 2 temporary files, which are both deleted at the end.
$outfile = [System.IO.Path]::GetTempFileName()
$errorfile = [System.IO.Path]::GetTempFileName()
$command = "write-host 'hello world!'"
$encodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($command))
$argumentList = #(
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-EncodedCommand",
$encodedCommand
)
Start-Process powershell `
-argumentlist $argumentList `
-PassThru `
-wait `
-RedirectStandardOut $outfile `
-NoNewWindow `
-RedirectStandardError $errorfile | Out-Null
if ((Get-Item $errorfile).length -gt 0)
{
Import-Clixml $errorfile | Out-String | Write-Warning
}
else
{
Write-Host "No Errors"
}
Get-Content $outfile
$outfile, $errorfile | Remove-item
So I needed a script that watches a directory and converts files using HandbrakeCLI. I found a part of this powershell here on stackoverflow and I adjusted some things for my project.
$global:watch = "C:\~\cmp\" ### watching directory
$global:convert = "C:\~\convert\" ### handbrakecli and preset location
$global:outp = "C:\~\output_folder\" ### output location
$global:extIn = ".mkv"
$global:extOut = ".mp4"
Write-Host "Watching directory = $watch"
Write-Host "HandbrakeCLI / json preset location = $convert"
Write-Host "Output directory = $outp"
Write-Host "Input extension = $extIn ; Output extension = $extOut"
Write-Host "Waiting for change in directory..."
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher;
$watcher.Path = $watch;
$watcher.Filter = "*"+$extIn;
$watcher.IncludeSubdirectories = $false;
$watcher.EnableRaisingEvents = $false;
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action =
{
$path = $Event.SourceEventArgs.FullPath;
$handbrakecli = $convert+"HandBrakeCLI.exe";
$fl = Split-Path $path -leaf;
Write-Host "New file found: $fl";
$flName, $flExt = $fl.split('.')
$mp4File = $watch+"\"+$flName+$extOut
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content -path ($convert+"log.txt") -value $logline
Write-Host "Added entry to log"
Write-Host "Start converting using HandbrakeCLI..."
& cmd.exe /c $handbrakecli -i $path -o $mp4File --preset-import-file my_preset.json
Write-Host "Done converting!"
Write-Host "Moving file to folder..."
& cmd.exe /c move /Y $mp4File $outp
Write-Host "File moved!"
& cmd.exe /c del $path /F
Write-Host "$fl has been removed from local folder"
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
Register-ObjectEvent $watcher "Changed" -Action $action
Register-ObjectEvent $watcher "Renamed" -Action $action
while ($true) {sleep 5}
While at first everything seemed to work, I started to notice that "sometimes" the subtitles were not added or green frames were inserted (or replaced original frame) after every frame (normal - green - normal - green - etc.).
An example: I added 2 mkv files to the directory, the 1st one got converted just fine with subtitles while the 2nd file didn't have any subtitles.
I'm an amateur when it comes to this stuff, but I think it has something to do with the & cmd.exe /c. I also found that you could to Start-Process in powershell, but I don't know how to use it.
So if someone could help me convert this & cmd.exe /c $handbrakecli -i $path -o $mp4File --preset-import-file my_preset.json to something with Start-Process ..., maybe it will help me out.
EDIT
So I made the changes that Tomalak suggested (simpler this way), but Move-Item and Remove-Item don't seem to work.
EDIT 2
Added -LiteralPath as argument for Move-Item / Remove-Item (needed for filenames containt square brackets)
$inputFolder = "C:\~\cmp\"
$outputFolder = "C:\~\output_folder\"
$handbrake = "C:\~\convert\HandBrakeCLI.exe"
$presetJson ="C:\~\convert\my_preset.json"
$extIn = "mkv"
$extOut = "mp4"
while ($true) {
Get-ChildItem -Path $inputFolder -Filter "*.$extIn" | ForEach-Object {
$inFile = $_.FullName
$outFile = $inputFolder + $_.FullName.split('\.')[-2] + ".$extOut" #changed this because I wanted the file in the same directory as input file
Write-Host "Converting: $inFile"
& $handbrake -i $inFile -o $outFile --preset-import-file $presetJson
if ($LASTEXITCODE -eq 0) {
Move-Item -LiteralPath $outFile $outputFolder -Force #move to output folder
Write-Host "Done: $outFile"
Remove-Item -LiteralPath $inFile -Force #removing the input item, not output
Write-Host "Removed input file!"
} else {
Write-Error "Conversion failed!"
}
}
sleep 5
}
While subtitles are added to all output files, I still get green-flickering sometimes. I used 3 files as a test run, result: 1st flickering, 2nd OK, 3rd flickering. I have no clue why some are fine and some got the flickering. So I'm considering to maybe use ffmpeg instead.
EDIT 3
For future visitors: use ffmpeg instead of HandbrakeCLI:
ffmpeg.exe -i "C:\~\inputfile.mkv" -filter_complex "subtitles='C\:/Users/~/inputfile.mkv'" -c:v libx264 -preset veryfast -b:v 2750k -c:a aac $outputfile.mp4
Instead of using file system notifications, structure your script around a simple endless loop:
$inputFolder = "C:\~\cmp"
$outputFolder = "C:\~\convert"
$handbrake = "C:\~\convert\HandBrakeCLI.exe"
$presetJson = "C:\~\convert\my_preset.json"
$extIn = "mkv"
$extOut = "mp4"
while ($true) {
Get-ChildItem -Path $inputFolder -Filter "*.$extIn" | ForEach-Object {
$inFile = $_.FullName
$outFile = "$($_.BaseName).$extOut"
if (Test-Path $outFile) { Remove-Item $outFile -Force -LiteralPath }
Write-Host "Converting: $inFile"
& $handbrake -i $inFile -o $outFile --preset-import-file $presetJson
if ($LASTEXITCODE -eq 0) {
Move-Item $outFile $outputFolder -Force -LiteralPath
Write-Host "Done: $outFile"
} else {
Write-Error "Conversion not successful."
}
}
sleep 5
}
The & makes Powershell execute whatever program the $handbrake variable points to.
As an exercise you can convert the top-level variables to script parameters, so that you can re-use the script for other batch jobs.
ReadPower.ps1 contain the following. And able to get output on output.csv locally.
$port= new-Object System.IO.Ports.SerialPort COM7,19200,None,8,one
$port.open()
$port.WriteLine("?p")
$port.ReadLine()
$MachinePower= $port.ReadLine()
$date = Get-Date
$TextTOsend = "MachineName,"+ $date +"," +$MachinePower
$TextTOsend | Out-File output.csv -Append -Encoding ASCII
$port.Close()
When run remotely, using this
psexec \\RemotePC -s -u username -p password powershell C:\ReadFAMLaser\ReadPower.ps1
No output on output.csv
Did I miss out anything?
I'm working on a simple script that monitors a folder for large video files and if it's larger then the set size it should send it to HandBrake to convert.
The main function of the script is done and works more or less but I am having trouble displaying proper output from HandBrake.
Here is what I have written so far:
$Folder = "E:\Series" #Folder to be monitored for large video files.
$Output = "E:\Encoded" #Folder where encoding jobs go to, and that will be monitored for completed jobs to replace the original file with.
$MaxMB = "15" #Max MB a minute
$MI_CLI = "C:\Program Files\MediaInfoCLI\MediaInfo.exe" #Location oo MediaInfoCLI
$HB_CLI = "C:\Program Files\Handbrake\HandBrakeCLI.exe" #Location of HandBrakeCLI
$HB_Container = "Copy" #HandBrake container output
$Filter = '*.*'
Write-Host "Monitoring "
$fsw = New-Object IO.FileSystemWatcher $Folder, $Filter
$fsw.IncludeSubdirectories = $true
Register-ObjectEvent $fsw Changed -SourceIdentifier FileUpdated -Action {
Start-Sleep -s 1
$FilePath = $EventArgs.FullPath
$FileName = $FilePath.split('\')[-1]
if($FilePath -imatch '\.(?:mp4|mkv)'){
if((Test-Path -LiteralPath $filePath) -and -not (Test-Path -LiteralPath "$Output\$FileName")){
if(Test-FileReady $FilePath){
$fileSize = (Get-Item $FilePath).length
if($fileSize -ge 734003200){
Write-Host ""
Write-Host "Large Video Detected: `"$($FileName)`""
Write-Host "Sending To HandBrake..."
HB-Convert $FilePath $Output
}
}
}
}
}
function HB-Convert{
param ([parameter(Mandatory=$true)][string]$source,[parameter(Mandatory=$true)][string]$dest)
if(-not (Test-Path $source) -or -not (Test-Path $dest)) {return}
$FileName = $source.split('\')[-1]
start-process $HB_CLI -ArgumentList "-i `"$source`" -t 13 --angle 1 -c 1 -o `"$dest\$FileName`" -f mkv -w 1280 --crop 0:0:0:0 --loose-anamorphic --modulus 2 -e x265 -q 20 --vfr -a 1 -E copy -6 dpl2 -R Auto -B 160 -D 0 --gain 0 --audio-fallback ac3 --encoder-preset=faster --verbose=0 2> log.txt" -wait -nonewwindow
#HandBrakeCLI -i `"$source`" -t 13 --angle 1 -c 1 -o `"$dest\$FileName`" -f mkv -w 1280 --crop 0:0:0:0 --loose-anamorphic --modulus 2 -e x265 -q 20 --vfr -a 1 -E copy -6 dpl2 -R Auto -B 160 -D 0 --gain 0 --audio-fallback ac3 --encoder-preset=faster --verbose=0 2> log.txt
}
function Test-FileReady {
Param([parameter(Mandatory=$true)]$path)
if (Test-Path -LiteralPath $path) {
trap {
return $false
}
$stream = New-Object system.IO.StreamReader $path
if ($stream) {
$stream.Close()
return $true
}
}
}
Now in HB-Convert function I have 2 lines that call HandBrakeCLI.
one through Start-Process
and one using HandBrakeCLI(I have added handbrake dir to my system environment variable)
the latter one is now marked out.
When I would call HB-Convert(Using HandBrakeCLI not Start-Process) manually in the command prompt everything works like it should I only get the progress of handbrake displayed.
Encoding: task 1 of 1, 74.66 % (26.78 fps, avg 47.17 fps, ETA
00h05m40s)
Now when this is called through the FileSystemWatcher It will not display anything from handbrake it will only show output from the script it self
Large Video Detected: "FileName"
Sending To HandBrake...
it will hang there till the encoding is done
now when FileSystemWatcher calls HB-Convert with start-process handbrake will output all data not just the progress witch is very annoying.
so how would I get it to display only the progress when its called true the FileSystemWatcher.
I have been trying to get it to work for hours its driving me nuts. hope some one here can fix it.
I only learned a bit of PS for this so when I say that I am a nub in PS thats a understatement :D
Ok I got it to work
adding this to the script
function global:HB-UpdateProgress{
Process{
$position = $host.ui.rawui.cursorposition
$position.X = 0
$host.ui.rawui.cursorposition = $position
Write-Host #($input)[0] -NoNewline
}
}
then use it as a pipeline when calling HandBrakeCLI will display the output properly