Separate table for camera models and improved search form (draft)

This commit is contained in:
Michael Mayer 2018-08-15 09:59:51 +02:00
parent cea3d70835
commit 144927b953
12 changed files with 236 additions and 72 deletions

144
Gopkg.lock generated
View file

@ -2,193 +2,273 @@
[[projects]]
digest = "1:8b95956b70e181b19025c7ba3578fdfd8efbec4ce916490700488afb9218972c"
name = "cloud.google.com/go"
packages = ["civil"]
revision = "aad3f485ee528456e0768f20397b4d9dd941e755"
version = "v0.25.0"
pruneopts = ""
revision = "64a2037ec6be8a4b0c1d1f706ed35b428b989239"
version = "v0.26.0"
[[projects]]
branch = "master"
digest = "1:31d8d99992991e1498c669ccff1090b195ac5c5084c8ffdb2c746aed432ff7d3"
name = "github.com/araddon/dateparse"
packages = ["."]
revision = "089f77b1d92b615cc77fde0d2fa528b5e85e832d"
pruneopts = ""
revision = "cfd92a431d0efe36a1b81ca25d15b98aae4dbdb6"
[[projects]]
branch = "master"
digest = "1:445872e4dcdd2820291be6f6d7b06d69f3847c39f31850196d99c3b55f50c503"
name = "github.com/bamiaux/rez"
packages = ["."]
pruneopts = ""
revision = "29f4463c688b986c11f166b12734f69b58b5555f"
[[projects]]
branch = "master"
digest = "1:35e720ea6905b542e60541b7850936472e8f12eb9ca1fea94f99079fe632f007"
name = "github.com/brett-lempereur/ish"
packages = ["."]
pruneopts = ""
revision = "bbdc45bcf55de61b38b4108871199a117aecd1be"
[[projects]]
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = ""
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:59d26e030b4edadff17e264d382f0631eac378de47073c9aa0da2b73e243f682"
name = "github.com/denisenkom/go-mssqldb"
packages = [
".",
"internal/cp"
"internal/cp",
]
pruneopts = ""
revision = "242fa5aa1b45aeb9fcdfeee88822982e3f548e22"
[[projects]]
digest = "1:ebcfd06c38a94af7f307d3ed0600e06b6da0ca9106f77e7ee4d79a33c8518529"
name = "github.com/disintegration/imaging"
packages = ["."]
pruneopts = ""
revision = "bbcee2f5c9d5e94ca42c8b50ec847fec64a6c134"
version = "v1.4.2"
[[projects]]
digest = "1:7b4b8c901568da024c49be7ff5e20fdecef629b60679c803041093823fb8d081"
name = "github.com/djherbis/times"
packages = ["."]
pruneopts = ""
revision = "95292e44976d1217cf3611dc7c8d9466877d3ed5"
version = "v1.0.1"
[[projects]]
branch = "master"
digest = "1:1120f960f5c334f0f94bad29eefaf73d52d226893369693686148f66c1993f15"
name = "github.com/gin-contrib/sse"
packages = ["."]
pruneopts = ""
revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae"
[[projects]]
digest = "1:748df8eda48a48d6d05ebb848a6cf966f846fa4e6179f8731385d04d7a287fc1"
name = "github.com/gin-gonic/gin"
packages = [
".",
"binding",
"render"
"json",
"render",
]
revision = "d459835d2b077e44f7c9b453505ee29881d5d12d"
version = "v1.2"
pruneopts = ""
revision = "b869fe1415e4b9eb52f247441830d502aece2d4d"
version = "v1.3.0"
[[projects]]
digest = "1:c07de423ca37dc2765396d6971599ab652a339538084b9b58c9f7fc533b28525"
name = "github.com/go-sql-driver/mysql"
packages = ["."]
pruneopts = ""
revision = "d523deb1b23d913de5bdada721a6071e71283618"
version = "v1.4.0"
[[projects]]
digest = "1:f958a1c137db276e52f0b50efee41a1a389dcdded59a69711f3e872757dab34b"
name = "github.com/golang/protobuf"
packages = ["proto"]
pruneopts = ""
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
version = "v1.1.0"
[[projects]]
digest = "1:d269638dbd514822446c3c818b6389c880058af79ec54d97731818b34fe66921"
name = "github.com/jinzhu/gorm"
packages = [
".",
"dialects/mssql",
"dialects/mysql",
"dialects/postgres",
"dialects/sqlite"
"dialects/sqlite",
]
pruneopts = ""
revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
version = "v1.9.1"
[[projects]]
branch = "master"
digest = "1:d9a7385b84d8187fd94e0357045c6fa1147ca94caa56fdd539336c7c102fc728"
name = "github.com/jinzhu/inflection"
packages = ["."]
pruneopts = ""
revision = "04140366298a54a039076d798123ffa108fff46c"
[[projects]]
digest = "1:b79fc583e4dc7055ed86742e22164ac41bf8c0940722dbcb600f1a3ace1a8cb5"
name = "github.com/json-iterator/go"
packages = ["."]
pruneopts = ""
revision = "1624edc4454b8682399def8740d46db5e4362ba4"
version = "1.1.5"
[[projects]]
branch = "master"
digest = "1:72ac8b303addf8e2ba29c3fab8feb6630d05b272ae07f9f60b281ad9ee97b61f"
name = "github.com/kylelemons/go-gypsy"
packages = ["yaml"]
pruneopts = ""
revision = "08cad365cd28a7fba23bb1e57aa43c5e18ad8bb8"
[[projects]]
branch = "master"
digest = "1:09792d732b079867772cdbabdf7dc54ef9f9d04c998a9ce6226657151fccbb94"
name = "github.com/lib/pq"
packages = [
".",
"hstore",
"oid"
"oid",
]
pruneopts = ""
revision = "90697d60dd844d5ef6ff15135d0203f65d2f53b8"
[[projects]]
digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = ""
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:bc03901fc8f0965ccba8bc453eae21a9b04f95999eab664c7de6dc7290f4e8f4"
name = "github.com/mattn/go-sqlite3"
packages = ["."]
pruneopts = ""
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
version = "v1.9.0"
[[projects]]
digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd"
name = "github.com/modern-go/concurrent"
packages = ["."]
pruneopts = ""
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855"
name = "github.com/modern-go/reflect2"
packages = ["."]
pruneopts = ""
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = ""
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = ""
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
branch = "go1"
digest = "1:6121942039481ed09e319a5aef3b7468b341d4c7d4a664a9f6e66eca535416cf"
name = "github.com/rwcarlsen/goexif"
packages = [
"exif",
"mknote",
"tiff"
"tiff",
]
pruneopts = ""
revision = "8d986c03457a2057c7b0fb0a48113f7dd48f9619"
[[projects]]
digest = "1:26a7745eb5573df3e4a288dfb8be79090c3e96aa741693838c6c9af85396b176"
name = "github.com/steakknife/hamming"
packages = ["."]
pruneopts = ""
revision = "5ac3f73b8842df21423978fbbeb5166670f6f73e"
version = "0.2.3"
[[projects]]
digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75"
name = "github.com/stretchr/testify"
packages = ["assert"]
pruneopts = ""
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
digest = "1:59a17c3323333602195b838058ac02843f709c8a295cda559f1c28d7e05bda7a"
name = "github.com/tensorflow/tensorflow"
packages = [
"tensorflow/go",
"tensorflow/go/op"
"tensorflow/go/op",
]
revision = "25c197e02393bd44f50079945409009dd4d434f8"
version = "v1.9.0"
pruneopts = ""
revision = "656e7a2b347c3c6eb76a6c130ed4b1def567b6c1"
version = "v1.10.0"
[[projects]]
digest = "1:2e7f653483e51243b6cd6de60ce39bde0d6927d10a3c24295ab0f82cb1efeae2"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = ""
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
version = "v1.1.1"
[[projects]]
digest = "1:e85837cb04b78f61688c6eba93ea9d14f60d611e2aaf8319999b1a60d2dafbfa"
name = "github.com/urfave/cli"
packages = ["."]
pruneopts = ""
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
version = "v1.20.0"
[[projects]]
branch = "master"
digest = "1:4cae11053a5fc8e7b08228fcc14d161d3e60b64ba508a8b216937da472690991"
name = "golang.org/x/crypto"
packages = ["md4"]
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
pruneopts = ""
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
[[projects]]
branch = "master"
digest = "1:0960a9f676f505c50b45b2db87151ef60991c6f4764083b02acfabf09bcdc5cc"
name = "golang.org/x/image"
packages = [
"bmp",
@ -197,37 +277,67 @@
"tiff/lzw",
"vp8",
"vp8l",
"webp"
"webp",
]
pruneopts = ""
revision = "c73c2afc3b812cdd6385de5a50616511c4a3d458"
[[projects]]
branch = "master"
digest = "1:b779cc85de245422bf70d8a21e6afcf3c0591eca64dc507feb9f054f64b21ab9"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4"
pruneopts = ""
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
[[projects]]
digest = "1:c1771ca6060335f9768dff6558108bc5ef6c58506821ad43377ee23ff059e472"
name = "google.golang.org/appengine"
packages = ["cloudsql"]
pruneopts = ""
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
version = "v1.1.0"
[[projects]]
digest = "1:dd549e360e5a8f982a28c2bcbe667307ceffe538ed9afc7c965524f1ac285b3f"
name = "gopkg.in/go-playground/validator.v8"
packages = ["."]
pruneopts = ""
revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf"
version = "v8.18.2"
[[projects]]
digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = ""
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "43ead2ce99499d86f5ea942bd5e6f14819fcaad810c70b336115eed6ac15f6f5"
input-imports = [
"github.com/araddon/dateparse",
"github.com/brett-lempereur/ish",
"github.com/disintegration/imaging",
"github.com/djherbis/times",
"github.com/gin-gonic/gin",
"github.com/gin-gonic/gin/binding",
"github.com/jinzhu/gorm",
"github.com/jinzhu/gorm/dialects/mssql",
"github.com/jinzhu/gorm/dialects/mysql",
"github.com/jinzhu/gorm/dialects/postgres",
"github.com/jinzhu/gorm/dialects/sqlite",
"github.com/kylelemons/go-gypsy/yaml",
"github.com/pkg/errors",
"github.com/rwcarlsen/goexif/exif",
"github.com/rwcarlsen/goexif/mknote",
"github.com/steakknife/hamming",
"github.com/stretchr/testify/assert",
"github.com/tensorflow/tensorflow/tensorflow/go",
"github.com/tensorflow/tensorflow/tensorflow/go/op",
"github.com/urfave/cli",
]
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -15,6 +15,8 @@ PhotoPrism is a free tool for importing, filtering and archiving large amounts o
JPEG and RAW files. Originals, thumbnails and metadata are stored in the file system for easy
backup and reliable long-term accessibility.
![](docs/img/search.png)
Setup
-----
Before you start, make sure you got Git and Docker installed on your system.
@ -42,8 +44,8 @@ docker-compose up
docker-compose exec photoprism bash
dep ensure
go test
go build cmd/photoprism/photoprism.go
./photoprism
go run cmd/photoprism/photoprism.go migrate-db
go run cmd/photoprism/photoprism.go start
```
About

30
camera.go Normal file
View file

@ -0,0 +1,30 @@
package photoprism
import (
"github.com/jinzhu/gorm"
)
type Camera struct {
gorm.Model
ModelName string
Type string
Notes string
}
func NewCamera(modelName string) *Camera {
if modelName == "" {
modelName = "Unknown"
}
result := &Camera{
ModelName: modelName,
}
return result
}
func (c *Camera) FirstOrCreate(db *gorm.DB) *Camera {
db.FirstOrCreate(c, "model_name = ?", c.ModelName)
return c
}

View file

@ -133,5 +133,5 @@ func (c *Config) GetDb() *gorm.DB {
func (c *Config) MigrateDb() {
db := c.GetDb()
db.AutoMigrate(&File{}, &Photo{}, &Tag{}, &Album{}, &Location{})
db.AutoMigrate(&File{}, &Photo{}, &Tag{}, &Album{}, &Location{}, &Camera{})
}

BIN
docs/img/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

View file

@ -186,10 +186,4 @@ main div.page-settings {
.photo-grid .photo .actions .action.favorite {
display: inline;
cursor: pointer;
}
@media(min-width: 576px) {
.mb-sm-0, .my-sm-0 {
margin-bottom: 8px !important;
}
}

View file

@ -2,41 +2,41 @@
<div class="page page-photos">
<div class="page-form">
<b-form inline @submit="formChange">
<b-form-select class="mb-2 mr-sm-2 mb-sm-0"
<b-form-select class="mb-2 mr-sm-2"
v-b-tooltip.hover title="Category"
v-model="form.category"
v-model="query.category"
:options="{ 'junction': 'Junction', 'tourism': 'Tourism', 'historic': 'Historic' }"
id="inlineFormCustomSelectPref">
<option slot="first" :value="null"></option>
</b-form-select>
<b-form-select @change="formChange" class="mb-2 mr-sm-2 mb-sm-0"
v-model="form.country"
<b-form-select @change="formChange" class="mb-2 mr-sm-2"
v-model="query.country"
:options="{ '1': 'One', '2': 'Two', '3': 'Three' }"
id="inlineFormCustomSelectPref">
<option slot="first" :value="null">Country</option>
</b-form-select>
<b-form-select @change="formChange" class="mb-2 mr-sm-2 mb-sm-0"
:v-model="form.camera"
<b-form-select @change="formChange" class="mb-2 mr-sm-2"
:v-model="query.camera"
:options="{ '1': 'One', '2': 'Two', '3': 'Three' }"
id="inlineFormCustomSelectPref">
<option slot="first" :value="null">Camera Model</option>
</b-form-select>
<b-form-select @change="formChange" class="mb-2 mr-sm-2 mb-sm-0"
<b-form-select @change="formChange" class="mb-2 mr-sm-2"
v-model="dir"
:options="{ 'asc': 'Ascending', 'desc': 'Descending' }"
id="inlineFormCustomSelectPref">
<option slot="first" :value="null">Sort Order</option>
</b-form-select>
<b-form-select @change="formChange" class="mb-2 mr-sm-2 mb-sm-0"
v-model="form.view"
:options="{ 'list': 'List View', 'tile': 'Tile View (small)', 'tilel_large': 'Tile View (large)' }"
<b-form-select @change="formChange" class="mb-2 mr-sm-2"
v-model="view"
:options="{ 'list': 'List View', 'tile': 'Tile View (small)', 'tile_large': 'Tile View (large)' }"
id="inlineFormCustomSelectPref">
</b-form-select>
<b-form-input class="mb-2 mr-sm-2 mb-sm-0" v-b-tooltip.hover title="Date" type="date"/>
<b-form-input class="mb-2 mr-sm-2 mb-sm-0" placeholder="Tags" v-b-tooltip.hover title="Tags" type="text"/>
<b-form-input class="mb-2 mr-sm-2" v-b-tooltip.hover title="After" type="date"/>
<b-form-input class="mb-2 mr-sm-2" v-b-tooltip.hover title="Before" type="date"/>
<b-form-checkbox class="mb-2 mr-sm-2 mb-sm-0">
Favorites only
@ -45,7 +45,7 @@
<div class="clearfix"></div>
</div>
<div class="page-container photo-grid">
<template v-for="photo in rows">
<template v-for="photo in items">
<div class="photo">
<div class="info">{{ photo.TakenAt | moment("DD.MM.YYYY hh:mm:ss") }}<span class="right">{{ photo.CameraModel }}</span></div>
<div class="actions">
@ -79,28 +79,33 @@
export default {
name: 'photos',
props: {},
props: {
},
data() {
const query = this.$route.query;
const resultCount = query.hasOwnProperty('count') ? parseInt(query['count']) : 70;
const resultPage = query.hasOwnProperty('page') ? parseInt(query['page']) : 1;
const resultOffset = resultCount * (resultPage - 1);
const order = query.hasOwnProperty('order') ? query['order'] : 'taken_at';
const dir = query.hasOwnProperty('dir') ? query['dir'] : '';
const q = query.hasOwnProperty('q') ? query['q'] : '';
const view = query.hasOwnProperty('view') ? query['view'] : 'tile';
return {
'rows': [],
'images': [],
'form': {
'items': [],
'query': {
category: '',
country: '',
camera: '',
dir: 'asc',
view: 'list',
after: '',
before: '',
favorites_only: '',
q: q,
},
'page': resultPage,
'order': order,
'dir': dir,
'q': q,
'pageOptions': [15, 30, 50, 100],
'view': view,
'resultCount': resultCount,
'resultOffset': resultOffset,
'resultTotal': 'Many',
@ -124,7 +129,7 @@
const params = {
count: this.resultCount,
offset: this.resultCount * (this.page - 1),
dir: this.dir,
order: this.order !== '' ? this.order + ' ' + this.dir : '',
};
Object.assign(params, this.query);
@ -138,21 +143,21 @@
const urlParams = {
count: this.resultCount,
page: this.page,
order: this.order,
dir: this.dir,
q: this.q
};
Object.assign(urlParams, this.query);
this.$router.replace({query: urlParams});
Photo.search(urlParams).then(response => {
Photo.search(params).then(response => {
console.log(response);
this.resultTotal = parseInt(response.headers['x-result-total']);
this.resultCount = parseInt(response.headers['x-result-count']);
this.resultOffset = parseInt(response.headers['x-result-offset']);
this.rows = response.models;
this.$alert.info(this.rows.length + ' photos found');
this.items = response.models;
this.$alert.info(this.items.length + ' photos found');
});
}
},

View file

@ -74,12 +74,12 @@ func (i *Indexer) IndexMediaFile(mediaFile *MediaFile) {
photo.Location = location
}
photo.Camera = NewCamera(mediaFile.GetCameraModel()).FirstOrCreate(i.db)
photo.TakenAt = mediaFile.GetDateCreated()
photo.CanonicalName = canonicalName
photo.Files = []File{}
photo.Albums = []Album{}
photo.Author = ""
photo.CameraModel = mediaFile.GetCameraModel()
photo.Favorite = false
photo.Private = true

View file

@ -78,26 +78,31 @@ func (m *MediaFile) GetDateCreated() time.Time {
return m.dateCreated
}
m.dateCreated = time.Now()
info, err := m.GetExifData()
if err == nil {
if err == nil && !info.DateTime.IsZero() {
m.dateCreated = info.DateTime
return info.DateTime
return m.dateCreated
}
t, err := times.Stat(m.GetFilename())
if err != nil {
log.Fatal(err.Error())
log.Println(err.Error())
return m.dateCreated
}
if t.HasBirthTime() {
m.dateCreated = t.BirthTime()
return t.BirthTime()
} else {
m.dateCreated = t.ModTime()
}
m.dateCreated = t.ModTime()
return t.ModTime()
return m.dateCreated
}
func (m *MediaFile) GetCameraModel() string {

View file

@ -8,15 +8,16 @@ import (
type Photo struct {
gorm.Model
Title string
Description string `gorm:"type:text;"`
Description string `gorm:"type:text;"`
TakenAt time.Time
CanonicalName string
PerceptualHash string
Tags []Tag `gorm:"many2many:photo_tags;"`
Tags []Tag `gorm:"many2many:photo_tags;"`
Files []File
Albums []Album `gorm:"many2many:album_photos;"`
Camera *Camera
CameraID uint
Author string
CameraModel string
Lat float64
Long float64
Location *Location
@ -24,4 +25,4 @@ type Photo struct {
Favorite bool
Private bool
Deleted bool
}
}

View file

@ -0,0 +1,19 @@
package forms
import (
"time"
)
type PhotoSearchForm struct {
Query string `form:"q"`
Tags string `form:"tags"`
Category string `form:"category"`
Country string `form:"country"`
CameraID int `form:"camera_id"`
Order string `form:"order"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Before time.Time `form:"before" time_format:"2006-01-02"`
After time.Time `form:"after" time_format:"2006-01-02"`
FavoritesOnly bool `form:"category"`
}

View file

@ -6,7 +6,8 @@ import (
"net/http"
"github.com/photoprism/photoprism"
"strconv"
"log"
"github.com/photoprism/photoprism/server/forms"
"github.com/gin-gonic/gin/binding"
)
func Start(address string, port int, conf *photoprism.Config) {
@ -23,18 +24,15 @@ func Start(address string, port int, conf *photoprism.Config) {
v1.GET("/photos", func(c *gin.Context) {
search := photoprism.NewQuery(conf.OriginalsPath, conf.GetDb())
count, _ := strconv.Atoi(c.DefaultQuery("count", "50"))
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
var form forms.PhotoSearchForm
query := c.DefaultQuery("q", "")
c.MustBindWith(&form, binding.Form)
photos := search.FindPhotos(query, count, offset)
log.Printf("Query: %s, Count: %d", query, count)
photos := search.FindPhotos(form.Query, form.Count, form.Offset)
c.Header("x-result-total", strconv.Itoa(len(photos)))
c.Header("x-result-count", strconv.Itoa(count))
c.Header("x-result-offset", strconv.Itoa(offset))
c.Header("x-result-count", strconv.Itoa(form.Count))
c.Header("x-result-offset", strconv.Itoa(form.Offset))
c.JSON(http.StatusOK, photos)
})