Skip to content

Commit 594896a

Browse files
tablackburnclaude
andauthored
feat: add formatted output properties to Get-PatMediaInfo (#24)
* feat: add formatted output properties to Get-PatMediaInfo Enhance Get-PatMediaInfo output with human-readable formatted values: - MediaInfo: Add DurationFormatted, ContentRating, Rating - MediaVersion: Add BitrateFormatted (e.g., "25.5 Mbps") - MediaPart: Add SizeFormatted (e.g., "4.21 GB") - MediaStream: Add StreamTypeName (Video/Audio/Subtitle), remove duplicate StreamTypeId New Format-PatBitrate private helper for bitrate formatting. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle complex rating object from Plex API The Plex API can return `rating` as either a simple decimal value or a complex object like `{image: '...', value: 7.9, type: 'audience'}`. Update Get-PatMediaInfo to handle both formats by extracting the `.value` property when rating is an object/hashtable. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle rating as array from Plex API The Plex API can also return `rating` as an array of rating objects. Extract the value from the first element when rating is an array. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: bump version to 0.10.3 and update changelog Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5236082 commit 594896a

6 files changed

Lines changed: 508 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
## [0.10.3] - 2026-01-11
11+
12+
### Added
13+
14+
- Extended `Get-PatMediaInfo` output with human-readable formatted properties:
15+
- `DurationFormatted` - human-readable duration (e.g., "2h 16m")
16+
- `ContentRating` - age rating (e.g., "PG-13", "R", "TV-MA")
17+
- `Rating` - critic/Plex rating (handles complex Plex API formats)
18+
- `BitrateFormatted` on MediaVersion (e.g., "25.5 Mbps")
19+
- `SizeFormatted` on MediaPart (e.g., "4.21 GB")
20+
- `StreamTypeName` on MediaStream (Video, Audio, Subtitle)
21+
- New `Format-PatBitrate` private helper for bitrate formatting
22+
23+
### Fixed
24+
25+
- Handle Plex API returning `rating` as complex object or array instead of simple value
26+
27+
### Changed
28+
29+
- Remove duplicate `StreamTypeId` property from MediaStream (use `StreamType` instead)
30+
1031
## [0.10.2] - 2026-01-11
1132

1233
### Added

PlexAutomationToolkit/PlexAutomationToolkit.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'PlexAutomationToolkit.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.10.2'
15+
ModuleVersion = '0.10.3'
1616

1717
# Supported PSEditions
1818
CompatiblePSEditions = @('Desktop', 'Core')
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function Format-PatBitrate {
2+
<#
3+
.SYNOPSIS
4+
Formats a bitrate value as a human-readable string.
5+
6+
.DESCRIPTION
7+
Internal helper function that converts a bitrate in kilobits per second (kbps)
8+
to a human-readable string with appropriate units (kbps or Mbps).
9+
10+
.PARAMETER Kbps
11+
The bitrate in kilobits per second.
12+
13+
.OUTPUTS
14+
System.String
15+
Returns a formatted string representing the bitrate (e.g., "25.5 Mbps", "800 kbps").
16+
Returns $null if input is $null or 0.
17+
18+
.EXAMPLE
19+
Format-PatBitrate -Kbps 25500
20+
Returns: "25.5 Mbps"
21+
22+
.EXAMPLE
23+
Format-PatBitrate -Kbps 800
24+
Returns: "800 kbps"
25+
26+
.EXAMPLE
27+
25500, 800, 5000 | Format-PatBitrate
28+
Returns: "25.5 Mbps", "800 kbps", "5.0 Mbps"
29+
#>
30+
[CmdletBinding()]
31+
[OutputType([string])]
32+
param (
33+
[Parameter(Mandatory = $false, ValueFromPipeline = $true)]
34+
[AllowNull()]
35+
[long]
36+
$Kbps
37+
)
38+
39+
process {
40+
if ($null -eq $Kbps -or $Kbps -eq 0) {
41+
return $null
42+
}
43+
44+
if ($Kbps -ge 1000) {
45+
return '{0:N1} Mbps' -f ($Kbps / 1000)
46+
}
47+
else {
48+
return '{0:N0} kbps' -f $Kbps
49+
}
50+
}
51+
}

PlexAutomationToolkit/Public/Get-PatMediaInfo.ps1

Lines changed: 95 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,15 @@ function Get-PatMediaInfo {
5656
- ParentIndex: Season number (episodes)
5757
- Index: Episode number (episodes)
5858
- Duration: Duration in milliseconds
59+
- DurationFormatted: Human-readable duration (e.g., "2h 16m")
60+
- ContentRating: Age rating (e.g., "PG-13", "R", "TV-MA")
61+
- Rating: Critic/Plex rating (0-10)
5962
- ViewCount: Number of times watched
6063
- LastViewedAt: Last watched timestamp
6164
- Media: Array of media versions with file info
65+
- MediaVersion includes BitrateFormatted (e.g., "25.5 Mbps")
66+
- MediaPart includes SizeFormatted (e.g., "4.50 GB")
67+
- MediaStream includes StreamTypeName (Video, Audio, Subtitle)
6268
- ServerUri: The Plex server URI
6369
#>
6470
[CmdletBinding()]
@@ -69,7 +75,7 @@ function Get-PatMediaInfo {
6975
$RatingKey,
7076

7177
[Parameter(Mandatory = $false)]
72-
[string]
78+
[string]
7379
$ServerName,
7480

7581
[Parameter(Mandatory = $false)]
@@ -128,87 +134,120 @@ function Get-PatMediaInfo {
128134
$streams = @()
129135
if ($part.Stream) {
130136
foreach ($stream in $part.Stream) {
137+
# Map stream type ID to human-readable name
138+
$streamTypeName = switch ([int]$stream.streamType) {
139+
1 { 'Video' }
140+
2 { 'Audio' }
141+
3 { 'Subtitle' }
142+
default { 'Unknown' }
143+
}
144+
131145
$streamObj = [PSCustomObject]@{
132-
PSTypeName = 'PlexAutomationToolkit.MediaStream'
133-
StreamId = [int]$stream.id
134-
StreamType = [int]$stream.streamType
135-
StreamTypeId = [int]$stream.streamType
136-
Codec = $stream.codec
137-
Language = $stream.language
138-
LanguageCode = $stream.languageCode
139-
LanguageTag = $stream.languageTag
140-
Title = $stream.title
141-
DisplayTitle = $stream.displayTitle
142-
Key = $stream.key # For external subtitles
143-
External = ($stream.streamType -eq 3 -and $null -ne $stream.key)
144-
Default = ($stream.default -eq 1 -or $stream.default -eq '1')
145-
Forced = ($stream.forced -eq 1 -or $stream.forced -eq '1')
146-
Format = $stream.format
146+
PSTypeName = 'PlexAutomationToolkit.MediaStream'
147+
StreamId = [int]$stream.id
148+
StreamType = [int]$stream.streamType
149+
StreamTypeName = $streamTypeName
150+
Codec = $stream.codec
151+
Language = $stream.language
152+
LanguageCode = $stream.languageCode
153+
LanguageTag = $stream.languageTag
154+
Title = $stream.title
155+
DisplayTitle = $stream.displayTitle
156+
Key = $stream.key # For external subtitles
157+
External = ($stream.streamType -eq 3 -and $null -ne $stream.key)
158+
Default = ($stream.default -eq 1 -or $stream.default -eq '1')
159+
Forced = ($stream.forced -eq 1 -or $stream.forced -eq '1')
160+
Format = $stream.format
147161
}
148162
$streams += $streamObj
149163
}
150164
}
151165

152166
$partObj = [PSCustomObject]@{
153-
PSTypeName = 'PlexAutomationToolkit.MediaPart'
154-
PartId = [int]$part.id
155-
Key = $part.key # Download path: /library/parts/{id}/...
156-
File = $part.file # Original file path on server
157-
Size = [long]$part.size
158-
Container = $part.container
159-
Duration = [long]$part.duration
160-
Streams = $streams
167+
PSTypeName = 'PlexAutomationToolkit.MediaPart'
168+
PartId = [int]$part.id
169+
Key = $part.key # Download path: /library/parts/{id}/...
170+
File = $part.file # Original file path on server
171+
Size = [long]$part.size
172+
SizeFormatted = Format-ByteSize -Bytes ([long]$part.size)
173+
Container = $part.container
174+
Duration = [long]$part.duration
175+
Streams = $streams
161176
}
162177
$parts += $partObj
163178
}
164179
}
165180

166181
$mediaObj = [PSCustomObject]@{
167-
PSTypeName = 'PlexAutomationToolkit.MediaVersion'
168-
MediaId = [int]$media.id
169-
Container = $media.container
170-
VideoCodec = $media.videoCodec
171-
AudioCodec = $media.audioCodec
172-
Width = [int]$media.width
173-
Height = [int]$media.height
174-
Bitrate = [long]$media.bitrate
175-
VideoResolution = $media.videoResolution
176-
AspectRatio = $media.aspectRatio
177-
Part = $parts
182+
PSTypeName = 'PlexAutomationToolkit.MediaVersion'
183+
MediaId = [int]$media.id
184+
Container = $media.container
185+
VideoCodec = $media.videoCodec
186+
AudioCodec = $media.audioCodec
187+
Width = [int]$media.width
188+
Height = [int]$media.height
189+
Bitrate = [long]$media.bitrate
190+
BitrateFormatted = Format-PatBitrate -Kbps $media.bitrate
191+
VideoResolution = $media.videoResolution
192+
AspectRatio = $media.aspectRatio
193+
Part = $parts
178194
}
179195
$mediaVersions += $mediaObj
180196
}
181197
}
182198

183199
# Build the final MediaInfo object
184200
$mediaInformation = [PSCustomObject]@{
185-
PSTypeName = 'PlexAutomationToolkit.MediaInfo'
186-
RatingKey = [int]$metadata.ratingKey
187-
Key = $metadata.key
188-
Guid = $metadata.guid
189-
Title = $metadata.title
190-
OriginalTitle = $metadata.originalTitle
191-
Type = $metadata.type
192-
Year = if ($metadata.year) { [int]$metadata.year } else { $null }
193-
GrandparentTitle = $metadata.grandparentTitle # Show name for episodes
194-
GrandparentKey = $metadata.grandparentKey
195-
ParentTitle = $metadata.parentTitle # Season title for episodes
196-
ParentIndex = if ($metadata.parentIndex) { [int]$metadata.parentIndex } else { $null } # Season number
197-
Index = if ($metadata.index) { [int]$metadata.index } else { $null } # Episode number
198-
Duration = if ($metadata.duration) { [long]$metadata.duration } else { 0 }
199-
Summary = $metadata.summary
200-
ViewCount = if ($metadata.viewCount) { [int]$metadata.viewCount } else { 0 }
201-
LastViewedAt = if ($metadata.lastViewedAt) {
201+
PSTypeName = 'PlexAutomationToolkit.MediaInfo'
202+
RatingKey = [int]$metadata.ratingKey
203+
Key = $metadata.key
204+
Guid = $metadata.guid
205+
Title = $metadata.title
206+
OriginalTitle = $metadata.originalTitle
207+
Type = $metadata.type
208+
Year = if ($metadata.year) { [int]$metadata.year } else { $null }
209+
GrandparentTitle = $metadata.grandparentTitle # Show name for episodes
210+
GrandparentKey = $metadata.grandparentKey
211+
ParentTitle = $metadata.parentTitle # Season title for episodes
212+
ParentIndex = if ($metadata.parentIndex) { [int]$metadata.parentIndex } else { $null } # Season number
213+
Index = if ($metadata.index) { [int]$metadata.index } else { $null } # Episode number
214+
Duration = if ($metadata.duration) { [long]$metadata.duration } else { 0 }
215+
DurationFormatted = Format-PatDuration -Milliseconds $metadata.duration
216+
ContentRating = $metadata.contentRating
217+
Rating = if ($metadata.rating) {
218+
# Rating can be: simple value, object/hashtable with value property, or array
219+
$ratingValue = $metadata.rating
220+
if ($ratingValue -is [array]) {
221+
# Array of rating objects - try to get value from first item
222+
$firstRating = $ratingValue | Select-Object -First 1
223+
if ($null -ne $firstRating.value) {
224+
[decimal]$firstRating.value
225+
} else {
226+
$null
227+
}
228+
} elseif ($ratingValue -is [hashtable] -or $ratingValue -is [PSCustomObject]) {
229+
if ($null -ne $ratingValue.value) {
230+
[decimal]$ratingValue.value
231+
} else {
232+
$null
233+
}
234+
} else {
235+
[decimal]$ratingValue
236+
}
237+
} else { $null }
238+
Summary = $metadata.summary
239+
ViewCount = if ($metadata.viewCount) { [int]$metadata.viewCount } else { 0 }
240+
LastViewedAt = if ($metadata.lastViewedAt) {
202241
[DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.lastViewedAt).LocalDateTime
203242
} else { $null }
204-
AddedAt = if ($metadata.addedAt) {
243+
AddedAt = if ($metadata.addedAt) {
205244
[DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.addedAt).LocalDateTime
206245
} else { $null }
207-
UpdatedAt = if ($metadata.updatedAt) {
246+
UpdatedAt = if ($metadata.updatedAt) {
208247
[DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.updatedAt).LocalDateTime
209248
} else { $null }
210-
Media = $mediaVersions
211-
ServerUri = $effectiveUri
249+
Media = $mediaVersions
250+
ServerUri = $effectiveUri
212251
}
213252

214253
$mediaInformation

0 commit comments

Comments
 (0)