runapi-suno 0.1.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.
- runapi_suno-0.1.0/.gitignore +29 -0
- runapi_suno-0.1.0/PKG-INFO +103 -0
- runapi_suno-0.1.0/README.md +90 -0
- runapi_suno-0.1.0/pyproject.toml +33 -0
- runapi_suno-0.1.0/src/runapi/suno/__init__.py +24 -0
- runapi_suno-0.1.0/src/runapi/suno/_validators.py +208 -0
- runapi_suno-0.1.0/src/runapi/suno/client.py +70 -0
- runapi_suno-0.1.0/src/runapi/suno/contract_gen.py +723 -0
- runapi_suno-0.1.0/src/runapi/suno/py.typed +0 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/__init__.py +45 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/add_instrumental.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/add_vocals.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/boost_style.py +34 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/check_voice.py +34 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/convert_audio.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/cover_audio.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/create_mashup.py +61 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/extend_music.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/generate_artwork.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/generate_lyrics.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/generate_midi.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/generate_persona.py +34 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/generate_voice.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/get_timestamped_lyrics.py +34 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/regenerate_validation_phrase.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/replace_section.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/separate_audio_stems.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/text_to_music.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/text_to_sound.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/visualize_music.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/resources/voice_to_validation_phrase.py +58 -0
- runapi_suno-0.1.0/src/runapi/suno/types.py +366 -0
- runapi_suno-0.1.0/tests/test_client.py +320 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Build artifacts
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
|
|
7
|
+
# Bytecode
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
|
|
15
|
+
# uv
|
|
16
|
+
uv.lock
|
|
17
|
+
|
|
18
|
+
# Test / type caches
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# IDE / OS
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
*.swp
|
|
29
|
+
.DS_Store
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runapi-suno
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Suno music, sound, lyrics, and voice client for RunAPI
|
|
5
|
+
Project-URL: Homepage, https://runapi.ai/models/suno
|
|
6
|
+
Project-URL: Documentation, https://runapi.ai/docs#sdk-suno
|
|
7
|
+
Author-email: RunAPI <contact@runapi.ai>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Keywords: ai,audio,lyrics,music,runapi,sdk,suno,voice
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: runapi-core
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Suno Python SDK for RunAPI
|
|
15
|
+
|
|
16
|
+
The Suno Python SDK is the language-specific package for Suno on RunAPI. Use it
|
|
17
|
+
for music, sound effect, lyrics, stem, MIDI, and custom voice flows when your
|
|
18
|
+
application needs JSON request bodies, task status lookup, and consistent RunAPI
|
|
19
|
+
errors in Python.
|
|
20
|
+
|
|
21
|
+
For model details, use https://runapi.ai/models/suno; for API reference, use
|
|
22
|
+
https://runapi.ai/docs#suno; for SDK docs, use https://runapi.ai/docs#sdk-suno.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install runapi-suno
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from runapi.suno import SunoClient
|
|
34
|
+
|
|
35
|
+
client = SunoClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
|
|
36
|
+
|
|
37
|
+
# Generate music and poll until completion.
|
|
38
|
+
result = client.text_to_music.run(
|
|
39
|
+
model="suno-v4.5-plus",
|
|
40
|
+
vocal_mode="auto_lyrics",
|
|
41
|
+
prompt="A chill lo-fi beat with soft vocals",
|
|
42
|
+
)
|
|
43
|
+
print(result.audios[0].audio_url)
|
|
44
|
+
|
|
45
|
+
# Generate lyrics from a prompt.
|
|
46
|
+
lyrics = client.generate_lyrics.run(prompt="a song about the ocean at night")
|
|
47
|
+
print(lyrics.lyrics[0].text)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `create` to submit a task and return quickly, `get` to fetch the latest task
|
|
51
|
+
state, and `run` to create and poll until completion:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
task = client.text_to_music.create(
|
|
55
|
+
model="suno-v4.5-plus",
|
|
56
|
+
vocal_mode="instrumental",
|
|
57
|
+
style="ambient piano",
|
|
58
|
+
title="Quiet Morning",
|
|
59
|
+
)
|
|
60
|
+
status = client.text_to_music.get(task.id)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
In web request handlers, prefer `create` plus webhook or later `get` polling so a
|
|
64
|
+
worker is not held open.
|
|
65
|
+
|
|
66
|
+
RunAPI-generated file URLs are temporary. Download and store generated audio,
|
|
67
|
+
images, and videos in your own durable storage within 7 days; do not treat
|
|
68
|
+
returned URLs as long-term assets.
|
|
69
|
+
|
|
70
|
+
## Resources
|
|
71
|
+
|
|
72
|
+
The client exposes one accessor per resource:
|
|
73
|
+
|
|
74
|
+
- Music: `text_to_music`, `extend_music`, `cover_audio`, `add_instrumental`,
|
|
75
|
+
`add_vocals`, `replace_section`, `create_mashup`
|
|
76
|
+
- Sound and lyrics: `text_to_sound`, `generate_lyrics`, `get_timestamped_lyrics`
|
|
77
|
+
- Audio tooling: `separate_audio_stems`, `generate_midi`, `convert_audio`,
|
|
78
|
+
`visualize_music`, `generate_artwork`
|
|
79
|
+
- Voice and style: `voice_to_validation_phrase`, `regenerate_validation_phrase`,
|
|
80
|
+
`generate_voice`, `check_voice`, `generate_persona`, `boost_style`
|
|
81
|
+
|
|
82
|
+
Music, sound, stem, MIDI, conversion, visualization, artwork, replace-section,
|
|
83
|
+
mashup, validation-phrase, and voice resources are asynchronous: call `run` to
|
|
84
|
+
create and poll, or `create` plus `get`. Lyrics lookup, persona, voice check,
|
|
85
|
+
and style boost return their result directly from `run`.
|
|
86
|
+
|
|
87
|
+
## Language notes
|
|
88
|
+
|
|
89
|
+
Pass parameters as keyword arguments and catch the `runapi.suno` error classes
|
|
90
|
+
when building audio jobs or scripts. Keep `RUNAPI_API_KEY` in the environment or
|
|
91
|
+
your secret manager; never commit API keys or callback secrets.
|
|
92
|
+
|
|
93
|
+
## Links
|
|
94
|
+
|
|
95
|
+
- Model page: https://runapi.ai/models/suno
|
|
96
|
+
- SDK docs: https://runapi.ai/docs#sdk-suno
|
|
97
|
+
- Product docs: https://runapi.ai/docs#suno
|
|
98
|
+
- Pricing and rate limits: https://runapi.ai/models/suno
|
|
99
|
+
- Full catalog: https://runapi.ai/models
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Suno Python SDK for RunAPI
|
|
2
|
+
|
|
3
|
+
The Suno Python SDK is the language-specific package for Suno on RunAPI. Use it
|
|
4
|
+
for music, sound effect, lyrics, stem, MIDI, and custom voice flows when your
|
|
5
|
+
application needs JSON request bodies, task status lookup, and consistent RunAPI
|
|
6
|
+
errors in Python.
|
|
7
|
+
|
|
8
|
+
For model details, use https://runapi.ai/models/suno; for API reference, use
|
|
9
|
+
https://runapi.ai/docs#suno; for SDK docs, use https://runapi.ai/docs#sdk-suno.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install runapi-suno
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from runapi.suno import SunoClient
|
|
21
|
+
|
|
22
|
+
client = SunoClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
|
|
23
|
+
|
|
24
|
+
# Generate music and poll until completion.
|
|
25
|
+
result = client.text_to_music.run(
|
|
26
|
+
model="suno-v4.5-plus",
|
|
27
|
+
vocal_mode="auto_lyrics",
|
|
28
|
+
prompt="A chill lo-fi beat with soft vocals",
|
|
29
|
+
)
|
|
30
|
+
print(result.audios[0].audio_url)
|
|
31
|
+
|
|
32
|
+
# Generate lyrics from a prompt.
|
|
33
|
+
lyrics = client.generate_lyrics.run(prompt="a song about the ocean at night")
|
|
34
|
+
print(lyrics.lyrics[0].text)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Use `create` to submit a task and return quickly, `get` to fetch the latest task
|
|
38
|
+
state, and `run` to create and poll until completion:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
task = client.text_to_music.create(
|
|
42
|
+
model="suno-v4.5-plus",
|
|
43
|
+
vocal_mode="instrumental",
|
|
44
|
+
style="ambient piano",
|
|
45
|
+
title="Quiet Morning",
|
|
46
|
+
)
|
|
47
|
+
status = client.text_to_music.get(task.id)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
In web request handlers, prefer `create` plus webhook or later `get` polling so a
|
|
51
|
+
worker is not held open.
|
|
52
|
+
|
|
53
|
+
RunAPI-generated file URLs are temporary. Download and store generated audio,
|
|
54
|
+
images, and videos in your own durable storage within 7 days; do not treat
|
|
55
|
+
returned URLs as long-term assets.
|
|
56
|
+
|
|
57
|
+
## Resources
|
|
58
|
+
|
|
59
|
+
The client exposes one accessor per resource:
|
|
60
|
+
|
|
61
|
+
- Music: `text_to_music`, `extend_music`, `cover_audio`, `add_instrumental`,
|
|
62
|
+
`add_vocals`, `replace_section`, `create_mashup`
|
|
63
|
+
- Sound and lyrics: `text_to_sound`, `generate_lyrics`, `get_timestamped_lyrics`
|
|
64
|
+
- Audio tooling: `separate_audio_stems`, `generate_midi`, `convert_audio`,
|
|
65
|
+
`visualize_music`, `generate_artwork`
|
|
66
|
+
- Voice and style: `voice_to_validation_phrase`, `regenerate_validation_phrase`,
|
|
67
|
+
`generate_voice`, `check_voice`, `generate_persona`, `boost_style`
|
|
68
|
+
|
|
69
|
+
Music, sound, stem, MIDI, conversion, visualization, artwork, replace-section,
|
|
70
|
+
mashup, validation-phrase, and voice resources are asynchronous: call `run` to
|
|
71
|
+
create and poll, or `create` plus `get`. Lyrics lookup, persona, voice check,
|
|
72
|
+
and style boost return their result directly from `run`.
|
|
73
|
+
|
|
74
|
+
## Language notes
|
|
75
|
+
|
|
76
|
+
Pass parameters as keyword arguments and catch the `runapi.suno` error classes
|
|
77
|
+
when building audio jobs or scripts. Keep `RUNAPI_API_KEY` in the environment or
|
|
78
|
+
your secret manager; never commit API keys or callback secrets.
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- Model page: https://runapi.ai/models/suno
|
|
83
|
+
- SDK docs: https://runapi.ai/docs#sdk-suno
|
|
84
|
+
- Product docs: https://runapi.ai/docs#suno
|
|
85
|
+
- Pricing and rate limits: https://runapi.ai/models/suno
|
|
86
|
+
- Full catalog: https://runapi.ai/models
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runapi-suno"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Suno music, sound, lyrics, and voice client for RunAPI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
|
|
13
|
+
keywords = ["runapi", "suno", "music", "audio", "lyrics", "voice", "ai", "sdk"]
|
|
14
|
+
dependencies = ["runapi-core"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://runapi.ai/models/suno"
|
|
18
|
+
Documentation = "https://runapi.ai/docs#sdk-suno"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/runapi"]
|
|
22
|
+
|
|
23
|
+
[tool.uv]
|
|
24
|
+
package = true
|
|
25
|
+
|
|
26
|
+
[tool.uv.sources]
|
|
27
|
+
runapi-core = { workspace = true }
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = ["pytest>=8"]
|
|
31
|
+
|
|
32
|
+
[tool.runapi]
|
|
33
|
+
slug = "suno"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Suno client for RunAPI."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
InsufficientCreditsError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
TaskFailedError,
|
|
9
|
+
TaskTimeoutError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .client import SunoClient
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"SunoClient",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"RateLimitError",
|
|
19
|
+
"InsufficientCreditsError",
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ValidationError",
|
|
22
|
+
"TaskFailedError",
|
|
23
|
+
"TaskTimeoutError",
|
|
24
|
+
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Shared Suno request validators.
|
|
2
|
+
|
|
3
|
+
Ported from the Ruby ``RunApi::Suno::Validators`` module. Each ``validate_*``
|
|
4
|
+
function mirrors its Ruby counterpart, including the exact ``ValidationError``
|
|
5
|
+
message strings. Resources call these from their ``_validate_params`` hook.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, Sequence
|
|
11
|
+
|
|
12
|
+
from runapi.core import ValidationError
|
|
13
|
+
|
|
14
|
+
from . import types
|
|
15
|
+
|
|
16
|
+
_TRUTHY_VALUES = [True, 1, "1", "true", "TRUE", "True"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _param(params: Dict[str, Any], key: str) -> Any:
|
|
20
|
+
return params.get(key)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _truthy(value: Any) -> bool:
|
|
24
|
+
return value in _TRUTHY_VALUES
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _truthy_presence(value: Any) -> bool:
|
|
28
|
+
if hasattr(value, "__len__"):
|
|
29
|
+
return len(value) > 0
|
|
30
|
+
return value is not None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _to_i(value: Any) -> int:
|
|
34
|
+
try:
|
|
35
|
+
return int(value)
|
|
36
|
+
except (TypeError, ValueError):
|
|
37
|
+
if isinstance(value, str):
|
|
38
|
+
digits = ""
|
|
39
|
+
for ch in value.strip():
|
|
40
|
+
if ch in "+-" and not digits:
|
|
41
|
+
digits += ch
|
|
42
|
+
elif ch.isdigit():
|
|
43
|
+
digits += ch
|
|
44
|
+
else:
|
|
45
|
+
break
|
|
46
|
+
try:
|
|
47
|
+
return int(digits)
|
|
48
|
+
except ValueError:
|
|
49
|
+
return 0
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _to_f(value: Any) -> float:
|
|
54
|
+
try:
|
|
55
|
+
return float(value)
|
|
56
|
+
except (TypeError, ValueError):
|
|
57
|
+
return 0.0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def require_param(params: Dict[str, Any], key: str) -> None:
|
|
61
|
+
if _param(params, key) is None:
|
|
62
|
+
raise ValidationError(f"{key} is required")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def require_all(params: Dict[str, Any], *keys: str) -> None:
|
|
66
|
+
for key in keys:
|
|
67
|
+
require_param(params, key)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def validate_optional(params: Dict[str, Any], key: str, allowed: Sequence[Any]) -> None:
|
|
71
|
+
value = params.get(key)
|
|
72
|
+
if value is None:
|
|
73
|
+
return
|
|
74
|
+
if value not in allowed:
|
|
75
|
+
joined = ", ".join(str(option) for option in allowed)
|
|
76
|
+
raise ValidationError(f"Invalid {key}: {value}. Must be one of: {joined}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def validate_extend_music_prompt_shape(params: Dict[str, Any]) -> None:
|
|
80
|
+
if not _truthy_presence(_param(params, "lyrics")):
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
if _truthy_presence(_param(params, "prompt")):
|
|
84
|
+
raise ValidationError("prompt cannot be combined with lyrics")
|
|
85
|
+
|
|
86
|
+
if _truthy(_param(params, "instrumental")):
|
|
87
|
+
raise ValidationError("lyrics cannot be used when instrumental is true")
|
|
88
|
+
|
|
89
|
+
upload_mode = any(_truthy_presence(_param(params, key)) for key in ("audio_url", "upload_url"))
|
|
90
|
+
if _param(params, "parameter_mode") == "custom" and upload_mode:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
raise ValidationError("lyrics can only be used when extending uploaded audio with custom parameters")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def validate_extend_music(params: Dict[str, Any]) -> None:
|
|
97
|
+
if not any(_param(params, key) for key in ("task_id", "audio_id", "audio_url", "upload_url")):
|
|
98
|
+
raise ValidationError("task_id, audio_id, audio_url, or upload_url is required")
|
|
99
|
+
require_param(params, "parameter_mode")
|
|
100
|
+
require_param(params, "model")
|
|
101
|
+
|
|
102
|
+
validate_optional(params, "parameter_mode", types.PARAMETER_MODES)
|
|
103
|
+
if _param(params, "parameter_mode") == "custom":
|
|
104
|
+
require_param(params, "style")
|
|
105
|
+
require_param(params, "title")
|
|
106
|
+
require_param(params, "continue_at")
|
|
107
|
+
validate_extend_music_prompt_shape(params)
|
|
108
|
+
validate_optional(params, "model", types.MODELS)
|
|
109
|
+
validate_optional(params, "vocal_gender", types.VOCAL_GENDERS)
|
|
110
|
+
validate_optional(params, "persona_type", types.PERSONA_TYPES)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def validate_generate_artwork(params: Dict[str, Any]) -> None:
|
|
114
|
+
require_param(params, "task_id")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def validate_add_instrumental(params: Dict[str, Any]) -> None:
|
|
118
|
+
require_all(params, "upload_url", "title", "negative_tags", "tags", "model")
|
|
119
|
+
validate_optional(params, "model", types.MODELS)
|
|
120
|
+
validate_optional(params, "vocal_gender", types.VOCAL_GENDERS)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def validate_add_vocals(params: Dict[str, Any]) -> None:
|
|
124
|
+
require_all(params, "upload_url", "lyrics", "title", "negative_tags", "style", "model")
|
|
125
|
+
validate_optional(params, "model", types.MODELS)
|
|
126
|
+
validate_optional(params, "vocal_gender", types.VOCAL_GENDERS)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def validate_separate_audio_stems(params: Dict[str, Any]) -> None:
|
|
130
|
+
require_all(params, "task_id", "audio_id")
|
|
131
|
+
validate_optional(params, "type", types.SEPARATE_AUDIO_STEMS_TYPES)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def validate_generate_midi(params: Dict[str, Any]) -> None:
|
|
135
|
+
require_param(params, "task_id")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validate_convert_audio(params: Dict[str, Any]) -> None:
|
|
139
|
+
require_all(params, "task_id", "audio_id")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def validate_visualize_music(params: Dict[str, Any]) -> None:
|
|
143
|
+
require_all(params, "task_id", "audio_id")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def validate_generate_lyrics(params: Dict[str, Any]) -> None:
|
|
147
|
+
require_param(params, "prompt")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate_get_timestamped_lyrics(params: Dict[str, Any]) -> None:
|
|
151
|
+
require_all(params, "task_id", "audio_id")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def validate_replace_section(params: Dict[str, Any]) -> None:
|
|
155
|
+
require_all(
|
|
156
|
+
params,
|
|
157
|
+
"task_id",
|
|
158
|
+
"audio_id",
|
|
159
|
+
"lyrics",
|
|
160
|
+
"tags",
|
|
161
|
+
"title",
|
|
162
|
+
"infill_start_time",
|
|
163
|
+
"infill_end_time",
|
|
164
|
+
)
|
|
165
|
+
if _to_f(_param(params, "infill_end_time")) <= _to_f(_param(params, "infill_start_time")):
|
|
166
|
+
raise ValidationError("infill_end_time must be greater than infill_start_time")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def validate_text_to_sound(params: Dict[str, Any]) -> None:
|
|
170
|
+
require_all(params, "prompt", "model")
|
|
171
|
+
validate_optional(params, "model", types.SOUND_MODELS)
|
|
172
|
+
validate_optional(params, "sound_key", types.SOUND_KEYS)
|
|
173
|
+
tempo = _param(params, "sound_tempo")
|
|
174
|
+
if tempo is not None and not (1 <= _to_i(tempo) <= 300):
|
|
175
|
+
raise ValidationError("sound_tempo must be between 1 and 300")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def validate_voice_to_validation_phrase(params: Dict[str, Any]) -> None:
|
|
179
|
+
require_all(params, "voice_url", "vocal_start_seconds", "vocal_end_seconds")
|
|
180
|
+
validate_optional(params, "language", types.VALIDATION_PHRASE_LANGUAGES)
|
|
181
|
+
|
|
182
|
+
start_seconds = _to_i(_param(params, "vocal_start_seconds"))
|
|
183
|
+
end_seconds = _to_i(_param(params, "vocal_end_seconds"))
|
|
184
|
+
if end_seconds > start_seconds:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
raise ValidationError("vocal_end_seconds must be greater than vocal_start_seconds")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def validate_regenerate_validation_phrase(params: Dict[str, Any]) -> None:
|
|
191
|
+
require_param(params, "task_id")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def validate_generate_voice(params: Dict[str, Any]) -> None:
|
|
195
|
+
require_all(params, "task_id", "verify_url")
|
|
196
|
+
validate_optional(params, "singer_skill_level", types.SINGER_SKILL_LEVELS)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def validate_check_voice(params: Dict[str, Any]) -> None:
|
|
200
|
+
require_param(params, "task_id")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def validate_generate_persona(params: Dict[str, Any]) -> None:
|
|
204
|
+
require_all(params, "task_id", "audio_id", "name", "description")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def validate_boost_style(params: Dict[str, Any]) -> None:
|
|
208
|
+
require_param(params, "description")
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Suno client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import ClientOptions, HttpClient, resolve_api_key
|
|
8
|
+
|
|
9
|
+
from .resources.add_instrumental import AddInstrumental
|
|
10
|
+
from .resources.add_vocals import AddVocals
|
|
11
|
+
from .resources.boost_style import BoostStyle
|
|
12
|
+
from .resources.check_voice import CheckVoice
|
|
13
|
+
from .resources.convert_audio import ConvertAudio
|
|
14
|
+
from .resources.cover_audio import CoverAudio
|
|
15
|
+
from .resources.create_mashup import CreateMashup
|
|
16
|
+
from .resources.extend_music import ExtendMusic
|
|
17
|
+
from .resources.generate_artwork import GenerateArtwork
|
|
18
|
+
from .resources.generate_lyrics import GenerateLyrics
|
|
19
|
+
from .resources.generate_midi import GenerateMidi
|
|
20
|
+
from .resources.generate_persona import GeneratePersona
|
|
21
|
+
from .resources.generate_voice import GenerateVoice
|
|
22
|
+
from .resources.get_timestamped_lyrics import GetTimestampedLyrics
|
|
23
|
+
from .resources.regenerate_validation_phrase import RegenerateValidationPhrase
|
|
24
|
+
from .resources.replace_section import ReplaceSection
|
|
25
|
+
from .resources.separate_audio_stems import SeparateAudioStems
|
|
26
|
+
from .resources.text_to_music import TextToMusic
|
|
27
|
+
from .resources.text_to_sound import TextToSound
|
|
28
|
+
from .resources.visualize_music import VisualizeMusic
|
|
29
|
+
from .resources.voice_to_validation_phrase import VoiceToValidationPhrase
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SunoClient:
|
|
33
|
+
"""Suno music, sound, lyrics, and voice client.
|
|
34
|
+
|
|
35
|
+
Example::
|
|
36
|
+
|
|
37
|
+
client = SunoClient(api_key="sk-...")
|
|
38
|
+
result = client.text_to_music.run(
|
|
39
|
+
prompt="A chill lo-fi beat with soft vocals",
|
|
40
|
+
model="suno-v4.5-plus",
|
|
41
|
+
vocal_mode="auto_lyrics",
|
|
42
|
+
)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
|
|
46
|
+
resolved_api_key = resolve_api_key(api_key)
|
|
47
|
+
client_options = ClientOptions(api_key=resolved_api_key, **options)
|
|
48
|
+
http = client_options.http_client or HttpClient(client_options)
|
|
49
|
+
|
|
50
|
+
self.text_to_music = TextToMusic(http)
|
|
51
|
+
self.extend_music = ExtendMusic(http)
|
|
52
|
+
self.generate_artwork = GenerateArtwork(http)
|
|
53
|
+
self.cover_audio = CoverAudio(http)
|
|
54
|
+
self.add_instrumental = AddInstrumental(http)
|
|
55
|
+
self.add_vocals = AddVocals(http)
|
|
56
|
+
self.separate_audio_stems = SeparateAudioStems(http)
|
|
57
|
+
self.generate_midi = GenerateMidi(http)
|
|
58
|
+
self.convert_audio = ConvertAudio(http)
|
|
59
|
+
self.visualize_music = VisualizeMusic(http)
|
|
60
|
+
self.generate_lyrics = GenerateLyrics(http)
|
|
61
|
+
self.get_timestamped_lyrics = GetTimestampedLyrics(http)
|
|
62
|
+
self.replace_section = ReplaceSection(http)
|
|
63
|
+
self.create_mashup = CreateMashup(http)
|
|
64
|
+
self.text_to_sound = TextToSound(http)
|
|
65
|
+
self.voice_to_validation_phrase = VoiceToValidationPhrase(http)
|
|
66
|
+
self.regenerate_validation_phrase = RegenerateValidationPhrase(http)
|
|
67
|
+
self.generate_voice = GenerateVoice(http)
|
|
68
|
+
self.check_voice = CheckVoice(http)
|
|
69
|
+
self.generate_persona = GeneratePersona(http)
|
|
70
|
+
self.boost_style = BoostStyle(http)
|