Как в Python отправить видео в мессенджер MAX и при этом отображать прогресс загрузки с помощью tqdm?

Ниже приведена функция, которой я пытаюсь загрузить видео на URL, полученный от мессенджера MAX, но при попытке загрузки возвращается ошибка 412 ( <b>HTTP Status 412 – Precondition Failed</b> ). Код функции:
  • 30 мая 2026 г. 17:00
Ответы на вопрос 1
Ошибка 412 (Precondition Failed) при загрузке по URL, который выдал мессенджер (обычно presigned URL), чаще всего возникает потому, что сервер ожидает определённые заголовки / размер тела / хэш содержимого, а вы их не передаёте или передаёте неверно. Частые причины:

- не задан Content-Length (при использовании генератора requests будет ставить chunked transfer, а некоторые серверы не принимают chunked или проверяют длину);
- ожидается точный Content-Type (например video/mp4);
- ожидается контрольная сумма (Content-MD5 или похожий заголовок);
- ожидались специфичные заголовки (If-Match, x-amz-* и т.п.).

Ниже — два рабочих подхода с прогресс-баром tqdm: 1) если URL — presigned PUT (тогда нужно отправлять "raw" тело и указать Content-Length), 2) если это обычный multipart/form-data POST к API (тогда удобнее использовать requests-toolbelt для отслеживания прогресса).

1) Presigned PUT (рекомендуемый способ для "raw" загрузок)
- Обязательно вычислите размер файла и передайте Content-Length — тогда requests не будет использовать chunked.
- Читайте файл порциями и отдавайте их генератором, который обновляет tqdm.

Пример:

```python
import os
import requests
from tqdm import tqdm

def upload_put_with_progress(url: str, filepath: str, chunk_size: int = 1024*1024):
    file_size = os.path.getsize(filepath)
    headers = {
        'Content-Type': 'video/mp4',          # или тот Content-Type, который требует сервер
        'Content-Length': str(file_size),
    }

    with open(filepath, 'rb') as f:
        with tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024) as pbar:
            def gen():
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    pbar.update(len(chunk))
                    yield chunk

            # используем PUT, data=генератор, но Content-Length уже указан
            resp = requests.put(url, data=gen(), headers=headers)
    resp.raise_for_status()
    return resp
```

Ключевые моменты:
- Content-Length обязателен для многих presigned URL. Если его не указать — Requests пошлёт chunked transfer encoding и сервер может вернуть 412/411.
- Укажите Content-Type, который ожидает сервер. Иногда presigned URL подписан под конкретный Content-Type — он должен совпадать.
- Если сервер ожидает Content-MD5, см. пункт ниже.

2) Если API требует multipart/form-data (обычный upload endpoint)
- requests сам формирует тело и заголовки, но трудно отслеживать прогресс. Для этого удобно requests-toolbelt:

Установите:
pip install requests-toolbelt

Пример:

```python
from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor
import requests
from tqdm import tqdm

def upload_multipart_with_progress(url: str, filepath: str):
    encoder = MultipartEncoder(fields={'file': ('video.mp4', open(filepath, 'rb'), 'video/mp4')})
    bar = tqdm(total=encoder.len, unit='B', unit_scale=True, unit_divisor=1024)

    def callback(monitor):
        bar.update(monitor.bytes_read - bar.n)

    monitor = MultipartEncoderMonitor(encoder, callback)
    headers = {'Content-Type': monitor.content_type}

    resp = requests.post(url, data=monitor, headers=headers)
    bar.close()
    resp.raise_for_status()
    return resp
```

Когда использовать что:
- Если вам дали presigned URL (часто в ответе API вы получаете конкретный URL для прямой загрузки) — используйте первый способ (PUT/Content-Length).
- Если API просит POST multipart к своему endpoint — используйте второй способ.

Если сервер требует Content-MD5
- Иногда presigned URL или API требует хэш тела (Content-MD5). Нужно посчитать md5 файла и передать в заголовке base64-encoded:
```python
import hashlib, base64
md5 = hashlib.md5()
with open(filepath, 'rb') as f:
    for chunk in iter(lambda: f.read(1024*1024), b''):
        md5.update(chunk)
content_md5 = base64.b64encode(md5.digest()).decode('ascii')
headers['Content-MD5'] = content_md5
```
Добавьте этот заголовок в запрос, если это требуется.

Диагностика 412
- Посмотрите в документации MAX messenger (или API, который вам дал URL) — какие заголовки обязательны для загрузки.
- Сравните требуемый Content-Type / Content-Length / Content-MD5.
- Логи сервера или тело ответа иногда содержат подсказку о том, какое условие не выполнено.

Если вы пришлёте ваш текущий код (тот самый, который даёт 412) и пример заголовков/ответа от сервера, помогу точнее: укажу, какой заголовок/формат вы упускаете и поправлю код.
Похожие вопросы