Мы делаем агрегатор нейросетей Aigena, поэтому новые видеомодели подключаем в первые дни после выхода. Kling 4.0 от Kuaishou открыли через API в конце октября 2026 года, и на ней мы поймали неприятную ошибку: при оживлении фото модель без спроса рендерила 1080 p и брала деньги за 1080 p, хотя пользователь выбирал 720 p. Ниже разбор: как мы это нашли, что поменяли в коде и как теперь проверяем сгенерированные ролики, не открывая их в плеере.

Ошибки валидации вместо документации

Описание модели у провайдера (мы ходим через Runware) появилось раньше, чем точная спецификация параметров. Самый дешёвый способ узнать правду: отправить заведомо неверный запрос. Ошибку валидации API возвращает сразу, генерация не запускается, и денег за неё не берут.

const base = {
  taskType: "videoInference",
  taskUUID: crypto.randomUUID(),
  model: "klingai:kling-video@4.0",
  positivePrompt: "A red fox walks through fresh snow",
  deliveryMethod: "async",
  outputFormat: "MP4",
};

for (const extra of [
  { duration: 3, width: 1000, height: 1000 },
  { duration: 31, width: 1280, height: 720 },
  { duration: 3, width: 1280, height: 720, providerSettings: { klingai: { sound: true } } },
  { duration: 3, resolution: "720p" },
]) {
  const res = await runware([{ ...base, ...extra }]);
  console.log(JSON.stringify(extra), "=>", res.errors?.[0]?.message);
}

Что ответил API:

Запрос

Ответ

1000×1000

Supported values are: '1280x720', '720x1280', '940x940', '1920x1080', '1080x1920', '1440x1440', '1460x624', '2200x940'

duration 31

Duration must be an integer between 3 and 30

providerSettings.klingai.sound

Unsupported use of 'providerSettings' parameter

resolution: "720p"

Unsupported use of 'resolution' parameter

По четырём ответам стало понятно почти всё:

  • качества два, 720 p и 1080 p, в четырёх пропорциях: 16:9, 9:16, 1:1 и 21:9;

  • длительность от 3 до 30 секунд с шагом в секунду;

  • выключателя звука нет: у Kling 3.0 звук включался через providerSettings.klingai.sound, у 4.0 он генерируется всегда, а сам параметр запрещён;

  • параметра resolution нет вообще, разрешение задаётся только через width и height.

Последний пункт и оказался ловушкой.

Как модель выбирала 1080 p сама

У Kling 3.0 качество задаётся отдельными моделями: @3-standard, @3-pro, @3-4k. Когда пользователь оживляет фото, мы передаём кадр в inputs.frameImages, а размеры не передаём: пропорции берутся из снимка, и модель не обрезает его под чужой формат. Эта логика жила в общем коде для всех видеомоделей.

У Kling 4.0 одна модель на все качества. Мы запустили три пробы по 3 секунды:

Проба

Что передали

Стоимость

Что получили

Текст → видео

1280×720

$0.4004

1280×720

Фото → видео

кадр, без размеров

$0.5993

1440×1440

Фото → видео

кадр + 1280×720

$0.4004

1280×720

Без размеров модель решила, что квадратное фото надо рендерить в максимальном качестве, и выставила счёт за 1080 p. Пользователю при этом показана и списана цена 720 p. Каждый такой ролик стоил бы нам в полтора раза больше, чем мы за него берём, и узнали бы мы об этом только из счёта в конце месяца.

Заодно выяснилось, что реальная цена ниже прайса: $0.1335 за секунду 720 p против $0.157 на странице модели, то есть на 15%. Поэтому себестоимость мы всегда сверяем по полю cost в ответе API (его возвращает includeCost: true), а не по прайсу.

Что поменяли в коде

Параметры моделей у нас лежат в базе, а не в коде: какие качества доступны, таблица размеров, как включать звук. Общая функция собирает задачу для провайдера по этим параметрам. Чинить логику для всех моделей было нельзя: у Seedance, Wan и Kling 3.0 размеры вместе с кадром действительно ломают пропорции. Поэтому появился флаг модели:

// Kling 4.0: одна модель на все качества и параметра resolution не знает:
// без width/height по кадру она сама берёт 1080p. Флаг dimensions_with_frames
// отправляет размеры из таблицы и вместе с кадрами.
const framesTakeDimensions = runware.dimensions_with_frames === true;
if (ownDimensions && (!inputs.frameImages || framesTakeDimensions) && !inputs.video) {
  task.width = ownDimensions.width;
  task.height = ownDimensions.height;
}

А в параметрах модели лежит таблица размеров по качеству и пропорциям:

{
  "model": "klingai:kling-video@4.0",
  "dimensions": {
    "std": { "16:9": [1280, 720], "9:16": [720, 1280], "1:1": [940, 940] },
    "pro": { "16:9": [1920, 1080], "9:16": [1080, 1920], "1:1": [1440, 1440] }
  },
  "dimensions_with_frames": true
}

Обратите внимание на 940×940: квадрат в 720 p у этой модели не 960×960, как у большинства, а 940×940. С 960 запрос просто не пройдёт.

Перед включением модели для пользователей мы собрали задачи для всех сочетаний (текст и фото, 720 p и 1080 p, три пропорции) тем же кодом, что работает на сервере, и сравнили с запросами, которые прошли в пробах. Модель включили только после деплоя: если бы строка в базе появилась раньше кода, первые пользователи получили бы старое поведение.

Проверяем ролики, не открывая плеер

Для статьи с примерами мы сгенерировали четыре ролика по 5 и 10 секунд. Смотреть каждый вручную долго, а главное, глазами плохо проверяется то, что нам было важно: совпадает ли речь, встали ли склейки там, где просили, и попадает ли звук в действие. Всё это проверяется из консоли.

Размер, длительность, звук

ffprobe -v error -show_entries stream=codec_type,width,height,channels,duration -of csv=p=0 v.mp4
audio,2,10.030998
video,720,1280,10.041667

Два канала, то есть стерео, как и обещано. Размер совпал с запрошенным.

Склейки по таймкодам

В Kling 4.0 промпт можно разбить на кадры с таймкодами, и модель сама склеивает сцены. Мы попросили рекламный ролик кроссовка из трёх сцен: «Кадр 1, 0–3 с … Кадр 2, 3–6 с … Кадр 3, 6–10 с». Чтобы проверить, где встали склейки, достаточно одного кадра в секунду, собранного в полосу:

ffmpeg -v error -i v.mp4 -vf "fps=1,scale=144:256,tile=10x1" -frames:v 1 strip.png

На полосе из десяти кадров смена сцены видна между вторым и третьим и между пятым и шестым кадрами, то есть ровно на 3-й и 6-й секунде.

Совпадает ли звук с действием

Один ролик классический: кот сталкивает кружку со стола. Хотелось проверить, что звон звучит в момент падения, а не просто где‑то в ролике. Пики громкости по полусекундам:

ffmpeg -i v.mp4 -af "asetnsamples=24000,astats=metadata=1:reset=1,ametadata=print:key=lavfi.astats.Overall.Peak_level" -f null - 2>&1 | grep -o "Peak_level=[-0-9.]*"
Peak_level=-36.53 Peak_level=-34.27 Peak_level=-30.87 Peak_level=-24.17
Peak_level=-9.20  Peak_level=-14.72 Peak_level=0.81  Peak_level=0.71 ...

asetnsamples=24000 при 48 кГц даёт окно в полсекунды. До трёх секунд почти тишина, на 3–4 секунде пик выше нуля. На раскадровке кружка уходит за край стола как раз там.

Говорит ли модель по‑русски

В официальном гайде Kling 4.0 перечислены английский, китайский, японский, корейский, испанский, португальский, немецкий, французский и хинди. Русского нет. Мы попросили персонажа произнести фразу, указав в промпте «говорит по‑русски» и взяв реплику в кавычки, и прогнали звук через распознавание:

ffmpeg -v error -i v.mp4 -vn -ac 1 -ar 16000 -b:a 48k speech.mp3

curl -s https://api.openai.com/v1/audio/transcriptions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -F file=@speech.mp3 -F model=whisper-1 -F response_format=verbose_json
russian | Я думала, это займёт весь вечер, а получилось за 5 минут.

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

Сколько стоили тесты

Что

Параметры

Стоимость

Три пробы параметров

3 с, 720 p и 1080 p

$1.40

Четыре ролика для примеров

720×1280, 10 + 10 + 5 + 5 с

$4.00

Исходные картинки

GPT Image 2.5, 5 штук

$0.07

Все четыре ролика получились с первой попытки: речь, склейки и физика совпали с промптом.

Что мы теперь делаем с каждой новой видеомоделью

  1. Отправляем заведомо неверные параметры и собираем допустимые значения из ошибок валидации. Это бесплатно.

  2. Делаем короткие пробы на минимальной длительности во всех режимах: текст, фото, фото с последним кадром.

  3. Сравниваем поле cost в ответе с ценой, которую видит пользователь, и размер готового файла с запрошенным.

  4. Прогоняем готовые ролики через ffprobe, раскадровку и распознавание речи.

  5. Включаем модель в каталоге только после деплоя кода, который её поддерживает.

Самое полезное здесь третий пункт. Ошибку с 1080 p не видно ни в интерфейсе, ни в логах: генерация проходит успешно, пользователь доволен качеством. Видно её только в сравнении стоимости одной пробы с другой.