# 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.

=== "Debian/Ubuntu"
    ``` { .bash .copy}
    sudo apt install ffmpeg imagemagick
    ```

=== "Arch"
    ``` { .bash .copy}
    sudo pacman -S ffmpeg imagemagick
    ```

=== "Fedora"
    ``` { .bash .copy}
    sudo dnf install ffmpeg imagemagick
    ```

=== "macOS"
    ``` { .bash .copy}
    brew install ffmpeg imagemagick
    ```

=== "Windows"
    ``` { .powershell .copy }
    choco install ffmpeg
    choco install imagemagick
    ```
    Or, install it yourself.
    Make sure the binaries are in your PATH:

    * [gyan.dev/ffmpeg/builds/ffmpeg-git-essentials.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-essentials.7z)
    * [imagemagick.org/script/download.php](https://imagemagick.org/script/download.php#windows)

## FAQ
??? question "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.

??? question "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:
```python
from niobot import NioBot, Context, FileAttachment
from pathlib import Path
...

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

This results in the following:
![image](https://nexy7574.github.io/niobot/assets/guides/images/sending-attachments/file-send.avif)

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.

??? note "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)](https://nexy7574.github.io/niobot/assets/guides/images/sending-attachments/blurhash-hashed.avif){ loading=lazy }
![post-blurhash (after loading the image)](https://nexy7574.github.io/niobot/assets/guides/images/sending-attachments/blurhash-dehashed.avif){ loading=lazy }

??? info "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](#disabling-extra-media-features).

And here's an example:
```python
from niobot import NioBot, Context, ImageAttachment
from pathlib import Path
...


@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.

??? warning "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:
```python
from niobot import NioBot, Context, AudioAttachment
from pathlib import Path
...


@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](#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.

!!! Danger "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](#audio) for more information about audio codecs.

Here's an example:
```python
from niobot import NioBot, Context, VideoAttachment
from pathlib import Path
...


@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)
```


## Disabling extra media features
### Disabling blurhash generation
??? warning "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][]:
```python
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
??? warning "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:
```python
from niobot import NioBot, Context, ImageAttachment, VideoAttachment
...

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

### Disabling rich data
!!! danger "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][niobot.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
```python
from niobot import ImageAttachment

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

#### Videos
```python
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
```python
from niobot import AudioAttachment

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