supaphoto
v1.0.2
Published
CLI tool and library to save photos to Supabase with automatic metadata extraction
Downloads
276
Maintainers
Readme
SupaPhoto
A cost-effective CLI tool and Node.js library to save photos to Supabase with automatic metadata extraction.
Use it as a command-line tool for quick uploads, or import it as a library in your Node.js projects.
Architecture
- Photos: Stored in Supabase Storage (cheap object storage ~$0.021/GB/month)
- Metadata: Stored in PostgreSQL table (prompts, tags, dates, etc.)
- Cost: Free tier includes 1GB storage + 2GB bandwidth/month
Setup
1. Install Dependencies
Already done! Dependencies are installed.
2. Configure Environment Variables
Update .env with your Supabase credentials:
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_KEY="your-supabase-key-here"Get your key:
- Go to https://app.supabase.com
- Select your project
- Settings > API
- Copy either:
anonpublickey (for client-side use)service_rolesecretkey (recommended for CLI, has full access)
3. Set Up Database
Run the SQL in schema.sql in your Supabase SQL Editor:
- Go to https://app.supabase.com
- Select your project
- SQL Editor (left sidebar)
- Copy and paste contents of
schema.sql - Click "Run"
4. Create Storage Bucket
Follow instructions in setup-storage.md:
- Go to Storage section in Supabase Dashboard
- Create a new bucket named
photos - Choose public or private based on your needs
5. Install CLI Globally
Already done with npm link!
Usage
As a Library
Install in your project:
npm install supaphotoUse in your code:
const { savePhoto, createSupabaseClient, extractImageMetadata } = require('supaphoto');
// Initialize Supabase client
const supabase = createSupabaseClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
);
// Save a photo
async function uploadPhoto() {
try {
const result = await savePhoto(supabase, '/path/to/photo.jpg', {
prompt: 'A beautiful landscape',
metadata: { model: 'dall-e-3', style: 'photorealistic' }
});
console.log('Photo saved!', result);
console.log('ID:', result.id);
console.log('Public URL:', result.publicUrl);
} catch (error) {
console.error('Error:', error.message);
}
}
// Or just extract metadata without saving
async function getMetadata() {
const metadata = await extractImageMetadata('/path/to/photo.jpg');
console.log('Prompt:', metadata.prompt);
console.log('All metadata:', metadata.metadata);
}API:
createSupabaseClient(url, key)- Creates a Supabase clientsavePhoto(supabase, filePath, options)- Saves a photo with automatic metadata extractionoptions.prompt- Override extracted promptoptions.metadata- Additional metadata (object or JSON string)options.bucket- Storage bucket name (default: 'photos')- Returns:
{id, filename, storage_path, prompt, metadata, created_at, publicUrl, extractedPrompt}
extractImageMetadata(filePath)- Extract metadata without saving- Returns:
{prompt, metadata}
- Returns:
isDirectory(filePath)- Check if a path is a directory- Returns:
boolean
- Returns:
isImageFile(filePath)- Check if a file is a supported image type- Returns:
boolean
- Returns:
findImageFiles(dirPath)- Recursively find all image files in a directory- Returns:
string[](sorted array of absolute file paths)
- Returns:
SUPPORTED_IMAGE_EXTENSIONS- Set of supported image extensions- Value:
Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.tiff', '.tif'])
- Value:
As a CLI
Save a photo
supaphoto --save /path/to/photo.jpgSave with a prompt/description
supaphoto --save photo.jpg --prompt "A beautiful sunset over the mountains"Save with metadata
supaphoto --save photo.jpg --prompt "AI generated image" --metadata '{"model":"dall-e-3","style":"photorealistic"}'Short options
supaphoto -s photo.jpg -p "My prompt" -m '{"key":"value"}'Upload an entire directory (recursive)
Upload all images in a directory and its subdirectories:
supaphoto --save /path/to/photo-directoryFeatures:
- Recursively processes all subdirectories
- Continues on errors (shows summary at end)
- Uploads one file at a time (serial processing)
- Each file uses only its extracted metadata
--promptand--metadataflags are ignored for directory uploads
Example output:
Scanning directory: /Users/petter/photos
Found 10 image file(s) to upload
[1/10] Processing sunset.jpg...
[1/10] ✓ Uploaded sunset.jpg
[1/10] Found embedded prompt
[2/10] Processing mountain.png...
[2/10] ✓ Uploaded mountain.png
============================================================
UPLOAD SUMMARY
============================================================
Total files processed: 10
✓ Successful uploads: 9
✗ Failed uploads: 1
Successfully uploaded:
✓ sunset.jpg
ID: 123e4567-e89b-12d3-a456-426614174000
Prompt: A beautiful sunset over the ocean...
...
Failed uploads:
✗ corrupted.jpg
Error: Upload error: Invalid image format
============================================================Automatic Metadata Extraction
The CLI automatically extracts metadata from your images, including prompts that were embedded by AI image generation tools!
Supported metadata formats:
- EXIF (JPEG, TIFF, etc.)
- IPTC (image description fields)
- PNG metadata chunks
- XMP data
Prompt detection: The CLI looks for prompts in common fields used by popular tools:
- Stable Diffusion (stores in PNG
parametersfield) - DALL-E (often in
ImageDescription) - Midjourney (varies)
- Other tools using standard EXIF/IPTC fields
How it works:
# If the image has embedded metadata, it will be automatically extracted
supaphoto --save ai-generated.png
# Output: ✓ Found prompt in image metadata
# You can override the extracted prompt
supaphoto --save ai-generated.png --prompt "My custom description"
# All extracted metadata is stored in the database
# User-provided metadata takes precedence over extractedExamples
# Simple save
supaphoto --save vacation.jpg
# With prompt
supaphoto --save portrait.png --prompt "Professional headshot"
# With full metadata
supaphoto --save generated.png \
--prompt "A cyberpunk cityscape at night" \
--metadata '{"model":"midjourney","version":"v6","seed":12345}'What Happens When You Save a Photo?
- Metadata extraction: The CLI reads EXIF/IPTC/PNG metadata from the image
- Automatically detects prompts from AI image generators
- Extracts camera info, creation date, software used, etc.
- Upload: Photo is uploaded to Supabase Storage (
photosbucket) - Database record: Created with:
- Unique ID
- Filename
- Storage path
- Prompt (extracted or provided)
- Metadata (merged: extracted + user-provided)
- Timestamp
- Returns: Photo ID and public URL (if bucket is public)
Database Schema
The photos table stores:
id- Unique UUIDfilename- Original filenamestorage_path- Path in storage bucketprompt- Text description/promptmetadata- JSONB field for flexible metadatacreated_at- Timestamp
Cost Optimization
This setup is cost-effective because:
- Binary photo data stored in cheap object storage (not database)
- Database only stores lightweight metadata records
- Free tier covers most personal use cases
- Scales efficiently as you add more photos
Troubleshooting
"SUPABASE_URL and SUPABASE_KEY must be set"
- Check your
.envfile has correct values - Make sure you replaced
your-actual-key-herewith real key
"Upload error: Bucket not found"
- Create the
photosbucket in Supabase Dashboard (see setup-storage.md)
"Database error"
- Run the SQL in
schema.sqlto create the table
"Permission denied"
- Use
service_rolekey instead ofanonkey - Check RLS policies in Supabase
