pensiev 0.25.5__py3-none-any.whl
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.
- memos/__init__.py +6 -0
- memos/cmds/__init__.py +0 -0
- memos/cmds/library.py +1289 -0
- memos/cmds/plugin.py +96 -0
- memos/commands.py +865 -0
- memos/config.py +225 -0
- memos/crud.py +605 -0
- memos/databases/__init__.py +0 -0
- memos/databases/initializers.py +481 -0
- memos/dataset_extractor_for_florence.py +165 -0
- memos/dataset_extractor_for_internvl2.py +192 -0
- memos/default_config.yaml +88 -0
- memos/embedding.py +129 -0
- memos/frame_extractor.py +53 -0
- memos/logging_config.py +35 -0
- memos/main.py +104 -0
- memos/migrations/alembic/README +1 -0
- memos/migrations/alembic/__pycache__/env.cpython-310.pyc +0 -0
- memos/migrations/alembic/env.py +108 -0
- memos/migrations/alembic/script.py.mako +30 -0
- memos/migrations/alembic/versions/00904ac8c6fc_add_indexes_to_entitymodel.py +63 -0
- memos/migrations/alembic/versions/04acdaf75664_add_indices_to_entitytags_and_metadata.py +86 -0
- memos/migrations/alembic/versions/12504c5b1d3c_add_extra_columns_for_embedding.py +67 -0
- memos/migrations/alembic/versions/31a1ad0e10b3_add_entity_plugin_status.py +71 -0
- memos/migrations/alembic/versions/__pycache__/00904ac8c6fc_add_indexes_to_entitymodel.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/04acdaf75664_add_indices_to_entitytags_and_metadata.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/12504c5b1d3c_add_extra_columns_for_embedding.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/20f5ecab014d_add_entity_plugin_status.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/31a1ad0e10b3_add_entity_plugin_status.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/4fcb062c5128_add_extra_columns_for_embedding.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/d10c55fbb7d2_add_index_for_entity_file_type_group_.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/__pycache__/f8f158182416_add_active_app_index.cpython-310.pyc +0 -0
- memos/migrations/alembic/versions/d10c55fbb7d2_add_index_for_entity_file_type_group_.py +44 -0
- memos/migrations/alembic/versions/f8f158182416_add_active_app_index.py +75 -0
- memos/migrations/alembic.ini +116 -0
- memos/migrations.py +19 -0
- memos/models.py +199 -0
- memos/plugins/__init__.py +0 -0
- memos/plugins/ocr/__init__.py +0 -0
- memos/plugins/ocr/main.py +251 -0
- memos/plugins/ocr/models/ch_PP-OCRv4_det_infer.onnx +0 -0
- memos/plugins/ocr/models/ch_PP-OCRv4_rec_infer.onnx +0 -0
- memos/plugins/ocr/models/ch_ppocr_mobile_v2.0_cls_train.onnx +0 -0
- memos/plugins/ocr/ppocr-gpu.yaml +43 -0
- memos/plugins/ocr/ppocr.yaml +44 -0
- memos/plugins/ocr/server.py +227 -0
- memos/plugins/ocr/temp_ppocr.yaml +42 -0
- memos/plugins/vlm/__init__.py +0 -0
- memos/plugins/vlm/main.py +251 -0
- memos/prepare_dataset.py +107 -0
- memos/process_webp.py +55 -0
- memos/read_metadata.py +32 -0
- memos/record.py +358 -0
- memos/schemas.py +289 -0
- memos/search.py +1198 -0
- memos/server.py +883 -0
- memos/shotsum.py +105 -0
- memos/shotsum_with_ocr.py +145 -0
- memos/simple_tokenizer/dict/README.md +31 -0
- memos/simple_tokenizer/dict/hmm_model.utf8 +34 -0
- memos/simple_tokenizer/dict/idf.utf8 +258826 -0
- memos/simple_tokenizer/dict/jieba.dict.utf8 +348982 -0
- memos/simple_tokenizer/dict/pos_dict/char_state_tab.utf8 +6653 -0
- memos/simple_tokenizer/dict/pos_dict/prob_emit.utf8 +166 -0
- memos/simple_tokenizer/dict/pos_dict/prob_start.utf8 +259 -0
- memos/simple_tokenizer/dict/pos_dict/prob_trans.utf8 +5222 -0
- memos/simple_tokenizer/dict/stop_words.utf8 +1534 -0
- memos/simple_tokenizer/dict/user.dict.utf8 +4 -0
- memos/simple_tokenizer/linux/libsimple.so +0 -0
- memos/simple_tokenizer/macos/libsimple.dylib +0 -0
- memos/simple_tokenizer/windows/simple.dll +0 -0
- memos/static/_app/immutable/assets/0.e250c031.css +1 -0
- memos/static/_app/immutable/assets/_layout.e7937cfe.css +1 -0
- memos/static/_app/immutable/chunks/index.5c08976b.js +1 -0
- memos/static/_app/immutable/chunks/index.60ee613b.js +4 -0
- memos/static/_app/immutable/chunks/runtime.a7926cf6.js +5 -0
- memos/static/_app/immutable/chunks/scheduler.5c1cff6e.js +1 -0
- memos/static/_app/immutable/chunks/singletons.583bdf4e.js +1 -0
- memos/static/_app/immutable/entry/app.666c1643.js +1 -0
- memos/static/_app/immutable/entry/start.aed5c701.js +3 -0
- memos/static/_app/immutable/nodes/0.5862ea38.js +7 -0
- memos/static/_app/immutable/nodes/1.35378a5e.js +1 -0
- memos/static/_app/immutable/nodes/2.1ccf9ea5.js +81 -0
- memos/static/_app/version.json +1 -0
- memos/static/app.html +36 -0
- memos/static/favicon.png +0 -0
- memos/static/logos/memos_logo_1024.png +0 -0
- memos/static/logos/memos_logo_1024@2x.png +0 -0
- memos/static/logos/memos_logo_128.png +0 -0
- memos/static/logos/memos_logo_128@2x.png +0 -0
- memos/static/logos/memos_logo_16.png +0 -0
- memos/static/logos/memos_logo_16@2x.png +0 -0
- memos/static/logos/memos_logo_256.png +0 -0
- memos/static/logos/memos_logo_256@2x.png +0 -0
- memos/static/logos/memos_logo_32.png +0 -0
- memos/static/logos/memos_logo_32@2x.png +0 -0
- memos/static/logos/memos_logo_512.png +0 -0
- memos/static/logos/memos_logo_512@2x.png +0 -0
- memos/static/logos/memos_logo_64.png +0 -0
- memos/static/logos/memos_logo_64@2x.png +0 -0
- memos/test_server.py +802 -0
- memos/utils.py +49 -0
- memos_ml_backends/florence2_server.py +176 -0
- memos_ml_backends/qwen2vl_server.py +182 -0
- memos_ml_backends/schemas.py +48 -0
- pensiev-0.25.5.dist-info/LICENSE +201 -0
- pensiev-0.25.5.dist-info/METADATA +541 -0
- pensiev-0.25.5.dist-info/RECORD +111 -0
- pensiev-0.25.5.dist-info/WHEEL +5 -0
- pensiev-0.25.5.dist-info/entry_points.txt +2 -0
- pensiev-0.25.5.dist-info/top_level.txt +2 -0
memos/read_metadata.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
import json
|
2
|
+
import argparse
|
3
|
+
from .utils import get_image_metadata
|
4
|
+
|
5
|
+
|
6
|
+
def read_metadata(image_path):
|
7
|
+
try:
|
8
|
+
metadata = get_image_metadata(image_path)
|
9
|
+
|
10
|
+
if metadata is None:
|
11
|
+
print("No metadata found or unsupported file format.")
|
12
|
+
return None
|
13
|
+
|
14
|
+
return metadata if metadata else None
|
15
|
+
|
16
|
+
except Exception as e:
|
17
|
+
print(f"An error occurred: {str(e)}")
|
18
|
+
return None
|
19
|
+
|
20
|
+
|
21
|
+
def main():
|
22
|
+
parser = argparse.ArgumentParser(description="Read metadata from a screenshot")
|
23
|
+
parser.add_argument("image_path", type=str, help="Path to the screenshot image")
|
24
|
+
args = parser.parse_args()
|
25
|
+
|
26
|
+
metadata = read_metadata(args.image_path)
|
27
|
+
if metadata is not None:
|
28
|
+
print(json.dumps(metadata, indent=4))
|
29
|
+
|
30
|
+
|
31
|
+
if __name__ == "__main__":
|
32
|
+
main()
|
memos/record.py
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
import logging
|
5
|
+
import platform
|
6
|
+
import subprocess
|
7
|
+
import argparse
|
8
|
+
from PIL import Image
|
9
|
+
import imagehash
|
10
|
+
from memos.utils import write_image_metadata
|
11
|
+
import ctypes
|
12
|
+
from mss import mss
|
13
|
+
from pathlib import Path
|
14
|
+
from memos.config import settings
|
15
|
+
|
16
|
+
# Import platform-specific modules
|
17
|
+
if platform.system() == "Windows":
|
18
|
+
import win32gui
|
19
|
+
import win32process
|
20
|
+
import psutil
|
21
|
+
elif platform.system() == "Darwin":
|
22
|
+
from AppKit import NSWorkspace
|
23
|
+
from Quartz import (
|
24
|
+
CGWindowListCopyWindowInfo,
|
25
|
+
kCGWindowListOptionOnScreenOnly,
|
26
|
+
kCGNullWindowID,
|
27
|
+
CGSessionCopyCurrentDictionary,
|
28
|
+
)
|
29
|
+
|
30
|
+
logging.basicConfig(
|
31
|
+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
32
|
+
)
|
33
|
+
|
34
|
+
|
35
|
+
# Functions moved from common.py
|
36
|
+
def load_screen_sequences(base_dir, date):
|
37
|
+
try:
|
38
|
+
with open(os.path.join(base_dir, date, ".screen_sequences"), "r") as f:
|
39
|
+
return json.load(f)
|
40
|
+
except FileNotFoundError:
|
41
|
+
return {}
|
42
|
+
|
43
|
+
|
44
|
+
def save_screen_sequences(base_dir, screen_sequences, date):
|
45
|
+
with open(os.path.join(base_dir, date, ".screen_sequences"), "w") as f:
|
46
|
+
json.dump(screen_sequences, f)
|
47
|
+
f.flush()
|
48
|
+
os.fsync(f.fileno())
|
49
|
+
|
50
|
+
|
51
|
+
def load_previous_hashes(base_dir):
|
52
|
+
date = time.strftime("%Y%m%d")
|
53
|
+
hash_file = os.path.join(base_dir, date, ".previous_hashes")
|
54
|
+
try:
|
55
|
+
with open(hash_file, "r") as f:
|
56
|
+
return json.load(f)
|
57
|
+
except FileNotFoundError:
|
58
|
+
return {}
|
59
|
+
|
60
|
+
|
61
|
+
def save_previous_hashes(base_dir, previous_hashes):
|
62
|
+
date = time.strftime("%Y%m%d")
|
63
|
+
hash_file = os.path.join(base_dir, date, ".previous_hashes")
|
64
|
+
os.makedirs(os.path.dirname(hash_file), exist_ok=True)
|
65
|
+
with open(hash_file, "w") as f:
|
66
|
+
json.dump(previous_hashes, f)
|
67
|
+
|
68
|
+
|
69
|
+
def get_active_window_info_darwin():
|
70
|
+
active_app = NSWorkspace.sharedWorkspace().activeApplication()
|
71
|
+
app_name = active_app["NSApplicationName"]
|
72
|
+
app_pid = active_app["NSApplicationProcessIdentifier"]
|
73
|
+
|
74
|
+
windows = CGWindowListCopyWindowInfo(
|
75
|
+
kCGWindowListOptionOnScreenOnly, kCGNullWindowID
|
76
|
+
)
|
77
|
+
for window in windows:
|
78
|
+
if window["kCGWindowOwnerPID"] == app_pid:
|
79
|
+
window_title = window.get("kCGWindowName", "")
|
80
|
+
if window_title:
|
81
|
+
return app_name, window_title
|
82
|
+
|
83
|
+
return app_name, "" # 如果没有找到窗口标题,则返回空字符串作为标题
|
84
|
+
|
85
|
+
|
86
|
+
def get_active_window_info_windows():
|
87
|
+
try:
|
88
|
+
window = win32gui.GetForegroundWindow()
|
89
|
+
_, pid = win32process.GetWindowThreadProcessId(window)
|
90
|
+
app_name = psutil.Process(pid).name()
|
91
|
+
window_title = win32gui.GetWindowText(window)
|
92
|
+
return app_name, window_title
|
93
|
+
except:
|
94
|
+
return "", ""
|
95
|
+
|
96
|
+
|
97
|
+
def get_active_window_info():
|
98
|
+
if platform.system() == "Darwin":
|
99
|
+
return get_active_window_info_darwin()
|
100
|
+
elif platform.system() == "Windows":
|
101
|
+
return get_active_window_info_windows()
|
102
|
+
|
103
|
+
|
104
|
+
def take_screenshot_macos(
|
105
|
+
base_dir,
|
106
|
+
previous_hashes,
|
107
|
+
threshold,
|
108
|
+
screen_sequences,
|
109
|
+
date,
|
110
|
+
timestamp,
|
111
|
+
app_name,
|
112
|
+
window_title,
|
113
|
+
):
|
114
|
+
screenshots = []
|
115
|
+
result = subprocess.check_output(["system_profiler", "SPDisplaysDataType", "-json"])
|
116
|
+
displays_info = json.loads(result)["SPDisplaysDataType"][0]["spdisplays_ndrvs"]
|
117
|
+
screen_names = {}
|
118
|
+
|
119
|
+
for display_index, display_info in enumerate(displays_info):
|
120
|
+
base_screen_name = display_info["_name"].replace(" ", "_").lower()
|
121
|
+
if base_screen_name in screen_names:
|
122
|
+
screen_names[base_screen_name] += 1
|
123
|
+
screen_name = f"{base_screen_name}_{screen_names[base_screen_name]}"
|
124
|
+
else:
|
125
|
+
screen_names[base_screen_name] = 1
|
126
|
+
screen_name = base_screen_name
|
127
|
+
|
128
|
+
temp_filename = os.path.join(
|
129
|
+
base_dir, date, f"temp_screenshot-{timestamp}-of-{screen_name}.png"
|
130
|
+
)
|
131
|
+
subprocess.run(
|
132
|
+
["screencapture", "-C", "-x", "-D", str(display_index + 1), temp_filename]
|
133
|
+
)
|
134
|
+
|
135
|
+
with Image.open(temp_filename) as img:
|
136
|
+
img = img.convert("RGB")
|
137
|
+
current_hash = str(imagehash.phash(img))
|
138
|
+
|
139
|
+
if (
|
140
|
+
screen_name in previous_hashes
|
141
|
+
and imagehash.hex_to_hash(current_hash)
|
142
|
+
- imagehash.hex_to_hash(previous_hashes[screen_name])
|
143
|
+
< threshold
|
144
|
+
):
|
145
|
+
logging.info(
|
146
|
+
f"Screenshot for {screen_name} is similar to the previous one. Skipping."
|
147
|
+
)
|
148
|
+
os.remove(temp_filename)
|
149
|
+
yield screen_name, None, "Skipped (similar to previous)"
|
150
|
+
continue
|
151
|
+
|
152
|
+
previous_hashes[screen_name] = current_hash
|
153
|
+
screen_sequences[screen_name] = screen_sequences.get(screen_name, 0) + 1
|
154
|
+
|
155
|
+
metadata = {
|
156
|
+
"timestamp": timestamp,
|
157
|
+
"active_app": app_name,
|
158
|
+
"active_window": window_title,
|
159
|
+
"screen_name": screen_name,
|
160
|
+
"sequence": screen_sequences[screen_name],
|
161
|
+
}
|
162
|
+
|
163
|
+
# Save as WebP with metadata included
|
164
|
+
webp_filename = os.path.join(
|
165
|
+
base_dir, date, f"screenshot-{timestamp}-of-{screen_name}.webp"
|
166
|
+
)
|
167
|
+
img.save(webp_filename, format="WebP", quality=85)
|
168
|
+
write_image_metadata(webp_filename, metadata)
|
169
|
+
|
170
|
+
save_screen_sequences(base_dir, screen_sequences, date)
|
171
|
+
|
172
|
+
os.remove(temp_filename)
|
173
|
+
screenshots.append(webp_filename)
|
174
|
+
yield screen_name, webp_filename, "Saved"
|
175
|
+
|
176
|
+
|
177
|
+
def take_screenshot_windows(
|
178
|
+
base_dir,
|
179
|
+
previous_hashes,
|
180
|
+
threshold,
|
181
|
+
screen_sequences,
|
182
|
+
date,
|
183
|
+
timestamp,
|
184
|
+
app_name,
|
185
|
+
window_title,
|
186
|
+
):
|
187
|
+
with mss() as sct:
|
188
|
+
for i, monitor in enumerate(
|
189
|
+
sct.monitors[1:], 1
|
190
|
+
): # Skip the first monitor (entire screen)
|
191
|
+
safe_monitor_name = f"monitor_{i}"
|
192
|
+
logging.info(f"Processing monitor: {safe_monitor_name}")
|
193
|
+
|
194
|
+
webp_filename = os.path.join(
|
195
|
+
base_dir, date, f"screenshot-{timestamp}-of-{safe_monitor_name}.webp"
|
196
|
+
)
|
197
|
+
|
198
|
+
img = sct.grab(monitor)
|
199
|
+
img = Image.frombytes("RGB", img.size, img.bgra, "raw", "BGRX")
|
200
|
+
current_hash = str(imagehash.phash(img))
|
201
|
+
|
202
|
+
if (
|
203
|
+
safe_monitor_name in previous_hashes
|
204
|
+
and imagehash.hex_to_hash(current_hash)
|
205
|
+
- imagehash.hex_to_hash(previous_hashes[safe_monitor_name])
|
206
|
+
< threshold
|
207
|
+
):
|
208
|
+
logging.info(
|
209
|
+
f"Screenshot for {safe_monitor_name} is similar to the previous one. Skipping."
|
210
|
+
)
|
211
|
+
yield safe_monitor_name, None, "Skipped (similar to previous)"
|
212
|
+
continue
|
213
|
+
|
214
|
+
previous_hashes[safe_monitor_name] = current_hash
|
215
|
+
screen_sequences[safe_monitor_name] = (
|
216
|
+
screen_sequences.get(safe_monitor_name, 0) + 1
|
217
|
+
)
|
218
|
+
|
219
|
+
metadata = {
|
220
|
+
"timestamp": timestamp,
|
221
|
+
"active_app": app_name,
|
222
|
+
"active_window": window_title,
|
223
|
+
"screen_name": safe_monitor_name,
|
224
|
+
"sequence": screen_sequences[safe_monitor_name],
|
225
|
+
}
|
226
|
+
|
227
|
+
img.save(webp_filename, format="WebP", quality=85)
|
228
|
+
write_image_metadata(webp_filename, metadata)
|
229
|
+
save_screen_sequences(base_dir, screen_sequences, date)
|
230
|
+
|
231
|
+
yield safe_monitor_name, webp_filename, "Saved"
|
232
|
+
|
233
|
+
|
234
|
+
def take_screenshot(
|
235
|
+
base_dir, previous_hashes, threshold, screen_sequences, date, timestamp
|
236
|
+
):
|
237
|
+
app_name, window_title = get_active_window_info()
|
238
|
+
os.makedirs(os.path.join(base_dir, date), exist_ok=True)
|
239
|
+
worklog_path = os.path.join(base_dir, date, "worklog")
|
240
|
+
|
241
|
+
with open(worklog_path, "a") as worklog:
|
242
|
+
if platform.system() == "Darwin":
|
243
|
+
screenshot_generator = take_screenshot_macos(
|
244
|
+
base_dir,
|
245
|
+
previous_hashes,
|
246
|
+
threshold,
|
247
|
+
screen_sequences,
|
248
|
+
date,
|
249
|
+
timestamp,
|
250
|
+
app_name,
|
251
|
+
window_title,
|
252
|
+
)
|
253
|
+
elif platform.system() == "Windows":
|
254
|
+
screenshot_generator = take_screenshot_windows(
|
255
|
+
base_dir,
|
256
|
+
previous_hashes,
|
257
|
+
threshold,
|
258
|
+
screen_sequences,
|
259
|
+
date,
|
260
|
+
timestamp,
|
261
|
+
app_name,
|
262
|
+
window_title,
|
263
|
+
)
|
264
|
+
else:
|
265
|
+
raise NotImplementedError(
|
266
|
+
f"Unsupported operating system: {platform.system()}"
|
267
|
+
)
|
268
|
+
|
269
|
+
screenshots = []
|
270
|
+
for screen_name, screenshot_file, status in screenshot_generator:
|
271
|
+
worklog.write(f"{timestamp} - {screen_name} - {status}\n")
|
272
|
+
if screenshot_file:
|
273
|
+
screenshots.append(screenshot_file)
|
274
|
+
|
275
|
+
return screenshots
|
276
|
+
|
277
|
+
|
278
|
+
def is_screen_locked():
|
279
|
+
if platform.system() == "Darwin":
|
280
|
+
session_dict = CGSessionCopyCurrentDictionary()
|
281
|
+
if session_dict:
|
282
|
+
screen_locked = session_dict.get("CGSSessionScreenIsLocked", 0)
|
283
|
+
return bool(screen_locked)
|
284
|
+
return False
|
285
|
+
elif platform.system() == "Windows":
|
286
|
+
user32 = ctypes.windll.User32
|
287
|
+
return user32.GetForegroundWindow() == 0
|
288
|
+
|
289
|
+
|
290
|
+
def run_screen_recorder_once(threshold, base_dir, previous_hashes):
|
291
|
+
if not is_screen_locked():
|
292
|
+
date = time.strftime("%Y%m%d")
|
293
|
+
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
294
|
+
screen_sequences = load_screen_sequences(base_dir, date)
|
295
|
+
screenshot_files = take_screenshot(
|
296
|
+
base_dir, previous_hashes, threshold, screen_sequences, date, timestamp
|
297
|
+
)
|
298
|
+
for screenshot_file in screenshot_files:
|
299
|
+
logging.info(f"Screenshot saved: {screenshot_file}")
|
300
|
+
save_previous_hashes(base_dir, previous_hashes)
|
301
|
+
else:
|
302
|
+
logging.info("Screen is locked. Skipping screenshot.")
|
303
|
+
|
304
|
+
|
305
|
+
def run_screen_recorder(threshold, base_dir, previous_hashes):
|
306
|
+
while True:
|
307
|
+
try:
|
308
|
+
if not is_screen_locked():
|
309
|
+
date = time.strftime("%Y%m%d")
|
310
|
+
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
311
|
+
screen_sequences = load_screen_sequences(base_dir, date)
|
312
|
+
screenshot_files = take_screenshot(
|
313
|
+
base_dir,
|
314
|
+
previous_hashes,
|
315
|
+
threshold,
|
316
|
+
screen_sequences,
|
317
|
+
date,
|
318
|
+
timestamp,
|
319
|
+
)
|
320
|
+
for screenshot_file in screenshot_files:
|
321
|
+
logging.info(f"Screenshot saved: {screenshot_file}")
|
322
|
+
else:
|
323
|
+
logging.info("Screen is locked. Skipping screenshot.")
|
324
|
+
except Exception as e:
|
325
|
+
logging.error(f"An error occurred: {str(e)}. Skipping this iteration.")
|
326
|
+
|
327
|
+
time.sleep(settings.record_interval)
|
328
|
+
|
329
|
+
|
330
|
+
def main():
|
331
|
+
parser = argparse.ArgumentParser(description="Screen Recorder")
|
332
|
+
parser.add_argument(
|
333
|
+
"--threshold", type=int, default=4, help="Threshold for image similarity"
|
334
|
+
)
|
335
|
+
parser.add_argument("--base-dir", type=str, help="Base directory for screenshots")
|
336
|
+
parser.add_argument("--once", action="store_true", help="Run once and exit")
|
337
|
+
args = parser.parse_args()
|
338
|
+
|
339
|
+
base_dir = (
|
340
|
+
os.path.expanduser(args.base_dir) if args.base_dir else settings.resolved_screenshots_dir
|
341
|
+
)
|
342
|
+
previous_hashes = load_previous_hashes(base_dir)
|
343
|
+
|
344
|
+
if args.once:
|
345
|
+
run_screen_recorder_once(args, base_dir, previous_hashes)
|
346
|
+
else:
|
347
|
+
while True:
|
348
|
+
try:
|
349
|
+
run_screen_recorder(args, base_dir, previous_hashes)
|
350
|
+
except Exception as e:
|
351
|
+
logging.error(
|
352
|
+
f"Critical error occurred, program will restart in 10 seconds: {str(e)}"
|
353
|
+
)
|
354
|
+
time.sleep(10)
|
355
|
+
|
356
|
+
|
357
|
+
if __name__ == "__main__":
|
358
|
+
main()
|
memos/schemas.py
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
from pydantic import (
|
2
|
+
BaseModel,
|
3
|
+
ConfigDict,
|
4
|
+
DirectoryPath,
|
5
|
+
HttpUrl,
|
6
|
+
Field,
|
7
|
+
model_validator,
|
8
|
+
)
|
9
|
+
from typing import List, Optional, Any, Dict
|
10
|
+
from datetime import datetime
|
11
|
+
from enum import Enum
|
12
|
+
|
13
|
+
|
14
|
+
class FolderType(Enum):
|
15
|
+
DEFAULT = "DEFAULT"
|
16
|
+
DUMMY = "DUMMY"
|
17
|
+
|
18
|
+
|
19
|
+
class MetadataSource(Enum):
|
20
|
+
USER_GENERATED = "user_generated"
|
21
|
+
SYSTEM_GENERATED = "system_generated"
|
22
|
+
PLUGIN_GENERATED = "plugin_generated"
|
23
|
+
|
24
|
+
|
25
|
+
class MetadataType(Enum):
|
26
|
+
JSON_DATA = "json"
|
27
|
+
TEXT_DATA = "text"
|
28
|
+
NUMBER_DATA = "number"
|
29
|
+
|
30
|
+
|
31
|
+
class NewFolderParam(BaseModel):
|
32
|
+
path: DirectoryPath
|
33
|
+
last_modified_at: datetime
|
34
|
+
type: str = FolderType.DEFAULT
|
35
|
+
|
36
|
+
|
37
|
+
class NewLibraryParam(BaseModel):
|
38
|
+
name: str
|
39
|
+
folders: List[NewFolderParam] = []
|
40
|
+
|
41
|
+
|
42
|
+
class NewFoldersParam(BaseModel):
|
43
|
+
folders: List[NewFolderParam] = []
|
44
|
+
|
45
|
+
|
46
|
+
class EntityMetadataParam(BaseModel):
|
47
|
+
key: str
|
48
|
+
value: str
|
49
|
+
source: str
|
50
|
+
data_type: MetadataType
|
51
|
+
|
52
|
+
|
53
|
+
class NewEntityParam(BaseModel):
|
54
|
+
filename: str
|
55
|
+
filepath: str
|
56
|
+
size: int
|
57
|
+
file_created_at: datetime
|
58
|
+
file_last_modified_at: datetime
|
59
|
+
file_type: str
|
60
|
+
file_type_group: str
|
61
|
+
folder_id: int
|
62
|
+
tags: List[str] | None = None
|
63
|
+
metadata_entries: List[EntityMetadataParam] | None = None
|
64
|
+
|
65
|
+
|
66
|
+
class UpdateEntityParam(BaseModel):
|
67
|
+
size: int | None = None
|
68
|
+
file_created_at: datetime | None = None
|
69
|
+
file_last_modified_at: datetime | None = None
|
70
|
+
file_type: str | None = None
|
71
|
+
file_type_group: str | None = None
|
72
|
+
tags: List[str] | None = None
|
73
|
+
metadata_entries: List[EntityMetadataParam] | None = None
|
74
|
+
|
75
|
+
|
76
|
+
class UpdateTagParam(BaseModel):
|
77
|
+
description: str | None
|
78
|
+
color: str | None
|
79
|
+
|
80
|
+
|
81
|
+
class UpdateEntityTagsParam(BaseModel):
|
82
|
+
tags: List[str] = []
|
83
|
+
|
84
|
+
|
85
|
+
class UpdateEntityMetadataParam(BaseModel):
|
86
|
+
metadata_entries: List[EntityMetadataParam]
|
87
|
+
|
88
|
+
|
89
|
+
class NewPluginParam(BaseModel):
|
90
|
+
name: str
|
91
|
+
description: str | None
|
92
|
+
webhook_url: HttpUrl
|
93
|
+
|
94
|
+
|
95
|
+
class NewLibraryPluginParam(BaseModel):
|
96
|
+
plugin_id: Optional[int] = None
|
97
|
+
plugin_name: Optional[str] = None
|
98
|
+
|
99
|
+
@model_validator(mode="after")
|
100
|
+
def check_either_id_or_name(self):
|
101
|
+
plugin_id = self.plugin_id
|
102
|
+
plugin_name = self.plugin_name
|
103
|
+
if not (plugin_id or plugin_name):
|
104
|
+
raise ValueError("Either plugin_id or plugin_name must be provided")
|
105
|
+
if plugin_id is not None and plugin_name is not None:
|
106
|
+
raise ValueError("Only one of plugin_id or plugin_name should be provided")
|
107
|
+
return self
|
108
|
+
|
109
|
+
|
110
|
+
class Folder(BaseModel):
|
111
|
+
id: int
|
112
|
+
path: str
|
113
|
+
last_modified_at: datetime
|
114
|
+
type: FolderType
|
115
|
+
|
116
|
+
model_config = ConfigDict(from_attributes=True)
|
117
|
+
|
118
|
+
|
119
|
+
class Plugin(BaseModel):
|
120
|
+
id: int
|
121
|
+
name: str
|
122
|
+
description: str | None
|
123
|
+
webhook_url: str
|
124
|
+
|
125
|
+
model_config = ConfigDict(from_attributes=True)
|
126
|
+
|
127
|
+
|
128
|
+
class Library(BaseModel):
|
129
|
+
id: int
|
130
|
+
name: str
|
131
|
+
folders: List[Folder] = []
|
132
|
+
plugins: List[Plugin] = []
|
133
|
+
|
134
|
+
model_config = ConfigDict(from_attributes=True)
|
135
|
+
|
136
|
+
|
137
|
+
class Tag(BaseModel):
|
138
|
+
id: int
|
139
|
+
name: str
|
140
|
+
description: str | None
|
141
|
+
color: str | None
|
142
|
+
created_at: datetime
|
143
|
+
# source: str
|
144
|
+
|
145
|
+
model_config = ConfigDict(from_attributes=True)
|
146
|
+
|
147
|
+
|
148
|
+
class EntityMetadata(BaseModel):
|
149
|
+
id: int
|
150
|
+
entity_id: int
|
151
|
+
key: str
|
152
|
+
value: str
|
153
|
+
source: str
|
154
|
+
data_type: MetadataType
|
155
|
+
|
156
|
+
model_config = ConfigDict(from_attributes=True)
|
157
|
+
|
158
|
+
|
159
|
+
class EntityPluginStatus(BaseModel):
|
160
|
+
plugin_id: int
|
161
|
+
processed_at: datetime
|
162
|
+
|
163
|
+
|
164
|
+
class Entity(BaseModel):
|
165
|
+
id: int
|
166
|
+
filepath: str
|
167
|
+
filename: str
|
168
|
+
size: int
|
169
|
+
file_created_at: datetime
|
170
|
+
file_last_modified_at: datetime
|
171
|
+
file_type: str
|
172
|
+
file_type_group: str
|
173
|
+
last_scan_at: datetime | None
|
174
|
+
folder_id: int
|
175
|
+
library_id: int
|
176
|
+
tags: List[Tag] = []
|
177
|
+
metadata_entries: List[EntityMetadata] = []
|
178
|
+
plugin_status: List[EntityPluginStatus] = []
|
179
|
+
|
180
|
+
model_config = ConfigDict(from_attributes=True)
|
181
|
+
|
182
|
+
def get_metadata_by_key(self, key: str) -> Optional[EntityMetadata]:
|
183
|
+
"""
|
184
|
+
Get EntityMetadata by key.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
key (str): The key to search for in metadata entries.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Optional[EntityMetadata]: The EntityMetadata if found, None otherwise.
|
191
|
+
"""
|
192
|
+
for metadata in self.metadata_entries:
|
193
|
+
if metadata.key == key:
|
194
|
+
return metadata
|
195
|
+
return None
|
196
|
+
|
197
|
+
|
198
|
+
class MetadataIndexItem(BaseModel):
|
199
|
+
key: str
|
200
|
+
value: Any
|
201
|
+
source: str
|
202
|
+
|
203
|
+
|
204
|
+
class EntitySearchResult(BaseModel):
|
205
|
+
id: str
|
206
|
+
filepath: str
|
207
|
+
filename: str
|
208
|
+
size: int
|
209
|
+
file_created_at: int = Field(..., description="Unix timestamp")
|
210
|
+
created_date: Optional[str] = None
|
211
|
+
created_month: Optional[str] = None
|
212
|
+
created_year: Optional[str] = None
|
213
|
+
file_last_modified_at: int = Field(..., description="Unix timestamp")
|
214
|
+
file_type: str
|
215
|
+
file_type_group: str
|
216
|
+
last_scan_at: Optional[int] = Field(None, description="Unix timestamp")
|
217
|
+
library_id: int
|
218
|
+
folder_id: int
|
219
|
+
tags: List[str]
|
220
|
+
metadata_entries: List[MetadataIndexItem]
|
221
|
+
facets: Optional[Dict[str, Any]] = None
|
222
|
+
|
223
|
+
|
224
|
+
class FacetCount(BaseModel):
|
225
|
+
count: int
|
226
|
+
highlighted: str
|
227
|
+
value: str
|
228
|
+
|
229
|
+
|
230
|
+
class FacetStats(BaseModel):
|
231
|
+
total_values: int
|
232
|
+
|
233
|
+
|
234
|
+
class Facet(BaseModel):
|
235
|
+
counts: List[FacetCount]
|
236
|
+
field_name: str
|
237
|
+
sampled: bool
|
238
|
+
stats: FacetStats
|
239
|
+
|
240
|
+
|
241
|
+
class TextMatchInfo(BaseModel):
|
242
|
+
best_field_score: str
|
243
|
+
best_field_weight: int
|
244
|
+
fields_matched: int
|
245
|
+
num_tokens_dropped: int
|
246
|
+
score: str
|
247
|
+
tokens_matched: int
|
248
|
+
typo_prefix_score: int
|
249
|
+
|
250
|
+
|
251
|
+
class HybridSearchInfo(BaseModel):
|
252
|
+
rank_fusion_score: float
|
253
|
+
|
254
|
+
|
255
|
+
class SearchHit(BaseModel):
|
256
|
+
document: EntitySearchResult
|
257
|
+
highlight: Dict[str, Any] = {}
|
258
|
+
highlights: List[Any] = []
|
259
|
+
hybrid_search_info: Optional[HybridSearchInfo] = None
|
260
|
+
text_match: Optional[int] = None
|
261
|
+
text_match_info: Optional[TextMatchInfo] = None
|
262
|
+
|
263
|
+
|
264
|
+
class RequestParams(BaseModel):
|
265
|
+
collection_name: str
|
266
|
+
first_q: str
|
267
|
+
per_page: int
|
268
|
+
q: str
|
269
|
+
app_names: List[str] | None = None
|
270
|
+
|
271
|
+
|
272
|
+
class SearchResult(BaseModel):
|
273
|
+
facet_counts: List[Facet]
|
274
|
+
found: int
|
275
|
+
hits: List[SearchHit]
|
276
|
+
out_of: int
|
277
|
+
page: int
|
278
|
+
request_params: RequestParams
|
279
|
+
search_cutoff: bool
|
280
|
+
search_time_ms: int
|
281
|
+
|
282
|
+
|
283
|
+
class EntityContext(BaseModel):
|
284
|
+
prev: List[Entity]
|
285
|
+
next: List[Entity]
|
286
|
+
|
287
|
+
|
288
|
+
class BatchIndexRequest(BaseModel):
|
289
|
+
entity_ids: List[int]
|