runapi-suno 0.1.0

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.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class GetTimestampedLyrics
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/get_timestamped_lyrics"
10
+ RESPONSE_CLASS = Types::GetTimestampedLyricsResponse
11
+
12
+ def initialize(http)
13
+ @http = http
14
+ end
15
+
16
+ def run(**params)
17
+ params = compact_params(params)
18
+ validate_params!(params)
19
+ request(:post, ENDPOINT, body: params)
20
+ end
21
+
22
+ private
23
+
24
+ def validate_params!(params)
25
+ Validators.validate_get_timestamped_lyrics!(params, self)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class ReplaceSection
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/replace_section"
10
+ RESPONSE_CLASS = Types::ReplaceSectionResponse
11
+ COMPLETED_RESPONSE_CLASS = Types::CompletedReplaceSectionResponse
12
+
13
+ def initialize(http)
14
+ @http = http
15
+ end
16
+
17
+ def run(**params)
18
+ task = create(**params)
19
+ poll_until_complete { get(task.id) }
20
+ end
21
+
22
+ def create(**params)
23
+ params = compact_params(params)
24
+ validate_params!(params)
25
+ request(:post, ENDPOINT, body: params)
26
+ end
27
+
28
+ def get(id)
29
+ request(:get, "#{ENDPOINT}/#{id}")
30
+ end
31
+
32
+ private
33
+
34
+ def validate_params!(params)
35
+ Validators.validate_replace_section!(params, self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class SeparateAudioStems
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/separate_audio_stems"
10
+ RESPONSE_CLASS = Types::SeparateAudioStemsResponse
11
+ COMPLETED_RESPONSE_CLASS = Types::CompletedSeparateAudioStemsResponse
12
+
13
+ def initialize(http)
14
+ @http = http
15
+ end
16
+
17
+ def run(**params)
18
+ task = create(**params)
19
+ poll_until_complete { get(task.id) }
20
+ end
21
+
22
+ def create(**params)
23
+ params = compact_params(params)
24
+ validate_params!(params)
25
+ request(:post, ENDPOINT, body: params)
26
+ end
27
+
28
+ def get(id)
29
+ request(:get, "#{ENDPOINT}/#{id}")
30
+ end
31
+
32
+ private
33
+
34
+ def validate_params!(params)
35
+ Validators.validate_separate_audio_stems!(params, self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class TextToMusic
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/text_to_music"
10
+ RESPONSE_CLASS = Types::TextToMusicResponse
11
+ COMPLETED_RESPONSE_CLASS = Types::CompletedTextToMusicResponse
12
+
13
+ def initialize(http)
14
+ @http = http
15
+ end
16
+
17
+ def run(**params)
18
+ task = create(**params)
19
+ poll_until_complete { get(task.id) }
20
+ end
21
+
22
+ def create(**params)
23
+ params = compact_params(params)
24
+ validate_params!(params)
25
+ request(:post, ENDPOINT, body: params)
26
+ end
27
+
28
+ def get(id)
29
+ request(:get, "#{ENDPOINT}/#{id}")
30
+ end
31
+
32
+ private
33
+
34
+ def validate_params!(params)
35
+ Validators.validate_text_to_music!(params, self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class TextToSound
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/text_to_sound"
10
+ RESPONSE_CLASS = Types::TextToSoundResponse
11
+ COMPLETED_RESPONSE_CLASS = Types::CompletedTextToSoundResponse
12
+
13
+ def initialize(http)
14
+ @http = http
15
+ end
16
+
17
+ def run(**params)
18
+ task = create(**params)
19
+ poll_until_complete { get(task.id) }
20
+ end
21
+
22
+ def create(**params)
23
+ params = compact_params(params)
24
+ validate_params!(params)
25
+ request(:post, ENDPOINT, body: params)
26
+ end
27
+
28
+ def get(id)
29
+ request(:get, "#{ENDPOINT}/#{id}")
30
+ end
31
+
32
+ private
33
+
34
+ def validate_params!(params)
35
+ Validators.validate_text_to_sound!(params, self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Resources
6
+ class VisualizeMusic
7
+ include RunApi::Core::ResourceHelpers
8
+
9
+ ENDPOINT = "/api/v1/suno/visualize_music"
10
+ RESPONSE_CLASS = Types::VisualizeMusicResponse
11
+ COMPLETED_RESPONSE_CLASS = Types::CompletedVisualizeMusicResponse
12
+
13
+ def initialize(http)
14
+ @http = http
15
+ end
16
+
17
+ def run(**params)
18
+ task = create(**params)
19
+ poll_until_complete { get(task.id) }
20
+ end
21
+
22
+ def create(**params)
23
+ params = compact_params(params)
24
+ validate_params!(params)
25
+ request(:post, ENDPOINT, body: params)
26
+ end
27
+
28
+ def get(id)
29
+ request(:get, "#{ENDPOINT}/#{id}")
30
+ end
31
+
32
+ private
33
+
34
+ def validate_params!(params)
35
+ Validators.validate_visualize_music!(params, self)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Types
6
+ MODELS = %w[V5_5 V5 V4_5PLUS V4_5ALL V4_5 V4 V3_5].freeze
7
+ SOUND_MODELS = %w[V5 V5_5].freeze
8
+ SOUND_KEYS = %w[
9
+ Cm C#m Dm D#m Em Fm F#m Gm G#m Am A#m Bm
10
+ C C# D D# E F F# G G# A A# B
11
+ ].freeze
12
+ VOCAL_GENDERS = %w[f m].freeze
13
+ PERSONA_MODELS = %w[style_persona voice_persona].freeze
14
+ SEPARATE_AUDIO_STEMS_TYPES = %w[separate_vocal split_stem].freeze
15
+
16
+ class Audio < RunApi::Core::BaseModel
17
+ optional :id, String
18
+ optional :audio_url, String
19
+ optional :stream_audio_url, String
20
+ optional :image_url, String
21
+ optional :prompt, String
22
+ optional :model_name, String
23
+ optional :title, String
24
+ optional :tags, [ String ]
25
+ optional :duration, Numeric
26
+ end
27
+
28
+ class Cover < RunApi::Core::BaseModel
29
+ required :url, String
30
+ end
31
+
32
+ class AlignedWord < RunApi::Core::BaseModel
33
+ required :word, String
34
+ required :success
35
+ required :start_time, Numeric
36
+ required :end_time, Numeric
37
+ required :palign, Numeric
38
+ end
39
+
40
+ class SeparatedAudio < RunApi::Core::BaseModel
41
+ optional :vocal_url, String
42
+ optional :instrumental_url, String
43
+ optional :backing_vocals_url, String
44
+ optional :bass_url, String
45
+ optional :brass_url, String
46
+ optional :drums_url, String
47
+ optional :fx_url, String
48
+ optional :guitar_url, String
49
+ optional :keyboard_url, String
50
+ optional :percussion_url, String
51
+ optional :piano_url, String
52
+ optional :strings_url, String
53
+ optional :synth_url, String
54
+ optional :woodwinds_url, String
55
+ end
56
+
57
+ class MidiNote < RunApi::Core::BaseModel
58
+ required :pitch, Numeric
59
+ required :start_time, Numeric
60
+ required :end_time, Numeric
61
+ required :velocity, Numeric
62
+ end
63
+
64
+ class MidiInstrument < RunApi::Core::BaseModel
65
+ required :name, String
66
+ optional :notes, [ -> { MidiNote } ]
67
+ end
68
+
69
+ class Lyric < RunApi::Core::BaseModel
70
+ optional :title, String
71
+ required :text, String
72
+ end
73
+
74
+ class Persona < RunApi::Core::BaseModel
75
+ required :id, String
76
+ required :name, String
77
+ required :description, String
78
+ end
79
+
80
+ class AsyncTaskResponse < RunApi::Core::TaskResponse
81
+ required :id, String
82
+ required :status, String, enum: -> { RunApi::Core::TaskResponse::Status::ALL }
83
+ optional :generation_stage, String
84
+ optional :error, String
85
+ end
86
+
87
+ class TextToMusicResponse < AsyncTaskResponse
88
+ optional :audios, [ -> { Audio } ]
89
+ optional :audio_url, String
90
+ end
91
+
92
+ class ExtendMusicResponse < AsyncTaskResponse
93
+ optional :audios, [ -> { Audio } ]
94
+ optional :original_task_id, String
95
+ end
96
+
97
+ class GenerateArtworkResponse < AsyncTaskResponse
98
+ optional :covers, [ -> { Cover } ]
99
+ end
100
+
101
+ class CoverAudioResponse < AsyncTaskResponse
102
+ optional :audios, [ -> { Audio } ]
103
+ end
104
+
105
+ class AddInstrumentalResponse < TextToMusicResponse; end
106
+ class AddVocalsResponse < TextToMusicResponse; end
107
+ class TextToSoundResponse < TextToMusicResponse; end
108
+
109
+ class SeparateAudioStemsResponse < AsyncTaskResponse
110
+ optional :separated_audios, -> { SeparatedAudio }
111
+ end
112
+
113
+ class GenerateMidiResponse < AsyncTaskResponse
114
+ optional :instruments, [ -> { MidiInstrument } ]
115
+ end
116
+
117
+ class ConvertAudioResponse < AsyncTaskResponse
118
+ optional :wav_url, String
119
+ optional :original_task_id, String
120
+ end
121
+
122
+ class VisualizeMusicResponse < AsyncTaskResponse
123
+ optional :video_url, String
124
+ optional :original_task_id, String
125
+ end
126
+
127
+ class GenerateLyricsResponse < AsyncTaskResponse
128
+ optional :lyrics, [ -> { Lyric } ]
129
+ end
130
+
131
+ class GetTimestampedLyricsResponse < RunApi::Core::BaseModel
132
+ optional :aligned_words, [ -> { AlignedWord } ]
133
+ optional :waveform_data, [ Numeric ]
134
+ optional :hoot_cer, Numeric
135
+ optional :is_streamed
136
+ end
137
+
138
+ class ReplaceSectionResponse < AsyncTaskResponse
139
+ optional :track, -> { Audio }
140
+ optional :audios, [ -> { Audio } ]
141
+ end
142
+
143
+ class GeneratePersonaResponse < RunApi::Core::BaseModel
144
+ required :persona, -> { Persona }
145
+ optional :error, String
146
+ end
147
+
148
+ class BoostStyleResponse < RunApi::Core::BaseModel
149
+ optional :style, String
150
+ optional :error, String
151
+ end
152
+
153
+ class CreateMashupResponse < AsyncTaskResponse
154
+ optional :audio, -> { Audio }
155
+ optional :audios, [ -> { Audio } ]
156
+ end
157
+
158
+ class CompletedTextToMusicResponse < TextToMusicResponse
159
+ required :audios, [ -> { Audio } ]
160
+ end
161
+
162
+ class CompletedExtendMusicResponse < ExtendMusicResponse
163
+ required :audios, [ -> { Audio } ]
164
+ end
165
+
166
+ class CompletedGenerateArtworkResponse < GenerateArtworkResponse
167
+ required :covers, [ -> { Cover } ]
168
+ end
169
+
170
+ class CompletedCoverAudioResponse < CoverAudioResponse
171
+ required :audios, [ -> { Audio } ]
172
+ end
173
+
174
+ class CompletedAddInstrumentalResponse < AddInstrumentalResponse
175
+ required :audios, [ -> { Audio } ]
176
+ end
177
+
178
+ class CompletedAddVocalsResponse < AddVocalsResponse
179
+ required :audios, [ -> { Audio } ]
180
+ end
181
+
182
+ class CompletedSeparateAudioStemsResponse < SeparateAudioStemsResponse
183
+ required :separated_audios, -> { SeparatedAudio }
184
+ end
185
+
186
+ class CompletedGenerateMidiResponse < GenerateMidiResponse
187
+ required :instruments, [ -> { MidiInstrument } ]
188
+ end
189
+
190
+ class CompletedConvertAudioResponse < ConvertAudioResponse
191
+ required :wav_url, String
192
+ end
193
+
194
+ class CompletedVisualizeMusicResponse < VisualizeMusicResponse
195
+ required :video_url, String
196
+ end
197
+
198
+ class CompletedGenerateLyricsResponse < GenerateLyricsResponse
199
+ required :lyrics, [ -> { Lyric } ]
200
+ end
201
+
202
+ class CompletedReplaceSectionResponse < ReplaceSectionResponse
203
+ required :track, -> { Audio }
204
+ end
205
+
206
+ class CompletedCreateMashupResponse < CreateMashupResponse
207
+ required :audios, [ -> { Audio } ]
208
+ end
209
+
210
+ class CompletedTextToSoundResponse < TextToSoundResponse
211
+ required :audios, [ -> { Audio } ]
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RunApi
4
+ module Suno
5
+ module Validators
6
+ module_function
7
+
8
+ def validate_text_to_music!(params, resource)
9
+ if param(resource, params, :custom_mode)
10
+ require_param!(resource, params, :style)
11
+ require_param!(resource, params, :title)
12
+ else
13
+ require_param!(resource, params, :prompt)
14
+ end
15
+ require_param!(resource, params, :model)
16
+ validate_optional!(resource, params, :model, Types::MODELS)
17
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
18
+ validate_optional!(resource, params, :persona_model, Types::PERSONA_MODELS)
19
+ end
20
+
21
+ def validate_extend_music!(params, resource)
22
+ unless %i[task_id audio_id audio_url upload_url].any? { |key| param(resource, params, key) }
23
+ raise Core::ValidationError, "task_id, audio_id, audio_url, or upload_url is required"
24
+ end
25
+ require_param!(resource, params, :default_param_flag)
26
+ require_param!(resource, params, :model)
27
+
28
+ if truthy?(param(resource, params, :default_param_flag))
29
+ require_param!(resource, params, :style)
30
+ require_param!(resource, params, :title)
31
+ require_param!(resource, params, :continue_at)
32
+ end
33
+ validate_optional!(resource, params, :model, Types::MODELS)
34
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
35
+ validate_optional!(resource, params, :persona_model, Types::PERSONA_MODELS)
36
+ end
37
+
38
+ def validate_generate_artwork!(params, resource)
39
+ require_param!(resource, params, :task_id)
40
+ end
41
+
42
+ def validate_cover_audio!(params, resource)
43
+ require_param!(resource, params, :upload_url)
44
+ require_param!(resource, params, :model)
45
+ if param(resource, params, :custom_mode)
46
+ require_param!(resource, params, :style)
47
+ require_param!(resource, params, :title)
48
+ else
49
+ require_param!(resource, params, :prompt)
50
+ end
51
+ validate_optional!(resource, params, :model, Types::MODELS)
52
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
53
+ validate_optional!(resource, params, :persona_model, Types::PERSONA_MODELS)
54
+ end
55
+
56
+ def validate_add_instrumental!(params, resource)
57
+ require_all!(resource, params, :upload_url, :title, :negative_tags, :tags, :model)
58
+ validate_optional!(resource, params, :model, Types::MODELS)
59
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
60
+ end
61
+
62
+ def validate_add_vocals!(params, resource)
63
+ require_all!(resource, params, :upload_url, :prompt, :title, :negative_tags, :style, :model)
64
+ validate_optional!(resource, params, :model, Types::MODELS)
65
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
66
+ end
67
+
68
+ def validate_separate_audio_stems!(params, resource)
69
+ require_all!(resource, params, :task_id, :audio_id)
70
+ validate_optional!(resource, params, :type, Types::SEPARATE_AUDIO_STEMS_TYPES)
71
+ end
72
+
73
+ def validate_generate_midi!(params, resource)
74
+ require_param!(resource, params, :task_id)
75
+ end
76
+
77
+ def validate_convert_audio!(params, resource)
78
+ require_all!(resource, params, :task_id, :audio_id)
79
+ end
80
+
81
+ def validate_visualize_music!(params, resource)
82
+ require_all!(resource, params, :task_id, :audio_id)
83
+ end
84
+
85
+ def validate_generate_lyrics!(params, resource)
86
+ require_param!(resource, params, :prompt)
87
+ end
88
+
89
+ def validate_get_timestamped_lyrics!(params, resource)
90
+ require_all!(resource, params, :task_id, :audio_id)
91
+ end
92
+
93
+ def validate_replace_section!(params, resource)
94
+ require_all!(resource, params, :task_id, :audio_id, :prompt, :tags, :title, :infill_start_time, :infill_end_time)
95
+ if param(resource, params, :infill_end_time).to_f <= param(resource, params, :infill_start_time).to_f
96
+ raise Core::ValidationError, "infill_end_time must be greater than infill_start_time"
97
+ end
98
+ end
99
+
100
+ def validate_create_mashup!(params, resource)
101
+ upload_url_list = param(resource, params, :upload_url_list)
102
+ unless upload_url_list.is_a?(Array) && upload_url_list.size == 2
103
+ raise Core::ValidationError, "upload_url_list must contain exactly 2 URLs"
104
+ end
105
+ require_param!(resource, params, :model)
106
+ if param(resource, params, :custom_mode)
107
+ require_all!(resource, params, :style, :title)
108
+ require_param!(resource, params, :prompt) unless truthy?(param(resource, params, :instrumental))
109
+ else
110
+ require_param!(resource, params, :prompt)
111
+ end
112
+ validate_optional!(resource, params, :model, Types::MODELS)
113
+ validate_optional!(resource, params, :vocal_gender, Types::VOCAL_GENDERS)
114
+ end
115
+
116
+ def validate_text_to_sound!(params, resource)
117
+ require_all!(resource, params, :prompt, :model)
118
+ validate_optional!(resource, params, :model, Types::SOUND_MODELS)
119
+ validate_optional!(resource, params, :sound_key, Types::SOUND_KEYS)
120
+ tempo = param(resource, params, :sound_tempo)
121
+ if tempo && !(1..300).cover?(tempo.to_i)
122
+ raise Core::ValidationError, "sound_tempo must be between 1 and 300"
123
+ end
124
+ end
125
+
126
+ def validate_generate_persona!(params, resource)
127
+ require_all!(resource, params, :task_id, :audio_id, :name, :description)
128
+ end
129
+
130
+ def validate_boost_style!(params, resource)
131
+ require_param!(resource, params, :description)
132
+ end
133
+
134
+ def require_all!(resource, params, *keys)
135
+ keys.each { |key| require_param!(resource, params, key) }
136
+ end
137
+
138
+ def require_param!(resource, params, key)
139
+ raise Core::ValidationError, "#{key} is required" if param(resource, params, key).nil?
140
+ end
141
+
142
+ def param(resource, params, key)
143
+ resource.send(:param, params, key)
144
+ end
145
+
146
+ def validate_optional!(resource, params, key, allowed)
147
+ resource.send(:validate_optional!, params, key, allowed)
148
+ end
149
+
150
+ def truthy?(value)
151
+ [ true, 1, "1", "true", "TRUE", "True" ].include?(value)
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "runapi/core"
4
+ require_relative "suno/types"
5
+ require_relative "suno/validators"
6
+ require_relative "suno/resources/text_to_music"
7
+ require_relative "suno/resources/extend_music"
8
+ require_relative "suno/resources/generate_artwork"
9
+ require_relative "suno/resources/cover_audio"
10
+ require_relative "suno/resources/add_instrumental"
11
+ require_relative "suno/resources/add_vocals"
12
+ require_relative "suno/resources/separate_audio_stems"
13
+ require_relative "suno/resources/generate_midi"
14
+ require_relative "suno/resources/convert_audio"
15
+ require_relative "suno/resources/visualize_music"
16
+ require_relative "suno/resources/generate_lyrics"
17
+ require_relative "suno/resources/get_timestamped_lyrics"
18
+ require_relative "suno/resources/replace_section"
19
+ require_relative "suno/resources/create_mashup"
20
+ require_relative "suno/resources/text_to_sound"
21
+ require_relative "suno/resources/generate_persona"
22
+ require_relative "suno/resources/boost_style"
23
+ require_relative "suno/client"
24
+
25
+ module RunApi
26
+ module Suno
27
+ AuthenticationError = RunApi::Core::AuthenticationError
28
+ RateLimitError = RunApi::Core::RateLimitError
29
+ InsufficientCreditsError = RunApi::Core::InsufficientCreditsError
30
+ ValidationError = RunApi::Core::ValidationError
31
+ NotFoundError = RunApi::Core::NotFoundError
32
+ TaskFailedError = RunApi::Core::TaskFailedError
33
+ TaskTimeoutError = RunApi::Core::TaskTimeoutError
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "runapi/suno"