parent
c56911709c
commit
af226344b8
|
@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
- **Feature**: Allow adding photos to multiple albums together ([#752](https://github.com/pulsejet/memories/pull/752))
|
||||
- **Feature**: Show albums of photo in metadata ([#752](https://github.com/pulsejet/memories/pull/752))
|
||||
- **Feature**: Show faces in photo in sidebar metadata
|
||||
|
||||
## [v5.2.1] - 2023-07-03
|
||||
|
||||
|
|
|
@ -131,6 +131,10 @@ class RecognizeBackend extends Backend
|
|||
|
||||
public function getClusters(): array
|
||||
{
|
||||
/** @var \OCP\IRequest $request */
|
||||
$request = \OC::$server->get(\OCP\IRequest::class);
|
||||
$fileid = (int) $request->getParam('fileid', -1);
|
||||
|
||||
$query = $this->tq->getBuilder();
|
||||
|
||||
// SELECT all face clusters
|
||||
|
@ -149,6 +153,20 @@ class RecognizeBackend extends Backend
|
|||
// WHERE this cluster belongs to the user
|
||||
$query->where($query->expr()->eq('rfc.user_id', $query->createNamedParameter(Util::getUID())));
|
||||
|
||||
// WHERE these clusters contain fileid if specified
|
||||
if ($fileid > 0) {
|
||||
$fSq = $this->tq->getBuilder()
|
||||
->select('rfd.file_id')
|
||||
->from('recognize_face_detections', 'rfd')
|
||||
->where($query->expr()->andX(
|
||||
$query->expr()->eq('rfd.cluster_id', 'rfc.id'),
|
||||
$query->expr()->eq('rfd.file_id', $query->createNamedParameter($fileid, \PDO::PARAM_INT)),
|
||||
))
|
||||
->getSQL()
|
||||
;
|
||||
$query->andWhere($query->createFunction("EXISTS ({$fSq})"));
|
||||
}
|
||||
|
||||
// GROUP by ID of face cluster
|
||||
$query->groupBy('rfc.id');
|
||||
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<template>
|
||||
<div class="outer" v-if="fileid">
|
||||
<div v-if="people.length" class="people">
|
||||
<div class="section-title">{{ t('memories', 'People') }}</div>
|
||||
<div class="container" v-for="face of people" :key="face.cluster_id">
|
||||
<Cluster :data="face" :counters="false"> </Cluster>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="albums.length">
|
||||
<div class="section-title">{{ t('memories', 'Albums') }}</div>
|
||||
<AlbumsList class="albums" :albums="albums" />
|
||||
|
@ -64,6 +71,7 @@ import { DateTime } from 'luxon';
|
|||
|
||||
import UserConfig from '../mixins/UserConfig';
|
||||
import AlbumsList from './modal/AlbumsList.vue';
|
||||
import Cluster from './frame/Cluster.vue';
|
||||
|
||||
import EditIcon from 'vue-material-design-icons/Pencil.vue';
|
||||
import CalendarIcon from 'vue-material-design-icons/Calendar.vue';
|
||||
|
@ -77,7 +85,7 @@ import * as utils from '../services/Utils';
|
|||
import * as dav from '../services/DavRequests';
|
||||
import { API } from '../services/API';
|
||||
|
||||
import type { IAlbum, IImageInfo, IPhoto } from '../types';
|
||||
import type { IAlbum, IFace, IImageInfo, IPhoto } from '../types';
|
||||
|
||||
interface TopField {
|
||||
title: string;
|
||||
|
@ -92,18 +100,24 @@ export default defineComponent({
|
|||
components: {
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
EditIcon,
|
||||
AlbumsList,
|
||||
Cluster,
|
||||
EditIcon,
|
||||
},
|
||||
|
||||
mixins: [UserConfig],
|
||||
|
||||
data: () => ({
|
||||
// Basic info and metadata
|
||||
fileid: null as number | null,
|
||||
filename: '',
|
||||
exif: {} as { [prop: string]: any },
|
||||
baseInfo: {} as IImageInfo,
|
||||
|
||||
// Cluster lists
|
||||
albums: [] as IAlbum[],
|
||||
people: [] as IFace[],
|
||||
|
||||
state: 0,
|
||||
}),
|
||||
|
||||
|
@ -342,6 +356,7 @@ export default defineComponent({
|
|||
this.fileid = null;
|
||||
this.exif = {};
|
||||
this.albums = [];
|
||||
this.people = [];
|
||||
|
||||
const state = this.state;
|
||||
const url = API.Q(utils.getImageInfoUrl(photo), { tags: 1 });
|
||||
|
@ -355,24 +370,26 @@ export default defineComponent({
|
|||
|
||||
// trigger other refreshes
|
||||
this.refreshAlbums();
|
||||
this.refreshPeople();
|
||||
|
||||
return this.baseInfo;
|
||||
},
|
||||
|
||||
async refreshAlbums(): Promise<IAlbum[]> {
|
||||
if (!this.config.albums_enabled) return [];
|
||||
async refreshAlbums(): Promise<void> {
|
||||
if (!this.config.albums_enabled) return;
|
||||
this.albums = await this.guardState(dav.getAlbums(1, this.fileid!));
|
||||
},
|
||||
|
||||
async refreshPeople(): Promise<void> {
|
||||
if (!this.config.recognize_enabled) return;
|
||||
this.people = await this.guardState(dav.getFaceList('recognize', this.fileid!));
|
||||
},
|
||||
|
||||
async guardState<T>(promise: Promise<T>): Promise<T> {
|
||||
const state = this.state;
|
||||
|
||||
let list: IAlbum[] = [];
|
||||
try {
|
||||
list = await dav.getAlbums(1, this.fileid!);
|
||||
} catch (e) {
|
||||
console.error('metadata: failed to load albums', e);
|
||||
}
|
||||
|
||||
if (state !== this.state) return list;
|
||||
return (this.albums = list);
|
||||
const res = await promise;
|
||||
if (state === this.state) return res;
|
||||
throw new Error('state changed');
|
||||
},
|
||||
|
||||
handleFileUpdated({ fileid }: { fileid: number }) {
|
||||
|
@ -397,6 +414,19 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.people {
|
||||
> .section-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
> .container {
|
||||
width: calc(100% / 3);
|
||||
aspect-ratio: 1;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.top-field {
|
||||
margin: 10px;
|
||||
margin-bottom: 25px;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<router-link draggable="false" class="cluster fill-block" :class="{ error }" :to="target" @click.native="click">
|
||||
<div class="count-bubble" v-if="data.count">
|
||||
<div class="count-bubble" v-if="counters && data.count">
|
||||
<NcCounterBubble> {{ data.count }} </NcCounterBubble>
|
||||
</div>
|
||||
<div class="name">
|
||||
|
@ -55,6 +55,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
counters: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
|
|
@ -106,8 +106,8 @@ export class API {
|
|||
return gen(`${BASE}/tags/set/{fileid}`, { fileid });
|
||||
}
|
||||
|
||||
static FACE_LIST(app: 'recognize' | 'facerecognition') {
|
||||
return gen(`${BASE}/clusters/${app}`);
|
||||
static FACE_LIST(app: 'recognize' | 'facerecognition', fileid?: number) {
|
||||
return API.Q(gen(`${BASE}/clusters/${app}`), { fileid });
|
||||
}
|
||||
|
||||
static CLUSTER_PREVIEW(backend: ClusterTypes, name: string | number) {
|
||||
|
|
|
@ -8,8 +8,13 @@ import client from '../DavClient';
|
|||
import * as utils from '../Utils';
|
||||
import * as base from './base';
|
||||
|
||||
export async function getFaceList(app: 'recognize' | 'facerecognition') {
|
||||
return (await axios.get<IFace[]>(API.FACE_LIST(app))).data;
|
||||
/**
|
||||
* Get list of faces
|
||||
* @param app Backend app to use
|
||||
* @param fileid File to filter by (optional)
|
||||
*/
|
||||
export async function getFaceList(app: 'recognize' | 'facerecognition', fileid?: number) {
|
||||
return (await axios.get<IFace[]>(API.FACE_LIST(app, fileid))).data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue