
Muapi Drone Style Video
- 567 installs
- 4k repo stars
- Updated August 4, 2026
- samuraigpt/generative-media-skills
muapi-drone-style-video is an agent skill that generates drone-style aerial videos through muapi.ai for developers who need cinematic overhead footage without scheduling physical drone production.
About
muapi-drone-style-video in samuraigpt/generative-media-skills produces synthetic aerial and drone-style video through the muapi.ai generative media API. The skill fits ecommerce and SaaS teams that want sweeping overhead shots for property listings, product hero videos, or location promos when real drone capture is costly or unavailable. Agents supply scene prompts and optional reference inputs, then call MuAPI workflows to return hosted or downloadable video suited for web players and ad platforms. The parent generative-media-skills repo documents muapi-media-editing at version 0.1.0 with prompt-based video effects and models including Flux Kontext, GPT-4o, Midjourney, and Qwen in related scripts. Developers reach for muapi-drone-style-video when creative briefs ask for drone aesthetics but timelines forbid on-site filming. Triggers include drone-style video, aerial product shot, muapi.ai cinematic clip, and AI overhead footage generation.
- muapi-drone-style-video
Muapi Drone Style Video by the numbers
- 567 all-time installs (skills.sh)
- +47 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #741 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/samuraigpt/generative-media-skills --skill muapi-drone-style-videoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 567 |
|---|---|
| repo stars | ★ 4k |
| Last updated | August 4, 2026 |
| Repository | samuraigpt/generative-media-skills ↗ |
How do you generate drone-style aerial videos with AI?
Use muapi-drone-style-video for development tasks
Who is it for?
Developers producing ecommerce or real-estate marketing assets who need cinematic aerial-style clips via muapi.ai without a drone crew.
Skip if: Regulated surveying requiring geotagged drone telemetry, frame-perfect match to existing footage, or offline-only generative pipelines.
When should I use this skill?
User requests drone-style video, aerial product footage, or muapi.ai cinematic overhead clips for marketing or listing content.
What you get
AI-generated drone-style aerial video files or URLs suitable for landing pages, listings, and ad creatives.
- Drone-style video file
- Hosted aerial clip URL
By the numbers
- Sibling muapi-media-editing skill documented at version 0.1.0
Files
✏️ MuAPI Media Editing & Enhancement
Advanced editing and enhancement operations for images and videos.
Apply AI-powered edits, enhancements, and effects to existing media. Supports prompt-based editing with Flux Kontext, GPT-4o, and Midjourney, plus one-click operations like upscaling and background removal.
Available Scripts
| Script | Description |
|---|---|
edit-image.sh | Prompt-based image editing (Flux Kontext, GPT-4o, Midjourney, Qwen, and more) |
enhance-image.sh | One-click operations: upscale, background removal, face swap, colorize, Ghibli style, product shots |
lipsync.sh | Sync video lip movement to audio (Sync Labs, LatentSync, Creatify, Veed) |
video-effects.sh | Video/image effects: Wan AI, face swap, dance, dress change, Luma modify/reframe |
Quick Start
# Edit an image with a prompt
bash edit-image.sh --image-url "https://..." --prompt "add sunglasses" --model flux-kontext-pro
# Upscale an image
bash enhance-image.sh --op upscale --image-url "https://..."
# Remove background
bash enhance-image.sh --op background-remove --image-url "https://..."
# Lipsync a video
bash lipsync.sh --video-url "https://..." --audio-url "https://..." --model sync
# Apply dance effect
bash video-effects.sh --op dance --image-url "https://..." --audio-url "https://..."Common Flags
All scripts support: --async, --json, --timeout N, --help
Requirements
MUAPI_KEYenvironment variable (set viacore/platform/setup.sh)curl,jq,python3
#!/bin/bash
# muapi.ai Image Editing
# Usage: ./edit-image.sh --image-url URL --prompt "..." [--model MODEL]
set -e
IMAGE_URL=""
IMAGE_FILE=""
PROMPT=""
MODEL="flux-kontext-pro"
ASPECT_RATIO="1:1"
NUM_IMAGES=1
ASYNC=false
JSON_ONLY=false
JQ_EXPR=""
TIMEOUT=300
while [[ $# -gt 0 ]]; do
case $1 in
--image-url) IMAGE_URL="$2"; shift 2 ;;
--file|-f) IMAGE_FILE="$2"; shift 2 ;;
--prompt|-p) PROMPT="$2"; shift 2 ;;
--model|-m) MODEL="$2"; shift 2 ;;
--aspect-ratio) ASPECT_RATIO="$2"; shift 2 ;;
--num-images) NUM_IMAGES="$2"; shift 2 ;;
--async) ASYNC=true; shift ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--json) JSON_ONLY=true; shift ;;
--jq) JQ_EXPR="$2"; shift 2 ;;
--help|-h)
echo "Usage: ./edit-image.sh --image-url URL --prompt \"...\" [options]"
echo ""
muapi image models
exit 0 ;;
*) shift ;;
esac
done
if [ -z "$PROMPT" ]; then echo "Error: --prompt is required" >&2; exit 1; fi
# Auto-upload local file if provided
if [ -n "$IMAGE_FILE" ]; then
echo "Uploading $(basename "$IMAGE_FILE")..." >&2
IMAGE_URL=$(bash "$(dirname "$0")/../media/upload.sh" --file "$IMAGE_FILE")
fi
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url or --file is required" >&2; exit 1; fi
ARGS=(--model "$MODEL" --image "$IMAGE_URL" --aspect-ratio "$ASPECT_RATIO" --num-images "$NUM_IMAGES")
[ "$JSON_ONLY" = true ] && ARGS+=(--output-json)
[ -n "$JQ_EXPR" ] && ARGS+=(--jq "$JQ_EXPR")
[ "$ASYNC" = true ] && ARGS+=(--no-wait) || ARGS+=(--wait)
muapi image edit "$PROMPT" "${ARGS[@]}"
#!/bin/bash
# muapi.ai Image Enhancement (one-click operations)
# Usage: ./enhance-image.sh --op upscale --image-url URL
set -e
MUAPI_BASE="https://api.muapi.ai/api/v1"
OP=""
IMAGE_URL=""
IMAGE_FILE=""
FACE_URL=""
FACE_FILE=""
MASK_URL=""
MASK_FILE=""
PROMPT=""
ASYNC=false
JSON_ONLY=false
MAX_WAIT=300
POLL_INTERVAL=3
for arg in "$@"; do
if [ "$arg" = "--add-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then KEY_VALUE="$1"; fi
if [ -z "$KEY_VALUE" ]; then echo "Enter your muapi.ai API key:" >&2; read -r KEY_VALUE; fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^MUAPI_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "MUAPI_KEY=$KEY_VALUE" >> .env
echo "MUAPI_KEY saved to .env" >&2
fi
exit 0
fi
done
if [ -f ".env" ]; then source .env 2>/dev/null || true; fi
while [[ $# -gt 0 ]]; do
case $1 in
--op) OP="$2"; shift 2 ;;
--image-url) IMAGE_URL="$2"; shift 2 ;;
--file) IMAGE_FILE="$2"; shift 2 ;;
--face-url) FACE_URL="$2"; shift 2 ;;
--face-file) FACE_FILE="$2"; shift 2 ;;
--mask-url) MASK_URL="$2"; shift 2 ;;
--mask-file) MASK_FILE="$2"; shift 2 ;;
--prompt|-p) PROMPT="$2"; shift 2 ;;
--async) ASYNC=true; shift ;;
--timeout) MAX_WAIT="$2"; shift 2 ;;
--json) JSON_ONLY=true; shift ;;
--help|-h)
echo "muapi.ai Image Enhancement" >&2
echo "" >&2
echo "Usage: ./enhance-image.sh --op OPERATION --image-url URL" >&2
echo "" >&2
echo "Operations (--op):" >&2
echo " upscale Upscale image resolution" >&2
echo " background-remove Remove image background" >&2
echo " face-swap Swap face (requires --face-url)" >&2
echo " skin-enhance Smooth and enhance skin" >&2
echo " colorize Colorize black & white photo" >&2
echo " ghibli Studio Ghibli art style" >&2
echo " anime Anime style (optional --prompt)" >&2
echo " extend Extend/outpaint image" >&2
echo " product-shot Clean product shot background" >&2
echo " product-photo Professional product photography (requires --prompt)" >&2
echo " object-erase Erase object (requires --mask-url or --mask-file index)" >&2
echo "" >&2
echo "File Inputs:" >&2
echo " --file Main image file" >&2
echo " --face-file Face image for swap" >&2
echo " --mask-file Mask image for erase" >&2
exit 0 ;;
*) shift ;;
esac
done
if [ -z "$MUAPI_KEY" ]; then echo "Error: MUAPI_KEY not set" >&2; exit 1; fi
if [ -z "$OP" ]; then echo "Error: --op is required" >&2; exit 1; fi
HEADERS=(-H "x-api-key: $MUAPI_KEY" -H "Content-Type: application/json")
# Auto-upload local files
upload_file() {
local FPATH="$1"
if [ ! -f "$FPATH" ]; then echo "Error: File not found: $FPATH" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Uploading $(basename "$FPATH")..." >&2
local RESP=$(curl -s -X POST "${MUAPI_BASE}/upload_file" -H "x-api-key: $MUAPI_KEY" -F "file=@${FPATH}")
local URL=$(echo "$RESP" | jq -r '.url // empty')
if [ -z "$URL" ]; then
local ERR=$(echo "$RESP" | jq -r '.error // .detail // "Upload failed"')
echo "Error: $ERR" >&2; exit 1
fi
echo "$URL"
}
if [ -n "$IMAGE_FILE" ]; then IMAGE_URL=$(upload_file "$IMAGE_FILE"); fi
if [ -n "$FACE_FILE" ]; then FACE_URL=$(upload_file "$FACE_FILE"); fi
if [ -n "$MASK_FILE" ]; then MASK_URL=$(upload_file "$MASK_FILE"); fi
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url or --file is required" >&2; exit 1; fi
IMAGE_URL_CLEAN=$(echo "$IMAGE_URL" | tr -d '"')
# Map operation to endpoint and payload
PROMPT_JSON=$(echo "${PROMPT:-}" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip()))')
case $OP in
upscale)
ENDPOINT="ai-image-upscale"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
background-remove)
ENDPOINT="ai-background-remover"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
face-swap)
if [ -z "$FACE_URL" ]; then echo "Error: --face-url is required for face-swap" >&2; exit 1; fi
FACE_CLEAN=$(echo "$FACE_URL" | tr -d '"')
ENDPOINT="ai-image-face-swap"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\", \"face_image_url\": \"$FACE_CLEAN\"}" ;;
skin-enhance)
ENDPOINT="ai-skin-enhancer"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
colorize)
ENDPOINT="ai-color-photo"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
ghibli)
ENDPOINT="ai-ghibli-style"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
anime)
ENDPOINT="ai-anime-generator"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\", \"prompt\": $PROMPT_JSON}" ;;
extend)
ENDPOINT="ai-image-extension"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
product-shot)
ENDPOINT="ai-product-shot"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\"}" ;;
product-photo)
ENDPOINT="ai-product-photography"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\", \"prompt\": $PROMPT_JSON}" ;;
object-erase)
if [ -z "$MASK_URL" ]; then echo "Error: --mask-url is required for object-erase" >&2; exit 1; fi
MASK_CLEAN=$(echo "$MASK_URL" | tr -d '"')
ENDPOINT="ai-object-eraser"
PAYLOAD="{\"image_url\": \"$IMAGE_URL_CLEAN\", \"mask_url\": \"$MASK_CLEAN\"}" ;;
*)
echo "Error: Unknown operation '$OP'" >&2
echo "Valid: upscale, background-remove, face-swap, skin-enhance, colorize," >&2
echo " ghibli, anime, extend, product-shot, product-photo, object-erase" >&2
exit 1 ;;
esac
[ "$JSON_ONLY" = false ] && echo "Submitting $OP to $ENDPOINT..." >&2
SUBMIT=$(curl -s -X POST "${MUAPI_BASE}/${ENDPOINT}" "${HEADERS[@]}" -d "$PAYLOAD")
if echo "$SUBMIT" | grep -q '"error"\|"detail"'; then
ERR=$(echo "$SUBMIT" | grep -o '"detail":"[^"]*"' | head -1 | cut -d'"' -f4)
[ -z "$ERR" ] && ERR=$(echo "$SUBMIT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-$SUBMIT}" >&2; exit 1
fi
REQUEST_ID=$(echo "$SUBMIT" | grep -oE '"request_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ -z "$REQUEST_ID" ]; then echo "Error: No request_id" >&2; echo "$SUBMIT" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Request ID: $REQUEST_ID" >&2
if [ "$ASYNC" = true ]; then
[ "$JSON_ONLY" = false ] && echo "Check: bash check-result.sh --id \"$REQUEST_ID\"" >&2
echo "$SUBMIT"; exit 0
fi
[ "$JSON_ONLY" = false ] && echo "Processing..." >&2
ELAPSED=0; LAST_STATUS=""
while [ $ELAPSED -lt $MAX_WAIT ]; do
sleep $POLL_INTERVAL; ELAPSED=$((ELAPSED + POLL_INTERVAL))
RESULT=$(curl -s -X GET "${MUAPI_BASE}/predictions/${REQUEST_ID}/result" "${HEADERS[@]}")
STATUS=$(echo "$RESULT" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ "$STATUS" != "$LAST_STATUS" ] && [ "$JSON_ONLY" = false ]; then echo "Status: $STATUS" >&2; LAST_STATUS="$STATUS"; fi
case $STATUS in
completed)
[ "$JSON_ONLY" = false ] && echo "Done!" >&2
URL=$(echo "$RESULT" | grep -o '"outputs":\[[^]]*\]' | grep -o '"[^"]*\.\(png\|jpg\|jpeg\|webp\)"' | head -1 | tr -d '"')
[ -n "$URL" ] && [ "$JSON_ONLY" = false ] && echo "Image URL: $URL" >&2
echo "$RESULT"; exit 0 ;;
failed)
ERR=$(echo "$RESULT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-Enhancement failed}" >&2; echo "$RESULT"; exit 1 ;;
esac
done
echo "Error: Timeout after ${MAX_WAIT}s — Request ID: $REQUEST_ID" >&2; exit 1
#!/bin/bash
# muapi.ai Lipsync
# Usage: ./lipsync.sh --video-url URL --audio-url URL [--model sync]
set -e
MUAPI_BASE="https://api.muapi.ai/api/v1"
VIDEO_URL=""
VIDEO_FILE=""
AUDIO_URL=""
AUDIO_FILE=""
MODEL="sync"
ASYNC=false
JSON_ONLY=false
MAX_WAIT=300
POLL_INTERVAL=5
for arg in "$@"; do
if [ "$arg" = "--add-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then KEY_VALUE="$1"; fi
if [ -z "$KEY_VALUE" ]; then echo "Enter your muapi.ai API key:" >&2; read -r KEY_VALUE; fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^MUAPI_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "MUAPI_KEY=$KEY_VALUE" >> .env
echo "MUAPI_KEY saved to .env" >&2
fi
exit 0
fi
done
if [ -f ".env" ]; then source .env 2>/dev/null || true; fi
while [[ $# -gt 0 ]]; do
case $1 in
--video-url) VIDEO_URL="$2"; shift 2 ;;
--video-file) VIDEO_FILE="$2"; shift 2 ;;
--audio-url) AUDIO_URL="$2"; shift 2 ;;
--audio-file) AUDIO_FILE="$2"; shift 2 ;;
--model|-m) MODEL="$2"; shift 2 ;;
--async) ASYNC=true; shift ;;
--timeout) MAX_WAIT="$2"; shift 2 ;;
--json) JSON_ONLY=true; shift ;;
--help|-h)
echo "muapi.ai Lipsync" >&2
echo "" >&2
echo "Usage: ./lipsync.sh --video-url URL --audio-url URL [options]" >&2
echo "" >&2
echo "Models (--model):" >&2
echo " sync Sync Labs — high quality (default)" >&2
echo " latent LatentSync — open source" >&2
echo " creatify Creatify — fast" >&2
echo " veed Veed — reliable" >&2
echo "" >&2
echo "File Inputs:" >&2
echo " --video-file Local video file" >&2
echo " --audio-file Local audio file" >&2
exit 0 ;;
*) shift ;;
esac
done
if [ -z "$MUAPI_KEY" ]; then echo "Error: MUAPI_KEY not set" >&2; exit 1; fi
# Auto-upload local files
upload_file() {
local FPATH="$1"
if [ ! -f "$FPATH" ]; then echo "Error: File not found: $FPATH" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Uploading $(basename "$FPATH")..." >&2
local RESP=$(curl -s -X POST "${MUAPI_BASE}/upload_file" -H "x-api-key: $MUAPI_KEY" -F "file=@${FPATH}")
local URL=$(echo "$RESP" | jq -r '.url // empty')
if [ -z "$URL" ]; then
local ERR=$(echo "$RESP" | jq -r '.error // .detail // "Upload failed"')
echo "Error: $ERR" >&2; exit 1
fi
echo "$URL"
}
if [ -n "$VIDEO_FILE" ]; then VIDEO_URL=$(upload_file "$VIDEO_FILE"); fi
if [ -n "$AUDIO_FILE" ]; then AUDIO_URL=$(upload_file "$AUDIO_FILE"); fi
if [ -z "$VIDEO_URL" ]; then echo "Error: --video-url or --video-file is required" >&2; exit 1; fi
if [ -z "$AUDIO_URL" ]; then echo "Error: --audio-url or --audio-file is required" >&2; exit 1; fi
HEADERS=(-H "x-api-key: $MUAPI_KEY" -H "Content-Type: application/json")
VIDEO_CLEAN=$(echo "$VIDEO_URL" | tr -d '"')
AUDIO_CLEAN=$(echo "$AUDIO_URL" | tr -d '"')
case $MODEL in
sync) ENDPOINT="sync-lipsync" ;;
latent) ENDPOINT="latentsync-video" ;;
creatify) ENDPOINT="creatify-lipsync" ;;
veed) ENDPOINT="veed-lipsync" ;;
*)
echo "Error: Unknown model '$MODEL'" >&2
echo "Valid: sync, latent, creatify, veed" >&2
exit 1 ;;
esac
PAYLOAD="{\"video_url\": \"$VIDEO_CLEAN\", \"audio_url\": \"$AUDIO_CLEAN\"}"
[ "$JSON_ONLY" = false ] && echo "Submitting lipsync (model: $MODEL)..." >&2
SUBMIT=$(curl -s -X POST "${MUAPI_BASE}/${ENDPOINT}" "${HEADERS[@]}" -d "$PAYLOAD")
if echo "$SUBMIT" | grep -q '"error"\|"detail"'; then
ERR=$(echo "$SUBMIT" | grep -o '"detail":"[^"]*"' | head -1 | cut -d'"' -f4)
[ -z "$ERR" ] && ERR=$(echo "$SUBMIT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-$SUBMIT}" >&2; exit 1
fi
REQUEST_ID=$(echo "$SUBMIT" | grep -oE '"request_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ -z "$REQUEST_ID" ]; then echo "Error: No request_id" >&2; echo "$SUBMIT" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Request ID: $REQUEST_ID" >&2
if [ "$ASYNC" = true ]; then
[ "$JSON_ONLY" = false ] && echo "Lipsync takes 30–120s. Check: bash check-result.sh --id \"$REQUEST_ID\"" >&2
echo "$SUBMIT"; exit 0
fi
[ "$JSON_ONLY" = false ] && echo "Processing lipsync (30–120s)..." >&2
ELAPSED=0; LAST_STATUS=""
while [ $ELAPSED -lt $MAX_WAIT ]; do
sleep $POLL_INTERVAL; ELAPSED=$((ELAPSED + POLL_INTERVAL))
RESULT=$(curl -s -X GET "${MUAPI_BASE}/predictions/${REQUEST_ID}/result" "${HEADERS[@]}")
STATUS=$(echo "$RESULT" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ "$STATUS" != "$LAST_STATUS" ] && [ "$JSON_ONLY" = false ]; then echo "Status: $STATUS (${ELAPSED}s)" >&2; LAST_STATUS="$STATUS"; fi
case $STATUS in
completed)
[ "$JSON_ONLY" = false ] && echo "" >&2
[ "$JSON_ONLY" = false ] && echo "Lipsync complete!" >&2
URL=$(echo "$RESULT" | grep -o '"outputs":\[[^]]*\]' | grep -o '"[^"]*\.mp4"' | head -1 | tr -d '"')
[ -n "$URL" ] && [ "$JSON_ONLY" = false ] && echo "Video URL: $URL" >&2
echo "$RESULT"; exit 0 ;;
failed)
ERR=$(echo "$RESULT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-Lipsync failed}" >&2; echo "$RESULT"; exit 1 ;;
esac
done
echo "Error: Timeout after ${MAX_WAIT}s — Request ID: $REQUEST_ID" >&2; exit 1
#!/bin/bash
# muapi.ai Video Effects
# Usage: ./video-effects.sh --op face-swap --video-url URL --face-url URL
set -e
MUAPI_BASE="https://api.muapi.ai/api/v1"
OP=""
VIDEO_URL=""
VIDEO_FILE=""
IMAGE_URL=""
IMAGE_FILE=""
FACE_URL=""
FACE_FILE=""
AUDIO_URL=""
AUDIO_FILE=""
PROMPT=""
EFFECT=""
ASYNC=false
JSON_ONLY=false
MAX_WAIT=300
POLL_INTERVAL=5
for arg in "$@"; do
if [ "$arg" = "--add-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then KEY_VALUE="$1"; fi
if [ -z "$KEY_VALUE" ]; then echo "Enter your muapi.ai API key:" >&2; read -r KEY_VALUE; fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^MUAPI_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "MUAPI_KEY=$KEY_VALUE" >> .env
echo "MUAPI_KEY saved to .env" >&2
fi
exit 0
fi
done
if [ -f ".env" ]; then source .env 2>/dev/null || true; fi
while [[ $# -gt 0 ]]; do
case $1 in
--op) OP="$2"; shift 2 ;;
--video-url) VIDEO_URL="$2"; shift 2 ;;
--video-file) VIDEO_FILE="$2"; shift 2 ;;
--image-url) IMAGE_URL="$2"; shift 2 ;;
--image-file) IMAGE_FILE="$2"; shift 2 ;;
--face-url) FACE_URL="$2"; shift 2 ;;
--face-file) FACE_FILE="$2"; shift 2 ;;
--audio-url) AUDIO_URL="$2"; shift 2 ;;
--audio-file) AUDIO_FILE="$2"; shift 2 ;;
--prompt|-p) PROMPT="$2"; shift 2 ;;
--effect) EFFECT="$2"; shift 2 ;;
--async) ASYNC=true; shift ;;
--timeout) MAX_WAIT="$2"; shift 2 ;;
--json) JSON_ONLY=true; shift ;;
--help|-h)
echo "muapi.ai Video Effects" >&2
echo "" >&2
echo "Operations (--op):" >&2
echo " wan-effects Wan AI effects on image (--image-url, --prompt)" >&2
echo " video-effect Named effect on video (--video-url, --effect)" >&2
echo " image-effect Named effect on image (--image-url, --effect)" >&2
echo " dance Dance animation (--image-url, --audio-url)" >&2
echo " face-swap Face swap in video (--video-url, --face-url)" >&2
echo " dress-change Change outfit (--image-url, --prompt)" >&2
echo " luma-modify Modify video with prompt (--video-url, --prompt)" >&2
echo " luma-reframe Reframe video (--video-url, --prompt)" >&2
echo " vidu-reference Vidu character reference (--image-url, --prompt)" >&2
echo "" >&2
echo "File Inputs:" >&2
echo " --video-file Local video file" >&2
echo " --image-file Local image file" >&2
echo " --face-file Local face image file" >&2
echo " --audio-file Local audio file" >&2
exit 0 ;;
*) shift ;;
esac
done
if [ -z "$MUAPI_KEY" ]; then echo "Error: MUAPI_KEY not set" >&2; exit 1; fi
if [ -z "$OP" ]; then echo "Error: --op is required" >&2; exit 1; fi
HEADERS=(-H "x-api-key: $MUAPI_KEY" -H "Content-Type: application/json")
PROMPT_JSON=$(echo "${PROMPT:-}" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip()))')
EFFECT_JSON=$(echo "${EFFECT:-}" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip()))')
clean_url() { echo "$1" | tr -d '"'; }
# Auto-upload local files
upload_file() {
local FPATH="$1"
if [ ! -f "$FPATH" ]; then echo "Error: File not found: $FPATH" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Uploading $(basename "$FPATH")..." >&2
local RESP=$(curl -s -X POST "${MUAPI_BASE}/upload_file" -H "x-api-key: $MUAPI_KEY" -F "file=@${FPATH}")
local URL=$(echo "$RESP" | jq -r '.url // empty')
if [ -z "$URL" ]; then
local ERR=$(echo "$RESP" | jq -r '.error // .detail // "Upload failed"')
echo "Error: $ERR" >&2; exit 1
fi
echo "$URL"
}
if [ -n "$VIDEO_FILE" ]; then VIDEO_URL=$(upload_file "$VIDEO_FILE"); fi
if [ -n "$IMAGE_FILE" ]; then IMAGE_URL=$(upload_file "$IMAGE_FILE"); fi
if [ -n "$FACE_FILE" ]; then FACE_URL=$(upload_file "$FACE_FILE"); fi
if [ -n "$AUDIO_FILE" ]; then AUDIO_URL=$(upload_file "$AUDIO_FILE"); fi
case $OP in
wan-effects)
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url required" >&2; exit 1; fi
ENDPOINT="generate_wan_ai_effects"
PAYLOAD="{\"image_url\": \"$(clean_url "$IMAGE_URL")\", \"prompt\": $PROMPT_JSON}" ;;
video-effect)
if [ -z "$VIDEO_URL" ]; then echo "Error: --video-url required" >&2; exit 1; fi
ENDPOINT="video-effects"
PAYLOAD="{\"video_url\": \"$(clean_url "$VIDEO_URL")\", \"effect\": $EFFECT_JSON}" ;;
image-effect)
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url required" >&2; exit 1; fi
ENDPOINT="image-effects"
PAYLOAD="{\"image_url\": \"$(clean_url "$IMAGE_URL")\", \"effect\": $EFFECT_JSON}" ;;
dance)
if [ -z "$IMAGE_URL" ] || [ -z "$AUDIO_URL" ]; then
echo "Error: --image-url and --audio-url are required for dance" >&2; exit 1
fi
ENDPOINT="ai-dance-effects"
PAYLOAD="{\"image_url\": \"$(clean_url "$IMAGE_URL")\", \"audio_url\": \"$(clean_url "$AUDIO_URL")\"}" ;;
face-swap)
if [ -z "$VIDEO_URL" ] || [ -z "$FACE_URL" ]; then
echo "Error: --video-url and --face-url are required for face-swap" >&2; exit 1
fi
ENDPOINT="ai-video-face-swap"
PAYLOAD="{\"video_url\": \"$(clean_url "$VIDEO_URL")\", \"face_image_url\": \"$(clean_url "$FACE_URL")\"}" ;;
dress-change)
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url required" >&2; exit 1; fi
ENDPOINT="ai-dress-change"
PAYLOAD="{\"image_url\": \"$(clean_url "$IMAGE_URL")\", \"prompt\": $PROMPT_JSON}" ;;
luma-modify)
if [ -z "$VIDEO_URL" ]; then echo "Error: --video-url required" >&2; exit 1; fi
ENDPOINT="luma-modify-video"
PAYLOAD="{\"video_url\": \"$(clean_url "$VIDEO_URL")\", \"prompt\": $PROMPT_JSON}" ;;
luma-reframe)
if [ -z "$VIDEO_URL" ]; then echo "Error: --video-url required" >&2; exit 1; fi
ENDPOINT="luma-flash-reframe"
PAYLOAD="{\"video_url\": \"$(clean_url "$VIDEO_URL")\", \"prompt\": $PROMPT_JSON}" ;;
vidu-reference)
if [ -z "$IMAGE_URL" ]; then echo "Error: --image-url required" >&2; exit 1; fi
ENDPOINT="vidu-q1-reference"
PAYLOAD="{\"image_url\": \"$(clean_url "$IMAGE_URL")\", \"prompt\": $PROMPT_JSON}" ;;
*)
echo "Error: Unknown operation '$OP'" >&2; exit 1 ;;
esac
[ "$JSON_ONLY" = false ] && echo "Submitting $OP to $ENDPOINT..." >&2
SUBMIT=$(curl -s -X POST "${MUAPI_BASE}/${ENDPOINT}" "${HEADERS[@]}" -d "$PAYLOAD")
if echo "$SUBMIT" | grep -q '"error"\|"detail"'; then
ERR=$(echo "$SUBMIT" | grep -o '"detail":"[^"]*"' | head -1 | cut -d'"' -f4)
[ -z "$ERR" ] && ERR=$(echo "$SUBMIT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-$SUBMIT}" >&2; exit 1
fi
REQUEST_ID=$(echo "$SUBMIT" | grep -oE '"request_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ -z "$REQUEST_ID" ]; then echo "Error: No request_id" >&2; echo "$SUBMIT" >&2; exit 1; fi
[ "$JSON_ONLY" = false ] && echo "Request ID: $REQUEST_ID" >&2
if [ "$ASYNC" = true ]; then
[ "$JSON_ONLY" = false ] && echo "Check: bash check-result.sh --id \"$REQUEST_ID\"" >&2
echo "$SUBMIT"; exit 0
fi
[ "$JSON_ONLY" = false ] && echo "Processing..." >&2
ELAPSED=0; LAST_STATUS=""
while [ $ELAPSED -lt $MAX_WAIT ]; do
sleep $POLL_INTERVAL; ELAPSED=$((ELAPSED + POLL_INTERVAL))
RESULT=$(curl -s -X GET "${MUAPI_BASE}/predictions/${REQUEST_ID}/result" "${HEADERS[@]}")
STATUS=$(echo "$RESULT" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ "$STATUS" != "$LAST_STATUS" ] && [ "$JSON_ONLY" = false ]; then echo "Status: $STATUS (${ELAPSED}s)" >&2; LAST_STATUS="$STATUS"; fi
case $STATUS in
completed)
[ "$JSON_ONLY" = false ] && echo "Done!" >&2
URL=$(echo "$RESULT" | grep -o '"outputs":\[[^]]*\]' | grep -o '"[^"]*\.\(mp4\|gif\|png\|jpg\)"' | head -1 | tr -d '"')
[ -n "$URL" ] && [ "$JSON_ONLY" = false ] && echo "Result URL: $URL" >&2
echo "$RESULT"; exit 0 ;;
failed)
ERR=$(echo "$RESULT" | grep -o '"error":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: ${ERR:-Operation failed}" >&2; echo "$RESULT"; exit 1 ;;
esac
done
echo "Error: Timeout after ${MAX_WAIT}s — Request ID: $REQUEST_ID" >&2; exit 1
Related skills
How it compares
Pick muapi-drone-style-video for synthetic aerial marketing clips; pick stock-footage search skills when you need licensed real-world drone takes.
FAQ
Does muapi-drone-style-video require a physical drone?
muapi-drone-style-video synthesizes aerial drone-style footage through muapi.ai prompts and API calls, removing the need for on-site drone capture while delivering marketing-ready video assets.
What repo version context applies to muapi-drone-style-video?
muapi-drone-style-video lives in samuraigpt/generative-media-skills where sibling muapi-media-editing documentation is version 0.1.0 with prompt-based muapi.ai video and image operations.