Skip to content

Commit

Permalink
Chore: Audiobookshelf - adapt schema to reflect the naming scheme use…
Browse files Browse the repository at this point in the history
…d in the API docs (#1898)
  • Loading branch information
fmunkes authored Jan 21, 2025
1 parent bbe219d commit 1b1bdec
Show file tree
Hide file tree
Showing 3 changed files with 586 additions and 252 deletions.
18 changes: 11 additions & 7 deletions music_assistant/providers/audiobookshelf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
from music_assistant.models.music_provider import MusicProvider
from music_assistant.providers.audiobookshelf.abs_client import ABSClient
from music_assistant.providers.audiobookshelf.abs_schema import (
ABSAudioBook,
ABSDeviceInfo,
ABSLibrary,
ABSLibraryItemExpandedBook,
ABSLibraryItemExpandedPodcast,
ABSPlaybackSessionExpanded,
ABSPodcast,
ABSPodcastEpisodeExpanded,
)

Expand Down Expand Up @@ -174,7 +174,7 @@ async def sync_library(self, media_types: tuple[MediaType, ...]) -> None:
await self._client.sync()
await super().sync_library(media_types=media_types)

def _parse_podcast(self, abs_podcast: ABSPodcast) -> Podcast:
def _parse_podcast(self, abs_podcast: ABSLibraryItemExpandedPodcast) -> Podcast:
"""Translate ABSPodcast to MassPodcast."""
title = abs_podcast.media.metadata.title
# Per API doc title may be None.
Expand All @@ -185,7 +185,7 @@ def _parse_podcast(self, abs_podcast: ABSPodcast) -> Podcast:
name=title,
publisher=abs_podcast.media.metadata.author,
provider=self.lookup_key,
total_episodes=abs_podcast.media.num_episodes,
total_episodes=len(abs_podcast.media.episodes),
provider_mappings={
ProviderMapping(
item_id=abs_podcast.id_,
Expand Down Expand Up @@ -309,7 +309,7 @@ async def get_podcast_episode(self, prov_episode_id: str) -> PodcastEpisode:
episode_cnt += 1
raise MediaNotFoundError("Episode not found")

async def _parse_audiobook(self, abs_audiobook: ABSAudioBook) -> Audiobook:
async def _parse_audiobook(self, abs_audiobook: ABSLibraryItemExpandedBook) -> Audiobook:
mass_audiobook = Audiobook(
item_id=abs_audiobook.id_,
provider=self.lookup_key,
Expand Down Expand Up @@ -431,7 +431,9 @@ async def get_stream_details(
return await self._get_stream_details_audiobook(abs_audiobook)
raise MediaNotFoundError("Stream unknown")

async def _get_stream_details_audiobook(self, abs_audiobook: ABSAudioBook) -> StreamDetails:
async def _get_stream_details_audiobook(
self, abs_audiobook: ABSLibraryItemExpandedBook
) -> StreamDetails:
"""Only single audio file in audiobook."""
self.logger.debug(
f"Using direct playback for audiobook {abs_audiobook.media.metadata.title}"
Expand Down Expand Up @@ -554,7 +556,9 @@ async def _browse_lib(
if library is None:
raise MediaNotFoundError("Lib missing.")

def get_item_mapping(item: ABSAudioBook | ABSPodcast) -> ItemMapping:
def get_item_mapping(
item: ABSLibraryItemExpandedBook | ABSLibraryItemExpandedPodcast,
) -> ItemMapping:
title = item.media.metadata.title
if title is None:
title = "UNKNOWN"
Expand Down
53 changes: 35 additions & 18 deletions music_assistant/providers/audiobookshelf/abs_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@
from music_assistant_models.media_items import UniqueList

from music_assistant.providers.audiobookshelf.abs_schema import (
ABSAudioBook,
ABSDeviceInfo,
ABSLibrariesItemsResponse,
ABSLibrariesItemsMinifiedBookResponse,
ABSLibrariesItemsMinifiedPodcastResponse,
ABSLibrariesResponse,
ABSLibrary,
ABSLibraryItem,
ABSLibraryItemExpandedBook,
ABSLibraryItemExpandedPodcast,
ABSLibraryItemMinifiedBook,
ABSLibraryItemMinifiedPodcast,
ABSLoginResponse,
ABSMediaProgress,
ABSPlaybackSession,
ABSPlaybackSessionExpanded,
ABSPlayRequest,
ABSPodcast,
ABSSessionsResponse,
ABSSessionUpdate,
ABSUser,
Expand Down Expand Up @@ -163,43 +165,54 @@ async def sync(self) -> None:
self.podcast_libraries.append(library)
self.user = await self.get_authenticated_user()

async def get_all_podcasts(self) -> AsyncGenerator[ABSPodcast]:
async def get_all_podcasts(self) -> AsyncGenerator[ABSLibraryItemExpandedPodcast]:
"""Get all available podcasts."""
for library in self.podcast_libraries:
async for podcast in self.get_all_podcasts_by_library(library):
yield podcast

async def _get_lib_items(self, lib: ABSLibrary) -> AsyncGenerator[bytes]:
"""Get library items with pagination."""
"""Get library items with pagination.
Note:
- minified=1 -> minified items. However, there appears to be
a bug in abs, so we always get minified items. Still there for
consistency
- collapseseries=0 -> even if books are part of a series, they will be single items
"""
page_cnt = 0
while True:
data = await self._get(
f"/libraries/{lib.id_}/items",
f"/libraries/{lib.id_}/items?minified=1&collapseseries=0",
params={"limit": LIMIT_ITEMS_PER_PAGE, "page": page_cnt},
)
page_cnt += 1
yield data

async def get_all_podcasts_by_library(self, lib: ABSLibrary) -> AsyncGenerator[ABSPodcast]:
async def get_all_podcasts_by_library(
self, lib: ABSLibrary
) -> AsyncGenerator[ABSLibraryItemExpandedPodcast]:
"""Get all podcasts in a library."""
async for podcast_data in self._get_lib_items(lib):
podcast_list = ABSLibrariesItemsResponse.from_json(podcast_data).results
podcast_list = ABSLibrariesItemsMinifiedPodcastResponse.from_json(podcast_data).results
if not podcast_list: # [] if page exceeds
return

async def _get_id(plist: list[ABSLibraryItem] = podcast_list) -> AsyncGenerator[str]:
async def _get_id(
plist: list[ABSLibraryItemMinifiedPodcast] = podcast_list,
) -> AsyncGenerator[str]:
for entry in plist:
yield entry.id_

async for id_ in _get_id():
podcast = await self.get_podcast(id_)
yield podcast

async def get_podcast(self, id_: str) -> ABSPodcast:
async def get_podcast(self, id_: str) -> ABSLibraryItemExpandedPodcast:
"""Get a single Podcast by ID."""
# this endpoint gives more podcast extra data
data = await self._get(f"items/{id_}?expanded=1")
return ABSPodcast.from_json(data)
return ABSLibraryItemExpandedPodcast.from_json(data)

async def _get_progress_ms(
self,
Expand Down Expand Up @@ -288,32 +301,36 @@ async def update_audiobook_progress(
endpoint = f"me/progress/{audiobook_id}"
await self._update_progress(endpoint, progress_s, duration_s, is_finished)

async def get_all_audiobooks(self) -> AsyncGenerator[ABSAudioBook]:
async def get_all_audiobooks(self) -> AsyncGenerator[ABSLibraryItemExpandedBook]:
"""Get all audiobooks."""
for library in self.audiobook_libraries:
async for book in self.get_all_audiobooks_by_library(library):
yield book

async def get_all_audiobooks_by_library(self, lib: ABSLibrary) -> AsyncGenerator[ABSAudioBook]:
async def get_all_audiobooks_by_library(
self, lib: ABSLibrary
) -> AsyncGenerator[ABSLibraryItemExpandedBook]:
"""Get all Audiobooks in a library."""
async for audiobook_data in self._get_lib_items(lib):
audiobook_list = ABSLibrariesItemsResponse.from_json(audiobook_data).results
audiobook_list = ABSLibrariesItemsMinifiedBookResponse.from_json(audiobook_data).results
if not audiobook_list: # [] if page exceeds
return

async def _get_id(alist: list[ABSLibraryItem] = audiobook_list) -> AsyncGenerator[str]:
async def _get_id(
alist: list[ABSLibraryItemMinifiedBook] = audiobook_list,
) -> AsyncGenerator[str]:
for entry in alist:
yield entry.id_

async for id_ in _get_id():
audiobook = await self.get_audiobook(id_)
yield audiobook

async def get_audiobook(self, id_: str) -> ABSAudioBook:
async def get_audiobook(self, id_: str) -> ABSLibraryItemExpandedBook:
"""Get a single Audiobook by ID."""
# this endpoint gives more audiobook extra data
audiobook = await self._get(f"items/{id_}?expanded=1")
return ABSAudioBook.from_json(audiobook)
return ABSLibraryItemExpandedBook.from_json(audiobook)

async def get_playback_session_podcast(
self, device_info: ABSDeviceInfo, podcast_id: str, episode_id: str
Expand Down
Loading

0 comments on commit 1b1bdec

Please sign in to comment.