Video

Django Cast provides video file management with poster frame extraction, dimension detection, and integration with Wagtail’s StreamField editor. The built-in upload workflows generate posters automatically.

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

The built-in admin and API upload workflows check whether a poster image already exists and, when needed, extract a single frame to use as the poster. Programmatic callers opt into the same workflow with save_video_with_derivations().

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. cast.media_derivation.save_video_with_derivations(video) persists the video and calls create_poster(). A plain Video.save() only persists fields.

  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 durably regenerate the poster for a video by clearing the existing poster and saving through save_video_with_derivations():

from cast.media_derivation import save_video_with_derivations

video.poster = None
video.poster_seconds = 5.0   # extract at 5 seconds instead
save_video_with_derivations(video)

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, persist that change, and then re-run the command or call save_video_with_derivations(video).

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.