188 lines
6.6 KiB
PowerShell
188 lines
6.6 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Extracts and displays video metadata from the comment tag in a video file's format metadata.
|
|
.PARAMETER Video
|
|
Path to the input video file. Can be absolute or relative to current directory.
|
|
.PARAMETER Full
|
|
When present, outputs the parsed comment metadata as JSON.
|
|
.DESCRIPTION
|
|
This script uses ffprobe to extract video format metadata, parses the comment tag as JSON,
|
|
and outputs the structured information with proper error handling.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Video,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[switch]$Full
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
$VideoPath = $Video
|
|
|
|
if (-not (Test-Path -LiteralPath $VideoPath)) {
|
|
Write-Error ('Video file not found: {0}' -f $VideoPath)
|
|
exit 1
|
|
}
|
|
|
|
$PSNativeCommandUseErrorActionPreference = $false
|
|
|
|
try {
|
|
$ffprobeOutput = & ffprobe -v quiet -print_format json -show_format -show_streams -- $VideoPath 2>&1
|
|
$ffprobeExit = $LASTEXITCODE
|
|
|
|
if ($ffprobeExit -ne 0) {
|
|
$ffprobeOutput | ForEach-Object { Write-Error $_ }
|
|
Write-Error ('ffprobe failed to read video file: {0}' -f $VideoPath)
|
|
exit 1
|
|
}
|
|
|
|
$raw = $ffprobeOutput | ConvertFrom-Json
|
|
|
|
if (-not $raw -or -not $raw.format) {
|
|
Write-Error ('ffprobe did not return valid format metadata for: {0}' -f $VideoPath)
|
|
exit 1
|
|
}
|
|
|
|
if (-not $raw.format.tags -or -not $raw.format.tags.comment) {
|
|
Write-Error ('Video file does not contain a comment tag in format metadata: {0}' -f $VideoPath)
|
|
exit 1
|
|
}
|
|
|
|
$commentJson = $raw.format.tags.comment
|
|
|
|
# Extract the actual (encoded) resolution and frame rate from the first video
|
|
# stream reported by ffprobe. These may differ from the values stored in the
|
|
# JSON comment metadata (e.g. WanGP/LTX records "720x1280" but the file is
|
|
# actually encoded at 704x1280 after the model's internal padding/cropping).
|
|
$actualResolution = $null
|
|
$actualFps = $null
|
|
$get = { param($obj, $name) if ($obj -and ($obj.PSObject.Properties.Name -contains $name)) { $obj.$name } else { $null } }
|
|
|
|
if ($raw.PSObject.Properties.Name -contains 'streams') {
|
|
$videoStream = $raw.streams | Where-Object { $_.codec_type -eq 'video' } | Select-Object -First 1
|
|
if ($videoStream) {
|
|
$w = & $get $videoStream 'width'
|
|
$h = & $get $videoStream 'height'
|
|
if ($w -and $h) {
|
|
$actualResolution = '{0}x{1}' -f [int]$w, [int]$h
|
|
}
|
|
|
|
$rfr = & $get $videoStream 'r_frame_rate'
|
|
if ($rfr -and $rfr -match '^(\d+)/(\d+)$') {
|
|
$num = [double]$matches[1]
|
|
$den = [double]$matches[2]
|
|
if ($den -ne 0) {
|
|
$actualFps = [Math]::Round($num / $den, 3)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
$parsedComment = $commentJson | ConvertFrom-Json
|
|
}
|
|
catch {
|
|
Write-Error ('Failed to parse comment tag as JSON: {0}' -f $_.Exception.Message)
|
|
exit 1
|
|
}
|
|
|
|
$output = $parsedComment | ConvertTo-Json -Depth 20
|
|
|
|
if ($Full) {
|
|
Write-Output $output
|
|
}
|
|
|
|
Write-Output '----------------------------------------'
|
|
Write-Output ('File Name: {0}' -f $Video)
|
|
|
|
$iniPath = Join-Path $PSScriptRoot 'Get-VideoInfo.ini'
|
|
if (-not (Test-Path -LiteralPath $iniPath)) {
|
|
Write-Error ('INI configuration file not found: {0}' -f $iniPath)
|
|
exit 1
|
|
}
|
|
|
|
$iniLines = Get-Content -LiteralPath $iniPath -Encoding UTF8
|
|
foreach ($line in $iniLines) {
|
|
$trimmedLine = $line.Trim()
|
|
|
|
if ([string]::IsNullOrWhiteSpace($trimmedLine) -or $trimmedLine.StartsWith('#')) {
|
|
continue
|
|
}
|
|
|
|
if ($trimmedLine -notmatch '^([^=]+)=([^=]+)$') {
|
|
continue
|
|
}
|
|
|
|
$displayName = $matches[1].Trim()
|
|
$jsonFieldName = $matches[2].Trim()
|
|
|
|
$displayNameFormatted = $displayName -replace '_', ' '
|
|
|
|
$fieldExists = $parsedComment.PSObject.Properties.Name -contains $jsonFieldName
|
|
|
|
if (-not $fieldExists) {
|
|
$valueDisplay = 'Not found'
|
|
}
|
|
else {
|
|
$fieldValue = $parsedComment.$jsonFieldName
|
|
|
|
if ($null -eq $fieldValue) {
|
|
$valueDisplay = 'Not found'
|
|
}
|
|
elseif ($jsonFieldName -eq 'generation_time') {
|
|
$totalSeconds = [int]$fieldValue
|
|
$minutes = [Math]::Floor($totalSeconds / 60)
|
|
$seconds = $totalSeconds % 60
|
|
$valueDisplay = ('{0}s ({1}m {2}s)' -f $totalSeconds, $minutes, $seconds)
|
|
}
|
|
elseif ($jsonFieldName -eq 'creation_date') {
|
|
try {
|
|
$dateTime = [DateTime]::Parse($fieldValue)
|
|
$valueDisplay = $dateTime.ToString('yyyy-MM-dd HH:mm:ss')
|
|
}
|
|
catch {
|
|
$valueDisplay = $fieldValue -replace 'T', ' '
|
|
}
|
|
}
|
|
elseif ($jsonFieldName -eq 'video_length') {
|
|
# Always use the real frame rate reported by ffprobe for the
|
|
# duration calculation. The JSON metadata's force_fps may be a
|
|
# non-numeric mode marker (e.g. "control") or simply not match
|
|
# the actual encoded frame rate.
|
|
$fps = if ($actualFps) { $actualFps } else { 24.0 }
|
|
$frames = [int]$fieldValue
|
|
$durationSeconds = [Math]::Round($frames / $fps, 1)
|
|
$valueDisplay = ('{0} frames ({1}s, {2} fps)' -f $frames, $durationSeconds, $fps)
|
|
}
|
|
elseif ($fieldValue -is [array]) {
|
|
$valueDisplay = ($fieldValue | ConvertTo-Json -Compress -Depth 10)
|
|
}
|
|
else {
|
|
$valueDisplay = $fieldValue
|
|
# If the value looks like a WxH resolution string and ffprobe
|
|
# reports a different actual encoded resolution, surface both.
|
|
if ($actualResolution `
|
|
-and ($fieldValue -is [string]) `
|
|
-and ($fieldValue -match '^\d+x\d+$') `
|
|
-and ($fieldValue -ne $actualResolution)) {
|
|
$valueDisplay = "$fieldValue (real: $actualResolution)"
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Output ('{0}: {1}' -f $displayNameFormatted, $valueDisplay)
|
|
}
|
|
|
|
Write-Output '----------------------------------------'
|
|
}
|
|
catch {
|
|
Write-Error ('Unexpected error: {0}' -f $_.Exception.Message)
|
|
exit 1
|
|
}
|