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