ListenHubSDKs & CLI
JavaScript SDK

サンプル

OpenAPIClient 向けの実行可能な TypeScript レシピ 8 本——ポッドキャスト、flow speech、TTS、画像、動画、音楽、コンテンツ抽出、エラー処理。

OpenAPIClient(API キー、サーバーサイド)の上に組み立てた、そのままコピー&ペーストで使える完全なレシピ集です。それぞれが tsx で実行できる自己完結した TypeScript ファイルになっています。全体に共通する 2 つの前提を、最初に一度だけまとめておきます:

  • クライアントは環境変数から構築します。 new OpenAPIClient()LISTENHUB_API_KEY を読み取ります。listenhub.ai/settings/api-keys でキーを作成し、サーバーサイドで保管してください。認証 を参照。
  • 生成は非同期です。 作成系の呼び出しはすぐに episodeId または taskId を返します。その後、ステータスが pending / generating を抜けるまで get* メソッドをポーリングします。以下のレシピはいずれも、ポーリングループに同じ sleep ヘルパーを使います。

どのレシピも LISTENHUB_API_KEY=lh_sk_... npx tsx recipe.ts で実行できます。SDK は ESM のみ対応で、Node.js >= 20 が必要です。ログイン済みユーザーを代理して動作するユーザー向けアプリでは、代わりに OAuth を使う ListenHubClient を利用してください——認証 を参照。

ポッドキャストを作成して完了までポーリングする

URL を根拠にした 2 人ホストのポッドキャストです。言語を指定してスピーカーを一覧し、エピソードを開始してから、processStatuspending を抜けるまで getPodcast をポーリングします。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient(); // reads LISTENHUB_API_KEY

// Pick two voices for the language.
const { items: speakers } = await client.listSpeakers({ language: 'en' });
const [host, guest] = speakers;

const { episodeId } = await client.createPodcast({
  query: 'Explain how transformers work in large language models',
  sources: [
    {
      type: 'url',
      content: 'https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)',
    },
  ],
  speakers: [{ speakerId: host.speakerId }, { speakerId: guest.speakerId }],
  language: 'en',
});
console.log(`Created podcast: ${episodeId}`);

let detail = await client.getPodcast(episodeId);
while (detail.processStatus === 'pending') {
  await sleep(5000);
  detail = await client.getPodcast(episodeId);
  console.log(`Status: ${detail.processStatus}`);
}

if (detail.audioUrl) {
  console.log(`Title: ${detail.title}`);
  console.log(`Audio: ${detail.audioUrl}`);
} else {
  console.error(`Generation failed (failCode ${detail.failCode}): ${detail.message}`);
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

複数スピーカーの flow speech

Flow speech は、テキストや Web ページを 1 つ以上の声でナレーションします。speakers に複数のエントリを渡すと、スクリプト全体でそれらを交互に使います。「作成してポーリングする」という形はポッドキャストのレシピと同じで、変わるのはメソッド名だけです。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

const { items: speakers } = await client.listSpeakers({ language: 'en' });
const [voiceA, voiceB] = speakers;

const { episodeId } = await client.createFlowSpeech({
  sources: [{ type: 'url', uri: 'https://en.wikipedia.org/wiki/Mars' }],
  speakers: [{ speakerId: voiceA.speakerId }, { speakerId: voiceB.speakerId }],
  language: 'en',
  mode: 'smart', // 'smart' rewrites the source into a script; 'direct' reads it as-is
});
console.log(`Created flow speech: ${episodeId}`);

let detail = await client.getFlowSpeech(episodeId);
while (detail.processStatus === 'pending') {
  await sleep(3000);
  detail = await client.getFlowSpeech(episodeId);
  console.log(`Status: ${detail.processStatus}`);
}

if (detail.audioUrl) {
  console.log(`Title: ${detail.title}`);
  console.log(`Audio: ${detail.audioUrl}`);
} else {
  console.error(`Generation failed (failCode ${detail.failCode}): ${detail.message}`);
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

テキスト読み上げをファイルに保存する

tts は同期的で、生の音声 Response を返します(エンベロープなし、ポーリングなし)。ボディはそのままディスクに書き出せます。voicelistSpeakers で得た speakerId)と、任意で response_format を渡します。

import { writeFile } from 'node:fs/promises';
import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

const { items: speakers } = await client.listSpeakers({ language: 'en' });
const voice = speakers[0];

// `tts` returns the raw Response — read its bytes directly.
const response = await client.tts({
  input: 'Hello world, this is ListenHub speaking.',
  voice: voice.speakerId,
  response_format: 'mp3', // 'mp3' | 'opus' | 'aac' | 'flac' | 'wav' | 'pcm'
});

const audio = Buffer.from(await response.arrayBuffer());
await writeFile('speech.mp3', audio);
console.log(`Wrote ${audio.byteLength} bytes to speech.mp3`);

生のバイト列ではなく、ホスト済み URL と字幕を返す複数行スクリプトが必要な場合は、speech({ scripts: [{ content, speakerId }] }) を使ってください——{ audioUrl, audioDuration, subtitlesUrl, taskId, credits } に解決されます。

参照画像を使って画像を生成する

createImage は同期的で、結果を直接返します。referenceImages を渡すと、既存の画像を土台にして生成できます——ホスト済みファイル(fileData)か、インラインの base64(inlineData)のどちらかです。レスポンスは型付けされていないオブジェクトなので、フィールドは防御的に読み取ってください。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

const result = await client.createImage({
  provider: 'gemini',
  prompt: 'Redraw this scene as a watercolor painting at golden hour',
  referenceImages: [
    {
      fileData: {
        fileUri: 'https://storage.googleapis.com/your-bucket/reference.png',
        mimeType: 'image/png',
      },
    },
  ],
  imageConfig: {
    imageSize: '2K', // '1K' | '2K' | '4K'
    aspectRatio: '16:9', // '16:9' | '4:3' | '1:1' | '3:4' | '9:16' | '21:9'
  },
});

// createImage returns an untyped record — log it to see the shape, then read fields.
console.log(result);

画像データをインラインで渡す場合は、fileDatainlineData: { data: '<base64>', mimeType: 'image/png' } に置き換えます。参照画像は構図とスタイルを誘導しますが、どう変えたいかを決めるのは引き続き prompt です。

動画(SeeDance)を生成してポーリングする

createVideoGeneration は Doubao SeeDance モデルを実行します。まず estimateVideoCredits でコストを見積もり、タスクを開始してから、ステータスが successfailed になるまで getVideoGenerationTask をポーリングします。content はテキストと、任意の参照フレームから組み立てます。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

// Estimate before committing to an expensive job.
const estimate = await client.estimateVideoCredits({
  model: 'doubao-seedance-2-fast',
  resolution: '720p',
  duration: 5,
});
console.log(`Estimated credits: ${estimate.credits}`);

const task = await client.createVideoGeneration({
  model: 'doubao-seedance-2-fast', // 'doubao-seedance-2-pro' | 'doubao-seedance-2-fast' | 'happyhorse'
  content: [
    { type: 'text', text: 'A cat sprinting through a sunlit garden' },
    {
      type: 'image_url',
      image_url: { url: 'https://example.com/cat.jpg' },
      role: 'first_frame', // 'first_frame' | 'last_frame' | 'reference_image'
    },
  ],
  resolution: '720p', // '480p' | '720p' | '1080p'
  duration: 5,
});
console.log(`Task created: ${task.taskId} (${task.status})`);

let detail = await client.getVideoGenerationTask(task.taskId);
while (detail.status !== 'success' && detail.status !== 'failed') {
  await sleep(10_000);
  detail = await client.getVideoGenerationTask(task.taskId);
  console.log(`Status: ${detail.status}`);
}

if (detail.status === 'success') {
  console.log(`Video: ${detail.videoUrl}`);
  console.log(`Seed: ${detail.seed}`);
} else {
  console.error('Video generation failed');
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

音楽を生成してポーリングする

createMusicGenerate はタスクを返します。進行中のステータスを抜けるまで getMusicTask をポーリングしてください。完了したタスクは tracks 配列を持ち、各トラックがそれぞれの titleaudioUrl を持ちます。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

const job = await client.createMusicGenerate({
  prompt: 'Upbeat lo-fi hip hop beat with jazzy piano chords',
  style: 'lo-fi',
  title: 'Late Night Study',
});
console.log(`Music task: ${job.taskId} (${job.status})`);

let task = await client.getMusicTask(job.taskId);
while (task.status !== 'success' && task.status !== 'failed') {
  await sleep(10_000);
  task = await client.getMusicTask(job.taskId);
  console.log(`Status: ${task.status}`);
}

if (task.status === 'success') {
  for (const track of task.tracks) {
    console.log(`${track.title} — ${track.audioUrl}`);
  }
} else {
  console.error(`Failed: ${task.errorMessage}`);
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

URL からコンテンツを抽出する

createContentExtract は Web ページから整形済みのテキスト(と任意でメタデータ)を取り出します。返るのは taskId なので、ステータスが completedfailed になるまで getContentExtract をポーリングし、data.content を読み取ります。ポッドキャストや flow speech の前段で、内容の根拠を用意する手順として便利です。

import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

const { taskId } = await client.createContentExtract({
  source: { type: 'url', uri: 'https://en.wikipedia.org/wiki/Mars' },
  options: { summarize: true, maxLength: 2000 },
});
console.log(`Extract task: ${taskId}`);

let detail = await client.getContentExtract(taskId);
while (detail.status === 'processing') {
  await sleep(3000);
  detail = await client.getContentExtract(taskId);
  console.log(`Status: ${detail.status}`);
}

if (detail.status === 'completed') {
  console.log(detail.data?.content);
} else {
  console.error(`Extract failed (failCode ${detail.failCode}): ${detail.message}`);
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

ListenHubError でエラーを処理する

どのメソッドも、code が 0 以外の場合や HTTP エラーが起きた場合に ListenHubError を投げます。これを捕捉すれば、API の失敗をネットワークエラーやプログラムのバグと切り分けられます。さらに status で分岐すれば、認証やレート制限のケースに個別に対応できます。サポートに問い合わせるときに伝えるべき値は requestId です。

import { OpenAPIClient, ListenHubError } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

try {
  // A bad ID surfaces as a structured API error.
  const detail = await client.getPodcast('nonexistent-id');
  console.log(detail.title);
} catch (err) {
  if (err instanceof ListenHubError) {
    console.error(`[${err.status}] ${err.code}: ${err.message}`);
    if (err.requestId) console.error(`request ${err.requestId}`);

    if (err.status === 401 || err.status === 403) {
      // Credential rejected — rotate the API key.
    } else if (err.status === 429) {
      // The client already retried up to maxRetries; you are still limited.
    }
  } else {
    // Network failure, timeout, or a bug — not an API error.
    throw err;
  }
}

429 Too Many Requests は、あなたの catch に届く前に Retry-After ヘッダーに従って自動でリトライされます(最大 maxRetries 回、デフォルトは 2)。設定 を参照してください。

生成する前にコストを確認する

クレジットのコストは製品、長さ、オプションによって変わるため、これらのレシピでは具体的な数値を示しません。getSubscription()totalAvailableCredits フィールド)で現在の残高を読み取り、コストの高いジョブの前に該当する見積もりエンドポイント——たとえば estimateVideoCredits——を呼び出してください。どの製品が見積もり用エンドポイントを提供しているかは OpenAPI リファレンス を参照してください。

次のステップ

このページの内容