runapi-suno 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/PKG-INFO +11 -5
  2. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/README.md +7 -1
  3. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/pyproject.toml +4 -4
  4. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/client.py +2 -0
  5. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/contract_gen.py +36 -7
  6. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/__init__.py +2 -0
  7. runapi_suno-0.2.0/src/runapi/suno/resources/blend_lyrics.py +30 -0
  8. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/create_mashup.py +1 -4
  9. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/types.py +12 -0
  10. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/tests/test_client.py +21 -2
  11. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/__init__.py +0 -0
  12. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/_validators.py +0 -0
  13. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/py.typed +0 -0
  14. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/add_instrumental.py +0 -0
  15. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/add_vocals.py +0 -0
  16. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/boost_style.py +0 -0
  17. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/check_voice.py +0 -0
  18. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/convert_audio.py +0 -0
  19. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/cover_audio.py +0 -0
  20. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/extend_music.py +0 -0
  21. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/generate_artwork.py +0 -0
  22. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/generate_lyrics.py +0 -0
  23. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/generate_midi.py +0 -0
  24. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/generate_persona.py +0 -0
  25. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/generate_voice.py +0 -0
  26. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/get_timestamped_lyrics.py +0 -0
  27. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/regenerate_validation_phrase.py +0 -0
  28. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/replace_section.py +0 -0
  29. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/separate_audio_stems.py +0 -0
  30. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/text_to_music.py +0 -0
  31. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/text_to_sound.py +0 -0
  32. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/visualize_music.py +0 -0
  33. {runapi_suno-0.1.2 → runapi_suno-0.2.0}/src/runapi/suno/resources/voice_to_validation_phrase.py +0 -0
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runapi-suno
3
- Version: 0.1.2
4
- Summary: RunAPI Suno SDK for text-to-music, cover audio, music extension, stem separation, voice validation phrase, custom voice, and related audio workflows in JavaScript, Python, Ruby, Go, Java, and PHP
3
+ Version: 0.2.0
4
+ Summary: RunAPI Suno SDK for text-to-music, lyric generation and blending, cover audio, music extension, stem separation, voice validation phrase, custom voice, and related audio workflows in JavaScript, Python, Ruby, Go, Java, and PHP
5
5
  Project-URL: Homepage, https://runapi.ai/models/suno
6
6
  Project-URL: Documentation, https://runapi.ai/docs#sdk-suno
7
7
  Project-URL: Source, https://github.com/runapi-ai/suno-sdk
8
8
  Project-URL: Issues, https://github.com/runapi-ai/suno-sdk/issues
9
9
  Project-URL: Changelog, https://github.com/runapi-ai/suno-sdk/blob/main/CHANGELOG.md
10
- Project-URL: Release Notes, https://github.com/runapi-ai/suno-sdk/releases/tag/python%2Fv0.1.2
10
+ Project-URL: Release Notes, https://github.com/runapi-ai/suno-sdk/releases/tag/python%2Fv0.2.0
11
11
  Author-email: RunAPI <contact@runapi.ai>
12
12
  License-Expression: Apache-2.0
13
13
  Keywords: api,audio-generation,golang,gradle,java,maven,music-api,music-generation,python,ruby,runapi,runapi-ai,sdk,suno,suno-ai-api,typescript
14
14
  Requires-Python: >=3.9
15
- Requires-Dist: runapi-core>=0.2.0
15
+ Requires-Dist: runapi-core>=0.2.2
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  # Suno API Python SDK for RunAPI
@@ -45,6 +45,12 @@ print(result.audios[0].audio_url)
45
45
  # Generate lyrics from a prompt.
46
46
  lyrics = client.generate_lyrics.run(prompt="a song about the ocean at night")
47
47
  print(lyrics.lyrics[0].text)
48
+
49
+ blended = client.blend_lyrics.run(
50
+ lyrics_a="[Verse]\nCity lights fade into dawn",
51
+ lyrics_b="[Chorus]\nWe rise with the morning sun",
52
+ )
53
+ print(blended.lyrics[0].text)
48
54
  ```
49
55
 
50
56
  Use `create` to submit a task and return quickly, `get` to fetch the latest task state, and `run` when a script should create and poll until completion:
@@ -65,7 +71,7 @@ RunAPI-generated file URLs are temporary. Download and store generated images, v
65
71
 
66
72
  ## Language notes
67
73
 
68
- Pass parameters as keyword arguments and catch the `runapi.suno` error classes when building music jobs or scripts. The available resources are `text_to_music`, `extend_music`, `generate_artwork`, `cover_audio`, `add_instrumental`, `add_vocals`, `separate_audio_stems`, `generate_midi`, `convert_audio`, `visualize_music`, `generate_lyrics`, `get_timestamped_lyrics`, `replace_section`, `create_mashup`, `text_to_sound`, `voice_to_validation_phrase`, `regenerate_validation_phrase`, `generate_voice`, `check_voice`, `generate_persona`, and `boost_style`. Keep `RUNAPI_API_KEY` in the environment or your secret manager; never commit API keys or callback secrets.
74
+ Pass parameters as keyword arguments and catch the `runapi.suno` error classes when building music jobs or scripts. The available resources are `text_to_music`, `extend_music`, `generate_artwork`, `cover_audio`, `add_instrumental`, `add_vocals`, `separate_audio_stems`, `generate_midi`, `convert_audio`, `visualize_music`, `generate_lyrics`, `blend_lyrics`, `get_timestamped_lyrics`, `replace_section`, `create_mashup`, `text_to_sound`, `voice_to_validation_phrase`, `regenerate_validation_phrase`, `generate_voice`, `check_voice`, `generate_persona`, and `boost_style`. Keep `RUNAPI_API_KEY` in the environment or your secret manager; never commit API keys or callback secrets.
69
75
 
70
76
  ## Links
71
77
 
@@ -28,6 +28,12 @@ print(result.audios[0].audio_url)
28
28
  # Generate lyrics from a prompt.
29
29
  lyrics = client.generate_lyrics.run(prompt="a song about the ocean at night")
30
30
  print(lyrics.lyrics[0].text)
31
+
32
+ blended = client.blend_lyrics.run(
33
+ lyrics_a="[Verse]\nCity lights fade into dawn",
34
+ lyrics_b="[Chorus]\nWe rise with the morning sun",
35
+ )
36
+ print(blended.lyrics[0].text)
31
37
  ```
32
38
 
33
39
  Use `create` to submit a task and return quickly, `get` to fetch the latest task state, and `run` when a script should create and poll until completion:
@@ -48,7 +54,7 @@ RunAPI-generated file URLs are temporary. Download and store generated images, v
48
54
 
49
55
  ## Language notes
50
56
 
51
- Pass parameters as keyword arguments and catch the `runapi.suno` error classes when building music jobs or scripts. The available resources are `text_to_music`, `extend_music`, `generate_artwork`, `cover_audio`, `add_instrumental`, `add_vocals`, `separate_audio_stems`, `generate_midi`, `convert_audio`, `visualize_music`, `generate_lyrics`, `get_timestamped_lyrics`, `replace_section`, `create_mashup`, `text_to_sound`, `voice_to_validation_phrase`, `regenerate_validation_phrase`, `generate_voice`, `check_voice`, `generate_persona`, and `boost_style`. Keep `RUNAPI_API_KEY` in the environment or your secret manager; never commit API keys or callback secrets.
57
+ Pass parameters as keyword arguments and catch the `runapi.suno` error classes when building music jobs or scripts. The available resources are `text_to_music`, `extend_music`, `generate_artwork`, `cover_audio`, `add_instrumental`, `add_vocals`, `separate_audio_stems`, `generate_midi`, `convert_audio`, `visualize_music`, `generate_lyrics`, `blend_lyrics`, `get_timestamped_lyrics`, `replace_section`, `create_mashup`, `text_to_sound`, `voice_to_validation_phrase`, `regenerate_validation_phrase`, `generate_voice`, `check_voice`, `generate_persona`, and `boost_style`. Keep `RUNAPI_API_KEY` in the environment or your secret manager; never commit API keys or callback secrets.
52
58
 
53
59
  ## Links
54
60
 
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "runapi-suno"
7
- version = "0.1.2"
8
- description = "RunAPI Suno SDK for text-to-music, cover audio, music extension, stem separation, voice validation phrase, custom voice, and related audio workflows in JavaScript, Python, Ruby, Go, Java, and PHP"
7
+ version = "0.2.0"
8
+ description = "RunAPI Suno SDK for text-to-music, lyric generation and blending, cover audio, music extension, stem separation, voice validation phrase, custom voice, and related audio workflows in JavaScript, Python, Ruby, Go, Java, and PHP"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = "Apache-2.0"
12
12
  authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
13
13
  keywords = ["runapi", "runapi-ai", "suno", "api", "sdk", "typescript", "python", "ruby", "golang", "java", "maven", "gradle", "music-generation", "audio-generation", "music-api", "suno-ai-api"]
14
- dependencies = ["runapi-core>=0.2.0"]
14
+ dependencies = ["runapi-core>=0.2.2"]
15
15
 
16
16
  [project.urls]
17
17
  Homepage = "https://runapi.ai/models/suno"
@@ -19,7 +19,7 @@ Documentation = "https://runapi.ai/docs#sdk-suno"
19
19
  Source = "https://github.com/runapi-ai/suno-sdk"
20
20
  Issues = "https://github.com/runapi-ai/suno-sdk/issues"
21
21
  Changelog = "https://github.com/runapi-ai/suno-sdk/blob/main/CHANGELOG.md"
22
- "Release Notes" = "https://github.com/runapi-ai/suno-sdk/releases/tag/python%2Fv0.1.2"
22
+ "Release Notes" = "https://github.com/runapi-ai/suno-sdk/releases/tag/python%2Fv0.2.0"
23
23
 
24
24
  [tool.hatch.build.targets.wheel]
25
25
  packages = ["src/runapi"]
@@ -9,6 +9,7 @@ from runapi.core import ClientOptions, HttpClient, resolve_api_key
9
9
  from .resources.add_instrumental import AddInstrumental
10
10
  from .resources.add_vocals import AddVocals
11
11
  from .resources.boost_style import BoostStyle
12
+ from .resources.blend_lyrics import BlendLyrics
12
13
  from .resources.check_voice import CheckVoice
13
14
  from .resources.convert_audio import ConvertAudio
14
15
  from .resources.cover_audio import CoverAudio
@@ -58,6 +59,7 @@ class SunoClient:
58
59
  self.convert_audio = ConvertAudio(http)
59
60
  self.visualize_music = VisualizeMusic(http)
60
61
  self.generate_lyrics = GenerateLyrics(http)
62
+ self.blend_lyrics = BlendLyrics(http)
61
63
  self.get_timestamped_lyrics = GetTimestampedLyrics(http)
62
64
  self.replace_section = ReplaceSection(http)
63
65
  self.create_mashup = CreateMashup(http)
@@ -70,6 +70,19 @@ CONTRACT = {
70
70
  }
71
71
  }
72
72
  },
73
+ "blend-lyrics": {
74
+ "models": [],
75
+ "fields_by_model": {
76
+ "_": {
77
+ "lyrics_a": {
78
+ "required": True
79
+ },
80
+ "lyrics_b": {
81
+ "required": True
82
+ }
83
+ }
84
+ }
85
+ },
73
86
  "boost-style": {
74
87
  "models": [],
75
88
  "fields_by_model": {
@@ -235,7 +248,9 @@ CONTRACT = {
235
248
  "enum": ["style", "voice"]
236
249
  },
237
250
  "upload_url_list": {
238
- "required": True
251
+ "required": True,
252
+ "min_items": 2,
253
+ "max_items": 2
239
254
  },
240
255
  "vocal_gender": {
241
256
  "enum": ["male", "female"]
@@ -253,7 +268,9 @@ CONTRACT = {
253
268
  "enum": ["style", "voice"]
254
269
  },
255
270
  "upload_url_list": {
256
- "required": True
271
+ "required": True,
272
+ "min_items": 2,
273
+ "max_items": 2
257
274
  },
258
275
  "vocal_gender": {
259
276
  "enum": ["male", "female"]
@@ -271,7 +288,9 @@ CONTRACT = {
271
288
  "enum": ["style", "voice"]
272
289
  },
273
290
  "upload_url_list": {
274
- "required": True
291
+ "required": True,
292
+ "min_items": 2,
293
+ "max_items": 2
275
294
  },
276
295
  "vocal_gender": {
277
296
  "enum": ["male", "female"]
@@ -289,7 +308,9 @@ CONTRACT = {
289
308
  "enum": ["style", "voice"]
290
309
  },
291
310
  "upload_url_list": {
292
- "required": True
311
+ "required": True,
312
+ "min_items": 2,
313
+ "max_items": 2
293
314
  },
294
315
  "vocal_gender": {
295
316
  "enum": ["male", "female"]
@@ -307,7 +328,9 @@ CONTRACT = {
307
328
  "enum": ["style", "voice"]
308
329
  },
309
330
  "upload_url_list": {
310
- "required": True
331
+ "required": True,
332
+ "min_items": 2,
333
+ "max_items": 2
311
334
  },
312
335
  "vocal_gender": {
313
336
  "enum": ["male", "female"]
@@ -325,7 +348,9 @@ CONTRACT = {
325
348
  "enum": ["style", "voice"]
326
349
  },
327
350
  "upload_url_list": {
328
- "required": True
351
+ "required": True,
352
+ "min_items": 2,
353
+ "max_items": 2
329
354
  },
330
355
  "vocal_gender": {
331
356
  "enum": ["male", "female"]
@@ -458,7 +483,11 @@ CONTRACT = {
458
483
  "generate-lyrics": {
459
484
  "models": [],
460
485
  "fields_by_model": {
461
- "_": {}
486
+ "_": {
487
+ "prompt": {
488
+ "required": True
489
+ }
490
+ }
462
491
  }
463
492
  },
464
493
  "generate-midi": {
@@ -1,6 +1,7 @@
1
1
  from .add_instrumental import AddInstrumental
2
2
  from .add_vocals import AddVocals
3
3
  from .boost_style import BoostStyle
4
+ from .blend_lyrics import BlendLyrics
4
5
  from .check_voice import CheckVoice
5
6
  from .convert_audio import ConvertAudio
6
7
  from .cover_audio import CoverAudio
@@ -32,6 +33,7 @@ __all__ = [
32
33
  "ConvertAudio",
33
34
  "VisualizeMusic",
34
35
  "GenerateLyrics",
36
+ "BlendLyrics",
35
37
  "GetTimestampedLyrics",
36
38
  "ReplaceSection",
37
39
  "CreateMashup",
@@ -0,0 +1,30 @@
1
+ """Suno blend_lyrics resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from runapi.core import RequestOptions, Resource
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import BlendLyricsResponse, CompletedBlendLyricsResponse
11
+
12
+
13
+ class BlendLyrics(Resource):
14
+ """Blend two caller-authored lyrics texts."""
15
+
16
+ ENDPOINT = "/api/v1/suno/blend_lyrics"
17
+ RESPONSE_CLASS = BlendLyricsResponse
18
+ COMPLETED_RESPONSE_CLASS = CompletedBlendLyricsResponse
19
+
20
+ def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
21
+ task = self.create(options=options, **params)
22
+ return self._poll_until_complete(lambda: self.get(task.id, options=options))
23
+
24
+ def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
25
+ compacted = self._compact_params(params)
26
+ self._validate_contract(CONTRACT["blend-lyrics"], compacted)
27
+ return self._request("post", self.ENDPOINT, body=compacted, options=options)
28
+
29
+ def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
30
+ return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Dict, Optional
6
6
 
7
- from runapi.core import Resource, ValidationError, RequestOptions
7
+ from runapi.core import Resource, RequestOptions
8
8
 
9
9
  from ..contract_gen import CONTRACT
10
10
  from ..types import CompletedCreateMashupResponse, CreateMashupResponse
@@ -55,7 +55,4 @@ class CreateMashup(Resource):
55
55
  return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
56
56
 
57
57
  def _validate_params(self, params: Dict[str, Any]) -> None:
58
- upload_url_list = params.get("upload_url_list")
59
- if not (isinstance(upload_url_list, list) and len(upload_url_list) == 2):
60
- raise ValidationError("upload_url_list must contain exactly 2 URLs")
61
58
  self._validate_contract(CONTRACT["create-mashup"], params)
@@ -228,6 +228,12 @@ class GenerateLyricsResponse(AsyncTaskResponse):
228
228
  lyrics = optional([lambda: Lyric])
229
229
 
230
230
 
231
+ class BlendLyricsResponse(AsyncTaskResponse):
232
+ """Suno lyrics blending task status response."""
233
+
234
+ lyrics = optional([lambda: Lyric])
235
+
236
+
231
237
  class GetTimestampedLyricsResponse(BaseModel):
232
238
  """Suno timestamped-lyrics result."""
233
239
 
@@ -352,6 +358,12 @@ class CompletedGenerateLyricsResponse(GenerateLyricsResponse):
352
358
  lyrics = required([lambda: Lyric])
353
359
 
354
360
 
361
+ class CompletedBlendLyricsResponse(BlendLyricsResponse):
362
+ """Narrowed lyrics blending response once polling observes completion."""
363
+
364
+ lyrics = required([lambda: Lyric])
365
+
366
+
355
367
  class CompletedReplaceSectionResponse(ReplaceSectionResponse):
356
368
  """Narrowed replace-section response once polling observes completion."""
357
369
 
@@ -80,6 +80,7 @@ def test_exposes_all_resource_accessors():
80
80
  "convert_audio": R.ConvertAudio,
81
81
  "visualize_music": R.VisualizeMusic,
82
82
  "generate_lyrics": R.GenerateLyrics,
83
+ "blend_lyrics": R.BlendLyrics,
83
84
  "get_timestamped_lyrics": R.GetTimestampedLyrics,
84
85
  "replace_section": R.ReplaceSection,
85
86
  "create_mashup": R.CreateMashup,
@@ -91,7 +92,7 @@ def test_exposes_all_resource_accessors():
91
92
  "generate_persona": R.GeneratePersona,
92
93
  "boost_style": R.BoostStyle,
93
94
  }
94
- assert len(expected) == 21
95
+ assert len(expected) == 22
95
96
  for name, cls in expected.items():
96
97
  assert isinstance(getattr(client, name), cls), name
97
98
 
@@ -122,6 +123,24 @@ def test_get_fetches_by_id():
122
123
  assert fake.calls == [("get", "/api/v1/suno/text_to_music/t1", None)]
123
124
 
124
125
 
126
+ def test_blend_lyrics_posts_flat_lyrics_pair():
127
+ fake = FakeHttp({"id": "blend_1", "status": "processing"})
128
+ client = SunoClient(api_key="k", http_client=fake)
129
+
130
+ client.blend_lyrics.create(lyrics_a="First verse", lyrics_b="Second verse")
131
+
132
+ assert fake.calls == [
133
+ ("post", "/api/v1/suno/blend_lyrics", {"lyrics_a": "First verse", "lyrics_b": "Second verse"}),
134
+ ]
135
+
136
+
137
+ def test_blend_lyrics_requires_both_lyrics_texts():
138
+ client = SunoClient(api_key="k", http_client=FakeHttp())
139
+
140
+ with pytest.raises(ValidationError, match="lyrics_b"):
141
+ client.blend_lyrics.create(lyrics_a="First verse")
142
+
143
+
125
144
  def test_run_narrows_completed_type():
126
145
  fake = FakeHttp(
127
146
  {"id": "t1", "status": "pending"},
@@ -326,7 +345,7 @@ def test_replace_section_rejects_mixed_sources():
326
345
 
327
346
  def test_create_mashup_requires_two_urls():
328
347
  client = SunoClient(api_key="k", http_client=FakeHttp())
329
- with pytest.raises(ValidationError, match="upload_url_list must contain exactly 2 URLs"):
348
+ with pytest.raises(ValidationError, match="upload_url_list must contain between 2 and 2 items"):
330
349
  client.create_mashup.create(upload_url_list=["only-one"], model="suno-v5")
331
350
 
332
351