Skip to content

feat: Add sonicAdventure method to MusicSection #1361

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

Merged
merged 12 commits into from
Mar 16, 2024
Merged
18 changes: 18 additions & 0 deletions plexapi/audio.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

import os
from pathlib import Path
from urllib.parse import quote_plus
Expand Down Expand Up @@ -527,6 +529,22 @@ def metadataDirectory(self):
guid_hash = utils.sha1hash(self.parentGuid)
return str(Path('Metadata') / 'Albums' / guid_hash[0] / f'{guid_hash[1:]}.bundle')

def sonicAdventure(
self: TAudio,
to: TAudio,
**kwargs: Any,
) -> list[TAudio]:
"""Returns a sonic adventure from the current track to the specified track.

Parameters:
to: The target track for the sonic adventure.
**kwargs: Additional options passed into :func:`~plexapi.library.MusicSection.sonicAdventure`.

Returns:
List[:class:`~plexapi.audio.Track`]: list of tracks in the sonic adventure.
"""
return self.section().sonicAdventure(self, to, **kwargs)


@utils.registerPlexObject
class TrackSession(PlexSession, Track):
Expand Down
32 changes: 32 additions & 0 deletions plexapi/library.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

import re
from typing import Any, TYPE_CHECKING
import warnings
from collections import defaultdict
from datetime import datetime
Expand All @@ -17,6 +20,10 @@
from plexapi.utils import deprecated


if TYPE_CHECKING:
from plexapi.audio import Track


class Library(PlexObject):
""" Represents a PlexServer library. This contains all sections of media defined
in your Plex server including video, shows and audio.
Expand Down Expand Up @@ -2033,6 +2040,31 @@ def sync(self, bitrate, limit=None, **kwargs):
kwargs['policy'] = Policy.create(limit)
return super(MusicSection, self).sync(**kwargs)

def sonicAdventure(
self,
start: Track | int,
end: Track | int,
**kwargs: Any,
) -> list[Track]:
""" Returns a list of tracks from this library section that are part of a sonic adventure.
ID's should be of a track, other ID's will return an empty list or items itself or an error.

Parameters:
start (Track | int): The :class:`~plexapi.audio.Track` or ID of the first track in the sonic adventure.
end (Track | int): The :class:`~plexapi.audio.Track` or ID of the last track in the sonic adventure.
kwargs: Additional parameters to pass to :func:`~plexapi.base.PlexObject.fetchItems`.

Returns:
List[:class:`~plexapi.audio.Track`]: a list of tracks from this library section
that are part of a sonic adventure.
"""
# can not use Track due to circular import
startID = start if isinstance(start, int) else start.ratingKey
endID = end if isinstance(end, int) else end.ratingKey

key = f"/library/sections/{self.key}/computePath?startID={startID}&endID={endID}"
return self.fetchItems(key, **kwargs)


class PhotoSection(LibrarySection, PhotoalbumEditMixins, PhotoEditMixins):
""" Represents a :class:`~plexapi.library.LibrarySection` section containing photos.
Expand Down
9 changes: 9 additions & 0 deletions tests/test_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from urllib.parse import quote_plus

import pytest
import plexapi
from plexapi.exceptions import BadRequest

from . import conftest as utils
Expand Down Expand Up @@ -398,6 +399,14 @@ def test_audio_Track_lyricStreams(track):
assert not track.lyricStreams()


@pytest.mark.authenticated
def test_audio_Track_sonicAdventure(music):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these tests work on the bootstrap test server? It only has one track and we disable all the analysis tasks (although it looks like we forgot to disable sonic analysis).

https://github.com/pkkid/python-plexapi/blob/b51aa1924b5b28ba61aefbb4162aff6e6864561b/tools/plex-bootstraptest.py#L502-L503

Also needs a Plex Pass account.

Suggested change
def test_audio_Track_sonicAdventure(music):
def test_audio_Track_sonicAdventure(account_plexpass, music):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might not, I changed my bootstrap server by adding a few tracks from my collection to test these features

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this does not work on the bootstrap server with a single track, or even two tracks.

The tests fail at assert len(adventure) because there are no computed tracks. I think we just check if it is a list assert isinstance(adventure, list) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this does not work on the bootstrap server with a single track, or even two tracks.

would return list of 2 tracks, same as the args if not adventure between them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this on my own server and it returns a blank list. The start and end are not included.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I would be wrong on this then, however the tests should be okay regardless

tracks = music.searchTracks()
adventure = tracks[0].sonicAdventure(tracks[-1])
assert len(adventure)
assert all(isinstance(t, plexapi.audio.Track) for t in adventure)


def test_audio_Track_mixins_images(track):
test_mixins.attr_artUrl(track)
test_mixins.attr_posterUrl(track)
Expand Down
8 changes: 8 additions & 0 deletions tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,14 @@ def test_library_MusicSection_recentlyAdded(music, artist):
assert track in music.recentlyAddedTracks()


@pytest.mark.authenticated
def test_library_MusicSection_sonicAdventure(music):
tracks = music.searchTracks()
adventure = music.sonicAdventure(tracks[0], tracks[-1].ratingKey)
assert len(adventure)
assert all(isinstance(t, plexapi.audio.Track) for t in adventure)


def test_library_PhotoSection_searchAlbums(photos, photoalbum):
title = photoalbum.title
assert len(photos.searchAlbums(title=title))
Expand Down