Videos: Improve indexing and searching #312

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-05-16 17:07:44 +02:00
parent ba524d05d6
commit abfd2fa79d
12 changed files with 127 additions and 38 deletions

View File

@@ -56,6 +56,11 @@ class Viewer {
getImageURLForShare: function (button) { getImageURLForShare: function (button) {
const item = gallery.currItem; const item = gallery.currItem;
if (!item.original_w) {
button.label = button.template.replace("size", "not available");
return item.download_url;
}
if(button.id === "original") { if(button.id === "original") {
button.label = button.template.replace("size", item.original_w + " × " + item.original_h); button.label = button.template.replace("size", item.original_w + " × " + item.original_h);
return item.download_url; return item.download_url;

View File

@@ -32,7 +32,7 @@
let uri = video.videoUri(); let uri = video.videoUri();
if (!uri) { if (!uri) {
this.$notify.error("no video file found"); this.$notify.error("no video selected");
return; return;
} }
@@ -44,7 +44,7 @@
if(file.FileWidth > 0) { if(file.FileWidth > 0) {
width = file.FileWidth; width = file.FileWidth;
} else if(main.FileWidth > 0) { } else if(main && main.FileWidth > 0) {
width = main.FileWidth; width = main.FileWidth;
} else { } else {
width = this.defaultWidth; width = this.defaultWidth;
@@ -52,7 +52,7 @@
if(file.FileHeight > 0) { if(file.FileHeight > 0) {
height = file.FileHeight; height = file.FileHeight;
} else if(main.FileHeight > 0) { } else if(main && main.FileHeight > 0) {
height = main.FileHeight; height = main.FileHeight;
} else { } else {
height = this.defaultHeight; height = this.defaultHeight;

View File

@@ -178,6 +178,12 @@ class Photo extends RestModel {
let hash = this.mainFileHash(); let hash = this.mainFileHash();
if (!hash) { if (!hash) {
let video = this.videoFile();
if (video && video.FileHash) {
return "/api/v1/thumbnails/" + video.FileHash + "/" + type;
}
return "/api/v1/svg/photo"; return "/api/v1/svg/photo";
} }
@@ -272,12 +278,17 @@ class Photo extends RestModel {
} }
addSizeInfo(file, info) { addSizeInfo(file, info) {
if(!file) { if (!file) {
return; return;
} }
if (file.FileWidth && file.FileHeight) { if (file.FileWidth && file.FileHeight) {
info.push(file.FileWidth + " × " + file.FileHeight); info.push(file.FileWidth + " × " + file.FileHeight);
} else if (!file.FilePrimary) {
let main = this.mainFile();
if (main && main.FileWidth && main.FileHeight) {
info.push(main.FileWidth + " × " + main.FileHeight);
}
} }
if (file.FileSize > 102400) { if (file.FileSize > 102400) {

View File

@@ -9,8 +9,8 @@ class Thumb extends Model {
uuid: "", uuid: "",
title: "", title: "",
favorite: false, favorite: false,
original_w: "", original_w: 0,
original_h: "", original_h: 0,
download_url: "", download_url: "",
}; };
} }
@@ -25,15 +25,33 @@ class Thumb extends Model {
} }
} }
static thumbNotFound() {
const result = {
uuid: "",
title: "Not Found",
favorite: false,
original_w: 0,
original_h: 0,
download_url: "",
};
for (let i = 0; i < thumbs.length; i++) {
result[thumbs[i].Name] = {
src: "/api/v1/svg/photo",
w: thumbs[i].Width,
h: thumbs[i].Height,
};
}
return result;
}
static fromPhotos(photos) { static fromPhotos(photos) {
let result = []; let result = [];
photos.forEach((p) => { photos.forEach((p) => {
let thumb = this.fromPhoto(p); let thumb = this.fromPhoto(p);
result.push(thumb);
if(thumb) {
result.push(thumb);
}
}); });
return result; return result;
@@ -44,8 +62,8 @@ class Thumb extends Model {
return this.fromFile(photo, photo.Files.find(f => !!f.FilePrimary)); return this.fromFile(photo, photo.Files.find(f => !!f.FilePrimary));
} }
if(!photo || !photo.FileHash) { if (!photo || !photo.FileHash) {
return false; return this.thumbNotFound();
} }
const result = { const result = {
@@ -71,8 +89,8 @@ class Thumb extends Model {
} }
static fromFile(photo, file) { static fromFile(photo, file) {
if(!photo || !file || !file.FileHash) { if (!photo || !file || !file.FileHash) {
return false; return this.thumbNotFound();
} }
const result = { const result = {
@@ -107,7 +125,7 @@ class Thumb extends Model {
if (f && f.FileType === "jpg") { if (f && f.FileType === "jpg") {
let thumb = this.fromFile(p, f); let thumb = this.fromFile(p, f);
if(thumb) { if (thumb) {
result.push(thumb); result.push(thumb);
} }
} }

4
go.mod
View File

@@ -11,8 +11,8 @@ require (
github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 // indirect github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8 // indirect
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/djherbis/times v1.2.0 github.com/djherbis/times v1.2.0
github.com/dsoprea/go-exif/v2 v2.0.0-20200506085928-e7aea1340ccb github.com/dsoprea/go-exif/v2 v2.0.0-20200516122116-a45cc7cfd55e
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200504080818-288e72e0addf github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200516131022-2c0537684b05
github.com/dsoprea/go-png-image-structure v0.0.0-20200402000326-c0fdb803026f github.com/dsoprea/go-png-image-structure v0.0.0-20200402000326-c0fdb803026f
github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176 // indirect github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176 // indirect
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0

4
go.sum
View File

@@ -70,10 +70,14 @@ github.com/dsoprea/go-exif/v2 v2.0.0-20200506054703-8da3881353b3 h1:hgFwhM9Sjozd
github.com/dsoprea/go-exif/v2 v2.0.0-20200506054703-8da3881353b3/go.mod h1:YXOyDqCYjBuHHRw4JIGPgOgMit0IDvVSjjhsqOAFTYQ= github.com/dsoprea/go-exif/v2 v2.0.0-20200506054703-8da3881353b3/go.mod h1:YXOyDqCYjBuHHRw4JIGPgOgMit0IDvVSjjhsqOAFTYQ=
github.com/dsoprea/go-exif/v2 v2.0.0-20200506085928-e7aea1340ccb h1:En5n3RCyOL1Wnf78SNCzqFT9u4+kWJoSQEK/Z8MoZQE= github.com/dsoprea/go-exif/v2 v2.0.0-20200506085928-e7aea1340ccb h1:En5n3RCyOL1Wnf78SNCzqFT9u4+kWJoSQEK/Z8MoZQE=
github.com/dsoprea/go-exif/v2 v2.0.0-20200506085928-e7aea1340ccb/go.mod h1:YXOyDqCYjBuHHRw4JIGPgOgMit0IDvVSjjhsqOAFTYQ= github.com/dsoprea/go-exif/v2 v2.0.0-20200506085928-e7aea1340ccb/go.mod h1:YXOyDqCYjBuHHRw4JIGPgOgMit0IDvVSjjhsqOAFTYQ=
github.com/dsoprea/go-exif/v2 v2.0.0-20200516122116-a45cc7cfd55e h1:tPHXVRs63sg0ajoZjdmMa5aZuyjnSAt3Anwh2F4XsJM=
github.com/dsoprea/go-exif/v2 v2.0.0-20200516122116-a45cc7cfd55e/go.mod h1:YXOyDqCYjBuHHRw4JIGPgOgMit0IDvVSjjhsqOAFTYQ=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200419165912-75b7a4f392e6 h1:6oyE0L+MX1iUjldwrLdAaU95g36UrKpbmlyslhyoJj4= github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200419165912-75b7a4f392e6 h1:6oyE0L+MX1iUjldwrLdAaU95g36UrKpbmlyslhyoJj4=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200419165912-75b7a4f392e6/go.mod h1:5vwlfhyZI7u8AuvTl0G70sdqVdH41f7dscvBQ6mEbHs= github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200419165912-75b7a4f392e6/go.mod h1:5vwlfhyZI7u8AuvTl0G70sdqVdH41f7dscvBQ6mEbHs=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200504080818-288e72e0addf h1:fHaBuk2M/HNmLyQlQH91UNmsc883sNLukYXEsk7coZM= github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200504080818-288e72e0addf h1:fHaBuk2M/HNmLyQlQH91UNmsc883sNLukYXEsk7coZM=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200504080818-288e72e0addf/go.mod h1:dSPqu4ZEK+hLQmKh1XkwVdNkWwI8G7TOiZMLZrxx/j0= github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200504080818-288e72e0addf/go.mod h1:dSPqu4ZEK+hLQmKh1XkwVdNkWwI8G7TOiZMLZrxx/j0=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200516131022-2c0537684b05 h1:aBQuseJ46TDgCd1a/hYzJvvWNRTbGwt6k8/lOYzR9xU=
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200516131022-2c0537684b05/go.mod h1:7V7bSB5y4Zwomfq6jz43dABqCs9RFCgV+gakP7lIcHY=
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y=
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
github.com/dsoprea/go-logging v0.0.0-20200502201358-170ff607885f h1:FonKAuW3PmNtqk9tOR+Z7bnyQHytmnZBCmm5z1PQMss= github.com/dsoprea/go-logging v0.0.0-20200502201358-170ff607885f h1:FonKAuW3PmNtqk9tOR+Z7bnyQHytmnZBCmm5z1PQMss=

View File

@@ -37,6 +37,15 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
return return
} }
if f.FileVideo {
f, err = query.FileByPhotoUUID(f.PhotoUUID)
if err != nil {
c.Data(http.StatusOK, "image/svg+xml", videoIconSvg)
return
}
}
if f.FileError != "" { if f.FileError != "" {
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg) c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
return return

View File

@@ -7,10 +7,8 @@ import (
) )
var photoIconSvg = []byte(` var photoIconSvg = []byte(`
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M0 0h24v24H0z" fill="none"/> <path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z"/></svg>`)
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</svg>`)
var videoIconSvg = []byte(`<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"> var videoIconSvg = []byte(`<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">
<path d="M0 0h24v24H0z" fill="none"/><path d="M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/></svg>`) <path d="M0 0h24v24H0z" fill="none"/><path d="M10 8v8l5-4-5-4zm9-5H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/></svg>`)

View File

@@ -8,6 +8,7 @@ import (
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
"github.com/ulule/deepcopier"
) )
// File represents an image or sidecar file that belongs to a photo // File represents an image or sidecar file that belongs to a photo
@@ -53,6 +54,18 @@ type File struct {
DeletedAt *time.Time `sql:"index"` DeletedAt *time.Time `sql:"index"`
} }
type FileInfos struct {
FileWidth int
FileHeight int
FileOrientation int
FileAspectRatio float32
FileMainColor string
FileColors string
FileLuminance string
FileDiff uint32
FileChroma uint8
}
// FirstFileByHash gets a file in db from its hash // FirstFileByHash gets a file in db from its hash
func FirstFileByHash(fileHash string) (File, error) { func FirstFileByHash(fileHash string) (File, error) {
var file File var file File
@@ -136,3 +149,14 @@ func (m *File) Save() error {
return Db().Model(m).Related(Photo{}).Error return Db().Model(m).Related(Photo{}).Error
} }
// UpdateVideoInfos updates related video infos based on this file.
func (m *File) UpdateVideoInfos() error {
values := FileInfos{}
if err := deepcopier.Copy(&values).From(m); err != nil {
return err
}
return Db().Model(File{}).Where("photo_id = ? AND file_video = 1", m.PhotoID).Updates(values).Error
}

View File

@@ -150,7 +150,7 @@ func (data *Data) Exif(fileName string) (err error) {
return nil return nil
} }
_, err = exif.Visit(exifcommon.IfdStandard, im, ti, rawExif, visitor) _, _, err = exif.Visit(exifcommon.IfdStandard, im, ti, rawExif, visitor)
if err != nil { if err != nil {
return err return err

View File

@@ -135,22 +135,6 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
photo.PhotoPath = filePath photo.PhotoPath = filePath
photo.PhotoName = fileBase photo.PhotoName = fileBase
if m.IsVideo() {
photo.PhotoVideo = true
metaData, _ = m.MetaData()
file.FileCodec = metaData.Codec
file.FileWidth = metaData.Width
file.FileHeight = metaData.Height
file.FileDuration = metaData.Duration
file.FileAspectRatio = metaData.AspectRatio()
file.FilePortrait = metaData.Portrait()
if res := metaData.Megapixels(); res > photo.PhotoResolution {
photo.PhotoResolution = res
}
}
if !file.FilePrimary { if !file.FilePrimary {
if photoExists { if photoExists {
if q := ind.db.Where("file_type = 'jpg' AND file_primary = 1 AND photo_id = ?", photo.ID).First(&primaryFile); q.Error != nil { if q := ind.db.Where("file_type = 'jpg' AND file_primary = 1 AND photo_id = ?", photo.ID).First(&primaryFile); q.Error != nil {
@@ -170,6 +154,35 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
return result return result
} }
if m.IsVideo() {
photo.PhotoVideo = true
metaData, _ = m.MetaData()
file.FileCodec = metaData.Codec
file.FileWidth = metaData.Width
file.FileHeight = metaData.Height
file.FileDuration = metaData.Duration
file.FileAspectRatio = metaData.AspectRatio()
file.FilePortrait = metaData.Portrait()
if res := metaData.Megapixels(); res > photo.PhotoResolution {
photo.PhotoResolution = res
}
if file.FileWidth == 0 && primaryFile.FileWidth > 0 {
file.FileWidth = primaryFile.FileWidth
file.FileHeight = primaryFile.FileHeight
file.FileAspectRatio = primaryFile.FileAspectRatio
file.FilePortrait = primaryFile.FilePortrait
}
file.FileDiff = primaryFile.FileDiff
file.FileMainColor = primaryFile.FileMainColor
file.FileChroma = primaryFile.FileChroma
file.FileLuminance = primaryFile.FileLuminance
file.FileColors = primaryFile.FileColors
}
// file obviously exists: remove deleted and missing flags // file obviously exists: remove deleted and missing flags
file.DeletedAt = nil file.DeletedAt = nil
file.FileMissing = false file.FileMissing = false
@@ -448,6 +461,12 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
result.Status = IndexAdded result.Status = IndexAdded
} }
if photo.PhotoVideo && file.FilePrimary {
if err := file.UpdateVideoInfos(); err != nil {
log.Errorf("index: %s", err)
}
}
result.FileID = file.ID result.FileID = file.ID
result.FileUUID = file.FileUUID result.FileUUID = file.FileUUID

View File

@@ -262,6 +262,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
case entity.SortOrderImported: case entity.SortOrderImported:
s = s.Order("photos.id DESC, files.file_primary DESC") s = s.Order("photos.id DESC, files.file_primary DESC")
case entity.SortOrderSimilar: case entity.SortOrderSimilar:
s = s.Where("files.file_diff > 0")
s = s.Order("files.file_main_color, photos.location_id, files.file_diff, taken_at DESC, files.file_primary DESC") s = s.Order("files.file_main_color, photos.location_id, files.file_diff, taken_at DESC, files.file_primary DESC")
default: default:
s = s.Order("taken_at DESC, photos.photo_uuid, files.file_primary DESC") s = s.Order("taken_at DESC, photos.photo_uuid, files.file_primary DESC")