stability_ai 0.1.0 → 0.1.1
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 +4 -4
- data/CHANGELOG.md +5 -1
- data/Gemfile +6 -0
- data/Gemfile.lock +79 -0
- data/README.md +73 -18
- data/lib/stability_ai/api/engines.rb +16 -0
- data/lib/stability_ai/api/generation.rb +176 -0
- data/lib/stability_ai/api/user.rb +16 -0
- data/lib/stability_ai/api.rb +4 -0
- data/lib/stability_ai/client.rb +39 -0
- data/lib/stability_ai/configuration.rb +12 -0
- data/lib/stability_ai/helpers/image_helper.rb +16 -0
- data/lib/stability_ai/response/base_response.rb +14 -0
- data/lib/stability_ai/response/engines_response.rb +9 -0
- data/lib/stability_ai/response/image_response.rb +39 -0
- data/lib/stability_ai/response/user_response.rb +10 -0
- data/lib/stability_ai/stability_ai_error.rb +14 -0
- data/lib/stability_ai/version.rb +2 -2
- data/lib/stability_ai.rb +26 -2
- data/stability_ai.gemspec +1 -2
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb17addfc822fe3363f6d07a04a1055abc3bf7cc00d39e4d498bfcb0a10ee41c
|
|
4
|
+
data.tar.gz: 19dc57a7b3b13e09906c3c356931c253749d47c6412083f0ac690f93c5b5e32a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 186e15ecb08fa19b21e0cc51529541193dc5900f5b6c8b06156cdf866b35202c4937eda49ad62835aa4f93b573d864b37875fe9f2ed3ebf7eae8ccdb7d439403
|
|
7
|
+
data.tar.gz: 47f8aff5d068b97b19153be5b17bf210ad582f9806af8a80b8214f2def367666c48c914fc81a23072d56b63cb40e3706808c01b8ccdee3ccde998fddab096259
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
stability_ai (0.1.1)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.4.2)
|
|
10
|
+
byebug (11.1.3)
|
|
11
|
+
coderay (1.1.3)
|
|
12
|
+
diff-lcs (1.5.0)
|
|
13
|
+
httparty (0.20.0)
|
|
14
|
+
mime-types (~> 3.0)
|
|
15
|
+
multi_xml (>= 0.5.2)
|
|
16
|
+
json (2.6.3)
|
|
17
|
+
method_source (1.0.0)
|
|
18
|
+
mime-types (3.4.1)
|
|
19
|
+
mime-types-data (~> 3.2015)
|
|
20
|
+
mime-types-data (3.2023.0218.1)
|
|
21
|
+
multi_xml (0.6.0)
|
|
22
|
+
parallel (1.23.0)
|
|
23
|
+
parser (3.2.2.0)
|
|
24
|
+
ast (~> 2.4.1)
|
|
25
|
+
pkg-config (1.5.1)
|
|
26
|
+
pry (0.14.2)
|
|
27
|
+
coderay (~> 1.1)
|
|
28
|
+
method_source (~> 1.0)
|
|
29
|
+
pry-byebug (3.10.1)
|
|
30
|
+
byebug (~> 11.0)
|
|
31
|
+
pry (>= 0.13, < 0.15)
|
|
32
|
+
rainbow (3.1.1)
|
|
33
|
+
rake (13.0.6)
|
|
34
|
+
regexp_parser (2.8.0)
|
|
35
|
+
rexml (3.2.5)
|
|
36
|
+
rmagick (5.2.0)
|
|
37
|
+
pkg-config (~> 1.4)
|
|
38
|
+
rspec (3.12.0)
|
|
39
|
+
rspec-core (~> 3.12.0)
|
|
40
|
+
rspec-expectations (~> 3.12.0)
|
|
41
|
+
rspec-mocks (~> 3.12.0)
|
|
42
|
+
rspec-core (3.12.2)
|
|
43
|
+
rspec-support (~> 3.12.0)
|
|
44
|
+
rspec-expectations (3.12.3)
|
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
46
|
+
rspec-support (~> 3.12.0)
|
|
47
|
+
rspec-mocks (3.12.5)
|
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
49
|
+
rspec-support (~> 3.12.0)
|
|
50
|
+
rspec-support (3.12.0)
|
|
51
|
+
rubocop (1.50.2)
|
|
52
|
+
json (~> 2.3)
|
|
53
|
+
parallel (~> 1.10)
|
|
54
|
+
parser (>= 3.2.0.0)
|
|
55
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
56
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
57
|
+
rexml (>= 3.2.5, < 4.0)
|
|
58
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
|
59
|
+
ruby-progressbar (~> 1.7)
|
|
60
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
61
|
+
rubocop-ast (1.28.0)
|
|
62
|
+
parser (>= 3.2.1.0)
|
|
63
|
+
ruby-progressbar (1.13.0)
|
|
64
|
+
unicode-display_width (2.4.2)
|
|
65
|
+
|
|
66
|
+
PLATFORMS
|
|
67
|
+
x86_64-darwin-22
|
|
68
|
+
|
|
69
|
+
DEPENDENCIES
|
|
70
|
+
httparty (~> 0.20.0)
|
|
71
|
+
pry-byebug
|
|
72
|
+
rake (~> 13.0)
|
|
73
|
+
rmagick (~> 5.2.0)
|
|
74
|
+
rspec (~> 3.0)
|
|
75
|
+
rubocop (~> 1.21)
|
|
76
|
+
stability_ai!
|
|
77
|
+
|
|
78
|
+
BUNDLED WITH
|
|
79
|
+
2.4.12
|
data/README.md
CHANGED
|
@@ -1,39 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# StabilityAI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
StabilityAI is a Ruby gem that simplifies interactions with the Stability AI API. It supports image generation, image-to-image manipulation, upscaling, and masking.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Demo
|
|
6
|
+
|
|
7
|
+
I made this `gem` part of a personal project: [ciel.chat](https://ciel.chat) - an AI supercharged WhatsApp bot, using ChatGPT, Bard, StableDiffusion, Dalle, GoogleSpeech, ElevenLabs.
|
|
8
|
+
You can try it there for free ✌️
|
|
9
|
+
|
|
10
|
+
## Disclaimer
|
|
11
|
+
|
|
12
|
+
It's my first gem ever. There are plenty of room for improvements there - feel free to contribute!
|
|
6
13
|
|
|
7
14
|
## Installation
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
Add this line to your application's Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem 'stability_ai'
|
|
20
|
+
```
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
And then execute:
|
|
12
23
|
|
|
13
|
-
|
|
24
|
+
```ruby
|
|
25
|
+
$ bundle install
|
|
26
|
+
```
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
Or install it yourself as:
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
```ruby
|
|
31
|
+
gem install stability_ai
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Create an initializer in your Rails project or any Ruby application (e.g., `config/initializers/stability_ai.rb`) to configure your API keys and credentials:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
StabilityAI.configure do |config|
|
|
40
|
+
config.stability_api_key = 'your_stability_api_key'
|
|
41
|
+
config.path_prefix = './'
|
|
42
|
+
end
|
|
43
|
+
```
|
|
18
44
|
|
|
19
45
|
## Usage
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
Here's an example of how to use the gem with the Stability AI API:
|
|
48
|
+
Check [StabilityAI API](https://api.stability.ai/docs) for all the options available. If there are not passed in the `options` hash, the default value is used.
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
```ruby
|
|
51
|
+
require 'stability_ai'
|
|
24
52
|
|
|
25
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
26
53
|
|
|
27
|
-
|
|
54
|
+
client = StabilityAI::Client.new
|
|
28
55
|
|
|
29
|
-
|
|
56
|
+
# Text to image
|
|
57
|
+
text_to_image_response = client.text_to_image('engine_id', text_prompts: [{ text: 'A lighthouse on a cliff' }])
|
|
58
|
+
uri_image_1 = text_to_image_response.image_uris.first # -> <img src="#{uri_image_1}"/> or image_tag(uri_image_1)
|
|
59
|
+
text_to_image_response.save_images('your_image_name_prefix') # -> returns ["your_image_name_prefix_1.png", "your_image_name_prefix_2.png", ...]
|
|
30
60
|
|
|
31
|
-
|
|
61
|
+
# Image to image
|
|
32
62
|
|
|
33
|
-
|
|
63
|
+
options = {
|
|
64
|
+
text_prompts: [
|
|
65
|
+
{
|
|
66
|
+
text: "Snow",
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
image_strength: 0.35, # Default: 0.35 How much influence the init_image has on the diffusion process. Values close to 1 will yield images very similar to the init_image while values close to 0 will yield images wildly different than the init_image
|
|
70
|
+
cfg_scale: 7, # DEFAULT: [0..35] How strictly the diffusion process adheres to the prompt text (higher values keep your image closer to your prompt),
|
|
71
|
+
# clip_guidance_preset: "FAST_BLUE", # DEFAULT: NONE, WTF IS THAT?? FAST_BLUE FAST_GREEN NONE SIMPLE SLOW SLOWER SLOWEST
|
|
72
|
+
# sampler: "DDIM", # DEFAULT: Automatically choose by StableAI. DDIM DDPM K_DPMPP_2M K_DPMPP_2S_ANCESTRAL K_DPM_2 K_DPM_2_ANCESTRAL K_EULER K_EULER_ANCESTRAL K_HEUN K_LMS
|
|
73
|
+
sytle_preset: "neon-punk", # check
|
|
74
|
+
}
|
|
75
|
+
response = client.image_to_image(image_path: image_path, )
|
|
76
|
+
|
|
77
|
+
# Image to image upscale
|
|
78
|
+
response = client.image_to_image_upscale(engine_id: "esrgan-v1-x2plus", image_path: image_path, options: { width: 1024 })
|
|
79
|
+
response = client.image_to_image_upscale(image_path: image_path, use_maximum_resolution: true) # default engine: esrgan-v1-x2plus / use `use_maximum_resolution: true` is to use 2048px
|
|
80
|
+
response = client.image_to_image_upscale(image_binary: image_binary, )
|
|
34
81
|
|
|
35
|
-
|
|
82
|
+
# Image to image masking
|
|
83
|
+
# TODO
|
|
84
|
+
```
|
|
36
85
|
|
|
37
|
-
|
|
86
|
+
Replace `'engine_id'` with the appropriate engine ID for each method call.
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mael-ha/stability_ai.
|
|
91
|
+
|
|
92
|
+
## License
|
|
38
93
|
|
|
39
|
-
|
|
94
|
+
The gem is available as open source under the terms of the MIT License.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
module API
|
|
3
|
+
module Engines
|
|
4
|
+
def engines_list
|
|
5
|
+
response = self.class.get('/v1/engines/list')
|
|
6
|
+
if response.code == 200
|
|
7
|
+
StabilityAI::Response::EnginesListResponse.new(response)
|
|
8
|
+
else
|
|
9
|
+
error_data = response.parsed_response
|
|
10
|
+
raise StabilityAI::StabilityAIError.new(response.code, error_data["id"], error_data["name"], error_data["message"])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "rmagick"
|
|
3
|
+
|
|
4
|
+
module StabilityAI
|
|
5
|
+
module API
|
|
6
|
+
module Generation
|
|
7
|
+
def text_to_image(engine_id: nil, options: {})
|
|
8
|
+
response = self.class.post("/v1/generation/#{get_engine_id(engine_id)}/text-to-image", body: options.to_json,
|
|
9
|
+
headers: { "Content-Type" => "application/json" })
|
|
10
|
+
handle_response(response)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def image_to_image(engine_id: nil, image_binary: nil, image_base64: nil, image_path: nil, options: {})
|
|
14
|
+
raise "No image file path provided." unless image_path || image_binary || image_base64
|
|
15
|
+
raise "No prompt provided." if options[:text_prompts].nil?
|
|
16
|
+
|
|
17
|
+
image_payload = create_payload(image_binary, image_base64, image_path)
|
|
18
|
+
image = convert_and_resize_image(image_payload)
|
|
19
|
+
temp_file = create_temp_file_from_image(image)
|
|
20
|
+
form_data = set_form_data(image:, endpoint: :image_to_image, options:, temp_file:)
|
|
21
|
+
|
|
22
|
+
default_weight = options[:text_prompts].count > 1 ? (1 / options[:text_prompts].count).round(2) : 1
|
|
23
|
+
options[:text_prompts].each_with_index do |text_prompt, i|
|
|
24
|
+
form_data.merge!("text_prompts[#{i}][text]" => text_prompt[:text]) unless text_prompt[:text].nil?
|
|
25
|
+
form_data.merge!("text_prompts[#{i}][weight]" => text_prompt[:weight] || default_weight) unless text_prompt[:weight].nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
headers = { "Content-Type" => "multipart/form-data" }
|
|
29
|
+
response = self.class.post("/v1/generation/#{get_engine_id(engine_id)}/image-to-image",
|
|
30
|
+
headers: headers, multipart: true, body: form_data)
|
|
31
|
+
handle_response(response)
|
|
32
|
+
ensure
|
|
33
|
+
if temp_file
|
|
34
|
+
temp_file.close
|
|
35
|
+
temp_file.unlink
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def image_to_image_upscale(engine_id: "esrgan-v1-x2plus", image_binary: nil, image_base64: nil, image_path: nil, use_maximum_resolution: false, options: {})
|
|
40
|
+
raise "No image file path provided." unless image_path || image_binary || image_base64
|
|
41
|
+
|
|
42
|
+
image_payload = create_payload(image_binary, image_base64, image_path)
|
|
43
|
+
image = convert_and_resize_image(image_payload)
|
|
44
|
+
temp_file = create_temp_file_from_image(image)
|
|
45
|
+
form_data = set_form_data(image:, endpoint: :image_to_image_upscale, options: options, temp_file:)
|
|
46
|
+
|
|
47
|
+
headers = { "Content-Type" => "multipart/form-data" }
|
|
48
|
+
response = self.class.post("/v1/generation/#{engine_id}/image-to-image/upscale", headers: headers, multipart: true, body: form_data)
|
|
49
|
+
handle_response(response)
|
|
50
|
+
ensure
|
|
51
|
+
if temp_file
|
|
52
|
+
temp_file.close
|
|
53
|
+
temp_file.unlink
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# def text_to_animation(engine_id: nil, options: {})
|
|
58
|
+
# response = self.class.post("/v1/generation/#{get_engine_id(engine_id)}/text-to-animation", body: options.to_json,
|
|
59
|
+
# headers: { "Content-Type" => "application/json" })
|
|
60
|
+
# handle_response(response)
|
|
61
|
+
# end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def get_engine_id(engine_id)
|
|
66
|
+
engine_id || StabilityAI.configuration.default_engine_id
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def handle_response(response)
|
|
70
|
+
if response.code == 200
|
|
71
|
+
StabilityAI::Response::ImageResponse.new(response)
|
|
72
|
+
else
|
|
73
|
+
error_data = response.parsed_response
|
|
74
|
+
raise StabilityAI::StabilityAIError.new(response.code, error_data["id"], error_data["name"],
|
|
75
|
+
error_data["message"])
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def create_payload(image_binary, image_base64, image_path)
|
|
80
|
+
if image_binary
|
|
81
|
+
StringIO.new(image_binary).read
|
|
82
|
+
elsif image_base64
|
|
83
|
+
StringIO.new(Base64.decode64(image_base64)).read
|
|
84
|
+
elsif image_path
|
|
85
|
+
File.read(File.open(image_path, 'rb'))
|
|
86
|
+
else
|
|
87
|
+
raise "No image binary, base64, or file path provided."
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create_temp_file_from_image(image)
|
|
92
|
+
temp_file = Tempfile.new(['image', '.png'])
|
|
93
|
+
temp_file.binmode
|
|
94
|
+
image.format = 'PNG' # Set the format explicitly before calling to_blob
|
|
95
|
+
temp_file.write(image.to_blob)
|
|
96
|
+
temp_file.flush
|
|
97
|
+
temp_file.rewind
|
|
98
|
+
temp_file
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def get_image_dimensions(image)
|
|
102
|
+
[image.columns, image.rows]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def set_max_dimension(image, options, use_maximum_resolution)
|
|
106
|
+
if use_maximum_resolution
|
|
107
|
+
width, height = get_image_dimensions(image)
|
|
108
|
+
(width > height) ? { 'width' => 2048 } : { 'height' => 2048 }
|
|
109
|
+
else
|
|
110
|
+
case
|
|
111
|
+
when !options[:width].nil?
|
|
112
|
+
{ 'width' => options[:width] }
|
|
113
|
+
when !options[:height].nil?
|
|
114
|
+
{ 'height' => options[:height] }
|
|
115
|
+
else
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def set_form_data(image:, endpoint:, options:, temp_file:, use_maximum_resolution: true)
|
|
122
|
+
raise "No image file provided." unless temp_file
|
|
123
|
+
|
|
124
|
+
form_data = {}
|
|
125
|
+
case endpoint
|
|
126
|
+
when :image_to_image
|
|
127
|
+
form_data.merge!("init_image" => temp_file)
|
|
128
|
+
parameters = %w[image_strength, init_image_mode, cfg_scale, clip_guidance_preset, samples, steps]
|
|
129
|
+
parameters.each do |parameter|
|
|
130
|
+
next if options[parameter.to_sym].nil?
|
|
131
|
+
form_data.merge!(parameter => options[parameter.to_sym])
|
|
132
|
+
end
|
|
133
|
+
form_data
|
|
134
|
+
when :image_to_image_upscale
|
|
135
|
+
form_data.merge!("image" => temp_file)
|
|
136
|
+
max_dimension = set_max_dimension(image, options, use_maximum_resolution)
|
|
137
|
+
form_data.merge!(max_dimension) if max_dimension
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def get_new_dimensions(width, height, step = 64)
|
|
142
|
+
new_width = (width / step) * step
|
|
143
|
+
new_height = (height / step) * step
|
|
144
|
+
[new_width, new_height]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def convert_and_resize_image(image_payload)
|
|
148
|
+
# Convert the input image to PNG format if it's a JPG or JPEG file
|
|
149
|
+
image = Magick::Image.from_blob(image_payload).first
|
|
150
|
+
image.format = 'PNG' if %w[JPG JPEG].include?(image.format)
|
|
151
|
+
|
|
152
|
+
if image.columns % 64 != 0 || image.rows % 64 != 0
|
|
153
|
+
# Calculate new dimensions
|
|
154
|
+
new_width, new_height = get_new_dimensions(image.columns, image.rows)
|
|
155
|
+
|
|
156
|
+
# Resize the image while maintaining aspect ratio
|
|
157
|
+
image.change_geometry("#{new_width}x#{new_height}") do |cols, rows, img|
|
|
158
|
+
img.resize!(cols, rows)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Find the nearest multiples of 64 for both width and height
|
|
162
|
+
width_multiple = (image.columns / 64.0).floor * 64
|
|
163
|
+
height_multiple = (image.rows / 64.0).floor * 64
|
|
164
|
+
|
|
165
|
+
# Calculate new offsets for cropping
|
|
166
|
+
x_offset = (image.columns - width_multiple) / 2
|
|
167
|
+
y_offset = (image.rows - height_multiple) / 2
|
|
168
|
+
|
|
169
|
+
# Crop the centered part to ensure dimensions are multiples of 64
|
|
170
|
+
image.crop!(x_offset, y_offset, width_multiple, height_multiple)
|
|
171
|
+
end
|
|
172
|
+
image
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
module API
|
|
3
|
+
module User
|
|
4
|
+
def balance
|
|
5
|
+
response = self.class.get('/v1/user/balance')
|
|
6
|
+
if response.code == 200
|
|
7
|
+
StabilityAI::Response::UserResponse.new(response)
|
|
8
|
+
else
|
|
9
|
+
error_data = response.parsed_response
|
|
10
|
+
raise StabilityAI::StabilityAIError.new(response.code, error_data["id"], error_data["name"], error_data["message"])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
class Client
|
|
3
|
+
include HTTParty
|
|
4
|
+
include StabilityAI::API::User
|
|
5
|
+
include StabilityAI::API::Engines
|
|
6
|
+
include StabilityAI::API::Generation
|
|
7
|
+
|
|
8
|
+
base_uri "https://api.stability.ai"
|
|
9
|
+
format :json
|
|
10
|
+
|
|
11
|
+
def initialize(api_key = nil)
|
|
12
|
+
@api_key = api_key || StabilityAI.configuration.stability_api_key || ENV["STABILITY_API_KEY"]
|
|
13
|
+
raise ArgumentError, "Missing Stability API key." unless @api_key
|
|
14
|
+
|
|
15
|
+
self.class.default_options.merge!(
|
|
16
|
+
headers: {
|
|
17
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
18
|
+
"Content-Type" => "application/json",
|
|
19
|
+
"Accept" => "application/json"
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
set_default_engine_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def set_default_engine_id
|
|
29
|
+
# Setting stable-diffusion-xl as default engine
|
|
30
|
+
return if StabilityAI.configuration.default_engine_id
|
|
31
|
+
|
|
32
|
+
engines_list_response = engines_list
|
|
33
|
+
engines = engines_list_response.engines
|
|
34
|
+
|
|
35
|
+
default_engine = engines.find { |engine| engine.id.include?("stable-diffusion-xl") }
|
|
36
|
+
StabilityAI.configuration.default_engine_id = default_engine.id if default_engine
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
module Helpers
|
|
3
|
+
class ImageHelper
|
|
4
|
+
def self.save_artifacts(artifacts, prefix = "output")
|
|
5
|
+
artifacts.each_with_index do |artifact, i|
|
|
6
|
+
save_base64_image("#{prefix}_#{i}.png", artifact.base64)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.save_base64_image(file_path, base64_image)
|
|
11
|
+
File.binwrite(file_path, Base64.decode64(base64_image))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
module Response
|
|
3
|
+
class BaseResponse
|
|
4
|
+
attr_reader :raw_response, :http_status
|
|
5
|
+
|
|
6
|
+
def initialize(raw_response)
|
|
7
|
+
@raw_response = raw_response
|
|
8
|
+
@parsed_response = @raw_response.parsed_response
|
|
9
|
+
@http_status = raw_response.code
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
module Response
|
|
3
|
+
class ImageResponse < BaseResponse
|
|
4
|
+
attr_reader :artifacts
|
|
5
|
+
|
|
6
|
+
def initialize(response)
|
|
7
|
+
super(response)
|
|
8
|
+
@artifacts = response.parsed_response["artifacts"]
|
|
9
|
+
convert_artifacts_to_openstruct
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def convert_artifacts_to_openstruct
|
|
13
|
+
@artifacts.map! do |artifact|
|
|
14
|
+
image_binary_data = Base64.decode64(artifact["base64"])
|
|
15
|
+
artifact["image_binary"] = image_binary_data
|
|
16
|
+
artifact.delete("base64")
|
|
17
|
+
OpenStruct.new(artifact)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def save_images(filename_prefix: nil)
|
|
22
|
+
filename_prefix = Time.now.utc.strftime("%Y%m%d%H%M%S") if filename_prefix.nil?
|
|
23
|
+
downloaded_images = []
|
|
24
|
+
path_prefix = StabilityAI.configuration.path_prefix
|
|
25
|
+
@artifacts.each_with_index do |artifact, i|
|
|
26
|
+
image_name = "#{filename_prefix}_#{i}.png"
|
|
27
|
+
image_path = path_prefix + image_name
|
|
28
|
+
File.binwrite(image_path, artifact["image_binary"])
|
|
29
|
+
downloaded_images << {
|
|
30
|
+
file_name: image_name,
|
|
31
|
+
seed: artifact["seed"],
|
|
32
|
+
finish_reason: artifact["finishReason"],
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
downloaded_images
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module StabilityAI
|
|
2
|
+
class StabilityAIError < StandardError
|
|
3
|
+
attr_reader :id, :name, :message
|
|
4
|
+
|
|
5
|
+
def initialize(code, id, name, message)
|
|
6
|
+
@code = code
|
|
7
|
+
@id = id
|
|
8
|
+
@name = name
|
|
9
|
+
@message= message
|
|
10
|
+
super("#{code}# #{name}: #{message} (ID: #{id})")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
data/lib/stability_ai/version.rb
CHANGED
data/lib/stability_ai.rb
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "httparty"
|
|
4
|
+
require "pry-byebug"
|
|
5
|
+
require "dotenv/load"
|
|
6
|
+
require "base64"
|
|
7
|
+
require "uri"
|
|
3
8
|
require_relative "stability_ai/version"
|
|
9
|
+
require_relative "stability_ai/api"
|
|
10
|
+
require_relative "stability_ai/api/user"
|
|
11
|
+
require_relative "stability_ai/api/engines"
|
|
12
|
+
require_relative "stability_ai/api/generation"
|
|
13
|
+
require_relative "stability_ai/client"
|
|
14
|
+
require_relative "stability_ai/response/base_response"
|
|
15
|
+
require_relative "stability_ai/response/user_response"
|
|
16
|
+
require_relative "stability_ai/response/engines_response"
|
|
17
|
+
require_relative "stability_ai/response/image_response"
|
|
18
|
+
require_relative "stability_ai/stability_ai_error"
|
|
19
|
+
require_relative "stability_ai/configuration"
|
|
20
|
+
|
|
21
|
+
module StabilityAI
|
|
22
|
+
class << self
|
|
23
|
+
attr_accessor :configuration
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.configure
|
|
27
|
+
self.configuration ||= Configuration.new
|
|
28
|
+
yield(configuration)
|
|
29
|
+
end
|
|
4
30
|
|
|
5
|
-
module StabilityAi
|
|
6
31
|
class Error < StandardError; end
|
|
7
|
-
# Your code goes here...
|
|
8
32
|
end
|
data/stability_ai.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ require_relative "lib/stability_ai/version"
|
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "stability_ai"
|
|
7
|
-
spec.version =
|
|
7
|
+
spec.version = StabilityAI::VERSION
|
|
8
8
|
spec.authors = ["Maël Harnois"]
|
|
9
9
|
spec.email = ["m@maelus.com"]
|
|
10
10
|
|
|
@@ -13,7 +13,6 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.license = "MIT"
|
|
14
14
|
spec.required_ruby_version = ">= 2.6.0"
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
17
|
spec.metadata["source_code_uri"] = "https://github.com/mael-ha/stability_ai"
|
|
19
18
|
spec.metadata["changelog_uri"] = "https://github.com/mael-ha/stability_ai/blob/main/CHANGELOG.md"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stability_ai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maël Harnois
|
|
@@ -22,10 +22,23 @@ files:
|
|
|
22
22
|
- CHANGELOG.md
|
|
23
23
|
- CODE_OF_CONDUCT.md
|
|
24
24
|
- Gemfile
|
|
25
|
+
- Gemfile.lock
|
|
25
26
|
- LICENSE.txt
|
|
26
27
|
- README.md
|
|
27
28
|
- Rakefile
|
|
28
29
|
- lib/stability_ai.rb
|
|
30
|
+
- lib/stability_ai/api.rb
|
|
31
|
+
- lib/stability_ai/api/engines.rb
|
|
32
|
+
- lib/stability_ai/api/generation.rb
|
|
33
|
+
- lib/stability_ai/api/user.rb
|
|
34
|
+
- lib/stability_ai/client.rb
|
|
35
|
+
- lib/stability_ai/configuration.rb
|
|
36
|
+
- lib/stability_ai/helpers/image_helper.rb
|
|
37
|
+
- lib/stability_ai/response/base_response.rb
|
|
38
|
+
- lib/stability_ai/response/engines_response.rb
|
|
39
|
+
- lib/stability_ai/response/image_response.rb
|
|
40
|
+
- lib/stability_ai/response/user_response.rb
|
|
41
|
+
- lib/stability_ai/stability_ai_error.rb
|
|
29
42
|
- lib/stability_ai/version.rb
|
|
30
43
|
- sig/stability_ai.rbs
|
|
31
44
|
- stability_ai.gemspec
|