Gemini 3.6 Flash and 3.5 Flash Lite are now live on CometAPI →
Q

Happy Horse 1.0

초당:$0.112
출시일:May 31, 2026

Happy Horse 1.0 — 텍스트-투-비디오 및 이미지-투-비디오 생성을 지원하는 고품질 오디오·비디오 생성 모델입니다. 영상, 오디오, 입 모양을 동기화하여 생성할 수 있어 단편 영화, 광고 크리에이티브, 제품 쇼케이스에 적합합니다.

새로운
상업적 사용

Happy Horse 1.0의 Playground

Happy Horse 1.0의 Playground를 탐색하세요 — 모델을 테스트하고 실시간으로 쿼리를 실행하는 대화형 환경입니다. 프롬프트를 시도하고, 매개변수를 조정하며, 즉시 반복하여 개발을 가속화하고 사용 사례를 검증하세요.

HappyHorse-1.0의 기술 사양

항목HappyHorse-1.0
제공사Alibaba(익명 벤치마크 데뷔 후 공개적으로 보고됨)
모델 유형멀티모달 AI 비디오 생성
입력텍스트, 이미지
출력비디오 + 동기화된 오디오
아키텍처통합 단일 스트림 트랜스포머
파라미터~15B
해상도네이티브 1080p 생성
생성 모드오디오-비디오 동시 생성
디노이징증류된 추론(~8단계로 보고됨)
언어 지원다국어 립싱크(7개 언어 보고됨)

HappyHorse-1.0란 무엇인가

HappyHorse-1.0은 여러 모델을 이어 붙이는 대신 단일 생성 파이프라인에서 비디오와 동기화된 오디오를 함께 생성하도록 설계된 최첨단 AI 비디오 생성 모델이다. 공개 보도에 따르면, 이 모델은 초기에는 벤치마크 아레나에 익명으로 등장했으며 이후 Alibaba의 AI 노력과 연관된 것으로 알려졌다.

시각 요소를 먼저 렌더링하고 이후 사운드를 덧입히는 기존 텍스트-투-비디오 시스템과 달리, HappyHorse는 동작, 음성, 분위기, 타이밍 간의 네이티브 동기화를 중시한다.

HappyHorse-1.0의 주요 기능

  • 한 번에 오디오 + 비디오 동시 생성
  • 강제 업스케일링 없이 네이티브 1080p 출력
  • 텍스트-투-비디오 및 이미지-투-비디오 워크플로
  • 빠른 증류 기반 생성 파이프라인
  • 다국어 립싱크
  • 시네마틱 카메라 무빙과 장면 연속성에 중점

HappyHorse-1.0의 벤치마크 성능

공개된 벤치마크 보고에 따르면:

  • Artificial Analysis Arena:
    • 텍스트-투-비디오 Elo: ~1330+
    • 이미지-투-비디오 Elo: ~1390+
  • 초기 공개 기간 동안 공개 리더보드 스냅샷에서 1위 또는 최상위권에 랭크됨.

벤치마크 해석: 이는 선호도 기반 리더보드 점수이며, 모든 프로덕션 워크로드 전반의 보편적 품질 순위로 해석해서는 안 된다.

HappyHorse-1.0 vs 유사 모델

기능HappyHorse-1.0Seedance 2.0Kling 3.0
오디오 + 비디오 동시부분적
네이티브 1080p
오픈 릴리스 방향발표됨독점독점
텍스트→비디오
이미지→비디오
다국어 립싱크7개 보고됨다국어다국어

CometAPI에서 HappyHorse-1.0을 사용하려면?

  1. API 자격 증명을 발급받는다.
  2. happyhorse-1.0을 선택한다.
  3. 프롬프트와 생성 옵션을 포함해 생성 요청을 전송한다.
  4. 생성된 미디어 출력을 수신한다.

자주 묻는 질문

Happy Horse 1.0 가격

[모델명]의 경쟁력 있는 가격을 살펴보세요. 다양한 예산과 사용 요구에 맞게 설계되었습니다. 유연한 요금제로 사용한 만큼만 지불하므로 요구사항이 증가함에 따라 쉽게 확장할 수 있습니다. [모델명]이 비용을 관리 가능한 수준으로 유지하면서 프로젝트를 어떻게 향상시킬 수 있는지 알아보세요.

Happyhorse Video Generation Pricing

Pricing (Per Second)

Model720p1080p
happyhorse-1.0$0.112$0.192

💡 Billed per second. Total cost = price per second × video duration (seconds).

Happy Horse 1.0의 샘플 코드 및 API

[모델 이름]의 포괄적인 샘플 코드와 API 리소스에 액세스하여 통합 프로세스를 간소화하세요. 자세한 문서는 단계별 가이드를 제공하여 프로젝트에서 [모델 이름]의 모든 잠재력을 활용할 수 있도록 돕습니다.

#!/bin/bash

set -euo pipefail

if [[ -z "${COMETAPI_KEY:-}" ]]; then
  echo "Set COMETAPI_KEY before running this example."
  exit 0
fi

MODEL="happyhorse-1.0"
PROMPT='A miniature city built from cardboard and bottle caps comes alive at night. A tiny cardboard train rolls through the streets while warm lights glow from paper windows, physically realistic motion, detailed tabletop cinematography.'
VIDEO_SECONDS="3"
SIZE="1280x720"
BASE_URL="https://api.cometapi.com"
OUTPUT_DIR="./output"
POLL_INTERVAL_SECONDS=10
RETRY_DELAY_SECONDS=5
MAX_CREATE_ATTEMPTS=5

is_progress_complete() {
  local progress="$1"
  local normalized="${progress%%%}"

  if [[ -z "$normalized" ]]; then
    return 1
  fi

  [[ "$normalized" =~ ^[0-9]+([.][0-9]+)?$ ]] || return 1
  awk -v value="$normalized" 'BEGIN { exit !(value >= 100) }'
}

is_transient_status() {
  local status_code="$1"
  [[ "$status_code" == "429" || "$status_code" =~ ^5 ]]
}

create_task() {
  local attempt=1
  while (( attempt <= MAX_CREATE_ATTEMPTS )); do
    local response
    local status_code
    if ! response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos"       -H "Authorization: Bearer $COMETAPI_KEY"       -F "model=${MODEL}"       -F "prompt=${PROMPT}"       -F "seconds=${VIDEO_SECONDS}"       -F "size=${SIZE}"); then
      if (( attempt == MAX_CREATE_ATTEMPTS )); then
        return 1
      fi
      echo "Create request failed, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    status_code=$(echo "$response" | tail -n 1)
    CREATE_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if is_transient_status "$status_code" && (( attempt < MAX_CREATE_ATTEMPTS )); then
      echo "Create request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$CREATE_RESPONSE"
    return 1
  done
}

get_task() {
  local task_id="$1"
  while true; do
    local response
    local status_code
    if ! response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos/${task_id}"       -H "Authorization: Bearer $COMETAPI_KEY"); then
      echo "Status request failed, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      continue
    fi

    status_code=$(echo "$response" | tail -n 1)
    TASK_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if is_transient_status "$status_code"; then
      echo "Status request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      continue
    fi

    echo "$TASK_RESPONSE"
    return 1
  done
}

download_video() {
  local content_url="$1"
  local output_path="$2"

  while true; do
    if curl -fsS "$content_url"       -H "Authorization: Bearer $COMETAPI_KEY"       -o "$output_path"; then
      if [[ -s "$output_path" ]]; then
        return 0
      fi
      echo "Content download was empty, retrying..."
    else
      echo "Content request failed, retrying..."
    fi
    sleep "$RETRY_DELAY_SECONDS"
  done
}

create_task

TASK_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // .task_id // empty')

if [[ -z "$TASK_ID" ]]; then
  echo "$CREATE_RESPONSE" | jq .
  echo "No task id returned."
  exit 1
fi

echo "Task created: $TASK_ID"
echo "Initial status: $(echo "$CREATE_RESPONSE" | jq -r '.status // empty')"

while true; do
  get_task "$TASK_ID"

  STATUS=$(echo "$TASK_RESPONSE" | jq -r '.status // empty')
  NORMALIZED_STATUS=$(echo "$STATUS" | tr '[:upper:]' '[:lower:]')
  PROGRESS=$(echo "$TASK_RESPONSE" | jq -r '.progress // empty')
  SHOULD_DOWNLOAD=0

  if [[ "$NORMALIZED_STATUS" == "completed" || "$NORMALIZED_STATUS" == "success" ]]; then
    SHOULD_DOWNLOAD=1
  elif [[ "$NORMALIZED_STATUS" == "unknown" ]] && is_progress_complete "$PROGRESS"; then
    SHOULD_DOWNLOAD=1
  fi

  echo "Status: ${STATUS}, progress: ${PROGRESS}"

  if [[ "$SHOULD_DOWNLOAD" == "1" || "$NORMALIZED_STATUS" == "failed" || "$NORMALIZED_STATUS" == "error" ]]; then
    if [[ "$SHOULD_DOWNLOAD" != "1" ]]; then
      echo "$TASK_RESPONSE" | jq .
      exit 1
    fi

    VIDEO_URL=$(echo "$TASK_RESPONSE" | jq -r '.video_url // empty')
    CONTENT_URL="${BASE_URL}/v1/videos/${TASK_ID}/content"
    OUTPUT_PATH="${OUTPUT_DIR}/${TASK_ID}.mp4"

    mkdir -p "$OUTPUT_DIR"
    download_video "$CONTENT_URL" "$OUTPUT_PATH"

    echo "Video URL: ${VIDEO_URL}"
    echo "Content endpoint: ${CONTENT_URL}"
    echo "Saved to ${OUTPUT_PATH}"
    echo "File size: $(wc -c < "$OUTPUT_PATH" | tr -d ' ') bytes"
    break
  fi

  sleep "$POLL_INTERVAL_SECONDS"
done

cURL Code Example

#!/bin/bash

set -euo pipefail

if [[ -z "${COMETAPI_KEY:-}" ]]; then
  echo "Set COMETAPI_KEY before running this example."
  exit 0
fi

MODEL="happyhorse-1.0"
PROMPT='A miniature city built from cardboard and bottle caps comes alive at night. A tiny cardboard train rolls through the streets while warm lights glow from paper windows, physically realistic motion, detailed tabletop cinematography.'
VIDEO_SECONDS="3"
SIZE="1280x720"
BASE_URL="https://api.cometapi.com"
OUTPUT_DIR="./output"
POLL_INTERVAL_SECONDS=10
RETRY_DELAY_SECONDS=5
MAX_CREATE_ATTEMPTS=5

is_progress_complete() {
  local progress="$1"
  local normalized="${progress%%%}"

  if [[ -z "$normalized" ]]; then
    return 1
  fi

  [[ "$normalized" =~ ^[0-9]+([.][0-9]+)?$ ]] || return 1
  awk -v value="$normalized" 'BEGIN { exit !(value >= 100) }'
}

is_transient_status() {
  local status_code="$1"
  [[ "$status_code" == "429" || "$status_code" =~ ^5 ]]
}

create_task() {
  local attempt=1
  while (( attempt <= MAX_CREATE_ATTEMPTS )); do
    local response
    local status_code
    if ! response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos"       -H "Authorization: Bearer $COMETAPI_KEY"       -F "model=${MODEL}"       -F "prompt=${PROMPT}"       -F "seconds=${VIDEO_SECONDS}"       -F "size=${SIZE}"); then
      if (( attempt == MAX_CREATE_ATTEMPTS )); then
        return 1
      fi
      echo "Create request failed, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    status_code=$(echo "$response" | tail -n 1)
    CREATE_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if is_transient_status "$status_code" && (( attempt < MAX_CREATE_ATTEMPTS )); then
      echo "Create request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      (( attempt += 1 ))
      continue
    fi

    echo "$CREATE_RESPONSE"
    return 1
  done
}

get_task() {
  local task_id="$1"
  while true; do
    local response
    local status_code
    if ! response=$(curl -sS -w $'
%{http_code}' "${BASE_URL}/v1/videos/${task_id}"       -H "Authorization: Bearer $COMETAPI_KEY"); then
      echo "Status request failed, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      continue
    fi

    status_code=$(echo "$response" | tail -n 1)
    TASK_RESPONSE=$(echo "$response" | sed '$d')

    if [[ "$status_code" =~ ^2 ]]; then
      return 0
    fi

    if is_transient_status "$status_code"; then
      echo "Status request returned ${status_code}, retrying..."
      sleep "$RETRY_DELAY_SECONDS"
      continue
    fi

    echo "$TASK_RESPONSE"
    return 1
  done
}

download_video() {
  local content_url="$1"
  local output_path="$2"

  while true; do
    if curl -fsS "$content_url"       -H "Authorization: Bearer $COMETAPI_KEY"       -o "$output_path"; then
      if [[ -s "$output_path" ]]; then
        return 0
      fi
      echo "Content download was empty, retrying..."
    else
      echo "Content request failed, retrying..."
    fi
    sleep "$RETRY_DELAY_SECONDS"
  done
}

create_task

TASK_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id // .task_id // empty')

if [[ -z "$TASK_ID" ]]; then
  echo "$CREATE_RESPONSE" | jq .
  echo "No task id returned."
  exit 1
fi

echo "Task created: $TASK_ID"
echo "Initial status: $(echo "$CREATE_RESPONSE" | jq -r '.status // empty')"

while true; do
  get_task "$TASK_ID"

  STATUS=$(echo "$TASK_RESPONSE" | jq -r '.status // empty')
  NORMALIZED_STATUS=$(echo "$STATUS" | tr '[:upper:]' '[:lower:]')
  PROGRESS=$(echo "$TASK_RESPONSE" | jq -r '.progress // empty')
  SHOULD_DOWNLOAD=0

  if [[ "$NORMALIZED_STATUS" == "completed" || "$NORMALIZED_STATUS" == "success" ]]; then
    SHOULD_DOWNLOAD=1
  elif [[ "$NORMALIZED_STATUS" == "unknown" ]] && is_progress_complete "$PROGRESS"; then
    SHOULD_DOWNLOAD=1
  fi

  echo "Status: ${STATUS}, progress: ${PROGRESS}"

  if [[ "$SHOULD_DOWNLOAD" == "1" || "$NORMALIZED_STATUS" == "failed" || "$NORMALIZED_STATUS" == "error" ]]; then
    if [[ "$SHOULD_DOWNLOAD" != "1" ]]; then
      echo "$TASK_RESPONSE" | jq .
      exit 1
    fi

    VIDEO_URL=$(echo "$TASK_RESPONSE" | jq -r '.video_url // empty')
    CONTENT_URL="${BASE_URL}/v1/videos/${TASK_ID}/content"
    OUTPUT_PATH="${OUTPUT_DIR}/${TASK_ID}.mp4"

    mkdir -p "$OUTPUT_DIR"
    download_video "$CONTENT_URL" "$OUTPUT_PATH"

    echo "Video URL: ${VIDEO_URL}"
    echo "Content endpoint: ${CONTENT_URL}"
    echo "Saved to ${OUTPUT_PATH}"
    echo "File size: $(wc -c < "$OUTPUT_PATH" | tr -d ' ') bytes"
    break
  fi

  sleep "$POLL_INTERVAL_SECONDS"
done

Python Code Example

import json
import os
import time

import requests


MODEL = "happyhorse-1.0"
PROMPT = (
    "A miniature city built from cardboard and bottle caps comes alive at night. "
    "A tiny cardboard train rolls through the streets while warm lights glow from "
    "paper windows, physically realistic motion, detailed tabletop cinematography."
)
SECONDS = "3"
SIZE = "1280x720"
BASE_URL = "https://api.cometapi.com"
OUTPUT_DIR = "./output"
POLL_INTERVAL_SECONDS = 10
RETRY_DELAY_SECONDS = 5
MAX_CREATE_ATTEMPTS = 5
TERMINAL_STATUSES = {"completed", "failed", "error", "success"}
SUCCESS_STATUSES = {"completed", "success"}


try:
    COMETAPI_KEY = os.environ["COMETAPI_KEY"]
except KeyError:
    print("Set COMETAPI_KEY before running this example.")
    raise SystemExit(0)


headers = {"Authorization": f"Bearer {COMETAPI_KEY}"}


def is_progress_complete(progress):
    if isinstance(progress, (int, float)):
        return progress >= 100
    if isinstance(progress, str):
        try:
            return float(progress.rstrip("%")) >= 100
        except ValueError:
            return False
    return False


def is_transient_status(status_code):
    return status_code == 429 or 500 <= status_code < 600


def create_task():
    fields = {
        "model": (None, MODEL),
        "prompt": (None, PROMPT),
        "seconds": (None, SECONDS),
        "size": (None, SIZE),
    }
    for attempt in range(1, MAX_CREATE_ATTEMPTS + 1):
        try:
            response = requests.post(
                f"{BASE_URL}/v1/videos",
                headers=headers,
                files=fields,
                timeout=30,
            )
        except requests.RequestException as error:
            if attempt == MAX_CREATE_ATTEMPTS:
                raise
            print(f"Create request failed: {error}, retrying...")
            time.sleep(RETRY_DELAY_SECONDS)
            continue

        if response.ok:
            return response
        if not is_transient_status(response.status_code) or attempt == MAX_CREATE_ATTEMPTS:
            response.raise_for_status()
        print(f"Create request returned {response.status_code}, retrying...")
        time.sleep(RETRY_DELAY_SECONDS)
    raise SystemExit("Failed to create task.")


def get_task(task_id):
    while True:
        try:
            response = requests.get(
                f"{BASE_URL}/v1/videos/{task_id}",
                headers=headers,
                timeout=15,
            )
        except requests.RequestException as error:
            print(f"Status request failed: {error}, retrying...")
            time.sleep(RETRY_DELAY_SECONDS)
            continue

        if response.ok:
            return response
        if not is_transient_status(response.status_code):
            response.raise_for_status()
        print(f"Status request returned {response.status_code}, retrying...")
        time.sleep(RETRY_DELAY_SECONDS)


def download_video(content_url, output_path):
    while True:
        try:
            with requests.get(content_url, headers=headers, timeout=120, stream=True) as response:
                if response.ok:
                    with open(output_path, "wb") as output_file:
                        for chunk in response.iter_content(chunk_size=8192):
                            if chunk:
                                output_file.write(chunk)
                    return
                if not is_transient_status(response.status_code):
                    response.raise_for_status()
                print(f"Content request returned {response.status_code}, retrying...")
        except requests.RequestException as error:
            print(f"Content request failed: {error}, retrying...")
        time.sleep(RETRY_DELAY_SECONDS)


create_response = create_task()
create_result = create_response.json()

task_id = create_result.get("id") or create_result.get("task_id")
if not task_id:
    print(json.dumps(create_result, indent=2))
    raise SystemExit("No task id returned.")

print(f"Task created: {task_id}")
print(f"Initial status: {create_result.get('status')}")

while True:
    task_response = get_task(task_id)
    task = task_response.json()
    status = str(task.get("status") or "unknown")
    normalized_status = status.lower()
    progress = task.get("progress")
    should_download = normalized_status in SUCCESS_STATUSES or (
        normalized_status == "unknown" and is_progress_complete(progress)
    )

    print(f"Status: {status}, progress: {progress}")

    if should_download or normalized_status in TERMINAL_STATUSES:
        if not should_download:
            print(json.dumps(task, indent=2))
            raise SystemExit(1)

        content_url = f"{BASE_URL}/v1/videos/{task_id}/content"
        output_path = os.path.join(OUTPUT_DIR, f"{task_id}.mp4")
        os.makedirs(OUTPUT_DIR, exist_ok=True)
        download_video(content_url, output_path)

        print(f"Video URL: {task.get('video_url') or ''}")
        print(f"Content endpoint: {content_url}")
        print(f"Saved to {output_path}")
        print(f"File size: {os.path.getsize(output_path)} bytes")
        break

    time.sleep(POLL_INTERVAL_SECONDS)

JavaScript Code Example

import fs from "fs";
import path from "path";

const model = "happyhorse-1.0";
const prompt = "A miniature city built from cardboard and bottle caps comes alive at night. A tiny cardboard train rolls through the streets while warm lights glow from paper windows, physically realistic motion, detailed tabletop cinematography.";
const seconds = "3";
const size = "1280x720";
const api_key = process.env.COMETAPI_KEY;
const base_url = "https://api.cometapi.com";
const output_dir = "./output";
const poll_interval_ms = 10_000;
const retry_delay_ms = 5_000;
const max_create_attempts = 5;
const terminal_statuses = new Set(["completed", "failed", "error", "success"]);
const success_statuses = new Set(["completed", "success"]);

if (!api_key) {
  console.log("Set COMETAPI_KEY before running this example.");
  process.exit(0);
}

function is_progress_complete(progress) {
  if (typeof progress === "number") {
    return progress >= 100;
  }
  if (typeof progress === "string") {
    const numeric = Number(progress.replace(/%$/, ""));
    return Number.isFinite(numeric) && numeric >= 100;
  }
  return false;
}

function is_transient_status(status) {
  return status === 429 || status >= 500;
}

async function sleep(ms) {
  await new Promise((resolve) => setTimeout(resolve, ms));
}

async function fetch_with_retry(url, options, attempts, label) {
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      const response = await fetch(url, options);
      if (response.ok) {
        return response;
      }
      if (!is_transient_status(response.status) || attempt === attempts) {
        return response;
      }
      console.log(`${label} returned ${response.status}, retrying...`);
    } catch (error) {
      if (attempt === attempts) {
        throw error;
      }
      console.log(`${label} failed: ${error.message}, retrying...`);
    }
    await sleep(retry_delay_ms);
  }
  throw new Error(`${label} failed`);
}

async function fetch_status(task_id) {
  while (true) {
    try {
      const response = await fetch(`${base_url}/v1/videos/${task_id}`, { headers });
      if (response.ok) {
        return response;
      }
      if (!is_transient_status(response.status)) {
        return response;
      }
      console.log(`Status request returned ${response.status}, retrying...`);
    } catch (error) {
      console.log(`Status request failed: ${error.message}, retrying...`);
    }
    await sleep(retry_delay_ms);
  }
}

async function download_video(content_url, output_path) {
  while (true) {
    try {
      const response = await fetch(content_url, { headers });
      if (response.ok) {
        const video_buffer = Buffer.from(await response.arrayBuffer());
        fs.writeFileSync(output_path, video_buffer);
        return;
      }
      if (!is_transient_status(response.status)) {
        console.log(await response.text());
        process.exit(1);
      }
      console.log(`Content request returned ${response.status}, retrying...`);
    } catch (error) {
      console.log(`Content request failed: ${error.message}, retrying...`);
    }
    await sleep(retry_delay_ms);
  }
}

const form = new FormData();
form.set("model", model);
form.set("prompt", prompt);
form.set("seconds", seconds);
form.set("size", size);

const headers = { Authorization: `Bearer ${api_key}` };
const create_response = await fetch_with_retry(`${base_url}/v1/videos`, {
  method: "POST",
  headers,
  body: form,
}, max_create_attempts, "Create request");

if (!create_response.ok) {
  console.log(await create_response.text());
  process.exit(1);
}

const create_result = await create_response.json();
const task_id = create_result.id || create_result.task_id;

if (!task_id) {
  console.log(JSON.stringify(create_result, null, 2));
  process.exit(1);
}

console.log(`Task created: ${task_id}`);
console.log(`Initial status: ${create_result.status}`);

while (true) {
  const task_response = await fetch_status(task_id);

  if (!task_response.ok) {
    console.log(await task_response.text());
    process.exit(1);
  }

  const task = await task_response.json();
  const status = String(task.status || "unknown");
  const normalized_status = status.toLowerCase();
  const progress = task.progress;
  const should_download = success_statuses.has(normalized_status) || (
    normalized_status === "unknown" && is_progress_complete(progress)
  );

  console.log(`Status: ${status}, progress: ${progress}`);

  if (should_download || terminal_statuses.has(normalized_status)) {
    if (!should_download) {
      console.log(JSON.stringify(task, null, 2));
      process.exit(1);
    }

    const content_url = `${base_url}/v1/videos/${task_id}/content`;
    const output_path = path.join(output_dir, `${task_id}.mp4`);
    fs.mkdirSync(output_dir, { recursive: true });
    await download_video(content_url, output_path);

    console.log(`Video URL: ${task.video_url || ""}`);
    console.log(`Content endpoint: ${content_url}`);
    console.log(`Saved to ${output_path}`);
    console.log(`File size: ${fs.statSync(output_path).size} bytes`);
    break;
  }

  await sleep(poll_interval_ms);
}

Happy Horse 1.0의 버전

Happy Horse 1.0에 여러 스냅샷이 존재하는 이유는 업데이트 후 출력 변동으로 인해 일관성을 유지하기 위해 이전 스냅샷을 보관하거나, 개발자에게 적응 및 마이그레이션을 위한 전환 기간을 제공하거나, 글로벌 또는 지역별 엔드포인트에 따라 다양한 스냅샷을 제공하여 사용자 경험을 최적화하기 위한 것 등이 포함될 수 있습니다. 버전 간 상세한 차이점은 공식 문서를 참고해 주시기 바랍니다.

Version
happyhorse-1.0