This commit is contained in:
8
fal-generate/.skillshare-meta.json
Normal file
8
fal-generate/.skillshare-meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"source": "github.com/fal-ai-community/skills/tree/main/skills/claude.ai/fal-generate",
|
||||
"type": "github-subdir",
|
||||
"installed_at": "2026-01-30T02:27:01.659331774Z",
|
||||
"repo_url": "https://github.com/fal-ai-community/skills.git",
|
||||
"subdir": "skills/claude.ai/fal-generate",
|
||||
"version": "69efe6e"
|
||||
}
|
||||
315
fal-generate/SKILL.md
Normal file
315
fal-generate/SKILL.md
Normal file
@@ -0,0 +1,315 @@
|
||||
---
|
||||
name: fal-generate
|
||||
description: Generate images and videos using fal.ai AI models with queue support. Use when the user requests "Generate image", "Create video", "Make a picture of...", "Text to image", "Image to video", "Search models", or similar generation tasks.
|
||||
metadata:
|
||||
author: fal-ai
|
||||
version: "3.0.0"
|
||||
---
|
||||
|
||||
# fal.ai Generate
|
||||
|
||||
Generate images and videos using state-of-the-art AI models on fal.ai.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `generate.sh` | Generate images/videos (queue-based) |
|
||||
| `upload.sh` | Upload local files to fal CDN |
|
||||
| `search-models.sh` | Search and discover models |
|
||||
| `get-schema.sh` | Get OpenAPI schema for any model |
|
||||
|
||||
## Queue System (Default)
|
||||
|
||||
All requests use the queue system by default for reliability:
|
||||
|
||||
```
|
||||
User Request → Queue Submit → Poll Status → Get Result
|
||||
↓
|
||||
request_id
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Long-running tasks (video) won't timeout
|
||||
- Can check status anytime
|
||||
- Can cancel queued requests
|
||||
- Results retrievable even if connection drops
|
||||
|
||||
## Generate Content
|
||||
|
||||
```bash
|
||||
bash /mnt/skills/user/fal-generate/scripts/generate.sh [options]
|
||||
```
|
||||
|
||||
### Basic Usage (Queue Mode)
|
||||
|
||||
```bash
|
||||
# Image - submits to queue, waits for completion
|
||||
bash generate.sh --prompt "A serene mountain landscape" --model "fal-ai/nano-banana-pro"
|
||||
|
||||
# Video - same, but takes longer
|
||||
bash generate.sh --prompt "Ocean waves crashing" --model "fal-ai/veo3.1"
|
||||
|
||||
# Image-to-Video
|
||||
bash generate.sh \
|
||||
--prompt "Camera slowly zooms in" \
|
||||
--model "fal-ai/kling-video/v2.6/pro/image-to-video" \
|
||||
--image-url "https://example.com/image.jpg"
|
||||
```
|
||||
|
||||
### Async Mode (Return Immediately)
|
||||
|
||||
For long video jobs, use `--async` to get request_id immediately:
|
||||
|
||||
```bash
|
||||
# Submit and return immediately
|
||||
bash generate.sh --prompt "Epic battle scene" --model "fal-ai/veo3.1" --async
|
||||
|
||||
# Output:
|
||||
# Request ID: abc123-def456
|
||||
# Request submitted. Use these commands to check:
|
||||
# Status: ./generate.sh --status "abc123-def456" --model "fal-ai/veo3.1"
|
||||
# Result: ./generate.sh --result "abc123-def456" --model "fal-ai/veo3.1"
|
||||
```
|
||||
|
||||
### Queue Operations
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
bash generate.sh --status "request_id" --model "fal-ai/veo3.1"
|
||||
# → IN_QUEUE (position: 3) | IN_PROGRESS | COMPLETED
|
||||
|
||||
# Get result (when COMPLETED)
|
||||
bash generate.sh --result "request_id" --model "fal-ai/veo3.1"
|
||||
|
||||
# Cancel (only if still queued)
|
||||
bash generate.sh --cancel "request_id" --model "fal-ai/veo3.1"
|
||||
```
|
||||
|
||||
### Show Logs During Generation
|
||||
|
||||
```bash
|
||||
bash generate.sh --prompt "A sunset" --model "fal-ai/flux/dev" --logs
|
||||
# Status: IN_QUEUE (position: 2)
|
||||
# Status: IN_PROGRESS
|
||||
# > Loading model...
|
||||
# > Generating image...
|
||||
# Status: COMPLETED
|
||||
```
|
||||
|
||||
## File Upload
|
||||
|
||||
### Option 1: Auto-upload with --file
|
||||
|
||||
```bash
|
||||
# Local file is automatically uploaded to fal CDN
|
||||
bash generate.sh \
|
||||
--file "/path/to/photo.jpg" \
|
||||
--model "fal-ai/kling-video/v2.6/pro/image-to-video" \
|
||||
--prompt "Camera zooms in slowly"
|
||||
```
|
||||
|
||||
### Option 2: Manual upload with upload.sh
|
||||
|
||||
```bash
|
||||
# Upload first
|
||||
URL=$(bash upload.sh --file "/path/to/photo.jpg")
|
||||
# → https://v3.fal.media/files/xxx/photo.jpg
|
||||
|
||||
# Then generate
|
||||
bash generate.sh --image-url "$URL" --model "..." --prompt "..."
|
||||
```
|
||||
|
||||
### Option 3: Use existing URL
|
||||
|
||||
```bash
|
||||
# Any public URL works
|
||||
bash generate.sh --image-url "https://example.com/image.jpg" ...
|
||||
```
|
||||
|
||||
**Supported file types:**
|
||||
- Images: jpg, jpeg, png, gif, webp
|
||||
- Videos: mp4, mov, webm
|
||||
- Audio: mp3, wav, flac
|
||||
|
||||
**Upload flow (two-step):**
|
||||
```
|
||||
1. POST rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3
|
||||
→ {"token": "...", "base_url": "https://v3b.fal.media"}
|
||||
|
||||
2. POST {base_url}/files/upload
|
||||
Authorization: Bearer {token}
|
||||
→ {"access_url": "https://v3b.fal.media/files/..."}
|
||||
```
|
||||
|
||||
**Max file size:** 100MB (simple upload)
|
||||
|
||||
## Arguments Reference
|
||||
|
||||
| Argument | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `--prompt`, `-p` | Text description | (required) |
|
||||
| `--model`, `-m` | Model ID | `fal-ai/flux/dev` |
|
||||
| `--image-url` | Input image URL for I2V | - |
|
||||
| `--file`, `--image` | Local file (auto-uploads) | - |
|
||||
| `--size` | `square`, `portrait`, `landscape` | `landscape_4_3` |
|
||||
| `--num-images` | Number of images | 1 |
|
||||
|
||||
**Mode Options:**
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| (default) | Queue mode - submit and poll until complete |
|
||||
| `--async` | Submit to queue, return request_id immediately |
|
||||
| `--sync` | Synchronous (not recommended for video) |
|
||||
| `--logs` | Show generation logs while polling |
|
||||
|
||||
**Queue Operations:**
|
||||
| Argument | Description |
|
||||
|----------|-------------|
|
||||
| `--status ID` | Check status of a queued request |
|
||||
| `--result ID` | Get result of a completed request |
|
||||
| `--cancel ID` | Cancel a queued request |
|
||||
|
||||
**Advanced:**
|
||||
| Argument | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `--poll-interval` | Seconds between status checks | 2 |
|
||||
| `--timeout` | Max seconds to wait | 600 |
|
||||
| `--lifecycle N` | Object expiration in seconds | - |
|
||||
| `--schema [MODEL]` | Get OpenAPI schema | - |
|
||||
|
||||
## Recommended Models
|
||||
|
||||
### Text-to-Image
|
||||
|
||||
| Model | Notes |
|
||||
|-------|-------|
|
||||
| `fal-ai/nano-banana-pro` | **Best overall** - T2I and editing |
|
||||
| `fal-ai/flux-2-turbo` | Open source, high quality |
|
||||
| `fal-ai/flux-2-klein-9b` | Open source, fast |
|
||||
| `fal-ai/flux/dev` | Good balance (default) |
|
||||
| `fal-ai/flux/schnell` | ~1 second |
|
||||
| `fal-ai/ideogram/v3` | Best for text rendering |
|
||||
|
||||
### Text-to-Video
|
||||
|
||||
| Model | Notes |
|
||||
|-------|-------|
|
||||
| `fal-ai/veo3.1` | High quality |
|
||||
| `fal-ai/bytedance/seedance/v1/pro` | Fast, good quality |
|
||||
| `fal-ai/sora-2/pro` | OpenAI Sora |
|
||||
| `fal-ai/kling-video/v2.5-turbo/pro` | Fast, reliable |
|
||||
| `fal-ai/minimax/hailuo-02/pro` | Good for characters |
|
||||
|
||||
### Image-to-Video
|
||||
|
||||
| Model | Notes |
|
||||
|-------|-------|
|
||||
| `fal-ai/kling-video/v2.6/pro/image-to-video` | **Best overall** |
|
||||
| `fal-ai/veo3/fast` | Fast, high quality |
|
||||
| `fal-ai/bytedance/seedance/v1.5/pro/image-to-video` | Smooth motion |
|
||||
| `fal-ai/minimax/hailuo-02/standard/image-to-video` | Good balance |
|
||||
|
||||
## Search Models
|
||||
|
||||
```bash
|
||||
# Search by keyword
|
||||
bash search-models.sh --query "flux"
|
||||
|
||||
# Filter by category
|
||||
bash search-models.sh --category "text-to-video"
|
||||
```
|
||||
|
||||
**Categories:** `text-to-image`, `image-to-image`, `text-to-video`, `image-to-video`, `text-to-speech`, `speech-to-text`
|
||||
|
||||
## Get Model Schema (OpenAPI)
|
||||
|
||||
**IMPORTANT:** Fetch schema to see exact parameters for any model.
|
||||
|
||||
```bash
|
||||
# Get schema
|
||||
bash get-schema.sh --model "fal-ai/nano-banana-pro"
|
||||
|
||||
# Show only input parameters
|
||||
bash get-schema.sh --model "fal-ai/kling-video/v2.6/pro/image-to-video" --input
|
||||
|
||||
# Quick schema via generate.sh
|
||||
bash generate.sh --schema "fal-ai/veo3.1"
|
||||
```
|
||||
|
||||
**API Endpoint:**
|
||||
```
|
||||
https://fal.ai/api/openapi/queue/openapi.json?endpoint_id={model-id}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
**Queue Submit Response:**
|
||||
```json
|
||||
{
|
||||
"request_id": "abc123-def456",
|
||||
"status": "IN_QUEUE",
|
||||
"response_url": "https://queue.fal.run/.../requests/abc123-def456",
|
||||
"status_url": "https://queue.fal.run/.../requests/abc123-def456/status",
|
||||
"cancel_url": "https://queue.fal.run/.../requests/abc123-def456/cancel"
|
||||
}
|
||||
```
|
||||
|
||||
**Final Result:**
|
||||
```json
|
||||
{
|
||||
"images": [{ "url": "https://v3.fal.media/files/...", "width": 1024, "height": 768 }]
|
||||
}
|
||||
```
|
||||
|
||||
## Present Results to User
|
||||
|
||||
**Images:**
|
||||
```
|
||||

|
||||
• 1024×768 | Generated in 2.2s
|
||||
```
|
||||
|
||||
**Videos:**
|
||||
```
|
||||
[Click to view video](https://v3.fal.media/files/.../video.mp4)
|
||||
• Duration: 5s | Generated in 45s
|
||||
```
|
||||
|
||||
**Async Submission:**
|
||||
```
|
||||
Request submitted to queue.
|
||||
• Request ID: abc123-def456
|
||||
• Model: fal-ai/veo3
|
||||
• Check status: --status "abc123-def456"
|
||||
```
|
||||
|
||||
## Object Lifecycle (Optional)
|
||||
|
||||
Control how long generated files remain accessible:
|
||||
|
||||
```bash
|
||||
# Files expire after 1 hour (3600 seconds)
|
||||
bash generate.sh --prompt "..." --lifecycle 3600
|
||||
|
||||
# Files expire after 24 hours
|
||||
bash generate.sh --prompt "..." --lifecycle 86400
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Timeout Error
|
||||
```
|
||||
Error: Timeout after 600s
|
||||
Request ID: abc123-def456
|
||||
```
|
||||
**Solution:** Use `--status` and `--result` to check manually, or increase `--timeout`.
|
||||
|
||||
### API Key Error
|
||||
```
|
||||
Error: FAL_KEY not set
|
||||
```
|
||||
**Solution:** Run `./generate.sh --add-fal-key` or `export FAL_KEY=your_key`.
|
||||
|
||||
### Network Error (claude.ai)
|
||||
Go to `claude.ai/settings/capabilities` and add `*.fal.ai` to allowed domains.
|
||||
519
fal-generate/scripts/generate.sh
Executable file
519
fal-generate/scripts/generate.sh
Executable file
@@ -0,0 +1,519 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fal.ai Generation Script with Queue Support
|
||||
# Usage: ./generate.sh --prompt "..." [--model MODEL] [options]
|
||||
# Returns: JSON with generated media URLs
|
||||
#
|
||||
# Queue Mode (default): Submits to queue, polls for completion
|
||||
# Async Mode: Returns request_id immediately
|
||||
# Sync Mode: Direct request (not recommended for long tasks)
|
||||
|
||||
set -e
|
||||
|
||||
FAL_QUEUE_ENDPOINT="https://queue.fal.run"
|
||||
FAL_SYNC_ENDPOINT="https://fal.run"
|
||||
FAL_TOKEN_ENDPOINT="https://rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3"
|
||||
|
||||
# Default values
|
||||
MODEL="fal-ai/flux/dev"
|
||||
PROMPT=""
|
||||
IMAGE_URL=""
|
||||
IMAGE_FILE=""
|
||||
IMAGE_SIZE="landscape_4_3"
|
||||
NUM_IMAGES=1
|
||||
MODE="queue" # queue (default), async, sync
|
||||
REQUEST_ID=""
|
||||
ACTION="generate" # generate, status, result, cancel
|
||||
POLL_INTERVAL=2
|
||||
MAX_POLL_TIME=600
|
||||
LIFECYCLE=""
|
||||
SHOW_LOGS=false
|
||||
|
||||
# Check for --add-fal-key first
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--add-fal-key" ]; then
|
||||
shift
|
||||
KEY_VALUE=""
|
||||
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
|
||||
KEY_VALUE="$1"
|
||||
fi
|
||||
if [ -z "$KEY_VALUE" ]; then
|
||||
echo "Enter your fal.ai API key:" >&2
|
||||
read -r KEY_VALUE
|
||||
fi
|
||||
if [ -n "$KEY_VALUE" ]; then
|
||||
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
|
||||
mv .env.tmp .env 2>/dev/null || true
|
||||
echo "FAL_KEY=$KEY_VALUE" >> .env
|
||||
echo "FAL_KEY saved to .env" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Load .env if exists
|
||||
if [ -f ".env" ]; then
|
||||
source .env 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--prompt|-p)
|
||||
PROMPT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--model|-m)
|
||||
MODEL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image-url)
|
||||
IMAGE_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--file|--image)
|
||||
IMAGE_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--size)
|
||||
case $2 in
|
||||
square) IMAGE_SIZE="square" ;;
|
||||
portrait) IMAGE_SIZE="portrait_4_3" ;;
|
||||
landscape) IMAGE_SIZE="landscape_4_3" ;;
|
||||
*) IMAGE_SIZE="$2" ;;
|
||||
esac
|
||||
shift 2
|
||||
;;
|
||||
--num-images)
|
||||
NUM_IMAGES="$2"
|
||||
shift 2
|
||||
;;
|
||||
# Mode options
|
||||
--async)
|
||||
MODE="async"
|
||||
shift
|
||||
;;
|
||||
--sync)
|
||||
MODE="sync"
|
||||
shift
|
||||
;;
|
||||
--logs)
|
||||
SHOW_LOGS=true
|
||||
shift
|
||||
;;
|
||||
# Queue operations
|
||||
--status)
|
||||
ACTION="status"
|
||||
REQUEST_ID="$2"
|
||||
shift 2
|
||||
;;
|
||||
--result)
|
||||
ACTION="result"
|
||||
REQUEST_ID="$2"
|
||||
shift 2
|
||||
;;
|
||||
--cancel)
|
||||
ACTION="cancel"
|
||||
REQUEST_ID="$2"
|
||||
shift 2
|
||||
;;
|
||||
# Polling options
|
||||
--poll-interval)
|
||||
POLL_INTERVAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--timeout)
|
||||
MAX_POLL_TIME="$2"
|
||||
shift 2
|
||||
;;
|
||||
# Object lifecycle (optional)
|
||||
--lifecycle)
|
||||
LIFECYCLE="$2"
|
||||
shift 2
|
||||
;;
|
||||
# Schema lookup
|
||||
--schema)
|
||||
SCHEMA_MODEL="${2:-$MODEL}"
|
||||
ENCODED=$(echo "$SCHEMA_MODEL" | sed 's/\//%2F/g')
|
||||
echo "Fetching schema for $SCHEMA_MODEL..." >&2
|
||||
curl -s "https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=$ENCODED"
|
||||
exit 0
|
||||
;;
|
||||
--help|-h)
|
||||
echo "fal.ai Generation Script (Queue-based)" >&2
|
||||
echo "" >&2
|
||||
echo "Usage:" >&2
|
||||
echo " ./generate.sh --prompt \"...\" [options]" >&2
|
||||
echo "" >&2
|
||||
echo "Generation Options:" >&2
|
||||
echo " --prompt, -p Text description (required for generate)" >&2
|
||||
echo " --model, -m Model ID (default: fal-ai/flux/dev)" >&2
|
||||
echo " --image-url Input image URL for I2V models" >&2
|
||||
echo " --file, --image Local file (auto-uploads to fal CDN)" >&2
|
||||
echo " --size square, portrait, landscape" >&2
|
||||
echo " --num-images Number of images (default: 1)" >&2
|
||||
echo "" >&2
|
||||
echo "Mode Options:" >&2
|
||||
echo " (default) Queue mode - submit and poll until complete" >&2
|
||||
echo " --async Submit to queue, return request_id immediately" >&2
|
||||
echo " --sync Synchronous request (not recommended for video)" >&2
|
||||
echo " --logs Show generation logs while polling" >&2
|
||||
echo "" >&2
|
||||
echo "Queue Operations:" >&2
|
||||
echo " --status ID Check status of a queued request" >&2
|
||||
echo " --result ID Get result of a completed request" >&2
|
||||
echo " --cancel ID Cancel a queued request" >&2
|
||||
echo "" >&2
|
||||
echo "Advanced Options:" >&2
|
||||
echo " --poll-interval Seconds between status checks (default: 2)" >&2
|
||||
echo " --timeout Max seconds to wait (default: 600)" >&2
|
||||
echo " --lifecycle N Object expiration in seconds (optional)" >&2
|
||||
echo " --schema [MODEL] Get OpenAPI schema for model" >&2
|
||||
echo " --add-fal-key Setup FAL_KEY in .env" >&2
|
||||
echo "" >&2
|
||||
echo "Examples:" >&2
|
||||
echo " # Generate image (waits for completion)" >&2
|
||||
echo " ./generate.sh --prompt \"a sunset\" --model \"fal-ai/flux/dev\"" >&2
|
||||
echo "" >&2
|
||||
echo " # Generate video async (returns immediately)" >&2
|
||||
echo " ./generate.sh --prompt \"ocean waves\" --model \"fal-ai/veo3\" --async" >&2
|
||||
echo "" >&2
|
||||
echo " # Check status" >&2
|
||||
echo " ./generate.sh --status \"request_id\" --model \"fal-ai/veo3\"" >&2
|
||||
echo "" >&2
|
||||
echo " # Get result" >&2
|
||||
echo " ./generate.sh --result \"request_id\" --model \"fal-ai/veo3\"" >&2
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate FAL_KEY
|
||||
if [ -z "$FAL_KEY" ]; then
|
||||
echo "Error: FAL_KEY not set" >&2
|
||||
echo "" >&2
|
||||
echo "Run: ./generate.sh --add-fal-key" >&2
|
||||
echo "Or: export FAL_KEY=your_key_here" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Handle local file upload
|
||||
if [ -n "$IMAGE_FILE" ]; then
|
||||
if [ ! -f "$IMAGE_FILE" ]; then
|
||||
echo "Error: File not found: $IMAGE_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILENAME=$(basename "$IMAGE_FILE")
|
||||
EXTENSION="${FILENAME##*.}"
|
||||
EXTENSION_LOWER=$(echo "$EXTENSION" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Detect content type
|
||||
case "$EXTENSION_LOWER" in
|
||||
jpg|jpeg) CONTENT_TYPE="image/jpeg" ;;
|
||||
png) CONTENT_TYPE="image/png" ;;
|
||||
gif) CONTENT_TYPE="image/gif" ;;
|
||||
webp) CONTENT_TYPE="image/webp" ;;
|
||||
mp4) CONTENT_TYPE="video/mp4" ;;
|
||||
mov) CONTENT_TYPE="video/quicktime" ;;
|
||||
*) CONTENT_TYPE="application/octet-stream" ;;
|
||||
esac
|
||||
|
||||
echo "Uploading $FILENAME..." >&2
|
||||
|
||||
# Step 1: Get CDN token
|
||||
TOKEN_RESPONSE=$(curl -s -X POST "$FAL_TOKEN_ENDPOINT" \
|
||||
-H "Authorization: Key $FAL_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}')
|
||||
|
||||
CDN_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||
CDN_TOKEN_TYPE=$(echo "$TOKEN_RESPONSE" | grep -o '"token_type":"[^"]*"' | cut -d'"' -f4)
|
||||
CDN_BASE_URL=$(echo "$TOKEN_RESPONSE" | grep -o '"base_url":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$CDN_TOKEN" ] || [ -z "$CDN_BASE_URL" ]; then
|
||||
echo "Error: Failed to get CDN token" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Upload file
|
||||
UPLOAD_RESPONSE=$(curl -s -X POST "${CDN_BASE_URL}/files/upload" \
|
||||
-H "Authorization: $CDN_TOKEN_TYPE $CDN_TOKEN" \
|
||||
-H "Content-Type: $CONTENT_TYPE" \
|
||||
-H "X-Fal-File-Name: $FILENAME" \
|
||||
--data-binary "@$IMAGE_FILE")
|
||||
|
||||
# Check for upload error
|
||||
if echo "$UPLOAD_RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Upload error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract URL
|
||||
IMAGE_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"access_url":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$IMAGE_URL" ]; then
|
||||
echo "Error: Failed to get URL from upload response" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uploaded: $IMAGE_URL" >&2
|
||||
fi
|
||||
|
||||
# Build headers
|
||||
HEADERS=(-H "Authorization: Key $FAL_KEY" -H "Content-Type: application/json")
|
||||
|
||||
# Add lifecycle header if specified
|
||||
if [ -n "$LIFECYCLE" ]; then
|
||||
HEADERS+=(-H "X-Fal-Object-Lifecycle-Preference: {\"expiration_duration_seconds\": $LIFECYCLE}")
|
||||
fi
|
||||
|
||||
# Handle queue operations
|
||||
case $ACTION in
|
||||
status)
|
||||
if [ -z "$REQUEST_ID" ]; then
|
||||
echo "Error: Request ID required for --status" >&2
|
||||
exit 1
|
||||
fi
|
||||
LOGS_PARAM=""
|
||||
if [ "$SHOW_LOGS" = true ]; then
|
||||
LOGS_PARAM="?logs=1"
|
||||
fi
|
||||
echo "Checking status for $REQUEST_ID..." >&2
|
||||
RESPONSE=$(curl -s -X GET "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID/status$LOGS_PARAM" "${HEADERS[@]}")
|
||||
|
||||
# Parse and display status
|
||||
STATUS=$(echo "$RESPONSE" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
echo "Status: $STATUS" >&2
|
||||
|
||||
if [ "$STATUS" = "IN_QUEUE" ]; then
|
||||
POSITION=$(echo "$RESPONSE" | grep -o '"queue_position":[0-9]*' | cut -d':' -f2)
|
||||
[ -n "$POSITION" ] && echo "Queue position: $POSITION" >&2
|
||||
fi
|
||||
|
||||
echo "$RESPONSE"
|
||||
exit 0
|
||||
;;
|
||||
result)
|
||||
if [ -z "$REQUEST_ID" ]; then
|
||||
echo "Error: Request ID required for --result" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Getting result for $REQUEST_ID..." >&2
|
||||
RESPONSE=$(curl -s -X GET "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID" "${HEADERS[@]}")
|
||||
|
||||
# Check for error
|
||||
if echo "$RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract URL
|
||||
if echo "$RESPONSE" | grep -q '"video"'; then
|
||||
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Video URL: $URL" >&2
|
||||
elif echo "$RESPONSE" | grep -q '"images"'; then
|
||||
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Image URL: $URL" >&2
|
||||
fi
|
||||
|
||||
echo "$RESPONSE"
|
||||
exit 0
|
||||
;;
|
||||
cancel)
|
||||
if [ -z "$REQUEST_ID" ]; then
|
||||
echo "Error: Request ID required for --cancel" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Cancelling request $REQUEST_ID..." >&2
|
||||
RESPONSE=$(curl -s -X PUT "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID/cancel" "${HEADERS[@]}")
|
||||
echo "$RESPONSE"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Generate action requires prompt
|
||||
if [ -z "$PROMPT" ]; then
|
||||
echo "Error: --prompt is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build the request payload based on model type
|
||||
if [[ "$MODEL" == *"image-to-video"* ]] || [[ "$MODEL" == *"i2v"* ]]; then
|
||||
if [ -z "$IMAGE_URL" ]; then
|
||||
echo "Error: --image-url is required for image-to-video models" >&2
|
||||
exit 1
|
||||
fi
|
||||
PAYLOAD=$(cat <<EOF
|
||||
{"prompt": "$PROMPT", "image_url": "$IMAGE_URL"}
|
||||
EOF
|
||||
)
|
||||
elif [[ "$MODEL" == *"video"* ]] || [[ "$MODEL" == *"veo"* ]] || [[ "$MODEL" == *"text-to-video"* ]]; then
|
||||
PAYLOAD=$(cat <<EOF
|
||||
{"prompt": "$PROMPT"}
|
||||
EOF
|
||||
)
|
||||
else
|
||||
PAYLOAD=$(cat <<EOF
|
||||
{"prompt": "$PROMPT", "image_size": "$IMAGE_SIZE", "num_images": $NUM_IMAGES}
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
# Synchronous mode
|
||||
if [ "$MODE" = "sync" ]; then
|
||||
echo "Generating with $MODEL (sync mode)..." >&2
|
||||
RESPONSE=$(curl -s -X POST "$FAL_SYNC_ENDPOINT/$MODEL" "${HEADERS[@]}" -d "$PAYLOAD")
|
||||
|
||||
if echo "$RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Generation complete!" >&2
|
||||
if echo "$RESPONSE" | grep -q '"video"'; then
|
||||
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Video URL: $URL" >&2
|
||||
elif echo "$RESPONSE" | grep -q '"images"'; then
|
||||
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Image URL: $URL" >&2
|
||||
fi
|
||||
|
||||
echo "$RESPONSE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Queue mode (async or poll)
|
||||
echo "Submitting to queue: $MODEL..." >&2
|
||||
|
||||
SUBMIT_RESPONSE=$(curl -s -X POST "$FAL_QUEUE_ENDPOINT/$MODEL" "${HEADERS[@]}" -d "$PAYLOAD")
|
||||
|
||||
# Check for submit error
|
||||
if echo "$SUBMIT_RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$SUBMIT_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract request_id and URLs (handle both "key": "value" and "key":"value" formats)
|
||||
REQUEST_ID=$(echo "$SUBMIT_RESPONSE" | grep -oE '"request_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
STATUS_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"status_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
RESPONSE_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"response_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
CANCEL_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"cancel_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
|
||||
if [ -z "$REQUEST_ID" ]; then
|
||||
echo "Error: Failed to get request_id" >&2
|
||||
echo "$SUBMIT_RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Request ID: $REQUEST_ID" >&2
|
||||
|
||||
# Async mode - return immediately
|
||||
if [ "$MODE" = "async" ]; then
|
||||
echo "" >&2
|
||||
echo "Request submitted. Use these commands to check:" >&2
|
||||
echo " Status: ./generate.sh --status \"$REQUEST_ID\" --model \"$MODEL\"" >&2
|
||||
echo " Result: ./generate.sh --result \"$REQUEST_ID\" --model \"$MODEL\"" >&2
|
||||
echo " Cancel: ./generate.sh --cancel \"$REQUEST_ID\" --model \"$MODEL\"" >&2
|
||||
echo "$SUBMIT_RESPONSE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Queue mode - poll until complete
|
||||
echo "Waiting for completion..." >&2
|
||||
|
||||
ELAPSED=0
|
||||
LAST_STATUS=""
|
||||
|
||||
while [ $ELAPSED -lt $MAX_POLL_TIME ]; do
|
||||
sleep $POLL_INTERVAL
|
||||
ELAPSED=$((ELAPSED + POLL_INTERVAL))
|
||||
|
||||
LOGS_PARAM=""
|
||||
if [ "$SHOW_LOGS" = true ]; then
|
||||
LOGS_PARAM="?logs=1"
|
||||
fi
|
||||
|
||||
STATUS_RESPONSE=$(curl -s -X GET "${STATUS_URL}${LOGS_PARAM}" "${HEADERS[@]}")
|
||||
STATUS=$(echo "$STATUS_RESPONSE" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
|
||||
|
||||
# Show status change
|
||||
if [ "$STATUS" != "$LAST_STATUS" ]; then
|
||||
case $STATUS in
|
||||
IN_QUEUE)
|
||||
POSITION=$(echo "$STATUS_RESPONSE" | grep -o '"queue_position":[0-9]*' | cut -d':' -f2)
|
||||
echo "Status: IN_QUEUE (position: ${POSITION:-?})" >&2
|
||||
;;
|
||||
IN_PROGRESS)
|
||||
echo "Status: IN_PROGRESS" >&2
|
||||
;;
|
||||
COMPLETED)
|
||||
echo "Status: COMPLETED" >&2
|
||||
;;
|
||||
*)
|
||||
echo "Status: $STATUS" >&2
|
||||
;;
|
||||
esac
|
||||
LAST_STATUS="$STATUS"
|
||||
fi
|
||||
|
||||
# Show logs if enabled
|
||||
if [ "$SHOW_LOGS" = true ]; then
|
||||
LOGS=$(echo "$STATUS_RESPONSE" | grep -o '"logs":\[[^]]*\]' | head -1)
|
||||
if [ -n "$LOGS" ] && [ "$LOGS" != "[]" ]; then
|
||||
echo "$LOGS" | tr ',' '\n' | grep -o '"message":"[^"]*"' | cut -d'"' -f4 | while read -r log; do
|
||||
echo " > $log" >&2
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "COMPLETED" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "FAILED" ]; then
|
||||
echo "Error: Generation failed" >&2
|
||||
echo "$STATUS_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$STATUS" != "COMPLETED" ]; then
|
||||
echo "Error: Timeout after ${MAX_POLL_TIME}s" >&2
|
||||
echo "Request ID: $REQUEST_ID" >&2
|
||||
echo "Check status with: ./generate.sh --status \"$REQUEST_ID\" --model \"$MODEL\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get final result
|
||||
echo "Fetching result..." >&2
|
||||
RESULT=$(curl -s -X GET "$RESPONSE_URL" "${HEADERS[@]}")
|
||||
|
||||
# Check for error in result
|
||||
if echo "$RESULT" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$RESULT" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
echo "Generation complete!" >&2
|
||||
|
||||
# Extract and display URL
|
||||
if echo "$RESULT" | grep -q '"video"'; then
|
||||
URL=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Video URL: $URL" >&2
|
||||
elif echo "$RESULT" | grep -q '"images"'; then
|
||||
URL=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Image URL: $URL" >&2
|
||||
fi
|
||||
|
||||
# Output JSON
|
||||
echo "$RESULT"
|
||||
216
fal-generate/scripts/get-schema.sh
Normal file
216
fal-generate/scripts/get-schema.sh
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fal.ai Model Schema Script
|
||||
# Usage: ./get-schema.sh --model MODEL
|
||||
# Returns: OpenAPI 3.0 schema for the model
|
||||
|
||||
set -e
|
||||
|
||||
FAL_SCHEMA_ENDPOINT="https://fal.ai/api/openapi/queue/openapi.json"
|
||||
|
||||
# Default values
|
||||
MODEL=""
|
||||
OUTPUT_JSON=false
|
||||
SHOW_INPUT=false
|
||||
SHOW_OUTPUT=false
|
||||
|
||||
# Check for --add-fal-key first
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--add-fal-key" ]; then
|
||||
shift
|
||||
KEY_VALUE=""
|
||||
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
|
||||
KEY_VALUE="$1"
|
||||
fi
|
||||
if [ -z "$KEY_VALUE" ]; then
|
||||
echo "Enter your fal.ai API key:" >&2
|
||||
read -r KEY_VALUE
|
||||
fi
|
||||
if [ -n "$KEY_VALUE" ]; then
|
||||
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
|
||||
mv .env.tmp .env 2>/dev/null || true
|
||||
echo "FAL_KEY=$KEY_VALUE" >> .env
|
||||
echo "FAL_KEY saved to .env" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--model|-m)
|
||||
MODEL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--input|-i)
|
||||
SHOW_INPUT=true
|
||||
shift
|
||||
;;
|
||||
--output|-o)
|
||||
SHOW_OUTPUT=true
|
||||
shift
|
||||
;;
|
||||
--json)
|
||||
OUTPUT_JSON=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "fal.ai Model Schema Script" >&2
|
||||
echo "" >&2
|
||||
echo "Usage:" >&2
|
||||
echo " ./get-schema.sh --model MODEL [options]" >&2
|
||||
echo "" >&2
|
||||
echo "Options:" >&2
|
||||
echo " --model, -m Model ID (required)" >&2
|
||||
echo " --input, -i Show only input schema" >&2
|
||||
echo " --output, -o Show only output schema" >&2
|
||||
echo " --json Output raw JSON" >&2
|
||||
echo " --add-fal-key Setup FAL_KEY in .env" >&2
|
||||
echo "" >&2
|
||||
echo "Examples:" >&2
|
||||
echo " ./get-schema.sh --model \"fal-ai/flux-pro/v1.1-ultra\"" >&2
|
||||
echo " ./get-schema.sh --model \"fal-ai/kling-video/v2.1/pro/image-to-video\" --input" >&2
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$MODEL" ]; then
|
||||
echo "Error: --model is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# URL encode the model ID
|
||||
ENCODED_MODEL=$(echo "$MODEL" | sed 's/\//%2F/g')
|
||||
|
||||
echo "Fetching schema for $MODEL..." >&2
|
||||
|
||||
# Fetch OpenAPI schema
|
||||
RESPONSE=$(curl -s -X GET "$FAL_SCHEMA_ENDPOINT?endpoint_id=$ENCODED_MODEL" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
# Check for errors
|
||||
if echo "$RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$OUTPUT_JSON" = true ]; then
|
||||
echo "$RESPONSE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse and display schema summary
|
||||
if command -v python3 &> /dev/null; then
|
||||
python3 << 'PYTHON_EOF' - "$RESPONSE" "$MODEL" "$SHOW_INPUT" "$SHOW_OUTPUT"
|
||||
import json
|
||||
import sys
|
||||
|
||||
response = json.loads(sys.argv[1])
|
||||
model = sys.argv[2]
|
||||
show_input = sys.argv[3] == "true"
|
||||
show_output = sys.argv[4] == "true"
|
||||
|
||||
# Get metadata
|
||||
info = response.get('info', {})
|
||||
metadata = info.get('x-fal-metadata', {})
|
||||
schemas = response.get('components', {}).get('schemas', {})
|
||||
|
||||
print("", file=sys.stderr)
|
||||
print(f"Model: {model}", file=sys.stderr)
|
||||
print("=" * (len(model) + 7), file=sys.stderr)
|
||||
|
||||
# Category
|
||||
category = metadata.get('category', 'unknown')
|
||||
print(f"Category: {category}", file=sys.stderr)
|
||||
|
||||
# URLs
|
||||
if metadata.get('playgroundUrl'):
|
||||
print(f"Playground: {metadata['playgroundUrl']}", file=sys.stderr)
|
||||
if metadata.get('documentationUrl'):
|
||||
print(f"Docs: {metadata['documentationUrl']}", file=sys.stderr)
|
||||
|
||||
# Find input/output schemas
|
||||
input_schema = None
|
||||
output_schema = None
|
||||
|
||||
for name, schema in schemas.items():
|
||||
if 'Input' in name and name != 'QueueStatus':
|
||||
input_schema = schema
|
||||
elif 'Output' in name and name != 'QueueStatus':
|
||||
output_schema = schema
|
||||
|
||||
# Show input schema
|
||||
if input_schema and (show_input or (not show_input and not show_output)):
|
||||
print("", file=sys.stderr)
|
||||
print("Input Parameters", file=sys.stderr)
|
||||
print("-" * 16, file=sys.stderr)
|
||||
|
||||
props = input_schema.get('properties', {})
|
||||
required = input_schema.get('required', [])
|
||||
order = input_schema.get('x-fal-order-properties', list(props.keys()))
|
||||
|
||||
for prop_name in order:
|
||||
if prop_name not in props:
|
||||
continue
|
||||
prop = props[prop_name]
|
||||
|
||||
# Type
|
||||
prop_type = prop.get('type', 'any')
|
||||
if 'enum' in prop:
|
||||
prop_type = f"enum: {prop['enum']}"
|
||||
elif 'anyOf' in prop:
|
||||
types = [t.get('type', t.get('enum', ['?'])) for t in prop['anyOf']]
|
||||
prop_type = f"oneOf: {types}"
|
||||
|
||||
# Required marker
|
||||
req_mark = "*" if prop_name in required else " "
|
||||
|
||||
# Default value
|
||||
default = prop.get('default', '')
|
||||
default_str = f" (default: {default})" if default != '' else ""
|
||||
|
||||
print(f" {req_mark} {prop_name}: {prop_type}{default_str}", file=sys.stderr)
|
||||
|
||||
# Description
|
||||
desc = prop.get('description', '')
|
||||
if desc:
|
||||
# Truncate long descriptions
|
||||
if len(desc) > 60:
|
||||
desc = desc[:57] + "..."
|
||||
print(f" {desc}", file=sys.stderr)
|
||||
|
||||
# Show output schema
|
||||
if output_schema and (show_output or (not show_input and not show_output)):
|
||||
print("", file=sys.stderr)
|
||||
print("Output Fields", file=sys.stderr)
|
||||
print("-" * 13, file=sys.stderr)
|
||||
|
||||
props = output_schema.get('properties', {})
|
||||
order = output_schema.get('x-fal-order-properties', list(props.keys()))
|
||||
|
||||
for prop_name in order:
|
||||
if prop_name not in props:
|
||||
continue
|
||||
prop = props[prop_name]
|
||||
|
||||
prop_type = prop.get('type', 'any')
|
||||
if prop_type == 'array':
|
||||
items = prop.get('items', {})
|
||||
item_type = items.get('type', items.get('$ref', '').split('/')[-1])
|
||||
prop_type = f"array<{item_type}>"
|
||||
|
||||
print(f" {prop_name}: {prop_type}", file=sys.stderr)
|
||||
|
||||
print("", file=sys.stderr)
|
||||
print("* = required parameter", file=sys.stderr)
|
||||
PYTHON_EOF
|
||||
fi
|
||||
|
||||
# Output full JSON for programmatic use
|
||||
echo "$RESPONSE"
|
||||
154
fal-generate/scripts/search-models.sh
Executable file
154
fal-generate/scripts/search-models.sh
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fal.ai Model Search Script
|
||||
# Usage: ./search-models.sh [--query QUERY] [--category CATEGORY] [--limit N]
|
||||
# Returns: JSON with matching models
|
||||
|
||||
set -e
|
||||
|
||||
FAL_API_ENDPOINT="https://api.fal.ai/v1/models"
|
||||
|
||||
# Default values
|
||||
QUERY=""
|
||||
CATEGORY=""
|
||||
LIMIT=20
|
||||
|
||||
# Load .env if exists
|
||||
if [ -f ".env" ]; then
|
||||
source .env 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check for --add-fal-key first
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--add-fal-key" ]; then
|
||||
shift
|
||||
KEY_VALUE=""
|
||||
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
|
||||
KEY_VALUE="$1"
|
||||
fi
|
||||
if [ -z "$KEY_VALUE" ]; then
|
||||
echo "Enter your fal.ai API key:" >&2
|
||||
read -r KEY_VALUE
|
||||
fi
|
||||
if [ -n "$KEY_VALUE" ]; then
|
||||
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
|
||||
mv .env.tmp .env 2>/dev/null || true
|
||||
echo "FAL_KEY=$KEY_VALUE" >> .env
|
||||
echo "FAL_KEY saved to .env" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--query|-q)
|
||||
QUERY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--category|-c)
|
||||
CATEGORY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--limit|-l)
|
||||
LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
echo "fal.ai Model Search Script" >&2
|
||||
echo "" >&2
|
||||
echo "Usage:" >&2
|
||||
echo " ./search-models.sh [options]" >&2
|
||||
echo "" >&2
|
||||
echo "Options:" >&2
|
||||
echo " --query, -q Search query (e.g., 'flux', 'video', 'upscale')" >&2
|
||||
echo " --category, -c Filter by category:" >&2
|
||||
echo " text-to-image, image-to-image, text-to-video," >&2
|
||||
echo " image-to-video, text-to-speech, speech-to-text" >&2
|
||||
echo " --limit, -l Max results (default: 20)" >&2
|
||||
echo " --add-fal-key Setup FAL_KEY in .env" >&2
|
||||
echo "" >&2
|
||||
echo "Examples:" >&2
|
||||
echo " ./search-models.sh --query 'flux'" >&2
|
||||
echo " ./search-models.sh --category 'text-to-video'" >&2
|
||||
echo " ./search-models.sh --query 'upscale' --limit 5" >&2
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate FAL_KEY
|
||||
if [ -z "$FAL_KEY" ]; then
|
||||
echo "Error: FAL_KEY not set" >&2
|
||||
echo "" >&2
|
||||
echo "Run: ./search-models.sh --add-fal-key" >&2
|
||||
echo "Or: export FAL_KEY=your_key_here" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Searching fal.ai models..." >&2
|
||||
|
||||
# Build query parameters
|
||||
PARAMS="limit=$LIMIT"
|
||||
|
||||
if [ -n "$QUERY" ]; then
|
||||
PARAMS="$PARAMS&q=$(echo "$QUERY" | sed 's/ /%20/g')"
|
||||
fi
|
||||
|
||||
if [ -n "$CATEGORY" ]; then
|
||||
PARAMS="$PARAMS&category=$CATEGORY"
|
||||
fi
|
||||
|
||||
# Make API request
|
||||
AUTH_HEADER=""
|
||||
if [ -n "$FAL_KEY" ]; then
|
||||
AUTH_HEADER="-H \"Authorization: Key $FAL_KEY\""
|
||||
fi
|
||||
|
||||
RESPONSE=$(curl -s -X GET "$FAL_API_ENDPOINT?$PARAMS" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Key $FAL_KEY")
|
||||
|
||||
# Check for errors
|
||||
if echo "$RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Count results
|
||||
COUNT=$(echo "$RESPONSE" | grep -o '"endpoint_id"' | wc -l | tr -d ' ')
|
||||
echo "Found $COUNT models" >&2
|
||||
echo "" >&2
|
||||
|
||||
# Display summary to stderr
|
||||
if command -v python3 &> /dev/null; then
|
||||
python3 << 'PYTHON_EOF' - "$RESPONSE"
|
||||
import json
|
||||
import sys
|
||||
|
||||
response = json.loads(sys.argv[1])
|
||||
models = response.get('data', response) if isinstance(response, dict) else response
|
||||
|
||||
if isinstance(models, list):
|
||||
for m in models[:10]:
|
||||
name = m.get('display_name', m.get('endpoint_id', 'Unknown'))
|
||||
endpoint = m.get('endpoint_id', '')
|
||||
category = m.get('category', '')
|
||||
print(f" {name}", file=sys.stderr)
|
||||
print(f" ID: {endpoint}", file=sys.stderr)
|
||||
if category:
|
||||
print(f" Category: {category}", file=sys.stderr)
|
||||
print("", file=sys.stderr)
|
||||
|
||||
if len(models) > 10:
|
||||
print(f" ... and {len(models) - 10} more", file=sys.stderr)
|
||||
PYTHON_EOF
|
||||
fi
|
||||
|
||||
# Output JSON for programmatic use
|
||||
echo "$RESPONSE"
|
||||
216
fal-generate/scripts/upload.sh
Normal file
216
fal-generate/scripts/upload.sh
Normal file
@@ -0,0 +1,216 @@
|
||||
#!/bin/bash
|
||||
|
||||
# fal.ai File Upload Script
|
||||
# Usage: ./upload.sh --file /path/to/file
|
||||
# Returns: CDN URL for the uploaded file
|
||||
#
|
||||
# Upload flow:
|
||||
# 1. Get CDN token from rest.alpha.fal.ai
|
||||
# 2. Upload file to CDN (v3b.fal.media)
|
||||
# 3. Return access_url
|
||||
|
||||
set -e
|
||||
|
||||
FAL_TOKEN_ENDPOINT="https://rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3"
|
||||
|
||||
# Default values
|
||||
FILE_PATH=""
|
||||
OUTPUT_JSON=false
|
||||
|
||||
# Check for --add-fal-key first
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--add-fal-key" ]; then
|
||||
shift
|
||||
KEY_VALUE=""
|
||||
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
|
||||
KEY_VALUE="$1"
|
||||
fi
|
||||
if [ -z "$KEY_VALUE" ]; then
|
||||
echo "Enter your fal.ai API key:" >&2
|
||||
read -r KEY_VALUE
|
||||
fi
|
||||
if [ -n "$KEY_VALUE" ]; then
|
||||
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
|
||||
mv .env.tmp .env 2>/dev/null || true
|
||||
echo "FAL_KEY=$KEY_VALUE" >> .env
|
||||
echo "FAL_KEY saved to .env" >&2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Load .env if exists
|
||||
if [ -f ".env" ]; then
|
||||
source .env 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--file|-f)
|
||||
FILE_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--json)
|
||||
OUTPUT_JSON=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "fal.ai File Upload Script" >&2
|
||||
echo "" >&2
|
||||
echo "Usage:" >&2
|
||||
echo " ./upload.sh --file /path/to/file" >&2
|
||||
echo "" >&2
|
||||
echo "Options:" >&2
|
||||
echo " --file, -f Local file path (required)" >&2
|
||||
echo " --json Output raw JSON response" >&2
|
||||
echo " --add-fal-key Setup FAL_KEY in .env" >&2
|
||||
echo "" >&2
|
||||
echo "Supported file types:" >&2
|
||||
echo " Images: jpg, jpeg, png, gif, webp, bmp, tiff" >&2
|
||||
echo " Videos: mp4, mov, avi, webm, mkv" >&2
|
||||
echo " Audio: mp3, wav, flac, ogg, m4a" >&2
|
||||
echo "" >&2
|
||||
echo "Examples:" >&2
|
||||
echo " ./upload.sh --file photo.jpg" >&2
|
||||
echo " ./upload.sh --file video.mp4" >&2
|
||||
echo "" >&2
|
||||
echo "Usage with generate.sh:" >&2
|
||||
echo " URL=\$(./upload.sh --file photo.jpg)" >&2
|
||||
echo " ./generate.sh --image-url \"\$URL\" --prompt \"...\"" >&2
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# If no flag, treat as file path
|
||||
if [ -z "$FILE_PATH" ] && [ -f "$1" ]; then
|
||||
FILE_PATH="$1"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate FAL_KEY
|
||||
if [ -z "$FAL_KEY" ]; then
|
||||
echo "Error: FAL_KEY not set" >&2
|
||||
echo "" >&2
|
||||
echo "Run: ./upload.sh --add-fal-key" >&2
|
||||
echo "Or: export FAL_KEY=your_key_here" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate file
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
echo "Error: --file is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$FILE_PATH" ]; then
|
||||
echo "Error: File not found: $FILE_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get filename
|
||||
FILENAME=$(basename "$FILE_PATH")
|
||||
|
||||
# Detect content type
|
||||
EXTENSION="${FILENAME##*.}"
|
||||
EXTENSION_LOWER=$(echo "$EXTENSION" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$EXTENSION_LOWER" in
|
||||
jpg|jpeg) CONTENT_TYPE="image/jpeg" ;;
|
||||
png) CONTENT_TYPE="image/png" ;;
|
||||
gif) CONTENT_TYPE="image/gif" ;;
|
||||
webp) CONTENT_TYPE="image/webp" ;;
|
||||
bmp) CONTENT_TYPE="image/bmp" ;;
|
||||
tiff|tif) CONTENT_TYPE="image/tiff" ;;
|
||||
mp4) CONTENT_TYPE="video/mp4" ;;
|
||||
mov) CONTENT_TYPE="video/quicktime" ;;
|
||||
avi) CONTENT_TYPE="video/x-msvideo" ;;
|
||||
webm) CONTENT_TYPE="video/webm" ;;
|
||||
mkv) CONTENT_TYPE="video/x-matroska" ;;
|
||||
mp3) CONTENT_TYPE="audio/mpeg" ;;
|
||||
wav) CONTENT_TYPE="audio/wav" ;;
|
||||
flac) CONTENT_TYPE="audio/flac" ;;
|
||||
ogg) CONTENT_TYPE="audio/ogg" ;;
|
||||
m4a) CONTENT_TYPE="audio/mp4" ;;
|
||||
*) CONTENT_TYPE="application/octet-stream" ;;
|
||||
esac
|
||||
|
||||
# Get file size
|
||||
FILE_SIZE=$(stat -f%z "$FILE_PATH" 2>/dev/null || stat -c%s "$FILE_PATH" 2>/dev/null)
|
||||
FILE_SIZE_MB=$((FILE_SIZE / 1024 / 1024))
|
||||
|
||||
# Check if file is too large (>100MB needs multipart)
|
||||
if [ "$FILE_SIZE" -gt 104857600 ]; then
|
||||
echo "Error: File too large (${FILE_SIZE_MB}MB > 100MB)" >&2
|
||||
echo "Large file multipart upload not yet supported." >&2
|
||||
echo "Please use the Python client for files >100MB." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uploading: $FILENAME (${FILE_SIZE_MB}MB)" >&2
|
||||
|
||||
# Step 1: Get CDN token
|
||||
echo "Getting CDN token..." >&2
|
||||
|
||||
TOKEN_RESPONSE=$(curl -s -X POST "$FAL_TOKEN_ENDPOINT" \
|
||||
-H "Authorization: Key $FAL_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}')
|
||||
|
||||
# Check for token error
|
||||
if echo "$TOKEN_RESPONSE" | grep -q '"detail"'; then
|
||||
ERROR_MSG=$(echo "$TOKEN_RESPONSE" | grep -o '"msg":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Token error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
|
||||
TOKEN_TYPE=$(echo "$TOKEN_RESPONSE" | grep -o '"token_type":"[^"]*"' | cut -d'"' -f4)
|
||||
BASE_URL=$(echo "$TOKEN_RESPONSE" | grep -o '"base_url":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$TOKEN" ] || [ -z "$BASE_URL" ]; then
|
||||
echo "Error: Failed to get CDN token" >&2
|
||||
echo "$TOKEN_RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Upload file
|
||||
echo "Uploading to CDN..." >&2
|
||||
|
||||
UPLOAD_RESPONSE=$(curl -s -X POST "${BASE_URL}/files/upload" \
|
||||
-H "Authorization: $TOKEN_TYPE $TOKEN" \
|
||||
-H "Content-Type: $CONTENT_TYPE" \
|
||||
-H "X-Fal-File-Name: $FILENAME" \
|
||||
--data-binary "@$FILE_PATH")
|
||||
|
||||
# Check for upload error
|
||||
if echo "$UPLOAD_RESPONSE" | grep -q '"error"'; then
|
||||
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
if [ -z "$ERROR_MSG" ]; then
|
||||
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
|
||||
fi
|
||||
echo "Upload error: $ERROR_MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract URL
|
||||
ACCESS_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"access_url":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$ACCESS_URL" ]; then
|
||||
echo "Error: Failed to get URL from response" >&2
|
||||
echo "$UPLOAD_RESPONSE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Upload complete!" >&2
|
||||
echo "URL: $ACCESS_URL" >&2
|
||||
|
||||
# Output
|
||||
if [ "$OUTPUT_JSON" = true ]; then
|
||||
echo "$UPLOAD_RESPONSE"
|
||||
else
|
||||
# Output just the URL for easy piping
|
||||
echo "$ACCESS_URL"
|
||||
fi
|
||||
Reference in New Issue
Block a user