Browse Source

Face wake (#1843)

## Description

- Fix issue with thumbnail decoding in indexing
- Fix show correct cluster progress counter
- Add wakelock to ML settings page
- Show in settings when device health is low

## Tests

Tested in debug on my pixel
Laurens Priem 1 year ago
parent
commit
06a698ddbb
33 changed files with 200 additions and 52 deletions
  1. 2 2
      mobile/lib/core/configuration.dart
  2. 24 7
      mobile/lib/face/db.dart
  3. 2 0
      mobile/lib/generated/intl/messages_cs.dart
  4. 2 0
      mobile/lib/generated/intl/messages_de.dart
  5. 2 0
      mobile/lib/generated/intl/messages_en.dart
  6. 2 0
      mobile/lib/generated/intl/messages_es.dart
  7. 2 0
      mobile/lib/generated/intl/messages_fr.dart
  8. 2 0
      mobile/lib/generated/intl/messages_it.dart
  9. 2 0
      mobile/lib/generated/intl/messages_ko.dart
  10. 2 0
      mobile/lib/generated/intl/messages_nl.dart
  11. 2 0
      mobile/lib/generated/intl/messages_no.dart
  12. 2 0
      mobile/lib/generated/intl/messages_pl.dart
  13. 17 6
      mobile/lib/generated/intl/messages_pt.dart
  14. 2 0
      mobile/lib/generated/intl/messages_zh.dart
  15. 10 0
      mobile/lib/generated/l10n.dart
  16. 2 1
      mobile/lib/l10n/intl_cs.arb
  17. 2 1
      mobile/lib/l10n/intl_de.arb
  18. 2 1
      mobile/lib/l10n/intl_en.arb
  19. 2 1
      mobile/lib/l10n/intl_es.arb
  20. 2 1
      mobile/lib/l10n/intl_fr.arb
  21. 2 1
      mobile/lib/l10n/intl_it.arb
  22. 2 1
      mobile/lib/l10n/intl_ko.arb
  23. 2 1
      mobile/lib/l10n/intl_nl.arb
  24. 2 1
      mobile/lib/l10n/intl_no.arb
  25. 2 1
      mobile/lib/l10n/intl_pl.arb
  26. 2 1
      mobile/lib/l10n/intl_pt.arb
  27. 2 1
      mobile/lib/l10n/intl_zh.arb
  28. 12 0
      mobile/lib/services/machine_learning/face_ml/face_ml_exceptions.dart
  29. 18 2
      mobile/lib/services/machine_learning/face_ml/face_ml_service.dart
  30. 2 0
      mobile/lib/services/machine_learning/machine_learning_controller.dart
  31. 25 4
      mobile/lib/ui/settings/machine_learning_settings_page.dart
  32. 6 19
      mobile/lib/ui/viewer/file/video_widget.dart
  33. 38 0
      mobile/lib/utils/wakelock_util.dart

+ 2 - 2
mobile/lib/core/configuration.dart

@@ -35,10 +35,10 @@ import 'package:photos/services/sync_service.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/file_uploader.dart';
 import 'package:photos/utils/validator_util.dart';
+import "package:photos/utils/wakelock_util.dart";
 import 'package:shared_preferences/shared_preferences.dart';
 import "package:tuple/tuple.dart";
 import 'package:uuid/uuid.dart';
-import 'package:wakelock_plus/wakelock_plus.dart';
 
 class Configuration {
   Configuration._privateConstructor();
@@ -585,7 +585,7 @@ class Configuration {
 
   Future<void> setShouldKeepDeviceAwake(bool value) async {
     await _preferences.setBool(keyShouldKeepDeviceAwake, value);
-    await WakelockPlus.toggle(enable: value);
+    await EnteWakeLock.toggle(enable: value);
   }
 
   Future<void> setShouldBackupVideos(bool value) async {

+ 24 - 7
mobile/lib/face/db.dart

@@ -668,22 +668,39 @@ class FaceMLDataDB {
     return maps.first['count'] as int;
   }
 
-  Future<int> getClusteredFileCount() async {
+  Future<int> getClusteredOrFacelessFileCount() async {
     final db = await instance.asyncDB;
-    final List<Map<String, dynamic>> maps = await db.getAll(
+    final List<Map<String, dynamic>> clustered = await db.getAll(
       'SELECT $fcFaceId FROM $faceClustersTable',
     );
-    final Set<int> fileIDs = {};
-    for (final map in maps) {
+    final Set<int> clusteredFileIDs = {};
+    for (final map in clustered) {
       final int fileID = getFileIdFromFaceId(map[fcFaceId] as String);
-      fileIDs.add(fileID);
+      clusteredFileIDs.add(fileID);
+    }
+
+    final List<Map<String, dynamic>> badFacesFiles = await db.getAll(
+      'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore <= $kMinimumQualityFaceScore OR $faceBlur <= $kLaplacianHardThreshold',
+    );
+    final Set<int> badFileIDs = {};
+    for (final map in badFacesFiles) {
+      badFileIDs.add(map[fileIDColumn] as int);
+    }
+
+    final List<Map<String, dynamic>> goodFacesFiles = await db.getAll(
+      'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore > $kMinimumQualityFaceScore AND $faceBlur > $kLaplacianHardThreshold',
+    );
+    final Set<int> goodFileIDs = {};
+    for (final map in goodFacesFiles) {
+      goodFileIDs.add(map[fileIDColumn] as int);
     }
-    return fileIDs.length;
+    final trulyFacelessFiles = badFileIDs.difference(goodFileIDs);
+    return clusteredFileIDs.length + trulyFacelessFiles.length;
   }
 
   Future<double> getClusteredToIndexableFilesRatio() async {
     final int indexableFiles = (await getIndexableFileIDs()).length;
-    final int clusteredFiles = await getClusteredFileCount();
+    final int clusteredFiles = await getClusteredOrFacelessFileCount();
 
     return clusteredFiles / indexableFiles;
   }

+ 2 - 0
mobile/lib/generated/intl/messages_cs.dart

@@ -54,6 +54,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "longPressAnEmailToVerifyEndToEndEncryption":

+ 2 - 0
mobile/lib/generated/intl/messages_de.dart

@@ -819,6 +819,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "Falscher Wiederherstellungs-Schlüssel"),
         "indexedItems":
             MessageLookupByLibrary.simpleMessage("Indizierte Elemente"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_en.dart

@@ -813,6 +813,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "incorrectRecoveryKeyTitle":
             MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
         "indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Insecure device"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_es.dart

@@ -699,6 +699,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "La clave de recuperación introducida es incorrecta"),
         "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
             "Clave de recuperación incorrecta"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_fr.dart

@@ -804,6 +804,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "La clé de secours que vous avez entrée est incorrecte"),
         "incorrectRecoveryKeyTitle":
             MessageLookupByLibrary.simpleMessage("Clé de secours non valide"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Appareil non sécurisé"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_it.dart

@@ -773,6 +773,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "Il codice che hai inserito non è corretto"),
         "incorrectRecoveryKeyTitle":
             MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Dispositivo non sicuro"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_ko.dart

@@ -54,6 +54,8 @@ class MessageLookup extends MessageLookupByLibrary {
             "Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "longPressAnEmailToVerifyEndToEndEncryption":

+ 2 - 0
mobile/lib/generated/intl/messages_nl.dart

@@ -840,6 +840,8 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"),
         "indexedItems":
             MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Onveilig apparaat"),
         "installManually":

+ 2 - 0
mobile/lib/generated/intl/messages_no.dart

@@ -72,6 +72,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "invalidEmailAddress":
             MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),

+ 2 - 0
mobile/lib/generated/intl/messages_pl.dart

@@ -131,6 +131,8 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"),
         "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
             "Nieprawidłowy klucz odzyskiwania"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "invalidEmailAddress":
             MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),

+ 17 - 6
mobile/lib/generated/intl/messages_pt.dart

@@ -98,7 +98,7 @@ class MessageLookup extends MessageLookupByLibrary {
       "${storageAmountInGB} GB cada vez que alguém se inscrever para um plano pago e aplica o seu código";
 
   static String m25(freeAmount, storageUnit) =>
-      "${freeAmount} ${storageUnit} grátis";
+      "${freeAmount} ${storageUnit} livre";
 
   static String m26(endDate) => "Teste gratuito acaba em ${endDate}";
 
@@ -225,6 +225,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são <underline>criptografados de ponta a ponta</underline>."),
         "activeSessions":
             MessageLookupByLibrary.simpleMessage("Sessões ativas"),
+        "addAName": MessageLookupByLibrary.simpleMessage("Adicione um nome"),
         "addANewEmail":
             MessageLookupByLibrary.simpleMessage("Adicionar um novo email"),
         "addCollaborator":
@@ -446,7 +447,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "clubByFileName": MessageLookupByLibrary.simpleMessage(
             "Agrupar pelo nome de arquivo"),
         "clusteringProgress":
-            MessageLookupByLibrary.simpleMessage("Clustering progress"),
+            MessageLookupByLibrary.simpleMessage("Progresso de agrupamento"),
         "codeAppliedPageTitle":
             MessageLookupByLibrary.simpleMessage("Código aplicado"),
         "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@@ -692,6 +693,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "enterPassword": MessageLookupByLibrary.simpleMessage("Digite a senha"),
         "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
             "Insira a senha para criptografar seus dados"),
+        "enterPersonName":
+            MessageLookupByLibrary.simpleMessage("Inserir nome da pessoa"),
         "enterReferralCode": MessageLookupByLibrary.simpleMessage(
             "Insira o código de referência"),
         "enterThe6digitCodeFromnyourAuthenticatorApp":
@@ -717,9 +720,9 @@ class MessageLookup extends MessageLookupByLibrary {
         "exportYourData":
             MessageLookupByLibrary.simpleMessage("Exportar seus dados"),
         "faceRecognition":
-            MessageLookupByLibrary.simpleMessage("Face recognition"),
+            MessageLookupByLibrary.simpleMessage("Reconhecimento facial"),
         "faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
-            "Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
+            "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados."),
         "faces": MessageLookupByLibrary.simpleMessage("Rostos"),
         "failedToApplyCode":
             MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"),
@@ -761,12 +764,15 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Arquivos excluídos"),
         "filesSavedToGallery":
             MessageLookupByLibrary.simpleMessage("Arquivos salvos na galeria"),
+        "findPeopleByName": MessageLookupByLibrary.simpleMessage(
+            "Encontre pessoas rapidamente por nome"),
         "flip": MessageLookupByLibrary.simpleMessage("Inverter"),
         "forYourMemories":
             MessageLookupByLibrary.simpleMessage("para suas memórias"),
         "forgotPassword":
             MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"),
-        "foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
+        "foundFaces":
+            MessageLookupByLibrary.simpleMessage("Rostos encontrados"),
         "freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
             "Armazenamento gratuito reivindicado"),
         "freeStorageOnReferralSuccess": m24,
@@ -830,6 +836,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
             "Chave de recuperação incorreta"),
         "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice":
             MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"),
         "installManually":
@@ -1064,6 +1072,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "pendingItems": MessageLookupByLibrary.simpleMessage("Itens pendentes"),
         "pendingSync":
             MessageLookupByLibrary.simpleMessage("Sincronização pendente"),
+        "people": MessageLookupByLibrary.simpleMessage("Pessoas"),
         "peopleUsingYourCode":
             MessageLookupByLibrary.simpleMessage("Pessoas que usam seu código"),
         "permDeleteWarning": MessageLookupByLibrary.simpleMessage(
@@ -1197,6 +1206,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "removeParticipant":
             MessageLookupByLibrary.simpleMessage("Remover participante"),
         "removeParticipantBody": m43,
+        "removePersonLabel":
+            MessageLookupByLibrary.simpleMessage("Remover etiqueta da pessoa"),
         "removePublicLink":
             MessageLookupByLibrary.simpleMessage("Remover link público"),
         "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(
@@ -1260,7 +1271,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
             "Pesquisar por data, mês ou ano"),
         "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
-            "Encontre todas as fotos de uma pessoa"),
+            "Pessoas serão exibidas aqui uma vez que a indexação é feita"),
         "searchFileTypesAndNamesEmptySection":
             MessageLookupByLibrary.simpleMessage("Tipos de arquivo e nomes"),
         "searchHint1": MessageLookupByLibrary.simpleMessage(

+ 2 - 0
mobile/lib/generated/intl/messages_zh.dart

@@ -686,6 +686,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "incorrectRecoveryKeyTitle":
             MessageLookupByLibrary.simpleMessage("不正确的恢复密钥"),
         "indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"),
+        "indexingIsPaused": MessageLookupByLibrary.simpleMessage(
+            "Indexing is paused, will automatically resume when device is ready"),
         "insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"),
         "installManually": MessageLookupByLibrary.simpleMessage("手动安装"),
         "invalidEmailAddress":

+ 10 - 0
mobile/lib/generated/l10n.dart

@@ -8793,6 +8793,16 @@ class S {
       args: [],
     );
   }
+
+  /// `Indexing is paused, will automatically resume when device is ready`
+  String get indexingIsPaused {
+    return Intl.message(
+      'Indexing is paused, will automatically resume when device is ready',
+      name: 'indexingIsPaused',
+      desc: '',
+      args: [],
+    );
+  }
 }
 
 class AppLocalizationDelegate extends LocalizationsDelegate<S> {

+ 2 - 1
mobile/lib/l10n/intl_cs.arb

@@ -24,5 +24,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_de.arb

@@ -1212,5 +1212,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_en.arb

@@ -1235,5 +1235,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_es.arb

@@ -986,5 +986,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_fr.arb

@@ -1167,5 +1167,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_it.arb

@@ -1129,5 +1129,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_ko.arb

@@ -24,5 +24,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_nl.arb

@@ -1230,5 +1230,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_no.arb

@@ -38,5 +38,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_pl.arb

@@ -125,5 +125,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_pt.arb

@@ -1235,5 +1235,6 @@
   "faceRecognition": "Reconhecimento facial",
   "faceRecognitionIndexingDescription": "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados.",
   "foundFaces": "Rostos encontrados",
-  "clusteringProgress": "Progresso de agrupamento"
+  "clusteringProgress": "Progresso de agrupamento",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 2 - 1
mobile/lib/l10n/intl_zh.arb

@@ -1230,5 +1230,6 @@
   "faceRecognition": "Face recognition",
   "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
   "foundFaces": "Found faces",
-  "clusteringProgress": "Clustering progress"
+  "clusteringProgress": "Clustering progress",
+  "indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
 }

+ 12 - 0
mobile/lib/services/machine_learning/face_ml/face_ml_exceptions.dart

@@ -8,6 +8,18 @@ class GeneralFaceMlException implements Exception {
   String toString() => 'GeneralFaceMlException: $message';
 }
 
+class ThumbnailRetrievalException implements Exception {
+  final String message;
+  final StackTrace stackTrace;
+
+  ThumbnailRetrievalException(this.message, this.stackTrace);
+
+  @override
+  String toString() {
+    return 'ThumbnailRetrievalException: $message\n$stackTrace';
+  }
+}
+
 class CouldNotRetrieveAnyFileData implements Exception {}
 
 class CouldNotInitializeFaceDetector implements Exception {}

+ 18 - 2
mobile/lib/services/machine_learning/face_ml/face_ml_service.dart

@@ -9,6 +9,7 @@ import "dart:ui" show Image;
 import "package:computer/computer.dart";
 import "package:dart_ui_isolate/dart_ui_isolate.dart";
 import "package:flutter/foundation.dart" show debugPrint, kDebugMode;
+import "package:flutter/services.dart";
 import "package:logging/logging.dart";
 import "package:onnxruntime/onnxruntime.dart";
 import "package:package_info_plus/package_info_plus.dart";
@@ -446,7 +447,8 @@ class FaceMlService {
 
         if (LocalSettings.instance.remoteFetchEnabled) {
           try {
-            final Set<int> fileIds = {}; // if there are duplicates here server returns 400
+            final Set<int> fileIds =
+                {}; // if there are duplicates here server returns 400
             // Try to find embeddings on the remote server
             for (final f in chunk) {
               fileIds.add(f.uploadedFileID!);
@@ -844,13 +846,22 @@ class FaceMlService {
       }
       await FaceMLDataDB.instance.bulkInsertFaces(faces);
       return true;
+    } on ThumbnailRetrievalException catch (e, s) {
+      _logger.severe(
+        'ThumbnailRetrievalException while processing image with ID ${enteFile.uploadedFileID}, storing empty face so indexing does not get stuck',
+        e,
+        s,
+      );
+      await FaceMLDataDB.instance
+          .bulkInsertFaces([Face.empty(enteFile.uploadedFileID!, error: true)]);
+      return true;
     } catch (e, s) {
       _logger.severe(
         "Failed to analyze using FaceML for image with ID: ${enteFile.uploadedFileID}",
         e,
         s,
       );
-      return true;
+      return false;
     }
   }
 
@@ -1004,7 +1015,12 @@ class FaceMlService {
         final stopwatch = Stopwatch()..start();
         File? file;
         if (enteFile.fileType == FileType.video) {
+          try {
           file = await getThumbnailForUploadedFile(enteFile);
+          } on PlatformException catch (e, s) {
+            _logger.severe("Could not get thumbnail for $enteFile due to PlatformException", e, s);
+            throw ThumbnailRetrievalException(e.toString(), s);
+          }
         } else {
           file = await getFile(enteFile, isOrigin: true);
           // TODO: This is returning null for Pragadees for all files, so something is wrong here!

+ 2 - 0
mobile/lib/services/machine_learning/machine_learning_controller.dart

@@ -28,6 +28,8 @@ class MachineLearningController {
   bool _canRunML = false;
   late Timer _userInteractionTimer;
 
+  bool get isDeviceHealthy => _isDeviceHealthy;
+
   void init() {
     if (Platform.isAndroid) {
       _startInteractionTimer();

+ 25 - 4
mobile/lib/ui/settings/machine_learning_settings_page.dart

@@ -11,6 +11,7 @@ import "package:photos/generated/l10n.dart";
 import "package:photos/models/ml/ml_versions.dart";
 import "package:photos/service_locator.dart";
 import "package:photos/services/machine_learning/face_ml/face_ml_service.dart";
+import "package:photos/services/machine_learning/machine_learning_controller.dart";
 import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart';
 import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
 import "package:photos/services/remote_assets_service.dart";
@@ -27,6 +28,7 @@ import "package:photos/ui/components/toggle_switch_widget.dart";
 import "package:photos/utils/data_util.dart";
 import "package:photos/utils/local_settings.dart";
 import "package:photos/utils/ml_util.dart";
+import "package:photos/utils/wakelock_util.dart";
 
 final _logger = Logger("MachineLearningSettingsPage");
 
@@ -41,6 +43,7 @@ class MachineLearningSettingsPage extends StatefulWidget {
 class _MachineLearningSettingsPageState
     extends State<MachineLearningSettingsPage> {
   late InitializationState _state;
+  final EnteWakeLock _wakeLock = EnteWakeLock();
 
   late StreamSubscription<MLFrameworkInitializationUpdateEvent>
       _eventSubscription;
@@ -54,6 +57,7 @@ class _MachineLearningSettingsPageState
       setState(() {});
     });
     _fetchState();
+    _wakeLock.enable();
   }
 
   void _fetchState() {
@@ -64,6 +68,7 @@ class _MachineLearningSettingsPageState
   void dispose() {
     super.dispose();
     _eventSubscription.cancel();
+    _wakeLock.disable();
   }
 
   @override
@@ -439,16 +444,24 @@ class FaceRecognitionStatusWidgetState
     });
   }
 
-  Future<(int, int, double)> getIndexStatus() async {
+  Future<(int, int, double, bool)> getIndexStatus() async {
     try {
       final indexedFiles = await FaceMLDataDB.instance
           .getIndexedFileCount(minimumMlVersion: faceMlVersion);
       final indexableFiles = (await getIndexableFileIDs()).length;
       final showIndexedFiles = min(indexedFiles, indexableFiles);
       final pendingFiles = max(indexableFiles - indexedFiles, 0);
-      final clusteringDoneRatio = await FaceMLDataDB.instance.getClusteredToIndexableFilesRatio();
-
-      return (showIndexedFiles, pendingFiles, clusteringDoneRatio);
+      final clusteringDoneRatio =
+          await FaceMLDataDB.instance.getClusteredToIndexableFilesRatio();
+      final bool deviceIsHealthy =
+          MachineLearningController.instance.isDeviceHealthy;
+
+      return (
+        showIndexedFiles,
+        pendingFiles,
+        clusteringDoneRatio,
+        deviceIsHealthy
+      );
     } catch (e, s) {
       _logger.severe('Error getting face recognition status', e, s);
       rethrow;
@@ -480,6 +493,14 @@ class FaceRecognitionStatusWidgetState
               final double clusteringDoneRatio = snapshot.data!.$3;
               final double clusteringPercentage =
                   (clusteringDoneRatio * 100).clamp(0, 100);
+              final bool isDeviceHealthy = snapshot.data!.$4;
+
+              if (!isDeviceHealthy &&
+                  (pendingFiles > 0 || clusteringPercentage < 99)) {
+                return MenuSectionDescriptionWidget(
+                  content: S.of(context).indexingIsPaused,
+                );
+              }
 
               return Column(
                 children: [

+ 6 - 19
mobile/lib/ui/viewer/file/video_widget.dart

@@ -17,9 +17,9 @@ import 'package:photos/ui/viewer/file/video_controls.dart';
 import "package:photos/utils/dialog_util.dart";
 import 'package:photos/utils/file_util.dart';
 import 'package:photos/utils/toast_util.dart';
+import "package:photos/utils/wakelock_util.dart";
 import 'package:video_player/video_player.dart';
 import 'package:visibility_detector/visibility_detector.dart';
-import 'package:wakelock_plus/wakelock_plus.dart';
 
 class VideoWidget extends StatefulWidget {
   final EnteFile file;
@@ -45,7 +45,7 @@ class _VideoWidgetState extends State<VideoWidget> {
   ChewieController? _chewieController;
   final _progressNotifier = ValueNotifier<double?>(null);
   bool _isPlaying = false;
-  bool _wakeLockEnabledHere = false;
+  final EnteWakeLock _wakeLock = EnteWakeLock();
 
   @override
   void initState() {
@@ -126,13 +126,7 @@ class _VideoWidgetState extends State<VideoWidget> {
     _chewieController?.dispose();
     _progressNotifier.dispose();
 
-    if (_wakeLockEnabledHere) {
-      unawaited(
-        WakelockPlus.enabled.then((isEnabled) {
-          isEnabled ? WakelockPlus.disable() : null;
-        }),
-      );
-    }
+    _wakeLock.dispose();
     super.dispose();
   }
 
@@ -257,17 +251,10 @@ class _VideoWidgetState extends State<VideoWidget> {
 
   Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
     if (isPlaying) {
-      return WakelockPlus.enabled.then((value) {
-        if (value == false) {
-          WakelockPlus.enable();
-          //wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
-          //We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
-          _wakeLockEnabledHere = true;
-        }
-      });
+      _wakeLock.enable();
     }
-    if (_wakeLockEnabledHere && !isPlaying) {
-      return WakelockPlus.disable();
+    if (!isPlaying) {
+      _wakeLock.disable();
     }
   }
 

+ 38 - 0
mobile/lib/utils/wakelock_util.dart

@@ -0,0 +1,38 @@
+import "dart:async" show unawaited;
+
+import "package:wakelock_plus/wakelock_plus.dart";
+
+class EnteWakeLock {
+  bool _wakeLockEnabledHere = false;
+
+  void enable() {
+    WakelockPlus.enabled.then((value) {
+        if (value == false) {
+          WakelockPlus.enable();
+          //wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
+          //We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
+          _wakeLockEnabledHere = true;
+        }
+      });
+  }
+
+  void disable() {
+    if (_wakeLockEnabledHere) {
+      WakelockPlus.disable();
+    }
+  }
+
+  void dispose() {
+    if (_wakeLockEnabledHere) {
+      unawaited(
+        WakelockPlus.enabled.then((isEnabled) {
+          isEnabled ? WakelockPlus.disable() : null;
+        }),
+      );
+    }
+  }
+
+  static Future<void> toggle({required bool enable}) async {
+    await WakelockPlus.toggle(enable: enable);
+  }
+}