OSINT^:OSINT

Image & Video Forensics

Read a file's metadata, test it for manipulation, and work out what the camera and encoder can tell you.

intermediateupdated 2026-10-03exiftool · ffprobe · ffmpeg · mediainfo

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

ToolWhat it does
Reveal Image Verification AssistantRuns 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.
JimplBrowser EXIF and GPS viewer with a map pin. Quick, and fine for a file you do not mind uploading.
xIFrDetailed EXIF viewer that decodes maker notes, which is the one thing a casual viewer usually drops.
metadata2goMetadata for both images and video in a browser, when you cannot install anything.
DeepFake-O-MeterAcademic 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.

ToolWhat it doesCost
Amazon RekognitionCheck how similar two faces arefree
Azure AI Video IndexerAI video tool for facial detection and other types of insights.paid
Face Comparison by ToolPieCompares two human face photos to determine similarity.free
FaceCheck.IDA facial recognition search engine that tries to find photos of people that look similar to a person of interest.paid
PimEyesAn AI-powered facial recognition reverse image search tool.paid
Search4FacesUpload the picture of a face and find pictures of similar looking people on VKontakte, Odnoklassniki, TikTok and Clubhouse.free

Tool reference

ToolWhat it doesCost
Am I Real?A simple online tool that allows users to check whether a photo might have been generated by ThisPersonDoesNotExist.com.free
AutoStitchAutostitch is a free tool for seamlessly combining multiple photos into a single panoramic image, making it ideal for creating wide-angle photography…free
Cleanup.PicturesWeb tool for quickly removing objects from an image.free
DeepFake-O-MeterDeepFake-O-Meter is an online tool designed to detect deepfake media and help users differentiate between genuine and manipulated content.free
ExifPurgeEXIF 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
fdupesGithub - Locating exact matches of duplicate files.free
HuginHugin is a free and open-source panorama photo stitching and HDR (High Dynamic Range imaging) merging software that helps users create seamless panoramic…free
InVIDA toolkit that supports the verification of videos and images.free
IrfanviewWindows-based software to extract metadata.free
metadata2goCheck metadata for both photos and videos online.free
PureRefImage workspace; lets you arrange images in groups, organize them, etc.free

Pitfalls

  • Metadata is trivially forged. exiftool writes 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.

ToolWhat it does
Crowd CounterEstimates crowd sizes from images.
DeepwareAn AI-powered deepfake detection platform that analyses video and audio content for signs of AI-generated or manipulated media.
FilmotA search engine that lets you search through YouTube video subtitles and metadata to find videos that contain specific words or…
Google LensIdentify objects or locations that are visible in an image.
Hippie OSINT ToolkitAn OSINT web toolkit that allows you to reverse search a domain, a TikTok post, an image, or username (and more).
Human or AIA game-style OSINT training tool that challenges you to spot the difference between real human profile photos and AI-generated…
Image WhispererA fast, browser-based media verification tool that helps you detect AI-generated images, analyse visual anomalies, and assess…
ImgOpsA free image investigation hub that provides quick access to multiple reverse image search engines and image analysis tools from…
MW MetadataA free web-based OSINT utility that extracts publicly available metadata from YouTube videos.
OceanirHelps verify where images and video frames may have been captured when EXIF/GPS data is missing.
OSINT IndustriesAn all-encompassing OSINT platform that gathers and correlates publicly available digital data such as emails, domains, phone…
Pic DetectiveReverse Image Search
Profile Image IntelA 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…
TinEyeA 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.

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.

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.