サンプル
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 人ホストのポッドキャストです。言語を指定してスピーカーを一覧し、エピソードを開始してから、processStatus が pending を抜けるまで 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 を返します(エンベロープなし、ポーリングなし)。ボディはそのままディスクに書き出せます。voice(listSpeakers で得た 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);画像データをインラインで渡す場合は、fileData を inlineData: { data: '<base64>', mimeType: 'image/png' } に置き換えます。参照画像は構図とスタイルを誘導しますが、どう変えたいかを決めるのは引き続き prompt です。
動画(SeeDance)を生成してポーリングする
createVideoGeneration は Doubao SeeDance モデルを実行します。まず estimateVideoCredits でコストを見積もり、タスクを開始してから、ステータスが success か failed になるまで 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 配列を持ち、各トラックがそれぞれの title と audioUrl を持ちます。
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 なので、ステータスが completed か failed になるまで 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 リファレンス を参照してください。