How to Reverse Videos in Python

Learn how to reverse videos by extracting frames and loading them in the inverted order using MoviePy library in Python.
  · 5 min read · Updated nov 2021 · Python for Multimedia


Digital media such as videos created a lot of creative options where we can process video and audio files and create awesome effects with them. A lot of media processing programs and tools offer the reversing feature.

In this tutorial, you will learn how you can reverse a video in Python using the MoviePy library.

The basic idea behind the code you'll see in this tutorial, is we extract all the frames from the video using a configurable fps parameter, then we load these frames back into a video in the reverse order.

To get started, let's install the required libraries:

$ pip install tqdm moviepy numpy

Let's start now by importing our modules:

from moviepy.editor import VideoFileClip, ImageSequenceClip
import numpy as np
import os
from datetime import timedelta, datetime
from glob import glob
from tqdm import tqdm
import shutil

Next, to avoid redundancy, I'm bringing the below code from this tutorial:

# i.e if video of duration 30 seconds, saves 10 frame per second = 300 frames saved in total
SAVING_FRAMES_PER_SECOND = 30

def format_timedelta(td):
    """Utility function to format timedelta objects in a cool way (e.g 00:00:20.05) 
    omitting microseconds and retaining milliseconds"""
    result = str(td)
    try:
        result, ms = result.split(".")
    except ValueError:
        return result + ".00".replace(":", "-")
    ms = int(ms)
    ms = round(ms / 1e4)
    return f"{result}.{ms:02}".replace(":", "-")


def extract_frames(video_file, verbose=1):
    # load the video clip
    video_clip = VideoFileClip(video_file)
    # make a folder by the name of the video file
    filename, _ = os.path.splitext(video_file)
    if not os.path.isdir(filename):
        os.mkdir(filename)
    # if the SAVING_FRAMES_PER_SECOND is above video FPS, then set it to FPS (as maximum)
    saving_frames_per_second = min(video_clip.fps, SAVING_FRAMES_PER_SECOND)
    # if SAVING_FRAMES_PER_SECOND is set to 0, step is 1/fps, else 1/SAVING_FRAMES_PER_SECOND
    step = 1 / video_clip.fps if saving_frames_per_second == 0 else 1 / saving_frames_per_second
    iteration = np.arange(0, video_clip.duration, step)
    if verbose:
        iteration = tqdm(iteration, desc="Extracting video frames")
    # iterate over each possible frame
    for current_duration in iteration:
        # format the file name and save it
        frame_duration_formatted = format_timedelta(timedelta(seconds=current_duration)).replace(":", "-")
        frame_filename = os.path.join(filename, f"frame{frame_duration_formatted}.jpg")
        # save the frame with the current duration
        video_clip.save_frame(frame_filename, current_duration)
    return filename, video_clip.fps

You'll get a lot of details in the extracting frames tutorial. However, in brief, the extract_frames() function accepts the video file path as a parameter and extracts frames with the corresponding duration into a folder named after the original video file name. Finally, it returns that folder name.

Now, let's make a function that reads these extract frames in the reverse order and save them as an inverted video:

def reverse_video(frames_path, video_fps, remove_extracted_frames=True):
    frame_files = glob(os.path.join(frames_path, "*"))
    # sort by duration in descending order
    frame_files = sorted(frame_files, key=lambda d: datetime.strptime(d.split("frame")[1], "%H-%M-%S.%f.jpg"), reverse=True)
    # calculate the FPS, getting the minimum between the original FPS and the parameter we set
    saving_frames_per_second = min(video_fps, SAVING_FRAMES_PER_SECOND)
    if saving_frames_per_second == 0:
        # if the parameter is set to 0, automatically set it to the original video fps
        saving_frames_per_second = video_fps
    print("Saving the video with FPS:", saving_frames_per_second)
    # load the frames into a image sequence clip (MoviePy)
    image_sequence_clip = ImageSequenceClip(frame_files, fps=saving_frames_per_second)
    # write the video file to disk
    output_filename = f"{frames_path}-inverted.mp4"
    image_sequence_clip.write_videofile(output_filename)
    if remove_extracted_frames:
        # if set to True, then remove the folder that contain the extracted frames
        shutil.rmtree(frames_path)

The reverse_video() function expects the folder name that contains the video frames extracted by the previous function as an argument. We use the glob() function from the glob module to get all the file names of the frames.

Next, we sort those frame files by the duration in descending order. After that, we pass these frames in the reversed order to the ImageSequenceClip() object from MoviePy, and set the FPS to the minimum of SAVING_FRAMES_PER_SECOND we used in the frame extraction process and the original video FPS, the reason is when we set a higher FPS than the original video FPS, the resulting video will be speeded.

We then use the write_videofile() method to save the reversed video to a video file on the disk.

If you set the remove_extracted_frames to True (as the default), the folder where the extracted frames are located will be deleted along with its contents.

Finally, let's use these functions to accomplish our task:

if __name__ == "__main__":
    import sys
    video_file = sys.argv[1]
    frames_folder_path, video_fps = extract_frames(video_file)
    reverse_video(frames_folder_path, video_fps=video_fps)

We're done! Let's try it out with a YouTube video of a scene from TENET movie:

$ python reverse_video.py Tenet-the-breach-scene-in-forward.mp4

This is the output of the code:

Extracting video frames: 100%|██████████████████████████████████████████████████████████████████████████████████████████| 485/485 [00:10<00:00, 47.71it/s]
Moviepy - Building video Tenet-the-breach-scene-in-forward-inverted.mp4.
Moviepy - Writing video Tenet-the-breach-scene-in-forward-inverted.mp4

Moviepy - Done !
Moviepy - video ready Tenet-the-breach-scene-in-forward-inverted.mp4

And the reversed video appeared in the current directory!

Conclusion

If your video is quite long, then make sure to reduce the FPS (the SAVING_FRAMES_PER_SECOND parameter), I have set it to as low as 10, you can increase it if you feel the lag in the output video, this will increase the size of the video, as well as the execution time of the program to extract frames and load them in the reverse order.

Obviously, the output video will have no sound, you can use AudioClip() loaded from audio (maybe the audio extracted from the original video), and simply set image_sequence_clip.audio to this newly created AudioClip() object, and then continue the same process of saving the video.

You can get the demo video here, and the full code here.

Learn also: How to Combine a Static Image with Audio in Python

Happy coding ♥

View Full Code
Sharing is caring!



Read Also




Comment panel