elevenlabs_client 0.1.0 → 0.3.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.
@@ -7,13 +7,21 @@ module ElevenlabsClient
7
7
  class Client
8
8
  DEFAULT_BASE_URL = "https://api.elevenlabs.io"
9
9
 
10
- attr_reader :base_url, :api_key, :dubs
10
+ attr_reader :base_url, :api_key, :dubs, :text_to_speech, :text_to_speech_stream, :text_to_dialogue, :sound_generation, :text_to_voice, :models, :voices, :music
11
11
 
12
12
  def initialize(api_key: nil, base_url: nil, api_key_env: "ELEVENLABS_API_KEY", base_url_env: "ELEVENLABS_BASE_URL")
13
13
  @api_key = api_key || fetch_api_key(api_key_env)
14
14
  @base_url = base_url || fetch_base_url(base_url_env)
15
15
  @conn = build_connection
16
16
  @dubs = Dubs.new(self)
17
+ @text_to_speech = TextToSpeech.new(self)
18
+ @text_to_speech_stream = TextToSpeechStream.new(self)
19
+ @text_to_dialogue = TextToDialogue.new(self)
20
+ @sound_generation = SoundGeneration.new(self)
21
+ @text_to_voice = TextToVoice.new(self)
22
+ @models = Models.new(self)
23
+ @voices = Voices.new(self)
24
+ @music = Endpoints::Music.new(self)
17
25
  end
18
26
 
19
27
  # Makes an authenticated GET request
@@ -35,7 +43,19 @@ module ElevenlabsClient
35
43
  def post(path, body = nil)
36
44
  response = @conn.post(path) do |req|
37
45
  req.headers["xi-api-key"] = api_key
38
- req.body = body if body
46
+ req.headers["Content-Type"] = "application/json"
47
+ req.body = body.to_json if body
48
+ end
49
+
50
+ handle_response(response)
51
+ end
52
+
53
+ # Makes an authenticated DELETE request
54
+ # @param path [String] API endpoint path
55
+ # @return [Hash] Response body
56
+ def delete(path)
57
+ response = @conn.delete(path) do |req|
58
+ req.headers["xi-api-key"] = api_key
39
59
  end
40
60
 
41
61
  handle_response(response)
@@ -54,6 +74,62 @@ module ElevenlabsClient
54
74
  handle_response(response)
55
75
  end
56
76
 
77
+ # Makes an authenticated POST request expecting binary response
78
+ # @param path [String] API endpoint path
79
+ # @param body [Hash, nil] Request body
80
+ # @return [String] Binary response body
81
+ def post_binary(path, body = nil)
82
+ response = @conn.post(path) do |req|
83
+ req.headers["xi-api-key"] = api_key
84
+ req.headers["Content-Type"] = "application/json"
85
+ req.body = body.to_json if body
86
+ end
87
+
88
+ handle_response(response)
89
+ end
90
+
91
+ # Makes an authenticated POST request with custom headers
92
+ # @param path [String] API endpoint path
93
+ # @param body [Hash, nil] Request body
94
+ # @param custom_headers [Hash] Additional headers
95
+ # @return [String] Response body (binary or text)
96
+ def post_with_custom_headers(path, body = nil, custom_headers = {})
97
+ response = @conn.post(path) do |req|
98
+ req.headers["xi-api-key"] = api_key
99
+ req.headers["Content-Type"] = "application/json"
100
+ custom_headers.each { |key, value| req.headers[key] = value }
101
+ req.body = body.to_json if body
102
+ end
103
+
104
+ # For streaming/binary responses, return raw body
105
+ if custom_headers["Accept"]&.include?("audio") || custom_headers["Transfer-Encoding"] == "chunked"
106
+ handle_response(response)
107
+ else
108
+ handle_response(response)
109
+ end
110
+ end
111
+
112
+ # Makes an authenticated POST request with streaming response
113
+ # @param path [String] API endpoint path
114
+ # @param body [Hash, nil] Request body
115
+ # @param block [Proc] Block to handle each chunk
116
+ # @return [Faraday::Response] Response object
117
+ def post_streaming(path, body = nil, &block)
118
+ response = @conn.post(path) do |req|
119
+ req.headers["xi-api-key"] = api_key
120
+ req.headers["Content-Type"] = "application/json"
121
+ req.headers["Accept"] = "audio/mpeg"
122
+ req.body = body.to_json if body
123
+
124
+ # Set up streaming callback
125
+ req.options.on_data = proc do |chunk, _|
126
+ block.call(chunk) if block_given?
127
+ end
128
+ end
129
+
130
+ handle_response(response)
131
+ end
132
+
57
133
  # Helper method to create Faraday::Multipart::FilePart
58
134
  # @param file_io [IO] File IO object
59
135
  # @param filename [String] Original filename
@@ -97,14 +173,58 @@ module ElevenlabsClient
97
173
  case response.status
98
174
  when 200..299
99
175
  response.body
176
+ when 400
177
+ error_message = extract_error_message(response.body)
178
+ raise BadRequestError, error_message.empty? ? "Bad request - invalid parameters" : error_message
100
179
  when 401
101
- raise AuthenticationError, "Invalid API key or authentication failed"
180
+ error_message = extract_error_message(response.body)
181
+ raise AuthenticationError, error_message.empty? ? "Invalid API key or authentication failed" : error_message
182
+ when 404
183
+ error_message = extract_error_message(response.body)
184
+ raise NotFoundError, error_message.empty? ? "Resource not found" : error_message
185
+ when 422
186
+ error_message = extract_error_message(response.body)
187
+ raise UnprocessableEntityError, error_message.empty? ? "Unprocessable entity - invalid data" : error_message
102
188
  when 429
103
- raise RateLimitError, "Rate limit exceeded"
189
+ error_message = extract_error_message(response.body)
190
+ raise RateLimitError, error_message.empty? ? "Rate limit exceeded" : error_message
104
191
  when 400..499
105
- raise ValidationError, response.body.inspect
192
+ error_message = extract_error_message(response.body)
193
+ raise ValidationError, error_message.empty? ? "Client error occurred with status #{response.status}" : error_message
106
194
  else
107
- raise APIError, "API request failed with status #{response.status}: #{response.body.inspect}"
195
+ error_message = extract_error_message(response.body)
196
+ raise APIError, error_message.empty? ? "API request failed with status #{response.status}" : error_message
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def extract_error_message(response_body)
203
+ return "" if response_body.nil? || response_body.empty?
204
+
205
+ # Handle non-string response bodies
206
+ body_str = response_body.is_a?(String) ? response_body : response_body.to_s
207
+
208
+ begin
209
+ error_info = JSON.parse(body_str)
210
+
211
+ # Try different common error message fields
212
+ message = error_info["detail"] ||
213
+ error_info["message"] ||
214
+ error_info["error"] ||
215
+ error_info["errors"]
216
+
217
+ # Handle nested detail objects
218
+ if message.is_a?(Hash)
219
+ message = message["message"] || message.to_s
220
+ elsif message.is_a?(Array)
221
+ message = message.first.to_s
222
+ end
223
+
224
+ message.to_s
225
+ rescue JSON::ParserError, TypeError
226
+ # If not JSON or can't be parsed, return the raw body (truncated if too long)
227
+ body_str.length > 200 ? "#{body_str[0..200]}..." : body_str
108
228
  end
109
229
  end
110
230
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class Models
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # GET /v1/models
10
+ # Gets a list of available models
11
+ # Documentation: https://elevenlabs.io/docs/api-reference/models/list
12
+ #
13
+ # @return [Hash] The JSON response containing an array of models
14
+ def list
15
+ endpoint = "/v1/models"
16
+ @client.get(endpoint)
17
+ end
18
+
19
+ # Alias for backward compatibility and convenience
20
+ alias_method :list_models, :list
21
+
22
+ private
23
+
24
+ attr_reader :client
25
+ end
26
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ module Endpoints
5
+ class Music
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ # POST /v1/music
11
+ # Compose music and return binary audio data
12
+ # Documentation: https://elevenlabs.io/docs/api-reference/music/compose
13
+ #
14
+ # @param options [Hash] Music composition parameters
15
+ # @option options [String] :prompt Text description of the music to generate
16
+ # @option options [Hash] :composition_plan Detailed composition structure (optional)
17
+ # @option options [Integer] :music_length_ms Length of music in milliseconds (optional)
18
+ # @option options [String] :model_id Model to use for generation (default: "music_v1")
19
+ # @option options [String] :output_format Audio format (e.g., "mp3_44100_128")
20
+ # @return [String] Binary audio data
21
+ def compose(options = {})
22
+ endpoint = "/v1/music"
23
+ request_body = build_music_request_body(options)
24
+
25
+ query_params = {}
26
+ query_params[:output_format] = options[:output_format] if options[:output_format]
27
+
28
+ endpoint_with_query = query_params.empty? ? endpoint : "#{endpoint}?#{URI.encode_www_form(query_params)}"
29
+
30
+ @client.post_binary(endpoint_with_query, request_body)
31
+ end
32
+
33
+ # POST /v1/music/stream
34
+ # Compose music with streaming audio response
35
+ # Documentation: https://elevenlabs.io/docs/api-reference/music/compose-stream
36
+ #
37
+ # @param options [Hash] Music composition parameters
38
+ # @option options [String] :prompt Text description of the music to generate
39
+ # @option options [Hash] :composition_plan Detailed composition structure (optional)
40
+ # @option options [Integer] :music_length_ms Length of music in milliseconds (optional)
41
+ # @option options [String] :model_id Model to use for generation (default: "music_v1")
42
+ # @option options [String] :output_format Audio format (e.g., "mp3_44100_128")
43
+ # @param block [Proc] Block to handle streaming audio chunks
44
+ # @return [nil] Audio is streamed via the block
45
+ def compose_stream(options = {}, &block)
46
+ endpoint = "/v1/music/stream"
47
+ request_body = build_music_request_body(options)
48
+
49
+ query_params = {}
50
+ query_params[:output_format] = options[:output_format] if options[:output_format]
51
+
52
+ endpoint_with_query = query_params.empty? ? endpoint : "#{endpoint}?#{URI.encode_www_form(query_params)}"
53
+
54
+ @client.post_streaming(endpoint_with_query, request_body, &block)
55
+ end
56
+
57
+ # POST /v1/music/detailed
58
+ # Compose music and return detailed response with metadata and audio
59
+ # Documentation: https://elevenlabs.io/docs/api-reference/music/compose-detailed
60
+ #
61
+ # @param options [Hash] Music composition parameters
62
+ # @option options [String] :prompt Text description of the music to generate
63
+ # @option options [Hash] :composition_plan Detailed composition structure (optional)
64
+ # @option options [Integer] :music_length_ms Length of music in milliseconds (optional)
65
+ # @option options [String] :model_id Model to use for generation (default: "music_v1")
66
+ # @option options [String] :output_format Audio format (e.g., "mp3_44100_128")
67
+ # @return [String] Multipart response with JSON metadata and binary audio
68
+ def compose_detailed(options = {})
69
+ endpoint = "/v1/music/detailed"
70
+ request_body = build_music_request_body(options)
71
+
72
+ query_params = {}
73
+ query_params[:output_format] = options[:output_format] if options[:output_format]
74
+
75
+ endpoint_with_query = query_params.empty? ? endpoint : "#{endpoint}?#{URI.encode_www_form(query_params)}"
76
+
77
+ # Use post_with_custom_headers to handle multipart response
78
+ @client.post_with_custom_headers(
79
+ endpoint_with_query,
80
+ request_body,
81
+ { "Accept" => "multipart/mixed" }
82
+ )
83
+ end
84
+
85
+ # POST /v1/music/plan
86
+ # Create a composition plan for music generation
87
+ # Documentation: https://elevenlabs.io/docs/api-reference/music/create-plan
88
+ #
89
+ # @param options [Hash] Plan creation parameters
90
+ # @option options [String] :prompt Text description of the music style/structure
91
+ # @option options [Integer] :music_length_ms Desired length of music in milliseconds
92
+ # @option options [Hash] :source_composition_plan Base plan to modify (optional)
93
+ # @option options [String] :model_id Model to use for plan generation (default: "music_v1")
94
+ # @return [Hash] JSON response containing the composition plan
95
+ def create_plan(options = {})
96
+ endpoint = "/v1/music/plan"
97
+ request_body = {
98
+ prompt: options[:prompt],
99
+ music_length_ms: options[:music_length_ms],
100
+ source_composition_plan: options[:source_composition_plan],
101
+ model_id: options[:model_id] || "music_v1"
102
+ }.compact
103
+
104
+ @client.post(endpoint, request_body)
105
+ end
106
+
107
+ # Alias methods for convenience
108
+ alias_method :compose_music, :compose
109
+ alias_method :compose_music_stream, :compose_stream
110
+ alias_method :compose_music_detailed, :compose_detailed
111
+ alias_method :create_music_plan, :create_plan
112
+
113
+ private
114
+
115
+ attr_reader :client
116
+
117
+ def build_music_request_body(options)
118
+ {
119
+ prompt: options[:prompt],
120
+ composition_plan: options[:composition_plan],
121
+ music_length_ms: options[:music_length_ms],
122
+ model_id: options[:model_id] || "music_v1"
123
+ }.compact
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class SoundGeneration
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # POST /v1/sound-generation
10
+ # Convert text to sound effects and retrieve audio (binary data)
11
+ # Documentation: https://elevenlabs.io/docs/api-reference/sound-generation
12
+ #
13
+ # @param text [String] Text prompt describing the sound effect
14
+ # @param options [Hash] Optional parameters
15
+ # @option options [Boolean] :loop Whether to create a looping sound effect (default: false)
16
+ # @option options [Float] :duration_seconds Duration in seconds (0.5 to 30, default: nil for auto-detection)
17
+ # @option options [Float] :prompt_influence Prompt influence (0.0 to 1.0, default: 0.3)
18
+ # @option options [String] :output_format Output format (e.g., "mp3_22050_32", default: "mp3_44100_128")
19
+ # @return [String] The binary audio data (usually an MP3)
20
+ def generate(text, **options)
21
+ endpoint = "/v1/sound-generation"
22
+ request_body = { text: text }
23
+
24
+ # Add optional parameters if provided
25
+ request_body[:loop] = options[:loop] unless options[:loop].nil?
26
+ request_body[:duration_seconds] = options[:duration_seconds] if options[:duration_seconds]
27
+ request_body[:prompt_influence] = options[:prompt_influence] if options[:prompt_influence]
28
+
29
+ # Handle output_format as query parameter
30
+ query_params = {}
31
+ query_params[:output_format] = options[:output_format] if options[:output_format]
32
+
33
+ # Build endpoint with query parameters if any
34
+ full_endpoint = query_params.any? ? "#{endpoint}?#{URI.encode_www_form(query_params)}" : endpoint
35
+
36
+ @client.post_binary(full_endpoint, request_body)
37
+ end
38
+
39
+ # Alias for backward compatibility and convenience
40
+ alias_method :sound_generation, :generate
41
+
42
+ private
43
+
44
+ attr_reader :client
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class TextToDialogue
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # POST /v1/text-to-dialogue
10
+ # Converts a list of text and voice ID pairs into speech (dialogue) and returns audio.
11
+ # Documentation: https://elevenlabs.io/docs/api-reference/text-to-dialogue/convert
12
+ #
13
+ # @param inputs [Array<Hash>] A list of dialogue inputs, each containing text and a voice ID
14
+ # @option inputs [String] :text The text to be converted to speech
15
+ # @option inputs [String] :voice_id The voice ID to use for this text
16
+ # @param options [Hash] Optional parameters
17
+ # @option options [String] :model_id Identifier of the model to be used
18
+ # @option options [Hash] :settings Settings controlling the dialogue generation
19
+ # @option options [Integer] :seed Best effort to sample deterministically
20
+ # @return [String] The binary audio data (usually an MP3)
21
+ def convert(inputs, **options)
22
+ endpoint = "/v1/text-to-dialogue"
23
+ request_body = { inputs: inputs }
24
+
25
+ # Add optional parameters
26
+ request_body[:model_id] = options[:model_id] if options[:model_id]
27
+ request_body[:settings] = options[:settings] if options[:settings] && !options[:settings].empty?
28
+ request_body[:seed] = options[:seed] if options[:seed]
29
+
30
+ @client.post_binary(endpoint, request_body)
31
+ end
32
+
33
+ # Alias for backward compatibility and convenience
34
+ alias_method :text_to_dialogue, :convert
35
+
36
+ private
37
+
38
+ attr_reader :client
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class TextToSpeech
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # POST /v1/text-to-speech/{voice_id}
10
+ # Convert text to speech and retrieve audio (binary data)
11
+ # Documentation: https://elevenlabs.io/docs/api-reference/text-to-speech/convert
12
+ #
13
+ # @param voice_id [String] The ID of the voice to use
14
+ # @param text [String] Text to synthesize
15
+ # @param options [Hash] Optional TTS parameters
16
+ # @option options [String] :model_id Model to use (e.g. "eleven_monolingual_v1" or "eleven_multilingual_v1")
17
+ # @option options [Hash] :voice_settings Voice configuration (stability, similarity_boost, style, use_speaker_boost, etc.)
18
+ # @option options [Boolean] :optimize_streaming Whether to receive chunked streaming audio
19
+ # @return [String] The binary audio data (usually an MP3)
20
+ def convert(voice_id, text, **options)
21
+ endpoint = "/v1/text-to-speech/#{voice_id}"
22
+ request_body = { text: text }
23
+
24
+ # Add optional parameters
25
+ request_body[:model_id] = options[:model_id] if options[:model_id]
26
+ request_body[:voice_settings] = options[:voice_settings] if options[:voice_settings]
27
+
28
+ # Handle streaming optimization
29
+ if options[:optimize_streaming]
30
+ @client.post_with_custom_headers(endpoint, request_body, streaming_headers)
31
+ else
32
+ @client.post_binary(endpoint, request_body)
33
+ end
34
+ end
35
+
36
+ # Alias for backward compatibility and convenience
37
+ alias_method :text_to_speech, :convert
38
+
39
+ private
40
+
41
+ attr_reader :client
42
+
43
+ def streaming_headers
44
+ {
45
+ "Accept" => "audio/mpeg",
46
+ "Transfer-Encoding" => "chunked"
47
+ }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class TextToSpeechStream
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # POST /v1/text-to-speech/{voice_id}/stream
10
+ # Stream text-to-speech audio in real-time chunks
11
+ #
12
+ # @param voice_id [String] The ID of the voice to use
13
+ # @param text [String] Text to synthesize
14
+ # @param options [Hash] Optional TTS parameters
15
+ # @option options [String] :model_id Model to use (defaults to "eleven_multilingual_v2")
16
+ # @option options [String] :output_format Output format (defaults to "mp3_44100_128")
17
+ # @option options [Hash] :voice_settings Voice configuration
18
+ # @param block [Proc] Block to handle each audio chunk
19
+ # @return [Faraday::Response] The response object
20
+ def stream(voice_id, text, **options, &block)
21
+ output_format = options[:output_format] || "mp3_44100_128"
22
+ endpoint = "/v1/text-to-speech/#{voice_id}/stream?output_format=#{output_format}"
23
+
24
+ request_body = {
25
+ text: text,
26
+ model_id: options[:model_id] || "eleven_multilingual_v2"
27
+ }
28
+
29
+ # Add voice_settings if provided
30
+ request_body[:voice_settings] = options[:voice_settings] if options[:voice_settings]
31
+
32
+ @client.post_streaming(endpoint, request_body, &block)
33
+ end
34
+
35
+ # Alias for backward compatibility
36
+ alias_method :text_to_speech_stream, :stream
37
+
38
+ private
39
+
40
+ attr_reader :client
41
+ end
42
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenlabsClient
4
+ class TextToVoice
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ # POST /v1/text-to-voice/design
10
+ # Designs a voice based on a description
11
+ # Documentation: https://elevenlabs.io/docs/api-reference/text-to-voice/design
12
+ #
13
+ # @param voice_description [String] Description of the voice (20-1000 characters)
14
+ # @param options [Hash] Optional parameters
15
+ # @option options [String] :output_format Output format (e.g., "mp3_44100_192")
16
+ # @option options [String] :model_id Model to use (e.g., "eleven_multilingual_ttv_v2", "eleven_ttv_v3")
17
+ # @option options [String] :text Text to generate (100-1000 characters, optional)
18
+ # @option options [Boolean] :auto_generate_text Auto-generate text (default: false)
19
+ # @option options [Float] :loudness Loudness level (-1 to 1, default: 0.5)
20
+ # @option options [Integer] :seed Random seed (0 to 2147483647, optional)
21
+ # @option options [Float] :guidance_scale Guidance scale (0 to 100, default: 5)
22
+ # @option options [Boolean] :stream_previews Stream previews (default: false)
23
+ # @option options [String] :remixing_session_id Remixing session ID (optional)
24
+ # @option options [String] :remixing_session_iteration_id Remixing session iteration ID (optional)
25
+ # @option options [Float] :quality Quality level (-1 to 1, optional)
26
+ # @option options [String] :reference_audio_base64 Base64 encoded reference audio (optional, requires eleven_ttv_v3)
27
+ # @option options [Float] :prompt_strength Prompt strength (0 to 1, optional, requires eleven_ttv_v3)
28
+ # @return [Hash] JSON response containing previews and text
29
+ def design(voice_description, **options)
30
+ endpoint = "/v1/text-to-voice/design"
31
+ request_body = { voice_description: voice_description }
32
+
33
+ # Add optional parameters if provided
34
+ request_body[:output_format] = options[:output_format] if options[:output_format]
35
+ request_body[:model_id] = options[:model_id] if options[:model_id]
36
+ request_body[:text] = options[:text] if options[:text]
37
+ request_body[:auto_generate_text] = options[:auto_generate_text] unless options[:auto_generate_text].nil?
38
+ request_body[:loudness] = options[:loudness] if options[:loudness]
39
+ request_body[:seed] = options[:seed] if options[:seed]
40
+ request_body[:guidance_scale] = options[:guidance_scale] if options[:guidance_scale]
41
+ request_body[:stream_previews] = options[:stream_previews] unless options[:stream_previews].nil?
42
+ request_body[:remixing_session_id] = options[:remixing_session_id] if options[:remixing_session_id]
43
+ request_body[:remixing_session_iteration_id] = options[:remixing_session_iteration_id] if options[:remixing_session_iteration_id]
44
+ request_body[:quality] = options[:quality] if options[:quality]
45
+ request_body[:reference_audio_base64] = options[:reference_audio_base64] if options[:reference_audio_base64]
46
+ request_body[:prompt_strength] = options[:prompt_strength] if options[:prompt_strength]
47
+
48
+ @client.post(endpoint, request_body)
49
+ end
50
+
51
+ # POST /v1/text-to-voice
52
+ # Creates a voice from the designed voice generated_voice_id
53
+ # Documentation: https://elevenlabs.io/docs/api-reference/text-to-voice
54
+ #
55
+ # @param voice_name [String] Name of the voice
56
+ # @param voice_description [String] Description of the voice (20-1000 characters)
57
+ # @param generated_voice_id [String] The generated voice ID from design_voice
58
+ # @param options [Hash] Optional parameters
59
+ # @option options [Hash] :labels Optional metadata for the voice
60
+ # @option options [Array<String>] :played_not_selected_voice_ids Optional list of voice IDs played but not selected
61
+ # @return [Hash] JSON response containing voice_id and other voice details
62
+ def create(voice_name, voice_description, generated_voice_id, **options)
63
+ endpoint = "/v1/text-to-voice"
64
+ request_body = {
65
+ voice_name: voice_name,
66
+ voice_description: voice_description,
67
+ generated_voice_id: generated_voice_id
68
+ }
69
+
70
+ # Add optional parameters if provided
71
+ request_body[:labels] = options[:labels] if options[:labels]
72
+ request_body[:played_not_selected_voice_ids] = options[:played_not_selected_voice_ids] if options[:played_not_selected_voice_ids]
73
+
74
+ @client.post(endpoint, request_body)
75
+ end
76
+
77
+ # GET /v1/voices
78
+ # Retrieves all voices associated with your Elevenlabs account
79
+ # Documentation: https://elevenlabs.io/docs/api-reference/voices
80
+ #
81
+ # @return [Hash] The JSON response containing an array of voices
82
+ def list_voices
83
+ endpoint = "/v1/voices"
84
+ @client.get(endpoint)
85
+ end
86
+
87
+ # Alias methods for backward compatibility and convenience
88
+ alias_method :design_voice, :design
89
+ alias_method :create_from_generated_voice, :create
90
+
91
+ private
92
+
93
+ attr_reader :client
94
+ end
95
+ end