)
if cached_libraries is None:
self.libraries = LibrariesHelper()
+ # We need the library ids for recommendations. If the cache got cleared e.g. by a db
+ # migration, we might end up with empty library helpers on a configured provider. Note,
+ # that the lib item ids are not synced, still only on full provider sync, instead the
+ # sets are empty. Full sync is expensive.
+ # See warning in browse_lib_podcasts / _browse_books
+ libraries = await self._client.get_all_libraries()
+ for library in libraries:
+ if library.media_type == AbsLibraryMediaType.BOOK:
+ self.libraries.audiobooks[library.id_] = LibraryHelper(name=library.name)
+ elif library.media_type == AbsLibraryMediaType.PODCAST:
+ self.libraries.podcasts[library.id_] = LibraryHelper(name=library.name)
else:
self.libraries = LibrariesHelper.from_dict(cached_libraries)
async def sync_library(self, media_type: MediaType) -> None:
"""Obtain audiobook library ids and podcast library ids."""
libraries = await self._client.get_all_libraries()
+ if len(libraries) == 0:
+ self._log_no_libraries()
for library in libraries:
if library.media_type == AbsLibraryMediaType.BOOK and media_type == MediaType.AUDIOBOOK:
self.libraries.audiobooks[library.id_] = LibraryHelper(name=library.name)
all_libraries = {**self.libraries.audiobooks, **self.libraries.podcasts}
max_items_per_row = 20
- limit_items_per_lib = max_items_per_row // len(all_libraries)
+ num_libraries = len(all_libraries)
+
+ if num_libraries == 0:
+ self._log_no_libraries()
+ return []
+
+ limit_items_per_lib = max_items_per_row // num_libraries
limit_items_per_lib = 1 if limit_items_per_lib == 0 else limit_items_per_lib
for library_id in all_libraries:
path=f"{self.instance_id}://{path}",
)
+ if len(self.libraries.audiobooks) == 0 and len(self.libraries.podcasts) == 0:
+ self._log_no_libraries()
+ return []
+
for lib_id, lib in self.libraries.audiobooks.items():
path = f"{AbsBrowsePaths.LIBRARIES_BOOK} {lib_id}"
if append_mediatype_suffix:
async def _browse_lib_podcasts(self, library_id: str) -> list[MediaItemTypeOrItemMapping]:
"""No sub categories for podcasts."""
+ if len(self.libraries.podcasts[library_id].item_ids) == 0:
+ self._log_no_helper_item_ids()
items = []
for podcast_id in self.libraries.podcasts[library_id].item_ids:
mass_item = await self.mass.music.get_library_item_by_prov_id(
return sorted(items, key=lambda x: x.name)
async def _browse_books(self, library_id: str) -> Sequence[MediaItemTypeOrItemMapping]:
+ if len(self.libraries.audiobooks[library_id].item_ids) == 0:
+ self._log_no_helper_item_ids()
items = []
for book_id in self.libraries.audiobooks[library_id].item_ids:
mass_item = await self.mass.music.get_library_item_by_prov_id(
category=CACHE_CATEGORY_LIBRARIES,
data=self.libraries.to_dict(),
)
+
+ def _log_no_libraries(self) -> None:
+ self.logger.error("There are no libraries visible to the Audiobookshelf provider.")
+
+ def _log_no_helper_item_ids(self) -> None:
+ self.logger.warning(
+ "Cached item ids are missing. "
+ "Please trigger a full resync of the Audiobookshelf provider manually."
+ )
for cnt, episode in enumerate(episodes):
episode_enclosures = episode.get("enclosures", [])
if len(episode_enclosures) < 1:
- raise RuntimeError
+ raise MediaNotFoundError
stream_url = episode_enclosures[0].get("url", None)
if guid_or_stream_url == episode.get("guid", stream_url):
return parse_podcast_episode(
prov_podcast_id, headers={"User-Agent": "Mozilla/5.0"}
)
if response.status != 200:
- raise RuntimeError
+ raise MediaNotFoundError
feed_data = await response.read()
feed_stream = BytesIO(feed_data)
parsed_podcast = podcastparser.parse(
if top_podcasts_response.feed is None:
return []
+ include_explicit = bool(self.config.get_value(CONF_EXPLICIT))
+
helper = TopPodcastsHelper()
for top_podcast in top_podcasts_response.feed.results:
- podcast_search_result = await self._get_podcast_search_result_from_itunes_id(
- int(top_podcast.id_)
- )
+ if not include_explicit and top_podcast.content_advisory_rating is not None:
+ # the spelling within the API is wrong.
+ if top_podcast.content_advisory_rating in [
+ "explicit",
+ "Explicit",
+ "Explict",
+ "explict",
+ ]:
+ continue
+ try:
+ podcast_search_result = await self._get_podcast_search_result_from_itunes_id(
+ int(top_podcast.id_)
+ )
+ except MediaNotFoundError:
+ continue
helper.top_podcasts.append(podcast_search_result)
await self._cache_set_top_podcasts(top_podcast_helper=helper)
"""TopPodcastsResult."""
artist_name: str = field(metadata=field_options(alias="artistName"), default="")
- id_: str | int = field(metadata=field_options(alias="id"))
- name: str
+ id_: str | int = field(metadata=field_options(alias="id"), default="")
+ name: str = ""
genres: list[TopPodcastsGenres] = field(default_factory=list)
artwork_url_30: str | None = field(metadata=field_options(alias="artworkUrl30"), default=None)
artwork_url_60: str | None = field(metadata=field_options(alias="artworkUrl60"), default=None)
artwork_url_100: str | None = field(metadata=field_options(alias="artworkUrl100"), default=None)
artwork_url_600: str | None = field(metadata=field_options(alias="artworkUrl600"), default=None)
+ content_advisory_rating: str | None = field(
+ metadata=field_options(alias="contentAdvisoryRating"), default=None
+ )
@dataclass(kw_only=True)