Мы делаем агрегатор нейросетей Aigena, поэтому новые видеомодели подключаем в первые дни после выхода. Kling 4.0 от Kuaishou открыли через API в конце октября 2026 года, и на ней мы поймали неприятную ошибку: при оживлении фото модель без спроса рендерила 1080p и брала деньги за 1080p, хотя пользователь выбирал 720p. Ниже разбор: как мы это нашли, что поменяли в коде и как теперь проверяем сгенерированные ролики, не открывая их в плеере.
Ошибки валидации вместо документации
Описание модели у провайдера (мы ходим через 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 |
|
|
|
|
|
|
|
По четырём ответам стало понятно почти всё:
-
качества два, 720p и 1080p, в четырёх пропорциях: 16:9, 9:16, 1:1 и 21:9;
-
длительность от 3 до 30 секунд с шагом в секунду;
-
выключателя звука нет: у Kling 3.0 звук включался через
providerSettings.klingai.sound, у 4.0 он генерируется всегда, а сам параметр запрещён; -
параметра
resolutionнет вообще, разрешение задаётся только черезwidthиheight.
Последний пункт и оказался ловушкой.
Как модель выбирала 1080p сама
У 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 |
Без размеров модель решила, что квадратное фото надо рендерить в максимальном качестве, и выставила счёт за 1080p. Пользователю при этом показана и списана цена 720p. Каждый такой ролик стоил бы нам в полтора раза больше, чем мы за него берём, и узнали бы мы об этом только из счёта в конце месяца.
Заодно выяснилось, что реальная цена ниже прайса: $0.1335 за секунду 720p против $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: квадрат в 720p у этой модели не 960×960, как у большинства, а 940×940. С 960 запрос просто не пройдёт.
Перед включением модели для пользователей мы собрали задачи для всех сочетаний (текст и фото, 720p и 1080p, три пропорции) тем же кодом, что работает на сервере, и сравнили с запросами, которые прошли в пробах. Модель включили только после деплоя: если бы строка в базе появилась раньше кода, первые пользователи получили бы старое поведение.
Проверяем ролики, не открывая плеер
Для статьи с примерами мы сгенерировали четыре ролика по 5 и 10 секунд. Смотреть каждый вручную долго, а главное, глазами плохо проверяется то, что нам было важно: совпадает ли речь, встали ли склейки там, где просили, и попадает ли звук в действие. Всё это проверяется из консоли.
Размер, длительность, звук
ffprobe -v error -show_entries stream=codec_type,width,height,channels,duration -of csv=p=0 v.mp4
audio,2,10.030998video,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.17Peak_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.mp3curl -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 с, 720p и 1080p |
$1.40 |
|
Четыре ролика для примеров |
720×1280, 10 + 10 + 5 + 5 с |
$4.00 |
|
Исходные картинки |
GPT Image 2.5, 5 штук |
$0.07 |
Все четыре ролика получились с первой попытки: речь, склейки и физика совпали с промптом.
Что мы теперь делаем с каждой новой видеомоделью
-
Отправляем заведомо неверные параметры и собираем допустимые значения из ошибок валидации. Это бесплатно.
-
Делаем короткие пробы на минимальной длительности во всех режимах: текст, фото, фото с последним кадром.
-
Сравниваем поле
costв ответе с ценой, которую видит пользователь, и размер готового файла с запрошенным. -
Прогоняем готовые ролики через ffprobe, раскадровку и распознавание речи.
-
Включаем модель в каталоге только после деплоя кода, который её поддерживает.
Самое полезное здесь третий пункт. Ошибку с 1080p не видно ни в интерфейсе, ни в логах: генерация проходит успешно, пользователь доволен качеством. Видно её только в сравнении стоимости одной пробы с другой.
ссылка на оригинал статьи https://habr.com/ru/articles/1099336/