Changes
3 changed files (+39/-25)
-
-
@@ -16,6 +16,6 @@ This program uses a client-server architecture to watch directories with inotify and keep the model in memory so the client doesn't have to wait several seconds to load the model. It uses file inodes and modification times to avoid unnecessary re-indexing.Run `python server.py DIRS_TO_INDEX` to start the server. Make sure that none of the directories contain other directories on that list or weird stuff will happen. The server currently only indexes images although more modalities may be supported in the future. There are probably some weird race condition bugs if you modify a lot of files at the same time. Then run `python client.py SEARCH_TEXT NUM_RESULTS` to get a list of the most similar files. You can pass this list to an image viewer such as Gwenview to view image results. Note that Gwenview doesn't preserve the order of the images. Alternatively, add a third parameter to `python client.py` and it will symlink `res0`, `res1`, and so on to the files on the list. This can be used in conjunction with the Dolphin file manager's integrated terminal to get thumbnails of the search results. Then run `python client.py SEARCH_TEXT NUM_RESULTS` to get a list of the most similar files. You can pass this list to an image viewer such as Gwenview to view image results, although note that Gwenview doesn't preserve the order of the images. Alternatively, add a third parameter to `python client.py` and it will create a temporary directory containing symlinks to the search results and return the directory name. Check out [TIDY](https://github.com/slavabarkov/tidy) for a similar awesome program that runs on Android. Check out [TIDY](https://github.com/slavabarkov/tidy) (for Android) and [rclip](https://github.com/yurijmikhalevich/rclip) for similar projects, although this one is probably fastest!
-
-
-
@@ -1,18 +1,26 @@import os import sys import tempfile import xmlrpc.client from unixsocket import UnixStreamTransport sockpath = os.path.join(os.environ["XDG_RUNTIME_DIR"], "search.sock") proxy = xmlrpc.client.ServerProxy( "http://localhost", transport=UnixStreamTransport("search.sock") "http://localhost", transport=UnixStreamTransport(sockpath) ) res = proxy.search(sys.argv[1], sys.argv[2]) print("\n".join(res)) if len(sys.argv) > 3: for path in os.listdir(): if path.startswith("res"): os.unlink(path) if len(sys.argv) == 3: print("\n".join(res)) else: temp_dir = tempfile.TemporaryDirectory(delete=False) for i, path in enumerate(res): os.symlink(path, f"res{i}") os.symlink(path, os.path.join(temp_dir.name, str(i))) print(temp_dir.name) # Very slow # elif sys.argv[3] == "sixel": # for imgpath in res: # print(imgpath) # c = converter.SixelConverter(imgpath, h=50, w=50) # c.write(sys.stdout)
-
-
-
@@ -15,7 +15,7 @@DIM = 768 print("Connecting to DB") con = sqlite3.connect("search.db") # , check_same_thread=False) con = sqlite3.connect("search.db") con.execute("PRAGMA journal_mode=WAL") con.enable_load_extension(True) sqlite_vec.load(con)
-
@@ -76,12 +76,9 @@ returnprint("Indexing", path, parent) s = os.stat(path) res = cur.execute("SELECT time, parent, path FROM idx WHERE ino = ?", (s.st_ino,)) db_vals = res.fetchone() if os.path.isfile(path): res = cur.execute( "SELECT time, parent, path FROM idx WHERE ino = ?", (s.st_ino,) ) db_vals = res.fetchone() if db_vals is None or db_vals[0] != s.st_mtime_ns or db_vals[1] != parent: # Not in DB or modified # Probably faster to query DB first instead of guessing mimetype first
-
@@ -94,8 +91,10 @@ emb = model.embed_image(path)# sqlite-vec doesn't support INSERT OR REPLACE and UPSERT # https://github.com/asg017/sqlite-vec/issues/127 if db_vals is not None: cur.execute("DELETE FROM idx WHERE ino = ?", (s.st_ino,)) cur.execute( "INSERT OR REPLACE INTO idx VALUES (?, ?, ?, ?, ?)", "INSERT INTO idx VALUES (?, ?, ?, ?, ?)", (s.st_ino, emb, s.st_mtime_ns, parent, path), ) con.commit()
-
@@ -106,11 +105,15 @@ cur.execute("UPDATE idx SET path = ? WHERE ino = ?", (path, s.st_ino))con.commit() elif os.path.isdir(path): cur.execute( "INSERT OR REPLACE INTO idx VALUES (?, ?, ?, ?, ?)", (s.st_ino, np.zeros((DIM,), dtype=np.float32), s.st_mtime_ns, parent, path), ) con.commit() if db_vals != (s.st_mtime_ns, parent, path): if db_vals is not None: cur.execute("DELETE FROM idx WHERE ino = ?", (s.st_ino,)) emb = np.ones((DIM,), dtype=np.float32) cur.execute( "INSERT INTO idx VALUES (?, ?, ?, ?, ?)", (s.st_ino, emb, s.st_mtime_ns, parent, path), ) con.commit() for child in os.listdir(path): index(os.path.join(path, child), s.st_ino)
-
@@ -126,9 +129,11 @@ con.commit()def search(text, limit): # TODO: Search using image path print("Search", text, limit) emb = model.embed_text(text) if os.path.exists(text): emb = model.embed_image(text) else: emb = model.embed_text(text) res = cur.execute( "SELECT path FROM idx WHERE emb MATCH ? AND k = ? ORDER BY distance", (emb, limit),
-
@@ -158,7 +163,8 @@ con.commit()print("Starting RPC server") pathlib.Path("search.sock").unlink(missing_ok=True) server = UnixStreamXMLRPCServer("search.sock") sockpath = os.path.join(os.environ["XDG_RUNTIME_DIR"], "search.sock") pathlib.Path(sockpath).unlink(missing_ok=True) server = UnixStreamXMLRPCServer(sockpath) server.register_function(search) server.serve_forever()
-