Feat file info - #149
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Здравия всем. Что-то много коммитов на присоединил. Там по сути только последний один по делу. И я понимаю что там могут быть косяки. Форматы разные — не все случаи проверены. |
|
Может надо пересоздать PR, чтобы правильно коммиты подгрузились. Чтоб лишнего не показывали. |
получение метаинформации о файлах без полной загрузки Добавлен модуль file_inspector.py с классами: - FileInfo — pydantic-модель с метаинформацией о файле (format, width, height, duration, fps, sample_rate, bitrate, error_desc). Статус ok/partial/error определяется автоматически по наличию ключевых полей. - FetchPlan — план загрузки файла. Определяет сколько байт читать из начала (initial_head), размер докачки (expand_chunk), лимиты (max_head, min_head) и нужно ли читать хвост (need_tail). Метод from_content_type() строит план по MIME-типу и размеру файла. - RangeReader — базовый класс для чтения файлов частями. Единый интерфейс: async for chunks in reader. Свойства head, tail, head_size, tail_size. - RangeDownloader(HTTP) — загрузчик по URL. Retry при 429/500/502/503/504 с экспоненциальным backoff. Прогрессивная докачка с удвоением чанка. Определяет Content-Type из заголовков, строит план, читает head с докачкой и tail через Range-запросы. - RangeFileReader — читает локальный файл (anyio). Head, tail через seek, докачка по плану. - RangeBytesReader — читает из bytes/BytesIO/NamedBytesIO. Использует memoryview без копирования данных. Для маленьких файлов читает целиком. - FileInspector — высокоуровневая обёртка. Три метода: inspect_url(url) — удалённый файл inspect_file(path) — локальный файл inspect_bytes(data) — уже загруженные байты Возвращает FileInfo. Сохраняет last_file_info, head, tail после инспекции. Парсеры встроены в FileInspector как @classmethod. Поддерживают: Изображения: JPEG, PNG, GIF, WebP (VP8/VP8L/VP8X) Видео: MP4/MOV, AVI, MKV, WEBM, OGV Аудио: MP3, AAC, WAV, WMA, FLAC, OGG, M4A Добавлен метод bot.get_file_info(url) — получение метаинформации через FileInspector без полной загрузки файла. Примеры: 05_media_bot.py — добавлен /info метаинформация о replied-вложении через info = await bot.get_file_info(url) Тесты: - Параметризованные тесты на всех фикстурах из fixtures.json - Фикстуры можно расширять и править с помощью prepare_fixtures.py - inspect_url, inspect_file, inspect_bytes — сравнение результатов - Retry-логика: 503 → retry → успех, исчерпание retry, 404 без retry - Создание сессии когда не передана - HTML-страница → error - Пустой Content-Type → определение формата по байтам
|
В общем, да тесты упали. потому что ребейз сделал. |
fix: JPEG parser fix: bot.get_file_info kwargs для передачи в RangeDownloader
There was a problem hiding this comment.
Pull request overview
Добавляется FileInspector/RangeDownloader для извлечения метаданных медиафайлов (формат, размеры, длительность, битрейты) по URL/локальному файлу/байтам без полной загрузки, а также публичная модель FileInfo и удобный метод bot.get_file_info().
Changes:
- Добавлены
FileInspector,RangeDownloaderи набор парсеров сигнатур/заголовков для популярных форматов медиа. - Добавлена pydantic-модель
FileInfoи экспорт вmaxapi.types, плюс методBot.get_file_info(). - Добавлены тесты с фикстурами и вспомогательный скрипт генерации
fixtures.json; обновлён пример05_media_bot.py.
Reviewed changes
Copilot reviewed 7 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_utils/fileinfo/test_file_info.py | Новые параметризованные тесты FileInspector/RangeDownloader + моки aiohttp. |
| tests/test_utils/fileinfo/prepare_fixtures.py | Скрипт скачивания/генерации фикстур head/tail и expected-полей в fixtures.json. |
| tests/test_utils/fileinfo/init.py | Пакет тестов для fileinfo. |
| maxapi/utils/file_inspector.py | Основная реализация инспекции: планы загрузки, range-ридеры, парсеры форматов. |
| maxapi/types/file_info.py | Новая модель FileInfo со статусом и человекочитаемым выводом. |
| maxapi/types/init.py | Экспорт FileInfo в публичный maxapi.types. |
| maxapi/connection/base.py | Добавлен NamedBytesIO (BytesIO с .name). |
| maxapi/bot.py | Добавлен метод Bot.get_file_info(). |
| examples/05_media_bot.py | Пример дополнен командой /info и авто-инспекцией первого вложения. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Olegt0rr, спасибо. Я тут посмотрел аналогию FileInfo в телеграмм. Получилось мало общего (в том смысле, что у нас намного шире представленная информация. Я бы даже назвал этот класс MediaInfo, но обычные файлы тоже можно посмотреть размер и тип (zip, rar, pdf, и др.)) |
Доработка выглядит весьма адекватно. По поводу того брать или нет – здесь финальное слово за @love-apples |
- file_size = exp.get("file_size") or (len(head) + len(tail)) упадёт с TypeError
- В mock tail-чтения при tail is None возвращается b"self.tail empty"
- Docstring теста
- _m4a_parse_mvhd_duration(data: bytes) -> float
- опечатки и орфография
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 10 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
tests/test_utils/fileinfo/prepare_fixtures.py:61
- Опечатка в комментарии: «Провеирть» → «Проверить».
# Провеирть ключи. Совпадающие заменят данные, новые добавят.
- mp4, m4a check - опечатки
RangeDownloader теперь не делает отдельный GET в _fetch_meta(), а сохраняет ответ для использования при чтении head
Проверка на содержание Authorization или Cookie Параметр allow_external_auth для разрешения + docstrings
Упрощение (уменьшение) кода: - mp4 m4a теперь общий парсер - удалён класс MediaChunks за ненадобностью fix: prepare_fixtures отключет полную загрузку файла в фикстуру. fix: передача параметра allow_external_auth + docstrings
fix: test_retry_then_success update fixtures.json
Удалить FetchPlan как класс. Заменить константами: INITIAL_HEAD=4096, MAX_HEAD=256000, EXPAND_CHUNK=4096, MAX_TAIL=65536. Переработать RangeDownloader.__aiter__: Сразу получаем минимальные GET 4096 байт + meta из заголовков (Content-Type, Length, filename). yield чанк Отправляем в парсер, он возвращает _need_head / _need_tail _need_head == -1 → докачка удвоением (используем существующий) _need_head > 0 → докачка до конкретного размера (используем существующий) _need_tail > 0 → Отдельный Range-запрос Без ключей — хватило Цикл пока парсер просит ещё. Адаптировать FileInspector._inspect под новую логику. Выигрыш: Меньше кода. Меньше Классов. Понятная логика для всех случаев. Надёжнее: план строится по реальным данным, а не по MIME-типу от сервера (который может быть application/octet-stream или врать). Точнее: парсер знает сколько ему нужно или просто просит ещё. Меньше запросов: meta + head в одном GET (вместо двух). Проще расширять: новый формат — только парсер, без правок плана. + Переименованы поля: FileInfo error_desc -> parse_note full_read_limit -> full_read_threshold
|
Olegt0rr попроси Copilot проанализировать. Там сейчас прям сильно поменялось всё. |
|
Я бы в идеале разместил эту утилиту прямо в URL Сделать как кэшируемое проперти. При первом запросе лезет в сеть, а потом просто берёт из кэша. UPD: Нет погорячился. тут же надо эвэйтить. Кстати, может быть duration сделать по умолчанию ноль? |
|
Посмотрел. URL в атачментах просто строка. Тогда старое поведение будет сохранено, но будет добавлен новый метод. В общем надо решить, двигаться в этом направлении или нет. |
Прямой доступ к информации о файле по URL
в attachmets. Пример:
```
for att in event.message.body.attachments:
if hasattr(att, url):
info = await att.url.get_info()
if info.duration and info.duration > 3:
do_some_thing(att.url)
```
fix:
remove: дублирование данных: self.content_type, self.file_name, self.file_size Теперь всё находится в self.meta
- внедрено начиная с абстрактного класса RangeReader. - использует активное соединение или reconnect через Range-запрос refactor: RangeDownloader - _closed: изменён на property (self._response.closed), - _request_with_retry: allow_range/range_bytes → range_start/range_end
+ докстринги * get_info возвращает кэш при неизменном url * удобный импорт from maxapi.types
from maxapi.utils
+ FileInspector.downloaded size info
Пока не изменял описание заголовока PR |
| photo_id: int | ||
| token: str | ||
| url: str | ||
| url: UrlStr | str |
| """ | ||
|
|
||
| url: str | ||
| url: UrlStr | str |
| """ | ||
|
|
||
| url: str | None = None | ||
| url: UrlStr | str | None = None |
| """ | ||
|
|
||
| url: str | None = None | ||
| url: UrlStr | str | None = None |
|
С аннотацией UrlStr такая тема. Если убрать | str, то Argument of type "Literal['https://example.com/file.bin']" cannot be assigned to parameter "url" of type "UrlStr" in function "init" Но в рантайме работает и так и так по факту. |
|
ffprobe |
В общем он конечно же крутой и недосягаемый. На самом деле не знал что ffmpeg может определить формат не скачивая файл целиком. Но всё равно ценность от этого не уменьшается. Легковесный, на чистом пайтон, не запускает процессов, которые могут повиснуть, можно запускать на серверлесс. |
Так же заменены названия методов и классов под новое представление.
- url_str.py: FileInspector → MediaProbe, FileInfo → MediaInfo - examples.md: url.inspector → url.media_probe - file_inspector.md: bitrate → bitrate_nominal, bitrate_avg
| "pydantic>=2,<3", | ||
| "aiofiles>=24.1,<26", | ||
| "puremagic>=1.30,<2", | ||
| "url-media-probe>=0.1.0", |
There was a problem hiding this comment.
optional-dependencies pyproject.toml
There was a problem hiding this comment.
лучше вообще полностью отдельный пакет и 0 его упоминаний в коде maxapi, только в docs examples
There was a problem hiding this comment.
не понятно. Так и сделал отдельный пакет и минимальная интеграция для удобства пользования.
FileInspector — метаинформация о медиафайлах без полной загрузки
Что это
Быстрое определение параметров медиафайла по URL, локальному пути или байтам.
Работает без дополнительных библиотек — анализирует сигнатуры и заголовки
в первых и последних килобайтах файла. Скачивает минимум данных, докачивает
только если не хватило.
Было навеяно
get_fileиз Telegram API, в частности узнать размер файла, но разрослось в Media Info с информацией о размере кадра, длительности, частоты кадров, битрейте.Примеры использования
Как работает
Сначала скачивает заголовки вместе начальными 4 Кб данных.
Отправляет данные в парсер, если парсер сообщает о недостатке данных,
то докачивает не разрывая соединения.
Загрузка.
RangeDownloader делает Range-запросы, докачивает
прогрессивно (удвоение чанка: 8 → 16 → 32 → 64 КБ), повторяет
при 429/5xx ошибках. Всё в одном keep-alive соединении.
Парсинг.
Сигнатуры байт определяют формат, из заголовков
извлекаются размеры, длительность, FPS, sample rate, битрейт.
То есть по байтам (без знания имени и mimetype файла)
можно узнать что там за файл.
Результат. FileInfo — pydantic-модель. Статус
okесли всёнайдено,
partialесли не хватило данных,errorпри ошибке.Поддерживаемые форматы
Изображения: JPEG, PNG, GIF, WebP (VP8/VP8L/VP8X)
Видео: MP4/MOV, AVI, MKV, WebM, OGV
Аудио: MP3, AAC, WAV, WMA, FLAC, OGG, M4A
Что добавлено
FileInfo — модель с полями width, height, duration, fps, sample_rate,
bitrate_nominal, bitrate_avg, format, status, parse_note.
Метод str для красивого вывода.
FileInspector — три метода: inspect_url(), inspect_file(),
inspect_bytes(). Работает без доп. библиотек.
RangeDownloader — HTTP-загрузчик с retry и прогрессивной докачкой.
RangeFileReader / RangeBytesReader — локальные файлы и байты.
bot.get_file_info(url) — удобный метод для ботов.
Дополнен пример 05_media_bot.py:
/info ответом на вложение — присылает метаданные.
На все входящие вложения присылается подробные данные о формате и параметрах медиа.
Тесты
На всех форматах с моками HTTP, retry, ошибок.
Фикстуры в fixtures.json. Содержат в себе head и tail байты
реальных файлов для тестирования парсинга.
Скрипт prepare_fixtures.py для обновления фикстур
по ссылке в интернете или из локального файла.