Как считать стоимость 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: клиент заранее покупает объём, а затем расходует его на конкретную услугу. Важно, что такие кредиты — не электронный кошелёк и не деньги для переводов третьим лицам, а право потребить услугу внутри продукта. Это же различие стоит явно закрепить в условиях сервиса.
У кредитов есть четыре продуктовых преимущества:
- Пользователь один раз проходит оплату и запускает несколько задач без новых чек-аутов.
- Цена каждой задачи всё равно остаётся связанной с потреблением.
- Можно безопасно выдавать небольшой тестовый объём или компенсацию поддержки.
- Возврат при техническом сбое происходит мгновенно внутри баланса, без отдельной банковской операции.
Но есть и обязательства:
- неиспользованные кредиты остаются обязательством продукта;
- баланс должен быть бухгалтерским журналом, а не одним изменяемым числом;
- условия сгорания или бессрочности должны быть понятны до покупки;
- рекламные и купленные кредиты желательно различать;
- поддержке нужна история всех начислений и списаний.
Баланс — это журнал операций, а не поле 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-ответ, возникают таймауты, повторные запросы и непонятное состояние после закрытия вкладки.
Надёжнее разделить загрузку, запуск и получение результата:
- Браузер получает ограниченные временные права на загрузку одного объекта.
- Видео загружается напрямую в объектное хранилище.
- Сервер создаёт задачу и запускает durable workflow.
- Workflow списывает кредиты.
- Внешний AI-провайдер получает короткоживущую ссылку на исходник.
- Workflow ждёт webhook или событие завершения.
- Результат копируется в собственное хранилище.
- Задача отмечается завершённой.
- Браузер показывает результат при следующем открытии страницы.
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 тарификации
- Единица потребления связана с реальной себестоимостью.
- Цена известна пользователю до запуска.
- Сервер независимо проверяет длительность и стоимость.
- Все операции баланса записываются в журнал.
- Списание и возврат имеют уникальные идемпотентные ключи.
- Определена точка, после которой задача считается успешно оказанной услугой.
- Неидемпотентные внешние вызовы не повторяются автоматически.
- Ошибка после списания гарантированно запускает компенсацию.
- Интерфейс показывает возврат без ручной проверки баланса.
- Срок хранения известен до обработки.
- Считается себестоимость не только успешных, но и всех запусков.
- Скидки пакетов проверены на фактической марже.
- Поддержка видит полную историю задачи и баланса.
Вывод
Оплата по факту использования кажется простой только на лендинге. Внутри продукта это договор между измерением, биллингом, асинхронной обработкой, хранением и компенсацией ошибок.
Хорошая модель отвечает на четыре вопроса без участия поддержки:
- Сколько будет стоить задача?
- Когда спишутся кредиты?
- Что произойдёт при сбое?
- До какого момента будет доступен результат?
Если ответы видны до запуска, а журнал операций и workflow выдерживают повторы, pay-as-you-go становится не только способом монетизации, но и преимуществом продукта. Пользователь платит за понятный объём работы, а команда видит экономику каждой выполненной задачи, а не среднюю температуру по подпискам.
Материал подготовлен с AI-помощью, существенно отредактирован и проверен автором на основе практической разработки продукта.