@@ -25,8 +25,7 @@ import com.adamratzman.spotify.models.serialization.toPagingObject
25
25
import com.neovisionaries.i18n.CountryCode
26
26
import java.text.SimpleDateFormat
27
27
import java.time.Instant
28
- import java.util.Date
29
- import java.util.HashMap
28
+ import java.util.*
30
29
import java.util.function.Supplier
31
30
32
31
/* *
@@ -229,19 +228,75 @@ class BrowseAPI(api: SpotifyAPI) : SpotifyEndpoint(api) {
229
228
*
230
229
* @throws BadRequestException if any filter is applied illegally
231
230
*/
231
+ @Suppress(" DEPRECATION" )
232
+ fun getTrackRecommendations (
233
+ seedArtists : List <String >? = null,
234
+ seedGenres : List <String >? = null,
235
+ seedTracks : List <String >? = null,
236
+ limit : Int? = null,
237
+ market : CountryCode ? = null,
238
+ targetAttributes : List <TrackAttribute <* >> = listOf(),
239
+ minAttributes : List <TrackAttribute <* >> = listOf(),
240
+ maxAttributes : List <TrackAttribute <* >> = listOf()
241
+ ): SpotifyRestAction <RecommendationResponse > =
242
+ getRecommendations(
243
+ seedArtists,
244
+ seedGenres,
245
+ seedTracks,
246
+ limit,
247
+ market,
248
+ targetAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
249
+ minAttributes.map { it.tuneableTrackAttribute to it.value }.toMap(),
250
+ maxAttributes.map { it.tuneableTrackAttribute to it.value }.toMap()
251
+ )
252
+
253
+
254
+ /* *
255
+ * Create a playlist-style listening experience based on seed artists, tracks and genres.
256
+ * Recommendations are generated based on the available information for a given seed entity and matched against similar
257
+ * artists and tracks. If there is sufficient information about the provided seeds, a list of tracks will be returned
258
+ * together with pool size details. For artists and tracks that are very new or obscure there might not be enough data
259
+ * to generate a list of tracks.
260
+ *
261
+ * **5** seeds of any combination of [seedArtists], [seedGenres], and [seedTracks] can be provided. AT LEAST 1 seed must be provided.
262
+ *
263
+ * **All attributes** are weighted equally.
264
+ *
265
+ * See [here](https://developer.spotify.com/documentation/web-api/reference/browse/get-recommendations/#tuneable-track-attributes) for a list
266
+ * and descriptions of tuneable track attributes and their ranges.
267
+ *
268
+ * @param seedArtists A possibly null provided list of <b>Artist IDs</b> to be used to generate recommendations
269
+ * @param seedGenres A possibly null provided list of <b>Genre IDs</b> to be used to generate recommendations. Invalid genres are ignored
270
+ * @param seedTracks A possibly null provided list of <b>Track IDs</b> to be used to generate recommendations
271
+ * @param limit The number of objects to return. Default: 20. Minimum: 1. Maximum: 50.
272
+ * @param market Provide this parameter if you want the list of returned items to be relevant to a particular country.
273
+ * If omitted, the returned items will be relevant to all countries.
274
+ * @param targetAttributes For each of the tunable track attributes a target value may be provided.
275
+ * Tracks with the attribute values nearest to the target values will be preferred.
276
+ * @param minAttributes For each tunable track attribute, a hard floor on the selected track attribute’s value can be provided.
277
+ * @param maxAttributes For each tunable track attribute, a hard ceiling on the selected track attribute’s value can be provided.
278
+ * For example, setting max instrumentalness equal to 0.35 would filter out most tracks that are likely to be instrumental.
279
+ *
280
+ * @return [RecommendationResponse] with [RecommendationSeed]s used and [SimpleTrack]s found
281
+ *
282
+ * @throws BadRequestException if any filter is applied illegally
283
+ *
284
+ */
285
+ @Deprecated(" Ambiguous track attribute setting. Please use BrowseAPI#getTrackRecommendations instead" )
232
286
fun getRecommendations (
233
287
seedArtists : List <String >? = null,
234
288
seedGenres : List <String >? = null,
235
289
seedTracks : List <String >? = null,
236
290
limit : Int? = null,
237
291
market : CountryCode ? = null,
238
- targetAttributes : HashMap <TuneableTrackAttribute , Number > = hashMapOf (),
239
- minAttributes : HashMap <TuneableTrackAttribute , Number > = hashMapOf (),
240
- maxAttributes : HashMap <TuneableTrackAttribute , Number > = hashMapOf ()
292
+ targetAttributes : Map <TuneableTrackAttribute < * > , Number > = mapOf (),
293
+ minAttributes : Map <TuneableTrackAttribute < * > , Number > = mapOf (),
294
+ maxAttributes : Map <TuneableTrackAttribute < * > , Number > = mapOf ()
241
295
): SpotifyRestAction <RecommendationResponse > {
242
296
if (seedArtists?.isEmpty() != false && seedGenres?.isEmpty() != false && seedTracks?.isEmpty() != false ) {
243
297
throw BadRequestException (ErrorObject (400 , " At least one seed (genre, artist, track) must be provided." ))
244
298
}
299
+
245
300
return toAction(Supplier {
246
301
val builder = EndpointBuilder (" /recommendations" ).with (" limit" , limit).with (" market" , market?.name)
247
302
.with (" seed_artists" , seedArtists?.joinToString(" ," ) { ArtistURI (it).id.encode() })
@@ -253,75 +308,77 @@ class BrowseAPI(api: SpotifyAPI) : SpotifyEndpoint(api) {
253
308
get(builder.toString()).toObject<RecommendationResponse >(api)
254
309
})
255
310
}
311
+
312
+
256
313
}
257
314
258
315
/* *
259
316
* Describes a track attribute
260
317
*
261
318
* @param attribute The spotify id for the track attribute
262
319
*/
263
- enum class TuneableTrackAttribute ( private val attribute : String ) {
320
+ sealed class TuneableTrackAttribute < T : Number >( val attribute : String , val integerOnly : Boolean , val min : T ? , val max : T ? ) {
264
321
/* *
265
322
* A confidence measure from 0.0 to 1.0 of whether the track is acoustic.
266
323
* 1.0 represents high confidence the track is acoustic.
267
324
*/
268
- ACOUSTICNESS (" acousticness" ),
325
+ object ACOUSTICNESS : TuneableTrackAttribute<Float> (" acousticness" , false , 0f , 1f )
269
326
/* *
270
327
* Danceability describes how suitable a track is for dancing based on a combination of musical
271
328
* elements including tempo, rhythm stability, beat strength, and overall regularity. A value of 0.0 is
272
329
* least danceable and 1.0 is most danceable.
273
330
*/
274
- DANCEABILITY (" danceability" ),
331
+ object DANCEABILITY : TuneableTrackAttribute<Float> (" danceability" , false , 0f , 1f )
275
332
/* *
276
333
* The duration of the track in milliseconds.
277
334
*/
278
- DURATION_IN_MILLISECONDS (" duration_ms" ),
335
+ object DURATION_IN_MILLISECONDS : TuneableTrackAttribute<Int> (" duration_ms" , true , 0 , null )
279
336
/* *
280
337
* Energy is a measure from 0.0 to 1.0 and represents a perceptual measure of intensity and activity.
281
338
* Typically, energetic tracks feel fast, loud, and noisy. For example, death metal has high energy,
282
339
* while a Bach prelude scores low on the scale. Perceptual features contributing to this attribute
283
340
* include dynamic range, perceived loudness, timbre, onset rate, and general entropy.
284
341
*/
285
- ENERGY (" energy" ),
342
+ object ENERGY : TuneableTrackAttribute<Float> (" energy" , false , 0f , 1f )
286
343
/* *
287
344
* Predicts whether a track contains no vocals. “Ooh” and “aah” sounds are treated as
288
345
* instrumental in this context. Rap or spoken word tracks are clearly “vocal”. The
289
346
* closer the instrumentalness value is to 1.0, the greater likelihood the track contains
290
347
* no vocal content. Values above 0.5 are intended to represent instrumental tracks, but
291
348
* confidence is higher as the value approaches 1.0.
292
349
*/
293
- INSTRUMENTALNESS (" instrumentalness" ),
350
+ object INSTRUMENTALNESS : TuneableTrackAttribute<Float> (" instrumentalness" , false , 0f , 1f )
294
351
/* *
295
352
* The key the track is in. Integers map to pitches using standard Pitch Class notation.
296
353
* E.g. 0 = C, 1 = C♯/D♭, 2 = D, and so on.
297
354
*/
298
- KEY (" key" ),
355
+ object KEY : TuneableTrackAttribute<Int> (" key" , true , 0 , null )
299
356
/* *
300
357
* Detects the presence of an audience in the recording. Higher liveness values represent an increased
301
358
* probability that the track was performed live. A value above 0.8 provides strong likelihood
302
359
* that the track is live.
303
360
*/
304
- LIVENESS (" liveness" ),
361
+ object LIVENESS : TuneableTrackAttribute<Float> (" liveness" , false , 0f , 1f )
305
362
/* *
306
363
* The overall loudness of a track in decibels (dB). Loudness values are averaged across the
307
364
* entire track and are useful for comparing relative loudness of tracks. Loudness is the
308
365
* quality of a sound that is the primary psychological correlate of physical strength (amplitude).
309
- * Values typical range between -60 and 0 db.
366
+ * Values typically range between -60 and 0 db.
310
367
*/
311
- LOUDNESS (" loudness" ),
368
+ object LOUDNESS : TuneableTrackAttribute<Float> (" loudness" , false , null , null )
312
369
/* *
313
370
* Mode indicates the modality (major or minor) of a track, the type of scale from which its
314
371
* melodic content is derived. Major is represented by 1 and minor is 0.
315
372
*/
316
- MODE (" mode" ),
373
+ object MODE : TuneableTrackAttribute<Int> (" mode" , true , 0 , 1 )
317
374
/* *
318
375
* The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.
319
376
* The popularity is calculated by algorithm and is based, in the most part, on the total number of
320
377
* plays the track has had and how recent those plays are. Note: When applying track relinking via
321
378
* the market parameter, it is expected to find relinked tracks with popularities that do not match
322
379
* min_*, max_*and target_* popularities. These relinked tracks are accurate replacements for unplayable tracks with the expected popularity scores. Original, non-relinked tracks are available via the linked_from attribute of the relinked track response.
323
380
*/
324
- POPULARITY (" popularity" ),
381
+ object POPULARITY : TuneableTrackAttribute<Int> (" popularity" , true , 0 , 100 )
325
382
/* *
326
383
* Speechiness detects the presence of spoken words in a track. The more exclusively speech-like the
327
384
* recording (e.g. talk show, audio book, poetry), the closer to 1.0 the attribute value. Values above
@@ -330,23 +387,36 @@ enum class TuneableTrackAttribute(private val attribute: String) {
330
387
* such cases as rap music. Values below 0.33 most likely represent music and other non-speech-like
331
388
* tracks.
332
389
*/
333
- SPEECHINESS (" speechiness" ),
390
+ object SPEECHINESS : TuneableTrackAttribute<Float> (" speechiness" , false , 0f , 1f )
334
391
/* *
335
392
* The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the
336
393
* speed or pace of a given piece and derives directly from the average beat duration.
337
394
*/
338
- TEMPO (" tempo" ),
395
+ object TEMPO : TuneableTrackAttribute<Float> (" tempo" , false , 0f , null )
339
396
/* *
340
397
* An estimated overall time signature of a track. The time signature (meter)
341
398
* is a notational convention to specify how many beats are in each bar (or measure).
342
399
*/
343
- TIME_SIGNATURE (" time_signature" ),
400
+ object TIME_SIGNATURE :TuneableTrackAttribute<Int> (" time_signature" , true , null , null )
344
401
/* *
345
402
* A measure from 0.0 to 1.0 describing the musical positiveness conveyed by a track. Tracks with high
346
403
* valence sound more positive (e.g. happy, cheerful, euphoric), while tracks with low valence
347
404
* sound more negative (e.g. sad, depressed, angry).
348
405
*/
349
- VALENCE (" valence" );
406
+ object VALENCE : TuneableTrackAttribute<Float> (" valence" , false , 0f , 1f )
350
407
351
408
override fun toString () = attribute
409
+
410
+ fun asTrackAttribute (value : T ): TrackAttribute <T > {
411
+ if (min != null && min.toDouble() > value.toDouble()) throw IllegalArgumentException (" Attribute value for $this must be greater than $min !" )
412
+ if (max != null && max.toDouble() < value.toDouble()) throw IllegalArgumentException (" Attribute value for $this must be less than $max !" )
413
+
414
+ return TrackAttribute (this , value)
415
+ }
416
+ }
417
+
418
+ data class TrackAttribute <T : Number >(val tuneableTrackAttribute : TuneableTrackAttribute <T >, val value : T ) {
419
+ companion object {
420
+ fun <T : Number > create (tuneableTrackAttribute : TuneableTrackAttribute <T >, value : T ) = tuneableTrackAttribute.asTrackAttribute(value)
421
+ }
352
422
}
0 commit comments