Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Audiobookshelf - adapt schema to reflect the naming scheme used in the API docs #1898

Merged
merged 5 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading