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
19 changes: 19 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 All @@ -17,6 +19,7 @@


TAudio = TypeVar("TAudio", bound="Audio")
TTrack = TypeVar("TTrack", bound="Track")


class Audio(PlexPartialObject, PlayedUnplayedMixin):
Expand Down Expand Up @@ -527,6 +530,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: TTrack,
to: TTrack,
**kwargs: Any,
) -> list[TTrack]:
"""Returns a sonic adventure from the current track to the specified track.

Parameters:
to (:class:`~plexapi.audio.Track`): 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
8 changes: 8 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,13 @@ def test_audio_Track_lyricStreams(track):
assert not track.lyricStreams()


@pytest.mark.authenticated
def test_audio_Track_sonicAdventure(account_plexpass, music):
tracks = music.searchTracks()
adventure = tracks[0].sonicAdventure(tracks[-1])
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
7 changes: 7 additions & 0 deletions tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,13 @@ def test_library_MusicSection_recentlyAdded(music, artist):
assert track in music.recentlyAddedTracks()


@pytest.mark.authenticated
def test_library_MusicSection_sonicAdventure(account_plexpass, music):
tracks = music.searchTracks()
adventure = music.sonicAdventure(tracks[0], tracks[-1].ratingKey)
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