Capture ffmpeg's metadata output in powershell - powershell

I'm trying to capture the output of ffmpeg in PowerShell(tm) to get some metadata on some ogg & mp3 files. But when I do:
ffmpeg -i file.ogg 2>&1 | sls GENRE
The output includes a bunch of lines without my matching string, "GENRE":
album_artist : Post Human Era
ARTIST : Post Human Era
COMMENT : Visit http://posthumanera.bandcamp.com
DATE : 2013
GENRE : Music
TITLE : Supplies
track : 1
At least one output file must be specified
I am guessing something is different in the encoding. ffmpeg's output is colored, so maybe there are color control characters in the output that are breaking things? Or, maybe ffmpeg's output isn't playing nicely with powershell's default UTF-16? I can't figure out if there is another way to redirect stderr and remove the color characters or change the encoding of stderr.
EDIT:
Strangely, I also get indeterminate output. Sometimes the output is as shown above. Sometimes with precisely the same command the output is:
GENRE :
Which makes slightly more sense, but is still missing the part of the line I care about ('Music').
Somewhere powershell is interpreting something as newlines that is not newlines.

I am still seeing this behavior when I use the old powershell, but I have since upgraded to PowerShell Core (7.0.2), and the problem seems to be solved. I read somewhere that with PowerShell Core they've changed the default encoding to UTF-8, so perhaps it is something related to that.
My theory is that in the old version, whatever code combines the outputstreams normally would make sure that individual lines were preserved and interleaved instead of of cut up. But I would guess that this code is looking for newlines in the default encoding, not UTF-8, so when it receives two UTF-8 streams it doesn't parse the line delimiters correctly and you get weird splits. It seems like there should be a way to change the encoding before it gets to mixing the output streams, but I'm not sure (and now it doesn't matter since it works). Why the output seems to change nondeterministically, I don't know, unless there is something nondeterministic about parsing UTF8 bytes as if they were UTF16 or whatever the default is.

I got something working for my script catching all the output with regex and pipe to a custom object
Function Rotate-Video {
param(
[STRING]$FFMPEGEXE = "P:\Video Editing\ffmpeg-4.3.1-2020-10-01-full_build\bin\ffmpeg.exe",
[parameter(ValueFromPipeline = $true)]
[STRING]$Source = "D:\Video\Source",
[STRING]$Destination = 'D:\Video\Destination',
[STRING]$DestinationExtention='mp4'
)
(Get-ChildItem $Source) | ForEach-Object {
$FileExist = $false
$Source = $_.fullname
$Name = $_.basename
$outputName = $name+'.'+$DestinationExtention
$Fullpath = Join-Path -Path $Destination -ChildPath $outputName
$Regex = "(\w+)=\s+(\d+)\s+(\w+)=(\d+.\d+)\s+(\w)=(\d+.\d+)\s+(\w+)=\s+(\d+)\w+\s+(\w+)=(\d+:\d+:\d+.\d+)\s+(\w+)=(\d+.\d+)\w+\/s\s+(\w+)=(\d+.\d+)"
&$FFMPEGEXE -i $Source -vf transpose=clock $Fullpath 2>&1 | Select-String -Pattern $Regex | ForEach-Object {
$output = ($_ | Select-String -Pattern $regex).Matches.Groups
[PSCUSTOMOBJECT]#{
Source = $source
Destination = $Fullpath
$output[1] = $output[2]
$output[3] = $output[4]
$output[5] = $output[6]
$output[7] = $output[8]
$output[9] = $output[10]
$output[11] = $output[12]
$output[13] = $output[14]
}
}
}
}

Related

Powershell script to write to file maintaining structure

I am working with powershell to read in a file. See sample content in the file.
This is my file with content
-- #Start
This is more content
across different lines
etc etc
-- #End
I am using this code to read in file to a variable.
$content = Get-Content "Myfile.txt";
I then use this code to strip a particular section from the file and based on opening and closing tag.
$stringBuilder = New-Object System.Text.StringBuilder;
$pattern = "-- #Start(.*?)-- #End";
$matched = [regex]::match($content, $pattern).Groups[1].Value;
$stringBuilder.AppendLine($matched.Trim());
$stringBuilder.ToString() | Out-File "Newfile.txt" -Encoding utf8;
The problem that I have is in the file I write to, the formatting is not maintained. So what I want is:
This is more content
across different lines
etc etc
But what I am getting is:
This is more content across different lines etc etc
Any ideas how I can alter my code so that in the outputted file the structures is maintained (multiple lines)?
This regex might do what you're looking for, don't see a point on using a StringBuilder in this case. Do note, since this is a multi-line regex pattern you need to use the -Raw switch to read your file's content.
$re = [regex] '(?ms)(?<=^-- #Start\s*\r?\n).+?(?=^-- #End)'
$re.Match((Get-Content path\to\Myfile.txt -Raw)).Value |
Set-Content path\to\newFile.txt -NoNewLine
See https://regex101.com/r/82HJxf/1 for details.
If you want to do line-by-line processing, you could use a switch to read and process the lines of interest. This is particularly useful if the file is very big and doesn't fit in memory.
& {
$capture = $false
switch -Rege -File path\to\Myfile.txt {
'^-- #Start' { $capture = $true }
'^-- #End' { $capture = $false }
Default { if($capture) { $_ } }
}
} | Set-Content path\to\newFile.txt
If there is only one appearance of the opening and closing tag, you could even break the switch as soon as it encounters the closing tag to stop processing:
'^-- #End' { break }

Powershell optimizing script wildcard/regex/code golfing

I have the following powershell code I wrote that I am trying to optimize even further. I essentially need to get this code block down to under 259 characters. It's currently at 319, this is a challenge I know.
Using a mix of regex/wildcard matching/code golfing I think it is possible. But this is something I'm still learning.
This function will convert each character in the z file into its capslock and numlock value, then use send keys to in a very simplified way of explaining use the lights as a form of Morse code but in binary format instead.
I need this to run from the run box hence the character limit.
Why am I doing this? I'm passing data through the channel that controls the lock keys on the keyboard.
powershell "foreach($b in $(cat $env:tmp\z -En by)){foreach($a in 0x80,
0x40,0x20,0x10,0x08,0x04,0x02,0x01){if($b-band$a){$o+='%{NUMLOCK}'}else
{$o+='%{CAPSLOCK}'}}};$o+='%{SCROLLLOCK}';echo $o >$env:tmp\z;$f=(cat $env:tmp\z);Add-Type -A System.Windows.Forms;[System.Windows.Forms.SendKeys]::SendWait($f);rm $env:tmp\z"
I'm at work so I haven't gotten to test it yet but I think I got it down to 268 characters. And again it's needs to be at 259 or less.
powershell "$d='$env:tmp\z'%($b in $(cat -En by)){%($a in 0x80,
0x40,0x20,0x10,0x08,0x04,0x02,0x01){if($b-band$a){$o+='%{NUMLOCK}'}else
{$o+='%{CAPSLOCK}'}}};$o+='%{SCROLLLOCK}';echo $o >$d;$o=(cat $d);Add-Type -A *m.W*s.F*s;[*m.W*s.F*s.SendKeys]::SendWait($o);rm $d"
You'll know your solution works if you have a file in the tmp folder called "z" with no extention, and after running your code the lights for your lock keys should look like a rave.
Just to post the entirety of the code in readable format, here's the result with Mclayton's suggestion included:
# Gather the content from the file
# The use of (...) around the variable assignment lets the value pass through evaluating it as an expression.
Get-Content ($d=".\desktop\Abe.txt") -Encoding byte |
ForEach-Object -Process {
# Assign the current object in the pipeline to $b.
# This will allow the use of another Foreach-Object (%) for shorter code.
$b = $_;
128,64,32,16,8,4,2,1 |
Foreach-Object -Process {
# Append the results to $o as a concatenated string.
# Given a hashtable with the wanted values, you can access the value by providing the name in []'s.
# Since the bitwise operator -BAND only operates "properly" on two equal-length binary representations and if statement is needed.
# The if statement will return values 1/0 in accordance with -BAND
$o += "%{$(#{1="NUM";0="CAPS"}[$( if ($_-band$b) { 1 } else { 0 } )])LOCK}%{SCROLLLOCK}"
}
};
# Concatenate again to $o, while assigning to $f then outputting to $d.
($f = $o + "%{SCROLLLOCK}") | Out-File -FilePath $d;
Add-Type -AssemblyName 'System.Windows.Forms';
[System.Windows.Forms.SendKeys]::SendWait($f);
Remove-Item -Path $d
...and in shorter form:
gc ($d="$env:TEMP\z") -en by|%{$b=$_;128,64,32,16,8,4,2,1|%{$o+="%{$(#{1="NUM";0="CAPS"}[$(if($_-band$b){1}else{0})])LOCK}%{SCROLLLOCK}"}};($f=$o+"%{SCROLLLOCK}")>$d;Add-Type -A *m.W*s.F*s;[System.Windows.Forms.SendKeys]::SendWait($f);rm $d
I added comments in the code just for future readers trying to follow along.

Parse MDT Log using PowerShell

I am trying to setup a log which would pull different information from another log file to log assets build by MDT using PowerShell. I can extract a line of log using simple get-content | select-string to get the lines i need so output looks like that
[LOG[Validate Domain Credentials [domain\user]]LOG]!
time="16:55:42.000+000" date="10-20-2017" component="Wizard"
context="" type="1" thread="" file="Wizard"
and I am curious if there is a way of capturing things like domain\user, time and date in a separate variables so it can be later passed with another data captured in a similar way in output file in a single line.
This is how you could do it:
$line = Get-Content "<your_log_path>" | Select-String "Validate Domain Credentials" | select -First 1
$regex = '\[(?<domain>[^\\[]+)\\(?<user>[^]]+)\].*time="(?<time>[^"]*)".*date="(?<date>[^"]*)".*component="(?<component>[^"]*)".*context="(?<context>[^"]*)".*type="(?<type>[^"]*)".*thread="(?<thread>[^"]*)".*file="(?<file>[^"]*)"'
if ($line -match $regex) {
$user = $Matches.user
$date = $Matches.date
$time = $Matches.time
# ... now do stuff with your variables ...
}
You might want to build in some error checking etc. (e.g. when no line is found or does not match etc.)
Also you could greatly simplify the regex if you only need those 3 values. I designed it so that all fields from the line are included.
Also, you could convert the values into more appropriate types, which (depending on what you want to do with them afterwards) might make handling them easier:
$type = [int]$Matches.type
$credential = New-Object System.Net.NetworkCredential($Matches.user, $null, $Matches.domain)
$datetime = [DateTime]::ParseExact(($Matches.date + $Matches.time), "MM-dd-yyyyHH:mm:ss.fff+000", [CultureInfo]::InvariantCulture)

How to get output in desired encoding scheme using powershell out-fil

I have a requirement, in which I need to do read line by line, and then do string/character replacement in a datafile having data in windows latin 1.
I've written this powershell (my first one) initially using out-file -encoding option. However the output file thus created was doing some character translation. Then I searched and came across WriteAllLines, but I'm unable to use it in my code.
$encoding =[Text.Encoding]::GetEncoding('iso-8859-1')
$pdsname="ABCD.XYZ.PQRST"
$datafile="ABCD.SCHEMA.TABLE.DAT"
Get-Content ABCD.SCHEMA.TABLE.DAT | ForEach-Object {
$matches = [regex]::Match($_,'ABCD')
$string_to_be_replaced=$_.substring($matches.Index,$pdsname.Length+10)
$string_to_be_replaced="`"$string_to_be_replaced`""
$member = [regex]::match($_,"`"$pdsname\(([^\)]+)\)`"").Groups[1].Value
$_ -replace $([regex]::Escape($string_to_be_replaced)),$member
} | [System.IO.File]::WriteAllLines("C:\Users\USer01", "ABCD.SCHEMA.TABLE.NEW.DAT", $encoding)
With the help of an answer from #Gzeh Niert, I updated my above script. However, when I execute the script the output file being generated by the script has just the last record, as it was unable to append, and it did an overwrite, I tried using System.IO.File]::AppendAllText, but this strangely creates a larger file, and has only the last record. In short its likely that empty lines are being written.
param(
[String]$datafile
)
$pdsname="ABCD.XYZ.PQRST"
$encoding =[Text.Encoding]::GetEncoding('iso-8859-1')
$datafile = "ABCD.SCHEMA.TABLE.DAT"
$datafile2="ABCD.SCHEMA.TABLE.NEW.DAT"
Get-Content $datafile | ForEach-Object {
$matches = [regex]::Match($_,'ABCD')
if($matches.Success) {
$string_to_be_replaced=$_.substring($matches.Index,$pdsname.Length+10)
$string_to_be_replaced="`"$string_to_be_replaced`""
$member = [regex]::match($_,"`"$pdsname\(([^\)]+)\)`"").Groups[1].Value
$replacedContent = $_ -replace $([regex]::Escape($string_to_be_replaced)),$member
[System.IO.File]::AppendAllText($datafile2, $replacedContent, $encoding)
}
else {
[System.IO.File]::AppendAllText($datafile2, $_, $encoding)
}
#[System.IO.File]::WriteAllLines($datafile2, $replacedContent, $encoding)
}
Please help me figure out where I am going wrong.
System.IO.File.WriteAllLines is getting either an array of strings or an IEnumerable of strings as second parameter and cannot be piped to a command because it is not a CmdLet handling pipeline input but a .NET Framework method.
You should try storing your replaced content into a string[]to use it as parameter when saving the file.
param(
[String]$file
)
$encoding =[Text.Encoding]::GetEncoding('iso-8859-1')
$replacedContent = [string[]]#(Get-Content $file | ForEach-Object {
# Do stuff
})
[System.IO.File]::WriteAllLines($file, $replacedContent, $encoding)

Renaming one file (and nothing more than ONE file) using PowerShell

The problem
I constantly find myself in need of quick-method to rename a random file here and there while I work. I need to bring these filenames down to a structure compatible with web standards and some personal needs. A few examples below:
When I find I need
---------------------------------- -----------------------------------------
Welcome to the party.JPG welcome_to_the_party.jpg
Instructions (and some other tips) instructions_and_some_other_tips
Bar Drinks – The Best Recipes bar_drinks_the_best_recipes
La mañana del águila y el ratón la_manana_del_aguila_y_el_raton
Basically I need:
all uppercase characters to become lowercase
spaces to become underscore
some other special characters and diacritics for other languages to become their closest match (á is a, é is e, ç is c, and so on...)
Symbols like ( ) [ ] { } ' ; , to completely dissapear
Perhaps some replacements (optional) as: # = no; # = at or & = and
Not the question, but just FYI and you can see the big picture
I will be using a registry entry [HKEY_CLASSES_ROOT*\shell...] so I can call a batch file and/or a PowerShell Script by right-clicking the desired file, passing the argument information (the file in question) to the script that way.
My guesses
I have been looking closely at PowerShell Scripts, but I am not very knowledgeable about this area yet and all the solutions provided so far are addressing the entire folder (Dir/Get-ChildItem) instead of a specific file.
For example, I was successful using the line below (PowerShell) to replace all spaces by underscore, but then it affects other files in the directory as well.
Dir | Rename-Item –NewName { $_.name –replace “ “,”_“ }
Again, I do not need to address this problem for the entire folder, since I already have ways of doing so using software like Total Commander.
Thanks for any help you can give me.
Ruy
may be this code can help you
function Remove-Diacritics([string]$String)
{
$objD = $String.Normalize([Text.NormalizationForm]::FormD)
$sb = New-Object Text.StringBuilder
for ($i = 0; $i -lt $objD.Length; $i++) {
$c = [Globalization.CharUnicodeInfo]::GetUnicodeCategory($objD[$i])
if($c -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
[void]$sb.Append($objD[$i])
}
}
return("$sb".Normalize([Text.NormalizationForm]::FormC))
}
function Clean-String([string]$String)
{
return(Remove-Diacritics ($String.ToLower() -replace "#", "no" -replace "\#", "at" -replace "&", "and" -replace "\(|\)|\[|\]|\{|\}|'|;|\,", "" -replace " ", "_"))
}
$youfile="C:\tmp4\121948_DRILLG.tif"
$younewnamefile=Clean-String $youfile
Rename-Item -Path $youfile $younewnamefile
Place this script somewhere (let's call it WebRename.ps1):
$old = $args -join ' '
$new = $old.ToLower().Replace(' ', '_')
# add all the remaining transformations you need here
Rename-Item $old $new
In the registry use this as the command (with your own path of course):
PowerShell -c C:\WebRename.ps1 "%1"
If your looking to be able to do this quickly and always want the same changes to be made you can add the following function to a .psm1 file and then place the file in one of your module folders (C:\Program Files\WindowsPowerShell\Modules is the most common one) you'll be able to just call WebRename-File filePath any time you need to quickly rename a file, the function is set up in such a way as to work fine if you pass in a single file path or you can pipe the results of a get-childitem to it if you ever do find the need to do bulk renames.
function WebRename-File {
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
$filePath
)
begin{}
Process{
foreach($path in $filePath){
$newPath = $path.ToLower()
$newPath = $newPath.Replace(' ','_')
###add other operations here###
Rename-Item -Path $path -NewName $newPath
}
}
end{}
}