Как считать стоимость AI-обработки видео: оплата по секундам без обязательной подписки

Практический разбор единицы потребления, предоплаченных кредитов, возвратов при сбоях и асинхронной обработки.

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

У AI-обработки видео другая экономика. Себестоимость возникает почти при каждом запуске и обычно растёт вместе с длительностью материала. Поэтому логичная продуктовая модель — брать деньги за фактическое потребление, но реализовать её честно сложнее, чем просто умножить минуты на цену.

Нужно решить:

  • что считать единицей потребления;
  • когда именно списывать баланс;
  • как не списать дважды при повторном запросе;
  • что делать, если внешний AI-провайдер упал после списания;
  • считать ли успешной задачу, если модель закончила работу, а сохранение файла не удалось;
  • сколько хранить исходники и результаты;
  • как объяснить пользователю цену до запуска;
  • какие метрики покажут реальную маржу, а не красивую выручку.

Ниже — схема, к которой мы пришли при разработке сервиса обработки видео.

Дисклеймер: я участвую в разработке сервиса удаления фона из видео. Ссылка дана один раз как контекст реализации. Статья посвящена продуктовой и технической модели, а не обзору или рекомендации сервиса.

Почему «цена за файл» почти всегда несправедлива

Два файла с расширением MP4 могут отличаться в сотни раз:

  • ролик на 12 секунд и вебинар на 80 минут;
  • вертикальное видео 720p и исходник 4K;
  • статичная говорящая голова и сцена с быстрым движением;
  • один выходной формат и несколько вариантов экспорта;
  • файл с готовым звуком и материал, требующий дополнительной обработки аудио.

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

Лучшей единицей становится параметр, который сильнее всего связан с переменной себестоимостью. Для фонового удаления и многих других видеоопераций это длительность.

Упрощённую себестоимость одной задачи можно представить так:

cost = duration × ai_rate + encoding + storage + traffic + expected_failure_cost

В конкретном продукте формула может учитывать разрешение, число кадров, модель или тип результата. Важно не копировать чужую единицу тарификации, а найти собственный главный драйвер затрат.

Секунда лучше минуты, если обработка бывает короткой

Тарификация по минутам выглядит проще, но создаёт грубое округление. Пользователь с роликом на 61 секунду платит за две минуты, хотя потребил чуть больше одной.

Мы выбрали начатую секунду:

constcredits=Math.ceil(durationMs/1000);

Если видео длится 12,1 секунды, задача требует 13 кредитов. Такая схема:

  • достаточно точна для коротких роликов;
  • понятна без калькулятора;
  • совпадает с тем, как растут основные переменные расходы;
  • позволяет показать точную стоимость до запуска.

Миллисекунды нужно нормализовать в одном месте на сервере. Нельзя позволять браузеру окончательно определять цену: клиентские метаданные можно подменить, а разные браузеры иногда по-разному округляют длительность.

Браузер может показать предварительную оценку, но сервер должен повторно проверить входные данные и рассчитать окончательное списание по единому правилу.

Зачем нужны кредиты, если можно списывать деньги после каждой задачи

Оплата банковской картой за каждый 15-секундный ролик создаёт слишком много трения и комиссий. Предоплаченный баланс объединяет несколько маленьких потреблений в одну покупку.

Stripe в документации по billing credits описывает кредитную модель как один из вариантов usage-based billing: клиент заранее покупает объём, а затем расходует его на конкретную услугу. Важно, что такие кредиты — не электронный кошелёк и не деньги для переводов третьим лицам, а право потребить услугу внутри продукта. Это же различие стоит явно закрепить в условиях сервиса.

У кредитов есть четыре продуктовых преимущества:

  1. Пользователь один раз проходит оплату и запускает несколько задач без новых чек-аутов.
  2. Цена каждой задачи всё равно остаётся связанной с потреблением.
  3. Можно безопасно выдавать небольшой тестовый объём или компенсацию поддержки.
  4. Возврат при техническом сбое происходит мгновенно внутри баланса, без отдельной банковской операции.

Но есть и обязательства:

  • неиспользованные кредиты остаются обязательством продукта;
  • баланс должен быть бухгалтерским журналом, а не одним изменяемым числом;
  • условия сгорания или бессрочности должны быть понятны до покупки;
  • рекламные и купленные кредиты желательно различать;
  • поддержке нужна история всех начислений и списаний.

Баланс — это журнал операций, а не поле balance

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

Минимальная запись в журнале содержит:

  • идентификатор операции;
  • идентификатор пользователя;
  • положительную или отрицательную сумму;
  • тип: покупка, потребление, возврат, бонус или ручная корректировка;
  • ссылку на задачу или платёж;
  • идемпотентный ключ;
  • время создания;
  • пояснение для поддержки.

Текущий баланс можно вычислять как сумму журнала или поддерживать отдельно как кеш внутри транзакции. Источником истины остаются операции.

Пример ключей:

consume:task_123refund:task_123purchase:payment_456support:ticket_789

Уникальное ограничение на идемпотентный ключ защищает от двойного списания, если браузер повторил запрос, воркер перезапустился или webhook пришёл дважды.

Когда списывать: до обработки или после

Есть два рабочих подхода.

Резервирование

Система временно блокирует нужный объём, а после успеха превращает резерв в окончательное списание. При неудаче резерв снимается.

Плюсы:

  • модель легко объяснить пользователю;
  • баланс нельзя потратить параллельно на несколько задач;
  • журнал явно разделяет резерв и фактическое потребление.

Минусы:

  • появляется дополнительное состояние;
  • нужно освобождать зависшие резервы;
  • поддержка должна понимать разницу между доступным и полным балансом.

Списание с компенсацией

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

Плюсы:

  • проще реализация;
  • нет зависших резервов;
  • вся логика укладывается в обычные операции журнала.

Минусы:

  • компенсация должна быть гарантированно идемпотентной;
  • пользователь временно видит уменьшенный баланс;
  • при ошибке в workflow возврат может потребовать отдельного восстановления.

Для первого релиза мы выбрали списание с компенсацией. Ключевое условие — возврат является не изменением старой записи, а новой положительной операцией с уникальным ключом refund:taskId.

Что считать успешной задачей

Внешний AI-провайдер может закончить вычисление, но это ещё не значит, что пользователь получил результат.

Практическая цепочка выглядит так:

uploaded → queued → processing → saving → completed

Задача становится completed только после того, как результат сохранён в хранилище продукта и доступен через контролируемую ссылку скачивания.

Если провайдер вернул файл, но копирование в собственное хранилище оборвалось, для пользователя задача ещё не выполнена. Списывать деньги за «успех, который нельзя скачать» — плохой продуктовый договор.

Полезно заранее оформить таблицу решений:

  • неверный формат обнаружен до запуска AI — не списывать;
  • не хватает баланса — не запускать;
  • AI-провайдер отклонил задачу — вернуть списание;
  • AI закончил, но сохранение результата окончательно не удалось — вернуть;
  • результат сохранён и доступен — оставить списание;
  • пользователь не скачал готовый результат — не возвращать автоматически;
  • срок хранения закончился по заранее объявленному правилу — не возвращать автоматически.

Последние два пункта важны: продукт оплатил успешное вычисление и хранение результата. Возврат должен компенсировать техническую неудачу сервиса, а не отсутствие интереса после успешной обработки.

Длинную AI-задачу нельзя держать внутри HTTP-запроса

Видео может обрабатываться минуты или часы. Если браузер ждёт один длинный HTTP-ответ, возникают таймауты, повторные запросы и непонятное состояние после закрытия вкладки.

Надёжнее разделить загрузку, запуск и получение результата:

  1. Браузер получает ограниченные временные права на загрузку одного объекта.
  2. Видео загружается напрямую в объектное хранилище.
  3. Сервер создаёт задачу и запускает durable workflow.
  4. Workflow списывает кредиты.
  5. Внешний AI-провайдер получает короткоживущую ссылку на исходник.
  6. Workflow ждёт webhook или событие завершения.
  7. Результат копируется в собственное хранилище.
  8. Задача отмечается завершённой.
  9. Браузер показывает результат при следующем открытии страницы.

Cloudflare в документации Workflows описывает эту модель как durable execution: состояние сохраняется между шагами, а отдельные операции можно повторять после временного сбоя. Для больших файлов документация R2 рекомендует прямую или multipart-загрузку вместо проксирования всего видео через приложение.

Такая архитектура влияет не только на надёжность, но и на деньги. Если шаг сохранения можно безопасно повторить, временная ошибка хранилища не превращается в дорогой повторный запуск AI.

Повторять можно не каждый шаг

Автоматический retry полезен только для идемпотентных операций.

Безопасно повторять:

  • чтение задачи;
  • проверку существования объекта;
  • сохранение результата под тем же ключом;
  • запись статуса с условием на текущее состояние;
  • возврат с уникальным идемпотентным ключом.

Опасно автоматически повторять:

  • создание внешней AI-задачи, если провайдер не принимает idempotency key;
  • списание без уникального ключа;
  • отправку необратимого внешнего действия;
  • создание нескольких выходных объектов со случайными именами.

Худший сценарий выглядит так: провайдер создал задачу, но соединение оборвалось до получения ответа. Приложение повторяет запрос и оплачивает второй AI-запуск.

Если внешнее API не поддерживает идемпотентность, шаг отправки лучше не повторять автоматически. Задачу безопаснее завершить ошибкой и вернуть кредиты, чем создать неизвестное число платных вычислений.

Срок хранения — часть цены и доверия

Видео тяжёлое и часто содержит личные данные. Бессрочное хранение увеличивает расходы, риски и объём обязательств.

Нужно отдельно определить сроки для:

  • незавершённых загрузок;
  • исходных файлов;
  • готовых результатов;
  • служебных метаданных;
  • бухгалтерского журнала операций.

Исходники и результаты можно удалять через несколько дней, если этот срок показан до запуска и в истории задач. Платёжные и кредитные операции обычно приходится хранить дольше для поддержки и учёта.

Короткий срок хранения работает только при хорошем интерфейсе:

  • пользователь видит дату удаления;
  • результат можно скачать повторно до этой даты;
  • система не обещает бессрочный архив;
  • поддержка не создаёт ожидание, что удалённый файл можно восстановить.

Как показать цену до запуска

Usage-based модель вызывает доверие, когда пользователь понимает расчёт заранее.

До кнопки запуска стоит показать:

  • длительность файла;
  • требуемое количество кредитов;
  • текущий баланс;
  • остаток после запуска;
  • правило округления;
  • условия возврата при сбое;
  • срок доступности результата.

Плохой вариант — написать «от одного кредита», а окончательную сумму показать после обработки. Хороший — ещё до списания вывести: «Видео 2:14, потребуется 134 кредита».

Статус задачи тоже должен описывать реальный этап, а не бесконечное processing:

  • загрузка;
  • подготовка;
  • обработка;
  • сохранение;
  • готово;
  • ошибка и возврат.

Если задача упала, интерфейс должен явно сообщить, что кредиты возвращены. Пользователь не обязан сравнивать баланс вручную.

Как строить пакеты кредитов

Пакеты решают сразу две задачи: уменьшают количество платежей и дают продукту запас маржи на колебания себестоимости.

Но скидка за большой пакет имеет смысл только после расчёта:

gross_margin = revenue - ai_cost - payment_fee - storage_and_traffic - expected_refunds - support_cost

Ошибка — смотреть только на тариф внешнего AI API. Маленькие платежи сильнее чувствуют фиксированную часть комиссии. Длинные видео повышают вероятность сетевого сбоя. Неудачная задача может потребовать и возврата кредитов, и уже понесённых расходов у провайдера.

Для начала достаточно трёх-четырёх пакетов с понятным объёмом в минутах. Не стоит имитировать подписку скрытым сгоранием баланса, если продукт позиционируется как pay-as-you-go.

Какие метрики показывают настоящую экономику

Выручка от продажи пакетов ещё не равна заработанной выручке от потреблённой услуги. Полезно разделять покупку и использование.

Минимальный набор метрик:

  • куплено кредитов за период;
  • использовано кредитов;
  • неиспользованный баланс;
  • себестоимость успешной минуты;
  • себестоимость всех запусков, включая неудачные;
  • доля задач с возвратом;
  • причины возвратов;
  • p50 и p95 времени обработки;
  • доля пользователей, завершивших скачивание;
  • повторное использование после первой успешной задачи;
  • маржа по размеру видео, формату и модели;
  • стоимость поддержки на 100 задач.

Особенно полезно разделять ошибки по этапам. Сбой до вызова AI почти ничего не стоит. Сбой после дорогого вычисления, но до сохранения результата — одна из самых неприятных категорий для маржи.

Когда кредиты не подходят

Кредитная модель — не универсальный ответ.

Лучше выбрать подписку или контрактный тариф, если:

  • потребление стабильно каждый месяц;
  • клиенту нужен гарантированный объём и SLA;
  • продукт включает большую постоянную ценность помимо вычислений;
  • корпоративным клиентам требуется счёт по факту использования;
  • единица расхода слишком сложна или постоянно меняется;
  • маленькие остатки баланса создают больше раздражения, чем пользы.

Возможна гибридная схема: базовая подписка включает объём, превышение тарифицируется отдельно, а редкие пользователи покупают пакеты без подписки. Но начинать лучше с одной понятной модели, иначе команда не поймёт, какая экономика действительно работает.

Чек-лист перед запуском usage-based тарификации

  • Единица потребления связана с реальной себестоимостью.
  • Цена известна пользователю до запуска.
  • Сервер независимо проверяет длительность и стоимость.
  • Все операции баланса записываются в журнал.
  • Списание и возврат имеют уникальные идемпотентные ключи.
  • Определена точка, после которой задача считается успешно оказанной услугой.
  • Неидемпотентные внешние вызовы не повторяются автоматически.
  • Ошибка после списания гарантированно запускает компенсацию.
  • Интерфейс показывает возврат без ручной проверки баланса.
  • Срок хранения известен до обработки.
  • Считается себестоимость не только успешных, но и всех запусков.
  • Скидки пакетов проверены на фактической марже.
  • Поддержка видит полную историю задачи и баланса.

Вывод

Оплата по факту использования кажется простой только на лендинге. Внутри продукта это договор между измерением, биллингом, асинхронной обработкой, хранением и компенсацией ошибок.

Хорошая модель отвечает на четыре вопроса без участия поддержки:

  1. Сколько будет стоить задача?
  2. Когда спишутся кредиты?
  3. Что произойдёт при сбое?
  4. До какого момента будет доступен результат?

Если ответы видны до запуска, а журнал операций и workflow выдерживают повторы, pay-as-you-go становится не только способом монетизации, но и преимуществом продукта. Пользователь платит за понятный объём работы, а команда видит экономику каждой выполненной задачи, а не среднюю температуру по подпискам.

Материал подготовлен с AI-помощью, существенно отредактирован и проверен автором на основе практической разработки продукта.