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