From e213f0792b0d52ee9e4768cd30cf7507d5d83f37 Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Sat, 16 Dec 2023 21:14:27 +0100 Subject: Store image dimensions in DB --- initialize.sql | 10 ++++++---- main.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- public/gallery.js | 8 +++++++- public/style.css | 2 +- 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/initialize.sql b/initialize.sql index 31f4b13..f42e55b 100644 --- a/initialize.sql +++ b/initialize.sql @@ -1,8 +1,10 @@ CREATE TABLE IF NOT EXISTS image( - sha1 TEXT NOT NULL, -- SHA-1 hash of file in lowercase hexadecimal - thumbw INTEGER, -- cached thumbnail width, if known - thumbh INTEGER, -- cached thumbnail height, if known - dhash INTEGER, -- uint64 perceptual hash as a signed integer + sha1 TEXT NOT NULL, -- SHA-1 hash of file in lowercase hexadecimal + width INTEGER NOT NULL, -- cached media width + height INTEGER NOT NULL, -- cached media height + thumbw INTEGER, -- cached thumbnail width, if known + thumbh INTEGER, -- cached thumbnail height, if known + dhash INTEGER, -- uint64 perceptual hash as a signed integer PRIMARY KEY (sha1) ) STRICT; diff --git a/main.go b/main.go index 8c154e8..4cad26f 100644 --- a/main.go +++ b/main.go @@ -275,6 +275,12 @@ func handleAPIBrowse(w http.ResponseWriter, r *http.Request) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +func getImageDimensions(sha1 string) (w int64, h int64, err error) { + err = db.QueryRow(`SELECT width, height FROM image WHERE sha1 = ?`, + sha1).Scan(&w, &h) + return +} + func getImagePaths(sha1 string) (paths []string, err error) { rows, err := db.Query(`WITH RECURSIVE paths(parent, path) AS ( SELECT parent, name AS path FROM entry WHERE sha1 = ? @@ -338,12 +344,19 @@ func handleAPIInfo(w http.ResponseWriter, r *http.Request) { } var result struct { - Paths []string `json:"paths"` - Tags map[string]map[string]float32 `json:"tags"` + Width int64 `json:"width"` + Height int64 `json:"height"` + Paths []string `json:"paths"` + Tags map[string]map[string]float32 `json:"tags"` // TODO: Maybe add perceptual hash collisions. } var err error + result.Width, result.Height, err = getImageDimensions(params.SHA1) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } result.Paths, err = getImagePaths(params.SHA1) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -489,6 +502,31 @@ func isImage(path string) (bool, error) { return bytes.HasPrefix(out, []byte("image/")), nil } +func pingImage(path string) (int, int, error) { + cmd := exec.Command("identify", "-limit", "thread", "1", "-ping", + "-format", "%w %h", path+"[0]") + + // XXX: Early returns may leak resources. + stdout, err := cmd.StdoutPipe() + if err != nil { + return 0, 0, err + } + if err := cmd.Start(); err != nil { + return 0, 0, err + } + out, err := io.ReadAll(stdout) + if err != nil { + return 0, 0, err + } + if err := cmd.Wait(); err != nil { + return 0, 0, err + } + + var w, h int + _, err = fmt.Fscanf(bytes.NewReader(out), "%d %d", &w, &h) + return w, h, err +} + type importer struct { dm directoryManager dmMutex sync.Mutex @@ -512,6 +550,11 @@ func (i *importer) Import(path string) error { return nil } + width, height, err := pingImage(path) + if err != nil { + return err + } + f, err := os.Open(path) if err != nil { return err @@ -551,8 +594,8 @@ func (i *importer) Import(path string) error { } defer tx.Rollback() - if _, err = tx.Exec(`INSERT INTO image(sha1) VALUES (?) - ON CONFLICT(sha1) DO NOTHING`, hexSHA1); err != nil { + if _, err = tx.Exec(`INSERT INTO image(sha1, width, height) VALUES (?, ?, ?) + ON CONFLICT(sha1) DO NOTHING`, hexSHA1, width, height); err != nil { return err } diff --git a/public/gallery.js b/public/gallery.js index 1abb114..0967534 100644 --- a/public/gallery.js +++ b/public/gallery.js @@ -115,15 +115,20 @@ let Browse = { let ViewModel = { sha1: undefined, + width: 0, + height: 0, paths: [], tags: {}, async reload(sha1) { this.sha1 = sha1 + this.width = this.height = 0 this.paths = [] this.tags = {} let resp = await call('info', {sha1: sha1}) + this.width = resp.width + this.height = resp.height this.paths = resp.paths this.tags = resp.tags }, @@ -185,7 +190,8 @@ let View = { view(vnode) { const view = m('.view', [ ViewModel.sha1 !== undefined - ? m('img', {src: `/image/${ViewModel.sha1}`}) + ? m('img', {src: `/image/${ViewModel.sha1}`, + width: ViewModel.width, height: ViewModel.height}) : "No image.", ]) return m('.container', {}, [ diff --git a/public/style.css b/public/style.css index 7feddb2..8a42a51 100644 --- a/public/style.css +++ b/public/style.css @@ -36,7 +36,7 @@ ul.sidebar li.child a { .view { display: flex; flex-grow: 1; overflow: hidden; justify-content: center; align-items: center; } -.view img { max-width: 100%; max-height: 100%; } +.view img { max-width: 100%; max-height: 100%; object-fit: contain; } .viewbar { padding: .25rem .5rem; background: #eee; border-left: 1px solid #ccc; min-width: 20rem; overflow: auto; } -- cgit v1.2.3-70-g09d2