Merge remote-tracking branch 'origin' into auth-deskicons

This commit is contained in:
Prateek Sunal 2024-05-20 21:18:11 +05:30
commit 3d91b548db
235 changed files with 19784 additions and 3885 deletions

6
.gitignore vendored
View file

@ -1,8 +1,6 @@
# Let folks use their custom .vscode settings
# Let folks use their custom editor settings
.vscode
.idea
# macOS
.DS_Store
.idea
.ente.authenticator.db
.ente.offline_authenticator.db

View file

@ -85,20 +85,16 @@ PODS:
- SDWebImage (5.19.2):
- SDWebImage/Core (= 5.19.2)
- SDWebImage/Core (5.19.2)
- Sentry/HybridSDK (8.21.0):
- SentryPrivate (= 8.21.0)
- sentry_flutter (0.0.1):
- Sentry/HybridSDK (8.25.0)
- sentry_flutter (7.20.1):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.21.0)
- SentryPrivate (8.21.0)
- Sentry/HybridSDK (= 8.25.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- smart_auth (0.0.1):
- Flutter
- sodium_libs (2.2.1):
- Flutter
- sqflite (0.0.3):
@ -115,7 +111,7 @@ PODS:
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- sqlite3 (~> 3.45.1)
- "sqlite3 (~> 3.45.3+1)"
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
@ -148,7 +144,6 @@ DEPENDENCIES:
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
@ -163,7 +158,6 @@ SPEC REPOS:
- ReachabilitySwift
- SDWebImage
- Sentry
- SentryPrivate
- sqlite3
- SwiftyGif
- Toast
@ -215,8 +209,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
smart_auth:
:path: ".symlinks/plugins/smart_auth/ios"
sodium_libs:
:path: ".symlinks/plugins/sodium_libs/ios"
sqflite:
@ -236,13 +228,13 @@ SPEC CHECKSUMS:
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
fluttertoast: 9f2f8e81bb5ce18facb9748d7855bf5a756fe3db
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
@ -253,16 +245,14 @@ SPEC CHECKSUMS:
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
ReachabilitySwift: 2128f3a8c9107e1ad33574c6e58e8285d460b149
SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a
Sentry: ebc12276bd17613a114ab359074096b6b3725203
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
Sentry: cd86fc55628f5b7c572cabe66cc8f95a9d2f165a
sentry_flutter: 4cb24c1055c556d7b27262ab2e179d1e5a0b9b0c
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 02d1f07eaaa01f80a1c16b4b31dfcbb3345ee01a
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
sqlite3_flutter_libs: 9bfe005308998aeca155330bbc2ea6dddf834a3b
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586

View file

@ -188,6 +188,8 @@
"recoveryKeySaveDescription": "Мы не храним этот ключ, пожалуйста, сохраните этот ключ в безопасном месте.",
"doThisLater": "Сделать позже",
"saveKey": "Сохранить ключ",
"save": "Сохранить",
"send": "Отправить",
"back": "Вернуться",
"createAccount": "Создать аккаунт",
"passwordStrength": "Мощность пароля: {passwordStrengthValue}",
@ -394,5 +396,13 @@
"signOutOtherDevices": "Выйти из других устройств",
"doNotSignOut": "Не выходить",
"hearUsWhereTitle": "Как вы узнали о Ente? (необязательно)",
"hearUsExplanation": "Будет полезно, если вы укажете, где нашли нас, так как мы не отслеживаем установки приложения"
"hearUsExplanation": "Будет полезно, если вы укажете, где нашли нас, так как мы не отслеживаем установки приложения",
"waitingForVerification": "Ожидание подтверждения...",
"developerSettingsWarning": "Вы уверены, что хотите изменить настройки разработчика?",
"developerSettings": "Настройки разработчика",
"serverEndpoint": "Конечная точка сервера",
"invalidEndpoint": "Неверная конечная точка",
"invalidEndpointMessage": "Извините, введенная вами конечная точка неверна. Пожалуйста, введите корректную конечную точку и повторите попытку.",
"endpointUpdatedMessage": "Конечная точка успешно обновлена",
"customEndpoint": "Подключено к {endpoint}"
}

View file

@ -29,20 +29,16 @@ PODS:
- ReachabilitySwift (5.2.2)
- screen_retriever (0.0.1):
- FlutterMacOS
- Sentry/HybridSDK (8.21.0):
- SentryPrivate (= 8.21.0)
- sentry_flutter (0.0.1):
- Sentry/HybridSDK (8.25.0)
- sentry_flutter (7.20.1):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.21.0)
- SentryPrivate (8.21.0)
- Sentry/HybridSDK (= 8.25.0)
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- smart_auth (0.0.1):
- FlutterMacOS
- sodium_libs (2.2.1):
- FlutterMacOS
- sqflite (0.0.3):
@ -59,7 +55,7 @@ PODS:
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- FlutterMacOS
- sqlite3 (~> 3.45.1)
- "sqlite3 (~> 3.45.3+1)"
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
@ -87,7 +83,6 @@ DEPENDENCIES:
- sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`)
- sodium_libs (from `Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
@ -100,7 +95,6 @@ SPEC REPOS:
- OrderedSet
- ReachabilitySwift
- Sentry
- SentryPrivate
- sqlite3
EXTERNAL SOURCES:
@ -136,8 +130,6 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
smart_auth:
:path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos
sodium_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos
sqflite:
@ -167,16 +159,14 @@ SPEC CHECKSUMS:
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
ReachabilitySwift: 2128f3a8c9107e1ad33574c6e58e8285d460b149
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
Sentry: ebc12276bd17613a114ab359074096b6b3725203
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
Sentry: cd86fc55628f5b7c572cabe66cc8f95a9d2f165a
sentry_flutter: 4cb24c1055c556d7b27262ab2e179d1e5a0b9b0c
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9
sodium_libs: d39bd76697736cb11ce4a0be73b9b4bc64466d6f
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 02d1f07eaaa01f80a1c16b4b31dfcbb3345ee01a
sqlite3_flutter_libs: 06a05802529659a272beac4ee1350bfec294f386
sqlite3_flutter_libs: 8d204ef443cf0d5c1c8b058044eab53f3943a9c5
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8

View file

@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.58+258
version: 3.0.2+302
publish_to: none
environment:

View file

@ -14,7 +14,7 @@
"build:ci": "yarn build-renderer && tsc",
"build:quick": "yarn build-renderer && yarn build-main:quick",
"dev": "concurrently --kill-others --success first --names 'main,rndr' \"yarn dev-main\" \"yarn dev-renderer\"",
"dev-main": "tsc && electron app/main.js",
"dev-main": "tsc && electron .",
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
"postinstall": "electron-builder install-app-deps",
"lint": "yarn prettier --check --log-level warn . && eslint --ext .ts src && yarn tsc",

View file

@ -241,7 +241,7 @@ const uniqueSavePath = (dirPath: string, fileName: string) => {
*
* @param webContents The renderer to configure.
*/
export const allowExternalLinks = (webContents: WebContents) => {
export const allowExternalLinks = (webContents: WebContents) =>
// By default, if the user were open a link, say
// https://github.com/ente-io/ente/discussions, then it would open a _new_
// BrowserWindow within our app.
@ -253,13 +253,37 @@ export const allowExternalLinks = (webContents: WebContents) => {
// Returning `action` "deny" accomplishes this.
webContents.setWindowOpenHandler(({ url }) => {
if (!url.startsWith(rendererURL)) {
// This does not work in Ubuntu currently: mailto links seem to just
// get ignored, and HTTP links open in the text editor instead of in
// the browser.
// https://github.com/electron/electron/issues/31485
void shell.openExternal(url);
return { action: "deny" };
} else {
return { action: "allow" };
}
});
};
/**
* Allow uploading to arbitrary S3 buckets.
*
* The files in the desktop app are served over the ente:// protocol. During
* testing or self-hosting, we might be using a S3 bucket that does not allow
* whitelisting a custom URI scheme. To avoid requiring the bucket to set an
* "Access-Control-Allow-Origin: *" or do a echo-back of `Origin`, we add a
* workaround here instead, intercepting the ACAO header and allowing `*`.
*/
export const allowAllCORSOrigins = (webContents: WebContents) =>
webContents.session.webRequest.onHeadersReceived(
({ responseHeaders }, callback) => {
const headers: NonNullable<typeof responseHeaders> = {};
for (const [key, value] of Object.entries(responseHeaders ?? {}))
if (key.toLowerCase() != "access-control-allow-origin")
headers[key] = value;
headers["Access-Control-Allow-Origin"] = ["*"];
callback({ responseHeaders: headers });
},
);
/**
* Add an icon for our app in the system tray.
@ -291,32 +315,18 @@ const setupTrayItem = (mainWindow: BrowserWindow) => {
/**
* Older versions of our app used to maintain a cache dir using the main
* process. This has been removed in favor of cache on the web layer.
* process. This has been removed in favor of cache on the web layer. Delete the
* old cache dir if it exists.
*
* Delete the old cache dir if it exists.
*
* This will happen in two phases. The cache had three subdirectories:
*
* - Two of them, "thumbs" and "files", will be removed now (v1.7.0, May 2024).
*
* - The third one, "face-crops" will be removed once we finish the face search
* changes. See: [Note: Legacy face crops].
*
* This migration code can be removed after some time once most people have
* upgraded to newer versions.
* Added May 2024, v1.7.0. This migration code can be removed after some time
* once most people have upgraded to newer versions.
*/
const deleteLegacyDiskCacheDirIfExists = async () => {
const removeIfExists = async (dirPath: string) => {
if (existsSync(dirPath)) {
log.info(`Removing legacy disk cache from ${dirPath}`);
await fs.rm(dirPath, { recursive: true });
}
};
// [Note: Getting the cache path]
//
// The existing code was passing "cache" as a parameter to getPath.
//
// However, "cache" is not a valid parameter to getPath. It works! (for
// However, "cache" is not a valid parameter to getPath. It works (for
// example, on macOS I get `~/Library/Caches`), but it is intentionally not
// documented as part of the public API:
//
@ -329,8 +339,8 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
// @ts-expect-error "cache" works but is not part of the public API.
const cacheDir = path.join(app.getPath("cache"), "ente");
if (existsSync(cacheDir)) {
await removeIfExists(path.join(cacheDir, "thumbs"));
await removeIfExists(path.join(cacheDir, "files"));
log.info(`Removing legacy disk cache from ${cacheDir}`);
await fs.rm(cacheDir, { recursive: true });
}
};
@ -390,8 +400,10 @@ const main = () => {
registerStreamProtocol();
// Configure the renderer's environment.
setDownloadPath(mainWindow.webContents);
allowExternalLinks(mainWindow.webContents);
const webContents = mainWindow.webContents;
setDownloadPath(webContents);
allowExternalLinks(webContents);
allowAllCORSOrigins(webContents);
// Start loading the renderer.
void mainWindow.loadURL(rendererURL);

View file

@ -24,7 +24,6 @@ import {
updateOnNextRestart,
} from "./services/app-update";
import {
legacyFaceCrop,
openDirectory,
openLogDirectory,
selectDirectory,
@ -43,10 +42,10 @@ import {
import { convertToJPEG, generateImageThumbnail } from "./services/image";
import { logout } from "./services/logout";
import {
clipImageEmbedding,
clipTextEmbeddingIfAvailable,
computeCLIPImageEmbedding,
computeCLIPTextEmbeddingIfAvailable,
} from "./services/ml-clip";
import { detectFaces, faceEmbeddings } from "./services/ml-face";
import { computeFaceEmbeddings, detectFaces } from "./services/ml-face";
import { encryptionKey, saveEncryptionKey } from "./services/store";
import {
clearPendingUploads,
@ -170,24 +169,22 @@ export const attachIPCHandlers = () => {
// - ML
ipcMain.handle("clipImageEmbedding", (_, jpegImageData: Uint8Array) =>
clipImageEmbedding(jpegImageData),
ipcMain.handle(
"computeCLIPImageEmbedding",
(_, jpegImageData: Uint8Array) =>
computeCLIPImageEmbedding(jpegImageData),
);
ipcMain.handle("clipTextEmbeddingIfAvailable", (_, text: string) =>
clipTextEmbeddingIfAvailable(text),
ipcMain.handle("computeCLIPTextEmbeddingIfAvailable", (_, text: string) =>
computeCLIPTextEmbeddingIfAvailable(text),
);
ipcMain.handle("detectFaces", (_, input: Float32Array) =>
detectFaces(input),
);
ipcMain.handle("faceEmbeddings", (_, input: Float32Array) =>
faceEmbeddings(input),
);
ipcMain.handle("legacyFaceCrop", (_, faceID: string) =>
legacyFaceCrop(faceID),
ipcMain.handle("computeFaceEmbeddings", (_, input: Float32Array) =>
computeFaceEmbeddings(input),
);
// - Upload

View file

@ -70,8 +70,9 @@ const logInfo = (...params: unknown[]) => {
const message = params
.map((p) => (typeof p == "string" ? p : util.inspect(p)))
.join(" ");
log.info(`[main] ${message}`);
if (isDev) console.log(`[info] ${message}`);
const m = `[info] ${message}`;
if (isDev) console.log(m);
log.info(`[main] ${m}`);
};
const logDebug = (param: () => unknown) => {

View file

@ -163,7 +163,7 @@ const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => {
};
/**
* Return the version of the desktop app
* Return the version of the desktop app.
*
* The return value is of the form `v1.2.3`.
*/

View file

@ -1,7 +1,5 @@
import { shell } from "electron/common";
import { app, dialog } from "electron/main";
import { existsSync } from "fs";
import fs from "node:fs/promises";
import path from "node:path";
import { posixPath } from "../utils/electron";
@ -53,14 +51,6 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath());
* "userData" directory. This is the **primary** place applications are meant to
* store user's data, e.g. various configuration files and saved state.
*
* During development, our app name is "Electron", so this'd be, for example,
* `~/Library/Application Support/Electron` if we run using `yarn dev`. For the
* packaged production app, our app name is "ente", so this would be:
*
* - Windows: `%APPDATA%\ente`, e.g. `C:\Users\<username>\AppData\Local\ente`
* - Linux: `~/.config/ente`
* - macOS: `~/Library/Application Support/ente`
*
* Note that Chromium also stores the browser state, e.g. localStorage or disk
* caches, in userData.
*
@ -73,21 +63,7 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath());
* "ente.log", it can be found at:
*
* - macOS: ~/Library/Logs/ente/ente.log (production)
* - macOS: ~/Library/Logs/Electron/ente.log (dev)
* - Linux: ~/.config/ente/logs/ente.log
* - Windows: %USERPROFILE%\AppData\Roaming\ente\logs\ente.log
*/
const logDirectoryPath = () => app.getPath("logs");
/**
* See: [Note: Legacy face crops]
*/
export const legacyFaceCrop = async (
faceID: string,
): Promise<Uint8Array | undefined> => {
// See: [Note: Getting the cache path]
// @ts-expect-error "cache" works but is not part of the public API.
const cacheDir = path.join(app.getPath("cache"), "ente");
const filePath = path.join(cacheDir, "face-crops", faceID);
return existsSync(filePath) ? await fs.readFile(filePath) : undefined;
};

View file

@ -3,7 +3,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import { CustomErrorMessage, type ZipItem } from "../../types/ipc";
import log from "../log";
import { execAsync, isDev } from "../utils/electron";
import {
deleteTempFileIgnoringErrors,
@ -93,9 +92,6 @@ export const generateImageThumbnail = async (
let thumbnail: Uint8Array;
do {
await execAsync(command);
// TODO(MR): release 1.7
// TODO(MR): imagemagick debugging. Remove me after verifying logs.
log.info(`Generated thumbnail using ${command.join(" ")}`);
thumbnail = new Uint8Array(await fs.readFile(outputFilePath));
quality -= 10;
command = generateImageThumbnailCommand(

View file

@ -11,7 +11,7 @@ import * as ort from "onnxruntime-node";
import Tokenizer from "../../thirdparty/clip-bpe-ts/mod";
import log from "../log";
import { writeStream } from "../stream";
import { ensure } from "../utils/common";
import { ensure, wait } from "../utils/common";
import { deleteTempFile, makeTempFilePath } from "../utils/temp";
import { makeCachedInferenceSession } from "./ml";
@ -20,7 +20,7 @@ const cachedCLIPImageSession = makeCachedInferenceSession(
351468764 /* 335.2 MB */,
);
export const clipImageEmbedding = async (jpegImageData: Uint8Array) => {
export const computeCLIPImageEmbedding = async (jpegImageData: Uint8Array) => {
const tempFilePath = await makeTempFilePath();
const imageStream = new Response(jpegImageData.buffer).body;
await writeStream(tempFilePath, ensure(imageStream));
@ -42,7 +42,7 @@ const clipImageEmbedding_ = async (jpegFilePath: string) => {
const results = await session.run(feeds);
log.debug(
() =>
`onnx/clip image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
`ONNX/CLIP image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
);
/* Need these model specific casts to type the result */
const imageEmbedding = ensure(results.output).data as Float32Array;
@ -140,21 +140,23 @@ const getTokenizer = () => {
return _tokenizer;
};
export const clipTextEmbeddingIfAvailable = async (text: string) => {
const sessionOrStatus = await Promise.race([
export const computeCLIPTextEmbeddingIfAvailable = async (text: string) => {
const sessionOrSkip = await Promise.race([
cachedCLIPTextSession(),
"downloading-model",
// Wait for a tick to get the session promise to resolved the first time
// this code runs on each app start (and the model has been downloaded).
wait(0).then(() => 1),
]);
// Don't wait for the download to complete
if (typeof sessionOrStatus == "string") {
// Don't wait for the download to complete.
if (typeof sessionOrSkip == "number") {
log.info(
"Ignoring CLIP text embedding request because model download is pending",
);
return undefined;
}
const session = sessionOrStatus;
const session = sessionOrSkip;
const t1 = Date.now();
const tokenizer = getTokenizer();
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
@ -165,7 +167,7 @@ export const clipTextEmbeddingIfAvailable = async (text: string) => {
const results = await session.run(feeds);
log.debug(
() =>
`onnx/clip text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
`ONNX/CLIP text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
);
const textEmbedding = ensure(results.output).data as Float32Array;
return normalizeEmbedding(textEmbedding);

View file

@ -23,7 +23,7 @@ export const detectFaces = async (input: Float32Array) => {
input: new ort.Tensor("float32", input, [1, 3, 640, 640]),
};
const results = await session.run(feeds);
log.debug(() => `onnx/yolo face detection took ${Date.now() - t} ms`);
log.debug(() => `ONNX/YOLO face detection took ${Date.now() - t} ms`);
return ensure(results.output).data;
};
@ -32,7 +32,7 @@ const cachedFaceEmbeddingSession = makeCachedInferenceSession(
5286998 /* 5 MB */,
);
export const faceEmbeddings = async (input: Float32Array) => {
export const computeFaceEmbeddings = async (input: Float32Array) => {
// Dimension of each face (alias)
const mobileFaceNetFaceSize = 112;
// Smaller alias
@ -45,7 +45,7 @@ export const faceEmbeddings = async (input: Float32Array) => {
const t = Date.now();
const feeds = { img_inputs: inputTensor };
const results = await session.run(feeds);
log.debug(() => `onnx/yolo face embedding took ${Date.now() - t} ms`);
log.debug(() => `ONNX/MFNT face embedding took ${Date.now() - t} ms`);
/* Need these model specific casts to extract and type the result */
return (results.embeddings as unknown as Record<string, unknown>)
.cpuData as Float32Array;

View file

@ -18,10 +18,7 @@ export const clearStores = () => {
* [Note: Safe storage keys]
*
* On macOS, `safeStorage` stores our data under a Keychain entry named
* "<app-name> Safe Storage". Which resolves to:
*
* - Electron Safe Storage (dev)
* - ente Safe Storage (prod)
* "<app-name> Safe Storage". In our case, "ente Safe Storage".
*/
export const saveEncryptionKey = (encryptionKey: string) => {
const encryptedKey = safeStorage.encryptString(encryptionKey);

View file

@ -13,3 +13,12 @@ export const ensure = <T>(v: T | null | undefined): T => {
if (v === undefined) throw new Error("Required value was not found");
return v;
};
/**
* Wait for {@link ms} milliseconds
*
* This function is a promisified `setTimeout`. It returns a promise that
* resolves after {@link ms} milliseconds.
*/
export const wait = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));

View file

@ -55,9 +55,7 @@ export const execAsync = async (command: string | string[]) => {
: command;
const startTime = Date.now();
const result = await execAsync_(escapedCommand);
log.debug(
() => `${escapedCommand} (${Math.round(Date.now() - startTime)} ms)`,
);
log.debug(() => `${escapedCommand} (${Date.now() - startTime} ms)`);
return result;
};

View file

@ -65,7 +65,7 @@ const selectDirectory = () => ipcRenderer.invoke("selectDirectory");
const logout = () => {
watchRemoveListeners();
ipcRenderer.send("logout");
return ipcRenderer.invoke("logout");
};
const encryptionKey = () => ipcRenderer.invoke("encryptionKey");
@ -153,20 +153,17 @@ const ffmpegExec = (
// - ML
const clipImageEmbedding = (jpegImageData: Uint8Array) =>
ipcRenderer.invoke("clipImageEmbedding", jpegImageData);
const computeCLIPImageEmbedding = (jpegImageData: Uint8Array) =>
ipcRenderer.invoke("computeCLIPImageEmbedding", jpegImageData);
const clipTextEmbeddingIfAvailable = (text: string) =>
ipcRenderer.invoke("clipTextEmbeddingIfAvailable", text);
const computeCLIPTextEmbeddingIfAvailable = (text: string) =>
ipcRenderer.invoke("computeCLIPTextEmbeddingIfAvailable", text);
const detectFaces = (input: Float32Array) =>
ipcRenderer.invoke("detectFaces", input);
const faceEmbeddings = (input: Float32Array) =>
ipcRenderer.invoke("faceEmbeddings", input);
const legacyFaceCrop = (faceID: string) =>
ipcRenderer.invoke("legacyFaceCrop", faceID);
const computeFaceEmbeddings = (input: Float32Array) =>
ipcRenderer.invoke("computeFaceEmbeddings", input);
// - Watch
@ -340,11 +337,10 @@ contextBridge.exposeInMainWorld("electron", {
// - ML
clipImageEmbedding,
clipTextEmbeddingIfAvailable,
computeCLIPImageEmbedding,
computeCLIPTextEmbeddingIfAvailable,
detectFaces,
faceEmbeddings,
legacyFaceCrop,
computeFaceEmbeddings,
// - Watch

View file

@ -43,7 +43,7 @@ android {
defaultConfig {
applicationId "io.ente.photos"
minSdkVersion 21
minSdkVersion 26
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@ -70,6 +70,10 @@ android {
dimension "default"
applicationIdSuffix ".dev"
}
face {
dimension "default"
applicationIdSuffix ".face"
}
playstore {
dimension "default"
}

View file

@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.ente.photos">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View file

@ -0,0 +1,4 @@
<resources>
<string name="app_name">ente face</string>
<string name="backup">backup face</string>
</resources>

View file

@ -1,91 +0,0 @@
unknown
person
bicycle
car
motorcycle
airplane
bus
train
truck
boat
traffic light
fire hydrant
unknown
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
unknown
backpack
umbrella
unknown
unknown
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
unknown
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
couch
potted plant
bed
unknown
dining table
unknown
unknown
toilet
unknown
tv
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
unknown
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

View file

@ -1,30 +0,0 @@
waterfall
snow
landscape
underwater
architecture
sunset / sunrise
blue sky
cloudy sky
greenery
autumn leaves
portrait
flower
night shot
stage concert
fireworks
candle light
neon lights
indoor
backlight
text documents
qr images
group portrait
computer screens
kids
dog
cat
macro
food
beach
mountain

View file

@ -6,6 +6,8 @@ PODS:
- connectivity_plus (0.0.1):
- Flutter
- FlutterMacOS
- dart_ui_isolate (0.0.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- file_saver (0.0.1):
@ -226,6 +228,7 @@ DEPENDENCIES:
- background_fetch (from `.symlinks/plugins/background_fetch/ios`)
- battery_info (from `.symlinks/plugins/battery_info/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- dart_ui_isolate (from `.symlinks/plugins/dart_ui_isolate/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
@ -302,6 +305,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/battery_info/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin"
dart_ui_isolate:
:path: ".symlinks/plugins/dart_ui_isolate/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_saver:
@ -397,6 +402,7 @@ SPEC CHECKSUMS:
background_fetch: 2319bf7e18237b4b269430b7f14d177c0df09c5a
battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: 91fefd38712feb9186ea8996af6cbdef41473442

View file

@ -293,6 +293,7 @@
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
"${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/dart_ui_isolate/dart_ui_isolate.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework",
"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
@ -374,6 +375,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/dart_ui_isolate.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",

View file

@ -65,9 +65,9 @@
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>FLTEnableImpeller</key>
<false />
<true />
<key>FLTEnableWideGamut</key>
<false/>
<true/>
<key>NSFaceIDUsageDescription</key>
<string>Please allow ente to lock itself with FaceID or TouchID</string>
<key>NSCameraUsageDescription</key>

View file

@ -19,6 +19,7 @@ import 'package:photos/db/upload_locks_db.dart';
import "package:photos/events/endpoint_updated_event.dart";
import 'package:photos/events/signed_in_event.dart';
import 'package:photos/events/user_logged_out_event.dart';
import "package:photos/face/db.dart";
import 'package:photos/models/key_attributes.dart';
import 'package:photos/models/key_gen_result.dart';
import 'package:photos/models/private_key_attributes.dart';
@ -187,6 +188,7 @@ class Configuration {
: null;
await CollectionsDB.instance.clearTable();
await MemoriesDB.instance.clearTable();
await FaceMLDataDB.instance.clearTable();
await UploadLocksDB.instance.clearTable();
await IgnoredFilesService.instance.reset();

View file

@ -99,6 +99,9 @@ const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=';
const localFileServer =
String.fromEnvironment("localFileServer", defaultValue: "");
const uploadTempFilePrefix = "upload_file_";
final tempDirCleanUpInterval = kDebugMode
? const Duration(seconds: 30).inMicroseconds

View file

@ -9,7 +9,7 @@ extension EntitiesDB on FilesDB {
List<LocalEntityData> data, {
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
}) async {
debugPrint("Inserting missing PathIDToLocalIDMapping");
debugPrint("entitiesDB: upsertEntities ${data.length} entities");
final db = await database;
var batch = db.batch();
int batchCounter = 0;
@ -62,4 +62,17 @@ extension EntitiesDB on FilesDB {
return LocalEntityData.fromJson(maps[i]);
});
}
Future<LocalEntityData?> getEntity(EntityType type, String id) async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(
"entities",
where: "type = ? AND id = ?",
whereArgs: [type.typeToString(), id],
);
if (maps.isEmpty) {
return null;
}
return LocalEntityData.fromJson(maps.first);
}
}

View file

@ -491,6 +491,18 @@ class FilesDB {
return convertToFiles(results)[0];
}
Future<EnteFile?> getAnyUploadedFile(int uploadedID) async {
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
'SELECT * FROM $filesTable WHERE $columnUploadedFileID = ?',
[uploadedID],
);
if (results.isEmpty) {
return null;
}
return convertToFiles(results)[0];
}
Future<Set<int>> getUploadedFileIDs(int collectionID) async {
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
@ -683,6 +695,17 @@ class FilesDB {
return files;
}
Future<List<EnteFile>> getAllFilesFromCollections(
Iterable<int> collectionID,
) async {
final db = await instance.sqliteAsyncDB;
final String sql =
'SELECT * FROM $filesTable WHERE $columnCollectionID IN (${collectionID.join(',')})';
final results = await db.getAll(sql);
final files = convertToFiles(results);
return files;
}
Future<List<EnteFile>> getNewFilesInCollection(
int collectionID,
int addedTime,
@ -1304,6 +1327,23 @@ class FilesDB {
return result;
}
Future<Map<int, int>> getFileIDToCreationTime() async {
final db = await instance.sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT $columnUploadedFileID, $columnCreationTime
FROM $filesTable
WHERE
($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1);
''',
);
final result = <int, int>{};
for (final row in rows) {
result[row[columnUploadedFileID] as int] = row[columnCreationTime] as int;
}
return result;
}
// getCollectionFileFirstOrLast returns the first or last uploaded file in
// the collection based on the given collectionID and the order.
Future<EnteFile?> getCollectionFileFirstOrLast(
@ -1643,13 +1683,14 @@ class FilesDB {
}
Future<List<int>> getOwnedFileIDs(int ownerID) async {
final db = await instance.database;
final results = await db.query(
filesTable,
columns: [columnUploadedFileID],
where:
'($columnOwnerID = $ownerID AND $columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)',
distinct: true,
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
'''
SELECT DISTINCT $columnUploadedFileID FROM $filesTable
WHERE ($columnOwnerID = ? AND $columnUploadedFileID IS NOT NULL AND
$columnUploadedFileID IS NOT -1)
''',
[ownerID],
);
final ids = <int>[];
for (final result in results) {
@ -1659,16 +1700,17 @@ class FilesDB {
}
Future<List<EnteFile>> getUploadedFiles(List<int> uploadedIDs) async {
final db = await instance.database;
final db = await instance.sqliteAsyncDB;
String inParam = "";
for (final id in uploadedIDs) {
inParam += "'" + id.toString() + "',";
}
inParam = inParam.substring(0, inParam.length - 1);
final results = await db.query(
filesTable,
where: '$columnUploadedFileID IN ($inParam)',
groupBy: columnUploadedFileID,
final results = await db.getAll(
'''
SELECT * FROM $filesTable WHERE $columnUploadedFileID IN ($inParam)
GROUP BY $columnUploadedFileID
''',
);
if (results.isEmpty) {
return <EnteFile>[];

View file

@ -26,4 +26,6 @@ enum EventType {
hide,
unhide,
coverChanged,
peopleChanged,
peopleClusterChanged,
}

View file

@ -0,0 +1,22 @@
import "package:photos/events/event.dart";
import "package:photos/models/file/file.dart";
class PeopleChangedEvent extends Event {
final List<EnteFile>? relevantFiles;
final PeopleEventType type;
final String source;
PeopleChangedEvent({
this.relevantFiles,
this.type = PeopleEventType.defaultType,
this.source = "",
});
@override
String get reason => '$runtimeType{type: ${type.name}, "via": $source}';
}
enum PeopleEventType {
defaultType,
removedFilesFromCluster,
}

View file

@ -0,0 +1,193 @@
import 'dart:math' as math show sin, cos, atan2, sqrt, pow;
import 'package:ml_linalg/linalg.dart';
extension SetVectorValues on Vector {
Vector setValues(int start, int end, Iterable<double> values) {
if (values.length > length) {
throw Exception('Values cannot be larger than vector');
} else if (end - start != values.length) {
throw Exception('Values must be same length as range');
} else if (start < 0 || end > length) {
throw Exception('Range must be within vector');
}
final tempList = toList();
tempList.replaceRange(start, end, values);
final newVector = Vector.fromList(tempList);
return newVector;
}
}
extension SetMatrixValues on Matrix {
Matrix setSubMatrix(
int startRow,
int endRow,
int startColumn,
int endColumn,
Iterable<Iterable<double>> values,
) {
if (values.length > rowCount) {
throw Exception('New values cannot have more rows than original matrix');
} else if (values.elementAt(0).length > columnCount) {
throw Exception(
'New values cannot have more columns than original matrix',
);
} else if (endRow - startRow != values.length) {
throw Exception('Values (number of rows) must be same length as range');
} else if (endColumn - startColumn != values.elementAt(0).length) {
throw Exception(
'Values (number of columns) must be same length as range',
);
} else if (startRow < 0 ||
endRow > rowCount ||
startColumn < 0 ||
endColumn > columnCount) {
throw Exception('Range must be within matrix');
}
final tempList = asFlattenedList
.toList(); // You need `.toList()` here to make sure the list is growable, otherwise `replaceRange` will throw an error
for (var i = startRow; i < endRow; i++) {
tempList.replaceRange(
i * columnCount + startColumn,
i * columnCount + endColumn,
values.elementAt(i).toList(),
);
}
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix setValues(
int startRow,
int endRow,
int startColumn,
int endColumn,
Iterable<double> values,
) {
if ((startRow - endRow) * (startColumn - endColumn) != values.length) {
throw Exception('Values must be same length as range');
} else if (startRow < 0 ||
endRow > rowCount ||
startColumn < 0 ||
endColumn > columnCount) {
throw Exception('Range must be within matrix');
}
final tempList = asFlattenedList
.toList(); // You need `.toList()` here to make sure the list is growable, otherwise `replaceRange` will throw an error
var index = 0;
for (var i = startRow; i < endRow; i++) {
for (var j = startColumn; j < endColumn; j++) {
tempList[i * columnCount + j] = values.elementAt(index);
index++;
}
}
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix setValue(int row, int column, double value) {
if (row < 0 || row > rowCount || column < 0 || column > columnCount) {
throw Exception('Index must be within range of matrix');
}
final tempList = asFlattenedList;
tempList[row * columnCount + column] = value;
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix appendRow(List<double> row) {
final oldNumberOfRows = rowCount;
final oldNumberOfColumns = columnCount;
if (row.length != oldNumberOfColumns) {
throw Exception('Row must have same number of columns as matrix');
}
final flatListMatrix = asFlattenedList;
flatListMatrix.addAll(row);
return Matrix.fromFlattenedList(
flatListMatrix,
oldNumberOfRows + 1,
oldNumberOfColumns,
);
}
}
extension MatrixCalculations on Matrix {
double determinant() {
final int length = rowCount;
if (length != columnCount) {
throw Exception('Matrix must be square');
}
if (length == 1) {
return this[0][0];
} else if (length == 2) {
return this[0][0] * this[1][1] - this[0][1] * this[1][0];
} else {
throw Exception('Determinant for Matrix larger than 2x2 not implemented');
}
}
/// Computes the singular value decomposition of a matrix, using https://lucidar.me/en/mathematics/singular-value-decomposition-of-a-2x2-matrix/ as reference, but with slightly different signs for the second columns of U and V
Map<String, dynamic> svd() {
if (rowCount != 2 || columnCount != 2) {
throw Exception('Matrix must be 2x2');
}
final a = this[0][0];
final b = this[0][1];
final c = this[1][0];
final d = this[1][1];
// Computation of U matrix
final tempCalc = a * a + b * b - c * c - d * d;
final theta = 0.5 * math.atan2(2 * a * c + 2 * b * d, tempCalc);
final U = Matrix.fromList([
[math.cos(theta), math.sin(theta)],
[math.sin(theta), -math.cos(theta)],
]);
// Computation of S matrix
// ignore: non_constant_identifier_names
final S1 = a * a + b * b + c * c + d * d;
// ignore: non_constant_identifier_names
final S2 =
math.sqrt(math.pow(tempCalc, 2) + 4 * math.pow(a * c + b * d, 2));
final sigma1 = math.sqrt((S1 + S2) / 2);
final sigma2 = math.sqrt((S1 - S2) / 2);
final S = Vector.fromList([sigma1, sigma2]);
// Computation of V matrix
final tempCalc2 = a * a - b * b + c * c - d * d;
final phi = 0.5 * math.atan2(2 * a * b + 2 * c * d, tempCalc2);
final s11 = (a * math.cos(theta) + c * math.sin(theta)) * math.cos(phi) +
(b * math.cos(theta) + d * math.sin(theta)) * math.sin(phi);
final s22 = (a * math.sin(theta) - c * math.cos(theta)) * math.sin(phi) +
(-b * math.sin(theta) + d * math.cos(theta)) * math.cos(phi);
final V = Matrix.fromList([
[s11.sign * math.cos(phi), s22.sign * math.sin(phi)],
[s11.sign * math.sin(phi), -s22.sign * math.cos(phi)],
]);
return {
'U': U,
'S': S,
'V': V,
};
}
int matrixRank() {
final svdResult = svd();
final Vector S = svdResult['S']!;
final rank = S.toList().where((element) => element > 1e-10).length;
return rank;
}
}
extension TransformMatrix on Matrix {
List<List<double>> to2DList() {
final List<List<double>> outerList = [];
for (var i = 0; i < rowCount; i++) {
final innerList = this[i].toList();
outerList.add(innerList);
}
return outerList;
}
}

View file

@ -23,4 +23,9 @@ class EnteWatch extends Stopwatch {
reset();
previousElapsed = 0;
}
void stopWithLog(String msg) {
log(msg);
stop();
}
}

1018
mobile/lib/face/db.dart Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
// Faces Table Fields & Schema Queries
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
const facesTable = 'faces';
const fileIDColumn = 'file_id';
const faceIDColumn = 'face_id';
const faceDetectionColumn = 'detection';
const faceEmbeddingBlob = 'eBlob';
const faceScore = 'score';
const faceBlur = 'blur';
const isSideways = 'is_sideways';
const imageWidth = 'width';
const imageHeight = 'height';
const faceClusterId = 'cluster_id';
const mlVersionColumn = 'ml_version';
const createFacesTable = '''CREATE TABLE IF NOT EXISTS $facesTable (
$fileIDColumn INTEGER NOT NULL,
$faceIDColumn TEXT NOT NULL UNIQUE,
$faceDetectionColumn TEXT NOT NULL,
$faceEmbeddingBlob BLOB NOT NULL,
$faceScore REAL NOT NULL,
$faceBlur REAL NOT NULL DEFAULT $kLapacianDefault,
$isSideways INTEGER NOT NULL DEFAULT 0,
$imageHeight INTEGER NOT NULL DEFAULT 0,
$imageWidth INTEGER NOT NULL DEFAULT 0,
$mlVersionColumn INTEGER NOT NULL DEFAULT -1,
PRIMARY KEY($fileIDColumn, $faceIDColumn)
);
''';
const deleteFacesTable = 'DROP TABLE IF EXISTS $facesTable';
// End of Faces Table Fields & Schema Queries
//##region Face Clusters Table Fields & Schema Queries
const faceClustersTable = 'face_clusters';
const fcClusterID = 'cluster_id';
const fcFaceId = 'face_id';
// fcClusterId & fcFaceId are the primary keys and fcClusterId is a foreign key to faces table
const createFaceClustersTable = '''
CREATE TABLE IF NOT EXISTS $faceClustersTable (
$fcFaceId TEXT NOT NULL,
$fcClusterID INTEGER NOT NULL,
PRIMARY KEY($fcFaceId)
);
''';
// -- Creating a non-unique index on clusterID for query optimization
const fcClusterIDIndex =
'''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);''';
const dropFaceClustersTable = 'DROP TABLE IF EXISTS $faceClustersTable';
//##endregion
// People Table Fields & Schema Queries
const personTable = 'person';
const deletePersonTable = 'DROP TABLE IF EXISTS $personTable';
//End People Table Fields & Schema Queries
// Clusters Table Fields & Schema Queries
const clusterPersonTable = 'cluster_person';
const personIdColumn = 'person_id';
const clusterIDColumn = 'cluster_id';
const createClusterPersonTable = '''
CREATE TABLE IF NOT EXISTS $clusterPersonTable (
$personIdColumn TEXT NOT NULL,
$clusterIDColumn INTEGER NOT NULL,
PRIMARY KEY($personIdColumn, $clusterIDColumn)
);
''';
const dropClusterPersonTable = 'DROP TABLE IF EXISTS $clusterPersonTable';
// End Clusters Table Fields & Schema Queries
/// Cluster Summary Table Fields & Schema Queries
const clusterSummaryTable = 'cluster_summary';
const avgColumn = 'avg';
const countColumn = 'count';
const createClusterSummaryTable = '''
CREATE TABLE IF NOT EXISTS $clusterSummaryTable (
$clusterIDColumn INTEGER NOT NULL,
$avgColumn BLOB NOT NULL,
$countColumn INTEGER NOT NULL,
PRIMARY KEY($clusterIDColumn)
);
''';
const dropClusterSummaryTable = 'DROP TABLE IF EXISTS $clusterSummaryTable';
/// End Cluster Summary Table Fields & Schema Queries
/// notPersonFeedback Table Fields & Schema Queries
const notPersonFeedback = 'not_person_feedback';
const createNotPersonFeedbackTable = '''
CREATE TABLE IF NOT EXISTS $notPersonFeedback (
$personIdColumn TEXT NOT NULL,
$clusterIDColumn INTEGER NOT NULL,
PRIMARY KEY($personIdColumn, $clusterIDColumn)
);
''';
const dropNotPersonFeedbackTable = 'DROP TABLE IF EXISTS $notPersonFeedback';
// End Clusters Table Fields & Schema Queries

View file

@ -0,0 +1,57 @@
import "dart:convert";
import 'package:photos/face/db_fields.dart';
import "package:photos/face/model/detection.dart";
import "package:photos/face/model/face.dart";
import "package:photos/generated/protos/ente/common/vector.pb.dart";
import "package:photos/models/ml/ml_versions.dart";
int boolToSQLInt(bool? value, {bool defaultValue = false}) {
final bool v = value ?? defaultValue;
if (v == false) {
return 0;
} else {
return 1;
}
}
bool sqlIntToBool(int? value, {bool defaultValue = false}) {
final int v = value ?? (defaultValue ? 1 : 0);
if (v == 0) {
return false;
} else {
return true;
}
}
Map<String, dynamic> mapRemoteToFaceDB(Face face) {
return {
faceIDColumn: face.faceID,
fileIDColumn: face.fileID,
faceDetectionColumn: json.encode(face.detection.toJson()),
faceEmbeddingBlob: EVector(
values: face.embedding,
).writeToBuffer(),
faceScore: face.score,
faceBlur: face.blur,
isSideways: face.detection.faceIsSideways() ? 1 : 0,
mlVersionColumn: faceMlVersion,
imageWidth: face.fileInfo?.imageWidth ?? 0,
imageHeight: face.fileInfo?.imageHeight ?? 0,
};
}
Face mapRowToFace(Map<String, dynamic> row) {
return Face(
row[faceIDColumn] as String,
row[fileIDColumn] as int,
EVector.fromBuffer(row[faceEmbeddingBlob] as List<int>).values,
row[faceScore] as double,
Detection.fromJson(json.decode(row[faceDetectionColumn] as String)),
row[faceBlur] as double,
fileInfo: FileInfo(
imageWidth: row[imageWidth] as int,
imageHeight: row[imageHeight] as int,
),
);
}

View file

@ -0,0 +1,43 @@
/// Bounding box of a face.
///
/// [xMin] and [yMin] are the coordinates of the top left corner of the box, and
/// [width] and [height] are the width and height of the box.
///
/// WARNING: All values are relative to the original image size, so in the range [0, 1].
class FaceBox {
final double xMin;
final double yMin;
final double width;
final double height;
FaceBox({
required this.xMin,
required this.yMin,
required this.width,
required this.height,
});
factory FaceBox.fromJson(Map<String, dynamic> json) {
return FaceBox(
xMin: (json['xMin'] is int
? (json['xMin'] as int).toDouble()
: json['xMin'] as double),
yMin: (json['yMin'] is int
? (json['yMin'] as int).toDouble()
: json['yMin'] as double),
width: (json['width'] is int
? (json['width'] as int).toDouble()
: json['width'] as double),
height: (json['height'] is int
? (json['height'] as int).toDouble()
: json['height'] as double),
);
}
Map<String, dynamic> toJson() => {
'xMin': xMin,
'yMin': yMin,
'width': width,
'height': height,
};
}

View file

@ -0,0 +1,120 @@
import "dart:math" show min, max;
import "package:photos/face/model/box.dart";
import "package:photos/face/model/landmark.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
/// Stores the face detection data, notably the bounding box and landmarks.
///
/// - Bounding box: [FaceBox] with xMin, yMin (so top left corner), width, height
/// - Landmarks: list of [Landmark]s, namely leftEye, rightEye, nose, leftMouth, rightMouth
///
/// WARNING: All coordinates are relative to the image size, so in the range [0, 1]!
class Detection {
FaceBox box;
List<Landmark> landmarks;
Detection({
required this.box,
required this.landmarks,
});
bool get isEmpty => box.width == 0 && box.height == 0 && landmarks.isEmpty;
// empty box
Detection.empty()
: box = FaceBox(
xMin: 0,
yMin: 0,
width: 0,
height: 0,
),
landmarks = [];
Map<String, dynamic> toJson() => {
'box': box.toJson(),
'landmarks': landmarks.map((x) => x.toJson()).toList(),
};
factory Detection.fromJson(Map<String, dynamic> json) {
return Detection(
box: FaceBox.fromJson(json['box'] as Map<String, dynamic>),
landmarks: List<Landmark>.from(
json['landmarks']
.map((x) => Landmark.fromJson(x as Map<String, dynamic>)),
),
);
}
int getFaceArea(int imageWidth, int imageHeight) {
return (box.width * imageWidth * box.height * imageHeight).toInt();
}
FaceDirection getFaceDirection() {
if (isEmpty) {
return FaceDirection.straight;
}
final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y];
final leftMouth = [landmarks[3].x, landmarks[3].y];
final rightMouth = [landmarks[4].x, landmarks[4].y];
final double eyeDistanceX = (rightEye[0] - leftEye[0]).abs();
final double eyeDistanceY = (rightEye[1] - leftEye[1]).abs();
final double mouthDistanceY = (rightMouth[1] - leftMouth[1]).abs();
final bool faceIsUpright =
(max(leftEye[1], rightEye[1]) + 0.5 * eyeDistanceY < nose[1]) &&
(nose[1] + 0.5 * mouthDistanceY < min(leftMouth[1], rightMouth[1]));
final bool noseStickingOutLeft = (nose[0] < min(leftEye[0], rightEye[0])) &&
(nose[0] < min(leftMouth[0], rightMouth[0]));
final bool noseStickingOutRight =
(nose[0] > max(leftEye[0], rightEye[0])) &&
(nose[0] > max(leftMouth[0], rightMouth[0]));
final bool noseCloseToLeftEye =
(nose[0] - leftEye[0]).abs() < 0.2 * eyeDistanceX;
final bool noseCloseToRightEye =
(nose[0] - rightEye[0]).abs() < 0.2 * eyeDistanceX;
// if (faceIsUpright && (noseStickingOutLeft || noseCloseToLeftEye)) {
if (noseStickingOutLeft || (faceIsUpright && noseCloseToLeftEye)) {
return FaceDirection.left;
// } else if (faceIsUpright && (noseStickingOutRight || noseCloseToRightEye)) {
} else if (noseStickingOutRight || (faceIsUpright && noseCloseToRightEye)) {
return FaceDirection.right;
}
return FaceDirection.straight;
}
bool faceIsSideways() {
if (isEmpty) {
return false;
}
final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y];
final leftMouth = [landmarks[3].x, landmarks[3].y];
final rightMouth = [landmarks[4].x, landmarks[4].y];
final double eyeDistanceX = (rightEye[0] - leftEye[0]).abs();
final double eyeDistanceY = (rightEye[1] - leftEye[1]).abs();
final double mouthDistanceY = (rightMouth[1] - leftMouth[1]).abs();
final bool faceIsUpright =
(max(leftEye[1], rightEye[1]) + 0.5 * eyeDistanceY < nose[1]) &&
(nose[1] + 0.5 * mouthDistanceY < min(leftMouth[1], rightMouth[1]));
final bool noseStickingOutLeft =
(nose[0] < min(leftEye[0], rightEye[0]) - 0.5 * eyeDistanceX) &&
(nose[0] < min(leftMouth[0], rightMouth[0]));
final bool noseStickingOutRight =
(nose[0] > max(leftEye[0], rightEye[0]) + 0.5 * eyeDistanceX) &&
(nose[0] > max(leftMouth[0], rightMouth[0]));
return faceIsUpright && (noseStickingOutLeft || noseStickingOutRight);
}
}

View file

@ -0,0 +1,25 @@
class Dimensions {
final int width;
final int height;
const Dimensions({required this.width, required this.height});
@override
String toString() {
return 'Dimensions(width: $width, height: $height})';
}
Map<String, int> toJson() {
return {
'width': width,
'height': height,
};
}
factory Dimensions.fromJson(Map<String, dynamic> json) {
return Dimensions(
width: json['width'] as int,
height: json['height'] as int,
);
}
}

View file

@ -0,0 +1,85 @@
import "package:photos/face/model/detection.dart";
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
import "package:photos/services/machine_learning/face_ml/face_ml_result.dart";
// FileInfo contains the image width and height of the image the face was detected in.
class FileInfo {
int? imageWidth;
int? imageHeight;
FileInfo({
this.imageWidth,
this.imageHeight,
});
}
class Face {
final String faceID;
final List<double> embedding;
Detection detection;
final double score;
final double blur;
///#region Local DB fields
// This is not stored on the server, using it for local DB row
FileInfo? fileInfo;
final int fileID;
///#endregion
bool get isBlurry => blur < kLaplacianHardThreshold;
bool get hasHighScore => score > kMinimumQualityFaceScore;
bool get isHighQuality => (!isBlurry) && hasHighScore;
int area({int? w, int? h}) {
return detection.getFaceArea(
fileInfo?.imageWidth ?? w ?? 0,
fileInfo?.imageHeight ?? h ?? 0,
);
}
Face(
this.faceID,
this.fileID,
this.embedding,
this.score,
this.detection,
this.blur, {
this.fileInfo,
});
factory Face.empty(int fileID, {bool error = false}) {
return Face(
"$fileID-0",
fileID,
<double>[],
error ? -1.0 : 0.0,
Detection.empty(),
0.0,
);
}
factory Face.fromJson(Map<String, dynamic> json) {
final String faceID = json['faceID'] as String;
final int fileID = getFileIdFromFaceId(faceID);
return Face(
faceID,
fileID,
List<double>.from((json['embedding'] ?? json['embeddings']) as List),
json['score'] as double,
Detection.fromJson(json['detection'] as Map<String, dynamic>),
// high value means t
(json['blur'] ?? kLapacianDefault) as double,
);
}
// Note: Keep the information in toJson minimum. Keep in sync with desktop.
// Derive fields like fileID from other values whenever possible
Map<String, dynamic> toJson() => {
'faceID': faceID,
'embedding': embedding,
'detection': detection.toJson(),
'score': score,
'blur': blur,
};
}

View file

@ -0,0 +1,33 @@
/// Landmark coordinate data.
///
/// WARNING: All coordinates are relative to the image size, so in the range [0, 1]!
class Landmark {
double x;
double y;
Landmark({
required this.x,
required this.y,
});
Map<String, dynamic> toJson() => {
'x': x,
'y': y,
};
factory Landmark.fromJson(Map<String, dynamic> json) {
return Landmark(
x: (json['x'] is int
? (json['x'] as int).toDouble()
: json['x'] as double),
y: (json['y'] is int
? (json['y'] as int).toDouble()
: json['y'] as double),
);
}
@override
toString() {
return '(x: ${x.toStringAsFixed(4)}, y: ${y.toStringAsFixed(4)})';
}
}

View file

@ -0,0 +1,139 @@
// PersonEntity represents information about a Person in the context of FaceClustering that is stored.
// On the remote server, the PersonEntity is stored as {Entity} with type person.
// On the device, this information is stored as [LocalEntityData] with type person.
import "package:flutter/foundation.dart";
class PersonEntity {
final String remoteID;
final PersonData data;
PersonEntity(
this.remoteID,
this.data,
);
// copyWith
PersonEntity copyWith({
String? remoteID,
PersonData? data,
}) {
return PersonEntity(
remoteID ?? this.remoteID,
data ?? this.data,
);
}
}
class ClusterInfo {
final int id;
final Set<String> faces;
ClusterInfo({
required this.id,
required this.faces,
});
// toJson
Map<String, dynamic> toJson() => {
'id': id,
'faces': faces.toList(),
};
// from Json
factory ClusterInfo.fromJson(Map<String, dynamic> json) {
return ClusterInfo(
id: json['id'] as int,
faces: (json['faces'] as List<dynamic>).map((e) => e as String).toSet(),
);
}
}
class PersonData {
final String name;
final bool isHidden;
String? avatarFaceId;
List<ClusterInfo>? assigned = List<ClusterInfo>.empty();
List<ClusterInfo>? rejected = List<ClusterInfo>.empty();
final String? birthDate;
bool hasAvatar() => avatarFaceId != null;
bool get isIgnored =>
(name.isEmpty || name == '(hidden)' || name == '(ignored)');
PersonData({
required this.name,
this.assigned,
this.rejected,
this.avatarFaceId,
this.isHidden = false,
this.birthDate,
});
// copyWith
PersonData copyWith({
String? name,
List<ClusterInfo>? assigned,
String? avatarFaceId,
bool? isHidden,
int? version,
String? birthDate,
}) {
return PersonData(
name: name ?? this.name,
assigned: assigned ?? this.assigned,
avatarFaceId: avatarFaceId ?? this.avatarFaceId,
isHidden: isHidden ?? this.isHidden,
birthDate: birthDate ?? this.birthDate,
);
}
void logStats() {
if (kDebugMode == false) return;
// log number of assigned and rejected clusters and total number of faces in each cluster
final StringBuffer sb = StringBuffer();
sb.writeln('Person: $name');
int assignedCount = 0;
for (final a in (assigned ?? <ClusterInfo>[])) {
assignedCount += a.faces.length;
}
sb.writeln('Assigned: ${assigned?.length} withFaces $assignedCount');
sb.writeln('Rejected: ${rejected?.length}');
if (assigned != null) {
for (var cluster in assigned!) {
sb.writeln('Cluster: ${cluster.id} - ${cluster.faces.length}');
}
}
debugPrint(sb.toString());
}
// toJson
Map<String, dynamic> toJson() => {
'name': name,
'assigned': assigned?.map((e) => e.toJson()).toList(),
'rejected': rejected?.map((e) => e.toJson()).toList(),
'avatarFaceId': avatarFaceId,
'isHidden': isHidden,
'birthDate': birthDate,
};
// fromJson
factory PersonData.fromJson(Map<String, dynamic> json) {
final assigned = (json['assigned'] == null || json['assigned'].length == 0)
? <ClusterInfo>[]
: List<ClusterInfo>.from(
json['assigned'].map((x) => ClusterInfo.fromJson(x)),
);
final rejected = (json['rejected'] == null || json['rejected'].length == 0)
? <ClusterInfo>[]
: List<ClusterInfo>.from(
json['rejected'].map((x) => ClusterInfo.fromJson(x)),
);
return PersonData(
name: json['name'] as String,
assigned: assigned,
rejected: rejected,
avatarFaceId: json['avatarFaceId'] as String?,
isHidden: json['isHidden'] as bool? ?? false,
birthDate: json['birthDate'] as String?,
);
}
}

View file

@ -34,6 +34,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addViewers": m1,
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"createCollaborativeLink":
MessageLookupByLibrary.simpleMessage("Create collaborative link"),
@ -44,7 +46,14 @@ class MessageLookup extends MessageLookupByLibrary {
"editsToLocationWillOnlyBeSeenWithinEnte":
MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"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"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
"longPressAnEmailToVerifyEndToEndEncryption":
@ -55,6 +64,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"),
"moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -227,6 +227,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Ich verstehe, dass ich meine Daten verlieren kann, wenn ich mein Passwort vergesse, da meine Daten <underline>Ende-zu-Ende-verschlüsselt</underline> sind."),
"activeSessions":
MessageLookupByLibrary.simpleMessage("Aktive Sitzungen"),
"addAName": MessageLookupByLibrary.simpleMessage("Add a name"),
"addANewEmail": MessageLookupByLibrary.simpleMessage(
"Neue E-Mail-Adresse hinzufügen"),
"addCollaborator":
@ -435,6 +436,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Nach Aufnahmezeit gruppieren"),
"clubByFileName":
MessageLookupByLibrary.simpleMessage("Nach Dateiname gruppieren"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code eingelöst"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -675,6 +678,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Passwort eingeben"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Gib ein Passwort ein, mit dem wir deine Daten verschlüsseln können"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Gib den Weiterempfehlungs-Code ein"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
@ -699,6 +704,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Protokolle exportieren"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Daten exportieren"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gesichter"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Der Code konnte nicht aktiviert werden"),
@ -738,11 +747,14 @@ class MessageLookup extends MessageLookupByLibrary {
"filesBackedUpInAlbum": m23,
"filesDeleted":
MessageLookupByLibrary.simpleMessage("Dateien gelöscht"),
"findPeopleByName": MessageLookupByLibrary.simpleMessage(
"Find people quickly by searching by name"),
"flip": MessageLookupByLibrary.simpleMessage("Spiegeln"),
"forYourMemories":
MessageLookupByLibrary.simpleMessage("Als Erinnerung"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Passwort vergessen"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Kostenlos hinzugefügter Speicherplatz"),
"freeStorageOnReferralSuccess": m24,
@ -1164,6 +1176,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Teilnehmer entfernen"),
"removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink":
MessageLookupByLibrary.simpleMessage("Öffentlichen Link entfernen"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -132,7 +132,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Please talk to ${providerName} support if you were charged";
static String m38(endDate) =>
"Free trial valid till ${endDate}.\nYou can purchase a paid plan afterwards.";
"Free trial valid till ${endDate}.\nYou can choose a paid plan afterwards.";
static String m39(toEmail) => "Please email us at ${toEmail}";
@ -225,6 +225,7 @@ class MessageLookup extends MessageLookupByLibrary {
"I understand that if I lose my password, I may lose my data since my data is <underline>end-to-end encrypted</underline>."),
"activeSessions":
MessageLookupByLibrary.simpleMessage("Active sessions"),
"addAName": MessageLookupByLibrary.simpleMessage("Add a name"),
"addANewEmail": MessageLookupByLibrary.simpleMessage("Add a new email"),
"addCollaborator":
MessageLookupByLibrary.simpleMessage("Add collaborator"),
@ -434,6 +435,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Club by capture time"),
"clubByFileName":
MessageLookupByLibrary.simpleMessage("Club by file name"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code applied"),
"codeCopiedToClipboard":
@ -675,6 +678,8 @@ class MessageLookup extends MessageLookupByLibrary {
"enterPassword": MessageLookupByLibrary.simpleMessage("Enter password"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Enter a password we can use to encrypt your data"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode":
MessageLookupByLibrary.simpleMessage("Enter referral code"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
@ -697,6 +702,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Export your data"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Faces"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Failed to apply code"),
@ -736,11 +745,14 @@ class MessageLookup extends MessageLookupByLibrary {
"filesDeleted": MessageLookupByLibrary.simpleMessage("Files deleted"),
"filesSavedToGallery":
MessageLookupByLibrary.simpleMessage("Files saved to gallery"),
"findPeopleByName":
MessageLookupByLibrary.simpleMessage("Find people quickly by name"),
"flip": MessageLookupByLibrary.simpleMessage("Flip"),
"forYourMemories":
MessageLookupByLibrary.simpleMessage("for your memories"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Forgot password"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Free storage claimed"),
"freeStorageOnReferralSuccess": m24,
@ -1022,6 +1034,7 @@ class MessageLookup extends MessageLookupByLibrary {
"paymentFailedTalkToProvider": m37,
"pendingItems": MessageLookupByLibrary.simpleMessage("Pending items"),
"pendingSync": MessageLookupByLibrary.simpleMessage("Pending sync"),
"people": MessageLookupByLibrary.simpleMessage("People"),
"peopleUsingYourCode":
MessageLookupByLibrary.simpleMessage("People using your code"),
"permDeleteWarning": MessageLookupByLibrary.simpleMessage(
@ -1151,6 +1164,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Remove participant"),
"removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink":
MessageLookupByLibrary.simpleMessage("Remove public link"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(
@ -1208,8 +1223,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Add descriptions like \"#trip\" in photo info to quickly find them here"),
"searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
"Search by a date, month or year"),
"searchFaceEmptySection":
MessageLookupByLibrary.simpleMessage("Find all photos of a person"),
"searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
"Persons will be shown here once indexing is done"),
"searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("File types and names"),
"searchHint1":

View file

@ -367,6 +367,8 @@ class MessageLookup extends MessageLookupByLibrary {
"close": MessageLookupByLibrary.simpleMessage("Cerrar"),
"clubByCaptureTime": MessageLookupByLibrary.simpleMessage(
"Agrupar por tiempo de captura"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Código aplicado"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -585,6 +587,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Introduzca contraseña"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Introduzca una contraseña que podamos usar para cifrar sus datos"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Ingresar código de referencia"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
@ -609,6 +613,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exportar registros"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exportar tus datos"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Error al aplicar el código"),
"failedToCancel":
@ -647,6 +655,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("para tus recuerdos"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Olvidé mi contraseña"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Almacenamiento gratuito reclamado"),
"freeStorageOnReferralSuccess": m24,
@ -997,6 +1006,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Quitar participante"),
"removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink":
MessageLookupByLibrary.simpleMessage("Quitar enlace público"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -425,6 +425,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Grouper par durée"),
"clubByFileName":
MessageLookupByLibrary.simpleMessage("Grouper par nom de fichier"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code appliqué"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -665,6 +667,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Saisissez le mot de passe"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Entrez un mot de passe que nous pouvons utiliser pour chiffrer vos données"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Entrez le code de parrainage"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
@ -688,6 +692,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exportez vos données"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Visages"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossible d\'appliquer le code"),
@ -732,6 +740,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("pour vos souvenirs"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Mot de passe oublié"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Stockage gratuit réclamé"),
"freeStorageOnReferralSuccess": m24,
@ -1129,6 +1138,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Supprimer le participant"),
"removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink":
MessageLookupByLibrary.simpleMessage("Supprimer le lien public"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -411,6 +411,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Club per tempo di cattura"),
"clubByFileName":
MessageLookupByLibrary.simpleMessage("Unisci per nome file"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Codice applicato"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -644,6 +646,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Inserisci password"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Inserisci una password per criptare i tuoi dati"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Inserisci il codice di invito"),
"enterThe6digitCodeFromnyourAuthenticatorApp":
@ -665,6 +669,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Questo link è scaduto. Si prega di selezionare un nuovo orario di scadenza o disabilitare la scadenza del link."),
"exportLogs": MessageLookupByLibrary.simpleMessage("Esporta log"),
"exportYourData": MessageLookupByLibrary.simpleMessage("Esporta dati"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossibile applicare il codice"),
"failedToCancel":
@ -704,6 +712,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("per i tuoi ricordi"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Password dimenticata"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Spazio gratuito richiesto"),
"freeStorageOnReferralSuccess": m24,
@ -1090,6 +1099,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant":
MessageLookupByLibrary.simpleMessage("Rimuovi partecipante"),
"removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink":
MessageLookupByLibrary.simpleMessage("Rimuovi link pubblico"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -34,6 +34,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addViewers": m1,
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"createCollaborativeLink":
MessageLookupByLibrary.simpleMessage("Create collaborative link"),
@ -44,7 +46,14 @@ class MessageLookup extends MessageLookupByLibrary {
"editsToLocationWillOnlyBeSeenWithinEnte":
MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"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"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
"longPressAnEmailToVerifyEndToEndEncryption":
@ -55,6 +64,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"),
"moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -447,6 +447,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Samenvoegen op tijd"),
"clubByFileName":
MessageLookupByLibrary.simpleMessage("Samenvoegen op bestandsnaam"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code toegepast"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -723,6 +725,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Logboek exporteren"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gezichten"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Code toepassen mislukt"),
@ -771,6 +777,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("voor uw herinneringen"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Wachtwoord vergeten"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Gratis opslag geclaimd"),
"freeStorageOnReferralSuccess": m24,

View file

@ -39,6 +39,8 @@ class MessageLookup extends MessageLookupByLibrary {
"cancel": MessageLookupByLibrary.simpleMessage("Avbryt"),
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"confirmAccountDeletion":
MessageLookupByLibrary.simpleMessage("Bekreft sletting av konto"),
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
@ -57,12 +59,19 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"),
"email": MessageLookupByLibrary.simpleMessage("E-post"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterValidEmail": MessageLookupByLibrary.simpleMessage(
"Vennligst skriv inn en gyldig e-postadresse."),
"enterYourEmailAddress": MessageLookupByLibrary.simpleMessage(
"Skriv inn e-postadressen din"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"invalidEmailAddress":
MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
@ -77,6 +86,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"),
"moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -49,6 +49,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Zmień hasło"),
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
"Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
"Kod został skopiowany do schowka"),
"confirm": MessageLookupByLibrary.simpleMessage("Potwierdź"),
@ -101,6 +103,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Wprowadź hasło, którego możemy użyć do zaszyfrowania Twoich danych"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterValidEmail": MessageLookupByLibrary.simpleMessage(
"Podaj poprawny adres e-mail."),
"enterYourEmailAddress":
@ -109,10 +113,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Wprowadź hasło"),
"enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
"Wprowadź swój klucz odzyskiwania"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Informacja zwrotna"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Nie pamiętam hasła"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage(
"Generowanie kluczy szyfrujących..."),
"howItWorks": MessageLookupByLibrary.simpleMessage("Jak to działa"),
@ -166,6 +175,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Jeśli zapomnisz hasła, jedynym sposobem odzyskania danych jest ten klucz."),
"recoverySuccessful":
MessageLookupByLibrary.simpleMessage("Odzyskano pomyślnie!"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"resendEmail":
MessageLookupByLibrary.simpleMessage("Wyślij e-mail ponownie"),
"resetPasswordTitle":

View file

@ -445,6 +445,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Agrupar por tempo de captura"),
"clubByFileName": MessageLookupByLibrary.simpleMessage(
"Agrupar pelo nome de arquivo"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Código aplicado"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -587,7 +589,7 @@ class MessageLookup extends MessageLookupByLibrary {
"descriptions": MessageLookupByLibrary.simpleMessage("Descrições"),
"deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"),
"designedToOutlive":
MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"),
MessageLookupByLibrary.simpleMessage("Feito para ter longevidade"),
"details": MessageLookupByLibrary.simpleMessage("Detalhes"),
"devAccountChanged": MessageLookupByLibrary.simpleMessage(
"A conta de desenvolvedor que usamos para publicar o Ente na App Store foi alterada. Por esse motivo, você precisará fazer entrar novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável."),
@ -714,6 +716,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Exportar logs"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exportar seus dados"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Rostos"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"),
@ -760,6 +766,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("para suas memórias"),
"forgotPassword":
MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Armazenamento gratuito reivindicado"),
"freeStorageOnReferralSuccess": m24,

View file

@ -382,6 +382,8 @@ class MessageLookup extends MessageLookupByLibrary {
"close": MessageLookupByLibrary.simpleMessage("关闭"),
"clubByCaptureTime": MessageLookupByLibrary.simpleMessage("按拍摄时间分组"),
"clubByFileName": MessageLookupByLibrary.simpleMessage("按文件名排序"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": MessageLookupByLibrary.simpleMessage("代码已应用"),
"codeCopiedToClipboard":
MessageLookupByLibrary.simpleMessage("代码已复制到剪贴板"),
@ -543,7 +545,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emailVerificationToggle":
MessageLookupByLibrary.simpleMessage("电子邮件验证"),
"emailYourLogs": MessageLookupByLibrary.simpleMessage("通过电子邮件发送您的日志"),
"empty": MessageLookupByLibrary.simpleMessage(""),
"empty": MessageLookupByLibrary.simpleMessage(""),
"emptyTrash": MessageLookupByLibrary.simpleMessage("要清空回收站吗?"),
"enableMaps": MessageLookupByLibrary.simpleMessage("启用地图"),
"enableMapsDesc": MessageLookupByLibrary.simpleMessage(
@ -592,6 +594,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("此链接已过期。请选择新的过期时间或禁用链接有效期。"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("人脸"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法使用此代码"),
"failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"),
@ -626,6 +632,7 @@ class MessageLookup extends MessageLookupByLibrary {
"flip": MessageLookupByLibrary.simpleMessage("上下翻转"),
"forYourMemories": MessageLookupByLibrary.simpleMessage("为您的回忆"),
"forgotPassword": MessageLookupByLibrary.simpleMessage("忘记密码"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage("已领取的免费存储"),
"freeStorageOnReferralSuccess": m24,
"freeStorageSpace": m25,

View file

@ -4034,10 +4034,10 @@ class S {
);
}
/// `Free trial valid till {endDate}.\nYou can purchase a paid plan afterwards.`
/// `Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.`
String playStoreFreeTrialValidTill(Object endDate) {
return Intl.message(
'Free trial valid till $endDate.\nYou can purchase a paid plan afterwards.',
'Free trial valid till $endDate.\nYou can choose a paid plan afterwards.',
name: 'playStoreFreeTrialValidTill',
desc: '',
args: [endDate],
@ -6969,10 +6969,10 @@ class S {
);
}
/// `Find all photos of a person`
/// `Persons will be shown here once indexing is done`
String get searchFaceEmptySection {
return Intl.message(
'Find all photos of a person',
'Persons will be shown here once indexing is done',
name: 'searchFaceEmptySection',
desc: '',
args: [],
@ -8168,6 +8168,16 @@ class S {
);
}
/// `People`
String get people {
return Intl.message(
'People',
name: 'people',
desc: '',
args: [],
);
}
/// `Contents`
String get contents {
return Intl.message(
@ -8388,26 +8398,6 @@ class S {
);
}
/// `Auto pair`
String get autoPair {
return Intl.message(
'Auto pair',
name: 'autoPair',
desc: '',
args: [],
);
}
/// `Pair with PIN`
String get pairWithPin {
return Intl.message(
'Pair with PIN',
name: 'pairWithPin',
desc: '',
args: [],
);
}
/// `Device not found`
String get deviceNotFound {
return Intl.message(
@ -8468,6 +8458,26 @@ class S {
);
}
/// `Add a name`
String get addAName {
return Intl.message(
'Add a name',
name: 'addAName',
desc: '',
args: [],
);
}
/// `Find people quickly by name`
String get findPeopleByName {
return Intl.message(
'Find people quickly by name',
name: 'findPeopleByName',
desc: '',
args: [],
);
}
/// `{count, plural, zero {Add viewer} one {Add viewer} other {Add viewers}}`
String addViewers(num count) {
return Intl.plural(
@ -8594,6 +8604,26 @@ class S {
);
}
/// `Enter person name`
String get enterPersonName {
return Intl.message(
'Enter person name',
name: 'enterPersonName',
desc: '',
args: [],
);
}
/// `Remove person label`
String get removePersonLabel {
return Intl.message(
'Remove person label',
name: 'removePersonLabel',
desc: '',
args: [],
);
}
/// `Auto pair works only with devices that support Chromecast.`
String get autoPairDesc {
return Intl.message(
@ -8703,6 +8733,66 @@ class S {
args: [],
);
}
/// `Auto pair`
String get autoPair {
return Intl.message(
'Auto pair',
name: 'autoPair',
desc: '',
args: [],
);
}
/// `Pair with PIN`
String get pairWithPin {
return Intl.message(
'Pair with PIN',
name: 'pairWithPin',
desc: '',
args: [],
);
}
/// `Face recognition`
String get faceRecognition {
return Intl.message(
'Face recognition',
name: 'faceRecognition',
desc: '',
args: [],
);
}
/// `Please note that this will result in a higher bandwidth and battery usage until all items are indexed.`
String get faceRecognitionIndexingDescription {
return Intl.message(
'Please note that this will result in a higher bandwidth and battery usage until all items are indexed.',
name: 'faceRecognitionIndexingDescription',
desc: '',
args: [],
);
}
/// `Found faces`
String get foundFaces {
return Intl.message(
'Found faces',
name: 'foundFaces',
desc: '',
args: [],
);
}
/// `Clustering progress`
String get clusteringProgress {
return Intl.message(
'Clustering progress',
name: 'clusteringProgress',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View file

@ -0,0 +1,111 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// CenterBox is a box where x,y is the center of the box
class CenterBox extends $pb.GeneratedMessage {
factory CenterBox({
$core.double? x,
$core.double? y,
$core.double? height,
$core.double? width,
}) {
final $result = create();
if (x != null) {
$result.x = x;
}
if (y != null) {
$result.y = y;
}
if (height != null) {
$result.height = height;
}
if (width != null) {
$result.width = width;
}
return $result;
}
CenterBox._() : super();
factory CenterBox.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory CenterBox.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CenterBox', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
..a<$core.double>(3, _omitFieldNames ? '' : 'height', $pb.PbFieldType.OF)
..a<$core.double>(4, _omitFieldNames ? '' : 'width', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
CenterBox clone() => CenterBox()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
CenterBox copyWith(void Function(CenterBox) updates) => super.copyWith((message) => updates(message as CenterBox)) as CenterBox;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static CenterBox create() => CenterBox._();
CenterBox createEmptyInstance() => create();
static $pb.PbList<CenterBox> createRepeated() => $pb.PbList<CenterBox>();
@$core.pragma('dart2js:noInline')
static CenterBox getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CenterBox>(create);
static CenterBox? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
@$pb.TagNumber(3)
$core.double get height => $_getN(2);
@$pb.TagNumber(3)
set height($core.double v) { $_setFloat(2, v); }
@$pb.TagNumber(3)
$core.bool hasHeight() => $_has(2);
@$pb.TagNumber(3)
void clearHeight() => clearField(3);
@$pb.TagNumber(4)
$core.double get width => $_getN(3);
@$pb.TagNumber(4)
set width($core.double v) { $_setFloat(3, v); }
@$pb.TagNumber(4)
$core.bool hasWidth() => $_has(3);
@$pb.TagNumber(4)
void clearWidth() => clearField(4);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,38 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use centerBoxDescriptor instead')
const CenterBox$json = {
'1': 'CenterBox',
'2': [
{'1': 'x', '3': 1, '4': 1, '5': 2, '9': 0, '10': 'x', '17': true},
{'1': 'y', '3': 2, '4': 1, '5': 2, '9': 1, '10': 'y', '17': true},
{'1': 'height', '3': 3, '4': 1, '5': 2, '9': 2, '10': 'height', '17': true},
{'1': 'width', '3': 4, '4': 1, '5': 2, '9': 3, '10': 'width', '17': true},
],
'8': [
{'1': '_x'},
{'1': '_y'},
{'1': '_height'},
{'1': '_width'},
],
};
/// Descriptor for `CenterBox`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List centerBoxDescriptor = $convert.base64Decode(
'CglDZW50ZXJCb3gSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBARIbCgZoZW'
'lnaHQYAyABKAJIAlIGaGVpZ2h0iAEBEhkKBXdpZHRoGAQgASgCSANSBXdpZHRoiAEBQgQKAl94'
'QgQKAl95QgkKB19oZWlnaHRCCAoGX3dpZHRo');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'box.pb.dart';

View file

@ -0,0 +1,83 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// EPoint is a point in 2D space
class EPoint extends $pb.GeneratedMessage {
factory EPoint({
$core.double? x,
$core.double? y,
}) {
final $result = create();
if (x != null) {
$result.x = x;
}
if (y != null) {
$result.y = y;
}
return $result;
}
EPoint._() : super();
factory EPoint.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EPoint.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EPoint', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EPoint clone() => EPoint()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EPoint copyWith(void Function(EPoint) updates) => super.copyWith((message) => updates(message as EPoint)) as EPoint;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EPoint create() => EPoint._();
EPoint createEmptyInstance() => create();
static $pb.PbList<EPoint> createRepeated() => $pb.PbList<EPoint>();
@$core.pragma('dart2js:noInline')
static EPoint getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EPoint>(create);
static EPoint? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,33 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use ePointDescriptor instead')
const EPoint$json = {
'1': 'EPoint',
'2': [
{'1': 'x', '3': 1, '4': 1, '5': 2, '9': 0, '10': 'x', '17': true},
{'1': 'y', '3': 2, '4': 1, '5': 2, '9': 1, '10': 'y', '17': true},
],
'8': [
{'1': '_x'},
{'1': '_y'},
],
};
/// Descriptor for `EPoint`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List ePointDescriptor = $convert.base64Decode(
'CgZFUG9pbnQSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBAUIECgJfeEIECg'
'JfeQ==');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'point.pb.dart';

View file

@ -0,0 +1,64 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// Vector is generic message for dealing with lists of doubles
/// It should ideally be used independently and not as a submessage
class EVector extends $pb.GeneratedMessage {
factory EVector({
$core.Iterable<$core.double>? values,
}) {
final $result = create();
if (values != null) {
$result.values.addAll(values);
}
return $result;
}
EVector._() : super();
factory EVector.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory EVector.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EVector', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
..p<$core.double>(1, _omitFieldNames ? '' : 'values', $pb.PbFieldType.KD)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
EVector clone() => EVector()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
EVector copyWith(void Function(EVector) updates) => super.copyWith((message) => updates(message as EVector)) as EVector;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EVector create() => EVector._();
EVector createEmptyInstance() => create();
static $pb.PbList<EVector> createRepeated() => $pb.PbList<EVector>();
@$core.pragma('dart2js:noInline')
static EVector getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EVector>(create);
static EVector? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.double> get values => $_getList(0);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,27 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use eVectorDescriptor instead')
const EVector$json = {
'1': 'EVector',
'2': [
{'1': 'values', '3': 1, '4': 3, '5': 1, '10': 'values'},
],
};
/// Descriptor for `EVector`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List eVectorDescriptor = $convert.base64Decode(
'CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'vector.pb.dart';

View file

@ -0,0 +1,169 @@
//
// Generated code. Do not modify.
// source: ente/ml/face.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
import '../common/box.pb.dart' as $0;
import '../common/point.pb.dart' as $1;
class Detection extends $pb.GeneratedMessage {
factory Detection({
$0.CenterBox? box,
$1.EPoint? landmarks,
}) {
final $result = create();
if (box != null) {
$result.box = box;
}
if (landmarks != null) {
$result.landmarks = landmarks;
}
return $result;
}
Detection._() : super();
factory Detection.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Detection.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Detection', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..aOM<$0.CenterBox>(1, _omitFieldNames ? '' : 'box', subBuilder: $0.CenterBox.create)
..aOM<$1.EPoint>(2, _omitFieldNames ? '' : 'landmarks', subBuilder: $1.EPoint.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Detection clone() => Detection()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Detection copyWith(void Function(Detection) updates) => super.copyWith((message) => updates(message as Detection)) as Detection;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Detection create() => Detection._();
Detection createEmptyInstance() => create();
static $pb.PbList<Detection> createRepeated() => $pb.PbList<Detection>();
@$core.pragma('dart2js:noInline')
static Detection getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Detection>(create);
static Detection? _defaultInstance;
@$pb.TagNumber(1)
$0.CenterBox get box => $_getN(0);
@$pb.TagNumber(1)
set box($0.CenterBox v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasBox() => $_has(0);
@$pb.TagNumber(1)
void clearBox() => clearField(1);
@$pb.TagNumber(1)
$0.CenterBox ensureBox() => $_ensure(0);
@$pb.TagNumber(2)
$1.EPoint get landmarks => $_getN(1);
@$pb.TagNumber(2)
set landmarks($1.EPoint v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasLandmarks() => $_has(1);
@$pb.TagNumber(2)
void clearLandmarks() => clearField(2);
@$pb.TagNumber(2)
$1.EPoint ensureLandmarks() => $_ensure(1);
}
class Face extends $pb.GeneratedMessage {
factory Face({
$core.String? id,
Detection? detection,
$core.double? confidence,
}) {
final $result = create();
if (id != null) {
$result.id = id;
}
if (detection != null) {
$result.detection = detection;
}
if (confidence != null) {
$result.confidence = confidence;
}
return $result;
}
Face._() : super();
factory Face.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Face.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Face', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'id')
..aOM<Detection>(2, _omitFieldNames ? '' : 'detection', subBuilder: Detection.create)
..a<$core.double>(3, _omitFieldNames ? '' : 'confidence', $pb.PbFieldType.OF)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Face clone() => Face()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Face copyWith(void Function(Face) updates) => super.copyWith((message) => updates(message as Face)) as Face;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Face create() => Face._();
Face createEmptyInstance() => create();
static $pb.PbList<Face> createRepeated() => $pb.PbList<Face>();
@$core.pragma('dart2js:noInline')
static Face getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Face>(create);
static Face? _defaultInstance;
@$pb.TagNumber(1)
$core.String get id => $_getSZ(0);
@$pb.TagNumber(1)
set id($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearId() => clearField(1);
@$pb.TagNumber(2)
Detection get detection => $_getN(1);
@$pb.TagNumber(2)
set detection(Detection v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasDetection() => $_has(1);
@$pb.TagNumber(2)
void clearDetection() => clearField(2);
@$pb.TagNumber(2)
Detection ensureDetection() => $_ensure(1);
@$pb.TagNumber(3)
$core.double get confidence => $_getN(2);
@$pb.TagNumber(3)
set confidence($core.double v) { $_setFloat(2, v); }
@$pb.TagNumber(3)
$core.bool hasConfidence() => $_has(2);
@$pb.TagNumber(3)
void clearConfidence() => clearField(3);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/ml/face.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,55 @@
//
// Generated code. Do not modify.
// source: ente/ml/face.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use detectionDescriptor instead')
const Detection$json = {
'1': 'Detection',
'2': [
{'1': 'box', '3': 1, '4': 1, '5': 11, '6': '.ente.common.CenterBox', '9': 0, '10': 'box', '17': true},
{'1': 'landmarks', '3': 2, '4': 1, '5': 11, '6': '.ente.common.EPoint', '9': 1, '10': 'landmarks', '17': true},
],
'8': [
{'1': '_box'},
{'1': '_landmarks'},
],
};
/// Descriptor for `Detection`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List detectionDescriptor = $convert.base64Decode(
'CglEZXRlY3Rpb24SLQoDYm94GAEgASgLMhYuZW50ZS5jb21tb24uQ2VudGVyQm94SABSA2JveI'
'gBARI2CglsYW5kbWFya3MYAiABKAsyEy5lbnRlLmNvbW1vbi5FUG9pbnRIAVIJbGFuZG1hcmtz'
'iAEBQgYKBF9ib3hCDAoKX2xhbmRtYXJrcw==');
@$core.Deprecated('Use faceDescriptor instead')
const Face$json = {
'1': 'Face',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'id', '17': true},
{'1': 'detection', '3': 2, '4': 1, '5': 11, '6': '.ente.ml.Detection', '9': 1, '10': 'detection', '17': true},
{'1': 'confidence', '3': 3, '4': 1, '5': 2, '9': 2, '10': 'confidence', '17': true},
],
'8': [
{'1': '_id'},
{'1': '_detection'},
{'1': '_confidence'},
],
};
/// Descriptor for `Face`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List faceDescriptor = $convert.base64Decode(
'CgRGYWNlEhMKAmlkGAEgASgJSABSAmlkiAEBEjUKCWRldGVjdGlvbhgCIAEoCzISLmVudGUubW'
'wuRGV0ZWN0aW9uSAFSCWRldGVjdGlvbogBARIjCgpjb25maWRlbmNlGAMgASgCSAJSCmNvbmZp'
'ZGVuY2WIAQFCBQoDX2lkQgwKCl9kZXRlY3Rpb25CDQoLX2NvbmZpZGVuY2U=');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/ml/face.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'face.pb.dart';

View file

@ -0,0 +1,179 @@
//
// Generated code. Do not modify.
// source: ente/ml/fileml.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
import 'face.pb.dart' as $2;
class FileML extends $pb.GeneratedMessage {
factory FileML({
$fixnum.Int64? id,
$core.Iterable<$core.double>? clip,
}) {
final $result = create();
if (id != null) {
$result.id = id;
}
if (clip != null) {
$result.clip.addAll(clip);
}
return $result;
}
FileML._() : super();
factory FileML.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileML.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileML', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'id')
..p<$core.double>(2, _omitFieldNames ? '' : 'clip', $pb.PbFieldType.KD)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
FileML clone() => FileML()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileML copyWith(void Function(FileML) updates) => super.copyWith((message) => updates(message as FileML)) as FileML;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static FileML create() => FileML._();
FileML createEmptyInstance() => create();
static $pb.PbList<FileML> createRepeated() => $pb.PbList<FileML>();
@$core.pragma('dart2js:noInline')
static FileML getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileML>(create);
static FileML? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get id => $_getI64(0);
@$pb.TagNumber(1)
set id($fixnum.Int64 v) { $_setInt64(0, v); }
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearId() => clearField(1);
@$pb.TagNumber(2)
$core.List<$core.double> get clip => $_getList(1);
}
class FileFaces extends $pb.GeneratedMessage {
factory FileFaces({
$core.Iterable<$2.Face>? faces,
$core.int? height,
$core.int? width,
$core.int? version,
$core.String? error,
}) {
final $result = create();
if (faces != null) {
$result.faces.addAll(faces);
}
if (height != null) {
$result.height = height;
}
if (width != null) {
$result.width = width;
}
if (version != null) {
$result.version = version;
}
if (error != null) {
$result.error = error;
}
return $result;
}
FileFaces._() : super();
factory FileFaces.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory FileFaces.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileFaces', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
..pc<$2.Face>(1, _omitFieldNames ? '' : 'faces', $pb.PbFieldType.PM, subBuilder: $2.Face.create)
..a<$core.int>(2, _omitFieldNames ? '' : 'height', $pb.PbFieldType.O3)
..a<$core.int>(3, _omitFieldNames ? '' : 'width', $pb.PbFieldType.O3)
..a<$core.int>(4, _omitFieldNames ? '' : 'version', $pb.PbFieldType.O3)
..aOS(5, _omitFieldNames ? '' : 'error')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
FileFaces clone() => FileFaces()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
FileFaces copyWith(void Function(FileFaces) updates) => super.copyWith((message) => updates(message as FileFaces)) as FileFaces;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static FileFaces create() => FileFaces._();
FileFaces createEmptyInstance() => create();
static $pb.PbList<FileFaces> createRepeated() => $pb.PbList<FileFaces>();
@$core.pragma('dart2js:noInline')
static FileFaces getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileFaces>(create);
static FileFaces? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$2.Face> get faces => $_getList(0);
@$pb.TagNumber(2)
$core.int get height => $_getIZ(1);
@$pb.TagNumber(2)
set height($core.int v) { $_setSignedInt32(1, v); }
@$pb.TagNumber(2)
$core.bool hasHeight() => $_has(1);
@$pb.TagNumber(2)
void clearHeight() => clearField(2);
@$pb.TagNumber(3)
$core.int get width => $_getIZ(2);
@$pb.TagNumber(3)
set width($core.int v) { $_setSignedInt32(2, v); }
@$pb.TagNumber(3)
$core.bool hasWidth() => $_has(2);
@$pb.TagNumber(3)
void clearWidth() => clearField(3);
@$pb.TagNumber(4)
$core.int get version => $_getIZ(3);
@$pb.TagNumber(4)
set version($core.int v) { $_setSignedInt32(3, v); }
@$pb.TagNumber(4)
$core.bool hasVersion() => $_has(3);
@$pb.TagNumber(4)
void clearVersion() => clearField(4);
@$pb.TagNumber(5)
$core.String get error => $_getSZ(4);
@$pb.TagNumber(5)
set error($core.String v) { $_setString(4, v); }
@$pb.TagNumber(5)
$core.bool hasError() => $_has(4);
@$pb.TagNumber(5)
void clearError() => clearField(5);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/ml/fileml.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import

View file

@ -0,0 +1,57 @@
//
// Generated code. Do not modify.
// source: ente/ml/fileml.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use fileMLDescriptor instead')
const FileML$json = {
'1': 'FileML',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 3, '9': 0, '10': 'id', '17': true},
{'1': 'clip', '3': 2, '4': 3, '5': 1, '10': 'clip'},
],
'8': [
{'1': '_id'},
],
};
/// Descriptor for `FileML`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List fileMLDescriptor = $convert.base64Decode(
'CgZGaWxlTUwSEwoCaWQYASABKANIAFICaWSIAQESEgoEY2xpcBgCIAMoAVIEY2xpcEIFCgNfaW'
'Q=');
@$core.Deprecated('Use fileFacesDescriptor instead')
const FileFaces$json = {
'1': 'FileFaces',
'2': [
{'1': 'faces', '3': 1, '4': 3, '5': 11, '6': '.ente.ml.Face', '10': 'faces'},
{'1': 'height', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'height', '17': true},
{'1': 'width', '3': 3, '4': 1, '5': 5, '9': 1, '10': 'width', '17': true},
{'1': 'version', '3': 4, '4': 1, '5': 5, '9': 2, '10': 'version', '17': true},
{'1': 'error', '3': 5, '4': 1, '5': 9, '9': 3, '10': 'error', '17': true},
],
'8': [
{'1': '_height'},
{'1': '_width'},
{'1': '_version'},
{'1': '_error'},
],
};
/// Descriptor for `FileFaces`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List fileFacesDescriptor = $convert.base64Decode(
'CglGaWxlRmFjZXMSIwoFZmFjZXMYASADKAsyDS5lbnRlLm1sLkZhY2VSBWZhY2VzEhsKBmhlaW'
'dodBgCIAEoBUgAUgZoZWlnaHSIAQESGQoFd2lkdGgYAyABKAVIAVIFd2lkdGiIAQESHQoHdmVy'
'c2lvbhgEIAEoBUgCUgd2ZXJzaW9uiAEBEhkKBWVycm9yGAUgASgJSANSBWVycm9yiAEBQgkKB1'
'9oZWlnaHRCCAoGX3dpZHRoQgoKCF92ZXJzaW9uQggKBl9lcnJvcg==');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/ml/fileml.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'fileml.pb.dart';

View file

@ -18,5 +18,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -1187,6 +1187,8 @@
"changeLocationOfSelectedItems": "Standort der gewählten Elemente ändern?",
"editsToLocationWillOnlyBeSeenWithinEnte": "Änderungen des Standorts werden nur in ente sichtbar sein",
"cleanUncategorized": "Unkategorisiert leeren",
"addAName": "Add a name",
"findPeopleByName": "Find people quickly by searching by name",
"cleanUncategorizedDescription": "Entferne alle Dateien von \"Unkategorisiert\" die in anderen Alben vorhanden sind",
"waitingForVerification": "Warte auf Bestätigung...",
"passkey": "Passkey",
@ -1204,5 +1206,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -569,7 +569,7 @@
"freeTrialValidTill": "Free trial valid till {endDate}",
"validTill": "Valid till {endDate}",
"addOnValidTill": "Your {storageAmount} add-on is valid till {endDate}",
"playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can purchase a paid plan afterwards.",
"playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.",
"subWillBeCancelledOn": "Your subscription will be cancelled on {endDate}",
"subscription": "Subscription",
"paymentDetails": "Payment details",
@ -987,7 +987,7 @@
"fileTypesAndNames": "File types and names",
"location": "Location",
"moments": "Moments",
"searchFaceEmptySection": "Find all photos of a person",
"searchFaceEmptySection": "Persons will be shown here once indexing is done",
"searchDatesEmptySection": "Search by a date, month or year",
"searchLocationEmptySection": "Group photos that are taken within some radius of a photo",
"searchPeopleEmptySection": "Invite people, and you'll see all photos shared by them here",
@ -1171,6 +1171,7 @@
}
},
"faces": "Faces",
"people": "People",
"contents": "Contents",
"addNew": "Add new",
"@addNew": {
@ -1196,14 +1197,14 @@
"verifyPasskey": "Verify passkey",
"playOnTv": "Play album on TV",
"pair": "Pair",
"autoPair": "Auto pair",
"pairWithPin": "Pair with PIN",
"deviceNotFound": "Device not found",
"castInstruction": "Visit cast.ente.io on the device you want to pair.\n\nEnter the code below to play the album on your TV.",
"deviceCodeHint": "Enter the code",
"joinDiscord": "Join Discord",
"locations": "Locations",
"descriptions": "Descriptions",
"addAName": "Add a name",
"findPeopleByName": "Find people quickly by name",
"addViewers": "{count, plural, zero {Add viewer} one {Add viewer} other {Add viewers}}",
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
@ -1216,6 +1217,8 @@
"customEndpoint": "Connected to {endpoint}",
"createCollaborativeLink": "Create collaborative link",
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"autoPairDesc": "Auto pair works only with devices that support Chromecast.",
"manualPairDesc": "Pair with PIN works with any screen you wish to view your album on.",
"connectToDevice": "Connect to device",
@ -1226,5 +1229,11 @@
"stopCastingBody": "Do you want to stop casting?",
"castIPMismatchTitle": "Failed to cast album",
"castIPMismatchBody": "Please make sure you are on the same network as the TV.",
"pairingComplete": "Pairing complete"
"pairingComplete": "Pairing complete",
"autoPair": "Auto pair",
"pairWithPin": "Pair with PIN",
"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"
}

View file

@ -980,5 +980,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -1161,5 +1161,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -1123,5 +1123,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -18,5 +18,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -1226,5 +1226,9 @@
"stopCastingBody": "Wil je stoppen met casten?",
"castIPMismatchTitle": "Album casten mislukt",
"castIPMismatchBody": "Zorg ervoor dat je op hetzelfde netwerk zit als de tv.",
"pairingComplete": "Koppeling voltooid"
"pairingComplete": "Koppeling voltooid",
"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"
}

View file

@ -32,5 +32,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -119,5 +119,11 @@
"addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}",
"longPressAnEmailToVerifyEndToEndEncryption": "Long press an email to verify end to end encryption.",
"createCollaborativeLink": "Create collaborative link",
"search": "Search"
"search": "Search",
"enterPersonName": "Enter person name",
"removePersonLabel": "Remove person label",
"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"
}

View file

@ -1226,5 +1226,9 @@
"stopCastingBody": "Você quer parar a transmissão?",
"castIPMismatchTitle": "Falha ao transmitir álbum",
"castIPMismatchBody": "Certifique-se de estar na mesma rede que a TV.",
"pairingComplete": "Pareamento concluído"
"pairingComplete": "Pareamento concluído",
"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"
}

View file

@ -1226,5 +1226,9 @@
"stopCastingBody": "您想停止投放吗?",
"castIPMismatchTitle": "投放相册失败",
"castIPMismatchBody": "请确保您的设备与电视处于同一网络。",
"pairingComplete": "配对完成"
"pairingComplete": "配对完成",
"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"
}

View file

@ -21,6 +21,7 @@ import 'package:photos/core/errors.dart';
import 'package:photos/core/network/network.dart';
import 'package:photos/db/upload_locks_db.dart';
import 'package:photos/ente_theme_data.dart';
import "package:photos/face/db.dart";
import "package:photos/l10n/l10n.dart";
import "package:photos/service_locator.dart";
import 'package:photos/services/app_lifecycle_service.dart';
@ -32,6 +33,9 @@ import 'package:photos/services/home_widget_service.dart';
import 'package:photos/services/local_file_update_service.dart';
import 'package:photos/services/local_sync_service.dart';
import "package:photos/services/location_service.dart";
import 'package:photos/services/machine_learning/face_ml/face_ml_service.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart';
import "package:photos/services/machine_learning/machine_learning_controller.dart";
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
import 'package:photos/services/memories_service.dart';
@ -213,6 +217,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
LocalFileUpdateService.instance.init(preferences);
SearchService.instance.init();
StorageBonusService.instance.init(preferences);
RemoteFileMLService.instance.init(preferences);
if (!isBackground &&
Platform.isAndroid &&
await HomeWidgetService.instance.countHomeWidgets() == 0) {
@ -233,9 +238,23 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
// Can not including existing tf/ml binaries as they are not being built
// from source.
// See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819
// if (!UpdateService.instance.isFdroidFlavor()) {
if (!UpdateService.instance.isFdroidFlavor()) {
// unawaited(ObjectDetectionService.instance.init());
// }
if (flagService.faceSearchEnabled) {
unawaited(FaceMlService.instance.init());
FaceMlService.instance.listenIndexOnDiffSync();
FaceMlService.instance.listenOnPeopleChangedSync();
} else {
if (LocalSettings.instance.isFaceIndexingEnabled) {
unawaited(LocalSettings.instance.toggleFaceIndexing());
}
}
}
PersonService.init(
EntityService.instance,
FaceMLDataDB.instance,
preferences,
);
_logger.info("Initialization done");
}

View file

@ -2,6 +2,7 @@ import "package:flutter/foundation.dart";
enum EntityType {
location,
person,
unknown,
}
@ -9,6 +10,8 @@ EntityType typeFromString(String type) {
switch (type) {
case "location":
return EntityType.location;
case "person":
return EntityType.location;
}
debugPrint("unexpected collection type $type");
return EntityType.unknown;
@ -19,6 +22,8 @@ extension EntityTypeExtn on EntityType {
switch (this) {
case EntityType.location:
return "location";
case EntityType.person:
return "person";
case EntityType.unknown:
return "unknown";
}

View file

@ -243,6 +243,9 @@ class EnteFile {
}
String get downloadUrl {
if (localFileServer.isNotEmpty) {
return "$localFileServer/$uploadedFileID";
}
final endpoint = Configuration.instance.getHttpEndpoint();
if (endpoint != kDefaultProductionEndpoint || flagService.disableCFWorker) {
return endpoint + "/files/download/" + uploadedFileID.toString();
@ -256,6 +259,9 @@ class EnteFile {
}
String get thumbnailUrl {
if (localFileServer.isNotEmpty) {
return "$localFileServer/thumb/$uploadedFileID";
}
final endpoint = Configuration.instance.getHttpEndpoint();
if (endpoint != kDefaultProductionEndpoint || flagService.disableCFWorker) {
return endpoint + "/files/preview/" + uploadedFileID.toString();

View file

@ -18,6 +18,8 @@ enum GalleryType {
searchResults,
locationTag,
quickLink,
peopleTag,
cluster,
}
extension GalleyTypeExtension on GalleryType {
@ -32,12 +34,14 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.locationTag:
case GalleryType.quickLink:
case GalleryType.uncategorized:
case GalleryType.peopleTag:
case GalleryType.sharedCollection:
return true;
case GalleryType.hiddenSection:
case GalleryType.hiddenOwnedCollection:
case GalleryType.trash:
case GalleryType.cluster:
return false;
}
}
@ -50,6 +54,7 @@ extension GalleyTypeExtension on GalleryType {
return true;
case GalleryType.hiddenSection:
case GalleryType.peopleTag:
case GalleryType.hiddenOwnedCollection:
case GalleryType.favorite:
case GalleryType.searchResults:
@ -59,6 +64,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.trash:
case GalleryType.sharedCollection:
case GalleryType.locationTag:
case GalleryType.cluster:
return false;
}
}
@ -75,12 +81,14 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.uncategorized:
case GalleryType.locationTag:
case GalleryType.quickLink:
case GalleryType.peopleTag:
return true;
case GalleryType.trash:
case GalleryType.archive:
case GalleryType.hiddenSection:
case GalleryType.hiddenOwnedCollection:
case GalleryType.sharedCollection:
case GalleryType.cluster:
return false;
}
}
@ -98,8 +106,10 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.localFolder:
case GalleryType.locationTag:
case GalleryType.quickLink:
case GalleryType.peopleTag:
return true;
case GalleryType.trash:
case GalleryType.cluster:
case GalleryType.sharedCollection:
return false;
}
@ -114,8 +124,10 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.archive:
case GalleryType.uncategorized:
case GalleryType.locationTag:
case GalleryType.peopleTag:
return true;
case GalleryType.hiddenSection:
case GalleryType.cluster:
case GalleryType.hiddenOwnedCollection:
case GalleryType.localFolder:
case GalleryType.trash:
@ -132,6 +144,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.quickLink:
return true;
case GalleryType.hiddenSection:
case GalleryType.peopleTag:
case GalleryType.hiddenOwnedCollection:
case GalleryType.uncategorized:
case GalleryType.favorite:
@ -139,6 +152,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.homepage:
case GalleryType.archive:
case GalleryType.localFolder:
case GalleryType.cluster:
case GalleryType.trash:
case GalleryType.locationTag:
return false;
@ -154,6 +168,7 @@ extension GalleyTypeExtension on GalleryType {
return true;
case GalleryType.hiddenSection:
case GalleryType.peopleTag:
case GalleryType.hiddenOwnedCollection:
case GalleryType.favorite:
case GalleryType.searchResults:
@ -162,6 +177,7 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.trash:
case GalleryType.sharedCollection:
case GalleryType.locationTag:
case GalleryType.cluster:
return false;
}
}
@ -182,10 +198,12 @@ extension GalleyTypeExtension on GalleryType {
return true;
case GalleryType.hiddenSection:
case GalleryType.peopleTag:
case GalleryType.hiddenOwnedCollection:
case GalleryType.localFolder:
case GalleryType.trash:
case GalleryType.favorite:
case GalleryType.cluster:
case GalleryType.sharedCollection:
return false;
}
@ -203,12 +221,14 @@ extension GalleyTypeExtension on GalleryType {
case GalleryType.searchResults:
case GalleryType.uncategorized:
case GalleryType.locationTag:
case GalleryType.peopleTag:
return true;
case GalleryType.hiddenSection:
case GalleryType.hiddenOwnedCollection:
case GalleryType.quickLink:
case GalleryType.favorite:
case GalleryType.cluster:
case GalleryType.archive:
case GalleryType.localFolder:
case GalleryType.trash:
@ -244,7 +264,7 @@ extension GalleyTypeExtension on GalleryType {
}
bool showEditLocation() {
return this != GalleryType.sharedCollection;
return this != GalleryType.sharedCollection && this != GalleryType.cluster;
}
}
@ -334,7 +354,9 @@ extension GalleryAppBarExtn on GalleryType {
case GalleryType.locationTag:
case GalleryType.searchResults:
return false;
case GalleryType.cluster:
case GalleryType.uncategorized:
case GalleryType.peopleTag:
case GalleryType.ownedCollection:
case GalleryType.sharedCollection:
case GalleryType.quickLink:

View file

@ -1,6 +1,7 @@
import "package:equatable/equatable.dart";
import "package:photos/models/api/entity/type.dart";
// LocalEntityData is a class that represents the data of an entity stored locally.
class LocalEntityData {
final String id;
final EntityType type;

View file

@ -0,0 +1,7 @@
typedef Embedding = List<double>;
typedef Num3DInputMatrix = List<List<List<num>>>;
typedef Int3DInputMatrix = List<List<List<int>>>;
typedef Double3DInputMatrix = List<List<List<double>>>;

View file

@ -0,0 +1,3 @@
const faceMlVersion = 1;
const clusterMlVersion = 1;
const minimumClusterSize = 2;

View file

@ -8,8 +8,15 @@ class GenericSearchResult extends SearchResult {
final List<EnteFile> _files;
final ResultType _type;
final Function(BuildContext context)? onResultTap;
final Map<String, dynamic> params;
GenericSearchResult(this._type, this._name, this._files, {this.onResultTap});
GenericSearchResult(
this._type,
this._name,
this._files, {
this.onResultTap,
this.params = const {},
});
@override
String name() {

Some files were not shown because too many files have changed in this diff Show more