0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

無料のテキストモデルで動画プロンプトを作り、Seedance 2 Mini で下書きまで回す(Node.js・1ファイル)

0
Posted at

img-W44-Qiita-02.png

前回は Seedance 2 API の「送信 → 状態確認 → 保存」をまとめました。今回はその手前、プロンプトづくりを自動化します。

やることはシンプルです。

  1. 商品の説明文を、無料のテキストモデル deepseek-v4.1-flash-free に渡して、動画プロンプトを3案つくる
  2. 3案を seedance-2-mini(480p・8秒)で下書きする
  3. 一番良い1本だけ、あとで seedance-2 で作り直す

テキストも動画も同じ API キーで呼べるので、1ファイルで完結します。

コストの目安

工程 モデル 金額
プロンプト3案 deepseek-v4.1-flash-free \$0
下書き3本 seedance-2-mini・480p・8秒 \$0.22 × 3 = \$0.66

料金は 2026年11月2日時点の beatapi.io の表示価格です。無料モデルの提供内容は変わることがあるので、使う前に料金ページで確認し、本番の処理には有料モデルの代わりも用意しておくと安心です。

前提

  • Node.js 18 以上(fetch が標準で使えます)
  • 環境変数 BEATAPI_API_KEY
  • テキストは OpenAI 互換のエンドポイント https://api.beatapi.io/v1/chat/completions を使います

1. 無料モデルでプロンプトを3案つくる

うまくいく動画プロンプトには型があります。被写体、動き(動詞は1つ)、カメラの動き(1つ)、光(1つ)、変えてはいけないもの。これをシステムプロンプトで縛ります。

const base = "https://api.beatapi.io/v1";
const headers = {
  Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
  "Content-Type": "application/json",
};

async function makePrompts(product: string): Promise<string[]> {
  const res = await fetch(`${base}/chat/completions`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      model: "deepseek-v4.1-flash-free",
      messages: [
        {
          role: "system",
          content:
            "You write 8-second product video prompts. Each prompt: subject and look, ONE action, ONE camera move, ONE light source, and what must NOT change (logo, color). English, under 60 words, no text overlay. Return only a JSON array of 3 strings.",
        },
        { role: "user", content: product },
      ],
    }),
  });
  if (!res.ok) throw new Error(`chat failed: ${res.status}`);
  const json = await res.json();
  const text: string = json.choices[0].message.content;
  // モデルが前後に文章を付けても拾えるように、最初の [ から最後の ] までを読む
  const arr = JSON.parse(text.slice(text.indexOf("["), text.lastIndexOf("]") + 1));
  if (!Array.isArray(arr) || arr.length === 0) throw new Error("no prompts");
  return arr.slice(0, 3);
}

2. Seedance 2 Mini で3本送る

1案ごとに Idempotency-Key を分けます。通信が切れて再送するときは、同じキー・同じ本文で送ります。

async function submitDraft(prompt: string, key: string): Promise<string> {
  const res = await fetch(`${base}/videos/tasks`, {
    method: "POST",
    headers: { ...headers, "Idempotency-Key": key },
    body: JSON.stringify({
      model: "seedance-2-mini",
      prompt,
      duration: 8,
      aspect_ratio: "16:9",
      resolution: "480p",
    }),
  });
  if (!res.ok) throw new Error(`submit failed: ${res.status}`);
  const { data } = await res.json();
  return data.id; // まず保存する
}

3. 終わるまで待って、URL を並べる

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function waitForTask(id: string, timeoutMs = 12 * 60_000) {
  // 待つ上限(ローカルの期限)。超えてもタスクの失敗ではない
  const deadline = Date.now() + timeoutMs;
  let delay = 5_000;
  while (Date.now() < deadline) {
    const res = await fetch(`${base}/tasks/${id}`, { headers });
    if (res.status === 429) {
      // Retry-After を秒で読む。読めなければ 10 秒待つ
      const ra = Number(res.headers.get("retry-after"));
      await sleep((Number.isFinite(ra) && ra > 0 ? ra : 10) * 1_000);
      continue;
    }
    if (res.ok) {
      const { data } = await res.json();
      if (data.status === "succeeded" || data.status === "failed") return data;
    } else if (res.status < 500) {
      throw new Error(`task query failed: ${res.status}`);
    }
    // 一時的なエラーは少し待ってから再確認する
    await sleep(delay + Math.floor(Math.random() * 1_000));
    delay = Math.min(Math.round(delay * 1.35), 15_000);
  }
  // 待つのをやめるだけ。タスクIDは残して、あとで照合する
  throw new Error(`client deadline reached; keep the task id: ${id}`);
}

async function main() {
  const product =
    "Amber perfume bottle with a gold cap, label 'NOIR'. Premium, warm, for an online store hero video.";
  const prompts = await makePrompts(product);

  const ids: string[] = [];
  for (const [i, p] of prompts.entries()) {
    ids.push(await submitDraft(p, `noir-draft-${i}-v1`));
  }

  const results = await Promise.all(ids.map(waitForTask));
  results.forEach((t, i) => {
    const url = t.output?.media?.find((m: any) => m.type === "video")?.url;
    console.log(`#${i + 1} ${t.status}\n  prompt: ${prompts[i]}\n  video: ${url ?? t.error_code}`);
  });
}

main();

実行すると、3案のプロンプトと、それぞれの下書き動画の URL が並びます。見比べて1本を選び、同じプロンプトで model を seedance-2、resolution を 720p に変えて送れば仕上げです(720p・8秒で \$0.99)。

ハマりどころ

  • JSON で返ってこない:テキストモデルが説明文を付けることがあります。上のコードのように配列部分だけを切り出すか、失敗したらもう一度だけ聞き直します。
  • 同じ動画が2本できる:タイムアウト後に新しいキーで送り直すと起きます。キーは「1案につき1つ」で固定します。
  • generate_audio を付けてエラー:generate_audio は Fast と Standard のパラメータです。Mini には付けず、必要なら seedance-2-fast を使います。
  • 無料モデルが使えない日:提供内容が変わることがあります。model を有料のテキストモデルに差し替えられるようにしておきます。

まとめ

プロンプトは無料モデル、下書きは一番安い動画モデル、仕上げだけ良いモデル。1商品の下書き3本で \$0.66 です。商品が10個なら、まず \$6.60 で方向を決められます。

出典・確認日

  • テキスト:OpenAI 互換 POST /v1/chat/completions(BeatAPI 公開 API ドキュメント)
  • 無料モデル:beatapi.io のモデル一覧で deepseek-v4.1-flash-free が無料であることを 2026年11月2日に確認
  • 動画料金:seedance-2-mini 480p・8秒 \$0.22、seedance-2 720p・8秒 \$0.99(2026年11月2日)

モデルの仕様と料金:https://beatapi.io/seedance-2-api

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?