Analyze synthetic media using Python-based deepfake detection tools, frequency analysis, and metadata forensics. Learn to identify GAN artifacts, JPEG compression inconsistencies, and audio manipulation — critical skills as synthetic media becomes a primary vector for fraud and disinformation.
On Kali Linux, set up the detection environment:
sudo apt update && sudo apt install -y python3-pip ffmpeg exiftool libgl1
pip3 install opencv-python-headless numpy matplotlib scipy \
Pillow imageio scikit-learn torch torchvision
mkdir ~/deepfake-lab && cd ~/deepfake-lab
mkdir samples/real samples/fake analysis reports
# Install py-feat for facial action unit analysis (optional)
pip3 install py-feat
# Verify
python3 -c "import cv2, torch, PIL; print(f'OpenCV {cv2.__version__}, PyTorch {torch.__version__}')"
Download sample media from the FaceForensics++ dataset (research use) and public deepfake samples:
cd ~/deepfake-lab
# Download FaceForensics++ sample (requires academic registration at github.com/ondyari/FaceForensics)
# Alternative: use this public deepfake detection dataset subset
wget -P samples/ \
"https://github.com/yuezunli/celeb-deepfakeforensics/raw/master/README.md"
# Generate synthetic "deepfake" test images using StyleGAN-generated faces
# thispersondoesnotexist.com API (public faces, no real person)
python3 << 'EOF'
import requests, os, time
print("Downloading GAN-generated faces (this-person-does-not-exist.com)...")
for i in range(10):
resp = requests.get("https://thispersondoesnotexist.com/",
headers={"User-Agent": "Mozilla/5.0"})
if resp.status_code == 200:
with open(f"samples/fake/gan_face_{i:03d}.jpg", 'wb') as f:
f.write(resp.content)
print(f" Downloaded fake face {i+1}/10")
time.sleep(2) # Rate limit
# Real photos: download from Flickr Creative Commons (public domain faces)
print("\nNote: Add real photos to samples/real/ for comparison")
print("Source: commons.wikimedia.org/wiki/Category:Photographs_of_people")
EOF
GAN-generated images have characteristic frequency artifacts visible in the DFT spectrum:
cat > ~/deepfake-lab/frequency_analysis.py << 'EOF'
import cv2, numpy as np, matplotlib.pyplot as plt
import os, glob
def analyze_frequency(image_path, label):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img is None: return None
# Resize to standard size
img = cv2.resize(img, (256, 256))
# 2D Discrete Fourier Transform
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude = 20 * np.log(np.abs(fshift) + 1)
# GAN artifact: periodic grid pattern at specific frequencies
# Compute radial power spectrum
h, w = magnitude.shape
cy, cx = h//2, w//2
radial_power = []
for r in range(1, min(h,w)//2):
mask = np.zeros((h,w), bool)
for y in range(h):
for x in range(w):
if abs(np.sqrt((y-cy)**2 + (x-cx)**2) - r) < 1:
mask[y,x] = True
radial_power.append(magnitude[mask].mean())
return {
'path': image_path,
'label': label,
'magnitude': magnitude,
'radial_power': np.array(radial_power),
'mean_high_freq': np.array(radial_power[50:]).mean(),
'std_magnitude': magnitude.std(),
}
# Analyze samples
results = []
for f in glob.glob('samples/fake/*.jpg')[:5]:
r = analyze_frequency(f, 'DEEPFAKE')
if r: results.append(r)
# Plot DFT spectrums side by side
fig, axes = plt.subplots(2, len(results), figsize=(15, 6))
for i, r in enumerate(results):
axes[0,i].imshow(cv2.imread(r['path'])[:,:,::-1])
axes[0,i].set_title(f"{r['label']}", fontsize=8)
axes[0,i].axis('off')
axes[1,i].imshow(r['magnitude'], cmap='hot')
axes[1,i].set_title(f"DFT (σ={r['std_magnitude']:.1f})", fontsize=8)
axes[1,i].axis('off')
plt.suptitle('Frequency Domain Analysis — GAN Artifact Detection')
plt.tight_layout()
plt.savefig('analysis/frequency_analysis.png', dpi=150)
print("Saved frequency_analysis.png")
for r in results:
print(f"\n{os.path.basename(r['path'])}")
print(f" Mean high-freq power: {r['mean_high_freq']:.2f}")
print(f" DFT std deviation: {r['std_magnitude']:.2f}")
print(f" GAN artifacts: {'LIKELY' if r['std_magnitude'] > 80 else 'UNCLEAR'}")
EOF
python3 frequency_analysis.py
ELA reveals image regions with different compression histories — common in spliced or manipulated photos:
cat > ~/deepfake-lab/ela_analysis.py << 'EOF'
import cv2, numpy as np, matplotlib.pyplot as plt
import io
from PIL import Image
def ela(image_path, quality=95, scale=20):
"""Error Level Analysis — detect re-saved JPEG regions."""
original = Image.open(image_path).convert('RGB')
# Re-save at known quality level
buffer = io.BytesIO()
original.save(buffer, 'JPEG', quality=quality)
buffer.seek(0)
recompressed = Image.open(buffer).convert('RGB')
# Compute pixel-wise difference
orig_arr = np.array(original, dtype=float)
recomp_arr = np.array(recompressed, dtype=float)
ela_arr = np.abs(orig_arr - recomp_arr) * scale
ela_arr = np.clip(ela_arr, 0, 255).astype(np.uint8)
# Metrics
ela_mean = ela_arr.mean()
ela_std = ela_arr.std()
return ela_arr, ela_mean, ela_std
import glob, os
fig, axes = plt.subplots(2, 5, figsize=(15,6))
for i, path in enumerate(glob.glob('samples/fake/*.jpg')[:5]):
ela_img, ela_mean, ela_std = ela(path)
img = cv2.imread(path)[:,:,::-1]
axes[0,i].imshow(img); axes[0,i].axis('off')
axes[0,i].set_title(os.path.basename(path)[:15], fontsize=7)
axes[1,i].imshow(ela_img); axes[1,i].axis('off')
axes[1,i].set_title(f"ELA μ={ela_mean:.1f} σ={ela_std:.1f}", fontsize=7)
plt.suptitle('Error Level Analysis (ELA)')
plt.tight_layout()
plt.savefig('analysis/ela_results.png', dpi=150)
print("ELA analysis complete — saved ela_results.png")
# Interpretation guide
print("\nInterpretation:")
print(" Uniform ELA = consistent compression history (real or fully GAN-generated)")
print(" Bright patches in ELA = regions with different compression = SPLICED/MANIPULATED")
print(" GAN images: often uniform ELA (no splicing) but unusual DFT spectrum")
EOF
python3 ela_analysis.py
GAN-generated faces often have artifacts around eyes, teeth, hair edges, and accessories:
cat > ~/deepfake-lab/face_analysis.py << 'EOF'
import cv2, numpy as np, matplotlib.pyplot as plt, glob, os
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_eye.xml')
def analyze_face(image_path):
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(100,100))
if len(faces) == 0:
return {'face_detected': False}
x, y, w, h = faces[0]
face_roi = gray[y:y+h, x:x+w]
face_color = img[y:y+h, x:x+w]
# Detect eyes in face region
eyes = eye_cascade.detectMultiScale(face_roi, 1.1, 5)
# Laplacian for blur detection (deepfakes often have uneven sharpness)
laplacian = cv2.Laplacian(face_roi, cv2.CV_64F)
sharpness = laplacian.var()
# Symmetry analysis (deepfakes can have L-R asymmetry artifacts)
left_half = face_roi[:, :w//2]
right_half = cv2.flip(face_roi[:, w//2:], 1)
if left_half.shape == right_half.shape:
symmetry_score = cv2.matchTemplate(
left_half.astype(float), right_half.astype(float),
cv2.TM_CCOEFF_NORMED)[0][0]
else:
symmetry_score = 0
return {
'face_detected': True,
'face_size': (w, h),
'eyes_detected': len(eyes),
'sharpness': sharpness,
'symmetry_score': float(symmetry_score),
'face_img': face_color,
}
results = []
for path in glob.glob('samples/fake/*.jpg')[:5]:
r = analyze_face(path)
r['path'] = path
results.append(r)
if r['face_detected']:
print(f"\n{os.path.basename(path)}")
print(f" Eyes detected: {r['eyes_detected']} (expect 2)")
print(f" Sharpness: {r['sharpness']:.1f}")
print(f" L-R Symmetry: {r['symmetry_score']:.3f}")
if r['eyes_detected'] != 2:
print(f" [!] Unusual eye count — possible GAN artifact")
if r['symmetry_score'] < 0.7:
print(f" [!] Low facial symmetry — possible manipulation")
EOF
python3 face_analysis.py
Genuine photos from cameras contain rich EXIF data. GAN-generated images have none or suspicious metadata:
for img in samples/fake/*.jpg; do echo "=== $(basename $img) ===" exiftool "$img" | grep -E "File Type|Image Size|Camera Model|GPS|Software|Create Date|Modify Date|Color Space|Focal Length" echo "" done # Key red flags in EXIF metadata: # - No Camera Model / Make → likely GAN-generated or screenshot # - Software field contains "GIMP", "Photoshop", "Stable Diffusion", etc. # - Create Date = Modify Date exactly → no camera processing # - GPS coordinates absent → untraceable origin # - Color Space = sRGB only (cameras use Adobe RGB or camera-specific profiles) # Compare with a known real photo exiftool samples/real/reference_photo.jpg 2>/dev/null || \ echo "Add a real JPEG to samples/real/ for comparison"
cat > ~/deepfake-lab/video_analysis.py << 'EOF'
import cv2, numpy as np, subprocess, os
def analyze_video(video_path, output_dir='analysis/frames'):
os.makedirs(output_dir, exist_ok=True)
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"Video: {os.path.basename(video_path)}")
print(f" FPS: {fps:.1f}, Total frames: {total}")
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
frame_results = []
for frame_idx in range(0, total, max(1, total//20)): # Sample 20 frames
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ret, frame = cap.read()
if not ret: break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
laplacian = cv2.Laplacian(gray, cv2.CV_64F).var()
frame_results.append({
'frame': frame_idx,
'faces': len(faces),
'sharpness': laplacian,
})
cap.release()
# Detect temporal inconsistencies
sharpness = [r['sharpness'] for r in frame_results]
sharpness_std = np.std(sharpness)
print(f" Sharpness variance across frames: {sharpness_std:.1f}")
if sharpness_std > 500:
print(f" [!] HIGH sharpness variance — possible face-swap seams")
face_counts = [r['faces'] for r in frame_results]
if max(face_counts, default=0) == 0:
print(f" [!] No faces detected — may need different cascade")
return frame_results
# Usage (provide a sample video)
# analyze_video('samples/deepfake_sample.mp4')
print("Video analysis module ready. Provide a video file path.")
print("Download FaceForensics++ samples or use: youtube-dl [public deepfake sample]")
EOF
# Extract key video metadata with ffprobe
ffprobe -v quiet -print_format json -show_streams -show_format \
samples/deepfake_video.mp4 2>/dev/null || \
echo "Add a video to samples/ to analyze. FFprobe ready."
# Look for encoding inconsistencies
# Real cameras: consistent keyframe intervals, single codec
# Deepfakes: often re-encoded multiple times, irregular keyframes
Cloned voices show artifacts in the spectrogram — unnatural pitch, formant discontinuities:
pip3 install librosa soundfile
cat > ~/deepfake-lab/audio_analysis.py << 'EOF'
import librosa, librosa.display, numpy as np, matplotlib.pyplot as plt
def analyze_audio(audio_path):
y, sr = librosa.load(audio_path, sr=None, mono=True)
print(f"\nAudio: {audio_path}")
print(f" Duration: {len(y)/sr:.2f}s, Sample rate: {sr}Hz")
# Mel spectrogram (visual fingerprint of voice)
S = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128, fmax=8000)
S_dB = librosa.power_to_db(S, ref=np.max)
# Spectral features
spec_centroid = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
spec_rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr, roll_percent=0.85)[0]
zero_crossing = librosa.feature.zero_crossing_rate(y)[0]
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
print(f" Spectral centroid mean: {spec_centroid.mean():.1f}Hz")
print(f" Spectral rolloff mean: {spec_rolloff.mean():.1f}Hz")
print(f" Zero crossing rate: {zero_crossing.mean():.4f}")
# Plot
fig, axes = plt.subplots(2, 1, figsize=(12, 6))
librosa.display.specshow(S_dB, x_axis='time', y_axis='mel',
sr=sr, fmax=8000, ax=axes[0])
axes[0].set_title('Mel Spectrogram (Voice Fingerprint)')
axes[0].colorbar(format='%+2.0f dB')
librosa.display.specshow(mfcc, x_axis='time', ax=axes[1])
axes[1].set_title('MFCC Features (look for discontinuities)')
axes[1].colorbar()
plt.tight_layout()
plt.savefig('analysis/audio_spectrogram.png', dpi=150)
# Cloning artifacts: sudden spectral jumps, unnatural pauses
centroid_diff = np.diff(spec_centroid)
jumps = (np.abs(centroid_diff) > centroid_diff.std() * 3).sum()
print(f" Spectral discontinuities: {jumps}")
if jumps > len(centroid_diff) * 0.05:
print(f" [!] HIGH discontinuity rate — possible voice clone splice")
print("Audio analysis module ready.")
print("Provide an MP3/WAV file path to analyze_audio()")
EOF
python3 audio_analysis.py
Combine all signals into a structured assessment framework:
| Detection Method | Indicator | Weight | Score |
|---|---|---|---|
| EXIF Metadata | No camera make/model | HIGH | |
| ELA Analysis | Inconsistent compression | HIGH | |
| DFT Spectrum | GAN grid artifacts | MEDIUM | |
| Facial Analysis | Eye count, symmetry | MEDIUM | |
| Audio Spectrogram | Splice discontinuities | HIGH | |
| Video Temporal | Sharpness variance | MEDIUM |
Document the real-world threat landscape and organizational risks:
| Threat Scenario | Vector | Example |
|---|---|---|
| CEO Fraud / BEC | Voice clone in video call | CFO instructed to wire $25M |
| Political Disinformation | Face-swap video | Fake speech of official |
| Fake Evidence | Image manipulation | Planted in legal/HR case |
| Identity Fraud | GAN-generated ID photo | Bypasses KYC system |
| Synthetic Phishing | Cloned voice/video | IT helpdesk impersonation |
| Metric | Value |
|---|---|
| Images analyzed | |
| EXIF anomalies found | |
| ELA inconsistencies | |
| DFT artifacts detected | |
| Facial anomalies | |
| Composite verdict |