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