Загрузка 0
ПОДЕЛИТЬСЯ

Мой блог

Листай вниз

Как узнать длительность видео по ссылке, скачав 7% файла

Как узнать длительность видео по ссылке, скачав 7% файла

Подробности изложены в материале первоисточника. Когда один из пользователей загрузил в мой сервис ролик весом 37 мегабайт, система выдала ошибку определения хронометража. При этом сам файл успешно воспроизводился в любых плеерах, а утилита ffprobe показывала стандартные 18 секунд без каких-либо видимых сбоев в логах. Разобрать эту проблему и понять внутреннее устройство MP4-контейнеров оказалось крайне полезно для оптимизации серверной логики.

Подобные технические задачи возникают регулярно при разработке платформ, где ключевым фактором является автоматизация процессов. Я стараюсь подробно документировать такие нестандартные кейсы на страницах проекта Sergey Bagrov.

Зачем мерить длительность на сервере

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

Реклама

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

Единственным надежным выходом стало самостоятельное вычисление хронометража на основе уже сохраненного в облачном хранилище файла. Первой мыслью было задействовать ffprobe, который отлично умеет работать по протоколу HTTP и самостоятельно отправляет range-запросы. Однако я отказался от этого пути по двум причинам: мне не хотелось порождать сторонние дочерние процессы в критически важных точках обработки платежей и тащить на рабочий сервер громоздкие бинарники ради получения одного числового параметра.

Как устроен MP4-контейнер и где искать метаданные

Структура любого MP4-файла представляет собой иерархическую систему вложенных блоков, называемых боксами. Каждый такой элемент начинается с четырехбайтника своего размера и четырех байт текстового идентификатора вроде ftyp, moov или mdat.

Вся исчерпывающая информация о медиафайле сосредоточена внутри бокса moov. Внутри него располагается заголовок mvhd, содержащий важнейшие поля timescale (количество таймтиков за одну секунду) и duration (общая продолжительность в тех же тиках). Простая математическая операция деления второго на первое дает искомое значение в секундах.

Главная сложность заключается в расположении этого блока. Если видеоматериал оптимизировался под веб-трансляцию, moov находится в самом начале. Если же файл был напрямую экспортирован из видеоредактора, заголовок перемещается в самый хвост после всех медиаданных. Моя первая версия парсера запрашивала начальные 256 килобайт, а при отсутствии нужных данных добирала последние два мегабайта хранилища, что успешно справлялось с большинством стандартных тестовых роликов.

Что сломалось на нестандартном файле

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

Реклама

Исследование загруженного файла показало интересную картину: заголовок mvhd присутствовал на своем месте, базовый timescale выглядел корректно, однако поле duration равнялось нулю. Это был не поврежденный мусор, а осознанно записанный создателями фрагментированный MP4.

Подобный формат активно применяется для потокового вещания и записи информации на лету. Сначала фиксируется общий заголовок с нулевой длиной, а затем поток пишется небольшими порциями (moof и mdat), каждая из которых обладает собственными локальными метаданными. Это позволяет сохранить читаемость файла даже при внезапном обрыве записи, но полностью лишает нас точных цифр в глобальном заголовке.

Как вычислить длительность фрагментированного видео

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

Алгоритм извлечения данных выглядит следующим образом:

  • Из основного блока moov извлекаются базовый timescale дорожки (mdhd) и длительность кадра по умолчанию (trex).
  • Из хвостового оглавления mfra считывается точное байтовое смещение последней порции данных.
  • Выполняется точечный запрос небольшой порции данных по найденному смещению для чтения бокса moof.
  • Суммируются временные метки начала фрагментов (tfdt) и длительности входящих кадров (trun) с последующим делением на шкалу времени.

В результате для проблемного видеоролика система совершила всего три точечных range-запроса, выкачав из сети суммарно около двух с половиной мегабайт вместо полного скачивания объекта.

Архитектурные нюансы и подводные камни

Разработка собственного легковесного парсера вскрыла несколько важных нюансов серверного программирования. Во-первых, важно контролировать корректность ответов хранилища: если сервер проигнорирует заголовок Range и вернет код 200 вместе со всем файлом целиком, пропускная способность сети и оперативная память будут потрачены напрасно.

Во-вторых, спецификация MP4 поддерживает разные версии боксов (версия 0 с 32-битными полями и версия 1 с 64-битными), что напрямую влияет на смещение байтов при чтении. Ошибки в определении версий приводят к неверному распарсиванию числовых значений.

Отдельным открытием стала специфика работы фреймворков вроде Next.js, где встроенные промежуточные обработчики (middleware) по умолчанию буферизуют входящие тела запросов в оперативной памяти с жестким лимитом в десять мегабайт. Попытка загрузить крупные файлы через защищенный прокси приводила к незаметному сбросу соединения до того, как данные доходили до основного алгоритма проверки.

01.