キャラクター
client.characters を使って、TypeScript からキャラクターを作成し、ポートレート候補を生成して 1 つを承認し、表情、ポーズ、モーションクリップを追加します。
client.characters は、キャラクタースタジオが行うことをすべてスクリプトで再現します。キャラクターの作成と編集、ポートレート候補の生成、候補の 1 つをキャラクターの顔として承認すること、そして表情、ポーズ、ライティング、アングル、モーションクリップの追加です。キャラクターは、ポートレート、アセットのコレクション、リファレンス写真、そして人物を説明するキャプションを保持するため、以降のすべての画像と動画で同じ人物を表示できます。これらのメソッドは、キャラクター REST API を呼び出します。エディターでの操作については、キャラクタースタジオを参照してください。
メソッド
| メソッド | 内容 |
|---|---|
list(params?) | 自分のキャラクターを、ページ単位で一覧表示します |
get(id) | 1 人のキャラクターを、進行中のジョブとともに読み取ります |
create(input)、update(id, input)、upsert(input) | キャラクターを作成または変更します |
delete(id) | キャラクターをアーカイブします |
restore(id) | アーカイブされたキャラクターを元に戻します |
duplicate(id, input?) | キャラクターを複製します |
usage(id) | キャラクターを使うワークフローを数えます |
generate(input) | ポートレート候補を生成します |
generateAsset(input) | 表情、ポーズ、ライティング、アングルのバリエーションを生成します |
generateMotion(input) | ポートレートをアニメーション化してモーションクリップにします |
approvePortrait(id, candidateJobId) | 候補をキャラクターのポートレートにします |
recaption(id) | キャラクターの説明を書き直します |
キャラクターを最初から最後まで作成する
通常は、キャラクターを作成し、ポートレート候補を生成し、その 1 つを承認してから、その上にバリエーションを追加していきます。
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})
async function waitForJob(jobId: string) {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed" || data.status === "failed" || data.status === "cancelled") return data
await new Promise((resolve) => setTimeout(resolve, 3_000))
}
}
// 1. Create the character
const { id: characterId } = await client.characters.create({
nodeId: "scripted",
name: "Kira",
description: "Young protagonist with auburn hair",
style: "realistic",
seedPrompt: "Kira portrait, warm natural lighting",
})
// 2. Generate 4 portrait candidates
const { jobIds } = await client.characters.generate({
name: "Kira",
seedPrompt: "Kira portrait, warm natural lighting",
count: 4,
attachToCharacterId: characterId,
})
const results = await Promise.all(jobIds.map(waitForJob))
const firstDone = jobIds[results.findIndex((r) => r.status === "completed")]
// 3. Approve one candidate as the portrait
const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, firstDone)
// 4. Add a smile
await client.characters.generateAsset({
name: "Kira",
assetType: "expressions",
variant: "smile",
attachToCharacterId: characterId,
attachToColumn: "expressions",
attachName: "smile",
})
// 5. Animate the portrait
await client.characters.generateMotion({
name: "Kira",
motionPrompt: "Slow head turn to the left, soft smile",
provider: "kling",
attachToCharacterId: characterId,
attachName: "head turn",
})ジョブが完了したら、get() でキャラクターをもう一度読み取ります。ポートレート、smile の表情、モーションクリップが揃っています。生成先にはキャラクターアセット(Character Asset)ノードを接続するか、プロンプトで @ を使ってメンションします。キャラクターの一貫性を参照してください。
client.characters
list(params?)
自分のキャラクターを、新しい順に一覧表示します。デフォルトでは、有効なキャラクターだけを返します。
list(params?: { projectId?: string; archived?: boolean; limit?: number; cursor?: string }): Promise<{
characters: Character[]
nextCursor: string | null
}>Prop
Type
1 回の呼び出しで返るのは最大 limit 件のキャラクターまでなので、1 回の呼び出しで、すべてのキャラクターが返るとは限りません。nextCursor が null になるまでページを取得してください。
import type { Character } from "@nodaro/sdk"
const all: Character[] = []
let cursor: string | undefined
do {
const page = await client.characters.list({ projectId, cursor })
all.push(...page.characters)
cursor = page.nextCursor ?? undefined
} while (cursor)nextCursor は、そのままの形で渡してください。保存したり解析したりしないでください。不正な形式のカーソルは、最初のページからやり直すのではなく、validation_error で失敗します。
get(id)
1 人のキャラクターを、スタジオが再読み込み後に使う 3 つの追加の一覧とともに読み取ります。pendingJobs(まだ生成中のバリエーション)、portraitCandidates(現在のポートレート生成の候補と、その進行状況)、previousCandidates(それより前の候補)です。
get(id: string): Promise<CharacterDetail>Prop
Type
const character = await client.characters.get(characterId)
console.log(character.sourceImageUrl, character.expressions, character.motions)アーカイブされたキャラクターも、ID を指定すれば引き続き返されるため、それを参照するワークフローのノードは読み込み続けられます。Character は、sourceImageUrl にポートレートを持ち、6 つのアセットコレクション(expressions、poses、motions、angles、bodyAngles、lightingVariations。いずれも { name, url } のリストです)、boards、voice、personality、canonicalDescription、identityLock を持ちます。
upsert(input)、create(input)、update(id, input)
upsert() は、input.id がない場合はキャラクターを作成し、id が設定されている場合は更新します。create() と update() は、id を自動的に設定するショートカットです。更新では、送信したフィールドだけが書き込まれます。name を含む、それ以外のフィールドはそのまま残ります。
upsert(input: UpsertCharacterInput): Promise<{ id: string; name?: string }>
create(input: Omit<UpsertCharacterInput, "id"> & { name: string }): Promise<{ id: string; name?: string }>
update(id: string, input: Omit<UpsertCharacterInput, "id">): Promise<{ id: string; name?: string }>Prop
Type
const { id } = await client.characters.create({
nodeId: "scripted",
name: "Kira",
description: "Young protagonist with auburn hair",
style: "realistic",
identityLock: "strict",
})
await client.characters.update(id, { baseOutfit: "Green raincoat and boots" })すでに使われている名前は、409 name_taken で失敗します。
delete(id)
キャラクターをアーカイブします。list() には表示されなくなりますが、get(id) では引き続き読み込めます。元に戻すには restore() を使います。
delete(id: string): Promise<{ success: true; archived: true }>Prop
Type
await client.characters.delete(characterId)restore(id)
アーカイブされたキャラクターを元に戻します。その名前がすでにほかの有効なキャラクターに使われている場合、サーバーは名前に (restored) を追加し、実際に使った名前を返します。
restore(id: string): Promise<{ id: string; name: string }>Prop
Type
const { name } = await client.characters.restore(characterId)duplicate(id, input?)
キャラクターを、名前が (copy) で終わる新しいキャラクターとしてコピーします。コピーは、新しいものを生成するまで、元のアセットの URL を共有します。
duplicate(id: string, input?: { nodeId?: string; projectId?: string }): Promise<{ id: string; name: string }>Prop
Type
const { id: copyId, name } = await client.characters.duplicate(characterId)usage(id)
キャラクターを使っているワークフローの数と、その内訳を返します。エディターは、アーカイブする前にこれを表示します。
usage(id: string): Promise<{ workflowCount: number; workflows: Array<{ id: string; name: string }> }>Prop
Type
const { workflowCount } = await client.characters.usage(characterId)generate(input)
ポートレート候補を生成します(POST /v1/generate-character)。count が 1 を超える場合、どのジョブを開始するよりも前に、すべてのジョブの分が確保されるため、途中で失敗すると、バッチ全体がロールバックされます。
generate(input: GenerateCharacterInput): Promise<{ jobId: string; jobIds: string[] }>Prop
Type
const { jobIds } = await client.characters.generate({
name: "Kira",
seedPrompt: "Kira portrait, warm natural lighting",
count: 4,
attachToCharacterId: characterId,
provider: "gpt-image",
quality: "high", // priced as gpt-image:high
})attachToCharacterId を指定し、候補が 1 つの場合、ジョブが完了すると、その結果がそのままポートレートになります。候補が複数の場合は、approvePortrait() で 1 つを選びます。quality と resolution は、画像生成(Generate Image)とまったく同じ基準で価格が決まるため、4K や高品質での実行では、より多くのクレジットが確保されます。モデルが対応していない値は、拒否されるのではなく無視されます。
generateAsset(input)
キャラクターのポートレートから、表情、ポーズ、ライティング、アングルのいずれか 1 つのバリエーションを生成します。attachToCharacterId、attachToColumn、attachName を指定すると、ジョブが完了したときに、結果がキャラクターの該当するコレクションに追加されます。
generateAsset(input: GenerateAssetInput): Promise<{ jobId: string }>Prop
Type
await client.characters.generateAsset({
name: "Kira",
assetType: "expressions",
variant: "smile",
attachToCharacterId: characterId,
attachToColumn: "expressions",
attachName: "smile",
})generateMotion(input)
動画生成(Generate Video)を画像から動画のモードで使い、キャラクターのポートレートをモーションクリップにアニメーション化します。attachToCharacterId を指定すると、クリップはキャラクターの motions に追加されます。sourceImageUrl を指定しない場合は、キャラクターのポートレートが使われます。
generateMotion(input: GenerateMotionInput): Promise<{ jobId: string }>Prop
Type
await client.characters.generateMotion({
name: "Kira",
motionPrompt: "Slow head turn to the left, soft smile",
provider: "kling",
attachToCharacterId: characterId,
attachName: "head turn",
})approvePortrait(id, candidateJobId)
generate() で完了した候補を、キャラクターのポートレートにします。その後、ビジョンモデルがキャラクターの説明を書き、このメソッドは両方を返します。
approvePortrait(id: string, candidateJobId: string): Promise<{ portraitUrl: string; canonicalDescription: string | null }>Prop
Type
const { portraitUrl, canonicalDescription } = await client.characters.approvePortrait(characterId, jobIds[0])説明を書けなかった場合、canonicalDescription は null になります。それでもポートレートは設定されます。もう一度試すには、recaption() を呼び出してください。
recaption(id)
現在のポートレートから、キャラクターの説明を書き直します。
recaption(id: string): Promise<{ canonicalDescription: string }>Prop
Type
const { canonicalDescription } = await client.characters.recaption(characterId)キャラクターにポートレートがない場合は 400 no_portrait で、ビジョンモデルが失敗した場合は 502 で失敗します。
よくある質問
関連ページ
キャラクタースタジオ
キャラクターの一貫性
キャラクターアセット
キャラクター
ロケーション
最終更新