The Problem

You record a video on your iPhone and try to set it as your Telegram video avatar. Telegram silently rejects it. No error, no dialog, just nothing happens.

The culprit is HEVC (H.265). iPhones have recorded in HEVC by default since iOS 11. Telegram's video avatar feature only accepts H.264. It doesn't tell you that.

I hit this myself, then saw it come up repeatedly in Telegram support threads. So I built a bot to handle the conversion.

What Telegram Actually Requires

After reading the docs and doing a lot of trial and error, the full spec for a Telegram video avatar is:

  • Codec: H.264 (not HEVC, not VP9, not AV1)
  • Pixel format: yuv420p
  • Resolution: 800x800 pixels, square
  • Duration: 10 seconds maximum
  • File size: 2MB maximum
  • Audio: none (video avatars are silent)

That's six constraints. Miss any one and Telegram rejects the file, usually without explanation.

The resolution is the awkward one. Phone videos are 16:9. You need a square crop centered on something useful. ffmpeg can handle all of this in a single pass.

Solving It With ffmpeg

The pipeline has two jobs: crop to square and encode to spec.

ffmpeg -i input.mov \ -vf "fps=30,scale=800:800:force_original_aspect_ratio=increase,crop=800:800,format=yuv420p" \ -c:v libx264 \ -crf 28 \ -preset fast \ -movflags +faststart \ -an \ -t 10 \ output.mp4 

What each part does:

  • fps=30 normalizes frame rate before scaling
  • scale=800:800:force_original_aspect_ratio=increase upscales to fill the 800x800 box without distortion
  • crop=800:800 cuts the center square
  • format=yuv420p ensures H.264 compatibility with all players
  • -an strips audio
  • -t 10 caps duration at 10 seconds
  • -movflags +faststart moves the moov atom to the front so the file streams correctly

The -crf 28 with -preset fast keeps a 5-10 second clip well under 2MB. For longer clips or high-motion content, -crf 32 or -crf 34 usually gets it under the limit.

The Aiogram 3 Handler

The bot accepts both video files and GIF documents. One thing to watch: Telegram converts GIFs to MP4 on upload, so they arrive as document with mime type video/mp4, not as animation.

from aiogram import Router, F from aiogram.types import Message, FSInputFile import subprocess, tempfile, os router = Router() FFMPEG_ARGS = [ "ffmpeg", "-y", "-i", "{input}", "-vf", "fps=30,scale=800:800:force_original_aspect_ratio=increase,crop=800:800,format=yuv420p", "-c:v", "libx264", "-crf", "28", "-preset", "fast", "-movflags", "+faststart", "-an", "-t", "10", "{output}" ] @router.message(F.video | (F.document & F.document.mime_type.startswith("video/"))) async def handle_video(message: Message): file = message.video or message.document with tempfile.TemporaryDirectory() as tmpdir: src = os.path.join(tmpdir, "input") dst = os.path.join(tmpdir, "output.mp4") await message.bot.download(file, destination=src) cmd = [p.format(input=src, output=dst) for p in FFMPEG_ARGS] result = subprocess.run(cmd, capture_output=True, timeout=60) if result.returncode != 0: await message.reply("Conversion failed. The file might be corrupted or in an unsupported format.") return if os.path.getsize(dst) > 2 * 1024 * 1024: await message.reply("Result is over 2MB. Try a shorter clip (under 8 seconds usually works).") return await message.reply_video( FSInputFile(dst), caption="Done. Go to Telegram Settings > Set Profile Photo > choose this video." ) 

The explicit size check after encoding matters. ffmpeg doesn't know about Telegram's 2MB limit, so it will happily produce a larger file. Better to give the user a clear message than let Telegram silently reject it again.

Packaging It as @liveavabot

I wrapped this into a hosted Telegram bot: @LiveAvaBot.

Send it any video or GIF, it runs the ffmpeg pipeline on a VPS and sends back the converted file. Round-trip is usually 5-20 seconds depending on input length.

Currently at 526 users. The main patterns I see in logs: iPhone owners setting video avatars, people converting Instagram or TikTok clips to fit the format, and a few folks who just want a looping video profile picture.

Built by me: @LiveAvaBot.

Edge Cases Worth Knowing

Portrait mode videos from newer iPhones include rotation metadata. ffmpeg respects it by default, so the output orientation is correct. Some older processing tools strip the metadata and produce sideways video.

4K input is slow on a cheap VPS. I added a pre-scale step if either input dimension exceeds 1920. It cuts encoding time by roughly 60% with no visible quality difference at 800x800 output.

Corrupted moov atoms from some Android screen recorders cause ffmpeg to throw moov atom not found. I catch that string in stderr and return a user-friendly message rather than a generic failure.

High-motion clips at crf 28 can exceed 2MB even at 8-9 seconds. The fix is a two-pass encode or bumping crf, not truncating the clip. I went with crf stepping: try 28, check size, retry at 32 if over limit.

The main takeaway from building this: Telegram's avatar spec is strict and almost entirely undocumented. I figured out the full constraint set through several days of testing. Hopefully this saves someone that time.