# Objetos e criaturas

> Crie objetos e criaturas, gere imagens principais e variações, anime-os e faça uma criatura falar em TypeScript com o SDK do Nodaro.

Source: https://nodaro.ai/pt-BR/docs/developers/sdk/objects-and-creatures

**`client.objects`** faz por código tudo o que o Estúdio de objetos/adereços faz com adereços, produtos e veículos, e **`client.creatures`** faz o mesmo com animais e criaturas. Os dois criam e editam itens, geram candidatos a imagem principal, aprovam um deles, adicionam variações e clipes de movimento e escrevem uma descrição que mantém o item consistente nos prompts seguintes. Os métodos chamam as APIs REST de [Objetos](https://nodaro.ai/docs/developers/api/objects) e de [Criaturas](https://nodaro.ai/docs/developers/api/creatures). Veja [Objetos e adereços](https://nodaro.ai/docs/guides/objects) e [Animais e criaturas](https://nodaro.ai/docs/guides/creatures) para a visão do editor.

## Métodos
Os dois recursos têm os mesmos métodos.

| Método | O que faz |
| --- | --- |
| [`objects.list(params?)`](#objectslistparams), [`creatures.list(params?)`](#creatureslistparams) | Lista os seus itens |
| [`objects.listArchived(params?)`](#objectslistarchivedparams) | Lista os seus itens arquivados |
| [`objects.get(id)`](#objectsgetid) | Lê um item, com os jobs em andamento |
| [`objects.create(input)`](#objectscreateinput), [`creatures.create(input)`](#creaturescreateinput) | Cria um item |
| [`objects.update(id, input)`](#objectsupdateid-input), [`creatures.update(id, input)`](#creaturesupdateid-input) | Altera um item |
| [`objects.delete(id)` e `restore(id)`](#objectsdeleteid-and-restoreid) | Arquiva um item ou o traz de volta |
| [`objects.permanentDelete(id)`](#objectspermanentdeleteid) | Destrói um item arquivado e os arquivos dele |
| [`objects.generate(input)`](#objectsgenerateinput), [`creatures.generate(input)`](#creaturesgenerateinput) | Gera candidatos a imagem principal |
| [`objects.generateAsset(input)`](#objectsgenerateassetinput), [`creatures.generateAsset(input)`](#creaturesgenerateassetinput) | Gera uma variação |
| [`objects.generateMotion(input)`](#objectsgeneratemotioninput), [`creatures.generateMotion(input)`](#creaturesgeneratemotioninput) | Anima a imagem principal em um clipe |
| [`objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?)`](#objectsapprovemainimageid-candidatejobid-expectedupdatedat) | Define um candidato como a imagem principal |
| [`objects.recaption(id)`](#objectsrecaptionid) | Escreve a descrição de novo |

## client.objects
Um objeto tem a imagem principal em `sourceImageUrl`, quatro coleções (`angles`, `materials`, `variations` e `motionClips`, cada uma uma lista de `{ name, url }`), `boards`, `referencePhotos`, `canonicalDescription` e `styleLock`. `category` é `furniture`, `vehicle`, `weapon`, `food`, `clothing`, `electronics`, `nature`, `tool`, `animal` ou `other`.

`Object` tem o mesmo nome do objeto global do JavaScript. Quando você precisar dos dois, importe-o com outro nome: `import type { Object as NodaroObject } from "@nodaro/sdk"`.

### objects.list(params?)
Lista os seus objetos. Por padrão, retorna só os objetos ativos. A paginação é opcional: sem `limit`, você recebe a lista inteira; com `limit`, de no máximo 500, você recebe uma página e um `nextCursor`.

```ts
list(params?: { archived?: boolean; projectId?: string; limit?: number; cursor?: string }): Promise<{
objects: Object[]
nextCursor?: string | null
}>
```

<TypeTable
type={{
archived: { type: 'boolean', default: 'false', description: "true lista os objetos arquivados em vez dos ativos." },
projectId: { type: 'string', description: "Só os objetos deste projeto." },
limit: { type: 'number', description: "O tamanho da página, no máximo 500. Omita-o para receber a lista inteira." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const { objects } = await client.objects.list()
const page = await client.objects.list({ limit: 100 })
```

### objects.listArchived(params?)
Lista os seus objetos arquivados, como um atalho para `list({ archived: true })`. `creatures.listArchived()` funciona do mesmo jeito.

```ts
listArchived(params?: { projectId?: string; limit?: number; cursor?: string }): Promise<{ objects: Object[]; nextCursor?: string | null }>
```

<TypeTable
type={{
projectId: { type: 'string', description: "Só os objetos deste projeto." },
limit: { type: 'number', description: "O tamanho da página, no máximo 500." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const { objects: archived } = await client.objects.listArchived()
```

### objects.get(id)
Lê um objeto, com `pendingJobs`, as variações que ainda estão sendo geradas. Um objeto arquivado **não** é retornado: a chamada lança `NotFoundError`. `creatures.get()` funciona do mesmo jeito.

```ts
get(id: string): Promise<ObjectDetail>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do objeto." },
}}
/>

```ts
const object = await client.objects.get(objectId)
console.log(object.sourceImageUrl, object.materials)
```

### objects.create(input)
Cria um objeto. `name` e `nodeId` são obrigatórios. Um script sem nó no canvas pode passar `"mcp-managed"` como `nodeId`.

```ts
create(input: CreateObjectInput): Promise<{ id: string }>
```

<TypeTable
type={{
nodeId: { type: 'string', required: true, description: "O nó do canvas ao qual o objeto pertence, ou mcp-managed." },
name: { type: 'string', required: true, description: "O nome." },
description: { type: 'string', description: "Uma descrição em texto livre." },
category: { type: 'ObjectCategory', description: "furniture, vehicle, weapon, food, clothing, electronics, nature, tool, animal ou other." },
style: { type: 'string', description: "O estilo visual, como realistic." },
projectId: { type: 'string', description: "O projeto em que o objeto fica guardado." },
workflowId: { type: 'string', description: "O workflow de onde o objeto vem." },
sourceImageUrl: { type: 'string', description: "A URL da imagem principal." },
imageProvider: { type: 'string | null', description: "O modelo de imagem da imagem principal." },
referencePhotos: { type: 'ObjectReferencePhoto[]', description: "Fotos de referência, cada uma { url, kind }. kind é front, side, detail, context, moodBoard ou other." },
canonicalDescription: { type: 'string', description: "A descrição usada nos prompts." },
styleLock: { type: 'boolean', description: "Mantém as variações no estilo aprovado." },
}}
/>

```ts
const { id: objectId } = await client.objects.create({
nodeId: "mcp-managed",
name: "Antique Lantern",
description: "Weathered brass lantern with hand-engraved filigree",
category: "tool",
style: "realistic",
})
```

### objects.update(id, input)
Altera um objeto. Só os campos que você envia são gravados. As coleções de variações não fazem parte desta chamada, porque os jobs de geração adicionam itens a elas enquanto você trabalha.

```ts
update(id: string, input: UpdateObjectInput): Promise<{ id: string; updatedAt: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do objeto." },
name: { type: 'string', description: "O nome." },
description: { type: 'string', description: "A descrição." },
category: { type: 'ObjectCategory', description: "A categoria." },
style: { type: 'string', description: "O estilo visual." },
sourceImageUrl: { type: 'string', description: "A URL da imagem principal." },
imageProvider: { type: 'string | null', description: "O modelo de imagem da imagem principal." },
referencePhotos: { type: 'ObjectReferencePhoto[]', description: "Fotos de referência." },
canonicalDescription: { type: 'string', description: "A descrição usada nos prompts." },
styleLock: { type: 'boolean', description: "Mantém as variações no estilo aprovado." },
boards: { type: 'Array<{ name, url, type?, sourceImages? }>', description: "Painéis de referência." },
selectedAssetByVariant: { type: 'Record<string, string>', description: "O take escolhido de cada variação." },
expectedUpdatedAt: { type: 'string', description: "O valor de updatedAt que você leu. Se o objeto mudou desde então, a atualização falha com 409 concurrent_modification." },
}}
/>

```ts
await client.objects.update(objectId, {
canonicalDescription: "A weathered brass lantern with engraved filigree and a glass chimney",
expectedUpdatedAt: object.updatedAt,
})
```

O `409 concurrent_modification` chega como um `NodaroError` comum. Leia o objeto de novo, mescle as alterações e tente outra vez.

### objects.delete(id) e restore(id)
`delete()` arquiva um objeto. Repetir a chamada em um objeto arquivado não muda nada. `restore()` traz o objeto de volta; quando o nome agora coincide com o de um objeto ativo, sem diferenciar maiúsculas de minúsculas, o servidor acrescenta `(restored)` e retorna o nome que usou.

```ts
delete(id: string): Promise<{ success: true; archived: true }>
restore(id: string): Promise<{ id: string; name: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do objeto." },
}}
/>

```ts
await client.objects.delete(objectId)
const { name } = await client.objects.restore(objectId)
```

### objects.permanentDelete(id)
Destrói um objeto arquivado e todos os arquivos armazenados que ele referencia. Só funciona em objetos arquivados: um objeto ativo falha com `400 not_archived`. Arquive primeiro com `delete()`. `creatures.permanentDelete()` funciona do mesmo jeito.

```ts
permanentDelete(id: string): Promise<{ success: true; permanent: true }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID de um objeto arquivado." },
}}
/>

```ts
await client.objects.delete(objectId)
await client.objects.permanentDelete(objectId)
```

As ferramentas MCP do Nodaro não oferecem esta operação, então um assistente de IA não consegue destruir os seus objetos.

### objects.generate(input)
Gera candidatos a imagem principal (`POST /v1/generate-object`). Com `count` acima de 1, todos os jobs são reservados antes de qualquer um começar, então uma falha no meio do caminho desfaz o lote inteiro.

```ts
generate(input: GenerateObjectInput): Promise<{ jobIds: string[]; jobId?: string }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "O nome do objeto." },
description: { type: 'string', description: "A aparência do objeto." },
userPrompt: { type: 'string', description: "Instruções extras." },
category: { type: 'ObjectCategory', description: "A categoria." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "O estilo visual." },
sourceImageUrl: { type: 'string', description: "Uma foto em que basear a imagem." },
provider: { type: 'string', description: "O ID do modelo de imagem." },
count: { type: 'number', description: "Quantos candidatos gerar." },
seedPromptHint: { type: 'string', description: "Uma escolha de seletor a adicionar ao prompt, como antique brass do seletor Material." },
attachToObjectId: { type: 'string', description: "O objeto no qual gravar um único resultado." },
attachName: { type: 'string', description: "Um nome para o resultado anexado." },
expectedUpdatedAt: { type: 'string', description: "O valor de updatedAt que você leu, para não sobrescrever uma alteração mais recente." },
}}
/>

```ts
const { jobIds } = await client.objects.generate({ name: "Antique Lantern", count: 4 })
for (const jobId of jobIds) {
// poll each candidate with client.jobs.getStatus(jobId)
}
```

`jobIds` sempre está presente, com um ID por candidato. `jobId` é um alias antigo para um único candidato; use `jobIds`. Com `attachToObjectId` e um único candidato, o resultado vira a imagem principal quando o job é concluído. Caso contrário, escolha um candidato com `approveMainImage()`.

### objects.generateAsset(input)
Gera uma variação (`POST /v1/generate-object-asset`). Com `attachToObjectId`, `attachToColumn` e `attachName`, o resultado é adicionado a essa coleção quando o job é concluído.

```ts
generateAsset(input: GenerateObjectAssetInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
assetType: { type: '"angles" | "materials" | "variations" | "motion" | "custom"', required: true, description: "O tipo de variação." },
variant: { type: 'string', required: true, description: "A variação, como gold ou three-quarter." },
name: { type: 'string', required: true, description: "O nome do objeto." },
description: { type: 'string', description: "O prompt da variação. Se ele for omitido e o resultado for anexado, um modelo de linguagem escreve um prompt a partir da descrição do objeto e do nome da variação." },
userPrompt: { type: 'string', description: "Instruções extras." },
sourceImageUrl: { type: 'string', description: "A imagem de partida, normalmente a imagem principal." },
provider: { type: 'string', description: "O ID do modelo de imagem." },
seedPromptHint: { type: 'string', description: "Uma escolha de seletor a adicionar ao prompt." },
attachToObjectId: { type: 'string', description: "O objeto ao qual adicionar o resultado." },
attachToColumn: { type: 'string', description: "A coleção: angles, materials, variations, motion_clips, sheets ou detail_closeups. Obrigatório para custom." },
attachName: { type: 'string', description: "O nome da nova entrada." },
}}
/>

```ts
const { jobId } = await client.objects.generateAsset({
name: "Antique Lantern",
assetType: "materials",
variant: "gold",
attachToObjectId: objectId,
attachToColumn: "materials",
attachName: "gold",
})
```

Para `angles`, `materials`, `variations` e `motion`, a coleção é definida pelo tipo de variação. Uma variação `custom` precisa de `attachToColumn`.

### objects.generateMotion(input)
Anima a imagem do objeto em um clipe (`POST /v1/generate-object-motion`), com o nó [**Gerar vídeo** (Generate Video)](https://nodaro.ai/docs/nodes/video/generate-video) no modo de imagem para vídeo. O clipe sempre vai para `motionClips`. Os padrões são pensados para fotos de produto: o modelo é `kling-turbo` e o quadro é `1:1`.

```ts
generateMotion(input: GenerateObjectMotionInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
motionPrompt: { type: 'string', required: true, description: "Como o objeto se move, ou como a câmera se move ao redor dele." },
sourceImageUrl: { type: 'string', required: true, description: "A imagem a animar. Não há imagem alternativa, então passe a imagem principal." },
name: { type: 'string', required: true, description: "O nome do objeto." },
provider: { type: 'string', default: '"kling-turbo"', description: "O ID do modelo de vídeo." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16" | "4:3"', default: '"1:1"', description: "O formato do clipe. Os objetos acrescentam 4:3 para fotos de catálogo." },
duration: { type: 'number', description: "A duração do clipe em segundos." },
refineFromVideoUrl: { type: 'string', description: "Um clipe existente a refazer com o novo prompt, no modo de vídeo para vídeo, em vez de partir da imagem." },
category: { type: 'string', description: "A categoria." },
style: { type: '"realistic" | "anime" | "3d-pixar" | "illustration"', description: "O estilo visual." },
canonicalDescription: { type: 'string', description: "A descrição do objeto." },
seedPromptHint: { type: 'string', description: "Uma escolha de seletor a adicionar ao prompt." },
attachToObjectId: { type: 'string', description: "O objeto ao qual adicionar o clipe." },
attachName: { type: 'string', description: "O nome do novo clipe." },
}}
/>

```ts
const { jobId } = await client.objects.generateMotion({
name: "Antique Lantern",
motionPrompt: "Slow 360-degree rotation, soft golden rim light",
sourceImageUrl: object.sourceImageUrl!,
attachToObjectId: objectId,
attachName: "rotate-360",
})
```

### objects.approveMainImage(id, candidateJobId, expectedUpdatedAt?)
Define um candidato concluído de `generate()` como a imagem principal do objeto. Em seguida, um modelo de visão escreve a descrição do objeto, e o método retorna as duas.

```ts
approveMainImage(id: string, candidateJobId: string, expectedUpdatedAt?: string): Promise<{
sourceImageUrl: string
canonicalDescription: string | null
}>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do objeto." },
candidateJobId: { type: 'string', required: true, description: "O ID do job do candidato concluído." },
expectedUpdatedAt: { type: 'string', description: "O valor de updatedAt que você leu. Se o objeto mudou desde então, a chamada falha com 409 concurrent_modification." },
}}
/>

```ts
const { sourceImageUrl, canonicalDescription } = await client.objects.approveMainImage(objectId, jobIds[0])
```

`canonicalDescription` é `null` quando não foi possível escrever a descrição. A imagem principal é definida mesmo assim; chame `recaption()` para tentar de novo.

### objects.recaption(id)
Escreve de novo a descrição do objeto a partir da imagem principal atual. É seguro repetir a chamada, e ela não recebe token de concorrência.

```ts
recaption(id: string): Promise<{ canonicalDescription: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID do objeto." },
}}
/>

```ts
const { canonicalDescription } = await client.objects.recaption(objectId)
```

Falha com `400 main_image_required` quando o objeto não tem imagem principal, e com um erro 502 quando o modelo de visão falha.

## client.creatures
Uma criatura é um animal ou um ser fantástico. Ela funciona como um objeto, com quatro diferenças:

- **`species`** é um tipo em texto livre, como `dragon` ou `wolf`, e é o assunto do prompt da imagem principal. `category` também é texto livre.
- **`poses`** substitui `materials`, então as coleções são `angles`, `poses`, `variations` e `motionClips`. Os tipos de variação são `angles`, `poses`, `variations` e `custom`.
- **`boards`** guarda até 24 Painéis de criatura com nome, folhas de referência densas feitas com a predefinição **Painel de criatura** (Creature Board) do nó [**Gerar imagem** (Generate Image)](https://nodaro.ai/docs/nodes/image/generate-image). Você controla essa lista: `create()` e `update()` a substituem por inteiro.
- **`voice`** transforma a criatura em uma criatura falante. Ela tem o mesmo formato da voz de um personagem: `{ voiceId, voiceName, traits, voiceType?, previewUrl?, ttsProvider? }`. Passe `voice: null` para removê-la.

`creatures.listArchived()`, `get()`, `delete()`, `restore()`, `permanentDelete()`, `approveMainImage()` e `recaption()` recebem os mesmos argumentos e se comportam como os métodos de objeto acima.

### creatures.list(params?)
```ts
list(params?: { archived?: boolean; projectId?: string; limit?: number; cursor?: string }): Promise<{
creatures: Creature[]
nextCursor?: string | null
}>
```

<TypeTable
type={{
archived: { type: 'boolean', default: 'false', description: "true lista as criaturas arquivadas em vez das ativas." },
projectId: { type: 'string', description: "Só as criaturas deste projeto." },
limit: { type: 'number', description: "O tamanho da página, no máximo 500. Omita-o para receber a lista inteira." },
cursor: { type: 'string', description: "O nextCursor da página anterior." },
}}
/>

```ts
const { creatures } = await client.creatures.list()
```

### creatures.create(input)
```ts
create(input: CreateCreatureInput): Promise<{ id: string }>
```

<TypeTable
type={{
nodeId: { type: 'string', required: true, description: "O nó do canvas ao qual a criatura pertence, ou mcp-managed." },
name: { type: 'string', required: true, description: "O nome." },
species: { type: 'string', description: "O tipo de criatura, como dragon ou wolf." },
description: { type: 'string', description: "Uma descrição em texto livre." },
category: { type: 'string', description: "Uma categoria em texto livre." },
style: { type: 'string', description: "O estilo visual." },
projectId: { type: 'string', description: "O projeto em que a criatura fica guardada." },
sourceImageUrl: { type: 'string', description: "A URL da imagem principal." },
referencePhotos: { type: 'CreatureReferencePhoto[]', description: "Fotos de referência, cada uma { url, kind }. kind é front, side, detail, context, moodBoard ou other." },
canonicalDescription: { type: 'string', description: "A descrição usada nos prompts." },
styleLock: { type: 'boolean', description: "Mantém as variações no estilo aprovado." },
voice: { type: 'CreatureVoice | null', description: "A voz da criatura." },
}}
/>

```ts
const { id: creatureId } = await client.creatures.create({
nodeId: "mcp-managed",
name: "Biscuit",
species: "ginger cat",
style: "realistic",
})
```

### creatures.update(id, input)
Recebe os campos de `create()`, exceto `nodeId`, além de `boards`, `selectedAssetByVariant` e `expectedUpdatedAt`. Só os campos que você envia são gravados.

```ts
update(id: string, input: UpdateCreatureInput): Promise<{ id: string; updatedAt: string }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "O ID da criatura." },
boards: { type: 'Array<{ name, url, type?, sourceImages? }>', description: "Os Painéis de criatura, até 24, substituídos por inteiro." },
voice: { type: 'CreatureVoice | null', description: "A voz. null a remove." },
expectedUpdatedAt: { type: 'string', description: "O valor de updatedAt que você leu, para não sobrescrever uma alteração mais recente." },
}}
/>

```ts
await client.creatures.update(creatureId, {
voice: { voiceId: chosenVoiceId, voiceName: "Aria", traits: "smug, unhurried" },
})
```

### creatures.generate(input)
Gera candidatos a imagem principal. Recebe os campos de `objects.generate()`, além de `species`, e retorna `{ jobIds }`.

```ts
generate(input: GenerateCreatureInput): Promise<{ jobIds: string[]; jobId?: string }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "O nome da criatura." },
species: { type: 'string', description: "O tipo de criatura." },
description: { type: 'string', description: "A aparência da criatura." },
count: { type: 'number', description: "Quantos candidatos gerar." },
provider: { type: 'string', description: "O ID do modelo de imagem." },
attachToCreatureId: { type: 'string', description: "A criatura na qual gravar um único resultado." },
}}
/>

```ts
const { jobIds } = await client.creatures.generate({ name: "Biscuit", species: "ginger cat", count: 4 })
```

### creatures.generateAsset(input)
Gera uma variação. Funciona como `objects.generateAsset()`, com `attachToCreatureId`.

```ts
generateAsset(input: GenerateCreatureAssetInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
assetType: { type: '"angles" | "poses" | "variations" | "custom"', required: true, description: "O tipo de variação." },
variant: { type: 'string', required: true, description: "A variação, como walking ou sitting." },
name: { type: 'string', required: true, description: "O nome da criatura." },
attachToCreatureId: { type: 'string', description: "A criatura à qual adicionar o resultado." },
attachToColumn: { type: 'string', description: "angles, poses, variations, motion_clips, sheets ou detail_closeups. Obrigatório para custom." },
attachName: { type: 'string', description: "O nome da nova entrada." },
}}
/>

```ts
await client.creatures.generateAsset({
name: "Biscuit",
assetType: "poses",
variant: "sitting",
attachToCreatureId: creatureId,
attachToColumn: "poses",
attachName: "sitting",
})
```

### creatures.generateMotion(input)
Anima a imagem da criatura em um clipe. Funciona como `objects.generateMotion()`, com os mesmos padrões, `kling-turbo` e `1:1`, e adiciona o clipe a `motionClips`.

```ts
generateMotion(input: GenerateCreatureMotionInput): Promise<{ jobId: string }>
```

<TypeTable
type={{
motionPrompt: { type: 'string', required: true, description: "O que a criatura faz." },
sourceImageUrl: { type: 'string', required: true, description: "A imagem a animar." },
name: { type: 'string', required: true, description: "O nome da criatura." },
provider: { type: 'string', default: '"kling-turbo"', description: "O ID do modelo de vídeo." },
aspectRatio: { type: '"1:1" | "3:4" | "16:9" | "9:16" | "4:3"', default: '"1:1"', description: "O formato do clipe." },
attachToCreatureId: { type: 'string', description: "A criatura à qual adicionar o clipe." },
attachName: { type: 'string', description: "O nome do novo clipe." },
}}
/>

```ts
await client.creatures.generateMotion({
name: "Biscuit",
motionPrompt: "The cat stretches, then yawns",
sourceImageUrl: creature.sourceImageUrl!,
attachToCreatureId: creatureId,
})
```

## Fazer uma criatura falar
A fala não precisa de um método específico de criatura. Gere a fala com a voz da criatura e depois aplique a sincronização labial na imagem da criatura:

```ts
const creature = await client.creatures.get(creatureId)

// 1. Speak the line in the creature's voice
const speech = await client.nodes.runAndWait("text-to-speech", {
text: "I knocked the vase off the shelf. I regret nothing.",
voice: creature.voice!.voiceId,
provider: creature.voice!.ttsProvider,
voiceType: creature.voice!.voiceType,
})

// 2. Lip-sync the audio onto the creature's main image
const clip = await client.nodes.runAndWait("lip-sync", {
imageUrl: creature.sourceImageUrl!,
audioUrl: speech.audioUrl,
provider: "kling-avatar",
})
console.log(clip.videoUrl)
```

O nó [**Sincronização labial** (Lip Sync)](https://nodaro.ai/docs/nodes/video/lip-sync) também dubla um vídeo existente: passe `videoUrl` e um modelo que aceite vídeo. `volcengine-lipsync` é a opção de menor preço para dublagem e a única que lida com vários falantes:

```ts
const dub = await client.nodes.runAndWait("lip-sync", {
videoUrl: "https://example.com/scene.mp4",
audioUrl: "https://example.com/new-vocal.mp3",
provider: "volcengine-lipsync",
mode: "basic",          // for complex scenes
openScenedet: true,     // several speakers: scene and speaker detection
audioDurationSec: 42,   // sets the per-second price; without it, you pay for 5 minutes
})
```

Para incluir uma criatura em uma tomada como referência, monte a referência com `toConnectedReference({ kind: "creature", id, name, url, description })` de `@nodaro/shared`. O nó Gerar imagem então adiciona uma linha que mantém a anatomia, as marcas e as cores da criatura. Veja [Referências](https://nodaro.ai/docs/developers/sdk/nodes#references).

## Frequently asked questions

### Qual é a diferença entre um objeto e uma criatura no Nodaro?

Um objeto é um adereço, um produto ou um veículo, com ângulos, materiais e variações. Uma criatura é um animal ou um ser fantástico, com ângulos, poses e variações, uma espécie, painéis de referência e uma voz opcional.

### Como gero candidatos para a imagem principal de um objeto?

Chame client.objects.generate com um nome e uma quantidade. A chamada sempre retorna jobIds, um por candidato. Quando os jobs terminarem, aprove um candidato com client.objects.approveMainImage.

### Posso excluir um objeto permanentemente com o SDK?

Sim, em duas etapas. Arquive-o com delete() e depois chame permanentDelete(), que remove o objeto e todos os arquivos que ele referencia. Um objeto ativo é recusado com 400 not_archived.

### Como faço uma criatura falar?

Gere a fala com o nó Texto para fala e a voz da criatura, depois execute o nó Sincronização labial com a imagem principal da criatura e esse áudio.
