|
@@ -1,7 +1,10 @@
|
|
-import "package:flutter/foundation.dart" show kDebugMode;
|
|
|
|
|
|
+import "dart:developer" as dev show log;
|
|
|
|
+
|
|
|
|
+import "package:flutter/foundation.dart" show Uint8List, kDebugMode;
|
|
import "package:flutter/material.dart";
|
|
import "package:flutter/material.dart";
|
|
import "package:logging/logging.dart";
|
|
import "package:logging/logging.dart";
|
|
import "package:photos/face/db.dart";
|
|
import "package:photos/face/db.dart";
|
|
|
|
+import "package:photos/face/model/box.dart";
|
|
import "package:photos/face/model/face.dart";
|
|
import "package:photos/face/model/face.dart";
|
|
import "package:photos/face/model/person.dart";
|
|
import "package:photos/face/model/person.dart";
|
|
import "package:photos/models/file/file.dart";
|
|
import "package:photos/models/file/file.dart";
|
|
@@ -10,6 +13,8 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
|
|
import "package:photos/ui/components/buttons/chip_button_widget.dart";
|
|
import "package:photos/ui/components/buttons/chip_button_widget.dart";
|
|
import "package:photos/ui/components/info_item_widget.dart";
|
|
import "package:photos/ui/components/info_item_widget.dart";
|
|
import "package:photos/ui/viewer/file_details/face_widget.dart";
|
|
import "package:photos/ui/viewer/file_details/face_widget.dart";
|
|
|
|
+import "package:photos/utils/face/face_box_crop.dart";
|
|
|
|
+import "package:photos/utils/thumbnail_util.dart";
|
|
|
|
|
|
class FacesItemWidget extends StatefulWidget {
|
|
class FacesItemWidget extends StatefulWidget {
|
|
final EnteFile file;
|
|
final EnteFile file;
|
|
@@ -119,6 +124,7 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
|
|
final lastViewedClusterID = ClusterFeedbackService.lastViewedClusterID;
|
|
final lastViewedClusterID = ClusterFeedbackService.lastViewedClusterID;
|
|
|
|
|
|
final faceWidgets = <FaceWidget>[];
|
|
final faceWidgets = <FaceWidget>[];
|
|
|
|
+ final faceCrops = getRelevantFaceCrops(faces);
|
|
for (final Face face in faces) {
|
|
for (final Face face in faces) {
|
|
final int? clusterID = faceIdsToClusterIds[face.faceID];
|
|
final int? clusterID = faceIdsToClusterIds[face.faceID];
|
|
final PersonEntity? person = clusterIDToPerson[clusterID] != null
|
|
final PersonEntity? person = clusterIDToPerson[clusterID] != null
|
|
@@ -130,6 +136,7 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
|
|
FaceWidget(
|
|
FaceWidget(
|
|
file,
|
|
file,
|
|
face,
|
|
face,
|
|
|
|
+ faceCrops: faceCrops,
|
|
clusterID: clusterID,
|
|
clusterID: clusterID,
|
|
person: person,
|
|
person: person,
|
|
highlight: highlight,
|
|
highlight: highlight,
|
|
@@ -144,4 +151,59 @@ class _FacesItemWidgetState extends State<FacesItemWidget> {
|
|
return <FaceWidget>[];
|
|
return <FaceWidget>[];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ Future<Map<String, Uint8List>?> getRelevantFaceCrops(
|
|
|
|
+ Iterable<Face> faces,
|
|
|
|
+ ) async {
|
|
|
|
+ try {
|
|
|
|
+ final faceIdToCrop = <String, Uint8List>{};
|
|
|
|
+ final facesWithoutCrops = <String, FaceBox>{};
|
|
|
|
+ for (final face in faces) {
|
|
|
|
+ final Uint8List? cachedFace = faceCropCache.get(face.faceID);
|
|
|
|
+ if (cachedFace != null) {
|
|
|
|
+ faceIdToCrop[face.faceID] = cachedFace;
|
|
|
|
+ } else {
|
|
|
|
+ final faceCropCacheFile = cachedFaceCropPath(face.faceID);
|
|
|
|
+ if ((await faceCropCacheFile.exists())) {
|
|
|
|
+ final data = await faceCropCacheFile.readAsBytes();
|
|
|
|
+ faceCropCache.put(face.faceID, data);
|
|
|
|
+ faceIdToCrop[face.faceID] = data;
|
|
|
|
+ } else {
|
|
|
|
+ facesWithoutCrops[face.faceID] = face.detection.box;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (facesWithoutCrops.isEmpty) {
|
|
|
|
+ return faceIdToCrop;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final result = await pool.withResource(
|
|
|
|
+ () async => await getFaceCrops(
|
|
|
|
+ widget.file,
|
|
|
|
+ facesWithoutCrops,
|
|
|
|
+ ),
|
|
|
|
+ );
|
|
|
|
+ if (result == null) {
|
|
|
|
+ return (faceIdToCrop.isEmpty) ? null : faceIdToCrop;
|
|
|
|
+ }
|
|
|
|
+ for (final entry in result.entries) {
|
|
|
|
+ final Uint8List? computedCrop = result[entry.key];
|
|
|
|
+ if (computedCrop != null) {
|
|
|
|
+ faceCropCache.put(entry.key, computedCrop);
|
|
|
|
+ final faceCropCacheFile = cachedFaceCropPath(entry.key);
|
|
|
|
+ faceCropCacheFile.writeAsBytes(computedCrop).ignore();
|
|
|
|
+ faceIdToCrop[entry.key] = computedCrop;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return (faceIdToCrop.isEmpty) ? null : faceIdToCrop;
|
|
|
|
+ } catch (e, s) {
|
|
|
|
+ dev.log(
|
|
|
|
+ "Error getting face crops for faceIDs: ${faces.map((face) => face.faceID).toList()}",
|
|
|
|
+ error: e,
|
|
|
|
+ stackTrace: s,
|
|
|
|
+ );
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|