Skip to content

Commit 710ed84

Browse files
committed
fix: parse video URLs by MP4 quality
1 parent 67896d0 commit 710ed84

2 files changed

Lines changed: 53 additions & 9 deletions

File tree

src/pymax/types/domain/attachments/video.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,18 @@ class VideoRequest(CamelModel):
6262
:vartype external: str | bool | None
6363
:ivar cache: Использовать ли кеш.
6464
:vartype cache: bool
65-
:ivar url: URL видео.
66-
:vartype url: str
65+
:ivar url: Прямой URL видео или ``None`` для внешнего видео.
66+
:vartype url: str | None
6767
"""
6868

6969
external: str | bool | None = Field(default=None, alias="EXTERNAL")
7070
cache: bool
71-
url: str
71+
url: str | None = None
7272

7373
@model_validator(mode="before")
7474
@classmethod
75-
def unwrap_dynamic_url(cls, value: Any) -> Any:
76-
"""Нормализует динамический ключ URL в поле ``url``.
75+
def select_video_url(cls, value: Any) -> Any:
76+
"""Выбирает прямой URL с максимальным доступным MP4-качеством.
7777
7878
:param value: Значение, переданное в валидатор модели.
7979
:type value: Any
@@ -83,8 +83,29 @@ def unwrap_dynamic_url(cls, value: Any) -> Any:
8383
if not isinstance(value, dict) or "url" in value:
8484
return value
8585

86+
mp4_urls: list[tuple[int, str]] = []
8687
for key, url in value.items():
87-
if key not in ("EXTERNAL", "cache"):
88-
return {**value, "url": url}
88+
if not isinstance(key, str) or not isinstance(url, str):
89+
continue
90+
91+
normalized_key = key.upper()
92+
if not normalized_key.startswith("MP4_"):
93+
continue
94+
95+
try:
96+
quality = int(normalized_key.removeprefix("MP4_"))
97+
except ValueError:
98+
continue
99+
100+
if quality > 0:
101+
mp4_urls.append((quality, url))
102+
103+
if mp4_urls:
104+
_, url = max(mp4_urls, key=lambda item: item[0])
105+
return {**value, "url": url}
106+
107+
legacy_url = value.get("dynamicUrl", value.get("dynamic_url"))
108+
if isinstance(legacy_url, str):
109+
return {**value, "url": legacy_url}
89110

90111
return value

tests/api/test_message_service.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pymax.exceptions import UploadError
88
from pymax.files import File, Photo, Video
99
from pymax.protocol import Opcode
10+
from pymax.types.domain.attachments import VideoRequest
1011
from tests.conftest import FakeApp, frame, message_payload
1112

1213

@@ -372,7 +373,15 @@ async def test_reaction_methods_parse_optional_reaction_info() -> None:
372373
async def test_get_video_and_file_by_id_parse_request_models() -> None:
373374
app = FakeApp(
374375
[
375-
frame({"cache": True, "dynamicUrl": "https://video.test"}),
376+
frame(
377+
{
378+
"cache": True,
379+
"FAILOVER_HOSTS": ["maxvd759.okcdn.ru"],
380+
"MP4_480": "https://video.test/480",
381+
"EXTERNAL": "https://m.ok.ru/video/1",
382+
"MP4_720": "https://video.test/720",
383+
}
384+
),
376385
frame({"unsafe": False, "url": "https://file.test"}),
377386
]
378387
)
@@ -381,7 +390,8 @@ async def test_get_video_and_file_by_id_parse_request_models() -> None:
381390
file = await app.api.messages.get_file_by_id(100, "10", 30)
382391

383392
assert video is not None
384-
assert video.url == "https://video.test"
393+
assert video.url == "https://video.test/720"
394+
assert video.external == "https://m.ok.ru/video/1"
385395
assert file is not None
386396
assert file.url == "https://file.test"
387397
assert [call.opcode for call in app.calls] == [
@@ -400,6 +410,19 @@ async def test_get_video_and_file_by_id_parse_request_models() -> None:
400410
}
401411

402412

413+
def test_video_request_supports_legacy_and_external_only_payloads() -> None:
414+
legacy = VideoRequest.model_validate(
415+
{"cache": True, "dynamicUrl": "https://video.test/legacy"}
416+
)
417+
external = VideoRequest.model_validate(
418+
{"cache": True, "EXTERNAL": "https://m.ok.ru/video/1"}
419+
)
420+
421+
assert legacy.url == "https://video.test/legacy"
422+
assert external.url is None
423+
assert external.external == "https://m.ok.ru/video/1"
424+
425+
403426
def test_next_cid_is_monotonic_when_clock_does_not_move(
404427
monkeypatch: pytest.MonkeyPatch,
405428
) -> None:

0 commit comments

Comments
 (0)