2222
2323import copy
2424from 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
2727from tidalapi .exceptions import ObjectNotFound , TooManyRequests
2828from tidalapi .types import JsonObj
@@ -233,13 +233,19 @@ def wide_image(self, width: int = 1080, height: int = 720) -> str:
233233
234234class 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