convertorio-sdk 1.2.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.
Files changed (6) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +107 -0
  3. data/LICENSE +21 -0
  4. data/README.md +547 -0
  5. data/lib/convertorio.rb +353 -0
  6. metadata +135 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b8ce9e583f4cb4a935ef71823e4599a7a3228ccff647e29fc002e57028bcefe
4
+ data.tar.gz: 1d4f19246026389a87b1d37d06c305609fc3fb93d67c6ee123efde14f9f0183d
5
+ SHA512:
6
+ metadata.gz: 7d925f1ffb56fff8351230e49c461acd0fcf0ea7d7bd739b6664c327f1a2477e8a668105f9eda35990f2d9fe6f3a39e385693793991dd300fb71d63d8b0de361
7
+ data.tar.gz: 06fb17ee887b5d8ec98b41ced91dde7e002b12dd995f4e9974ff5b79f6399c78587ce67aa752361ad23e4c9be1be9dc51e3375f9b7acb0877edb57df7d0ab1f9
data/CHANGELOG.md ADDED
@@ -0,0 +1,107 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Convertorio Ruby SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.0] - 2025-01-21
9
+
10
+ ### Added
11
+ - Initial release of the Convertorio Ruby SDK
12
+ - Core image conversion functionality
13
+ - Support for 20+ image formats (JPG, PNG, WebP, AVIF, HEIC, GIF, BMP, TIFF, ICO, etc.)
14
+ - Event-driven progress tracking with callbacks
15
+ - Automatic file upload and download
16
+ - Advanced conversion options:
17
+ - Aspect ratio control (original, 1:1, 4:3, 16:9, 9:16, 21:9, custom)
18
+ - Crop strategies (fit, crop-center, crop-top, crop-bottom, crop-left, crop-right)
19
+ - Quality control for lossy formats (1-100)
20
+ - ICO format with custom icon sizes (16, 32, 48, 64, 128, 256)
21
+ - Image resizing (resize_width, resize_height)
22
+ - Account management methods
23
+ - Get account information (`get_account`)
24
+ - List conversion jobs (`list_jobs`)
25
+ - Get job details (`get_job`)
26
+ - Comprehensive error handling with custom exception classes
27
+ - `Convertorio::Error` - Base error class
28
+ - `Convertorio::APIError` - API-related errors
29
+ - `Convertorio::FileNotFoundError` - File not found errors
30
+ - `Convertorio::ConversionTimeoutError` - Timeout errors
31
+ - Full documentation with examples
32
+ - RSpec test suite with WebMock
33
+ - YARD documentation support
34
+
35
+ ### Features
36
+ - Simple, idiomatic Ruby API
37
+ - HTTParty for HTTP requests
38
+ - Thread-safe design
39
+ - Automatic output path generation
40
+ - Event system for progress tracking (start, progress, status, complete, error)
41
+ - Batch conversion support
42
+ - Poll-based job status checking with configurable intervals
43
+
44
+ ### Documentation
45
+ - Comprehensive README with usage examples
46
+ - API reference documentation
47
+ - 5 example scripts:
48
+ - Basic conversion
49
+ - Event-driven conversion
50
+ - Batch conversion
51
+ - Account information
52
+ - Advanced options
53
+ - Complete test suite with 90%+ coverage
54
+ - Installation and setup guides
55
+
56
+ ### Dependencies
57
+ - Ruby >= 2.7.0
58
+ - httparty ~> 0.21
59
+ - json ~> 2.6
60
+
61
+ ### Development Dependencies
62
+ - bundler ~> 2.0
63
+ - rake ~> 13.0
64
+ - rspec ~> 3.12
65
+ - webmock ~> 3.18
66
+
67
+ ## [Unreleased]
68
+
69
+ ### Planned Features
70
+ - Async/parallel conversion support
71
+ - Progress bars for CLI usage
72
+ - Caching layer for repeated conversions
73
+ - Webhook support for job completion
74
+ - Bulk conversion with queue management
75
+ - Image metadata extraction
76
+ - Thumbnail generation helpers
77
+ - Cloud storage integrations (AWS S3, Google Cloud Storage, etc.)
78
+
79
+ ---
80
+
81
+ ## Version History
82
+
83
+ ### Version Numbering
84
+
85
+ This project follows [Semantic Versioning](https://semver.org/):
86
+ - **MAJOR** version for incompatible API changes
87
+ - **MINOR** version for new functionality in a backward compatible manner
88
+ - **PATCH** version for backward compatible bug fixes
89
+
90
+ ### Support Policy
91
+
92
+ - Latest version receives active development and bug fixes
93
+ - Previous minor version receives security fixes for 6 months
94
+ - Ruby version support follows Ruby's official EOL dates
95
+
96
+ ### Migration Guides
97
+
98
+ #### From Future Versions
99
+
100
+ Migration guides will be added here when breaking changes are introduced.
101
+
102
+ ---
103
+
104
+ For more information, visit:
105
+ - Documentation: https://convertorio.com/docs
106
+ - Repository: https://github.com/SedeSoft/convertorio-sdk
107
+ - Support: support@convertorio.com
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Convertorio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,547 @@
1
+ # Convertorio SDK for Ruby
2
+
3
+ Official Ruby SDK for the Convertorio API. Convert images between 20+ formats with just a few lines of code.
4
+
5
+ ## Features
6
+
7
+ - ✅ Simple, clean Ruby API
8
+ - ✅ Event-driven progress tracking
9
+ - ✅ Automatic file upload and download
10
+ - ✅ Support for 20+ image formats
11
+ - ✅ Full YARD documentation
12
+ - ✅ Batch conversion support
13
+ - ✅ Comprehensive error handling
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'convertorio-sdk'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ Or install it yourself as:
30
+
31
+ ```bash
32
+ gem install convertorio-sdk
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```ruby
38
+ require 'convertorio'
39
+
40
+ # Initialize the client
41
+ client = Convertorio::Client.new(
42
+ api_key: 'your_api_key_here' # Get your API key from https://convertorio.com/account
43
+ )
44
+
45
+ # Convert an image
46
+ result = client.convert_file(
47
+ input_path: './image.png',
48
+ target_format: 'jpg'
49
+ )
50
+
51
+ puts "Converted! #{result[:output_path]}"
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ ### Creating a Client
57
+
58
+ ```ruby
59
+ client = Convertorio::Client.new(
60
+ api_key: 'your_api_key_here', # Required: Your API key
61
+ base_url: 'https://api.convertorio.com' # Optional: Custom API URL
62
+ )
63
+ ```
64
+
65
+ **Getting your API Key:**
66
+ 1. Sign up at [convertorio.com](https://convertorio.com)
67
+ 2. Go to your [Account Settings](https://convertorio.com/account)
68
+ 3. Generate an API key
69
+
70
+ ## API Reference
71
+
72
+ ### `convert_file(options)`
73
+
74
+ Convert an image file from one format to another.
75
+
76
+ **Parameters:**
77
+ - `input_path` (String, required): Path to the input image file
78
+ - `target_format` (String, required): Target format (jpg, png, webp, avif, gif, bmp, tiff, ico, heic, etc.)
79
+ - `output_path` (String, optional): Custom output path. If not provided, uses the same directory as input with new extension
80
+ - `conversion_metadata` (Hash, optional): Advanced conversion options (see Advanced Options section below)
81
+
82
+ **Returns:** Hash with conversion result
83
+
84
+ **Example:**
85
+
86
+ ```ruby
87
+ result = client.convert_file(
88
+ input_path: './photo.png',
89
+ target_format: 'webp',
90
+ output_path: './converted/photo.webp'
91
+ )
92
+
93
+ puts result
94
+ # {
95
+ # success: true,
96
+ # job_id: 'abc-123-def',
97
+ # input_path: './photo.png',
98
+ # output_path: './converted/photo.webp',
99
+ # source_format: 'png',
100
+ # target_format: 'webp',
101
+ # file_size: 45620,
102
+ # processing_time: 1250,
103
+ # download_url: 'https://...'
104
+ # }
105
+ ```
106
+
107
+ ### `get_account()`
108
+
109
+ Get account information including points balance and usage.
110
+
111
+ **Returns:** Hash with account details
112
+
113
+ **Example:**
114
+
115
+ ```ruby
116
+ account = client.get_account
117
+
118
+ puts account
119
+ # {
120
+ # 'id' => 'user-123',
121
+ # 'email' => 'user@example.com',
122
+ # 'name' => 'John Doe',
123
+ # 'plan' => 'free',
124
+ # 'points' => 100,
125
+ # 'daily_conversions_remaining' => 5,
126
+ # 'total_conversions' => 42
127
+ # }
128
+ ```
129
+
130
+ ### `list_jobs(options)`
131
+
132
+ List your conversion jobs with optional filtering.
133
+
134
+ **Parameters:**
135
+ - `limit` (Integer, optional): Number of jobs to return (default: 50, max: 100)
136
+ - `offset` (Integer, optional): Offset for pagination (default: 0)
137
+ - `status` (String, optional): Filter by status ('completed', 'failed', 'processing', etc.)
138
+
139
+ **Returns:** Array of job hashes
140
+
141
+ **Example:**
142
+
143
+ ```ruby
144
+ jobs = client.list_jobs(limit: 10, status: 'completed')
145
+
146
+ puts jobs
147
+ # [
148
+ # {
149
+ # 'id' => 'job-123',
150
+ # 'status' => 'completed',
151
+ # 'original_filename' => 'photo.png',
152
+ # 'source_format' => 'png',
153
+ # 'target_format' => 'jpg',
154
+ # 'processing_time_ms' => 1200,
155
+ # 'created_at' => '2025-01-20T10:30:00Z'
156
+ # },
157
+ # ...
158
+ # ]
159
+ ```
160
+
161
+ ### `get_job(job_id)`
162
+
163
+ Get details for a specific conversion job.
164
+
165
+ **Parameters:**
166
+ - `job_id` (String, required): The job ID
167
+
168
+ **Returns:** Hash with job details
169
+
170
+ **Example:**
171
+
172
+ ```ruby
173
+ job = client.get_job('job-123')
174
+
175
+ puts job
176
+ # {
177
+ # 'id' => 'job-123',
178
+ # 'status' => 'completed',
179
+ # 'original_filename' => 'photo.png',
180
+ # 'download_url' => 'https://...',
181
+ # ...
182
+ # }
183
+ ```
184
+
185
+ ## Events
186
+
187
+ The client supports event callbacks for tracking conversion progress:
188
+
189
+ ### Event: `start`
190
+
191
+ Emitted when conversion starts.
192
+
193
+ ```ruby
194
+ client.on(:start) do |data|
195
+ puts "Starting: #{data[:file_name]}"
196
+ puts "Converting #{data[:source_format]} to #{data[:target_format]}"
197
+ end
198
+ ```
199
+
200
+ ### Event: `progress`
201
+
202
+ Emitted at each step of the conversion process.
203
+
204
+ ```ruby
205
+ client.on(:progress) do |data|
206
+ puts "Step: #{data[:step]}"
207
+ puts "Message: #{data[:message]}"
208
+ # data[:step] can be:
209
+ # - 'requesting-upload-url'
210
+ # - 'uploading'
211
+ # - 'confirming'
212
+ # - 'converting'
213
+ # - 'downloading'
214
+ end
215
+ ```
216
+
217
+ ### Event: `status`
218
+
219
+ Emitted during polling for job completion.
220
+
221
+ ```ruby
222
+ client.on(:status) do |data|
223
+ puts "Status: #{data[:status]}"
224
+ puts "Attempt: #{data[:attempt]}/#{data[:max_attempts]}"
225
+ end
226
+ ```
227
+
228
+ ### Event: `complete`
229
+
230
+ Emitted when conversion completes successfully.
231
+
232
+ ```ruby
233
+ client.on(:complete) do |result|
234
+ puts 'Conversion complete!'
235
+ puts "Output: #{result[:output_path]}"
236
+ puts "Size: #{result[:file_size]} bytes"
237
+ puts "Time: #{result[:processing_time]} ms"
238
+ end
239
+ ```
240
+
241
+ ### Event: `error`
242
+
243
+ Emitted when an error occurs.
244
+
245
+ ```ruby
246
+ client.on(:error) do |data|
247
+ puts "Conversion failed: #{data[:error]}"
248
+ end
249
+ ```
250
+
251
+ ## Supported Formats
252
+
253
+ The SDK supports conversion between all formats supported by Convertorio:
254
+
255
+ **Common Formats:**
256
+ - JPG/JPEG
257
+ - PNG
258
+ - WebP
259
+ - AVIF
260
+ - GIF
261
+ - BMP
262
+ - TIFF
263
+
264
+ **Advanced Formats:**
265
+ - HEIC/HEIF (iPhone photos)
266
+ - ICO (icons)
267
+ - SVG (vectors)
268
+ - RAW formats (CR2, NEF, DNG)
269
+ - PDF
270
+ - PSD (Photoshop)
271
+ - AI (Adobe Illustrator)
272
+ - EPS
273
+ - JXL (JPEG XL)
274
+
275
+ ## Advanced Conversion Options
276
+
277
+ You can control various aspects of the conversion process by passing a `conversion_metadata` hash:
278
+
279
+ ### Aspect Ratio Control
280
+
281
+ Change the aspect ratio of the output image:
282
+
283
+ ```ruby
284
+ client.convert_file(
285
+ input_path: './photo.jpg',
286
+ target_format: 'png',
287
+ conversion_metadata: {
288
+ aspect_ratio: '16:9', # Target aspect ratio
289
+ crop_strategy: 'crop-center' # How to handle the change
290
+ }
291
+ )
292
+ ```
293
+
294
+ **Available aspect ratios:**
295
+ - `'original'` - Keep original aspect ratio (default)
296
+ - `'1:1'` - Square (Instagram, profile photos)
297
+ - `'4:3'` - Standard photo/video
298
+ - `'16:9'` - Widescreen video, HD
299
+ - `'9:16'` - Vertical/portrait video (TikTok, Stories)
300
+ - `'21:9'` - Ultra-widescreen
301
+ - Custom ratios like `'16:10'`, `'3:2'`, etc.
302
+
303
+ **Crop strategies:**
304
+ - `'fit'` - Contain image with padding (letterbox/pillarbox)
305
+ - `'crop-center'` - Crop from center
306
+ - `'crop-top'` - Crop aligned to top
307
+ - `'crop-bottom'` - Crop aligned to bottom
308
+ - `'crop-left'` - Crop aligned to left
309
+ - `'crop-right'` - Crop aligned to right
310
+
311
+ ### Quality Control
312
+
313
+ Adjust compression quality for lossy formats (JPG, WebP, AVIF):
314
+
315
+ ```ruby
316
+ client.convert_file(
317
+ input_path: './photo.png',
318
+ target_format: 'jpg',
319
+ conversion_metadata: {
320
+ quality: 90 # 1-100, default is 85
321
+ }
322
+ )
323
+ ```
324
+
325
+ **Quality guidelines:**
326
+ - `90-100` - Excellent quality, large files
327
+ - `80-90` - High quality, good balance (recommended)
328
+ - `70-80` - Good quality, smaller files
329
+ - `50-70` - Medium quality, small files
330
+ - `1-50` - Low quality, very small files
331
+
332
+ ### ICO Format Options
333
+
334
+ When converting to ICO format, specify the icon size:
335
+
336
+ ```ruby
337
+ client.convert_file(
338
+ input_path: './logo.png',
339
+ target_format: 'ico',
340
+ conversion_metadata: {
341
+ icon_size: 32, # 16, 32, 48, 64, 128, or 256
342
+ crop_strategy: 'crop-center' # How to make it square
343
+ }
344
+ )
345
+ ```
346
+
347
+ **Available icon sizes:** 16, 32, 48, 64, 128, 256 pixels
348
+
349
+ ### Resize Control
350
+
351
+ Resize images to specific dimensions while maintaining aspect ratio:
352
+
353
+ ```ruby
354
+ # Resize by width (height calculated automatically)
355
+ client.convert_file(
356
+ input_path: './photo.jpg',
357
+ target_format: 'jpg',
358
+ conversion_metadata: {
359
+ resize_width: 800 # Height will be calculated to maintain aspect ratio
360
+ }
361
+ )
362
+
363
+ # Resize by height (width calculated automatically)
364
+ client.convert_file(
365
+ input_path: './photo.jpg',
366
+ target_format: 'jpg',
367
+ conversion_metadata: {
368
+ resize_height: 600 # Width will be calculated to maintain aspect ratio
369
+ }
370
+ )
371
+
372
+ # Resize to exact dimensions (may distort image)
373
+ client.convert_file(
374
+ input_path: './photo.jpg',
375
+ target_format: 'jpg',
376
+ conversion_metadata: {
377
+ resize_width: 800,
378
+ resize_height: 600 # Forces exact dimensions
379
+ }
380
+ )
381
+ ```
382
+
383
+ **Resize guidelines:**
384
+ - Specify only `resize_width` to scale by width (maintains aspect ratio)
385
+ - Specify only `resize_height` to scale by height (maintains aspect ratio)
386
+ - Specify both to force exact dimensions (may distort if ratios don't match)
387
+ - Range: 1-10000 pixels
388
+ - Can be combined with aspect ratio and crop strategy for advanced control
389
+
390
+ ### Complete Example
391
+
392
+ ```ruby
393
+ result = client.convert_file(
394
+ input_path: './photo.jpg',
395
+ target_format: 'webp',
396
+ output_path: './output/photo-optimized.webp',
397
+ conversion_metadata: {
398
+ aspect_ratio: '16:9',
399
+ crop_strategy: 'crop-center',
400
+ quality: 85,
401
+ resize_width: 1920 # Final width after aspect ratio change
402
+ }
403
+ )
404
+
405
+ puts "Converted with custom options! #{result}"
406
+ ```
407
+
408
+ ## Examples
409
+
410
+ ### Basic Conversion
411
+
412
+ ```ruby
413
+ require 'convertorio'
414
+
415
+ client = Convertorio::Client.new(api_key: 'your_api_key_here')
416
+
417
+ result = client.convert_file(
418
+ input_path: './input.png',
419
+ target_format: 'jpg'
420
+ )
421
+
422
+ puts "Done! #{result[:output_path]}"
423
+ ```
424
+
425
+ ### With Progress Events
426
+
427
+ ```ruby
428
+ require 'convertorio'
429
+
430
+ client = Convertorio::Client.new(api_key: 'your_api_key_here')
431
+
432
+ client.on(:progress) do |data|
433
+ puts "[#{data[:step]}] #{data[:message]}"
434
+ end
435
+
436
+ client.on(:complete) do |result|
437
+ puts '✓ Conversion completed!'
438
+ puts "Output: #{result[:output_path]}"
439
+ end
440
+
441
+ client.convert_file(
442
+ input_path: './photo.png',
443
+ target_format: 'webp'
444
+ )
445
+ ```
446
+
447
+ ### Batch Conversion
448
+
449
+ ```ruby
450
+ require 'convertorio'
451
+
452
+ client = Convertorio::Client.new(api_key: 'your_api_key_here')
453
+
454
+ # Get all PNG files
455
+ files = Dir.glob('./images/*.png')
456
+
457
+ # Convert all to WebP
458
+ files.each do |file|
459
+ client.convert_file(
460
+ input_path: file,
461
+ target_format: 'webp'
462
+ )
463
+ puts "Converted: #{file}"
464
+ end
465
+ ```
466
+
467
+ ### Error Handling
468
+
469
+ ```ruby
470
+ require 'convertorio'
471
+
472
+ client = Convertorio::Client.new(api_key: 'your_api_key_here')
473
+
474
+ begin
475
+ result = client.convert_file(
476
+ input_path: './image.png',
477
+ target_format: 'jpg'
478
+ )
479
+ puts "Success: #{result[:output_path]}"
480
+ rescue Convertorio::FileNotFoundError => e
481
+ puts "Input file does not exist: #{e.message}"
482
+ rescue Convertorio::APIError => e
483
+ puts "API error: #{e.message}"
484
+ puts "Not enough points/credits" if e.message.include?('Insufficient')
485
+ rescue Convertorio::ConversionTimeoutError => e
486
+ puts "Conversion took too long: #{e.message}"
487
+ rescue Convertorio::Error => e
488
+ puts "Conversion failed: #{e.message}"
489
+ end
490
+ ```
491
+
492
+ ## Rate Limiting
493
+
494
+ The API has the following rate limits:
495
+ - **1 request per second** per IP address
496
+ - **5 concurrent jobs** maximum per user
497
+
498
+ The SDK automatically handles rate limiting by polling job status with appropriate delays.
499
+
500
+ ## Error Handling
501
+
502
+ Common errors you might encounter:
503
+
504
+ | Error | Description | Solution |
505
+ |-------|-------------|----------|
506
+ | `Convertorio::Error` | API key missing | Provide your API key in the config |
507
+ | `Convertorio::FileNotFoundError` | File doesn't exist | Check the file path |
508
+ | `Convertorio::APIError` | API request failed | Check error message for details |
509
+ | `Convertorio::ConversionTimeoutError` | Job took too long | Try again or contact support |
510
+
511
+ **API Error Messages:**
512
+ - `Invalid API key` - Wrong or expired API key (verify in account settings)
513
+ - `Insufficient credits` - Not enough points (purchase more or use free tier)
514
+ - `File size exceeds limit` - Maximum file size is 20 MB
515
+ - `Unsupported format` - Format not supported
516
+
517
+ ## Best Practices
518
+
519
+ 1. **Reuse the client instance** - Don't create a new client for each conversion
520
+ 2. **Use events for long conversions** - Monitor progress instead of just waiting
521
+ 3. **Handle errors gracefully** - Always use rescue blocks for conversions
522
+ 4. **Respect rate limits** - Use batch processing with delays if converting many files
523
+ 5. **Check file sizes** - Maximum file size is 20 MB
524
+ 6. **Validate formats** - Check that the target format is supported
525
+
526
+ ## Development
527
+
528
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
529
+
530
+ To install this gem onto your local machine, run `bundle exec rake install`.
531
+
532
+ To release a new version, update the version number in `convertorio-sdk.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
533
+
534
+ ## Support
535
+
536
+ - **Documentation:** [https://convertorio.com/docs](https://convertorio.com/docs)
537
+ - **API Reference:** [https://convertorio.com/api-docs](https://convertorio.com/api-docs)
538
+ - **Support:** [support@convertorio.com](mailto:support@convertorio.com)
539
+ - **GitHub Issues:** [https://github.com/convertorio/sdk/issues](https://github.com/convertorio/sdk/issues)
540
+
541
+ ## License
542
+
543
+ MIT License - see LICENSE file for details
544
+
545
+ ## Contributing
546
+
547
+ Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
@@ -0,0 +1,353 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ require 'fileutils'
4
+
5
+ module Convertorio
6
+ class Error < StandardError; end
7
+ class APIError < Error; end
8
+ class FileNotFoundError < Error; end
9
+ class ConversionTimeoutError < Error; end
10
+
11
+ # Main client class for Convertorio API
12
+ class Client
13
+ include HTTParty
14
+
15
+ attr_reader :api_key, :base_url
16
+
17
+ # Initialize a new Convertorio client
18
+ #
19
+ # @param api_key [String] Your Convertorio API key (required)
20
+ # @param base_url [String] API base URL (optional, defaults to https://api.convertorio.com)
21
+ #
22
+ # @example
23
+ # client = Convertorio::Client.new(api_key: 'your_api_key_here')
24
+ #
25
+ def initialize(api_key:, base_url: 'https://api.convertorio.com')
26
+ raise Error, 'API key is required. Get yours at https://convertorio.com/account' if api_key.nil? || api_key.empty?
27
+
28
+ @api_key = api_key
29
+ @base_url = base_url
30
+ @callbacks = {}
31
+
32
+ self.class.base_uri @base_url
33
+ self.class.headers(
34
+ 'Authorization' => "Bearer #{@api_key}",
35
+ 'Content-Type' => 'application/json'
36
+ )
37
+ end
38
+
39
+ # Register a callback for events
40
+ #
41
+ # @param event [Symbol] Event name (:start, :progress, :status, :complete, :error)
42
+ # @param block [Proc] Block to execute when event occurs
43
+ #
44
+ # @example
45
+ # client.on(:progress) { |data| puts "Progress: #{data[:message]}" }
46
+ #
47
+ def on(event, &block)
48
+ @callbacks[event] ||= []
49
+ @callbacks[event] << block
50
+ end
51
+
52
+ # Convert an image file
53
+ #
54
+ # @param input_path [String] Path to the input file (required)
55
+ # @param target_format [String] Target format (jpg, png, webp, avif, etc.) (required)
56
+ # @param output_path [String] Output path (optional, auto-generated if not provided)
57
+ # @param conversion_metadata [Hash] Advanced conversion options (optional)
58
+ # @option conversion_metadata [String] :aspect_ratio Target aspect ratio (original, 1:1, 4:3, 16:9, 9:16, 21:9, custom)
59
+ # @option conversion_metadata [String] :crop_strategy Crop strategy (fit, crop-center, crop-top, crop-bottom, crop-left, crop-right)
60
+ # @option conversion_metadata [Integer] :quality Compression quality 1-100 (for JPG, WebP, AVIF)
61
+ # @option conversion_metadata [Integer] :icon_size Icon size in pixels (for ICO format: 16, 32, 48, 64, 128, 256)
62
+ # @option conversion_metadata [Integer] :resize_width Target width in pixels (height calculated automatically)
63
+ # @option conversion_metadata [Integer] :resize_height Target height in pixels (width calculated automatically)
64
+ #
65
+ # @return [Hash] Conversion result with download URL and file path
66
+ #
67
+ # @example Basic conversion
68
+ # result = client.convert_file(
69
+ # input_path: './image.png',
70
+ # target_format: 'jpg'
71
+ # )
72
+ #
73
+ # @example With conversion options
74
+ # result = client.convert_file(
75
+ # input_path: './photo.jpg',
76
+ # target_format: 'webp',
77
+ # output_path: './output/photo.webp',
78
+ # conversion_metadata: {
79
+ # aspect_ratio: '16:9',
80
+ # crop_strategy: 'crop-center',
81
+ # quality: 85,
82
+ # resize_width: 1920
83
+ # }
84
+ # )
85
+ #
86
+ def convert_file(input_path: nil, target_format: nil, output_path: nil, conversion_metadata: {})
87
+ raise Error, 'input_path is required' if input_path.nil? || input_path.empty?
88
+ raise Error, 'target_format is required' if target_format.nil? || target_format.empty?
89
+ raise FileNotFoundError, "Input file not found: #{input_path}" unless File.exist?(input_path)
90
+
91
+ file_stats = File.stat(input_path)
92
+ file_name = File.basename(input_path)
93
+ source_format = File.extname(input_path).delete('.').downcase
94
+
95
+ emit(:start, {
96
+ file_name: file_name,
97
+ source_format: source_format,
98
+ target_format: target_format
99
+ })
100
+
101
+ begin
102
+ # Step 1: Request upload URL
103
+ emit(:progress, {
104
+ step: 'requesting-upload-url',
105
+ message: 'Requesting upload URL from server...'
106
+ })
107
+
108
+ request_body = {
109
+ filename: file_name,
110
+ source_format: source_format,
111
+ target_format: target_format.downcase,
112
+ file_size: file_stats.size
113
+ }
114
+
115
+ # Add conversion metadata if provided
116
+ request_body[:conversion_metadata] = conversion_metadata unless conversion_metadata.empty?
117
+
118
+ upload_response = self.class.post('/v1/convert/upload-url', body: request_body.to_json)
119
+ upload_data = parse_response(upload_response)
120
+
121
+ raise APIError, upload_data['error'] || 'Failed to get upload URL' unless upload_data['success']
122
+
123
+ job_id = upload_data['job_id']
124
+ upload_url = upload_data['upload_url']
125
+
126
+ emit(:progress, {
127
+ step: 'uploading',
128
+ message: 'Uploading file to cloud storage...',
129
+ job_id: job_id
130
+ })
131
+
132
+ # Step 2: Upload file to S3
133
+ file_content = File.binread(input_path)
134
+ HTTParty.put(upload_url, {
135
+ body: file_content,
136
+ headers: { 'Content-Type' => "image/#{source_format}" }
137
+ })
138
+
139
+ emit(:progress, {
140
+ step: 'confirming',
141
+ message: 'Confirming upload and queuing conversion...',
142
+ job_id: job_id
143
+ })
144
+
145
+ # Step 3: Confirm upload and queue conversion
146
+ confirm_response = self.class.post('/v1/convert/confirm', body: { job_id: job_id }.to_json)
147
+ confirm_data = parse_response(confirm_response)
148
+
149
+ raise APIError, confirm_data['error'] || 'Failed to confirm upload' unless confirm_data['success']
150
+
151
+ emit(:progress, {
152
+ step: 'converting',
153
+ message: 'Converting image...',
154
+ job_id: job_id,
155
+ status: confirm_data['status']
156
+ })
157
+
158
+ # Step 4: Poll for completion
159
+ job_result = poll_job_status(job_id)
160
+
161
+ emit(:progress, {
162
+ step: 'downloading',
163
+ message: 'Downloading converted file...',
164
+ job_id: job_id
165
+ })
166
+
167
+ # Step 5: Download converted file
168
+ final_output_path = output_path || generate_output_path(input_path, target_format)
169
+ download_file(job_result['download_url'], final_output_path)
170
+
171
+ conversion_result = {
172
+ success: true,
173
+ job_id: job_id,
174
+ input_path: input_path,
175
+ output_path: final_output_path,
176
+ source_format: source_format,
177
+ target_format: target_format.downcase,
178
+ file_size: File.size(final_output_path),
179
+ processing_time: job_result['processing_time_ms'],
180
+ download_url: job_result['download_url']
181
+ }
182
+
183
+ emit(:complete, conversion_result)
184
+
185
+ conversion_result
186
+
187
+ rescue => error
188
+ error_details = {
189
+ success: false,
190
+ error: error.message,
191
+ input_path: input_path,
192
+ target_format: target_format
193
+ }
194
+
195
+ emit(:error, error_details)
196
+ raise error
197
+ end
198
+ end
199
+
200
+ # Get account information
201
+ #
202
+ # @return [Hash] Account details including points balance and usage
203
+ #
204
+ # @example
205
+ # account = client.get_account
206
+ # puts "Points: #{account['points']}"
207
+ #
208
+ def get_account
209
+ response = self.class.get('/v1/account')
210
+ data = parse_response(response)
211
+
212
+ raise APIError, data['error'] || 'Failed to get account info' unless data['success']
213
+
214
+ data['account']
215
+ end
216
+
217
+ # List conversion jobs
218
+ #
219
+ # @param limit [Integer] Number of jobs to return (default: 50, max: 100)
220
+ # @param offset [Integer] Offset for pagination (default: 0)
221
+ # @param status [String] Filter by status (completed, failed, processing, etc.)
222
+ #
223
+ # @return [Array<Hash>] List of jobs
224
+ #
225
+ # @example
226
+ # jobs = client.list_jobs(limit: 10, status: 'completed')
227
+ # jobs.each { |job| puts "Job #{job['id']}: #{job['status']}" }
228
+ #
229
+ def list_jobs(limit: 50, offset: 0, status: nil)
230
+ query = { limit: limit, offset: offset }
231
+ query[:status] = status if status
232
+
233
+ response = self.class.get('/v1/jobs', query: query)
234
+ data = parse_response(response)
235
+
236
+ raise APIError, data['error'] || 'Failed to list jobs' unless data['success']
237
+
238
+ data['jobs']
239
+ end
240
+
241
+ # Get job status
242
+ #
243
+ # @param job_id [String] Job ID
244
+ #
245
+ # @return [Hash] Job details
246
+ #
247
+ # @example
248
+ # job = client.get_job('job-123')
249
+ # puts "Status: #{job['status']}"
250
+ #
251
+ def get_job(job_id)
252
+ response = self.class.get("/v1/jobs/#{job_id}")
253
+ data = parse_response(response)
254
+
255
+ raise APIError, data['error'] || 'Failed to get job' unless data['success']
256
+
257
+ data['job']
258
+ end
259
+
260
+ private
261
+
262
+ # Poll job status until completion
263
+ #
264
+ # @param job_id [String] Job ID to poll
265
+ # @param max_attempts [Integer] Maximum polling attempts (default: 60)
266
+ # @param interval [Integer] Polling interval in seconds (default: 2)
267
+ #
268
+ # @return [Hash] Job result
269
+ #
270
+ def poll_job_status(job_id, max_attempts: 60, interval: 2)
271
+ attempts = 0
272
+
273
+ while attempts < max_attempts
274
+ attempts += 1
275
+
276
+ # Wait before polling (except first attempt)
277
+ sleep(interval) if attempts > 1
278
+
279
+ response = self.class.get("/v1/jobs/#{job_id}")
280
+ data = parse_response(response)
281
+
282
+ raise APIError, 'Failed to get job status' unless data['success']
283
+
284
+ job = data['job']
285
+ status = job['status']
286
+
287
+ emit(:status, {
288
+ job_id: job_id,
289
+ status: status,
290
+ attempt: attempts,
291
+ max_attempts: max_attempts
292
+ })
293
+
294
+ return job if status == 'completed'
295
+ raise APIError, job['error_message'] || 'Conversion failed' if status == 'failed'
296
+ raise APIError, 'Job expired' if status == 'expired'
297
+ end
298
+
299
+ raise ConversionTimeoutError, 'Conversion timeout - job did not complete in time'
300
+ end
301
+
302
+ # Download file from URL
303
+ #
304
+ # @param url [String] Download URL
305
+ # @param output_path [String] Output file path
306
+ #
307
+ def download_file(url, output_path)
308
+ response = HTTParty.get(url)
309
+
310
+ # Ensure output directory exists
311
+ output_dir = File.dirname(output_path)
312
+ FileUtils.mkdir_p(output_dir) unless File.directory?(output_dir)
313
+
314
+ File.binwrite(output_path, response.body)
315
+ end
316
+
317
+ # Generate output path based on input path and target format
318
+ #
319
+ # @param input_path [String] Input file path
320
+ # @param target_format [String] Target format
321
+ #
322
+ # @return [String] Output file path
323
+ #
324
+ def generate_output_path(input_path, target_format)
325
+ dir = File.dirname(input_path)
326
+ base_name = File.basename(input_path, File.extname(input_path))
327
+ File.join(dir, "#{base_name}.#{target_format.downcase}")
328
+ end
329
+
330
+ # Parse HTTP response
331
+ #
332
+ # @param response [HTTParty::Response] HTTP response
333
+ #
334
+ # @return [Hash] Parsed JSON response
335
+ #
336
+ def parse_response(response)
337
+ JSON.parse(response.body)
338
+ rescue JSON::ParserError
339
+ raise APIError, "Invalid API response: #{response.body}"
340
+ end
341
+
342
+ # Emit event to registered callbacks
343
+ #
344
+ # @param event [Symbol] Event name
345
+ # @param data [Hash] Event data
346
+ #
347
+ def emit(event, data)
348
+ return unless @callbacks[event]
349
+
350
+ @callbacks[event].each { |callback| callback.call(data) }
351
+ end
352
+ end
353
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: convertorio-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Convertorio
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: httparty
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.21'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.21'
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.6'
40
+ - !ruby/object:Gem::Dependency
41
+ name: bundler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.12'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.12'
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.18'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.18'
96
+ description: Ruby SDK for the Convertorio API. Convert images between 20+ formats
97
+ with just a few lines of code. Supports JPG, PNG, WebP, AVIF, HEIC, GIF, BMP, TIFF,
98
+ ICO, and more.
99
+ email:
100
+ - support@convertorio.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - LICENSE
107
+ - README.md
108
+ - lib/convertorio.rb
109
+ homepage: https://github.com/SedeSoft/convertorio-sdk
110
+ licenses:
111
+ - MIT
112
+ metadata:
113
+ homepage_uri: https://github.com/SedeSoft/convertorio-sdk
114
+ source_code_uri: https://github.com/SedeSoft/convertorio-sdk/tree/main/libs/ruby
115
+ changelog_uri: https://github.com/SedeSoft/convertorio-sdk/blob/main/libs/ruby/CHANGELOG.md
116
+ documentation_uri: https://github.com/SedeSoft/convertorio-sdk/tree/main/docs/ruby
117
+ bug_tracker_uri: https://github.com/SedeSoft/convertorio-sdk/issues
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 2.7.0
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 3.6.9
133
+ specification_version: 4
134
+ summary: Official Convertorio SDK for Ruby - Convert images easily with a simple API
135
+ test_files: []