runapi-suno 0.2.6 → 0.2.8

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.
@@ -3,6 +3,7 @@
3
3
  module RunApi
4
4
  module Suno
5
5
  module Resources
6
+ # Step 1 of voice cloning: extracts a validation phrase from a voice recording for the user to re-record.
6
7
  class VoiceToValidationPhrase
7
8
  include RunApi::Core::ResourceHelpers
8
9
 
@@ -14,19 +15,19 @@ module RunApi
14
15
  @http = http
15
16
  end
16
17
 
17
- def run(**params)
18
- task = create(**params)
19
- poll_until_complete { get(task.id) }
18
+ def run(options: nil, **params)
19
+ task = create(options: options, **params)
20
+ poll_until_complete { get(task.id, options: options) }
20
21
  end
21
22
 
22
- def create(**params)
23
+ def create(options: nil, **params)
23
24
  params = compact_params(params)
24
25
  validate_params!(params)
25
- request(:post, ENDPOINT, body: params)
26
+ request(:post, ENDPOINT, body: params, options: options)
26
27
  end
27
28
 
28
- def get(id)
29
- request(:get, "#{ENDPOINT}/#{id}")
29
+ def get(id, options: nil)
30
+ request(:get, "#{ENDPOINT}/#{id}", options: options)
30
31
  end
31
32
 
32
33
  private
@@ -2,21 +2,32 @@
2
2
 
3
3
  module RunApi
4
4
  module Suno
5
+ # Suno type definitions, response models, and enum constants.
5
6
  module Types
7
+ # Suno music generation engine versions. V5.5 has highest quality; V4 is the earliest available.
6
8
  MODELS = %w[suno-v5.5 suno-v5 suno-v4.5-plus suno-v4.5-all suno-v4.5 suno-v4].freeze
9
+ # Model versions that support sound effect generation (subset of MODELS).
7
10
  SOUND_MODELS = %w[suno-v5 suno-v5.5].freeze
11
+ # Musical keys (major and minor) for sound effect generation.
8
12
  SOUND_KEYS = %w[
9
13
  Cm C#m Dm D#m Em Fm F#m Gm G#m Am A#m Bm
10
14
  C C# D D# E F F# G G# A A# B
11
15
  ].freeze
12
16
  VOCAL_GENDERS = %w[female male].freeze
17
+ # "style" applies genre/mood without changing voice; "voice" applies cloned voice characteristics.
13
18
  PERSONA_TYPES = %w[style voice].freeze
19
+ # "source" inherits settings from the original track; "custom" requires explicit values.
14
20
  PARAMETER_MODES = %w[source custom].freeze
21
+ # auto_lyrics: generates from prompt; exact_lyrics: sings literal lyrics; instrumental: no vocals.
15
22
  VOCAL_MODES = %w[auto_lyrics exact_lyrics instrumental].freeze
23
+ # separate_vocal: isolates vocals+instrumental; split_stem: splits into all individual instruments.
16
24
  SEPARATE_AUDIO_STEMS_TYPES = %w[separate_vocal split_stem].freeze
25
+ # Language for the voice-cloning validation phrase the user must read back.
17
26
  VALIDATION_PHRASE_LANGUAGES = %w[en zh es fr pt de ja ko hi ru].freeze
27
+ # Singing ability of the voice being cloned; calibrates model expectations.
18
28
  SINGER_SKILL_LEVELS = %w[beginner intermediate advanced professional].freeze
19
29
 
30
+ # Metadata and URLs for a generated music track.
20
31
  class Audio < RunApi::Core::BaseModel
21
32
  optional :id, String
22
33
  optional :audio_url, String
@@ -29,6 +40,7 @@ module RunApi
29
40
  optional :duration, Numeric
30
41
  end
31
42
 
43
+ # Metadata and URLs for a generated sound effect (distinct from music Audio).
32
44
  class SoundAudio < RunApi::Core::BaseModel
33
45
  optional :id, String
34
46
  optional :audio_url, String
@@ -41,10 +53,12 @@ module RunApi
41
53
  optional :duration, Numeric
42
54
  end
43
55
 
56
+ # A URL to a generated cover artwork image.
44
57
  class Cover < RunApi::Core::BaseModel
45
58
  required :url, String
46
59
  end
47
60
 
61
+ # Word-level timing alignment for a single word in a track.
48
62
  class AlignedWord < RunApi::Core::BaseModel
49
63
  required :word, String
50
64
  required :success
@@ -53,6 +67,7 @@ module RunApi
53
67
  required :palign, Numeric
54
68
  end
55
69
 
70
+ # URLs for each isolated instrument stem after separation. Only populated stems have URLs.
56
71
  class SeparatedAudio < RunApi::Core::BaseModel
57
72
  optional :vocal_url, String
58
73
  optional :instrumental_url, String
@@ -70,6 +85,7 @@ module RunApi
70
85
  optional :woodwinds_url, String
71
86
  end
72
87
 
88
+ # A single MIDI note event within an instrument track.
73
89
  class MidiNote < RunApi::Core::BaseModel
74
90
  required :pitch, Numeric
75
91
  required :start_time, Numeric
@@ -77,22 +93,26 @@ module RunApi
77
93
  required :velocity, Numeric
78
94
  end
79
95
 
96
+ # All notes for a single instrument extracted from a track.
80
97
  class MidiInstrument < RunApi::Core::BaseModel
81
98
  required :name, String
82
99
  optional :notes, [-> { MidiNote }]
83
100
  end
84
101
 
102
+ # A section of generated lyrics with an optional section title (e.g. "Chorus", "Verse 1").
85
103
  class Lyric < RunApi::Core::BaseModel
86
104
  optional :title, String
87
105
  required :text, String
88
106
  end
89
107
 
108
+ # A reusable style or voice persona, referenced by ID in generation params.
90
109
  class Persona < RunApi::Core::BaseModel
91
110
  required :id, String
92
111
  required :name, String
93
112
  required :description, String
94
113
  end
95
114
 
115
+ # Base response for all Suno async tasks, carrying lifecycle status and generation progress.
96
116
  class AsyncTaskResponse < RunApi::Core::TaskResponse
97
117
  required :id, String
98
118
  required :status, String, enum: -> { RunApi::Core::TaskResponse::Status::ALL }
@@ -100,53 +120,66 @@ module RunApi
100
120
  optional :error, String
101
121
  end
102
122
 
123
+ # Result of a text-to-music generation task.
103
124
  class TextToMusicResponse < AsyncTaskResponse
104
125
  optional :audios, [-> { Audio }]
105
126
  optional :audio_url, String
106
127
  end
107
128
 
129
+ # Result of a music extension task. +original_task_id+ references the source track.
108
130
  class ExtendMusicResponse < AsyncTaskResponse
109
131
  optional :audios, [-> { Audio }]
110
132
  optional :original_task_id, String
111
133
  end
112
134
 
135
+ # Result of an artwork generation task containing cover image URLs.
113
136
  class GenerateArtworkResponse < AsyncTaskResponse
114
137
  optional :covers, [-> { Cover }]
115
138
  end
116
139
 
140
+ # Result of a cover audio task.
117
141
  class CoverAudioResponse < AsyncTaskResponse
118
142
  optional :audios, [-> { Audio }]
119
143
  end
120
144
 
145
+ # Result of adding an instrumental backing track.
121
146
  class AddInstrumentalResponse < TextToMusicResponse; end
147
+ # Result of adding vocals to a track.
122
148
  class AddVocalsResponse < TextToMusicResponse; end
123
149
 
150
+ # Result of a sound effect generation task (uses SoundAudio instead of Audio).
124
151
  class TextToSoundResponse < AsyncTaskResponse
125
152
  optional :audios, [-> { SoundAudio }]
126
153
  end
127
154
 
155
+ # Result of a stem separation task.
128
156
  class SeparateAudioStemsResponse < AsyncTaskResponse
129
157
  optional :separated_audios, -> { SeparatedAudio }
130
158
  end
131
159
 
160
+ # Result of a MIDI extraction task with per-instrument note data.
132
161
  class GenerateMidiResponse < AsyncTaskResponse
133
162
  optional :instruments, [-> { MidiInstrument }]
134
163
  end
135
164
 
165
+ # Result of a WAV conversion task.
136
166
  class ConvertAudioResponse < AsyncTaskResponse
137
167
  optional :wav_url, String
138
168
  optional :original_task_id, String
139
169
  end
140
170
 
171
+ # Result of a music visualization task containing the generated video URL.
141
172
  class VisualizeMusicResponse < AsyncTaskResponse
142
173
  optional :video_url, String
143
174
  optional :original_task_id, String
144
175
  end
145
176
 
177
+ # Result of a lyrics generation task with sectioned lyrics.
146
178
  class GenerateLyricsResponse < AsyncTaskResponse
147
179
  optional :lyrics, [-> { Lyric }]
148
180
  end
149
181
 
182
+ # Synchronous response containing word-level timing data and waveform for a track.
150
183
  class GetTimestampedLyricsResponse < RunApi::Core::BaseModel
151
184
  optional :aligned_words, [-> { AlignedWord }]
152
185
  optional :waveform_data, [Numeric]
@@ -154,36 +187,43 @@ module RunApi
154
187
  optional :is_streamed
155
188
  end
156
189
 
190
+ # Result of a section replacement task.
157
191
  class ReplaceSectionResponse < AsyncTaskResponse
158
192
  optional :track, -> { Audio }
159
193
  optional :audios, [-> { Audio }]
160
194
  end
161
195
 
196
+ # Synchronous result of persona creation.
162
197
  class GeneratePersonaResponse < RunApi::Core::BaseModel
163
198
  required :persona, -> { Persona }
164
199
  optional :error, String
165
200
  end
166
201
 
202
+ # Synchronous result of style tag generation. +style+ contains the generated tags string.
167
203
  class BoostStyleResponse < RunApi::Core::BaseModel
168
204
  optional :style, String
169
205
  optional :error, String
170
206
  end
171
207
 
208
+ # Result of a mashup task.
172
209
  class CreateMashupResponse < AsyncTaskResponse
173
210
  optional :audio, -> { Audio }
174
211
  optional :audios, [-> { Audio }]
175
212
  end
176
213
 
214
+ # Result of a voice-cloning validation phrase task. The user must re-record this phrase.
177
215
  class ValidationPhraseResponse < AsyncTaskResponse
178
216
  optional :provider_status, String
179
217
  optional :validation_phrase, String
180
218
  end
181
219
 
220
+ # Result of a voice generation (training) task. +voice_id+ is usable in subsequent generation params.
182
221
  class VoiceGenerationResponse < AsyncTaskResponse
183
222
  optional :provider_status, String
184
223
  optional :voice_id, String
185
224
  end
186
225
 
226
+ # Synchronous result indicating whether a custom voice is ready for use.
187
227
  class CheckVoiceResponse < RunApi::Core::BaseModel
188
228
  optional :is_available
189
229
  optional :error, String
@@ -5,15 +5,8 @@ module RunApi
5
5
  module Validators
6
6
  module_function
7
7
 
8
- MUSIC_PROMPT_SHAPE_ERROR = "choose a valid vocal_mode: auto_lyrics, exact_lyrics, or instrumental"
9
-
10
8
  def validate_text_to_music!(params, resource)
11
- validate_music_prompt_shape!(params, resource)
12
- require_param!(resource, params, :model)
13
- validate_optional!(resource, params, :vocal_mode, Types::VOCAL_MODES)
14
- validate_optional!(resource, params, :model, Types::MODELS)
15
- validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
16
- validate_optional!(resource, params, :persona_type, Types::PERSONA_TYPES)
9
+ resource.send(:validate_contract!, CONTRACT["text-to-music"], params)
17
10
  end
18
11
 
19
12
  def validate_extend_music!(params, resource)
@@ -40,13 +33,7 @@ module RunApi
40
33
  end
41
34
 
42
35
  def validate_cover_audio!(params, resource)
43
- require_param!(resource, params, :upload_url)
44
- require_param!(resource, params, :model)
45
- validate_music_prompt_shape!(params, resource)
46
- validate_optional!(resource, params, :vocal_mode, Types::VOCAL_MODES)
47
- validate_optional!(resource, params, :model, Types::MODELS)
48
- validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
49
- validate_optional!(resource, params, :persona_type, Types::PERSONA_TYPES)
36
+ resource.send(:validate_contract!, CONTRACT["cover-audio"], params)
50
37
  end
51
38
 
52
39
  def validate_add_instrumental!(params, resource)
@@ -87,10 +74,18 @@ module RunApi
87
74
  end
88
75
 
89
76
  def validate_replace_section!(params, resource)
90
- require_all!(resource, params, :task_id, :audio_id, :lyrics, :tags, :title, :infill_start_time, :infill_end_time)
91
- if param(resource, params, :infill_end_time).to_f <= param(resource, params, :infill_start_time).to_f
77
+ resource.send(:validate_contract!, CONTRACT["replace-section"], params)
78
+ validate_replace_section_source!(params, resource)
79
+ start_time = replace_section_time!(params, resource, :infill_start_time)
80
+ end_time = replace_section_time!(params, resource, :infill_end_time)
81
+ if end_time <= start_time
92
82
  raise Core::ValidationError, "infill_end_time must be greater than infill_start_time"
93
83
  end
84
+
85
+ duration = end_time - start_time
86
+ return if duration.between?(6, 60)
87
+
88
+ raise Core::ValidationError, "replacement duration must be between 6 and 60 seconds"
94
89
  end
95
90
 
96
91
  def validate_create_mashup!(params, resource)
@@ -98,12 +93,7 @@ module RunApi
98
93
  unless upload_url_list.is_a?(Array) && upload_url_list.size == 2
99
94
  raise Core::ValidationError, "upload_url_list must contain exactly 2 URLs"
100
95
  end
101
- require_param!(resource, params, :model)
102
- validate_music_prompt_shape!(params, resource)
103
- validate_optional!(resource, params, :vocal_mode, Types::VOCAL_MODES)
104
- validate_optional!(resource, params, :model, Types::MODELS)
105
- validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
106
- validate_optional!(resource, params, :persona_type, Types::PERSONA_TYPES)
96
+ resource.send(:validate_contract!, CONTRACT["create-mashup"], params)
107
97
  end
108
98
 
109
99
  def validate_text_to_sound!(params, resource)
@@ -148,28 +138,6 @@ module RunApi
148
138
  require_param!(resource, params, :description)
149
139
  end
150
140
 
151
- def validate_music_prompt_shape!(params, resource)
152
- mode = param(resource, params, :vocal_mode).to_s
153
- has_prompt = truthy_presence?(param(resource, params, :prompt))
154
- has_lyrics = truthy_presence?(param(resource, params, :lyrics))
155
- has_style = truthy_presence?(param(resource, params, :style))
156
- has_title = truthy_presence?(param(resource, params, :title))
157
-
158
- valid_shape = case mode
159
- when "auto_lyrics"
160
- has_prompt && !has_lyrics && !has_style && !has_title
161
- when "exact_lyrics"
162
- !has_prompt && has_lyrics && has_style && has_title
163
- when "instrumental"
164
- !has_prompt && !has_lyrics && has_style && has_title
165
- else
166
- false
167
- end
168
- return if valid_shape
169
-
170
- raise Core::ValidationError, MUSIC_PROMPT_SHAPE_ERROR
171
- end
172
-
173
141
  def validate_extend_music_prompt_shape!(params, resource)
174
142
  return unless truthy_presence?(param(resource, params, :lyrics))
175
143
 
@@ -187,6 +155,30 @@ module RunApi
187
155
  raise Core::ValidationError, "lyrics can only be used when extending uploaded audio with custom parameters"
188
156
  end
189
157
 
158
+ def validate_replace_section_source!(params, resource)
159
+ has_existing_source = %i[task_id audio_id].any? { |key| truthy_presence?(param(resource, params, key)) }
160
+ has_uploaded_source = %i[upload_url model].any? { |key| truthy_presence?(param(resource, params, key)) }
161
+
162
+ if has_existing_source && has_uploaded_source
163
+ raise Core::ValidationError, "task_id/audio_id cannot be combined with upload_url/model"
164
+ end
165
+
166
+ if has_existing_source
167
+ require_all!(resource, params, :task_id, :audio_id)
168
+ elsif has_uploaded_source
169
+ require_all!(resource, params, :upload_url, :model)
170
+ else
171
+ raise Core::ValidationError, "task_id and audio_id, or upload_url and model are required"
172
+ end
173
+ end
174
+
175
+ def replace_section_time!(params, resource, key)
176
+ value = param(resource, params, key)
177
+ return value.to_f if value.is_a?(Numeric)
178
+
179
+ raise Core::ValidationError, "#{key} must be a number"
180
+ end
181
+
190
182
  def require_all!(resource, params, *keys)
191
183
  keys.each { |key| require_param!(resource, params, key) }
192
184
  end
data/lib/runapi/suno.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "runapi/core"
4
4
  require_relative "suno/types"
5
+ require_relative "suno/contract_gen"
5
6
  require_relative "suno/validators"
6
7
  require_relative "suno/resources/text_to_music"
7
8
  require_relative "suno/resources/extend_music"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runapi-suno
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - RunAPI
@@ -15,18 +15,18 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.2.5
18
+ version: 0.2.10
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.2.5
26
- description: The suno ai api Ruby SDK is the language-specific package for Suno on
27
- RunAPI. Use this suno ai api package for song generation, lyrics, vocal, extension,
28
- and audio transformation flows when your application needs JSON request bodies,
29
- task status lookup, and consistent RunAPI errors in Ruby.
25
+ version: 0.2.10
26
+ description: The Suno Ruby SDK is the language-specific package for Suno on RunAPI.
27
+ Use this package for song generation, lyrics, vocals, extension, and audio transformation
28
+ workflows when your application needs request bodies, task status lookup, and consistent
29
+ RunAPI errors in Ruby.
30
30
  email:
31
31
  - contact@runapi.ai
32
32
  executables: []
@@ -39,6 +39,7 @@ files:
39
39
  - lib/runapi-suno.rb
40
40
  - lib/runapi/suno.rb
41
41
  - lib/runapi/suno/client.rb
42
+ - lib/runapi/suno/contract_gen.rb
42
43
  - lib/runapi/suno/resources/add_instrumental.rb
43
44
  - lib/runapi/suno/resources/add_vocals.rb
44
45
  - lib/runapi/suno/resources/boost_style.rb
@@ -66,9 +67,11 @@ homepage: https://runapi.ai/models/suno
66
67
  licenses:
67
68
  - Apache-2.0
68
69
  metadata:
70
+ runapi_slug: suno
69
71
  homepage_uri: https://runapi.ai/models/suno
70
72
  documentation_uri: https://github.com/runapi-ai/suno-sdk/blob/main/ruby/README.md
71
73
  source_code_uri: https://github.com/runapi-ai/suno-sdk
74
+ bug_tracker_uri: https://github.com/runapi-ai/suno-sdk/issues
72
75
  changelog_uri: https://github.com/runapi-ai/suno-sdk/blob/main/CHANGELOG.md
73
76
  rdoc_options: []
74
77
  require_paths:
@@ -86,5 +89,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
89
  requirements: []
87
90
  rubygems_version: 4.0.10
88
91
  specification_version: 4
89
- summary: Suno AI API Ruby SDK for RunAPI
92
+ summary: Suno API Ruby SDK for RunAPI
90
93
  test_files: []