fix: add missing AudioFormat metadata to Jellyfin provider (#1890)
authorJc2k <john.carr@unrouted.co.uk>
Mon, 20 Jan 2025 20:48:30 +0000 (20:48 +0000)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2025 20:48:30 +0000 (21:48 +0100)
* feat: populate more stream details in Jellyfin

* fix: defensive parsing of optional fields. tests.

* chore: refactor and unify how we get an AudioFormat from a jellyfin Track

* chore: more refactoring

* fix: breaking api change

music_assistant/providers/jellyfin/__init__.py
music_assistant/providers/jellyfin/manifest.json
music_assistant/providers/jellyfin/parsers.py
requirements_all.txt
tests/providers/jellyfin/__snapshots__/test_parsers.ambr
tests/providers/jellyfin/fixtures/tracks/do_i_wanna_know.json [new file with mode: 0644]

index e5f934bde7bc8a29b53854cce521032bcfecc809..f3e53ac6fb9897df974eb52c540b87039547bc35 100644 (file)
@@ -3,7 +3,6 @@
 from __future__ import annotations
 
 import hashlib
-import mimetypes
 import socket
 from asyncio import TaskGroup
 from collections.abc import AsyncGenerator
@@ -11,21 +10,13 @@ from typing import TYPE_CHECKING
 
 from aiojellyfin import MediaLibrary as JellyMediaLibrary
 from aiojellyfin import NotFound, authenticate_by_name
-from aiojellyfin import Track as JellyTrack
 from aiojellyfin.session import SessionConfiguration
 from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig
-from music_assistant_models.enums import (
-    ConfigEntryType,
-    ContentType,
-    MediaType,
-    ProviderFeature,
-    StreamType,
-)
+from music_assistant_models.enums import ConfigEntryType, MediaType, ProviderFeature, StreamType
 from music_assistant_models.errors import LoginFailed, MediaNotFoundError
 from music_assistant_models.media_items import (
     Album,
     Artist,
-    AudioFormat,
     Playlist,
     ProviderMapping,
     SearchResults,
@@ -38,6 +29,7 @@ from music_assistant.mass import MusicAssistant
 from music_assistant.models import ProviderInstanceType
 from music_assistant.models.music_provider import MusicProvider
 from music_assistant.providers.jellyfin.parsers import (
+    audio_format,
     parse_album,
     parse_artist,
     parse_playlist,
@@ -49,9 +41,6 @@ from .const import (
     ARTIST_FIELDS,
     ITEM_KEY_COLLECTION_TYPE,
     ITEM_KEY_ID,
-    ITEM_KEY_MEDIA_CHANNELS,
-    ITEM_KEY_MEDIA_CODEC,
-    ITEM_KEY_MEDIA_SOURCES,
     ITEM_KEY_MEDIA_STREAMS,
     ITEM_KEY_NAME,
     ITEM_KEY_RUNTIME_TICKS,
@@ -451,20 +440,13 @@ class JellyfinProvider(MusicProvider):
     ) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         jellyfin_track = await self._client.get_track(item_id)
-        mimetype = self._media_mime_type(jellyfin_track)
-        media_stream = jellyfin_track[ITEM_KEY_MEDIA_STREAMS][0]
-        url = self._client.audio_url(jellyfin_track[ITEM_KEY_ID], SUPPORTED_CONTAINER_FORMATS)
-        if ITEM_KEY_MEDIA_CODEC in media_stream:
-            content_type = ContentType.try_parse(media_stream[ITEM_KEY_MEDIA_CODEC])
-        else:
-            content_type = ContentType.try_parse(mimetype) if mimetype else ContentType.UNKNOWN
+        url = self._client.audio_url(
+            jellyfin_track[ITEM_KEY_ID], container=SUPPORTED_CONTAINER_FORMATS
+        )
         return StreamDetails(
             item_id=jellyfin_track[ITEM_KEY_ID],
             provider=self.lookup_key,
-            audio_format=AudioFormat(
-                content_type=content_type,
-                channels=jellyfin_track[ITEM_KEY_MEDIA_STREAMS][0][ITEM_KEY_MEDIA_CHANNELS],
-            ),
+            audio_format=audio_format(jellyfin_track),
             stream_type=StreamType.HTTP,
             duration=int(
                 jellyfin_track[ITEM_KEY_RUNTIME_TICKS] / 10000000
@@ -504,18 +486,3 @@ class JellyfinProvider(MusicProvider):
             ):
                 result.append(library)
         return result
-
-    def _media_mime_type(self, media_item: JellyTrack) -> str | None:
-        """Return the mime type of a media item."""
-        if not media_item.get(ITEM_KEY_MEDIA_SOURCES):
-            return None
-
-        media_source = media_item[ITEM_KEY_MEDIA_SOURCES][0]
-
-        if "Path" not in media_source:
-            return None
-
-        path = media_source["Path"]
-        mime_type, _ = mimetypes.guess_type(path)
-
-        return mime_type
index af85a57b7e6904d374821938550778ff46a40830..d895263f3ab2b0e032595d92011a915b749df694 100644 (file)
@@ -4,7 +4,7 @@
   "name": "Jellyfin Media Server Library",
   "description": "Support for the Jellyfin streaming provider in Music Assistant.",
   "codeowners": ["@lokiberra", "@Jc2k"],
-  "requirements": ["aiojellyfin==0.11.2"],
+  "requirements": ["aiojellyfin==0.14.1"],
   "documentation": "https://music-assistant.io/music-providers/jellyfin/",
   "multi_instance": true
 }
index 96545118d5f9bca7735506848f2b8c01082fac4d..a657997a66bd552f0a18f833766afa278d3b3254 100644 (file)
@@ -31,6 +31,7 @@ from .const import (
     ITEM_KEY_CAN_DOWNLOAD,
     ITEM_KEY_ID,
     ITEM_KEY_IMAGE_TAGS,
+    ITEM_KEY_MEDIA_CHANNELS,
     ITEM_KEY_MEDIA_CODEC,
     ITEM_KEY_MEDIA_STREAMS,
     ITEM_KEY_MUSICBRAINZ_ALBUM,
@@ -171,14 +172,25 @@ def parse_artist(
     return artist
 
 
+def audio_format(track: JellyTrack) -> AudioFormat:
+    """Build an AudioFormat model from a Jellyfin track."""
+    stream = track[ITEM_KEY_MEDIA_STREAMS][0]
+    codec = stream[ITEM_KEY_MEDIA_CODEC]
+    return AudioFormat(
+        content_type=(ContentType.try_parse(codec) if codec else ContentType.UNKNOWN),
+        channels=stream[ITEM_KEY_MEDIA_CHANNELS],
+        sample_rate=stream.get("SampleRate", 44100),
+        bit_rate=stream.get("BitRate"),
+        bit_depth=stream.get("BitDepth", 16),
+    )
+
+
 def parse_track(
     logger: Logger, instance_id: str, client: Connection, jellyfin_track: JellyTrack
 ) -> Track:
     """Parse a Jellyfin Track response to a Track model object."""
     available = False
-    content = None
     available = jellyfin_track[ITEM_KEY_CAN_DOWNLOAD]
-    content = jellyfin_track[ITEM_KEY_MEDIA_STREAMS][0][ITEM_KEY_MEDIA_CODEC]
     track = Track(
         item_id=jellyfin_track[ITEM_KEY_ID],
         provider=instance_id,
@@ -189,11 +201,7 @@ def parse_track(
                 provider_domain=DOMAIN,
                 provider_instance=instance_id,
                 available=available,
-                audio_format=AudioFormat(
-                    content_type=(
-                        ContentType.try_parse(content) if content else ContentType.UNKNOWN
-                    ),
-                ),
+                audio_format=audio_format(jellyfin_track),
                 url=client.audio_url(jellyfin_track[ITEM_KEY_ID]),
             )
         },
index 115c5812658d59b8d72b36c8a9b516ac2f5c8635..3aefb689d0de569e59a37efd22f0567d5761f8ee 100644 (file)
@@ -4,7 +4,7 @@ Brotli>=1.0.9
 aiodns>=3.0.0
 aiofiles==24.1.0
 aiohttp==3.11.6
-aiojellyfin==0.11.2
+aiojellyfin==0.14.1
 aiorun==2024.8.1
 aioslimproto==3.1.0
 aiosonos==0.1.7
index 9d4901b7996393d73fa683a2c45cfae240038584..e585da90fdc2c64095d89dd29adef04b3af341c9 100644 (file)
     'version': '',
   })
 # ---
+# name: test_parse_tracks[do_i_wanna_know]
+  dict({
+    'album': dict({
+      'available': True,
+      'external_ids': list([
+      ]),
+      'image': None,
+      'item_id': 'd42d74e134693184e7adc73106238e89',
+      'media_type': 'album',
+      'name': 'AM',
+      'provider': 'xx-instance-id-xx',
+      'sort_name': 'am',
+      'uri': 'xx-instance-id-xx://album/d42d74e134693184e7adc73106238e89',
+      'version': '',
+    }),
+    'artists': list([
+      dict({
+        'available': True,
+        'external_ids': list([
+        ]),
+        'image': None,
+        'item_id': 'cc940aeb8a99149f159fe9794f136071',
+        'media_type': 'artist',
+        'name': 'Arctic Monkeys',
+        'provider': 'xx-instance-id-xx',
+        'sort_name': 'arctic monkeys',
+        'uri': 'xx-instance-id-xx://artist/cc940aeb8a99149f159fe9794f136071',
+        'version': '',
+      }),
+    ]),
+    'disc_number': 1,
+    'duration': 272,
+    'external_ids': list([
+    ]),
+    'favorite': False,
+    'item_id': 'da9c458e425584680765ddc3a89cbc0c',
+    'media_type': 'track',
+    'metadata': dict({
+      'chapters': None,
+      'copyright': None,
+      'description': None,
+      'explicit': None,
+      'genres': None,
+      'images': list([
+        dict({
+          'path': 'http://localhost:1234/Items/da9c458e425584680765ddc3a89cbc0c/Images/Primary?api_key=ACCESS_TOKEN',
+          'provider': 'xx-instance-id-xx',
+          'remotely_accessible': False,
+          'type': 'thumb',
+        }),
+      ]),
+      'label': None,
+      'languages': None,
+      'last_refresh': None,
+      'links': None,
+      'lyrics': None,
+      'mood': None,
+      'performers': None,
+      'popularity': None,
+      'preview': None,
+      'release_date': None,
+      'review': None,
+      'style': None,
+    }),
+    'name': 'Do I Wanna Know?',
+    'position': 1,
+    'provider': 'xx-instance-id-xx',
+    'provider_mappings': list([
+      dict({
+        'audio_format': dict({
+          'bit_depth': 24,
+          'bit_rate': 1546,
+          'channels': 2,
+          'content_type': 'flac',
+          'output_format_str': 'flac',
+          'sample_rate': 44100,
+        }),
+        'available': True,
+        'details': None,
+        'item_id': 'da9c458e425584680765ddc3a89cbc0c',
+        'provider_domain': 'jellyfin',
+        'provider_instance': 'xx-instance-id-xx',
+        'url': 'http://localhost:1234/Audio/da9c458e425584680765ddc3a89cbc0c/universal?userId=USER_ID&deviceId=X&maxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
+      }),
+    ]),
+    'sort_name': 'do i wanna know?',
+    'track_number': 1,
+    'uri': 'xx-instance-id-xx://track/da9c458e425584680765ddc3a89cbc0c',
+    'version': '',
+  })
+# ---
 # name: test_parse_tracks[thrown_away]
   dict({
     'album': dict({
       dict({
         'audio_format': dict({
           'bit_depth': 16,
-          'bit_rate': 0,
+          'bit_rate': 156,
           'channels': 2,
           'content_type': 'mp3',
           'output_format_str': 'mp3',
         'item_id': 'b5319fb11cde39fca2023184fcfa9862',
         'provider_domain': 'jellyfin',
         'provider_instance': 'xx-instance-id-xx',
-        'url': 'http://localhost:1234/Audio/b5319fb11cde39fca2023184fcfa9862/universal?UserId=USER_ID&DeviceId=X&MaxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
+        'url': 'http://localhost:1234/Audio/b5319fb11cde39fca2023184fcfa9862/universal?userId=USER_ID&deviceId=X&maxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
       }),
     ]),
     'sort_name': '11 thrown away',
       dict({
         'audio_format': dict({
           'bit_depth': 16,
-          'bit_rate': 0,
+          'bit_rate': 278,
           'channels': 2,
           'content_type': 'aac',
           'output_format_str': 'aac',
         'item_id': '54918f75ee8f6c8b8dc5efd680644f29',
         'provider_domain': 'jellyfin',
         'provider_instance': 'xx-instance-id-xx',
-        'url': 'http://localhost:1234/Audio/54918f75ee8f6c8b8dc5efd680644f29/universal?UserId=USER_ID&DeviceId=X&MaxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
+        'url': 'http://localhost:1234/Audio/54918f75ee8f6c8b8dc5efd680644f29/universal?userId=USER_ID&deviceId=X&maxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
       }),
     ]),
     'sort_name': 'where the bands are (2018 version)',
       dict({
         'audio_format': dict({
           'bit_depth': 16,
-          'bit_rate': 0,
+          'bit_rate': 267,
           'channels': 2,
           'content_type': 'aac',
           'output_format_str': 'aac',
         'item_id': 'fb12a77f49616a9fc56a6fab2b94174c',
         'provider_domain': 'jellyfin',
         'provider_instance': 'xx-instance-id-xx',
-        'url': 'http://localhost:1234/Audio/fb12a77f49616a9fc56a6fab2b94174c/universal?UserId=USER_ID&DeviceId=X&MaxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
+        'url': 'http://localhost:1234/Audio/fb12a77f49616a9fc56a6fab2b94174c/universal?userId=USER_ID&deviceId=X&maxStreamingBitrate=140000000&api_key=ACCESS_TOKEN',
       }),
     ]),
     'sort_name': 'zombie christmas',
diff --git a/tests/providers/jellyfin/fixtures/tracks/do_i_wanna_know.json b/tests/providers/jellyfin/fixtures/tracks/do_i_wanna_know.json
new file mode 100644 (file)
index 0000000..2466a96
--- /dev/null
@@ -0,0 +1,265 @@
+{
+    "Name": "Do I Wanna Know?",
+    "ServerId": "a9038ca202a94606b3764740074fec3a",
+    "Id": "da9c458e425584680765ddc3a89cbc0c",
+    "Etag": "6a569c1c1d153acad95ffcaf531b6ccb",
+    "DateCreated": "2025-01-07T20:04:36.5322321Z",
+    "CanDelete": false,
+    "CanDownload": true,
+    "HasLyrics": true,
+    "Container": "flac",
+    "SortName": "0001 - 0001 - Do I Wanna Know?",
+    "PremiereDate": "2013-01-01T00:00:00.0000000Z",
+    "ExternalUrls": [],
+    "MediaSources": [
+        {
+            "Protocol": "File",
+            "Id": "da9c458e425584680765ddc3a89cbc0c",
+            "Path": "/media/music/Arctic Monkeys/AM/01-01 Do I Wanna Know.flac",
+            "Type": "Default",
+            "Container": "flac",
+            "Size": 52672047,
+            "Name": "01-01 Do I Wanna Know",
+            "IsRemote": false,
+            "ETag": "09f1d9b8dcbcd5b61c65d4fdc2aadc32",
+            "RunTimeTicks": 2723941040,
+            "ReadAtNativeFramerate": false,
+            "IgnoreDts": false,
+            "IgnoreIndex": false,
+            "GenPtsInput": false,
+            "SupportsTranscoding": true,
+            "SupportsDirectStream": true,
+            "SupportsDirectPlay": true,
+            "IsInfiniteStream": false,
+            "UseMostCompatibleTranscodingProfile": false,
+            "RequiresOpening": false,
+            "RequiresClosing": false,
+            "RequiresLooping": false,
+            "SupportsProbing": true,
+            "MediaStreams": [
+                {
+                    "Codec": "flac",
+                    "TimeBase": "1/44100",
+                    "VideoRange": "Unknown",
+                    "VideoRangeType": "Unknown",
+                    "AudioSpatialFormat": "None",
+                    "LocalizedDefault": "Default",
+                    "LocalizedExternal": "External",
+                    "DisplayTitle": "FLAC - Stereo",
+                    "IsInterlaced": false,
+                    "IsAVC": false,
+                    "ChannelLayout": "stereo",
+                    "BitRate": 1546936,
+                    "BitDepth": 24,
+                    "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": 600,
+                    "Width": 600,
+                    "RealFrameRate": 90000,
+                    "ReferenceFrameRate": 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": "/var/lib/jellyfin/metadata/library/da/da9c458e425584680765ddc3a89cbc0c/01-01 Do I Wanna Know.lrc"
+                }
+            ],
+            "MediaAttachments": [],
+            "Formats": [],
+            "Bitrate": 1546936,
+            "RequiredHttpHeaders": {},
+            "TranscodingSubProtocol": "http",
+            "DefaultAudioStreamIndex": 0,
+            "HasSegments": false
+        }
+    ],
+    "Path": "/media/music/Arctic Monkeys/AM/01-01 Do I Wanna Know.flac",
+    "EnableMediaSourceDisplay": true,
+    "ChannelId": null,
+    "Taglines": [],
+    "Genres": ["Alternative \u0026 Indie"],
+    "RunTimeTicks": 2723941040,
+    "PlayAccess": "Full",
+    "ProductionYear": 2013,
+    "IndexNumber": 1,
+    "ParentIndexNumber": 1,
+    "RemoteTrailers": [],
+    "ProviderIds": {},
+    "IsFolder": false,
+    "ParentId": "d42d74e134693184e7adc73106238e89",
+    "Type": "Audio",
+    "People": [],
+    "Studios": [],
+    "GenreItems": [
+        {
+            "Name": "Alternative \u0026 Indie",
+            "Id": "daf2f5737c11cea90c2ef7376801d804"
+        }
+    ],
+    "ParentLogoItemId": "cc940aeb8a99149f159fe9794f136071",
+    "ParentBackdropItemId": "cc940aeb8a99149f159fe9794f136071",
+    "ParentBackdropImageTags": ["c0bbf52814fd50ec5417617da2766e43"],
+    "LocalTrailerCount": 0,
+    "UserData": {
+        "PlaybackPositionTicks": 0,
+        "PlayCount": 4,
+        "IsFavorite": false,
+        "LastPlayedDate": "2025-01-18T12:09:39.3190466Z",
+        "Played": true,
+        "Key": "Arctic Monkeys-AM-0001-0001Do I Wanna Know?",
+        "ItemId": "00000000000000000000000000000000"
+    },
+    "SpecialFeatureCount": 0,
+    "DisplayPreferencesId": "61bba315f137702baa296a1c417faada",
+    "Tags": [],
+    "PrimaryImageAspectRatio": 1,
+    "Artists": ["Arctic Monkeys"],
+    "ArtistItems": [
+        { "Name": "Arctic Monkeys", "Id": "cc940aeb8a99149f159fe9794f136071" }
+    ],
+    "Album": "AM",
+    "AlbumId": "d42d74e134693184e7adc73106238e89",
+    "AlbumPrimaryImageTag": "0e9e1da3e4efc8f37917cc6a4d8f626d",
+    "AlbumArtist": "Arctic Monkeys",
+    "AlbumArtists": [
+        { "Name": "Arctic Monkeys", "Id": "cc940aeb8a99149f159fe9794f136071" }
+    ],
+    "MediaStreams": [
+        {
+            "Codec": "flac",
+            "TimeBase": "1/44100",
+            "VideoRange": "Unknown",
+            "VideoRangeType": "Unknown",
+            "AudioSpatialFormat": "None",
+            "LocalizedDefault": "Default",
+            "LocalizedExternal": "External",
+            "DisplayTitle": "FLAC - Stereo",
+            "IsInterlaced": false,
+            "IsAVC": false,
+            "ChannelLayout": "stereo",
+            "BitRate": 1546936,
+            "BitDepth": 24,
+            "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": 600,
+            "Width": 600,
+            "RealFrameRate": 90000,
+            "ReferenceFrameRate": 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": "/var/lib/jellyfin/metadata/library/da/da9c458e425584680765ddc3a89cbc0c/01-01 Do I Wanna Know.lrc"
+        }
+    ],
+    "ImageTags": { "Primary": "7225206d6903fc4ab214375fd25d9308" },
+    "BackdropImageTags": [],
+    "ParentLogoImageTag": "3203dbc243aec2cc2e17d054a13a51cf",
+    "ImageBlurHashes": {
+        "Primary": {
+            "7225206d6903fc4ab214375fd25d9308": "eG7_4nofayofIUozfQj[j[WB00WBj[WB%MM{ayWBayt7_3j[WBofRj",
+            "0e9e1da3e4efc8f37917cc6a4d8f626d": "eG7_4nt7kCt7D%xuj[ofofWB00WBf6Rj%MD%WVRjWBt7?bofWBofM{"
+        },
+        "Logo": {
+            "3203dbc243aec2cc2e17d054a13a51cf": "OlJ8V0M{00ofD%xuRjIUM{j[xuoft7t7D%ayxut7t7RjWB"
+        },
+        "Backdrop": {
+            "c0bbf52814fd50ec5417617da2766e43": "WYEVg7?Hoeo}X9%L~qxuV@s:W=oz%gozRPjFWBWB%3xaR*S2R*ae"
+        }
+    },
+    "LocationType": "FileSystem",
+    "MediaType": "Audio",
+    "LockedFields": [],
+    "LockData": false,
+    "NormalizationGain": -8.4
+}