Skip to content

Instantly share code, notes, and snippets.

@roelven
Created October 24, 2025 13:59
Show Gist options
  • Save roelven/0b99791d8794251161cf04a9308339bd to your computer and use it in GitHub Desktop.
Save roelven/0b99791d8794251161cf04a9308339bd to your computer and use it in GitHub Desktop.
Generate Sora 2 videos by calling the API directly.
#!/usr/bin/env bash
# Simple CLI for OpenAI Sora 2 video generation.
# Requirements: bash, curl, jq
# Usage examples at bottom.
set -euo pipefail
API_BASE="${OPENAI_API_BASE:-https://api.openai.com}"
API_KEY="YOUR_API_KEY"
MODEL="${MODEL:-sora-2}" # or "sora-2-pro"
PROMPT="${PROMPT:-}"
SIZE="${SIZE:-1280x720}" # e.g. 1280x720, 720x1280, 1792x1024
SECONDS="${SECONDS:-8}" # typical allowed values: 4, 8, 12 (per docs)
REF_IMAGE="${REF_IMAGE:-}" # optional: path to a JPEG/PNG/WebP; should match SIZE
OUT="${OUT:-sora_output.mp4}" # output filename
POLL_INTERVAL="${POLL_INTERVAL:-8}"
if [[ -z "$API_KEY" ]]; then
echo "ERROR: Set OPENAI_API_KEY in your environment." >&2
exit 1
fi
if [[ -z "$PROMPT" ]]; then
echo "ERROR: Provide a prompt via PROMPT='your text' sora2.sh" >&2
exit 1
fi
# 1) Create video job (multipart to support optional image reference)
echo "Submitting video job..."
CREATE_URL="${API_BASE}/v1/videos"
# Build -F arguments
FORM=(-F "model=${MODEL}" -F "prompt=${PROMPT}" -F "size=${SIZE}" -F "seconds=${SECONDS}")
if [[ -n "${REF_IMAGE}" ]]; then
# Best effort to infer mime; override with REF_MIME if needed.
EXT="${REF_IMAGE##*.}"
case "${REF_MIME:-$EXT}" in
jpg|jpeg|image/jpeg) MIME="image/jpeg" ;;
png|image/png) MIME="image/png" ;;
webp|image/webp) MIME="image/webp" ;;
*) MIME="application/octet-stream" ;;
esac
FORM+=(-F "input_reference=@${REF_IMAGE};type=${MIME}")
fi
CREATE_RESP="$(curl -sS -X POST "$CREATE_URL" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Accept: application/json" \
-H "Expect:" \
-H "Connection: keep-alive" \
-F "model=${MODEL}" \
-F "prompt=${PROMPT}" \
-F "size=${SIZE}" \
-F "seconds=${SECONDS}" \
${REF_IMAGE:+-F "input_reference=@${REF_IMAGE};type=${MIME}"} )"
if [[ -z "$CREATE_RESP" ]]; then
echo "ERROR: empty response creating video job" >&2
exit 1
fi
echo "$CREATE_RESP" | jq . >/dev/null || { echo "ERROR: non-JSON response:"; echo "$CREATE_RESP"; exit 1; }
VIDEO_ID="$(echo "$CREATE_RESP" | jq -r '.id')"
STATUS="$(echo "$CREATE_RESP" | jq -r '.status')"
if [[ "$VIDEO_ID" == "null" || -z "$VIDEO_ID" ]]; then
echo "ERROR: Could not find video id in response:"
echo "$CREATE_RESP"
exit 1
fi
echo "Video ID: $VIDEO_ID (status: $STATUS)"
# 2) Poll for completion
RETRIEVE_URL="${API_BASE}/v1/videos/${VIDEO_ID}"
while true; do
RESP="$(curl -sS -X GET "$RETRIEVE_URL" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Accept: application/json")"
STATUS="$(echo "$RESP" | jq -r '.status')"
PROG="$(echo "$RESP" | jq -r '.progress // 0')"
echo "Status: $STATUS Progress: ${PROG}%"
case "$STATUS" in
completed) break ;;
failed|canceled)
echo "Job ended with status: $STATUS"
echo "$RESP" | jq .
exit 2
;;
queued|in_progress)
sleep "$POLL_INTERVAL"
;;
*)
echo "Unexpected status: $STATUS"
echo "$RESP" | jq .
sleep "$POLL_INTERVAL"
;;
esac
done
# 3) Download MP4 content
CONTENT_URL="${API_BASE}/v1/videos/${VIDEO_ID}/content"
echo "Downloading video content to ${OUT} ..."
curl -sS -L "$CONTENT_URL" \
-H "Authorization: Bearer ${API_KEY}" \
--output "$OUT"
# (Optional) also fetch a thumbnail
# THUMB_URL="${CONTENT_URL}?variant=thumbnail"
# curl -sS -L "$THUMB_URL" -H "Authorization: Bearer ${API_KEY}" --output "${OUT%.*}.webp"
echo "Done. File saved: $OUT"
echo "Tip: delete remote artifact when you’re done:"
echo "curl -X DELETE \"${API_BASE}/v1/videos/${VIDEO_ID}\" -H \"Authorization: Bearer ${API_KEY}\" | jq ."
# --- Example usage ---
# PROMPT="Wide shot of a child flying a red kite across a grassy park, soft golden-hour light, cinematic" \
# SIZE=1280x720 SECONDS=8 OUT=park.mp4 bash sora2.sh
#
# With image reference (must match SIZE):
# PROMPT="Animate this scene so the kite lifts and the camera slowly dollies right" \
# SIZE=1280x720 SECONDS=8 REF_IMAGE=./first_frame_1280x720.jpg OUT=guided.mp4 bash sora2.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment