Support for recognize v3.8 (fix #618)

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/653/head
Varun Patil 2023-04-28 22:29:10 -07:00
parent e1986b6991
commit 429c821fbf
7 changed files with 114 additions and 56 deletions

View File

@ -57,9 +57,13 @@ class RecognizeBackend extends Backend
public function transformDayQuery(&$query, bool $aggregate): void public function transformDayQuery(&$query, bool $aggregate): void
{ {
$faceStr = (string) $this->request->getParam('recognize'); // Check if Recognize is enabled
if (!$this->isEnabled()) {
throw \OCA\Memories\Exceptions::NotEnabled('Recognize');
}
// Get name and uid of face user // Get name and uid of face user
$faceStr = (string) $this->request->getParam('recognize');
$faceNames = explode('/', $faceStr); $faceNames = explode('/', $faceStr);
if (2 !== \count($faceNames)) { if (2 !== \count($faceNames)) {
throw new \Exception('Invalid face query'); throw new \Exception('Invalid face query');

View File

@ -92,7 +92,7 @@ class Util
} }
$v = $appManager->getAppVersion('recognize'); $v = $appManager->getAppVersion('recognize');
if (!version_compare($v, '3.0.0-alpha', '>=')) { if (!version_compare($v, '3.8.0', '>=')) {
return false; return false;
} }

View File

@ -801,17 +801,23 @@ export default defineComponent({
// Check photo ownership // Check photo ownership
if (this.$route.params.user !== getCurrentUser()?.uid) { if (this.$route.params.user !== getCurrentUser()?.uid) {
showError( showError(this.t('memories', 'Only user "{user}" can update this person', { user }));
this.t('memories', 'Only user "{user}" can update this person', {
user,
})
);
return; return;
} }
// Run query // Make map to get back photo from faceid
for await (let delIds of dav.removeFaceImages(<string>user, <string>name, Array.from(selection.values()))) { const map = new Map<number, IPhoto>();
this.deleteSelectedPhotosById(delIds, selection); for (const photo of selection.values()) {
if (photo.faceid) {
map.set(photo.faceid, photo);
}
}
const photos = Array.from(map.values());
// Run WebDAV query
for await (let delIds of dav.recognizeDeleteFaceImages(user, name, photos)) {
const fileIds = delIds.map((id) => map.get(id)?.fileid ?? 0).filter((id) => id);
this.deleteSelectedPhotosById(fileIds, selection);
} }
}, },

View File

@ -77,9 +77,9 @@ export default defineComponent({
async save() { async save() {
try { try {
if (this.$route.name === 'recognize') { if (this.$route.name === 'recognize') {
await client.deleteFile(`/recognize/${this.user}/faces/${this.name}`); await dav.recognizeDeleteFace(this.user, this.name);
} else { } else {
await dav.setVisibilityPeopleFaceRecognition(this.name, false); await dav.faceRecognitionSetPersonVisibility(this.name, false);
} }
this.$router.push({ name: this.$route.name as string }); this.$router.push({ name: this.$route.name as string });
this.close(); this.close();

View File

@ -89,12 +89,9 @@ export default defineComponent({
async save() { async save() {
try { try {
if (this.$route.name === 'recognize') { if (this.$route.name === 'recognize') {
await client.moveFile( await dav.recognizeRenameFace(this.user, this.oldName, this.name);
`/recognize/${this.user}/faces/${this.oldName}`,
`/recognize/${this.user}/faces/${this.name}`
);
} else { } else {
await dav.renamePeopleFaceRecognition(this.oldName, this.name); await dav.faceRecognitionRenamePerson(this.oldName, this.name);
} }
this.$router.push({ this.$router.push({
name: this.$route.name as string, name: this.$route.name as string,

View File

@ -89,14 +89,13 @@ export default defineComponent({
async clickFace(face: IFace) { async clickFace(face: IFace) {
const user = this.$route.params.user || ''; const user = this.$route.params.user || '';
const name = this.$route.params.name || ''; const name = this.$route.params.name || '';
const target = String(face.name || face.cluster_id);
const newName = String(face.name || face.cluster_id);
if ( if (
!confirm( !confirm(
this.t('memories', 'Are you sure you want to move the selected photos from {name} to {newName}?', { this.t('memories', 'Are you sure you want to move the selected photos from {name} to {target}?', {
name, name,
newName, target,
}) })
) )
) { ) {
@ -108,31 +107,19 @@ export default defineComponent({
this.updateLoading(1); this.updateLoading(1);
// Create map to return IPhoto later // Create map to return IPhoto later
let photoMap = new Map<number, IPhoto>(); const map = new Map<number, IPhoto>();
for (const photo of this.photos) { for (const photo of this.photos.filter((p) => p.faceid)) {
photoMap.set(photo.fileid, photo); map.set(photo.faceid!, photo);
} }
// Create move calls // Run WebDAV query
const calls = this.photos.map((p) => async () => { const photos = Array.from(map.values());
try { for await (let delIds of dav.recognizeMoveFaceImages(user, name, target, photos)) {
await client.moveFile( this.moved(delIds.filter((id) => id).map((id) => map.get(id)!));
`/recognize/${user}/faces/${name}/${p.fileid}-${p.basename}`,
`/recognize/${face.user_id}/faces/${newName}/${p.fileid}-${p.basename}`
);
return photoMap.get(p.fileid);
} catch (e) {
console.error(e);
showError(this.t('memories', 'Error while moving {basename}', <any>p));
}
});
for await (const resp of dav.runInParallel(calls, 10)) {
const valid = resp.filter((r): r is IPhoto => r !== undefined);
this.moved(valid);
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
showError(this.t('photos', 'Failed to move {name}.', { name })); showError(this.t('photos', 'An error occured while moving photos from {name}.', { name }));
} finally { } finally {
this.updateLoading(-1); this.updateLoading(-1);
this.close(); this.close();

View File

@ -11,7 +11,12 @@ export async function getFaceList(app: 'recognize' | 'facerecognition') {
return (await axios.get<IFace[]>(API.FACE_LIST(app))).data; return (await axios.get<IFace[]>(API.FACE_LIST(app))).data;
} }
export async function updatePeopleFaceRecognition(name: string, params: object) { /**
* Update a person or cluster in face recognition
* @param name Name of face (or ID)
* @param params Parameters to update
*/
export async function faceRecognitionUpdatePerson(name: string, params: object) {
if (Number.isInteger(Number(name))) { if (Number.isInteger(Number(name))) {
return await axios.put(generateUrl(`/apps/facerecognition/api/2.0/cluster/${name}`), params); return await axios.put(generateUrl(`/apps/facerecognition/api/2.0/cluster/${name}`), params);
} else { } else {
@ -19,16 +24,22 @@ export async function updatePeopleFaceRecognition(name: string, params: object)
} }
} }
export async function renamePeopleFaceRecognition(name: string, newName: string) { /**
return await updatePeopleFaceRecognition(name, { * Rename a face in face recognition
name: newName, * @param name Name of face (or ID)
}); * @param target Target name of face
*/
export async function faceRecognitionRenamePerson(name: string, target: string) {
return await faceRecognitionUpdatePerson(name, { name: target });
} }
export async function setVisibilityPeopleFaceRecognition(name: string, visibility: boolean) { /**
return await updatePeopleFaceRecognition(name, { * Set visibility of a face
visible: visibility, * @param name Name of face (or ID)
}); * @param visible Visibility of face
*/
export async function faceRecognitionSetPersonVisibility(name: string, visible: boolean) {
return await faceRecognitionUpdatePerson(name, { visible });
} }
/** /**
@ -37,19 +48,19 @@ export async function setVisibilityPeopleFaceRecognition(name: string, visibilit
* @param user User ID of face * @param user User ID of face
* @param name Name of face (or ID) * @param name Name of face (or ID)
* @param photos List of photos to remove * @param photos List of photos to remove
* @returns Generator * @returns Generator for face IDs
*/ */
export async function* removeFaceImages(user: string, name: string, photos: IPhoto[]) { export async function* recognizeDeleteFaceImages(user: string, name: string, photos: IPhoto[]) {
// Remove each file // Remove each file
const calls = photos.map((f) => async () => { const calls = photos.map((p) => async () => {
try { try {
await client.deleteFile(`/recognize/${user}/faces/${name}/${f.fileid}-${f.basename}`); await client.deleteFile(`/recognize/${user}/faces/${name}/${p.faceid}-${p.basename}`);
return f.fileid; return p.faceid!;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
showError( showError(
t('memories', 'Failed to remove {filename} from face.', { t('memories', 'Failed to remove {filename} from face.', {
filename: f.basename ?? f.fileid, filename: p.basename ?? p.fileid,
}) })
); );
return 0; return 0;
@ -58,3 +69,56 @@ export async function* removeFaceImages(user: string, name: string, photos: IPho
yield* base.runInParallel(calls, 10); yield* base.runInParallel(calls, 10);
} }
/**
* Move faces from one face to another
*
* @param user User ID of face
* @param face Name of face (or ID)
* @param target Name of target face (or ID)
* @param photos List of photos to move
* @returns Generator for face IDs
*/
export async function* recognizeMoveFaceImages(user: string, face: string, target: string, photos: IPhoto[]) {
// Remove each file
const calls = photos.map((p) => async () => {
try {
await client.moveFile(
`/recognize/${user}/faces/${face}/${p.faceid}-${p.basename}`,
`/recognize/${user}/faces/${target}/${p.faceid}-${p.basename}`
);
return p.faceid!;
} catch (e) {
console.error(e);
showError(
t('memories', 'Failed to move {filename} from face.', {
filename: p.basename ?? p.fileid,
})
);
return 0;
}
});
yield* base.runInParallel(calls, 10);
}
/**
* Remove a face entirely
*
* @param user User ID of face
* @param name Name of face (or ID)
*/
export async function recognizeDeleteFace(user: string, name: string) {
return await client.deleteFile(`/recognize/${user}/faces/${name}`);
}
/**
* Rename a face in recognize
*
* @param user User ID of face
* @param name Name of face (or ID)
* @param target Target name of face
*/
export async function recognizeRenameFace(user: string, name: string, target: string) {
await client.moveFile(`/recognize/${user}/faces/${name}`, `/recognize/${user}/faces/${target}`);
}