runway-ruby 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.
- checksums.yaml +7 -0
- data/.tool-versions +1 -0
- data/LICENSE.txt +21 -0
- data/OPENAPI_VALIDATION.md +100 -0
- data/README.md +789 -0
- data/Rakefile +11 -0
- data/lib/runway_ml/character_performance.rb +106 -0
- data/lib/runway_ml/client.rb +84 -0
- data/lib/runway_ml/contract_validator.rb +151 -0
- data/lib/runway_ml/errors.rb +179 -0
- data/lib/runway_ml/image_processor.rb +128 -0
- data/lib/runway_ml/image_to_video.rb +158 -0
- data/lib/runway_ml/media_processor.rb +343 -0
- data/lib/runway_ml/openapi_spec_loader.rb +120 -0
- data/lib/runway_ml/organization.rb +89 -0
- data/lib/runway_ml/sound_effect.rb +57 -0
- data/lib/runway_ml/spec_coverage_analyzer.rb +134 -0
- data/lib/runway_ml/speech_to_speech.rb +90 -0
- data/lib/runway_ml/task.rb +116 -0
- data/lib/runway_ml/test_client.rb +62 -0
- data/lib/runway_ml/text_to_speech.rb +52 -0
- data/lib/runway_ml/text_to_video.rb +103 -0
- data/lib/runway_ml/uploads.rb +74 -0
- data/lib/runway_ml/validator_builder.rb +95 -0
- data/lib/runway_ml/validators/base_validators.rb +338 -0
- data/lib/runway_ml/validators/character_performance_validator.rb +41 -0
- data/lib/runway_ml/validators/gen3a_turbo_validator.rb +48 -0
- data/lib/runway_ml/validators/gen4_turbo_validator.rb +50 -0
- data/lib/runway_ml/validators/prompt_image_validator.rb +97 -0
- data/lib/runway_ml/validators/sound_effect_validator.rb +27 -0
- data/lib/runway_ml/validators/speech_to_speech_validator.rb +57 -0
- data/lib/runway_ml/validators/text_to_speech_validator.rb +28 -0
- data/lib/runway_ml/validators/text_veo3_stable_validator.rb +35 -0
- data/lib/runway_ml/validators/text_veo3_validator.rb +33 -0
- data/lib/runway_ml/validators/uploads_validator.rb +44 -0
- data/lib/runway_ml/validators/veo3_stable_validator.rb +43 -0
- data/lib/runway_ml/validators/veo3_validator.rb +46 -0
- data/lib/runway_ml/validators/voice_dubbing_validator.rb +128 -0
- data/lib/runway_ml/validators/voice_isolation_validator.rb +35 -0
- data/lib/runway_ml/validators/voice_validator.rb +32 -0
- data/lib/runway_ml/version.rb +5 -0
- data/lib/runway_ml/voice_dubbing.rb +74 -0
- data/lib/runway_ml/voice_isolation.rb +57 -0
- data/lib/runway_ml.rb +81 -0
- data/lib/tasks/openapi.rake +33 -0
- metadata +90 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
require_relative "voice_validator"
|
|
5
|
+
|
|
6
|
+
module RunwayML
|
|
7
|
+
module Validators
|
|
8
|
+
class SpeechToSpeechValidator
|
|
9
|
+
def initialize
|
|
10
|
+
@voice_validator = VoiceValidator.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def validate(media:, voice:, remove_background_noise:)
|
|
14
|
+
errors = {}
|
|
15
|
+
|
|
16
|
+
validate_media(media, errors)
|
|
17
|
+
voice_validator.validate(voice, errors)
|
|
18
|
+
validate_remove_background_noise(remove_background_noise, errors)
|
|
19
|
+
|
|
20
|
+
{ errors: errors }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :voice_validator
|
|
26
|
+
|
|
27
|
+
def validate_media(media, errors)
|
|
28
|
+
if media.nil?
|
|
29
|
+
errors[:media] = "cannot be empty"
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
unless media.is_a?(Hash)
|
|
34
|
+
errors[:media] = "must be a hash"
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
media_type = media[:type]
|
|
39
|
+
media_uri = media[:uri]
|
|
40
|
+
|
|
41
|
+
unless SpeechToSpeech::VALID_MEDIA_TYPES.include?(media_type)
|
|
42
|
+
errors[:media] = "type must be one of: #{SpeechToSpeech::VALID_MEDIA_TYPES.join(', ')}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if media_uri.nil? || media_uri.empty?
|
|
46
|
+
errors[:media] = "uri cannot be empty" if errors[:media].nil?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate_remove_background_noise(remove_background_noise, errors)
|
|
51
|
+
unless [ true, false ].include?(remove_background_noise)
|
|
52
|
+
errors[:remove_background_noise] = "must be a boolean"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
require_relative "voice_validator"
|
|
5
|
+
|
|
6
|
+
module RunwayML
|
|
7
|
+
module Validators
|
|
8
|
+
class TextToSpeechValidator
|
|
9
|
+
def initialize
|
|
10
|
+
@prompt_text_validator = PromptTextValidator.new(max_length: 1000, required: true)
|
|
11
|
+
@voice_validator = VoiceValidator.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def validate(prompt_text:, voice:)
|
|
15
|
+
errors = {}
|
|
16
|
+
|
|
17
|
+
prompt_text_validator.validate(prompt_text, errors)
|
|
18
|
+
voice_validator.validate(voice, errors)
|
|
19
|
+
|
|
20
|
+
{ errors: errors }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :prompt_text_validator, :voice_validator
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
|
|
5
|
+
module RunwayML
|
|
6
|
+
module Validators
|
|
7
|
+
class TextVeo3StableValidator
|
|
8
|
+
def initialize
|
|
9
|
+
@ratio_validator = RatioValidator.new(
|
|
10
|
+
valid_ratios: [ "1280:720", "720:1280", "1080:1920", "1920:1080" ]
|
|
11
|
+
)
|
|
12
|
+
@prompt_text_validator = PromptTextValidator.new(max_length: 1000, required: true)
|
|
13
|
+
@duration_validator = DurationValidator.new(exact: 8)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate(prompt_text:, ratio:, duration:, audio: nil)
|
|
17
|
+
errors = {}
|
|
18
|
+
|
|
19
|
+
ratio_validator.validate(ratio, errors)
|
|
20
|
+
prompt_text_validator.validate(prompt_text, errors)
|
|
21
|
+
duration_validator.validate(duration, errors)
|
|
22
|
+
|
|
23
|
+
if !audio.nil?
|
|
24
|
+
errors[:audio] = "is not supported for model veo3"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
{ errors: errors }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :ratio_validator, :prompt_text_validator, :duration_validator
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
|
|
5
|
+
module RunwayML
|
|
6
|
+
module Validators
|
|
7
|
+
class TextVeo3Validator
|
|
8
|
+
def initialize
|
|
9
|
+
@ratio_validator = RatioValidator.new(
|
|
10
|
+
valid_ratios: [ "1280:720", "720:1280", "1080:1920", "1920:1080" ]
|
|
11
|
+
)
|
|
12
|
+
@prompt_text_validator = PromptTextValidator.new(max_length: 1000, required: true)
|
|
13
|
+
@duration_validator = DurationValidator.new(valid_values: [ 4, 6, 8 ])
|
|
14
|
+
@audio_validator = AudioValidator.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate(prompt_text:, ratio:, duration:, audio: nil)
|
|
18
|
+
errors = {}
|
|
19
|
+
|
|
20
|
+
ratio_validator.validate(ratio, errors)
|
|
21
|
+
prompt_text_validator.validate(prompt_text, errors)
|
|
22
|
+
duration_validator.validate(duration, errors)
|
|
23
|
+
audio_validator.validate(audio, errors)
|
|
24
|
+
|
|
25
|
+
{ errors: errors }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :ratio_validator, :prompt_text_validator, :duration_validator, :audio_validator
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RunwayML
|
|
4
|
+
module Validators
|
|
5
|
+
class UploadsValidator
|
|
6
|
+
def initialize
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def validate(filename:)
|
|
10
|
+
errors = {}
|
|
11
|
+
|
|
12
|
+
validate_filename(filename, errors)
|
|
13
|
+
|
|
14
|
+
{ errors: errors }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def validate_filename(filename, errors)
|
|
20
|
+
unless filename.is_a?(String)
|
|
21
|
+
errors[:filename] = "must be a string"
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if filename.nil? || filename.empty?
|
|
26
|
+
errors[:filename] = "cannot be empty"
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if filename.length < 3 || filename.length > 255
|
|
31
|
+
errors[:filename] = "must be between 3 and 255 characters"
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Extract extension
|
|
36
|
+
extension = filename.split(".").last&.downcase
|
|
37
|
+
unless extension && Uploads::SUPPORTED_EXTENSIONS.key?(extension)
|
|
38
|
+
errors[:filename] = "must have a valid extension. Supported: #{Uploads::SUPPORTED_EXTENSIONS.keys.join(', ')}"
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
require_relative "prompt_image_validator"
|
|
5
|
+
|
|
6
|
+
module RunwayML
|
|
7
|
+
module Validators
|
|
8
|
+
class Veo3StableValidator
|
|
9
|
+
def initialize(image_processor:)
|
|
10
|
+
@image_processor = image_processor
|
|
11
|
+
@ratio_validator = RatioValidator.new(
|
|
12
|
+
valid_ratios: [ "1280:720", "720:1280", "1080:1920", "1920:1080" ]
|
|
13
|
+
)
|
|
14
|
+
@prompt_text_validator = PromptTextValidator.new(max_length: 1000, required: false)
|
|
15
|
+
@duration_validator = DurationValidator.new(exact: 8)
|
|
16
|
+
@prompt_image_validator = PromptImageValidator.new(
|
|
17
|
+
image_processor: image_processor,
|
|
18
|
+
array_size: 1,
|
|
19
|
+
valid_positions: [ "first" ]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate(prompt_image:, prompt_text:, ratio:, duration:)
|
|
24
|
+
errors = {}
|
|
25
|
+
|
|
26
|
+
ratio_validator.validate(ratio, errors)
|
|
27
|
+
prompt_text_validator.validate(prompt_text, errors)
|
|
28
|
+
duration_validator.validate(duration, errors)
|
|
29
|
+
|
|
30
|
+
# Process and validate prompt_image
|
|
31
|
+
processed_prompt_image = image_processor.process(prompt_image, errors)
|
|
32
|
+
prompt_image_validator.validate(processed_prompt_image, errors)
|
|
33
|
+
|
|
34
|
+
{ errors: errors, processed_prompt_image: processed_prompt_image }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :image_processor, :ratio_validator, :prompt_text_validator,
|
|
40
|
+
:duration_validator, :prompt_image_validator
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
require_relative "prompt_image_validator"
|
|
5
|
+
|
|
6
|
+
module RunwayML
|
|
7
|
+
module Validators
|
|
8
|
+
class Veo3Validator
|
|
9
|
+
def initialize(image_processor:)
|
|
10
|
+
@image_processor = image_processor
|
|
11
|
+
@ratio_validator = RatioValidator.new(
|
|
12
|
+
valid_ratios: [ "1280:720", "720:1280", "1080:1920", "1920:1080" ]
|
|
13
|
+
)
|
|
14
|
+
@prompt_text_validator = PromptTextValidator.new(max_length: 1000, required: true)
|
|
15
|
+
@duration_validator = DurationValidator.new(valid_values: [ 4, 6, 8 ])
|
|
16
|
+
@audio_validator = AudioValidator.new
|
|
17
|
+
@prompt_image_validator = PromptImageValidator.new(
|
|
18
|
+
image_processor: image_processor,
|
|
19
|
+
array_size: 1..2,
|
|
20
|
+
valid_positions: [ "first", "last" ],
|
|
21
|
+
only_first_frame: true
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def validate(prompt_image:, prompt_text:, ratio:, duration:, audio:)
|
|
26
|
+
errors = {}
|
|
27
|
+
|
|
28
|
+
ratio_validator.validate(ratio, errors)
|
|
29
|
+
prompt_text_validator.validate(prompt_text, errors)
|
|
30
|
+
duration_validator.validate(duration, errors)
|
|
31
|
+
audio_validator.validate(audio, errors)
|
|
32
|
+
|
|
33
|
+
# Process and validate prompt_image
|
|
34
|
+
processed_prompt_image = image_processor.process(prompt_image, errors)
|
|
35
|
+
prompt_image_validator.validate(processed_prompt_image, errors)
|
|
36
|
+
|
|
37
|
+
{ errors: errors, processed_prompt_image: processed_prompt_image }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :image_processor, :ratio_validator, :prompt_text_validator,
|
|
43
|
+
:duration_validator, :audio_validator, :prompt_image_validator
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
|
|
5
|
+
module RunwayML
|
|
6
|
+
module Validators
|
|
7
|
+
class VoiceDubbingValidator
|
|
8
|
+
def initialize
|
|
9
|
+
@audio_uri_validator = AudioUriValidator.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validate(audio_uri:, target_lang:, disable_voice_cloning:, drop_background_audio:, num_speakers:)
|
|
13
|
+
errors = {}
|
|
14
|
+
|
|
15
|
+
validate_audio_uri(audio_uri, errors)
|
|
16
|
+
validate_target_lang(target_lang, errors)
|
|
17
|
+
validate_disable_voice_cloning(disable_voice_cloning, errors)
|
|
18
|
+
validate_drop_background_audio(drop_background_audio, errors)
|
|
19
|
+
validate_num_speakers(num_speakers, errors)
|
|
20
|
+
|
|
21
|
+
{ errors: errors }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :audio_uri_validator
|
|
27
|
+
|
|
28
|
+
def validate_audio_uri(audio_uri, errors)
|
|
29
|
+
if audio_uri.nil? || (audio_uri.is_a?(String) && audio_uri.empty?)
|
|
30
|
+
errors[:audio_uri] = "cannot be empty"
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
audio_uri_validator.validate(audio_uri, errors, field: :audio_uri)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_target_lang(target_lang, errors)
|
|
38
|
+
if target_lang.nil? || target_lang.empty?
|
|
39
|
+
errors[:target_lang] = "cannot be empty"
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless VoiceDubbing::VALID_TARGET_LANGS.include?(target_lang)
|
|
44
|
+
errors[:target_lang] = "must be one of: #{VoiceDubbing::VALID_TARGET_LANGS.join(', ')}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_disable_voice_cloning(disable_voice_cloning, errors)
|
|
49
|
+
return if disable_voice_cloning.nil?
|
|
50
|
+
|
|
51
|
+
unless [ true, false ].include?(disable_voice_cloning)
|
|
52
|
+
errors[:disable_voice_cloning] = "must be a boolean"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_drop_background_audio(drop_background_audio, errors)
|
|
57
|
+
return if drop_background_audio.nil?
|
|
58
|
+
|
|
59
|
+
unless [ true, false ].include?(drop_background_audio)
|
|
60
|
+
errors[:drop_background_audio] = "must be a boolean"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_num_speakers(num_speakers, errors)
|
|
65
|
+
return if num_speakers.nil?
|
|
66
|
+
|
|
67
|
+
unless num_speakers.is_a?(Integer)
|
|
68
|
+
errors[:num_speakers] = "must be an integer"
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if num_speakers < 0 || num_speakers > 9007199254740991
|
|
73
|
+
errors[:num_speakers] = "must be between 0 and 9007199254740991"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class AudioUriValidator
|
|
79
|
+
VALID_CONTENT_TYPES = [ "audio/mpeg", "audio/mp3", "audio/wav", "audio/flac", "audio/m4a", "audio/aac", "audio/ogg", "audio/webm" ].freeze
|
|
80
|
+
|
|
81
|
+
def validate(uri, errors, field: :audio_uri)
|
|
82
|
+
return unless uri.is_a?(String)
|
|
83
|
+
|
|
84
|
+
if uri.length < 13
|
|
85
|
+
errors[field] = "URI must be at least 13 characters"
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if uri.start_with?("https://")
|
|
90
|
+
validate_https_url(uri, errors, field)
|
|
91
|
+
elsif uri.start_with?("runway://")
|
|
92
|
+
validate_runway_uri(uri, errors, field)
|
|
93
|
+
elsif uri.start_with?("data:audio/")
|
|
94
|
+
validate_data_uri(uri, errors, field)
|
|
95
|
+
else
|
|
96
|
+
errors[field] = "must be a valid HTTPS URL, Runway URI (runway://), or data URI (data:audio/)"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def validate_https_url(uri, errors, field)
|
|
103
|
+
if uri.length > 2048
|
|
104
|
+
errors[field] = "HTTPS URL must be at most 2048 characters"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def validate_runway_uri(uri, errors, field)
|
|
109
|
+
if uri.length > 5000
|
|
110
|
+
errors[field] = "Runway URI must be at most 5000 characters"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def validate_data_uri(uri, errors, field)
|
|
115
|
+
if uri.length > 16777216
|
|
116
|
+
errors[field] = "Data URI must be at most 16777216 characters"
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
content_type_match = uri.match(/^data:(audio\/[^;,]+)/)
|
|
121
|
+
return unless content_type_match
|
|
122
|
+
|
|
123
|
+
content_type = content_type_match[1]
|
|
124
|
+
errors[field] = "unsupported audio type '#{content_type}'" unless VALID_CONTENT_TYPES.include?(content_type)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
require_relative "voice_dubbing_validator"
|
|
5
|
+
|
|
6
|
+
module RunwayML
|
|
7
|
+
module Validators
|
|
8
|
+
class VoiceIsolationValidator
|
|
9
|
+
def initialize
|
|
10
|
+
@audio_uri_validator = AudioUriValidator.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def validate(audio_uri:)
|
|
14
|
+
errors = {}
|
|
15
|
+
|
|
16
|
+
validate_audio_uri(audio_uri, errors)
|
|
17
|
+
|
|
18
|
+
{ errors: errors }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :audio_uri_validator
|
|
24
|
+
|
|
25
|
+
def validate_audio_uri(audio_uri, errors)
|
|
26
|
+
if audio_uri.nil? || (audio_uri.is_a?(String) && audio_uri.empty?)
|
|
27
|
+
errors[:audio_uri] = "cannot be empty"
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
audio_uri_validator.validate(audio_uri, errors, field: :audio_uri)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_validators"
|
|
4
|
+
|
|
5
|
+
module RunwayML
|
|
6
|
+
module Validators
|
|
7
|
+
class VoiceValidator
|
|
8
|
+
def validate(voice, errors)
|
|
9
|
+
if voice.nil?
|
|
10
|
+
errors[:voice] = "cannot be empty"
|
|
11
|
+
return
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
unless voice.is_a?(Hash)
|
|
15
|
+
errors[:voice] = "must be a hash"
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
voice_type = voice[:type]
|
|
20
|
+
preset_id = voice[:presetId]
|
|
21
|
+
|
|
22
|
+
unless SpeechToSpeech::VALID_VOICE_TYPES.include?(voice_type)
|
|
23
|
+
errors[:voice] = "type must be one of: #{SpeechToSpeech::VALID_VOICE_TYPES.join(', ')}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
unless SpeechToSpeech::VALID_PRESET_IDS.include?(preset_id)
|
|
27
|
+
errors[:voice] = "presetId must be one of: #{SpeechToSpeech::VALID_PRESET_IDS.join(', ')}" if errors[:voice].nil?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "media_processor"
|
|
5
|
+
require_relative "validators/voice_dubbing_validator"
|
|
6
|
+
|
|
7
|
+
module RunwayML
|
|
8
|
+
class VoiceDubbing
|
|
9
|
+
VALID_MODELS = [ "eleven_voice_dubbing" ].freeze
|
|
10
|
+
VALID_TARGET_LANGS = [
|
|
11
|
+
"en", "hi", "pt", "zh", "es", "fr", "de", "ja", "ar", "ru", "ko", "id", "it", "nl", "tr",
|
|
12
|
+
"pl", "sv", "fil", "ms", "ro", "uk", "el", "cs", "da", "fi", "bg", "hr", "sk", "ta"
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
def initialize(client:)
|
|
16
|
+
@client = client
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create(model:, audio_uri:, target_lang:, disable_voice_cloning: nil, drop_background_audio: nil, num_speakers: nil, auto_upload: true)
|
|
20
|
+
errors = {}
|
|
21
|
+
|
|
22
|
+
unless VALID_MODELS.include?(model)
|
|
23
|
+
errors[:model] = "must be one of: #{VALID_MODELS.join(', ')}"
|
|
24
|
+
raise ValidationError, errors
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Process audio_uri with auto-upload support
|
|
28
|
+
processed_audio_uri = MediaProcessor.process(
|
|
29
|
+
audio_uri,
|
|
30
|
+
errors,
|
|
31
|
+
client: client,
|
|
32
|
+
auto_upload: auto_upload
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
validator = Validators::VoiceDubbingValidator.new
|
|
36
|
+
result = validator.validate(
|
|
37
|
+
audio_uri: processed_audio_uri,
|
|
38
|
+
target_lang: target_lang,
|
|
39
|
+
disable_voice_cloning: disable_voice_cloning,
|
|
40
|
+
drop_background_audio: drop_background_audio,
|
|
41
|
+
num_speakers: num_speakers
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
errors = result[:errors]
|
|
45
|
+
raise ValidationError, errors unless errors.empty?
|
|
46
|
+
|
|
47
|
+
inputs = build_inputs(processed_audio_uri, target_lang, disable_voice_cloning, drop_background_audio, num_speakers)
|
|
48
|
+
|
|
49
|
+
response = client.post(
|
|
50
|
+
"voice_dubbing",
|
|
51
|
+
inputs.merge(model: model)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
Task.new(id: response["id"], client: client)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
attr_reader :client
|
|
60
|
+
|
|
61
|
+
def build_inputs(audio_uri, target_lang, disable_voice_cloning, drop_background_audio, num_speakers)
|
|
62
|
+
inputs = {
|
|
63
|
+
audioUri: audio_uri,
|
|
64
|
+
targetLang: target_lang
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
inputs[:disableVoiceCloning] = disable_voice_cloning unless disable_voice_cloning.nil?
|
|
68
|
+
inputs[:dropBackgroundAudio] = drop_background_audio unless drop_background_audio.nil?
|
|
69
|
+
inputs[:numSpeakers] = num_speakers unless num_speakers.nil?
|
|
70
|
+
|
|
71
|
+
inputs
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "media_processor"
|
|
5
|
+
require_relative "validators/voice_isolation_validator"
|
|
6
|
+
|
|
7
|
+
module RunwayML
|
|
8
|
+
class VoiceIsolation
|
|
9
|
+
VALID_MODELS = [ "eleven_voice_isolation" ].freeze
|
|
10
|
+
|
|
11
|
+
def initialize(client:)
|
|
12
|
+
@client = client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create(model:, audio_uri:, auto_upload: true)
|
|
16
|
+
errors = {}
|
|
17
|
+
|
|
18
|
+
unless VALID_MODELS.include?(model)
|
|
19
|
+
errors[:model] = "must be one of: #{VALID_MODELS.join(', ')}"
|
|
20
|
+
raise ValidationError, errors
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Process audio_uri with auto-upload support
|
|
24
|
+
processed_audio_uri = MediaProcessor.process(
|
|
25
|
+
audio_uri,
|
|
26
|
+
errors,
|
|
27
|
+
client: client,
|
|
28
|
+
auto_upload: auto_upload
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
validator = Validators::VoiceIsolationValidator.new
|
|
32
|
+
result = validator.validate(audio_uri: processed_audio_uri)
|
|
33
|
+
|
|
34
|
+
errors = result[:errors]
|
|
35
|
+
raise ValidationError, errors unless errors.empty?
|
|
36
|
+
|
|
37
|
+
inputs = build_inputs(processed_audio_uri)
|
|
38
|
+
|
|
39
|
+
response = client.post(
|
|
40
|
+
"voice_isolation",
|
|
41
|
+
inputs.merge(model: model)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
Task.new(id: response["id"], client: client)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :client
|
|
50
|
+
|
|
51
|
+
def build_inputs(audio_uri)
|
|
52
|
+
{
|
|
53
|
+
audioUri: audio_uri
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|