Files
hermes-agent/tests/gateway/test_telegram_video_metadata.py
teknium1 8fefd35bb9 test(telegram): skip the real-ffmpeg geometry check where ffmpeg is absent
``test_video_helpers_read_real_geometry`` renders a clip with ffmpeg and
reads it back with ffprobe. The Linux CI runner ships without either, so
the test errored with FileNotFoundError instead of exercising anything.
Skip it when the binaries are missing (same shape as
tests/gateway/test_voice_command.py); the two mocked tests still cover the
send path everywhere.
2026-09-20 11:20:26 -07:00

106 lines
4.5 KiB
Python

"""Telegram videos over ~10 MB must carry explicit geometry and a thumbnail.
Telegram runs its own video processing only for small uploads: above roughly 10 MB the
``sendVideo`` response comes back as ``width=320 height=320 duration=0`` with no thumbnail, and
clients then draw the message as a square tile whatever the real aspect ratio. Measured on one
6 s 2560x1440 clip: 4.9 MB and 9.8 MB keep ``2560x1440``, duration 7 and a 320x180 thumbnail;
14.8 MB, 19.4 MB and 23.0 MB degrade to the square placeholder, while the same 23.0 MB file sent
with ``width``/``height``/``duration`` plus a JPEG ``thumbnail`` comes back ``2560x1440`` with a
320x180 thumbnail.
``TelegramAdapter.send_video`` therefore probes the local file and forwards that metadata, so
portrait reels and landscape clips stay correct at any size.
These tests confirm:
1. the probed geometry and thumbnail reach ``bot.send_video``,
2. a file ffprobe/ffmpeg cannot read still sends, just without metadata,
3. the temporary thumbnail is removed after the send,
4. on a real file, the helpers report the true geometry and a same-aspect thumbnail.
"""
import contextlib
import os
import shutil
import subprocess
from unittest.mock import AsyncMock, MagicMock
import pytest
from gateway.config import PlatformConfig
from plugins.platforms.telegram import adapter as telegram_mod
from plugins.platforms.telegram.adapter import TelegramAdapter
def _make_adapter() -> TelegramAdapter:
adapter = TelegramAdapter(PlatformConfig(enabled=True, token="***"))
adapter._bot = MagicMock()
adapter._bot.send_video = AsyncMock(return_value=MagicMock(message_id=7))
return adapter
@pytest.mark.asyncio
async def test_send_video_forwards_geometry_and_thumbnail(monkeypatch, tmp_path):
"""A video Telegram will not process itself still advertises its real shape."""
thumb = tmp_path / "thumb.jpg"
thumb.write_bytes(b"\xff\xd8\xff\xe0" + b"\x00" * 32)
monkeypatch.setattr(
telegram_mod, "_probe_video_geometry",
lambda _p: {"width": 2560, "height": 1440, "duration": 98})
monkeypatch.setattr(telegram_mod, "_video_thumbnail_jpeg", lambda _p, _d: str(thumb))
video = tmp_path / "clip.mp4"
video.write_bytes(b"\x00" * 64)
adapter = _make_adapter()
result = await adapter.send_video("123", str(video))
assert result.success is True
adapter._bot.send_video.assert_awaited_once()
kwargs = adapter._bot.send_video.await_args.kwargs
assert (kwargs["width"], kwargs["height"], kwargs["duration"]) == (2560, 1440, 98)
assert kwargs["thumbnail"] == str(thumb)
assert not thumb.exists(), "the temporary thumbnail must not be left behind"
@pytest.mark.asyncio
async def test_send_video_without_probeable_media_omits_metadata(monkeypatch, tmp_path):
"""Unreadable or non-media files must still deliver, with no geometry kwargs."""
monkeypatch.setattr(telegram_mod, "_probe_video_geometry", lambda _p: {})
video = tmp_path / "clip.bin"
video.write_bytes(b"\x00" * 16)
adapter = _make_adapter()
result = await adapter.send_video("123", str(video))
assert result.success is True
kwargs = adapter._bot.send_video.await_args.kwargs
assert "width" not in kwargs and "height" not in kwargs
assert "duration" not in kwargs and "thumbnail" not in kwargs
@pytest.mark.skipif(
shutil.which("ffmpeg") is None or shutil.which("ffprobe") is None,
reason="ffmpeg/ffprobe not installed (the CI runner ships without them)",
)
def test_video_helpers_read_real_geometry(tmp_path):
"""On a real 16:9 clip the helpers report the true size and a 16:9 thumbnail."""
clip = tmp_path / "clip.mp4"
subprocess.run(
["ffmpeg", "-y", "-f", "lavfi", "-i", "testsrc=size=640x360:rate=10:duration=2",
"-c:v", "libx264", "-pix_fmt", "yuv420p", str(clip)],
capture_output=True, check=True)
geometry = telegram_mod._probe_video_geometry(str(clip))
assert (geometry["width"], geometry["height"]) == (640, 360)
assert geometry["duration"] == 2
thumb = telegram_mod._video_thumbnail_jpeg(str(clip), geometry["duration"])
assert thumb is not None
try:
probe = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height", "-of", "csv=p=0", thumb],
capture_output=True, text=True, check=True).stdout.strip()
assert probe == "320,180", "thumbnails keep the source aspect ratio"
finally:
with contextlib.suppress(OSError):
os.remove(thumb)