Major updates: - Added 35+ new skills from awesome-opencode-skills and antigravity repos - Merged SEO skills into seo-master - Merged architecture skills into architecture - Merged security skills into security-auditor and security-coder - Merged testing skills into testing-master and testing-patterns - Merged pentesting skills into pentesting - Renamed website-creator to thai-frontend-dev - Replaced skill-creator with github version - Removed Chutes references (use MiniMax API instead) - Added install-openclaw-skills.sh for cross-platform installation - Updated .env.example with MiniMax API credentials
267 lines
9.3 KiB
Bash
Executable File
267 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# MiniMax Music Generation CLI (pure bash)
|
|
#
|
|
# Usage:
|
|
# bash scripts/music/generate_music.sh --lyrics "[verse]\nHello world" --output output/song.mp3 --download
|
|
# bash scripts/music/generate_music.sh --instrumental --prompt "ambient electronic" -o output/ambient.mp3 --download
|
|
# bash scripts/music/generate_music.sh --lyrics "[verse]\nStars" --genre pop --mood happy -o output/happy.mp3 --download
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
# ============================================================================
|
|
# Common functions (shared with generate_voice.sh)
|
|
# ============================================================================
|
|
|
|
load_env() {
|
|
local env_file
|
|
for env_file in "$PROJECT_ROOT/.env" "$(pwd)/.env"; do
|
|
if [[ -f "$env_file" ]]; then
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line="${line%%#*}"
|
|
line="$(echo "$line" | xargs)"
|
|
[[ -z "$line" || "$line" != *=* ]] && continue
|
|
local key="${line%%=*}" val="${line#*=}"
|
|
key="$(echo "$key" | xargs)"; val="$(echo "$val" | xargs)"
|
|
if [[ ${#val} -ge 2 ]]; then
|
|
case "$val" in
|
|
\"*\") val="${val:1:${#val}-2}" ;;
|
|
\'*\') val="${val:1:${#val}-2}" ;;
|
|
esac
|
|
fi
|
|
[[ -z "${!key:-}" ]] && export "$key=$val"
|
|
done < "$env_file"
|
|
return 0
|
|
fi
|
|
done
|
|
}
|
|
|
|
check_api_key() {
|
|
if [[ -z "${MINIMAX_API_KEY:-}" ]]; then
|
|
echo "Error: MINIMAX_API_KEY environment variable is not set." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
main() {
|
|
load_env
|
|
check_api_key
|
|
|
|
local lyrics="" prompt="" model="music-2.5" instrumental=false
|
|
local genre="" mood="" tempo="" bpm="" key="" instruments="" vocals=""
|
|
local use_case="" structure="" avoid="" references=""
|
|
local output="" output_format="url" stream=false download=false
|
|
local sample_rate="" bitrate="" format="" aigc_watermark=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--lyrics) lyrics="$2"; shift 2 ;;
|
|
--prompt) prompt="$2"; shift 2 ;;
|
|
--model) model="$2"; shift 2 ;;
|
|
--instrumental) instrumental=true; shift ;;
|
|
--genre) genre="$2"; shift 2 ;;
|
|
--mood) mood="$2"; shift 2 ;;
|
|
--tempo) tempo="$2"; shift 2 ;;
|
|
--bpm) bpm="$2"; shift 2 ;;
|
|
--key) key="$2"; shift 2 ;;
|
|
--instruments) instruments="$2"; shift 2 ;;
|
|
--vocals) vocals="$2"; shift 2 ;;
|
|
--use-case) use_case="$2"; shift 2 ;;
|
|
--structure) structure="$2"; shift 2 ;;
|
|
--avoid) avoid="$2"; shift 2 ;;
|
|
--references) references="$2"; shift 2 ;;
|
|
-o|--output) output="$2"; shift 2 ;;
|
|
--output-format) output_format="$2"; shift 2 ;;
|
|
--stream) stream=true; shift ;;
|
|
--download) download=true; shift ;;
|
|
--sample-rate) sample_rate="$2"; shift 2 ;;
|
|
--bitrate) bitrate="$2"; shift 2 ;;
|
|
--format) format="$2"; shift 2 ;;
|
|
--aigc-watermark) aigc_watermark="$2"; shift 2 ;;
|
|
-h|--help)
|
|
cat <<'USAGE'
|
|
MiniMax Music Generation CLI
|
|
|
|
Usage:
|
|
generate_music.sh [options]
|
|
|
|
Options:
|
|
--lyrics TEXT Song lyrics (with [verse]/[chorus] tags)
|
|
--prompt TEXT Music style/description prompt
|
|
--instrumental Generate instrumental (no vocals)
|
|
--model MODEL Model name (default: music-2.5)
|
|
--genre TEXT Genre (e.g. pop, rock, jazz)
|
|
--mood TEXT Mood (e.g. happy, melancholic)
|
|
--tempo TEXT Tempo description (e.g. fast, slow)
|
|
--bpm NUMBER Beats per minute
|
|
--key TEXT Musical key (e.g. C major, A minor)
|
|
--instruments TEXT Instruments to include
|
|
--vocals TEXT Vocal style description
|
|
--use-case TEXT Use case (e.g. background, theme song)
|
|
--structure TEXT Song structure
|
|
--avoid TEXT Elements to avoid
|
|
--references TEXT Reference tracks/artists
|
|
--output-format FMT Output format: url (default) or hex
|
|
--download Download audio file (for url format)
|
|
--sample-rate N Audio sample rate
|
|
--bitrate N Audio bitrate
|
|
--format FMT Audio format (mp3, wav, etc.)
|
|
-o, --output FILE Output file path (required)
|
|
|
|
Examples:
|
|
generate_music.sh --instrumental --prompt "ambient electronic" -o ambient.mp3 --download
|
|
generate_music.sh --lyrics "[verse]\nHello world" -o song.mp3 --download
|
|
generate_music.sh --lyrics "[verse]\nStars" --genre pop --mood happy -o happy.mp3 --download
|
|
USAGE
|
|
exit 0
|
|
;;
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$output" ]]; then
|
|
echo "Error: --output / -o is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Build prompt from structured fields
|
|
local field_parts=()
|
|
[[ -n "$genre" ]] && field_parts+=("Genre: $genre")
|
|
[[ -n "$mood" ]] && field_parts+=("Mood: $mood")
|
|
[[ -n "$tempo" ]] && field_parts+=("Tempo: $tempo")
|
|
[[ -n "$bpm" ]] && field_parts+=("BPM: $bpm")
|
|
[[ -n "$key" ]] && field_parts+=("Key: $key")
|
|
[[ -n "$instruments" ]] && field_parts+=("Instruments: $instruments")
|
|
[[ -n "$vocals" ]] && field_parts+=("Vocals: $vocals")
|
|
[[ -n "$use_case" ]] && field_parts+=("Use case: $use_case")
|
|
[[ -n "$structure" ]] && field_parts+=("Structure: $structure")
|
|
[[ -n "$avoid" ]] && field_parts+=("Avoid: $avoid")
|
|
[[ -n "$references" ]] && field_parts+=("References: $references")
|
|
|
|
local field_prompt=""
|
|
if [[ ${#field_parts[@]} -gt 0 ]]; then
|
|
field_prompt="$(IFS='. '; echo "${field_parts[*]}")"
|
|
fi
|
|
|
|
if [[ -n "$field_prompt" ]]; then
|
|
if [[ -n "$prompt" ]]; then
|
|
prompt="$prompt. $field_prompt"
|
|
else
|
|
prompt="$field_prompt"
|
|
fi
|
|
fi
|
|
|
|
# Build payload
|
|
local payload
|
|
payload=$(jq -n \
|
|
--arg model "$model" \
|
|
--arg prompt "$prompt" \
|
|
--arg of "$output_format" \
|
|
--argjson stream "$stream" \
|
|
'{model: $model, prompt: $prompt, output_format: $of, stream: $stream}')
|
|
|
|
if $instrumental; then
|
|
# music-2.5 does not support is_instrumental — use lyrics workaround
|
|
payload=$(echo "$payload" | jq '. + {lyrics: "[intro] [outro]"}')
|
|
local current_prompt
|
|
current_prompt="$(echo "$payload" | jq -r '.prompt // ""')"
|
|
if [[ -n "$current_prompt" ]]; then
|
|
payload=$(echo "$payload" | jq --arg p "$current_prompt. pure music, no lyrics" '.prompt = $p')
|
|
else
|
|
payload=$(echo "$payload" | jq '.prompt = "pure music, no lyrics"')
|
|
fi
|
|
else
|
|
payload=$(echo "$payload" | jq --arg l "$lyrics" '. + {lyrics: $l}')
|
|
fi
|
|
|
|
# Audio settings
|
|
local audio_setting="{}"
|
|
[[ -n "$sample_rate" ]] && audio_setting=$(echo "$audio_setting" | jq --argjson sr "$sample_rate" '. + {sample_rate: $sr}')
|
|
[[ -n "$bitrate" ]] && audio_setting=$(echo "$audio_setting" | jq --argjson br "$bitrate" '. + {bitrate: $br}')
|
|
[[ -n "$format" ]] && audio_setting=$(echo "$audio_setting" | jq --arg f "$format" '. + {format: $f}')
|
|
if [[ "$audio_setting" != "{}" ]]; then
|
|
payload=$(echo "$payload" | jq --argjson as "$audio_setting" '. + {audio_setting: $as}')
|
|
fi
|
|
|
|
[[ -n "$aigc_watermark" ]] && payload=$(echo "$payload" | jq --argjson aw "$aigc_watermark" '. + {aigc_watermark: $aw}')
|
|
|
|
local api_host="${MINIMAX_API_HOST:-https://api.minimaxi.com}"
|
|
local api_url="${api_host}/v1/music_generation"
|
|
|
|
echo "Generating music with model: $model"
|
|
echo "Output format: $output_format"
|
|
|
|
# Send request via curl
|
|
local raw_output http_code response
|
|
raw_output="$(curl -s -w "\n%{http_code}" \
|
|
-X POST "$api_url" \
|
|
-H "Authorization: Bearer ${MINIMAX_API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
--max-time 300 \
|
|
-d "$payload" 2>/dev/null)" || {
|
|
echo "Error: curl request failed" >&2
|
|
exit 1
|
|
}
|
|
|
|
http_code="${raw_output##*$'\n'}"
|
|
response="${raw_output%$'\n'*}"
|
|
|
|
if [[ "$http_code" -ge 400 ]] 2>/dev/null; then
|
|
echo "Error: API returned HTTP $http_code" >&2
|
|
echo "$response" >&2
|
|
exit 1
|
|
fi
|
|
|
|
local status_code
|
|
status_code="$(echo "$response" | jq -r '.base_resp.status_code // 0')" 2>/dev/null || true
|
|
if [[ "$status_code" != "0" && -n "$status_code" ]]; then
|
|
echo "API error: $(echo "$response" | jq '.base_resp')" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$output")"
|
|
|
|
if [[ "$output_format" == "hex" ]]; then
|
|
local audio_hex
|
|
audio_hex="$(echo "$response" | jq -r '.data.audio // empty')"
|
|
if [[ -z "$audio_hex" ]]; then
|
|
echo "Error: No audio hex data in response." >&2
|
|
exit 1
|
|
fi
|
|
echo "$audio_hex" | xxd -r -p > "$output"
|
|
echo "Audio saved to: $output"
|
|
|
|
elif [[ "$output_format" == "url" ]]; then
|
|
local audio_url
|
|
audio_url="$(echo "$response" | jq -r '.data.audio_url // .data.audio // .data.audio_file.download_url // empty')"
|
|
if [[ -z "$audio_url" ]]; then
|
|
echo "Error: No audio URL in response." >&2
|
|
echo "$response" | jq . >&2
|
|
exit 1
|
|
fi
|
|
echo "Audio URL: $audio_url"
|
|
if $download; then
|
|
curl -s -o "$output" --max-time 120 "$audio_url"
|
|
echo "Audio downloaded to: $output"
|
|
else
|
|
echo "Use --download to save the file, or download manually from the URL above."
|
|
echo "$audio_url" > "$output"
|
|
echo "URL written to: $output"
|
|
fi
|
|
fi
|
|
|
|
# Print extra info if present
|
|
local extra
|
|
extra="$(echo "$response" | jq -r '.extra_info // .data.extra_info // empty')" 2>/dev/null || true
|
|
if [[ -n "$extra" && "$extra" != "null" ]]; then
|
|
echo "Extra info: $extra"
|
|
fi
|
|
}
|
|
|
|
main "$@"
|