Мы делаем агрегатор нейросетей 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 |
|
duration 31 |
|
|
|
|
|
По четырём ответам стало понятно почти всё:
качества два, 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 |
Все четыре ролика получились с первой попытки: речь, склейки и физика совпали с промптом.
Что мы теперь делаем с каждой новой видеомоделью
Отправляем заведомо неверные параметры и собираем допустимые значения из ошибок валидации. Это бесплатно.
Делаем короткие пробы на минимальной длительности во всех режимах: текст, фото, фото с последним кадром.
Сравниваем поле
costв ответе с ценой, которую видит пользователь, и размер готового файла с запрошенным.Прогоняем готовые ролики через ffprobe, раскадровку и распознавание речи.
Включаем модель в каталоге только после деплоя кода, который её поддерживает.
Самое полезное здесь третий пункт. Ошибку с 1080 p не видно ни в интерфейсе, ни в логах: генерация проходит успешно, пользователь доволен качеством. Видно её только в сравнении стоимости одной пробы с другой.

