-
-
Notifications
You must be signed in to change notification settings - Fork 384
Add support for series #3569
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
base: dev
Are you sure you want to change the base?
Add support for series #3569
Changes from 10 commits
2ace3b4
fdc0de6
32f3585
762c68e
b5e2c4c
d86574d
2329e11
b5e06d8
b116221
77815af
d212207
61f98d9
6b7a048
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
|
|
||
| from music_assistant_models.enums import MediaType, ProviderFeature | ||
| from music_assistant_models.media_items import Audiobook, ProviderMapping, UniqueList | ||
| from music_assistant_models.media_items.helpers import AudiobookSeries | ||
|
|
||
| from music_assistant.constants import DB_TABLE_AUDIOBOOKS, DB_TABLE_PLAYLOG | ||
| from music_assistant.controllers.media.base import MediaControllerBase | ||
|
|
@@ -63,6 +64,7 @@ def __init__(self, mass: MusicAssistant) -> None: | |
| # register (extra) api handlers | ||
| api_base = self.api_base | ||
| self.mass.register_api_command(f"music/{api_base}/audiobook_versions", self.versions) | ||
| self.mass.register_api_command(f"music/{api_base}/get_series", self.series) | ||
|
|
||
| async def library_items( | ||
| self, | ||
|
|
@@ -73,6 +75,7 @@ async def library_items( | |
| order_by: str = "sort_name", | ||
| provider: str | list[str] | None = None, | ||
| genre: int | list[int] | None = None, | ||
| without_series: bool | None = None, | ||
| **kwargs: Any, | ||
| ) -> list[Audiobook]: | ||
| """Get in-database audiobooks. | ||
|
|
@@ -84,9 +87,16 @@ async def library_items( | |
| :param order_by: Order by field (e.g. 'sort_name', 'timestamp_added'). | ||
| :param provider: Filter by provider instance ID (single string or list). | ||
| :param genre: Filter by genre id(s). | ||
| :param without_series: Do not return audiobooks which are part of a series | ||
| """ | ||
| extra_query_params: dict[str, Any] = {} | ||
| extra_query_parts: list[str] = [] | ||
| self.logger.error(without_series) | ||
| if without_series: | ||
| extra_query_parts = [ | ||
| "WHERE json_extract(audiobooks.metadata, '$.series') IS NULL " | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's be defensive here and already add |
||
| "OR json_extract(audiobooks.metadata, '$.series') = '[]'", | ||
| ] | ||
| result = await self.get_library_items_by_query( | ||
| favorite=favorite, | ||
| search=search, | ||
|
|
@@ -362,3 +372,57 @@ async def _set_playlog(self, db_id: int, media_item: Audiobook) -> None: | |
| }, | ||
| allow_replace=True, | ||
| ) | ||
|
|
||
| async def series( | ||
| self, | ||
| limit: int = 500, | ||
| offset: int = 0, | ||
| ) -> list[AudiobookSeries]: | ||
| """Get all available audiobook series. | ||
|
|
||
| :param limit: Maximum number of items to return. | ||
| :param offset: Number of items to skip. | ||
| """ | ||
| # key is the series' title | ||
| series_dict: dict[str, list[Audiobook]] = {} | ||
| audiobooks_with_series = await self.get_library_items_by_query( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think limit and offset are now applied on AudioBooks here rather than on the series itself?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, that's true. I removed it completely now, as I don't think that I can apply this offset by pure db methods? The default limit for items is 500, and having more than 500 series would be extraordinary I think. Not certain here. |
||
| limit=limit, | ||
| offset=offset, | ||
| extra_query_parts=[ | ||
| "WHERE json_extract(audiobooks.metadata, '$.series') IS NOT NULL " | ||
| "AND json_extract(audiobooks.metadata, '$.series') != '[]'", | ||
| ], | ||
| ) | ||
| for audiobook in audiobooks_with_series: | ||
| if audiobook.metadata.series is None: | ||
| # this should never happen | ||
| continue | ||
| for series_info in audiobook.metadata.series: | ||
| audiobook_list = series_dict.get(series_info.title, []) | ||
| audiobook_list.append(audiobook) | ||
| series_dict[series_info.title] = audiobook_list | ||
|
|
||
| result: list[AudiobookSeries] = [] | ||
| # Sort series, first by number then alphabetically | ||
| for series_title, audiobook_list in series_dict.items(): | ||
| audiobooks_with_number: list[tuple[Audiobook, float]] = [] | ||
| audiobooks_with_string: list[tuple[Audiobook, str]] = [] | ||
| audiobooks_with_none: list[Audiobook] = [] | ||
| for audiobook in audiobook_list: | ||
| assert audiobook.metadata.series is not None # for type checking | ||
| series_info = next(x for x in audiobook.metadata.series if x.title == series_title) | ||
| if series_info.sequence is None: | ||
| audiobooks_with_none.append(audiobook) | ||
| continue | ||
| try: | ||
| sort_by = float(series_info.sequence) | ||
| audiobooks_with_number.append((audiobook, sort_by)) | ||
| except ValueError: | ||
| audiobooks_with_string.append((audiobook, str(series_info.sequence))) | ||
| final_list = [x[0] for x in sorted(audiobooks_with_number, key=lambda x: x[1])] | ||
| final_list.extend([x[0] for x in sorted(audiobooks_with_string, key=lambda x: x[1])]) | ||
| final_list.extend(audiobooks_with_none) | ||
|
|
||
| result.append(AudiobookSeries(title=series_title, audiobooks=final_list)) | ||
|
|
||
| return result | ||
Uh oh!
There was an error while loading. Please reload this page.