screengraft 0.62.1 → 0.62.2
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.
- package/package.json +1 -1
- package/scripts/recents.py +13 -0
- package/scripts/ui.py +14 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.62.
|
|
3
|
+
"version": "0.62.2",
|
|
4
4
|
"description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mockup",
|
package/scripts/recents.py
CHANGED
|
@@ -39,7 +39,16 @@ def _load() -> dict:
|
|
|
39
39
|
if not isinstance(d, dict):
|
|
40
40
|
raise ValueError
|
|
41
41
|
except (OSError, ValueError):
|
|
42
|
+
# No store (or an unreadable one): build it from the sessions on disk
|
|
43
|
+
# and WRITE it, or every read until the first record() walks them again.
|
|
42
44
|
d = _seed()
|
|
45
|
+
for r in ROLES:
|
|
46
|
+
d.setdefault(r, [])
|
|
47
|
+
try:
|
|
48
|
+
_save(d)
|
|
49
|
+
except OSError:
|
|
50
|
+
pass
|
|
51
|
+
return d
|
|
43
52
|
for r in ROLES:
|
|
44
53
|
d.setdefault(r, [])
|
|
45
54
|
return d
|
|
@@ -52,6 +61,10 @@ def _save(d: dict):
|
|
|
52
61
|
with open(tmp, "w") as f:
|
|
53
62
|
json.dump(d, f, indent=1)
|
|
54
63
|
os.replace(tmp, p)
|
|
64
|
+
try:
|
|
65
|
+
os.chmod(p, 0o600) # paths under the user's home: owner-only
|
|
66
|
+
except OSError:
|
|
67
|
+
pass
|
|
55
68
|
|
|
56
69
|
|
|
57
70
|
def _seed() -> dict:
|
package/scripts/ui.py
CHANGED
|
@@ -28,6 +28,7 @@ stdlib only on the server side; OpenCV via detect/warp.
|
|
|
28
28
|
"""
|
|
29
29
|
import argparse
|
|
30
30
|
import atexit
|
|
31
|
+
import hashlib
|
|
31
32
|
import json
|
|
32
33
|
import mimetypes
|
|
33
34
|
import os
|
|
@@ -733,7 +734,10 @@ def _thumb_for(item):
|
|
|
733
734
|
out_dir = os.path.join(SESSION.dir, "thumbs")
|
|
734
735
|
if item.get("ext") in VIDEO_EXT:
|
|
735
736
|
os.makedirs(out_dir, exist_ok=True)
|
|
736
|
-
|
|
737
|
+
# A stable name: hash() is salted per process, so a name built on it
|
|
738
|
+
# misses the cache on every launch.
|
|
739
|
+
key = hashlib.sha1(item["path"].encode()).hexdigest()[:16]
|
|
740
|
+
out = os.path.join(out_dir, f"{key}_{int(item.get('mtime') or 0)}_clip.jpg")
|
|
737
741
|
if not os.path.exists(out):
|
|
738
742
|
try:
|
|
739
743
|
fr = W.read_frame_at(item["path"], 0)
|
|
@@ -981,7 +985,10 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
981
985
|
# The folder scan (scan.py) still exists for the CLI and for a
|
|
982
986
|
# first run with nothing used yet; ?role= selects the list.
|
|
983
987
|
role = (q.get("role") or ["photo"])[0]
|
|
984
|
-
|
|
988
|
+
try:
|
|
989
|
+
limit = max(1, min(200, int(q.get("limit", ["40"])[0])))
|
|
990
|
+
except ValueError:
|
|
991
|
+
limit = 40
|
|
985
992
|
items = RECENT.items(role, limit)
|
|
986
993
|
for it in items:
|
|
987
994
|
it["thumb"] = _thumb_for(it)
|
|
@@ -1546,6 +1553,11 @@ def _publish_current(payload):
|
|
|
1546
1553
|
"""
|
|
1547
1554
|
os.makedirs(os.path.dirname(CURRENT), exist_ok=True)
|
|
1548
1555
|
_write_json_atomic(CURRENT, payload)
|
|
1556
|
+
# The pointer carries the session token: owner-only, like a credential.
|
|
1557
|
+
try:
|
|
1558
|
+
os.chmod(CURRENT, 0o600)
|
|
1559
|
+
except OSError:
|
|
1560
|
+
pass
|
|
1549
1561
|
|
|
1550
1562
|
def clear():
|
|
1551
1563
|
cur = None
|