A Django app for file deduplication and indexing using SHA hashes
Project description
django-fileindex
A Django app for file deduplication and indexing. It implements a content-addressable storage system that prevents duplicate files and tracks file metadata.
Why?
I built this as part of a larger system to manage my family's photos, videos, documents, and digital archives. Over the years, files ended up scattered across old hard drives, cloud services, SD cards, and USB sticks — with copies of copies everywhere and no easy way to know what I actually had. On top of that, I've been digitizing old MiniDV camcorder tapes and wanted to preserve the original recording dates and timecodes baked into the DV stream (which is why MediaInfo support exists).
django-fileindex is the piece that answers "have I seen this file before?" and "where did I find it?" — so I can throw files at it from any source without worrying about duplicates piling up.
PostgreSQL only — no effort has been made to support other databases.
Features
- File Deduplication: SHA-512 hashing identifies and prevents duplicate file storage
- Content-Addressable Storage: Files organized by hash for efficient retrieval
- Metadata Extraction: Automatic extraction of image dimensions, video/audio duration, frame rates, and thumbhashes
- Optional MediaInfo Support: Enhanced metadata for professional formats like DV (recording dates, timecodes)
- File Watching: Monitor directories and automatically import new files
- Form Integration: Custom form fields and ModelForm mixin for file uploads
- Admin Interface: Read-only admin with file path tracking and metadata display
Installation
pip install django-fileindex
For enhanced video metadata (DV recording dates, timecodes):
pip install django-fileindex[mediainfo]
Quick Start
- Add
fileindexto yourINSTALLED_APPS:
INSTALLED_APPS = [
...
"fileindex",
]
- Run migrations:
python manage.py migrate fileindex
Usage
Basic File Operations
from fileindex.models import IndexedFile
# Index a file from the filesystem
indexed_file, created = IndexedFile.objects.get_or_create_from_file("/path/to/file.jpg")
# Access file information
print(f"SHA-512: {indexed_file.sha512}")
print(f"Size: {indexed_file.size}")
print(f"MIME type: {indexed_file.mime_type}")
print(f"Storage path: {indexed_file.path}")
File Import Service
from fileindex.services.file_import import import_file, batch_import_files
# Import a single file
indexed_file, created, error = import_file("/path/to/file.jpg")
# Import multiple files in batch
stats = batch_import_files(
["/path/to/file1.jpg", "/path/to/file2.png"],
delete_after=True, # Delete originals after import
)
Management Commands
# Add files to the index
python manage.py fileindex_add /path/to/file1 /path/to/directory
# Watch directories for new files
python manage.py fileindex_watch /path/to/directory --remove-after-import
# Backup files no longer referenced by any FilePath
python manage.py fileindex_backup_orphaned
# Generate missing metadata for existing files
python manage.py fileindex_populate_missing_metadata
Form Integration
from fileindex.fields import IndexedFileField
from fileindex.forms import IndexedFileModelForm
# Simple form with file upload
class DocumentForm(forms.Form):
file = IndexedFileField(
allowed_extensions=[".pdf", ".doc"],
max_file_size=10 * 1024 * 1024, # 10MB
)
# ModelForm with automatic IndexedFile creation
class ImageForm(IndexedFileModelForm):
class Meta:
model = MyModel
fields = ["title", "description"]
indexed_file_field_name = "image"
Models
IndexedFile
The main model for storing file information:
sha512: SHA-512 hash (unique, primary identifier)sha1: SHA-1 hash (optional)size: File size in bytesmime_type: MIME type of the filefile: FileField pointing to content-addressable storagefirst_seen: Timestamp when first indexedcorrupt: Flag indicating metadata extraction failurederived_from: ForeignKey to source file (for thumbnails, conversions)derived_for: Type of derivation ("thumbnail","compression", or None)metadata: JSONField with type-specific metadata (dimensions, duration, thumbhash, ffprobe/mediainfo output)
FilePath
Tracks all file paths that point to an indexed file:
indexedfile: ForeignKey to IndexedFilepath: Original file pathmtime: Last modification timectime: Creation timehostname: Hostname where file was found
Metadata Extraction
Metadata is stored as JSON in IndexedFile.metadata and varies by file type:
- Images: Width, height, thumbhash, animated detection
- Video: Width, height, duration, frame rate, codec info (via ffprobe)
- Audio: Duration, codec info (via ffprobe)
- DV/Professional formats: Recording date, timecode, format details (via optional MediaInfo)
Signals
indexedfile_added: Sent when a new IndexedFile is created and saved
Development
git clone https://github.com/myers/django-fileindex.git
cd django-fileindex
uv sync
uv run pytest --cov=fileindex --cov-report=html -v
PostgreSQL is required for tests and is automatically started via Docker in the test configuration.
External Tools
- ffmpeg/ffprobe (required for video/audio metadata):
- macOS:
brew install ffmpeg - Linux:
apt install ffmpeg
- macOS:
- MediaInfo (optional, for DV and professional formats):
- macOS:
brew install mediainfo - Linux:
apt install mediainfo
- macOS:
License
MIT License. See LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_fileindex-1.0.3.tar.gz.
File metadata
- Download URL: django_fileindex-1.0.3.tar.gz
- Upload date:
- Size: 430.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9880a94f3f95e30674c654828f795c8b85d531199f02263c5144f735f218073c
|
|
| MD5 |
e518d4308b3ebf0c03e639f553f7d2e6
|
|
| BLAKE2b-256 |
096f34829f0549366d0299005c82c27a30988751c5b0caf2602b96a69dbb5674
|
Provenance
The following attestation bundles were made for django_fileindex-1.0.3.tar.gz:
Publisher:
publish.yml on myers/django-fileindex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_fileindex-1.0.3.tar.gz -
Subject digest:
9880a94f3f95e30674c654828f795c8b85d531199f02263c5144f735f218073c - Sigstore transparency entry: 1004087044
- Sigstore integration time:
-
Permalink:
myers/django-fileindex@792d9ebad9e604138709676774572fda51b8792c -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/myers
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@792d9ebad9e604138709676774572fda51b8792c -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_fileindex-1.0.3-py3-none-any.whl.
File metadata
- Download URL: django_fileindex-1.0.3-py3-none-any.whl
- Upload date:
- Size: 60.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
555c688ce91042e7891d5b9e9dd8a7af1d43fa3f77011d5c5c39ae3c560d378f
|
|
| MD5 |
b4710dc295030873e1dc2e96769ca0cc
|
|
| BLAKE2b-256 |
6ddac1f93c3550b3dd67aef7f75d9269b3693ea29da31d2410181f571ddfac00
|
Provenance
The following attestation bundles were made for django_fileindex-1.0.3-py3-none-any.whl:
Publisher:
publish.yml on myers/django-fileindex
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_fileindex-1.0.3-py3-none-any.whl -
Subject digest:
555c688ce91042e7891d5b9e9dd8a7af1d43fa3f77011d5c5c39ae3c560d378f - Sigstore transparency entry: 1004087056
- Sigstore integration time:
-
Permalink:
myers/django-fileindex@792d9ebad9e604138709676774572fda51b8792c -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/myers
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@792d9ebad9e604138709676774572fda51b8792c -
Trigger Event:
push
-
Statement type: