Skip to content

Commit 9cc5bed

Browse files
authored
Add support for retrieving audio features (#354)
1 parent 9158913 commit 9cc5bed

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

src/spotifyaio/models.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from dataclasses import dataclass, field
66
from datetime import datetime # noqa: TCH003
7-
from enum import StrEnum
7+
from enum import IntEnum, StrEnum
88
from typing import Annotated, Any
99

1010
from mashumaro import field_options
@@ -533,3 +533,55 @@ class AlbumTracksResponse(DataClassORJSONMixin):
533533
"""Album tracks response model."""
534534

535535
items: list[SimplifiedTrack]
536+
537+
538+
class Key(IntEnum):
539+
"""Key of a track."""
540+
541+
C = 0
542+
C_SHARP = 1
543+
D = 2
544+
D_SHARP = 3
545+
E = 4
546+
F = 5
547+
F_SHARP = 6
548+
G = 7
549+
G_SHARP = 8
550+
A = 9
551+
A_SHARP = 10
552+
B = 11
553+
554+
555+
class Mode(IntEnum):
556+
"""Mode of a track."""
557+
558+
MINOR = 0
559+
MAJOR = 1
560+
561+
562+
class TimeSignature(IntEnum):
563+
"""Time signature of a track."""
564+
565+
THREE_FOUR = 3
566+
FOUR_FOUR = 4
567+
FIVE_FOUR = 5
568+
SIX_FOUR = 6
569+
SEVEN_FOUR = 7
570+
571+
572+
@dataclass
573+
class AudioFeatures(DataClassORJSONMixin):
574+
"""Audio features model."""
575+
576+
danceability: float
577+
energy: float
578+
key: Key | None
579+
loudness: float
580+
mode: Mode
581+
speechiness: float
582+
acousticness: float
583+
instrumentalness: float
584+
liveness: float
585+
valence: float
586+
tempo: float
587+
time_signature: TimeSignature

src/spotifyaio/spotify.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
AlbumTracksResponse,
2020
Artist,
2121
ArtistResponse,
22+
AudioFeatures,
2223
BasePlaylist,
2324
BaseUserProfile,
2425
CategoriesResponse,
@@ -503,7 +504,11 @@ async def get_saved_tracks(self) -> list[SavedTrack]:
503504

504505
# Get audio features for several tracks
505506

506-
# Get audio features for a track
507+
async def get_audio_features(self, track_id: str) -> AudioFeatures:
508+
"""Get audio features."""
509+
identifier = get_identifier(track_id)
510+
response = await self._get(f"v1/audio-features/{identifier}")
511+
return AudioFeatures.from_json(response)
507512

508513
# Get audio analysis for a track
509514

tests/__snapshots__/test_spotify.ambr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2487,6 +2487,22 @@
24872487
}),
24882488
])
24892489
# ---
2490+
# name: test_get_audio_features
2491+
dict({
2492+
'acousticness': 0.011,
2493+
'danceability': 0.696,
2494+
'energy': 0.905,
2495+
'instrumentalness': 0.000905,
2496+
'key': <Key.D: 2>,
2497+
'liveness': 0.302,
2498+
'loudness': -2.743,
2499+
'mode': <Mode.MAJOR: 1>,
2500+
'speechiness': 0.103,
2501+
'tempo': 114.944,
2502+
'time_signature': <TimeSignature.FOUR_FOUR: 4>,
2503+
'valence': 0.625,
2504+
})
2505+
# ---
24902506
# name: test_get_categories
24912507
list([
24922508
dict({

tests/fixtures/audio_features.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"danceability": 0.696,
3+
"energy": 0.905,
4+
"key": 2,
5+
"loudness": -2.743,
6+
"mode": 1,
7+
"speechiness": 0.103,
8+
"acousticness": 0.011,
9+
"instrumentalness": 0.000905,
10+
"liveness": 0.302,
11+
"valence": 0.625,
12+
"tempo": 114.944,
13+
"type": "audio_features",
14+
"id": "11dFghVXANMlKmJXsNCbNl",
15+
"uri": "spotify:track:11dFghVXANMlKmJXsNCbNl",
16+
"track_href": "https://api.spotify.com/v1/tracks/11dFghVXANMlKmJXsNCbNl",
17+
"analysis_url": "https://api.spotify.com/v1/audio-analysis/11dFghVXANMlKmJXsNCbNl",
18+
"duration_ms": 207960,
19+
"time_signature": 4
20+
}

tests/test_spotify.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,3 +1297,25 @@ async def test_get_categories(
12971297
params={"limit": 48},
12981298
json=None,
12991299
)
1300+
1301+
1302+
async def test_get_audio_features(
1303+
responses: aioresponses,
1304+
snapshot: SnapshotAssertion,
1305+
authenticated_client: SpotifyClient,
1306+
) -> None:
1307+
"""Test retrieving audio features."""
1308+
responses.get(
1309+
f"{SPOTIFY_URL}/v1/audio-features/11dFghVXANMlKmJXsNCbNl",
1310+
status=200,
1311+
body=load_fixture("audio_features.json"),
1312+
)
1313+
response = await authenticated_client.get_audio_features("11dFghVXANMlKmJXsNCbNl")
1314+
assert response == snapshot
1315+
responses.assert_called_once_with(
1316+
f"{SPOTIFY_URL}/v1/audio-features/11dFghVXANMlKmJXsNCbNl",
1317+
METH_GET,
1318+
headers=HEADERS,
1319+
params=None,
1320+
json=None,
1321+
)

0 commit comments

Comments
 (0)