Public/Get-PatMediaInfo.ps1

function Get-PatMediaInfo {
    <#
    .SYNOPSIS
        Retrieves detailed media information from a Plex server.
 
    .DESCRIPTION
        Gets comprehensive metadata for a media item including file paths, sizes, codecs,
        and subtitle streams. This information is essential for downloading media files
        and their associated subtitles.
 
    .PARAMETER RatingKey
        The unique identifier (ratingKey) of the media item to retrieve.
        This is the Plex internal ID for movies, episodes, or other media.
 
    .PARAMETER ServerName
        The name of a stored server to use. Use Get-PatStoredServer to see available servers.
        This is more convenient than ServerUri as you don't need to remember the URI or token.
 
    .PARAMETER ServerUri
        The base URI of the Plex server (e.g., http://plex.example.com:32400).
        If not specified, uses the default stored server.
 
    .PARAMETER Token
        The Plex authentication token. Required when using -ServerUri to authenticate
        with the server. If not specified with -ServerUri, requests may fail with 401.
 
    .EXAMPLE
        Get-PatMediaInfo -RatingKey 12345
 
        Retrieves detailed media information for the item with ratingKey 12345.
 
    .EXAMPLE
        Get-PatMediaInfo -RatingKey 12345 -ServerName 'Home'
 
        Retrieves media info from the stored server named 'Home'.
 
    .EXAMPLE
        Get-PatPlaylist -PlaylistName 'Travel' -IncludeItems | Select-Object -ExpandProperty Items | Get-PatMediaInfo
 
        Retrieves media info for all items in the 'Travel' playlist.
 
    .EXAMPLE
        12345, 67890 | Get-PatMediaInfo
 
        Retrieves media info for multiple items via pipeline.
 
    .OUTPUTS
        PlexAutomationToolkit.MediaInfo
 
        Objects with properties:
        - RatingKey: Unique media identifier
        - Title: Media title
        - Type: 'movie' or 'episode'
        - Year: Release year (movies)
        - GrandparentTitle: Show name (episodes)
        - ParentIndex: Season number (episodes)
        - Index: Episode number (episodes)
        - Duration: Duration in milliseconds
        - DurationFormatted: Human-readable duration (e.g., "2h 16m")
        - ContentRating: Age rating (e.g., "PG-13", "R", "TV-MA")
        - Rating: Critic/Plex rating (0-10)
        - ViewCount: Number of times watched
        - LastViewedAt: Last watched timestamp
        - Media: Array of media versions with file info
          - MediaVersion includes BitrateFormatted (e.g., "25.5 Mbps")
          - MediaPart includes SizeFormatted (e.g., "4.50 GB")
          - MediaStream includes StreamTypeName (Video, Audio, Subtitle)
        - ServerUri: The Plex server URI
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]
        $RatingKey,

        [Parameter(Mandatory = $false)]
        [string]
        $ServerName,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-PatServerUri -Uri $_ })]
        [string]
        $ServerUri,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Token
    )

    begin {
        try {
            $script:serverContext = Resolve-PatServerContext -ServerName $ServerName -ServerUri $ServerUri -Token $Token
        }
        catch {
            throw "Failed to resolve server: $($_.Exception.Message)"
        }

        $effectiveUri = $script:serverContext.Uri
        $headers = $script:serverContext.Headers
    }

    process {
        try {
            $endpoint = "/library/metadata/$RatingKey"
            Write-Verbose "Retrieving media info for ratingKey $RatingKey from $effectiveUri"

            $uri = Join-PatUri -BaseUri $effectiveUri -Endpoint $endpoint
            $result = Invoke-PatApi -Uri $uri -Headers $headers -ErrorAction 'Stop'

            # Handle response - API returns Metadata array even for single item
            $metadata = if ($result.Metadata) {
                $result.Metadata | Select-Object -First 1
            }
            else {
                $result
            }

            if (-not $metadata) {
                Write-Warning "No metadata found for ratingKey $RatingKey"
                return
            }

            # Parse Media array with nested Part and Stream objects
            $mediaVersions = @()
            if ($metadata.Media) {
                foreach ($media in $metadata.Media) {
                    $parts = @()
                    if ($media.Part) {
                        foreach ($part in $media.Part) {
                            # Parse streams (video, audio, subtitles)
                            $streams = @()
                            if ($part.Stream) {
                                foreach ($stream in $part.Stream) {
                                    # Map stream type ID to human-readable name
                                    $streamTypeName = switch ([int]$stream.streamType) {
                                        1 { 'Video' }
                                        2 { 'Audio' }
                                        3 { 'Subtitle' }
                                        default { 'Unknown' }
                                    }

                                    $streamObj = [PSCustomObject]@{
                                        PSTypeName     = 'PlexAutomationToolkit.MediaStream'
                                        StreamId       = [int]$stream.id
                                        StreamType     = [int]$stream.streamType
                                        StreamTypeName = $streamTypeName
                                        Codec          = $stream.codec
                                        Language       = $stream.language
                                        LanguageCode   = $stream.languageCode
                                        LanguageTag    = $stream.languageTag
                                        Title          = $stream.title
                                        DisplayTitle   = $stream.displayTitle
                                        Key            = $stream.key  # For external subtitles
                                        External       = ($stream.streamType -eq 3 -and $null -ne $stream.key)
                                        Default        = ($stream.default -eq 1 -or $stream.default -eq '1')
                                        Forced         = ($stream.forced -eq 1 -or $stream.forced -eq '1')
                                        Format         = $stream.format
                                    }
                                    $streams += $streamObj
                                }
                            }

                            $partObj = [PSCustomObject]@{
                                PSTypeName    = 'PlexAutomationToolkit.MediaPart'
                                PartId        = [int]$part.id
                                Key           = $part.key  # Download path: /library/parts/{id}/...
                                File          = $part.file  # Original file path on server
                                Size          = [long]$part.size
                                SizeFormatted = Format-ByteSize -Bytes ([long]$part.size)
                                Container     = $part.container
                                Duration      = [long]$part.duration
                                Streams       = $streams
                            }
                            $parts += $partObj
                        }
                    }

                    $mediaObj = [PSCustomObject]@{
                        PSTypeName       = 'PlexAutomationToolkit.MediaVersion'
                        MediaId          = [int]$media.id
                        Container        = $media.container
                        VideoCodec       = $media.videoCodec
                        AudioCodec       = $media.audioCodec
                        Width            = [int]$media.width
                        Height           = [int]$media.height
                        Bitrate          = [long]$media.bitrate
                        BitrateFormatted = Format-PatBitrate -Kbps $media.bitrate
                        VideoResolution  = $media.videoResolution
                        AspectRatio      = $media.aspectRatio
                        Part             = $parts
                    }
                    $mediaVersions += $mediaObj
                }
            }

            # Build the final MediaInfo object
            $mediaInformation = [PSCustomObject]@{
                PSTypeName        = 'PlexAutomationToolkit.MediaInfo'
                RatingKey         = [int]$metadata.ratingKey
                Key               = $metadata.key
                Guid              = $metadata.guid
                Title             = $metadata.title
                OriginalTitle     = $metadata.originalTitle
                Type              = $metadata.type
                Year              = if ($metadata.year) { [int]$metadata.year } else { $null }
                GrandparentTitle  = $metadata.grandparentTitle  # Show name for episodes
                GrandparentKey    = $metadata.grandparentKey
                ParentTitle       = $metadata.parentTitle  # Season title for episodes
                ParentIndex       = if ($metadata.parentIndex) { [int]$metadata.parentIndex } else { $null }  # Season number
                Index             = if ($metadata.index) { [int]$metadata.index } else { $null }  # Episode number
                Duration          = if ($metadata.duration) { [long]$metadata.duration } else { 0 }
                DurationFormatted = Format-PatDuration -Milliseconds $metadata.duration
                ContentRating     = $metadata.contentRating
                Rating            = if ($metadata.rating) {
                    # Rating can be: simple value, object/hashtable with value property, or array
                    $ratingValue = $metadata.rating
                    if ($ratingValue -is [array]) {
                        # Array of rating objects - try to get value from first item
                        $firstRating = $ratingValue | Select-Object -First 1
                        if ($null -ne $firstRating.value) {
                            [decimal]$firstRating.value
                        } else {
                            $null
                        }
                    } elseif ($ratingValue -is [hashtable] -or $ratingValue -is [PSCustomObject]) {
                        if ($null -ne $ratingValue.value) {
                            [decimal]$ratingValue.value
                        } else {
                            $null
                        }
                    } else {
                        [decimal]$ratingValue
                    }
                } else { $null }
                Summary           = $metadata.summary
                ViewCount         = if ($metadata.viewCount) { [int]$metadata.viewCount } else { 0 }
                LastViewedAt      = if ($metadata.lastViewedAt) {
                    [DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.lastViewedAt).LocalDateTime
                } else { $null }
                AddedAt           = if ($metadata.addedAt) {
                    [DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.addedAt).LocalDateTime
                } else { $null }
                UpdatedAt         = if ($metadata.updatedAt) {
                    [DateTimeOffset]::FromUnixTimeSeconds([long]$metadata.updatedAt).LocalDateTime
                } else { $null }
                Media             = $mediaVersions
                ServerUri         = $effectiveUri
            }

            $mediaInformation
        }
        catch {
            throw "Failed to get media info for ratingKey $RatingKey`: $($_.Exception.Message)"
        }
    }
}