/
/
/
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
726CONF_ENTRY_LIBRARY_SYNC_DELETIONS = ConfigEntry(
727 key="library_sync_deletions",
728 type=ConfigEntryType.BOOLEAN,
729 label="Sync library deletions",
730 description="When enabled, items removed from the provider's library will also be "
731 "hidden from the Music Assistant library.\n\n"
732 "When disabled, items removed from the provider will remain visible in the "
733 "Music Assistant library.",
734 default_value=True,
735 category="sync_options",
736 advanced=True,
737)
738
739
740CONF_PROVIDER_SYNC_INTERVAL_OPTIONS = [
741 ConfigValueOption("Disable automatic sync for this mediatype", 0),
742 ConfigValueOption("Every 30 minutes", 30),
743 ConfigValueOption("Every hour", 60),
744 ConfigValueOption("Every 3 hours", 180),
745 ConfigValueOption("Every 6 hours", 360),
746 ConfigValueOption("Every 12 hours", 720),
747 ConfigValueOption("Every 24 hours", 1440),
748 ConfigValueOption("Every 36 hours", 2160),
749 ConfigValueOption("Every 48 hours", 2880),
750 ConfigValueOption("Once a week", 10080),
751]
752CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ARTISTS = ConfigEntry(
753 key="provider_sync_interval_artists",
754 type=ConfigEntryType.INTEGER,
755 label="Automatic Sync Interval for Artists",
756 description="The interval at which the Artists are synced to the library for this provider.",
757 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
758 default_value=720,
759 category="sync_options",
760 depends_on=CONF_ENTRY_LIBRARY_SYNC_ARTISTS.key,
761 depends_on_value=True,
762 required=True,
763)
764CONF_ENTRY_PROVIDER_SYNC_INTERVAL_ALBUMS = ConfigEntry(
765 key="provider_sync_interval_albums",
766 type=ConfigEntryType.INTEGER,
767 label="Automatic Sync Interval for Albums",
768 description="The interval at which the Albums are synced to the library for this provider.",
769 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
770 default_value=720,
771 category="sync_options",
772 depends_on=CONF_ENTRY_LIBRARY_SYNC_ALBUMS.key,
773 depends_on_value=True,
774 required=True,
775)
776CONF_ENTRY_PROVIDER_SYNC_INTERVAL_TRACKS = ConfigEntry(
777 key="provider_sync_interval_tracks",
778 type=ConfigEntryType.INTEGER,
779 label="Automatic Sync Interval for Tracks",
780 description="The interval at which the Tracks are synced to the library for this provider.",
781 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
782 default_value=720,
783 category="sync_options",
784 depends_on=CONF_ENTRY_LIBRARY_SYNC_TRACKS.key,
785 depends_on_value=True,
786 required=True,
787)
788CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PLAYLISTS = ConfigEntry(
789 key="provider_sync_interval_playlists",
790 type=ConfigEntryType.INTEGER,
791 label="Automatic Sync Interval for Playlists",
792 description="The interval at which the Playlists are synced to the library for this provider.",
793 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
794 default_value=720,
795 category="sync_options",
796 depends_on=CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS.key,
797 depends_on_value=True,
798 required=True,
799)
800CONF_ENTRY_PROVIDER_SYNC_INTERVAL_PODCASTS = ConfigEntry(
801 key="provider_sync_interval_podcasts",
802 type=ConfigEntryType.INTEGER,
803 label="Automatic Sync Interval for Podcasts",
804 description="The interval at which the Podcasts are synced to the library for this provider.",
805 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
806 default_value=720,
807 category="sync_options",
808 depends_on=CONF_ENTRY_LIBRARY_SYNC_PODCASTS.key,
809 depends_on_value=True,
810 required=True,
811)
812CONF_ENTRY_PROVIDER_SYNC_INTERVAL_AUDIOBOOKS = ConfigEntry(
813 key="provider_sync_interval_audiobooks",
814 type=ConfigEntryType.INTEGER,
815 label="Automatic Sync Interval for Audiobooks",
816 description="The interval at which the Audiobooks are synced to the library for this provider.",
817 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
818 default_value=720,
819 category="sync_options",
820 depends_on=CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS.key,
821 depends_on_value=True,
822 required=True,
823)
824CONF_ENTRY_PROVIDER_SYNC_INTERVAL_RADIOS = ConfigEntry(
825 key="provider_sync_interval_radios",
826 type=ConfigEntryType.INTEGER,
827 label="Automatic Sync Interval for Radios",
828 description="The interval at which the Radios are synced to the library for this provider.",
829 options=CONF_PROVIDER_SYNC_INTERVAL_OPTIONS,
830 default_value=720,
831 category="sync_options",
832 depends_on=CONF_ENTRY_LIBRARY_SYNC_RADIOS.key,
833 depends_on_value=True,
834 required=True,
835)
836
837
838CONF_ENTRY_PLAYER_ICON = ConfigEntry(
839 key=CONF_ICON,
840 type=ConfigEntryType.ICON,
841 default_value="mdi-speaker",
842 label="Icon",
843 description="Material design icon for this player. "
844 "\n\nSee https://pictogrammers.com/library/mdi/",
845 category="generic",
846)
847
848CONF_ENTRY_PLAYER_ICON_GROUP = ConfigEntry.from_dict(
849 {**CONF_ENTRY_PLAYER_ICON.to_dict(), "default_value": "mdi-speaker-multiple"}
850)
851
852
853def create_sample_rates_config_entry(
854 supported_sample_rates: list[int] | None = None,
855 supported_bit_depths: list[int] | None = None,
856 hidden: bool = False,
857 max_sample_rate: int | None = None,
858 max_bit_depth: int | None = None,
859 safe_max_sample_rate: int = 48000,
860 safe_max_bit_depth: int = 16,
861) -> ConfigEntry:
862 """Create sample rates config entry based on player specific helpers."""
863 assert CONF_ENTRY_SAMPLE_RATES.options
864 # if no supported sample rates are defined, we apply the default 44100 as only option
865 if not supported_sample_rates and max_sample_rate is None:
866 supported_sample_rates = [44100]
867 if not supported_bit_depths and max_bit_depth is None:
868 supported_bit_depths = [16]
869 final_supported_sample_rates = supported_sample_rates or []
870 final_supported_bit_depths = supported_bit_depths or []
871 conf_entry = deepcopy(CONF_ENTRY_SAMPLE_RATES)
872 conf_entry.hidden = hidden
873 options: list[ConfigValueOption] = []
874 default_value: list[str] = []
875
876 for option in CONF_ENTRY_SAMPLE_RATES.options:
877 option_value = cast("str", option.value)
878 sample_rate_str, bit_depth_str = option_value.split(MULTI_VALUE_SPLITTER, 1)
879 sample_rate = int(sample_rate_str)
880 bit_depth = int(bit_depth_str)
881 # if no supported sample rates are defined, we accept all within max_sample_rate
882 if not supported_sample_rates and max_sample_rate and sample_rate <= max_sample_rate:
883 final_supported_sample_rates.append(sample_rate)
884 if not supported_bit_depths and max_bit_depth and bit_depth <= max_bit_depth:
885 final_supported_bit_depths.append(bit_depth)
886
887 if sample_rate not in final_supported_sample_rates:
888 continue
889 if bit_depth not in final_supported_bit_depths:
890 continue
891 options.append(option)
892 if sample_rate <= safe_max_sample_rate and bit_depth <= safe_max_bit_depth:
893 default_value.append(option_value)
894 conf_entry.options = options
895 conf_entry.default_value = default_value
896 return conf_entry
897
898
899DEFAULT_STREAM_HEADERS = {
900 "Server": APPLICATION_NAME,
901 "transferMode.dlna.org": "Streaming",
902 "contentFeatures.dlna.org": "DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000",
903 "Cache-Control": "no-cache",
904 "Pragma": "no-cache",
905 "icy-name": APPLICATION_NAME,
906}
907ICY_HEADERS = {
908 "icy-name": APPLICATION_NAME,
909 "icy-description": f"{APPLICATION_NAME} - Your personal music assistant",
910 "icy-version": "1",
911 "icy-logo": MASS_LOGO_ONLINE,
912}
913
914INTERNAL_PCM_FORMAT = AudioFormat(
915 # always prefer float32 as internal pcm format to create headroom
916 # for filters such as dsp and volume normalization
917 content_type=ContentType.PCM_F32LE,
918 bit_depth=32, # related to float32
919 sample_rate=48000, # static for flow stream, dynamic for anything else
920 channels=2, # static for flow stream, dynamic for anything else
921)
922
923# extra data / extra attributes keys
924ATTR_FAKE_POWER: Final[str] = "fake_power"
925ATTR_FAKE_VOLUME: Final[str] = "fake_volume_level"
926ATTR_FAKE_MUTE: Final[str] = "fake_volume_muted"
927ATTR_ANNOUNCEMENT_IN_PROGRESS: Final[str] = "announcement_in_progress"
928ATTR_PREVIOUS_VOLUME: Final[str] = "previous_volume"
929ATTR_LAST_POLL: Final[str] = "last_poll"
930ATTR_GROUP_MEMBERS: Final[str] = "group_members"
931ATTR_ELAPSED_TIME: Final[str] = "elapsed_time"
932ATTR_ENABLED: Final[str] = "enabled"
933ATTR_AVAILABLE: Final[str] = "available"
934ATTR_MUTE_LOCK: Final[str] = "mute_lock"
935ATTR_ACTIVE_SOURCE: Final[str] = "active_source"
936
937# Album type detection patterns
938LIVE_INDICATORS = [
939 r"\bunplugged\b",
940 r"\bin concert\b",
941 r"\bon stage\b",
942 r"\blive\b",
943]
944
945SOUNDTRACK_INDICATORS = [
946 r"\bsoundtrack\b", # Catches all soundtrack variations
947 r"\bmusic from the .* motion picture\b",
948 r"\boriginal score\b",
949 r"\bthe score\b",
950 r"\bfilm score\b",
951 r"(^|\b)score:\s*", # e.g., "Score: The Two Towers"
952 r"\bfrom the film\b",
953 r"\boriginal.*cast.*recording\b",
954]
955
956# how often we report the playback progress in the player_queues controller
957PLAYBACK_REPORT_INTERVAL_SECONDS = 30
958
959# List of providers that do not use HTTP streaming
960# but consume raw audio data over other protocols
961# for provider domains in this list, we won't show the default
962# http-streaming specific config options in player settings
963NON_HTTP_PROVIDERS = ("airplay", "sendspin", "snapcast")
964
965# Protocol priority values (lower = more preferred)
966PROTOCOL_PRIORITY: Final[dict[str, int]] = {
967 "sendspin": 10,
968 "squeezelite": 20,
969 "chromecast": 30,
970 "airplay": 40,
971 "dlna": 50,
972}
973
974PROTOCOL_FEATURES: Final[set[PlayerFeature]] = {
975 # Player features that may be copied from (inactive) protocol implementations
976 PlayerFeature.VOLUME_SET,
977 PlayerFeature.VOLUME_MUTE,
978 PlayerFeature.PLAY_ANNOUNCEMENT,
979 PlayerFeature.SET_MEMBERS,
980}
981
982ACTIVE_PROTOCOL_FEATURES: Final[set[PlayerFeature]] = {
983 # Player features that may be copied from the active output protocol
984 *PROTOCOL_FEATURES,
985 PlayerFeature.ENQUEUE,
986 PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE,
987 PlayerFeature.GAPLESS_PLAYBACK,
988 PlayerFeature.MULTI_DEVICE_DSP,
989 PlayerFeature.PAUSE,
990}
991
992DEFAULT_PROVIDERS: Final[set[tuple[str, bool]]] = {
993 # list of providers that are setup by default once
994 # (and they can be removed/disabled by the user if they want to)
995 # the boolean value indicates whether it needs to be discovered on mdns
996 ("airplay", False),
997 ("chromecast", False),
998 ("dlna", False),
999 ("sonos", True),
1000 ("bluesound", True),
1001 ("heos", True),
1002}
1003