A cross-platform desktop application for fast, concurrent extraction of embedded JPEG data from RAW camera files and other binary media containers — preserving original quality.
JPEG Extractor Tool is a PyQt6 GUI application that scans RAW image files (and other binary media) for embedded JPEG streams, carves them out byte-for-byte, validates their integrity, and saves them as standalone .jpg files. It is designed for photographers and recovery engineers who need to pull usable JPEG previews or thumbnails out of RAW containers without decoding the full RAW data.
It works as a companion to the RAW Repair Tool and the JPEG Repair Tool.
| Recovery dashboard | Extraction in progress |
|---|---|
![]() |
![]() |
Replace the image paths above with the actual screenshot filenames in the repository.
- Broad RAW support — recognises
.cr3,.cr2,.nef,.arw,.raf,.dng,.raw,.bin,.imgfiles. - Drag & drop — drop a folder (or a file) straight onto the window to set the source.
- Subfolder scanning — optional recursive walk of the source tree.
- Discovered media table — lists every matching file with a thumbnail preview, file name, size, and live recovery status.
- Live recovery view — the most recently carved JPEG is displayed in a large preview pane with source/target filenames.
- Recovery statistics — running counters for files recovered, files failed, and total MB extracted.
- Threaded worker — extraction runs on a
QThreadso the UI stays responsive. - Cancel support — abort a running batch at any time.
- JPEG validation — every carved file is verified by magic-byte check (
FF D8header,FF D9footer) and minimum size before being marked as recovered. - Export recovery report — save a session log as a
.txtreport. - Dark dashboard theme — modern
#0f172a/#1e293bslate palette with blue accents.
- Python 3.6 or newer
- PyQt6
git clone https://github.com/DRCRecoveryData/JPEG-Extractor-Tool.git
cd JPEG-Extractor-ToolModern Linux distributions (Debian 12+, Ubuntu 23.04+, Fedora 38+) block system-wide pip install to protect the OS Python. You will see:
error: externally-managed-environment
The correct fix is a virtual environment, not --break-system-packages.
Linux / macOS
python3 -m venv .venv
source .venv/bin/activateWindows (PowerShell)
python -m venv .venv
.venv\Scripts\Activate.ps1Your shell prompt should now show (.venv) at the start.
Ubuntu note: if
python3 -m venvfails, install the venv support package first:sudo apt update && sudo apt install -y python3-full python3-venv
pip install PyQt6python3 jpegextractor-gui.py- Select the RAW source folder — click Browse next to RAW Source Folder, or drag & drop a folder onto the window. The discovered files populate the table immediately.
- Toggle subfolder scanning (optional) — tick Include Subfolders to scan recursively.
- Select the output folder — click Browse next to Extracted JPEGs Folder to choose where carved
.jpgfiles will be saved. - Click Start JPEG Extraction — the worker thread scans each file, carves the embedded JPEG, validates it, and writes it out.
- Monitor progress — the progress bar tracks completion, the table updates each row's status (
Extracting…→Recovered/Corrupt/Failed), and the log console prints timestamped messages. - Preview results — click any row to preview the carved JPEG; double-click to open it in the system viewer.
- Export the report — click Export Log Report to save a session summary as a text file.
To abort a running job, click Cancel. The current file finishes, then the worker stops.
| Status | Colour | Meaning |
|---|---|---|
Extracting… |
blue | File is currently being scanned |
Recovered |
green | JPEG carved and passed validation |
Corrupt |
amber | JPEG carved but failed integrity check |
Failed |
red | No JPEG signature found in the file |
| (blank) | grey | Not yet processed |
The extractor reads the entire RAW file into memory as a byte string and uses regular expressions to locate JPEG start markers:
pattern_1_exif = rb"\xFF\xD8\xFF\xE1.{2}Exif" # EXIF-bearing JPEG
pattern_1_jfif = rb"\xFF\xD8\xFF\xE0.{2}JFIF" # JFIF JPEG
pattern_2 = rb"\xFF\xD8\xFF\xDB.{4,8}" # raw DQT-start JPEGAll matches are collected and sorted by offset. End markers (FF D9) are also located. The algorithm then iterates the start positions in reverse and picks the first one that has a matching end marker after it — effectively extracting the last, largest, most complete JPEG embedded in the file (typically the full-size preview rather than a thumbnail).
Every carved file is checked before it is reported as recovered:
def validate_jpeg(file_path):
size = os.path.getsize(file_path)
if size < 1024:
return False
with open(file_path, 'rb') as f:
header = f.read(2)
f.seek(-2, 2)
footer = f.read(2)
return header == b'\xFF\xD8' and footer == b'\xFF\xD9'A file must be at least 1 KB, start with FF D8, and end with FF D9 to be marked Recovered.
| Class | Responsibility |
|---|---|
JPEGExtractionWorker |
QThread subclass; loops over the file list, emits per-file signals |
JPEGRepairApp |
QWidget dashboard holding the layout, stylesheet, table, and callbacks |
extract_jpeg_from_raw() |
Standalone carving function (regex + reverse scan) |
validate_jpeg() |
Binary integrity check |
Signals emitted by the worker:
| Signal | Payload | Purpose |
|---|---|---|
progress_updated |
int |
Overall completion 0–100 |
log_updated |
str |
Console message |
file_status_updated |
int, str |
Row index + new status text |
jpeg_carved |
int, str, str |
Row index, source RAW path, carved JPEG path |
extraction_finished |
str |
Final summary message |
JPEG-Extractor-Tool/
├── jpegextractor-gui.py # Entire application (GUI + worker + carving logic)
├── screenshot.png # UI screenshot referenced by README
├── LICENSE # MIT License
└── README.md
The whole application lives in a single file, jpegextractor-gui.py, organised into these sections:
| Section | Responsibility |
|---|---|
| Constants | RAW_EXTENSIONS tuple of supported extensions |
validate_jpeg() |
Binary JPEG integrity check |
JPEGRepairApp |
Main QWidget: splitter layout, config frame, files table, preview card, stats, log, actions |
JPEGExtractionWorker |
QThread subclass running the extraction loop |
extract_jpeg_from_raw() |
Regex-based JPEG carver |
- Python 3 — application language
- PyQt6 — GUI framework (widgets, signals/slots,
QThread, drag & drop,QTableWidget,QPixmap) re— binary regex pattern matching for JPEG marker detectionthreading.Event— cooperative cancellation flag shared with the worker
- The carver extracts only one JPEG per source file — the last valid start/end pair found. Multi-image RAW files (e.g. RAW + embedded thumbnail) yield the largest/last JPEG only.
- Only JPEGs beginning with
FF D8 FF E1(EXIF),FF D8 FF E0(JFIF), orFF D8 FF DB(DQT) are detected. Unusual JPEG variants with different APP markers may be missed. - Extraction depends on the RAW container actually holding an embedded JPEG preview. Some RAW formats store only compressed sensor data with no JPEG stream — those will report
Failed. - The entire source file is read into RAM; very large files (multi-GB) may be slow or memory-intensive.
- No EXIF metadata is preserved or re-attached to the carved JPEG.
- Report export writes plain text only (
.txt), not CSV or JSON.
Contributions, bug reports, and feature requests are welcome.
- Fork the repository.
- Create a feature branch (
git checkout -b feature/my-improvement). - Commit your changes with a clear message.
- Open a pull request describing what you changed and why.
If a specific camera model fails to yield any JPEG, please open an issue and include:
- The camera make and model
- The RAW file extension
- The log output from the app
- (If possible) a small sample of the RAW file
This project is licensed under the MIT License — see the LICENSE file for details.
- The PyQt6 team — GUI framework
- Special thanks to OpenAI for providing guidance and assistance during development
Part of the DRCRecoveryData toolkit — free recovery utilities for ransomware-affected data:
- RAW Repair Tool — repair encrypted ARW / NEF / CR2 files
- JPEG Repair Tool — repair corrupted JPEGs by shifting MCU and applying auto colour enhancement
- WAV Repair Tool — repair encrypted WAV audio files
