Skip to content

Commit

Permalink
Gracefully retrieve uploaded songs with a blank duration (#578) (#580)
Browse files Browse the repository at this point in the history
* add unit test that attempts to retrieve a song after its been uploaded

* prevent parsing errors when an uploaded song lacks a duration

* update unit test to delete/re-upload song if it already is uploaded

* format

* fix test

* add another sleep to end2end playlist

---------

Co-authored-by: sigma67 <ytmusicapi@gmail.com>
  • Loading branch information
apastel and sigma67 authored May 11, 2024
1 parent 614999c commit ae966bb
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 2 deletions.
1 change: 1 addition & 0 deletions tests/mixins/test_playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
35 changes: 35 additions & 0 deletions tests/mixins/test_uploads.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import tempfile
import time

import pytest

from tests.conftest import get_resource
from ytmusicapi.ytmusic import YTMusic


class TestUploads:
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion ytmusicapi/parsers/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion ytmusicapi/parsers/uploads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit ae966bb

Please sign in to comment.