| Crates.io | tinify |
| lib.rs | tinify |
| version | 0.1.0 |
| created_at | 2025-08-31 12:59:49.956463+00 |
| updated_at | 2025-08-31 12:59:49.956463+00 |
| description | A high-performance Rust client for the Tinify API, providing image compression and optimization capabilities |
| homepage | https://github.com/raynoryim/tinify |
| repository | https://github.com/raynoryim/tinify |
| max_upload_size | |
| id | 1818535 |
| size | 320,325 |
English | δΈζ
A high-performance Rust library for image compression and optimization, built on the TinyPNG API. Provides async support, intelligent retry mechanisms, rate limiting, and cloud storage integration.
Add to your Cargo.toml:
[dependencies]
tinify = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
use tinify::Tinify;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize client
let client = Tinify::new("your-api-key".to_string())?;
// Compress image
let source = client.source_from_file("input.png").await?;
source.to_file("output.png").await?;
println!("Image compression completed!");
Ok(())
}
use tinify::Tinify;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use builder pattern for advanced configuration
let client = Tinify::builder()
.api_key("your-api-key")
.app_identifier("MyApp/1.0")
.timeout(Duration::from_secs(30))
.max_retry_attempts(3)
.requests_per_minute(100)
.build()?;
let source = client.source_from_file("input.png").await?;
source.to_file("output.png").await?;
Ok(())
}
use tinify::{Tinify, ResizeOptions, ResizeMethod};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.png").await?;
// Configure resize options
let resize_options = ResizeOptions {
method: ResizeMethod::Fit,
width: Some(300),
height: Some(200),
};
// Resize image
let mut result = source.resize(resize_options).await?;
result.to_file("resized.png").await?;
// Get image information
if let Some(width) = result.image_width() {
println!("Resized width: {} pixels", width);
}
Ok(())
}
use tinify::{Tinify, ConvertOptions, ImageFormat};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.png").await?;
// Convert to WebP format
let convert_options = ConvertOptions {
format: ImageFormat::WebP,
background: Some("#FFFFFF".to_string()),
};
let mut result = source.convert(convert_options).await?;
result.to_file("output.webp").await?;
Ok(())
}
use tinify::{Tinify, PreserveOptions, PreserveMetadata};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.jpg").await?;
// Preserve copyright and creation time
let preserve_options = PreserveOptions {
preserve: vec![
PreserveMetadata::Copyright,
PreserveMetadata::Creation,
],
};
let mut result = source.preserve(preserve_options).await?;
result.to_file("preserved.jpg").await?;
Ok(())
}
use tinify::{Tinify, StoreOptions, S3Options};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.png").await?;
// Configure S3 storage options
let s3_options = S3Options {
service: "s3".to_string(),
aws_access_key_id: "your-access-key".to_string(),
aws_secret_access_key: "your-secret-key".to_string(),
region: "us-east-1".to_string(),
path: "my-bucket/images/compressed.png".to_string(),
headers: None,
acl: Some("public-read".to_string()),
};
// Store directly to S3
let result = source.store(StoreOptions::S3(s3_options)).await?;
if let Some(count) = result.compression_count() {
println!("API usage count: {}", count);
}
Ok(())
}
use tinify::{Tinify, StoreOptions, GCSOptions};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.png").await?;
// Configure GCS storage options
let gcs_options = GCSOptions {
service: "gcs".to_string(),
gcp_access_token: "your-access-token".to_string(),
path: "my-bucket/images/compressed.png".to_string(),
headers: Some(json!({
"Cache-Control": "public, max-age=31536000",
"X-Goog-Meta-Source": "tinify-rs"
})),
};
// Store directly to GCS
let result = source.store(StoreOptions::GCS(gcs_options)).await?;
Ok(())
}
use tinify::Tinify;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
// Load image from URL
let source = client.source_from_url("https://example.com/image.jpg").await?;
source.to_file("compressed.jpg").await?;
Ok(())
}
use tinify::Tinify;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
// Create source from in-memory bytes
let image_data = std::fs::read("input.png")?;
let source = client.source_from_buffer(image_data).await?;
// Get compressed bytes
let compressed_data = source.to_buffer().await?;
std::fs::write("output.png", compressed_data)?;
Ok(())
}
| Method | Description | Use Case |
|---|---|---|
Scale |
Proportional scaling | Precise width or height control |
Fit |
Fit within dimensions (preserve aspect ratio) | Create largest image within bounds |
Cover |
Cover dimensions (may crop) | Fill exact dimensions, preserve ratio |
Thumb |
Smart thumbnail | Auto-detect important regions |
| Format | Input Support | Output Support | Description |
|---|---|---|---|
| PNG | β | β | Lossless compression, transparency support |
| JPEG | β | β | Lossy compression, ideal for photos |
| WebP | β | β | Modern format, smaller file sizes |
| AVIF | β | β | Next-gen format, best compression |
| Service | Support Status | Notes |
|---|---|---|
| AWS S3 | β | Full support with custom headers and ACL |
| Google Cloud Storage | β | Full support with metadata |
| S3-Compatible Services | β | MinIO, DigitalOcean Spaces, Backblaze B2, etc. |
The library provides comprehensive error types:
use tinify::{Tinify, TinifyError};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("api-key".to_string())?;
match client.source_from_file("input.png").await {
Ok(source) => {
println!("Processing successful");
// Continue processing...
}
Err(TinifyError::FileNotFound { path }) => {
println!("File not found: {}", path);
}
Err(TinifyError::UnsupportedFormat { format }) => {
println!("Unsupported format: {}", format);
}
Err(TinifyError::FileTooLarge { size, max_size }) => {
println!("File too large: {} bytes (max: {} bytes)", size, max_size);
}
Err(TinifyError::QuotaExceeded) => {
println!("API quota exhausted");
}
Err(TinifyError::AccountError { status, message }) => {
println!("Account error [{}]: {}", status, message);
}
Err(e) => {
println!("Other error: {}", e);
}
}
Ok(())
}
use tinify::Tinify;
use tokio::task::JoinSet;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let mut join_set = JoinSet::new();
// Process multiple images concurrently
let files = vec!["image1.png", "image2.jpg", "image3.webp"];
for (i, file) in files.iter().enumerate() {
let client = client.clone();
let file = file.to_string();
join_set.spawn(async move {
let source = client.source_from_file(&file).await?;
let output = format!("compressed_{}.png", i);
source.to_file(&output).await?;
Ok::<String, tinify::TinifyError>(output)
});
}
// Wait for all tasks to complete
while let Some(result) = join_set.join_next().await {
match result {
Ok(Ok(filename)) => println!("β
Compressed: {}", filename),
Ok(Err(e)) => println!("β Compression failed: {}", e),
Err(e) => println!("β Task error: {}", e),
}
}
Ok(())
}
use tinify::{Tinify, ResizeOptions, ResizeMethod};
async fn batch_process_images(
client: &Tinify,
input_files: Vec<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
for file in input_files {
// Compress and resize
let source = client.source_from_file(file).await?;
let resize_options = ResizeOptions {
method: ResizeMethod::Fit,
width: Some(800),
height: Some(600),
};
let mut result = source.resize(resize_options).await?;
let output = format!("processed_{}", file);
result.to_file(&output).await?;
println!("β
Processed: {} -> {}", file, output);
}
Ok(())
}
use tinify::{Tinify, StoreOptions, S3Options};
use serde_json::json;
// Basic S3 upload
let s3_options = S3Options {
service: "s3".to_string(),
aws_access_key_id: "your-access-key".to_string(),
aws_secret_access_key: "your-secret-key".to_string(),
region: "us-east-1".to_string(),
path: "my-bucket/images/compressed.png".to_string(),
headers: None,
acl: Some("public-read".to_string()),
};
// S3 upload with custom headers
let s3_options_with_headers = S3Options {
service: "s3".to_string(),
aws_access_key_id: "your-access-key".to_string(),
aws_secret_access_key: "your-secret-key".to_string(),
region: "us-east-1".to_string(),
path: "my-bucket/images/compressed.png".to_string(),
headers: Some(json!({
"Cache-Control": "public, max-age=31536000",
"Content-Disposition": "inline; filename=\"optimized.png\""
})),
acl: Some("public-read".to_string()),
};
let source = client.source_from_file("input.png").await?;
let result = source.store(StoreOptions::S3(s3_options)).await?;
Supports various S3-compatible storage services:
// MinIO configuration example
let minio_options = S3Options {
service: "s3".to_string(),
aws_access_key_id: "minioadmin".to_string(),
aws_secret_access_key: "minioadmin".to_string(),
region: "us-east-1".to_string(),
path: "test-bucket/compressed.png".to_string(),
headers: None,
acl: None,
};
Check out examples in the examples/ directory:
01_compressing_images.rs - Basic image compression02_resizing_images.rs - Image resizing operations03_converting_images.rs - Format conversion04_preserving_metadata.rs - Metadata preservation05_saving_to_s3.rs - AWS S3 storage06_saving_to_gcs.rs - Google Cloud Storage07_error_handling.rs - Error handling patterns08_compression_count.rs - Compression counter tracking09_s3_compatible_storage.rs - S3-compatible services10_comprehensive_demo.rs - Complete feature demonstrationRun examples:
# Basic compression example
cargo run --example 01_compressing_images
# Cloud storage test
export TINIFY_API_KEY="your-api-key"
export AWS_ACCESS_KEY_ID="your-aws-key"
export AWS_SECRET_ACCESS_KEY="your-aws-secret"
cargo run --example 05_saving_to_s3
# Error handling demonstration
cargo run --example 07_error_handling
use tinify::Tinify;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Tinify::new("your-api-key".to_string())?;
let source = client.source_from_file("input.png").await?;
let result = source.to_buffer().await?;
// Check compression count
if let Some(count) = result.compression_count() {
println!("Current API usage: {}", count);
if count > 450 {
println!("β οΈ Approaching free quota limit (500/month)");
}
}
Ok(())
}
# Tinify API configuration
export TINIFY_API_KEY="your-tinify-api-key"
# AWS S3 configuration
export AWS_ACCESS_KEY_ID="your-aws-access-key"
export AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
# Google Cloud Storage configuration
export GCP_ACCESS_TOKEN="your-gcp-access-token"
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# Run all tests
cargo test
# Run doc tests
cargo test --doc
# Run specific example
cargo run --example 01_compressing_images
# Test with real images
cargo run --example test_real_image
# Cloud storage integration tests
./test_cloud_storage.sh
// Recommended error handling pattern
match client.source_from_file("input.png").await {
Ok(source) => {
// Successful processing
}
Err(TinifyError::QuotaExceeded) => {
// Quota exhausted, stop processing or wait for next month
eprintln!("API quota exhausted, wait for next month or upgrade plan");
}
Err(TinifyError::FileTooLarge { size, max_size }) => {
// File too large, consider preprocessing
eprintln!("File too large: {} bytes (max: {})", size, max_size);
}
Err(e) => {
// Other errors, log and possibly retry
eprintln!("Compression failed: {}", e);
}
}
We welcome contributions of all kinds!
# Clone repository
git clone https://github.com/raynoryim/tinify.git
cd tinify-rs
# Install dependencies and run tests
cargo test
# Run clippy checks
cargo clippy
# Run formatting
cargo fmt
# Run all checks
cargo check --examples
git checkout -b feature/amazing-featuregit commit -m 'feat: add amazing feature'git push origin feature/amazing-featurePlease report bugs or request features in GitHub Issues.
This project is licensed under the MIT License. See the LICENSE file for details.
β If this project helps you, please give us a star!