diff --git a/src/backend/sources/JellyfinApiSource.ts b/src/backend/sources/JellyfinApiSource.ts index 97887a23..5201758b 100644 --- a/src/backend/sources/JellyfinApiSource.ts +++ b/src/backend/sources/JellyfinApiSource.ts @@ -218,17 +218,21 @@ export default class JellyfinApiSource extends MemorySource { if(this.devicesBlock.length > 0 && this.devicesBlock.some(x => play.meta.deviceId.toLocaleLowerCase().includes(x))) { return `'devicesBlock includes a phrase found in ${play.meta.deviceId}`; } - if(play.meta.mediaType !== MediaType.Audio) { - if(play.meta.mediaType === MediaType.Unknown && this.config.data.allowUnknown) { - return true; - } - return `media type ${play.meta.mediaType} is not allowed`; + if(play.meta.mediaType !== MediaType.Audio + && (play.meta.mediaType !== MediaType.Unknown + || play.meta.mediaType === MediaType.Unknown && !this.config.data.allowUnknown + ) + ) { + return `media detected as ${play.meta.mediaType} (MediaType) is not allowed`; } if('ExtraType' in session.NowPlayingItem && session.NowPlayingItem.ExtraType === 'ThemeSong'/* || play.data.track === 'theme' && (play.data.artists === undefined || play.data.artists.length === 0) */) { - return `media type detected as a Theme Song is not allowed`; - } + return `media detected as a ThemeSong (ExtraType) is not allowed`; + } + if(session.NowPlayingItem.Type !== 'Audio') { + return `media detected as a ${session.NowPlayingItem.Type} (Type) is not allowed`; + } return true; } diff --git a/src/backend/tests/jellyfin/invalidSessionExtra.json b/src/backend/tests/jellyfin/invalidSessionExtra.json new file mode 100644 index 00000000..781d3bea --- /dev/null +++ b/src/backend/tests/jellyfin/invalidSessionExtra.json @@ -0,0 +1,84 @@ +{ + "NowPlayingItem": { + "Name": "theme", + "ServerId": "74d29a4870e84e8bb575b0e73f684a2e", + "Id": "b06904afd3caecadcb56648d494992f8", + "DateCreated": "2023-08-20T18:37:40.1902942Z", + "ExtraType": "ThemeSong", + "HasLyrics": false, + "ExternalUrls": [], + "Path": "/mnt/film/A Movie/theme.mp3", + "EnableMediaSourceDisplay": true, + "OfficialRating": "PG", + "ChannelId": null, + "Overview": "An overview", + "Taglines": [], + "Genres": [], + "CommunityRating": 7.3, + "RunTimeTicks": 1637006848, + "ProviderIds": {}, + "IsFolder": false, + "ParentId": null, + "Type": "Audio", + "Studios": [], + "GenreItems": [], + "ParentLogoItemId": "08a65b1c50ceeb0535f33d19d3b219d8", + "ParentBackdropItemId": "08a65b1c50ceeb0535f33d19d3b219d8", + "ParentBackdropImageTags": [ + "f106ecce6ad29fe66eed96ac7281dbb3" + ], + "LocalTrailerCount": 0, + "SpecialFeatureCount": 0, + "Artists": [], + "ArtistItems": [], + "AlbumArtists": [], + "MediaStreams": [ + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/44100", + "Title": "ISO Media file produced by Google Inc.", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "LocalizedDefault": "По умолчанию", + "LocalizedExternal": "Внешние", + "DisplayTitle": "ISO Media file produced by Google Inc. - Английский - AAC - Stereo - По умолчанию", + "IsInterlaced": false, + "ChannelLayout": "stereo", + "BitRate": 7830, + "Channels": 2, + "SampleRate": 44100, + "IsDefault": true, + "IsForced": false, + "IsHearingImpaired": false, + "Profile": "LC", + "Type": "Audio", + "Index": 0, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Level": 0 + } + ], + "ImageTags": {}, + "BackdropImageTags": [], + "ParentLogoImageTag": "f106ecce6ad29fe66eed96ac7281dbb3", + "ImageBlurHashes": { + "Logo": { + "f106ecce6ad29fe66eed96ac7281dbb3": "HEEMLDM{4nRjM{ofWBIUxu~qM{%MRjxuRjxuxuj[" + }, + "Thumb": { + "f106ecce6ad29fe66eed96ac7281dbb3": "NEDvTx569bxJjFs*1A^ixuNGxaM|8xOXxn%1NKNf" + }, + "Backdrop": { + "f106ecce6ad29fe66eed96ac7281dbb3": "WSC6p0EMIUxaR.t8tpaKflj?WBog57w[xtWTj[WB$+NZjYs:WBWC" + } + }, + "ParentThumbItemId": "08a65b1c50ceeb0535f33d19d3b219d8", + "ParentThumbImageTag": "f106ecce6ad29fe66eed96ac7281dbb3", + "LocationType": "FileSystem", + "MediaType": "Audio" + } +} \ No newline at end of file diff --git a/src/backend/tests/jellyfin/jellyfin.test.ts b/src/backend/tests/jellyfin/jellyfin.test.ts index 104d3bfe..952c7cb8 100644 --- a/src/backend/tests/jellyfin/jellyfin.test.ts +++ b/src/backend/tests/jellyfin/jellyfin.test.ts @@ -7,9 +7,14 @@ import { JsonPlayObject, PlayMeta, PlayObject } from "../../../core/Atomic.js"; import JellyfinSource from "../../sources/JellyfinSource.js"; import JellyfinApiSource from "../../sources/JellyfinApiSource.js"; import samplePayload from './playbackProgressSample.json'; +import validSession from './validSession.json'; import { JellyApiData } from "../../common/infrastructure/config/source/jellyfin.js"; import { generatePlay } from "../utils/PlayTestUtils.js"; import { fakerJA } from "@faker-js/faker"; +import { + // @ts-expect-error weird typings? + SessionInfo, +} from "@jellyfin/sdk/lib/generated-client/index.js"; const dataAsFixture = (data: any): TestFixture => { return data as TestFixture; @@ -32,7 +37,7 @@ const defaultJfApiCreds = {url: 'http://example.com', user: 'MyUser', apiKey: '1 const validPlay = generatePlay({}, {mediaType: 'Audio', user: 'MyUser', deviceId: '1234'}); const playWithMeta = (meta: PlayMeta): PlayObject => ({...validPlay, meta: {...validPlay.meta, ...meta}}); -const validSession = {NowPlayingItem: {}}; +const nowPlayingSession = (data: object): SessionInfo => ({...validSession, NowPlayingItem: {...validSession.NowPlayingItem, ...data}}); describe('Jellyfin Legacy Source', function() { describe('Jellyfin Payload Parsing', function () { @@ -141,24 +146,24 @@ describe("Jellyfin API Source", function() { await jf.destroy(); }); - it('Should disallow activity that is not valid audio', async function () { + it('Should disallow NowPlayingItem that is not valid Type', async function () { const jf = createJfApi({...defaultJfApiCreds}); await jf.buildInitData(); - expect(jf.isActivityValid(playWithMeta({mediaType: 'Video'}), validSession)).to.not.be.true; - expect(jf.isActivityValid(playWithMeta({mediaType: 'Unknown'}), validSession)).to.not.be.true; + expect(jf.isActivityValid(validPlay, nowPlayingSession({Type: 'Book'}))).to.not.be.true; await jf.destroy(); }); - it('Should disallow activity that is a theme song extra', async function () { + it('Should disallow Play that is not valid MediaType', async function () { const jf = createJfApi({...defaultJfApiCreds}); await jf.buildInitData(); - expect(jf.isActivityValid(validPlay, {NowPlayingItem: {ExtraType: 'ThemeSong'}})).to.not.be.true; + expect(jf.isActivityValid(playWithMeta({mediaType: 'Video'}), validSession)).to.not.be.true; + expect(jf.isActivityValid(playWithMeta({mediaType: 'Unknown'}), validSession)).to.not.be.true; await jf.destroy(); }); - it('Should allow unknown activity if specified in options', async function () { + it('Should allow Play with unknown mediaType if specified in options', async function () { const jf = createJfApi({...defaultJfApiCreds}); jf.config.data.allowUnknown = true; await jf.buildInitData(); @@ -166,5 +171,13 @@ describe("Jellyfin API Source", function() { expect(jf.isActivityValid(playWithMeta({mediaType: 'Unknown'}), validSession)).to.be.true; await jf.destroy(); }); + + it('Should disallow NowPlayingItem that is a theme song (ExtraType)', async function () { + const jf = createJfApi({...defaultJfApiCreds}); + await jf.buildInitData(); + + expect(jf.isActivityValid(validPlay, nowPlayingSession({ExtraType: 'ThemeSong'}))).to.not.be.true; + await jf.destroy(); + }); }); }); diff --git a/src/backend/tests/jellyfin/validSession.json b/src/backend/tests/jellyfin/validSession.json new file mode 100644 index 00000000..f5cab6ee --- /dev/null +++ b/src/backend/tests/jellyfin/validSession.json @@ -0,0 +1,547 @@ +{ + "PlayState": { + "PositionTicks": 160960000, + "CanSeek": true, + "IsPaused": true, + "IsMuted": false, + "VolumeLevel": 61, + "MediaSourceId": "7648c5d1a50b1e03f225a2ce76baef07", + "PlayMethod": "DirectPlay", + "RepeatMode": "RepeatNone", + "PlaybackOrder": "Default" + }, + "AdditionalUsers": [], + "Capabilities": { + "PlayableMediaTypes": [ + "Audio", + "Video" + ], + "SupportedCommands": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "SetShuffleQueue", + "ChannelUp", + "ChannelDown", + "PlayMediaSource", + "PlayTrailers" + ], + "SupportsMediaControl": true, + "SupportsPersistentIdentifier": false, + "SupportsContentUploading": false, + "SupportsSync": false + }, + "RemoteEndPoint": "192.168.0.220", + "PlayableMediaTypes": [ + "Audio", + "Video" + ], + "Id": "9717105bdbdb30d25f7338997910a147", + "UserId": "1ae8a43ed293456da28274f1a89cd2a5", + "UserName": "foxx", + "Client": "Jellyfin Web", + "LastActivityDate": "2024-10-15T13:36:46.6727027Z", + "LastPlaybackCheckIn": "2024-10-15T13:36:46.6727028Z", + "LastPausedDate": "2024-10-15T12:57:59.5443726Z", + "DeviceName": "Firefox", + "NowPlayingItem": { + "Name": "I GOT YOU", + "ServerId": "64fd848a38664fd2b8f168db7eede729", + "Id": "7648c5d1a50b1e03f225a2ce76baef07", + "DateCreated": "2024-08-26T18:10:25.0263918Z", + "HasLyrics": true, + "Container": "mp3", + "PremiereDate": "2024-01-01T00:00:00.0000000Z", + "ExternalUrls": [ + { + "Name": "MusicBrainz", + "Url": "https://musicbrainz.org/artist/8da127cc-c432-418f-b356-ef36210d82ac" + } + ], + "Path": "/data/allmusic/TWICE/I GOT YOU/01 - I GOT YOU.mp3", + "EnableMediaSourceDisplay": true, + "ChannelId": null, + "Taglines": [], + "Genres": [ + "Pop" + ], + "RunTimeTicks": 1732702040, + "ProductionYear": 2024, + "IndexNumber": 1, + "ParentIndexNumber": 1, + "ProviderIds": { + "MusicBrainzArtist": "8da127cc-c432-418f-b356-ef36210d82ac" + }, + "IsFolder": false, + "ParentId": "53eadcbac2228cd0879469c12b9e8c0c", + "Type": "Audio", + "Studios": [], + "GenreItems": [ + { + "Name": "Pop", + "Id": "f1f202f389018ad2c0766af4b0fcb155" + } + ], + "ParentLogoItemId": "fcc5032e8edca6a0f4bf89613638e016", + "ParentBackdropItemId": "fcc5032e8edca6a0f4bf89613638e016", + "ParentBackdropImageTags": [ + "9ba1ea2ca908c8f3a6ecfc708a994df7" + ], + "LocalTrailerCount": 0, + "SpecialFeatureCount": 0, + "PrimaryImageAspectRatio": 1, + "Artists": [ + "TWICE" + ], + "ArtistItems": [ + { + "Name": "TWICE", + "Id": "fcc5032e8edca6a0f4bf89613638e016" + } + ], + "Album": "I GOT YOU", + "AlbumId": "53eadcbac2228cd0879469c12b9e8c0c", + "AlbumPrimaryImageTag": "a10d57593fa2ecbf430e3bfd39225700", + "AlbumArtist": "TWICE", + "AlbumArtists": [ + { + "Name": "TWICE", + "Id": "fcc5032e8edca6a0f4bf89613638e016" + } + ], + "MediaStreams": [ + { + "Codec": "mp3", + "TimeBase": "1/14112000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "LocalizedDefault": "Default", + "LocalizedExternal": "External", + "DisplayTitle": "MP3 - Stereo", + "IsInterlaced": false, + "IsAVC": false, + "ChannelLayout": "stereo", + "BitRate": 320000, + "Channels": 2, + "SampleRate": 44100, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Audio", + "Index": 0, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Level": 0 + }, + { + "Codec": "mjpeg", + "ColorSpace": "bt470bg", + "Comment": "Cover (front)", + "TimeBase": "1/90000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsAVC": false, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Height": 1000, + "Width": 1000, + "RealFrameRate": 90000, + "Profile": "Baseline", + "Type": "EmbeddedImage", + "AspectRatio": "1:1", + "Index": 1, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "PixelFormat": "yuvj444p", + "Level": -99, + "IsAnamorphic": false + }, + { + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Lyric", + "Index": 2, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Path": "/config/data/metadata/library/76/7648c5d1a50b1e03f225a2ce76baef07/01 - I GOT YOU.lrc" + } + ], + "ImageTags": { + "Primary": "909f8ed25c11f7575842f09fb529e9d5" + }, + "BackdropImageTags": [], + "ParentLogoImageTag": "7f7855ce907ab29bbb13a873480dd3c6", + "ImageBlurHashes": { + "Primary": { + "909f8ed25c11f7575842f09fb529e9d5": "e+K_Xj~qRjR%t8InIARjofWBD%IoWVayWBRjaet7j[ayV@V@t7ofj[", + "a10d57593fa2ecbf430e3bfd39225700": "e+K_Xj~qRjR%t8InIARjofWBD%IoWVayWBRjaet7j[ayV@V@t7ofj[" + }, + "Logo": { + "7f7855ce907ab29bbb13a873480dd3c6": "OSA8+Na{0|oL=yS1I:xGfQR*jas.bGayI:ay$*oLR*WVbY" + }, + "Backdrop": { + "9ba1ea2ca908c8f3a6ecfc708a994df7": "WWKJvK-;N1oiIBRV]@V@sDs=tQxbvoovs:aKbb%2?1ozSwRlRPRk" + } + }, + "LocationType": "FileSystem", + "MediaType": "Audio" + }, + "DeviceId": "TW96aWxsYS81LjAgKFgxMTsgTGludXggeDg2XzY0OyBydjoxMjkuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC8xMjkuMHwxNzI1NjQzMjYwNTEy", + "ApplicationVersion": "10.9.11", + "IsActive": true, + "SupportsMediaControl": true, + "SupportsRemoteControl": true, + "NowPlayingQueue": [ + { + "Id": "7648c5d1a50b1e03f225a2ce76baef07", + "PlaylistItemId": "playlistItem1" + } + ], + "NowPlayingQueueFullItems": [ + { + "Name": "I GOT YOU", + "ServerId": "64fd848a38664fd2b8f168db7eede729", + "Id": "7648c5d1a50b1e03f225a2ce76baef07", + "Etag": "8162479b9f2fef290b521ea4d30ffef7", + "DateCreated": "2024-08-26T18:10:25.0263918Z", + "CanDelete": true, + "CanDownload": true, + "HasLyrics": true, + "Container": "mp3", + "SortName": "0001 - 0001 - I GOT YOU", + "PremiereDate": "2024-01-01T00:00:00.0000000Z", + "ExternalUrls": [ + { + "Name": "MusicBrainz", + "Url": "https://musicbrainz.org/artist/8da127cc-c432-418f-b356-ef36210d82ac" + } + ], + "MediaSources": [ + { + "Protocol": "File", + "Id": "7648c5d1a50b1e03f225a2ce76baef07", + "Path": "/data/allmusic/TWICE/I GOT YOU/01 - I GOT YOU.mp3", + "Type": "Default", + "Container": "mp3", + "Size": 7583921, + "Name": "01 - I GOT YOU", + "IsRemote": false, + "ETag": "98895d1d581b04e57e5bb70b7665b9f4", + "RunTimeTicks": 1732702040, + "ReadAtNativeFramerate": false, + "IgnoreDts": false, + "IgnoreIndex": false, + "GenPtsInput": false, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "IsInfiniteStream": false, + "RequiresOpening": false, + "RequiresClosing": false, + "RequiresLooping": false, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "mp3", + "TimeBase": "1/14112000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "LocalizedDefault": "Default", + "LocalizedExternal": "External", + "DisplayTitle": "MP3 - Stereo", + "IsInterlaced": false, + "IsAVC": false, + "ChannelLayout": "stereo", + "BitRate": 320000, + "Channels": 2, + "SampleRate": 44100, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Audio", + "Index": 0, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Level": 0 + }, + { + "Codec": "mjpeg", + "ColorSpace": "bt470bg", + "Comment": "Cover (front)", + "TimeBase": "1/90000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsAVC": false, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Height": 1000, + "Width": 1000, + "RealFrameRate": 90000, + "Profile": "Baseline", + "Type": "EmbeddedImage", + "AspectRatio": "1:1", + "Index": 1, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "PixelFormat": "yuvj444p", + "Level": -99, + "IsAnamorphic": false + }, + { + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Lyric", + "Index": 2, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Path": "/config/data/metadata/library/76/7648c5d1a50b1e03f225a2ce76baef07/01 - I GOT YOU.lrc" + } + ], + "MediaAttachments": [], + "Formats": [], + "Bitrate": 350154, + "RequiredHttpHeaders": {}, + "TranscodingSubProtocol": "http" + } + ], + "Path": "/data/allmusic/TWICE/I GOT YOU/01 - I GOT YOU.mp3", + "EnableMediaSourceDisplay": true, + "ChannelId": null, + "Taglines": [], + "Genres": [ + "Pop" + ], + "RunTimeTicks": 1732702040, + "ProductionYear": 2024, + "IndexNumber": 1, + "ParentIndexNumber": 1, + "RemoteTrailers": [], + "ProviderIds": { + "MusicBrainzArtist": "8da127cc-c432-418f-b356-ef36210d82ac" + }, + "IsFolder": false, + "ParentId": "53eadcbac2228cd0879469c12b9e8c0c", + "Type": "Audio", + "People": [], + "Studios": [], + "GenreItems": [ + { + "Name": "Pop", + "Id": "f1f202f389018ad2c0766af4b0fcb155" + } + ], + "ParentLogoItemId": "fcc5032e8edca6a0f4bf89613638e016", + "ParentBackdropItemId": "fcc5032e8edca6a0f4bf89613638e016", + "ParentBackdropImageTags": [ + "9ba1ea2ca908c8f3a6ecfc708a994df7" + ], + "LocalTrailerCount": 0, + "SpecialFeatureCount": 0, + "DisplayPreferencesId": "61bba315f137702baa296a1c417faada", + "Tags": [], + "PrimaryImageAspectRatio": 1, + "Artists": [ + "TWICE" + ], + "ArtistItems": [ + { + "Name": "TWICE", + "Id": "fcc5032e8edca6a0f4bf89613638e016" + } + ], + "Album": "I GOT YOU", + "AlbumId": "53eadcbac2228cd0879469c12b9e8c0c", + "AlbumPrimaryImageTag": "a10d57593fa2ecbf430e3bfd39225700", + "AlbumArtist": "TWICE", + "AlbumArtists": [ + { + "Name": "TWICE", + "Id": "fcc5032e8edca6a0f4bf89613638e016" + } + ], + "MediaStreams": [ + { + "Codec": "mp3", + "TimeBase": "1/14112000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "LocalizedDefault": "Default", + "LocalizedExternal": "External", + "DisplayTitle": "MP3 - Stereo", + "IsInterlaced": false, + "IsAVC": false, + "ChannelLayout": "stereo", + "BitRate": 320000, + "Channels": 2, + "SampleRate": 44100, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Audio", + "Index": 0, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Level": 0 + }, + { + "Codec": "mjpeg", + "ColorSpace": "bt470bg", + "Comment": "Cover (front)", + "TimeBase": "1/90000", + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsAVC": false, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Height": 1000, + "Width": 1000, + "RealFrameRate": 90000, + "Profile": "Baseline", + "Type": "EmbeddedImage", + "AspectRatio": "1:1", + "Index": 1, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "PixelFormat": "yuvj444p", + "Level": -99, + "IsAnamorphic": false + }, + { + "VideoRange": "Unknown", + "VideoRangeType": "Unknown", + "AudioSpatialFormat": "None", + "IsInterlaced": false, + "IsDefault": false, + "IsForced": false, + "IsHearingImpaired": false, + "Type": "Lyric", + "Index": 2, + "IsExternal": false, + "IsTextSubtitleStream": false, + "SupportsExternalStream": false, + "Path": "/config/data/metadata/library/76/7648c5d1a50b1e03f225a2ce76baef07/01 - I GOT YOU.lrc" + } + ], + "ImageTags": { + "Primary": "909f8ed25c11f7575842f09fb529e9d5" + }, + "BackdropImageTags": [], + "ParentLogoImageTag": "7f7855ce907ab29bbb13a873480dd3c6", + "ImageBlurHashes": { + "Primary": { + "909f8ed25c11f7575842f09fb529e9d5": "e+K_Xj~qRjR%t8InIARjofWBD%IoWVayWBRjaet7j[ayV@V@t7ofj[", + "a10d57593fa2ecbf430e3bfd39225700": "e+K_Xj~qRjR%t8InIARjofWBD%IoWVayWBRjaet7j[ayV@V@t7ofj[" + }, + "Logo": { + "7f7855ce907ab29bbb13a873480dd3c6": "OSA8+Na{0|oL=yS1I:xGfQR*jas.bGayI:ay$*oLR*WVbY" + }, + "Backdrop": { + "9ba1ea2ca908c8f3a6ecfc708a994df7": "WWKJvK-;N1oiIBRV]@V@sDs=tQxbvoovs:aKbb%2?1ozSwRlRPRk" + } + }, + "LocationType": "FileSystem", + "MediaType": "Audio", + "LockedFields": [], + "LockData": false + } + ], + "HasCustomDeviceName": false, + "PlaylistItemId": "playlistItem1", + "ServerId": "64fd848a38664fd2b8f168db7eede729", + "SupportedCommands": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "SetShuffleQueue", + "ChannelUp", + "ChannelDown", + "PlayMediaSource", + "PlayTrailers" + ] +} \ No newline at end of file