Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,25 @@ for song in songs:
`get_songs()`
- <b>Arguments</b>:
- <b>song_ids</b> (Optional[str]): A list of song IDs to fetch specific songs.
- <b>Returns</b>: A list of `Clip` objects representing the retrieved songs.
- <b>page</b> (Optional[int]): The page you want to fetch.
- <b>Returns</b>:
- <b>A list of `Clip` objects representing the retrieved songs.</b>
- <b>Amount of songs in your library</b>
- Example:
```python
songs = client.get_songs(song_ids="123,456")
songs, _ = client.get_songs(song_ids="123,456")
print(songs)
```
- Note: By default you will only get around 15-20 songs per "page" of the API, so you need to iterate through the pages to get all the songs available to you.
```python
songs_in_suno, songs_total = self.client.get_songs()
page = 0
while len(songs_in_suno) < songs_total:
page = page + 1
new_songs, _ = self.client.get_songs(page=page)
for s in new_songs:
songs_in_suno.append(s)
```
`set_visibility()`
- **Arguments**:
- **song_id** (str): The ID of the song to update.
Expand Down
85 changes: 62 additions & 23 deletions suno/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# © [2024] Malith-Rukshan. All rights reserved.
# Repository: https://github.com/Malith-Rukshan/Suno-API


from typing import List
from pydantic import BaseModel, ConfigDict

class ModelVersions:
Expand All @@ -17,17 +17,27 @@ class ModelVersions:
CHIRP_V3_5 = "chirp-v3-5"
AVAILABLE_MODELS = [CHIRP_V2_0, CHIRP_V3_0, CHIRP_V3_5]

class ClipMetadataHistory(BaseModel):
id: str | None = None
type: str | None = None
infill: bool | None = None
source: str | None = None
continue_at: float | None = None

class ClipMetadata(BaseModel):
tags: str | None = None
prompt: str | None = None
gpt_description_prompt: str | None = None
audio_prompt_id: str | None = None
history: str | None = None
history: List[ClipMetadataHistory] | None = None
concat_history: str | None = None
stem_from_id: str | None = None
type: str | None = None
duration: float | None = None
refund_credits: float | None = None
stream: bool | None = None
infill: bool | None = None
is_audio_upload_tos_accepted: bool | None = None
error_type: str | None = None
error_message: str | None = None

Expand Down Expand Up @@ -62,39 +72,68 @@ class Config:
protected_namespaces = ()
json_schema_extra = {
"example": {
"id": "124b735f-7fb0-42b9-8b35-761aed65a7f6",
"video_url": "",
"audio_url": "https://audiopipe.suno.ai/?item_id=124b735f-7fb0-42b9-8b35-761aed65a7f6",
"image_url": "https://cdn1.suno.ai/image_124b735f-7fb0-42b9-8b35-761aed65a7f6.png",
"image_large_url": "https://cdn1.suno.ai/image_large_124b735f-7fb0-42b9-8b35-761aed65a7f6.png",
"id": "aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa",
"video_url": "https://cdn1.suno.ai/aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa.mp4",
"audio_url": "https://cdn1.suno.ai/aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa.mp3",
"image_url": "https://cdn2.suno.ai/image_aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa.jpeg",
"image_large_url": "https://cdn2.suno.ai/image_large_aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa.jpeg",
"is_video_pending": False,
"major_model_version": "v3",
"major_model_version": "v3.5",
"model_name": "chirp-v3",
"metadata": {
"tags": "English men voice",
"prompt": "I found a love, for me\nDarling, just dive right in and follow my lead\nWell, I found a girl, beautiful and sweet\nOh, I never knew you were the someone waiting for me\n\n′Cause we were just kids when we fell in love\nNot knowing what it was\nI will not give you up this time\nBut darling, just kiss me slow\nYour heart is all I own\nAnd in your eyes, you're holding mine\n\nBaby, I′m dancing in the dark\nWith you between my arms\nBarefoot on the grass\nListening to our favourite song\nWhen you said you looked a mess\nI whispered underneath my breath\nBut you heard it\nDarling, you look perfect tonight",
"tags": "chuu chuu chuu, groovy, funky, pop, upbeat, funk, progressive",
"prompt": "",
"gpt_description_prompt": None,
"audio_prompt_id": None,
"history": None,
"audio_prompt_id": "aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa",
"history": [
{
"id": "aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa",
"type": "upload",
"infill": False,
"source": "web",
"continue_at": 46.83755102040816
},
{
"id": "aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaaa",
"type": "gen",
"infill": False,
"source": "web",
"continue_at": 192.96
}
],
"concat_history": None,
"stem_from_id": None,
"type": "gen",
"duration": None,
"refund_credits": None,
"duration": 12.34,
"refund_credits": False,
"stream": True,
"infill": False,
"has_vocal": False,
"is_audio_upload_tos_accepted": True,
"error_type": None,
"error_message": None
},
"is_liked": False,
"user_id": "2340653f-32cb-4343-artb-09203ty749e9",
"display_name": "Snonymous",
"handle": "anonymous",
"is_liked": True,
"user_id": "aaaaaaa-aaaa-aaaa-aaaaaaaaaaaaaaa",
"display_name": "demo",
"handle": "demo",
"is_handle_updated": False,
"avatar_image_url": "https://cdn1.suno.ai/defaultPink.webp",
"is_trashed": False,
"reaction": None,
"created_at": "2024-05-05T11:54:09.356Z",
"status": "streaming",
"title": "Perfect by Malith-Rukshan/Suno-API",
"play_count": 0,
"reaction": {
"clip": None,
"play_count": 7,
"skip_count": 0,
"flagged": False,
"flagged_reason": None,
"feedback_reason": None,
"reaction_type": None,
"updated_at": "2099-08-01T12:00:00.000Z"
},
"created_at": "2099-08-01T10:00:00.000Z",
"status": "complete",
"title": "demo",
"play_count": 7,
"upvote_count": 0,
"is_public": False
}
Expand Down
21 changes: 11 additions & 10 deletions suno/suno.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import random
import time
import logging
from typing import List, Optional
from typing import List, Optional, Tuple
import requests

from .models import ModelVersions, Clip, CreditsInfo
Expand Down Expand Up @@ -87,7 +87,7 @@ def _keep_alive(self, is_wait=False) -> None:
# Set New Token to Headers
self.client.headers['Authorization'] = f"Bearer {new_token}"

def _cehck_error(self, response):
def _check_for_error(self, response):
try:
resp = response.json()
if resp['detail']:
Expand Down Expand Up @@ -134,7 +134,7 @@ def generate(self, prompt, is_custom, tags="", title="", make_instrumental=False
f"{Suno.BASE_URL}/api/generate/v2/", json=payload)
logger.debug(response.text)

self._cehck_error(response)
self._check_for_error(response)

if response.status_code != 200:
logger.error("Audio Generate Failed ⁉️")
Expand Down Expand Up @@ -167,7 +167,7 @@ def _wait_for_audio(self, song_ids):
logger.info("Generated Audio Successfully ✅")
return last_clips

def get_songs(self, song_ids: List[str] | str = None) -> List[Clip]:
def get_songs(self, song_ids: List[str] | str = None, page: int = 0) -> Tuple[List[Clip], int]:
"""
Retrieve songs from the library. If song IDs are provided, fetches specific songs; otherwise, retrieves a general list of songs.

Expand All @@ -182,7 +182,7 @@ def get_songs(self, song_ids: List[str] | str = None) -> List[Clip]:
- To retrieve a list of all songs in the library: get_songs()
"""
self._keep_alive() # Ensure session is active
url = f"{Suno.BASE_URL}/api/feed/"
url = f"{Suno.BASE_URL}/api/feed/v2?page={page}"
if song_ids:
if isinstance(song_ids, list):
songIds = ",".join(song_ids)
Expand All @@ -192,8 +192,9 @@ def get_songs(self, song_ids: List[str] | str = None) -> List[Clip]:
logger.info("Getting Songs Info...")
response = self.client.get(url) # Call API
logger.debug(response.text)
self._cehck_error(response)
return response_to_clips(response.json())
self._check_for_error(response)
resp = response.json()
return response_to_clips(resp["clips"]), resp["num_total_results"]

def get_song(self, id: str) -> Clip:
"""
Expand All @@ -210,7 +211,7 @@ def get_song(self, id: str) -> Clip:
response = self.client.get(
f"{Suno.BASE_URL}/api/feed/?ids={id}") # Call API
logger.debug(response.text)
self._cehck_error(response)
self._check_for_error(response)
return create_clip_from_data(response.json()[0])

def set_visibility(self, song_id: str, is_public: bool) -> bool:
Expand Down Expand Up @@ -244,7 +245,7 @@ def get_credits(self) -> CreditsInfo:
response = self.client.get(
f"{Suno.BASE_URL}/api/billing/info/") # Call API
logger.debug(response.text)
self._cehck_error(response)
self._check_for_error(response)
if response.status_code == 200:
data = response.json()
result = {
Expand Down Expand Up @@ -301,4 +302,4 @@ def download(self, song: str | Clip, path: str = "./downloads",) -> str:
if chunk: # Filter out keep-alive chunks
f.write(chunk)
logger.info(f"Download complete: {filename}")
return filename
return filename