Integration with facerecognition
parent
3f92e5ec6a
commit
aeffe628f2
|
@ -23,7 +23,8 @@ return [
|
|||
// Routes with params
|
||||
w(['name' => 'Page#folder', 'url' => '/folders/{path}', 'verb' => 'GET'], 'path'),
|
||||
w(['name' => 'Page#albums', 'url' => '/albums/{id}', 'verb' => 'GET'], 'id'),
|
||||
w(['name' => 'Page#people', 'url' => '/people/{name}', 'verb' => 'GET'], 'name'),
|
||||
w(['name' => 'Page#recognize', 'url' => '/recognize/{name}', 'verb' => 'GET'], 'name'),
|
||||
w(['name' => 'Page#facerecognition', 'url' => '/facerecognition/{name}', 'verb' => 'GET'], 'name'),
|
||||
w(['name' => 'Page#tags', 'url' => '/tags/{name}', 'verb' => 'GET'], 'name'),
|
||||
|
||||
// Public folder share
|
||||
|
@ -50,8 +51,10 @@ return [
|
|||
['name' => 'Tags#tags', 'url' => '/api/tags', 'verb' => 'GET'],
|
||||
['name' => 'Tags#preview', 'url' => '/api/tags/preview/{tag}', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'Faces#faces', 'url' => '/api/faces', 'verb' => 'GET'],
|
||||
['name' => 'Faces#preview', 'url' => '/api/faces/preview/{id}', 'verb' => 'GET'],
|
||||
['name' => 'People#recognizePeople', 'url' => '/api/recognize/people', 'verb' => 'GET'],
|
||||
['name' => 'People#recognizePeoplePreview', 'url' => '/api/recognize/people/preview/{id}', 'verb' => 'GET'],
|
||||
['name' => 'People#facerecognitionPeople', 'url' => '/api/facerecognition/people', 'verb' => 'GET'],
|
||||
['name' => 'People#facerecognitionPeoplePreview', 'url' => '/api/facerecognition/people/preview/{id}', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'],
|
||||
|
||||
|
|
|
@ -291,6 +291,20 @@ class ApiBase extends Controller
|
|||
return \OCA\Memories\Util::recognizeIsEnabled($this->appManager);
|
||||
}
|
||||
|
||||
// Check if facerecognition is installed and enabled for this user.
|
||||
protected function facerecognitionIsInstalled(): bool
|
||||
{
|
||||
return \OCA\Memories\Util::facerecognitionIsInstalled($this->appManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if facerecognition is enabled for this user.
|
||||
*/
|
||||
protected function facerecognitionIsEnabled(): bool
|
||||
{
|
||||
return \OCA\Memories\Util::facerecognitionIsEnabled($this->config, $this->getUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get one file or null from a fiolder.
|
||||
*/
|
||||
|
|
|
@ -198,16 +198,24 @@ class DaysController extends ApiBase
|
|||
$transforms[] = [$this->timelineQuery, 'transformVideoFilter'];
|
||||
}
|
||||
|
||||
// Filter only for one face
|
||||
if ($this->recognizeIsEnabled()) {
|
||||
$face = $this->request->getParam('face');
|
||||
if ($face) {
|
||||
$transforms[] = [$this->timelineQuery, 'transformFaceFilter', $face];
|
||||
}
|
||||
// Filter only for one face on Recognize
|
||||
if (($recognize = $this->request->getParam('recognize')) && $this->recognizeIsEnabled()) {
|
||||
$transforms[] = [$this->timelineQuery, 'transformPeopleRecognitionFilter', $recognize];
|
||||
|
||||
$faceRect = $this->request->getParam('facerect');
|
||||
if ($faceRect && !$aggregateOnly) {
|
||||
$transforms[] = [$this->timelineQuery, 'transformFaceRect', $face];
|
||||
$transforms[] = [$this->timelineQuery, 'transformPeopleRecognizeRect', $recognize];
|
||||
}
|
||||
}
|
||||
|
||||
// Filter only for one face on Face Recognition
|
||||
if (($face = $this->request->getParam('facerecognition')) && $this->facerecognitionIsEnabled()) {
|
||||
$currentModel = (int) $this->config->getAppValue('facerecognition', 'model', -1);
|
||||
$transforms[] = [$this->timelineQuery, 'transformPeopleFaceRecognitionFilter', $currentModel, $face];
|
||||
|
||||
$faceRect = $this->request->getParam('facerect');
|
||||
if ($faceRect && !$aggregateOnly) {
|
||||
$transforms[] = [$this->timelineQuery, 'transformPeopleFaceRecognitionRect', $face];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,8 @@ class PageController extends Controller
|
|||
$this->initialState->provideInitialState('systemtags', true === $this->appManager->isEnabledForUser('systemtags'));
|
||||
$this->initialState->provideInitialState('maps', true === $this->appManager->isEnabledForUser('maps'));
|
||||
$this->initialState->provideInitialState('recognize', \OCA\Memories\Util::recognizeIsEnabled($this->appManager));
|
||||
$this->initialState->provideInitialState('facerecognitionInstalled', \OCA\Memories\Util::facerecognitionIsInstalled($this->appManager));
|
||||
$this->initialState->provideInitialState('facerecognitionEnabled', \OCA\Memories\Util::facerecognitionIsEnabled($this->config, $uid));
|
||||
$this->initialState->provideInitialState('albums', \OCA\Memories\Util::albumsIsEnabled($this->appManager));
|
||||
|
||||
// App version
|
||||
|
@ -181,7 +183,17 @@ class PageController extends Controller
|
|||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function people()
|
||||
public function recognize()
|
||||
{
|
||||
return $this->main();
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function facerecognition()
|
||||
{
|
||||
return $this->main();
|
||||
}
|
||||
|
|
|
@ -29,14 +29,14 @@ use OCP\AppFramework\Http\DataResponse;
|
|||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\Files\FileInfo;
|
||||
|
||||
class FacesController extends ApiBase
|
||||
class PeopleController extends ApiBase
|
||||
{
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* Get list of faces with counts of images
|
||||
*/
|
||||
public function faces(): JSONResponse
|
||||
public function recognizePeople(): JSONResponse
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
|
@ -45,7 +45,7 @@ class FacesController extends ApiBase
|
|||
|
||||
// Check faces enabled for this user
|
||||
if (!$this->recognizeIsEnabled()) {
|
||||
return new JSONResponse(['message' => 'Recognize app not enabled or not v3+'], Http::STATUS_PRECONDITION_FAILED);
|
||||
return new JSONResponse(['message' => 'Recognize app not enabled or not v3+.'], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// If this isn't the timeline folder then things aren't going to work
|
||||
|
@ -55,7 +55,7 @@ class FacesController extends ApiBase
|
|||
}
|
||||
|
||||
// Run actual query
|
||||
$list = $this->timelineQuery->getFaces(
|
||||
$list = $this->timelineQuery->getPeopleRecognize(
|
||||
$root,
|
||||
);
|
||||
|
||||
|
@ -71,7 +71,7 @@ class FacesController extends ApiBase
|
|||
*
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function preview(string $id): Http\Response
|
||||
public function recognizePeoplePreview(int $id): Http\Response
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
|
@ -90,11 +90,115 @@ class FacesController extends ApiBase
|
|||
}
|
||||
|
||||
// Run actual query
|
||||
$detections = $this->timelineQuery->getFacePreviewDetection($root, (int) $id);
|
||||
$detections = $this->timelineQuery->getPeopleRecognizePreview($root, $id);
|
||||
|
||||
if (null === $detections || 0 === \count($detections)) {
|
||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->getPreviewResponse($detections, $user, 1.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* Get list of faces with counts of images
|
||||
*/
|
||||
public function facerecognitionPeople(): JSONResponse
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// Check if face recognition is installed and enabled for this user
|
||||
if (!$this->facerecognitionIsInstalled()) {
|
||||
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// If this isn't the timeline folder then things aren't going to work
|
||||
$root = $this->getRequestRoot();
|
||||
if ($root->isEmpty()) {
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// If the user has recognition disabled, just returns an empty response.
|
||||
if (!$this->facerecognitionIsEnabled()) {
|
||||
return new JSONResponse([]);
|
||||
}
|
||||
|
||||
// Run actual query
|
||||
$currentModel = (int) $this->config->getAppValue('facerecognition', 'model', -1);
|
||||
$list = $this->timelineQuery->getPeopleFaceRecognition(
|
||||
$root,
|
||||
$currentModel,
|
||||
);
|
||||
// Just append unnamed clusters to the end.
|
||||
$list = array_merge($list, $this->timelineQuery->getPeopleFaceRecognition(
|
||||
$root,
|
||||
$currentModel,
|
||||
true
|
||||
));
|
||||
|
||||
return new JSONResponse($list, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* Get face preview image cropped with imagick
|
||||
*
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function facerecognitionPeoplePreview(string $id): Http\Response
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// Check if face recognition is installed and enabled for this user
|
||||
if (!$this->facerecognitionIsInstalled()) {
|
||||
return new DataResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
|
||||
// Get folder to search for
|
||||
$root = $this->getRequestRoot();
|
||||
if ($root->isEmpty()) {
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// If the user has facerecognition disabled, just returns an empty response.
|
||||
if (!$this->facerecognitionIsEnabled()) {
|
||||
return new JSONResponse([]);
|
||||
}
|
||||
|
||||
// Run actual query
|
||||
$currentModel = (int) $this->config->getAppValue('facerecognition', 'model', -1);
|
||||
$detections = $this->timelineQuery->getFaceRecognitionPreview($root, $currentModel, $id);
|
||||
|
||||
if (null === $detections || 0 === \count($detections)) {
|
||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->getPreviewResponse($detections, $user, 1.8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get face preview image cropped with imagick.
|
||||
*
|
||||
* @param array $detections Array of detections to search
|
||||
* @param \OCP\IUser $user User to search for
|
||||
* @param int $padding Padding to add to the face in preview
|
||||
*/
|
||||
private function getPreviewResponse(
|
||||
array $detections,
|
||||
\OCP\IUser $user,
|
||||
float $padding
|
||||
): Http\Response
|
||||
{
|
||||
// Get preview manager
|
||||
$previewManager = \OC::$server->get(\OCP\IPreview::class);
|
||||
|
||||
|
@ -152,7 +256,7 @@ class FacesController extends ApiBase
|
|||
$dh = (float) $detection['height'];
|
||||
$dcx = (float) $detection['x'] + (float) $detection['width'] / 2;
|
||||
$dcy = (float) $detection['y'] + (float) $detection['height'] / 2;
|
||||
$faceDim = max($dw * $iw, $dh * $ih) * 1.5;
|
||||
$faceDim = max($dw * $iw, $dh * $ih) * $padding;
|
||||
$image->cropImage(
|
||||
(int) $faceDim,
|
||||
(int) $faceDim,
|
|
@ -11,10 +11,11 @@ class TimelineQuery
|
|||
{
|
||||
use TimelineQueryAlbums;
|
||||
use TimelineQueryDays;
|
||||
use TimelineQueryFaces;
|
||||
use TimelineQueryFilters;
|
||||
use TimelineQueryFolders;
|
||||
use TimelineQueryLivePhoto;
|
||||
use TimelineQueryPeopleFaceRecognition;
|
||||
use TimelineQueryPeopleRecognize;
|
||||
use TimelineQueryTags;
|
||||
|
||||
protected IDBConnection $connection;
|
||||
|
|
|
@ -299,7 +299,8 @@ trait TimelineQueryDays
|
|||
}
|
||||
|
||||
// All transform processing
|
||||
$this->processFace($row);
|
||||
$this->processPeopleRecognizeDetection($row);
|
||||
$this->processFaceRecognitionDetection($row);
|
||||
|
||||
// We don't need these fields
|
||||
unset($row['datetaken'], $row['rootid']);
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Memories\Db;
|
||||
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
trait TimelineQueryPeopleFaceRecognition
|
||||
{
|
||||
protected IDBConnection $connection;
|
||||
|
||||
public function transformPeopleFaceRecognitionFilter(IQueryBuilder &$query, string $userId, int $currentModel, string $personStr)
|
||||
{
|
||||
// Get title and uid of face user
|
||||
$personNames = explode('/', $personStr);
|
||||
if (2 !== \count($personNames)) {
|
||||
throw new \Exception('Invalid person query');
|
||||
}
|
||||
|
||||
$personUid = $personNames[0];
|
||||
$personName = $personNames[1];
|
||||
|
||||
// Join with images
|
||||
$query->innerJoin('m', 'facerecog_images', 'fri', $query->expr()->andX(
|
||||
$query->expr()->eq('fri.file', 'm.fileid'),
|
||||
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
|
||||
));
|
||||
|
||||
// Join with faces
|
||||
$query->innerJoin(
|
||||
'fri',
|
||||
'facerecog_faces',
|
||||
'frf',
|
||||
$query->expr()->eq('frf.image', 'fri.id')
|
||||
);
|
||||
|
||||
// Join with persons
|
||||
$nameField = is_numeric($personName) ? 'frp.id' : 'frp.name';
|
||||
$query->innerJoin('frf', 'facerecog_persons', 'frp', $query->expr()->andX(
|
||||
$query->expr()->eq('frf.person', 'frp.id'),
|
||||
$query->expr()->eq('frp.user', $query->createNamedParameter($personUid)),
|
||||
$query->expr()->eq($nameField, $query->createNamedParameter($personName)),
|
||||
));
|
||||
}
|
||||
|
||||
public function transformPeopleFaceRecognitionRect(IQueryBuilder &$query, string $userId)
|
||||
{
|
||||
// Include detection params in response
|
||||
$query->addSelect(
|
||||
'frf.x AS face_x',
|
||||
'frf.y AS face_y',
|
||||
'frf.width AS face_width',
|
||||
'frf.height AS face_height',
|
||||
'm.w AS image_width',
|
||||
'm.h AS image_height',
|
||||
);
|
||||
}
|
||||
|
||||
public function getPeopleFaceRecognition(TimelineRoot &$root, int $currentModel, bool $show_clusters = false, bool $show_singles = false, bool $show_hidden = false)
|
||||
{
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
// SELECT all face clusters
|
||||
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
||||
$query->select('frp.id', 'frp.user as user_id', 'frp.name', $count)->from('facerecog_persons', 'frp');
|
||||
|
||||
// WHERE there are faces with this cluster
|
||||
$query->innerJoin('frp', 'facerecog_faces', 'frf', $query->expr()->eq('frp.id', 'frf.person'));
|
||||
|
||||
// WHERE faces are from images.
|
||||
$query->innerJoin('frf', 'facerecog_images', 'fri', $query->expr()->eq('fri.id', 'frf.image'));
|
||||
|
||||
// WHERE these items are memories indexed photos
|
||||
$query->innerJoin('fri', 'memories', 'm', $query->expr()->andX(
|
||||
$query->expr()->eq('fri.file', 'm.fileid'),
|
||||
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
|
||||
));
|
||||
|
||||
// WHERE these photos are in the user's requested folder recursively
|
||||
$query = $this->joinFilecache($query, $root, true, false);
|
||||
|
||||
if ($show_clusters) {
|
||||
// GROUP by ID of face cluster
|
||||
$query->groupBy('frp.id');
|
||||
$query->where($query->expr()->isNull('frp.name'));
|
||||
} else {
|
||||
// GROUP by name of face clusters
|
||||
$query->groupBy('frp.name');
|
||||
$query->where($query->expr()->isNotNull('frp.name'));
|
||||
}
|
||||
|
||||
// By default hides individual faces when they have no name.
|
||||
if ($show_clusters && !$show_singles) {
|
||||
$query->having($query->expr()->gt('count', $query->createNamedParameter(1)));
|
||||
}
|
||||
|
||||
// By default it shows the people who were not hidden
|
||||
if (!$show_hidden) {
|
||||
$query->andWhere($query->expr()->eq('frp.is_visible', $query->createNamedParameter(true)));
|
||||
}
|
||||
|
||||
// ORDER by number of faces in cluster
|
||||
$query->orderBy('count', 'DESC');
|
||||
$query->addOrderBy('name', 'ASC');
|
||||
$query->addOrderBy('frp.id'); // tie-breaker
|
||||
|
||||
// FETCH all faces
|
||||
$cursor = $this->executeQueryWithCTEs($query);
|
||||
$faces = $cursor->fetchAll();
|
||||
|
||||
// Post process
|
||||
foreach ($faces as &$row) {
|
||||
$row['id'] = $row['name'] ?: (int) $row['id'];
|
||||
$row['count'] = (int) $row['count'];
|
||||
}
|
||||
|
||||
return $faces;
|
||||
}
|
||||
|
||||
public function getFaceRecognitionPreview(TimelineRoot &$root, $currentModel, $previewId)
|
||||
{
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
// SELECT face detections
|
||||
$query->select(
|
||||
'fri.file as file_id', // Get actual file
|
||||
'frf.x', // Image cropping
|
||||
'frf.y',
|
||||
'frf.width',
|
||||
'frf.height',
|
||||
'm.w as image_width', // Scoring
|
||||
'm.h as image_height',
|
||||
'frf.confidence',
|
||||
'm.fileid',
|
||||
'm.datetaken', // Just in case, for postgres
|
||||
)->from('facerecog_faces', 'frf');
|
||||
|
||||
// WHERE faces are from images and current model.
|
||||
$query->innerJoin('frf', 'facerecog_images', 'fri', $query->expr()->andX(
|
||||
$query->expr()->eq('fri.id', 'frf.image'),
|
||||
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
|
||||
));
|
||||
|
||||
// WHERE these photos are memories indexed
|
||||
$query->innerJoin('fri', 'memories', 'm', $query->expr()->eq('m.fileid', 'fri.file'));
|
||||
|
||||
$query->innerJoin('frf', 'facerecog_persons', 'frp', $query->expr()->eq('frp.id', 'frf.person'));
|
||||
if (is_numeric($previewId)) {
|
||||
// WHERE faces are from id persons (a cluster).
|
||||
$query->where($query->expr()->eq('frp.id', $query->createNamedParameter($previewId)));
|
||||
} else {
|
||||
// WHERE faces are from name on persons.
|
||||
$query->where($query->expr()->eq('frp.name', $query->createNamedParameter($previewId)));
|
||||
}
|
||||
|
||||
// WHERE these photos are in the user's requested folder recursively
|
||||
$query = $this->joinFilecache($query, $root, true, false);
|
||||
|
||||
// LIMIT results
|
||||
$query->setMaxResults(15);
|
||||
|
||||
// Sort by date taken so we get recent photos
|
||||
$query->orderBy('m.datetaken', 'DESC');
|
||||
$query->addOrderBy('m.fileid', 'DESC'); // tie-breaker
|
||||
|
||||
// FETCH face detections
|
||||
$cursor = $this->executeQueryWithCTEs($query);
|
||||
$previews = $cursor->fetchAll();
|
||||
if (empty($previews)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Score the face detections
|
||||
foreach ($previews as &$p) {
|
||||
// Get actual pixel size of face
|
||||
$iw = min((int) ($p['image_width'] ?: 512), 2048);
|
||||
$ih = min((int) ($p['image_height'] ?: 512), 2048);
|
||||
|
||||
// Get percentage position and size
|
||||
$p['x'] = (float) $p['x'] / $p['image_width'];
|
||||
$p['y'] = (float) $p['y'] / $p['image_height'];
|
||||
$p['width'] = (float) $p['width'] / $p['image_width'];
|
||||
$p['height'] = (float) $p['height'] / $p['image_height'];
|
||||
|
||||
$w = (float) $p['width'];
|
||||
$h = (float) $p['height'];
|
||||
|
||||
// Get center of face
|
||||
$x = (float) $p['x'] + (float) $p['width'] / 2;
|
||||
$y = (float) $p['y'] + (float) $p['height'] / 2;
|
||||
|
||||
// 3D normal distribution - if the face is closer to the center, it's better
|
||||
$positionScore = exp(-($x - 0.5) ** 2 * 4) * exp(-($y - 0.5) ** 2 * 4);
|
||||
|
||||
// Root size distribution - if the image is bigger, it's better,
|
||||
// but it doesn't matter beyond a certain point
|
||||
$imgSizeScore = ($iw * 100) ** (1 / 2) * ($ih * 100) ** (1 / 2);
|
||||
|
||||
// Faces occupying too much of the image don't look particularly good
|
||||
$faceSizeScore = (-$w ** 2 + $w) * (-$h ** 2 + $h);
|
||||
|
||||
// Combine scores
|
||||
$p['score'] = $positionScore * $imgSizeScore * $faceSizeScore * $p['confidence'];
|
||||
}
|
||||
|
||||
// Sort previews by score descending
|
||||
usort($previews, function ($a, $b) {
|
||||
return $b['score'] <=> $a['score'];
|
||||
});
|
||||
|
||||
return $previews;
|
||||
}
|
||||
|
||||
/** Convert face fields to object */
|
||||
private function processFaceRecognitionDetection(&$row, $days = false)
|
||||
{
|
||||
if (!isset($row)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Differentiate Recognize queries from Face Recognition
|
||||
if (!isset($row['face_width']) || !isset($row['image_width'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$days) {
|
||||
$row['facerect'] = [
|
||||
// Get percentage position and size
|
||||
'w' => (float) $row['face_width'] / $row['image_width'],
|
||||
'h' => (float) $row['face_height'] / $row['image_height'],
|
||||
'x' => (float) $row['face_x'] / $row['image_width'],
|
||||
'y' => (float) $row['face_y'] / $row['image_height'],
|
||||
];
|
||||
}
|
||||
|
||||
unset($row['face_x'], $row['face_y'], $row['face_w'], $row['face_h'], $row['image_height'], $row['image_width']);
|
||||
}
|
||||
}
|
|
@ -7,13 +7,13 @@ namespace OCA\Memories\Db;
|
|||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
trait TimelineQueryFaces
|
||||
trait TimelineQueryPeopleRecognize
|
||||
{
|
||||
protected IDBConnection $connection;
|
||||
|
||||
public function transformFaceFilter(IQueryBuilder &$query, string $userId, string $faceStr)
|
||||
public function transformPeopleRecognitionFilter(IQueryBuilder &$query, string $userId, string $faceStr)
|
||||
{
|
||||
// Get title and uid of face user
|
||||
// Get name and uid of face user
|
||||
$faceNames = explode('/', $faceStr);
|
||||
if (2 !== \count($faceNames)) {
|
||||
throw new \Exception('Invalid face query');
|
||||
|
@ -35,7 +35,7 @@ trait TimelineQueryFaces
|
|||
));
|
||||
}
|
||||
|
||||
public function transformFaceRect(IQueryBuilder &$query, string $userId)
|
||||
public function transformPeopleRecognizeRect(IQueryBuilder &$query, string $userId)
|
||||
{
|
||||
// Include detection params in response
|
||||
$query->addSelect(
|
||||
|
@ -46,7 +46,7 @@ trait TimelineQueryFaces
|
|||
);
|
||||
}
|
||||
|
||||
public function getFaces(TimelineRoot &$root)
|
||||
public function getPeopleRecognize(TimelineRoot &$root)
|
||||
{
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
|
@ -86,7 +86,7 @@ trait TimelineQueryFaces
|
|||
return $faces;
|
||||
}
|
||||
|
||||
public function getFacePreviewDetection(TimelineRoot &$root, int $id)
|
||||
public function getPeopleRecognizePreview(TimelineRoot &$root, int $id)
|
||||
{
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
|
||||
|
@ -159,8 +159,9 @@ trait TimelineQueryFaces
|
|||
}
|
||||
|
||||
/** Convert face fields to object */
|
||||
private function processFace(&$row, $days = false)
|
||||
private function processPeopleRecognizeDetection(&$row, $days = false)
|
||||
{
|
||||
// Differentiate Recognize queries from Face Recognition
|
||||
if (!isset($row) || !isset($row['face_w'])) {
|
||||
return;
|
||||
}
|
41
lib/Util.php
41
lib/Util.php
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories;
|
||||
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\IConfig;
|
||||
|
||||
class Util
|
||||
{
|
||||
public static $TAG_DAYID_START = -(1 << 30); // the world surely didn't exist
|
||||
|
@ -46,10 +49,8 @@ class Util
|
|||
|
||||
/**
|
||||
* Check if albums are enabled for this user.
|
||||
*
|
||||
* @param mixed $appManager
|
||||
*/
|
||||
public static function albumsIsEnabled(&$appManager): bool
|
||||
public static function albumsIsEnabled(IAppManager &$appManager): bool
|
||||
{
|
||||
if (!$appManager->isEnabledForUser('photos')) {
|
||||
return false;
|
||||
|
@ -72,10 +73,8 @@ class Util
|
|||
|
||||
/**
|
||||
* Check if recognize is enabled for this user.
|
||||
*
|
||||
* @param mixed $appManager
|
||||
*/
|
||||
public static function recognizeIsEnabled(&$appManager): bool
|
||||
public static function recognizeIsEnabled(IAppManager &$appManager): bool
|
||||
{
|
||||
if (!$appManager->isEnabledForUser('recognize')) {
|
||||
return false;
|
||||
|
@ -87,11 +86,33 @@ class Util
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if link sharing is allowed.
|
||||
*
|
||||
* @param mixed $config
|
||||
* Check if Face Recognition is enabled by the user.
|
||||
*/
|
||||
public static function isLinkSharingEnabled(&$config): bool
|
||||
public static function facerecognitionIsEnabled(IConfig &$config, string $userId): bool
|
||||
{
|
||||
$e = $config->getUserValue($userId, 'facerecognition', 'enabled', 'false');
|
||||
|
||||
return 'true' === $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Face Recognition is installed and enabled for this user.
|
||||
*/
|
||||
public static function facerecognitionIsInstalled(IAppManager &$appManager): bool
|
||||
{
|
||||
if (!$appManager->isEnabledForUser('facerecognition')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$v = $appManager->getAppInfo('facerecognition')['version'];
|
||||
|
||||
return version_compare($v, '0.9.10-beta.2', '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if link sharing is allowed.
|
||||
*/
|
||||
public static function isLinkSharingEnabled(IConfig &$config): bool
|
||||
{
|
||||
// Check if the shareAPI is enabled
|
||||
if ('yes' !== $config->getAppValue('core', 'shareapi_enabled', 'yes')) {
|
||||
|
|
48
src/App.vue
48
src/App.vue
|
@ -99,7 +99,7 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
private metadataComponent!: Metadata;
|
||||
|
||||
private readonly navItemsAll = [
|
||||
private readonly navItemsAll = (self: typeof this) => [
|
||||
{
|
||||
name: "timeline",
|
||||
icon: ImageMultiple,
|
||||
|
@ -124,13 +124,19 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
name: "albums",
|
||||
icon: AlbumIcon,
|
||||
title: t("memories", "Albums"),
|
||||
if: (self: any) => self.showAlbums,
|
||||
if: self.showAlbums,
|
||||
},
|
||||
{
|
||||
name: "people",
|
||||
name: "recognize",
|
||||
icon: PeopleIcon,
|
||||
title: t("memories", "People"),
|
||||
if: (self: any) => self.showPeople,
|
||||
title: self.recognize,
|
||||
if: self.recognize,
|
||||
},
|
||||
{
|
||||
name: "facerecognition",
|
||||
icon: PeopleIcon,
|
||||
title: self.facerecognition,
|
||||
if: self.facerecognition,
|
||||
},
|
||||
{
|
||||
name: "archive",
|
||||
|
@ -146,13 +152,13 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
name: "tags",
|
||||
icon: TagsIcon,
|
||||
title: t("memories", "Tags"),
|
||||
if: (self: any) => self.config_tagsEnabled,
|
||||
if: self.config_tagsEnabled,
|
||||
},
|
||||
{
|
||||
name: "maps",
|
||||
icon: MapIcon,
|
||||
title: t("memories", "Maps"),
|
||||
if: (self: any) => self.config_mapsEnabled,
|
||||
if: self.config_mapsEnabled,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -163,8 +169,28 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
return Number(version[0]);
|
||||
}
|
||||
|
||||
get showPeople() {
|
||||
return this.config_recognizeEnabled || getCurrentUser()?.isAdmin;
|
||||
get recognize() {
|
||||
if (!this.config_recognizeEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.config_facerecognitionInstalled) {
|
||||
return t("memories", "People (Recognize)");
|
||||
}
|
||||
|
||||
return t("memories", "People");
|
||||
}
|
||||
|
||||
get facerecognition() {
|
||||
if (!this.config_facerecognitionInstalled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.config_recognizeEnabled) {
|
||||
return t("memories", "People (Face Recognition)");
|
||||
}
|
||||
|
||||
return t("memories", "People");
|
||||
}
|
||||
|
||||
get isFirstStart() {
|
||||
|
@ -192,8 +218,8 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
this.doRouteChecks();
|
||||
|
||||
// Populate navigation
|
||||
this.navItems = this.navItemsAll.filter(
|
||||
(item) => !item.if || item.if(this)
|
||||
this.navItems = this.navItemsAll(this).filter(
|
||||
(item) => typeof item.if === "undefined" || Boolean(item.if)
|
||||
);
|
||||
|
||||
// Store CSS variables modified
|
||||
|
|
|
@ -209,13 +209,13 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
name: t("memories", "Move to another person"),
|
||||
icon: MoveIcon,
|
||||
callback: this.moveSelectionToPerson.bind(this),
|
||||
if: () => this.$route.name === "people",
|
||||
if: () => this.$route.name === "recognize",
|
||||
},
|
||||
{
|
||||
name: t("memories", "Remove from person"),
|
||||
icon: CloseIcon,
|
||||
callback: this.removeSelectionFromPerson.bind(this),
|
||||
if: () => this.$route.name === "people",
|
||||
if: () => this.$route.name === "recognize",
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -846,7 +846,7 @@ export default class SelectionManager extends Mixins(GlobalMixin, UserConfig) {
|
|||
private async removeSelectionFromPerson(selection: Selection) {
|
||||
// Make sure route is valid
|
||||
const { user, name } = this.$route.params;
|
||||
if (this.$route.name !== "people" || !user || !name) {
|
||||
if (this.$route.name !== "recognize" || !user || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
<!-- No content found and nothing is loading -->
|
||||
<NcEmptyContent
|
||||
title="Nothing to show here"
|
||||
:description="emptyViewDescription"
|
||||
v-if="loading === 0 && list.length === 0"
|
||||
>
|
||||
<template #icon>
|
||||
<PeopleIcon v-if="$route.name === 'people'" />
|
||||
<ArchiveIcon v-else-if="$route.name === 'archive'" />
|
||||
<PeopleIcon v-if="routeIsPeople" />
|
||||
<ArchiveIcon v-else-if="routeIsArchive" />
|
||||
<ImageMultipleIcon v-else />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
|
@ -43,7 +44,7 @@
|
|||
</div>
|
||||
|
||||
<OnThisDay
|
||||
v-if="$route.name === 'timeline'"
|
||||
v-if="routeIsBase"
|
||||
:key="config_timelinePath"
|
||||
:viewer="$refs.viewer"
|
||||
@load="scrollerManager.adjust()"
|
||||
|
@ -308,6 +309,18 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
window.removeEventListener("resize", this.handleResizeWithDelay);
|
||||
}
|
||||
|
||||
get routeIsBase() {
|
||||
return this.$route.name === "timeline";
|
||||
}
|
||||
|
||||
get routeIsPeople() {
|
||||
return ["recognize", "facerecognition"].includes(this.$route.name);
|
||||
}
|
||||
|
||||
get routeIsArchive() {
|
||||
return this.$route.name === "archive";
|
||||
}
|
||||
|
||||
updateLoading(delta: number) {
|
||||
this.loading += delta;
|
||||
}
|
||||
|
@ -574,12 +587,12 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
// People
|
||||
if (
|
||||
this.$route.name === "people" &&
|
||||
this.routeIsPeople &&
|
||||
this.$route.params.user &&
|
||||
this.$route.params.name
|
||||
) {
|
||||
query.set(
|
||||
"face",
|
||||
this.$route.name, // "recognize" or "facerecognition"
|
||||
`${this.$route.params.user}/${this.$route.params.name}`
|
||||
);
|
||||
|
||||
|
@ -618,7 +631,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
return this.t("memories", "Your Timeline");
|
||||
case "favorites":
|
||||
return this.t("memories", "Favorites");
|
||||
case "people":
|
||||
case "recognize":
|
||||
case "facerecognition":
|
||||
return this.t("memories", "People");
|
||||
case "videos":
|
||||
return this.t("memories", "Videos");
|
||||
|
@ -663,6 +677,33 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
return head.name;
|
||||
}
|
||||
|
||||
/* Get a friendly description of empty view */
|
||||
get emptyViewDescription() {
|
||||
switch (this.$route.name) {
|
||||
case "facerecognition":
|
||||
if (this.config_facerecognitionEnabled)
|
||||
return this.t(
|
||||
"memories",
|
||||
"You will find your friends soon. Please, be patient."
|
||||
);
|
||||
else
|
||||
return this.t(
|
||||
"memories",
|
||||
"Face Recognition is disabled. Enable in settings to find your friends."
|
||||
);
|
||||
case "timeline":
|
||||
case "favorites":
|
||||
case "recognize":
|
||||
case "videos":
|
||||
case "albums":
|
||||
case "archive":
|
||||
case "thisday":
|
||||
case "tags":
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch timeline main call */
|
||||
async fetchDays(noCache = false) {
|
||||
const url = API.Q(API.DAYS(), this.getQuery());
|
||||
|
@ -683,8 +724,8 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
data = await dav.getOnThisDayData();
|
||||
} else if (this.$route.name === "tags" && !this.$route.params.name) {
|
||||
data = await dav.getTagsData();
|
||||
} else if (this.$route.name === "people" && !this.$route.params.name) {
|
||||
data = await dav.getPeopleData();
|
||||
} else if (this.routeIsPeople && !this.$route.params.name) {
|
||||
data = await dav.getPeopleData(this.$route.name as any);
|
||||
} else if (this.$route.name === "albums" && !this.$route.params.name) {
|
||||
data = await dav.getAlbumsData("3");
|
||||
} else {
|
||||
|
|
|
@ -58,7 +58,7 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
|
||||
get previewUrl() {
|
||||
if (this.face) {
|
||||
return API.FACE_PREVIEW(this.face.fileid);
|
||||
return API.FACE_PREVIEW(this.faceApp, this.face.fileid);
|
||||
}
|
||||
|
||||
if (this.album) {
|
||||
|
@ -78,7 +78,16 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
}
|
||||
|
||||
get face() {
|
||||
return this.data.flag & constants.c.FLAG_IS_FACE ? this.data : null;
|
||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNIZE ||
|
||||
this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||
? this.data
|
||||
: null;
|
||||
}
|
||||
|
||||
get faceApp() {
|
||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||
? "facerecognition"
|
||||
: "recognize";
|
||||
}
|
||||
|
||||
get album() {
|
||||
|
@ -94,7 +103,7 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
if (this.face) {
|
||||
const name = this.face.name || this.face.fileid.toString();
|
||||
const user = this.face.user_id;
|
||||
return { name: "people", params: { name, user } };
|
||||
return { name: this.faceApp, params: { name, user } };
|
||||
}
|
||||
|
||||
if (this.album) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import { getCurrentUser } from "@nextcloud/auth";
|
|||
import Modal from "./Modal.vue";
|
||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
||||
import client from "../../services/DavClient";
|
||||
import * as dav from "../../services/DavRequests";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -74,8 +75,12 @@ export default class FaceDeleteModal extends Mixins(GlobalMixin) {
|
|||
|
||||
public async save() {
|
||||
try {
|
||||
await client.deleteFile(`/recognize/${this.user}/faces/${this.name}`);
|
||||
this.$router.push({ name: "people" });
|
||||
if (this.$route.name === "recognize") {
|
||||
await client.deleteFile(`/recognize/${this.user}/faces/${this.name}`);
|
||||
} else {
|
||||
await dav.setVisibilityPeopleFaceRecognition(this.name, false);
|
||||
}
|
||||
this.$router.push({ name: this.$route.name });
|
||||
this.close();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
|
@ -34,6 +34,7 @@ import { getCurrentUser } from "@nextcloud/auth";
|
|||
import Modal from "./Modal.vue";
|
||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
||||
import client from "../../services/DavClient";
|
||||
import * as dav from "../../services/DavRequests";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -83,12 +84,16 @@ export default class FaceEditModal extends Mixins(GlobalMixin) {
|
|||
|
||||
public async save() {
|
||||
try {
|
||||
await client.moveFile(
|
||||
`/recognize/${this.user}/faces/${this.oldName}`,
|
||||
`/recognize/${this.user}/faces/${this.name}`
|
||||
);
|
||||
if (this.$route.name === "recognize") {
|
||||
await client.moveFile(
|
||||
`/recognize/${this.user}/faces/${this.oldName}`,
|
||||
`/recognize/${this.user}/faces/${this.name}`
|
||||
);
|
||||
} else {
|
||||
await dav.renamePeopleFaceRecognition(this.oldName, this.name);
|
||||
}
|
||||
this.$router.push({
|
||||
name: "people",
|
||||
name: this.$route.name,
|
||||
params: { user: this.user, name: this.name },
|
||||
});
|
||||
this.close();
|
||||
|
|
|
@ -44,10 +44,18 @@ export default class FaceMergeModal extends Mixins(GlobalMixin) {
|
|||
this.name = this.$route.params.name || "";
|
||||
this.detail = null;
|
||||
|
||||
const data = await dav.getPeopleData();
|
||||
let data = [];
|
||||
let flags = this.c.FLAG_IS_TAG;
|
||||
if (this.$route.name === "recognize") {
|
||||
data = await dav.getPeopleData("recognize");
|
||||
flags |= this.c.FLAG_IS_FACE_RECOGNIZE;
|
||||
} else {
|
||||
data = await dav.getPeopleData("facerecognition");
|
||||
flags |= this.c.FLAG_IS_FACE_RECOGNITION;
|
||||
}
|
||||
let detail = data[0].detail;
|
||||
detail.forEach((photo: IPhoto) => {
|
||||
photo.flag = this.c.FLAG_IS_FACE | this.c.FLAG_IS_TAG;
|
||||
photo.flag = flags;
|
||||
});
|
||||
detail = detail.filter((photo: ITag) => {
|
||||
const pname = photo.name || photo.fileid.toString();
|
||||
|
|
|
@ -135,7 +135,7 @@ export default class FaceMergeModal extends Mixins(GlobalMixin) {
|
|||
// Go to new face
|
||||
if (failures === 0) {
|
||||
this.$router.push({
|
||||
name: "people",
|
||||
name: "recognize",
|
||||
params: { user: face.user_id, name: newName },
|
||||
});
|
||||
this.close();
|
||||
|
|
|
@ -99,7 +99,7 @@ export default class FaceTopMatter extends Mixins(GlobalMixin, UserConfig) {
|
|||
}
|
||||
|
||||
back() {
|
||||
this.$router.push({ name: "people" });
|
||||
this.$router.push({ name: this.$route.name });
|
||||
}
|
||||
|
||||
changeShowFaceRect() {
|
||||
|
|
|
@ -47,7 +47,8 @@ export default class TopMatter extends Mixins(GlobalMixin) {
|
|||
return this.$route.params.name
|
||||
? TopMatterType.TAG
|
||||
: TopMatterType.NONE;
|
||||
case "people":
|
||||
case "recognize":
|
||||
case "facerecognition":
|
||||
return this.$route.params.name
|
||||
? TopMatterType.FACE
|
||||
: TopMatterType.NONE;
|
||||
|
|
|
@ -26,6 +26,12 @@ export default class UserConfig extends Vue {
|
|||
config_recognizeEnabled = Boolean(
|
||||
loadState("memories", "recognize", <string>"")
|
||||
);
|
||||
config_facerecognitionInstalled = Boolean(
|
||||
loadState("memories", "facerecognitionInstalled", <string>"")
|
||||
);
|
||||
config_facerecognitionEnabled = Boolean(
|
||||
loadState("memories", "facerecognitionEnabled", <string>"")
|
||||
);
|
||||
config_mapsEnabled = Boolean(loadState("memories", "maps", <string>""));
|
||||
config_albumsEnabled = Boolean(loadState("memories", "albums", <string>""));
|
||||
|
||||
|
|
|
@ -77,9 +77,18 @@ export default new Router({
|
|||
},
|
||||
|
||||
{
|
||||
path: "/people/:user?/:name?",
|
||||
path: "/recognize/:user?/:name?",
|
||||
component: Timeline,
|
||||
name: "people",
|
||||
name: "recognize",
|
||||
props: (route) => ({
|
||||
rootTitle: t("memories", "People"),
|
||||
}),
|
||||
},
|
||||
|
||||
{
|
||||
path: "/facerecognition/:user?/:name?",
|
||||
component: Timeline,
|
||||
name: "facerecognition",
|
||||
props: (route) => ({
|
||||
rootTitle: t("memories", "People"),
|
||||
}),
|
||||
|
|
|
@ -50,12 +50,15 @@ export class API {
|
|||
return gen(`${BASE}/tags/preview/{tag}`, { tag });
|
||||
}
|
||||
|
||||
static FACE_LIST() {
|
||||
return gen(`${BASE}/faces`);
|
||||
static FACE_LIST(app: "recognize" | "facerecognition") {
|
||||
return gen(`${BASE}/${app}/people`);
|
||||
}
|
||||
|
||||
static FACE_PREVIEW(face: string | number) {
|
||||
return gen(`${BASE}/faces/preview/{face}`, { face });
|
||||
static FACE_PREVIEW(
|
||||
app: "recognize" | "facerecognition",
|
||||
face: string | number
|
||||
) {
|
||||
return gen(`${BASE}/${app}/people/preview/{face}`, { face });
|
||||
}
|
||||
|
||||
static ARCHIVE(fileid: number) {
|
||||
|
|
|
@ -212,7 +212,12 @@ export function convertFlags(photo: IPhoto) {
|
|||
delete photo.isfolder;
|
||||
}
|
||||
if (photo.isface) {
|
||||
photo.flag |= constants.c.FLAG_IS_FACE;
|
||||
const app = photo.isface;
|
||||
if (app === "recognize") {
|
||||
photo.flag |= constants.c.FLAG_IS_FACE_RECOGNIZE;
|
||||
} else if (app === "facerecognition") {
|
||||
photo.flag |= constants.c.FLAG_IS_FACE_RECOGNITION;
|
||||
}
|
||||
delete photo.isface;
|
||||
}
|
||||
if (photo.istag) {
|
||||
|
@ -310,10 +315,11 @@ export const constants = {
|
|||
FLAG_IS_FAVORITE: 1 << 3,
|
||||
FLAG_IS_FOLDER: 1 << 4,
|
||||
FLAG_IS_TAG: 1 << 5,
|
||||
FLAG_IS_FACE: 1 << 6,
|
||||
FLAG_IS_ALBUM: 1 << 7,
|
||||
FLAG_SELECTED: 1 << 8,
|
||||
FLAG_LEAVING: 1 << 9,
|
||||
FLAG_IS_FACE_RECOGNIZE: 1 << 6,
|
||||
FLAG_IS_FACE_RECOGNITION: 1 << 7,
|
||||
FLAG_IS_ALBUM: 1 << 8,
|
||||
FLAG_SELECTED: 1 << 9,
|
||||
FLAG_LEAVING: 1 << 10,
|
||||
},
|
||||
|
||||
TagDayID: TagDayID,
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import axios from "@nextcloud/axios";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { translate as t } from "@nextcloud/l10n";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { IDay, IPhoto } from "../../types";
|
||||
import { API } from "../API";
|
||||
import client from "../DavClient";
|
||||
import { constants } from "../Utils";
|
||||
import client from "../DavClient";
|
||||
import * as base from "./base";
|
||||
|
||||
/**
|
||||
* Get list of tags and convert to Days response
|
||||
*/
|
||||
export async function getPeopleData(): Promise<IDay[]> {
|
||||
export async function getPeopleData(
|
||||
app: "recognize" | "facerecognition"
|
||||
): Promise<IDay[]> {
|
||||
// Query for photos
|
||||
let data: {
|
||||
id: number;
|
||||
|
@ -19,7 +22,7 @@ export async function getPeopleData(): Promise<IDay[]> {
|
|||
previews: IPhoto[];
|
||||
}[] = [];
|
||||
try {
|
||||
const res = await axios.get<typeof data>(API.FACE_LIST());
|
||||
const res = await axios.get<typeof data>(API.FACE_LIST(app));
|
||||
data = res.data;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
|
@ -39,13 +42,48 @@ export async function getPeopleData(): Promise<IDay[]> {
|
|||
...face,
|
||||
fileid: face.id,
|
||||
istag: true,
|
||||
isface: true,
|
||||
isface: app,
|
||||
} as any)
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function updatePeopleFaceRecognition(
|
||||
name: string,
|
||||
params: object
|
||||
) {
|
||||
if (Number.isInteger(Number(name))) {
|
||||
return await axios.put(
|
||||
generateUrl(`/apps/facerecognition/api/2.0/cluster/${name}`),
|
||||
params
|
||||
);
|
||||
} else {
|
||||
return await axios.put(
|
||||
generateUrl(`/apps/facerecognition/api/2.0/person/${name}`),
|
||||
params
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function renamePeopleFaceRecognition(
|
||||
name: string,
|
||||
newName: string
|
||||
) {
|
||||
return await updatePeopleFaceRecognition(name, {
|
||||
name: newName,
|
||||
});
|
||||
}
|
||||
|
||||
export async function setVisibilityPeopleFaceRecognition(
|
||||
name: string,
|
||||
visibility: boolean
|
||||
) {
|
||||
return await updatePeopleFaceRecognition(name, {
|
||||
visible: visibility,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove images from a face.
|
||||
*
|
||||
|
|
|
@ -105,7 +105,7 @@ export type IPhoto = {
|
|||
/** Is this an album */
|
||||
isalbum?: boolean;
|
||||
/** Is this a face */
|
||||
isface?: boolean;
|
||||
isface?: "recognize" | "facerecognition";
|
||||
/** Optional datetaken epoch */
|
||||
datetaken?: number;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue