| 1 |
class cMedia04 {
[string]$FullPath
[string]$Directory
[string]$Name
[bool]$Found
[string]$Extension
[string]$MediaKind
[int]$Width
[int]$Height
[double]$HorizontalResolution
[double]$VerticalResolution
[string]$RawFormat
[string]$PixelFormat
[string]$Latitude
[string]$Longitude
[string]$LatitudeRef
[string]$LongitudeRef
[double]$LatitudeDec
[double]$LongitudeDec
[double]$Altitude
[string]$City
[string]$State
[string]$Country
[string]$DateCreated
[string]$CameraModel
[string]$CameraMaker
[string]$BitDepth
[nullable[timespan]]$Duration
[long]$FileSize
[string]$Created
[string]$LastWrite
[string]$NewFolder
[object]$Extended = @()
hidden [object]$shell
hidden [object]$finfo
hidden [object]$file
hidden [object]$ns
hidden [bool]$useBitmap
cMedia04([string]$FullPath, [bool]$UseBitmap) {
$this.FullPath = $FullPath
$this.useBitmap = $UseBitmap
$this.shell = $global:shell
$this.Find()
if ($this.Found) {
$this.LoadInfo()
$this.LoadExtended()
$this.LoadDimensions()
$this.GetLocation()
$this.ResolveLocation()
}
}
Find() {
$this.Found = [bool](Test-Path -LiteralPath $this.FullPath -PathType Leaf)
}
LoadInfo() {
if (-not $this.Found) { return }
try {
$this.finfo = Get-Item -LiteralPath $this.FullPath
$this.Directory = $this.finfo.DirectoryName
$this.Name = $this.finfo.Name
$this.Extension = $this.finfo.Extension.TrimStart('.')
$this.Created = $this.finfo.CreationTime.ToString('yyyy-MM-dd HH:mm:ss')
$this.LastWrite = $this.finfo.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')
$this.FileSize = [long]$this.finfo.Length
switch ($this.Extension.ToLowerInvariant()) {
{ $_ -in @('jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'heic', 'heif', 'webp', 'jfif') } {
$this.MediaKind = 'Image'
}
{ $_ -in @('mp4', 'mov', 'm4v', 'avi', 'mkv', 'wmv', 'mpg', 'mpeg', '3gp', 'webm') } {
$this.MediaKind = 'Video'
}
default { $this.MediaKind = 'Other' }
}
} catch {
Write-Verbose "LoadInfo: $($_.Exception.Message)"
}
}
LoadExtended() {
if (-not $this.Found) { return }
try {
$folder = [System.IO.Path]::GetDirectoryName($this.FullPath)
$fname = [System.IO.Path]::GetFileName($this.FullPath)
$this.ns = $this.shell.NameSpace($folder)
$this.file = $this.ns.ParseName($fname)
if (-not $this.file) { return }
$taken = $this.file.ExtendedProperty('System.Photo.DateTaken')
if ($taken) {
$this.DateCreated = ([datetime]$taken).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss')
} else {
$encoded = $this.file.ExtendedProperty('System.Media.DateEncoded')
if ($encoded) {
$this.DateCreated = ([datetime]$encoded).ToLocalTime().ToString('yyyy-MM-dd HH:mm:ss')
}
}
$this.CameraModel = $this.file.ExtendedProperty('System.Photo.CameraModel')
$this.CameraMaker = $this.file.ExtendedProperty('System.Photo.CameraManufacturer')
$this.BitDepth = $this.file.ExtendedProperty('System.Image.BitDepth')
if ($this.MediaKind -eq 'Video') {
$dur = $this.file.ExtendedProperty('System.Media.Duration')
if ($null -ne $dur -and $dur -ne '') {
try {
if ($dur -is [timespan]) {
$this.Duration = $dur
} elseif ($dur -is [string] -and $dur -match '^\d+$') {
$this.Duration = [timespan]::FromTicks([long]$dur)
} elseif ($dur -is [valuetype]) {
$this.Duration = [timespan]::FromTicks([long]$dur)
}
} catch {
Write-Verbose "Duration parse: $($_.Exception.Message)"
}
}
}
} catch {
Write-Verbose "LoadExtended: $($_.Exception.Message)"
}
}
LoadDimensions() {
if (-not $this.Found) { return }
$this.LoadDimensionsFromShell()
if (-not $this.RawFormat) {
$this.RawFormat = switch ($this.Extension.ToLowerInvariant()) {
{ $_ -in @('jpg', 'jpeg', 'jfif') } { 'Jpeg' }
'png' { 'Png' }
'bmp' { 'Bmp' }
'gif' { 'Gif' }
{ $_ -in @('tif', 'tiff') } { 'Tiff' }
{ $_ -in @('heic', 'heif') } { 'Heif' }
'webp' { 'Webp' }
default {
if ($this.MediaKind -eq 'Video') { $this.Extension.ToUpperInvariant() }
else { $this.Extension }
}
}
}
if (-not $this.PixelFormat -and $this.BitDepth) {
switch ([string]$this.BitDepth) {
'24' { $this.PixelFormat = 'Format24bppRgb' }
'32' { $this.PixelFormat = 'Format32bppArgb' }
'8' { $this.PixelFormat = 'Format8bppIndexed' }
}
}
$needBitmap = $this.useBitmap -or (
$this.MediaKind -eq 'Image' -and ($this.Width -le 0 -or $this.Height -le 0)
)
if ($needBitmap) {
$this.LoadDimensionsFromBitmap()
}
}
LoadDimensionsFromShell() {
if (-not $this.file) { return }
try {
$w = $this.file.ExtendedProperty('System.Image.HorizontalSize')
$h = $this.file.ExtendedProperty('System.Image.VerticalSize')
if (-not $w) { $w = $this.file.ExtendedProperty('System.Video.FrameWidth') }
if (-not $h) { $h = $this.file.ExtendedProperty('System.Video.FrameHeight') }
if ($w) { $this.Width = [int]$w }
if ($h) { $this.Height = [int]$h }
$hr = $this.file.ExtendedProperty('System.Image.HorizontalResolution')
$vr = $this.file.ExtendedProperty('System.Image.VerticalResolution')
if ($null -ne $hr -and $hr -ne '') { $this.HorizontalResolution = [double]$hr }
if ($null -ne $vr -and $vr -ne '') { $this.VerticalResolution = [double]$vr }
} catch {
Write-Verbose "LoadDimensionsFromShell: $($_.Exception.Message)"
}
}
LoadDimensionsFromBitmap() {
if ($this.MediaKind -ne 'Image') { return }
if (-not ("System.Drawing.Bitmap" -as [type])) {
Add-Type -AssemblyName System.Drawing
}
$fs = $null
$bmp = $null
try {
$fs = [IO.File]::OpenRead($this.FullPath)
$bmp = [Drawing.Bitmap]::FromStream($fs)
$this.Width = $bmp.Width
$this.Height = $bmp.Height
$this.HorizontalResolution = $bmp.HorizontalResolution
$this.VerticalResolution = $bmp.VerticalResolution
$this.PixelFormat = $bmp.PixelFormat.ToString()
$this.RawFormat = switch ($bmp.RawFormat.Guid.ToString()) {
'b96b3cae-0728-11d3-9d7b-0000f81ef32e' { 'Jpeg' }
'b96b3caf-0728-11d3-9d7b-0000f81ef32e' { 'Png' }
'b96b3cab-0728-11d3-9d7b-0000f81ef32e' { 'Bmp' }
'b96b3cb0-0728-11d3-9d7b-0000f81ef32e' { 'Gif' }
'b96b3cb1-0728-11d3-9d7b-0000f81ef32e' { 'Tiff' }
default { $bmp.RawFormat.ToString() }
}
} catch {
Write-Verbose "LoadDimensionsFromBitmap: $($_.Exception.Message)"
} finally {
if ($bmp) { $bmp.Dispose() }
if ($fs) { $fs.Dispose() }
}
}
GetLocation() {
if (-not $this.Found -or -not $this.file) { return }
try {
$this.Latitude = @($this.file.ExtendedProperty('System.GPS.Latitude')) -join ';'
$this.Longitude = @($this.file.ExtendedProperty('System.GPS.Longitude')) -join ';'
$this.LatitudeRef = [string]$this.file.ExtendedProperty('System.GPS.LatitudeRef')
$this.LongitudeRef = [string]$this.file.ExtendedProperty('System.GPS.LongitudeRef')
if ($this.Latitude) {
$d, $m, $sec = $this.Latitude -split ';'
$this.LatitudeDec = [double]$d + ([double]$m / 60) + ([double]$sec / 3600)
if ($this.LatitudeRef -eq 'S') { $this.LatitudeDec = -1 * $this.LatitudeDec }
}
if ($this.Longitude) {
$d, $m, $sec = $this.Longitude -split ';'
$this.LongitudeDec = [double]$d + ([double]$m / 60) + ([double]$sec / 3600)
if ($this.LongitudeRef -eq 'W') {
$this.LongitudeDec = -1 * [math]::Abs($this.LongitudeDec)
} elseif ($this.LongitudeRef -eq 'E') {
$this.LongitudeDec = [math]::Abs($this.LongitudeDec)
}
}
$alt = $this.file.ExtendedProperty('System.GPS.Altitude')
if ($null -ne $alt -and $alt -ne '') {
$this.Altitude = [double]$alt
}
} catch {
Write-Verbose "GetLocation: $($_.Exception.Message)"
}
}
ResolveLocation() {
if (-not $this.Found) { return }
if (-not $this.LatitudeDec -or -not $this.LongitudeDec) { return }
if ($null -eq $global:MediaGeoCache) {
$global:MediaGeoCache = @{}
}
$key = '{0:N3},{1:N3}' -f $this.LatitudeDec, $this.LongitudeDec
if (-not $global:MediaGeoCache.ContainsKey($key)) {
Start-Sleep -Milliseconds 1100
try {
$global:MediaGeoCache[$key] = Get-PlaceFromGps -Latitude $this.LatitudeDec -Longitude $this.LongitudeDec
} catch {
Write-Verbose "ResolveLocation: $($_.Exception.Message)"
$global:MediaGeoCache[$key] = $null
}
}
$place = $global:MediaGeoCache[$key]
if ($place) {
$this.City = $place.City
$this.State = $place.State
$this.Country = $place.Country
}
}
}
function global:Get-MediaInfo {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$FullPath,
[Parameter()]
[switch]$UseBitmap
)
if ($null -eq $global:shell) {
$global:shell = New-Object -ComObject Shell.Application
}
if ($null -eq $global:MediaGeoCache) {
$global:MediaGeoCache = @{}
}
try {
return [cMedia04]::new($FullPath, [bool]$UseBitmap)
} catch {
Write-Error "Get-MediaInfo failed for '$FullPath': $($_.Exception.Message)"
return $null
}
}
function global:Get-PlaceFromGps {
param(
[Parameter(Mandatory = $true)][double]$Latitude,
[Parameter(Mandatory = $true)][double]$Longitude
)
$url = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&zoom=10&addressdetails=1&lat=$Latitude&lon=$Longitude"
$r = Invoke-RestMethod -Uri $url -Headers @{
'User-Agent' = 'PhotoVaultCatalog/1.0 (personal use)'
}
$a = $r.address
[pscustomobject]@{
City = @(
$a.city, $a.town, $a.village, $a.municipality, $a.hamlet
) | Where-Object { $_ } | Select-Object -First 1
State = $a.state
Country = $a.country
County = $a.county
Display = $r.display_name
}
}
function global:Convert-Dms {
param(
[Parameter(Mandatory = $true)][string]$Dms
)
$d, $m, $sec = $Dms -split ';'
[double]$d + ([double]$m / 60) + ([double]$sec / 3600)
}
|