Fix videos posting as downloadable documents instead of playable videos

Root cause of a real report (Russia Airsoft Awards post): none of the five
send_video/InputMediaVideo call sites in tg_poster.py set
supports_streaming=True. Without it, Telegram doesn't prepare the upload
for progressive/inline playback - showing "00:00" duration with a plain
download prompt on desktop, and, when it does play on mobile, audio with a
black video (same underlying cause as the faststart fix, but that only
handles the local moov-atom position; Telegram's own server-side handling
of the upload needs the flag regardless).

The real post went through the rich-message/file_id path
(upload_media_for_file_ids -> sendRichMessage): a file_id bakes in whatever
attributes the video was uploaded with, so fixing that call site fixes
every future post reusing the id. Also pass width/height/duration
explicitly everywhere instead of relying on Telegram's own probing, and
carry those through the plain-caption/media-group fallback paths for
consistency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 19:15:01 +05:00
parent eab965c02d
commit 5549b7d49b
+28 -5
View File
@@ -161,7 +161,10 @@ class TelegramPoster:
if item.media_type == "photo": if item.media_type == "photo":
group.append(InputMediaPhoto(media=fs)) group.append(InputMediaPhoto(media=fs))
else: else:
group.append(InputMediaVideo(media=fs)) group.append(InputMediaVideo(
media=fs, supports_streaming=True,
width=item.width, height=item.height, duration=item.duration_sec,
))
if not group: if not group:
return file_ids return file_ids
@@ -178,7 +181,14 @@ class TelegramPoster:
file_ids[item.attachment_id] = msg.photo[-1].file_id file_ids[item.attachment_id] = msg.photo[-1].file_id
else: else:
msg = await self.tg_retry_media( msg = await self.tg_retry_media(
lambda: self.bot.send_video(video=fs, **self.chat_kwargs(chat_id, thread_id, use_storage)) lambda: self.bot.send_video(
video=fs,
supports_streaming=True,
width=item.width,
height=item.height,
duration=item.duration_sec,
**self.chat_kwargs(chat_id, thread_id, use_storage),
)
) )
if msg.video: if msg.video:
file_ids[item.attachment_id] = msg.video.file_id file_ids[item.attachment_id] = msg.video.file_id
@@ -307,7 +317,10 @@ class TelegramPoster:
if item.media_type == "photo": if item.media_type == "photo":
input_media.append({"type": "photo", "media": val}) input_media.append({"type": "photo", "media": val})
else: else:
input_media.append({"type": "video", "media": val}) input_media.append({
"type": "video", "media": val,
"width": item.width, "height": item.height, "duration": item.duration_sec,
})
if not input_media: if not input_media:
# Text only post # Text only post
@@ -341,6 +354,10 @@ class TelegramPoster:
video=item["media"], video=item["media"],
caption=first_caption or None, caption=first_caption or None,
parse_mode="HTML", parse_mode="HTML",
supports_streaming=True,
width=item.get("width"),
height=item.get("height"),
duration=item.get("duration"),
**self.chat_kwargs(chat_id, thread_id), **self.chat_kwargs(chat_id, thread_id),
) )
) )
@@ -352,7 +369,10 @@ class TelegramPoster:
if item["type"] == "photo": if item["type"] == "photo":
group.append(InputMediaPhoto(media=item["media"], caption=cap, parse_mode="HTML")) group.append(InputMediaPhoto(media=item["media"], caption=cap, parse_mode="HTML"))
else: else:
group.append(InputMediaVideo(media=item["media"], caption=cap, parse_mode="HTML")) group.append(InputMediaVideo(
media=item["media"], caption=cap, parse_mode="HTML", supports_streaming=True,
width=item.get("width"), height=item.get("height"), duration=item.get("duration"),
))
msgs = await self.tg_retry_media( msgs = await self.tg_retry_media(
lambda: self.bot.send_media_group(media=group, **self.chat_kwargs(chat_id, thread_id)) lambda: self.bot.send_media_group(media=group, **self.chat_kwargs(chat_id, thread_id))
) )
@@ -365,7 +385,10 @@ class TelegramPoster:
g = [ g = [
InputMediaPhoto(media=item["media"]) InputMediaPhoto(media=item["media"])
if item["type"] == "photo" if item["type"] == "photo"
else InputMediaVideo(media=item["media"]) else InputMediaVideo(
media=item["media"], supports_streaming=True,
width=item.get("width"), height=item.get("height"), duration=item.get("duration"),
)
for item in chunk for item in chunk
] ]
msgs = await self.tg_retry_media( msgs = await self.tg_retry_media(