api.video

Features

Developers

Use Flask with Dropzone.js to Upload Videos Under 128MB (no client)

June 22, 2021 - Erikka Innes in video create, Video upload, JavaScript, Python

Hello Pythonistas! Today let's learn how to upload a video using Python's Flask and Dropzone.js. In this tutorial, we'll use Python (for a version where you use our new api.video client, please see: ) and Dropzone.js.

Flask is a WSGI web application framework. It makes getting started with building a web application quick and easy. It's based on the Werkzeug WSGI toolkit and the Jinja2 template engine. It's very popular for creating backends for webpages, and that's what I'll be using it for today. If you want to read more about what Flask is, check out What is Flask Python.

Dropzone.js is an opensource JavaScript library that lets you create drag n' drop file uploads with image previews (when available). It doesn't have dependencies on other libraries, and it does a lot of work for you so you can focus on styling and configuration. I'll use a very simple format in this tutorial, but you will be able to style it however you want.

Prerequisites

For this project you'll need:

Set up

To get started, you'll need to do these things:

  1. Grab the code from Flask and Dropzone.js.

  2. In my version of the project, Dropzone.js is attached using CDNs. You can also reference these using the static folder and placing dropzone.js and dropzone.css there. Just change the references in index.html if you want to do this instead.

What's in index.HTML

  1. Let's start by walking through the HTML file index.html. It's in the templates folder.
<!DOCTYPE html>
<html>
  <head>
    <title>File Upload</title>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.css">


    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script type="text/javascript">
      Dropzone.options.myDropzone = {


      // Prevents Dropzone from uploading dropped files immediately
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 10,
        maxFiles: 10,
        init: function() {
          myDropzone = this; // closure

          this.element.querySelector("button[type=submit]").addEventListener("click", function(e) {
            e.preventDefault();
            e.stopPropagation();
            myDropzone.processQueue();
          });

        }
      };
    </script>

    <style>
      .dropzone {
        box-shadow: 0px 2px 20px 0px #f2f2f2;
        border: 10px;
        padding: 10x;
        border-radius: 10px;
      }

    </style>
  </head>

  <body>
    <form action="/" class="dropzone" id="my-dropzone" method="POST">
      <div class="form-group">
        <button type="submit">Submit</button>
      </div>
      <div class="form-group">
        <label for="API Key">API Key</label>
        <input type="text" name="API Key" id="API Key" placeholder="Enter your API key" class="form-control" required/>
      </div>

    </form>

  </body>
</html>
  1. First add the scripts and stylesheets you'll need for the project. I'm using Dropzone with bootstrap. So I've added the scripts and stylesheets for that.
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.css">


    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script type="text/javascript">
  1. Next let's skip ahead to the body of the HTML code and look at the way the form is set up. I place the form between the body tags, and I'm using a simple form, so it looks like this:
  <body>
    <form action="/" class="dropzone" id="my-dropzone" method="POST">
      <div class="form-group">
        <button type="submit">Submit</button>
      </div>
      <div class="form-group">
        <label for="API Key">API Key</label>
        <input type="text" name="API Key" id="API Key" placeholder="Enter your API key" class="form-control" required/>
      </div>

    </form>

  </body>
  1. I add a form action to show where I want the information to be sent to. In this case I'm using "/" as the route. Next, I add the class dropzone. This tells Dropzone where to add the drag n' drop zone. In the form I add an ID, because I'll have to make some changes to how Dropzone is set up, and I specify that it's going to be a POST request to "/". Along with sending files, I want to send the API key, so I add a field for that, and I want everything to send when I press Submit so I add a button for that.

  2. Now let's take a look at customizing the dropzone. By default, Dropzone wants to upload your file the second you drop it in the zone. I don't want that to happen, because I'll get an error. The file (or files) need to be sent with my API key in order to be posted on the api.video servers. So the first thing I'm going to do is set up to configure some Dropzone options. In order to reference my Dropzone, I use the ID written in camelcase. The zone ID I chose is my-dropzone so that becomes myDropzone. I chose to add the script directly to the HTML in the head tags, but you can reference it from a separate file if you prefer.

<script type="text/javascript">
      Dropzone.options.myDropzone = {


      // Prevents Dropzone from uploading dropped files immediately
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 10,
        maxFiles: 10,
        init: function() {
          var submitButton = document.querySelector("#submit-all")
             myDropzone = this; // closure

          this.element.querySelector("button[type=submit]").addEventListener("click", function(e) {
            e.preventDefault();
            e.stopPropagation();
            myDropzone.processQueue();
          });

        }
      };
    </script>
  1. To prevent Dropzone from trying to upload files right away, I set autoProcessQueue to false. I want users to be able to upload multiple files, so I put true for uploadMultiple. parallelUploads I set to 10 max and I set the total number of files you can add at one time (maxFiles) to 10.

  2. Next I set up the submit button. The code here tells Dropzone to process the files after the user hits Submit. And that's it for the script! Let's set up the backend next.

What's in app.py

Here's what's set up for the backend:

from flask import Flask, render_template, request, redirect, url_for, abort
import os, requests

app = Flask(__name__)

app.config['UPLOAD_EXTENSIONS'] = ['.mov', '.mp4', '.m4v', '.jpm', '.jp2', '.3gp', '.3g2', '.mkv', '.mpg', '.ogv', '.webm', '.wmv' ]

@app.errorhandler(413)
def too_large(e):
    return "File is too large", 413

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/', methods=['POST'])
def upload_file():
    my_files = request.files
    api_key=request.form['API Key']

    # Set up variables for endpoints (we will create the third URL programmatically later)
    auth_url = "https://ws.api.video/auth/api-key"
    create_url = "https://ws.api.video/videos"    

    headers = {
        "Accept": "application/json",
        "Content-Type": "application/json"
    }

    payload = {
        "apiKey": api_key
    }
    # Send the first authentication request to get a token. The token can be used for one hour with the rest of the API endpoints.
    response = requests.request("POST", auth_url, json=payload, headers=headers)
    response = response.json()
    token = response.get("access_token")
    auth_string = "Bearer " + token

    # Set up headers for authentication
    headers_bearer = {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": auth_string
    }

    for item in my_files:
        uploaded_file = my_files.get(item)

        if uploaded_file.filename != '':
            file_ext = os.path.splitext(uploaded_file.filename)[1]
        if file_ext not in app.config['UPLOAD_EXTENSIONS']:
            abort(400)

    # Create the video container payload, you can add more parameters if you like, check the docs at [https://docs.api.video](https://docs.api.video)
        payload2 = {
            "title": uploaded_file.filename,
        }

    # Send the request to create the container, and retrieve the videoId from the response.
        response = requests.request("POST", create_url, json=payload2, headers=headers_bearer)
        response = response.json()
        videoId = response["videoId"]

    # Create endpoint to upload your video to - you have to add the videoId into the URL
        upload_url = create_url + "/" + videoId + "/source"

    # Create upload video headers 
        headers_upload = {
            "Accept": "application/vnd.api.video+json",
            "Authorization": auth_string
        }

    # Upload the file by specifying a path to it and opening it in binary, then attaching the open file to your request 

        file = {"file": uploaded_file.read()}
        response = requests.request("POST", upload_url, files=file, headers=headers_upload)


    return redirect(url_for('index'))
  1. I start with imports for features of Flask, the os library, and the requests library.

  2. Add limits to what kind of files a user can upload with the line: app.config['UPLOAD_EXTENSIONS'] = ['.mov', '.mp4', '.m4v', '.jpm', '.jp2', '.3gp', '.3g2', '.mkv', '.mpg', '.ogv', '.webm', '.wmv' ]

  3. This demo only handles files up to 128MB so let's add an error handler route (@app.errorhandler(413)).

  4. When you start up the app, I want you to see the index.html template. So that template is returned for @app.route('/').

  5. To process the file and API key, they must be retrieved from request.files and request.form. request.files can contain more than one file, so I'm assigning the files to my_files.

  6. Assign the API key from the form to api_key.

  7. You'll recognize the set up after this from previous tutorials - it's setting up to retrieve an access token with the API key, then creating the headers with the token so everything is ready to start posting files.

  8. Because my_files could have one or many files and I want to be sure to process them all, I'm using a for loop. The code here creates a video container to upload to and adds the title. It retrieves the video_id so a video can be uploade dto the container. Then it sets up the request and posts the video into the container. It will loop through every video and send it, so long as the video is under 128MB.

That's it! You now have a way to upload videos using Dropzone and Flask without using the Python client. In the next tutorial, we'll upload using the Python client. The client will allow us to automatically handle chunked uploads and handle videos of any size.

Resources

To build this tutorial, I used this documentation, and it might help you too:

  1. Handling File Uploads with Flask - A great tutorial that shows how to set up with a basic Dropzone.
  2. Dropzonejs.com - Details about how to customize Dropzone.

Erikka Innes

Developer Evangelist

Get started now

Connect your users with videos