spicyapiДокументация
Руководства
Основное содержимое

Замена человека в кадре

Один набор инструментов, две формы. Модели с одним вызовом меняют лицо или голову за один запрос; модели с двумя вызовами сначала разбирают клип, чтобы вы выбрали, кого менять.

Некоторые модели не создают новую сцену, а заменяют человека: переносят лицо с одной фотографии на другую, меняют голову целиком или подставляют другую фигуру в видеоклипе. В каталоге они собраны под одной меткой — toolkit: "subject-swap" — и встречаются в двух формах: один вызов или два вызова с выбором посередине.

Больше в них ничего особенного нет. Это обычные асинхронные задачи, параметры берутся из собственного inputSchema модели, а результат приходит в output.assets[].

toolkit показывает, что модель из этого набора

toolkit — это классифицирующая метка на элементе каталога; она есть и в ответе GET /api/v1/models с аутентификацией, и в публичном каталоге. Сегодня у неё единственное значение subject-swap, а у моделей вне наборов поле отсутствует целиком, поэтому проверяйте значение, а не наличие ключа.

Поле носит справочный характер. Оно не определяет, каким ключом можно вызвать модель, как считается стоимость запроса и что модель принимает на вход. Незнакомое значение стоит воспринимать так же, как незнакомую запись в badges, — просто пропустить.

curl -s "https://api.spicyapi.ai/api/v1/models?modality=video" \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  | jq -r '.data.items[]
      | select(.toolkit == "subject-swap" and .enabled and .available)
      | [.model, (.tasks | join(",")), .family] | @tsv'

Каждая строка — один вызываемый эндпоинт. Всё остальное на этой странице определяет колонка tasks: family с эндпоинтом video-analyze — это family на два вызова, и парный к нему video-edit сам по себе не вызывается.

Какая форма передо мной

Один вызовДва вызова
ЭндпоинтыОдин эндпоинт редактированияvideo-analyze, затем video-edit в том же family
Кого заменяютТого, кого найдёт модель. В запросе нет поля, которым можно указать человекаВыбранные вами цели — по их позиции
Что вы отправляетеИсходное изображение или клип и ваш референс, в одном запросеВ первом вызове — клип; во втором — выбранные позиции, каждая со своими референсами
Сколько платитеОдна задачаДве задачи, стоимость считается отдельно
Когда подходитВ кадре один очевидный объектВ кадре несколько человек либо цена ошибки высока

Обе формы работают в режиме async: true: вы создаёте задачу, а результат забираете опросом или через webhook.

Один вызов

Здесь нет ничего, что отличало бы такую модель от любой другой модели редактирования. Прочитайте inputSchema эндпоинта, заполните поля, создайте задачу:

const created = await fetch('https://api.spicyapi.ai/api/v1/jobs/createTask', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SPICY_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'order-8814-swap-1',
  },
  body: JSON.stringify({
    model: swap.model, // один из элементов запроса к каталогу выше
    input: {
      image_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
      reference_image_url: 'spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m',
    },
  }),
});

Имена полей у эндпоинтов разные: одни принимают prompt, seed или формат вывода, другие — только две картинки. Список задаёт схема; не переносите имена полей с одного эндпоинта на другой. Референсом может быть публичный HTTPS-адрес, небольшое изображение в Base64 прямо в запросе или загруженный spicy:// URI — см. Медиа.

Два вызова

Первый вызов разбирает клип и сообщает, что он там нашёл, — упорядоченным набором кадров предпросмотра. Что из этого заменять, решаете вы. Саму работу делает второй вызов.

ваш клип ──▶ video-analyze ──▶ output.assets[0], [1], [2] …   (по кадру на каждую цель)
                                          │
                     вы выбираете позиции │ 0 и 2
                                          ▼
          video-edit (analysis_task_id + swap_targets) ──▶ готовый клип

Почему не одним вызовом? Потому что в сцене с четырьмя людьми один вызов вынужден был бы угадывать, кого вы имели в виду, а неверная догадка — это готовое оплаченное видео, в котором заменили не того человека. Разделение на два шага делает выбор явным и проверяемым.

Разберите клип

Отправьте клип на эндпоинт video-analyze. Если у эндпоинта есть поле resolution, задайте его явно: второй вызов должен выполняться с тем же значением, поэтому выбирайте здесь то разрешение, которое нужно готовому клипу. На этих эндпоинтах стоимость разбора от него не зависит.

const analysis = await createTask({
  model: analyze.model, // элемент, у которого в tasks есть "video-analyze"
  input: {
    video_url: 'spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3',
    resolution: '720p',
  },
});
const analysisTaskId = analysis.taskId;

Прочитайте, что он нашёл

Дождитесь задачи обычным способом. Успешный разбор возвращает по одному кадру предпросмотра на каждую отслеженную цель в output.assets[]:

{
  "state": "succeeded",
  "output": {
    "assets": [
      { "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" },
      { "url": "https://…", "expiresAt": "2026-09-20T09:32:11Z", "mime": "image/jpeg" }
    ]
  },
  "settled": true
}

Порядок этого массива и есть контракт. Индекс элемента — это object_index, который вы отправляете обратно во втором вызове: первый кадр — 0, второй — 1 и так далее. Покажите кадры тому, кто выбирает, или сопоставьте их сами — в любом случае отправляйте назад позиции именно из этого массива.

Разбор, который не нашёл ничего для отслеживания, не завершается успехом с пустым массивом: он заканчивается как failed с errorCode: "generation_failed", а резерв освобождается полностью. Поэтому у разбора со статусом succeeded всегда есть хотя бы один кадр, и вашему коду остаётся единственное — выбрать из них.

Замените выбранные цели

Отправьте идентификатор задачи разбора и по одному элементу swap_targets на каждую цель, которую нужно заменить. Цели, которые вы не перечислили, останутся нетронутыми.

const swapped = await createTask({
  model: edit.model, // элемент "video-edit" того же family
  input: {
    analysis_task_id: analysisTaskId,
    resolution: '720p', // то же значение, с которым выполнялся разбор
    swap_targets: [
      { object_index: 0, image_urls: ['spicy://f/fil_01k3m8xb2c3d4e5f6g7h8j9k0m'] },
    ],
  },
});

Результат — один клип в output.assets[], как у любой другой видеозадачи.

Что второй вызов проверяет перед приёмом

Эти проверки выполняются при создании, поэтому отклонённый запрос ничего не создаёт и ничего не резервирует.

ПравилоЗачем
Разбор должен принадлежать вашему аккаунту и быть успешнымУ задачи в очереди или у неудавшейся ссылаться не на что
Оба вызова делайте одним и тем же API-ключомРазбор читается только тем ключом, который его создал, иначе вы вообще не увидите кадры, из которых выбираете
Каждый object_index должен попадать в диапазон и встречаться один разВыход за диапазон молча заменил бы не того человека, дубликат заменил бы одного и того же дважды. И то и другое завершилось бы и было бы оплачено как обычно
Поля, влияющие на цену, например resolution, должны совпадать с разборомЕсли наследовать их, станет невидно, по какому тарифному уровню вас считают
Исходный клип должен быть тем же самымОпустите video_url — возьмётся разобранный клип; передадите — он должен совпадать полностью
Разбор пригоден 24 часа после завершенияПозже данные отслеживания за ним пропадают, а нередко и сам исходный клип

Задавайте `resolution` в обоих вызовах или внимательно читайте значение по умолчанию

Опущенные поля получают значение default из схемы ещё до сравнения двух запросов. Поэтому разбор со значением по умолчанию и замена со значением по умолчанию совпадут — а вот разбор, где вы задали значение, и замена, где поле оставлено пустым, не совпадут, и расхождение будет отклонено, а не исправлено молча. Если передавать значение явно в обоих вызовах, вопрос снимается.

Когда второй вызов отклонён

Все эти случаи — HTTP 400 с бизнес-кодом code: 400. Поле msg принимает одно из значений:

msgЧто произошлоЧто делать
The referenced analysis task was not found.В этом аккаунте такой задачи нет либо analysis_task_id вообще не идентификатор задачиВозьмите taskId, который вернул ваш собственный разбор. Задача из чужого аккаунта намеренно отдаётся так же
The referenced task belongs to a different model.Идентификатор указывает на другую задачу — часто на саму задачу замены или на разбор другого familyУкажите задачу video-analyze из того же family
The referenced analysis did not complete successfully.Разбор ещё выполняется либо завершился как failed / expiredДождитесь его или запустите заново
That analysis has no reusable result. Run the analysis step again.Разбор прошёл успешно, но не оставил ничего, что пригодится заменеЗапустите разбор по тому же клипу ещё раз
That analysis result has expired. Run the analysis step again.Прошло больше 24 часовЗапустите разбор заново
"resolution" must match the analysis step.Два вызова расходятся в поле, влияющем на ценуПередайте значение из разбора либо запустите новый разбор с нужным значением
"video_url" must match the analysis step.Вы отправили другой клипПередайте тот же URI либо опустите поле
That analysis found N target(s); "object_index" is out of range.Позиция выходит за пределы того, что вернул разборПозиции нумеруются с нуля и заканчиваются на assets.length - 1
"object_index" is listed more than once.Два элемента указывают на одну и ту же цельОставьте по одному элементу на цель, а все её референсы соберите в image_urls этого элемента

Сколько стоят два вызова

Разбор и замена — это две отдельные задачи с двумя отдельными ценами, каждая со своим резервом и своим списанием. Отклонённый второй вызов не стоит ничего; успешный разбор оплачивается, даже если вы потом откажетесь что-либо заменять.

  • Разбор обычно тарифицируется per_request: одно списание за вызов, независимо от длины клипа и количества найденных целей.
  • Замена обычно тарифицируется per_second и считается целыми блоками, а длительность берётся из клипа, а не из поля, которое задаёте вы. Поэтому сумма, названная до отправки, рассчитана на самый длинный клип, который принимает эндпоинт: названная сумма и есть зарезервированная, итоговое списание считается по округлённой длительности и никогда не превышает резерв, а разница возвращается на баланс при закрытии задачи. Резерв, превышающий итоговое списание, здесь ожидаем. Прежде чем планировать бюджет, прочитайте Оплату.

Обе цены читайте из каталога, а не предполагайте, и, если важна точная сумма, запрашивайте расчёт стоимости для каждого вызова.

Дальше

На этой странице