ffmpegvideo-compressiondiscord

Compress Video for Discord: Hit 10, 50, or 500 MB on Purpose

·Javid Jamae·10 min read
Compress Video for Discord: Hit 10, 50, or 500 MB on Purpose

Your bot posts a clip, Discord returns a 413, and the clip that worked yesterday is now 2 MB too big. You bump the CRF down a notch, re-encode, and cross your fingers. That loop is the problem: CRF targets quality, not size, so it can't promise you anything about the number Discord actually checks.

Quick answer: To compress video for Discord, pick the ceiling your account really has (10 MB on free, 50 MB on Nitro Basic, 500 MB on Nitro), then compute a bitrate from it: video_kbps = (target_MiB × 8192 × 0.97 ÷ duration_seconds) − audio_kbps, and two-pass encode with ffmpeg -b:v <that>. Two-pass ABR lands within a few percent of the target every time, where -crf is a guess. If you'd rather not run FFmpeg binaries and pass-log files inside your bot process, send the same two-pass job to FFmpeg Micro as one API call and get a URL back.

Discord doesn't have a video size limit. It has four of them.

Conventional wisdom says Discord caps uploads at 25 MB, and for a couple of years that was true for everyone. The number moved. Free accounts now sit at 10 MB, Nitro Basic at 50 MB, and full Nitro at 500 MB, which is why the compressor sites you land on when you search quote 8 MB, 10 MB, 25 MB and 50 MB more or less interchangeably. They're all citing a different snapshot of the same moving ceiling.

The tier ceiling is only half of it for a bot. Server boost levels used to raise the attachment cap independently of who was posting, and Discord has changed that mapping more than once. Your bot can read premium_tier off the Guild object, but the tier-to-bytes table is not a stable contract, so hardcoding it is how you end up shipping a bot that silently breaks.

Treat the configured target as your source of truth and handle the rejection as a real branch, not an exception you log and forget:

{
  "message": "Request entity too large. Try again with a smaller body.",
  "code": 40005
}

That's an HTTP 413, and it applies to the whole multipart request, not per file. Two 6 MB attachments in one message fail a 10 MB ceiling just as hard as one 12 MB file.

The math that turns a target size into a bitrate

Target-size encoding is arithmetic, and no page in the consumer compressor crowd publishes it. A file's size is bitrate times duration, so invert it: budget the bits, subtract the audio track, leave a little room for container overhead, and hand the remainder to the video encoder.

def target_video_bitrate(target_mib, duration_s, audio_kbps=96, overhead=0.97):
    """Video bitrate in kbps that lands a file near target_mib."""
    total_kbit = target_mib * 8192 * overhead
    return int(total_kbit / duration_s - audio_kbps)

target_video_bitrate(10, 60)   # 1228 kbps for a 1-minute clip under 10 MB
target_video_bitrate(50, 600)  # 586 kbps for a 10-minute clip under 50 MB

Three details in that function matter more than they look. The 8192 is kibibits per mebibyte, because Discord counts in binary megabytes: using 8000 instead treats 10 MB as 10,000,000 bytes and hands you a file that's 4.8% over the real limit. The 0.97 covers MP4 container overhead, the moov atom, and x264's rate control slop. And the audio subtraction is not a rounding error at long durations, since 96 kbps AAC across a 10-minute clip is 7 MB on its own, leaving 3 MB of a 10 MB budget for picture.

The budget at each Discord ceiling works out like this, in total kbps before audio:

Clip length10 MB (free)50 MB (Nitro Basic)500 MB (Nitro)
30 seconds273013,650136,500
1 minute1365682768,270
5 minutes273136513,650
10 minutes1366836827
30 minutes452282276

Two-pass is the only way to hit the number

Two-pass ABR encoding measures the whole file's complexity on pass one, then distributes the bit budget across it on pass two, which is why it lands within a few percent of your target. Single-pass -b:v can miss by 10% or more on short clips because x264 is guessing at content it hasn't seen yet, and -crf makes no size promise at all.

ffmpeg -y -i input.mp4 \
  -c:v libx264 -b:v 1228k -preset slow -pass 1 -passlogfile /tmp/job-4417 \
  -vf "scale=-2:720" -an -f mp4 /dev/null

ffmpeg -y -i input.mp4 \
  -c:v libx264 -b:v 1228k -preset slow -pass 2 -passlogfile /tmp/job-4417 \
  -vf "scale=-2:720" -pix_fmt yuv420p \
  -c:a aac -b:a 96k -movflags +faststart output.mp4

The -passlogfile flag is the one people skip, and it's the one that bites bots specifically. FFmpeg writes ffmpeg2pass-0.log into the working directory by default, so two concurrent jobs in the same container read each other's statistics and produce garbage rate control. Give every job its own log path keyed on the job ID.

After the encode, check the result against the ceiling and against your own quality bar. ffprobe -v error -show_entries format=size -of csv=p=0 output.mp4 gives you bytes, and if you want to know whether the encode actually held up rather than just fit, VMAF scoring gives you a number instead of a vibe. Anything above 93 is usually indistinguishable at Discord's playback size.

When the math says post a link instead

The honest part of target-size encoding is knowing when to refuse. Below roughly 300 kbps of video, H.264 at any resolution turns into a slideshow of blocks, and shipping that clip is worse for your users than a link to the full file. Your bot should branch on the computed bitrate, not just clamp it.

A starting ladder for H.264 at 30 fps, medium motion:

  • 2500 kbps and up: keep 1080p
  • 1200 to 2500 kbps: scale to 720p
  • 600 to 1200 kbps: scale to 854x480
  • 300 to 600 kbps: scale to 640x360, and drop to 24 fps
  • Under 300 kbps: skip the encode and post a URL

That ladder is why a 10-minute clip and a 10 MB ceiling don't belong in the same message. At 136 kbps total you're below the floor at any resolution, and no amount of preset tuning fixes it. This is the same shape as the Whisper 25 MB cap and the Webflow 30 MB flat limit: a hard byte ceiling that people keep treating as a duration limit.

Pitfalls that only show up in a bot

Compressing one clip by hand and compressing clips on a queue fail in different ways. These are the ones that produce support tickets rather than bad output.

  1. Decimal versus binary megabytes. Ten million bytes is not 10 MiB. The 4.8% gap is enough to fail a file that your own size check just approved.
  2. Forgetting audio in the budget. Dropping to 64 kbps mono AAC frees 3.5 MB on a 10-minute clip. On voice content nobody will notice.
  3. Re-encoding an already-compressed clip on retry. If your retry path re-runs the pipeline on its own output, you stack generation loss. Retry from the source.
  4. Blocking the event loop. A subprocess.run on a 400 MB source inside a discord.py handler stalls every other command for the length of the encode. Run it off-process or off-box.
  5. Assuming 413 means the file is too big. Error 40005 covers the entire request body, so message content and a second attachment count against the same ceiling.

Running FFmpeg from a bot is where this gets expensive

Three separate open source projects exist purely to solve this one task: zfleeman's ffmpeg4discord, MyloBishop's discompress, and Czechball's discord-video. They all wrap the same two-pass math, and they all assume you have an FFmpeg binary, spare CPU, and somewhere to put temp files. On a Railway container or a Lambda-backed bot, you usually have none of those, and a 4K source that takes 90 seconds to two-pass encode blocks a process that's supposed to be answering slash commands.

Running the encode as a job somewhere else removes the FFmpeg install, the temp directory, and the CPU contention from your bot in one move. FFmpeg Micro takes the source URL and the encode you computed, runs it on managed infrastructure, and hands back an output URL your bot can attach or link. The API docs cover the job submit and result shape, and for anything slower than a few seconds you'll want the webhook pattern so your bot never sits in a polling loop. There are no servers to run, and the free tier covers a hobby bot's volume.

FAQ

What is the Discord video size limit right now?

Discord's upload limit is 10 MB on a free account, 50 MB with Nitro Basic, and 500 MB with full Nitro. The limit applies to the whole message payload, so multiple attachments share one budget.

Can a Discord bot upload files larger than the user limit?

A Discord bot is subject to the same per-message attachment ceiling as a user account, and there's no bot-specific exemption. Bots that need to deliver bigger files upload to object storage and post a link, or split the video into chunks that each fit under the cap.

Why do so many pages say the Discord limit is 25 MB?

Discord raised the free-tier limit from 8 MB to 25 MB in 2022 and later moved it to 10 MB, so pages published in between still quote 25 MB. Any article on this topic is quoting whichever number was true the week it was written, which is the argument for computing from a configured target rather than a remembered one.

How do I compress a video to exactly 10 MB with FFmpeg?

To hit 10 MB with FFmpeg, calculate (10 × 8192 × 0.97 ÷ duration_in_seconds) − audio_kbps to get a video bitrate in kbps, then run a two-pass libx264 encode with that value as -b:v. Two-pass lands within a few percent of the target; -crf gives you no size guarantee.

Should I use CRF or two-pass for Discord uploads?

Use two-pass ABR when the file has to fit under a byte ceiling, and CRF when quality is the constraint and size is free. CRF encodes the same footage to wildly different sizes depending on motion and grain, which is exactly the variable you can't have when Discord is going to reject anything over your limit.

If you're already computing the bitrate, the encode itself is the part worth handing off. Sign up free and run your next target-size job as one API call instead of an FFmpeg install inside your bot.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free