
Muapi One Shot Video
- 568 installs
- 4k repo stars
- Updated August 4, 2026
- samuraigpt/generative-media-skills
muapi-one-shot-video is a generative-media agent skill that creates single-take AI video clips through muapi.ai for developers who need continuous-scene footage without manual video editing timelines.
About
muapi-one-shot-video is a samuraigpt generative-media-skills agent skill that generates continuous one-shot style video clips via muapi.ai from text prompts or media inputs. The skill shares a repository with muapi-media-editing (version 0.1.0), which documents video effects, lipsync, upscaling, and edit-image.sh alongside Flux Kontext, GPT-4o, Midjourney, and Qwen backends. Developers reach for muapi-one-shot-video when they need uninterrupted hero loops, product reveal footage, or announcement clips for SaaS landing pages. Agents can iterate prompt direction and export web-ready video files for embeds and social posts.
- muapi-one-shot-video
Muapi One Shot Video by the numbers
- 568 all-time installs (skills.sh)
- +48 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #735 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-one-shot-videoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 568 |
|---|---|
| repo stars | ★ 4k |
| Last updated | August 4, 2026 |
| Repository | samuraigpt/generative-media-skills ↗ |
How do you generate one-shot AI video clips for demos?
Use muapi-one-shot-video for development tasks
Who is it for?
Developers building SaaS or product landing pages who need continuous one-shot demo footage from muapi.ai without a video editor.
Skip if: Multi-cut explainers with precise storyboards, voice-over timing, and chapter markers for long-form tutorials.
When should I use this skill?
A landing page or product announcement needs a continuous one-shot video clip generated via muapi.ai.
What you get
Single-take MP4 or WebM hero clips ready for landing-page embeds and announcements
- one-shot video clip files
By the numbers
- Related muapi-media-editing skill documented at version 0.1.0 with video effects and lipsync support
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
FAQ
What is a one-shot video in muapi-one-shot-video?
muapi-one-shot-video generates continuous single-take style clips through muapi.ai rather than stitched edits. Developers use the output as hero loops, product reveals, and announcement footage on landing pages and social posts.
Which muapi repo version documents related video tooling?
muapi-one-shot-video sits in samuraigpt/generative-media-skills with muapi-media-editing version 0.1.0, which lists video effects, lipsync, upscaling, and edit-image.sh for post-generation enhancement.