/
/
/
1"""All constants for Music Assistant."""
2
3import pathlib
4from typing import Final, cast
5
6from music_assistant_models.config_entries import (
7 MULTI_VALUE_SPLITTER,
8 ConfigEntry,
9 ConfigValueOption,
10)
11from music_assistant_models.enums import ConfigEntryType, ContentType
12from music_assistant_models.media_items import AudioFormat
13
14APPLICATION_NAME: Final = "Music Assistant"
15
16
17API_SCHEMA_VERSION: Final[int] = 28
18MIN_SCHEMA_VERSION: Final[int] = 28
19
20
21MASS_LOGGER_NAME: Final[str] = "music_assistant"
22
23# Home Assistant system user
24HOMEASSISTANT_SYSTEM_USER: Final[str] = "homeassistant_system"
25
26UNKNOWN_ARTIST: Final[str] = "[unknown]"
27UNKNOWN_ARTIST_ID_MBID: Final[str] = "125ec42a-7229-4250-afc5-e057484327fe"
28VARIOUS_ARTISTS_NAME: Final[str] = "Various Artists"
29VARIOUS_ARTISTS_MBID: Final[str] = "89ad4ac3-39f7-470e-963a-56509c546377"
30
31
32RESOURCES_DIR: Final[pathlib.Path] = (
33 pathlib.Path(__file__).parent.resolve().joinpath("helpers/resources")
34)
35
36ANNOUNCE_ALERT_FILE: Final[str] = str(RESOURCES_DIR.joinpath("announce.mp3"))
37SILENCE_FILE: Final[str] = str(RESOURCES_DIR.joinpath("silence.mp3"))
38SILENCE_FILE_LONG: Final[str] = str(RESOURCES_DIR.joinpath("silence_long.ogg"))
39VARIOUS_ARTISTS_FANART: Final[str] = str(RESOURCES_DIR.joinpath("fallback_fanart.jpeg"))
40MASS_LOGO: Final[str] = str(RESOURCES_DIR.joinpath("logo.png"))
41
42
43# config keys
44CONF_ONBOARD_DONE: Final[str] = "onboard_done"
45CONF_SERVER_ID: Final[str] = "server_id"
46CONF_IP_ADDRESS: Final[str] = "ip_address"
47CONF_PORT: Final[str] = "port"
48CONF_PROVIDERS: Final[str] = "providers"
49CONF_PLAYERS: Final[str] = "players"
50CONF_CORE: Final[str] = "core"
51CONF_PATH: Final[str] = "path"
52CONF_NAME: Final[str] = "name"
53CONF_USERNAME: Final[str] = "username"
54CONF_PASSWORD: Final[str] = "password"
55CONF_VOLUME_NORMALIZATION: Final[str] = "volume_normalization"
56CONF_VOLUME_NORMALIZATION_TARGET: Final[str] = "volume_normalization_target"
57CONF_OUTPUT_LIMITER: Final[str] = "output_limiter"
58CONF_PLAYER_DSP: Final[str] = "player_dsp"
59CONF_PLAYER_DSP_PRESETS: Final[str] = "player_dsp_presets"
60CONF_OUTPUT_CHANNELS: Final[str] = "output_channels"
61CONF_FLOW_MODE: Final[str] = "flow_mode"
62CONF_LOG_LEVEL: Final[str] = "log_level"
63CONF_HIDE_GROUP_CHILDS: Final[str] = "hide_group_childs"
64CONF_CROSSFADE_DURATION: Final[str] = "crossfade_duration"
65CONF_BIND_IP: Final[str] = "bind_ip"
66CONF_BIND_PORT: Final[str] = "bind_port"
67CONF_PUBLISH_IP: Final[str] = "publish_ip"
68CONF_AUTO_PLAY: Final[str] = "auto_play"
69CONF_GROUP_MEMBERS: Final[str] = "group_members"
70CONF_DYNAMIC_GROUP_MEMBERS: Final[str] = "dynamic_members"
71CONF_HIDE_IN_UI: Final[str] = "hide_in_ui"
72CONF_EXPOSE_PLAYER_TO_HA: Final[str] = "expose_player_to_ha"
73CONF_SYNC_ADJUST: Final[str] = "sync_adjust"
74CONF_TTS_PRE_ANNOUNCE: Final[str] = "tts_pre_announce"
75CONF_ANNOUNCE_VOLUME_STRATEGY: Final[str] = "announce_volume_strategy"
76CONF_ANNOUNCE_VOLUME: Final[str] = "announce_volume"
77CONF_ANNOUNCE_VOLUME_MIN: Final[str] = "announce_volume_min"
78CONF_ANNOUNCE_VOLUME_MAX: Final[str] = "announce_volume_max"
79CONF_PRE_ANNOUNCE_CHIME_URL: Final[str] = "pre_announcement_chime_url"
80CONF_ICON: Final[str] = "icon"
81CONF_LANGUAGE: Final[str] = "language"
82CONF_SAMPLE_RATES: Final[str] = "sample_rates"
83CONF_HTTP_PROFILE: Final[str] = "http_profile"
84CONF_BYPASS_NORMALIZATION_RADIO: Final[str] = "bypass_normalization_radio"
85CONF_ENABLE_ICY_METADATA: Final[str] = "enable_icy_metadata"
86CONF_VOLUME_NORMALIZATION_RADIO: Final[str] = "volume_normalization_radio"
87CONF_VOLUME_NORMALIZATION_TRACKS: Final[str] = "volume_normalization_tracks"
88CONF_VOLUME_NORMALIZATION_FIXED_GAIN_RADIO: Final[str] = "volume_normalization_fixed_gain_radio"
89CONF_VOLUME_NORMALIZATION_FIXED_GAIN_TRACKS: Final[str] = "volume_normalization_fixed_gain_tracks"
90CONF_POWER_CONTROL: Final[str] = "power_control"
91CONF_VOLUME_CONTROL: Final[str] = "volume_control"
92CONF_MUTE_CONTROL: Final[str] = "mute_control"
93CONF_OUTPUT_CODEC: Final[str] = "output_codec"
94CONF_ALLOW_AUDIO_CACHE: Final[str] = "allow_audio_cache"
95CONF_SMART_FADES_MODE: Final[str] = "smart_fades_mode"
96CONF_USE_SSL: Final[str] = "use_ssl"
97CONF_VERIFY_SSL: Final[str] = "verify_ssl"
98CONF_SSL_FINGERPRINT: Final[str] = "ssl_fingerprint"
99CONF_AUTH_ALLOW_SELF_REGISTRATION: Final[str] = "auth_allow_self_registration"
100CONF_ZEROCONF_INTERFACES: Final[str] = "zeroconf_interfaces"
101CONF_ENABLED: Final[str] = "enabled"
102
103# config default values
104DEFAULT_HOST: Final[str] = "0.0.0.0"
105DEFAULT_PORT: Final[int] = 8095
106
107
108# common db tables
109DB_TABLE_PLAYLOG: Final[str] = "playlog"
110DB_TABLE_ARTISTS: Final[str] = "artists"
111DB_TABLE_ALBUMS: Final[str] = "albums"
112DB_TABLE_TRACKS: Final[str] = "tracks"
113DB_TABLE_PLAYLISTS: Final[str] = "playlists"
114DB_TABLE_RADIOS: Final[str] = "radios"
115DB_TABLE_AUDIOBOOKS: Final[str] = "audiobooks"
116DB_TABLE_PODCASTS: Final[str] = "podcasts"
117DB_TABLE_CACHE: Final[str] = "cache"
118DB_TABLE_SETTINGS: Final[str] = "settings"
119DB_TABLE_THUMBS: Final[str] = "thumbnails"
120DB_TABLE_PROVIDER_MAPPINGS: Final[str] = "provider_mappings"
121DB_TABLE_ALBUM_TRACKS: Final[str] = "album_tracks"
122DB_TABLE_TRACK_ARTISTS: Final[str] = "track_artists"
123DB_TABLE_ALBUM_ARTISTS: Final[str] = "album_artists"
124DB_TABLE_LOUDNESS_MEASUREMENTS: Final[str] = "loudness_measurements"
125DB_TABLE_SMART_FADES_ANALYSIS: Final[str] = "smart_fades_analysis"
126
127
128# all other
129MASS_LOGO_ONLINE: Final[str] = (
130 "https://github.com/music-assistant/server/blob/dev/music_assistant/logo.png"
131)
132ENCRYPT_SUFFIX = "_encrypted_"
133CONFIGURABLE_CORE_CONTROLLERS = (
134 "streams",
135 "webserver",
136 "players",
137 "metadata",
138 "cache",
139 "music",
140 "player_queues",
141)
142VERBOSE_LOG_LEVEL: Final[int] = 5
143PROVIDERS_WITH_SHAREABLE_URLS = ("spotify", "qobuz")
144SYNCGROUP_PREFIX: Final[str] = "syncgroup_"
145
146####### REUSABLE CONFIG ENTRIES #######
147
148CONF_ENTRY_LOG_LEVEL = ConfigEntry(
149 key=CONF_LOG_LEVEL,
150 type=ConfigEntryType.STRING,
151 label="Log level",
152 options=[
153 ConfigValueOption("global", "GLOBAL"),
154 ConfigValueOption("info", "INFO"),
155 ConfigValueOption("warning", "WARNING"),
156 ConfigValueOption("error", "ERROR"),
157 ConfigValueOption("debug", "DEBUG"),
158 ConfigValueOption("verbose", "VERBOSE"),
159 ],
160 default_value="GLOBAL",
161 category="advanced",
162)
163
164DEFAULT_PROVIDER_CONFIG_ENTRIES = (CONF_ENTRY_LOG_LEVEL,)
165DEFAULT_CORE_CONFIG_ENTRIES = (CONF_ENTRY_LOG_LEVEL,)
166
167# some reusable player config entries
168
169CONF_ENTRY_FLOW_MODE = ConfigEntry(
170 key=CONF_FLOW_MODE,
171 type=ConfigEntryType.BOOLEAN,
172 label="Enforce Gapless playback with Queue Flow Mode streaming",
173 default_value=False,
174 category="advanced",
175)
176
177
178CONF_ENTRY_AUTO_PLAY = ConfigEntry(
179 key=CONF_AUTO_PLAY,
180 type=ConfigEntryType.BOOLEAN,
181 label="Automatically play/resume on power on",
182 default_value=False,
183 description="When this player is turned ON, automatically start playing "
184 "(if there are items in the queue).",
185 depends_on=CONF_POWER_CONTROL,
186 depends_on_value_not="none",
187 category="player_controls",
188)
189
190CONF_ENTRY_OUTPUT_CHANNELS = ConfigEntry(
191 key=CONF_OUTPUT_CHANNELS,
192 type=ConfigEntryType.STRING,
193 options=[
194 ConfigValueOption("Stereo (both channels)", "stereo"),
195 ConfigValueOption("Left channel", "left"),
196 ConfigValueOption("Right channel", "right"),
197 ConfigValueOption("Mono (both channels)", "mono"),
198 ],
199 default_value="stereo",
200 label="Output Channel Mode",
201 category="audio",
202 requires_reload=True,
203)
204
205CONF_ENTRY_VOLUME_NORMALIZATION = ConfigEntry(
206 key=CONF_VOLUME_NORMALIZATION,
207 type=ConfigEntryType.BOOLEAN,
208 label="Enable volume normalization",
209 default_value=True,
210 description="Enable volume normalization (EBU-R128 based)",
211 category="audio",
212 requires_reload=True,
213)
214
215CONF_ENTRY_VOLUME_NORMALIZATION_TARGET = ConfigEntry(
216 key=CONF_VOLUME_NORMALIZATION_TARGET,
217 type=ConfigEntryType.INTEGER,
218 range=(-70, -5),
219 default_value=-17,
220 label="Target level for volume normalization",
221 description="Adjust average (perceived) loudness to this target level",
222 depends_on=CONF_VOLUME_NORMALIZATION,
223 category="advanced",
224 requires_reload=True,
225)
226
227CONF_ENTRY_OUTPUT_LIMITER = ConfigEntry(
228 key=CONF_OUTPUT_LIMITER,
229 type=ConfigEntryType.BOOLEAN,
230 label="Enable limiting to prevent clipping",
231 default_value=True,
232 description="Activates a limiter that prevents audio distortion by making loud peaks quieter.",
233 category="audio",
234 requires_reload=True,
235)
236
237
238CONF_ENTRY_SMART_FADES_MODE = ConfigEntry(
239 key=CONF_SMART_FADES_MODE,
240 type=ConfigEntryType.STRING,
241 label="Enable Smart Fades",
242 options=[
243 ConfigValueOption("Disabled", "disabled"),
244 ConfigValueOption("Smart Crossfade", "smart_crossfade"),
245 ConfigValueOption("Standard Crossfade", "standard_crossfade"),
246 ],
247 default_value="disabled",
248 description="Select the crossfade mode to use when transitioning between tracks.\n\n"
249 "- 'Smart Crossfade': Uses beat matching and EQ filters to create smooth transitions"
250 " between tracks.\n"
251 "- 'Standard Crossfade': Regular crossfade that crossfades the last/first x-seconds of a "
252 "track.",
253 category="audio",
254 requires_reload=True,
255)
256
257CONF_ENTRY_CROSSFADE_DURATION = ConfigEntry(
258 key=CONF_CROSSFADE_DURATION,
259 type=ConfigEntryType.INTEGER,
260 range=(1, 15),
261 default_value=8,
262 label="Fallback crossfade duration",
263 description="Duration in seconds of the standard crossfade between tracks when"
264 " 'Enable Smart Fade' has been set to 'Standard Crossfade' or when a Smart Fade fails",
265 depends_on=CONF_SMART_FADES_MODE,
266 depends_on_value="standard_crossfade",
267 category="audio",
268 requires_reload=True,
269)
270
271
272CONF_ENTRY_OUTPUT_CODEC = ConfigEntry(
273 key=CONF_OUTPUT_CODEC,
274 type=ConfigEntryType.STRING,
275 label="Output codec to use for streaming audio to the player",
276 default_value="flac",
277 options=[
278 ConfigValueOption("FLAC (lossless, compressed)", "flac"),
279 ConfigValueOption("MP3 (lossy)", "mp3"),
280 ConfigValueOption("AAC (lossy)", "aac"),
281 ConfigValueOption("WAV (lossless, uncompressed)", "wav"),
282 ],
283 description="Select the codec to use for streaming audio to this player. \n"
284 "By default, Music Assistant sends lossless, high quality audio to all players and prefers "
285 "the FLAC codec because it offers some compression while still remaining lossless \n\n"
286 "Some players however do not support FLAC and require the stream to be packed "
287 "into e.g. a lossy mp3 codec or you like to save some network bandwidth. \n\n "
288 "Choosing a lossy codec saves some bandwidth at the cost of audio quality.",
289 category="advanced",
290 requires_reload=True,
291)
292
293CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3 = ConfigEntry.from_dict(
294 {**CONF_ENTRY_OUTPUT_CODEC.to_dict(), "default_value": "mp3"}
295)
296CONF_ENTRY_OUTPUT_CODEC_ENFORCE_MP3 = ConfigEntry.from_dict(
297 {**CONF_ENTRY_OUTPUT_CODEC.to_dict(), "default_value": "mp3", "hidden": True}
298)
299CONF_ENTRY_OUTPUT_CODEC_HIDDEN = ConfigEntry.from_dict(
300 {**CONF_ENTRY_OUTPUT_CODEC.to_dict(), "hidden": True}
301)
302CONF_ENTRY_OUTPUT_CODEC_ENFORCE_FLAC = ConfigEntry.from_dict(
303 {**CONF_ENTRY_OUTPUT_CODEC.to_dict(), "default_value": "flac", "hidden": True}
304)
305
306
307def create_output_codec_config_entry(
308 hidden: bool = False, default_value: str = "flac"
309) -> ConfigEntry:
310 """Create output codec config entry based on player specific helpers."""
311 conf_entry = ConfigEntry.from_dict(CONF_ENTRY_OUTPUT_CODEC.to_dict())
312 conf_entry.hidden = hidden
313 conf_entry.default_value = default_value
314 return conf_entry
315
316
317CONF_ENTRY_SYNC_ADJUST = ConfigEntry(
318 key=CONF_SYNC_ADJUST,
319 type=ConfigEntryType.INTEGER,
320 range=(-500, 500),
321 default_value=0,
322 label="Audio synchronization delay correction",
323 description="If this player is playing audio synced with other players "
324 "and you always hear the audio too early or late on this player, "
325 "you can shift the audio a bit.",
326 category="advanced",
327 requires_reload=True,
328)
329
330
331CONF_ENTRY_TTS_PRE_ANNOUNCE = ConfigEntry(
332 key=CONF_TTS_PRE_ANNOUNCE,
333 type=ConfigEntryType.BOOLEAN,
334 default_value=True,
335 label="Pre-announce TTS announcements",
336 category="announcements",
337)
338
339
340CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY = ConfigEntry(
341 key=CONF_ANNOUNCE_VOLUME_STRATEGY,
342 type=ConfigEntryType.STRING,
343 options=[
344 ConfigValueOption("Absolute volume", "absolute"),
345 ConfigValueOption("Relative volume increase", "relative"),
346 ConfigValueOption("Volume increase by fixed percentage", "percentual"),
347 ConfigValueOption("Do not adjust volume", "none"),
348 ],
349 default_value="percentual",
350 label="Volume strategy for Announcements",
351 category="announcements",
352)
353
354CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY_HIDDEN = ConfigEntry.from_dict(
355 {**CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY.to_dict(), "hidden": True}
356)
357
358CONF_ENTRY_ANNOUNCE_VOLUME = ConfigEntry(
359 key=CONF_ANNOUNCE_VOLUME,
360 type=ConfigEntryType.INTEGER,
361 default_value=85,
362 label="Volume for Announcements",
363 category="announcements",
364)
365CONF_ENTRY_ANNOUNCE_VOLUME_HIDDEN = ConfigEntry.from_dict(
366 {**CONF_ENTRY_ANNOUNCE_VOLUME.to_dict(), "hidden": True}
367)
368
369CONF_ENTRY_ANNOUNCE_VOLUME_MIN = ConfigEntry(
370 key=CONF_ANNOUNCE_VOLUME_MIN,
371 type=ConfigEntryType.INTEGER,
372 default_value=15,
373 label="Minimum Volume level for Announcements",
374 description="The volume (adjustment) of announcements should no go below this level.",
375 category="announcements",
376)
377CONF_ENTRY_ANNOUNCE_VOLUME_MIN_HIDDEN = ConfigEntry.from_dict(
378 {**CONF_ENTRY_ANNOUNCE_VOLUME_MIN.to_dict(), "hidden": True}
379)
380
381CONF_ENTRY_ANNOUNCE_VOLUME_MAX = ConfigEntry(
382 key=CONF_ANNOUNCE_VOLUME_MAX,
383 type=ConfigEntryType.INTEGER,
384 default_value=75,
385 label="Maximum Volume level for Announcements",
386 description="The volume (adjustment) of announcements should no go above this level.",
387 category="announcements",
388)
389CONF_ENTRY_ANNOUNCE_VOLUME_MAX_HIDDEN = ConfigEntry.from_dict(
390 {**CONF_ENTRY_ANNOUNCE_VOLUME_MAX.to_dict(), "hidden": True}
391)
392
393
394HIDDEN_ANNOUNCE_VOLUME_CONFIG_ENTRIES = (
395 CONF_ENTRY_ANNOUNCE_VOLUME_HIDDEN,
396 CONF_ENTRY_ANNOUNCE_VOLUME_MIN_HIDDEN,
397 CONF_ENTRY_ANNOUNCE_VOLUME_MAX_HIDDEN,
398 CONF_ENTRY_ANNOUNCE_VOLUME_STRATEGY_HIDDEN,
399)
400
401
402CONF_ENTRY_SAMPLE_RATES = ConfigEntry(
403 key=CONF_SAMPLE_RATES,
404 type=ConfigEntryType.SPLITTED_STRING,
405 multi_value=True,
406 options=[
407 ConfigValueOption("44.1kHz / 16 bits", f"44100{MULTI_VALUE_SPLITTER}16"),
408 ConfigValueOption("44.1kHz / 24 bits", f"44100{MULTI_VALUE_SPLITTER}24"),
409 ConfigValueOption("48kHz / 16 bits", f"48000{MULTI_VALUE_SPLITTER}16"),
410 ConfigValueOption("48kHz / 24 bits", f"48000{MULTI_VALUE_SPLITTER}24"),
411 ConfigValueOption("88.2kHz / 16 bits", f"88200{MULTI_VALUE_SPLITTER}16"),
412 ConfigValueOption("88.2kHz / 24 bits", f"88200{MULTI_VALUE_SPLITTER}24"),
413 ConfigValueOption("96kHz / 16 bits", f"96000{MULTI_VALUE_SPLITTER}16"),
414 ConfigValueOption("96kHz / 24 bits", f"96000{MULTI_VALUE_SPLITTER}24"),
415 ConfigValueOption("176.4kHz / 16 bits", f"176400{MULTI_VALUE_SPLITTER}16"),
416 ConfigValueOption("176.4kHz / 24 bits", f"176400{MULTI_VALUE_SPLITTER}24"),
417 ConfigValueOption("192kHz / 16 bits", f"192000{MULTI_VALUE_SPLITTER}16"),
418 ConfigValueOption("192kHz / 24 bits", f"192000{MULTI_VALUE_SPLITTER}24"),
419 ConfigValueOption("352.8kHz / 16 bits", f"352800{MULTI_VALUE_SPLITTER}16"),
420 ConfigValueOption("352.8kHz / 24 bits", f"352800{MULTI_VALUE_SPLITTER}24"),
421 ConfigValueOption("384kHz / 16 bits", f"384000{MULTI_VALUE_SPLITTER}16"),
422 ConfigValueOption("384kHz / 24 bits", f"384000{MULTI_VALUE_SPLITTER}24"),
423 ],
424 default_value=[f"44100{MULTI_VALUE_SPLITTER}16", f"48000{MULTI_VALUE_SPLITTER}16"],
425 required=True,
426 label="Sample rates supported by this player",
427 category="advanced",
428 description="The sample rates (and bit depths) supported by this player.\n"
429 "Content with unsupported sample rates will be automatically resampled.",
430 requires_reload=True,
431)
432
433
434CONF_ENTRY_HTTP_PROFILE = ConfigEntry(
435 key=CONF_HTTP_PROFILE,
436 type=ConfigEntryType.STRING,
437 options=[
438 ConfigValueOption("Profile 1 - chunked", "chunked"),
439 ConfigValueOption("Profile 2 - no content length", "no_content_length"),
440 ConfigValueOption("Profile 3 - forced content length", "forced_content_length"),
441 ],
442 default_value="no_content_length",
443 label="HTTP Profile used for sending audio",
444 category="advanced",
445 description="This is considered to be a very advanced setting, only adjust this if needed, "
446 "for example if your player stops playing halfway streams or if you experience "
447 "other playback related issues. In most cases the default setting is fine.",
448 requires_reload=True,
449)
450
451CONF_ENTRY_HTTP_PROFILE_DEFAULT_1 = ConfigEntry.from_dict(
452 {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "default_value": "chunked"}
453)
454
455CONF_ENTRY_HTTP_PROFILE_DEFAULT_2 = ConfigEntry.from_dict(
456 {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "default_value": "no_content_length"}
457)
458CONF_ENTRY_HTTP_PROFILE_DEFAULT_3 = ConfigEntry.from_dict(
459 {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "default_value": "forced_content_length"}
460)
461
462CONF_ENTRY_HTTP_PROFILE_FORCED_1 = ConfigEntry.from_dict(
463 {**CONF_ENTRY_HTTP_PROFILE_DEFAULT_1.to_dict(), "hidden": True}
464)
465CONF_ENTRY_HTTP_PROFILE_FORCED_2 = ConfigEntry.from_dict(
466 {
467 **CONF_ENTRY_HTTP_PROFILE.to_dict(),
468 "default_value": "no_content_length",
469 "hidden": True,
470 }
471)
472CONF_ENTRY_HTTP_PROFILE_HIDDEN = ConfigEntry.from_dict(
473 {**CONF_ENTRY_HTTP_PROFILE.to_dict(), "hidden": True}
474)
475
476
477CONF_ENTRY_ENABLE_ICY_METADATA = ConfigEntry(
478 key=CONF_ENABLE_ICY_METADATA,
479 type=ConfigEntryType.STRING,
480 options=[
481 ConfigValueOption("Disabled - do not send ICY metadata", "disabled"),
482 ConfigValueOption("Profile 1 - basic info", "basic"),
483 ConfigValueOption("Profile 2 - full info (including image)", "full"),
484 ],
485 depends_on=CONF_FLOW_MODE,
486 depends_on_value_not=False,
487 default_value="disabled",
488 label="Try to inject metadata into stream (ICY)",
489 category="advanced",
490 description="Try to inject metadata into the stream (ICY) to show track info on the player, "
491 "even when flow mode is enabled.\n\nThis is called ICY metadata and is what is used by "
492 "online radio stations to show you what is playing. \n\nBe aware that not all players support "
493 "this correctly. If you experience issues with playback, try disabling this setting.",
494 requires_reload=True,
495)
496
497CONF_ENTRY_ENABLE_ICY_METADATA_HIDDEN = ConfigEntry.from_dict(
498 {**CONF_ENTRY_ENABLE_ICY_METADATA.to_dict(), "hidden": True}
499)
500
501CONF_ENTRY_ICY_METADATA_HIDDEN_DISABLED = ConfigEntry.from_dict(
502 {
503 **CONF_ENTRY_ENABLE_ICY_METADATA.to_dict(),
504 "default_value": "disabled",
505 "value": "disabled",
506 "hidden": True,
507 }
508)
509
510CONF_ENTRY_ICY_METADATA_DEFAULT_FULL = ConfigEntry.from_dict(
511 {
512 **CONF_ENTRY_ENABLE_ICY_METADATA.to_dict(),
513 "default_value": "full",
514 }
515)
516
517CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES = ConfigEntry(
518 key="gapless_different_sample_rates",
519 type=ConfigEntryType.BOOLEAN,
520 label="Allow gapless playback (and crossfades) between tracks of different sample rates",
521 description="Enable this option to allow gapless playback between tracks that have different "
522 "sample rates (e.g. 44.1kHz to 48kHz). \n\n "
523 "Only enable this option if your player actually support this, otherwise you may "
524 "experience audio glitches during transitioning between tracks.",
525 default_value=False,
526 category="advanced",
527 requires_reload=True,
528)
529
530CONF_ENTRY_WARN_PREVIEW = ConfigEntry(
531 key="preview_note",
532 type=ConfigEntryType.ALERT,
533 label="Please note that this feature/provider is still in early stages. \n\n"
534 "Functionality may still be limited and/or bugs may occur!",
535 required=False,
536)
537
538CONF_ENTRY_MANUAL_DISCOVERY_IPS = ConfigEntry(
539 key="manual_discovery_ip_addresses",
540 type=ConfigEntryType.STRING,
541 label="Manual IP addresses for discovery",
542 description="In normal circumstances, "
543 "Music Assistant will automatically discover all players on the network. "
544 "using multicast discovery on the (L2) local network, such as mDNS or UPNP.\n\n"
545 "In case of special network setups or when you run into issues where "
546 "one or more players are not discovered, you can manually add the IP "
547 "addresses of the players here. \n\n"
548 "Note that this setting is not recommended for normal use and should only be used "
549 "if you know what you are doing. Also, if players are not on the same subnet as"
550 "the Music Assistant server, you may run into issues with streaming. "
551 "In that case always ensure that the players can reach the server on the network "
552 "and double check the base URL configuration of the Stream server in the settings.",
553 category="advanced",
554 default_value=[],
555 required=False,
556 multi_value=True,
557)
558
559CONF_ENTRY_LIBRARY_SYNC_ARTISTS = ConfigEntry(
560 key="library_sync_artists",
561 type=ConfigEntryType.BOOLEAN,
562 label="Sync Library Artists from this provider to Music Assistant",
563 description="Whether to synchronize (favourited/in-library) Artists from this "
564 "provider to the Music Assistant Library.",
565 default_value=True,
566 category="sync_options",
567)
568
569
570CONF_ENTRY_ZEROCONF_INTERFACES = ConfigEntry(
571 key=CONF_ZEROCONF_INTERFACES,
572 type=ConfigEntryType.STRING,
573 label="Mdns/Zeroconf discovery interface(s)",
574 description="In normal circumstances, Music Assistant will automatically "
575 "discover all players on the network using multicast discovery on the "
576 "(L2) local network, such as mDNS or UPNP.\n\n"
577 "By default, Music Assistant will only listen on the default interface. "
578 "If you have multiple network interfaces and you want to discover players "
579 "on all interfaces, you can change this setting to 'All interfaces'.",
580 options=[
581 ConfigValueOption("Default interface", "default"),
582 ConfigValueOption("All interfaces", "all"),
583 ],
584 default_value="default",
585 category="advanced",
586)
587CONF_ENTRY_LIBRARY_SYNC_ALBUMS = ConfigEntry(
588 key="library_sync_albums",
589 type=ConfigEntryType.BOOLEAN,
590 label="Sync Library Albums from this provider to Music Assistant",
591 description="Whether to import (favourited/in-library) Albums from this "
592 "provider to the Music Assistant Library. \n\n"
593 "Please note that by adding an Album into the Music Assistant library, "
594 "the Album Artists will always be imported as well.",
595 default_value=True,
596 category="sync_options",
597)
598CONF_ENTRY_LIBRARY_SYNC_TRACKS = ConfigEntry(
599 key="library_sync_tracks",
600 type=ConfigEntryType.BOOLEAN,
601 label="Sync Library Tracks from this provider to Music Assistant",
602 description="Whether to import (favourited/in-library) Tracks from this "
603 "provider to the Music Assistant Library. \n\n"
604 "Please note that by adding a Track into the Music Assistant library, "
605 "the Track's Artists and Album will always be imported as well.",
606 default_value=True,
607 category="sync_options",
608)
609CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS = ConfigEntry(
610 key="library_sync_playlists",
611 type=ConfigEntryType.BOOLEAN,
612 label="Sync Library Playlists from this provider to Music Assistant",
613 description="Whether to import (favourited/in-library) Playlists from this "
614 "provider to the Music Assistant Library.",
615 default_value=True,
616 category="sync_options",
617)
618CONF_ENTRY_LIBRARY_SYNC_PODCASTS = ConfigEntry(
619 key="library_sync_podcasts",
620 type=ConfigEntryType.BOOLEAN,
621 label="Sync Library Podcasts from this provider to Music Assistant",
622 description="Whether to import (favourited/in-library) Podcasts from this "
623 "provider to the Music Assistant Library.",
624 default_value=True,
625 category="sync_options",
626)
627CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS = ConfigEntry(
628 key="library_sync_audiobooks",
629 type=ConfigEntryType.BOOLEAN,
630 label="Sync Library Audiobooks from this provider to Music Assistant",
631 description="Whether to import (favourited/in-library) Audiobooks from this "
632 "provider to the Music Assistant Library.",
633 default_value=True,
634 category="sync_options",
635)
636CONF_ENTRY_LIBRARY_SYNC_RADIOS = ConfigEntry(
637 key="library_sync_radios",
638 type=ConfigEntryType.BOOLEAN,
639 label="Sync Library Radios from this provider to Music Assistant",
640 description="Whether to import (favourited/in-library) Radio stations from this "
641 "provider to the Music Assistant Library.",
642 default_value=True,
643 category="sync_options",
644)
645CONF_ENTRY_LIBRARY_SYNC_ALBUM_TRACKS = ConfigEntry(
646 key="library_sync_album_tracks",
647 type=ConfigEntryType.BOOLEAN,
648 label="Import album tracks",
649 description="By default, when importing Albums into the library, "
650 "only the Album itself will be imported into the Music Assistant Library, "
651 "allowing you to manually browse and select which tracks you want to import. \n\n"
652 "If you want to override this default behavior, "
653 "you can use this configuration option.\n\n"
654 "Please note that some (streaming) providers may already define this behavior unsolicited, "
655 "by automatically adding all tracks from the album to their library/favorites.",
656 default_value=False,
657 category="sync_options",
658)
659CONF_ENTRY_LIBRARY_SYNC_PLAYLIST_TRACKS = ConfigEntry(
660 key="library_sync_playlist_tracks",
661 type=ConfigEntryType.STRING,
662 label="Import playlist tracks",
663 description="By default, when importing Playlists into the library, "
664 "only the Playlist itself will be imported into the Music Assistant Library, "
665 "allowing you to browse and play the Playlist and optionally add any individual "
666 "tracks of the Playlist to the Music Assistant Library manually. \n\n"
667 "Use this configuration option to override this default behavior, "
668 "by specifying the Playlists for which you'd like to import all tracks.\n"
669 "You can either enter the Playlist name (case sensitive) or the Playlist URI.",
670 default_value=[],
671 category="sync_options",
672 multi_value=True,
673)
674
675CONF_ENTRY_LIBRARY_SYNC_BACK = ConfigEntry(
676 key="library_sync_back",
677 type=ConfigEntryType.BOOLEAN,
678 label="Sync back library additions/removals (2-way sync)",
679 description="Specify the behavior if an item is manually added to "
680 "(or removed from) the Music Assistant Library. \n"
681 "Should we synchronise that action back to the provider?\n\n"
682 "Please note that if you you don't sync back to the provider and you have enabled "
683 "automatic sync/import for this provider, a removed item may reappear in the library "
684 "the next time a sync is performed.",
685 default_value=True,
686 category="sync_options",
687)
688
689
690CONF_PROVIDER_SYNC_INTERVAL_OPTIONS = [
691 ConfigValueOption("Disable automatic sync for this mediatype", 0),
692 ConfigValueOption("Every 30 minutes", 30),
693 ConfigValueOption("Every hour", 60),
694 ConfigValueOption("Every 3 hours", 180),
695 ConfigValueOption("Every 6 hours", 360),
696 ConfigValueOption("Every 12 hours", 720),
697 ConfigValueOption("Every 24 hours", 1440),
698 ConfigValueOption("Every 36 hours", 2160),
699 ConfigValueOption("Every 48 hours", 2880),
700 ConfigValueOption("Once a week", 10080),
701]
702CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ARTISTS = ConfigEntry(
703 key="provider_sync_interval_artists",
704 type=ConfigEntryType.INTEGER,
705 label="Automatic Sync Interval for Artists",
706 description="The interval at which the Artists are synced to the library for this provider.",
707 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
708 default_value=720,
709 category="sync_options",
710 depends_on=CONF_ENTRY_LIBRARY_SYNC_ARTISTS.key,
711 depends_on_value=True,
712 required=True,
713)
714CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ALBUMS = ConfigEntry(
715 key="provider_sync_interval_albums",
716 type=ConfigEntryType.INTEGER,
717 label="Automatic Sync Interval for Albums",
718 description="The interval at which the Albums are synced to the library for this provider.",
719 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
720 default_value=720,
721 category="sync_options",
722 depends_on=CONF_ENTRY_LIBRARY_SYNC_ALBUMS.key,
723 depends_on_value=True,
724 required=True,
725)
726CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS = ConfigEntry(
727 key="provider_sync_interval_tracks",
728 type=ConfigEntryType.INTEGER,
729 label="Automatic Sync Interval for Tracks",
730 description="The interval at which the Tracks are synced to the library for this provider.",
731 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
732 default_value=720,
733 category="sync_options",
734 depends_on=CONF_ENTRY_LIBRARY_SYNC_TRACKS.key,
735 depends_on_value=True,
736 required=True,
737)
738CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS = ConfigEntry(
739 key="provider_sync_interval_playlists",
740 type=ConfigEntryType.INTEGER,
741 label="Automatic Sync Interval for Playlists",
742 description="The interval at which the Playlists are synced to the library for this provider.",
743 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
744 default_value=720,
745 category="sync_options",
746 depends_on=CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS.key,
747 depends_on_value=True,
748 required=True,
749)
750CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PODCASTS = ConfigEntry(
751 key="provider_sync_interval_podcasts",
752 type=ConfigEntryType.INTEGER,
753 label="Automatic Sync Interval for Podcasts",
754 description="The interval at which the Podcasts are synced to the library for this provider.",
755 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
756 default_value=720,
757 category="sync_options",
758 depends_on=CONF_ENTRY_LIBRARY_SYNC_PODCASTS.key,
759 depends_on_value=True,
760 required=True,
761)
762CONF_ENTRY_PROVIDER_SYNC_INTERVAL_AUDIOBOOKS = ConfigEntry(
763 key="provider_sync_interval_audiobooks",
764 type=ConfigEntryType.INTEGER,
765 label="Automatic Sync Interval for Audiobooks",
766 description="The interval at which the Audiobooks are synced to the library for this provider.",
767 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
768 default_value=720,
769 category="sync_options",
770 depends_on=CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS.key,
771 depends_on_value=True,
772 required=True,
773)
774CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS = ConfigEntry(
775 key="provider_sync_interval_radios",
776 type=ConfigEntryType.INTEGER,
777 label="Automatic Sync Interval for Radios",
778 description="The interval at which the Radios are synced to the library for this provider.",
779 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
780 default_value=720,
781 category="sync_options",
782 depends_on=CONF_ENTRY_LIBRARY_SYNC_RADIOS.key,
783 depends_on_value=True,
784 required=True,
785)
786
787
788CONF_ENTRY_PLAYER_ICON = ConfigEntry(
789 key=CONF_ICON,
790 type=ConfigEntryType.ICON,
791 default_value="mdi-speaker",
792 label="Icon",
793 description="Material design icon for this player. "
794 "\n\nSee https://pictogrammers.com/library/mdi/",
795 category="generic",
796)
797
798CONF_ENTRY_PLAYER_ICON_GROUP = ConfigEntry.from_dict(
799 {**CONF_ENTRY_PLAYER_ICON.to_dict(), "default_value": "mdi-speaker-multiple"}
800)
801
802
803def create_sample_rates_config_entry(
804 supported_sample_rates: list[int] | None = None,
805 supported_bit_depths: list[int] | None = None,
806 hidden: bool = False,
807 max_sample_rate: int | None = None,
808 max_bit_depth: int | None = None,
809 safe_max_sample_rate: int = 48000,
810 safe_max_bit_depth: int = 16,
811) -> ConfigEntry:
812 """Create sample rates config entry based on player specific helpers."""
813 assert CONF_ENTRY_SAMPLE_RATES.options
814 # if no supported sample rates are defined, we apply the default 44100 as only option
815 if not supported_sample_rates and max_sample_rate is None:
816 supported_sample_rates = [44100]
817 if not supported_bit_depths and max_bit_depth is None:
818 supported_bit_depths = [16]
819 final_supported_sample_rates = supported_sample_rates or []
820 final_supported_bit_depths = supported_bit_depths or []
821 conf_entry = ConfigEntry.from_dict(CONF_ENTRY_SAMPLE_RATES.to_dict())
822 conf_entry.hidden = hidden
823 options: list[ConfigValueOption] = []
824 default_value: list[str] = []
825
826 for option in CONF_ENTRY_SAMPLE_RATES.options:
827 option_value = cast("str", option.value)
828 sample_rate_str, bit_depth_str = option_value.split(MULTI_VALUE_SPLITTER, 1)
829 sample_rate = int(sample_rate_str)
830 bit_depth = int(bit_depth_str)
831 # if no supported sample rates are defined, we accept all within max_sample_rate
832 if not supported_sample_rates and max_sample_rate and sample_rate <= max_sample_rate:
833 final_supported_sample_rates.append(sample_rate)
834 if not supported_bit_depths and max_bit_depth and bit_depth <= max_bit_depth:
835 final_supported_bit_depths.append(bit_depth)
836
837 if sample_rate not in final_supported_sample_rates:
838 continue
839 if bit_depth not in final_supported_bit_depths:
840 continue
841 options.append(option)
842 if sample_rate <= safe_max_sample_rate and bit_depth <= safe_max_bit_depth:
843 default_value.append(option_value)
844 conf_entry.options = options
845 conf_entry.default_value = default_value
846 return conf_entry
847
848
849DEFAULT_STREAM_HEADERS = {
850 "Server": APPLICATION_NAME,
851 "transferMode.dlna.org": "Streaming",
852 "contentFeatures.dlna.org": "DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000",
853 "Cache-Control": "no-cache",
854 "Pragma": "no-cache",
855 "icy-name": APPLICATION_NAME,
856}
857ICY_HEADERS = {
858 "icy-name": APPLICATION_NAME,
859 "icy-description": f"{APPLICATION_NAME} - Your personal music assistant",
860 "icy-version": "1",
861 "icy-logo": MASS_LOGO_ONLINE,
862}
863
864INTERNAL_PCM_FORMAT = AudioFormat(
865 # always prefer float32 as internal pcm format to create headroom
866 # for filters such as dsp and volume normalization
867 content_type=ContentType.PCM_F32LE,
868 bit_depth=32, # related to float32
869 sample_rate=48000, # static for flow stream, dynamic for anything else
870 channels=2, # static for flow stream, dynamic for anything else
871)
872
873# extra data / extra attributes keys
874ATTR_FAKE_POWER: Final[str] = "fake_power"
875ATTR_FAKE_VOLUME: Final[str] = "fake_volume_level"
876ATTR_FAKE_MUTE: Final[str] = "fake_volume_muted"
877ATTR_ANNOUNCEMENT_IN_PROGRESS: Final[str] = "announcement_in_progress"
878ATTR_PREVIOUS_VOLUME: Final[str] = "previous_volume"
879ATTR_LAST_POLL: Final[str] = "last_poll"
880ATTR_GROUP_MEMBERS: Final[str] = "group_members"
881ATTR_ELAPSED_TIME: Final[str] = "elapsed_time"
882ATTR_ENABLED: Final[str] = "enabled"
883ATTR_AVAILABLE: Final[str] = "available"
884
885# Album type detection patterns
886LIVE_INDICATORS = [
887 r"\bunplugged\b",
888 r"\bin concert\b",
889 r"\bon stage\b",
890 r"\blive\b",
891]
892
893SOUNDTRACK_INDICATORS = [
894 r"\bsoundtrack\b", # Catches all soundtrack variations
895 r"\bmusic from the .* motion picture\b",
896 r"\boriginal score\b",
897 r"\bthe score\b",
898 r"\bfilm score\b",
899 r"(^|\b)score:\s*", # e.g., "Score: The Two Towers"
900 r"\bfrom the film\b",
901 r"\boriginal.*cast.*recording\b",
902]
903
904# List of providers that do not use HTTP streaming
905# but consume raw audio data over other protocols
906# for provider domains in this list, we won't show the default
907# http-streaming specific config options in player settings
908NON_HTTP_PROVIDERS = ("airplay", "sendspin", "snapcast")
909