diff options
| -rw-r--r-- | initialize.sql | 10 | ||||
| -rw-r--r-- | main.go | 51 | ||||
| -rw-r--r-- | public/gallery.js | 8 | ||||
| -rw-r--r-- | 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; @@ -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; }  | 
