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