Video Duplicate Detection
Gallery detects duplicate videos even when a copy has been re-encoded, resized, or converted to a different format. It runs the same CLIP-based model as image duplicate detection, extended to video by sampling several frames and averaging them.
How It Works
When a video is processed for Smart Search, Gallery extracts several frames and encodes each one with CLIP into an embedding vector. Those per-frame embeddings are then averaged into a single vector that stands for the whole video. Two videos with the same visual content land on nearly identical vectors, whatever their codec, resolution or bitrate.
Frame Sampling Strategy
The number of frames extracted depends on the video duration:
| Duration | Frames Extracted |
|---|---|
| Invalid / 0 | 1 frame at the start (t=0) |
| Under 2 seconds | 1 frame at the midpoint |
| 2+ seconds | 8 frames evenly spaced (5% to 95%) |
Short clips and corrupt videos still get processed this way. Longer videos get coverage spread across their whole timeline.
Matching Rules
- Videos only ever match other videos, never images.
- The duplicate detection distance threshold is the same one used for image duplicates, configurable in Administration > Machine Learning Settings.
- Byte-identical uploads per user are already blocked by checksum deduplication. Video duplicate detection catches re-encoded and resized copies that have completely different bytes but the same visual content.
Configuration
Video duplicate detection needs two features enabled in Administration > Machine Learning Settings:
- Smart Search (CLIP), so that video frames can be encoded into CLIP embeddings. On by default.
- Duplicate Detection, so that embeddings are compared to find matches. Also on by default.
If both are already on, video duplicates work with no further setup.
Settings
| Setting | Default | Description |
|---|---|---|
| Machine Learning > Enabled | On | Master toggle for all ML features. Turning it off disables Smart Search, duplicate detection, plus everything else. |
| Smart Search > Enabled | On | Enables CLIP encoding for both images and videos. |
| Duplicate Detection > Enabled | On | Enables duplicate grouping based on CLIP embedding similarity. |
| Duplicate Detection > Max Distance | 0.01 | Maximum cosine distance between two embeddings to consider them duplicates. Lower = stricter. |
Disabling
There is no video-only toggle. The duplicate detection setting covers images and videos alike. To disable all duplicate detection, turn off Duplicate Detection > Enabled. To keep image duplicates but skip videos, disable Smart Search, which also disables image smart search.
Using Video Duplicates
Reviewing Duplicates
- Go to Utilities > Duplicates.
- Video duplicates appear in the same list as image duplicates.
- Review them side by side. The comparison shows file size, resolution, codec, plus any other metadata.
- Choose to keep the highest quality version and trash the rest, or stack them together.
Re-scanning Existing Videos
To detect duplicates in videos uploaded before this feature was available:
- Go to Administration > Jobs.
- Run the Smart Search job for all assets. That generates CLIP embeddings for videos that don't have them yet.
- Run the Duplicate Detection job to find matches.
Technical Implementation
Encoding Pipeline
┌──────────────────────────────────────────────────────────────────────┐
│ Smart Search Job (per video asset) │
│ │
│ ┌─────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ ffprobe │───►│ Calculate │───►│ Extract │ │
│ │ duration │ │ timestamps │ │ frames (JPEG) │ │
│ └─────────┘ │ (8 @ 5%-95%) │ │ via ffmpeg │ │
│ └───────────────┘ └───────┬───────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ CLIP encode each │ │
│ │ frame (sequential) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Average embeddings │ │
│ │ (element-wise mean) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Upsert into │ │
│ │ smart_search table │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
- Probe:
ffprobereads the video duration from the container metadata. - Timestamp calculation: 8 evenly spaced timestamps are generated from 5% to 95% of the duration, adapted for short or invalid videos as described above.
- Frame extraction: for each timestamp,
ffmpeg -ss <t> -frames:v 1extracts a single JPEG frame into a temporary directory. - CLIP encoding: each extracted frame goes to the machine learning service's existing
/predictendpoint. Frames are encoded one at a time so the ML service is not overloaded. - Averaging: the per-frame embedding vectors are combined by element-wise mean into a single 512-dimensional vector.
- Storage: the averaged vector is upserted into the existing
smart_searchtable, the same table used for image embeddings.
Key Implementation Details
- No schema changes: video embeddings live in the same
smart_searchtable as image embeddings. One vector per asset. - No API changes: the
GET /duplicatesendpoint andDuplicateResponseDtoare type-agnostic and work with image and video duplicate groups alike. - No frontend changes: video duplicates appear in the existing Duplicates page, whose comparison UI already renders video assets.
- No ML service changes: the ML service receives individual frame images through the existing CLIP encoding endpoint.
- Temp file isolation: each job creates a unique temporary directory via
mkdtemp, so concurrent Smart Search jobs cannot collide. The directory is cleaned up in afinallyblock whether the job succeeded or failed. - Graceful degradation: if some frames fail to extract (a seek past the end of the file, say), the frames that did come out are averaged. The job only fails when every frame fails, or when
ffprobecannot read the video.
Job Flow
Video duplicate detection reuses the existing job pipeline. No new queues:
- Smart Search job: when processing a video asset,
handleEncodeClipinsmart-info.service.tsdetects the asset type and branches into the video encoding path (probe, extract frames, encode, average, upsert). - Duplicate Detection job: the existing
duplicate.service.tsqueues per-asset detection jobs.duplicate.repository.tsruns a vector similarity search filtered byasset.type, so videos only match videos. - No new configuration: the existing
duplicateDetectionadmin settings (enabled toggle, distance threshold) apply to images and videos alike.