Make Your Own Video Editor App with Python, Dash, & Moviepy!
By: Steve Kiefer
Tags: Video Editing, Web App Development, Python, Dash, Plotly

Make Your Own Video Editor App with Python, Dash, & Moviepy!

Background Video by NASA-Imagery from Pixabay

Plotly’s Dash framework is a great Python library for quickly and easily making interactive web apps without coding HTML or JavaScript! Moviepy is an excellent library for programmatically editing video files. However, out-of-the-box Dash is subject to browser storage limits which makes working with storage-hungry video files difficult. This article will cover how we can use np-8’s Dash-uploader library to get around the ~100MB–200MB browser storage limits which opens Dash apps up to very large user input files (video or data). We will also touch on thedirtyfew’s ServerSideOutput (part of dash-extensions) which allows for easy storing of data on the server. You can check out this GitHub repository which contains 3 Dash app files that I will go through below.

The Storage Issue...by Example

Ok, let's start with some data! I made a script ( st_make_data.py ) that creates a file with a json-like format, simulating sensor data that came over serial from a microcontroller. The data is just a sine and cosine curve. I used this to make a few files with increasing numbers of rows (10k, 100k, 1m, & 5m rows) which corresponded to ~0.8MB, 8MB, 82MB, & 409MB file sizes.

Here we have a simple Dash app ( user_large_data_sql.py ) that takes in the data, parses it and plots it (allowing the user to chose data for each axes). There are 2 options for uploading data: the standard Dash dcc.Store object and the Dash-uploader du.Upload object (which also uses its own dcc.Store object to old the filename).

The callback for this is a little special. It checks the context to see which upload object was fired. If the standard upload object was fired it parses the base64 object and collects the lines from the file as you would with small data. All this callback does, in the end, is enable a button for processing the data, but this is where we run into trouble with the larger files.

The smaller files work via the dcc.Upload component, but the largest file throughs a callback error

Dash-Uploader to the Rescue!

The next callback does the processing (after pressing the Process Data button). It processes the base64 data from the dcc.Upload component or opens the file on the server if it came via the Dash-uploader component. We then create an sqlite engine and write the processed dataframe data to it.

When files are uploaded via teh Dash-uploader object, even the largest file works just fine! The only issue is plotting. In the plotting callback I limit the total rows to plot to 200k so as not to overload the browser. This can be handled with resampling (for the plot) if needed but that seemed like a bit much for this example.

Easy Server Side Data Storage

In the last example I created temporary database files to store the large data on the server. The ServerSideOutput enrichment from the Dash-Extensions library makes storing data on the server much simpler. Instead of reading and writing files (and having to hang on to filenames) we just send the object back in the callback. In this case, its the processed Dataframe. The key things to note are the special ServersideOutput() at the top of the decorator and that we are just passing the df in the return statement. The user_large_data_cache.py file uses the server-side caching.

A Python Web App Video Editor

Now coding up a full-blown video editor in a web app is not practical. But automating some simple tasks for users that may not be media-editing-savvy can be helpful. Add a company logo or some text to videos. Or make a time-lapse. Maybe convert format and shrink down for adding to presentations. There could be lots of relatively simple canned editing schemes that a team could use.

With large file upload capability, we can now get video files from users onto the server for processing. Moviepy has tons of capability. The example app that we will walk through is barely going to scratch what it can do.

Using the bare-bones video editor

I’m using Dash-bootstrap-components to help with the layout and styling. The du.Upload is the dash-uploader component for large file uploads. Then I add a few dbc.FormGroups to house the user inputs. The global variable APP_ID keeps component names unique when combining multiple Dash apps. Its not needed here, just habit. I’m taking advantage of the Moviepy TextClip.list(‘font’) method to get system available fonts and populating the dropdown on initial load. At the end, I have 2 Div elements for sending the image preview (first frame of the final movie) and a video preview.

As far as callbacks go the first is the dash-uploader callback that links the local filename into the dcc.Store component. Another callback inspects the duration of the uploaded video and populates the Subclip End (s) and Video Width (px) user inputs. These can be over-ridden by the user but its nice to know the limits.

The main two callbacks are for creating the preview frame and preview (and final) video. For the preview image, I basically apply all of the video edits but instead of attaching the mask to the clip, I apply it using numpy functions before sending the array to PIL. PIL is used to write the image data to a buffer then encode to base64 string so the image data is embedded directly without having to deal with a file on disk.

Callback for creating the preview image

The video preview callback also creates the full size movie and returns the link info and enables the download button. This should probably be separated since you have to wait until both the preview and final videos are created before you can preview it. Homework!

Now I setup my Dash files a little differently. I enclose all of the callbacks in a add_dash() function which takes the Dash app object and attaches the callbacks when called. This helps when integrating multiple Dash apps together. See this article for more on that:

With this setup, I do all of the configuration inside of the if __name == ‘__main__’ statement.

Well, that’s it. I hope you found this useful and it helps you solve your large-user-file woes. Thanks for reading!