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

Add sonicallySimilar method to Audio class #1288

Merged
merged 9 commits into from
Nov 13, 2023
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
38 changes: 38 additions & 0 deletions plexapi/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from pathlib import Path
from urllib.parse import quote_plus

from typing import Any, Dict, List, Optional, TypeVar

from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject, PlexHistory, PlexSession
from plexapi.exceptions import BadRequest
Expand All @@ -14,6 +16,9 @@
from plexapi.playlist import Playlist


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


class Audio(PlexPartialObject, PlayedUnplayedMixin):
""" Base class for all audio objects including :class:`~plexapi.audio.Artist`,
:class:`~plexapi.audio.Album`, and :class:`~plexapi.audio.Track`.
Expand All @@ -22,6 +27,7 @@ class Audio(PlexPartialObject, PlayedUnplayedMixin):
addedAt (datetime): Datetime the item was added to the library.
art (str): URL to artwork image (/library/metadata/<ratingKey>/art/<artid>).
artBlurHash (str): BlurHash string for artwork image.
distance (float): Sonic Distance of the item from the seed item.
fields (List<:class:`~plexapi.media.Field`>): List of field objects.
guid (str): Plex GUID for the artist, album, or track (plex://artist/5d07bcb0403c64029053ac4c).
index (int): Plex index number (often the track number).
Expand Down Expand Up @@ -53,6 +59,7 @@ def _loadData(self, data):
self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
self.art = data.attrib.get('art')
self.artBlurHash = data.attrib.get('artBlurHash')
self.distance = utils.cast(float, data.attrib.get('distance'))
self.fields = self.findItems(data, media.Field)
self.guid = data.attrib.get('guid')
self.index = utils.cast(int, data.attrib.get('index'))
Expand Down Expand Up @@ -125,6 +132,37 @@ def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):

return myplex.sync(sync_item, client=client, clientId=clientId)

def sonicallySimilar(
self: TAudio,
limit: Optional[int] = None,
maxDistance: Optional[float] = None,
**kwargs,
) -> List[TAudio]:
"""Returns a list of sonically similar audio items.

Parameters:
limit (int): Maximum count of items to return. Default 50 (server default)
maxDistance (float): Maximum distance between tracks, 0.0 - 1.0. Default 0.25 (server default).
**kwargs: Additional options passed into :func:`~plexapi.base.PlexObject.fetchItems`.

Returns:
List[:class:`~plexapi.audio.Audio`]: list of sonically similar audio items.
"""

key = f"{self.key}/nearest"
params: Dict[str, Any] = {}
if limit is not None:
params['limit'] = limit
if maxDistance is not None:
params['maxDistance'] = maxDistance
key += utils.joinArgs(params)

return self.fetchItems(
key,
cls=self.__class__,
**kwargs,
)


@utils.registerPlexObject
class Artist(
Expand Down
13 changes: 13 additions & 0 deletions tests/test_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,19 @@ def test_audio_Audio_section(artist, album, track):
assert track.section().key == album.section().key == artist.section().key


@pytest.mark.authenticated
def test_audio_Audio_sonicallySimilar(artist):
Dr-Blank marked this conversation as resolved.
Show resolved Hide resolved
similar_audio = artist.sonicallySimilar()
assert isinstance(similar_audio, list)
assert all(isinstance(i, type(artist)) for i in similar_audio)

similar_audio = artist.sonicallySimilar(limit=1)
assert len(similar_audio) <= 1

similar_audio = artist.sonicallySimilar(maxDistance=0.1)
assert all(i.distance <= 0.1 for i in similar_audio)


def test_audio_Artist_download(monkeydownload, tmpdir, artist):
total = len(artist.tracks())
filepaths = artist.download(savepath=str(tmpdir))
Expand Down