Metadata-Version: 2.5
Name: moveproof
Version: 0.1.2
Summary: Fast, versioned file fingerprints and rename detection for local indexes
Project-URL: Repository, https://github.com/snchngny/moveproof
Project-URL: Documentation, https://github.com/snchngny/moveproof#readme
Project-URL: Changelog, https://github.com/snchngny/moveproof/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/snchngny/moveproof/issues
Project-URL: Funding, https://github.com/sponsors/snchngny
Author: snchngny
License-Expression: MIT
License-File: LICENSE
Keywords: filesystem,fingerprint,immich,media-library,move-detection,rename-detection
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: System :: Filesystems
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# Moveproof

[![PyPI](https://img.shields.io/pypi/v/moveproof.svg)](https://pypi.org/project/moveproof/)
[![CI](https://github.com/snchngny/moveproof/actions/workflows/ci.yml/badge.svg)](https://github.com/snchngny/moveproof/actions/workflows/ci.yml)
[![Python](https://img.shields.io/pypi/pyversions/moveproof.svg)](https://pypi.org/project/moveproof/)

Moveproof is a zero-dependency Python library for identifying large local files without reading them in full on every scan. It compares snapshots and reports modifications, renames, moves, copies, additions, and removals.

## How it differs

| Need | Best fit |
| --- | --- |
| Observe filesystem operations while a process is running | [watchdog](https://python-watchdog.readthedocs.io/) |
| Build JSON manifests and report added, changed, and deleted files | [file-watchman](https://pypi.org/project/file-watchman/) |
| Replay inode-detected renames before running `rsync` | [irsync](https://pypi.org/project/irsync/) |
| Compare snapshots across offline periods, classify moves, renames, and copies by content fingerprint, and produce a safe dry-run plan | **Moveproof** |

Moveproof does not watch, synchronize, or deduplicate files. It is the identification and comparison layer for preserving tags, history, and database records when paths in a local library change.

## Install

```bash
python -m pip install moveproof
```

Run `moveproof --help` after installation to verify the command is available.

If you have checked out the repository, run the [temporary-directory move demo](https://github.com/snchngny/moveproof/blob/main/examples/media_library_move.py). It does not touch your files or database.

```bash
python examples/media_library_move.py
```

It reports the move from `samples/kick.wav` to `archive/kick.wav` and `safe_to_apply: true`. This indicates an unambiguous candidate, not that a database update or backup has been performed.

Immich's [external library documentation](https://docs.immich.app/features/libraries/) warns that moving a file makes a rescan treat it as a new asset, which can detach albums, descriptions, and other Immich-only metadata. For a move or rename, use the [tool comparison, before/after audit, and read-only asset ID prototype](https://github.com/snchngny/moveproof/blob/main/docs/immich-external-library.md). Moveproof identifies old-path to new-path candidates before a rescan; it does not preserve or restore Immich metadata by itself.

The repository also includes a temporary-directory-only [Immich NAS migration preflight](https://github.com/snchngny/moveproof/blob/main/examples/immich_nas_preflight.py). It reproduces a verified NAS root move, a folder rename, and an empty mount that blocks the next step without touching Immich or your files.

After creating a reconciliation plan, the installed CLI can join its old paths to Immich asset IDs through search-only API calls. It never updates Immich:

```bash
moveproof immich-audit --api-url https://immich.example/api \
  --library-id YOUR_LIBRARY_ID --immich-root /mnt/photos \
  --plan plan.json --output asset-audit.json
```

## Python API

```python
from pathlib import Path
from moveproof import compare_snapshots, create_snapshot

before = create_snapshot(Path("media"))
# Move or add files.
after = create_snapshot(Path("media"))

for change in compare_snapshots(before, after).changes:
    print(change.kind, change.old_path, change.new_path)
```

## CLI

`inventory` is currently available on main. Until it is included in a PyPI release, run `python -m pip install .` from the checkout. Run `python examples/sample_inventory.py` for a temporary-folder demo: three dummy files, 14 bytes, and category totals. It does not parse audio formats or touch your library.

Create a read-only inventory of extensions, file counts, byte totals, and broad sample-library categories without reading file contents. It does not move, rename, or rewrite files.

```bash
moveproof inventory media --output inventory.json
```

The categories are deliberately coarse and intended for search and planning. Unknown extensions remain under `other`. Exit code 1 means unreadable paths were recorded in `issues`.

```bash
moveproof snapshot media --output before.json
moveproof snapshot media --output after.json
moveproof compare before.json after.json
```

Before updating paths in a database or media index, create a dry-run plan containing only move candidates. An automatically applicable plan requires snapshots with full fingerprints. Any unresolved ambiguity, modification, copy, addition, or removal is reported in `unresolved_changes` and produces `safe_to_apply: false` with exit code 1 so automation can stop. This command does not modify files or databases.

```bash
moveproof snapshot media --full --output before.json
# Move or rename files.
moveproof snapshot media --full --output after.json
moveproof reconcile before.json after.json --output plan.json
```

`--allow-sampled` and `--allow-incomplete` can emit advisory plans, but `safe_to_apply` remains false because they do not prove a complete exact match.

Before running automation, compare a baseline with the current library and block incomplete scans, empty mounts, or mass file disappearance. The default threshold is 10% of the baseline. Moves and renames do not count as missing files. This command is also read-only.

```bash
moveproof snapshot media --output baseline.json
moveproof guard baseline.json media --output guard.json
moveproof guard baseline.json media --max-missing-ratio 0.02
```

When only a library mount point or root directory changes, Moveproof emits a `root_move` if the full fingerprints, relative paths, and complete file set all match. This lets you review an old-root to new-root replacement plan before touching a database.

Limit a scan with relative-path globs. Excludes take precedence over includes, and the filter profile is stored in the snapshot. Snapshots with different filters cannot be compared accidentally.

```bash
moveproof snapshot media --include "*.wav" --include "*.aiff" --exclude "archive/*" -o audio.json
```

By default, one unreadable file stops snapshot creation. Use `--record-errors` to preserve diagnostics from a long scan: the CLI writes an incomplete snapshot with per-path issues and exits with code 1. Comparing incomplete snapshots is rejected by default to avoid false removal reports; use `--allow-incomplete` only when you understand the missing coverage.

Snapshot JSON is written to a temporary file in the same directory and then replaced, so a failed write does not leave an existing snapshot half-written.

By default, files up to 64 KiB are read in full. Larger files are sampled at the beginning, middle, and end, with the file size included in the digest. A file that changes while being read is rejected.

The snapshot records its sampling width. Comparing snapshots created with different modes or widths fails explicitly instead of producing misleading changes.

A sampled fingerprint is not proof of complete byte identity. Use `--full` when adversarial collisions or exact content identity matter.

## Benchmark

```bash
python benchmarks/benchmark_fingerprint.py --size-mib 256
```

The benchmark writes random data to a temporary file before timing; it does not touch your existing media. Repeated reads are still affected by the OS cache and storage. See the [measured example and reproduction notes](https://github.com/snchngny/moveproof/blob/main/docs/benchmark.md), and include your environment and run parameters when sharing results.

See the [contribution guide](https://github.com/snchngny/moveproof/blob/main/CONTRIBUTING.md) before proposing changes and the [security policy](https://github.com/snchngny/moveproof/blob/main/SECURITY.md) before reporting a vulnerability.

See the [roadmap](https://github.com/snchngny/moveproof/blob/main/docs/roadmap.md) for the path from reusable OSS components to a local-first product.

## Support development

If you use Moveproof to migrate an audio, photo, or video library, [share your use case](https://github.com/snchngny/moveproof/issues/new?template=feature.yml) with file count, size, and integration target. Support compatibility testing and ongoing OSS development through [GitHub Sponsors](https://github.com/sponsors/snchngny). Sponsorship does not guarantee a feature deadline or individual support.

## License

MIT
