Indexing bug fixes and UX improvements
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
73c4891cde
commit
ca8a8466d4
27 changed files with 404 additions and 263 deletions
|
@ -7,7 +7,7 @@ ENV TF_CPP_MIN_LOG_LEVEL 2
|
|||
COPY /docker/demo/index.tmpl /photoprism/assets/templates
|
||||
|
||||
# Download example photos
|
||||
RUN wget -qO- https://dl.photoprism.org/fixtures/demo.tar.gz | tar xvz -C /photoprism/import
|
||||
RUN wget -qO- https://dl.photoprism.org/fixtures/demo.tar.gz | tar xvz -C /photoprism/originals
|
||||
|
||||
# Configure PhotoPrism
|
||||
ENV PHOTOPRISM_STORAGE_PATH /photoprism/storage
|
||||
|
@ -27,9 +27,11 @@ ENV PHOTOPRISM_THUMB_SIZE 3840
|
|||
ENV PHOTOPRISM_THUMB_LIMIT 3840
|
||||
ENV PHOTOPRISM_JPEG_QUALITY 95
|
||||
ENV PHOTOPRISM_JPEG_HIDDEN false
|
||||
ENV PHOTOPRISM_SITE_CAPTION "Try our demo"
|
||||
|
||||
# Import example photos
|
||||
RUN photoprism import
|
||||
RUN photoprism index
|
||||
RUN photoprism moments
|
||||
|
||||
# Start PhotoPrism server
|
||||
CMD photoprism --public start
|
||||
|
|
|
@ -296,7 +296,7 @@
|
|||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/files" @click="" class="p-navigation-files" v-show="$config.feature('files')">
|
||||
<v-list-tile to="/library/files" @click="" class="p-navigation-files" v-show="$config.feature('files')">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Files">Files</translate>
|
||||
|
@ -304,6 +304,15 @@
|
|||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
<v-list-tile to="/library/hidden" @click="" class="p-navigation-hidden">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
<translate key="Hidden">Hidden</translate>
|
||||
<span v-show="config.count.hidden > 0" class="p-navigation-count">{{ config.count.hidden }}</span>
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-show="!config.disableSettings">
|
||||
|
|
|
@ -16,7 +16,38 @@
|
|||
|
||||
<v-container fluid>
|
||||
<p class="subheading">
|
||||
<span v-if="total === 0">Select files to start upload...</span>
|
||||
<v-combobox v-if="total === 0" flat solo hide-details chips deletable-chips
|
||||
multiple color="secondary-dark" class="my-0"
|
||||
v-model="selectedAlbums"
|
||||
:items="albums"
|
||||
item-text="Title"
|
||||
item-value="UID"
|
||||
:allow-overflow="false"
|
||||
label="Select albums or create a new one"
|
||||
return-object
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-tile>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Press <kbd>enter</kbd> to create a new album.
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
<template v-slot:selection="data">
|
||||
<v-chip
|
||||
:key="JSON.stringify(data.item)"
|
||||
:selected="data.selected"
|
||||
:disabled="data.disabled"
|
||||
class="v-chip--select-multi"
|
||||
@input="data.parent.selectItem(data.item)"
|
||||
>
|
||||
<v-icon class="pr-1">folder</v-icon>
|
||||
{{ data.item.Title ? data.item.Title : data.item | truncate(40) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
<span v-else-if="failed">Upload failed</span>
|
||||
<span v-else-if="total > 0 && completed < 100">
|
||||
Uploading {{current}} of {{total}}...
|
||||
|
@ -25,38 +56,6 @@
|
|||
<span v-else-if="completed === 100">Done.</span>
|
||||
</p>
|
||||
|
||||
<v-combobox flat solo hide-details chips deletable-chips
|
||||
multiple color="secondary-dark" class="my-3"
|
||||
v-model="selectedAlbums"
|
||||
:items="albums"
|
||||
item-text="Title"
|
||||
item-value="UID"
|
||||
:allow-overflow="false"
|
||||
label="Add to existing albums or create a new one."
|
||||
return-object
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-tile>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Press <kbd>enter</kbd> to create a new album.
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
<template v-slot:selection="data">
|
||||
<v-chip
|
||||
:key="JSON.stringify(data.item)"
|
||||
:selected="data.selected"
|
||||
:disabled="data.disabled"
|
||||
class="v-chip--select-multi"
|
||||
@input="data.parent.selectItem(data.item)"
|
||||
>
|
||||
<v-icon class="pr-1">folder</v-icon>
|
||||
{{ data.item.Title ? data.item.Title : data.item | truncate(40) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
|
||||
<v-progress-linear color="secondary-dark" v-model="completed"
|
||||
:indeterminate="indexing"></v-progress-linear>
|
||||
|
|
|
@ -369,6 +369,10 @@ export class Photo extends RestModel {
|
|||
info.push(Util.duration(file.Duration));
|
||||
}
|
||||
|
||||
if (file.Codec) {
|
||||
info.push(file.Codec.toUpperCase());
|
||||
}
|
||||
|
||||
this.addSizeInfo(file, info);
|
||||
|
||||
if (!info) {
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-toolbar-title>Events</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
</v-toolbar>
|
||||
|
||||
<v-container>
|
||||
<v-layout wrap>
|
||||
<v-flex
|
||||
xs12
|
||||
class="mb-3"
|
||||
>
|
||||
<v-sheet height="500">
|
||||
<v-calendar
|
||||
ref="calendar"
|
||||
v-model="start"
|
||||
:type="type"
|
||||
:end="end"
|
||||
color="primary"
|
||||
></v-calendar>
|
||||
</v-sheet>
|
||||
</v-flex>
|
||||
|
||||
<v-flex
|
||||
sm4
|
||||
xs12
|
||||
class="text-sm-left text-xs-center"
|
||||
>
|
||||
<v-btn @click="$refs.calendar.prev()">
|
||||
<v-icon
|
||||
dark
|
||||
left
|
||||
>
|
||||
keyboard_arrow_left
|
||||
</v-icon>
|
||||
<translate>Prev</translate>
|
||||
</v-btn>
|
||||
</v-flex>
|
||||
<v-flex
|
||||
sm4
|
||||
xs12
|
||||
class="text-xs-center"
|
||||
>
|
||||
<v-select
|
||||
v-model="type"
|
||||
:items="typeOptions"
|
||||
:label="labels.type"
|
||||
></v-select>
|
||||
</v-flex>
|
||||
<v-flex
|
||||
sm4
|
||||
xs12
|
||||
class="text-sm-right text-xs-center"
|
||||
>
|
||||
<v-btn @click="$refs.calendar.next()">
|
||||
<translate>Next</translate>
|
||||
<v-icon
|
||||
right
|
||||
dark
|
||||
>
|
||||
keyboard_arrow_right
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'calendar',
|
||||
data: () => ({
|
||||
type: 'month',
|
||||
start: '2019-01-01',
|
||||
end: '2019-01-06',
|
||||
typeOptions: [
|
||||
{text: this.$gettext('Day'), value: 'day'},
|
||||
{text: this.$gettext('4 Day'), value: '4day'},
|
||||
{text: this.$gettext('Week'), value: 'week'},
|
||||
{text: this.$gettext('Month'), value: 'month'},
|
||||
{text: this.$gettext('Custom Daily'), value: 'custom-daily'},
|
||||
{text: this.$gettext('Custom Weekly'), value: 'custom-weekly'}
|
||||
],
|
||||
labels: {
|
||||
type: this.$gettext("Type"),
|
||||
},
|
||||
}),
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
|
@ -40,6 +40,19 @@
|
|||
},
|
||||
data() {
|
||||
const s = this.$config.values.settings.maps;
|
||||
const filter = {
|
||||
q: this.query(),
|
||||
};
|
||||
|
||||
const settings = this.$config.settings();
|
||||
|
||||
if (settings && settings.features.private) {
|
||||
filter.public = true;
|
||||
}
|
||||
|
||||
if (settings && settings.features.review && (!this.staticFilter || !("quality" in this.staticFilter))) {
|
||||
filter.quality = 3;
|
||||
}
|
||||
|
||||
return {
|
||||
initialized: false,
|
||||
|
@ -58,9 +71,7 @@
|
|||
},
|
||||
photos: [],
|
||||
result: {},
|
||||
filter: {
|
||||
q: this.query(),
|
||||
},
|
||||
filter: filter,
|
||||
lastFilter: {},
|
||||
labels: {
|
||||
search: this.$gettext("Search"),
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-toolbar-title>Not implemented yet</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
</v-toolbar>
|
||||
|
||||
<v-container>
|
||||
<p>
|
||||
Issues labeled <a href="https://github.com/photoprism/photoprism/labels/help%20wanted">help wanted</a> /
|
||||
<a href="https://github.com/photoprism/photoprism/labels/easy">easy</a> can be good (first)
|
||||
contributions.
|
||||
Our <a href="https://github.com/photoprism/photoprism/wiki">Developer Guide</a> contains all information
|
||||
necessary to get you started.
|
||||
</p>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'todo',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<div class="p-page p-page-todo">
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-toolbar-title>Not implemented yet</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
</v-toolbar>
|
||||
|
||||
<v-container>
|
||||
<p>
|
||||
Issues labeled <a href="https://github.com/photoprism/photoprism/labels/help%20wanted">help wanted</a> /
|
||||
<a href="https://github.com/photoprism/photoprism/labels/easy">easy</a> can be good (first)
|
||||
contributions.
|
||||
Our <a href="https://github.com/photoprism/photoprism/wiki">Developer Guide</a> contains all information
|
||||
necessary to get you started.
|
||||
</p>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'p-page-todo',
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
|
@ -2,15 +2,13 @@ import Photos from "pages/photos.vue";
|
|||
import Albums from "pages/albums.vue";
|
||||
import AlbumPhotos from "pages/album/photos.vue";
|
||||
import Places from "pages/places.vue";
|
||||
import Files from "pages/files.vue";
|
||||
import Files from "pages/library/files.vue";
|
||||
import Labels from "pages/labels.vue";
|
||||
import People from "pages/people.vue";
|
||||
import Library from "pages/library.vue";
|
||||
import Share from "pages/share.vue";
|
||||
import Settings from "pages/settings.vue";
|
||||
import Login from "pages/login.vue";
|
||||
import Discover from "pages/discover.vue";
|
||||
import Todo from "pages/todo.vue";
|
||||
|
||||
const c = window.__CONFIG__;
|
||||
|
||||
|
@ -134,10 +132,16 @@ export default [
|
|||
},
|
||||
{
|
||||
name: "files",
|
||||
path: "/files*",
|
||||
path: "/library/files*",
|
||||
component: Files,
|
||||
meta: {title: "File Browser", auth: true},
|
||||
},
|
||||
{
|
||||
name: "hidden",
|
||||
path: "/library/hidden",
|
||||
component: Photos,
|
||||
props: {staticFilter: {hidden: true}},
|
||||
},
|
||||
{
|
||||
name: "labels",
|
||||
path: "/labels",
|
||||
|
@ -157,12 +161,6 @@ export default [
|
|||
component: People,
|
||||
meta: {title: "People", auth: true},
|
||||
},
|
||||
{
|
||||
name: "filters",
|
||||
path: "/filters",
|
||||
component: Todo,
|
||||
meta: {title: "Filters", auth: true},
|
||||
},
|
||||
{
|
||||
name: "library_logs",
|
||||
path: "/library/logs",
|
||||
|
@ -184,12 +182,6 @@ export default [
|
|||
meta: {title: "Originals", auth: true, background: "application-light"},
|
||||
props: {tab: 0},
|
||||
},
|
||||
{
|
||||
name: "share",
|
||||
path: "/share",
|
||||
component: Share,
|
||||
meta: {title: "Share with friends", auth: true},
|
||||
},
|
||||
{
|
||||
name: "settings",
|
||||
path: "/settings",
|
||||
|
|
|
@ -33,14 +33,45 @@ var UnknownCamera = Camera{
|
|||
|
||||
var CameraMakes = map[string]string{
|
||||
"OLYMPUS OPTICAL CO.,LTD": "Olympus",
|
||||
"samsung": "Samsung",
|
||||
}
|
||||
|
||||
var CameraModels = map[string]string{
|
||||
"ELE-L29": "P30",
|
||||
"ELE-AL00": "P30",
|
||||
"ELE-L04": "P30",
|
||||
"ELE-L09": "P30",
|
||||
"ELE-TL00": "P30",
|
||||
"ELE-L29": "P30",
|
||||
"ELE-AL00": "P30",
|
||||
"ELE-L04": "P30",
|
||||
"ELE-L09": "P30",
|
||||
"ELE-TL00": "P30",
|
||||
"VOG-L29": "P30 Pro",
|
||||
"VOG-L09": "P30 Pro",
|
||||
"VOG-L04": "P30 Pro",
|
||||
"VOG-AL00": "P30 Pro",
|
||||
"VOG-AL10": "P30 Pro",
|
||||
"VOG-TL00": "P30 Pro",
|
||||
"MAR-L01A": "P30 Lite",
|
||||
"MAR-L21A": "P30 Lite",
|
||||
"MAR-LX1A": "P30 Lite",
|
||||
"MAR-LX1M": "P30 Lite",
|
||||
"MAR-LX2": "P30 Lite",
|
||||
"MAR-L21MEA": "P30 Lite",
|
||||
"MAR-L22A": "P30 Lite",
|
||||
"MAR-L22B": "P30 Lite",
|
||||
"MAR-LX3A": "P30 Lite",
|
||||
"ANA-AN00": "P40",
|
||||
"ANA-TN00": "P40",
|
||||
"ELS-AN00": "P40 Pro",
|
||||
"ELS-TN00": "P40 Pro",
|
||||
"ELS-NX9": "P40 Pro",
|
||||
"ELS-N04": "P40 Pro",
|
||||
"JNY-L21A": "P40 Lite",
|
||||
"JNY-L01A": "P40 Lite",
|
||||
"JNY-L21B": "P40 Lite",
|
||||
"JNY-L22A": "P40 Lite",
|
||||
"JNY-L02A": "P40 Lite",
|
||||
"JNY-L22B": "P40 Lite",
|
||||
"STK-LX1": "Honor 9X",
|
||||
"HLK-AL00": "Honor 9X",
|
||||
"HLK-TL00": "Honor 9X",
|
||||
}
|
||||
|
||||
// CreateUnknownCamera initializes the database with an unknown camera if not exists
|
||||
|
|
|
@ -419,7 +419,18 @@ func (m *Photo) LoadPlace() error {
|
|||
}
|
||||
|
||||
var place Place
|
||||
return Db().Set("gorm:auto_preload", true).Model(m).Related(&place, "Place").Error
|
||||
|
||||
err := Db().Set("gorm:auto_preload", true).Model(m).Related(&place, "Place").Error
|
||||
|
||||
if m.Place == nil {
|
||||
m.Place = &place
|
||||
}
|
||||
|
||||
if m.Place.Unknown() {
|
||||
m.Place = &UnknownPlace
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// HasLatLng checks if the photo has a latitude and longitude.
|
||||
|
|
|
@ -102,7 +102,7 @@ func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classif
|
|||
if m.UnknownLocation() {
|
||||
m.Location = &UnknownLocation
|
||||
m.LocationID = UnknownLocation.ID
|
||||
} else if err := m.LoadLocation(); err == nil {
|
||||
} else if err := m.LoadLocation(); err == nil && m.Location != nil && m.Location.Place != nil {
|
||||
m.Place = m.Location.Place
|
||||
m.PlaceID = m.Location.PlaceID
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classif
|
|||
if m.UnknownPlace() {
|
||||
m.Place = &UnknownPlace
|
||||
m.PlaceID = UnknownPlace.ID
|
||||
} else if err := m.LoadPlace(); err == nil {
|
||||
} else if err := m.LoadPlace(); err == nil && m.Place != nil {
|
||||
m.PhotoCountry = m.Place.CountryCode()
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,16 @@ type GeoSearch struct {
|
|||
Favorite bool `form:"favorite"`
|
||||
Video bool `form:"video"`
|
||||
Photo bool `form:"photo"`
|
||||
Archived bool `form:"archived"`
|
||||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Review bool `form:"review"`
|
||||
Quality int `form:"quality"`
|
||||
Lat float32 `form:"lat"`
|
||||
Lng float32 `form:"lng"`
|
||||
S2 string `form:"s2"`
|
||||
Olc string `form:"olc"`
|
||||
Dist uint `form:"dist"`
|
||||
Quality int `form:"quality"`
|
||||
Review bool `form:"review"`
|
||||
Album string `form:"album"`
|
||||
Country string `form:"country"`
|
||||
Year int `form:"year"`
|
||||
|
|
|
@ -19,8 +19,13 @@ type PhotoSearch struct {
|
|||
Video bool `form:"video"`
|
||||
Photo bool `form:"photo"`
|
||||
Duplicate bool `form:"duplicate"`
|
||||
Archived bool `form:"archived"`
|
||||
Error bool `form:"error"`
|
||||
Hidden bool `form:"hidden"`
|
||||
Archived bool `form:"archived"`
|
||||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Favorite bool `form:"favorite"`
|
||||
Safe bool `form:"safe"`
|
||||
Lat float32 `form:"lat"`
|
||||
Lng float32 `form:"lng"`
|
||||
Dist uint `form:"dist"`
|
||||
|
@ -45,10 +50,6 @@ type PhotoSearch struct {
|
|||
Lens int `form:"lens"`
|
||||
Before time.Time `form:"before" time_format:"2006-01-02"`
|
||||
After time.Time `form:"after" time_format:"2006-01-02"`
|
||||
Favorite bool `form:"favorite"`
|
||||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Safe bool `form:"safe"`
|
||||
Count int `form:"count" binding:"required" serialize:"-"`
|
||||
Offset int `form:"offset" serialize:"-"`
|
||||
Order string `form:"order" serialize:"-"`
|
||||
|
|
|
@ -49,8 +49,8 @@ type Data struct {
|
|||
|
||||
// AspectRatio returns the aspect ratio based on width and height.
|
||||
func (data Data) AspectRatio() float32 {
|
||||
width := float64(data.Width)
|
||||
height := float64(data.Height)
|
||||
width := float64(data.ActualWidth())
|
||||
height := float64(data.ActualHeight())
|
||||
|
||||
if width <= 0 || height <= 0 {
|
||||
return 0
|
||||
|
@ -85,3 +85,21 @@ func (data Data) HasInstanceID() bool {
|
|||
func (data Data) HasTimeAndPlace() bool {
|
||||
return !data.TakenAt.IsZero() && data.Lat != 0 && data.Lng != 0
|
||||
}
|
||||
|
||||
// ActualWidth is the width after rotating the media file if needed.
|
||||
func (data Data) ActualWidth() int {
|
||||
if data.Orientation > 4 {
|
||||
return data.Height
|
||||
}
|
||||
|
||||
return data.Width
|
||||
}
|
||||
|
||||
// ActualHeight is the height after rotating the media file if needed.
|
||||
func (data Data) ActualHeight() int {
|
||||
if data.Orientation > 4 {
|
||||
return data.Width
|
||||
}
|
||||
|
||||
return data.Height
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
assert.Equal(t, "iPhone 7", data.CameraModel)
|
||||
assert.Equal(t, 74, data.FocalLength)
|
||||
assert.Equal(t, 6, int(data.Orientation))
|
||||
assert.Equal(t, 6, data.Orientation)
|
||||
assert.Equal(t, "Apple", data.LensMake)
|
||||
assert.Equal(t, "iPhone 7 back camera 3.99mm f/1.8", data.LensModel)
|
||||
|
||||
|
@ -232,4 +232,38 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, "721", data.All["PixelXDimension"])
|
||||
assert.Equal(t, "332", data.All["PixelYDimension"])
|
||||
})
|
||||
|
||||
t.Run("orientation.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/orientation.jpg")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "3264", data.All["PixelXDimension"])
|
||||
assert.Equal(t, "1836", data.All["PixelYDimension"])
|
||||
assert.Equal(t, 3264, data.Width)
|
||||
assert.Equal(t, 1836, data.Height)
|
||||
assert.Equal(t, 6, data.Orientation) // TODO: Should be 1
|
||||
|
||||
if err := data.JSON("testdata/orientation.json", "orientation.jpg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 326, data.Width)
|
||||
assert.Equal(t, 184, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
|
||||
if err := data.JSON("testdata/orientation.json", "foo.jpg"); err != nil {
|
||||
assert.EqualError(t, err, "meta: original name foo.jpg does not match orientation.jpg (json)")
|
||||
} else {
|
||||
t.Error("error expected when providing wrong orginal name")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("gopher-preview.jpg", func(t *testing.T) {
|
||||
_, err := Exif("testdata/gopher-preview.jpg")
|
||||
|
||||
assert.EqualError(t, err, "no exif data in gopher-preview.jpg")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ import (
|
|||
)
|
||||
|
||||
// JSON parses a json sidecar file (as used by Exiftool) and returns a Data struct.
|
||||
func JSON(fileName string) (data Data, err error) {
|
||||
err = data.JSON(fileName)
|
||||
func JSON(jsonName, originalName string) (data Data, err error) {
|
||||
err = data.JSON(jsonName, originalName)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
// JSON parses a json sidecar file (as used by Exiftool) and returns a Data struct.
|
||||
func (data *Data) JSON(fileName string) (err error) {
|
||||
func (data *Data) JSON(jsonName, originalName string) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%s (json metadata)", e)
|
||||
|
@ -32,17 +32,17 @@ func (data *Data) JSON(fileName string) (err error) {
|
|||
data.All = make(map[string]string)
|
||||
}
|
||||
|
||||
jsonString, err := ioutil.ReadFile(fileName)
|
||||
jsonString, err := ioutil.ReadFile(jsonName)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("meta: %s", err.Error())
|
||||
return fmt.Errorf("can't read %s (json)", txt.Quote(filepath.Base(fileName)))
|
||||
return fmt.Errorf("can't read %s (json)", txt.Quote(filepath.Base(jsonName)))
|
||||
}
|
||||
|
||||
j := gjson.GetBytes(jsonString, "@flatten|@join")
|
||||
|
||||
if !j.IsObject() {
|
||||
return fmt.Errorf("data is not an object in %s (json)", txt.Quote(filepath.Base(fileName)))
|
||||
return fmt.Errorf("data is not an object in %s (json)", txt.Quote(filepath.Base(jsonName)))
|
||||
}
|
||||
|
||||
jsonValues := j.Map()
|
||||
|
@ -51,6 +51,10 @@ func (data *Data) JSON(fileName string) (err error) {
|
|||
data.All[key] = val.String()
|
||||
}
|
||||
|
||||
if fileName, ok := data.All["FileName"]; ok && fileName != "" && originalName != "" && fileName != originalName {
|
||||
return fmt.Errorf("meta: original name %s does not match %s (json)", txt.Quote(originalName), txt.Quote(fileName))
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(data).Elem()
|
||||
|
||||
// Iterate through all config fields
|
||||
|
@ -132,10 +136,39 @@ func (data *Data) JSON(fileName string) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Fix rotation.
|
||||
if data.Rotation == 90 || data.Rotation == 270 || data.Rotation == -90 {
|
||||
data.Width, data.Height = data.Height, data.Width
|
||||
data.Rotation = 0
|
||||
if orientation, ok := data.All["Orientation"]; ok && orientation != "" {
|
||||
switch orientation {
|
||||
case "1", "Horizontal (normal)":
|
||||
data.Orientation = 1
|
||||
case "2":
|
||||
data.Orientation = 2
|
||||
case "3", "Rotate 180 CW":
|
||||
data.Orientation = 3
|
||||
case "4":
|
||||
data.Orientation = 4
|
||||
case "5":
|
||||
data.Orientation = 5
|
||||
case "6", "Rotate 90 CW":
|
||||
data.Orientation = 6
|
||||
case "7":
|
||||
data.Orientation = 7
|
||||
case "8", "Rotate 270 CW":
|
||||
data.Orientation = 8
|
||||
}
|
||||
}
|
||||
|
||||
if data.Orientation == 0 {
|
||||
// Set orientation based on rotation.
|
||||
switch data.Rotation {
|
||||
case 0:
|
||||
data.Orientation = 1
|
||||
case -180, 180:
|
||||
data.Orientation = 3
|
||||
case 90:
|
||||
data.Orientation = 6
|
||||
case -90, 270:
|
||||
data.Orientation = 8
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize compression information.
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
func TestJSON(t *testing.T) {
|
||||
t.Run("iphone-mov.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/iphone-mov.json")
|
||||
data, err := JSON("testdata/iphone-mov.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -21,8 +21,11 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "2018-09-08 17:20:14 +0000 UTC", data.TakenAtLocal.String())
|
||||
assert.Equal(t, "2018-09-08 15:20:14 +0000 UTC", data.TakenAt.String())
|
||||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 1080, data.Width)
|
||||
assert.Equal(t, 1920, data.Height)
|
||||
assert.Equal(t, 1920, data.Width)
|
||||
assert.Equal(t, 1080, data.Height)
|
||||
assert.Equal(t, 1080, data.ActualWidth())
|
||||
assert.Equal(t, 1920, data.ActualHeight())
|
||||
assert.Equal(t, 6, data.Orientation)
|
||||
assert.Equal(t, float32(52.4587), data.Lat)
|
||||
assert.Equal(t, float32(13.4593), data.Lng)
|
||||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
|
@ -31,7 +34,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("gopher-telegram.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/gopher-telegram.json")
|
||||
data, err := JSON("testdata/gopher-telegram.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -46,6 +49,9 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "", data.TimeZone)
|
||||
assert.Equal(t, 270, data.Width)
|
||||
assert.Equal(t, 480, data.Height)
|
||||
assert.Equal(t, 270, data.ActualWidth())
|
||||
assert.Equal(t, 480, data.ActualHeight())
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(0), data.Lat)
|
||||
assert.Equal(t, float32(0), data.Lng)
|
||||
assert.Equal(t, "", data.CameraMake)
|
||||
|
@ -54,7 +60,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("gopher-original.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/gopher-original.json")
|
||||
data, err := JSON("testdata/gopher-original.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -67,8 +73,12 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "2020-05-11 14:16:48 +0000 UTC", data.TakenAtLocal.String())
|
||||
assert.Equal(t, "2020-05-11 12:16:48 +0000 UTC", data.TakenAt.String())
|
||||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 1080, data.Width)
|
||||
assert.Equal(t, 1920, data.Height)
|
||||
assert.Equal(t, 1920, data.Width)
|
||||
assert.Equal(t, 1080, data.Height)
|
||||
assert.Equal(t, 1080, data.ActualWidth())
|
||||
assert.Equal(t, 1920, data.ActualHeight())
|
||||
assert.Equal(t, float32(0.5625), data.AspectRatio())
|
||||
assert.Equal(t, 6, data.Orientation)
|
||||
assert.Equal(t, float32(52.4596), data.Lat)
|
||||
assert.Equal(t, float32(13.3218), data.Lng)
|
||||
assert.Equal(t, "", data.CameraMake)
|
||||
|
@ -77,7 +87,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("berlin-landscape.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/berlin-landscape.json")
|
||||
data, err := JSON("testdata/berlin-landscape.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -92,6 +102,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 1920, data.Width)
|
||||
assert.Equal(t, 1080, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(52.4649), data.Lat)
|
||||
assert.Equal(t, float32(13.3148), data.Lng)
|
||||
assert.Equal(t, "", data.CameraMake)
|
||||
|
@ -100,7 +111,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("mp4.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/mp4.json")
|
||||
data, err := JSON("testdata/mp4.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -113,6 +124,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "2019-11-23 13:51:49 +0000 UTC", data.TakenAtLocal.String())
|
||||
assert.Equal(t, 848, data.Width)
|
||||
assert.Equal(t, 480, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Copyright)
|
||||
assert.Equal(t, "", data.CameraMake)
|
||||
assert.Equal(t, "", data.CameraModel)
|
||||
|
@ -120,7 +132,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("photoshop.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/photoshop.json")
|
||||
data, err := JSON("testdata/photoshop.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -142,10 +154,11 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "HUAWEI", data.CameraMake)
|
||||
assert.Equal(t, "ELE-L29", data.CameraModel)
|
||||
assert.Equal(t, "HUAWEI P30 Rear Main Camera", data.LensModel)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("canon_eos_6d.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/canon_eos_6d.json")
|
||||
data, err := JSON("testdata/canon_eos_6d.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -161,10 +174,11 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Canon", data.CameraMake)
|
||||
assert.Equal(t, "Canon EOS 6D", data.CameraModel)
|
||||
assert.Equal(t, "EF24-105mm f/4L IS USM", data.LensModel)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("gps-2000.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/gps-2000.json")
|
||||
data, err := JSON("testdata/gps-2000.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -181,10 +195,11 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "", data.CameraModel)
|
||||
assert.Equal(t, "", data.LensMake)
|
||||
assert.Equal(t, "", data.LensModel)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("ladybug.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/ladybug.json")
|
||||
data, err := JSON("testdata/ladybug.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -201,10 +216,11 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "", data.CameraModel)
|
||||
assert.Equal(t, "", data.LensMake)
|
||||
assert.Equal(t, "", data.LensModel)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
|
||||
t.Run("iphone_7.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/iphone_7.json")
|
||||
data, err := JSON("testdata/iphone_7.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -217,6 +233,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "", data.Artist)
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "", data.Copyright)
|
||||
assert.Equal(t, 6, data.Orientation)
|
||||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
assert.Equal(t, "iPhone 7", data.CameraModel)
|
||||
assert.Equal(t, "Apple", data.LensMake)
|
||||
|
@ -224,7 +241,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("uuid-original.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/uuid-original.json")
|
||||
data, err := JSON("testdata/uuid-original.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -241,6 +258,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 3024, data.Width)
|
||||
assert.Equal(t, 4032, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(48.300003), data.Lat)
|
||||
assert.Equal(t, float32(8.929067), data.Lng)
|
||||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
|
@ -249,7 +267,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("uuid-copy.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/uuid-copy.json")
|
||||
data, err := JSON("testdata/uuid-copy.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -266,6 +284,7 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 1024, data.Width)
|
||||
assert.Equal(t, 1365, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(48.300003), data.Lat)
|
||||
assert.Equal(t, float32(8.929067), data.Lng)
|
||||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
|
@ -274,7 +293,7 @@ func TestJSON(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("uuid-imagemagick.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/uuid-imagemagick.json")
|
||||
data, err := JSON("testdata/uuid-imagemagick.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -291,10 +310,23 @@ func TestJSON(t *testing.T) {
|
|||
assert.Equal(t, "Europe/Berlin", data.TimeZone)
|
||||
assert.Equal(t, 1125, data.Width)
|
||||
assert.Equal(t, 1500, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, float32(48.300003), data.Lat)
|
||||
assert.Equal(t, float32(8.929067), data.Lng)
|
||||
assert.Equal(t, "Apple", data.CameraMake)
|
||||
assert.Equal(t, "iPhone SE", data.CameraModel)
|
||||
assert.Equal(t, "iPhone SE back camera 4.15mm f/2.2", data.LensModel)
|
||||
})
|
||||
|
||||
t.Run("orientation.json", func(t *testing.T) {
|
||||
data, err := JSON("testdata/orientation.json", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 326, data.Width)
|
||||
assert.Equal(t, 184, data.Height)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
})
|
||||
}
|
||||
|
|
BIN
internal/meta/testdata/gopher-preview.jpg
vendored
Normal file
BIN
internal/meta/testdata/gopher-preview.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 128 KiB |
BIN
internal/meta/testdata/orientation.jpg
vendored
Normal file
BIN
internal/meta/testdata/orientation.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
91
internal/meta/testdata/orientation.json
vendored
Normal file
91
internal/meta/testdata/orientation.json
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
[{
|
||||
"SourceFile": "orientation.jpg",
|
||||
"ExifToolVersion": 10.80,
|
||||
"FileName": "orientation.jpg",
|
||||
"Directory": ".",
|
||||
"FileSize": "45 kB",
|
||||
"FileModifyDate": "2020:06:04 09:20:43+00:00",
|
||||
"FileAccessDate": "2020:06:04 09:20:45+00:00",
|
||||
"FileInodeChangeDate": "2020:06:04 09:20:43+00:00",
|
||||
"FilePermissions": "rw-r--r--",
|
||||
"FileType": "JPEG",
|
||||
"FileTypeExtension": "jpg",
|
||||
"MIMEType": "image/jpeg",
|
||||
"JFIFVersion": 1.01,
|
||||
"ExifByteOrder": "Little-endian (Intel, II)",
|
||||
"Make": "samsung",
|
||||
"Model": "SM-G900F",
|
||||
"Orientation": "Horizontal (normal)",
|
||||
"XResolution": 72,
|
||||
"YResolution": 72,
|
||||
"ResolutionUnit": "inches",
|
||||
"Software": "G900FXXS1CQD1",
|
||||
"ModifyDate": "2018:10:03 16:56:19",
|
||||
"YCbCrPositioning": "Centered",
|
||||
"ExposureTime": "1/2376",
|
||||
"FNumber": 2.2,
|
||||
"ExposureProgram": "Program AE",
|
||||
"ISO": 40,
|
||||
"ExifVersion": "0220",
|
||||
"DateTimeOriginal": "2018:10:03 16:56:19",
|
||||
"CreateDate": "2018:10:03 16:56:19",
|
||||
"ComponentsConfiguration": "Y, Cb, Cr, -",
|
||||
"ShutterSpeedValue": "1/2369",
|
||||
"ApertureValue": 2.2,
|
||||
"BrightnessValue": 9.92,
|
||||
"ExposureCompensation": 0,
|
||||
"MaxApertureValue": 2.2,
|
||||
"MeteringMode": "Center-weighted average",
|
||||
"LightSource": "Unknown",
|
||||
"Flash": "Fired",
|
||||
"FocalLength": "4.8 mm",
|
||||
"UserComment": "\n",
|
||||
"SubSecTime": 414,
|
||||
"SubSecTimeOriginal": 414,
|
||||
"SubSecTimeDigitized": 414,
|
||||
"FlashpixVersion": "0100",
|
||||
"ColorSpace": "sRGB",
|
||||
"ExifImageWidth": 3264,
|
||||
"ExifImageHeight": 1836,
|
||||
"InteropIndex": "R98 - DCF basic file (sRGB)",
|
||||
"InteropVersion": "0100",
|
||||
"SensingMethod": "One-chip color area",
|
||||
"SceneType": "Directly photographed",
|
||||
"ExposureMode": "Auto",
|
||||
"WhiteBalance": "Auto",
|
||||
"FocalLengthIn35mmFormat": "31 mm",
|
||||
"SceneCaptureType": "Standard",
|
||||
"ImageUniqueID": "F16QLHF01VB",
|
||||
"GPSVersionID": "2.2.0.0",
|
||||
"GPSLatitudeRef": "North",
|
||||
"GPSLongitudeRef": "East",
|
||||
"Compression": "JPEG (old-style)",
|
||||
"ThumbnailOffset": 3426,
|
||||
"ThumbnailLength": 12320,
|
||||
"XMPToolkit": "Image::ExifTool 10.80",
|
||||
"Subject": "Nice",
|
||||
"Title": "Côte d'Azur 3-9 octobre 2018 - Nice",
|
||||
"ImageWidth": 326,
|
||||
"ImageHeight": 184,
|
||||
"EncodingProcess": "Baseline DCT, Huffman coding",
|
||||
"BitsPerSample": 8,
|
||||
"ColorComponents": 3,
|
||||
"YCbCrSubSampling": "YCbCr4:2:0 (2 2)",
|
||||
"Aperture": 2.2,
|
||||
"GPSLatitude": "43 deg 41' 45.00\" N",
|
||||
"GPSLongitude": "7 deg 16' 17.00\" E",
|
||||
"GPSPosition": "43 deg 41' 45.00\" N, 7 deg 16' 17.00\" E",
|
||||
"ImageSize": "326x184",
|
||||
"Megapixels": 0.060,
|
||||
"ScaleFactor35efl": 6.5,
|
||||
"ShutterSpeed": "1/2376",
|
||||
"SubSecCreateDate": "2018:10:03 16:56:19.414",
|
||||
"SubSecDateTimeOriginal": "2018:10:03 16:56:19.414",
|
||||
"SubSecModifyDate": "2018:10:03 16:56:19.414",
|
||||
"ThumbnailImage": "(Binary data 12320 bytes, use -b option to extract)",
|
||||
"CircleOfConfusion": "0.005 mm",
|
||||
"FOV": "60.3 deg",
|
||||
"FocalLength35efl": "4.8 mm (35 mm equivalent: 31.0 mm)",
|
||||
"HyperfocalDistance": "2.25 m",
|
||||
"LightValue": 14.8
|
||||
}]
|
|
@ -226,8 +226,8 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
metaData = m.MetaData()
|
||||
|
||||
file.FileCodec = metaData.Codec
|
||||
file.FileWidth = metaData.Width
|
||||
file.FileHeight = metaData.Height
|
||||
file.FileWidth = metaData.ActualWidth()
|
||||
file.FileHeight = metaData.ActualHeight()
|
||||
file.FileDuration = metaData.Duration
|
||||
file.FileAspectRatio = metaData.AspectRatio()
|
||||
file.FilePortrait = metaData.Portrait()
|
||||
|
|
|
@ -309,6 +309,11 @@ func (m *MediaFile) FileName() string {
|
|||
return m.fileName
|
||||
}
|
||||
|
||||
// BaseName returns the filename without path.
|
||||
func (m *MediaFile) BaseName() string {
|
||||
return filepath.Base(m.fileName)
|
||||
}
|
||||
|
||||
// SetFileName sets the filename to the given string.
|
||||
func (m *MediaFile) SetFileName(fileName string) {
|
||||
m.fileName = fileName
|
||||
|
@ -620,7 +625,7 @@ func (m *MediaFile) HasJson() bool {
|
|||
}
|
||||
|
||||
func (m *MediaFile) decodeDimensions() error {
|
||||
if !m.IsPhoto() {
|
||||
if !m.IsMedia() {
|
||||
return fmt.Errorf("not a photo: %s", m.FileName())
|
||||
}
|
||||
|
||||
|
@ -665,7 +670,7 @@ func (m *MediaFile) decodeDimensions() error {
|
|||
|
||||
// Width return the width dimension of a MediaFile.
|
||||
func (m *MediaFile) Width() int {
|
||||
if !m.IsPhoto() {
|
||||
if !m.IsMedia() {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -680,7 +685,7 @@ func (m *MediaFile) Width() int {
|
|||
|
||||
// Height returns the height dimension of a MediaFile.
|
||||
func (m *MediaFile) Height() int {
|
||||
if !m.IsPhoto() {
|
||||
if !m.IsMedia() {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ func (m *MediaFile) MetaData() (result meta.Data) {
|
|||
|
||||
if jsonFile := fs.TypeJson.FindSub(m.FileName(), fs.HiddenPath, false); jsonFile == "" {
|
||||
log.Debugf("mediafile: no json sidecar file found for %s", txt.Quote(filepath.Base(m.FileName())))
|
||||
} else if jsonErr := m.metaData.JSON(jsonFile); jsonErr != nil {
|
||||
log.Warn(jsonErr)
|
||||
} else if jsonErr := m.metaData.JSON(jsonFile, m.BaseName()); jsonErr != nil {
|
||||
log.Debug(jsonErr)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
|
|
|
@ -138,10 +138,23 @@ func Geo(f form.GeoSearch) (results GeoResults, err error) {
|
|||
s = s.Where("photos.photo_name LIKE ?", strings.ReplaceAll(f.Name, "*", "%"))
|
||||
}
|
||||
|
||||
if f.Review {
|
||||
s = s.Where("photos.photo_quality < 3")
|
||||
} else if f.Quality != 0 {
|
||||
s = s.Where("photos.photo_quality >= ?", f.Quality)
|
||||
// Filter by status.
|
||||
if f.Archived {
|
||||
s = s.Where("photos.deleted_at IS NOT NULL")
|
||||
} else {
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
|
||||
if f.Private {
|
||||
s = s.Where("photos.photo_private = 1")
|
||||
} else if f.Public {
|
||||
s = s.Where("photos.photo_private = 0")
|
||||
}
|
||||
|
||||
if f.Review {
|
||||
s = s.Where("photos.photo_quality < 3")
|
||||
} else if f.Quality != 0 && f.Private == false {
|
||||
s = s.Where("photos.photo_quality >= ?", f.Quality)
|
||||
}
|
||||
}
|
||||
|
||||
if f.Favorite {
|
||||
|
|
|
@ -126,7 +126,10 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
|
|||
}
|
||||
|
||||
// Filter by status.
|
||||
if f.Archived {
|
||||
if f.Hidden {
|
||||
s = s.Where("photos.photo_quality = -1")
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
} else if f.Archived {
|
||||
s = s.Where("photos.deleted_at IS NOT NULL")
|
||||
} else {
|
||||
s = s.Where("photos.deleted_at IS NULL")
|
||||
|
|
Loading…
Reference in a new issue