What this covers
What the file itself says, as distinct from what its caption says. Metadata gives you camera, time and sometimes location; container and encoder artefacts tell you the processing history; manipulation analysis suggests where to look harder. None of it is proof on its own, and all of it is easy to over-read.
Method
- Hash the file before you touch it.
shasum -a 256 evidence.jpg, recorded with the time you received it and who from. Every later claim you make is about that hash. Work on a copy. - Ask for the original. Every major platform re-encodes and strips metadata on upload, so a file pulled from a timeline has already lost most of what you want. The original off the camera or phone is a different artefact from the one in the post.
- Read metadata before anything else, because it is cheap and it either corroborates the claim or contradicts it. A contradiction is a lead; agreement is weak evidence, since metadata is trivially written.
- Inventory the container separately from the codec. The file extension, the container brand and the codec inside it are three independent facts, and a mismatch between them means a tool touched the file after capture.
- Only then run manipulation analysis. Error Level Analysis and noise residual tell you where to look, not what happened. Decide in advance what result would change your conclusion; otherwise you will find whatever you went looking for.
- Corroborate outside the file. The defensible finding is the one that survives a second, independent source — a different upload of the same footage, a shadow angle consistent with the claimed time, a satellite pass over the claimed place. The file alone almost never settles it.
- Record what you ran. Keep the exact command, its full output and the tool version alongside the hash. “exiftool showed no GPS” is not reproducible; a saved JSON dump with a version string is.
Key tools
ExifTool
The reference metadata reader and writer, and the only one worth learning properly. It parses more tag families than anything else — EXIF, XMP, IPTC, ICC, maker notes, QuickTime atoms, PDF and RIFF — and it tells you which family each tag came from, which matters because the same logical field appears in several places and they disagree when a file has been edited.
brew install exiftool # or: apt install libimage-exiftool-perl
# everything, grouped and labelled by the tag family it came from
exiftool -a -G1 -s image.jpg
# the fields that usually decide a question, in one line
exiftool -Make -Model -DateTimeOriginal -CreateDate -ModifyDate \
-GPSLatitude -GPSLongitude -Software -Orientation image.jpg
# every timestamp in the file at once: camera, GPS, filesystem, XMP
exiftool -time:all -a -G1 -s image.jpg
# GPS as a decimal pair you can paste straight into a map
exiftool -n -p '$GPSLatitude,$GPSLongitude' image.jpg
# maker notes and undecoded tags — where lens, shutter count and burst IDs hide
exiftool -U -a -G1 image.jpg
# structural sanity check: does the file match its own format spec
exiftool -validate -warning -a image.jpg
# the full set as JSON, which is what you archive next to the hash
exiftool -j -g1 -a image.jpg > image.exif.json
# a whole directory into one CSV, so you can sort a set by camera and time
exiftool -r -csv -Make -Model -SerialNumber -DateTimeOriginal -GPSPosition ./images/ > meta.csv
# embedded metadata in video streams, not just the container header
exiftool -ee -G3 -a clip.mp4
# strip everything before you publish, to protect a source
exiftool -all= -overwrite_original copy.jpg
Reading the output: Make/Model should match the claimed device, and a SerialNumber that
recurs across a set ties those files to one body. Software naming an editor means the file was
processed — not that it was faked, but that this is not the original. DateTimeOriginal is capture
time from the camera clock, which carries no timezone and is routinely wrong by hours or years;
CreateDate and ModifyDate can be later. -validate flagging a truncated or non-conforming
structure is a genuine signal that something rewrote the file.
Missing metadata is the normal case, not a red flag — platform stripping accounts for almost all of
it. The reverse error is worse: metadata that supports a convenient claim proves very little,
because exiftool writes as easily as it reads, and a forged DateTimeOriginal takes one command.
Treat supporting metadata as consistent-with, and contradicting metadata as a thread to pull.
ffprobe
Stream and container inventory for video and audio. Use it to establish what the file actually is before you argue about what it shows: how many streams, which codec, what container brand, whether the frame rate is constant, and where the keyframes fall.
brew install ffmpeg # ffprobe ships with ffmpeg
# full container and stream dump, archived next to the hash
ffprobe -v error -print_format json -show_format -show_streams video.mp4 > video.probe.json
# the container's own identity: brand, duration, and who wrote it
ffprobe -v error -show_entries format=format_name,format_long_name,duration:format_tags \
-of default=nw=1 video.mp4
# codec vs container in one view — mismatches start here
ffprobe -v error -select_streams v -show_entries \
stream=codec_name,codec_tag_string,profile,level,pix_fmt,width,height,r_frame_rate,avg_frame_rate,nb_frames \
-of default=nw=1 video.mp4
# encoder string and creation time, which usually name the producing app
ffprobe -v error -show_entries format_tags=creation_time,encoder,com.apple.quicktime.software \
-of default=nw=1 video.mp4
# frame types and timestamps: is the cadence constant, where are the I-frames
ffprobe -v error -select_streams v -show_entries frame=pts_time,pict_type \
-of csv=p=0 video.mp4 | head -50
# keyframe interval from the packet layer, cheaper than decoding frames
ffprobe -v error -select_streams v -show_entries packet=pts_time,flags,size \
-of csv=p=0 video.mp4 | awk -F, '$2 ~ /K/ {print $1}' | head -20
# is there a second audio track, a timecode track, or stray metadata streams
ffprobe -v error -show_entries stream=index,codec_type,codec_name -of csv=p=0 video.mp4
Note that pkt_pts_time was removed in ffmpeg 5 — on any current build the entry is pts_time,
and an old recipe using the former silently returns an empty column rather than erroring.
Container-versus-codec mismatch is the single most useful manipulation signal in video. A file
named .mp4 whose format_name is matroska, an iPhone clip whose major brand is not qt/mp4,
H.264 in a container the capturing device never writes, or a creation_time later than the claimed
upload — each means something re-muxed or re-encoded the file. Encoder strings are a strong
fingerprint of the producing application: a clip claimed to be straight off a phone but carrying a
desktop editor’s encoder has been through an edit, and that is a finding you can state plainly.
Variable frame rate where the device records constant, or an nb_frames that does not match
duration times frame rate, points the same way.
What this does not tell you: nothing in the stream inventory distinguishes an innocent re-encode — a messaging app, a download wrapper, a format conversion for upload — from a deliberate edit. It tells you the file is not the original, and that you should be arguing about provenance rather than about pixels.
ffmpeg
Extraction, not analysis. Use it to pull the frames you will actually look at, and to cut the segment you will cite, without silently re-encoding what you cite.
# every keyframe, decoded fast because P- and B-frames are skipped entirely
ffmpeg -skip_frame nokey -i video.mp4 -fps_mode vfr keyframes/kf_%04d.png
# frames at scene changes: 0.3-0.5 is the useful threshold range
ffmpeg -i video.mp4 -vf "select='gt(scene,0.4)',showinfo" -fps_mode vfr scenes/sc_%04d.png
# a contact sheet of keyframes, for finding the shot you need
ffmpeg -skip_frame nokey -i video.mp4 -vf "scale=240:-1,tile=5x4" -frames:v 1 sheet.jpg
# the exact frame at a timestamp, for a geolocation attempt
ffmpeg -ss 00:01:37.500 -i video.mp4 -frames:v 1 -q:v 2 frame.jpg
# cut a clip without re-encoding, so the excerpt keeps the original stream
ffmpeg -ss 00:01:30 -to 00:01:50 -i video.mp4 -c copy excerpt.mp4
# extract the audio untouched — background sound often dates or places a clip
ffmpeg -i video.mp4 -vn -c:a copy audio.m4a
# log scene-change scores to a file instead of writing frames, to find the cut points
ffmpeg -i video.mp4 -vf "select='gt(scene,0.3)',metadata=print:file=scenes.txt" -f null -
-c copy matters for anything you will publish or hand to someone else: re-encoding an excerpt
destroys the compression history that any later analysis would use, and makes your excerpt
unfalsifiable in the wrong direction. -fps_mode vfr replaced -vsync vfr in ffmpeg 5, and -vsync
was removed outright in ffmpeg 9 — it now fails with Unrecognized option 'vsync'. Any older
cheatsheet or Stack Overflow answer using it will not run.
Scene-change detection finds cuts, and cuts in material claimed to be a single continuous recording are worth explaining. It also fires on pans, flashes and camera shake, so read the list as candidates.
MediaInfo
A second opinion on container structure, with a tag vocabulary closer to how broadcast and
camera manufacturers name things. Worth running alongside ffprobe because it surfaces writing
library, muxing application and per-track UUIDs that ffmpeg’s inventory does not break out.
brew install mediainfo # or: apt install mediainfo
# human-readable, every track
mediainfo video.mp4
# full detail including container atoms and writing library
mediainfo --Full video.mp4
# machine-readable, for archiving next to the hash
mediainfo --Output=JSON video.mp4 > video.mediainfo.json
# just the fields that identify the producing tool
mediainfo --Inform="General;%Format%|%Encoded_Application%|%Encoded_Library%|%Encoded_Date%" video.mp4
# batch a directory and diff the writing libraries across a set
mediainfo --Output=CSV ./clips/ > clips.csv
Where ffprobe and mediainfo disagree about the container, that disagreement is itself
informative: it usually means the file is malformed or was written by something that does not
follow the spec.
FotoForensics
Web-only Error Level Analysis, JPEG quality estimation and metadata, run on an upload. The useful part is not the ELA image, it is the combination of the quality estimate and the metadata panel on the same screen.
What you do: paste a URL or upload the file at fotoforensics.com. Read the panels in this order.
1. Metadata confirm it matches what exiftool told you; if it differs, the
upload was re-encoded in transit and the analysis is of the wrong file
2. JPEG Quality a quality estimate well below the camera's normal output means
at least one re-save; "last saved at ~75%" is a provenance fact
3. ELA uniform noise across a single-source photo is the expected result;
look for a region whose edge brightness differs sharply from
identically-textured regions elsewhere in the same frame
4. Hidden Pixels cropped-out image data still present in the file
Capture for the record: the analysis URL, a screenshot of each panel, and the quality estimate as a number. The URL alone is not enough, because the site’s analysis can change.
This site publicly retains uploads. Anything you submit becomes part of a public corpus. Do not put a source’s photograph, an unpublished image or anything identifying a person at risk into it — use Forensically instead, which runs locally in the browser.
ELA is the most over-interpreted technique in this field. Differing compression across regions has mundane causes: resizing, text overlay, a logo burned in, successive saves, a screenshot of a screenshot. ELA tells you where to look harder. It never tells you a region was pasted in, and an ELA image with arrows drawn on it is not a finding.
Forensically
The same family of analyses, running entirely client-side in your browser, so the file never leaves your machine. That makes it the right default for sensitive material, and it adds clone detection and a noise view that FotoForensics does not.
Open 29a.ch/photo-forensics and drop the file in. The panels worth your time:
Clone Detection finds regions similar to other regions in the same image; raise the
threshold until repetitive texture (foliage, gravel, sky) stops matching
itself, then look at what still matches
Noise Analysis a pasted region often carries a different sensor noise floor; flat or
smoothed patches in an otherwise noisy frame are the signal
Level Sweep sweeps the brightness range to expose banding and edges hidden in
shadow or blown highlights
Luminance Gradient surfaces inconsistent lighting direction across objects that should
share one light source
Magnifier pixel-level inspection for resampling edges around inserted text
Shut the browser tab when you are done and nothing has been transmitted. Noise analysis degrades badly on anything heavily compressed or resized, which is most images sourced from social media — if the file has been through a platform, expect all of these panels to be inconclusive and say so rather than straining to read them.
InVID / WeVerify plugin
A browser extension that bundles the video-specific steps: keyframe extraction from a platform URL without downloading the file yourself, reverse image search of those keyframes across several engines in one click, a Twitter/X timeline search by time window, and a magnifier.
Install from weverify.eu/verification-plugin. It is
maintained as part of an EU research project, so expect individual platform integrations to break
when those platforms change their markup; the keyframe and reverse-search functions are the durable
parts. Use it for triage, then come back to ffmpeg and exiftool for anything you intend to
publish, because the plugin does not give you a reproducible command or a hash.
Hashing and the record
The thing that makes a forensic finding defensible is not the analysis, it is the chain from the file you were given to the file you analysed.
# the hash you cite, computed before any processing
shasum -a 256 evidence.mp4 | tee evidence.sha256
# hash a whole set, so a later re-download can be compared
find ./evidence -type f -exec shasum -a 256 {} \; > manifest.sha256
# verify nothing drifted while you worked
shasum -a 256 -c manifest.sha256
# perceptual hash, for matching re-encodes of the same footage across platforms
ffmpeg -i a.mp4 -i b.mp4 -filter_complex "signature=nb_inputs=2:detectmode=full" -f null -
Record alongside the hash: where the file came from and when, the archive copy (see archiving and evidence), the tool versions, and the exact commands with their output. A cryptographic hash and a perceptual hash answer different questions — the first proves you analysed the file you were given, the second lets you say two differently-encoded uploads are the same recording.
Further viewers and one-pass analysers
The long tail, useful when you want a second opinion or have no shell to hand.
| Tool | What it does |
|---|---|
| Reveal Image Verification Assistant | Runs several forensic filters in one pass and produces a report, which saves time on triage; the filters are the same family as Forensically’s, so treat agreement between them as one result, not two. |
| Jimpl | Browser EXIF and GPS viewer with a map pin. Quick, and fine for a file you do not mind uploading. |
| xIFr | Detailed EXIF viewer that decodes maker notes, which is the one thing a casual viewer usually drops. |
| metadata2go | Metadata for both images and video in a browser, when you cannot install anything. |
| DeepFake-O-Meter | Academic ensemble of synthetic-media detectors. Reports a score, not a verdict, and false positives on compressed footage are common — never cite it alone. |
Any of these that works by upload has the same exposure problem as FotoForensics: assume the file is retained. For sensitive material the only safe tools are the local ones.
Facial recognition
Searching a face across the web, rather than searching an image. Treat this as a different category of act from reverse image search: it identifies a person, the error rate is not zero, and a false match aimed at the wrong human causes real harm. Several of these are barred or restricted in some jurisdictions, and uploading someone’s face to a commercial service hands that service biometric data about them.
Use it to corroborate an identity you already have reason to suspect, not to generate one from nothing, and never treat a match as confirmation on its own.
| Tool | What it does | Cost |
|---|---|---|
| Amazon Rekognition | Check how similar two faces are | free |
| Azure AI Video Indexer | AI video tool for facial detection and other types of insights. | paid |
| Face Comparison by ToolPie | Compares two human face photos to determine similarity. | free |
| FaceCheck.ID | A facial recognition search engine that tries to find photos of people that look similar to a person of interest. | paid |
| PimEyes | An AI-powered facial recognition reverse image search tool. | paid |
| Search4Faces | Upload the picture of a face and find pictures of similar looking people on VKontakte, Odnoklassniki, TikTok and Clubhouse. | free |
Tool reference
| Tool | What it does | Cost |
|---|---|---|
| Am I Real? | A simple online tool that allows users to check whether a photo might have been generated by ThisPersonDoesNotExist.com. | free |
| AutoStitch | Autostitch is a free tool for seamlessly combining multiple photos into a single panoramic image, making it ideal for creating wide-angle photography… | free |
| Cleanup.Pictures | Web tool for quickly removing objects from an image. | free |
| DeepFake-O-Meter | DeepFake-O-Meter is an online tool designed to detect deepfake media and help users differentiate between genuine and manipulated content. | free |
| ExifPurge | EXIF Purge is a small portable application to remove EXIF metadata from multiple images at once. With the click of a button you can remove the camera… | free |
| fdupes | Github - Locating exact matches of duplicate files. | free |
| Hugin | Hugin is a free and open-source panorama photo stitching and HDR (High Dynamic Range imaging) merging software that helps users create seamless panoramic… | free |
| InVID | A toolkit that supports the verification of videos and images. | free |
| Irfanview | Windows-based software to extract metadata. | free |
| metadata2go | Check metadata for both photos and videos online. | free |
| PureRef | Image workspace; lets you arrange images in groups, organize them, etc. | free |
Pitfalls
- Metadata is trivially forged.
exiftoolwrites as easily as it reads. Metadata that supports a claim is weak evidence; metadata that contradicts one is a lead. - Platform stripping destroys the evidence you want. Always ask for the original file.
- Clock error is normal. Do not build a timeline on an unverified camera timestamp.
- Facial recognition on found images raises real risk of misidentifying someone. Corroborate before acting, and consider whether identification is necessary at all.
Worked example
A 40-second clip arrives by Telegram, captioned as filmed that morning on a phone at a named street corner.
# 1. hash first, work on a copy
shasum -a 256 clip.mp4
# 9f2c...e41b — recorded with "received 2026-03-12 09:14 UTC from <source>"
# 2. metadata: no EXIF, no GPS, one QuickTime atom
exiftool -a -G1 -s -ee clip.mp4 | grep -iE 'software|encoder|create|model'
# [QuickTime] CreateDate : 2026:03:11 22:41:08
# [QuickTime] Encoder : Lavf60.16.100
Lavf is ffmpeg’s muxer, not a phone. The clip was written by a desktop or server-side tool, so
whatever it shows, this file is not the camera original — and CreateDate is the evening before
the claimed morning.
# 3. container inventory: does the shape match a phone capture
ffprobe -v error -select_streams v -show_entries \
stream=codec_name,codec_tag_string,pix_fmt,r_frame_rate,avg_frame_rate,nb_frames \
-of default=nw=1 clip.mp4
# codec_name=h264 codec_tag_string=avc1 pix_fmt=yuv420p
# r_frame_rate=30/1 avg_frame_rate=2997/100 nb_frames=1198
Constant 30 declared, 29.97 actual — a broadcast-standard rate a phone does not record. Two independent signals now say re-encoded.
# 4. find the cuts, because the caption says one continuous take
ffmpeg -i clip.mp4 -vf "select='gt(scene,0.4)',metadata=print:file=scenes.txt" -f null -
# 3 scene changes at 11.2s, 24.6s, 31.0s
# 5. pull the frame with the readable shopfront
ffmpeg -ss 00:00:26 -i clip.mp4 -frames:v 1 -q:v 2 frame26.jpg
Three cuts in a clip described as one take is the finding; the frame is the pivot. Reverse image
search on frame26.jpg (see reverse image search) returns
the same shopfront in a news photo dated fourteen months earlier, and
geolocation puts the corner two streets from the one named. Shadow
direction in the frame is consistent with late afternoon, not morning.
What you can assert: the file was produced by ffmpeg, contains at least three edits, is at
least fourteen months old in part, and shows a different corner than claimed — each tied to a named
command, its output, and the hash 9f2c...e41b. What you cannot say from the file alone is who
assembled it or why. That needs the second independent source: the earlier news photo, which is
what actually carries the date.
What would falsify it: the earlier image proving to be a later backdated copy, the scene-change threshold firing on flashes rather than edits, or the analysed hash not matching the preserved file. The fourteen-month bound rests on the independently dated earlier image; the ffmpeg encoder and frame-rate evidence establish re-encoding, not the age of the depicted footage.
Broader catalogues
More tools
Further tools for this area from the OSINT Newsletter Tools Library (Image and Video Analysis OSINT), excluding those already listed above.
| Tool | What it does |
|---|---|
| Crowd Counter | Estimates crowd sizes from images. |
| Deepware | An AI-powered deepfake detection platform that analyses video and audio content for signs of AI-generated or manipulated media. |
| Filmot | A search engine that lets you search through YouTube video subtitles and metadata to find videos that contain specific words or… |
| Google Lens | Identify objects or locations that are visible in an image. |
| Hippie OSINT Toolkit | An OSINT web toolkit that allows you to reverse search a domain, a TikTok post, an image, or username (and more). |
| Human or AI | A game-style OSINT training tool that challenges you to spot the difference between real human profile photos and AI-generated… |
| Image Whisperer | A fast, browser-based media verification tool that helps you detect AI-generated images, analyse visual anomalies, and assess… |
| ImgOps | A free image investigation hub that provides quick access to multiple reverse image search engines and image analysis tools from… |
| MW Metadata | A free web-based OSINT utility that extracts publicly available metadata from YouTube videos. |
| Oceanir | Helps verify where images and video frames may have been captured when EXIF/GPS data is missing. |
| OSINT Industries | An all-encompassing OSINT platform that gathers and correlates publicly available digital data such as emails, domains, phone… |
| Pic Detective | Reverse Image Search |
| Profile Image Intel | A reverse image search tool helping you find where a profile picture appears online and uncover linked accounts or identities. |
| Reverse Image Location (GeoSolver) | AI-assisted image geolocation tool that helps estimate where a photo was taken by reasoning from visible scene clues and map… |
| TinEye | A reverse image search engine that helps you find where an image appears online, track its origin, and detect edits or reuse. |
Sources
Both catalogues below are maintained by other people and are considerably larger than this page. Use them as the canonical index; this sheet is a working route through them.
- Bellingcat’s Online Investigation Toolkit — ~340 tools, each with its own review page covering cost, difficulty, requirements and limitations.
- OSINT Newsletter Tools Library — ~280 tools, organised by investigative goal.
Neither publishes a licence, so nothing here is copied from them: tool names, one-line descriptions, cost flags and links are catalogue facts, and the method and commentary are this site’s own. See credits.