Skip to content

Commit 067fa58

Browse files
committed
Remove multiple items from UserPlaylist. Set UserPlaylist public/private. Updated doxygen (Fixes #259)
Renamed delete method Delete UserPlaylist. Reparse after changing public/private state
1 parent 2989e6b commit 067fa58

File tree

1 file changed

+81
-18
lines changed

1 file changed

+81
-18
lines changed

tidalapi/playlist.py

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import copy
2424
from datetime import datetime
25-
from typing import TYPE_CHECKING, List, Optional, Sequence, Union, cast
25+
from typing import TYPE_CHECKING, List, Optional, Sequence, Union
2626

2727
from tidalapi.exceptions import ObjectNotFound, TooManyRequests
2828
from tidalapi.types import JsonObj
@@ -233,13 +233,19 @@ def wide_image(self, width: int = 1080, height: int = 720) -> str:
233233

234234
class UserPlaylist(Playlist):
235235
def _reparse(self) -> None:
236+
# Re-Read Playlist to get ETag
236237
request = self.request.request("GET", self._base_url % self.id)
237238
self._etag = request.headers["etag"]
238239
self.request.map_json(request.json(), parse=self.parse)
239240

240241
def edit(
241242
self, title: Optional[str] = None, description: Optional[str] = None
242243
) -> None:
244+
"""
245+
Edit UserPlaylist title & description
246+
:param title: Playlist title
247+
:param description: Playlist title
248+
"""
243249
if not title:
244250
title = self.name
245251
if not description:
@@ -248,10 +254,21 @@ def edit(
248254
data = {"title": title, "description": description}
249255
self.request.request("POST", self._base_url % self.id, data=data)
250256

251-
def delete(self) -> None:
252-
self.request.request("DELETE", self._base_url % self.id)
257+
def delete(self, media_ids: List[str]) -> None:
258+
"""
259+
Delete one or more items from the UserPlaylist
260+
:param media_ids: Lists of Media IDs to remove
261+
"""
262+
# Generate list of track indices of tracks found in the list of media_ids.
263+
track_ids = [str(track.id) for track in self.tracks()]
264+
matching_indices = [i for i, item in enumerate(track_ids) if item in media_ids]
265+
self.remove_by_indices(matching_indices)
253266

254267
def add(self, media_ids: List[str]) -> None:
268+
"""
269+
Add one or more items to the UserPlaylist
270+
:param media_ids: List of Media IDs to add
271+
"""
255272
data = {
256273
"onArtifactNotFound": "SKIP",
257274
"onDupes": "SKIP",
@@ -268,34 +285,80 @@ def add(self, media_ids: List[str]) -> None:
268285
)
269286
self._reparse()
270287

288+
def remove_by_id(self, media_id: str) -> None:
289+
"""
290+
Remove a single item from the playlist, using the media ID
291+
:param media_id: Media ID to remove
292+
"""
293+
track_ids = [str(track.id) for track in self.tracks()]
294+
try:
295+
index = track_ids.index(media_id)
296+
if index is not None and index < self.num_tracks:
297+
self.remove_by_index(index)
298+
except ValueError:
299+
pass
300+
271301
def remove_by_index(self, index: int) -> None:
302+
"""
303+
Remove a single item from the UserPlaylist, using item index.
304+
:param index: Media index to remove
305+
"""
272306
headers = {"If-None-Match": self._etag} if self._etag else None
273307
self.request.request(
274308
"DELETE", (self._base_url + "/items/%i") % (self.id, index), headers=headers
275309
)
276310

277311
def remove_by_indices(self, indices: Sequence[int]) -> None:
312+
"""
313+
Remove one or more items from the UserPlaylist, using list of indices
314+
:param indices: List containing indices to remove
315+
"""
278316
headers = {"If-None-Match": self._etag} if self._etag else None
279317
track_index_string = ",".join([str(x) for x in indices])
280318
self.request.request(
281319
"DELETE",
282-
(self._base_url + "/tracks/%s") % (self.id, track_index_string),
320+
(self._base_url + "/items/%s") % (self.id, track_index_string),
283321
headers=headers,
284322
)
323+
self._reparse()
285324

286-
def _calculate_id(self, media_id: str) -> Optional[int]:
287-
i = 0
288-
while i < self.num_tracks:
289-
items = self.items(100, i)
290-
for index, item in enumerate(items):
291-
if item.id == media_id:
292-
# Return the amount of items we have gone through plus the index in the last list.
293-
return index + i
325+
def clear(self, chunk_size: int = 50):
326+
"""
327+
Clear UserPlaylist
328+
:param chunk_size: Number of items to remove per request
329+
:return:
330+
"""
331+
while self.num_tracks:
332+
indices = range(min(self.num_tracks, chunk_size))
333+
self.remove_by_indices(indices)
294334

295-
i += len(items)
296-
return None
335+
def set_playlist_public(self):
336+
"""
337+
Set UserPlaylist as Public
338+
"""
339+
self.request.request(
340+
"PUT",
341+
base_url=self.session.config.api_v2_location,
342+
path=(self._base_url + "/set-public") % self.id,
343+
)
344+
self.public = True
345+
self._reparse()
297346

298-
def remove_by_id(self, media_id: str) -> None:
299-
index = self._calculate_id(media_id)
300-
if index is not None:
301-
self.remove_by_index(index)
347+
def set_playlist_private(self):
348+
"""
349+
Set UserPlaylist as Private
350+
"""
351+
self.request.request(
352+
"PUT",
353+
base_url=self.session.config.api_v2_location,
354+
path=(self._base_url + "/set-private") % self.id,
355+
)
356+
self.public = False
357+
self._reparse()
358+
359+
def delete_playlist(self):
360+
"""
361+
Delete UserPlaylist
362+
:return: True, if successful
363+
"""
364+
return self.request.request("DELETE", path="playlists/%s" % self.id).ok

0 commit comments

Comments
 (0)