source_api_Items.bs

import "pkg:/source/api/sdk.bs"

function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
    params = {
        "UserId": m.global.session.user.id,
        "StartTimeTicks": startTimeTicks,
        "IsPlayback": true,
        "AutoOpenLiveStream": true,
        "MaxStreamingBitrate": "140000000"
    }
    resp = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
    return getJson(resp)
end function

function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
    body = {
        "DeviceProfile": getDeviceProfile()
    }
    params = {
        "UserId": m.global.session.user.id,
        "StartTimeTicks": startTimeTicks,
        "IsPlayback": true,
        "AutoOpenLiveStream": true,
        "MaxStreamingBitrate": "140000000",
        "MaxStaticBitrate": "140000000",
        "SubtitleStreamIndex": subtitleTrackIndex
    }

    if mediaSourceId <> "" then params.MediaSourceId = mediaSourceId

    if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex

    req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
    req.SetRequest("POST")
    return postJson(req, FormatJson(body))
end function

' Search across all libraries
function searchMedia(query as string)
    if query <> ""
        data = api.users.GetItemsByQuery(m.global.session.user.id, {
            "searchTerm": query,
            "IncludePeople": true,
            "IncludeMedia": true,
            "IncludeShows": true,
            "IncludeGenres": true,
            "IncludeStudios": true,
            "IncludeArtists": true,
            "IncludeItemTypes": "LiveTvChannel,Movie,BoxSet,Series,Episode,Video,Person,Audio,MusicAlbum,MusicArtist,Playlist",
            "EnableTotalRecordCount": false,
            "ImageTypeLimit": 1,
            "Recursive": true,
            "limit": 100
        })

        results = []
        for each item in data.Items
            tmp = CreateObject("roSGNode", "SearchData")
            tmp.image = PosterImage(item.id)
            tmp.json = item
            results.push(tmp)
        end for
        data.Items = results
        return data
    end if
    return []
end function

' MetaData about an item
function ItemMetaData(id as string)
    url = Substitute("Users/{0}/Items/{1}", m.global.session.user.id, id)
    resp = APIRequest(url, { "fields": "Chapters" })
    data = getJson(resp)
    if data = invalid then return invalid

    imgParams = {}
    if data.type <> "Audio"
        if data.UserData <> invalid and data.UserData.PlayedPercentage <> invalid
            param = { "PercentPlayed": data.UserData.PlayedPercentage }
            imgParams.Append(param)
        end if
    end if
    if data.type = "Movie" or data.type = "MusicVideo"
        tmp = CreateObject("roSGNode", "MovieData")
        tmp.image = PosterImage(data.id, imgParams)
        tmp.json = data
        return tmp
    else if data.type = "Series"
        tmp = CreateObject("roSGNode", "SeriesData")
        tmp.image = PosterImage(data.id)
        tmp.json = data
        return tmp
    else if data.type = "Episode"
        tmp = CreateObject("roSGNode", "TVEpisodeData")
        tmp.image = PosterImage(data.id, imgParams)
        tmp.json = data
        return tmp
    else if LCase(data.type) = "recording"
        tmp = CreateObject("roSGNode", "RecordingData")
        tmp.image = PosterImage(data.id, imgParams)
        tmp.json = data
        return tmp
    else if data.type = "BoxSet" or data.type = "Playlist"
        tmp = CreateObject("roSGNode", "CollectionData")
        tmp.image = PosterImage(data.id, imgParams)
        tmp.json = data
        return tmp
    else if data.type = "Season"
        tmp = CreateObject("roSGNode", "TVSeasonData")
        tmp.image = PosterImage(data.id)
        tmp.json = data
        return tmp
    else if data.type = "Video"
        tmp = CreateObject("roSGNode", "VideoData")
        tmp.image = PosterImage(data.id)
        tmp.json = data
        return tmp
    else if data.type = "Trailer"
        tmp = CreateObject("roSGNode", "VideoData")
        tmp.json = data
        return tmp
    else if data.type = "TvChannel" or data.type = "Program"
        tmp = CreateObject("roSGNode", "ChannelData")
        tmp.image = PosterImage(data.id)
        tmp.isFavorite = data.UserData.isFavorite
        tmp.json = data
        return tmp
    else if data.type = "Person"
        tmp = CreateObject("roSGNode", "PersonData")
        tmp.image = PosterImage(data.id, { "MaxWidth": 300, "MaxHeight": 450 })
        tmp.json = data
        return tmp
    else if data.type = "MusicArtist"
        ' User clicked on an artist and wants to see the list of their albums
        tmp = CreateObject("roSGNode", "MusicArtistData")
        tmp.image = PosterImage(data.id)
        tmp.json = data
        return tmp
    else if data.type = "MusicAlbum"
        ' User clicked on an album and wants to see the list of songs
        tmp = CreateObject("roSGNode", "MusicAlbumSongListData")
        tmp.image = PosterImage(data.id)
        tmp.json = data
        return tmp
    else if data.type = "Audio"
        ' User clicked on a song and wants it to play
        tmp = CreateObject("roSGNode", "MusicSongData")

        ' Try using song's parent for poster image
        tmp.image = PosterImage(data.ParentId, { "MaxWidth": 500, "MaxHeight": 500 })

        ' Song's parent poster image is no good, try using the song's poster image
        if tmp.image = invalid
            tmp.image = PosterImage(data.id, { "MaxWidth": 500, "MaxHeight": 500 })
        end if

        tmp.json = data
        return tmp
    else if data.type = "Recording"
        ' We know it's "Recording", but we don't do any special preprocessing
        ' for this data type at the moment, so just return the json.
        return data
    else
        print "Items.brs::ItemMetaData processed unhandled type: " data.type
        ' Return json if we don't know what it is
        return data
    end if
end function

' Music Artist Data
function ArtistOverview(name as string)
    req = createObject("roUrlTransfer")
    url = Substitute("Artists/{0}", req.escape(name))
    resp = APIRequest(url)
    data = getJson(resp)
    if data = invalid then return invalid
    return data.overview
end function

' Get list of albums belonging to an artist
function MusicAlbumList(id as string)
    url = Substitute("Users/{0}/Items", m.global.session.user.id)
    resp = APIRequest(url, {
        "AlbumArtistIds": id,
        "includeitemtypes": "MusicAlbum",
        "sortBy": "SortName",
        "Recursive": true
    })

    data = getJson(resp)
    results = []
    for each item in data.Items
        tmp = CreateObject("roSGNode", "MusicAlbumData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Get list of albums an artist appears on
function AppearsOnList(id as string)
    url = Substitute("Users/{0}/Items", m.global.session.user.id)
    resp = APIRequest(url, {
        "ContributingArtistIds": id,
        "ExcludeItemIds": id,
        "includeitemtypes": "MusicAlbum",
        "sortBy": "PremiereDate,ProductionYear,SortName",
        "SortOrder": "Descending",
        "Recursive": true
    })

    data = getJson(resp)
    results = []
    for each item in data.Items
        tmp = CreateObject("roSGNode", "MusicAlbumData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Get list of songs belonging to an artist
function GetSongsByArtist(id as string, params = {} as object)
    url = Substitute("Users/{0}/Items", m.global.session.user.id)
    paramArray = {
        "AlbumArtistIds": id,
        "includeitemtypes": "Audio",
        "sortBy": "SortName",
        "Recursive": true
    }
    ' overwrite defaults with the params provided
    for each param in params
        paramArray.AddReplace(param, params[param])
    end for

    resp = APIRequest(url, paramArray)
    data = getJson(resp)
    results = []

    if data = invalid then return invalid
    if data.Items = invalid then return invalid
    if data.Items.Count() = 0 then return invalid

    for each item in data.Items
        tmp = CreateObject("roSGNode", "MusicAlbumData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Get Items that are under the provided item
function PlaylistItemList(id as string)
    url = Substitute("Playlists/{0}/Items", id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id
    })

    results = []
    data = getJson(resp)

    if data = invalid then return invalid
    if data.Items = invalid then return invalid
    if data.Items.Count() = 0 then return invalid

    for each item in data.Items
        tmp = CreateObject("roSGNode", "PlaylistData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Get Songs that are on an Album
function MusicSongList(id as string)
    url = Substitute("Users/{0}/Items", m.global.session.user.id, id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id,
        "parentId": id,
        "includeitemtypes": "Audio",
        "sortBy": "SortName"
    })

    results = []
    data = getJson(resp)

    if data = invalid then return invalid
    if data.Items = invalid then return invalid
    if data.Items.Count() = 0 then return invalid

    for each item in data.Items
        tmp = CreateObject("roSGNode", "MusicSongData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Get Songs that are on an Album
function AudioItem(id as string)
    url = Substitute("Users/{0}/Items/{1}", m.global.session.user.id, id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id,
        "includeitemtypes": "Audio",
        "sortBy": "SortName"
    })

    return getJson(resp)
end function

' Get Instant Mix based on item
function CreateInstantMix(id as string)
    url = Substitute("/Items/{0}/InstantMix", id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id,
        "Limit": 201
    })

    return getJson(resp)
end function

' Get Instant Mix based on item
function CreateArtistMix(id as string)
    url = Substitute("Users/{0}/Items", m.global.session.user.id)
    resp = APIRequest(url, {
        "ArtistIds": id,
        "Recursive": "true",
        "MediaTypes": "Audio",
        "Filters": "IsNotFolder",
        "SortBy": "SortName",
        "Limit": 300,
        "Fields": "Chapters",
        "ExcludeLocationTypes": "Virtual",
        "EnableTotalRecordCount": false,
        "CollapseBoxSetItems": false
    })

    return getJson(resp)
end function

' Get Intro Videos for an item
function GetIntroVideos(id as string)
    url = Substitute("Users/{0}/Items/{1}/Intros", m.global.session.user.id, id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id
    })

    return getJson(resp)
end function

function AudioStream(id as string)
    songData = AudioItem(id)
    if songData <> invalid
        content = createObject("RoSGNode", "ContentNode")
        if songData.title <> invalid
            content.title = songData.title
        end if

        playbackInfo = ItemPostPlaybackInfo(songData.id, songData.mediaSources[0].id)
        if playbackInfo <> invalid
            content.id = playbackInfo.PlaySessionId

            if useTranscodeAudioStream(playbackInfo)
                ' Transcode the audio
                content.url = buildURL(playbackInfo.mediaSources[0].TranscodingURL)
            else
                ' Direct Stream the audio
                params = {
                    "Static": "true",
                    "Container": songData.mediaSources[0].container,
                    "MediaSourceId": songData.mediaSources[0].id
                }
                content.streamformat = songData.mediaSources[0].container
                content.url = buildURL(Substitute("Audio/{0}/stream", songData.id), params)
            end if
        else
            return invalid
        end if

        return content
    else
        return invalid
    end if
end function

function useTranscodeAudioStream(playbackInfo)
    return playbackInfo.mediaSources[0] <> invalid and playbackInfo.mediaSources[0].TranscodingURL <> invalid
end function

function BackdropImage(id as string)
    imgParams = { "maxHeight": "720", "maxWidth": "1280" }
    return ImageURL(id, "Backdrop", imgParams)
end function

' Seasons for a TV Show
function TVSeasons(id as string) as dynamic
    url = Substitute("Shows/{0}/Seasons", id)
    resp = APIRequest(url, { "UserId": m.global.session.user.id })

    data = getJson(resp)
    ' validate data
    if data = invalid or data.Items = invalid then return invalid

    results = []
    for each item in data.Items
        tmp = CreateObject("roSGNode", "TVSeasonData")
        tmp.image = PosterImage(item.id)
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Returns a list of TV Shows for a given TV Show and season
' Accepts strings for the TV Show Id and the season Id
function TVEpisodes(showId as string, seasonId as string) as dynamic
    ' Get and validate data
    data = api.shows.GetEpisodes(showId, { "seasonId": seasonId, "UserId": m.global.session.user.id, "fields": "MediaStreams,MediaSources" })
    if data = invalid or data.Items = invalid then return invalid

    results = []
    for each item in data.Items
        tmp = CreateObject("roSGNode", "TVEpisodeData")
        tmp.image = PosterImage(item.id, { "maxWidth": 400, "maxheight": 250 })
        if isValid(tmp.image)
            tmp.image.posterDisplayMode = "scaleToZoom"
        end if
        tmp.json = item
        tmpMetaData = ItemMetaData(item.id)

        ' validate meta data
        if isValid(tmpMetaData) and isValid(tmpMetaData.overview)
            tmp.overview = tmpMetaData.overview
        end if
        results.push(tmp)
    end for
    data.Items = results
    return data
end function

' Returns a list of extra features for a TV Show season
' Accepts a string that is a TV Show season id
function TVSeasonExtras(seasonId as string) as dynamic
    ' Get and validate TV extra features data
    data = api.users.GetSpecialFeatures(m.global.session.user.id, seasonId)
    if not isValid(data) then return invalid

    results = []
    for each item in data
        tmp = CreateObject("roSGNode", "TVEpisodeData")
        tmp.image = PosterImage(item.id, { "maxWidth": 400, "maxheight": 250 })
        if isValid(tmp.image)
            tmp.image.posterDisplayMode = "scaleToZoom"
        end if
        tmp.json = item

        ' Force item type to Video so episode auto queue is not attempted
        tmp.type = "Video"
        tmpMetaData = ItemMetaData(item.id)

        ' Validate meta data
        if isValid(tmpMetaData) and isValid(tmpMetaData.overview)
            tmp.overview = tmpMetaData.overview
        end if
        results.push(tmp)
    end for

    ' Build that data format that the TVEpisodeRow expects
    return { Items: results }
end function

function TVEpisodeShuffleList(show_id as string)
    url = Substitute("Shows/{0}/Episodes", show_id)
    resp = APIRequest(url, {
        "UserId": m.global.session.user.id,
        "Limit": 200,
        "sortBy": "Random"
    })

    data = getJson(resp)
    results = []
    for each item in data.Items
        tmp = CreateObject("roSGNode", "TVEpisodeData")
        tmp.json = item
        results.push(tmp)
    end for
    data.Items = results

    return data
end function