/
/
/
1"""Constants for the Filesystem Local provider."""
2
3from __future__ import annotations
4
5from dataclasses import replace
6from typing import Final
7
8from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption
9from music_assistant_models.enums import ConfigEntryType, ImageType
10
11CONF_MISSING_ALBUM_ARTIST_ACTION = "missing_album_artist_action"
12CONF_CONTENT_TYPE = "content_type"
13
14CONF_ENTRY_MISSING_ALBUM_ARTIST = ConfigEntry(
15 key=CONF_MISSING_ALBUM_ARTIST_ACTION,
16 type=ConfigEntryType.STRING,
17 default_value="various_artists",
18 help_link="https://music-assistant.io/music-providers/local-files/#tagging-files",
19 required=False,
20 options=[
21 ConfigValueOption("track_artist"),
22 ConfigValueOption("various_artists"),
23 ConfigValueOption("folder_name"),
24 ],
25 depends_on=CONF_CONTENT_TYPE,
26 depends_on_value="music",
27)
28
29
30CONF_ENTRY_PATH = ConfigEntry(
31 key="path",
32 type=ConfigEntryType.STRING,
33 default_value="/media",
34)
35
36CONF_ENTRY_CONTENT_TYPE = ConfigEntry(
37 key=CONF_CONTENT_TYPE,
38 type=ConfigEntryType.STRING,
39 default_value="music",
40 required=False,
41 options=[
42 ConfigValueOption("music"),
43 ConfigValueOption("audiobooks"),
44 ConfigValueOption("podcasts"),
45 ConfigValueOption("sound_effects"),
46 ],
47)
48
49
50def content_type_config_entry(content_type: str) -> ConfigEntry:
51 """
52 Return the read-only mirror of the (setup flow owned) content type for the options page.
53
54 :param content_type: The content type resolved from the provider's setup data.
55 """
56 # mirrored as the entry default so the other entries resolve their depends_on chain
57 # against it without it ever being persisted back into the stored values
58 return replace(CONF_ENTRY_CONTENT_TYPE, read_only=True, default_value=content_type)
59
60
61CONF_ENTRY_LIBRARY_SYNC_TRACKS = ConfigEntry(
62 key="library_sync_tracks",
63 type=ConfigEntryType.BOOLEAN,
64 default_value=True,
65 category="sync_options",
66 depends_on=CONF_CONTENT_TYPE,
67 depends_on_value="music",
68)
69CONF_ENTRY_LIBRARY_SYNC_PLAYLISTS = ConfigEntry(
70 key="library_sync_playlists",
71 type=ConfigEntryType.BOOLEAN,
72 default_value=True,
73 category="sync_options",
74 depends_on=CONF_CONTENT_TYPE,
75 depends_on_value="music",
76)
77CONF_ENTRY_LIBRARY_SYNC_PODCASTS = ConfigEntry(
78 key="library_sync_podcasts",
79 type=ConfigEntryType.BOOLEAN,
80 default_value=True,
81 category="sync_options",
82 depends_on=CONF_CONTENT_TYPE,
83 depends_on_value="podcasts",
84)
85CONF_ENTRY_LIBRARY_SYNC_AUDIOBOOKS = ConfigEntry(
86 key="library_sync_audiobooks",
87 type=ConfigEntryType.BOOLEAN,
88 default_value=True,
89 category="sync_options",
90 depends_on=CONF_CONTENT_TYPE,
91 depends_on_value="audiobooks",
92)
93
94CONF_ENTRY_PROPAGATE_GENRES = ConfigEntry(
95 key="propagate_track_genres",
96 type=ConfigEntryType.BOOLEAN,
97 default_value=False,
98 required=False,
99 category="sync_options",
100 depends_on=CONF_CONTENT_TYPE,
101 depends_on_value="music",
102)
103
104CONF_ENTRY_IGNORE_ALBUM_PLAYLISTS = ConfigEntry(
105 key="ignore_album_playlists",
106 type=ConfigEntryType.BOOLEAN,
107 default_value=True,
108 required=False,
109 depends_on=CONF_CONTENT_TYPE,
110 depends_on_value="music",
111)
112
113TRACK_EXTENSIONS = {
114 "aac",
115 "mp3",
116 "m4a",
117 "mp4",
118 "flac",
119 "wav",
120 "ogg",
121 "aiff",
122 "wma",
123 "dsf",
124 "opus",
125 "wv",
126 "amr",
127 "awb",
128 "spx",
129 "tak",
130 "ape",
131 "mpc",
132 "mp2",
133 "m2a",
134 "mp1",
135 "dra",
136 "mpeg",
137 "mpg",
138 "ac3",
139 "ec3",
140 "aif",
141 "oga",
142 "dff",
143 "ts",
144 "m2ts",
145 "mp+",
146}
147PLAYLIST_EXTENSIONS = {"m3u", "pls", "m3u8"}
148CUE_EXTENSIONS = {"cue"}
149IMAGE_EXTENSIONS = {"jpg", "jpeg", "png", "gif"}
150AUDIOBOOK_EXTENSIONS = {"aa", "aax", "m4b", "m4a", "mp3", "mp4", "flac", "ogg", "opus"}
151PODCAST_EPISODE_EXTENSIONS = {"aa", "aax", "m4b", "m4a", "mp3", "mp4", "flac", "ogg", "opus"}
152SOUND_EFFECT_EXTENSIONS = TRACK_EXTENSIONS
153SUPPORTED_EXTENSIONS = {
154 *TRACK_EXTENSIONS,
155 *AUDIOBOOK_EXTENSIONS,
156 *PODCAST_EPISODE_EXTENSIONS,
157 *PLAYLIST_EXTENSIONS,
158 *CUE_EXTENSIONS,
159}
160
161# local metadata files (Kodi-style NFO and recognized folder images) are never imported as
162# media: they carry no provider mapping of their own and only feed the lightweight change
163# detection that reparses their representative track when one of them changes on disk
164NFO_FILENAMES = {"album.nfo", "artist.nfo"}
165METADATA_IMAGE_STEMS = {image_type.value for image_type in ImageType} | {
166 "folder",
167 "cover",
168 "album",
169 "artist",
170}
171METADATA_FILE_EXTENSIONS = {"nfo", *IMAGE_EXTENSIONS}
172# the walk collects both imported media and local metadata files in a single pass
173WALK_EXTENSIONS = SUPPORTED_EXTENSIONS | METADATA_FILE_EXTENSIONS
174
175
176class IsChapterFile(Exception):
177 """Exception to indicate that a file is part of a multi-part media (e.g. audiobook chapter)."""
178
179
180CACHE_CATEGORY_ARTIST_INFO: Final[int] = 1
181CACHE_CATEGORY_ALBUM_INFO: Final[int] = 2
182CACHE_CATEGORY_FOLDER_IMAGES: Final[int] = 3
183CACHE_CATEGORY_AUDIOBOOK_CHAPTERS: Final[int] = 4
184CACHE_CATEGORY_PODCAST_METADATA: Final[int] = 5
185CACHE_CATEGORY_CUE_SHEETS: Final[int] = 6
186CACHE_CATEGORY_SOUND_EFFECTS: Final[int] = 7
187CACHE_CATEGORY_PODCAST_EPISODES: Final[int] = 8
188# tracks the current change token + representative track of a local metadata file (NFO or
189# folder image); derivative and non-authoritative, so a cache miss is simply ignored
190CACHE_CATEGORY_METADATA_FILE: Final[int] = 9
191
192# a registration is only ever refreshed by actually reading the file again, never on a timer,
193# so it must not expire under normal operation: an infrequently-touched item (an unchanged NFO
194# for months) would otherwise silently fall back to "untracked" once the entry expired
195METADATA_FILE_CACHE_EXPIRATION: Final[int] = 86400 * 365 * 10 # ~permanent for the provider's life
196
197# how long a podcast episode listing that lost a file to a parse failure is cached for:
198# the missing episode cannot reappear any sooner than this
199PARTIAL_LISTING_CACHE_EXPIRATION: Final[int] = 300
200
201DEFAULT_AUDIOBOOK_PODCAST_GENRE: Final[str] = "Spoken Word"
202
203# how often storage that went away during a scan is re-checked, so the provider comes
204# back within minutes instead of waiting for the next scheduled sync
205AVAILABILITY_PROBE_INTERVAL: Final[int] = 300
206