Skip to content

Sending attachments

Sometimes, you want to upload attachments in your chat. Be that images, videos, or other kinds of files. NioBot supports this, and it's very easy to do.

Before you start

In order to use the majority of the features in this guide, you will need to install ffmpeg and imagemagick. These are used for thumbnail generation, and metadata detection.

You should use your package manager to install these, as they are not python packages.

sudo apt install ffmpeg imagemagick
sudo pacman -S ffmpeg imagemagick
sudo dnf install ffmpeg imagemagick
brew install ffmpeg imagemagick

choco install ffmpeg
choco install imagemagick
Or, install it yourself. Make sure the binaries are in your PATH:

FAQ

Why do I need to install ffmpeg and imagemagick?

imagemagick is actually optional - if you trust ffprobe to work with all of your images (in some cases it can fail to detect newer image formats), then you can skip installing it.

However, ffmpeg is required for all but file attachments. This is because in order to get some rich data, such as dimensions and duration, we need to use ffprobe to get this data. Furthermore, in the event imagemagick is not installed, the metadata fetcher falls back to ffprobe.

Not having these installed will result in a RuntimeError being raised when you try to send an attachment when it tries to fetch metadata. This is because the metadata fetcher will not be able to find ffprobe or imagemagick in your PATH.

Why does it take a couple of seconds for <attachment>.from_file() to return?

The from_file method (see: niobot.VideoAttachment.from_file, niobot.ImageAttachment.from_file, etc.) does a lot of heavy lifting in terms of preparing a file with all the bells and whistles for an upload. This means that it has to do a lot of processing, which may take a couple of seconds to return.


Sending:

Regular files

Here, regular files can be anything that isn't a video, image, or audio file. This includes text files, PDFs, etc. You can even send binary or pre-encrypted (why?) files if you want to.

Regular files are the simplest file type in niobot, in terms of code complexity and also features. Regular files do not support:

  • Thumbnails
  • Rich data
  • Previews

All you get from thumbnails is the file name, and the file size. That's it.

Anyway, here's how you could send an example text (foo.txt) file:

from niobot import NioBot, Context, FileAttachment
...

@bot.comand(name="upload.txt")
async def upload_txt(ctx: Context):
    """Sends a text file!"""
    attachment = await FileAttachment.from_file("file.txt")
    await ctx.respond(file=attachment)

This results in the following: image

You can then click on the file to download it!

Images

Images are a bit more complex than regular files. They support thumbnails, rich data, and previews.

Thumbnails for images

While you may think that thumbnails for images are useless, they are actually very useful for clients. Just beware though, having a larger or equal size image for your thumbnail is very counter-productive.

A valid use case for image thumbnails is for lower-resolution, likely compressed versions of the image you're sending. Paired with a blurhash, this can provide a very good "placeholder" image for people on painfully slow connections.

For your convenience, unless disabled, niobot will automatically generate a "blurhash" for your image.

A blurhash is very good for providing a "placeholder" image, as it is generated by a string of around 30 characters. This means people on super slow connections can see a pretty preview of the image (without much detail), instead of having an ugly loading spinner or outright blank space in place of a loading image.

For example:

pre-blurhash (after loading the image) post-blurhash (after loading the image)

This may slow down your image upload

Generating blurhashes, especially for large images, even more especially with a weak CPU, can be very slow. While this will not block your code execution, it means you must wait for the blurhash to be generated before you can do anything with the image.

You may want to disable this behaviour. See disabling extra media features.

And here's an example:

from niobot import NioBot, Context, ImageAttachment
...


@bot.comand(name="upload.png")
async def upload_png(ctx: Context):
    """Sends a png image!"""
    attachment = await ImageAttachment.from_file("file.png")
    await ctx.respond(file=attachment)

Audio

Audio files are actually simpler than images, however they do not support thumbnails or rich data outside of their duration.

Beware your codec!

You should aim to have your audio files as opus, vorbis, aac, flac, or mp3 encoded files, as some clients may not be able to play other formats. Also be mindful of their containers, since some (such as mkv) won't play in some clients.

Here's an example:

from niobot import NioBot, Context, AudioAttachment
...


@bot.comand(name="upload.mp3")
async def upload_mp3(ctx: Context):
    """Sends a mp3 audio file!"""
    attachment = await AudioAttachment.from_file("file.mp3")
    await ctx.respond(file=attachment)

Videos

Videos are the most complex file type in niobot. They support thumbnails, rich data, and previews.

Again though, NioBot makes this easy. All you need to do is pass a video file to VideoAttachment.from_file().

The same warnings apply as images, except for the blurhash. Blurhashes are not generated for videos. However, thumbnails are generated by default, with their own blurhashes. For simplicity, the video's auto-generated thumbnail is simply the first frame of the video.

Beware of your codec(s)!

A lot of matrix clients at the moment are simple HTML5-based clients - meaning they can only play a limited set of codecs out of the box.

You should aim to keep your video codecs as h264, vp8, or vp9, as these are the most widely supported. However, some native apps may not even support vp8/vp9. Use h264/avc when in doubt.

Look at audio's warning for more information about audio codecs.

Here's an example:

from niobot import NioBot, Context, VideoAttachment
...


@bot.comand(name="upload.mp4")
async def upload_mp4(ctx: Context):
    """Sends a mp4 video!"""
    attachment = await VideoAttachment.from_file("file.mp4")
    await ctx.respond(file=attachment)

Unsure which to use?

If you aren't sure which file type you have, you can find out the most appropriate Attachment type using niobot.attachment.which - this will return either VideoAttachment, ImageAttachment, AudioAttachment, or FileAttachment, based on the mime type of the file.

Fpr example:

import random
import niobot


...


@bot.comand(name="upload")
async def upload_mp4(ctx: Context):
    """Sends a random file!"""
    files = ("file.txt", "file.mp4", "file.mp3", "file.png")
    file_name = random.choice(files)

    attachment_type = niobot.which(file_name)
    attachment = await attachment_type.from_file(file_name)
    # ^ can also be written as `attachment = await niobot.which(file_name).from_file(file_name)`
    await ctx.respond(file=attachment)

This will upload using the appropriate file type.

Disabling extra media features

Disabling blurhash generation

This will harm the user experience

Disabling blurhash generation is a terrible idea - unless you make sure your uploads are a matter of kilobytes, you will always see blank spots while at least a thumbnail is loaded. Please consider alternative options.

for niobot.VideoAttachment and niobot.ImageAttachment:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment
...

async def foo():
    attachment = await ImageAttachment.from_file("file.png", generate_blurhash=False)
    # or for Videos
    attachment = await VideoAttachment.from_file("file.mp4", generate_blurhash=False)

Disabling thumbnail generation

This will harm the user experience

If you intend to disable thumbnail generation, you should provide your own thumbnail, or at the very least leave blurhash generation enabled.

Otherwise, while your video loads, clients will most likely just show a completely transparent box, with a loading spinner at a stretch. This leaves a massive chunk of the UI completely blank while your video loads.

for niobot.VideoAttachment only:

from niobot import NioBot, Context, ImageAttachment, VideoAttachment
...

async def foo():
    attachment = await VideoAttachment.from_file("file.mp4", thumbnail=False)

Disabling rich data

A lot of rich data fields will still require values for clients to properly render the media!

In this case, "rich data" refers to some "optional" fields in media uploads, such as height, width, duration, etc. These fields are not required for the server to accept the upload, but they are often used by clients to figure out how to properly display the media.

"Rich data" is gathered from the get_metadata function, which itself calls ffprobe/imagemagick as a subprocess. If, for whatever reason, this is undesirable, you can avoid it.

Disabling rich data is not 100% possible, but you can avoid it by passing minimal values where it would automatically be filled in:

Images

from niobot import ImageAttachment

async def foo():
    attachment = await ImageAttachment.from_file("file.png", width=0, height=0, unsafe=True)

Videos

from niobot import VideoAttachment

async def foo():
    attachment = await VideoAttachment.from_file("file.mp4", width=0, height=0, duration=0)
You may also want to consider either manually passing a thumbnail, or disabling thumbnail auto generation, as otherwise you'll still have ffmpeg/imagemagick called.

Audio

from niobot import AudioAttachment

async def foo():
    attachment = await AudioAttachment.from_file("file.mp3", duration=0)