Перейти к содержанию

Документация url-media-probe

url-media-probe — Минималистичный асинхронный пакет для определения параметров медиафайла без полной загрузки.

По URL скачивает минимум данных: начальный фрагмент всего 4 КБ, дальше парсер сам решает, что докачать (ещё head — точным размером или прогрессивно, слайс moov от известной позиции или tail), и всё — в пределах лимита. Локальные файлы и bytes по умолчанию читаются целиком (до 20 МБ) — для анимированных GIF/WebP это даёт точную длительность без экстраполяции. Есть режим «только заголовки» (max_ratio=0 или max_total=0) — тело не скачивается вовсе.

Лимиты чтения

Лимиты задаются едиными параметрами max_total (максимум байт) и max_ratio (доля размера файла) во всех источниках:

Источник По умолчанию
URL (from_url) max_total=256 000, max_ratio=0.1 → бюджет min(256 КБ, 10% размера)
Файл (from_file) max_total=20 971 520, max_ratio=1.0 — читается целиком
bytes (from_bytes) max_total=20 971 520, max_ratio=1.0 — читается целиком

Общий бюджет — минимум из абсолютного и относительного лимитов: min(max_total, file_size · max_ratio), но не меньше начального head (INITIAL_HEAD, 4 КБ). Head, tail и слайс суммарно не превышают бюджет; каждая порция ограничена остатком.

  • Начальный head (URL): минимум — INITIAL_HEAD (4 КБ) или размер файла, если он меньше. Дальше head растёт только по запросу парсера: точный размер (need_head=moov_end у MP4 с moov в начале) или прогрессивно (4 → 8 → … → 128 КБ).
  • Tail (хвост файла): верхняя граница — min(64 КБ, бюджет). Изначально читается конец файла (EOF), а при необходимости растёт: назад (если парсер запросил ещё хвоста) или вперёд от вычисленной позиции (например, атом moov у потоковых MP4). Резервирования места в бюджете нет: head минимален, и бюджет тратится только на запрошенное парсером.

max_ratio=0 или max_total=0 (URL) → режим «только заголовки»: тело не скачивается, MediaInfo(status="ok") строится по HTTP-метаданным (Content-Type, Content-Length, имя файла).

Быстрый старт

pip install url-media-probe

Пример — определение видео по URL

import asyncio
from url_media_probe import MediaProbe

async def main():
    probe = MediaProbe()
    info = await probe.from_url("https://example.com/video.mp4")

    print(info.format)    # "MP4"
    print(info.width)     # 1920
    print(info.height)    # 1080
    print(info.duration)  # 15.0
    print(info.status)    # "ok"

asyncio.run(main())

Пример — определение локального файла

info = await probe.from_file("/path/to/photo.jpg")
print(info.format)  # "JPEG"

Пример — определение из байтов

info = await probe.from_bytes(b"\xff\xd8\xff...")
print(info.format)  # "JPEG"

Источники данных

Источник Метод Описание
URL from_url() HTTP: минимальный head (4 КБ), докачка по запросу парсера (head/tail/слайс), retry; max_ratio=0 → только заголовки
Локальный файл from_file() Чтение целиком (до max_total = 20 МБ)
bytes / BytesIO from_bytes() Чтение целиком (до max_total = 20 МБ)
Готовые head/tail from_bytes_range() Парсинг по уже скачанным частям

Метаданные MediaInfo

info = await probe.from_url("https://example.com/audio.mp3")

# Общее
print(f"Формат: {info.format}")    # "MP3"
print(f"Статус: {info.status}")    # "ok" | "partial" | "error"

# Изображения
if info.width and info.height:
    print(f"Размеры: {info.width}x{info.height}")

# Видео / аудио
if info.duration:
    print(f"Длительность: {info.duration} сек")
if info.fps:
    print(f"FPS: {info.fps}")
if info.bitrate_avg:
    print(f"Битрейт: {info.bitrate_avg} kbps")
if info.sample_rate:
    print(f"Sample rate: {info.sample_rate} Hz")

# Кодеки
if info.video_codec:
    print(f"Видео: {info.video_codec_long or info.video_codec}")
if info.audio_codec:
    print(f"Аудио: {info.audio_codec_long or info.audio_codec}")

# Красивый вывод
print(info)

Как это работает

  1. Загрузка данных — RangeDownloader делает HTTP запросы:
  2. Сначала GET-запрос: читает метаданные (Content-Type, Content-Length, имя файла) и начальный фрагмент 4 КБ
  3. Парсер смотрит на head и сам сообщает, что докачать: ещё head (точный размер или прогрессивно 4 → 8 → 16 → ... → 128 КБ), слайс от известной позиции (moov) или tail — но не более общего бюджета min(max_total, file_size · max_ratio)
  4. Хвост файла (tail) — конец файла или слайс от известной позиции (moov у MP4), растёт по запросам парсера (до 64 КБ)
  5. Повторяет при 429/5xx ошибках
  6. Всё в одном keep-alive соединении
  7. max_ratio=0 или max_total=0 → режим «только заголовки»: тело не скачивается, MediaInfo(status="ok") строится по HTTP-метаданным

Для локальных файлов и bytes по умолчанию max_ratio=1.0 и max_total=20 МБ — данные читаются целиком.

  1. Парсинг — парсеры анализируют сигнатуры байт и заголовки файлов для определения формата, размеров, длительности, FPS, sample rate, битрейта

  2. Результат — dataclass MediaInfo с полями: format, status, width, height, duration, fps, sample_rate, bitrate_nominal, bitrate_avg, video_codec, audio_codec, parse_note

Никогда не бросает исключение. Все ошибки перехватываются внутри и отражаются в status и parse_note (см. Обработка ошибок).

Обработка ошибок

Никогда не бросает исключения — все ошибки попадают в MediaInfo.status / parse_note.

status Что значит
"ok" Все параметры определены
"partial" Часть данных получена, чего-то не хватает
"error" Формат не распознан, файл повреждён, данные не извлечены

Типичные parse_note

parse_note Причина Что делать
Недостаточно данных для определения параметров Файл слишком мал (0–3 байт) Увеличить max_total или max_ratio
Файл не является медиа (HTML-страница) По ссылке HTML, а не медиафайл Проверить URL
Формат не поддерживается или не распознан Формат нет в парсерах Использовать другой инструмент
Сетевая ошибка: ... Таймаут, ConnectionError Увеличить timeout, проверить сеть
Не удалось определить длительность Нет Xing/Info фрейма (VBR MP3) или moov (MP4) Увеличить max_total
Файл не найден Локальный файл не существует Проверить путь

Проброс сетевых ошибок

По умолчанию сетевые ошибки (aiohttp.ClientError) тоже попадают в status / parse_note. Если удобнее работать с исключениями:

import url_media_probe.media_probe as cfg
from url_media_probe import MediaProbe

cfg.raise_on_network = True  # глобально — ко всем экземплярам

probe = MediaProbe()  # наследует глобальную настройку
await probe.from_url("https://example.com/broken.mp4")
# → бросает aiohttp.ClientError (timeout, ConnectionError и т.д.)

Локально — на уровне экземпляра:

probe = MediaProbe(raise_on_network=True)
probe_safe = MediaProbe(raise_on_network=False)  # ловит ошибки как обычно

Парсерные ошибки (невалидный файл, неподдерживаемый формат) всегда остаются в status / parse_note — raise_on_network на них не влияет.

Подробнее — в разделе Обработка ошибок.

Поддерживаемые форматы

Изображения: JPEG, PNG, GIF, WebP (VP8/VP8L/VP8X)

Видео: MP4/MOV, AVI, MKV, WebM, OGV

Аудио: MP3, AAC, WAV, WMA, FLAC, OGG, M4A

Полезные ссылки