feat: manage persons in app.db, auto-rebuild face_gallery.db
This commit is contained in:
parent
c0fa9d0700
commit
d4fe5c8459
@ -9,7 +9,7 @@ import (
|
||||
type PersonRecord struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PhotoURL string `json:"photo_url,omitempty"`
|
||||
PhotoCount int `json:"photo_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
@ -24,27 +24,25 @@ func NewFaceGalleryRepo(dbPath string) *FaceGalleryRepo {
|
||||
func (r *FaceGalleryRepo) open() (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite", r.dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open face gallery: %w", err)
|
||||
return nil, fmt.Errorf("open app db: %w", err)
|
||||
}
|
||||
// Ensure schema
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS person (
|
||||
CREATE TABLE IF NOT EXISTS face_persons (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT '',
|
||||
extra TEXT NOT NULL DEFAULT ''
|
||||
updated_at TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS embedding (
|
||||
CREATE TABLE IF NOT EXISTS face_photos (
|
||||
id INTEGER PRIMARY KEY,
|
||||
person_id INTEGER NOT NULL,
|
||||
embedding BLOB NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY (person_id) REFERENCES person(id)
|
||||
photo_path TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("ensure schema: %w", err)
|
||||
return nil, fmt.Errorf("ensure face tables: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@ -56,8 +54,8 @@ func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(e.id) AS photo_count
|
||||
FROM person p LEFT JOIN embedding e ON e.person_id = p.id
|
||||
rows, err := db.Query(`SELECT p.id, p.name, p.created_at, COUNT(ph.id) AS photo_count
|
||||
FROM face_persons p LEFT JOIN face_photos ph ON ph.person_id = p.id
|
||||
GROUP BY p.id ORDER BY p.name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -67,8 +65,7 @@ GROUP BY p.id ORDER BY p.name`)
|
||||
var out []PersonRecord
|
||||
for rows.Next() {
|
||||
var p PersonRecord
|
||||
var photoCount int
|
||||
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &photoCount); err != nil {
|
||||
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt, &p.PhotoCount); err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
@ -86,30 +83,51 @@ func (r *FaceGalleryRepo) AddPerson(name string) error {
|
||||
}
|
||||
defer db.Close()
|
||||
now := time.Now().Format(time.RFC3339)
|
||||
_, err = db.Exec(`INSERT INTO person(name, created_at) VALUES(?, ?)`, name, now)
|
||||
_, err = db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *FaceGalleryRepo) AddPhoto(personID int, photoPath string) error {
|
||||
db, err := r.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
now := time.Now().Format(time.RFC3339)
|
||||
_, err = db.Exec(`INSERT INTO face_photos(person_id, photo_path, created_at) VALUES(?, ?, ?)`, personID, photoPath, now)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *FaceGalleryRepo) FindOrCreatePerson(name string) (int, error) {
|
||||
db, err := r.open()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer db.Close()
|
||||
var id int
|
||||
err = db.QueryRow(`SELECT id FROM face_persons WHERE name = ?`, name).Scan(&id)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
now := time.Now().Format(time.RFC3339)
|
||||
res, err := db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lid, _ := res.LastInsertId()
|
||||
return int(lid), nil
|
||||
}
|
||||
|
||||
func (r *FaceGalleryRepo) DeletePerson(id int) error {
|
||||
db, err := r.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
_, err = db.Exec(`DELETE FROM embedding WHERE person_id = ?`, id)
|
||||
_, err = db.Exec(`DELETE FROM face_photos WHERE person_id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(`DELETE FROM person WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *FaceGalleryRepo) RenamePerson(id int, name string) error {
|
||||
db, err := r.open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
_, err = db.Exec(`UPDATE person SET name = ? WHERE id = ?`, name, id)
|
||||
_, err = db.Exec(`DELETE FROM face_persons WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -667,7 +667,6 @@ func (u *UI) Routes() (chi.Router, error) {
|
||||
r.Post("/face-gallery/build", u.actionFaceGalleryBuild)
|
||||
r.Post("/face-gallery/add", u.actionFaceGalleryAdd)
|
||||
r.Post("/face-gallery/delete", u.actionFaceGalleryDelete)
|
||||
r.Post("/face-gallery/rename", u.actionFaceGalleryRename)
|
||||
r.Get("/monitor", u.pageMonitor)
|
||||
r.Get("/hls/*", u.proxyHLS)
|
||||
r.Get("/api/monitor/channels", u.apiMonitorChannels)
|
||||
@ -4009,10 +4008,11 @@ func (u *UI) actionFaceGalleryBuild(w http.ResponseWriter, r *http.Request) {
|
||||
func (u *UI) pageFaceGallery(w http.ResponseWriter, r *http.Request) {
|
||||
data := PageData{Title: "人脸库管理"}
|
||||
data.Message = r.URL.Query().Get("msg")
|
||||
dbPath := filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db")
|
||||
if repo := storage.NewFaceGalleryRepo(dbPath); repo != nil {
|
||||
if persons, err := repo.ListPersons(); err == nil {
|
||||
data.FaceGalleryPersons = persons
|
||||
if strings.TrimSpace(u.dbPath) != "" {
|
||||
if repo := storage.NewFaceGalleryRepo(u.dbPath); repo != nil {
|
||||
if persons, err := repo.ListPersons(); err == nil {
|
||||
data.FaceGalleryPersons = persons
|
||||
}
|
||||
}
|
||||
}
|
||||
u.render(w, r, "face_gallery", data)
|
||||
@ -4025,22 +4025,13 @@ func (u *UI) actionFaceGalleryDelete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
u.rebuildFaceGallery()
|
||||
repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db"))
|
||||
if strings.TrimSpace(u.dbPath) != "" { repo := storage.NewFaceGalleryRepo(u.dbPath)
|
||||
repo.DeletePerson(id)
|
||||
u.rebuildFaceGallery()
|
||||
}
|
||||
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
|
||||
}
|
||||
|
||||
func (u *UI) actionFaceGalleryRename(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.Atoi(r.FormValue("id"))
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
if id <= 0 || name == "" {
|
||||
http.Error(w, "invalid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
repo := storage.NewFaceGalleryRepo(filepath.Join("resources", "standard_resources", "face_gallery", "face_gallery.db"))
|
||||
repo.RenamePerson(id, name)
|
||||
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
|
||||
}
|
||||
|
||||
func (u *UI) actionFaceGalleryImport(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(100 << 20); err != nil {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user