Picture the duplicate originals review in the manual

The manual described the review (7c9a4ee) without a picture, because the
demo library holds no duplicates. The new duplicates scene makes two:
it copies two New York frames into a bck folder beside their own,
restarts the app so the scan finds them, waits for the sidebar row the
sweep's dating brings (54aee50), opens the review from it, presses
Check and takes the page. It deletes the copies and restarts on the
library as it was; record.sh's snapshot restore would remove them too.
It is registered last, so no other scene sees the copies.

The picture shows both groups proved the same file, the camera-named
copy outside bck marked Stays, and "Move 2 copies to trash" ready.
This commit is contained in:
2026-09-26 08:02:51 -04:00
parent 048d48f532
commit b1d36b9143
4 changed files with 68 additions and 0 deletions
+2
View File
@@ -145,6 +145,8 @@ flag or label the copies agree on, faces and the names on them, and the
edit if only a copy had one. Where the copies disagree on a flag or a
label, the one that stays keeps its own and the line says so.
![Two frames with a copy each in a bck folder, checked and proved the same, the copy that stays marked on each](media/duplicates.png)
`Move N copies to trash` does it, one group at a time: each group is merged
and its spare copies moved together, or not at all. The copies go to the
trash, not away — select them in the trash view and `Restore` puts them
+1
View File
@@ -242,6 +242,7 @@ that stays will gain: the highest rating, every keyword and collection, a
flag or label the copies agree on, faces and the names on them, and the
edit if only a copy had one. Where the copies disagree on a flag or a
label, the one that stays keeps its own and the line says so.</p>
<figure><img loading="lazy" src="media/duplicates.png" alt="Two frames with a copy each in a bck folder, checked and proved the same, the copy that stays marked on each"><figcaption>Two frames with a copy each in a bck folder, checked and proved the same, the copy that stays marked on each</figcaption></figure>
<p><code>Move N copies to trash</code> does it, one group at a time: each group is merged
and its spare copies moved together, or not at all. The copies go to the
trash, not away — select them in the trash view and <code>Restore</code> puts them
Binary file not shown.
+62
View File
@@ -34,6 +34,7 @@ import fnmatch
import inspect
import os
import re
import shutil
import subprocess
import sys
import tempfile
@@ -1182,6 +1183,67 @@ def settings():
pause(1.5)
# --- duplicate originals ---------------------------------------------------
DUPLICATED = [NY_FIRST, '_MG_8394'] # copied into a `bck` folder beside their own
def library_file(name):
"""The path of file `name` in the demo library."""
for root, _, files in os.walk(DEMO_LIBRARY):
if name in files:
return os.path.join(root, name)
raise FileNotFoundError(f'{name} is not in {DEMO_LIBRARY}')
@scene(media=['duplicates.png'],
sources=LIBRARY_SRC + ['ui/dr-ui/ui/duplicates.slint', 'ui/dr-ui/src/duplicates_ui.rs',
'ui/dr-ui/src/duplicates.rs', 'core/dr-catalog/src/duplicates.rs'])
def duplicates():
"""The demo library holds no duplicates, so this makes two: copies of two
New York frames in a `bck` folder beside theirs, the way a hand-kept
backup sits. The app is restarted so its scan finds them, and the row
under the trash appears once the sweep has read their capture times. The
review is opened from there and checked, and the copies are deleted again
before the app is restarted on the library as it was. (The snapshot
record.sh restores would remove them too.)"""
made = []
try:
for name in DUPLICATED:
src = library_file(f'{name}.CR2')
folder = os.path.join(os.path.dirname(src), 'bck')
os.makedirs(folder, exist_ok=True)
made.append(os.path.join(folder, f'{name}.CR2'))
shutil.copy2(src, made[-1])
dr.stop()
dr.launch([])
dr.wait_ready()
dr.wait_for('id:grid-scroll', 60)
dr.wait_for('Duplicate originals@Text', 300)
pause(1)
dr.click_on('Duplicate originals@Text')
dr.wait_for(f'Check {len(DUPLICATED)} groups@Button', 30)
pause(1)
dr.click_on(f'Check {len(DUPLICATED)} groups@Button')
dr.wait_gone(f'Check {len(DUPLICATED)} groups@Button', 60)
pause(3)
shot('duplicates')
dr.click_on('‹ Back@Button')
pause(1)
finally:
for f in made:
if os.path.exists(f):
os.remove(f)
for f in made:
d = os.path.dirname(f)
if os.path.isdir(d) and not os.listdir(d):
os.rmdir(d)
dr.stop()
dr.launch([])
dr.wait_ready()
dr.wait_for('id:grid-scroll', 60)
pause(3)
# --- running them -----------------------------------------------------------
def order(names):
"""`names` with their prerequisites, each once, prerequisites first."""