PeerTube API Video Import: What is the Thumbnail Format?

Does anyone know in what format thumbnails are to be imported into the Peertube API? Currently, I’m using base64 but to no avail. My function down below retrieves an image from a url (in this case, it’s the argument "content url" ) and converts it into base64. I’ve tried sending the base64 with and without the headers (i.e. "data:image/jpeg;base64," ) but the PeerTube API will process neither.

import base64
import requests

def get_base64(content_url):
    response = requests.get(content_url)

    content_decoded = base64.b64encode(response.content).decode('utf-8')
    content_type = response.headers['Content-Type']

    byte_string = f'data:{content_type};base64,{content_decoded}'

    return byte_string

Thank you for reading, and if you have any answers, feel free to reply!


Did you try to just provide the thumbnail path?

Edit: Or you can maybe provide try to provide a Blob instead of a b64 string.


By quickly looking the code of the update API endpoint:

I can see that it calls the function buildVideoThumbnailsFromReq:

If I read correctly this piece of code, it reads req.files.thumbnailfile to get the file.

I think that in ExpressJS req.files is the list of uploaded files in a request. So, I think you just have to add a file thumbnailfile in your request payload (just send the raw file).

By the way, the update API documentation says that the request body must be a multipart/form-data, and that the thumbnailfile parameter is a binary one. This match with my reading of the above code.

Thank you so much for the quick response! I just tried both of your suggestions but PeerTube still won’t upload thumbnails (the size of the image I’m debugging with is 640x360). Here is my journey of trouble-shooting:

  • I used the raw response (blob) and had the function return response.content, that gave a Payload Too Large error

  • I then took content_decoded and deleted the ".decode(‹ utf-8 ›)" part and then returned that, but PeerTube didn’t take it and just auto-generated a thumbnail

  • And then I tried giving it the URL to the image, that didn’t work either

  • Just for fun I gave it literal binary (1’s and 0’s) and of course that didn’t work. It was too big of a payload.

I’m wondering what else I could I try. But again, thank you for your time and the good ideas!

Can you share your code?
Maybe it is the way you craft the request that is not right.

Here’s the code (GitHub - syhammer/Avideo2PeerTube: This is a tool that is made to migrate data from Avideo to PeerTube as the name suggests.). It’s a bit messy due to time constraints. This is where the request is sent (Avideo2PeerTube/ at main · syhammer/Avideo2PeerTube · GitHub). The base64 code is located in (utils/

Sorry, but I’m not at liberty to share the code, but I can share the function that sends the request.

def create_peertube_video(peertube_user,avideo_user,peertube_channel,avideo_video,peertube_videos):
    avideo_video_title = avideo_video['title']

    log.log(f'\t\tImporting Avideo Video "{avideo_video_title}" to PeerTube',__file__)

    max_title_length = int(peertube_config['data']['max_video_title_length'])
    avideo_video_title_clipped = avideo_video_title[:max_title_length] if len(avideo_video_title) > max_title_length else avideo_video_title

    if not(avideo_video.get('duration_in_seconds')):
        log.log(f'\t\t\tAvideo Video "{avideo_video_title}" is missing crucial data',__file__)

    if is_peertube_video(avideo_video,peertube_channel,peertube_videos):
        log.log(f'\t\t\tAvideo Video "{avideo_video_title}" already exists on PeerTube',__file__)

    if not(is_valid_date(avideo_video,peertube_channel)):
        log.log(f'\t\t\tAvideo Video "{avideo_video_title}" doesn\'t have a valid date (see "config/peertube/config.json" for allowed dates)',__file__)

    avideo_video_source_url = get_avideo_video_source_url(avideo_video)

    if not(avideo_video_source_url):
        log.log(f'\t\t\tAvideo Video "{avideo_video_title}" is not a valid media format (see "config/peertube/config.json" for allowed content formats)',__file__)

    data = {
        'channelId': peertube_channel['id'],
        'name': avideo_video_title_clipped,
        'targetUrl': avideo_video_source_url,
        'commentsEnabled': True,
        'downloadEnabled': avideo_video['can_download'],
        'language': 'en',
        'nsfw': False,
        'originallyPublishedAt': avideo_video['videoCreation'],
        'privacy': 1,
        'thumbnailfile': get_base64(avideo_video['Thumbnail']),
        'waitTranscoding': False

    if avideo_video['description'] and avideo_video['description'] != '\n\n':
        data['description'] = avideo_video['description']

    if avideo_video['donationLink']:
        data['support'] = avideo_video['donationLink']

    response ='import_video',{

    if response.status_code == 200:
        log.log(f'\t\t\tAvideo Video "{avideo_video_title}" is now importing to PeerTube',__file__)
        thumbnail_url = avideo_video['Thumbnail']
        log.log(f'\t\t\tThumbnail URL: "{thumbnail_url}"',__file__)
        log.log(f'\t\t\tCould not import Avideo Video "{avideo_video_title}"',__file__)

And what is the content of the peertube_api function?

peertube_api is a class that handles the API calls. It contains a function call which recieves a call_type and a call_data. The call type is used to retrieve all of the call’s important data (Whether it’s a GET, POST, … and then the url extenstion to that particular call). The call_data just contains everything needed for the call, whether it be data or params. The headers are automatically taken care of within the class. The data variable, in the above code, is sent straight to the API without any formatting or data-corruption along the way.

Is this class sending the data using multipart/form-data?

As explained here POST - HTTP | MDN , application/x-www-form-urlencoded is not suitable to binary data.

If your class is using multipart/form-data (and is correctly encoding the multipart data), it should work by removing the get_base64 (just send the raw data).

Before you came up with that suggestion, I was passing the file into the data argument of my POST request. In order to send the data in multipart/form-data, I had to send the file in through a different argument called files. So now I’m sending the raw file to PeerTube and this is the response I get back:

  "type": "about:blank",
  "title": "Bad Request",
  "detail": "Incorrect request parameters: thumbnailfile",
  "instance": "/api/v1/videos/imports",
  "status": 400,
  "invalid-params": {
    "thumbnailfile": {
      "msg": "This thumbnail file is not supported or too large. Please, make sure it is of the following type: .png, .jpg, .jpeg, .webp",
      "param": "thumbnailfile", "location": "body"
  "error": "Incorrect request parameters: thumbnailfile"

I renamed the get_base64 function to the more appropriate get_thumbnail. Here’s the new code here:

import requests

def get_thumbnail(content_url):
    response = requests.get(content_url)
    return response.content

So now PeerTube is actually receiving and processing the file. The file that I’m passing is a « .jpeg » but the API doesn’t recognize it as such or it’s too big, which I doubt. The image is only 640x360.

Checking the Peertube code, it can be one of these:

  • filesize > 4MB
  • incorrect mimetype: the file mimetype must be one of: 'image/png, image/jpg, image/jpeg, image/webp (you have to specify the mime type using the content-disposition header)

It finally works! After looking into how python requests manages the content-disposition, I found out how to pass the content type (image/jpeg) into the script. Thank you so much for your invaluable guidance!

1 Like

Great news!

Don’t hesitate to share the code of your peertube_api function, it could help others :slight_smile:

Will do! I’ll see if I can add it to the PeerTube docs as well.