diff --git a/tests/mixins/test_playlists.py b/tests/mixins/test_playlists.py index 83c9402..f703592 100644 --- a/tests/mixins/test_playlists.py +++ b/tests/mixins/test_playlists.py @@ -107,6 +107,7 @@ def test_end2end(self, yt_brand, sample_video): assert len(response["playlistEditResults"]) > 0, "Adding playlist item failed" time.sleep(3) yt_brand.edit_playlist(playlist_id, addToTop=False) + time.sleep(3) playlist = yt_brand.get_playlist(playlist_id, related=True) assert len(playlist["tracks"]) == 46, "Getting playlist items failed" response = yt_brand.remove_playlist_items(playlist_id, playlist["tracks"]) diff --git a/tests/mixins/test_uploads.py b/tests/mixins/test_uploads.py index c37e6e0..ba0f24d 100644 --- a/tests/mixins/test_uploads.py +++ b/tests/mixins/test_uploads.py @@ -1,8 +1,10 @@ import tempfile +import time import pytest from tests.conftest import get_resource +from ytmusicapi.ytmusic import YTMusic class TestUploads: @@ -47,6 +49,39 @@ def test_upload_song(self, config, yt_auth): response = yt_auth.upload_song(get_resource(config["uploads"]["file"])) assert response.status_code == 409 + def test_upload_song_and_verify(self, config, yt_auth: YTMusic): + """Upload a song and verify it can be retrieved after it finishes processing.""" + upload_response = yt_auth.upload_song(get_resource(config["uploads"]["file"])) + if not isinstance(upload_response, str) and upload_response.status_code == 409: + # Song is already in uploads. Delete it and re-upload + songs = yt_auth.get_library_upload_songs(limit=None, order="recently_added") + delete_response = None + for song in songs: + if song.get("title") in config["uploads"]["file"]: + delete_response = yt_auth.delete_upload_entity(song["entityId"]) + assert delete_response == "STATUS_SUCCEEDED" + # Need to wait for song to be fully deleted + time.sleep(10) + # Now re-upload + upload_response = yt_auth.upload_song(get_resource(config["uploads"]["file"])) + + assert ( + upload_response == "STATUS_SUCCEEDED" or upload_response.status_code == 200 + ), f"Song failed to upload {upload_response}" + + # Wait for upload to finish processing and verify it can be retrieved + retries_remaining = 5 + while retries_remaining: + time.sleep(5) + songs = yt_auth.get_library_upload_songs(limit=None, order="recently_added") + for song in songs: + if song.get("title") in config["uploads"]["file"]: + # Uploaded song found + return + retries_remaining -= 1 + + raise AssertionError("Uploaded song was not found in library") + @pytest.mark.skip(reason="Do not delete uploads") def test_delete_upload_entity(self, yt_oauth): results = yt_oauth.get_library_upload_songs() diff --git a/ytmusicapi/parsers/_utils.py b/ytmusicapi/parsers/_utils.py index de6a90d..52059b9 100644 --- a/ytmusicapi/parsers/_utils.py +++ b/ytmusicapi/parsers/_utils.py @@ -61,7 +61,8 @@ def get_dot_separator_index(runs): def parse_duration(duration): - if duration is None: + # duration may be falsy or a single space: ' ' + if not duration or not duration.strip(): return duration mapped_increments = zip([1, 60, 3600], reversed(duration.split(":"))) seconds = sum(multiplier * int(time) for multiplier, time in mapped_increments) diff --git a/ytmusicapi/parsers/uploads.py b/ytmusicapi/parsers/uploads.py index 3f784d1..8caf8ec 100644 --- a/ytmusicapi/parsers/uploads.py +++ b/ytmusicapi/parsers/uploads.py @@ -30,7 +30,9 @@ def parse_uploaded_items(results): title = get_item_text(data, 0) like = nav(data, MENU_LIKE_STATUS) thumbnails = nav(data, THUMBNAILS) if "thumbnail" in data else None - duration = get_fixed_column_item(data, 0)["text"]["runs"][0]["text"] + duration = None + if "fixedColumns" in data: + duration = get_fixed_column_item(data, 0)["text"]["runs"][0]["text"] song = { "entityId": entityId, "videoId": videoId,