eleven_rb 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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Base class for all response objects
6
+ #
7
+ # Provides attribute accessors and common functionality for
8
+ # parsing API responses into structured objects.
9
+ class Base
10
+ class << self
11
+ # Create an object from an API response hash
12
+ #
13
+ # @param response [Hash] the API response
14
+ # @return [Base] the object instance
15
+ def from_response(response)
16
+ new(response)
17
+ end
18
+
19
+ # Define an attribute accessor
20
+ #
21
+ # @param name [Symbol] the attribute name
22
+ # @param key [String, nil] the JSON key (defaults to name.to_s)
23
+ # @param type [Class, Symbol, nil] optional type for conversion
24
+ def attribute(name, key: nil, type: nil)
25
+ key ||= name.to_s
26
+
27
+ define_method(name) do
28
+ value = @attributes[key]
29
+ return nil if value.nil?
30
+
31
+ case type
32
+ when :boolean
33
+ !!value
34
+ when Class
35
+ if value.is_a?(Array)
36
+ value.map { |v| type.from_response(v) }
37
+ else
38
+ type.from_response(value)
39
+ end
40
+ else
41
+ value
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Initialize with attributes hash
48
+ #
49
+ # @param attributes [Hash] the attributes
50
+ def initialize(attributes = {})
51
+ @attributes = attributes || {}
52
+ end
53
+
54
+ # Return raw attributes hash
55
+ #
56
+ # @return [Hash]
57
+ def to_h
58
+ @attributes.dup
59
+ end
60
+
61
+ # Access raw attribute by key
62
+ #
63
+ # @param key [String, Symbol] the attribute key
64
+ # @return [Object, nil]
65
+ def [](key)
66
+ @attributes[key.to_s]
67
+ end
68
+
69
+ # Check if attribute exists
70
+ #
71
+ # @param key [String, Symbol] the attribute key
72
+ # @return [Boolean]
73
+ def key?(key)
74
+ @attributes.key?(key.to_s)
75
+ end
76
+
77
+ # Inspect the object
78
+ #
79
+ # @return [String]
80
+ def inspect
81
+ attrs = @attributes.map { |k, v| "#{k}=#{v.inspect}" }.join(', ')
82
+ "#<#{self.class.name} #{attrs}>"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Cost information for TTS generation
6
+ class CostInfo
7
+ attr_reader :character_count, :voice_id, :model_id
8
+
9
+ # Approximate cost per 1000 characters by model
10
+ # These are estimates and may vary by subscription tier
11
+ COST_PER_1K_CHARS = {
12
+ 'eleven_monolingual_v1' => 0.30,
13
+ 'eleven_multilingual_v1' => 0.30,
14
+ 'eleven_multilingual_v2' => 0.30,
15
+ 'eleven_turbo_v2' => 0.18,
16
+ 'eleven_turbo_v2_5' => 0.18,
17
+ 'eleven_english_sts_v2' => 0.30,
18
+ 'eleven_flash_v2' => 0.10,
19
+ 'eleven_flash_v2_5' => 0.10
20
+ }.freeze
21
+
22
+ DEFAULT_COST_PER_1K = 0.30
23
+
24
+ # Initialize cost info
25
+ #
26
+ # @param text [String] the text being converted
27
+ # @param voice_id [String] the voice ID
28
+ # @param model_id [String] the model ID
29
+ def initialize(text:, voice_id:, model_id:)
30
+ @character_count = text.length
31
+ @voice_id = voice_id
32
+ @model_id = model_id
33
+ end
34
+
35
+ # Get estimated cost in USD
36
+ #
37
+ # @return [Float]
38
+ def estimated_cost
39
+ rate = COST_PER_1K_CHARS[model_id] || DEFAULT_COST_PER_1K
40
+ (character_count / 1000.0 * rate).round(4)
41
+ end
42
+
43
+ # Get cost per character for this model
44
+ #
45
+ # @return [Float]
46
+ def cost_per_character
47
+ rate = COST_PER_1K_CHARS[model_id] || DEFAULT_COST_PER_1K
48
+ rate / 1000.0
49
+ end
50
+
51
+ # Check if this is a turbo/cheaper model
52
+ #
53
+ # @return [Boolean]
54
+ def turbo_model?
55
+ model_id&.include?('turbo') || model_id&.include?('flash')
56
+ end
57
+
58
+ # Convert to hash
59
+ #
60
+ # @return [Hash]
61
+ def to_h
62
+ {
63
+ character_count: character_count,
64
+ voice_id: voice_id,
65
+ model_id: model_id,
66
+ estimated_cost: estimated_cost,
67
+ cost_per_character: cost_per_character
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Represents a voice from the shared voice library
6
+ class LibraryVoice < Base
7
+ attribute :voice_id
8
+ attribute :public_owner_id
9
+ attribute :name
10
+ attribute :description
11
+ attribute :category
12
+ attribute :preview_url
13
+ attribute :gender
14
+ attribute :age
15
+ attribute :accent
16
+ attribute :language
17
+ attribute :locale
18
+ attribute :use_cases
19
+ attribute :notice_period
20
+ attribute :rate
21
+ attribute :cloned_by_count
22
+ attribute :usage_character_count_1d
23
+ attribute :usage_character_count_7d
24
+ attribute :usage_character_count_30d
25
+ attribute :free_users_allowed, type: :boolean
26
+ attribute :live_moderation_enabled, type: :boolean
27
+ attribute :verified, type: :boolean
28
+
29
+ # Get parameters needed to add this voice to account
30
+ #
31
+ # @return [Hash]
32
+ def add_params
33
+ {
34
+ public_user_id: public_owner_id,
35
+ voice_id: voice_id,
36
+ name: name
37
+ }
38
+ end
39
+
40
+ # Human-readable display name with metadata
41
+ #
42
+ # @return [String]
43
+ def display_name
44
+ parts = [name]
45
+ parts << "(#{gender})" if gender
46
+ parts << "- #{accent || language}" if accent || language
47
+ parts.join(' ')
48
+ end
49
+
50
+ # Check if this voice is popular (high usage)
51
+ #
52
+ # @param threshold [Integer] minimum usage count
53
+ # @return [Boolean]
54
+ def popular?(threshold: 10_000)
55
+ (usage_character_count_30d || 0) >= threshold
56
+ end
57
+
58
+ # Check if voice is available for free users
59
+ #
60
+ # @return [Boolean]
61
+ def available_for_free?
62
+ free_users_allowed != false
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Represents an ElevenLabs TTS model
6
+ class Model < Base
7
+ attribute :model_id
8
+ attribute :name
9
+ attribute :description
10
+ attribute :can_be_finetuned, type: :boolean
11
+ attribute :can_do_text_to_speech, type: :boolean
12
+ attribute :can_do_voice_conversion, type: :boolean
13
+ attribute :can_use_style, type: :boolean
14
+ attribute :can_use_speaker_boost, type: :boolean
15
+ attribute :serves_pro_voices, type: :boolean
16
+ attribute :token_cost_factor
17
+ attribute :languages
18
+ attribute :max_characters_request_free_user
19
+ attribute :max_characters_request_subscribed_user
20
+ attribute :concurrency_group
21
+
22
+ # Check if this model supports a given language
23
+ #
24
+ # @param language_code [String] ISO language code
25
+ # @return [Boolean]
26
+ def supports_language?(language_code)
27
+ return false unless languages
28
+
29
+ languages.any? { |l| l['language_id'] == language_code }
30
+ end
31
+
32
+ # Get list of supported language codes
33
+ #
34
+ # @return [Array<String>]
35
+ def supported_language_codes
36
+ return [] unless languages
37
+
38
+ languages.map { |l| l['language_id'] }
39
+ end
40
+
41
+ # Check if this is a multilingual model
42
+ #
43
+ # @return [Boolean]
44
+ def multilingual?
45
+ name&.downcase&.include?('multilingual') || supported_language_codes.size > 1
46
+ end
47
+
48
+ # Check if this is a turbo/fast model
49
+ #
50
+ # @return [Boolean]
51
+ def turbo?
52
+ name&.downcase&.include?('turbo') || model_id&.include?('turbo')
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Represents user subscription information
6
+ class Subscription < Base
7
+ attribute :tier
8
+ attribute :character_count
9
+ attribute :character_limit
10
+ attribute :voice_limit
11
+ attribute :professional_voice_limit
12
+ attribute :can_extend_character_limit, type: :boolean
13
+ attribute :allowed_to_extend_character_limit, type: :boolean
14
+ attribute :next_character_count_reset_unix
15
+ attribute :can_extend_voice_limit, type: :boolean
16
+ attribute :can_use_instant_voice_cloning, type: :boolean
17
+ attribute :can_use_professional_voice_cloning, type: :boolean
18
+ attribute :currency
19
+ attribute :status
20
+
21
+ # Get the number of voice slots currently used
22
+ # Note: This requires checking voices.list count
23
+ #
24
+ # @return [Integer, nil]
25
+ attr_accessor :voice_slots_used
26
+
27
+ # Set voice slots used (called by VoiceSlotManager)
28
+ #
29
+ # @param count [Integer]
30
+
31
+ # Get available voice slots
32
+ #
33
+ # @return [Integer, nil]
34
+ def voice_slots_available
35
+ return nil unless voice_limit && voice_slots_used
36
+
37
+ voice_limit - voice_slots_used
38
+ end
39
+
40
+ # Check if voice slots are full
41
+ #
42
+ # @return [Boolean]
43
+ def voice_slots_full?
44
+ return false unless voice_slots_available
45
+
46
+ voice_slots_available <= 0
47
+ end
48
+
49
+ # Get remaining characters
50
+ #
51
+ # @return [Integer]
52
+ def characters_remaining
53
+ return 0 unless character_limit && character_count
54
+
55
+ character_limit - character_count
56
+ end
57
+
58
+ # Get percentage of characters used
59
+ #
60
+ # @return [Float]
61
+ def characters_used_percentage
62
+ return 0.0 unless character_limit&.positive?
63
+
64
+ (character_count.to_f / character_limit * 100).round(1)
65
+ end
66
+
67
+ # Get next reset time as Time object
68
+ #
69
+ # @return [Time, nil]
70
+ def next_reset_at
71
+ return nil unless next_character_count_reset_unix
72
+
73
+ Time.at(next_character_count_reset_unix)
74
+ end
75
+
76
+ # Check if subscription is active
77
+ #
78
+ # @return [Boolean]
79
+ def active?
80
+ status == 'active'
81
+ end
82
+
83
+ # Check if this is a free tier
84
+ #
85
+ # @return [Boolean]
86
+ def free?
87
+ tier&.downcase == 'free'
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Represents user account information
6
+ class UserInfo < Base
7
+ attribute :user_id
8
+ attribute :email
9
+ attribute :first_name
10
+ attribute :is_new_user, type: :boolean
11
+ attribute :xi_api_key
12
+ attribute :can_use_delayed_payment_methods, type: :boolean
13
+ attribute :is_onboarding_completed, type: :boolean
14
+ attribute :is_onboarding_checklist_completed, type: :boolean
15
+
16
+ # Get full display name
17
+ #
18
+ # @return [String]
19
+ def display_name
20
+ first_name || email&.split('@')&.first || 'User'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Represents an ElevenLabs voice
6
+ class Voice < Base
7
+ attribute :voice_id
8
+ attribute :name
9
+ attribute :description
10
+ attribute :category
11
+ attribute :preview_url
12
+ attribute :labels
13
+ attribute :settings, type: VoiceSettings
14
+ attribute :samples
15
+ attribute :sharing
16
+ attribute :high_quality_base_model_ids
17
+ attribute :safety_control
18
+
19
+ # Get the gender from labels
20
+ #
21
+ # @return [String, nil]
22
+ def gender
23
+ labels&.dig('gender')
24
+ end
25
+
26
+ # Get the accent from labels
27
+ #
28
+ # @return [String, nil]
29
+ def accent
30
+ labels&.dig('accent')
31
+ end
32
+
33
+ # Get the language from labels
34
+ #
35
+ # @return [String, nil]
36
+ def language
37
+ labels&.dig('language')
38
+ end
39
+
40
+ # Get the age from labels
41
+ #
42
+ # @return [String, nil]
43
+ def age
44
+ labels&.dig('age')
45
+ end
46
+
47
+ # Get the use case from labels
48
+ #
49
+ # @return [String, nil]
50
+ def use_case
51
+ labels&.dig('use_case')
52
+ end
53
+
54
+ # Check if this voice is banned
55
+ #
56
+ # @return [Boolean]
57
+ def banned?
58
+ safety_control == 'BAN'
59
+ end
60
+
61
+ # Provider identifier for wrapper compatibility
62
+ #
63
+ # @return [Symbol]
64
+ def provider
65
+ :elevenlabs
66
+ end
67
+
68
+ # Alias for voice_id for wrapper compatibility
69
+ #
70
+ # @return [String]
71
+ def provider_voice_id
72
+ voice_id
73
+ end
74
+
75
+ # Human-readable display name with metadata
76
+ #
77
+ # @return [String]
78
+ def display_name
79
+ parts = [name]
80
+ parts << "(#{gender})" if gender
81
+ parts << "- #{accent || language}" if accent || language
82
+ parts.join(' ')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Objects
5
+ # Voice settings for TTS generation
6
+ class VoiceSettings < Base
7
+ attribute :stability
8
+ attribute :similarity_boost
9
+ attribute :style
10
+ attribute :use_speaker_boost, type: :boolean
11
+
12
+ # Default settings for TTS generation
13
+ DEFAULTS = {
14
+ stability: 0.5,
15
+ similarity_boost: 0.75,
16
+ style: 0.0,
17
+ use_speaker_boost: true
18
+ }.freeze
19
+
20
+ # Create settings with defaults merged in
21
+ #
22
+ # @param overrides [Hash] settings to override defaults
23
+ # @return [VoiceSettings]
24
+ def self.with_defaults(overrides = {})
25
+ from_response(DEFAULTS.merge(overrides))
26
+ end
27
+
28
+ # Convert to hash suitable for API request
29
+ #
30
+ # @return [Hash]
31
+ def to_api_hash
32
+ {
33
+ stability: stability,
34
+ similarity_boost: similarity_boost,
35
+ style: style,
36
+ use_speaker_boost: use_speaker_boost
37
+ }.compact
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Resources
5
+ # Base class for API resources
6
+ class Base
7
+ attr_reader :http_client
8
+
9
+ # Initialize resource
10
+ #
11
+ # @param http_client [HTTP::Client]
12
+ def initialize(http_client)
13
+ @http_client = http_client
14
+ end
15
+
16
+ private
17
+
18
+ # Make a GET request
19
+ #
20
+ # @param path [String]
21
+ # @param params [Hash]
22
+ # @return [Hash, Array]
23
+ def get(path, params = {})
24
+ http_client.get(path, params)
25
+ end
26
+
27
+ # Make a POST request
28
+ #
29
+ # @param path [String]
30
+ # @param body [Hash]
31
+ # @param response_type [Symbol]
32
+ # @return [Hash, Array, String]
33
+ def post(path, body = {}, response_type: :json)
34
+ http_client.post(path, body, response_type: response_type)
35
+ end
36
+
37
+ # Make a DELETE request
38
+ #
39
+ # @param path [String]
40
+ # @return [Hash]
41
+ def delete(path)
42
+ http_client.delete(path)
43
+ end
44
+
45
+ # Make a binary POST request
46
+ #
47
+ # @param path [String]
48
+ # @param body [Hash]
49
+ # @return [String]
50
+ def post_binary(path, body = {})
51
+ http_client.post(path, body, response_type: :binary)
52
+ end
53
+
54
+ # Make a streaming POST request
55
+ #
56
+ # @param path [String]
57
+ # @param body [Hash]
58
+ # @yield [String] chunk
59
+ def post_stream(path, body = {}, &block)
60
+ http_client.post_stream(path, body, &block)
61
+ end
62
+
63
+ # Make a multipart POST request
64
+ #
65
+ # @param path [String]
66
+ # @param params [Hash]
67
+ # @return [Hash]
68
+ def post_multipart(path, params)
69
+ http_client.post_multipart(path, params)
70
+ end
71
+
72
+ # Validate presence of a value
73
+ #
74
+ # @param value [Object]
75
+ # @param name [String]
76
+ # @raise [Errors::ValidationError]
77
+ def validate_presence!(value, name)
78
+ return unless value.nil? || (value.respond_to?(:empty?) && value.empty?)
79
+
80
+ raise Errors::ValidationError, "#{name} cannot be blank"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElevenRb
4
+ module Resources
5
+ # Models resource
6
+ #
7
+ # @example List all models
8
+ # models = client.models.list
9
+ #
10
+ # @example Find multilingual models
11
+ # client.models.multilingual
12
+ class Models < Base
13
+ # List all available models
14
+ #
15
+ # @return [Array<Objects::Model>]
16
+ def list
17
+ response = get('/models')
18
+ response.map { |m| Objects::Model.from_response(m) }
19
+ end
20
+
21
+ # Get a specific model by ID
22
+ #
23
+ # @param model_id [String] the model ID
24
+ # @return [Objects::Model, nil]
25
+ def get(model_id)
26
+ list.find { |m| m.model_id == model_id }
27
+ end
28
+
29
+ # Get all multilingual models
30
+ #
31
+ # @return [Array<Objects::Model>]
32
+ def multilingual
33
+ list.select(&:multilingual?)
34
+ end
35
+
36
+ # Get all turbo/fast models
37
+ #
38
+ # @return [Array<Objects::Model>]
39
+ def turbo
40
+ list.select(&:turbo?)
41
+ end
42
+
43
+ # Get models that support TTS
44
+ #
45
+ # @return [Array<Objects::Model>]
46
+ def tts_capable
47
+ list.select(&:can_do_text_to_speech)
48
+ end
49
+
50
+ # Get the default/recommended model for TTS
51
+ #
52
+ # @return [Objects::Model, nil]
53
+ def default
54
+ get('eleven_multilingual_v2') || tts_capable.first
55
+ end
56
+
57
+ # Get model IDs as array
58
+ #
59
+ # @return [Array<String>]
60
+ def ids
61
+ list.map(&:model_id)
62
+ end
63
+ end
64
+ end
65
+ end