惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
G
Google Developers Blog
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
D
DataBreaches.Net
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
爱范儿
爱范儿
罗磊的独立博客
腾讯CDC
Hugging Face - Blog
Hugging Face - Blog
博客园 - 叶小钗
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog
C
Check Point Blog
美团技术团队
宝玉的分享
宝玉的分享
Microsoft Security Blog
Microsoft Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Why iPhone Videos Fail as Telegram Avatars (and How I Fix...
liveavabot · 2026-06-23 · via DEV Community

liveavabot

The Bug You Don't Know You Have

You record a clean 9-second clip on your iPhone. You try to set it as your Telegram video avatar. Telegram accepts the file and... nothing happens. No error. No confirmation. The avatar just stays the same.

This happened to me, and then I saw it happening to friends. The problem isn't your clip. It's the codec.

iPhone shoots in HEVC (H.265) by default, wrapped in a .mov container. Telegram's video avatar system silently rejects it. No error message. No "invalid format." It just ignores the upload.

What Telegram Actually Requires

Telegram's video avatar spec is strict, and almost nobody documents it clearly. After digging through the Bot API docs and testing around 40 different input files, here's what actually matters:

  • Codec: H.264 (libx264), not HEVC/H.265
  • Resolution: exactly 800x800 pixels, square crop
  • Duration: 10 seconds max
  • File size: 2 MB max
  • Audio: must be removed (Telegram rejects some audio tracks outright)
  • Pixel format: yuv420p (not yuv420p10le, which HEVC and HDR video use)
  • Faststart flag: moov atom must be at the front for streaming

Every one of these can trip you up independently. An iPhone HEVC clip fails on at least the first three simultaneously.

The ffmpeg Pipeline

The fix is a specific ffmpeg command. Here's what I landed on:

ffmpeg -i input.mov \
  -vf "cropdetect=24:16:0,crop=iw:iw,scale=800:800:flags=lanczos,format=yuv420p" \
  -c:v libx264 \
  -crf 28 \
  -preset fast \
  -t 10 \
  -an \
  -movflags +faststart \
  output.mp4

Breaking it down:

  • cropdetect=24:16:0 scans the first few frames to find natural crop boundaries, removing letterboxing or pillarboxing if present
  • crop=iw:iw crops to a square using the detected width
  • scale=800:800:flags=lanczos scales to exactly 800x800 with a quality-preserving filter
  • format=yuv420p forces the pixel format Telegram requires
  • libx264 with crf 28 hits a good quality/size balance for under 2 MB
  • -t 10 trims to 10 seconds
  • -an strips audio
  • +faststart moves the moov atom to the front

For most iPhone clips (30fps, 1080p or 4K), this runs in 2-4 seconds on a small VPS.

The tricky part is the cropdetect pass. Without it, portrait shots or screen recordings with vertical bars look wrong at 800x800. Running a short detection pass first and feeding its output back into the filter chain gives a cleaner square crop.

In Python, I call ffmpeg in two passes: one quick probe to detect crop params, then the actual encode:

import subprocess, re, tempfile, os

def detect_crop(input_path: str) -> str:
    result = subprocess.run(
        [
            'ffmpeg', '-t', '5', '-i', input_path,
            '-vf', 'cropdetect=24:16:0',
            '-f', 'null', '-'
        ],
        capture_output=True, text=True
    )
    # cropdetect writes to stderr
    crops = re.findall(r'crop=(\d+:\d+:\d+:\d+)', result.stderr)
    return crops[-1] if crops else 'iw:ih:0:0'

def encode_avatar(input_path: str, output_path: str) -> None:
    crop = detect_crop(input_path)
    w = crop.split(':')[0]
    subprocess.run(
        [
            'ffmpeg', '-y', '-i', input_path,
            '-vf', f'crop={w}:{w},scale=800:800:flags=lanczos,format=yuv420p',
            '-c:v', 'libx264',
            '-crf', '28',
            '-preset', 'fast',
            '-t', '10',
            '-an',
            '-movflags', '+faststart',
            output_path
        ],
        check=True
    )

The aiogram 3 Handler

I use aiogram 3.x for the Telegram bot side. Video uploads come in as a Message with either video or document. Some clients send .mov as an uncompressed document rather than a video, so you need to handle both. A minimal handler:

from aiogram import Router
from aiogram.types import Message, BufferedInputFile
import tempfile, os

router = Router()

@router.message()
async def handle_video(message: Message):
    file_id = None
    if message.video:
        file_id = message.video.file_id
    elif message.document and message.document.mime_type in (
        'video/mp4', 'video/quicktime', 'video/x-matroska'
    ):
        file_id = message.document.file_id

    if not file_id:
        return

    await message.answer('Processing, hold on...')

    with tempfile.TemporaryDirectory() as tmpdir:
        input_path = os.path.join(tmpdir, 'input.mov')
        output_path = os.path.join(tmpdir, 'output.mp4')

        bot_file = await message.bot.get_file(file_id)
        await message.bot.download_file(bot_file.file_path, input_path)

        encode_avatar(input_path, output_path)

        with open(output_path, 'rb') as f:
            result_bytes = f.read()

    await message.answer_video(
        BufferedInputFile(result_bytes, filename='avatar.mp4'),
        caption='Ready. Download and set as your Telegram video avatar.'
    )

A few things worth noting:

  • iPhone .mov files often arrive as document type when sent without compression. The video/quicktime mime check catches them.
  • tempfile.TemporaryDirectory() cleans up automatically, which matters when handling dozens of uploads per day.
  • The real bot also validates file size before downloading (rejects anything over 50 MB) and sends a ChatAction.upload_video typing indicator while processing.

Packaging It as @liveavabot

I wrapped this into @LiveAvaBot, a public Telegram bot anyone can use. Send it a video or GIF, it re-encodes and returns an 800x800 H.264 MP4 ready to set as a video avatar.

The bot runs on a Hetzner CX22, aiogram 3 with long-polling, ffmpeg 6. About 155 users so far, with a handful of conversions per day.

GIF support came for free. ffmpeg handles animated GIFs the same way as video, without audio to strip. Input format doesn't matter much as long as ffmpeg can read it.

One thing I didn't expect: some users send .mp4 files that look perfectly fine but still fail as Telegram avatars. Usually it's the pixel format (yuv420p10le from a phone that shot in HDR mode) or a non-square aspect ratio with no obvious letterboxing. The pipeline handles both cases without any special-casing.

Edge Cases and What's Next

4K input. Encoding 4K source is slow on a small VPS. I added a quick ffprobe check and reject anything above 3840px with a friendly message. Most users don't need 4K source for an 800x800 output anyway.

Corrupt files. ffmpeg exits non-zero on truly corrupt input. The subprocess call is wrapped in try/except and sends an error message back to the user rather than crashing the handler.

Duration over 10s. The -t 10 flag handles trimming silently. I added a note in the reply caption when the source was longer than 10 seconds so users know the clip got cut.

Output file size. CRF 28 lands under 2 MB for 10s at 800x800 in the vast majority of cases. For high-motion clips I added a fallback second encode at CRF 32. Haven't seen a failure after that.

What's coming next: a trimming UI so users can pick which 10-second window to use instead of always taking the beginning of the clip.

Built by me. @LiveAvaBot is open to use.