rekindle 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rekindle-0.1.0/.github/workflows/ci.yml +46 -0
- rekindle-0.1.0/.gitignore +116 -0
- rekindle-0.1.0/CONTRIBUTING.md +183 -0
- rekindle-0.1.0/LICENSE +21 -0
- rekindle-0.1.0/PKG-INFO +622 -0
- rekindle-0.1.0/README.md +579 -0
- rekindle-0.1.0/SECURITY.md +62 -0
- rekindle-0.1.0/docs/audit-m3-semantic.md +953 -0
- rekindle-0.1.0/docs/connecting-google-photos.md +232 -0
- rekindle-0.1.0/docs/decision-log-memory-builder.md +936 -0
- rekindle-0.1.0/docs/decision-log-memory-engine.md +715 -0
- rekindle-0.1.0/docs/decision-log-prompt-memories.md +565 -0
- rekindle-0.1.0/docs/decision-log-takeout-enrichment.md +181 -0
- rekindle-0.1.0/docs/gallery.md +111 -0
- rekindle-0.1.0/docs/known-limitations.md +921 -0
- rekindle-0.1.0/docs/publishing.md +90 -0
- rekindle-0.1.0/docs/writing-recipes.md +213 -0
- rekindle-0.1.0/docs/writing-sources.md +172 -0
- rekindle-0.1.0/pyproject.toml +154 -0
- rekindle-0.1.0/src/rekindle/__init__.py +3 -0
- rekindle-0.1.0/src/rekindle/calibrate/__init__.py +23 -0
- rekindle-0.1.0/src/rekindle/calibrate/cli.py +529 -0
- rekindle-0.1.0/src/rekindle/calibrate/impact.py +293 -0
- rekindle-0.1.0/src/rekindle/calibrate/judge.py +271 -0
- rekindle-0.1.0/src/rekindle/calibrate/plan.py +310 -0
- rekindle-0.1.0/src/rekindle/calibrate/preview.py +226 -0
- rekindle-0.1.0/src/rekindle/calibrate/sampling.py +199 -0
- rekindle-0.1.0/src/rekindle/calibrate/session.py +404 -0
- rekindle-0.1.0/src/rekindle/calibrate/state.py +275 -0
- rekindle-0.1.0/src/rekindle/cli.py +777 -0
- rekindle-0.1.0/src/rekindle/config/__init__.py +48 -0
- rekindle-0.1.0/src/rekindle/config/cli.py +122 -0
- rekindle-0.1.0/src/rekindle/config/defaults.toml +383 -0
- rekindle-0.1.0/src/rekindle/config/settings.py +398 -0
- rekindle-0.1.0/src/rekindle/config/writer.py +103 -0
- rekindle-0.1.0/src/rekindle/db.py +1023 -0
- rekindle-0.1.0/src/rekindle/doctor.py +354 -0
- rekindle-0.1.0/src/rekindle/enrich/__init__.py +1 -0
- rekindle-0.1.0/src/rekindle/enrich/takeout.py +992 -0
- rekindle-0.1.0/src/rekindle/extras.py +120 -0
- rekindle-0.1.0/src/rekindle/fetch.py +271 -0
- rekindle-0.1.0/src/rekindle/identity.py +93 -0
- rekindle-0.1.0/src/rekindle/memory/__init__.py +0 -0
- rekindle-0.1.0/src/rekindle/memory/albums.py +63 -0
- rekindle-0.1.0/src/rekindle/memory/captioning.py +232 -0
- rekindle-0.1.0/src/rekindle/memory/captions.py +145 -0
- rekindle-0.1.0/src/rekindle/memory/cli.py +1505 -0
- rekindle-0.1.0/src/rekindle/memory/composition.py +557 -0
- rekindle-0.1.0/src/rekindle/memory/corpus/caption_vocab.toml +191 -0
- rekindle-0.1.0/src/rekindle/memory/corpus/festivals.toml +116 -0
- rekindle-0.1.0/src/rekindle/memory/corpus/prompt_tags.json +153 -0
- rekindle-0.1.0/src/rekindle/memory/corpus/scenery.toml +163 -0
- rekindle-0.1.0/src/rekindle/memory/dedup.py +308 -0
- rekindle-0.1.0/src/rekindle/memory/diversity.py +453 -0
- rekindle-0.1.0/src/rekindle/memory/engine.py +401 -0
- rekindle-0.1.0/src/rekindle/memory/festivals.py +118 -0
- rekindle-0.1.0/src/rekindle/memory/fingerprint.py +622 -0
- rekindle-0.1.0/src/rekindle/memory/history.py +251 -0
- rekindle-0.1.0/src/rekindle/memory/index.py +351 -0
- rekindle-0.1.0/src/rekindle/memory/llm.py +528 -0
- rekindle-0.1.0/src/rekindle/memory/policy.py +311 -0
- rekindle-0.1.0/src/rekindle/memory/prompt.py +562 -0
- rekindle-0.1.0/src/rekindle/memory/recipes/__init__.py +68 -0
- rekindle-0.1.0/src/rekindle/memory/recipes/base.py +109 -0
- rekindle-0.1.0/src/rekindle/memory/recipes/builtin.py +607 -0
- rekindle-0.1.0/src/rekindle/memory/recipes/registry.py +159 -0
- rekindle-0.1.0/src/rekindle/memory/recurring.py +346 -0
- rekindle-0.1.0/src/rekindle/memory/render/__init__.py +23 -0
- rekindle-0.1.0/src/rekindle/memory/render/frames.py +338 -0
- rekindle-0.1.0/src/rekindle/memory/render/gif.py +126 -0
- rekindle-0.1.0/src/rekindle/memory/render/motion.py +593 -0
- rekindle-0.1.0/src/rekindle/memory/render/mp4.py +277 -0
- rekindle-0.1.0/src/rekindle/memory/render/music.py +102 -0
- rekindle-0.1.0/src/rekindle/memory/render/onsets.py +260 -0
- rekindle-0.1.0/src/rekindle/memory/render/timeline.py +271 -0
- rekindle-0.1.0/src/rekindle/memory/scenery.py +351 -0
- rekindle-0.1.0/src/rekindle/memory/spec.py +283 -0
- rekindle-0.1.0/src/rekindle/memory/strata.py +263 -0
- rekindle-0.1.0/src/rekindle/memory/tags.py +378 -0
- rekindle-0.1.0/src/rekindle/memory/videoframe.py +422 -0
- rekindle-0.1.0/src/rekindle/memory/vocab.py +304 -0
- rekindle-0.1.0/src/rekindle/memory/watch.py +163 -0
- rekindle-0.1.0/src/rekindle/meta/__init__.py +0 -0
- rekindle-0.1.0/src/rekindle/meta/exif.py +235 -0
- rekindle-0.1.0/src/rekindle/meta/orientation.py +479 -0
- rekindle-0.1.0/src/rekindle/meta/timestamps.py +170 -0
- rekindle-0.1.0/src/rekindle/meta/xmp.py +122 -0
- rekindle-0.1.0/src/rekindle/models.py +283 -0
- rekindle-0.1.0/src/rekindle/py.typed +0 -0
- rekindle-0.1.0/src/rekindle/semantic/__init__.py +14 -0
- rekindle-0.1.0/src/rekindle/semantic/aesthetic.py +205 -0
- rekindle-0.1.0/src/rekindle/semantic/availability.py +97 -0
- rekindle-0.1.0/src/rekindle/semantic/cli.py +895 -0
- rekindle-0.1.0/src/rekindle/semantic/cluster.py +304 -0
- rekindle-0.1.0/src/rekindle/semantic/describe.py +110 -0
- rekindle-0.1.0/src/rekindle/semantic/diversity.py +276 -0
- rekindle-0.1.0/src/rekindle/semantic/embed.py +510 -0
- rekindle-0.1.0/src/rekindle/semantic/encoder.py +431 -0
- rekindle-0.1.0/src/rekindle/semantic/faces.py +587 -0
- rekindle-0.1.0/src/rekindle/semantic/model-locks.json +23 -0
- rekindle-0.1.0/src/rekindle/semantic/photos.py +345 -0
- rekindle-0.1.0/src/rekindle/semantic/registry.py +280 -0
- rekindle-0.1.0/src/rekindle/semantic/runtime.py +197 -0
- rekindle-0.1.0/src/rekindle/semantic/search.py +141 -0
- rekindle-0.1.0/src/rekindle/semantic/setup.py +290 -0
- rekindle-0.1.0/src/rekindle/semantic/store.py +545 -0
- rekindle-0.1.0/src/rekindle/semantic/torchfile.py +177 -0
- rekindle-0.1.0/src/rekindle/semantic/vectors.py +110 -0
- rekindle-0.1.0/src/rekindle/sidecars.py +60 -0
- rekindle-0.1.0/src/rekindle/sources/__init__.py +0 -0
- rekindle-0.1.0/src/rekindle/sources/base.py +24 -0
- rekindle-0.1.0/src/rekindle/sources/folder.py +401 -0
- rekindle-0.1.0/src/rekindle/web/__init__.py +32 -0
- rekindle-0.1.0/src/rekindle/web/api.py +976 -0
- rekindle-0.1.0/src/rekindle/web/assets/app.css +949 -0
- rekindle-0.1.0/src/rekindle/web/assets/app.js +1202 -0
- rekindle-0.1.0/src/rekindle/web/assets/index.html +292 -0
- rekindle-0.1.0/src/rekindle/web/calibrate_api.py +338 -0
- rekindle-0.1.0/src/rekindle/web/cli.py +216 -0
- rekindle-0.1.0/src/rekindle/web/draft.py +528 -0
- rekindle-0.1.0/src/rekindle/web/library.py +334 -0
- rekindle-0.1.0/src/rekindle/web/renderer.py +253 -0
- rekindle-0.1.0/src/rekindle/web/server.py +640 -0
- rekindle-0.1.0/src/rekindle/web/thumbs.py +110 -0
- rekindle-0.1.0/tests/__init__.py +0 -0
- rekindle-0.1.0/tests/fixtures/__init__.py +0 -0
- rekindle-0.1.0/tests/fixtures/gen.py +307 -0
- rekindle-0.1.0/tests/fixtures/oriented.py +122 -0
- rekindle-0.1.0/tests/fixtures/prompt_gate_concepts.json +113 -0
- rekindle-0.1.0/tests/fixtures/semantic.py +231 -0
- rekindle-0.1.0/tests/fixtures/takeout.py +257 -0
- rekindle-0.1.0/tests/fixtures/web.py +215 -0
- rekindle-0.1.0/tests/test_calibrate.py +927 -0
- rekindle-0.1.0/tests/test_calibrate_cli.py +329 -0
- rekindle-0.1.0/tests/test_caption_real.py +160 -0
- rekindle-0.1.0/tests/test_caption_vocab.py +403 -0
- rekindle-0.1.0/tests/test_captioning.py +408 -0
- rekindle-0.1.0/tests/test_cli.py +324 -0
- rekindle-0.1.0/tests/test_composition.py +544 -0
- rekindle-0.1.0/tests/test_config.py +836 -0
- rekindle-0.1.0/tests/test_db.py +582 -0
- rekindle-0.1.0/tests/test_db_v3.py +369 -0
- rekindle-0.1.0/tests/test_dedup.py +412 -0
- rekindle-0.1.0/tests/test_diversity.py +492 -0
- rekindle-0.1.0/tests/test_doctor.py +342 -0
- rekindle-0.1.0/tests/test_engine.py +1478 -0
- rekindle-0.1.0/tests/test_exif.py +170 -0
- rekindle-0.1.0/tests/test_fetch.py +500 -0
- rekindle-0.1.0/tests/test_fingerprint.py +553 -0
- rekindle-0.1.0/tests/test_fixtures.py +120 -0
- rekindle-0.1.0/tests/test_folder_source.py +543 -0
- rekindle-0.1.0/tests/test_history.py +297 -0
- rekindle-0.1.0/tests/test_identity.py +113 -0
- rekindle-0.1.0/tests/test_llm.py +501 -0
- rekindle-0.1.0/tests/test_memory_cli.py +1461 -0
- rekindle-0.1.0/tests/test_memory_conformance.py +399 -0
- rekindle-0.1.0/tests/test_memory_index.py +535 -0
- rekindle-0.1.0/tests/test_meta_orientation.py +676 -0
- rekindle-0.1.0/tests/test_models.py +290 -0
- rekindle-0.1.0/tests/test_motion.py +405 -0
- rekindle-0.1.0/tests/test_onsets.py +257 -0
- rekindle-0.1.0/tests/test_orientation.py +416 -0
- rekindle-0.1.0/tests/test_packaging.py +296 -0
- rekindle-0.1.0/tests/test_policy.py +365 -0
- rekindle-0.1.0/tests/test_prompt.py +538 -0
- rekindle-0.1.0/tests/test_prompt_real.py +431 -0
- rekindle-0.1.0/tests/test_prompt_tags.py +310 -0
- rekindle-0.1.0/tests/test_recipes.py +728 -0
- rekindle-0.1.0/tests/test_recurring.py +502 -0
- rekindle-0.1.0/tests/test_render.py +1104 -0
- rekindle-0.1.0/tests/test_scenery.py +371 -0
- rekindle-0.1.0/tests/test_scenery_real.py +161 -0
- rekindle-0.1.0/tests/test_semantic_aesthetic.py +216 -0
- rekindle-0.1.0/tests/test_semantic_availability.py +102 -0
- rekindle-0.1.0/tests/test_semantic_cli.py +197 -0
- rekindle-0.1.0/tests/test_semantic_cluster.py +180 -0
- rekindle-0.1.0/tests/test_semantic_diversity.py +314 -0
- rekindle-0.1.0/tests/test_semantic_embed.py +479 -0
- rekindle-0.1.0/tests/test_semantic_face_detector.py +380 -0
- rekindle-0.1.0/tests/test_semantic_faces.py +197 -0
- rekindle-0.1.0/tests/test_semantic_gate_driver.py +521 -0
- rekindle-0.1.0/tests/test_semantic_imports.py +188 -0
- rekindle-0.1.0/tests/test_semantic_mutants.py +289 -0
- rekindle-0.1.0/tests/test_semantic_onnx_preprocess.py +172 -0
- rekindle-0.1.0/tests/test_semantic_photos.py +199 -0
- rekindle-0.1.0/tests/test_semantic_pipeline.py +302 -0
- rekindle-0.1.0/tests/test_semantic_runtime.py +162 -0
- rekindle-0.1.0/tests/test_semantic_search.py +184 -0
- rekindle-0.1.0/tests/test_semantic_staleness_real.py +156 -0
- rekindle-0.1.0/tests/test_semantic_store.py +295 -0
- rekindle-0.1.0/tests/test_semantic_torch_encoder.py +274 -0
- rekindle-0.1.0/tests/test_sidecars.py +65 -0
- rekindle-0.1.0/tests/test_smoke.py +6 -0
- rekindle-0.1.0/tests/test_spec.py +280 -0
- rekindle-0.1.0/tests/test_strata.py +375 -0
- rekindle-0.1.0/tests/test_takeout_albums.py +189 -0
- rekindle-0.1.0/tests/test_takeout_apply.py +260 -0
- rekindle-0.1.0/tests/test_takeout_conformance.py +197 -0
- rekindle-0.1.0/tests/test_takeout_derivatives.py +190 -0
- rekindle-0.1.0/tests/test_takeout_enricher.py +858 -0
- rekindle-0.1.0/tests/test_takeout_fixtures.py +95 -0
- rekindle-0.1.0/tests/test_takeout_index.py +363 -0
- rekindle-0.1.0/tests/test_takeout_parse.py +253 -0
- rekindle-0.1.0/tests/test_timeline.py +182 -0
- rekindle-0.1.0/tests/test_timestamps.py +215 -0
- rekindle-0.1.0/tests/test_videoframe.py +636 -0
- rekindle-0.1.0/tests/test_watch.py +222 -0
- rekindle-0.1.0/tests/test_web_api.py +306 -0
- rekindle-0.1.0/tests/test_web_assets.py +152 -0
- rekindle-0.1.0/tests/test_web_calibrate.py +305 -0
- rekindle-0.1.0/tests/test_web_cli.py +204 -0
- rekindle-0.1.0/tests/test_web_draft.py +350 -0
- rekindle-0.1.0/tests/test_web_progress.py +202 -0
- rekindle-0.1.0/tests/test_web_render.py +257 -0
- rekindle-0.1.0/tests/test_web_semantic.py +164 -0
- rekindle-0.1.0/tests/test_web_server.py +550 -0
- rekindle-0.1.0/tests/test_web_suggestions.py +265 -0
- rekindle-0.1.0/tests/test_web_thumbs.py +106 -0
- rekindle-0.1.0/tests/test_xmp.py +125 -0
- rekindle-0.1.0/uv.lock +1424 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
15
|
+
python-version: ["3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
# Without this the matrix axis is inert: setup-uv gets no version,
|
|
22
|
+
# there is no .python-version file, and all six legs run whatever
|
|
23
|
+
# interpreter the runner ships - so the 3.12 floor in pyproject.toml
|
|
24
|
+
# was never actually verified. The input sets UV_PYTHON, which
|
|
25
|
+
# `uv sync` and `uv run` both honour.
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- run: uv sync --all-extras
|
|
28
|
+
# Proves the pin took effect. A silently-ignored python-version would
|
|
29
|
+
# otherwise leave this job green while testing the wrong interpreter.
|
|
30
|
+
- run: uv run python -c "import sys; assert sys.version.startswith('${{ matrix.python-version }}'), sys.version"
|
|
31
|
+
- run: uv run python -VV
|
|
32
|
+
- run: uv run ruff check .
|
|
33
|
+
- run: uv run ruff format --check .
|
|
34
|
+
- run: uv run pytest -v
|
|
35
|
+
|
|
36
|
+
# --all-extras above means the no-HEIC path is never exercised. It is a
|
|
37
|
+
# supported configuration, so it needs a job.
|
|
38
|
+
test-minimal:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: astral-sh/setup-uv@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.12" # the floor, so the minimal job guards it too
|
|
45
|
+
- run: uv sync
|
|
46
|
+
- run: uv run pytest -v
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Secrets — never commit
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
*.key
|
|
6
|
+
credentials*.json
|
|
7
|
+
client_secret*.json
|
|
8
|
+
token*.json
|
|
9
|
+
|
|
10
|
+
# Personal data — never commit
|
|
11
|
+
# Case variants are spelled out: git matches these patterns case-SENSITIVELY
|
|
12
|
+
# unless core.ignorecase is set (it is, on Windows and default macOS, which
|
|
13
|
+
# is exactly why a lowercase-only `takeout/` looked fine here). On Linux or a
|
|
14
|
+
# case-sensitive macOS volume a real `Takeout/` export is NOT ignored, and
|
|
15
|
+
# one `git add .` publishes tens of thousands of personal photos to a public
|
|
16
|
+
# repo. There is no undo for that.
|
|
17
|
+
[Dd]ata/
|
|
18
|
+
[Tt]akeout/
|
|
19
|
+
[Pp]hotos/
|
|
20
|
+
[Tt]humbnails/
|
|
21
|
+
index/
|
|
22
|
+
memories/
|
|
23
|
+
|
|
24
|
+
# Media extensions, at any depth, on any filesystem. The directory rules above
|
|
25
|
+
# only protect an export that sits in a directory NAMED Takeout/Data/Photos -
|
|
26
|
+
# extract one anywhere else (or into a subfolder of a working directory) and a
|
|
27
|
+
# contributor's `git add .` publishes it. This repo is PUBLIC and there is no
|
|
28
|
+
# undo for that, so this is defence in depth on the one irreversible mistake.
|
|
29
|
+
#
|
|
30
|
+
# Spelled out with the same [Jj][Pp][Gg] bracket style as the directories
|
|
31
|
+
# above, for the same reason: git matches patterns case-SENSITIVELY unless
|
|
32
|
+
# core.ignorecase is set (it is on Windows and default macOS - and NOT on
|
|
33
|
+
# Linux or a case-sensitive macOS volume, where a `.JPG` from a camera is the
|
|
34
|
+
# common case, not the exotic one).
|
|
35
|
+
#
|
|
36
|
+
# Verified with `git ls-files` before and after: the repo tracks no file with
|
|
37
|
+
# any of these extensions, so nothing already tracked becomes ignored. If you
|
|
38
|
+
# ever need to commit a real media test asset, force it with `git add -f` and
|
|
39
|
+
# add an explicit `!` negation here next to it.
|
|
40
|
+
*.[Jj][Pp][Gg]
|
|
41
|
+
*.[Jj][Pp][Ee][Gg]
|
|
42
|
+
*.[Pp][Nn][Gg]
|
|
43
|
+
*.[Gg][Ii][Ff]
|
|
44
|
+
*.[Bb][Mm][Pp]
|
|
45
|
+
*.[Ww][Ee][Bb][Pp]
|
|
46
|
+
*.[Tt][Ii][Ff]
|
|
47
|
+
*.[Tt][Ii][Ff][Ff]
|
|
48
|
+
*.[Hh][Ee][Ii][Cc]
|
|
49
|
+
*.[Hh][Ee][Ii][Ff]
|
|
50
|
+
*.[Dd][Nn][Gg]
|
|
51
|
+
*.[Cc][Rr]2
|
|
52
|
+
*.[Cc][Rr]3
|
|
53
|
+
*.[Nn][Ee][Ff]
|
|
54
|
+
*.[Aa][Rr][Ww]
|
|
55
|
+
*.[Oo][Rr][Ff]
|
|
56
|
+
*.[Rr][Ww]2
|
|
57
|
+
*.[Rr][Aa][Ff]
|
|
58
|
+
*.[Rr][Aa][Ww]
|
|
59
|
+
*.[Mm][Pp]4
|
|
60
|
+
*.[Mm][Oo][Vv]
|
|
61
|
+
*.[Mm]4[Vv]
|
|
62
|
+
*.[Aa][Vv][Ii]
|
|
63
|
+
*.[Mm][Kk][Vv]
|
|
64
|
+
*.[Ww][Mm][Vv]
|
|
65
|
+
*.[Ww][Ee][Bb][Mm]
|
|
66
|
+
*.3[Gg][Pp]
|
|
67
|
+
*.[Mm][Tt][Ss]
|
|
68
|
+
*.[Mm]2[Tt][Ss]
|
|
69
|
+
*.[Mm][Pp][Gg]
|
|
70
|
+
*.[Mm][Pp][Ee][Gg]
|
|
71
|
+
# Google's motion-photo video half, exported beside the still as `<name>.MP`.
|
|
72
|
+
*.[Mm][Pp]
|
|
73
|
+
|
|
74
|
+
# Python
|
|
75
|
+
__pycache__/
|
|
76
|
+
*.py[cod]
|
|
77
|
+
.venv/
|
|
78
|
+
venv/
|
|
79
|
+
*.egg-info/
|
|
80
|
+
dist/
|
|
81
|
+
build/
|
|
82
|
+
.pytest_cache/
|
|
83
|
+
.ruff_cache/
|
|
84
|
+
.mypy_cache/
|
|
85
|
+
.coverage
|
|
86
|
+
htmlcov/
|
|
87
|
+
|
|
88
|
+
# Models
|
|
89
|
+
models/
|
|
90
|
+
*.safetensors
|
|
91
|
+
*.onnx
|
|
92
|
+
|
|
93
|
+
# OS / editors
|
|
94
|
+
.DS_Store
|
|
95
|
+
Thumbs.db
|
|
96
|
+
.idea/
|
|
97
|
+
.vscode/
|
|
98
|
+
|
|
99
|
+
# Archives, except test fixtures
|
|
100
|
+
*.zip
|
|
101
|
+
*.tgz
|
|
102
|
+
!tests/fixtures/**/*.zip
|
|
103
|
+
!tests/fixtures/**/*.tgz
|
|
104
|
+
|
|
105
|
+
# SDD scratch workspace
|
|
106
|
+
.superpowers/
|
|
107
|
+
|
|
108
|
+
# Agent harness scratch. `.claude/worktrees/` holds git worktrees, which git
|
|
109
|
+
# records as GITLINKS - a `git add .` commits a reference to a commit that
|
|
110
|
+
# does not exist for anyone who clones the repo, producing a broken empty
|
|
111
|
+
# directory and submodule warnings. Ignored so it cannot happen again.
|
|
112
|
+
.claude/
|
|
113
|
+
|
|
114
|
+
# Music beds for memory soundtracks. Bring-your-own or fetched;
|
|
115
|
+
# never committed - this is a public repo.
|
|
116
|
+
[Mm]usic/
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Contributing to rekindle
|
|
2
|
+
|
|
3
|
+
Thanks for considering it. This project is meant to be extended.
|
|
4
|
+
|
|
5
|
+
## The three extension points
|
|
6
|
+
|
|
7
|
+
Almost every contribution fits one of these, and none of them require touching
|
|
8
|
+
the core engine.
|
|
9
|
+
|
|
10
|
+
### 1. A new memory type (best first contribution)
|
|
11
|
+
|
|
12
|
+
A `Recipe` decides which photos belong in a memory and how they are ordered.
|
|
13
|
+
One file, one protocol, one registry entry:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
class Recipe(Protocol):
|
|
17
|
+
name: str
|
|
18
|
+
params_model: type[BaseModel]
|
|
19
|
+
|
|
20
|
+
def candidates(self, params, index) -> list[Photo]: ...
|
|
21
|
+
def order(self, photos, params) -> list[PhotoRef]: ...
|
|
22
|
+
def fact_sheet(self, ordered, params) -> FactSheet: ...
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Recipes choose photos and their order. They do **not** own timing — a separate
|
|
26
|
+
`Timeline` stage does, so beat-synced music doesn't break every recipe. That
|
|
27
|
+
stage now exists (`memory/render/timeline.py`) and nothing above it had to
|
|
28
|
+
change to get crossfades, Ken Burns or beat-snapped cuts. Full guide:
|
|
29
|
+
[writing-recipes.md](docs/writing-recipes.md).
|
|
30
|
+
|
|
31
|
+
A recipe also cannot reach the semantic layer, which is why **scenery memories
|
|
32
|
+
are not a recipe**: selection needs a retriever, `Recipe.select` has nowhere to
|
|
33
|
+
receive one, and CI has no embeddings at all. They hand the engine a
|
|
34
|
+
`Selection` directly, exactly as prompt memories do, and every guardrail below
|
|
35
|
+
`engine.build` still applies. See `memory/scenery.py`.
|
|
36
|
+
|
|
37
|
+
Ideas nobody has built yet: *Kids Growing Up*, *Every Sunset*, *This Café Over
|
|
38
|
+
The Years*, *Seasons In One Place*, *Everyone Who Came To Dinner*.
|
|
39
|
+
|
|
40
|
+
### 2. A new photo source
|
|
41
|
+
|
|
42
|
+
`Source` normalises any library into `Photo` records. Apple Photos, Immich,
|
|
43
|
+
Nextcloud, Synology Photos and PhotoPrism are all wanted. One protocol, two
|
|
44
|
+
members. Full guide: [writing-sources.md](docs/writing-sources.md).
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
class Source(Protocol):
|
|
48
|
+
name: str
|
|
49
|
+
|
|
50
|
+
def scan(self, root: Path) -> tuple[list[Photo], SourceReport]: ...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. A new model backend
|
|
54
|
+
|
|
55
|
+
`Embedder`, `Captioner` and `Narrator` are swappable. Ollama, llama.cpp,
|
|
56
|
+
Gemini and local VLMs all fit.
|
|
57
|
+
|
|
58
|
+
### 4. A corpus entry (the smallest useful contribution)
|
|
59
|
+
|
|
60
|
+
Three checked-in data files decide what rekindle can find and what it may say,
|
|
61
|
+
and none of them needs a line of code:
|
|
62
|
+
|
|
63
|
+
- `corpus/festivals.toml` — a festival, and what a photograph of it looks like.
|
|
64
|
+
- `corpus/scenery.toml` — a scene that can be the subject of a memory.
|
|
65
|
+
- `corpus/caption_vocab.toml` — a thing a caption is allowed to mention.
|
|
66
|
+
|
|
67
|
+
**Grade what you add.** Every entry in the first two carries the count someone
|
|
68
|
+
got by looking at a contact sheet of what it returned, and the weak entries
|
|
69
|
+
say what is wrong with them rather than being quietly dropped. A description
|
|
70
|
+
that "seems right" is how nine of twenty-four shots of a Kali Puja memory
|
|
71
|
+
ended up on a Durga Puja day. The caption vocabulary is stricter still: five
|
|
72
|
+
rules about what may never be in it, all enforced by
|
|
73
|
+
`tests/test_caption_vocab.py`, which fails the build rather than trusting a
|
|
74
|
+
comment.
|
|
75
|
+
|
|
76
|
+
## Ground rules
|
|
77
|
+
|
|
78
|
+
**Never commit personal data.** No photos, no Takeout exports, no `.env`. The
|
|
79
|
+
`.gitignore` is deliberately aggressive — please keep it that way. It ignores
|
|
80
|
+
the usual export folders (`Takeout/`, `Data/`, `Photos/`, `Thumbnails/`) **and**
|
|
81
|
+
every common photo and video extension at any depth — `.jpg`, `.JPG`, `.heic`,
|
|
82
|
+
`.dng`, `.CR2`, `.mp4`, `.MOV`, `.MP` and friends — each spelled with
|
|
83
|
+
`[Jj][Pp][Gg]`-style brackets so it still holds on a case-sensitive filesystem,
|
|
84
|
+
where an export extracted outside those folders would otherwise have no
|
|
85
|
+
protection at all. This repo is public and that mistake cannot be undone. If you
|
|
86
|
+
genuinely need to commit a media test asset, `git add -f` it and add an explicit
|
|
87
|
+
`!` negation beside the rule.
|
|
88
|
+
|
|
89
|
+
**Tests must run without photos, without a GPU, and without API keys.** CI has
|
|
90
|
+
none of those. Use the synthetic fixture generator in `tests/fixtures/` and the
|
|
91
|
+
`Fake*` providers. If your change needs a real model to be tested, it needs a
|
|
92
|
+
fake too.
|
|
93
|
+
|
|
94
|
+
**Cross-platform.** Windows, macOS and Linux are all supported. Use `pathlib`,
|
|
95
|
+
never hardcode separators or drive letters, and don't assume ffmpeg's location.
|
|
96
|
+
|
|
97
|
+
**Found a new Takeout quirk?** That's a genuinely valuable bug report. Please
|
|
98
|
+
include the filename shape and what `rekindle doctor` said — and if you can,
|
|
99
|
+
add a fixture case reproducing it.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv sync
|
|
105
|
+
uv run pytest
|
|
106
|
+
uv run ruff check .
|
|
107
|
+
uv run ruff format .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Packaging
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv build # wheel + sdist into dist/
|
|
114
|
+
uv run pytest tests/test_packaging.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`tests/test_packaging.py` checks the wheel's contents only when `dist/` holds
|
|
118
|
+
one, so build first or the interesting half silently skips.
|
|
119
|
+
|
|
120
|
+
**Publishing is not automated and no agent does it.** See
|
|
121
|
+
[docs/publishing.md](https://github.com/abhikatoldtrafford/rekindle/blob/main/docs/publishing.md)
|
|
122
|
+
for the single command, what it irreversibly does, and the checklist before it.
|
|
123
|
+
|
|
124
|
+
### The semantic extras
|
|
125
|
+
|
|
126
|
+
`rekindle semantic ...` (embeddings, search, scene clusters, aesthetic
|
|
127
|
+
ranking, the face gate) lives behind two optional extras, and **CI installs
|
|
128
|
+
neither**:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv sync --extra semantic # numpy + onnxruntime, CPU, ~120 MB
|
|
132
|
+
uv sync --extra semantic-gpu # adds torch + transformers, CUDA, ~2.5 GB
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You do not need either to work on rekindle, or to run the tests. The semantic
|
|
136
|
+
tests use `tests/fixtures/semantic.py`, whose `ToyEncoder` is a real (tiny)
|
|
137
|
+
joint image/text embedding function over a colour grid — not a mock. If you
|
|
138
|
+
add a test there, assert the *answer*, not that the encoder was called: a test
|
|
139
|
+
that mocks the model and checks the mock was called proves nothing.
|
|
140
|
+
|
|
141
|
+
Three rules for anything under `src/rekindle/semantic/`:
|
|
142
|
+
|
|
143
|
+
- **No heavy import at module level.** `rekindle --help` must not import
|
|
144
|
+
numpy, torch, onnxruntime or transformers.
|
|
145
|
+
`tests/test_semantic_imports.py` checks this in a clean subprocess.
|
|
146
|
+
- **Every feature degrades to a sentence**, never an ImportError, and exits 3.
|
|
147
|
+
- **Model weights are pinned to a commit and checksummed.** Adding a model
|
|
148
|
+
means adding a `RepoPin` with a 40-hex revision, a licence with a URL, and
|
|
149
|
+
an entry in `model-locks.json` (`rekindle semantic setup --write-lock`).
|
|
150
|
+
|
|
151
|
+
### Testing against a real Takeout export
|
|
152
|
+
|
|
153
|
+
Two of this project's three worst bugs were invisible to a green test suite and
|
|
154
|
+
obvious within one run against real data. If you have a Google Takeout export:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
REKINDLE_TAKEOUT_DIR="/path/to/Takeout/Google Photos" uv run pytest \
|
|
158
|
+
tests/test_takeout_conformance.py -v
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Those tests skip without the variable, so CI never needs your photos. Never
|
|
162
|
+
commit an export, or any file from one.
|
|
163
|
+
|
|
164
|
+
Every genuine bug in the Takeout enrichment milestone was found this way and
|
|
165
|
+
none by reading. The memory engine repeated the pattern: two of its bugs were
|
|
166
|
+
invisible to 700 passing tests and obvious within one run against real data.
|
|
167
|
+
|
|
168
|
+
If you have an index, the memory engine has its own conformance suite:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
REKINDLE_MEMORY_DB=data/rekindle.sqlite uv run pytest \
|
|
172
|
+
tests/test_memory_conformance.py -v
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Both decision logs record the defects, the judgement calls and the testing
|
|
176
|
+
discipline they led to:
|
|
177
|
+
[Takeout enrichment](docs/decision-log-takeout-enrichment.md) ·
|
|
178
|
+
[the memory engine](docs/decision-log-memory-engine.md).
|
|
179
|
+
|
|
180
|
+
## Pull requests
|
|
181
|
+
|
|
182
|
+
Keep them focused, explain the why, and add tests. If you're planning something
|
|
183
|
+
large, open an issue first so we can agree on the shape before you build it.
|
rekindle-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhik Maiti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|