ffmpeg/ffprobe (a build that includes the blurdetect, tile and showspectrumpic filters — confirm with the check in Step 1), exiftool, mediainfo, a shell (bash/zsh/WSL on Windows), about 1 GB of free disk space per 10 minutes of video for extracted frames.
What to do: install the toolchain, create a working directory, and confirm your ffmpeg build has the filters used later.
Why: almost every useful check — frame extraction, scene cutting, audio spectra — is a filter inside ffmpeg. If a filter is missing, later commands fail with a confusing error instead of a result, so verify first.
Install:
# Debian / Ubuntu
sudo apt update && sudo apt install -y ffmpeg libimage-exiftool-perl mediainfo
# macOS (Homebrew)
brew install ffmpeg exiftool mediainfo
# Windows (PowerShell) — confirm exact package IDs with: winget search exiftool
winget install Gyan.FFmpeg
winget install --id OliverBetz.ExifTool
Create the workspace and a fixed folder layout (all later commands assume it):
mkdir -p ~/dfcheck/{frames,scenes,audio,evidence}
cd ~/dfcheck
How to check: run the three verification commands and compare with the expected output below.
ffmpeg -version | head -n 1
exiftool -ver
ffmpeg -filters | grep -E "blurdetect|showspectrumpic|tile"
Expected: a version banner starting with ffmpeg version, a bare version number from exiftool, and three lines containing blurdetect, showspectrumpic and tile. If blurdetect is absent from that list, either upgrade ffmpeg through your package manager or skip the blur measurement in Step 2 — the guide still works without it. Exact filter availability per build is not guaranteed; check the documentation for your distribution's package.
Example: on a clean Ubuntu container after apt install ffmpeg, ffmpeg -version | head -n 1 prints something like ffmpeg version 6.1.1-3ubuntu5 Copyright (c) 2000-2023 the FFmpeg developers — the number after version is what matters, not the copyright tail.
Example (mobile): if the clip arrived on your phone and you have no laptop at hand, you can run the same ffmpeg commands inside a Linux environment on Android — see How to Run Linux on Android Without Root: Termux, PRoot, and AI Tools for the shell setup, then reuse every command below unchanged.
Finally, record a hash before you touch the file — it proves the file you analysed is the file you received:
sha256sum ~/dfcheck/evidence/incoming.mp4 | tee ~/dfcheck/evidence/hash.txt
What to do: dump the file's stream structure and all embedded tags into a report.
Why: most forwarded "deepfake" files are simply re-encoded or screen-recorded, and the container tells you that in seconds. A single video stream with no audio, an unusual encoder string, or tags that contradict the claimed source are all cheaper signals than pixel inspection.
ffprobe -v error -show_format -show_streams -of json \
~/dfcheck/evidence/incoming.mp4 > ~/dfcheck/evidence/probe.json
exiftool -a -G1 -s ~/dfcheck/evidence/incoming.mp4 \
> ~/dfcheck/evidence/exif.txt
mediainfo ~/dfcheck/evidence/incoming.mp4
Then pull the short summary you actually read:
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,nb_frames,duration \
-of default=noprint_wrappers=1 ~/dfcheck/evidence/incoming.mp4
Why this works: ffprobe reads the container index, not the picture, so it answers "how many streams, what codec, how many frames" without decoding — a one-second answer to questions a viewer cannot answer at all.
Things worth flagging in probe.json and exif.txt:
| Field | What to note |
|---|---|
| Number of streams | A "video call recording" with no audio stream is suspicious; a screen recording is not a deepfake, just a re-capture |
| `format_tags` / `encoder` | Names the writing software (e.g. `Lavf…` means an ffmpeg-based pipeline); a mismatch with the claimed device is a hint, not proof |
| `creation_time` vs `file:FileModifyDate` | Large gaps usually mean re-encoding or re-download |
| `nb_frames` vs `duration × r_frame_rate` | A mismatch means truncation or frame drops |
Example: if ffprobe reports codec_name=hevc, width=720, height=1280 and nb_frames=1820 with duration=60.6, the maths 1820 / 60.6 ≈ 30 matches r_frame_rate=30/1 — the file is internally consistent and the frames you extract in Step 2 will be evenly spaced.
How to check: probe.json exists and is non-empty, and exif.txt contains at least the File and System groups.
wc -c ~/dfcheck/evidence/probe.json && grep -c "\[System\]" ~/dfcheck/evidence/exif.txt
Expected: a byte count in the thousands and a non-zero count for [System]. If both commands return 0, the file did not open — go to Troubleshooting.

What to do: cut the video into reviewable images and separate the audio channel, then run the four checks below.
Why: deepfake artefacts are local and brief — a jaw edge that flickers for six frames, a tooth that melts during a smile. You cannot see them at 1× playback. Extracting stills at a fixed rate, plus a contact sheet, makes the artefacts visible and comparable.
Extract frames at 2 per second:
ffmpeg -v error -i ~/dfcheck/evidence/incoming.mp4 \
-vf "fps=2,scale=640:-1" ~/dfcheck/frames/frame_%04d.png
Cut scenes where the picture changes sharply (cuts, cuts to a different face):
ffmpeg -v error -i ~/dfcheck/evidence/incoming.mp4 \
-vf "select='gt(scene,0.3)',showinfo" -vsync vfr ~/dfcheck/scenes/scene_%03d.png
Build one contact sheet you can scan in a single glance:
ffmpeg -v error -i ~/dfcheck/evidence/incoming.mp4 \
-vf "fps=1,scale=320:-1,tile=4x3" -vsync vfr ~/dfcheck/frames/sheet_%02d.png
Extract audio, then draw its spectrum:
ffmpeg -v error -i ~/dfcheck/evidence/incoming.mp4 -vn -ac 1 -ar 16000 \
~/dfcheck/audio/track.wav
ffmpeg -v error -i ~/dfcheck/audio/track.wav \
-lavfi "showspectrumpic=s=1024x512:legend=1" ~/dfcheck/audio/spectrum.png
Optional blur measurement, if blurdetect is in your build:
ffmpeg -i ~/dfcheck/evidence/incoming.mp4 \
-vf "blurdetect=block_width=32:block_height=32,metadata=print:key=lavfi.blur" \
-f null - 2>&1 | head -n 20
Why this works: fps=2 gives you a deterministic, reproducible sample (the same file always yields the same frame numbers), tile packs 12 of them into one image so a flicker becomes visible as a row-to-row difference, and a spectrogram turns "the voice sounds flat" into a picture you can point at.
Read the following signals in the extracted material:
| Signal | Where to look | What is worth a second check |
|---|---|---|
| Hairline and jaw edge | Full-size frames, zoomed | A soft halo or a boundary that shifts between two consecutive frames |
| Teeth and tongue | Any laughing or speaking frame | Teeth merging into one shape, no gap between them |
| Ears and earrings | Profile or angled frames | Asymmetry that changes frame to frame, jewellery that disappears |
| Blinking | Contact sheet rows | No blink across many seconds, or a blink that looks like a wipe |
| Lip-sync | Spectrum next to the frame sheet | Audio events and mouth closures that drift apart over the clip |
| Spectrum shape | `spectrum.png` | A perfectly straight horizontal cut-off instead of natural roll-off |
Example: extracting a 60-second clip at fps=2 produces files frame_0001.png to frame_0120.png; the first contact sheet sheet_01.png holds frames 1–12, i.e. the first 12 seconds. Compare sheet_01.png and sheet_02.png side by side — a lighting or edge change exactly at the boundary is where a face swap was stitched in.
How to check: all three output sets exist and the frame count matches your rate.
ls ~/dfcheck/frames | wc -l
ls ~/dfcheck/audio
Expected: roughly 2 × duration_in_seconds PNG files in frames/, plus track.wav and spectrum.png in audio/. If audio/ is empty, the source has no audio stream — confirm with ffprobe -v error -select_streams a -show_entries stream=index -of csv=p=0 ~/dfcheck/evidence/incoming.mp4.
If you also produce synthetic video yourself and need to label it honestly, the production pipeline in How to Create Videos with AI and Remotion: Automation Guide shows where to attach a provenance note at render time instead of after the fact.

What to do: run a fixed protocol on any call where money, credentials or urgency is involved, then close the loop on an independent channel.
Why: metadata and frames decide whether a *recording* is suspicious. On a live call you cannot run ffprobe, so you rely on prompts that real-time face synthesis handles badly and on one rule that beats every detector: never trust the channel that contacted you.
Run these prompts in this order and note the reaction:
Example: a "bank security officer" calls from a spoofed number and asks you to confirm a code. You end the call, dial the number printed on the back of your card, and ask the bank to confirm whether any case exists under your name. That single step resolves the situation without any software.
How to check: you have a positive confirmation from the second channel — the person answering the official number confirms the request, or your colleague confirms on the internal chat that they called. Anything short of that is a not verified result, and the correct action is to stop the transaction.
Problem. ffprobe refuses to open the file at all.
moov atom not foundffmpeg -err_detect ignore_err -i incoming.mp4 -c copy -movflags +faststart fixed.mp4 and re-run ffprobe on fixed.mp4. If the same error appears, the source really is incomplete — request it again.Problem. Frame extraction stops immediately with a filter error.
No such filter: 'blurdetect'ffmpeg build was compiled without that filter (common on minimal distro packages and older static builds).ffmpeg -filters | grep blurdetect. If the line is absent, upgrade ffmpeg from the vendor's repository, or drop the blur step and continue with the frame sheet and spectrogram — those two carry most of the signal. Which filters ship in which build is version-dependent; check the documentation for your package.Problem. Audio extraction produces nothing.
Output file #0 does not contain any stream-vn (no video) against a file that has only a video stream — typical for screen recordings and for clips whose audio was stripped by a messenger.ffprobe -v error -show_entries stream=index,codec_type -of csv=p=0 incoming.mp4. If only video appears, obtain the original with sound; there is no audio to analyse in this copy.Hash the file, read the container with ffprobe and exiftool, then extract frames, a contact sheet and an audio spectrum before judging the picture. On a live call, skip the software entirely: use the head-turn, occlusion and knowledge prompts, then call back on the officially published number.
Can a deepfake be detected from a single screenshot?
Sometimes — melted teeth, an asymmetric ear or a soft jaw boundary may be visible — but a single frame gives you no flicker comparison, so absence of artefacts proves nothing. Always extract a sequence at a fixed rate before concluding.
Does clean metadata prove a video is genuine?
No. ffprobe and exiftool describe the container, and any container can be rewritten or stripped. Treat metadata as a filter for obviously re-encoded or truncated files, not as a certificate of authenticity.
What should I do if I already sent money after such a call?
Contact your bank immediately and request a recall, report the number and the account to the police, and preserve the evidence: the original file, hash.txt, probe.json and the call log. Do not delete the chat — timestamps and account details are what the investigation uses.
Are commercial deepfake detector services reliable enough to decide alone?
Accuracy varies by service, language and compression, and published claims are not a substitute for your own test set — check the vendor's documentation and validate on clips you know the answer for. Use a detector as one more signal next to the metadata checks and the call-back rule, never as the deciding vote.