Metadata-Version: 2.5
Name: url-media-probe
Version: 0.2.1
Summary: Inspect remote media files via HTTP Range requests — download only what's needed.
Project-URL: Homepage, https://github.com/Pankovea/url-media-probe
Project-URL: Repository, https://github.com/Pankovea/url-media-probe
Author: Pankovea
License-Expression: LGPL-2.1-or-later
License-File: LICENSE
Keywords: async,audio,ffprobe-alternative,http,image,media,metadata,no-ffmpeg,probe,range,video
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Multimedia
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Video
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: aiofiles>=23.0
Requires-Dist: aiohttp>=3.9
Requires-Dist: multidict>=6.0
Requires-Dist: yarl>=1.9
Provides-Extra: dev
Requires-Dist: mkdocs-material>=9.0; extra == 'dev'
Requires-Dist: mkdocs>=1.5; extra == 'dev'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: types-aiofiles; extra == 'dev'
Description-Content-Type: text/markdown

# ![url-media-probe](logo.png)

[![Deploy Docs](https://github.com/Pankovea/url-media-probe/actions/workflows/docs-build.yml/badge.svg)](https://github.com/Pankovea/url-media-probe/actions/workflows/docs-build.yml)
[![Tests](https://github.com/Pankovea/url-media-probe/actions/workflows/tests.yml/badge.svg)](https://github.com/Pankovea/url-media-probe/actions/workflows/tests.yml)

[![Docs](https://img.shields.io/badge/docs-MkDocs-blue?style=for-the-badge&logo=materialformkdocs)](https://pankovea.github.io/url-media-probe/)

📖 **Документация:** https://pankovea.github.io/url-media-probe/


**Узнавайте информацию о медиафайле без полной загрузки**

Минималистичный пакет для определения параметров медиафайла по URL, локальному пути или байтам без полной загрузки.

По URL скачивает минимум данных: начальный фрагмент всего 4 КБ, дальше парсер сам решает, что докачать, но всё — в пределах лимита `max_total`. Возможен режим «только заголовки» (`max_ratio=0`) — тело не скачивается вовсе, тогда мы получаем только то что сообщил сервер в заголовке
(имя файла, размер, mime-тип).

Локальные файлы и bytes по умолчанию читаются целиком (до 20 МБ) — для анимированных GIF/WebP это даёт точную длительность без экстраполяции. Лимиты для любого источника задаются одинаковыми параметрами `max_total` / `max_ratio`.

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

## Сравнение аналогичных проектов

| Характеристика | tinytag | -- url-media-probe -- | hachoir | pymediainfo | ffprobe (subprocess) |
|---|---|---|---|---|---|
| Pure Python + серверлес | Да | Да (async, без бинарников) | Да | Нет (libmediainfo) | Нет (ffmpeg binary) |
| Асинхронность | Синхронный | asyncio + aiohttp | Синхронный | Синхронный | Синхронный |
| Источники данных | Файл | URL, файл, байты в памяти | Файл | Файл | URL, файл |
| Оптимизация трафика | — | head 4 KB + докачка по запросу парсера (head/tail/слайс), ограничение `max_total` | — | — | Да, но может скачать весь файл |
| HTTP Range запросы | — | Начальный head прогрессивныая (4→8→..→128 KB) докачка, tail по запросу парсера (середина файла по известной позиции или хвост) | — | — | Да, но может скачать весь файл |
| Типизация | Type hints | dataclass (frozen) | — | — | JSON/dict |
| Форматы | Только аудио | Популярные аудио, видео и фото (~20) | 33 формата: аудио, видео, изображения, архивы, шрифты | Все форматы MediaInfo (100+) | Все форматы ffmpeg (100+) |
| Информативность | Базовая: duration, bitrate, sample rate, теги | Базовая: format, duration, fps, sample rate, bitrate, codec | Полная: codec, profile, bitrate, пиксели, каналы, теги | Полная: codec, profile, level, bitrate, контейнер | Полная: codec, profile, level, пиксели, каналы и т.д. |
| Чтение/запись тегов | Только чтение | — | Чтение + редактирование бинарных полей | Только чтение | Только чтение |
| Размер дистрибутива | ~33 KB | ~300 KB | ~650 KB | ~15 MB (libmediainfo) | >100 MB (ffmpeg) |

*Легковесные → тяжеловесные*

> **Примечание:** hachoir помечен как «No Maintenance Intended».

## Установка

```bash
pip install url-media-probe
```

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

```python
import asyncio
from url_media_probe import MediaProbe


async def main():
    probe = MediaProbe()

    # По URL
    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"

    # Локальный файл
    info = await probe.from_file("/path/to/photo.jpg")
    print(info.format)  # "JPEG"

    # Из байтов
    info = await probe.from_bytes(b"...")
    print(info.format)  # определяется по сигнатуре


asyncio.run(main())
```

## Метаданные MediaInfo

```python
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")

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

## Кодеки

```python
info = await probe.from_url("https://example.com/video.mp4")

# Краткие идентификаторы
print(info.video_codec)  # "h264"
print(info.audio_codec)  # "aac"

# Полные названия (из ffprobe)
print(info.video_codec_long)  # "H.264 / AVC / MPEG-4 part 10"
print(info.audio_codec_long)  # "Advanced Audio Coding"
```

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

Ограничения задаются параметрами `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`, имя файла).

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

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

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

2. **Парсинг** -- парсеры анализируют сигнатуры байт и заголовки файлов для определения формата, размеров, длительности, FPS, sample rate, битрейта. Формат определяется исходя из данных, а не из расширения файла.

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

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

По умолчанию библиотека **Никогда не бросает исключение.** Все ошибки перехватываются внутри и отражаются в `status` и `parse_note`. Для проверки результата смотрите `status` (`"ok"` | `"partial"` | `"error"`) и `parse_note` (описание проблемы или предупреждение).

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

При необходимости можно включить проброс сетевых ошибок:

```python
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 и т.д.)
```

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

```python
probe = MediaProbe(raise_on_network=True)  # только для этого probe
probe_strict = MediaProbe(raise_on_network=False)  # ловит ошибки как обычно
```

> Ошибки парсера (формат не распознан, невалидный файл, недостаточно данных)
> **всегда** остаются в `MediaInfo(status="error", parse_note="...")` — исключения не бросаются.

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

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

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

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

## Лицензия

LGPL-2.1
