Video

Django Cast provides video file management with automatic poster frame extraction, dimension detection, and integration with Wagtail’s StreamField editor.

Video Model

Videos are represented by the Video model. Each video has the following fields:

  • user – The user who uploaded the video (foreign key).

  • title – A short title for the video (up to 255 characters).

  • original – The uploaded video file (stored under cast_videos/).

  • poster – An auto-generated or manually uploaded poster image (stored under cast_videos/poster/). May be blank.

  • poster_seconds – The timestamp (in seconds) from which the poster frame is extracted. Defaults to 1.

  • tags – Tags for organising and searching videos.

The model also inherits Wagtail CollectionMember (so videos belong to collections) and TimeStampedModel (providing created and modified timestamps).

Poster Generation

Each time a video is saved, Django Cast checks whether a poster image already exists. If not, it automatically extracts a single frame to use as the poster.

Requirements

Both FFmpeg and FFprobe must be installed and available on the system PATH:

# Ubuntu / Debian
sudo apt-get install ffmpeg

# macOS
brew install ffmpeg

How It Works

  1. On Video.save(), the create_poster() method is called automatically.

  2. If a poster already exists or the class attribute calc_poster is False, the step is skipped.

  3. FFprobe is used to detect the original video dimensions (width and height), including portrait orientation handling.

  4. FFmpeg extracts a single frame at the timestamp given by poster_seconds (default 1 second) and writes it to a temporary JPEG file.

  5. The temporary file is saved to the poster field and then cleaned up.

If FFmpeg or FFprobe is not installed (or the command fails for any other reason), the error is logged and the video is saved without a poster. Poster generation never raises an exception to the caller.

You can regenerate the poster for a video by clearing the existing poster and calling create_poster():

video.poster = None
video.poster_seconds = 5.0   # extract at 5 seconds instead
video.create_poster()
video.save(poster=False)     # save without re-triggering poster generation

To create posters for all videos that are currently missing one, use the management command:

python manage.py recalc_video_posters

Note

This command only fills in missing posters. If a poster already exists it is kept. To force regeneration for a specific video, clear its poster first (video.poster = None; video.save(poster=False)) and then re-run the command or call video.create_poster().

Dimension Detection

Video dimensions are detected via FFprobe. The parser understands H.264, HEVC, and SAR-based dimension lines and automatically swaps width and height for portrait videos (detected by rotation metadata or a 9:16 display aspect ratio).

MIME Types

The get_mime_type() method returns a MIME type based on the file extension:

  • .mp4video/mp4

  • .movvideo/quicktime

  • .avivideo/x-msvideo

All other extensions default to video/mp4.

Using Videos in StreamField

Videos are added to posts via the VideoChooserBlock in Wagtail’s StreamField editor. The block uses a modal chooser that lets editors search and select existing videos or upload new ones.

In the post model the block is declared as part of the body StreamField:

from cast.blocks import VideoChooserBlock

body = StreamField([
    # ...
    ("video", VideoChooserBlock()),
    # ...
])

The VideoChooserBlock integrates with the repository pattern so that video data is prefetched efficiently when rendering pages.