diff --git a/lib/Controller/DaysController.php b/lib/Controller/DaysController.php index 4937d6a1..cedb4910 100644 --- a/lib/Controller/DaysController.php +++ b/lib/Controller/DaysController.php @@ -211,7 +211,7 @@ class DaysController extends ApiBase // Filter only for one face on Recognize if (($recognize = $this->request->getParam('recognize')) && $this->recognizeIsEnabled()) { - $transforms[] = [$this->timelineQuery, 'transformPeopleRecognitionFilter', $recognize]; + $transforms[] = [$this->timelineQuery, 'transformPeopleRecognitionFilter', $recognize, $aggregateOnly]; $faceRect = $this->request->getParam('facerect'); if ($faceRect && !$aggregateOnly) { diff --git a/lib/Db/TimelineQueryPeopleRecognize.php b/lib/Db/TimelineQueryPeopleRecognize.php index f54e0a83..7b4ae98a 100644 --- a/lib/Db/TimelineQueryPeopleRecognize.php +++ b/lib/Db/TimelineQueryPeopleRecognize.php @@ -11,7 +11,7 @@ trait TimelineQueryPeopleRecognize { protected IDBConnection $connection; - public function transformPeopleRecognitionFilter(IQueryBuilder &$query, string $userId, string $faceStr) + public function transformPeopleRecognitionFilter(IQueryBuilder &$query, string $userId, string $faceStr, bool $isAggregate) { // Get name and uid of face user $faceNames = explode('/', $faceStr); @@ -21,17 +21,28 @@ trait TimelineQueryPeopleRecognize $faceUid = $faceNames[0]; $faceName = $faceNames[1]; + if (!$isAggregate) { + // Multiple detections for the same image + $query->addSelect('rfd.id AS faceid'); + } + // Join with cluster - $nameField = is_numeric($faceName) ? 'rfc.id' : 'rfc.title'; - $query->innerJoin('m', 'recognize_face_clusters', 'rfc', $query->expr()->andX( - $query->expr()->eq('rfc.user_id', $query->createNamedParameter($faceUid)), - $query->expr()->eq($nameField, $query->createNamedParameter($faceName)), - )); + $clusterQuery = null; + if ($faceName !== 'NULL') { + $nameField = is_numeric($faceName) ? 'rfc.id' : 'rfc.title'; + $query->innerJoin('m', 'recognize_face_clusters', 'rfc', $query->expr()->andX( + $query->expr()->eq('rfc.user_id', $query->createNamedParameter($faceUid)), + $query->expr()->eq($nameField, $query->createNamedParameter($faceName)), + )); + $clusterQuery = $query->expr()->eq('rfd.cluster_id', 'rfc.id'); + } else { + $clusterQuery = $query->expr()->isNull('rfd.cluster_id'); + } // Join with detections $query->innerJoin('m', 'recognize_face_detections', 'rfd', $query->expr()->andX( $query->expr()->eq('rfd.file_id', 'm.fileid'), - $query->expr()->eq('rfd.cluster_id', 'rfc.id'), + $clusterQuery, )); } diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index f7565978..58855b7a 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -1189,13 +1189,14 @@ export default defineComponent({ // Duplicate detection. // These may be valid, e.g. in face rects. All we need to have // is a unique Vue key for the v-for loop. - if (seen.has(photo.fileid)) { - const val = seen.get(photo.fileid); - photo.key = `${photo.fileid}-${val}`; - seen.set(photo.fileid, val + 1); + const key = photo.faceid || photo.fileid; + if (seen.has(key)) { + const val = seen.get(key); + photo.key = `${key}-${val}`; + seen.set(key, val + 1); } else { - photo.key = `${photo.fileid}`; - seen.set(photo.fileid, 1); + photo.key = `${key}`; + seen.set(key, 1); } // Add photo to row diff --git a/src/types.ts b/src/types.ts index b425986a..cb5d033a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -92,6 +92,8 @@ export type IPhoto = { }; }; + /** Face detection ID */ + faceid?: number; /** Face dimensions */ facerect?: IFaceRect;