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
{
$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
$faceStr = (string) $this->request->getParam('recognize');
$faceNames = explode('/', $faceStr);
if (2 !== \count($faceNames)) {
throw new \Exception('Invalid face query');

View File

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

View File

@ -801,17 +801,23 @@ export default defineComponent({
// Check photo ownership
if (this.$route.params.user !== getCurrentUser()?.uid) {
showError(
this.t('memories', 'Only user "{user}" can update this person', {
user,
})
);
showError(this.t('memories', 'Only user "{user}" can update this person', { user }));
return;
}
// Run query
for await (let delIds of dav.removeFaceImages(<string>user, <string>name, Array.from(selection.values()))) {
this.deleteSelectedPhotosById(delIds, selection);
// Make map to get back photo from faceid
const map = new Map<number, IPhoto>();
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() {
try {
if (this.$route.name === 'recognize') {
await client.deleteFile(`/recognize/${this.user}/faces/${this.name}`);
await dav.recognizeDeleteFace(this.user, this.name);
} else {
await dav.setVisibilityPeopleFaceRecognition(this.name, false);
await dav.faceRecognitionSetPersonVisibility(this.name, false);
}
this.$router.push({ name: this.$route.name as string });
this.close();

View File

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

View File

@ -89,14 +89,13 @@ export default defineComponent({
async clickFace(face: IFace) {
const user = this.$route.params.user || '';
const name = this.$route.params.name || '';
const newName = String(face.name || face.cluster_id);
const target = String(face.name || face.cluster_id);
if (
!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,
newName,
target,
})
)
) {
@ -108,31 +107,19 @@ export default defineComponent({
this.updateLoading(1);
// Create map to return IPhoto later
let photoMap = new Map<number, IPhoto>();
for (const photo of this.photos) {
photoMap.set(photo.fileid, photo);
const map = new Map<number, IPhoto>();
for (const photo of this.photos.filter((p) => p.faceid)) {
map.set(photo.faceid!, photo);
}
// Create move calls
const calls = this.photos.map((p) => async () => {
try {
await client.moveFile(
`/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);
// Run WebDAV query
const photos = Array.from(map.values());
for await (let delIds of dav.recognizeMoveFaceImages(user, name, target, photos)) {
this.moved(delIds.filter((id) => id).map((id) => map.get(id)!));
}
} catch (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 {
this.updateLoading(-1);
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;
}
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))) {
return await axios.put(generateUrl(`/apps/facerecognition/api/2.0/cluster/${name}`), params);
} 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, {
name: newName,
});
/**
* Rename a face in face recognition
* @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, {
visible: visibility,
});
/**
* Set visibility of a face
* @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 name Name of face (or ID)
* @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
const calls = photos.map((f) => async () => {
const calls = photos.map((p) => async () => {
try {
await client.deleteFile(`/recognize/${user}/faces/${name}/${f.fileid}-${f.basename}`);
return f.fileid;
await client.deleteFile(`/recognize/${user}/faces/${name}/${p.faceid}-${p.basename}`);
return p.faceid!;
} catch (e) {
console.error(e);
showError(
t('memories', 'Failed to remove {filename} from face.', {
filename: f.basename ?? f.fileid,
filename: p.basename ?? p.fileid,
})
);
return 0;
@ -58,3 +69,56 @@ export async function* removeFaceImages(user: string, name: string, photos: IPho
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}`);
}