DEV Community

Cover image for Build a Powerful Video Processing Pipeline with AssemblyAI and Deploy it to Koyeb
alisdairbr for Koyeb

Posted on • Originally published at koyeb.com

Build a Powerful Video Processing Pipeline with AssemblyAI and Deploy it to Koyeb

In the digital era, video is the king, demanding innovative solutions for efficient processing and distribution.

This guide introduces a powerful approach using Koyeb's cloud services to build a scalable video processing pipeline. We will rely on secure protocols for video uploads, employ AI-driven tagging and classification via AssemblyAI, and leverage Koyeb's built-in CDN technology for global content distribution. Embrace the power of serverless architecture to meet the growing demand for video content, ensuring optimal performance and viewer satisfaction.

You can follow along with this guide by viewing the GitHub repositories for the video web app and the video worker service.

Requirements

Before diving into building your video processing pipeline with Koyeb, it's important to ensure that you have the necessary tools and knowledge. This section outlines the prerequisites needed to follow the upcoming guide successfully.

  • A Koyeb account will be required for deploying. It will be helpful to have a foundational understanding of its service offerings (web service and database, in this case).
  • An AssemblyAI API key to integrate AI-driven video tagging and classification capabilities. Note: You will need to add credit to your account to use the LLM features implemented in this guide.
  • Knowledge of Python programming for scripting and automation within the serverless architecture.
  • Experience with Django for developing robust, scalable web applications that interfaces effectively with back-end services.

Steps

  1. Set up the database: This step involves setting up a database service on Koyeb to store and manage video metadata. This database will be used by the web application to store and access data.
  2. Build the web application: This section guides you through developing a simple web application for video uploads along with features to view and search videos. The application is built with Django.
  3. Build the worker service: This step covers implementing a service API that processes the video uploads. The API will incorporate AI technologies from AssemblyAI for tagging, classifying, and potentially transcoding videos. Additionally, the API will utilize Koyeb's autoscaling features to efficiently manage varying video processing loads.
  4. Integrate with Koyeb's edge network: This section details how to integrate the web application with Koyeb's edge network to enhance the distribution of video content globally.

Set up the database

Setting up the database is an important part of this process. This database will be used to keep and organize video metadata, such as video titles, descriptions, lengths, file types, and other important information.

For this article you are going to setup a PostgreSQL database using Koyeb's recent fully-managed serverless PostgreSQL databases feature.

Here's a step-by-step guide on how to set up the database on Koyeb:

  1. In the Koyeb control panel, click Create Database Service.
  2. Choose alternatives to or confirm the provided defaults for the name and role fields.
  3. Choose the region closest to you or your users.
  4. Select the database size. If you are not already using it, you can deploy the Free tire as the Instance type.
  5. Click Create Database Service.

Once the database is created, access the database's detail page and there you can check the connection details.

Since you will be using Django later, select Django and copy the database connection details. Store this locally somewhere so that you can reference it later. It will look like something like this:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'koyebdb',
        'USER': 'koyeb-adm',
        'PASSWORD': <YOUR_DB_PASSWORD>,
        'HOST': '<YOUR_DB_URL>.eu-central-1.pg.koyeb.app',
        'OPTIONS': {'sslmode': 'require'},
    }
}
Enter fullscreen mode Exit fullscreen mode

Once you've completed these steps, your database will be ready to store and manage video metadata for your application.

Build the web application

This part of the guide will show you how to make a simple web application using Django and deploy it to Koyeb. This app will let users upload videos and also provide features to watch for these uploaded videos.

The focus of this article is the end-to-end process, so in this section we will only highlight the code relevant to this process. You can check the full source code in the project's GitHub repository.

Create a virtual environment and initialize a new project

To get started, create a project directory and then initialize a new virtual environment inside by typing:

mkdir example-video-app
cd example-video-app
python3 -m venv venv
Enter fullscreen mode Exit fullscreen mode

Activate the new virtual environment by typing:

source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Install Django within the virtual environment. We'll also install some additional libraries and packages that we'll use in the application while we're here:

pip install Django requests python-decouple psycopg2-binary Pillow
Enter fullscreen mode Exit fullscreen mode

Save the project's dependencies to a requirements.txt file by typing:

pip freeze > requirements.txt
Enter fullscreen mode Exit fullscreen mode

With Django installed, create a new Django project called VideoApp rooted in the existing project directory (be sure to include the trailing dot to avoid creating an extra directory hierarchy):

django-admin startproject VideoApp .
Enter fullscreen mode Exit fullscreen mode

Next, create a new Django application called App that the project will incorporate:

python manage.py startapp App
Enter fullscreen mode Exit fullscreen mode

This will create a new App directory alongside the existing VideoApp directory.

Set up the app models

In the App/models.py file, you can now define the Django models for the video metadata. Replace the current contents with the following:

# File: App/models.py
from django.db import models


# Model to store video metadata
class Video(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField()
    video_file = models.FileField(upload_to='videos/')
    uploaded_at = models.DateTimeField(auto_now_add=True)
    duration = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    size = models.CharField(max_length=10, null=True, blank=True)

    def __str__(self):
        return self.title


# Model to store video related tags
class Tag(models.Model):
    name = models.CharField(max_length=50)
    video = models.ManyToManyField(Video, related_name='tags')

    def __str__(self):
        return self.name


# Model to store video classification
class Category(models.Model):
    name = models.CharField(max_length=50)
    video = models.ManyToManyField(Video, related_name='categories')

    def __str__(self):
        return self.name
Enter fullscreen mode Exit fullscreen mode

Here you are defining three database models: Video, Tag, and Category.

The Video model is used to store video metadata, including the title, description, video file, upload date and time, duration, and size.

The Tag model is used to store video-related tags. It has a many-to-many relationship with the Video model, meaning that a video can have multiple tags and a tag can be associated with multiple videos. The related_name attribute specifies the name of the reverse relation from the Video model back to the Tag model.

The Category model is used to store video classifications. It also has a many-to-many relationship with the Video model, meaning that a video can belong to multiple categories and a category can contain multiple videos. The related_name attribute specifies the name of the reverse relation from the Video model back to the Category model.

Create a video upload form

In order to be able to upload any videos, you need to have a form defined in Django, let's now create that in App/forms.py:

# File: App/forms.py
from django import forms
from .models import Video, Tag, Category


# Form for video upload
class VideoForm(forms.ModelForm):
    class Meta:
        model = Video
        fields = ['title', 'description', 'video_file']
Enter fullscreen mode Exit fullscreen mode

The VideoForm is a ModelForm, a special form created from a Django model. In this case, the Video model is used.

The Meta class inside VideoForm is used to specify additional metadata for the form. The model attribute indicates which Django model this form is associated with, and the fields attribute is a list of model fields that should be included in the form. In this case, the form includes fields for title, description, video_file.

The tags and categories, as well as duration and size will be filled in later on with the results from the worker service API.

This form will allow users to input data for these fields, which will then be saved as a new Video object in the database when the form is submitted.

Configure the App views

With the form in place, next you can create the view that will handle the form submission. Replace the contents of App/views.py with the following:

# File: App/views.py
import requests
from decouple import config
from django.shortcuts import render
from App.forms import VideoForm
from App.models import Tag, Category, Video


# View to handle video upload from the video upload form
def upload_video(request):
    form = VideoForm()
    if request.method == 'POST':
        form = VideoForm(request.POST, request.FILES)
        if form.is_valid():
            # Save the video
            form.save()
            # Process the video file with the worker
            video_url = config("DOMAIN") + form.instance.video_file.url
            worker_url = config("WORKER_URL") + "/process_video?video_url=" + video_url
            # Send a GET request to the worker URL
            response = requests.get(worker_url)
            if response.status_code == 200:
                # Get the response data
                response_data = response.json()
                # Save the video tags
                tags = response_data.get('tags')
                for tag in tags:
                    tag, created = Tag.objects.get_or_create(name=tag)
                    form.instance.tags.add(tag)
                # Save the video categories
                categories = response_data.get('categories')
                for category in categories:
                    category, created = Category.objects.get_or_create(name=category)
                    form.instance.categories.add(category)
                # Save the video duration and resolution
                form.instance.duration = response_data.get('duration')
                form.instance.size = response_data.get('resolution')
                form.instance.save()
            # Return a success message
            return render(request, 'upload_video.html',
                          {'form': form, 'message': 'Video uploaded successfully!'})
        else:
            # Return the form with errors
            return render(request, 'upload_video.html', {'form': form,
                                                         'message': 'Error uploading video!'})
    return render(request, 'upload_video.html', {'form': form})


def list_videos(request):
    videos = Video.objects.all()
    return render(request, 'list_videos.html', {'videos': videos})
Enter fullscreen mode Exit fullscreen mode

The upload_video view function handles the video upload process. When a user submits the video upload form, this function is called to process the form data. It starts by creating an instance of the VideoForm with the form data and files. If the form is valid, it saves the video, processes the video file with a worker service, and saves the video metadata (tags, categories, duration, and resolution) returned by the worker service API. If the form is not valid, it returns the form with errors. If the request method is not POST, it simply renders the video upload form.

The list_videos view function retrieves all videos from the database and renders them in a template.

Create the application templates

In order for this view to work, you will need to create the HTML template. Create a new directory for HTML templates:

mkdir App/templates
Enter fullscreen mode Exit fullscreen mode

Inside, create a new file at App/templates/upload_video.html with the following content:

<!-- File: App/templates/upload_video.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Video App</title>
  </head>
  <body>
    <h1>Video App</h1>

    <p><a href="{% url 'list_videos' %}">Back to Video list</a></p>

    <h3>Upload your video</h3>
    <form method="post" enctype="multipart/form-data">
      {% csrf_token %} {{ form.as_p }}
      <button type="submit">Upload</button>
    </form>

    {{ message }}
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

This HTML markup creates a simple web page for uploading videos.

The video upload form is created using the HTML <form> tag. The method attribute is set to post, which means the form data will be sent to the server using the HTTP POST method. The enctype attribute is set to "multipart/form-data", which is necessary for forms that allow file uploads. The {% csrf_token %} template tag is used to protect against cross-site request forgery attacks.

When the form is submitted, the data is sent to the server and handled by the upload_video view function in the views.py file. As mentioned earlier, if the form is valid, the video is saved and processed by the worker service, and a success message is displayed. If the form is not valid, an error message is displayed.

You will also need an HTML template to list the videos. Add the following to a App/templates/list_videos.html file:

<!-- File: App/templates/list_videos.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Video App</title>
</head>
<body>

    <h1>Video App</h1>

    <p><a href="{% url 'upload_video' %}">Upload New Video</a></p>

    {% for video in videos %}

        <video controls width='50%' height='50%'>
            <source src="{{ video.video_file.url }}" type="video/mp4"></source>
        </video>
        <h2>{{ video.title }}</h2>
        <p>Description: {{ video.description }}</p>
        <p>Tags:
            {% for tag in video.tags.all %}
                {{ tag.name }},
            {% endfor %}
        </p>
        <p>Categories:
            {% for category in video.categories.all %}
                {{ category.name }},
            {% endfor %}
        </p>
        <p>Duration: {{ video.duration }} seconds</p>
        <p>Resolution: {{ video.size }}</p>

        <p>-----</p>
    {% endfor %}

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The {% for video in videos %} template tag is used to loop through the list of videos passed from the list_videos view function in the views.py file. For each video, it displays the video player, title, description, tags, categories, duration, and resolution.

The <video> element embeds a video player in the web page. The controls attribute is used to display the video controls, such as play, pause, and volume. The width and height attributes are used to set the size of the video player. The <source> element is used to specify the video file and its MIME type.

The {{ video.title }}, {{ video.description }}, {{ video.duration }}, and {{ video.size }} template tags are used to display the video metadata.

The {% for tag in video.tags.all %} and {% for category in video.categories.all %} template tags are used to loop through the list of tags and categories associated with the video. The {{ tag.name }} and {{ category.name }} template tags are used to display the name of each tag and category.

When the web page is loaded, the list_videos view function in the views.py file is called to retrieve the list of videos from the database and pass it to the template. The template then loops through the list of videos and displays them on the web page.

Define the URL routing

To access the views and HTML templates, you need to define the URLs, which you can add to App/urls.py:

# File: App/urls.py
from django.urls import path
from App import views


urlpatterns = [
    path('', views.list_videos, name='list_videos'),
    path('upload', views.upload_video, name='upload_video'),
]
Enter fullscreen mode Exit fullscreen mode

The urlpatterns list contains two path objects that map URLs to view functions. The first path object maps the root URL ('') to the list_videos view function and assigns it the name 'list_videos'. This means that when a user navigates to the / URL of the application, the list_videos view function will be called to handle the request and render the appropriate response.

The second path object maps the 'upload' URL to the upload_video view function and assigns it the name 'upload_video'. This means that when a user navigates to the /upload URL, the upload_video view function will be called to handle the request and render the appropriate response.

Hook the new App/urls.py file in to the project URL processing by editing the VideoApp/urls.py file as follows:

{/* prettier-ignore-start */}

# File: VideoApp/urls.py
from django.contrib import admin
from django.urls import path  # [!code --]
from django.urls import include, path  # [!code ++]
from django.conf import settings  # [!code ++]
from django.conf.urls.static import static  # [!code ++]

urlpatterns = [
    path('', include("App.urls")),  # [!code ++]
    path('admin/', admin.site.urls),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)  # [!code ++]
Enter fullscreen mode Exit fullscreen mode

{/* prettier-ignore-end */}

Adjust the project configuration

Hook the application up with the project by editing the VideoApp/settings.py file and adding the AppConfig instance declared in the App/apps.py file to the list of INSTALLED_APPS:

{/* prettier-ignore-start */}

# File: VideoApp/settings.py
. . .

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'App.apps.AppConfig',  # [!code ++]
]

. . .
Enter fullscreen mode Exit fullscreen mode

{/* prettier-ignore-end */}

You also need to make sure that we connect the Django project to the PostgreSQL database defined earlier. Also in the VideoApp/settings.py file, edit the DATABASES dictionary to take parameterized values from environment variables:

# File: VideoApp/settings.py
. . .

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': config("DJANGO_DB_NAME"),
        'USER': config("DJANGO_DB_USER"),
        'PASSWORD': config("DJANGO_DB_PASSWORD"),
        'HOST': config("DJANGO_DB_HOST"),
        'OPTIONS': {'sslmode': 'require'},
    }
}

. . .
Enter fullscreen mode Exit fullscreen mode

We'll finish the configuration by setting some required variables and configuring static files. First, add some new imports to the top of the file:

{/* prettier-ignore-start */}

# File: VideoApp/settigns.py
import os  # [!code ++]
from pathlib import Path

from decouple import config  # [!code ++]
. . .
Enter fullscreen mode Exit fullscreen mode

{/* prettier-ignore-end */}

Next, create or set the following variables:

# File: VideoApp/settings.py
. . .

SECRET_KEY = config("DJANGO_SECRET_KEY")
DEBUG = True

ALLOWED_HOSTS = config("ALLOWED_HOSTS", cast=lambda v: [s.strip() for s in v.split(',')])
DOMAIN = config("DOMAIN")
CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", cast=lambda v: [s.strip() for s in v.split(',')])

. . .
Enter fullscreen mode Exit fullscreen mode

The ALLOWED_HOSTS variable is used to set a list of allowed host names for the application. This is a security feature that prevents host header attacks.

The DOMAIN variable is used to set the domain name of the application. This is used in the upload_video view function to construct the URL for the worker service.

The CSRF_TRUSTED_ORIGINS variable is used to set a list of trusted origins for the Cross-Site Request Forgery (CSRF) protection in Django. This is a security feature that prevents malicious websites from making unauthorized requests to the application.

Finally, configure the static file configuration by adding the following:

# File: VideoApp/settings.py
. . .

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

. . .
Enter fullscreen mode Exit fullscreen mode

Now, you can create and run the migrations, with:

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

The main page and the video upload form are now complete and should render if you run the test server with the expected environment variables configured. To actually upload videos, however, you need to create the associated worker service.

To finish up with the web app, create a new repository on GitHub. Afterwards, initialize a git repository in the project root, download a basic Python .gitignore file, and push the changes:

git init
curl -L https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore -o .gitignore
echo "videos" >> .gitingore
git add :/
git commit -m "Initial commit"
git remote add origin git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPOSITORY>.git
git branch -M main
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Build the worker service

You will build a second application with FastAPI that will be responsible for processing uploads.

Outside of the Django project directory, create a new project directory for the worker API service. Deactivate any existing virtual environments and create a new virtual environment:

deactivate  # If you're not currently in a virtual environment, this command will fail. This is expected.
mkdir example-video-worker
cd example-video-worker
python3 -m venv venv
Enter fullscreen mode Exit fullscreen mode

Activate the new virtual environment by typing:

source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Create a requirements.txt file with the service's dependencies:

# File: requirements.txt
fastapi
assemblyai
python-decouple
requests
moviepy
uvicorn
Enter fullscreen mode Exit fullscreen mode

Install the dependencies by typing:

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

The important packages installed here are assemblyai, which is a speech recognition and natural language processing API, and moviepy, which is a library for video editing and processing.

Create a .env file to define your AssemblyAI API key as an environment variable:

# File: .env
ASSEMBLYAI_API_KEY=<YOUR_ASSEMBLYAI_API_KEY>
Enter fullscreen mode Exit fullscreen mode

You will need an API key from AssemblyAI, which you can get here. You will need to sign up for an account if you don't have one. To use the AI features (like LEMUR) you will need to add credits to the account.

Then you can create your main.py file inside the FastAPI project:

# File: main.py
import requests
from decouple import config
from fastapi import FastAPI
import assemblyai as aai
from moviepy.video.io.VideoFileClip import VideoFileClip
import os


# Set up the AssemblyAI client
aai.settings.api_key = config("ASSEMBLYAI_API_KEY")
transcriber = aai.Transcriber()


# Define a function to download a video file from a URL
def download_video(url, filename):
    # Send a GET request to the URL
    response = requests.get(url, stream=True)
    # Check if the request was successful
    if response.status_code == 200:
        # Open a local file in binary write mode
        with open(filename, 'wb') as file:
            # Write the content of the response to the file in chunks
            for chunk in response.iter_content(chunk_size=8192):
                file.write(chunk)


# Define a function to extract audio from a video file
def extract_audio_from_video(video_file_path, output_audio_path):
    # Load the video file
    video = VideoFileClip(video_file_path)
    # Extract the audio from the video
    audio = video.audio
    # Write the audio to a file
    audio.write_audiofile(output_audio_path)
    # Close the video file to free up resources
    video.close()


# Define a function to get the resolution and duration of a video file
def get_resolution_and_duration_from_video(video_file_path):
    # Load the video file
    video = VideoFileClip(video_file_path)
    # Get the resolution of the video
    resolution = video.size
    # Get the duration of the video
    duration = video.duration
    # Close the video file to free up resources
    video.close()
    return resolution, duration


# Create a FastAPI instance
app = FastAPI()


# Define a route handler for the default route, for health checks
@app.get("/")
async def version():
    return {"version": "v0.1"}


# Define a route handler for the /process_video route
@app.get("/process_video")
async def process_video(video_url: str):
    # Download the video file from the URL and save it locally
    print("Downloading video...")
    video_filename = "video.mp4"
    download_video(video_url, video_filename)

    # Get audio from video file with MoviePy
    print("Extracting audio from video...")
    audio_filename = "audio.mp3"
    extract_audio_from_video(video_filename, audio_filename)

    # Get resolution and duration of the video
    print("Getting resolution and duration...")
    resolution, duration = get_resolution_and_duration_from_video(video_filename)
    # Format the resolution as a string
    resolution = f"{resolution[0]}x{resolution[1]}"

    # Transcribe the audio with AssemblyAI
    print("Transcribing audio...")
    transcript = transcriber.transcribe(audio_filename)

    # Generate tags for the video
    print("Generating tags...")
    prompt_tags = ("Generate a list of tags (max 5) for this video."
                   "Return only the tags, separated by commas and nothing else.")
    result = transcript.lemur.task(prompt_tags)
    tags = result.response.replace("\n", " ").split(",")
    # Trim the tags
    tags = [tag.strip() for tag in tags]
    # Limit the number of tags to 5
    tags = tags[:5]

    # Generate the categories for the video
    print("Generating categories...")
    prompt_categories = ("Generate a list of categories (max 3) for this video."
                         "Return only the categories, separated by commas and nothing else.")
    result = transcript.lemur.task(prompt_categories)
    categories = result.response.replace("\n", " ").split(",")
    # Trim the categories
    categories = [category.strip() for category in categories]
    # Limit the number of categories to 3
    categories = categories[:3]

    # Delete the video and audio files
    print("Cleaning up...")
    os.remove(video_filename)
    os.remove(audio_filename)

    # Return the tags and categories
    print("Processing complete!")
    record = {"tags": tags, "categories": categories, "resolution": resolution, "duration": duration}
    print(record)
    return record
Enter fullscreen mode Exit fullscreen mode

This script first sets up an API key for the AssemblyAI service, which is used for transcribing audio. It then defines several functions for downloading a video file from a URL, extracting audio from a video file, and getting the resolution and duration of a video file.

Afterwards, it creates a FastAPI instance and defines two route handlers. The first route handler is for the default route (/) and simply returns the version number of the service.

The second route handler is for the /process_video route and performs the following steps:

  1. Downloads the video file from the provided URL and saves it locally.
  2. Extracts audio from the video file using MoviePy.
  3. Gets the resolution and duration of the video file.
  4. Transcribes the audio using AssemblyAI.
  5. Generates tags and categories for the video using AssemblyAI's LEMUR model.
  6. Deletes the video and audio files from local storage.
  7. Returns the generated tags, categories, resolution, and duration as a JSON object.

The assemblyai library is used for transcribing audio and generating tags and categories and the moviepy library is used for extracting audio from a video file and getting the resolution and duration of a video file.

To finish up with the worker service, create a new repository on GitHub. Afterwards, initialize a git repository in the project root, download a basic Python .gitignore file, and push the changes:

git init
curl -L https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore -o .gitignore
printf "%s\n" "*.mp4" "*.mp3" >> .gitignore
git add :/
git commit -m "Initial commit"
git remote add origin git@github.com:<YOUR_GITHUB_USERNAME>/<YOUR_GITHUB_REPOSITORY>.git
git branch -M main
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Integrate with Koyeb's edge network

Integrating with Koyeb's edge network requires nothing more than deploying the applications to Koyeb. All CDN features are enabled by default and all services are part of the service mesh network and edge network.

Let's now see how you can deploy the applications to Koyeb to build the pipeline.

Deploy the web application

You can start by first deploying the web application. For that go the Koyeb control panel and click Create Web Service:

  1. Select GitHub as your deployment method and select your GitHub project for the web application.
  2. In the Builder section, override the Run command with python manage.py runserver 0.0.0.0:8000.
  3. In the App and Service names section, configure the App name. This will impact the environment variable values you define next.
  4. In the Environment variables section, click Bulk edit and configure the following variables:
   DJANGO_DB_HOST=
   DJANGO_DB_USER=
   DJANGO_DB_PASSWORD=
   DJANGO_DB_NAME=
   ALLOWED_HOSTS=
   CSRF_TRUSTED_ORIGINS=
   DJANGO_SECRET_KEY=
   DOMAIN=
   WORKER_URL=
Enter fullscreen mode Exit fullscreen mode

Fill in the variables as follows:

  • DJANGO_DB_HOST: The hostname of the PostgreSQL database.
  • DJANGO_DB_USER: The PostgreSQL username to authenticate with.
  • DJANGO_DB_PASSWORD: The PostgreSQL password to authenticate with.
  • DJANGO_DB_NAME: The name of the PostgreSQL database to connect to.
  • ALLOWED_HOSTS: The bare hostname where this application will be deployed. It will begin with your App name followed by your Koyeb org name, a hash, and end with .koyeb.app.
  • CSRF_TRUSTED_ORIGINS: The domain where this application will be deployed. It will begin with https:// and include your App name, Koyeb org name, a hash, and end with .koyeb.app.
  • DOMAIN: The domain where this application will be deployed. It will begin with https:// and include your App name, Koyeb org name, a hash, and end with .koyeb.app.
  • DJANGO_SECRET_KEY: A secret key used for encryption by Django. You can follow the procedure in generate a secure Django secret key locally to generate a secure Django key.
  • WORKER_URL: The internal URL where your service worker will be deployed. This should take the following format: http://<WORKER_SERVICE_NAME>.<YOUR_KOYEB_ORG>.koyeb:8080. Use the name you plan to deploy your service worker under.
  1. Click Deploy.

After a couple of minutes the application should be deployed and accessible at the application's URL.

Deploy the worker service API

Next, deploy the worker API service. Navigate to the previous created application in the Koyeb control panel and click Create Service:

  1. Select GitHub as your deployment method and select your GitHub project for the worker service API.
  2. In the Builder section, override the Run command with uvicorn main:app --port 8080 --host 0.0.0.0.
  3. In the Environment variables section, configure the following environment variable: ASSEMBLYAI_API_KEY=<YOUR_ASSEMBLYAI_API_KEY>.
  4. In the Scaling section, select Autoscaling from 1 to 3 Instances. Set the number of requests per second to your desired threshold.
  5. In the Exposed ports section, deselect the Public toggle to make it only accessible from the service mesh and set the port to 8080.
  6. In the App and Service names section, set the Service name to the value you chose in the WORKER_URL variable when you deployed the Django application.
  7. Click Deploy.

After a couple of minutes the Worker Web API should be deployed.

Test the application

You can now test the web application and the worker API pipeline by accessing the web application URL and uploading a video file.

In this example, first we upload a file and fill in the title and description:

Video upload form

We can observe the different steps of the video worker API in the Koyeb logs:

Video processing logs

And finally, returning to the web application, we can see the categories and tags as well as the additional information filled in:

View categorized video

You now have a full functional working video pipeline which can automatically scale when the number of requests for the video worker API crosses the threshold.

Conclusion

This article described how to build a video processing app using FastAPI, AssemblyAI, and Django on Koyeb. We've covered everything from setting up the app and creating the FastAPI service to implementing video processing features using AssemblyAI and MoviePy.

Throughout the article, we've seen how to build a reliable and scalable app that can process videos and extract valuable metadata, such as transcriptions, tags, and categories. You can customize and extend the app to meet your specific needs and use cases.

With the skills and knowledge you've gained from this article, you can confidently create your own video processing apps and use Koyeb to deploy innovative solutions for video processing and analysis. The demand for video content and video processing is growing rapidly, so the abilities you've learned here will be extremely valuable if you want to develop advanced video processing apps.

Top comments (0)