source_data_JellyfinDataTransformer.bs
import "pkg:/source/api/Image.bs"
import "pkg:/source/utils/misc.bs"
' Transforms Jellyfin API responses to content nodes
' Handles server version differences automatically
class JellyfinDataTransformer
sub new()
end sub
' Transform BaseItemDto to JellyfinBaseItem content node
' Server version is automatically retrieved from m.global.server.version
' Only pass serverVersion explicitly for testing or when global session isn't available
function transformBaseItem(apiData as object, serverVersion = "" as string) as object
if not isValid(apiData)
return invalid
end if
' Get server version from global session if not provided
' Note: In classes, we can't access m.global directly, so serverVersion must be passed explicitly
' or retrieved from GetGlobalAA() at call time
if not isValidAndNotEmpty(serverVersion)
globalAA = GetGlobalAA()
if isValid(globalAA) and isValid(globalAA.session) and isValid(globalAA.session.server) and isValidAndNotEmpty(globalAA.session.server.version)
serverVersion = globalAA.session.server.version
else
serverVersion = "unknown"
end if
end if
try
item = CreateObject("roSGNode", "JellyfinBaseItem")
' Core identity fields
item.id = apiData.Id ?? ""
item.type = apiData.Type ?? ""
item.name = apiData.Name ?? ""
item.originalTitle = apiData.OriginalTitle ?? ""
item.sortName = apiData.SortName ?? ""
' Parent/Container info
item.serverId = apiData.ServerId ?? ""
item.parentId = apiData.ParentId ?? ""
item.channelId = apiData.ChannelId ?? ""
item.collectionType = apiData.CollectionType ?? ""
' Media metadata
item.overview = apiData.Overview ?? ""
item.tagline = apiData.Tagline ?? ""
item.runTimeTicks = apiData.RunTimeTicks ?? 0
item.premiereDate = apiData.PremiereDate ?? ""
item.endDate = apiData.EndDate ?? ""
item.productionYear = apiData.ProductionYear ?? 0
' Ratings
item.officialRating = apiData.OfficialRating ?? ""
item.communityRating = apiData.CommunityRating ?? 0.0
item.criticRating = apiData.CriticRating ?? 0.0
' Arrays (handle missing gracefully)
if isValid(apiData.Genres)
item.genres = apiData.Genres
end if
if isValid(apiData.Studios)
item.studios = m.extractStudioNames(apiData.Studios)
end if
if isValid(apiData.Tags)
item.tags = apiData.Tags
end if
' Transform user data (most important for UI state)
m.transformUserDataFields(item, apiData.UserData)
' Type-specific transformations
if apiData.Type = "Episode"
m.transformEpisodeFields(item, apiData)
else if apiData.Type = "Series"
m.transformSeriesFields(item, apiData)
else if apiData.Type = "Program"
m.transformProgramFields(item, apiData)
end if
' Store image tags only (URLs generated on-demand in components)
m.transformImageTags(item, apiData)
' Store media sources for playback (if present)
if isValid(apiData.MediaSources)
item.mediaSourcesData = { mediaSources: apiData.MediaSources }
end if
' Set loading state
item.isLoaded = true
item.loadedAt = CreateObject("roDateTime").AsSeconds()
' Metadata
item.serverVersion = serverVersion
#if debug
item.rawApiData = apiData
#end if
item.transformedAt = CreateObject("roDateTime").AsSeconds()
return item
catch error
return invalid
end try
end function
' Transform array of BaseItemDto objects
' Server version is automatically retrieved from global session
function transformBaseItemArray(apiArray as object, serverVersion = "" as string) as object
if not isValid(apiArray) or apiArray.Count() = 0
return []
end if
' Get server version from global session if not provided
if not isValidAndNotEmpty(serverVersion)
globalAA = GetGlobalAA()
if isValid(globalAA) and isValid(globalAA.session) and isValid(globalAA.session.server) and isValidAndNotEmpty(globalAA.session.server.version)
serverVersion = globalAA.session.server.version
else
serverVersion = "unknown"
end if
end if
results = []
for each apiItem in apiArray
transformedItem = m.transformBaseItem(apiItem, serverVersion)
if isValid(transformedItem)
results.push(transformedItem)
end if
end for
return results
end function
' Transform user data fields onto item (flattened for performance)
private sub transformUserDataFields(item as object, userData as object)
if not isValid(userData)
return
end if
' Core user data
item.playbackPositionTicks = userData.PlaybackPositionTicks ?? 0
item.playedPercentage = userData.PlayedPercentage ?? 0.0
item.isWatched = userData.Played ?? false
item.isFavorite = userData.IsFavorite ?? false
item.playCount = userData.PlayCount ?? 0
item.lastPlayedDate = userData.LastPlayedDate ?? ""
' Derived fields
item.isResumable = (item.playbackPositionTicks > 0) and (not item.isWatched)
' Unplayed count for series
if isValid(userData.UnplayedItemCount)
item.unplayedItemCount = userData.UnplayedItemCount
end if
end sub
' Transform episode-specific fields
private sub transformEpisodeFields(item as object, apiData as object)
item.seriesId = apiData.SeriesId ?? ""
item.seriesName = apiData.SeriesName ?? ""
item.seasonId = apiData.SeasonId ?? ""
item.seasonName = apiData.SeasonName ?? ""
item.episodeTitle = apiData.EpisodeTitle ?? ""
item.indexNumber = apiData.IndexNumber ?? 0
item.parentIndexNumber = apiData.ParentIndexNumber ?? 0
end sub
' Transform series-specific fields
private sub transformSeriesFields(item as object, apiData as object)
' Series-specific handling if needed
end sub
' Transform program (Live TV) specific fields
private sub transformProgramFields(item as object, apiData as object)
item.channelName = apiData.ChannelName ?? ""
item.isLive = apiData.IsLive ?? false
item.isNews = apiData.IsNews ?? false
item.isSports = apiData.IsSports ?? false
item.isKids = apiData.IsKids ?? false
item.isPremiere = apiData.IsPremiere ?? false
end sub
' Store image tags only - URLs generated on-demand
private sub transformImageTags(item as object, apiData as object)
' Primary image tag
if isValid(apiData.ImageTags)
item.primaryImageTag = apiData.ImageTags.Primary ?? ""
item.thumbImageTag = apiData.ImageTags.Thumb ?? ""
item.logoImageTag = apiData.ImageTags.Logo ?? ""
end if
' Backdrop image tags
if isValid(apiData.BackdropImageTags) and apiData.BackdropImageTags.Count() > 0
item.backdropImageTags = apiData.BackdropImageTags
end if
' Parent/fallback image tags (for episodes, music, etc.)
item.parentPrimaryImageTag = apiData.ParentPrimaryImageTag ?? ""
item.parentPrimaryImageItemId = apiData.ParentPrimaryImageItemId ?? ""
item.parentThumbImageTag = apiData.ParentThumbImageTag ?? ""
item.parentThumbItemId = apiData.ParentThumbItemId ?? ""
if isValid(apiData.ParentBackdropImageTags) and apiData.ParentBackdropImageTags.Count() > 0
item.parentBackdropImageTags = apiData.ParentBackdropImageTags
item.parentBackdropItemId = apiData.ParentBackdropItemId ?? ""
end if
item.seriesPrimaryImageTag = apiData.SeriesPrimaryImageTag ?? ""
end sub
' Extract studio names from Studios array (can be objects or strings)
private function extractStudioNames(studios as object) as object
if not isValid(studios) or studios.Count() = 0
return []
end if
names = []
for each studio in studios
if type(studio) = "roAssociativeArray" and isValid(studio.Name)
names.push(studio.Name)
else if type(studio) = "String" or type(studio) = "roString"
names.push(studio)
end if
end for
return names
end function
end class