Compare commits
1 Commits
chore/hand
...
dev/paymen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3e92fff0 |
@@ -4,7 +4,6 @@
|
||||
|
||||
design/
|
||||
docker/
|
||||
!docker/scripts
|
||||
docs/
|
||||
e2e/
|
||||
fastlane/
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
<a href="readme_i18n/README_zh_CN.md">中文</a>
|
||||
<a href="readme_i18n/README_ru_RU.md">Русский</a>
|
||||
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="readme_i18n/README_sv_SE.md">Svenska</a>
|
||||
<a href="readme_i18n/README_ar_JO.md">العربية</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
@@ -85,7 +85,7 @@ services:
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:11.0.0-ubuntu@sha256:dcd3ae78713958a862732c3608d32c03f0c279c35a2032d74b80b12c5cdc47b8
|
||||
image: grafana/grafana:11.0.0-ubuntu@sha256:02e99d1ee0b52dc9d3000c7b5314e7a07e0dfd69cc49bb3f8ce323491ed3406b
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: docker.io/redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
healthcheck:
|
||||
test: redis-cli ping || exit 1
|
||||
restart: always
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
LOG_LEVEL="${IMMICH_LOG_LEVEL:='info'}"
|
||||
|
||||
logDebug() {
|
||||
if [ "$LOG_LEVEL" = "debug" ] || [ "$LOG_LEVEL" = "verbose" ]; then
|
||||
echo "DEBUG: $1" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||
logDebug "cgroup v2 detected."
|
||||
if [ -f /sys/fs/cgroup/cpu.max ]; then
|
||||
read -r quota period </sys/fs/cgroup/cpu.max
|
||||
if [ "$quota" = "max" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu.max not found."
|
||||
fi
|
||||
else
|
||||
logDebug "cgroup v1 detected."
|
||||
|
||||
if [ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ] && [ -f /sys/fs/cgroup/cpu/cpu.cfs_period_us ]; then
|
||||
quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
|
||||
period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
|
||||
|
||||
if [ "$quota" = "-1" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu/cpu.cfs_quota_us or /sys/fs/cgroup/cpu/cpu.cfs_period_us not found."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${quota:-}" ] && [ -n "${period:-}" ]; then
|
||||
cpus=$((quota / period))
|
||||
if [ "$cpus" -eq 0 ]; then
|
||||
cpus=1
|
||||
fi
|
||||
else
|
||||
cpus=$(grep -c processor /proc/cpuinfo)
|
||||
fi
|
||||
|
||||
echo "$cpus"
|
||||
@@ -19,11 +19,6 @@ this advice improves throughput, not latency, for example, it will make Smart Se
|
||||
|
||||
It is important to remember that jobs like Smart Search, Face Detection, Facial Recognition, and Transcode Videos require a **lot** of processing power and therefore do not exaggerate the amount of jobs because you're probably thoroughly overloading the server.
|
||||
|
||||
:::danger IMPORTANT
|
||||
If you increase the concurrency from the defaults we set, especially for thumbnail generation, make sure you do not increase them past the amount of CPU cores you have available.
|
||||
Doing so can impact API responsiveness with no gain in thumbnail generation speed.
|
||||
:::
|
||||
|
||||
:::info Facial Recognition Concurrency
|
||||
The Facial Recognition Concurrency value cannot be changed because
|
||||
[DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok) is traditionally sequential, but there are parallel implementations of it out there. Our implementation isn't parallel.
|
||||
|
||||
@@ -38,17 +38,16 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
||||
|
||||
## General
|
||||
|
||||
| Variable | Description | Default | Containers | Workers |
|
||||
| :------------------------------ | :---------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||
| `TZ` | Timezone | | server | microservices |
|
||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
|
||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||
| Variable | Description | Default | Containers | Workers |
|
||||
| :------------------------------ | :---------------------------------------------- | :----------------------: | :----------------------- | :----------------- |
|
||||
| `TZ` | Timezone | | server | microservices |
|
||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
|
||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
|
||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||
|
||||
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
||||
It only need to be set if the Immich deployment method is changing.
|
||||
|
||||
BIN
docs/static/img/ios-app-store-badge.png
vendored
BIN
docs/static/img/ios-app-store-badge.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 15 KiB |
@@ -27,7 +27,7 @@ services:
|
||||
- 2283:3001
|
||||
|
||||
redis:
|
||||
image: redis:6.2-alpine@sha256:d6c2911ac51b289db208767581a5d154544f2b2fe4914ea5056443f62dc6e900
|
||||
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
|
||||
|
||||
database:
|
||||
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||
|
||||
@@ -5,61 +5,51 @@ export const errorDto = {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Authentication required',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
forbidden: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: expect.any(String),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
wrongPassword: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'Wrong password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidToken: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid user token',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidShareKey: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid share key',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
invalidSharePassword: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Invalid password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
badRequest: (message: any = null) => ({
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: message ?? expect.anything(),
|
||||
correlationId: expect.any(String),
|
||||
}),
|
||||
noPermission: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: expect.stringContaining('Not found or no'),
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
incorrectLogin: {
|
||||
error: 'Unauthorized',
|
||||
statusCode: 401,
|
||||
message: 'Incorrect email or password',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
alreadyHasAdmin: {
|
||||
error: 'Bad Request',
|
||||
statusCode: 400,
|
||||
message: 'The server already has an admin',
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -323,40 +323,6 @@ export const utils = {
|
||||
return body as AssetMediaResponseDto;
|
||||
},
|
||||
|
||||
replaceAsset: async (
|
||||
accessToken: string,
|
||||
assetId: string,
|
||||
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: AssetData },
|
||||
) => {
|
||||
const _dto = {
|
||||
deviceAssetId: 'test-1',
|
||||
deviceId: 'test',
|
||||
fileCreatedAt: new Date().toISOString(),
|
||||
fileModifiedAt: new Date().toISOString(),
|
||||
...dto,
|
||||
};
|
||||
|
||||
const assetData = dto?.assetData?.bytes || makeRandomImage();
|
||||
const filename = dto?.assetData?.filename || 'example.png';
|
||||
|
||||
if (dto?.assetData?.bytes) {
|
||||
console.log(`Uploading ${filename}`);
|
||||
}
|
||||
|
||||
const builder = request(app)
|
||||
.put(`/assets/${assetId}/original`)
|
||||
.attach('assetData', assetData, filename)
|
||||
.set('Authorization', `Bearer ${accessToken}`);
|
||||
|
||||
for (const [key, value] of Object.entries(_dto)) {
|
||||
void builder.field(key, String(value));
|
||||
}
|
||||
|
||||
const { body } = await builder;
|
||||
|
||||
return body as AssetMediaResponseDto;
|
||||
},
|
||||
|
||||
createImageFile: (path: string) => {
|
||||
if (!existsSync(dirname(path))) {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
|
||||
import { Page, expect, test } from '@playwright/test';
|
||||
import { utils } from 'src/utils';
|
||||
|
||||
function imageLocator(page: Page) {
|
||||
return page.getByAltText('Image taken on').locator('visible=true');
|
||||
}
|
||||
test.describe('Photo Viewer', () => {
|
||||
let admin: LoginResponseDto;
|
||||
let asset: AssetMediaResponseDto;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
utils.initSdk();
|
||||
await utils.resetDatabase();
|
||||
admin = await utils.adminSetup();
|
||||
asset = await utils.createAsset(admin.accessToken);
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
// before each test, login as user
|
||||
await utils.setAuthCookies(context, admin.accessToken);
|
||||
await page.goto('/photos');
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('initially shows a loading spinner', async ({ page }) => {
|
||||
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
|
||||
// slow down the request for thumbnail, so spiner has chance to show up
|
||||
await new Promise((f) => setTimeout(f, 2000));
|
||||
await route.continue();
|
||||
});
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await page.waitForLoadState('load');
|
||||
// this is the spinner
|
||||
await page.waitForSelector('svg[role=status]');
|
||||
await expect(page.getByRole('status')).toBeVisible();
|
||||
});
|
||||
|
||||
test('loads high resolution photo when zoomed', async ({ page }) => {
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||
const box = await imageLocator(page).boundingBox();
|
||||
expect(box).toBeTruthy;
|
||||
const { x, y, width, height } = box!;
|
||||
await page.mouse.move(x + width / 2, y + height / 2);
|
||||
await page.mouse.wheel(0, -1);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original');
|
||||
});
|
||||
|
||||
test('reloads photo when checksum changes', async ({ page }) => {
|
||||
await page.goto(`/photos/${asset.id}`);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
|
||||
const initialSrc = await imageLocator(page).getAttribute('src');
|
||||
await utils.replaceAsset(admin.accessToken, asset.id);
|
||||
await expect.poll(async () => await imageLocator(page).getAttribute('src')).not.toBe(initialSrc);
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,8 @@ from rich.logging import RichHandler
|
||||
from uvicorn import Server
|
||||
from uvicorn.workers import UvicornWorker
|
||||
|
||||
from .schemas import ModelType
|
||||
|
||||
|
||||
class PreloadModelData(BaseModel):
|
||||
clip: str | None
|
||||
@@ -19,7 +21,7 @@ class PreloadModelData(BaseModel):
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
cache_folder: Path = Path("/cache")
|
||||
cache_folder: str = "/cache"
|
||||
model_ttl: int = 300
|
||||
model_ttl_poll_s: int = 10
|
||||
host: str = "0.0.0.0"
|
||||
@@ -53,6 +55,14 @@ def clean_name(model_name: str) -> str:
|
||||
return model_name.split("/")[-1].translate(_clean_name)
|
||||
|
||||
|
||||
def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
|
||||
return Path(settings.cache_folder) / model_type.value / clean_name(model_name)
|
||||
|
||||
|
||||
def get_hf_model_name(model_name: str) -> str:
|
||||
return f"immich-app/{clean_name(model_name)}"
|
||||
|
||||
|
||||
LOG_LEVELS: dict[str, int] = {
|
||||
"critical": logging.ERROR,
|
||||
"error": logging.ERROR,
|
||||
|
||||
@@ -6,34 +6,22 @@ import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from contextlib import asynccontextmanager
|
||||
from functools import partial
|
||||
from typing import Any, AsyncGenerator, Callable, Iterator
|
||||
from zipfile import BadZipFile
|
||||
|
||||
import orjson
|
||||
from fastapi import Depends, FastAPI, File, Form, HTTPException
|
||||
from fastapi import Depends, FastAPI, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
|
||||
from PIL.Image import Image
|
||||
from pydantic import ValidationError
|
||||
from starlette.formparsers import MultiPartParser
|
||||
|
||||
from app.models import get_model_deps
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import decode_pil
|
||||
|
||||
from .config import PreloadModelData, log, settings
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import (
|
||||
InferenceEntries,
|
||||
InferenceEntry,
|
||||
InferenceResponse,
|
||||
MessageResponse,
|
||||
ModelIdentity,
|
||||
ModelTask,
|
||||
ModelType,
|
||||
PipelineRequest,
|
||||
T,
|
||||
TextResponse,
|
||||
)
|
||||
|
||||
@@ -75,21 +63,12 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
||||
gc.collect()
|
||||
|
||||
|
||||
async def preload_models(preload: PreloadModelData) -> None:
|
||||
log.info(f"Preloading models: {preload}")
|
||||
if preload.clip is not None:
|
||||
model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
if preload.facial_recognition is not None:
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await load(model)
|
||||
async def preload_models(preload_models: PreloadModelData) -> None:
|
||||
log.info(f"Preloading models: {preload_models}")
|
||||
if preload_models.clip is not None:
|
||||
await load(await model_cache.get(preload_models.clip, ModelType.CLIP))
|
||||
if preload_models.facial_recognition is not None:
|
||||
await load(await model_cache.get(preload_models.facial_recognition, ModelType.FACIAL_RECOGNITION))
|
||||
|
||||
|
||||
def update_state() -> Iterator[None]:
|
||||
@@ -102,27 +81,6 @@ def update_state() -> Iterator[None]:
|
||||
active_requests -= 1
|
||||
|
||||
|
||||
def get_entries(entries: str = Form()) -> InferenceEntries:
|
||||
try:
|
||||
request: PipelineRequest = orjson.loads(entries)
|
||||
without_deps: list[InferenceEntry] = []
|
||||
with_deps: list[InferenceEntry] = []
|
||||
for task, types in request.items():
|
||||
for type, entry in types.items():
|
||||
parsed: InferenceEntry = {
|
||||
"name": entry["modelName"],
|
||||
"task": task,
|
||||
"type": type,
|
||||
"options": entry.get("options", {}),
|
||||
}
|
||||
dep = get_model_deps(parsed["name"], type, task)
|
||||
(with_deps if dep else without_deps).append(parsed)
|
||||
return without_deps, with_deps
|
||||
except (orjson.JSONDecodeError, ValidationError, KeyError, AttributeError) as e:
|
||||
log.error(f"Invalid request format: {e}")
|
||||
raise HTTPException(422, "Invalid request format.")
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@@ -138,63 +96,42 @@ def ping() -> str:
|
||||
|
||||
@app.post("/predict", dependencies=[Depends(update_state)])
|
||||
async def predict(
|
||||
entries: InferenceEntries = Depends(get_entries),
|
||||
image: bytes | None = File(default=None),
|
||||
model_name: str = Form(alias="modelName"),
|
||||
model_type: ModelType = Form(alias="modelType"),
|
||||
options: str = Form(default="{}"),
|
||||
text: str | None = Form(default=None),
|
||||
image: UploadFile | None = None,
|
||||
) -> Any:
|
||||
if image is not None:
|
||||
inputs: Image | str = await run(lambda: decode_pil(image))
|
||||
inputs: str | bytes = await image.read()
|
||||
elif text is not None:
|
||||
inputs = text
|
||||
else:
|
||||
raise HTTPException(400, "Either image or text must be provided")
|
||||
response = await run_inference(inputs, entries)
|
||||
return ORJSONResponse(response)
|
||||
try:
|
||||
kwargs = orjson.loads(options)
|
||||
except orjson.JSONDecodeError:
|
||||
raise HTTPException(400, f"Invalid options JSON: {options}")
|
||||
|
||||
model = await load(await model_cache.get(model_name, model_type, ttl=settings.model_ttl, **kwargs))
|
||||
model.configure(**kwargs)
|
||||
outputs = await run(model.predict, inputs)
|
||||
return ORJSONResponse(outputs)
|
||||
|
||||
|
||||
async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:
|
||||
outputs: dict[ModelIdentity, Any] = {}
|
||||
response: InferenceResponse = {}
|
||||
|
||||
async def _run_inference(entry: InferenceEntry) -> None:
|
||||
model = await model_cache.get(entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl)
|
||||
inputs = [payload]
|
||||
for dep in model.depends:
|
||||
try:
|
||||
inputs.append(outputs[dep])
|
||||
except KeyError:
|
||||
message = f"Task {entry['task']} of type {entry['type']} depends on output of {dep}"
|
||||
raise HTTPException(400, message)
|
||||
model = await load(model)
|
||||
output = await run(model.predict, *inputs, **entry["options"])
|
||||
outputs[model.identity] = output
|
||||
response[entry["task"]] = output
|
||||
|
||||
without_deps, with_deps = entries
|
||||
await asyncio.gather(*[_run_inference(entry) for entry in without_deps])
|
||||
if with_deps:
|
||||
await asyncio.gather(*[_run_inference(entry) for entry in with_deps])
|
||||
if isinstance(payload, Image):
|
||||
response["imageHeight"], response["imageWidth"] = payload.height, payload.width
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def run(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
|
||||
async def run(func: Callable[..., Any], inputs: Any) -> Any:
|
||||
if thread_pool is None:
|
||||
return func(*args, **kwargs)
|
||||
partial_func = partial(func, *args, **kwargs)
|
||||
return await asyncio.get_running_loop().run_in_executor(thread_pool, partial_func)
|
||||
return func(inputs)
|
||||
return await asyncio.get_running_loop().run_in_executor(thread_pool, func, inputs)
|
||||
|
||||
|
||||
async def load(model: InferenceModel) -> InferenceModel:
|
||||
if model.loaded:
|
||||
return model
|
||||
|
||||
def _load(model: InferenceModel) -> InferenceModel:
|
||||
def _load(model: InferenceModel) -> None:
|
||||
with lock:
|
||||
model.load()
|
||||
return model
|
||||
|
||||
try:
|
||||
await run(_load, model)
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
from typing import Any
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder
|
||||
from app.models.clip.visual import OpenClipVisualEncoder
|
||||
from app.schemas import ModelSource, ModelTask, ModelType
|
||||
from app.schemas import ModelType
|
||||
|
||||
from .constants import get_model_source
|
||||
from .facial_recognition.detection import FaceDetector
|
||||
from .facial_recognition.recognition import FaceRecognizer
|
||||
from .base import InferenceModel
|
||||
from .clip import MCLIPEncoder, OpenCLIPEncoder
|
||||
from .constants import is_insightface, is_mclip, is_openclip
|
||||
from .facial_recognition import FaceRecognizer
|
||||
|
||||
|
||||
def get_model_class(model_name: str, model_type: ModelType, model_task: ModelTask) -> type[InferenceModel]:
|
||||
source = get_model_source(model_name)
|
||||
match source, model_type, model_task:
|
||||
case ModelSource.OPENCLIP | ModelSource.MCLIP, ModelType.VISUAL, ModelTask.SEARCH:
|
||||
return OpenClipVisualEncoder
|
||||
|
||||
case ModelSource.OPENCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
|
||||
return OpenClipTextualEncoder
|
||||
|
||||
case ModelSource.MCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
|
||||
return MClipTextualEncoder
|
||||
|
||||
case ModelSource.INSIGHTFACE, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION:
|
||||
return FaceDetector
|
||||
|
||||
case ModelSource.INSIGHTFACE, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION:
|
||||
return FaceRecognizer
|
||||
|
||||
def from_model_type(model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
|
||||
match model_type:
|
||||
case ModelType.CLIP:
|
||||
if is_openclip(model_name):
|
||||
return OpenCLIPEncoder(model_name, **model_kwargs)
|
||||
elif is_mclip(model_name):
|
||||
return MCLIPEncoder(model_name, **model_kwargs)
|
||||
case ModelType.FACIAL_RECOGNITION:
|
||||
if is_insightface(model_name):
|
||||
return FaceRecognizer(model_name, **model_kwargs)
|
||||
case _:
|
||||
raise ValueError(f"Unknown model combination: {source}, {model_type}, {model_task}")
|
||||
raise ValueError(f"Unknown model type {model_type}")
|
||||
|
||||
|
||||
def from_model_type(model_name: str, model_type: ModelType, model_task: ModelTask, **kwargs: Any) -> InferenceModel:
|
||||
return get_model_class(model_name, model_type, model_task)(model_name, **kwargs)
|
||||
|
||||
|
||||
def get_model_deps(model_name: str, model_type: ModelType, model_task: ModelTask) -> list[tuple[ModelType, ModelTask]]:
|
||||
return get_model_class(model_name, model_type, model_task).depends
|
||||
raise ValueError(f"Unknown {model_type} model {model_name}")
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any
|
||||
|
||||
import onnxruntime as ort
|
||||
from huggingface_hub import snapshot_download
|
||||
@@ -11,14 +11,13 @@ from huggingface_hub import snapshot_download
|
||||
import ann.ann
|
||||
from app.models.constants import SUPPORTED_PROVIDERS
|
||||
|
||||
from ..config import clean_name, log, settings
|
||||
from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType
|
||||
from ..config import get_cache_dir, get_hf_model_name, log, settings
|
||||
from ..schemas import ModelRuntime, ModelType
|
||||
from .ann import AnnSession
|
||||
|
||||
|
||||
class InferenceModel(ABC):
|
||||
depends: ClassVar[list[ModelIdentity]]
|
||||
identity: ClassVar[ModelIdentity]
|
||||
_model_type: ModelType
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -27,16 +26,16 @@ class InferenceModel(ABC):
|
||||
providers: list[str] | None = None,
|
||||
provider_options: list[dict[str, Any]] | None = None,
|
||||
sess_options: ort.SessionOptions | None = None,
|
||||
preferred_format: ModelFormat | None = None,
|
||||
preferred_runtime: ModelRuntime | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.loaded = False
|
||||
self.model_name = clean_name(model_name)
|
||||
self.model_name = model_name
|
||||
self.cache_dir = Path(cache_dir) if cache_dir is not None else self.cache_dir_default
|
||||
self.providers = providers if providers is not None else self.providers_default
|
||||
self.provider_options = provider_options if provider_options is not None else self.provider_options_default
|
||||
self.sess_options = sess_options if sess_options is not None else self.sess_options_default
|
||||
self.preferred_format = preferred_format if preferred_format is not None else self.preferred_format_default
|
||||
self.preferred_runtime = preferred_runtime if preferred_runtime is not None else self.preferred_runtime_default
|
||||
|
||||
def download(self) -> None:
|
||||
if not self.cached:
|
||||
@@ -48,36 +47,35 @@ class InferenceModel(ABC):
|
||||
def load(self) -> None:
|
||||
if self.loaded:
|
||||
return
|
||||
|
||||
self.download()
|
||||
log.info(f"Loading {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
|
||||
self.session = self._load()
|
||||
self._load()
|
||||
self.loaded = True
|
||||
|
||||
def predict(self, *inputs: Any, **model_kwargs: Any) -> Any:
|
||||
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
|
||||
self.load()
|
||||
if model_kwargs:
|
||||
self.configure(**model_kwargs)
|
||||
return self._predict(*inputs, **model_kwargs)
|
||||
return self._predict(inputs)
|
||||
|
||||
@abstractmethod
|
||||
def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ...
|
||||
def _predict(self, inputs: Any) -> Any: ...
|
||||
|
||||
def configure(self, **kwargs: Any) -> None:
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
pass
|
||||
|
||||
def _download(self) -> None:
|
||||
ignore_patterns = [] if self.preferred_format == ModelFormat.ARMNN else ["*.armnn"]
|
||||
ignore_patterns = [] if self.preferred_runtime == ModelRuntime.ARMNN else ["*.armnn"]
|
||||
snapshot_download(
|
||||
f"immich-app/{clean_name(self.model_name)}",
|
||||
get_hf_model_name(self.model_name),
|
||||
cache_dir=self.cache_dir,
|
||||
local_dir=self.cache_dir,
|
||||
local_dir_use_symlinks=False,
|
||||
ignore_patterns=ignore_patterns,
|
||||
)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
return self._make_session(self.model_path)
|
||||
@abstractmethod
|
||||
def _load(self) -> None: ...
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
if not self.cache_dir.exists():
|
||||
@@ -101,7 +99,7 @@ class InferenceModel(ABC):
|
||||
self.cache_dir.unlink()
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _make_session(self, model_path: Path) -> ModelSession:
|
||||
def _make_session(self, model_path: Path) -> AnnSession | ort.InferenceSession:
|
||||
if not model_path.is_file():
|
||||
onnx_path = model_path.with_suffix(".onnx")
|
||||
if not onnx_path.is_file():
|
||||
@@ -126,21 +124,9 @@ class InferenceModel(ABC):
|
||||
raise ValueError(f"Unsupported model file type: {model_path.suffix}")
|
||||
return session
|
||||
|
||||
@property
|
||||
def model_dir(self) -> Path:
|
||||
return self.cache_dir / self.model_type.value
|
||||
|
||||
@property
|
||||
def model_path(self) -> Path:
|
||||
return self.model_dir / f"model.{self.preferred_format}"
|
||||
|
||||
@property
|
||||
def model_task(self) -> ModelTask:
|
||||
return self.identity[1]
|
||||
|
||||
@property
|
||||
def model_type(self) -> ModelType:
|
||||
return self.identity[0]
|
||||
return self._model_type
|
||||
|
||||
@property
|
||||
def cache_dir(self) -> Path:
|
||||
@@ -152,11 +138,11 @@ class InferenceModel(ABC):
|
||||
|
||||
@property
|
||||
def cache_dir_default(self) -> Path:
|
||||
return settings.cache_folder / self.model_task.value / self.model_name
|
||||
return get_cache_dir(self.model_name, self.model_type)
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.model_path.is_file()
|
||||
return self.cache_dir.is_dir() and any(self.cache_dir.iterdir())
|
||||
|
||||
@property
|
||||
def providers(self) -> list[str]:
|
||||
@@ -240,14 +226,14 @@ class InferenceModel(ABC):
|
||||
return sess_options
|
||||
|
||||
@property
|
||||
def preferred_format(self) -> ModelFormat:
|
||||
return self._preferred_format
|
||||
def preferred_runtime(self) -> ModelRuntime:
|
||||
return self._preferred_runtime
|
||||
|
||||
@preferred_format.setter
|
||||
def preferred_format(self, preferred_format: ModelFormat) -> None:
|
||||
log.debug(f"Setting preferred format to {preferred_format}")
|
||||
self._preferred_format = preferred_format
|
||||
@preferred_runtime.setter
|
||||
def preferred_runtime(self, preferred_runtime: ModelRuntime) -> None:
|
||||
log.debug(f"Setting preferred runtime to {preferred_runtime}")
|
||||
self._preferred_runtime = preferred_runtime
|
||||
|
||||
@property
|
||||
def preferred_format_default(self) -> ModelFormat:
|
||||
return ModelFormat.ARMNN if ann.ann.is_available and settings.ann else ModelFormat.ONNX
|
||||
def preferred_runtime_default(self) -> ModelRuntime:
|
||||
return ModelRuntime.ARMNN if ann.ann.is_available and settings.ann else ModelRuntime.ONNX
|
||||
|
||||
@@ -5,9 +5,9 @@ from aiocache.lock import OptimisticLock
|
||||
from aiocache.plugins import TimingPlugin
|
||||
|
||||
from app.models import from_model_type
|
||||
from app.models.base import InferenceModel
|
||||
|
||||
from ..schemas import ModelTask, ModelType, has_profiling
|
||||
from ..schemas import ModelType, has_profiling
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class ModelCache:
|
||||
@@ -31,21 +31,28 @@ class ModelCache:
|
||||
if profiling:
|
||||
plugins.append(TimingPlugin())
|
||||
|
||||
self.should_revalidate = revalidate
|
||||
self.revalidate_enable = revalidate
|
||||
|
||||
self.cache = SimpleMemoryCache(timeout=timeout, plugins=plugins, namespace=None)
|
||||
|
||||
async def get(
|
||||
self, model_name: str, model_type: ModelType, model_task: ModelTask, **model_kwargs: Any
|
||||
) -> InferenceModel:
|
||||
key = f"{model_name}{model_type}{model_task}"
|
||||
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
|
||||
"""
|
||||
Args:
|
||||
model_name: Name of model in the model hub used for the task.
|
||||
model_type: Model type or task, which determines which model zoo is used.
|
||||
|
||||
Returns:
|
||||
model: The requested model.
|
||||
"""
|
||||
|
||||
key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}"
|
||||
|
||||
async with OptimisticLock(self.cache, key) as lock:
|
||||
model: InferenceModel | None = await self.cache.get(key)
|
||||
if model is None:
|
||||
model = from_model_type(model_name, model_type, model_task, **model_kwargs)
|
||||
model = from_model_type(model_type, model_name, **model_kwargs)
|
||||
await lock.cas(model, ttl=model_kwargs.get("ttl", None))
|
||||
elif self.should_revalidate:
|
||||
elif self.revalidate_enable:
|
||||
await self.revalidate(key, model_kwargs.get("ttl", None))
|
||||
return model
|
||||
|
||||
|
||||
189
machine-learning/app/models/clip.py
Normal file
189
machine-learning/app/models/clip.py
Normal file
@@ -0,0 +1,189 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
from tokenizers import Encoding, Tokenizer
|
||||
|
||||
from app.config import clean_name, log
|
||||
from app.models.transforms import crop, get_pil_resampling, normalize, resize, to_numpy
|
||||
from app.schemas import ModelType
|
||||
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class BaseCLIPEncoder(InferenceModel):
|
||||
_model_type = ModelType.CLIP
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: Path | str | None = None,
|
||||
mode: Literal["text", "vision"] | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.mode = mode
|
||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> None:
|
||||
if self.mode == "text" or self.mode is None:
|
||||
log.debug(f"Loading clip text model '{self.model_name}'")
|
||||
self.text_model = self._make_session(self.textual_path)
|
||||
log.debug(f"Loaded clip text model '{self.model_name}'")
|
||||
|
||||
if self.mode == "vision" or self.mode is None:
|
||||
log.debug(f"Loading clip vision model '{self.model_name}'")
|
||||
self.vision_model = self._make_session(self.visual_path)
|
||||
log.debug(f"Loaded clip vision model '{self.model_name}'")
|
||||
|
||||
def _predict(self, image_or_text: Image.Image | str) -> NDArray[np.float32]:
|
||||
if isinstance(image_or_text, bytes):
|
||||
image_or_text = Image.open(BytesIO(image_or_text))
|
||||
|
||||
match image_or_text:
|
||||
case Image.Image():
|
||||
if self.mode == "text":
|
||||
raise TypeError("Cannot encode image as text-only model")
|
||||
outputs: NDArray[np.float32] = self.vision_model.run(None, self.transform(image_or_text))[0][0]
|
||||
case str():
|
||||
if self.mode == "vision":
|
||||
raise TypeError("Cannot encode text as vision-only model")
|
||||
outputs = self.text_model.run(None, self.tokenize(image_or_text))[0][0]
|
||||
case _:
|
||||
raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}")
|
||||
|
||||
return outputs
|
||||
|
||||
@abstractmethod
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def textual_dir(self) -> Path:
|
||||
return self.cache_dir / "textual"
|
||||
|
||||
@property
|
||||
def visual_dir(self) -> Path:
|
||||
return self.cache_dir / "visual"
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def textual_path(self) -> Path:
|
||||
return self.textual_dir / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def visual_path(self) -> Path:
|
||||
return self.visual_dir / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def tokenizer_file_path(self) -> Path:
|
||||
return self.textual_dir / "tokenizer.json"
|
||||
|
||||
@property
|
||||
def tokenizer_cfg_path(self) -> Path:
|
||||
return self.textual_dir / "tokenizer_config.json"
|
||||
|
||||
@property
|
||||
def preprocess_cfg_path(self) -> Path:
|
||||
return self.visual_dir / "preprocess_cfg.json"
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.textual_path.is_file() and self.visual_path.is_file()
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def tokenizer_file(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
||||
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
|
||||
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
|
||||
return tokenizer_file
|
||||
|
||||
@cached_property
|
||||
def tokenizer_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
|
||||
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
|
||||
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
|
||||
return tokenizer_cfg
|
||||
|
||||
@cached_property
|
||||
def preprocess_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
|
||||
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
return preprocess_cfg
|
||||
|
||||
|
||||
class OpenCLIPEncoder(BaseCLIPEncoder):
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
cache_dir: Path | str | None = None,
|
||||
mode: Literal["text", "vision"] | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(clean_name(model_name), cache_dir, mode, **model_kwargs)
|
||||
|
||||
def _load(self) -> None:
|
||||
super()._load()
|
||||
self._load_tokenizer()
|
||||
|
||||
size: list[int] | int = self.preprocess_cfg["size"]
|
||||
self.size = size[0] if isinstance(size, list) else size
|
||||
|
||||
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
|
||||
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
|
||||
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
|
||||
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
||||
context_length: int = text_cfg.get("context_length", 77)
|
||||
pad_token: str = self.tokenizer_cfg["pad_token"]
|
||||
|
||||
self.tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
||||
|
||||
pad_id: int = self.tokenizer.token_to_id(pad_token)
|
||||
self.tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
|
||||
self.tokenizer.enable_truncation(max_length=context_length)
|
||||
|
||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
||||
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
image = resize(image, self.size)
|
||||
image = crop(image, self.size)
|
||||
image_np = to_numpy(image)
|
||||
image_np = normalize(image_np, self.mean, self.std)
|
||||
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}
|
||||
|
||||
|
||||
class MCLIPEncoder(OpenCLIPEncoder):
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {
|
||||
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
||||
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from tokenizers import Encoding, Tokenizer
|
||||
|
||||
from app.config import log
|
||||
from app.models.base import InferenceModel
|
||||
from app.schemas import ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class BaseCLIPTextualEncoder(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
|
||||
def _predict(self, inputs: str, **kwargs: Any) -> NDArray[np.float32]:
|
||||
res: NDArray[np.float32] = self.session.run(None, self.tokenize(inputs))[0][0]
|
||||
return res
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||
self.tokenizer = self._load_tokenizer()
|
||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||
|
||||
return super()._load()
|
||||
|
||||
@abstractmethod
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def tokenizer_file_path(self) -> Path:
|
||||
return self.model_dir / "tokenizer.json"
|
||||
|
||||
@property
|
||||
def tokenizer_cfg_path(self) -> Path:
|
||||
return self.model_dir / "tokenizer_config.json"
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def tokenizer_file(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
|
||||
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
|
||||
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
|
||||
return tokenizer_file
|
||||
|
||||
@cached_property
|
||||
def tokenizer_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
|
||||
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
|
||||
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
|
||||
return tokenizer_cfg
|
||||
|
||||
|
||||
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
|
||||
def _load_tokenizer(self) -> Tokenizer:
|
||||
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
|
||||
context_length: int = text_cfg.get("context_length", 77)
|
||||
pad_token: str = self.tokenizer_cfg["pad_token"]
|
||||
|
||||
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
|
||||
|
||||
pad_id: int = tokenizer.token_to_id(pad_token)
|
||||
tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
|
||||
tokenizer.enable_truncation(max_length=context_length)
|
||||
|
||||
return tokenizer
|
||||
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {"text": np.array([tokens.ids], dtype=np.int32)}
|
||||
|
||||
|
||||
class MClipTextualEncoder(OpenClipTextualEncoder):
|
||||
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
|
||||
tokens: Encoding = self.tokenizer.encode(text)
|
||||
return {
|
||||
"input_ids": np.array([tokens.ids], dtype=np.int32),
|
||||
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
|
||||
from app.config import log
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.transforms import crop_pil, decode_pil, get_pil_resampling, normalize, resize_pil, to_numpy
|
||||
from app.schemas import ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class BaseCLIPVisualEncoder(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.VISUAL, ModelTask.SEARCH)
|
||||
|
||||
def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> NDArray[np.float32]:
|
||||
image = decode_pil(inputs)
|
||||
res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0]
|
||||
return res
|
||||
|
||||
@abstractmethod
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
pass
|
||||
|
||||
@property
|
||||
def model_cfg_path(self) -> Path:
|
||||
return self.cache_dir / "config.json"
|
||||
|
||||
@property
|
||||
def preprocess_cfg_path(self) -> Path:
|
||||
return self.model_dir / "preprocess_cfg.json"
|
||||
|
||||
@cached_property
|
||||
def model_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
|
||||
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
|
||||
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
|
||||
return model_cfg
|
||||
|
||||
@cached_property
|
||||
def preprocess_cfg(self) -> dict[str, Any]:
|
||||
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
|
||||
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
|
||||
return preprocess_cfg
|
||||
|
||||
|
||||
class OpenClipVisualEncoder(BaseCLIPVisualEncoder):
|
||||
def _load(self) -> ModelSession:
|
||||
size: list[int] | int = self.preprocess_cfg["size"]
|
||||
self.size = size[0] if isinstance(size, list) else size
|
||||
|
||||
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
|
||||
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
|
||||
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
|
||||
|
||||
return super()._load()
|
||||
|
||||
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
|
||||
image = resize_pil(image, self.size)
|
||||
image = crop_pil(image, self.size)
|
||||
image_np = to_numpy(image)
|
||||
image_np = normalize(image_np, self.mean, self.std)
|
||||
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}
|
||||
@@ -1,5 +1,4 @@
|
||||
from app.config import clean_name
|
||||
from app.schemas import ModelSource
|
||||
|
||||
_OPENCLIP_MODELS = {
|
||||
"RN50__openai",
|
||||
@@ -55,16 +54,13 @@ _INSIGHTFACE_MODELS = {
|
||||
SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||
|
||||
|
||||
def get_model_source(model_name: str) -> ModelSource | None:
|
||||
cleaned_name = clean_name(model_name)
|
||||
def is_openclip(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _OPENCLIP_MODELS
|
||||
|
||||
if cleaned_name in _INSIGHTFACE_MODELS:
|
||||
return ModelSource.INSIGHTFACE
|
||||
|
||||
if cleaned_name in _MCLIP_MODELS:
|
||||
return ModelSource.MCLIP
|
||||
def is_mclip(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _MCLIP_MODELS
|
||||
|
||||
if cleaned_name in _OPENCLIP_MODELS:
|
||||
return ModelSource.OPENCLIP
|
||||
|
||||
return None
|
||||
def is_insightface(model_name: str) -> bool:
|
||||
return clean_name(model_name) in _INSIGHTFACE_MODELS
|
||||
|
||||
90
machine-learning/app/models/facial_recognition.py
Normal file
90
machine-learning/app/models/facial_recognition.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from insightface.model_zoo import ArcFaceONNX, RetinaFace
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from app.config import clean_name
|
||||
from app.schemas import Face, ModelType, is_ndarray
|
||||
|
||||
from .base import InferenceModel
|
||||
|
||||
|
||||
class FaceRecognizer(InferenceModel):
|
||||
_model_type = ModelType.FACIAL_RECOGNITION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> None:
|
||||
self.det_model = RetinaFace(session=self._make_session(self.det_file))
|
||||
self.rec_model = ArcFaceONNX(
|
||||
self.rec_file.with_suffix(".onnx").as_posix(),
|
||||
session=self._make_session(self.rec_file),
|
||||
)
|
||||
|
||||
self.det_model.prepare(
|
||||
ctx_id=0,
|
||||
det_thresh=self.min_score,
|
||||
input_size=(640, 640),
|
||||
)
|
||||
self.rec_model.prepare(ctx_id=0)
|
||||
|
||||
def _predict(self, image: NDArray[np.uint8] | bytes) -> list[Face]:
|
||||
if isinstance(image, bytes):
|
||||
decoded_image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
|
||||
else:
|
||||
decoded_image = image
|
||||
assert is_ndarray(decoded_image, np.uint8)
|
||||
bboxes, kpss = self.det_model.detect(decoded_image)
|
||||
if bboxes.size == 0:
|
||||
return []
|
||||
assert is_ndarray(kpss, np.float32)
|
||||
|
||||
scores = bboxes[:, 4].tolist()
|
||||
bboxes = bboxes[:, :4].round().tolist()
|
||||
|
||||
results = []
|
||||
height, width, _ = decoded_image.shape
|
||||
for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss):
|
||||
cropped_img = norm_crop(decoded_image, kps)
|
||||
embedding: NDArray[np.float32] = self.rec_model.get_feat(cropped_img)[0]
|
||||
face: Face = {
|
||||
"imageWidth": width,
|
||||
"imageHeight": height,
|
||||
"boundingBox": {
|
||||
"x1": x1,
|
||||
"y1": y1,
|
||||
"x2": x2,
|
||||
"y2": y2,
|
||||
},
|
||||
"score": score,
|
||||
"embedding": embedding,
|
||||
}
|
||||
results.append(face)
|
||||
return results
|
||||
|
||||
@property
|
||||
def cached(self) -> bool:
|
||||
return self.det_file.is_file() and self.rec_file.is_file()
|
||||
|
||||
@property
|
||||
def det_file(self) -> Path:
|
||||
return self.cache_dir / "detection" / f"model.{self.preferred_runtime}"
|
||||
|
||||
@property
|
||||
def rec_file(self) -> Path:
|
||||
return self.cache_dir / "recognition" / f"model.{self.preferred_runtime}"
|
||||
|
||||
def configure(self, **model_kwargs: Any) -> None:
|
||||
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)
|
||||
@@ -1,43 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.session import ort_has_batch_dim, ort_expand_outputs
|
||||
from app.models.transforms import decode_pil
|
||||
from app.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType
|
||||
from .scrfd import SCRFD
|
||||
from PIL import Image
|
||||
from PIL.ImageOps import pad
|
||||
|
||||
class FaceDetector(InferenceModel):
|
||||
depends = []
|
||||
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
session = self._make_session(self.model_path)
|
||||
if isinstance(session, ort.InferenceSession) and not ort_has_batch_dim(session):
|
||||
ort_expand_outputs(session)
|
||||
self.model = SCRFD(session=session)
|
||||
|
||||
return session
|
||||
|
||||
def _predict(self, inputs: NDArray[np.uint8] | bytes | Image.Image, **kwargs: Any) -> FaceDetectionOutput:
|
||||
inputs = self._transform(inputs)
|
||||
|
||||
[bboxes], [landmarks] = self.model.detect(inputs, threshold=kwargs.pop("minScore", 0.7))
|
||||
return {
|
||||
"boxes": bboxes[:, :4].round(),
|
||||
"scores": bboxes[:, 4],
|
||||
"landmarks": landmarks,
|
||||
}
|
||||
|
||||
def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
||||
return self.model.detect(inputs) # type: ignore
|
||||
|
||||
def _transform(self, inputs: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]:
|
||||
image = decode_pil(inputs)
|
||||
padded = pad(image, (640, 640), method=Image.Resampling.BICUBIC)
|
||||
return np.array(padded, dtype=np.uint8)[None, ...]
|
||||
@@ -1,64 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
from insightface.model_zoo import ArcFaceONNX
|
||||
from insightface.utils.face_align import norm_crop
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
|
||||
from app.config import clean_name, log
|
||||
from app.models.base import InferenceModel
|
||||
from app.models.session import ort_add_batch_dim, ort_has_batch_dim
|
||||
from app.models.transforms import decode_cv2
|
||||
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelSession, ModelTask, ModelType
|
||||
|
||||
|
||||
class FaceRecognizer(InferenceModel):
|
||||
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
|
||||
identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
min_score: float = 0.7,
|
||||
cache_dir: Path | str | None = None,
|
||||
**model_kwargs: Any,
|
||||
) -> None:
|
||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
|
||||
|
||||
def _load(self) -> ModelSession:
|
||||
session = self._make_session(self.model_path)
|
||||
if isinstance(session, ort.InferenceSession) and not ort_has_batch_dim(session):
|
||||
log.info(f"Adding batch dimension to recognition model {self.model_name}")
|
||||
ort_add_batch_dim(self.model_path, self.model_path)
|
||||
session = self._make_session(self.model_path)
|
||||
self.model = ArcFaceONNX(
|
||||
self.model_path.with_suffix(".onnx").as_posix(),
|
||||
session=session,
|
||||
)
|
||||
return session
|
||||
|
||||
def _predict(
|
||||
self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput, **kwargs: Any
|
||||
) -> FacialRecognitionOutput:
|
||||
if faces["boxes"].shape[0] == 0:
|
||||
return []
|
||||
inputs = decode_cv2(inputs)
|
||||
embeddings: NDArray[np.float32] = self.model.get_feat(self._crop(inputs, faces))
|
||||
return self.postprocess(faces, embeddings)
|
||||
|
||||
def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput:
|
||||
return [
|
||||
{
|
||||
"boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
|
||||
"embedding": embedding,
|
||||
"score": score,
|
||||
}
|
||||
for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"])
|
||||
]
|
||||
|
||||
def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]:
|
||||
return [norm_crop(image, landmark) for landmark in faces["landmarks"]]
|
||||
@@ -1,325 +0,0 @@
|
||||
# Based on InsightFace-REST by SthPhoenix https://github.com/SthPhoenix/InsightFace-REST/blob/master/src/api_trt/modules/model_zoo/detectors/scrfd.py
|
||||
# Primary changes made:
|
||||
# 1. Removed CuPy-related code
|
||||
# 2. Adapted proposal generation to be thread-safe
|
||||
# 3. Added typing
|
||||
# 4. Assume RGB input
|
||||
# 5. Removed unused variables
|
||||
|
||||
# Copyright 2021 SthPhoenix
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Based on Jia Guo reference implementation at
|
||||
# https://github.com/deepinsight/insightface/blob/master/detection/scrfd/tools/scrfd.py
|
||||
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
from app.schemas import ModelSession
|
||||
from numpy.typing import NDArray
|
||||
|
||||
|
||||
@njit(cache=True, nogil=True)
|
||||
def nms(dets, threshold: float = 0.4) -> NDArray[np.float32]:
|
||||
x1 = dets[:, 0]
|
||||
y1 = dets[:, 1]
|
||||
x2 = dets[:, 2]
|
||||
y2 = dets[:, 3]
|
||||
scores = dets[:, 4]
|
||||
|
||||
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
order = scores.argsort()[::-1]
|
||||
|
||||
keep = []
|
||||
while order.size > 0:
|
||||
i = order[0]
|
||||
keep.append(i)
|
||||
xx1 = np.maximum(x1[i], x1[order[1:]])
|
||||
yy1 = np.maximum(y1[i], y1[order[1:]])
|
||||
xx2 = np.minimum(x2[i], x2[order[1:]])
|
||||
yy2 = np.minimum(y2[i], y2[order[1:]])
|
||||
|
||||
w = np.maximum(0.0, xx2 - xx1 + 1)
|
||||
h = np.maximum(0.0, yy2 - yy1 + 1)
|
||||
inter = w * h
|
||||
ovr = inter / (areas[i] + areas[order[1:]] - inter)
|
||||
|
||||
inds = np.where(ovr <= threshold)[0]
|
||||
order = order[inds + 1]
|
||||
|
||||
return np.asarray(keep)
|
||||
|
||||
|
||||
@njit(fastmath=True, cache=True, nogil=True)
|
||||
def single_distance2bbox(point: NDArray[np.float32], distance: NDArray[np.float32], stride: int) -> NDArray[np.float32]:
|
||||
"""
|
||||
Fast conversion of single bbox distances to coordinates
|
||||
|
||||
:param point: Anchor point
|
||||
:param distance: Bbox distances from anchor point
|
||||
:param stride: Current stride scale
|
||||
:return: bbox
|
||||
"""
|
||||
distance[0] = point[0] - distance[0] * stride
|
||||
distance[1] = point[1] - distance[1] * stride
|
||||
distance[2] = point[0] + distance[2] * stride
|
||||
distance[3] = point[1] + distance[3] * stride
|
||||
return distance
|
||||
|
||||
|
||||
@njit(fastmath=True, cache=True, nogil=True)
|
||||
def single_distance2kps(point: NDArray[np.float32], distance: NDArray[np.float32], stride: int) -> NDArray[np.float32]:
|
||||
"""
|
||||
Fast conversion of single keypoint distances to coordinates
|
||||
|
||||
:param point: Anchor point
|
||||
:param distance: Keypoint distances from anchor point
|
||||
:param stride: Current stride scale
|
||||
:return: keypoint
|
||||
"""
|
||||
for ix in range(0, distance.shape[0], 2):
|
||||
distance[ix] = distance[ix] * stride + point[0]
|
||||
distance[ix + 1] = distance[ix + 1] * stride + point[1]
|
||||
return distance
|
||||
|
||||
|
||||
@njit(fastmath=True, cache=True, nogil=True)
|
||||
def generate_proposals(
|
||||
score_blob: NDArray[np.float32],
|
||||
bbox_blob: NDArray[np.float32],
|
||||
kpss_blob: NDArray[np.float32],
|
||||
stride: int,
|
||||
anchors: NDArray[np.float32],
|
||||
threshold: float,
|
||||
) -> tuple[NDArray[np.float32], NDArray[np.float32], NDArray[np.float32]]:
|
||||
"""
|
||||
Convert distances from anchors to actual coordinates on source image
|
||||
and filter proposals by confidence threshold.
|
||||
|
||||
:param score_blob: Raw scores for stride
|
||||
:param bbox_blob: Raw bbox distances for stride
|
||||
:param kpss_blob: Raw keypoints distances for stride
|
||||
:param stride: Stride scale
|
||||
:param anchors: Precomputed anchors for stride
|
||||
:param threshold: Confidence threshold
|
||||
:return: Filtered scores, bboxes and keypoints
|
||||
"""
|
||||
|
||||
idxs = []
|
||||
for ix in range(score_blob.shape[0]):
|
||||
if score_blob[ix][0] > threshold:
|
||||
idxs.append(ix)
|
||||
|
||||
score_out = np.empty((len(idxs), 1), dtype="float32")
|
||||
bbox_out = np.empty((len(idxs), 4), dtype="float32")
|
||||
kpss_out = np.empty((len(idxs), 10), dtype="float32")
|
||||
|
||||
for i in range(len(idxs)):
|
||||
ix = idxs[i]
|
||||
score_out[i] = score_blob[ix]
|
||||
bbox_out[i] = single_distance2bbox(anchors[ix], bbox_blob[ix], stride)
|
||||
kpss_out[i] = single_distance2kps(anchors[ix], kpss_blob[ix], stride)
|
||||
|
||||
return score_out, bbox_out, kpss_out
|
||||
|
||||
|
||||
@njit(fastmath=True, cache=True, nogil=True)
|
||||
def filter(
|
||||
bboxes_list: NDArray[np.float32],
|
||||
kpss_list: NDArray[np.float32],
|
||||
scores_list: NDArray[np.float32],
|
||||
nms_threshold: float = 0.4,
|
||||
) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
|
||||
"""
|
||||
Filter postprocessed network outputs with NMS
|
||||
|
||||
:param bboxes_list: List of bboxes (np.ndarray)
|
||||
:param kpss_list: List of keypoints (np.ndarray)
|
||||
:param scores_list: List of scores (np.ndarray)
|
||||
:return: Face bboxes with scores [t,l,b,r,score], and key points
|
||||
"""
|
||||
|
||||
pre_det = np.hstack((bboxes_list, scores_list))
|
||||
keep = nms(pre_det, threshold=nms_threshold)
|
||||
det = pre_det[keep, :]
|
||||
kpss = kpss_list[keep, :]
|
||||
kpss = kpss.reshape((kpss.shape[0], -1, 2))
|
||||
|
||||
return det, kpss
|
||||
|
||||
|
||||
class SCRFD:
|
||||
def __init__(self, session: ModelSession):
|
||||
self.session = session
|
||||
self.center_cache: dict[tuple[int, int], NDArray[np.float32]] = {}
|
||||
self.nms_threshold = 0.4
|
||||
self.fmc = 3
|
||||
self._feat_stride_fpn = [8, 16, 32]
|
||||
self._num_anchors = 2
|
||||
|
||||
def prepare(self, nms_threshold: float = 0.4) -> None:
|
||||
"""
|
||||
Populate class parameters
|
||||
|
||||
:param nms_threshold: Threshold for NMS IoU
|
||||
"""
|
||||
|
||||
self.nms_threshold = nms_threshold
|
||||
|
||||
def detect(
|
||||
self, imgs: NDArray[np.uint8], threshold: float = 0.5
|
||||
) -> tuple[list[NDArray[np.float32]], list[NDArray[np.float32]]]:
|
||||
"""
|
||||
Run detection pipeline for provided images
|
||||
|
||||
:param img: Raw image as nd.ndarray with HWC shape
|
||||
:param threshold: Confidence threshold
|
||||
:return: Face bboxes with scores [t,l,b,r,score], and key points
|
||||
"""
|
||||
|
||||
height, width = imgs.shape[1:3]
|
||||
blob = self._preprocess(imgs)
|
||||
net_outs = self._forward(blob)
|
||||
|
||||
batch_bboxes, batch_kpss, batch_scores = self._postprocess(net_outs, height, width, threshold)
|
||||
|
||||
dets_list = []
|
||||
kpss_list = []
|
||||
for e in range(imgs.shape[0]):
|
||||
if len(batch_bboxes[e]) == 0:
|
||||
det, kpss = np.zeros((0, 5), dtype="float32"), np.zeros((0, 10), dtype="float32")
|
||||
else:
|
||||
det, kpss = filter(batch_bboxes[e], batch_kpss[e], batch_scores[e], self.nms_threshold)
|
||||
|
||||
dets_list.append(det)
|
||||
kpss_list.append(kpss)
|
||||
|
||||
return dets_list, kpss_list
|
||||
|
||||
@staticmethod
|
||||
def _build_anchors(
|
||||
input_height: int, input_width: int, strides: list[int], num_anchors: int
|
||||
) -> NDArray[np.float32]:
|
||||
"""
|
||||
Precompute anchor points for provided image size
|
||||
|
||||
:param input_height: Input image height
|
||||
:param input_width: Input image width
|
||||
:param strides: Model strides
|
||||
:param num_anchors: Model num anchors
|
||||
:return: box centers
|
||||
"""
|
||||
|
||||
centers = []
|
||||
for stride in strides:
|
||||
height = input_height // stride
|
||||
width = input_width // stride
|
||||
|
||||
anchor_centers = np.stack(np.mgrid[:height, :width][::-1], axis=-1).astype(np.float32)
|
||||
anchor_centers = (anchor_centers * stride).reshape((-1, 2))
|
||||
if num_anchors > 1:
|
||||
anchor_centers = np.stack([anchor_centers] * num_anchors, axis=1).reshape((-1, 2))
|
||||
centers.append(anchor_centers)
|
||||
return centers
|
||||
|
||||
def _preprocess(self, images: NDArray[np.uint8]):
|
||||
"""
|
||||
Normalize image on CPU if backend can't provide CUDA stream,
|
||||
otherwise preprocess image on GPU using CuPy
|
||||
|
||||
:param img: Raw image as np.ndarray with HWC shape
|
||||
:return: Preprocessed image or None if image was processed on device
|
||||
"""
|
||||
|
||||
input_size = tuple(images[0].shape[0:2][::-1])
|
||||
return cv2.dnn.blobFromImages(images, 1.0 / 128, input_size, (127.5, 127.5, 127.5), swapRB=False)
|
||||
|
||||
def _forward(self, blob: NDArray[np.float32]) -> list[NDArray[np.float32]]:
|
||||
"""
|
||||
Send input data to inference backend.
|
||||
|
||||
:param blob: Preprocessed image of shape NCHW or None
|
||||
:return: network outputs
|
||||
"""
|
||||
|
||||
return self.session.run(None, {"input.1": blob})
|
||||
|
||||
def _postprocess(
|
||||
self, net_outs: list[NDArray[np.float32]], height: int, width: int, threshold: float
|
||||
) -> tuple[list[NDArray[np.float32]], list[NDArray[np.float32]], list[NDArray[np.float32]]]:
|
||||
"""
|
||||
Precompute anchor points for provided image size and process network outputs
|
||||
|
||||
:param net_outs: Network outputs
|
||||
:param input_height: Input image height
|
||||
:param input_width: Input image width
|
||||
:param threshold: Confidence threshold
|
||||
:return: filtered bboxes, keypoints and scores
|
||||
"""
|
||||
|
||||
key = (height, width)
|
||||
|
||||
if not self.center_cache.get(key):
|
||||
self.center_cache[key] = self._build_anchors(height, width, self._feat_stride_fpn, self._num_anchors)
|
||||
anchor_centers = self.center_cache[key]
|
||||
bboxes, kpss, scores = self._process_strides(net_outs, threshold, anchor_centers)
|
||||
return bboxes, kpss, scores
|
||||
|
||||
def _process_strides(
|
||||
self, net_outs: list[NDArray[np.float32]], threshold: float, anchors: NDArray[np.float32]
|
||||
) -> tuple[list[NDArray[np.float32]], list[NDArray[np.float32]], list[NDArray[np.float32]]]:
|
||||
"""
|
||||
Process network outputs by strides and return results proposals filtered by threshold
|
||||
|
||||
:param net_outs: Network outputs
|
||||
:param threshold: Confidence threshold
|
||||
:param anchor_centers: Precomputed anchor centers for all strides
|
||||
:return: filtered bboxes, keypoints and scores
|
||||
"""
|
||||
|
||||
batch_size = net_outs[0].shape[0]
|
||||
bboxes_by_img = []
|
||||
kpss_by_img = []
|
||||
scores_by_img = []
|
||||
|
||||
for batch in range(batch_size):
|
||||
scores_strided = []
|
||||
bboxes_strided = []
|
||||
kpss_strided = []
|
||||
for idx, stride in enumerate(self._feat_stride_fpn):
|
||||
score_blob = net_outs[idx][batch]
|
||||
bbox_blob = net_outs[idx + self.fmc][batch]
|
||||
kpss_blob = net_outs[idx + self.fmc * 2][batch]
|
||||
stride_anchors = anchors[idx]
|
||||
score_list, bbox_list, kpss_list = generate_proposals(
|
||||
score_blob,
|
||||
bbox_blob,
|
||||
kpss_blob,
|
||||
stride,
|
||||
stride_anchors,
|
||||
threshold,
|
||||
)
|
||||
|
||||
scores_strided.append(score_list)
|
||||
bboxes_strided.append(bbox_list)
|
||||
kpss_strided.append(kpss_list)
|
||||
bboxes_by_img.append(np.concatenate(bboxes_strided, axis=0))
|
||||
kpss_by_img.append(np.concatenate(kpss_strided, axis=0))
|
||||
scores_by_img.append(np.concatenate(scores_strided, axis=0))
|
||||
|
||||
return bboxes_by_img, kpss_by_img, scores_by_img
|
||||
@@ -1,34 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import onnx
|
||||
import onnxruntime as ort
|
||||
from numpy.typing import NDArray
|
||||
from onnx.shape_inference import infer_shapes
|
||||
from onnx.tools.update_model_dims import update_inputs_outputs_dims
|
||||
|
||||
|
||||
def ort_has_batch_dim(session: ort.InferenceSession) -> bool:
|
||||
return session.get_inputs()[0].shape[0] == "batch"
|
||||
|
||||
|
||||
def ort_expand_outputs(session: ort.InferenceSession) -> None:
|
||||
original_run = session.run
|
||||
|
||||
def run(output_names: list[str], input_feed: dict[str, NDArray[np.float32]]) -> list[NDArray[np.float32]]:
|
||||
out: list[NDArray[np.float32]] = original_run(output_names, input_feed)
|
||||
out = [np.expand_dims(o, axis=0) for o in out]
|
||||
return out
|
||||
|
||||
session.run = run
|
||||
|
||||
|
||||
def ort_add_batch_dim(input_path: Path, output_path: Path) -> None:
|
||||
proto = onnx.load(input_path)
|
||||
static_input_dims = [shape.dim_value for shape in proto.graph.input[0].type.tensor_type.shape.dim[1:]]
|
||||
static_output_dims = [shape.dim_value for shape in proto.graph.output[0].type.tensor_type.shape.dim[1:]]
|
||||
input_dims = {proto.graph.input[0].name: ["batch"] + static_input_dims}
|
||||
output_dims = {proto.graph.output[0].name: ["batch"] + static_output_dims}
|
||||
updated_proto = update_inputs_outputs_dims(proto, input_dims, output_dims)
|
||||
inferred = infer_shapes(updated_proto)
|
||||
onnx.save(inferred, output_path)
|
||||
@@ -1,16 +1,11 @@
|
||||
from io import BytesIO
|
||||
from typing import IO
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from numba import njit
|
||||
from numpy.typing import NDArray
|
||||
from PIL import Image
|
||||
|
||||
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
|
||||
|
||||
|
||||
def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
def resize(img: Image.Image, size: int) -> Image.Image:
|
||||
if img.width < img.height:
|
||||
return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC)
|
||||
else:
|
||||
@@ -18,7 +13,7 @@ def resize_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
|
||||
|
||||
# https://stackoverflow.com/a/60883103
|
||||
def crop_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
def crop(img: Image.Image, size: int) -> Image.Image:
|
||||
left = int((img.size[0] / 2) - (size / 2))
|
||||
upper = int((img.size[1] / 2) - (size / 2))
|
||||
right = left + size
|
||||
@@ -28,10 +23,9 @@ def crop_pil(img: Image.Image, size: int) -> Image.Image:
|
||||
|
||||
|
||||
def to_numpy(img: Image.Image) -> NDArray[np.float32]:
|
||||
return np.asarray(img if img.mode == "RGB" else img.convert("RGB"), dtype=np.float32) / 255.0
|
||||
return np.asarray(img.convert("RGB")).astype(np.float32) / 255.0
|
||||
|
||||
|
||||
@njit(cache=True, fastmath=True, nogil=True)
|
||||
def normalize(
|
||||
img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32]
|
||||
) -> NDArray[np.float32]:
|
||||
@@ -40,25 +34,3 @@ def normalize(
|
||||
|
||||
def get_pil_resampling(resample: str) -> Image.Resampling:
|
||||
return _PIL_RESAMPLING_METHODS[resample.lower()]
|
||||
|
||||
|
||||
def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]:
|
||||
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore
|
||||
|
||||
|
||||
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
|
||||
if isinstance(image_bytes, Image.Image):
|
||||
return image_bytes
|
||||
image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
|
||||
image.load() # type: ignore
|
||||
if not image.mode == "RGB":
|
||||
image = image.convert("RGB")
|
||||
return image
|
||||
|
||||
|
||||
def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]:
|
||||
if isinstance(image_bytes, bytes):
|
||||
image_bytes = decode_pil(image_bytes) # pillow is much faster than cv2
|
||||
if isinstance(image_bytes, Image.Image):
|
||||
return pil_to_cv2(image_bytes)
|
||||
return image_bytes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from enum import Enum
|
||||
from typing import Any, Literal, Protocol, TypedDict, TypeGuard, TypeVar
|
||||
from typing import Any, Protocol, TypedDict, TypeGuard
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
@@ -28,87 +28,31 @@ class BoundingBox(TypedDict):
|
||||
y2: int
|
||||
|
||||
|
||||
class ModelTask(StrEnum):
|
||||
FACIAL_RECOGNITION = "facial-recognition"
|
||||
SEARCH = "clip"
|
||||
|
||||
|
||||
class ModelType(StrEnum):
|
||||
DETECTION = "detection"
|
||||
RECOGNITION = "recognition"
|
||||
TEXTUAL = "textual"
|
||||
VISUAL = "visual"
|
||||
CLIP = "clip"
|
||||
FACIAL_RECOGNITION = "facial-recognition"
|
||||
|
||||
|
||||
class ModelFormat(StrEnum):
|
||||
ARMNN = "armnn"
|
||||
class ModelRuntime(StrEnum):
|
||||
ONNX = "onnx"
|
||||
|
||||
|
||||
class ModelSource(StrEnum):
|
||||
INSIGHTFACE = "insightface"
|
||||
MCLIP = "mclip"
|
||||
OPENCLIP = "openclip"
|
||||
|
||||
|
||||
ModelIdentity = tuple[ModelType, ModelTask]
|
||||
|
||||
|
||||
class ModelSession(Protocol):
|
||||
def run(
|
||||
self,
|
||||
output_names: list[str] | None,
|
||||
input_feed: dict[str, npt.NDArray[np.float32]] | dict[str, npt.NDArray[np.int32]],
|
||||
run_options: Any = None,
|
||||
) -> list[npt.NDArray[np.float32]]: ...
|
||||
ARMNN = "armnn"
|
||||
|
||||
|
||||
class HasProfiling(Protocol):
|
||||
profiling: dict[str, float]
|
||||
|
||||
|
||||
class FaceDetectionOutput(TypedDict):
|
||||
boxes: npt.NDArray[np.float32]
|
||||
scores: npt.NDArray[np.float32]
|
||||
landmarks: npt.NDArray[np.float32]
|
||||
|
||||
|
||||
class DetectedFace(TypedDict):
|
||||
class Face(TypedDict):
|
||||
boundingBox: BoundingBox
|
||||
embedding: npt.NDArray[np.float32]
|
||||
imageWidth: int
|
||||
imageHeight: int
|
||||
score: float
|
||||
|
||||
|
||||
FacialRecognitionOutput = list[DetectedFace]
|
||||
|
||||
|
||||
class PipelineEntry(TypedDict):
|
||||
modelName: str
|
||||
options: dict[str, Any]
|
||||
|
||||
|
||||
PipelineRequest = dict[ModelTask, dict[ModelType, PipelineEntry]]
|
||||
|
||||
|
||||
class InferenceEntry(TypedDict):
|
||||
name: str
|
||||
task: ModelTask
|
||||
type: ModelType
|
||||
options: dict[str, Any]
|
||||
|
||||
|
||||
InferenceEntries = tuple[list[InferenceEntry], list[InferenceEntry]]
|
||||
|
||||
|
||||
InferenceResponse = dict[ModelTask | Literal["imageHeight"] | Literal["imageWidth"], Any]
|
||||
|
||||
|
||||
def has_profiling(obj: Any) -> TypeGuard[HasProfiling]:
|
||||
return hasattr(obj, "profiling") and isinstance(obj.profiling, dict)
|
||||
|
||||
|
||||
def is_ndarray(obj: Any, dtype: "type[np._DTypeScalar_co]") -> "TypeGuard[npt.NDArray[np._DTypeScalar_co]]":
|
||||
return isinstance(obj, np.ndarray) and obj.dtype == dtype
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -7,7 +7,6 @@ from types import SimpleNamespace
|
||||
from typing import Any, Callable
|
||||
from unittest import mock
|
||||
|
||||
from app.models.session import ort_add_batch_dim, ort_has_batch_dim, ort_squeeze_outputs
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
@@ -18,15 +17,13 @@ from pytest import MonkeyPatch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from app.main import load, preload_models
|
||||
from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder
|
||||
from app.models.clip.visual import OpenClipVisualEncoder
|
||||
from app.models.facial_recognition.detection import FaceDetector
|
||||
from app.models.facial_recognition.recognition import FaceRecognizer
|
||||
|
||||
from .config import Settings, log, settings
|
||||
from .models.base import InferenceModel
|
||||
from .models.cache import ModelCache
|
||||
from .schemas import ModelFormat, ModelTask, ModelType
|
||||
from .models.clip import MCLIPEncoder, OpenCLIPEncoder
|
||||
from .models.facial_recognition import FaceRecognizer
|
||||
from .schemas import ModelRuntime, ModelType
|
||||
|
||||
|
||||
class TestBase:
|
||||
@@ -38,13 +35,13 @@ class TestBase:
|
||||
|
||||
@pytest.mark.providers(CPU_EP)
|
||||
def test_sets_cpu_provider(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CPU_EP
|
||||
|
||||
@pytest.mark.providers(CUDA_EP)
|
||||
def test_sets_cuda_provider_if_available(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
@@ -53,7 +50,7 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.OV_EP
|
||||
|
||||
@@ -62,25 +59,25 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CPU_EP
|
||||
|
||||
@pytest.mark.providers(CUDA_EP_OUT_OF_ORDER)
|
||||
def test_sets_providers_in_correct_order(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
@pytest.mark.providers(TRT_EP)
|
||||
def test_ignores_unsupported_providers(self, providers: list[str]) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.providers == self.CUDA_EP
|
||||
|
||||
def test_sets_provider_kwarg(self) -> None:
|
||||
providers = ["CUDAExecutionProvider"]
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", providers=providers)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=providers)
|
||||
|
||||
assert encoder.providers == providers
|
||||
|
||||
@@ -88,9 +85,7 @@ class TestBase:
|
||||
mocked = mocker.patch("app.models.base.ort.capi._pybind_state")
|
||||
mocked.get_available_openvino_device_ids.return_value = ["GPU.0", "CPU"]
|
||||
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.provider_options == [
|
||||
{"device_type": "GPU_FP32", "cache_dir": (encoder.cache_dir / "openvino").as_posix()},
|
||||
@@ -98,7 +93,7 @@ class TestBase:
|
||||
]
|
||||
|
||||
def test_sets_provider_options_kwarg(self) -> None:
|
||||
encoder = OpenClipTextualEncoder(
|
||||
encoder = OpenCLIPEncoder(
|
||||
"ViT-B-32__openai",
|
||||
providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"],
|
||||
provider_options=[],
|
||||
@@ -107,7 +102,7 @@ class TestBase:
|
||||
assert encoder.provider_options == []
|
||||
|
||||
def test_sets_default_sess_options(self) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.sess_options.execution_mode == ort.ExecutionMode.ORT_SEQUENTIAL
|
||||
assert encoder.sess_options.inter_op_num_threads == 1
|
||||
@@ -115,9 +110,7 @@ class TestBase:
|
||||
assert encoder.sess_options.enable_cpu_mem_arena is False
|
||||
|
||||
def test_sets_default_sess_options_does_not_set_threads_if_non_cpu_and_default_threads(self) -> None:
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.sess_options.inter_op_num_threads == 0
|
||||
assert encoder.sess_options.intra_op_num_threads == 0
|
||||
@@ -127,16 +120,14 @@ class TestBase:
|
||||
mock_settings.model_inter_op_threads = 2
|
||||
mock_settings.model_intra_op_threads = 4
|
||||
|
||||
encoder = OpenClipTextualEncoder(
|
||||
"ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
|
||||
|
||||
assert encoder.sess_options.inter_op_num_threads == 2
|
||||
assert encoder.sess_options.intra_op_num_threads == 4
|
||||
|
||||
def test_sets_sess_options_kwarg(self) -> None:
|
||||
sess_options = ort.SessionOptions()
|
||||
encoder = OpenClipTextualEncoder(
|
||||
encoder = OpenCLIPEncoder(
|
||||
"ViT-B-32__openai",
|
||||
providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"],
|
||||
provider_options=[],
|
||||
@@ -146,43 +137,43 @@ class TestBase:
|
||||
assert sess_options is encoder.sess_options
|
||||
|
||||
def test_sets_default_cache_dir(self) -> None:
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.cache_dir == Path(settings.cache_folder) / "clip" / "ViT-B-32__openai"
|
||||
|
||||
def test_sets_cache_dir_kwarg(self) -> None:
|
||||
cache_dir = Path("/test_cache")
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
|
||||
assert encoder.cache_dir == cache_dir
|
||||
|
||||
def test_sets_default_preferred_format(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_default_preferred_runtime(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", True)
|
||||
mocker.patch("ann.ann.is_available", False)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ONNX
|
||||
assert encoder.preferred_runtime == ModelRuntime.ONNX
|
||||
|
||||
def test_sets_default_preferred_format_to_armnn_if_available(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_default_preferred_runtime_to_armnn_if_available(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", True)
|
||||
mocker.patch("ann.ann.is_available", True)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ARMNN
|
||||
assert encoder.preferred_runtime == ModelRuntime.ARMNN
|
||||
|
||||
def test_sets_preferred_format_kwarg(self, mocker: MockerFixture) -> None:
|
||||
def test_sets_preferred_runtime_kwarg(self, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(settings, "ann", False)
|
||||
mocker.patch("ann.ann.is_available", False)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", preferred_runtime=ModelRuntime.ARMNN)
|
||||
|
||||
assert encoder.preferred_format == ModelFormat.ARMNN
|
||||
assert encoder.preferred_runtime == ModelRuntime.ARMNN
|
||||
|
||||
def test_casts_cache_dir_string_to_path(self) -> None:
|
||||
cache_dir = "/test_cache"
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=cache_dir)
|
||||
|
||||
assert encoder.cache_dir == Path(cache_dir)
|
||||
|
||||
@@ -195,7 +186,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
info = mocker.spy(log, "info")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_called_once_with(encoder.cache_dir)
|
||||
@@ -210,7 +201,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
warning = mocker.spy(log, "warning")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_not_called()
|
||||
@@ -224,7 +215,7 @@ class TestBase:
|
||||
mock_cache_dir.is_dir.return_value = True
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
with pytest.raises(RuntimeError):
|
||||
encoder.clear_cache()
|
||||
|
||||
@@ -239,7 +230,7 @@ class TestBase:
|
||||
mocker.patch("app.models.base.Path", return_value=mock_cache_dir)
|
||||
warning = mocker.spy(log, "warning")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir=mock_cache_dir)
|
||||
encoder.clear_cache()
|
||||
|
||||
mock_rmtree.assert_not_called()
|
||||
@@ -254,7 +245,7 @@ class TestBase:
|
||||
mock_model_path.with_suffix.return_value = mock_model_path
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
encoder._make_session(mock_model_path)
|
||||
|
||||
mock_ann.assert_called_once()
|
||||
@@ -272,7 +263,7 @@ class TestBase:
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
mock_ort = mocker.patch("app.models.base.ort.InferenceSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
encoder._make_session(mock_armnn_path)
|
||||
|
||||
mock_ort.assert_called_once()
|
||||
@@ -286,7 +277,7 @@ class TestBase:
|
||||
mock_ann = mocker.patch("app.models.base.AnnSession")
|
||||
mock_ort = mocker.patch("app.models.base.ort.InferenceSession")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai")
|
||||
with pytest.raises(ValueError):
|
||||
encoder._make_session(mock_model_path)
|
||||
|
||||
@@ -296,7 +287,7 @@ class TestBase:
|
||||
def test_download(self, mocker: MockerFixture) -> None:
|
||||
mock_snapshot_download = mocker.patch("app.models.base.snapshot_download")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="/path/to/cache")
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="/path/to/cache")
|
||||
encoder.download()
|
||||
|
||||
mock_snapshot_download.assert_called_once_with(
|
||||
@@ -307,10 +298,10 @@ class TestBase:
|
||||
ignore_patterns=["*.armnn"],
|
||||
)
|
||||
|
||||
def test_download_downloads_armnn_if_preferred_format(self, mocker: MockerFixture) -> None:
|
||||
def test_download_downloads_armnn_if_preferred_runtime(self, mocker: MockerFixture) -> None:
|
||||
mock_snapshot_download = mocker.patch("app.models.base.snapshot_download")
|
||||
|
||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
||||
encoder = OpenCLIPEncoder("ViT-B-32__openai", preferred_runtime=ModelRuntime.ARMNN)
|
||||
encoder.download()
|
||||
|
||||
mock_snapshot_download.assert_called_once_with(
|
||||
@@ -332,17 +323,21 @@ class TestCLIP:
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipVisualEncoder, "download")
|
||||
mocker.patch.object(OpenClipVisualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipVisualEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
|
||||
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mocked.run.return_value = [[self.embedding]]
|
||||
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
|
||||
|
||||
clip_encoder = OpenClipVisualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="vision")
|
||||
embedding = clip_encoder.predict(pil_image)
|
||||
|
||||
assert clip_encoder.mode == "vision"
|
||||
assert isinstance(embedding, np.ndarray)
|
||||
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
|
||||
assert embedding.dtype == np.float32
|
||||
@@ -352,19 +347,22 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipTextualEncoder, "download")
|
||||
mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
|
||||
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mocked.run.return_value = [[self.embedding]]
|
||||
mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True)
|
||||
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
|
||||
|
||||
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
embedding = clip_encoder.predict("test search query")
|
||||
|
||||
assert clip_encoder.mode == "text"
|
||||
assert isinstance(embedding, np.ndarray)
|
||||
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
|
||||
assert embedding.dtype == np.float32
|
||||
@@ -374,18 +372,19 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(OpenClipTextualEncoder, "download")
|
||||
mocker.patch.object(OpenClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mock_tokenizer = mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True).return_value
|
||||
mock_ids = [randint(0, 50000) for _ in range(77)]
|
||||
mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids)
|
||||
|
||||
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder._load()
|
||||
clip_encoder = OpenCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
clip_encoder._load_tokenizer()
|
||||
tokens = clip_encoder.tokenize("test search query")
|
||||
|
||||
assert "text" in tokens
|
||||
@@ -398,19 +397,20 @@ class TestCLIP:
|
||||
self,
|
||||
mocker: MockerFixture,
|
||||
clip_model_cfg: dict[str, Any],
|
||||
clip_preprocess_cfg: Callable[[Path], dict[str, Any]],
|
||||
clip_tokenizer_cfg: Callable[[Path], dict[str, Any]],
|
||||
) -> None:
|
||||
mocker.patch.object(MClipTextualEncoder, "download")
|
||||
mocker.patch.object(MClipTextualEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(MClipTextualEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
|
||||
mock_tokenizer = mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True).return_value
|
||||
mocker.patch.object(OpenCLIPEncoder, "download")
|
||||
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
|
||||
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
|
||||
mock_tokenizer = mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True).return_value
|
||||
mock_ids = [randint(0, 50000) for _ in range(77)]
|
||||
mock_attention_mask = [randint(0, 1) for _ in range(77)]
|
||||
mock_tokenizer.encode.return_value = SimpleNamespace(ids=mock_ids, attention_mask=mock_attention_mask)
|
||||
|
||||
clip_encoder = MClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
|
||||
clip_encoder._load()
|
||||
clip_encoder = MCLIPEncoder("ViT-B-32__openai", cache_dir="test_cache", mode="text")
|
||||
clip_encoder._load_tokenizer()
|
||||
tokens = clip_encoder.tokenize("test search query")
|
||||
|
||||
assert "input_ids" in tokens
|
||||
@@ -430,90 +430,59 @@ class TestFaceRecognition:
|
||||
|
||||
assert face_recognizer.min_score == 0.5
|
||||
|
||||
def test_detection(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceDetector, "load")
|
||||
face_detector = FaceDetector("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
def test_basic(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
|
||||
det_model = mock.Mock()
|
||||
num_faces = 2
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([[0.67]] * num_faces).astype(np.float32)
|
||||
score = np.array([[0.67]] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
det_model.detect.return_value = (np.concatenate([bbox, scores], axis=-1), kpss)
|
||||
face_detector.model = det_model
|
||||
|
||||
faces = face_detector.predict(cv_image)
|
||||
|
||||
assert isinstance(faces, dict)
|
||||
assert isinstance(faces.get("boxes", None), np.ndarray)
|
||||
assert isinstance(faces.get("landmarks", None), np.ndarray)
|
||||
assert isinstance(faces.get("scores", None), np.ndarray)
|
||||
assert np.equal(faces["boxes"], bbox.round()).all()
|
||||
assert np.equal(faces["landmarks"], kpss).all()
|
||||
assert np.equal(faces["scores"], scores).all()
|
||||
det_model.detect.assert_called_once()
|
||||
|
||||
def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None:
|
||||
mocker.patch.object(FaceRecognizer, "load")
|
||||
face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache")
|
||||
|
||||
num_faces = 2
|
||||
bbox = np.random.rand(num_faces, 4).astype(np.float32)
|
||||
scores = np.array([0.67] * num_faces).astype(np.float32)
|
||||
kpss = np.random.rand(num_faces, 5, 2).astype(np.float32)
|
||||
faces = {"boxes": bbox, "landmarks": kpss, "scores": scores}
|
||||
det_model.detect.return_value = (np.concatenate([bbox, score], axis=-1), kpss)
|
||||
face_recognizer.det_model = det_model
|
||||
|
||||
rec_model = mock.Mock()
|
||||
embedding = np.random.rand(num_faces, 512).astype(np.float32)
|
||||
rec_model.get_feat.return_value = embedding
|
||||
face_recognizer.model = rec_model
|
||||
face_recognizer.rec_model = rec_model
|
||||
|
||||
faces = face_recognizer.predict(cv_image, faces)
|
||||
faces = face_recognizer.predict(cv_image)
|
||||
|
||||
assert isinstance(faces, list)
|
||||
assert len(faces) == num_faces
|
||||
for face in faces:
|
||||
assert isinstance(face.get("boundingBox"), dict)
|
||||
assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"}
|
||||
assert all(isinstance(val, np.float32) for val in face["boundingBox"].values())
|
||||
assert isinstance(face.get("embedding"), np.ndarray)
|
||||
assert face["imageHeight"] == 800
|
||||
assert face["imageWidth"] == 600
|
||||
assert isinstance(face["embedding"], np.ndarray)
|
||||
assert face["embedding"].shape[0] == 512
|
||||
assert isinstance(face.get("score", None), np.float32)
|
||||
assert face["embedding"].dtype == np.float32
|
||||
|
||||
rec_model.get_feat.assert_called_once()
|
||||
call_args = rec_model.get_feat.call_args_list[0].args
|
||||
assert len(call_args) == 1
|
||||
assert isinstance(call_args[0], list)
|
||||
assert isinstance(call_args[0][0], np.ndarray)
|
||||
assert call_args[0][0].shape == (112, 112, 3)
|
||||
det_model.detect.assert_called_once()
|
||||
assert rec_model.get_feat.call_count == num_faces
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestCache:
|
||||
async def test_caches(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION)
|
||||
assert len(model_cache.cache._cache) == 1
|
||||
mock_get_model.assert_called_once()
|
||||
|
||||
async def test_kwargs_used(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get(
|
||||
"test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache"
|
||||
)
|
||||
mock_get_model.assert_called_once_with(
|
||||
"test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, cache_dir="test_cache"
|
||||
)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, cache_dir="test_cache")
|
||||
mock_get_model.assert_called_once_with(ModelType.FACIAL_RECOGNITION, "test_model_name", cache_dir="test_cache")
|
||||
|
||||
async def test_different_clip(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.VISUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_image_model_name", ModelType.CLIP)
|
||||
await model_cache.get("test_text_model_name", ModelType.CLIP)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("test_model_name", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
mock.call("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call(ModelType.CLIP, "test_image_model_name"),
|
||||
mock.call(ModelType.CLIP, "test_text_model_name"),
|
||||
]
|
||||
)
|
||||
assert len(model_cache.cache._cache) == 2
|
||||
@@ -521,19 +490,19 @@ class TestCache:
|
||||
@mock.patch("app.models.cache.OptimisticLock", autospec=True)
|
||||
async def test_model_ttl(self, mock_lock_cls: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache()
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
mock_lock_cls.return_value.__aenter__.return_value.cas.assert_called_with(mock.ANY, ttl=100)
|
||||
|
||||
@mock.patch("app.models.cache.SimpleMemoryCache.expire")
|
||||
async def test_revalidate_get(self, mock_cache_expire: mock.Mock, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache(revalidate=True)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
mock_cache_expire.assert_called_once_with(mock.ANY, 100)
|
||||
|
||||
async def test_profiling(self, mock_get_model: mock.Mock) -> None:
|
||||
model_cache = ModelCache(profiling=True)
|
||||
await model_cache.get("test_model_name", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION, ttl=100)
|
||||
await model_cache.get("test_model_name", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
profiling = await model_cache.get_profiling()
|
||||
assert isinstance(profiling, dict)
|
||||
assert profiling == model_cache.cache.profiling
|
||||
@@ -541,9 +510,9 @@ class TestCache:
|
||||
async def test_loads_mclip(self) -> None:
|
||||
model_cache = ModelCache()
|
||||
|
||||
model = await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
model = await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.CLIP, mode="text")
|
||||
|
||||
assert isinstance(model, MClipTextualEncoder)
|
||||
assert isinstance(model, MCLIPEncoder)
|
||||
assert model.model_name == "XLM-Roberta-Large-Vit-B-32"
|
||||
|
||||
async def test_raises_exception_if_invalid_model_type(self) -> None:
|
||||
@@ -551,55 +520,15 @@ class TestCache:
|
||||
model_cache = ModelCache()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await model_cache.get("XLM-Roberta-Large-Vit-B-32", ModelType.TEXTUAL, invalid)
|
||||
await model_cache.get("XLM-Roberta-Large-Vit-B-32", invalid, mode="text")
|
||||
|
||||
async def test_raises_exception_if_unknown_model_name(self) -> None:
|
||||
model_cache = ModelCache()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await model_cache.get("test_model_name", ModelType.CLIP, mode="text")
|
||||
|
||||
async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.clip == "ViT-B-32__openai"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
async def test_preloads_facial_recognition_models(
|
||||
self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock
|
||||
) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.facial_recognition == "buffalo_s"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION),
|
||||
mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
async def test_preloads_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
|
||||
@@ -612,15 +541,11 @@ class TestCache:
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
||||
await preload_models(settings.preload)
|
||||
mock_get_model.assert_has_calls(
|
||||
[
|
||||
mock.call("ViT-B-32__openai", ModelType.TEXTUAL, ModelTask.SEARCH),
|
||||
mock.call("ViT-B-32__openai", ModelType.VISUAL, ModelTask.SEARCH),
|
||||
mock.call("buffalo_s", ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION),
|
||||
mock.call("buffalo_s", ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
assert len(model_cache.cache._cache) == 2
|
||||
assert mock_get_model.call_count == 2
|
||||
await model_cache.get("ViT-B-32__openai", ModelType.CLIP, ttl=100)
|
||||
await model_cache.get("buffalo_s", ModelType.FACIAL_RECOGNITION, ttl=100)
|
||||
assert mock_get_model.call_count == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -647,8 +572,7 @@ class TestLoad:
|
||||
async def test_load_clears_cache_and_retries_if_os_error(self) -> None:
|
||||
mock_model = mock.Mock(spec=InferenceModel)
|
||||
mock_model.model_name = "test_model_name"
|
||||
mock_model.model_type = ModelType.VISUAL
|
||||
mock_model.model_task = ModelTask.SEARCH
|
||||
mock_model.model_type = ModelType.CLIP
|
||||
mock_model.load.side_effect = [OSError, None]
|
||||
mock_model.loaded = False
|
||||
|
||||
@@ -673,15 +597,13 @@ class TestEndpoints:
|
||||
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})},
|
||||
data={"modelName": "ViT-B-32__openai", "modelType": "clip", "options": json.dumps({"mode": "vision"})},
|
||||
files={"image": byte_image.getvalue()},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert isinstance(actual.get("clip", None), list)
|
||||
assert np.allclose(expected, actual["clip"])
|
||||
assert np.allclose(expected, actual)
|
||||
|
||||
def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
expected = responses["clip"]["text"]
|
||||
@@ -689,115 +611,38 @@ class TestEndpoints:
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"entries": json.dumps(
|
||||
{
|
||||
"clip": {"textual": {"modelName": "ViT-B-32__openai"}},
|
||||
},
|
||||
),
|
||||
"modelName": "ViT-B-32__openai",
|
||||
"modelType": "clip",
|
||||
"text": "test search query",
|
||||
"options": json.dumps({"mode": "text"}),
|
||||
},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert isinstance(actual.get("clip", None), list)
|
||||
assert np.allclose(expected, actual["clip"])
|
||||
assert np.allclose(expected, actual)
|
||||
|
||||
def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None:
|
||||
byte_image = BytesIO()
|
||||
pil_image.save(byte_image, format="jpeg")
|
||||
expected = responses["facial-recognition"]
|
||||
|
||||
response = deployed_app.post(
|
||||
"http://localhost:3003/predict",
|
||||
data={
|
||||
"entries": json.dumps(
|
||||
{
|
||||
"facial-recognition": {
|
||||
"detection": {"modelName": "buffalo_l", "options": {"minScore": 0.034}},
|
||||
"recognition": {"modelName": "buffalo_l"},
|
||||
}
|
||||
}
|
||||
)
|
||||
"modelName": "buffalo_l",
|
||||
"modelType": "facial-recognition",
|
||||
"options": json.dumps({"minScore": 0.034}),
|
||||
},
|
||||
files={"image": byte_image.getvalue()},
|
||||
)
|
||||
|
||||
actual = response.json()
|
||||
assert response.status_code == 200
|
||||
assert isinstance(actual, dict)
|
||||
assert actual.get("imageHeight", None) == responses["imageHeight"]
|
||||
assert actual.get("imageWidth", None) == responses["imageWidth"]
|
||||
assert "facial-recognition" in actual and isinstance(actual["facial-recognition"], list)
|
||||
assert len(actual["facial-recognition"]) == len(responses["facial-recognition"])
|
||||
|
||||
for expected_face, actual_face in zip(responses["facial-recognition"], actual["facial-recognition"]):
|
||||
assert len(expected) == len(actual)
|
||||
for expected_face, actual_face in zip(expected, actual):
|
||||
assert expected_face["imageHeight"] == actual_face["imageHeight"]
|
||||
assert expected_face["imageWidth"] == actual_face["imageWidth"]
|
||||
assert expected_face["boundingBox"] == actual_face["boundingBox"]
|
||||
assert np.allclose(expected_face["embedding"], actual_face["embedding"])
|
||||
assert np.allclose(expected_face["score"], actual_face["score"])
|
||||
|
||||
|
||||
class TestSessionUtils:
|
||||
def test_ort_has_batch_dim(self, mocker: MockerFixture) -> None:
|
||||
mock_session = mocker.Mock(spec=ort.InferenceSession)
|
||||
mock_session.get_inputs.return_value = [SimpleNamespace(shape=["batch", 3, 224, 224], name="input.1")]
|
||||
|
||||
assert ort_has_batch_dim(mock_session) is True
|
||||
|
||||
def test_ort_has_no_batch_dim(self, mocker: MockerFixture) -> None:
|
||||
mock_session = mocker.Mock(spec=ort.InferenceSession)
|
||||
mock_session.get_inputs.return_value = [SimpleNamespace(shape=[1, 3, 224, 224], name="input.1")]
|
||||
|
||||
assert ort_has_batch_dim(mock_session) is False
|
||||
|
||||
def test_ort_squeeze_outputs(self, mocker: MockerFixture) -> None:
|
||||
mock_session = mocker.Mock(spec=ort.InferenceSession)
|
||||
mock_session.run.return_value = [np.random.rand(1, 3, 224, 224).astype(np.float32)]
|
||||
|
||||
ort_squeeze_outputs(mock_session)
|
||||
out = mock_session.run(["output"], {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)})
|
||||
|
||||
assert len(out) == 1
|
||||
assert out[0].shape == (3, 224, 224)
|
||||
|
||||
def test_ort_add_batch_dim(self, mocker: MockerFixture) -> None:
|
||||
mock_proto = mocker.Mock()
|
||||
mock_proto.graph.input = [
|
||||
SimpleNamespace(name="input", type=mock.Mock(tensor_type=SimpleNamespace(shape=SimpleNamespace())))
|
||||
]
|
||||
mock_proto.graph.output = [
|
||||
SimpleNamespace(name="output", type=SimpleNamespace(tensor_type=SimpleNamespace(shape=SimpleNamespace())))
|
||||
]
|
||||
mock_proto.graph.input[0].type.tensor_type.shape.dim = [
|
||||
SimpleNamespace(dim_value=3),
|
||||
SimpleNamespace(dim_value=224),
|
||||
SimpleNamespace(dim_value=224),
|
||||
]
|
||||
mock_proto.graph.output[0].type.tensor_type.shape.dim = [
|
||||
SimpleNamespace(dim_value=3),
|
||||
SimpleNamespace(dim_value=224),
|
||||
SimpleNamespace(dim_value=224),
|
||||
]
|
||||
|
||||
mock_load = mocker.patch("app.models.session.onnx.load")
|
||||
mock_load.return_value = mock_proto
|
||||
|
||||
mock_update_dims = mocker.patch("app.models.session.update_inputs_outputs_dims")
|
||||
mock_updated = mocker.Mock()
|
||||
mock_update_dims.return_value = mock_updated
|
||||
|
||||
mock_infer_shapes = mocker.patch("app.models.session.infer_shapes")
|
||||
mock_inferred = mocker.Mock()
|
||||
mock_infer_shapes.return_value = mock_inferred
|
||||
|
||||
mock_save = mocker.patch("app.models.session.onnx.save")
|
||||
|
||||
input_path, output_path = Path("input.onnx"), Path("output.onnx")
|
||||
ort_add_batch_dim(input_path, output_path)
|
||||
|
||||
mock_load.assert_called_once_with(input_path)
|
||||
mock_save.assert_called_once_with(mock_inferred, output_path)
|
||||
mock_update_dims.assert_called_once_with(mock_proto, mock.ANY, mock.ANY)
|
||||
assert mock_update_dims.call_args_list == [
|
||||
mock.call(mock_proto, {"input": ["batch", 224, 224]}, {"output": ["batch", 224, 224]})
|
||||
]
|
||||
|
||||
@@ -37,6 +37,7 @@ def on_test_start(environment: Environment, **kwargs: Any) -> None:
|
||||
global byte_image
|
||||
assert environment.parsed_options is not None
|
||||
image = Image.new("RGB", (environment.parsed_options.image_size, environment.parsed_options.image_size))
|
||||
byte_image = BytesIO()
|
||||
image.save(byte_image, format="jpeg")
|
||||
|
||||
|
||||
@@ -44,25 +45,34 @@ class InferenceLoadTest(HttpUser):
|
||||
abstract: bool = True
|
||||
host = "http://127.0.0.1:3003"
|
||||
data: bytes
|
||||
headers: dict[str, str] = {"Content-Type": "image/jpg"}
|
||||
|
||||
# re-use the image across all instances in a process
|
||||
def on_start(self) -> None:
|
||||
global byte_image
|
||||
self.data = byte_image.getvalue()
|
||||
|
||||
|
||||
class CLIPTextFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_text(self) -> None:
|
||||
request = {"clip": {"textual": {"modelName": self.environment.parsed_options.clip_model}}}
|
||||
data = [("entries", json.dumps(request)), ("text", "test search query")]
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"mode": "text"})),
|
||||
("text", "test search query"),
|
||||
]
|
||||
self.client.post("/predict", data=data)
|
||||
|
||||
|
||||
class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def encode_image(self) -> None:
|
||||
request = {"clip": {"visual": {"modelName": self.environment.parsed_options.clip_model, "options": {}}}}
|
||||
data = [("entries", json.dumps(request))]
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.clip_model),
|
||||
("modelType", "clip"),
|
||||
("options", json.dumps({"mode": "vision"})),
|
||||
]
|
||||
files = {"image": self.data}
|
||||
self.client.post("/predict", data=data, files=files)
|
||||
|
||||
@@ -70,18 +80,11 @@ class CLIPVisionFormDataLoadTest(InferenceLoadTest):
|
||||
class RecognitionFormDataLoadTest(InferenceLoadTest):
|
||||
@task
|
||||
def recognize(self) -> None:
|
||||
request = {
|
||||
"facial-recognition": {
|
||||
"recognition": {
|
||||
"modelName": self.environment.parsed_options.face_model,
|
||||
"options": {"minScore": self.environment.parsed_options.face_min_score},
|
||||
},
|
||||
"detection": {
|
||||
"modelName": self.environment.parsed_options.face_model,
|
||||
},
|
||||
}
|
||||
}
|
||||
data = [("entries", json.dumps(request))]
|
||||
data = [
|
||||
("modelName", self.environment.parsed_options.face_model),
|
||||
("modelType", "facial-recognition"),
|
||||
("options", json.dumps({"minScore": self.environment.parsed_options.face_min_score})),
|
||||
]
|
||||
files = {"image": self.data}
|
||||
|
||||
self.client.post("/predict", data=data, files=files)
|
||||
|
||||
70
machine-learning/poetry.lock
generated
70
machine-learning/poetry.lock
generated
@@ -1528,36 +1528,6 @@ files = [
|
||||
lint = ["pre-commit (>=3.3)"]
|
||||
test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "llvmlite"
|
||||
version = "0.42.0"
|
||||
description = "lightweight wrapper around basic LLVM functionality"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "llvmlite-0.42.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3366938e1bf63d26c34fbfb4c8e8d2ded57d11e0567d5bb243d89aab1eb56098"},
|
||||
{file = "llvmlite-0.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c35da49666a21185d21b551fc3caf46a935d54d66969d32d72af109b5e7d2b6f"},
|
||||
{file = "llvmlite-0.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70f44ccc3c6220bd23e0ba698a63ec2a7d3205da0d848804807f37fc243e3f77"},
|
||||
{file = "llvmlite-0.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763f8d8717a9073b9e0246998de89929071d15b47f254c10eef2310b9aac033d"},
|
||||
{file = "llvmlite-0.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:8d90edf400b4ceb3a0e776b6c6e4656d05c7187c439587e06f86afceb66d2be5"},
|
||||
{file = "llvmlite-0.42.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ae511caed28beaf1252dbaf5f40e663f533b79ceb408c874c01754cafabb9cbf"},
|
||||
{file = "llvmlite-0.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81e674c2fe85576e6c4474e8c7e7aba7901ac0196e864fe7985492b737dbab65"},
|
||||
{file = "llvmlite-0.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb3975787f13eb97629052edb5017f6c170eebc1c14a0433e8089e5db43bcce6"},
|
||||
{file = "llvmlite-0.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5bece0cdf77f22379f19b1959ccd7aee518afa4afbd3656c6365865f84903f9"},
|
||||
{file = "llvmlite-0.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e0c4c11c8c2aa9b0701f91b799cb9134a6a6de51444eff5a9087fc7c1384275"},
|
||||
{file = "llvmlite-0.42.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:08fa9ab02b0d0179c688a4216b8939138266519aaa0aa94f1195a8542faedb56"},
|
||||
{file = "llvmlite-0.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b2fce7d355068494d1e42202c7aff25d50c462584233013eb4470c33b995e3ee"},
|
||||
{file = "llvmlite-0.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebe66a86dc44634b59a3bc860c7b20d26d9aaffcd30364ebe8ba79161a9121f4"},
|
||||
{file = "llvmlite-0.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d47494552559e00d81bfb836cf1c4d5a5062e54102cc5767d5aa1e77ccd2505c"},
|
||||
{file = "llvmlite-0.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:05cb7e9b6ce69165ce4d1b994fbdedca0c62492e537b0cc86141b6e2c78d5888"},
|
||||
{file = "llvmlite-0.42.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bdd3888544538a94d7ec99e7c62a0cdd8833609c85f0c23fcb6c5c591aec60ad"},
|
||||
{file = "llvmlite-0.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0936c2067a67fb8816c908d5457d63eba3e2b17e515c5fe00e5ee2bace06040"},
|
||||
{file = "llvmlite-0.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a78ab89f1924fc11482209f6799a7a3fc74ddc80425a7a3e0e8174af0e9e2301"},
|
||||
{file = "llvmlite-0.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7599b65c7af7abbc978dbf345712c60fd596aa5670496561cc10e8a71cebfb2"},
|
||||
{file = "llvmlite-0.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:43d65cc4e206c2e902c1004dd5418417c4efa6c1d04df05c6c5675a27e8ca90e"},
|
||||
{file = "llvmlite-0.42.0.tar.gz", hash = "sha256:f92b09243c0cc3f457da8b983f67bd8e1295d0f5b3746c7a1861d7a99403854a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "locust"
|
||||
version = "2.28.0"
|
||||
@@ -1894,40 +1864,6 @@ doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.
|
||||
extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"]
|
||||
test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "numba"
|
||||
version = "0.59.1"
|
||||
description = "compiling Python code using LLVM"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "numba-0.59.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97385a7f12212c4f4bc28f648720a92514bee79d7063e40ef66c2d30600fd18e"},
|
||||
{file = "numba-0.59.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0b77aecf52040de2a1eb1d7e314497b9e56fba17466c80b457b971a25bb1576d"},
|
||||
{file = "numba-0.59.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3476a4f641bfd58f35ead42f4dcaf5f132569c4647c6f1360ccf18ee4cda3990"},
|
||||
{file = "numba-0.59.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:525ef3f820931bdae95ee5379c670d5c97289c6520726bc6937a4a7d4230ba24"},
|
||||
{file = "numba-0.59.1-cp310-cp310-win_amd64.whl", hash = "sha256:990e395e44d192a12105eca3083b61307db7da10e093972ca285c85bef0963d6"},
|
||||
{file = "numba-0.59.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43727e7ad20b3ec23ee4fc642f5b61845c71f75dd2825b3c234390c6d8d64051"},
|
||||
{file = "numba-0.59.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:411df625372c77959570050e861981e9d196cc1da9aa62c3d6a836b5cc338966"},
|
||||
{file = "numba-0.59.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2801003caa263d1e8497fb84829a7ecfb61738a95f62bc05693fcf1733e978e4"},
|
||||
{file = "numba-0.59.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd2842fac03be4e5324ebbbd4d2d0c8c0fc6e0df75c09477dd45b288a0777389"},
|
||||
{file = "numba-0.59.1-cp311-cp311-win_amd64.whl", hash = "sha256:0594b3dfb369fada1f8bb2e3045cd6c61a564c62e50cf1f86b4666bc721b3450"},
|
||||
{file = "numba-0.59.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1cce206a3b92836cdf26ef39d3a3242fec25e07f020cc4feec4c4a865e340569"},
|
||||
{file = "numba-0.59.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c8b4477763cb1fbd86a3be7050500229417bf60867c93e131fd2626edb02238"},
|
||||
{file = "numba-0.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d80bce4ef7e65bf895c29e3889ca75a29ee01da80266a01d34815918e365835"},
|
||||
{file = "numba-0.59.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7ad1d217773e89a9845886401eaaab0a156a90aa2f179fdc125261fd1105096"},
|
||||
{file = "numba-0.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:5bf68f4d69dd3a9f26a9b23548fa23e3bcb9042e2935257b471d2a8d3c424b7f"},
|
||||
{file = "numba-0.59.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4e0318ae729de6e5dbe64c75ead1a95eb01fabfe0e2ebed81ebf0344d32db0ae"},
|
||||
{file = "numba-0.59.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0f68589740a8c38bb7dc1b938b55d1145244c8353078eea23895d4f82c8b9ec1"},
|
||||
{file = "numba-0.59.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:649913a3758891c77c32e2d2a3bcbedf4a69f5fea276d11f9119677c45a422e8"},
|
||||
{file = "numba-0.59.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9712808e4545270291d76b9a264839ac878c5eb7d8b6e02c970dc0ac29bc8187"},
|
||||
{file = "numba-0.59.1-cp39-cp39-win_amd64.whl", hash = "sha256:8d51ccd7008a83105ad6a0082b6a2b70f1142dc7cfd76deb8c5a862367eb8c86"},
|
||||
{file = "numba-0.59.1.tar.gz", hash = "sha256:76f69132b96028d2774ed20415e8c528a34e3299a40581bae178f0994a2f370b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
llvmlite = "==0.42.*"
|
||||
numpy = ">=1.22,<1.27"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.26.3"
|
||||
@@ -2101,8 +2037,11 @@ description = "ONNX Runtime is a runtime accelerator for Machine Learning models
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "onnxruntime_openvino-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ed693011b472f9a617b2d5c4785d5fa1e1b77f7cb2b02e47b899534ec6c6396"},
|
||||
{file = "onnxruntime_openvino-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:5152b5e56e83e022ced2986700d68dd8ba7b1466761725ce774f679c5710ab87"},
|
||||
{file = "onnxruntime_openvino-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce3b1aa06d6b8b732d314d217028ec4735de5806215c44d3bdbcad03b9260d5"},
|
||||
{file = "onnxruntime_openvino-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:21133a701bb07ea19e01f48b8c23beee575f2e879f49173843f275d7c91a625a"},
|
||||
{file = "onnxruntime_openvino-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76824dac3c392ad4b812f29c18be2055ab3bba2e3c111e44baae847b33d5b081"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2662,6 +2601,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
@@ -3632,4 +3572,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.12"
|
||||
content-hash = "a44e079d565fc1166458690ca2dc5826e198cc07ccab0ebaf71b5ab5e0eed150"
|
||||
content-hash = "db51ad1e631b569e106927683a13124252bd80974def1f2edbe23ac87d89c461"
|
||||
|
||||
@@ -23,7 +23,6 @@ orjson = ">=3.9.5"
|
||||
gunicorn = ">=21.1.0"
|
||||
huggingface-hub = ">=0.20.1,<1.0"
|
||||
tokenizers = ">=0.15.0,<1.0"
|
||||
numba = "^0.59.1"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = ">=1.3.0"
|
||||
|
||||
@@ -213,6 +213,8 @@
|
||||
},
|
||||
"facial-recognition": [
|
||||
{
|
||||
"imageWidth": 600,
|
||||
"imageHeight": 800,
|
||||
"boundingBox": {
|
||||
"x1": 690.0,
|
||||
"y1": -89.0,
|
||||
@@ -323,7 +325,5 @@
|
||||
-0.077056274, 0.002099529
|
||||
]
|
||||
}
|
||||
],
|
||||
"imageWidth": 600,
|
||||
"imageHeight": 800
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"action_common_back": "خلف",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "يلغي",
|
||||
"action_common_clear": "مسح",
|
||||
"action_common_confirm": "تأكيد",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "تحديث",
|
||||
"add_to_album_bottom_sheet_added": "تمت الاضافة{album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "موجودة مسبقا {album}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"advanced_settings_log_level_title": "تسجيل مستوى: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "تكون بعض الأجهزة بطيئة للغاية في تحميل الصور المصغرة من الأصول الموجودة على الجهاز. قم بتنشيط هذا الإعداد لتحميل الصور البعيدة بدلاً من ذلك.",
|
||||
"advanced_settings_prefer_remote_title": "تفضل الصور البعيدة",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
@@ -18,11 +18,11 @@
|
||||
"album_info_card_backup_album_excluded": "مستبعد",
|
||||
"album_info_card_backup_album_included": "متضمنة",
|
||||
"album_thumbnail_card_item": "عنصر واحد",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_items": "{} عناصر",
|
||||
"album_thumbnail_card_shared": " · . مشترك",
|
||||
"album_thumbnail_owned": "مملوكة",
|
||||
"album_thumbnail_shared_by": "Shared by {}",
|
||||
"album_viewer_appbar_delete_confirm": "هل أنت متأكد أنك تريد حذف هذا الألبوم من حسابك؟",
|
||||
"album_thumbnail_shared_by": "مشترك مع",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "حذف الألبوم",
|
||||
"album_viewer_appbar_share_err_delete": "فشل في حذف الألبوم",
|
||||
"album_viewer_appbar_share_err_leave": "فشل في ترك الألبوم",
|
||||
@@ -38,20 +38,20 @@
|
||||
"app_bar_signout_dialog_ok": "نعم",
|
||||
"app_bar_signout_dialog_title": "خروج",
|
||||
"archive_page_no_archived_assets": "لم يتم العثور على الأصول المؤرشفة",
|
||||
"archive_page_title": "Archive ({})",
|
||||
"archive_page_title": "أرشيف ({})",
|
||||
"asset_action_delete_err_read_only": "لا يمكن حذف الأصول ذات للقراءة فقط، وسوف يتم التخطي",
|
||||
"asset_action_share_err_offline": "لا يمكن جلب الأصول غير المتصلة بالإنترنت، وسوف يتم التخطي",
|
||||
"asset_list_group_by_sub_title": "تنظيم بواسطة",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "تخطيط ديناميكي",
|
||||
"asset_list_layout_settings_group_automatically": "تلقائي",
|
||||
"asset_list_layout_settings_group_by": "مجموعة الأصول حسب",
|
||||
"asset_list_layout_settings_group_by_month": "شهر",
|
||||
"asset_list_layout_settings_group_by_month_day": "شهر + يوم",
|
||||
"asset_list_layout_sub_title": "تصميم",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "إعدادات تخطيط شبكة الصور",
|
||||
"asset_list_settings_title": "شبكة الصور",
|
||||
"asset_viewer_settings_title": "عارض الأصول",
|
||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "الألبومات الموجودة على الجهاز ({})",
|
||||
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
||||
"backup_album_selection_page_assets_scatter": "يمكن أن تنتشر الأصول عبر ألبومات متعددة. وبالتالي، يمكن تضمين الألبومات أو استبعادها أثناء عملية النسخ الاحتياطي.",
|
||||
"backup_album_selection_page_select_albums": "حدد الألبومات",
|
||||
@@ -60,22 +60,22 @@
|
||||
"backup_all": "الجميع",
|
||||
"backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة...",
|
||||
"backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة...",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_current_upload_notification": "تحميل {}",
|
||||
"backup_background_service_default_notification": "التحقق من الأصول الجديدة ...",
|
||||
"backup_background_service_error_title": "خطأ في النسخ الاحتياطي",
|
||||
"backup_background_service_in_progress_notification": "النسخ الاحتياطي للأصول الخاصة بك...",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_background_service_upload_failure_notification": "فشل التحميل {}",
|
||||
"backup_controller_page_albums": "ألبومات احتياطية",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "قم بتمكين تحديث تطبيق الخلفية في الإعدادات > عام > تحديث تطبيق الخلفية لاستخدام النسخ الاحتياطي في الخلفية.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "تم تعطيل تحديث التطبيق في الخلفية",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "اذهب للاعدادات",
|
||||
"backup_controller_page_background_battery_info_link": "أرني كيف",
|
||||
"backup_controller_page_background_battery_info_message": "للحصول على أفضل تجربة نسخ احتياطي في الخلفية، يرجى تعطيل أي تحسينات للبطارية تقيد نشاط الخلفية لـ تطبيق.\n\nنظرًا لأن هذا خاص بالجهاز، يرجى البحث عن المعلومات المطلوبة للشركة المصنعة لجهازك.",
|
||||
"backup_controller_page_background_battery_info_message": "للحصول على أفضل تجربة نسخ احتياطي في الخلفية، يرجى تعطيل أي تحسينات للبطارية تقيد نشاط الخلفية لـ Immich.\n\nنظرًا لأن هذا خاص بالجهاز، يرجى البحث عن المعلومات المطلوبة للشركة المصنعة لجهازك.",
|
||||
"backup_controller_page_background_battery_info_ok": "نعم",
|
||||
"backup_controller_page_background_battery_info_title": "تحسين البطارية",
|
||||
"backup_controller_page_background_charging": "فقط أثناء الشحن",
|
||||
"backup_controller_page_background_configure_error": "فشل في تكوين خدمة الخلفية",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_delay": "تأخير النسخ الاحتياطي للأصول الجديدة: {}",
|
||||
"backup_controller_page_background_description": "قم بتشغيل خدمة الخلفية لإجراء نسخ احتياطي لأي أصول جديدة تلقائيًا دون الحاجة إلى فتح التطبيق",
|
||||
"backup_controller_page_background_is_off": "تم إيقاف النسخ الاحتياطي التلقائي للخلفية",
|
||||
"backup_controller_page_background_is_on": "النسخ الاحتياطي التلقائي للخلفية قيد التشغيل",
|
||||
@@ -86,12 +86,12 @@
|
||||
"backup_controller_page_backup_selected": "المحدد: ",
|
||||
"backup_controller_page_backup_sub": "النسخ الاحتياطي للصور ومقاطع الفيديو",
|
||||
"backup_controller_page_cancel": "يلغي",
|
||||
"backup_controller_page_created": "Created on: {}",
|
||||
"backup_controller_page_created": "تم إنشاؤها على: {}",
|
||||
"backup_controller_page_desc_backup": "قم بتشغيل النسخ الاحتياطي الأمامي لتحميل الأصول الجديدة تلقائيًا إلى الخادم عند فتح التطبيق.",
|
||||
"backup_controller_page_excluded": "مستبعد: ",
|
||||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_failed": "فشل ({})",
|
||||
"backup_controller_page_filename": "اسم الملف: {} [{}]",
|
||||
"backup_controller_page_id": "رقم البطاقة: {}",
|
||||
"backup_controller_page_info": "معلومات النسخ الاحتياطي",
|
||||
"backup_controller_page_none_selected": "لم يتم التحديد",
|
||||
"backup_controller_page_remainder": "بقية",
|
||||
@@ -101,7 +101,7 @@
|
||||
"backup_controller_page_start_backup": "بدء النسخ الاحتياطي",
|
||||
"backup_controller_page_status_off": "النسخة الاحتياطية التلقائية غير فعالة",
|
||||
"backup_controller_page_status_on": "النسخة الاحتياطية التلقائية فعالة",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_storage_format": "{} من {} المستخدم",
|
||||
"backup_controller_page_to_backup": "الألبومات الاحتياطية",
|
||||
"backup_controller_page_total": "المجموع",
|
||||
"backup_controller_page_total_sub": "جميع الصور ومقاطع الفيديو الفريدة من ألبومات مختارة",
|
||||
@@ -115,22 +115,22 @@
|
||||
"backup_manual_in_progress": "قيد التحميل حاول مره اخرى",
|
||||
"backup_manual_success": "نجاح",
|
||||
"backup_manual_title": "حالة التحميل",
|
||||
"backup_options_page_title": "خيارات النسخ الاحتياطي",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "صور صفحة المكتبة ({} الأصول)",
|
||||
"cache_settings_clear_cache_button": "مسح ذاكرة التخزين المؤقت",
|
||||
"cache_settings_clear_cache_button_title": "يقوم بمسح ذاكرة التخزين المؤقت للتطبيق.سيؤثر هذا بشكل كبير على أداء التطبيق حتى إعادة بناء ذاكرة التخزين المؤقت.",
|
||||
"cache_settings_duplicated_assets_clear_button": "واضح",
|
||||
"cache_settings_duplicated_assets_subtitle": "الصور ومقاطع الفيديو اللتي تم تجاهلها المدرجة في التطبيق",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_duplicated_assets_title": "الأصول المكررة ({})",
|
||||
"cache_settings_image_cache_size": "حجم ذاكرة التخزين المؤقت للصور ({} الأصول)",
|
||||
"cache_settings_statistics_album": "مكتبه الصور المصغره",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_assets": " ({})أصول ",
|
||||
"cache_settings_statistics_full": "صور كاملة",
|
||||
"cache_settings_statistics_shared": "صورة ألبوم مشتركة",
|
||||
"cache_settings_statistics_thumbnail": "الصورة المصغرة",
|
||||
"cache_settings_statistics_title": "استخدام ذاكرة التخزين المؤقت",
|
||||
"cache_settings_subtitle": "تحكم في سلوك التخزين المؤقت لتطبيق الجوال.",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_subtitle": "تحكم في سلوك التخزين المؤقت لتطبيق الجوال Imich",
|
||||
"cache_settings_thumbnail_size": "حجم ذاكرة التخزين المؤقت Thumbnail ({} الأصول)",
|
||||
"cache_settings_tile_subtitle": "التحكم في سلوك التخزين المحلي",
|
||||
"cache_settings_tile_title": "التخزين المحلي",
|
||||
"cache_settings_title": "إعدادات التخزين المؤقت",
|
||||
@@ -145,12 +145,12 @@
|
||||
"common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.",
|
||||
"common_shared": "مشترك",
|
||||
"control_bottom_app_bar_add_to_album": "أضف إلى الألبوم",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_album_info": "{} أغراض",
|
||||
"control_bottom_app_bar_album_info_shared": "{} العناصر المشتركة",
|
||||
"control_bottom_app_bar_archive": "أرشيف",
|
||||
"control_bottom_app_bar_create_new_album": "إنشاء ألبوم جديد",
|
||||
"control_bottom_app_bar_delete": "يمسح",
|
||||
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
||||
"control_bottom_app_bar_delete_from_immich": " Immich حذف منال تطبيق",
|
||||
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
||||
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
||||
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
||||
@@ -172,10 +172,10 @@
|
||||
"daily_title_text_date": "E ، MMM DD",
|
||||
"daily_title_text_date_year": "E ، MMM DD ، yyyy",
|
||||
"date_format": "E ، Lll D ، Y • H: MM A",
|
||||
"delete_dialog_alert": " هذه العناصر سيتم حذفها بشكل دائم من جهازك ومن تطبيق",
|
||||
"delete_dialog_alert_local": " العناصر التي تم حذفها من جهازك ولكنها موجوده في تطبيق",
|
||||
"delete_dialog_alert_local_non_backed_up": "بعض العناصر التي سيتم حذفها بشكل دائم ولا يوجد لها نسخه احتياطيه في تطبيق ",
|
||||
"delete_dialog_alert_remote": "العناصر التي سيتم حذفها بشكل دائم من تطبيق",
|
||||
"delete_dialog_alert": " Immich هذه العناصر سيتم حذفها بشكل دائم من جهازك ومن تطبيق",
|
||||
"delete_dialog_alert_local": " Immich العناصر التي تم حذفها من جهازك ولكنها موجوده في تطبيق",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immich بعض العناصر التي سيتم حذفها بشكل دائم ولا يوجد لها نسخه احتياطيه في تطبيق ",
|
||||
"delete_dialog_alert_remote": "Immich العناصر التي سيتم حذفها بشكل دائم من تطبيق",
|
||||
"delete_dialog_cancel": "يلغي",
|
||||
"delete_dialog_ok": "يمسح",
|
||||
"delete_dialog_ok_force": "احذف على أي حال",
|
||||
@@ -194,15 +194,15 @@
|
||||
"exif_bottom_sheet_location": "موقع",
|
||||
"exif_bottom_sheet_location_add": "إضافة موقع",
|
||||
"exif_bottom_sheet_people": "الناس",
|
||||
"exif_bottom_sheet_person_add_person": "اضف اسما",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "أعمال جارية",
|
||||
"experimental_settings_new_asset_list_title": "تمكين شبكة الصور التجريبية",
|
||||
"experimental_settings_subtitle": "استخدام على مسؤوليتك الخاصة!",
|
||||
"experimental_settings_title": "تجريبي",
|
||||
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
||||
"favorites_page_title": "المفضلة",
|
||||
"haptic_feedback_switch": "تمكين ردود الفعل اللمسية",
|
||||
"haptic_feedback_title": "ردود فعل لمسية",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "تمت إضافة {تمت إضافة} الأصول إلى الألبوم {الألبوم}.{فشل} الأصول موجودة بالفعل في الألبوم.",
|
||||
"home_page_add_to_album_err_local": "لا يمكن إضافة الأصول المحلية إلى الألبومات حتى الآن ، سوف يتخطى",
|
||||
"home_page_add_to_album_success": "تمت إضافة {تمت إضافة} الأصول إلى الألبوم {الألبوم}.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "لا يمكن مشاركة الأصول المحلية عبر الرابط ، سوف يتخطى",
|
||||
"home_page_upload_err_limit": "لا يمكن إلا تحميل 30 أحد الأصول في وقت واحد ، سوف يتخطى",
|
||||
"image_viewer_page_state_provider_download_error": "خطا في التحميل",
|
||||
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
||||
"image_viewer_page_state_provider_share_error": "خطأ في المشاركة",
|
||||
"library_page_albums": "ألبومات",
|
||||
@@ -265,8 +265,8 @@
|
||||
"login_form_server_error": "لا يمكن الاتصال بالخادم.",
|
||||
"login_password_changed_error": "كان هناك خطأ في تحديث كلمة المرور الخاصة بك",
|
||||
"login_password_changed_success": "تم تحديث كلمة السر بنجاح",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_assets_in_bound": "{} صورة",
|
||||
"map_assets_in_bounds": "{} الصور",
|
||||
"map_cannot_get_user_location": "لا يمكن الحصول على موقع المستخدم",
|
||||
"map_location_dialog_cancel": "يلغي",
|
||||
"map_location_dialog_yes": "نعم",
|
||||
@@ -279,27 +279,27 @@
|
||||
"map_settings_dark_mode": "الوضع المظلم",
|
||||
"map_settings_date_range_option_all": "الجميع",
|
||||
"map_settings_date_range_option_day": "24 ساعة الماضية",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_days": "الأيام الماضية {}",
|
||||
"map_settings_date_range_option_year": "السنة الفائتة",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_date_range_option_years": "السنوات الماضية {}",
|
||||
"map_settings_dialog_cancel": "يلغي",
|
||||
"map_settings_dialog_save": "يحفظ",
|
||||
"map_settings_dialog_title": "إعدادات الخريطة",
|
||||
"map_settings_include_show_archived": "تشمل الأرشفة",
|
||||
"map_settings_include_show_partners": "تضمين الشركاء",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "نطاق الموعد",
|
||||
"map_settings_only_show_favorites": "اظهار المفضلة فقط",
|
||||
"map_settings_theme_settings": "مظهر الخريطة",
|
||||
"map_zoom_to_see_photos": "قم بتصغيرها لرؤية الصور",
|
||||
"memories_all_caught_up": "كل شيء محدث",
|
||||
"memories_check_back_tomorrow": "التحقق مرة أخرى غدا لمزيد من الذكريات",
|
||||
"memories_start_over": "ابدأ من جديد",
|
||||
"memories_swipe_to_close": "اسحب لأعلى للإغلاق",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "ط ط ط",
|
||||
"motion_photos_page_title": "الصور المتحركة",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "لا يمكن تعديل تاريخ الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||
"no_assets_to_show": "لا توجد أصول لعرضها",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "يلغي",
|
||||
"notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.",
|
||||
"notification_permission_dialog_settings": "إعدادات",
|
||||
@@ -307,14 +307,14 @@
|
||||
"notification_permission_list_tile_enable_button": "تمكين الإخطارات",
|
||||
"notification_permission_list_tile_title": "إذن الإخطار",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "عرض الكل",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "أضف شريكًا",
|
||||
"partner_page_empty_message": "لم يتم مشاركة صورك بعد مع أي شريك.",
|
||||
"partner_page_no_more_users": "لا مزيد من المستخدمين لإضافة",
|
||||
"partner_page_partner_add_failed": "فشل في إضافة شريك",
|
||||
"partner_page_select_partner": "حدد شريكًا",
|
||||
"partner_page_shared_to_title": "مشترك ل",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_content": "{} لن يكون قادرًا على الوصول إلى صورك.",
|
||||
"partner_page_stop_sharing_title": "توقف عن مشاركة صورك؟",
|
||||
"partner_page_title": "شريك",
|
||||
"permission_onboarding_back": "خلف",
|
||||
@@ -327,7 +327,7 @@
|
||||
"permission_onboarding_permission_granted": "تم تأمين التصريح! وضعك تمام.",
|
||||
"permission_onboarding_permission_limited": "إذن محدود. للسماح بالنسخ الاحتياطي للتطبيق وإدارة مجموعة المعرض بالكامل، امنح أذونات الصور والفيديو في الإعدادات.",
|
||||
"permission_onboarding_request": "يتطلب التطبيق إذنًا لعرض الصور ومقاطع الفيديو الخاصة بك",
|
||||
"preferences_settings_title": "التفضيلات",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "السجلات",
|
||||
"profile_drawer_client_out_of_date_major": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار رئيسي.",
|
||||
"profile_drawer_client_out_of_date_minor": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار صغير.",
|
||||
@@ -342,18 +342,18 @@
|
||||
"recently_added_page_title": "أضيف مؤخرا",
|
||||
"scaffold_body_error_occurred": "حدث خطأ",
|
||||
"search_bar_hint": "ابحث عن صورك",
|
||||
"search_filter_apply": "اختار الفلتر ",
|
||||
"search_filter_camera_make": "صنع",
|
||||
"search_filter_camera_model": "نموذج",
|
||||
"search_filter_display_option_archive": "أرشيف",
|
||||
"search_filter_display_option_favorite": "مفضل",
|
||||
"search_filter_display_option_not_in_album": "ليس في الألبوم",
|
||||
"search_filter_location_city": "مدينة",
|
||||
"search_filter_location_country": "دولة",
|
||||
"search_filter_location_state": "ولاية",
|
||||
"search_filter_media_type_all": "الجميع",
|
||||
"search_filter_media_type_image": "صورة",
|
||||
"search_filter_media_type_video": "شريط فيديو",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "فئات",
|
||||
"search_page_favorites": "المفضلة",
|
||||
"search_page_motion_photos": "الصور المتحركه",
|
||||
@@ -391,15 +391,14 @@
|
||||
"setting_image_viewer_original_title": "تحميل الصورة الأصلية",
|
||||
"setting_image_viewer_preview_subtitle": "تمكين تحميل صورة متوسطة الدقة.تعطيل إما لتحميل مباشرة أو استخدام الصورة المصغرة مباشرة.",
|
||||
"setting_image_viewer_preview_title": "تحميل صورة معاينة",
|
||||
"setting_image_viewer_title": "الصور",
|
||||
"setting_languages_apply": "تغيير الإعدادات",
|
||||
"setting_languages_title": "اللغات",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "أخطر فشل النسخ الاحتياطي في الخلفية: {}",
|
||||
"setting_notifications_notify_hours": "{} ساعات",
|
||||
"setting_notifications_notify_immediately": "في الحال",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_minutes": "{} دقائق",
|
||||
"setting_notifications_notify_never": "أبداً",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_notify_seconds": "{} ثانية",
|
||||
"setting_notifications_single_progress_subtitle": "معلومات التقدم التفصيلية تحميل لكل أصل",
|
||||
"setting_notifications_single_progress_title": "إظهار تقدم التفاصيل الاحتياطية الخلفية",
|
||||
"setting_notifications_subtitle": "اضبط تفضيلات الإخطار",
|
||||
@@ -407,10 +406,7 @@
|
||||
"setting_notifications_total_progress_subtitle": "التقدم التحميل العام (تم القيام به/إجمالي الأصول)",
|
||||
"setting_notifications_total_progress_title": "إظهار النسخ الاحتياطي الخلفية التقدم المحرز",
|
||||
"setting_pages_app_bar_settings": "إعدادات",
|
||||
"settings_require_restart": "يرجى إعادة تشغيل لتطبيق هذا الإعداد",
|
||||
"setting_video_viewer_looping_subtitle": "تمكين تكرار مقطع فيديو تلقائيًا في عارض التفاصيل.",
|
||||
"setting_video_viewer_looping_title": "تكرار مقطع فيديو تلقائيًا",
|
||||
"setting_video_viewer_title": "أشرطة فيديو",
|
||||
"settings_require_restart": "يرجى إعادة تشغيل Imich لتطبيق هذا الإعداد",
|
||||
"share_add": "يضيف",
|
||||
"share_add_photos": "إضافة الصور",
|
||||
"share_add_title": "إضافة عنوان",
|
||||
@@ -430,7 +426,7 @@
|
||||
"share_dialog_preparing": "تحضير...",
|
||||
"shared_link_app_bar_title": "روابط مشتركة",
|
||||
"shared_link_clipboard_copied_massage": "نسخ إلى الحافظة",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_clipboard_text": "وصلة: {}كلمة المرور: {}",
|
||||
"shared_link_create_app_bar_title": "إنشاء رابط للمشاركة",
|
||||
"shared_link_create_error": "خطأ أثناء إنشاء رابط مشترك",
|
||||
"shared_link_create_info": "دع أي شخص لديه الرابط يرى الصور المحددة)",
|
||||
@@ -443,11 +439,11 @@
|
||||
"shared_link_edit_description_hint": "أدخل وصف المشاركة",
|
||||
"shared_link_edit_expire_after": "تنتهي بعد",
|
||||
"shared_link_edit_expire_after_option_day": "يوم 1",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_days": "{} أيام",
|
||||
"shared_link_edit_expire_after_option_hour": "1 ساعة",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_hours": "{} ساعات",
|
||||
"shared_link_edit_expire_after_option_minute": "1 دقيقة",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} دقائق",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "أبداً",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
@@ -458,21 +454,21 @@
|
||||
"shared_link_empty": "ليس لديك أي روابط مشتركة",
|
||||
"shared_link_error_server_url_fetch": "لا يمكن جلب عنوان الخادم",
|
||||
"shared_link_expired": "منتهي الصلاحية",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_day": "تنتهي في اليوم {}",
|
||||
"shared_link_expires_days": "تنتهي في {} أيام",
|
||||
"shared_link_expires_hour": "تنتهي في الساعة {}",
|
||||
"shared_link_expires_hours": "تنتهي في {} ساعات",
|
||||
"shared_link_expires_minute": "تنتهي في {} دقيقة",
|
||||
"shared_link_expires_minutes": "تنتهي في الدقائق {}",
|
||||
"shared_link_expires_never": "تنتهي ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_expires_second": "تنتهي في {} الثاني",
|
||||
"shared_link_expires_seconds": "تنتهي في ثواني {}",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "تحميل",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "رفع",
|
||||
"shared_link_manage_links": "إدارة الروابط المشتركة",
|
||||
"shared_link_public_album": "الألبوم العام",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "منتهي",
|
||||
"share_invite": "دعوة إلى الألبوم",
|
||||
"sharing_page_album": "ألبومات مشتركة",
|
||||
@@ -486,7 +482,7 @@
|
||||
"tab_controller_nav_search": "يبحث",
|
||||
"tab_controller_nav_sharing": "مشاركة",
|
||||
"theme_setting_asset_list_storage_indicator_title": "عرض مؤشر التخزين على بلاط الأصول",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "عدد الأصول لكل صف ({})",
|
||||
"theme_setting_dark_mode_switch": "الوضع المظلم",
|
||||
"theme_setting_image_viewer_quality_subtitle": "اضبط جودة عارض الصورة التفصيلية",
|
||||
"theme_setting_image_viewer_quality_title": "جودة عارض الصورة",
|
||||
@@ -501,13 +497,13 @@
|
||||
"trash_page_empty_trash_btn": "افرغ سله المهملات ",
|
||||
"trash_page_empty_trash_dialog_content": "هل تريد تفريغ أصولك المهملة؟ ستتم إزالة هذه العناصر نهائيًا من التطبيق",
|
||||
"trash_page_empty_trash_dialog_ok": "نعم",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_info": "سيتم حذف العناصر المحذوفة بشكل دائم بعد {} أيام",
|
||||
"trash_page_no_assets": "لا توجد اصول في سله المهملات",
|
||||
"trash_page_restore": "الترجيع من سله المهملات",
|
||||
"trash_page_restore_all": "استعادة الكل",
|
||||
"trash_page_select_assets_btn": "اختر الأصول ",
|
||||
"trash_page_select_btn": "يختار",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"trash_page_title": "نفايات ({})",
|
||||
"upload_dialog_cancel": "يلغي",
|
||||
"upload_dialog_info": "هل تريد النسخ الاحتياطي للأصول (الأصول) المحددة إلى الخادم؟",
|
||||
"upload_dialog_ok": "رفع",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Načíst původní obrázek",
|
||||
"setting_image_viewer_preview_subtitle": "Umožňuje načíst obrázek se středním rozlišením. Zakažte, pokud chcete přímo načíst originál nebo použít pouze miniaturu.",
|
||||
"setting_image_viewer_preview_title": "Načíst náhled obrázku",
|
||||
"setting_image_viewer_title": "Obrázky",
|
||||
"setting_languages_apply": "Použít",
|
||||
"setting_languages_title": "Jazyk",
|
||||
"setting_notifications_notify_failures_grace_period": "Oznámení o selhání zálohování na pozadí: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Zobrazit celkový průběh zálohování na pozadí",
|
||||
"setting_pages_app_bar_settings": "Nastavení",
|
||||
"settings_require_restart": "Pro použití tohoto nastavení restartujte Immich",
|
||||
"setting_video_viewer_looping_subtitle": "Povolení automatické smyčky videa v prohlížeči detailů.",
|
||||
"setting_video_viewer_looping_title": "Smyčka",
|
||||
"setting_video_viewer_title": "Videa",
|
||||
"share_add": "Přidat",
|
||||
"share_add_photos": "Přidat fotografie",
|
||||
"share_add_title": "Přidat název",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Indlæs originalbillede",
|
||||
"setting_image_viewer_preview_subtitle": "Slå indlæsning af et mediumstørrelse billede til. Slå fra for enten direkte at indlæse originalen eller kun at bruge miniaturebilledet.",
|
||||
"setting_image_viewer_preview_title": "Indlæs forhåndsvisning af billedet",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Anvend",
|
||||
"setting_languages_title": "Sprog",
|
||||
"setting_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus",
|
||||
"setting_pages_app_bar_settings": "Indstillinger",
|
||||
"settings_require_restart": "Genstart venligst Immich for at anvende denne ændring",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Tilføj",
|
||||
"share_add_photos": "Tilføj billeder",
|
||||
"share_add_title": "Tilføj en titel",
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
"asset_list_settings_title": "Fotogitter",
|
||||
"asset_viewer_settings_title": "Fotoanzeige",
|
||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||
"backup_album_selection_page_albums_tap": "Einmalig tippen um das Album zu verwenden, doppelt tippen um es zu entfernen.",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden.",
|
||||
"backup_album_selection_page_albums_tap": "Einmalig tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||
"backup_album_selection_page_select_albums": "Alben auswählen",
|
||||
"backup_album_selection_page_selection_info": "Information",
|
||||
"backup_album_selection_page_selection_info": "Auswahl",
|
||||
"backup_album_selection_page_total_assets": "Elemente",
|
||||
"backup_all": "Alle",
|
||||
"backup_background_service_backup_failed_message": "Fehler beim Sichern von Elementen. Probiere erneut...",
|
||||
@@ -75,7 +75,7 @@
|
||||
"backup_controller_page_background_battery_info_title": "Batterieoptimierungen",
|
||||
"backup_controller_page_background_charging": "Nur während des Ladens",
|
||||
"backup_controller_page_background_configure_error": "Konnte Hintergrundservice nicht konfigurieren",
|
||||
"backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern um: {}",
|
||||
"backup_controller_page_background_delay": "Sicherung neuer Elemente verzögern: {}",
|
||||
"backup_controller_page_background_description": "Schalte den Hintergrundservice ein, um neue Elemente automatisch im Hintergrund zu sichern ohne die App zu öffnen",
|
||||
"backup_controller_page_background_is_off": "Automatische Sicherung im Hintergrund ist deaktiviert",
|
||||
"backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert",
|
||||
@@ -331,7 +331,7 @@
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Major-Version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile-App ist veraltet. Bitte aktualisiere auf die neueste Minor-Version.",
|
||||
"profile_drawer_client_server_up_to_date": "Die App-Version / Server-Version sind aktuell.",
|
||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||
"profile_drawer_documentation": "Dokumentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Server-Version ist veraltet. Bitte aktualisiere auf die neueste Major-Version.",
|
||||
@@ -382,19 +382,19 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||
"select_user_for_sharing_page_share_suggestions": "Empfehlungen",
|
||||
"server_info_box_app_version": "App-Version",
|
||||
"server_info_box_app_version": "App Version",
|
||||
"server_info_box_latest_release": "Neueste Version",
|
||||
"server_info_box_server_url": "Server-URL",
|
||||
"server_info_box_server_version": "Server-Version",
|
||||
"setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst ein (kleines) Vorschaubild, dann ein Vorschaubild in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).",
|
||||
"server_info_box_server_version": "Server Version",
|
||||
"setting_image_viewer_help": "Der Detailbildbetrachter lädt zuerst die kleine Miniaturansicht, dann die Vorschau in mittlerer Größe (falls aktiviert) und schließlich das Original (falls aktiviert).",
|
||||
"setting_image_viewer_original_subtitle": "Aktivieren, um das Originalbild in voller Auflösung (groß!) zu laden. Deaktivieren, um den Datenverbrauch zu reduzieren (sowohl im Netzwerk als auch im Gerätespeicher).",
|
||||
"setting_image_viewer_original_title": "Original laden",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur das Vorschaubild zu verwenden.",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
|
||||
"setting_image_viewer_preview_title": "Vorschaubild laden",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Anwenden",
|
||||
"setting_languages_title": "Sprachen",
|
||||
"setting_notifications_notify_failures_grace_period": "Benachrichtigung bei Fehler/n in der Hintergrundsicherung: {}",
|
||||
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
|
||||
"setting_notifications_notify_hours": "{} Stunden",
|
||||
"setting_notifications_notify_immediately": "sofort",
|
||||
"setting_notifications_notify_minutes": "{} Minuten",
|
||||
@@ -407,10 +407,10 @@
|
||||
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
|
||||
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
|
||||
"setting_pages_app_bar_settings": "Einstellungen",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"setting_video_viewer_looping_subtitle": "Aktiviere diese Option, um ein Video in der Detailansicht automatisch in einer Schleife anzuzeigen.",
|
||||
"setting_video_viewer_looping_title": "Schleife / Looping",
|
||||
"setting_video_viewer_looping_subtitle": "Aktivieren, damit sich ein Video in der Detailansicht automatisch wiederholt.",
|
||||
"setting_video_viewer_looping_title": "Wiederholen",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"share_add": "Hinzufügen",
|
||||
"share_add_photos": "Fotos hinzufügen",
|
||||
"share_add_title": "Titel hinzufügen",
|
||||
@@ -485,12 +485,12 @@
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Suche",
|
||||
"tab_controller_nav_sharing": "Teilen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Zeige Sicherungsstatus auf Vorschaubild",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Zeige Sicherungsstatus auf Miniaturbild",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({})",
|
||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung)",
|
||||
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung folgen)",
|
||||
"theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
"theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -407,10 +407,10 @@
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Aplicar",
|
||||
"setting_languages_title": "Idiomas",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Ajustes",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Cargar imagen original",
|
||||
"setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resolución media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.",
|
||||
"setting_image_viewer_preview_title": "Cargar imagen de previsualización",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano",
|
||||
"setting_pages_app_bar_settings": "Configuración",
|
||||
"settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Agregar",
|
||||
"share_add_photos": "Agregar fotos",
|
||||
"share_add_title": "Agregar un título",
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla. Hypätään yli",
|
||||
"home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan",
|
||||
"image_viewer_page_state_provider_download_error": "Lataus epäonnistui",
|
||||
"image_viewer_page_state_provider_download_started": "Lataaminen aloitettu",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Lataus onnistui",
|
||||
"image_viewer_page_state_provider_share_error": "Jakovirhe",
|
||||
"library_page_albums": "Albumit",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Lataa alkuperäinen kuva",
|
||||
"setting_image_viewer_preview_subtitle": "Ota käyttöön ladataksesi keskitarkkuuksinen kuva. Poista käytöstä ladataksesi alkuperäinen kuva tai käyttääksesi vain esikatselukuvaa.",
|
||||
"setting_image_viewer_preview_title": "Lataa esikatselukuva",
|
||||
"setting_image_viewer_title": "Kuvat",
|
||||
"setting_languages_apply": "Käytä",
|
||||
"setting_languages_title": "Kieli",
|
||||
"setting_notifications_notify_failures_grace_period": "Ilmoita taustavarmuuskopioinnin epäonnistumisista: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen",
|
||||
"setting_pages_app_bar_settings": "Asetukset",
|
||||
"settings_require_restart": "Käynnistä Immich uudelleen ottaaksesti tämän asetuksen käyttöön",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videot",
|
||||
"share_add": "Lisää",
|
||||
"share_add_photos": "Lisää kuvia",
|
||||
"share_add_title": "Lisää nimi",
|
||||
"share_assets_selected": "{} valittu",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Luo albumi",
|
||||
"shared_album_activities_input_disable": "Kommentointi on kytketty pois päältä",
|
||||
"shared_album_activities_input_hint": "Sano jotain",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Charger l'image originale",
|
||||
"setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la vignette.",
|
||||
"setting_image_viewer_preview_title": "Charger l'image d'aperçu",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"action_common_back": "Retour",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Annuler",
|
||||
"action_common_clear": "Vider",
|
||||
"action_common_confirm": "Confirmer",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Mise à jour",
|
||||
"add_to_album_bottom_sheet_added": "Ajouté à {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Déjà dans {album}",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": " · Partagé",
|
||||
"album_thumbnail_owned": "Possédé",
|
||||
"album_thumbnail_shared_by": "Partagé par {}",
|
||||
"album_viewer_appbar_delete_confirm": "Êtes-vous sur de vouloir supprimer cet album de votre compte?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Supprimer l'album",
|
||||
"album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album",
|
||||
"album_viewer_appbar_share_err_leave": "Impossible de quitter l'album",
|
||||
@@ -41,7 +41,7 @@
|
||||
"archive_page_title": "Archive ({})",
|
||||
"asset_action_delete_err_read_only": "Impossible de supprimer le(s) élément(s) en lecture seule.",
|
||||
"asset_action_share_err_offline": "Impossible de récupérer le(s) élément(s) hors ligne.",
|
||||
"asset_list_group_by_sub_title": "Regrouper par",
|
||||
"asset_list_group_by_sub_title": "Groupe par",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique",
|
||||
"asset_list_layout_settings_group_automatically": "Automatique",
|
||||
"asset_list_layout_settings_group_by": "Grouper les éléments par",
|
||||
@@ -115,7 +115,7 @@
|
||||
"backup_manual_in_progress": "Téléchargement déjà en cours. Essayez après un instant",
|
||||
"backup_manual_success": "Succès ",
|
||||
"backup_manual_title": "Statut du téléchargement ",
|
||||
"backup_options_page_title": "Options de sauvegarde",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Miniatures de la page bibliothèque ({} éléments)",
|
||||
"cache_settings_clear_cache_button": "Effacer le cache",
|
||||
"cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Impossible de partager par lien les médias locaux, cette opération est donc ignorée.",
|
||||
"home_page_upload_err_limit": "Limite de téléchargement de 30 éléments en même temps, demande ignorée",
|
||||
"image_viewer_page_state_provider_download_error": "Erreur de téléchargement",
|
||||
"image_viewer_page_state_provider_download_started": "Téléchargement Démarré",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Téléchargement réussi",
|
||||
"image_viewer_page_state_provider_share_error": "Erreur de partage",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -286,7 +286,7 @@
|
||||
"map_settings_dialog_save": "Sauvegarder",
|
||||
"map_settings_dialog_title": "Paramètres de la carte",
|
||||
"map_settings_include_show_archived": "Inclure les archives",
|
||||
"map_settings_include_show_partners": "Inclure les partenaires",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Plage de dates",
|
||||
"map_settings_only_show_favorites": "Afficher uniquement les favoris",
|
||||
"map_settings_theme_settings": "Thème de la carte",
|
||||
@@ -299,15 +299,15 @@
|
||||
"motion_photos_page_title": "Photos avec mouvement",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Impossible de modifier la date d'un élément d'actif en lecture seule.",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Impossible de modifier l'emplacement d'un élément en lecture seule.",
|
||||
"no_assets_to_show": "Aucuns éléments à afficher",
|
||||
"no_assets_to_show": "Aucun élément à afficher",
|
||||
"notification_permission_dialog_cancel": "Annuler",
|
||||
"notification_permission_dialog_content": "Pour activer les notifications, allez dans Paramètres et sélectionnez Autoriser.",
|
||||
"notification_permission_dialog_settings": "Paramètres",
|
||||
"notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Activer les notifications",
|
||||
"notification_permission_list_tile_title": "Permission de notification",
|
||||
"partner_list_user_photos": "Photos de {user}",
|
||||
"partner_list_view_all": "Voir tous",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Ajouter un partenaire",
|
||||
"partner_page_empty_message": "Vos photos ne sont pas encore partagées avec un partenaire.",
|
||||
"partner_page_no_more_users": "Plus d'utilisateurs à ajouter",
|
||||
@@ -342,18 +342,18 @@
|
||||
"recently_added_page_title": "Récemment ajouté",
|
||||
"scaffold_body_error_occurred": "Une erreur s'est produite",
|
||||
"search_bar_hint": "Rechercher vos photos",
|
||||
"search_filter_apply": "Appliquer le filtre",
|
||||
"search_filter_camera_make": "Fabricant",
|
||||
"search_filter_camera_model": "Modéle",
|
||||
"search_filter_display_option_archive": "Achive",
|
||||
"search_filter_display_option_favorite": "Favoris",
|
||||
"search_filter_display_option_not_in_album": "Pas dans un album",
|
||||
"search_filter_location_city": "Ville",
|
||||
"search_filter_location_country": "Pays",
|
||||
"search_filter_location_state": "Région",
|
||||
"search_filter_media_type_all": "Tous",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Vidéo",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Catégories",
|
||||
"search_page_favorites": "Favoris",
|
||||
"search_page_motion_photos": "Photos animées",
|
||||
@@ -391,9 +391,8 @@
|
||||
"setting_image_viewer_original_title": "Charger l'image originale",
|
||||
"setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la miniature.",
|
||||
"setting_image_viewer_preview_title": "Charger l'image d'aperçu",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Appliquer",
|
||||
"setting_languages_title": "Langues",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {}",
|
||||
"setting_notifications_notify_hours": "{} heures",
|
||||
"setting_notifications_notify_immediately": "immédiatement",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"setting_video_viewer_looping_subtitle": "Activer pour voir les vidéos en boucle dans le lecteur détaillé",
|
||||
"setting_video_viewer_looping_title": "Boucle",
|
||||
"setting_video_viewer_title": "Vidéos",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
"share_assets_selected": "{} séléctionné(s)",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Créer un album",
|
||||
"shared_album_activities_input_disable": "Les commentaires sont désactivés",
|
||||
"shared_album_activities_input_hint": "Dire quelque chose",
|
||||
@@ -448,9 +444,9 @@
|
||||
"shared_link_edit_expire_after_option_hours": "{} heures",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} mois",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Jamais",
|
||||
"shared_link_edit_expire_after_option_year": "{} ans",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Mot de passe",
|
||||
"shared_link_edit_password_hint": "Saisir le mot de passe de partage",
|
||||
"shared_link_edit_show_meta": "Afficher les métadonnées",
|
||||
@@ -467,12 +463,12 @@
|
||||
"shared_link_expires_never": "Expire ∞",
|
||||
"shared_link_expires_second": "Expire dans {} seconde",
|
||||
"shared_link_expires_seconds": "Expire dans {} secondes",
|
||||
"shared_link_individual_shared": "Partagé individuellement",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Téléchargement",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Chargement",
|
||||
"shared_link_manage_links": "Gérer les liens partagés",
|
||||
"shared_link_public_album": "Album public",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Fait",
|
||||
"share_invite": "Inviter à l'album",
|
||||
"sharing_page_album": "Albums partagés",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"backup_controller_page_background_turn_off": "כבה שירות ברקע",
|
||||
"backup_controller_page_background_turn_on": "הפעל שירות ברקע",
|
||||
"backup_controller_page_background_wifi": "רק ברשת אלחוטית",
|
||||
"backup_controller_page_backup": "גובו",
|
||||
"backup_controller_page_backup": "גיבוי",
|
||||
"backup_controller_page_backup_selected": "נבחרו:",
|
||||
"backup_controller_page_backup_sub": "תמונות וסרטונים מגובים",
|
||||
"backup_controller_page_cancel": "ביטול",
|
||||
@@ -94,7 +94,7 @@
|
||||
"backup_controller_page_id": "מזהה: {}",
|
||||
"backup_controller_page_info": "פרטי גיבוי",
|
||||
"backup_controller_page_none_selected": "לא נבחרו",
|
||||
"backup_controller_page_remainder": "בתור לגיבוי",
|
||||
"backup_controller_page_remainder": "יתרה",
|
||||
"backup_controller_page_remainder_sub": "תמונות וסרטונים שנותרו לגבות מתוך בחירה",
|
||||
"backup_controller_page_select": "בחר",
|
||||
"backup_controller_page_server_storage": "אחסון שרת",
|
||||
@@ -107,7 +107,7 @@
|
||||
"backup_controller_page_total_sub": "כל התמונות והסרטונים הייחודיים מאלבומים שנבחרו",
|
||||
"backup_controller_page_turn_off": "כבה גיבוי חזית",
|
||||
"backup_controller_page_turn_on": "הפעל גיבוי חזית",
|
||||
"backup_controller_page_uploading_file_info": "מידע על הקובץ",
|
||||
"backup_controller_page_uploading_file_info": "מעלה פרטי קובץ",
|
||||
"backup_err_only_album": "לא ניתן להסיר את האלבום היחידי",
|
||||
"backup_info_card_assets": "נכסים",
|
||||
"backup_manual_cancelled": "בוטל",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "לא ניתן לשתף נכסים מקומיים על ידי קישור, מדלג",
|
||||
"home_page_upload_err_limit": "יכול רק להעלות מקסימום של 30 נכסים בכל פעם, מדלג",
|
||||
"image_viewer_page_state_provider_download_error": "שגיאת הורדה",
|
||||
"image_viewer_page_state_provider_download_started": "ההורדה החלה",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "הצלחת הורדה",
|
||||
"image_viewer_page_state_provider_share_error": "שיתוף שגיאה",
|
||||
"library_page_albums": "אלבומים",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "טען תמונה מקורית",
|
||||
"setting_image_viewer_preview_subtitle": "אפשר לטעון תמונה ברזלוציה בינונית. השבת כדי או לטעון את המקורית או רק להשתמש בתמונה הממוזערת.",
|
||||
"setting_image_viewer_preview_title": "טען תמונת תצוגה מקדימה",
|
||||
"setting_image_viewer_title": "תמונות",
|
||||
"setting_languages_apply": "החל",
|
||||
"setting_languages_title": "שפות",
|
||||
"setting_notifications_notify_failures_grace_period": "להודיע על כשלים בגיבוי ברקע: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "הראה סה״כ התקדמות גיבוי ברקע",
|
||||
"setting_pages_app_bar_settings": "הגדרות",
|
||||
"settings_require_restart": "אנא הפעל מחדש את Immich כדי להחיל הגדרה זו",
|
||||
"setting_video_viewer_looping_subtitle": "אפשר וידיאו ברצף אוטומטית בחלון המידע",
|
||||
"setting_video_viewer_looping_title": "לולאה",
|
||||
"setting_video_viewer_title": "סרטונים",
|
||||
"share_add": "הוסף",
|
||||
"share_add_photos": "הוסף תמונות",
|
||||
"share_add_title": "הוסף כותרת",
|
||||
"share_assets_selected": "{} נבחרו",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "צור אלבום",
|
||||
"shared_album_activities_input_disable": "התגובה מושבתת",
|
||||
"shared_album_activities_input_hint": "הגב/י משהו",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
"exif_bottom_sheet_location": "HELY",
|
||||
"exif_bottom_sheet_location_add": "Hely hozzáadása",
|
||||
"exif_bottom_sheet_people": "EMBEREK",
|
||||
"exif_bottom_sheet_person_add_person": "Elnevez",
|
||||
"exif_bottom_sheet_person_add_person": "Név hozzáadása",
|
||||
"experimental_settings_new_asset_list_subtitle": "Fejlesztés alatt",
|
||||
"experimental_settings_new_asset_list_title": "Kisérleti képrács engedélyezése",
|
||||
"experimental_settings_subtitle": "Csak saját felelősségre használd!",
|
||||
@@ -363,9 +363,9 @@
|
||||
"search_page_person_add_name_dialog_cancel": "Mégsem",
|
||||
"search_page_person_add_name_dialog_hint": "Név",
|
||||
"search_page_person_add_name_dialog_save": "Mentés",
|
||||
"search_page_person_add_name_dialog_title": "Elnevez",
|
||||
"search_page_person_add_name_dialog_title": "Név hozzáadása",
|
||||
"search_page_person_add_name_subtitle": "Név szerint gyorsan megtalálhatod a keresőben",
|
||||
"search_page_person_add_name_title": "Elnevez",
|
||||
"search_page_person_add_name_title": "Név hozzáadása",
|
||||
"search_page_person_edit_name": "Név módosítása",
|
||||
"search_page_places": "Helyek",
|
||||
"search_page_recently_added": "Nemrég hozzáadott",
|
||||
@@ -374,8 +374,8 @@
|
||||
"search_page_things": "Dolgok",
|
||||
"search_page_videos": "Videók",
|
||||
"search_page_view_all_button": "Összes mutatása",
|
||||
"search_page_your_activity": "Tevékenységek",
|
||||
"search_page_your_map": "Térkép",
|
||||
"search_page_your_activity": "Tevékenységeid",
|
||||
"search_page_your_map": "Térképed",
|
||||
"search_result_page_new_search_hint": "Új keresés",
|
||||
"search_suggestion_list_smart_search_hint_1": "Az intelligens keresés alapértelmezetten be van kapcsolva, metaadatokat így kereshetsz",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:keresési-kifejezés",
|
||||
@@ -386,12 +386,11 @@
|
||||
"server_info_box_latest_release": "Legfrissebb Verzió",
|
||||
"server_info_box_server_url": "Szerver Címe",
|
||||
"server_info_box_server_version": "Szerver Verzió",
|
||||
"setting_image_viewer_help": "Az Elem Megjelenítő először a kis bélyegképet tölti be, aztán a közepes méretű előnézetet (ha elérhető), végül az eredetit (ha elérhető).",
|
||||
"setting_image_viewer_help": "A képnézegető először a kis bélyegképet tölti be, aztán a közepes méretű előnézetet (ha elérhető), végül az eredetit (ha elérhető).",
|
||||
"setting_image_viewer_original_subtitle": "Engedélyezi az eredeti teljes felbontású kép betöltését (nagy!). Kikapcsolva csökkenti az adathasználatot (a neten és az eszköz gyorsítótárán is).",
|
||||
"setting_image_viewer_original_title": "Eredeti kép betöltése",
|
||||
"setting_image_viewer_preview_subtitle": "Engedélyezi a közepes felbontású kép betöltését. Kikapcsolva vagy az eredeti kép töltődik be, vagy csak a bélyegkép.",
|
||||
"setting_image_viewer_preview_title": "Előnézet betöltése",
|
||||
"setting_image_viewer_title": "Képek",
|
||||
"setting_languages_apply": "Alkalmaz",
|
||||
"setting_languages_title": "Nyelvek",
|
||||
"setting_notifications_notify_failures_grace_period": "Értesítés a háttérben történő mentés hibáiról: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mutassa a háttérben történő mentés teljes folyamatát",
|
||||
"setting_pages_app_bar_settings": "Beállítások",
|
||||
"settings_require_restart": "Ennek a beállításnak az érvénybe lépéséhez indítsd újra az Immich-et",
|
||||
"setting_video_viewer_looping_subtitle": "Engedélyezi a videók folyamatosan ismételt lejátszását az elem megjelenítőben",
|
||||
"setting_video_viewer_looping_title": "Ismétlés",
|
||||
"setting_video_viewer_title": "Videók",
|
||||
"share_add": "Hozzáadás",
|
||||
"share_add_photos": "Fotók hozzáadása",
|
||||
"share_add_title": "Album neve",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Carica l'immagine originale",
|
||||
"setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media.\nDisabilita per caricare direttamente l'immagine originale o usare la thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Carica immagine di anteprima",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Applica",
|
||||
"setting_languages_title": "Lingue",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostra avanzamento del backup in background",
|
||||
"setting_pages_app_bar_settings": "Impostazioni",
|
||||
"settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Aggiungi",
|
||||
"share_add_photos": "Aggiungi foto",
|
||||
"share_add_title": "Aggiungi un titolo ",
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
"date_format": "MM月 DD日, EE • hh時mm分",
|
||||
"delete_dialog_alert": "サーバーとデバイスの両方から永久的に削除されます!",
|
||||
"delete_dialog_alert_local": "選択された項目はデバイスから削除されますが、Immichには残ります",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immichにバックアップされていない項目があります。デバイスからも永久に削除されます",
|
||||
"delete_dialog_alert_local_non_backed_up": "Immichにバックアップされていない項目があります。それらはデバイスからも永久に削除されます",
|
||||
"delete_dialog_alert_remote": "選択された項目はImmichから永久に削除されます",
|
||||
"delete_dialog_cancel": "キャンセル",
|
||||
"delete_dialog_ok": "削除",
|
||||
@@ -201,8 +201,8 @@
|
||||
"experimental_settings_title": "試験的機能",
|
||||
"favorites_page_no_favorites": "お気に入り登録された写真またはビデオがありません",
|
||||
"favorites_page_title": "お気に入り",
|
||||
"haptic_feedback_switch": "ハプティックフィードバック",
|
||||
"haptic_feedback_title": "ハプティックフィードバックを有効にする",
|
||||
"haptic_feedback_switch": "触覚フィードバック",
|
||||
"haptic_feedback_title": "触覚フィードバックを有効にする",
|
||||
"home_page_add_to_album_conflicts": "{album}に{added}枚写真を追加しました。追加済みの{failed}枚はスキップしました。",
|
||||
"home_page_add_to_album_err_local": "まだアップロードされてない項目は、アルバムに登録できません",
|
||||
"home_page_add_to_album_success": "{album}に{added}枚写真を追加しました",
|
||||
@@ -299,7 +299,7 @@
|
||||
"motion_photos_page_title": "モーションフォト",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "読み取り専用の項目の日付を変更できません",
|
||||
"multiselect_grid_edit_gps_err_read_only": "読み取り専用の項目の位置情報を変更できません",
|
||||
"no_assets_to_show": "表示する項目がありません",
|
||||
"no_assets_to_show": "表示するアセットがありません",
|
||||
"notification_permission_dialog_cancel": "キャンセル",
|
||||
"notification_permission_dialog_content": "通知を許可するには設定を開いてオンにしてください",
|
||||
"notification_permission_dialog_settings": "設定",
|
||||
@@ -330,12 +330,12 @@
|
||||
"preferences_settings_title": "設定",
|
||||
"profile_drawer_app_logs": "ログ",
|
||||
"profile_drawer_client_out_of_date_major": "アプリが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_client_out_of_date_minor": "アプリが更新されてません。最新のマイナーバージョンに更新してください",
|
||||
"profile_drawer_client_server_up_to_date": "すべて最新です",
|
||||
"profile_drawer_documentation": "Immichのドキュメント",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "サーバーが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のバージョンに更新してください",
|
||||
"profile_drawer_server_out_of_date_minor": "サーバーが更新されてません。最新のマイナーバージョンに更新してください",
|
||||
"profile_drawer_settings": "設定",
|
||||
"profile_drawer_sign_out": "サインアウト",
|
||||
"profile_drawer_trash": "ゴミ箱",
|
||||
@@ -347,7 +347,7 @@
|
||||
"search_filter_camera_model": "モデル",
|
||||
"search_filter_display_option_archive": "アーカイブ",
|
||||
"search_filter_display_option_favorite": "お気に入り",
|
||||
"search_filter_display_option_not_in_album": "アルバムにありません",
|
||||
"search_filter_display_option_not_in_album": "アルバムにない",
|
||||
"search_filter_location_city": "市町村",
|
||||
"search_filter_location_country": "国",
|
||||
"search_filter_location_state": "都道府県",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "オリジナル画像を読み込む",
|
||||
"setting_image_viewer_preview_subtitle": "中画質の写真をロードしたいときにオンにしてください。直接最大画質の写真を表示したい場合はオフにしてください。(ロード中はサムネイルが代わりに表示されます)",
|
||||
"setting_image_viewer_preview_title": "プレビュー画像をロードする",
|
||||
"setting_image_viewer_title": "画像",
|
||||
"setting_languages_apply": "適用する",
|
||||
"setting_languages_title": "言語",
|
||||
"setting_notifications_notify_failures_grace_period": "バックアップ失敗の通知: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "実行中のバックアップの進行状況を表示",
|
||||
"setting_pages_app_bar_settings": "設定",
|
||||
"settings_require_restart": "Immichを再起動して設定を適用してください",
|
||||
"setting_video_viewer_looping_subtitle": "有効にするとディテールビューで自動で動画がループします",
|
||||
"setting_video_viewer_looping_title": "ループ中",
|
||||
"setting_video_viewer_title": "ビデオ",
|
||||
"share_add": "追加",
|
||||
"share_add_photos": "写真を追加",
|
||||
"share_add_title": "タイトルを追加",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "원본 이미지 불러오기",
|
||||
"setting_image_viewer_preview_subtitle": "중간 해상도 이미지를 로드하려면 활성화합니다. 원본을 직접 로드하거나 썸네일만 사용하려면 비활성화 하세요.",
|
||||
"setting_image_viewer_preview_title": "미리보기 이미지 불러오기",
|
||||
"setting_image_viewer_title": "이미지",
|
||||
"setting_languages_apply": "적용",
|
||||
"setting_languages_title": "언어",
|
||||
"setting_notifications_notify_failures_grace_period": "백그라운드 백업 실패 알림: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "백그라운드 작업 전체 진행률 표시",
|
||||
"setting_pages_app_bar_settings": "설정",
|
||||
"settings_require_restart": "설정을 적용하려면 Immich를 다시 시작하세요.",
|
||||
"setting_video_viewer_looping_subtitle": "상세 뷰어에서 비디오를 자동으로 반복하도록 활성화합니다.",
|
||||
"setting_video_viewer_looping_title": "반복",
|
||||
"setting_video_viewer_title": "비디오",
|
||||
"share_add": "추가",
|
||||
"share_add_photos": "사진 추가",
|
||||
"share_add_title": "새 앨범제목",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"action_common_back": "Atpakaļ",
|
||||
"action_common_cancel": "Atcelt",
|
||||
"action_common_clear": "Notīrīt",
|
||||
"action_common_confirm": "Apstiprināt",
|
||||
"action_common_update": "Atjaunināt",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Cancel",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Update",
|
||||
"add_to_album_bottom_sheet_added": "Pievienots {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Jau pievienots {album}",
|
||||
"advanced_settings_log_level_title": "Žurnalēšanas līmenis: {}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Dažās ierīcēs sīktēli no ierīcē esošajiem resursiem tiek ielādēti ļoti lēni. Aktivizējiet šo iestatījumu, lai tā vietā ielādētu attālus attēlus.",
|
||||
"advanced_settings_prefer_remote_title": "Dot priekšroku attāliem attēliem",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Izlaiž servera galapunkta SSL sertifikātu verifikāciju. Nepieciešams pašparakstītajiem sertifikātiem.",
|
||||
"advanced_settings_self_signed_ssl_title": "Atļaut pašparakstītus SSL sertifikātus",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
||||
"advanced_settings_tile_subtitle": "Lietotāja papildu iestatījumi",
|
||||
"advanced_settings_tile_title": "Papildu",
|
||||
"advanced_settings_troubleshooting_subtitle": "Iespējot papildu aktīvus problēmu novēršanai",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": "· Koplietots",
|
||||
"album_thumbnail_owned": "Īpašumā",
|
||||
"album_thumbnail_shared_by": "Kopīgoja {}",
|
||||
"album_viewer_appbar_delete_confirm": "Vai tiešām vēlaties dzēst šo albumu no sava konta?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Dzēst albumu",
|
||||
"album_viewer_appbar_share_err_delete": "Neizdevās izdzēst albumu",
|
||||
"album_viewer_appbar_share_err_leave": "Neizdevās pamest albumu",
|
||||
@@ -30,27 +30,27 @@
|
||||
"album_viewer_appbar_share_err_title": "Neizdevās mainīt albuma nosaukumu",
|
||||
"album_viewer_appbar_share_leave": "Pamest albumu",
|
||||
"album_viewer_appbar_share_remove": "Noņemt no albuma",
|
||||
"album_viewer_appbar_share_to": "Kopīgot Uz",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_page_share_add_users": "Pievienot lietotājus",
|
||||
"all_people_page_title": "Cilvēki",
|
||||
"all_videos_page_title": "Videoklipi",
|
||||
"app_bar_signout_dialog_content": "Vai tiešām vēlaties izrakstīties?",
|
||||
"app_bar_signout_dialog_ok": "Jā",
|
||||
"app_bar_signout_dialog_title": "Izrakstīties",
|
||||
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
||||
"app_bar_signout_dialog_ok": "Yes",
|
||||
"app_bar_signout_dialog_title": "Sign out",
|
||||
"archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktīvs",
|
||||
"archive_page_title": "Arhīvs ({})",
|
||||
"asset_action_delete_err_read_only": "Nevar dzēst read only aktīvu(-s), notiek izlaišana",
|
||||
"asset_action_share_err_offline": "Nevar iegūt bezsaistes aktīvu(-s), notiek izlaišana",
|
||||
"asset_list_group_by_sub_title": "Grupēt pēc",
|
||||
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums",
|
||||
"asset_list_layout_settings_group_automatically": "Automātiski",
|
||||
"asset_list_layout_settings_group_by": "Grupēt aktīvus pēc",
|
||||
"asset_list_layout_settings_group_by_month": "Mēnesis",
|
||||
"asset_list_layout_settings_group_by_month_day": "Mēnesis + diena",
|
||||
"asset_list_layout_sub_title": "Izvietojums",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "Fotorežģa izkārtojuma iestatījumi",
|
||||
"asset_list_settings_title": "Fotorežģis",
|
||||
"asset_viewer_settings_title": "Aktīvu Skatītājs",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "Albumi ierīcē ({})",
|
||||
"backup_album_selection_page_albums_tap": "Pieskarieties, lai iekļautu, veiciet dubultskārienu, lai izslēgtu",
|
||||
"backup_album_selection_page_assets_scatter": "Aktīvi var būt izmētāti pa vairākiem albumiem. Tādējādi dublēšanas procesā albumus var iekļaut vai neiekļaut.",
|
||||
@@ -110,18 +110,18 @@
|
||||
"backup_controller_page_uploading_file_info": "Faila informācijas augšupielāde",
|
||||
"backup_err_only_album": "Nevar noņemt vienīgo albumu",
|
||||
"backup_info_card_assets": "aktīvi",
|
||||
"backup_manual_cancelled": "Atcelts",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Neizdevās",
|
||||
"backup_manual_in_progress": "Augšupielāde jau notiek. Mēģiniet pēc kāda laika atkārtoti",
|
||||
"backup_manual_success": "Veiksmīgi",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Augšupielādes statuss",
|
||||
"backup_options_page_title": "Dublēšanas iestatījumi",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Bibliotēkas lapu sīktēli ({} aktīvi)",
|
||||
"cache_settings_clear_cache_button": "Iztīrīt kešatmiņu",
|
||||
"cache_settings_clear_cache_button_title": "Iztīra aplikācijas kešatmiņu. Tas būtiski ietekmēs lietotnes veiktspēju, līdz kešatmiņa būs pārbūvēta.",
|
||||
"cache_settings_duplicated_assets_clear_button": "NOTĪRĪT",
|
||||
"cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekļāvusi melnajā sarakstā",
|
||||
"cache_settings_duplicated_assets_title": "Dublicētie Aktīvi ({})",
|
||||
"cache_settings_duplicated_assets_clear_button": "CLEAR",
|
||||
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Attēlu kešatmiņas lielums ({} aktīvi)",
|
||||
"cache_settings_statistics_album": "Bibliotēkas sīktēli",
|
||||
"cache_settings_statistics_assets": "{} aktīvi ({})",
|
||||
@@ -131,8 +131,8 @@
|
||||
"cache_settings_statistics_title": "Kešatmiņas lietojums",
|
||||
"cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes kešdarbi",
|
||||
"cache_settings_thumbnail_size": "Sīktēlu keša lielums ({} aktīvi)",
|
||||
"cache_settings_tile_subtitle": "Kontrolēt lokālās krātuves uzvedību",
|
||||
"cache_settings_tile_title": "Lokālā Krātuve",
|
||||
"cache_settings_tile_subtitle": "Control the local storage behaviour",
|
||||
"cache_settings_tile_title": "Local Storage",
|
||||
"cache_settings_title": "Kešdarbes iestatījumi",
|
||||
"change_password_form_confirm_password": "Apstiprināt Paroli",
|
||||
"change_password_form_description": "Sveiki {name},\n\nŠī ir pirmā reize, kad pierakstāties sistēmā, vai arī ir iesniegts pieprasījums mainīt paroli. Lūdzu, zemāk ievadiet jauno paroli.",
|
||||
@@ -150,18 +150,18 @@
|
||||
"control_bottom_app_bar_archive": "Arhīvs",
|
||||
"control_bottom_app_bar_create_new_album": "Izveidot jaunu albumu",
|
||||
"control_bottom_app_bar_delete": "Dzēst",
|
||||
"control_bottom_app_bar_delete_from_immich": "Dzēst no Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Dzēst no ierīces",
|
||||
"control_bottom_app_bar_edit_location": "Rediģēt Atrašanās Vietu",
|
||||
"control_bottom_app_bar_edit_time": "Rediģēt Datumu un Laiku",
|
||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
||||
"control_bottom_app_bar_favorite": "Izlase",
|
||||
"control_bottom_app_bar_share": "Kopīgot",
|
||||
"control_bottom_app_bar_share_to": "Kopīgot Uz",
|
||||
"control_bottom_app_bar_stack": "Steks",
|
||||
"control_bottom_app_bar_trash_from_immich": "Pārvietot uz Atkritni",
|
||||
"control_bottom_app_bar_share_to": "Share To",
|
||||
"control_bottom_app_bar_stack": "Stack",
|
||||
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
|
||||
"control_bottom_app_bar_unarchive": "Atarhivēt",
|
||||
"control_bottom_app_bar_unfavorite": "Noņemt no Izlases",
|
||||
"control_bottom_app_bar_upload": "Augšupielādēt",
|
||||
"control_bottom_app_bar_unfavorite": "Unfavorite",
|
||||
"control_bottom_app_bar_upload": "Upload",
|
||||
"create_album_page_untitled": "Bez nosaukuma",
|
||||
"create_shared_album_page_create": "Izveidot",
|
||||
"create_shared_album_page_share": "Kopīgot",
|
||||
@@ -173,76 +173,76 @@
|
||||
"daily_title_text_date_year": "E, MMM dd, gggg",
|
||||
"date_format": "E, LLL d, g • h:mm a",
|
||||
"delete_dialog_alert": "Šie vienumi tiks neatgriezeniski dzēsti no Immich un jūsu ierīces",
|
||||
"delete_dialog_alert_local": "Šie vienumi tiks neatgriezeniski dzēsti no jūsu ierīces, bet joprojām būs pieejami Immich serverī.",
|
||||
"delete_dialog_alert_local_non_backed_up": "Daži no šiem elementiem netiek dublēti Immich un tiks neatgriezeniski dzēsti no jūsu ierīces.",
|
||||
"delete_dialog_alert_remote": "Šie vienumi tiks neatgriezeniski dzēsti no Immich servera.",
|
||||
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
|
||||
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
|
||||
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
|
||||
"delete_dialog_cancel": "Atcelt",
|
||||
"delete_dialog_ok": "Dzēst",
|
||||
"delete_dialog_ok_force": "Tā pat dzēst",
|
||||
"delete_dialog_ok_force": "Delete Anyway",
|
||||
"delete_dialog_title": "Neatgriezeniski Dzēst",
|
||||
"delete_local_dialog_ok_backed_up_only": "Dzēst tikai Dublētos",
|
||||
"delete_local_dialog_ok_force": "Tā pat dzēst",
|
||||
"delete_shared_link_dialog_content": "Vai tiešām vēlaties dzēst šo kopīgošanas saiti?",
|
||||
"delete_shared_link_dialog_title": "Dzēst Kopīgošanas saiti",
|
||||
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
|
||||
"delete_local_dialog_ok_force": "Delete Anyway",
|
||||
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
|
||||
"delete_shared_link_dialog_title": "Delete Shared Link",
|
||||
"description_input_hint_text": "Pievienot aprakstu...",
|
||||
"description_input_submit_error": "Atjauninot aprakstu, radās kļūda; papildinformāciju skatiet žurnālā",
|
||||
"edit_date_time_dialog_date_time": "Datums un Laiks",
|
||||
"edit_date_time_dialog_timezone": "Laika zona",
|
||||
"edit_location_dialog_title": "Atrašanās vieta",
|
||||
"edit_date_time_dialog_date_time": "Date and Time",
|
||||
"edit_date_time_dialog_timezone": "Timezone",
|
||||
"edit_location_dialog_title": "Location",
|
||||
"exif_bottom_sheet_description": "Pievienot Aprakstu...",
|
||||
"exif_bottom_sheet_details": "INFORMĀCIJA",
|
||||
"exif_bottom_sheet_location": "ATRAŠANĀS VIETA",
|
||||
"exif_bottom_sheet_location_add": "Pievienot atrašanās vietu",
|
||||
"exif_bottom_sheet_people": "CILVĒKI",
|
||||
"exif_bottom_sheet_person_add_person": "Pievienot vārdu",
|
||||
"exif_bottom_sheet_location_add": "Add a location",
|
||||
"exif_bottom_sheet_people": "PEOPLE",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "Izstrādes posmā",
|
||||
"experimental_settings_new_asset_list_title": "Iespējot eksperimentālo fotorežģi",
|
||||
"experimental_settings_subtitle": "Izmanto uzņemoties risku!",
|
||||
"experimental_settings_title": "Eksperimentāls",
|
||||
"favorites_page_no_favorites": "Nav atrasti iecienītākie aktīvi",
|
||||
"favorites_page_title": "Izlase",
|
||||
"haptic_feedback_switch": "Iestatīt haptisku reakciju",
|
||||
"haptic_feedback_title": "Haptiska Reakcija",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "Pievienoja {added} aktīvus albumam {album}. {failed} aktīvi jau ir albumā.",
|
||||
"home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_add_to_album_success": "Pievienoja {added} aktīvus albumam {album}.",
|
||||
"home_page_album_err_partner": "Pagaidām nevar pievienot partnera aktīvus albumam, notiek izlaišana",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_archive_err_partner": "Nevarēja arhivēt partnera aktīvus, notiek izlaišana",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Tiek izveidota laika skala",
|
||||
"home_page_delete_err_partner": "Nevarēja dzēst partnera aktīvus, notiek izlaišana",
|
||||
"home_page_delete_remote_err_local": "Lokālie aktīvi dzēšanai attālinātajā izvēlē, tiek izlaists",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
|
||||
"home_page_favorite_err_local": "Vēl nevar pievienot izlaisei vietējos aktīvus, notiek izlaišana",
|
||||
"home_page_favorite_err_partner": "Pagaidām nevar ievietot izlasē partnera aktīvus, notiek izlaišana",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Ja šī ir pirmā reize, kad izmantojat aplikāciju, lūdzu, izvēlieties dublējuma albumu(s), lai laika skala varētu aizpildīt fotoattēlus un videoklipus albumā(os).",
|
||||
"home_page_share_err_local": "Caur saiti nevarēja kopīgot lokālos aktīvus, notiek izlaišana",
|
||||
"home_page_upload_err_limit": "Vienlaikus var augšupielādēt ne vairāk kā 30 aktīvus, notiek izlaišana",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Lejupielādes Kļūda",
|
||||
"image_viewer_page_state_provider_download_started": "Lejupielāde Uzsākta",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Lejupielāde Izdevās",
|
||||
"image_viewer_page_state_provider_share_error": "Kopīgošanas Kļūda",
|
||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_archive": "Arhīvs",
|
||||
"library_page_device_albums": "Albumi ierīcē",
|
||||
"library_page_favorites": "Izlase",
|
||||
"library_page_new_album": "Jauns albums",
|
||||
"library_page_sharing": "Kopīgošana",
|
||||
"library_page_sort_asset_count": "Daudzums ar aktīviem",
|
||||
"library_page_sort_asset_count": "Number of assets",
|
||||
"library_page_sort_created": "Jaunākais izveidotais",
|
||||
"library_page_sort_last_modified": "Pēdējo reizi modificēts",
|
||||
"library_page_sort_most_oldest_photo": "Vecākais fotoattēls",
|
||||
"library_page_sort_most_recent_photo": "Jaunākais fotoattēls",
|
||||
"library_page_sort_last_modified": "Last modified",
|
||||
"library_page_sort_most_oldest_photo": "Oldest photo",
|
||||
"library_page_sort_most_recent_photo": "Most recent photo",
|
||||
"library_page_sort_title": "Albuma virsraksts",
|
||||
"location_picker_choose_on_map": "Izvēlēties uz kartes",
|
||||
"location_picker_latitude": "Ģeogrāfiskais platums",
|
||||
"location_picker_latitude_error": "Ievadiet korektu ģeogrāfisko platumu",
|
||||
"location_picker_latitude_hint": "Ievadiet savu ģeogrāfisko platumu šeit",
|
||||
"location_picker_longitude": "Ģeogrāfiskais garums",
|
||||
"location_picker_longitude_error": "Ievadiet korektu ģeogrāfisko garumu",
|
||||
"location_picker_longitude_hint": "Ievadiet savu ģeogrāfisko garumu šeit",
|
||||
"login_disabled": "Pieslēgšanās ir atslēgta",
|
||||
"location_picker_choose_on_map": "Choose on map",
|
||||
"location_picker_latitude": "Latitude",
|
||||
"location_picker_latitude_error": "Enter a valid latitude",
|
||||
"location_picker_latitude_hint": "Enter your latitude here",
|
||||
"location_picker_longitude": "Longitude",
|
||||
"location_picker_longitude_error": "Enter a valid longitude",
|
||||
"location_picker_longitude_hint": "Enter your longitude here",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API izņēmums. Lūdzu, pārbaudiet servera URL un mēģiniet vēlreiz.",
|
||||
"login_form_back_button_text": "Atpakaļ",
|
||||
"login_form_back_button_text": "Back",
|
||||
"login_form_button_text": "Pieteikties",
|
||||
"login_form_email_hint": "jūsuepasts@email.com",
|
||||
"login_form_endpoint_hint": "http://jūsu-servera-ip:ports/api",
|
||||
@@ -255,7 +255,7 @@
|
||||
"login_form_failed_get_oauth_server_config": "Pieslēdzoties, izmantojot OAuth, radās kļūda; pārbaudiet servera URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth līdzeklis šajā serverī nav pieejams",
|
||||
"login_form_failed_login": "Radās kļūda, piesakoties, pārbaudiet servera URL, e-pastu un paroli",
|
||||
"login_form_handshake_exception": "Ar serveri tika konstatēta Handshake Exception kļūda. Ja izmantojat pašparakstītu sertifikātu, tad iestatījumos iespējojiet pašparakstītu sertifikātu atbalstu.",
|
||||
"login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
|
||||
"login_form_label_email": "E-pasts",
|
||||
"login_form_label_password": "Parole",
|
||||
"login_form_next_button": "Nākošais",
|
||||
@@ -263,51 +263,51 @@
|
||||
"login_form_save_login": "Palikt pieteiktam",
|
||||
"login_form_server_empty": "Ieraksties servera URL.",
|
||||
"login_form_server_error": "Nevarēja izveidot savienojumu ar serveri.",
|
||||
"login_password_changed_error": "Atjaunojot paroli radās kļūda",
|
||||
"login_password_changed_success": "Parole veiksmīgi atjaunota",
|
||||
"map_assets_in_bound": "{} fotoattēls",
|
||||
"map_assets_in_bounds": "{} fotoattēli",
|
||||
"map_cannot_get_user_location": "Nevar iegūt lietotāja atrašanās vietu",
|
||||
"map_location_dialog_cancel": "Atcelt",
|
||||
"map_location_dialog_yes": "Jā",
|
||||
"map_location_picker_page_use_location": "Izvēlēties šo atrašanās vietu",
|
||||
"map_location_service_disabled_content": "Lai tiktu rādīti jūsu pašreizējās atrašanās vietas aktīvi, ir jāaktivizē atrašanās vietas pakalpojums. Vai vēlaties to iespējot tagad?",
|
||||
"map_location_service_disabled_title": "Atrašanās vietas Pakalpojums atslēgts",
|
||||
"map_no_assets_in_bounds": "Šajā lokācijā nav neviena fotoattēla",
|
||||
"map_no_location_permission_content": "Atrašanās vietas atļauja ir nepieciešama, lai parādītu jūsu pašreizējās atrašanās vietas aktīvus. Vai vēlaties to atļaut tagad?",
|
||||
"map_no_location_permission_title": "Atrašanās vietas Atļaujas liegtas",
|
||||
"map_settings_dark_mode": "Tumšais režīms",
|
||||
"map_settings_date_range_option_all": "Viss",
|
||||
"map_settings_date_range_option_day": "Pēdējās 24 stundas",
|
||||
"map_settings_date_range_option_days": "Pēdējās {} dienas",
|
||||
"map_settings_date_range_option_year": "Pēdējo gadu",
|
||||
"map_settings_date_range_option_years": "Pēdējos {} gadus",
|
||||
"map_settings_dialog_cancel": "Atcelt",
|
||||
"map_settings_dialog_save": "Saglabāt",
|
||||
"map_settings_dialog_title": "Kartes Iestatījumi",
|
||||
"map_settings_include_show_archived": "Iekļaut Arhivētos",
|
||||
"map_settings_include_show_partners": "Iekļaut Partnerus",
|
||||
"map_settings_only_relative_range": "Datumu diapazons",
|
||||
"map_settings_only_show_favorites": "Rādīt tikai Izlasi",
|
||||
"map_settings_theme_settings": "Kartes Dizains",
|
||||
"map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus",
|
||||
"memories_all_caught_up": "Šobrīd, tas arī viss",
|
||||
"memories_check_back_tomorrow": "Priekš vairāk atmiņām atgriezieties rītdien.",
|
||||
"memories_start_over": "Sākt no jauna",
|
||||
"memories_swipe_to_close": "Pavelciet uz augšu, lai aizvērtu",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "Cannot get user's location",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes",
|
||||
"map_location_picker_page_use_location": "Use this location",
|
||||
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
|
||||
"map_location_service_disabled_title": "Location Service disabled",
|
||||
"map_no_assets_in_bounds": "No photos in this area",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_settings_dark_mode": "Dark mode",
|
||||
"map_settings_date_range_option_all": "All",
|
||||
"map_settings_date_range_option_day": "Past 24 hours",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_year": "Past year",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_dialog_cancel": "Cancel",
|
||||
"map_settings_dialog_save": "Save",
|
||||
"map_settings_dialog_title": "Map Settings",
|
||||
"map_settings_include_show_archived": "Include Archived",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Date range",
|
||||
"map_settings_only_show_favorites": "Show Favorite Only",
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Zoom out to see photos",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM g",
|
||||
"motion_photos_page_title": "Kustību Fotoattēli",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Nevar rediģēt read only aktīva(-u) datumu, notiek izlaišana",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Nevar rediģēt atrašanās vietu read only aktīva(-u) datumu, notiek izlaišana",
|
||||
"no_assets_to_show": "Nav uzrādāmo aktīvu",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "Atcelt",
|
||||
"notification_permission_dialog_content": "Lai iespējotu paziņojumus, atveriet Iestatījumi un atlasiet Atļaut.",
|
||||
"notification_permission_dialog_settings": "Iestatījumi",
|
||||
"notification_permission_list_tile_content": "Piešķirt atļauju, lai iespējotu paziņojumus.",
|
||||
"notification_permission_list_tile_enable_button": "Iespējot Paziņojumus",
|
||||
"notification_permission_list_tile_title": "Paziņojumu Atļaujas",
|
||||
"partner_list_user_photos": "{user} fotoattēli",
|
||||
"partner_list_view_all": "Apskatīt visu",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Pievienot partneri",
|
||||
"partner_page_empty_message": "Jūsu fotogrāfijas pagaidām nav kopīgotas ar nevienu partneri.",
|
||||
"partner_page_no_more_users": "Nav vairs lietotāju, kurus var pievienot",
|
||||
@@ -317,7 +317,7 @@
|
||||
"partner_page_stop_sharing_content": "{} vairs nevarēs piekļūt jūsu fotoattēliem.",
|
||||
"partner_page_stop_sharing_title": "Beigt kopīgot jūsu fotogrāfijas?",
|
||||
"partner_page_title": "Partneris",
|
||||
"permission_onboarding_back": "Atpakaļ",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Tomēr turpināt",
|
||||
"permission_onboarding_get_started": "Darba sākšana",
|
||||
"permission_onboarding_go_to_settings": "Doties uz iestatījumiem",
|
||||
@@ -327,46 +327,46 @@
|
||||
"permission_onboarding_permission_granted": "Atļauja piešķirta! Jūs esat gatavi darbam.",
|
||||
"permission_onboarding_permission_limited": "Atļauja ierobežota. Lai atļautu Immich dublēšanu un varētu pārvaldīt visu galeriju kolekciju, sadaļā Iestatījumi piešķiriet fotoattēlu un video atļaujas.",
|
||||
"permission_onboarding_request": "Immich nepieciešama atļauja skatīt jūsu fotoattēlus un videoklipus.",
|
||||
"preferences_settings_title": "Iestatījumi",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "Žurnāli",
|
||||
"profile_drawer_client_out_of_date_major": "Mobilā Aplikācija ir novecojusi. Lūdzu atjaunojiet to uz jaunāko lielo versiju",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobilā Aplikācija ir novecojusi. Lūdzu atjaunojiet to uz jaunāko mazo versiju",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti",
|
||||
"profile_drawer_documentation": "Dokumentācija",
|
||||
"profile_drawer_documentation": "Documentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Serveris ir novecojis. Lūdzu atjaunojiet to uz jaunāko lielo versiju",
|
||||
"profile_drawer_server_out_of_date_minor": "Serveris ir novecojis. Lūdzu atjaunojiet to uz jaunāko mazo versiju",
|
||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_settings": "Iestatījumi",
|
||||
"profile_drawer_sign_out": "Izrakstīties",
|
||||
"profile_drawer_trash": "Atkritne",
|
||||
"profile_drawer_trash": "Trash",
|
||||
"recently_added_page_title": "Nesen Pievienotais",
|
||||
"scaffold_body_error_occurred": "Radās kļūda",
|
||||
"scaffold_body_error_occurred": "Error occurred",
|
||||
"search_bar_hint": "Meklēt Jūsu fotoattēlus",
|
||||
"search_filter_apply": "Lietot filtru",
|
||||
"search_filter_camera_make": "Firma",
|
||||
"search_filter_camera_model": "Modelis",
|
||||
"search_filter_display_option_archive": "Arhīvs",
|
||||
"search_filter_display_option_favorite": "Izlase",
|
||||
"search_filter_display_option_not_in_album": "Nav albumā",
|
||||
"search_filter_location_city": "Pilsēta",
|
||||
"search_filter_location_country": "Valsts",
|
||||
"search_filter_location_state": "Štats",
|
||||
"search_filter_media_type_all": "Viss",
|
||||
"search_filter_media_type_image": "Attēls",
|
||||
"search_filter_media_type_video": "Videoklips",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Kategorijas",
|
||||
"search_page_favorites": "Izlase",
|
||||
"search_page_motion_photos": "Kustību Fotoattēli",
|
||||
"search_page_no_objects": "Informācija par Objektiem nav pieejama",
|
||||
"search_page_no_places": "Nav pieejama Informācija par Vietām",
|
||||
"search_page_people": "Cilvēki",
|
||||
"search_page_person_add_name_dialog_cancel": "Atcelt",
|
||||
"search_page_person_add_name_dialog_hint": "Vārds",
|
||||
"search_page_person_add_name_dialog_save": "Saglabāt",
|
||||
"search_page_person_add_name_dialog_title": "Pievienot vārdu",
|
||||
"search_page_person_add_name_subtitle": "Atrast viņus ātri pēc vārdiem izmantojot meklēšanu",
|
||||
"search_page_person_add_name_title": "Pievienot vārdu",
|
||||
"search_page_person_edit_name": "Rediģēt vārdu",
|
||||
"search_page_person_add_name_dialog_cancel": "Cancel",
|
||||
"search_page_person_add_name_dialog_hint": "Name",
|
||||
"search_page_person_add_name_dialog_save": "Save",
|
||||
"search_page_person_add_name_dialog_title": "Add a name",
|
||||
"search_page_person_add_name_subtitle": "Find them fast by name with search",
|
||||
"search_page_person_add_name_title": "Add a name",
|
||||
"search_page_person_edit_name": "Edit name",
|
||||
"search_page_places": "Vietas",
|
||||
"search_page_recently_added": "Nesen Pievienotais",
|
||||
"search_page_screenshots": "Ekrānuzņēmumi",
|
||||
@@ -375,7 +375,7 @@
|
||||
"search_page_videos": "Videoklipi",
|
||||
"search_page_view_all_button": "Apskatīt visu",
|
||||
"search_page_your_activity": "Jūsu aktivitāte",
|
||||
"search_page_your_map": "Jūsu Karte",
|
||||
"search_page_your_map": "Your Map",
|
||||
"search_result_page_new_search_hint": "Jauns Meklējums",
|
||||
"search_suggestion_list_smart_search_hint_1": "Viedā meklēšana ir iespējota pēc noklusējuma, lai meklētu metadatus, izmantojiet sintaksi",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:jūsu-meklēšanas-frāze",
|
||||
@@ -383,17 +383,16 @@
|
||||
"select_user_for_sharing_page_err_album": "Neizdevās izveidot albumu",
|
||||
"select_user_for_sharing_page_share_suggestions": "Ieteikumi",
|
||||
"server_info_box_app_version": "Aplikācijas Versija",
|
||||
"server_info_box_latest_release": "Jaunākā Versija",
|
||||
"server_info_box_server_url": "Servera URL",
|
||||
"server_info_box_latest_release": "Latest Version",
|
||||
"server_info_box_server_url": "Server URL",
|
||||
"server_info_box_server_version": "Servera Versija",
|
||||
"setting_image_viewer_help": "Detaļu skatītājs vispirms ielādē mazo sīktēlu, pēc tam ielādē vidēja lieluma priekšskatījumu (ja iespējots), visbeidzot ielādē oriģinālu (ja iespējots).",
|
||||
"setting_image_viewer_original_subtitle": "Iespējojiet sākotnējā pilnas izšķirtspējas attēla (liels!) ielādi. Atspējot lai samazinātu datu lietojumu (gan tīklā, gan ierīces kešatmiņā).",
|
||||
"setting_image_viewer_original_title": "Ielādēt oriģinālo attēlu",
|
||||
"setting_image_viewer_preview_subtitle": "Iespējojiet vidējas izšķirtspējas attēla ielādēšanu. Atspējojiet vai nu tiešu oriģināla ielādi, vai izmantojiet tikai sīktēlu.",
|
||||
"setting_image_viewer_preview_title": "Ielādēt priekšskatījuma attēlu",
|
||||
"setting_image_viewer_title": "Attēli",
|
||||
"setting_languages_apply": "Lietot",
|
||||
"setting_languages_title": "Valodas",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Paziņot par fona dublēšanas kļūmēm: {}",
|
||||
"setting_notifications_notify_hours": "{} stundas",
|
||||
"setting_notifications_notify_immediately": "nekavējoties",
|
||||
@@ -408,78 +407,75 @@
|
||||
"setting_notifications_total_progress_title": "Rādīt fona dublējuma kopējo progresu",
|
||||
"setting_pages_app_bar_settings": "Iestatījumi",
|
||||
"settings_require_restart": "Lūdzu, restartējiet Immich, lai lietotu šo iestatījumu",
|
||||
"setting_video_viewer_looping_subtitle": "Iespējot, lai automātiski videoklips tiktu cikliski palaists detaļu skatītājā.",
|
||||
"setting_video_viewer_looping_title": "Cikliski",
|
||||
"setting_video_viewer_title": "Videoklipi",
|
||||
"share_add": "Pievienot",
|
||||
"share_add_photos": "Pievienot fotoattēlus",
|
||||
"share_add_title": "Pievienot virsrakstu",
|
||||
"share_assets_selected": "{} izvēlēti",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Izveidot albumu",
|
||||
"shared_album_activities_input_disable": "Komentāri atslēgti",
|
||||
"shared_album_activities_input_hint": "Teikt kaut ko",
|
||||
"shared_album_activity_remove_content": "Vai vēlaties šo aktivitāti dzēst?",
|
||||
"shared_album_activity_remove_title": "Dzēst Aktivitāti",
|
||||
"shared_album_activity_setting_subtitle": "Ļaut citiem atbildēt",
|
||||
"shared_album_activity_setting_title": "Komentāri un \"patīk\"",
|
||||
"shared_album_section_people_action_error": "Kļūme pametot/noņemot no albuma",
|
||||
"shared_album_section_people_action_leave": "Noņemt lietotāju no albuma",
|
||||
"shared_album_section_people_action_remove_user": "Noņemt lietotāju no albuma",
|
||||
"shared_album_section_people_owner_label": "Īpašnieks",
|
||||
"shared_album_section_people_title": "CILVĒKI",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_section_people_action_error": "Error leaving/removing from album",
|
||||
"shared_album_section_people_action_leave": "Remove user from album",
|
||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||
"shared_album_section_people_owner_label": "Owner",
|
||||
"shared_album_section_people_title": "PEOPLE",
|
||||
"share_dialog_preparing": "Notiek sagatavošana...",
|
||||
"shared_link_app_bar_title": "Kopīgotas Saites",
|
||||
"shared_link_clipboard_copied_massage": "Ievietots starpliktuvē",
|
||||
"shared_link_clipboard_text": "Saite: {}\nParole: {}",
|
||||
"shared_link_create_app_bar_title": "Izveidot kopīgošanas saiti",
|
||||
"shared_link_create_error": "Kļūda izveidojot kopīgošanas saiti",
|
||||
"shared_link_create_info": "Ļaut jebkuram ar saiti apskatīt izvēlēto(-os) attēlu(-us)",
|
||||
"shared_link_create_submit_button": "Izveidot saiti",
|
||||
"shared_link_edit_allow_download": "Ļaut publiskiem lietotājiem lejupielādēt",
|
||||
"shared_link_edit_allow_upload": "Ļaut publiskiem lietotājiem augšupielādēt",
|
||||
"shared_link_edit_app_bar_title": "Rediģēt saiti",
|
||||
"shared_link_edit_change_expiry": "Izmainīt derīguma termiņu",
|
||||
"shared_link_edit_description": "Apraksts",
|
||||
"shared_link_edit_description_hint": "Ievadiet kopīgojuma aprakstu",
|
||||
"shared_link_edit_expire_after": "Derīguma termiņš beidzas pēc",
|
||||
"shared_link_edit_expire_after_option_day": "1 diena",
|
||||
"shared_link_edit_expire_after_option_days": "{} dienas",
|
||||
"shared_link_edit_expire_after_option_hour": "1 stunda",
|
||||
"shared_link_edit_expire_after_option_hours": "{} stundas",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minūte",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minūtes",
|
||||
"shared_link_edit_expire_after_option_months": "{} mēneši",
|
||||
"shared_link_edit_expire_after_option_never": "Nekad",
|
||||
"shared_link_edit_expire_after_option_year": "{} gads",
|
||||
"shared_link_edit_password": "Parole",
|
||||
"shared_link_edit_password_hint": "Ierakstīt kopīgojuma paroli",
|
||||
"shared_link_edit_show_meta": "Rādīt metadatus",
|
||||
"shared_link_edit_submit_button": "Atjaunināt saiti",
|
||||
"shared_link_empty": "Jums nav nevienas kopīgotas saites",
|
||||
"shared_link_error_server_url_fetch": "Nevarēja ienest servera URL",
|
||||
"shared_link_expired": "Derīguma termiņš beidzās",
|
||||
"shared_link_expires_day": "Derīguma termiņš beigsies pēc {} dienas",
|
||||
"shared_link_expires_days": "Derīguma termiņš beigsies pēc {} dienām",
|
||||
"shared_link_expires_hour": "Derīguma termiņš beigsies pēc {} stundas",
|
||||
"shared_link_expires_hours": "Derīguma termiņš beigsies pēc {} stundām",
|
||||
"shared_link_expires_minute": "Derīguma termiņš beigsies pēc {} minūtes",
|
||||
"shared_link_expires_minutes": "Derīguma termiņš beidzas pēc {} minūtēm",
|
||||
"shared_link_expires_never": "Derīguma termiņš beigsies ∞",
|
||||
"shared_link_expires_second": "Derīguma termiņš beigsies pēc {} sekundes",
|
||||
"shared_link_expires_seconds": "Derīguma termiņš beidzas pēc {} sekundēm",
|
||||
"shared_link_individual_shared": "Individuāli kopīgots",
|
||||
"shared_link_info_chip_download": "Lejupielādēt",
|
||||
"shared_link_app_bar_title": "Shared Links",
|
||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_create_app_bar_title": "Create link to share",
|
||||
"shared_link_create_error": "Error while creating shared link",
|
||||
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
|
||||
"shared_link_create_submit_button": "Create link",
|
||||
"shared_link_edit_allow_download": "Allow public user to download",
|
||||
"shared_link_edit_allow_upload": "Allow public user to upload",
|
||||
"shared_link_edit_app_bar_title": "Edit link",
|
||||
"shared_link_edit_change_expiry": "Change expiration time",
|
||||
"shared_link_edit_description": "Description",
|
||||
"shared_link_edit_description_hint": "Enter the share description",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after_option_day": "1 day",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_hour": "1 hour",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Never",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Password",
|
||||
"shared_link_edit_password_hint": "Enter the share password",
|
||||
"shared_link_edit_show_meta": "Show metadata",
|
||||
"shared_link_edit_submit_button": "Update link",
|
||||
"shared_link_empty": "You don't have any shared links",
|
||||
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
|
||||
"shared_link_expired": "Expired",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_never": "Expires ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Download",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Augšupielādēt",
|
||||
"shared_link_manage_links": "Pārvaldīt Kopīgotās saites",
|
||||
"shared_link_public_album": "Publisks albums",
|
||||
"share_done": "Gatavs",
|
||||
"shared_link_info_chip_upload": "Upload",
|
||||
"shared_link_manage_links": "Manage Shared links",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Done",
|
||||
"share_invite": "Uzaicināt albumā",
|
||||
"sharing_page_album": "Kopīgotie albumi",
|
||||
"sharing_page_description": "Izveidojiet koplietojamus albumus, lai kopīgotu fotoattēlus un videoklipus ar Jūsu tīkla lietotājiem.",
|
||||
"sharing_page_empty_list": "TUKŠS SARAKSTS",
|
||||
"sharing_silver_appbar_create_shared_album": "Izveidot kopīgotu albumu",
|
||||
"sharing_silver_appbar_shared_links": "Kopīgotās saites",
|
||||
"sharing_silver_appbar_shared_links": "Shared links",
|
||||
"sharing_silver_appbar_share_partner": "Dalīties ar partneri",
|
||||
"tab_controller_nav_library": "Bibliotēka",
|
||||
"tab_controller_nav_photos": "Fotoattēli",
|
||||
@@ -495,30 +491,30 @@
|
||||
"theme_setting_theme_title": "Dizains",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trīspakāpju ielāde var palielināt ielādēšanas veiktspēju, bet izraisa ievērojami lielāku tīkla noslodzi",
|
||||
"theme_setting_three_stage_loading_title": "Iespējot trīspakāpju ielādi",
|
||||
"translated_text_options": "Iestatījumi",
|
||||
"trash_page_delete": "Dzēst",
|
||||
"trash_page_delete_all": "Dzēst Visu",
|
||||
"trash_page_empty_trash_btn": "Iztukšot atkritni",
|
||||
"trash_page_empty_trash_dialog_content": "Vai vēlaties iztukšot savus izmestos aktīvus? Tie tiks neatgriezeniski izņemti no Immich",
|
||||
"translated_text_options": "Options",
|
||||
"trash_page_delete": "Delete",
|
||||
"trash_page_delete_all": "Delete All",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Atkritnes vienumi tiks neatgriezeniski dzēsti pēc {} dienām",
|
||||
"trash_page_no_assets": "Atkritnē nav aktīvu",
|
||||
"trash_page_restore": "Atjaunot",
|
||||
"trash_page_restore_all": "Atjaunot Visu",
|
||||
"trash_page_select_assets_btn": "Atlasīt aktīvus",
|
||||
"trash_page_select_btn": "Atlasīt",
|
||||
"trash_page_title": "Atkritne ({})",
|
||||
"upload_dialog_cancel": "Atcelt",
|
||||
"upload_dialog_info": "Vai vēlaties veikt izvēlētā(-o) aktīva(-u) dublējumu uz servera?",
|
||||
"upload_dialog_ok": "Augšupielādēt",
|
||||
"upload_dialog_title": "Augšupielādēt Aktīvu",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_no_assets": "No trashed assets",
|
||||
"trash_page_restore": "Restore",
|
||||
"trash_page_restore_all": "Restore All",
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_select_btn": "Select",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Atzīt",
|
||||
"version_announcement_overlay_release_notes": "informācija par laidienu",
|
||||
"version_announcement_overlay_text_1": "Sveiks draugs, ir jauns izlaidums no",
|
||||
"version_announcement_overlay_text_2": "lūdzu, veltiet laiku, lai apmeklētu",
|
||||
"version_announcement_overlay_text_3": " un pārliecinieties, vai docker-compose un .env iestatījumi ir atjaunināti, lai novērstu jebkādas nepareizas konfigurācijas, īpaši, ja izmantojat WatchTower vai mehānismu, kas automātiski veic servera lietojumprogrammas atjaunināšanu.",
|
||||
"version_announcement_overlay_title": "Pieejama jauna servera versija \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Noņemt no Steka",
|
||||
"viewer_stack_use_as_main_asset": "Izmantot kā Galveno Aktīvu",
|
||||
"viewer_unstack": "At-Stekot"
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack"
|
||||
}
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"backup_controller_page_background_configure_error": "Konfigurering av bakgrunnstjenesten feilet",
|
||||
"backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye objekter: {}",
|
||||
"backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk sikkerhetskopiere alle nye objekter uten å måtte åpne appen",
|
||||
"backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnen er deaktivert",
|
||||
"backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnener deaktivert",
|
||||
"backup_controller_page_background_is_on": "Automatisk sikkerhetskopiering i bakgrunnen er aktivert",
|
||||
"backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten",
|
||||
"backup_controller_page_background_turn_on": "Skru på bakgrunnstjenesten",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Last originalbildet",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiver for å laste et bilde av medium oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.",
|
||||
"setting_image_viewer_preview_title": "Last forhåndsvisningsbilde",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Bekreft",
|
||||
"setting_languages_title": "Språk",
|
||||
"setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Vis status på sikkerhetskopiering i bakgrunnen",
|
||||
"setting_pages_app_bar_settings": "Innstillinger",
|
||||
"settings_require_restart": "Vennligst restart Immich for å aktivere denne innstillingen",
|
||||
"setting_video_viewer_looping_subtitle": "Aktiver for å automatisk loope en video i detaljeviseren.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videoer",
|
||||
"share_add": "Legg til",
|
||||
"share_add_photos": "Legg til bilder",
|
||||
"share_add_title": "Legg til tittel",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"advanced_settings_troubleshooting_subtitle": "Schakel extra functies voor probleemoplossing in ",
|
||||
"advanced_settings_troubleshooting_title": "Probleemoplossing",
|
||||
"album_info_card_backup_album_excluded": "UITGESLOTEN",
|
||||
"album_info_card_backup_album_included": "INBEGREPEN",
|
||||
"album_info_card_backup_album_included": "INGESLOTEN",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_shared": " · Gedeeld",
|
||||
@@ -53,7 +53,7 @@
|
||||
"asset_viewer_settings_title": "Foto weergave",
|
||||
"backup_album_selection_page_albums_device": "Albums op apparaat ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tik om in te voegen, dubbel tik om uit te sluiten",
|
||||
"backup_album_selection_page_assets_scatter": "Assets kunnen over verschillende albums verdeeld zijn, dus albums kunnen inbegrepen of uitgesloten zijn van het backup proces.",
|
||||
"backup_album_selection_page_assets_scatter": "Assets kunnen over verschillende albums verdeeld zijn, dus albums kunnen ingesloten of uitgesloten zijn van het backup proces.",
|
||||
"backup_album_selection_page_select_albums": "Albums selecteren",
|
||||
"backup_album_selection_page_selection_info": "Selectie info",
|
||||
"backup_album_selection_page_total_assets": "Totaal unieke assets",
|
||||
@@ -70,7 +70,7 @@
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Verversen op achtergrond uitgeschakeld",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Ga naar instellingen",
|
||||
"backup_controller_page_background_battery_info_link": "Laat zien hoe",
|
||||
"backup_controller_page_background_battery_info_message": "Voor de beste back-upervaring, schakel je alle batterijoptimalisaties uit omdat deze op-de-achtergrondactiviteiten van Immich beperken.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
|
||||
"backup_controller_page_background_battery_info_message": "Schakel voor de beste back-upervaring op de achtergrond alle batterijoptimalisaties uit, die de achtergrondactiviteit van Immich beperken.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Batterijoptimalisaties",
|
||||
"backup_controller_page_background_charging": "Alleen tijdens opladen",
|
||||
@@ -158,7 +158,7 @@
|
||||
"control_bottom_app_bar_share": "Delen",
|
||||
"control_bottom_app_bar_share_to": "Delen met",
|
||||
"control_bottom_app_bar_stack": "Stapel",
|
||||
"control_bottom_app_bar_trash_from_immich": "Naar prullenbak",
|
||||
"control_bottom_app_bar_trash_from_immich": "Verplaatsen naar prullenbak",
|
||||
"control_bottom_app_bar_unarchive": "Herstellen",
|
||||
"control_bottom_app_bar_unfavorite": "Onfavoriet",
|
||||
"control_bottom_app_bar_upload": "Uploaden",
|
||||
@@ -244,7 +244,7 @@
|
||||
"login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.",
|
||||
"login_form_back_button_text": "Terug",
|
||||
"login_form_button_text": "Inloggen",
|
||||
"login_form_email_hint": "jouwemail@email.nl",
|
||||
"login_form_email_hint": "jouwemail@email.com",
|
||||
"login_form_endpoint_hint": "http://jouw-server-ip:poort/api",
|
||||
"login_form_endpoint_url": "Server-URL",
|
||||
"login_form_err_http": "Voer http:// of https:// in",
|
||||
@@ -350,7 +350,7 @@
|
||||
"search_filter_display_option_not_in_album": "Niet in album",
|
||||
"search_filter_location_city": "Stad",
|
||||
"search_filter_location_country": "Land",
|
||||
"search_filter_location_state": "Status",
|
||||
"search_filter_location_state": "Staat",
|
||||
"search_filter_media_type_all": "Alle",
|
||||
"search_filter_media_type_image": "Afbeelding",
|
||||
"search_filter_media_type_video": "Video",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Originele afbeelding laden",
|
||||
"setting_image_viewer_preview_subtitle": "Schakel in om een afbeelding met middelgrote resolutie te laden. Schakel uit om alleen het origineel direct te laden of alleen de thumbnail te gebruiken.",
|
||||
"setting_image_viewer_preview_title": "Voorbeeldafbeelding laden",
|
||||
"setting_image_viewer_title": "Afbeeldingen",
|
||||
"setting_languages_apply": "Toepassen",
|
||||
"setting_languages_title": "Taal",
|
||||
"setting_notifications_notify_failures_grace_period": "Fouten van back-up op de achtergrond melden: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Totale voortgang van achtergrondback-up tonen",
|
||||
"setting_pages_app_bar_settings": "Instellingen",
|
||||
"settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen",
|
||||
"setting_video_viewer_looping_subtitle": "Inschakelen om video's automatisch te herhalen in de detailweergave.",
|
||||
"setting_video_viewer_looping_title": "Herhalen",
|
||||
"setting_video_viewer_title": "Video's",
|
||||
"share_add": "Toevoegen",
|
||||
"share_add_photos": "Foto's toevoegen",
|
||||
"share_add_title": "Titel toevoegen",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"action_common_back": "Cofnij",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Anuluj",
|
||||
"action_common_clear": "Wyrzuść",
|
||||
"action_common_confirm": "Potwierdzać",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Aktualizuj",
|
||||
"add_to_album_bottom_sheet_added": "Dodano do {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Już w {album}",
|
||||
@@ -22,7 +22,7 @@
|
||||
"album_thumbnail_card_shared": "Udostępniony",
|
||||
"album_thumbnail_owned": "Posiadany",
|
||||
"album_thumbnail_shared_by": "Udostępnione przez {}",
|
||||
"album_viewer_appbar_delete_confirm": "Czy na pewno chcesz usunąć ten album ze swojego konta?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Usuń album",
|
||||
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
|
||||
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
|
||||
@@ -115,7 +115,7 @@
|
||||
"backup_manual_in_progress": "Przesyłanie już trwa. Spróbuj po pewnym czasie",
|
||||
"backup_manual_success": "Sukces",
|
||||
"backup_manual_title": "Stan przesyłania",
|
||||
"backup_options_page_title": "Opcje kopi zapasowej",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
||||
"cache_settings_clear_cache_button": "Wyczyść Cache",
|
||||
"cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Nie można udostępniać zasobów lokalnych za pośrednictwem linku, pomijajam",
|
||||
"home_page_upload_err_limit": "Można przesłać maksymalnie 30 zasobów jednocześnie, pomijanie",
|
||||
"image_viewer_page_state_provider_download_error": "Błąd pobierania",
|
||||
"image_viewer_page_state_provider_download_started": "Pobieranie rozpoczęte",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Pobieranie zakończone",
|
||||
"image_viewer_page_state_provider_share_error": "Udostępnij błąd",
|
||||
"library_page_albums": "Albumy",
|
||||
@@ -292,22 +292,22 @@
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Pomniejsz, aby zobaczyć zdjęcia",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Wróć jutro po więcej wspomnień",
|
||||
"memories_start_over": "Zacznij od nowa",
|
||||
"memories_swipe_to_close": "Przesuń w górę, aby zamknąć",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Zdjęcia ruchome",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Nie można edytować daty zasobów tylko do odczytu, pomijanie",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Nie można edytować lokalizacji zasobów tylko do odczytu, pomijanie",
|
||||
"no_assets_to_show": "Brak zasobów do pokazania",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "Anuluj",
|
||||
"notification_permission_dialog_content": "Aby włączyć powiadomienia, przejdź do Ustawień i wybierz opcję Zezwalaj.",
|
||||
"notification_permission_dialog_settings": "Ustawienia",
|
||||
"notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.",
|
||||
"notification_permission_list_tile_enable_button": "Włącz Powiadomienia",
|
||||
"notification_permission_list_tile_title": "Pozwolenie na powiadomienia",
|
||||
"partner_list_user_photos": "{user} zdjęcia",
|
||||
"partner_list_view_all": "Pokaż wszystkie",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Dodaj partnera",
|
||||
"partner_page_empty_message": "Twoje zdjęcia nie są udostępnione żadnemu partnerowi",
|
||||
"partner_page_no_more_users": "Brak użytkowników do dodania",
|
||||
@@ -342,7 +342,7 @@
|
||||
"recently_added_page_title": "Ostatnio Dodane",
|
||||
"scaffold_body_error_occurred": "Wystąpił błąd",
|
||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||
"search_filter_apply": "Zastosuj filtr",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archiwum",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Załaduj oryginalny obraz",
|
||||
"setting_image_viewer_preview_subtitle": "Włącz ładowanie obrazu o średniej rozdzielczości. Wyłącz opcję bezpośredniego ładowania oryginału lub używania tylko miniatury.",
|
||||
"setting_image_viewer_preview_title": "Załaduj obraz podglądu",
|
||||
"setting_image_viewer_title": "Zdjęcia",
|
||||
"setting_languages_apply": "Zastosuj",
|
||||
"setting_languages_title": "Języki",
|
||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Pokaż całkowity postęp tworzenia kopii zapasowej w tle",
|
||||
"setting_pages_app_bar_settings": "Ustawienia",
|
||||
"settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich",
|
||||
"setting_video_viewer_looping_subtitle": "Włącz automatyczne zapętlanie wideo w przeglądarce szczegółów.",
|
||||
"setting_video_viewer_looping_title": "Zapętlenie",
|
||||
"setting_video_viewer_title": "Filmy",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj zdjęcia",
|
||||
"share_add_title": "Dodaj tytuł",
|
||||
"share_assets_selected": "{} wybrano ",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Utwórz album",
|
||||
"shared_album_activities_input_disable": "Komentarz jest wyłączony",
|
||||
"shared_album_activities_input_hint": "Powiedz coś",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Carregar imagem original",
|
||||
"setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resolução média. Desative para carregar diretamente o original ou usar apenas a miniatura.",
|
||||
"setting_image_viewer_preview_title": "Carregar imagem de visualização",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifique falhas de backup em segundo plano: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Mostrar progresso total do backup em segundo plano",
|
||||
"setting_pages_app_bar_settings": "Configurações",
|
||||
"settings_require_restart": "Reinicie o Immich para aplicar essa configuração",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Adicionar",
|
||||
"share_add_photos": "Adicionar fotos",
|
||||
"share_add_title": "Adicione um título",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Încarcă fotografia originală",
|
||||
"setting_image_viewer_preview_subtitle": "Activează pentru a încărca o imagine în rezoluție medie. Dezactivează pentru a încărca direct imaginea originală sau doar a utiliza miniatura.",
|
||||
"setting_image_viewer_preview_title": "Încarcă imaginea de previzualizare",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notificare eșuări backup în fundal: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Afișează progresul total al copiilor de siguranță în fundal",
|
||||
"setting_pages_app_bar_settings": "Setări",
|
||||
"settings_require_restart": "Te rugăm să repornești Immich pentru a aplica această setare",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Adaugă",
|
||||
"share_add_photos": "Adaugă fotografii",
|
||||
"share_add_title": "Adaugă un titlu",
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"home_page_album_err_partner": "Пока не удается добавить объекты партнера в альбом, пропуск...",
|
||||
"home_page_archive_err_local": "Пока невозможно добавить локальные объекты в архив, пропускаем",
|
||||
"home_page_archive_err_partner": "Невозможно архивировать объекты партнера, пропуск...",
|
||||
"home_page_building_timeline": "Построение хронологии",
|
||||
"home_page_building_timeline": "Построение временной шкалы",
|
||||
"home_page_delete_err_partner": "Невозможно удалить объекты партнера, пропуск...",
|
||||
"home_page_delete_remote_err_local": "Локальные объект(ы) уже в процессе удаления с сервера, пропуск...",
|
||||
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропуск...",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Загружать исходное изображение",
|
||||
"setting_image_viewer_preview_subtitle": "Включите для загрузки изображения среднего разрешения.\nОтключите, чтобы загружать оригинал напрямую или использовать только миниатюру.",
|
||||
"setting_image_viewer_preview_title": "Загружать изображение для предварительного просмотра",
|
||||
"setting_image_viewer_title": "Изображения",
|
||||
"setting_languages_apply": "Применить",
|
||||
"setting_languages_title": "Язык",
|
||||
"setting_notifications_notify_failures_grace_period": "Уведомлять об ошибках фонового резервного копирования: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Показать общий прогресс фонового резервного копирования",
|
||||
"setting_pages_app_bar_settings": "Настройки",
|
||||
"settings_require_restart": "Пожалуйста, перезапустите приложение, чтобы изменения вступили в силу",
|
||||
"setting_video_viewer_looping_subtitle": "Включить циклическое воспроизведение видео",
|
||||
"setting_video_viewer_looping_title": "Циклическое воспроизведение",
|
||||
"setting_video_viewer_title": "Видео",
|
||||
"share_add": "Добавить",
|
||||
"share_add_photos": "Добавить фото",
|
||||
"share_add_title": "Добавить название",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Načítať pôvodný obrázok",
|
||||
"setting_image_viewer_preview_subtitle": "Povolením umožníte načítať obrázok so stredným rozlíšením. Zakážte, ak chcete priamo načítať originál alebo použiť iba miniatúru.",
|
||||
"setting_image_viewer_preview_title": "Načítať náhľad obrázka",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Oznámenie o zlyhaní zálohovania na pozadí: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Zobraziť celkový priebeh zálohovania na pozadí",
|
||||
"setting_pages_app_bar_settings": "Nastavenia",
|
||||
"settings_require_restart": "Na použitie tohto nastavenia reštartujte Immich",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Pridať",
|
||||
"share_add_photos": "Pridať fotografie",
|
||||
"share_add_title": "Pridať názov",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Naloži originalno sliko",
|
||||
"setting_image_viewer_preview_subtitle": "Omogoči nalaganje slike srednje ločljivosti. Onemogočite neposredno nalaganje izvirnika ali uporabo samo sličice.",
|
||||
"setting_image_viewer_preview_title": "Naloži predogled slike",
|
||||
"setting_image_viewer_title": "Slike",
|
||||
"setting_languages_apply": "Uporabi",
|
||||
"setting_languages_title": "Jeziki",
|
||||
"setting_notifications_notify_failures_grace_period": "Obvesti o napakah varnostnega kopiranja v ozadju: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Prikaži skupni napredek varnostnega kopiranja v ozadju",
|
||||
"setting_pages_app_bar_settings": "Nastavitve",
|
||||
"settings_require_restart": "Znova zaženite Immich, da uporabite to nastavitev",
|
||||
"setting_video_viewer_looping_subtitle": "Omogočite samodejno ponavljanje videoposnetka v pregledovalniku podrobnosti.",
|
||||
"setting_video_viewer_looping_title": "V zanki",
|
||||
"setting_video_viewer_title": "Videoposnetki",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj fotografije",
|
||||
"share_add_title": "Dodaj naslov",
|
||||
"share_assets_selected": "{} izbrano",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Ustvari album",
|
||||
"shared_album_activities_input_disable": "Komentiranje je onemogočeno",
|
||||
"shared_album_activities_input_hint": "Reci kaj",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Učitaj originalnu sliku",
|
||||
"setting_image_viewer_preview_subtitle": "Aktiviraj učitavanje slika u srednjoj rezoluciji. Deaktiviraj da se direktno učitava original, ili da se samo koristi minijatura.",
|
||||
"setting_image_viewer_preview_title": "Pregledaj sliku",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Neuspešne rezervne kopije: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Prikaži ukupan napredak pozadinskog bekapovanja.\n\n",
|
||||
"setting_pages_app_bar_settings": "Opcije",
|
||||
"settings_require_restart": "Restartujte Immich da primenite ovu promenu",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj fotografije",
|
||||
"share_add_title": "Dodaj naslov",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"action_common_back": "Bakåt",
|
||||
"action_common_cancel": "Avbryt",
|
||||
"action_common_clear": "Rensa",
|
||||
"action_common_confirm": "Bekräfta",
|
||||
"action_common_update": "Uppdatera",
|
||||
"add_to_album_bottom_sheet_added": "Tillagd till {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Redan i {album}",
|
||||
"advanced_settings_log_level_title": "Loggnivå: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Vissa enheter är mycket långsamma på att ladda tumnaglar från resurser på enheten. Aktivera den här inställningen för att ladda bilder från servern istället.",
|
||||
"advanced_settings_prefer_remote_title": "Föredra bilder från servern",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Hoppar över SSL-certifikatverifiering för serverändpunkten. Krävs för självsignerade certifikat.",
|
||||
"advanced_settings_self_signed_ssl_title": "Tillåt självsignerade SSL-certifikat",
|
||||
"advanced_settings_tile_subtitle": "Avancerade användarinställningar",
|
||||
"action_common_back": "Back",
|
||||
"action_common_cancel": "Cancel",
|
||||
"action_common_clear": "Clear",
|
||||
"action_common_confirm": "Confirm",
|
||||
"action_common_update": "Update",
|
||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||
"advanced_settings_log_level_title": "Log level: {}",
|
||||
"advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
|
||||
"advanced_settings_prefer_remote_title": "Prefer remote images",
|
||||
"advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
|
||||
"advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
|
||||
"advanced_settings_tile_subtitle": "Advanced user's settings",
|
||||
"advanced_settings_tile_title": "Avancerad",
|
||||
"advanced_settings_troubleshooting_subtitle": "Aktivera funktioner för felsökning",
|
||||
"advanced_settings_troubleshooting_title": "Felsökning",
|
||||
"advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
|
||||
"advanced_settings_troubleshooting_title": "Troubleshooting",
|
||||
"album_info_card_backup_album_excluded": "EXKLUDERAD",
|
||||
"album_info_card_backup_album_included": "INKLUDERAD",
|
||||
"album_thumbnail_card_item": "1 objekt",
|
||||
"album_thumbnail_card_items": "{} objekt",
|
||||
"album_thumbnail_card_shared": " · Delad",
|
||||
"album_thumbnail_owned": "Ägd",
|
||||
"album_thumbnail_card_shared": ". Delad",
|
||||
"album_thumbnail_owned": "Owned",
|
||||
"album_thumbnail_shared_by": "Delat av {}",
|
||||
"album_viewer_appbar_delete_confirm": "Är du säker på att du vill ta bort albumet från ditt konto?",
|
||||
"album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?",
|
||||
"album_viewer_appbar_share_delete": "Radera album",
|
||||
"album_viewer_appbar_share_err_delete": "Kunde inte radera album",
|
||||
"album_viewer_appbar_share_err_leave": "Kunde inte lämna album",
|
||||
@@ -30,27 +30,27 @@
|
||||
"album_viewer_appbar_share_err_title": "Kunde inte ändra albumtitel",
|
||||
"album_viewer_appbar_share_leave": "Lämna album",
|
||||
"album_viewer_appbar_share_remove": "Ta bort från album",
|
||||
"album_viewer_appbar_share_to": "Dela Till",
|
||||
"album_viewer_appbar_share_to": "Share To",
|
||||
"album_viewer_page_share_add_users": "Lägg till användare",
|
||||
"all_people_page_title": "Personer",
|
||||
"all_videos_page_title": "Videor",
|
||||
"app_bar_signout_dialog_content": "Är du säker på att du vill logga ut?",
|
||||
"app_bar_signout_dialog_ok": "Ja",
|
||||
"app_bar_signout_dialog_title": "Logga ut",
|
||||
"archive_page_no_archived_assets": "Inga arkiverade resurser hittade",
|
||||
"all_people_page_title": "People",
|
||||
"all_videos_page_title": "Videos",
|
||||
"app_bar_signout_dialog_content": "Are you sure you want to sign out?",
|
||||
"app_bar_signout_dialog_ok": "Yes",
|
||||
"app_bar_signout_dialog_title": "Sign out",
|
||||
"archive_page_no_archived_assets": "No archived assets found",
|
||||
"archive_page_title": "Arkivera ({})",
|
||||
"asset_action_delete_err_read_only": "Kan inte ta bort skrivskyddade resurser, hoppar över",
|
||||
"asset_action_share_err_offline": "Kan inte hämta offline-resurs(er), hoppar över",
|
||||
"asset_list_group_by_sub_title": "Gruppera på",
|
||||
"asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping",
|
||||
"asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping",
|
||||
"asset_list_group_by_sub_title": "Group by",
|
||||
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
|
||||
"asset_list_layout_settings_group_automatically": "Automatiskt",
|
||||
"asset_list_layout_settings_group_automatically": "Automatic",
|
||||
"asset_list_layout_settings_group_by": "Gruppera bilder efter",
|
||||
"asset_list_layout_settings_group_by_month": "Månad",
|
||||
"asset_list_layout_settings_group_by_month_day": "Månad + dag",
|
||||
"asset_list_layout_sub_title": "Layout",
|
||||
"asset_list_settings_subtitle": "Layoutinställningar för bildrutnät",
|
||||
"asset_list_settings_title": "Bildrutnät",
|
||||
"asset_viewer_settings_title": "Resursvisare",
|
||||
"asset_viewer_settings_title": "Asset Viewer",
|
||||
"backup_album_selection_page_albums_device": "Album på enhet ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tryck en gång för att inkludera, tryck två gånger för att exkludera",
|
||||
"backup_album_selection_page_assets_scatter": "Objekt kan vara utspridda över flera album. Därför kan album inkluderas eller exkluderas under säkerhetskopieringsprocessen",
|
||||
@@ -66,8 +66,8 @@
|
||||
"backup_background_service_in_progress_notification": "Säkerhetskopierar dina foton och videor...",
|
||||
"backup_background_service_upload_failure_notification": "Kunde inte ladda upp {}",
|
||||
"backup_controller_page_albums": "Säkerhetskopiera album",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Aktivera uppdatering i bakgrunden i Inställningar > Allmänt > Uppdatering I Bakgrunden för att använda säkerhetskopiering i bakgrunden.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Uppdatering i bakgrunden är avaktiverat",
|
||||
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
|
||||
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
|
||||
"backup_controller_page_background_app_refresh_enable_button_text": "Gå till inställningar",
|
||||
"backup_controller_page_background_battery_info_link": "Visa mig hur",
|
||||
"backup_controller_page_background_battery_info_message": "För optimal säkerhetskopiering i bakgrunden bör du stänga av batterioptimering som begränsar bakgrundsaktivitet för Immich.\n\nEftersom detta är enhetsspecifikt så bör du söka instruktioner från din enhetstillverkare.",
|
||||
@@ -110,18 +110,18 @@
|
||||
"backup_controller_page_uploading_file_info": "Laddar upp filinformation",
|
||||
"backup_err_only_album": "Kan inte ta bort det enda albumet",
|
||||
"backup_info_card_assets": "objekt",
|
||||
"backup_manual_cancelled": "Avbrutet",
|
||||
"backup_manual_failed": "Misslyckades",
|
||||
"backup_manual_in_progress": "Uppladdning pågår redan. Försök igen om en liten stund",
|
||||
"backup_manual_success": "Klart",
|
||||
"backup_manual_title": "Uppladdningsstatus",
|
||||
"backup_options_page_title": "Säkerhetskopieringsinställningar",
|
||||
"backup_manual_cancelled": "Cancelled",
|
||||
"backup_manual_failed": "Failed",
|
||||
"backup_manual_in_progress": "Upload already in progress. Try after sometime",
|
||||
"backup_manual_success": "Success",
|
||||
"backup_manual_title": "Upload status",
|
||||
"backup_options_page_title": "Backup options",
|
||||
"cache_settings_album_thumbnails": "Miniatyrbilder för bibliotek ({} bilder och videor)",
|
||||
"cache_settings_clear_cache_button": "Rensa cacheminnet",
|
||||
"cache_settings_clear_cache_button_title": "Rensar appens cacheminne. Detta kommer att avsevärt påverka appens prestanda tills cachen har byggts om.",
|
||||
"cache_settings_duplicated_assets_clear_button": "RENSA",
|
||||
"cache_settings_duplicated_assets_subtitle": "Foton och videor som är svartlistade av appen",
|
||||
"cache_settings_duplicated_assets_title": "Duplicerade Resurser ({})",
|
||||
"cache_settings_duplicated_assets_clear_button": "CLEAR",
|
||||
"cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
|
||||
"cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
|
||||
"cache_settings_image_cache_size": "Cacheminnets storlek ({} bilder och videor)",
|
||||
"cache_settings_statistics_album": "Miniatyrbilder för bibliotek",
|
||||
"cache_settings_statistics_assets": "{} bilder och videor ({})",
|
||||
@@ -131,37 +131,37 @@
|
||||
"cache_settings_statistics_title": "Cacheförbrukning",
|
||||
"cache_settings_subtitle": "Hantera cachebeteendet för Immich-appen.",
|
||||
"cache_settings_thumbnail_size": "Storlek på cacheminnet ({} bilder och videor)",
|
||||
"cache_settings_tile_subtitle": "Kontrollera beteende för lokal lagring",
|
||||
"cache_settings_tile_title": "Lokal Lagring",
|
||||
"cache_settings_tile_subtitle": "Control the local storage behaviour",
|
||||
"cache_settings_tile_title": "Local Storage",
|
||||
"cache_settings_title": "Cache Inställningar",
|
||||
"change_password_form_confirm_password": "Bekräfta lösenord",
|
||||
"change_password_form_description": "Hej {name},\n\nDet är antingen första gången du loggar in i systemet, eller så har det skett en förfrågan om återställning av ditt lösenord. Ange ditt nya lösenord nedan.",
|
||||
"change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
|
||||
"change_password_form_new_password": "Nytt lösenord",
|
||||
"change_password_form_password_mismatch": "Lösenorden matchar inte",
|
||||
"change_password_form_reenter_new_password": "Ange Nytt Lösenord Igen",
|
||||
"common_add_to_album": "Lägg till, till album",
|
||||
"change_password_form_password_mismatch": "Passwords do not match",
|
||||
"change_password_form_reenter_new_password": "Re-enter New Password",
|
||||
"common_add_to_album": "Add to album",
|
||||
"common_change_password": "Ändra lösenord",
|
||||
"common_create_new_album": "Skapa ett nytt album",
|
||||
"common_server_error": "Kontrollera din nätverksanslutning, se till att servern går att nå och att app- och server-versioner är kompatibla.",
|
||||
"common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
|
||||
"common_shared": "Delad",
|
||||
"control_bottom_app_bar_add_to_album": "Lägg till i album",
|
||||
"control_bottom_app_bar_album_info": "{} objekt",
|
||||
"control_bottom_app_bar_album_info_shared": "{} objekt • Delat",
|
||||
"control_bottom_app_bar_archive": "Arkivera",
|
||||
"control_bottom_app_bar_archive": "Archive",
|
||||
"control_bottom_app_bar_create_new_album": "Skapa nytt album",
|
||||
"control_bottom_app_bar_delete": "Radera",
|
||||
"control_bottom_app_bar_delete_from_immich": "Ta bort från Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Ta bort från enhet",
|
||||
"control_bottom_app_bar_edit_location": "Redigera plats",
|
||||
"control_bottom_app_bar_edit_time": "Redigera Datum & Tid",
|
||||
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
|
||||
"control_bottom_app_bar_delete_from_local": "Delete from device",
|
||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
||||
"control_bottom_app_bar_favorite": "Favorit",
|
||||
"control_bottom_app_bar_share": "Dela",
|
||||
"control_bottom_app_bar_share_to": "Dela Till",
|
||||
"control_bottom_app_bar_stack": "Stapel",
|
||||
"control_bottom_app_bar_trash_from_immich": "Flytta till Papperskorgen",
|
||||
"control_bottom_app_bar_unarchive": "Avarkivera",
|
||||
"control_bottom_app_bar_unfavorite": "Avfavorisera",
|
||||
"control_bottom_app_bar_upload": "Ladda Upp",
|
||||
"control_bottom_app_bar_share_to": "Share To",
|
||||
"control_bottom_app_bar_stack": "Stack",
|
||||
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
|
||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||
"control_bottom_app_bar_unfavorite": "Unfavorite",
|
||||
"control_bottom_app_bar_upload": "Upload",
|
||||
"create_album_page_untitled": "Namnlös",
|
||||
"create_shared_album_page_create": "Skapa",
|
||||
"create_shared_album_page_share": "Dela",
|
||||
@@ -173,76 +173,76 @@
|
||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||
"date_format": "E d. LLL y • hh:mm",
|
||||
"delete_dialog_alert": "Dessa objekt kommer att raderas permanent från Immich och din enhet",
|
||||
"delete_dialog_alert_local": "Dessa saker kommer att tas bort från din enhet men fortsatt vara tillgängliga på Immich-servern",
|
||||
"delete_dialog_alert_local_non_backed_up": "Några av sakerna har inte säkerhetskopierats till Immich och kommer att tas bort permanent från din enhet.",
|
||||
"delete_dialog_alert_remote": "Dessa saker kommer att tas bort permanent från Immich-servern",
|
||||
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
|
||||
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
|
||||
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
|
||||
"delete_dialog_cancel": "Avbryt",
|
||||
"delete_dialog_ok": "Radera",
|
||||
"delete_dialog_ok_force": "Ta Bort Ändå",
|
||||
"delete_dialog_ok_force": "Delete Anyway",
|
||||
"delete_dialog_title": "Radera permanent",
|
||||
"delete_local_dialog_ok_backed_up_only": "Ta Bara Bort Säkerhetskopierade",
|
||||
"delete_local_dialog_ok_force": "Ta Bort Ändå",
|
||||
"delete_shared_link_dialog_content": "Är du säker på att du vill ta bort den här delade länken?",
|
||||
"delete_shared_link_dialog_title": "Ta Bort Delad Länk",
|
||||
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
|
||||
"delete_local_dialog_ok_force": "Delete Anyway",
|
||||
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
|
||||
"delete_shared_link_dialog_title": "Delete Shared Link",
|
||||
"description_input_hint_text": "Lägg till beskrivning...",
|
||||
"description_input_submit_error": "Fel vid uppdatering av beskrivning, se loggen för fler detaljer",
|
||||
"edit_date_time_dialog_date_time": "Datum och Tid",
|
||||
"edit_date_time_dialog_timezone": "Tidszon",
|
||||
"edit_location_dialog_title": "Plats",
|
||||
"description_input_submit_error": "Error updating description, check the log for more details",
|
||||
"edit_date_time_dialog_date_time": "Date and Time",
|
||||
"edit_date_time_dialog_timezone": "Timezone",
|
||||
"edit_location_dialog_title": "Location",
|
||||
"exif_bottom_sheet_description": "Lägg till beskrivning...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "PLATS",
|
||||
"exif_bottom_sheet_location_add": "Lägg till plats",
|
||||
"exif_bottom_sheet_people": "PERSONER",
|
||||
"exif_bottom_sheet_person_add_person": "Lägg till namn",
|
||||
"exif_bottom_sheet_location_add": "Add a location",
|
||||
"exif_bottom_sheet_people": "PEOPLE",
|
||||
"exif_bottom_sheet_person_add_person": "Add name",
|
||||
"experimental_settings_new_asset_list_subtitle": "Under uppbyggnad",
|
||||
"experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät",
|
||||
"experimental_settings_subtitle": "Använd på egen risk!",
|
||||
"experimental_settings_title": "Experimentellt",
|
||||
"favorites_page_no_favorites": "Inga favoritresurser hittades",
|
||||
"favorites_page_no_favorites": "No favorite assets found",
|
||||
"favorites_page_title": "Favoriter",
|
||||
"haptic_feedback_switch": "Aktivera haptisk feedback",
|
||||
"haptic_feedback_title": "Haptisk Feedback",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "Lade till {added} foton och videor i albumet {album}. {failed} foton och videor finns redan i albumet.",
|
||||
"home_page_add_to_album_err_local": "Kan inte lägga till lokala resurser till album ännu, hoppar över",
|
||||
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
|
||||
"home_page_add_to_album_success": "Lade till {added} foton och videor i albumet {album}.",
|
||||
"home_page_album_err_partner": "Kan inte lägga till partner-resurser till album ännu, hoppar över",
|
||||
"home_page_archive_err_local": "Kan inte arkivera lokala resurser ännu, hoppar över",
|
||||
"home_page_archive_err_partner": "Kan inte arkivera partner-resurs, hoppar över",
|
||||
"home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
|
||||
"home_page_archive_err_local": "Can not archive local assets yet, skipping",
|
||||
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
|
||||
"home_page_building_timeline": "Bygger tidslinjen",
|
||||
"home_page_delete_err_partner": "Kan inte ta bort partner-resurs, hoppar över",
|
||||
"home_page_delete_remote_err_local": "Lokala resurser i urvalet för att ta bort från servern, hoppar över",
|
||||
"home_page_favorite_err_local": "Kan inte favorisera lokala resurser ännu, hoppar över",
|
||||
"home_page_favorite_err_partner": "Kan inte favorisera partner-resurser ännu, hoppar över",
|
||||
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
|
||||
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
|
||||
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
|
||||
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
|
||||
"home_page_first_time_notice": "Om det här är första gången du använder appen, välj ett eller flera backup-album så att tidslinjen kan fyllas med foton och videor från albumen.",
|
||||
"home_page_share_err_local": "Kan inte dela lokal resurs via länk, hoppar över",
|
||||
"home_page_upload_err_limit": "Kan bara ladda upp max 30 resurser åt gången, hoppar över",
|
||||
"image_viewer_page_state_provider_download_error": "Fel Vid Nedladdning",
|
||||
"image_viewer_page_state_provider_download_started": "Nedladdning Påbörjad",
|
||||
"image_viewer_page_state_provider_download_success": "Nedladdningen Lyckades",
|
||||
"image_viewer_page_state_provider_share_error": "Delningsfel",
|
||||
"home_page_share_err_local": "Can not share local assets via link, skipping",
|
||||
"home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
|
||||
"image_viewer_page_state_provider_download_error": "Download Error",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_archive": "Arkiv",
|
||||
"library_page_device_albums": "Album på Enheten",
|
||||
"library_page_device_albums": "Albums on Device",
|
||||
"library_page_favorites": "Favoriter",
|
||||
"library_page_new_album": "Nytt album",
|
||||
"library_page_sharing": "Delas",
|
||||
"library_page_sort_asset_count": "Antal resurser",
|
||||
"library_page_sort_asset_count": "Number of assets",
|
||||
"library_page_sort_created": "Senast skapad",
|
||||
"library_page_sort_last_modified": "Senast ändrad",
|
||||
"library_page_sort_most_oldest_photo": "Äldsta foto",
|
||||
"library_page_sort_most_recent_photo": "Senaste foto",
|
||||
"library_page_sort_last_modified": "Last modified",
|
||||
"library_page_sort_most_oldest_photo": "Oldest photo",
|
||||
"library_page_sort_most_recent_photo": "Most recent photo",
|
||||
"library_page_sort_title": "Albumtitel",
|
||||
"location_picker_choose_on_map": "Välj på karta",
|
||||
"location_picker_latitude": "Latitud",
|
||||
"location_picker_latitude_error": "Ange en giltig latitud",
|
||||
"location_picker_latitude_hint": "Ange din latitud här",
|
||||
"location_picker_longitude": "Longitud",
|
||||
"location_picker_longitude_error": "Ange en giltig longitud",
|
||||
"location_picker_longitude_hint": "Ange din longitud här",
|
||||
"login_disabled": "Inloggning har inaktiverats",
|
||||
"login_form_api_exception": "API-undantag. Kontrollera server-URL:en och försök igen.",
|
||||
"login_form_back_button_text": "Bakåt",
|
||||
"location_picker_choose_on_map": "Choose on map",
|
||||
"location_picker_latitude": "Latitude",
|
||||
"location_picker_latitude_error": "Enter a valid latitude",
|
||||
"location_picker_latitude_hint": "Enter your latitude here",
|
||||
"location_picker_longitude": "Longitude",
|
||||
"location_picker_longitude_error": "Enter a valid longitude",
|
||||
"location_picker_longitude_hint": "Enter your longitude here",
|
||||
"login_disabled": "Login has been disabled",
|
||||
"login_form_api_exception": "API exception. Please check the server URL and try again.",
|
||||
"login_form_back_button_text": "Back",
|
||||
"login_form_button_text": "Logga in",
|
||||
"login_form_email_hint": "din.email@email.com",
|
||||
"login_form_endpoint_hint": "http://din-server-ip:port/api",
|
||||
@@ -255,145 +255,144 @@
|
||||
"login_form_failed_get_oauth_server_config": "Kunde inte logga in med OAuth. Kontrollera serverns webbadress",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth är inte tillgänglig på den här servern",
|
||||
"login_form_failed_login": "Kunde inte logga in. Kontrollera serverns webbadress, email och lösenord.",
|
||||
"login_form_handshake_exception": "Ett Undantag vid Handskakning med servern har skett. Aktivera stöd för självsignerade certifikat i inställningar om du använder ett självsignerat certifikat.",
|
||||
"login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Lösenord",
|
||||
"login_form_next_button": "Nästa",
|
||||
"login_form_password_hint": "lösenord",
|
||||
"login_form_save_login": "Håll mig inloggad",
|
||||
"login_form_server_empty": "Ange en server-URL.",
|
||||
"login_form_server_empty": "Enter a server URL.",
|
||||
"login_form_server_error": "Kunde inte ansluta till servern",
|
||||
"login_password_changed_error": "Ett fel uppstod vid uppdatering av ditt lösenord",
|
||||
"login_password_changed_success": "Uppdatering av lösenord lyckades",
|
||||
"map_assets_in_bound": "{} foto",
|
||||
"map_assets_in_bounds": "{} foton",
|
||||
"map_cannot_get_user_location": "Kan inte hämta användarens plats",
|
||||
"map_location_dialog_cancel": "Avbryt",
|
||||
"map_location_dialog_yes": "Ja",
|
||||
"map_location_picker_page_use_location": "Använd den här platsen",
|
||||
"map_location_service_disabled_content": "Platstjänst måste vara aktiverad för att visa resurser från din nuvarande plats. Vill du aktivera den nu?",
|
||||
"map_location_service_disabled_title": "Platstjänst inaktiverad",
|
||||
"map_no_assets_in_bounds": "Inga foton i området",
|
||||
"map_no_location_permission_content": "Platsrättighet är nödvändigt för att kunna visa resurser från din nuvarande plats. Vill du tillåta det nu?",
|
||||
"map_no_location_permission_title": "Platsrättighet nekad",
|
||||
"map_settings_dark_mode": "Mörkt tema",
|
||||
"map_settings_date_range_option_all": "Alla",
|
||||
"map_settings_date_range_option_day": "Senaste 24 timmarna",
|
||||
"map_settings_date_range_option_days": "Senaste {} dagarna",
|
||||
"map_settings_date_range_option_year": "Senaste året",
|
||||
"map_settings_date_range_option_years": "Senaste {} åren",
|
||||
"map_settings_dialog_cancel": "Avbryt",
|
||||
"map_settings_dialog_save": "Spara",
|
||||
"map_settings_dialog_title": "Kartinställningar",
|
||||
"map_settings_include_show_archived": "Inkludera Arkiverade",
|
||||
"map_settings_include_show_partners": "Inkludera Partners",
|
||||
"map_settings_only_relative_range": "Datumintervall",
|
||||
"map_settings_only_show_favorites": "Visa Endast Favoriter",
|
||||
"map_settings_theme_settings": "Kart-tema",
|
||||
"map_zoom_to_see_photos": "Zooma ut för att se foton",
|
||||
"memories_all_caught_up": "Du är ikapp",
|
||||
"memories_check_back_tomorrow": "Kom tillbaka imorgon för fler minnen",
|
||||
"memories_start_over": "Börja Om",
|
||||
"memories_swipe_to_close": "Svep upp för att stänga",
|
||||
"login_password_changed_error": "There was an error updating your password",
|
||||
"login_password_changed_success": "Password updated successfully",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "Cannot get user's location",
|
||||
"map_location_dialog_cancel": "Cancel",
|
||||
"map_location_dialog_yes": "Yes",
|
||||
"map_location_picker_page_use_location": "Use this location",
|
||||
"map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
|
||||
"map_location_service_disabled_title": "Location Service disabled",
|
||||
"map_no_assets_in_bounds": "No photos in this area",
|
||||
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
|
||||
"map_no_location_permission_title": "Location Permission denied",
|
||||
"map_settings_dark_mode": "Dark mode",
|
||||
"map_settings_date_range_option_all": "All",
|
||||
"map_settings_date_range_option_day": "Past 24 hours",
|
||||
"map_settings_date_range_option_days": "Past {} days",
|
||||
"map_settings_date_range_option_year": "Past year",
|
||||
"map_settings_date_range_option_years": "Past {} years",
|
||||
"map_settings_dialog_cancel": "Cancel",
|
||||
"map_settings_dialog_save": "Save",
|
||||
"map_settings_dialog_title": "Map Settings",
|
||||
"map_settings_include_show_archived": "Include Archived",
|
||||
"map_settings_include_show_partners": "Include Partners",
|
||||
"map_settings_only_relative_range": "Date range",
|
||||
"map_settings_only_show_favorites": "Show Favorite Only",
|
||||
"map_settings_theme_settings": "Map Theme",
|
||||
"map_zoom_to_see_photos": "Zoom out to see photos",
|
||||
"memories_all_caught_up": "All caught up",
|
||||
"memories_check_back_tomorrow": "Check back tomorrow for more memories",
|
||||
"memories_start_over": "Start Over",
|
||||
"memories_swipe_to_close": "Swipe up to close",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "Rörelsefoton",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Kan inte ändra datum på skrivskyddade resurser, hoppar över",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Kan inte ändra plats på skrivskyddade resurser, hoppar över",
|
||||
"no_assets_to_show": "Inga resurser att visa",
|
||||
"motion_photos_page_title": "Motion Photos",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "Avbryt",
|
||||
"notification_permission_dialog_content": "För att aktivera notiser, gå till Inställningar och välj tillåt",
|
||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||
"notification_permission_dialog_settings": "Inställningar",
|
||||
"notification_permission_list_tile_content": "Tillåt rättighet för att slå på notiser.",
|
||||
"notification_permission_list_tile_enable_button": "Aktivera Notiser",
|
||||
"notification_permission_list_tile_title": "Notisrättighet",
|
||||
"partner_list_user_photos": "{user}s foton",
|
||||
"partner_list_view_all": "Visa alla",
|
||||
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
|
||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
||||
"notification_permission_list_tile_title": "Notification Permission",
|
||||
"partner_list_user_photos": "{user}'s photos",
|
||||
"partner_list_view_all": "View all",
|
||||
"partner_page_add_partner": "Lägg till partner",
|
||||
"partner_page_empty_message": "Dina foton är inte ännu delade med någon partner.",
|
||||
"partner_page_no_more_users": "Inga fler användare att lägga till",
|
||||
"partner_page_partner_add_failed": "Misslyckades med att lägga till partner",
|
||||
"partner_page_select_partner": "Välj partner",
|
||||
"partner_page_shared_to_title": "Delad till",
|
||||
"partner_page_stop_sharing_content": "{} kommer inte längre att komma åt dina foton.",
|
||||
"partner_page_empty_message": "Your photos are not yet shared with any partner.",
|
||||
"partner_page_no_more_users": "No more users to add",
|
||||
"partner_page_partner_add_failed": "Failed to add partner",
|
||||
"partner_page_select_partner": "Select partner",
|
||||
"partner_page_shared_to_title": "Shared to",
|
||||
"partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
|
||||
"partner_page_stop_sharing_title": "Sluta dela dina foton?",
|
||||
"partner_page_title": "Partner",
|
||||
"permission_onboarding_back": "Bakåt",
|
||||
"permission_onboarding_continue_anyway": "Fortsätt ändå",
|
||||
"permission_onboarding_back": "Back",
|
||||
"permission_onboarding_continue_anyway": "Continue anyway",
|
||||
"permission_onboarding_get_started": "Kom igång",
|
||||
"permission_onboarding_go_to_settings": "Gå till inställningar",
|
||||
"permission_onboarding_grant_permission": "Tillåt",
|
||||
"permission_onboarding_log_out": "Logga ut",
|
||||
"permission_onboarding_permission_denied": "Rättighet nekad. För att använda Immich, tillåt foto- och video-rättigheter i Inställningar.",
|
||||
"permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.",
|
||||
"permission_onboarding_permission_limited": "Rättighet begränsad. För att låta Immich säkerhetskopiera och hantera hela ditt galleri, tillåt foto- och video-rättigheter i Inställningar.",
|
||||
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
|
||||
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
|
||||
"permission_onboarding_request": "Immich kräver tillstånd för att se dina foton och videor.",
|
||||
"preferences_settings_title": "Inställningar",
|
||||
"preferences_settings_title": "Preferences",
|
||||
"profile_drawer_app_logs": "Loggar",
|
||||
"profile_drawer_client_out_of_date_major": "Mobilappen är utdaterad. Uppdatera till senaste huvudversionen.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobilappen är utdaterad. Uppdatera till senaste mindre versionen.",
|
||||
"profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade",
|
||||
"profile_drawer_documentation": "Dokumentation",
|
||||
"profile_drawer_documentation": "Documentation",
|
||||
"profile_drawer_github": "GitHub",
|
||||
"profile_drawer_server_out_of_date_major": "Servern är utdaterad. Uppdatera till senaste huvudversionen.",
|
||||
"profile_drawer_server_out_of_date_minor": "Servern är utdaterad. Uppdatera till senaste mindre versionen.",
|
||||
"profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
|
||||
"profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
|
||||
"profile_drawer_settings": "Inställningar",
|
||||
"profile_drawer_sign_out": "Logga ut",
|
||||
"profile_drawer_trash": "Papperskorg",
|
||||
"profile_drawer_trash": "Trash",
|
||||
"recently_added_page_title": "Nyligen tillagda",
|
||||
"scaffold_body_error_occurred": "Fel uppstod",
|
||||
"scaffold_body_error_occurred": "Error occurred",
|
||||
"search_bar_hint": "Sök bland dina foton",
|
||||
"search_filter_apply": "Aktivera filter",
|
||||
"search_filter_camera_make": "Tillverkare",
|
||||
"search_filter_camera_model": "Modell",
|
||||
"search_filter_display_option_archive": "Arkiv",
|
||||
"search_filter_display_option_favorite": "Favorit",
|
||||
"search_filter_display_option_not_in_album": "Ej i album",
|
||||
"search_filter_location_city": "Stad",
|
||||
"search_filter_location_country": "Land",
|
||||
"search_filter_location_state": "Stat",
|
||||
"search_filter_media_type_all": "Alla",
|
||||
"search_filter_media_type_image": "Bild",
|
||||
"search_filter_media_type_video": "Videor",
|
||||
"search_filter_apply": "Apply filter",
|
||||
"search_filter_camera_make": "Make",
|
||||
"search_filter_camera_model": "Model",
|
||||
"search_filter_display_option_archive": "Archive",
|
||||
"search_filter_display_option_favorite": "Favorite",
|
||||
"search_filter_display_option_not_in_album": "Not in album",
|
||||
"search_filter_location_city": "City",
|
||||
"search_filter_location_country": "Country",
|
||||
"search_filter_location_state": "State",
|
||||
"search_filter_media_type_all": "All",
|
||||
"search_filter_media_type_image": "Image",
|
||||
"search_filter_media_type_video": "Video",
|
||||
"search_page_categories": "Kategorier",
|
||||
"search_page_favorites": "Favoriter",
|
||||
"search_page_motion_photos": "Rörelsefoton",
|
||||
"search_page_motion_photos": "Motion Photos",
|
||||
"search_page_no_objects": "Inga objekt är tillgängliga",
|
||||
"search_page_no_places": "Ingen platsinformation finns tillgänglig",
|
||||
"search_page_people": "Personer",
|
||||
"search_page_person_add_name_dialog_cancel": "Avbryt",
|
||||
"search_page_person_add_name_dialog_hint": "Namn",
|
||||
"search_page_person_add_name_dialog_save": "Spara",
|
||||
"search_page_person_add_name_dialog_title": "Lägg till ett namn",
|
||||
"search_page_person_add_name_subtitle": "Hitta dem snabbt på namn med sök",
|
||||
"search_page_person_add_name_title": "Lägg till ett namn",
|
||||
"search_page_person_edit_name": "Redigera namn",
|
||||
"search_page_people": "People",
|
||||
"search_page_person_add_name_dialog_cancel": "Cancel",
|
||||
"search_page_person_add_name_dialog_hint": "Name",
|
||||
"search_page_person_add_name_dialog_save": "Save",
|
||||
"search_page_person_add_name_dialog_title": "Add a name",
|
||||
"search_page_person_add_name_subtitle": "Find them fast by name with search",
|
||||
"search_page_person_add_name_title": "Add a name",
|
||||
"search_page_person_edit_name": "Edit name",
|
||||
"search_page_places": "Platser",
|
||||
"search_page_recently_added": "Nyligen tillagda",
|
||||
"search_page_screenshots": "Skärmdumpar",
|
||||
"search_page_screenshots": "Screenshots",
|
||||
"search_page_selfies": "Selfies",
|
||||
"search_page_things": "Saker",
|
||||
"search_page_videos": "Videor",
|
||||
"search_page_videos": "Videos",
|
||||
"search_page_view_all_button": "Visa alla",
|
||||
"search_page_your_activity": "Dina aktiviteter",
|
||||
"search_page_your_map": "Din Karta",
|
||||
"search_page_your_map": "Your Map",
|
||||
"search_result_page_new_search_hint": "Ny sökning",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smartsök är aktiverat som standard, för att söka efter metadata, använd syntaxen",
|
||||
"search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
|
||||
"search_suggestion_list_smart_search_hint_2": "m:ditt-sökord",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Förslag",
|
||||
"select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Förslag",
|
||||
"server_info_box_app_version": "App version",
|
||||
"server_info_box_latest_release": "Senaste Version",
|
||||
"server_info_box_server_url": "Server-URL",
|
||||
"server_info_box_latest_release": "Latest Version",
|
||||
"server_info_box_server_url": "Server URL",
|
||||
"server_info_box_server_version": "Server version",
|
||||
"setting_image_viewer_help": "Detaljerad vy laddar miniatyrer först. Efter detta laddas den medelstora förhandsgranskningen av bilden (om detta är aktiverat), och visar slutligen originalet (om detta är aktiverat).",
|
||||
"setting_image_viewer_original_subtitle": "Aktivera för att ladda originalbilden i full storlek (stor!). Inaktivera för att minska dataanvändningen (både i nätverket och för enhetscache).",
|
||||
"setting_image_viewer_original_title": "Ladda originalbilden",
|
||||
"setting_image_viewer_preview_subtitle": "Aktivera för att ladda en mellanstor bild. Stäng av för att antingen ladda originalet direkt eller bara använda miniatyrbilden.",
|
||||
"setting_image_viewer_preview_title": "Ladda förhandsgranskning av bild",
|
||||
"setting_image_viewer_title": "Bilder",
|
||||
"setting_languages_apply": "Verkställ",
|
||||
"setting_languages_title": "Språk",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Rapportera säkerhetskopieringsfel i bakgrunden: {}",
|
||||
"setting_notifications_notify_hours": "{} timmar",
|
||||
"setting_notifications_notify_immediately": "omedelbart",
|
||||
@@ -408,78 +407,75 @@
|
||||
"setting_notifications_total_progress_title": "Visa totalt uppladdningsförlopp",
|
||||
"setting_pages_app_bar_settings": "Inställningar",
|
||||
"settings_require_restart": "Starta om Immich för att tillämpa den här inställningen",
|
||||
"setting_video_viewer_looping_subtitle": "Aktivera för att automatiskt loopa en video i detaljvisaren.",
|
||||
"setting_video_viewer_looping_title": "Loopar",
|
||||
"setting_video_viewer_title": "Videor",
|
||||
"share_add": "Lägg till",
|
||||
"share_add_photos": "Lägg till foton",
|
||||
"share_add_title": "Lägg till en titel",
|
||||
"share_assets_selected": "{} valda",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Skapa album",
|
||||
"shared_album_activities_input_disable": "Kommentar är inaktiverad",
|
||||
"shared_album_activities_input_hint": "Säg något",
|
||||
"shared_album_activity_remove_content": "Vill du ta bort den här aktiviteten?",
|
||||
"shared_album_activity_remove_title": "Ta Bort Aktivitet",
|
||||
"shared_album_activity_setting_subtitle": "Låt andra svara",
|
||||
"shared_album_activity_setting_title": "Kommentarer & gillamarkeringar",
|
||||
"shared_album_section_people_action_error": "Fel vid lämnande/borttagning från album",
|
||||
"shared_album_section_people_action_leave": "Ta bort användare från album",
|
||||
"shared_album_section_people_action_remove_user": "Ta bort användare från album",
|
||||
"shared_album_section_people_owner_label": "Ägare",
|
||||
"shared_album_section_people_title": "PERSONER",
|
||||
"shared_album_activities_input_disable": "Comment is disabled",
|
||||
"shared_album_activities_input_hint": "Say something",
|
||||
"shared_album_activity_remove_content": "Do you want to delete this activity?",
|
||||
"shared_album_activity_remove_title": "Delete Activity",
|
||||
"shared_album_activity_setting_subtitle": "Let others respond",
|
||||
"shared_album_activity_setting_title": "Comments & likes",
|
||||
"shared_album_section_people_action_error": "Error leaving/removing from album",
|
||||
"shared_album_section_people_action_leave": "Remove user from album",
|
||||
"shared_album_section_people_action_remove_user": "Remove user from album",
|
||||
"shared_album_section_people_owner_label": "Owner",
|
||||
"shared_album_section_people_title": "PEOPLE",
|
||||
"share_dialog_preparing": "Förbereder...",
|
||||
"shared_link_app_bar_title": "Delade Länkar",
|
||||
"shared_link_clipboard_copied_massage": "Kopierad till urklipp",
|
||||
"shared_link_clipboard_text": "Länk: {}\nLösenord: {}",
|
||||
"shared_link_create_app_bar_title": "Skapa länk att dela",
|
||||
"shared_link_create_error": "Fel vid skapande av delad länk",
|
||||
"shared_link_create_info": "Tillåt alla med länken att se valda foton",
|
||||
"shared_link_create_submit_button": "Skapa länk",
|
||||
"shared_link_edit_allow_download": "Tillåt publik användare att ladda ner",
|
||||
"shared_link_edit_allow_upload": "Tillåt publik användare att ladda upp",
|
||||
"shared_link_edit_app_bar_title": "Redigera länk",
|
||||
"shared_link_edit_change_expiry": "Ändra utgångstid",
|
||||
"shared_link_edit_description": "Beskrivning",
|
||||
"shared_link_edit_description_hint": "Lägg till delnings-beskrivningen",
|
||||
"shared_link_edit_expire_after": "Går ut efter",
|
||||
"shared_link_edit_expire_after_option_day": "1 dag",
|
||||
"shared_link_edit_expire_after_option_days": "{} dagar",
|
||||
"shared_link_edit_expire_after_option_hour": "1 timme",
|
||||
"shared_link_edit_expire_after_option_hours": "{} timmar",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minut",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minuter",
|
||||
"shared_link_edit_expire_after_option_months": "{} månader",
|
||||
"shared_link_edit_expire_after_option_never": "Aldrig",
|
||||
"shared_link_edit_expire_after_option_year": "{} år",
|
||||
"shared_link_edit_password": "Lösenord",
|
||||
"shared_link_edit_password_hint": "Ange delningslösenordet",
|
||||
"shared_link_edit_show_meta": "Visa metadata",
|
||||
"shared_link_edit_submit_button": "Uppdatera länk",
|
||||
"shared_link_empty": "Du har inga delade länkar",
|
||||
"shared_link_error_server_url_fetch": "Kan inte hämta server-urlen",
|
||||
"shared_link_expired": "Gått ut",
|
||||
"shared_link_expires_day": "Går ut om {} dag",
|
||||
"shared_link_expires_days": "Går ut om {} dagar",
|
||||
"shared_link_expires_hour": "Går ut om {} timme",
|
||||
"shared_link_expires_hours": "Går ut om {} timmar",
|
||||
"shared_link_expires_minute": "Går ut om {} minut",
|
||||
"shared_link_expires_minutes": "Går ut om {} minuter",
|
||||
"shared_link_expires_never": "Går ut ∞",
|
||||
"shared_link_expires_second": "Går ut om {} sekunder",
|
||||
"shared_link_expires_seconds": "Går ut om {} sekunder",
|
||||
"shared_link_individual_shared": "Individdelad",
|
||||
"shared_link_info_chip_download": "Ladda ner",
|
||||
"shared_link_app_bar_title": "Shared Links",
|
||||
"shared_link_clipboard_copied_massage": "Copied to clipboard",
|
||||
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
|
||||
"shared_link_create_app_bar_title": "Create link to share",
|
||||
"shared_link_create_error": "Error while creating shared link",
|
||||
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",
|
||||
"shared_link_create_submit_button": "Create link",
|
||||
"shared_link_edit_allow_download": "Allow public user to download",
|
||||
"shared_link_edit_allow_upload": "Allow public user to upload",
|
||||
"shared_link_edit_app_bar_title": "Edit link",
|
||||
"shared_link_edit_change_expiry": "Change expiration time",
|
||||
"shared_link_edit_description": "Description",
|
||||
"shared_link_edit_description_hint": "Enter the share description",
|
||||
"shared_link_edit_expire_after": "Expire after",
|
||||
"shared_link_edit_expire_after_option_day": "1 day",
|
||||
"shared_link_edit_expire_after_option_days": "{} days",
|
||||
"shared_link_edit_expire_after_option_hour": "1 hour",
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "Never",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "Password",
|
||||
"shared_link_edit_password_hint": "Enter the share password",
|
||||
"shared_link_edit_show_meta": "Show metadata",
|
||||
"shared_link_edit_submit_button": "Update link",
|
||||
"shared_link_empty": "You don't have any shared links",
|
||||
"shared_link_error_server_url_fetch": "Cannot fetch the server url",
|
||||
"shared_link_expired": "Expired",
|
||||
"shared_link_expires_day": "Expires in {} day",
|
||||
"shared_link_expires_days": "Expires in {} days",
|
||||
"shared_link_expires_hour": "Expires in {} hour",
|
||||
"shared_link_expires_hours": "Expires in {} hours",
|
||||
"shared_link_expires_minute": "Expires in {} minute",
|
||||
"shared_link_expires_minutes": "Expires in {} minutes",
|
||||
"shared_link_expires_never": "Expires ∞",
|
||||
"shared_link_expires_second": "Expires in {} second",
|
||||
"shared_link_expires_seconds": "Expires in {} seconds",
|
||||
"shared_link_individual_shared": "Individual shared",
|
||||
"shared_link_info_chip_download": "Download",
|
||||
"shared_link_info_chip_metadata": "EXIF",
|
||||
"shared_link_info_chip_upload": "Ladda upp",
|
||||
"shared_link_manage_links": "Hantera Delade länkar",
|
||||
"shared_link_public_album": "Publikt album",
|
||||
"share_done": "Klart",
|
||||
"shared_link_info_chip_upload": "Upload",
|
||||
"shared_link_manage_links": "Manage Shared links",
|
||||
"shared_link_public_album": "Public album",
|
||||
"share_done": "Done",
|
||||
"share_invite": "Bjuder in till album",
|
||||
"sharing_page_album": "Delade album",
|
||||
"sharing_page_description": "Skapa delade album för att dela foton och video med personer i ditt nätverk.",
|
||||
"sharing_page_empty_list": "TOM LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Skapa delat album",
|
||||
"sharing_silver_appbar_shared_links": "Delada länkar",
|
||||
"sharing_silver_appbar_shared_links": "Shared links",
|
||||
"sharing_silver_appbar_share_partner": "Dela med partner",
|
||||
"tab_controller_nav_library": "Bibliotek",
|
||||
"tab_controller_nav_photos": "Bilder",
|
||||
@@ -495,30 +491,30 @@
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trestegsladdning kan öka prestandan, men kan också leda till signifikant högre nätverksbelastning",
|
||||
"theme_setting_three_stage_loading_title": "Aktivera trestegsladdning",
|
||||
"translated_text_options": "Val",
|
||||
"trash_page_delete": "Ta Bort",
|
||||
"trash_page_delete_all": "Ta Bort Alla",
|
||||
"trash_page_empty_trash_btn": "Töm papperskorg",
|
||||
"trash_page_empty_trash_dialog_content": "Vill du ta bort dina slängda resurser? De kommer att tas bort permanent från Immich",
|
||||
"translated_text_options": "Options",
|
||||
"trash_page_delete": "Delete",
|
||||
"trash_page_delete_all": "Delete All",
|
||||
"trash_page_empty_trash_btn": "Empty trash",
|
||||
"trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
|
||||
"trash_page_empty_trash_dialog_ok": "Ok",
|
||||
"trash_page_info": "Saker i papperskorgen tas bort permanent efter {} dagar",
|
||||
"trash_page_no_assets": "Inga slängda resurser",
|
||||
"trash_page_restore": "Återställ",
|
||||
"trash_page_restore_all": "Återställ Alla",
|
||||
"trash_page_select_assets_btn": "Välj resurser",
|
||||
"trash_page_select_btn": "Välj",
|
||||
"trash_page_title": "Papperskorg ({})",
|
||||
"upload_dialog_cancel": "Avbryt",
|
||||
"upload_dialog_info": "Vill du säkerhetskopiera de valda resurserna till servern?",
|
||||
"upload_dialog_ok": "Ladda Upp",
|
||||
"upload_dialog_title": "Ladda Upp Resurs",
|
||||
"trash_page_info": "Trashed items will be permanently deleted after {} days",
|
||||
"trash_page_no_assets": "No trashed assets",
|
||||
"trash_page_restore": "Restore",
|
||||
"trash_page_restore_all": "Restore All",
|
||||
"trash_page_select_assets_btn": "Select assets",
|
||||
"trash_page_select_btn": "Select",
|
||||
"trash_page_title": "Trash ({})",
|
||||
"upload_dialog_cancel": "Cancel",
|
||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||
"upload_dialog_ok": "Upload",
|
||||
"upload_dialog_title": "Upload Asset",
|
||||
"version_announcement_overlay_ack": "Bekräfta",
|
||||
"version_announcement_overlay_release_notes": "versionsinformation",
|
||||
"version_announcement_overlay_text_1": "Hej vännen, det finns en ny version av",
|
||||
"version_announcement_overlay_text_2": ". Ta gärna din tid att besöka ",
|
||||
"version_announcement_overlay_text_3": " för att se till att din docker-compose och .env-fil är uppdaterad för att undvika felkonfiguration, speciellt om du använder WatchTower eller liknande mekanism som automatiskt uppdaterar din container",
|
||||
"version_announcement_overlay_title": "Ny serverversion finns tillgänglig \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Ta bort från Stapeln",
|
||||
"viewer_stack_use_as_main_asset": "Använd som Huvudresurs",
|
||||
"viewer_unstack": "Stapla Av"
|
||||
"version_announcement_overlay_title": "Ny serverversion är tillgänglig \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "Remove from Stack",
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack"
|
||||
}
|
||||
@@ -142,7 +142,7 @@
|
||||
"common_add_to_album": "เพิ่มเข้าอัลบั้ม",
|
||||
"common_change_password": "เปลี่ยนรหัสผ่าน",
|
||||
"common_create_new_album": "สร้างอัลบั้มใหม่",
|
||||
"common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชันแอพกับเซิร์ฟเวอร์เข้ากันได้",
|
||||
"common_server_error": "กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต ให้แน่ใจว่าเซิร์ฟเวอร์สามารถเข้าถึงได้ และเวอร์ชั่นแอพกับเซิร์ฟเวอร์เข้ากันได้",
|
||||
"common_shared": "แชร์",
|
||||
"control_bottom_app_bar_add_to_album": "เพิ่มลงอัลบั้ม",
|
||||
"control_bottom_app_bar_album_info": "{} รายการ",
|
||||
@@ -201,8 +201,8 @@
|
||||
"experimental_settings_title": "ทดลอง",
|
||||
"favorites_page_no_favorites": "ไม่พบทรัพยากรในรายการโปรด",
|
||||
"favorites_page_title": "รายการโปรด",
|
||||
"haptic_feedback_switch": "เปิดการตอบสนองแบบสัมผัส",
|
||||
"haptic_feedback_title": "การตอบสนองแบบสัมผัส",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
"haptic_feedback_title": "Haptic Feedback",
|
||||
"home_page_add_to_album_conflicts": "เพิ่ม {added} ทรัพยากรเข้าอัลบั้ม {album}. {failed} ทรัพยากรอยู่ในอัลบั้มอยู่แล้ว",
|
||||
"home_page_add_to_album_err_local": " ไม่สามารถเพิ่มทรัพยากรบนเครื่องเข้าอัลบั้ม กำลังข้าม",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "ไม่สามารถแชร์ผ่านลิงค์ได้ กำลังข้าม",
|
||||
"home_page_upload_err_limit": "สามารถอัพโหลดได้มากสุดครั้งละ 30 ทรัพยากร กำลังข้าม",
|
||||
"image_viewer_page_state_provider_download_error": "ดาวน์โหลดผิดพลาด",
|
||||
"image_viewer_page_state_provider_download_started": "ดาวน์โหลดเริ่มต้น",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "ดาวน์โหลดสำเร็จ",
|
||||
"image_viewer_page_state_provider_share_error": "แชร์ผิดพลาด",
|
||||
"library_page_albums": "Albums",
|
||||
@@ -265,8 +265,8 @@
|
||||
"login_form_server_error": "ไม่สามารถติดต่อกับเซิร์ฟเวอร์",
|
||||
"login_password_changed_error": "เกิดข้อผิดพลาดขณะเปลี่ยนรหัสผ่าน",
|
||||
"login_password_changed_success": "เปลี่ยนรหัสผ่านสำเร็จ",
|
||||
"map_assets_in_bound": "{} รูปภาพ",
|
||||
"map_assets_in_bounds": "{} รูปภาพ",
|
||||
"map_assets_in_bound": "{} photo",
|
||||
"map_assets_in_bounds": "{} photos",
|
||||
"map_cannot_get_user_location": "ไม่สามารถหาตำแหน่งผู้ใช้งานได้",
|
||||
"map_location_dialog_cancel": "ยกเลิก",
|
||||
"map_location_dialog_yes": "ใช่",
|
||||
@@ -298,8 +298,8 @@
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"motion_photos_page_title": "ภาพเคลื่อนไหว",
|
||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
||||
"multiselect_grid_edit_gps_err_read_only": "ไม่สามารถแก้",
|
||||
"no_assets_to_show": "ไม่มีทรัพยากรให้แสดง",
|
||||
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||
"no_assets_to_show": "No assets to show",
|
||||
"notification_permission_dialog_cancel": "ยกเลิก",
|
||||
"notification_permission_dialog_content": "เพื่อเปิดการแจ้งเตือน เข้าตั้งค่าแล้วกดอนุญาต",
|
||||
"notification_permission_dialog_settings": "ตั้งค่า",
|
||||
@@ -382,18 +382,17 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "ข้อเสนอแนะ",
|
||||
"select_user_for_sharing_page_err_album": "สร้างอัลบั้มล้มเหลว",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"server_info_box_app_version": "เวอร์ชันแอพ",
|
||||
"server_info_box_app_version": "เวอร์ชั่นแอพ",
|
||||
"server_info_box_latest_release": "เวอร์ชันล่าสุด",
|
||||
"server_info_box_server_url": "URL เซิร์ฟเวอร์",
|
||||
"server_info_box_server_version": "เวอร์ชันเซิร์ฟเวอร์",
|
||||
"server_info_box_server_version": "เวอร์ชั้นเซิร์ฟเวอร์",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "โหลดรูปต้นฉบับ",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "โหลดรูปภาพตัวอย่าง",
|
||||
"setting_image_viewer_title": "รูปภาพ",
|
||||
"setting_languages_apply": "บันทึก",
|
||||
"setting_languages_title": "ภาษา",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "แจ้งการสำรองข้อมูลในเบื้องหลังล้มเหลว: {}",
|
||||
"setting_notifications_notify_hours": "{} ชั่วโมง",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "แสดงสถานะการสำรองข้อมูลในเบื้องหลังทั้งหมด",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "กรุณารีสตาร์ท Immmich เพื่อใช้การตั้งค่า",
|
||||
"setting_video_viewer_looping_subtitle": "เปิดเพื่อให้วิดีโอวนลูปในที่ดูรายละเอียด",
|
||||
"setting_video_viewer_looping_title": "วนลูป",
|
||||
"setting_video_viewer_title": "วิดีโอ",
|
||||
"share_add": "เพิ่ม",
|
||||
"share_add_photos": "เพิ่มรูปภาพ",
|
||||
"share_add_title": "เพิ่มชื่อ",
|
||||
"share_assets_selected": "{} ถูกเลือก",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "สร้างอัลบั้ม",
|
||||
"shared_album_activities_input_disable": "คอมเมนต์ถูกปิด",
|
||||
"shared_album_activities_input_hint": "พูดอะไรสักอย่าง",
|
||||
@@ -448,9 +444,9 @@
|
||||
"shared_link_edit_expire_after_option_hours": "{} hours",
|
||||
"shared_link_edit_expire_after_option_minute": "1 minute",
|
||||
"shared_link_edit_expire_after_option_minutes": "{} minutes",
|
||||
"shared_link_edit_expire_after_option_months": "{} เดือน",
|
||||
"shared_link_edit_expire_after_option_months": "{} months",
|
||||
"shared_link_edit_expire_after_option_never": "ไม่กำหนด",
|
||||
"shared_link_edit_expire_after_option_year": "{} ปี",
|
||||
"shared_link_edit_expire_after_option_year": "{} year",
|
||||
"shared_link_edit_password": "รหัสผ่าน",
|
||||
"shared_link_edit_password_hint": "กรอกรหัสผ่านแชร์",
|
||||
"shared_link_edit_show_meta": "แสดง metadata",
|
||||
@@ -476,7 +472,7 @@
|
||||
"share_done": "เสร็จ",
|
||||
"share_invite": "เชิญเข้าอัลบั้ม",
|
||||
"sharing_page_album": "อัลบั้มที่แชร์",
|
||||
"sharing_page_description": "สร้างอัลบั้มที่แชร์เพื่อแชร์รูปภาพและวิดีโอให้กับคนบนเครือข่ายคุณ",
|
||||
"sharing_page_description": "สร้างอัลบั้มที่แชร์เพื่อแชร์รูปภาพและวิดีโอให้กับคนบนเครื่อข่ายคุณ",
|
||||
"sharing_page_empty_list": "รายการว่างเปล่า",
|
||||
"sharing_silver_appbar_create_shared_album": "สร้างอัลบั้มแชร์",
|
||||
"sharing_silver_appbar_shared_links": "ลิงก์ที่แชร์",
|
||||
@@ -512,12 +508,12 @@
|
||||
"upload_dialog_info": "คุณต้องการอัพโหลดทรัพยากรดังกล่าวบนเซิร์ฟเวอร์หรือไม่?",
|
||||
"upload_dialog_ok": "อัปโหลด",
|
||||
"upload_dialog_title": "อัปโหลดทรัพยากร",
|
||||
"version_announcement_overlay_ack": "รับทราบ",
|
||||
"version_announcement_overlay_ack": "Acknowledge",
|
||||
"version_announcement_overlay_release_notes": "release notes",
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "มีเวอร์ชันใหม่สำหรับเซิร์ฟเวอร์ \uD83C\uDF89",
|
||||
"version_announcement_overlay_title": "มีเวอร์ชั่นใหม่สำหรับเซิร์ฟเวอร์ \uD83C\uDF89",
|
||||
"viewer_remove_from_stack": "เอาออกจากที่ซ้อน",
|
||||
"viewer_stack_use_as_main_asset": "ใช้เป็นทรัพยากรหลัก",
|
||||
"viewer_unstack": "หยุดซ้อน"
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Завантажувати оригінальне зображення",
|
||||
"setting_image_viewer_preview_subtitle": "Увімкніть для завантаження зображень середньої роздільної здатності.\nВимкніть для безпосереднього завантаження оригіналу або використовувати лише мініатюру.",
|
||||
"setting_image_viewer_preview_title": "Завантажувати зображення попереднього перегляду",
|
||||
"setting_image_viewer_title": "Зображення",
|
||||
"setting_languages_apply": "Застосувати",
|
||||
"setting_languages_title": "Мова",
|
||||
"setting_notifications_notify_failures_grace_period": "Повідомити про помилки фонового резервного копіювання: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Показати загальний хід фонового резервного копіювання",
|
||||
"setting_pages_app_bar_settings": "Налаштування",
|
||||
"settings_require_restart": "Перезавантажте програму для застосування цього налаштування",
|
||||
"setting_video_viewer_looping_subtitle": "Увімкнути циклічне відтворення відео",
|
||||
"setting_video_viewer_looping_title": "Циклічне відтворення",
|
||||
"setting_video_viewer_title": "Відео",
|
||||
"share_add": "Додати",
|
||||
"share_add_photos": "Додати знімки",
|
||||
"share_add_title": "Додати назву",
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
"home_page_share_err_local": "Không thể chia sẻ ảnh cục bộ qua liên kết, bỏ qua",
|
||||
"home_page_upload_err_limit": "Chỉ có thể tải lên tối đa 30 ảnh cùng một lúc, bỏ qua",
|
||||
"image_viewer_page_state_provider_download_error": "Tải xuống không thành công",
|
||||
"image_viewer_page_state_provider_download_started": "Đã bắt đầu tải xuống",
|
||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||
"image_viewer_page_state_provider_download_success": "Tải xuống thành công",
|
||||
"image_viewer_page_state_provider_share_error": "Chia sẻ không thành công",
|
||||
"library_page_albums": "Album",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Tải ảnh gốc",
|
||||
"setting_image_viewer_preview_subtitle": "Bật để tải ảnh độ phân giải trung bình. Tắt để tải trực tiếp ảnh gốc hoặc chỉ sử dụng hình thu nhỏ.",
|
||||
"setting_image_viewer_preview_title": "Tải ảnh xem trước",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Áp dụng",
|
||||
"setting_languages_title": "Ngôn ngữ",
|
||||
"setting_notifications_notify_failures_grace_period": "Thông báo sao lưu nền thất bại: {}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "Hiện thị toàn bộ sao lưu nền đang thực hiện",
|
||||
"setting_pages_app_bar_settings": "Cài đặt",
|
||||
"settings_require_restart": "Vui lòng khởi động lại Immich để áp dụng cài đặt này",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Thêm",
|
||||
"share_add_photos": "Thêm ảnh",
|
||||
"share_add_title": "Thêm tiêu đề",
|
||||
"share_assets_selected": "{} đã chọn",
|
||||
"share_assets_selected": "{} selected",
|
||||
"share_create_album": "Tạo album",
|
||||
"shared_album_activities_input_disable": "Nhận xét hiện đã tắt",
|
||||
"shared_album_activities_input_hint": "Nói điều gì đó",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "加载原图",
|
||||
"setting_image_viewer_preview_subtitle": "启用以加载中等质量的图像,禁用以加载原图或缩略图。",
|
||||
"setting_image_viewer_preview_title": "加载预览图",
|
||||
"setting_image_viewer_title": "图片",
|
||||
"setting_languages_apply": "应用",
|
||||
"setting_languages_title": "语言",
|
||||
"setting_notifications_notify_failures_grace_period": "后台备份失败通知:{}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "显示后台备份总进度",
|
||||
"setting_pages_app_bar_settings": "设置",
|
||||
"settings_require_restart": "请重启 Immich 以使设置生效",
|
||||
"setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。",
|
||||
"setting_video_viewer_looping_title": "循环播放",
|
||||
"setting_video_viewer_title": "视频",
|
||||
"share_add": "添加",
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_assets_selected": "{} 已选择",
|
||||
"share_assets_selected": "{}已选择",
|
||||
"share_create_album": "创建相册",
|
||||
"shared_album_activities_input_disable": "评论已禁用",
|
||||
"shared_album_activities_input_hint": "说些什么",
|
||||
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "加载原图",
|
||||
"setting_image_viewer_preview_subtitle": "启用以加载中等质量的图像,禁用以加载原图或缩略图。",
|
||||
"setting_image_viewer_preview_title": "加载预览图",
|
||||
"setting_image_viewer_title": "图片",
|
||||
"setting_languages_apply": "应用",
|
||||
"setting_languages_title": "语言",
|
||||
"setting_notifications_notify_failures_grace_period": "后台备份失败通知:{}",
|
||||
@@ -408,13 +407,10 @@
|
||||
"setting_notifications_total_progress_title": "显示后台备份总进度",
|
||||
"setting_pages_app_bar_settings": "设置",
|
||||
"settings_require_restart": "请重启 Immich 以使设置生效",
|
||||
"setting_video_viewer_looping_subtitle": "对播放窗口中的视频开启循环播放。",
|
||||
"setting_video_viewer_looping_title": "循环播放",
|
||||
"setting_video_viewer_title": "视频",
|
||||
"share_add": "添加",
|
||||
"share_add_photos": "添加项目",
|
||||
"share_add_title": "添加标题",
|
||||
"share_assets_selected": "{} 已选择",
|
||||
"share_assets_selected": "{}已选择",
|
||||
"share_create_album": "创建相册",
|
||||
"shared_album_activities_input_disable": "评论已禁用",
|
||||
"shared_album_activities_input_hint": "说些什么",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"backup_controller_page_status_off": "Automatic foreground backup is off",
|
||||
"backup_controller_page_status_on": "Automatic foreground backup is on",
|
||||
"backup_controller_page_storage_format": "{} of {} used",
|
||||
"backup_controller_page_to_backup": "Albums to be backed up",
|
||||
"backup_controller_page_to_backup": "Albums to be backup",
|
||||
"backup_controller_page_total": "Total",
|
||||
"backup_controller_page_total_sub": "All unique photos and videos from selected albums",
|
||||
"backup_controller_page_turn_off": "Turn off foreground backup",
|
||||
@@ -391,7 +391,6 @@
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_image_viewer_title": "Images",
|
||||
"setting_languages_apply": "Apply",
|
||||
"setting_languages_title": "Languages",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
@@ -408,9 +407,6 @@
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
|
||||
"setting_video_viewer_looping_title": "Looping",
|
||||
"setting_video_viewer_title": "Videos",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
|
||||
@@ -73,7 +73,7 @@ String getThumbnailUrlForRemoteId(
|
||||
final String id, {
|
||||
AssetMediaSize type = AssetMediaSize.thumbnail,
|
||||
}) {
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}';
|
||||
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?format=${type.value}';
|
||||
}
|
||||
|
||||
String getFaceThumbnailUrl(final String personId) {
|
||||
|
||||
5
mobile/openapi/README.md
generated
5
mobile/openapi/README.md
generated
@@ -144,7 +144,6 @@ Class | Method | HTTP request | Description
|
||||
*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets |
|
||||
*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories |
|
||||
*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} |
|
||||
*NotificationsApi* | [**sendTestEmail**](doc//NotificationsApi.md#sendtestemail) | **POST** /notifications/test-email |
|
||||
*OAuthApi* | [**finishOAuth**](doc//OAuthApi.md#finishoauth) | **POST** /oauth/callback |
|
||||
*OAuthApi* | [**linkOAuthAccount**](doc//OAuthApi.md#linkoauthaccount) | **POST** /oauth/link |
|
||||
*OAuthApi* | [**redirectOAuthToMobile**](doc//OAuthApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect |
|
||||
@@ -280,6 +279,7 @@ Class | Method | HTTP request | Description
|
||||
- [BulkIdResponseDto](doc//BulkIdResponseDto.md)
|
||||
- [BulkIdsDto](doc//BulkIdsDto.md)
|
||||
- [CLIPConfig](doc//CLIPConfig.md)
|
||||
- [CLIPMode](doc//CLIPMode.md)
|
||||
- [CQMode](doc//CQMode.md)
|
||||
- [ChangePasswordDto](doc//ChangePasswordDto.md)
|
||||
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
||||
@@ -299,7 +299,6 @@ Class | Method | HTTP request | Description
|
||||
- [EntityType](doc//EntityType.md)
|
||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||
- [FaceDto](doc//FaceDto.md)
|
||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||
- [FileChecksumDto](doc//FileChecksumDto.md)
|
||||
- [FileChecksumResponseDto](doc//FileChecksumResponseDto.md)
|
||||
- [FileReportDto](doc//FileReportDto.md)
|
||||
@@ -329,6 +328,7 @@ Class | Method | HTTP request | Description
|
||||
- [MemoryUpdateDto](doc//MemoryUpdateDto.md)
|
||||
- [MergePersonDto](doc//MergePersonDto.md)
|
||||
- [MetadataSearchDto](doc//MetadataSearchDto.md)
|
||||
- [ModelType](doc//ModelType.md)
|
||||
- [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
|
||||
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
|
||||
- [OAuthConfigDto](doc//OAuthConfigDto.md)
|
||||
@@ -348,6 +348,7 @@ Class | Method | HTTP request | Description
|
||||
- [QueueStatusDto](doc//QueueStatusDto.md)
|
||||
- [ReactionLevel](doc//ReactionLevel.md)
|
||||
- [ReactionType](doc//ReactionType.md)
|
||||
- [RecognitionConfig](doc//RecognitionConfig.md)
|
||||
- [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md)
|
||||
- [ScanLibraryDto](doc//ScanLibraryDto.md)
|
||||
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
|
||||
|
||||
5
mobile/openapi/lib/api.dart
generated
5
mobile/openapi/lib/api.dart
generated
@@ -43,7 +43,6 @@ part 'api/jobs_api.dart';
|
||||
part 'api/libraries_api.dart';
|
||||
part 'api/map_api.dart';
|
||||
part 'api/memories_api.dart';
|
||||
part 'api/notifications_api.dart';
|
||||
part 'api/o_auth_api.dart';
|
||||
part 'api/partners_api.dart';
|
||||
part 'api/people_api.dart';
|
||||
@@ -107,6 +106,7 @@ part 'model/avatar_update.dart';
|
||||
part 'model/bulk_id_response_dto.dart';
|
||||
part 'model/bulk_ids_dto.dart';
|
||||
part 'model/clip_config.dart';
|
||||
part 'model/clip_mode.dart';
|
||||
part 'model/cq_mode.dart';
|
||||
part 'model/change_password_dto.dart';
|
||||
part 'model/check_existing_assets_dto.dart';
|
||||
@@ -126,7 +126,6 @@ part 'model/email_notifications_update.dart';
|
||||
part 'model/entity_type.dart';
|
||||
part 'model/exif_response_dto.dart';
|
||||
part 'model/face_dto.dart';
|
||||
part 'model/facial_recognition_config.dart';
|
||||
part 'model/file_checksum_dto.dart';
|
||||
part 'model/file_checksum_response_dto.dart';
|
||||
part 'model/file_report_dto.dart';
|
||||
@@ -156,6 +155,7 @@ part 'model/memory_update.dart';
|
||||
part 'model/memory_update_dto.dart';
|
||||
part 'model/merge_person_dto.dart';
|
||||
part 'model/metadata_search_dto.dart';
|
||||
part 'model/model_type.dart';
|
||||
part 'model/o_auth_authorize_response_dto.dart';
|
||||
part 'model/o_auth_callback_dto.dart';
|
||||
part 'model/o_auth_config_dto.dart';
|
||||
@@ -175,6 +175,7 @@ part 'model/places_response_dto.dart';
|
||||
part 'model/queue_status_dto.dart';
|
||||
part 'model/reaction_level.dart';
|
||||
part 'model/reaction_type.dart';
|
||||
part 'model/recognition_config.dart';
|
||||
part 'model/reverse_geocoding_state_response_dto.dart';
|
||||
part 'model/scan_library_dto.dart';
|
||||
part 'model/search_album_response_dto.dart';
|
||||
|
||||
57
mobile/openapi/lib/api/notifications_api.dart
generated
57
mobile/openapi/lib/api/notifications_api.dart
generated
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class NotificationsApi {
|
||||
NotificationsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'POST /notifications/test-email' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SystemConfigSmtpDto] systemConfigSmtpDto (required):
|
||||
Future<Response> sendTestEmailWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/notifications/test-email';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = systemConfigSmtpDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SystemConfigSmtpDto] systemConfigSmtpDto (required):
|
||||
Future<void> sendTestEmail(SystemConfigSmtpDto systemConfigSmtpDto,) async {
|
||||
final response = await sendTestEmailWithHttpInfo(systemConfigSmtpDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
mobile/openapi/lib/api_client.dart
generated
8
mobile/openapi/lib/api_client.dart
generated
@@ -276,6 +276,8 @@ class ApiClient {
|
||||
return BulkIdsDto.fromJson(value);
|
||||
case 'CLIPConfig':
|
||||
return CLIPConfig.fromJson(value);
|
||||
case 'CLIPMode':
|
||||
return CLIPModeTypeTransformer().decode(value);
|
||||
case 'CQMode':
|
||||
return CQModeTypeTransformer().decode(value);
|
||||
case 'ChangePasswordDto':
|
||||
@@ -314,8 +316,6 @@ class ApiClient {
|
||||
return ExifResponseDto.fromJson(value);
|
||||
case 'FaceDto':
|
||||
return FaceDto.fromJson(value);
|
||||
case 'FacialRecognitionConfig':
|
||||
return FacialRecognitionConfig.fromJson(value);
|
||||
case 'FileChecksumDto':
|
||||
return FileChecksumDto.fromJson(value);
|
||||
case 'FileChecksumResponseDto':
|
||||
@@ -374,6 +374,8 @@ class ApiClient {
|
||||
return MergePersonDto.fromJson(value);
|
||||
case 'MetadataSearchDto':
|
||||
return MetadataSearchDto.fromJson(value);
|
||||
case 'ModelType':
|
||||
return ModelTypeTypeTransformer().decode(value);
|
||||
case 'OAuthAuthorizeResponseDto':
|
||||
return OAuthAuthorizeResponseDto.fromJson(value);
|
||||
case 'OAuthCallbackDto':
|
||||
@@ -412,6 +414,8 @@ class ApiClient {
|
||||
return ReactionLevelTypeTransformer().decode(value);
|
||||
case 'ReactionType':
|
||||
return ReactionTypeTypeTransformer().decode(value);
|
||||
case 'RecognitionConfig':
|
||||
return RecognitionConfig.fromJson(value);
|
||||
case 'ReverseGeocodingStateResponseDto':
|
||||
return ReverseGeocodingStateResponseDto.fromJson(value);
|
||||
case 'ScanLibraryDto':
|
||||
|
||||
6
mobile/openapi/lib/api_helper.dart
generated
6
mobile/openapi/lib/api_helper.dart
generated
@@ -76,6 +76,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is AudioCodec) {
|
||||
return AudioCodecTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is CLIPMode) {
|
||||
return CLIPModeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is CQMode) {
|
||||
return CQModeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
@@ -103,6 +106,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is MemoryType) {
|
||||
return MemoryTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is ModelType) {
|
||||
return ModelTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is PathEntityType) {
|
||||
return PathEntityTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
9
mobile/openapi/lib/model/asset_response_dto.dart
generated
9
mobile/openapi/lib/model/asset_response_dto.dart
generated
@@ -43,7 +43,6 @@ class AssetResponseDto {
|
||||
this.tags = const [],
|
||||
required this.thumbhash,
|
||||
required this.type,
|
||||
this.unassignedFaces = const [],
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
@@ -127,8 +126,6 @@ class AssetResponseDto {
|
||||
|
||||
AssetTypeEnum type;
|
||||
|
||||
List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
|
||||
|
||||
DateTime updatedAt;
|
||||
|
||||
@override
|
||||
@@ -163,7 +160,6 @@ class AssetResponseDto {
|
||||
_deepEquality.equals(other.tags, tags) &&
|
||||
other.thumbhash == thumbhash &&
|
||||
other.type == type &&
|
||||
_deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
|
||||
other.updatedAt == updatedAt;
|
||||
|
||||
@override
|
||||
@@ -199,11 +195,10 @@ class AssetResponseDto {
|
||||
(tags.hashCode) +
|
||||
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||
(type.hashCode) +
|
||||
(unassignedFaces.hashCode) +
|
||||
(updatedAt.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]';
|
||||
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, stackCount=$stackCount, stackParentId=$stackParentId, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -273,7 +268,6 @@ class AssetResponseDto {
|
||||
// json[r'thumbhash'] = null;
|
||||
}
|
||||
json[r'type'] = this.type;
|
||||
json[r'unassignedFaces'] = this.unassignedFaces;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
return json;
|
||||
}
|
||||
@@ -316,7 +310,6 @@ class AssetResponseDto {
|
||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
);
|
||||
}
|
||||
|
||||
40
mobile/openapi/lib/model/clip_config.dart
generated
40
mobile/openapi/lib/model/clip_config.dart
generated
@@ -14,31 +14,63 @@ class CLIPConfig {
|
||||
/// Returns a new [CLIPConfig] instance.
|
||||
CLIPConfig({
|
||||
required this.enabled,
|
||||
this.mode,
|
||||
required this.modelName,
|
||||
this.modelType,
|
||||
});
|
||||
|
||||
bool enabled;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
CLIPMode? mode;
|
||||
|
||||
String modelName;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
ModelType? modelType;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is CLIPConfig &&
|
||||
other.enabled == enabled &&
|
||||
other.modelName == modelName;
|
||||
other.mode == mode &&
|
||||
other.modelName == modelName &&
|
||||
other.modelType == modelType;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(enabled.hashCode) +
|
||||
(modelName.hashCode);
|
||||
(mode == null ? 0 : mode!.hashCode) +
|
||||
(modelName.hashCode) +
|
||||
(modelType == null ? 0 : modelType!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'CLIPConfig[enabled=$enabled, modelName=$modelName]';
|
||||
String toString() => 'CLIPConfig[enabled=$enabled, mode=$mode, modelName=$modelName, modelType=$modelType]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'enabled'] = this.enabled;
|
||||
if (this.mode != null) {
|
||||
json[r'mode'] = this.mode;
|
||||
} else {
|
||||
// json[r'mode'] = null;
|
||||
}
|
||||
json[r'modelName'] = this.modelName;
|
||||
if (this.modelType != null) {
|
||||
json[r'modelType'] = this.modelType;
|
||||
} else {
|
||||
// json[r'modelType'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -51,7 +83,9 @@ class CLIPConfig {
|
||||
|
||||
return CLIPConfig(
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
mode: CLIPMode.fromJson(json[r'mode']),
|
||||
modelName: mapValueOfType<String>(json, r'modelName')!,
|
||||
modelType: ModelType.fromJson(json[r'modelType']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
85
mobile/openapi/lib/model/clip_mode.dart
generated
Normal file
85
mobile/openapi/lib/model/clip_mode.dart
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class CLIPMode {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const CLIPMode._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const vision = CLIPMode._(r'vision');
|
||||
static const text = CLIPMode._(r'text');
|
||||
|
||||
/// List of all possible values in this [enum][CLIPMode].
|
||||
static const values = <CLIPMode>[
|
||||
vision,
|
||||
text,
|
||||
];
|
||||
|
||||
static CLIPMode? fromJson(dynamic value) => CLIPModeTypeTransformer().decode(value);
|
||||
|
||||
static List<CLIPMode> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <CLIPMode>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = CLIPMode.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [CLIPMode] to String,
|
||||
/// and [decode] dynamic data back to [CLIPMode].
|
||||
class CLIPModeTypeTransformer {
|
||||
factory CLIPModeTypeTransformer() => _instance ??= const CLIPModeTypeTransformer._();
|
||||
|
||||
const CLIPModeTypeTransformer._();
|
||||
|
||||
String encode(CLIPMode data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a CLIPMode.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
CLIPMode? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'vision': return CLIPMode.vision;
|
||||
case r'text': return CLIPMode.text;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [CLIPModeTypeTransformer] instance.
|
||||
static CLIPModeTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
85
mobile/openapi/lib/model/model_type.dart
generated
Normal file
85
mobile/openapi/lib/model/model_type.dart
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class ModelType {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const ModelType._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const facialRecognition = ModelType._(r'facial-recognition');
|
||||
static const clip = ModelType._(r'clip');
|
||||
|
||||
/// List of all possible values in this [enum][ModelType].
|
||||
static const values = <ModelType>[
|
||||
facialRecognition,
|
||||
clip,
|
||||
];
|
||||
|
||||
static ModelType? fromJson(dynamic value) => ModelTypeTypeTransformer().decode(value);
|
||||
|
||||
static List<ModelType> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <ModelType>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = ModelType.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [ModelType] to String,
|
||||
/// and [decode] dynamic data back to [ModelType].
|
||||
class ModelTypeTypeTransformer {
|
||||
factory ModelTypeTypeTransformer() => _instance ??= const ModelTypeTypeTransformer._();
|
||||
|
||||
const ModelTypeTypeTransformer._();
|
||||
|
||||
String encode(ModelType data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a ModelType.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
ModelType? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'facial-recognition': return ModelType.facialRecognition;
|
||||
case r'clip': return ModelType.clip;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [ModelTypeTypeTransformer] instance.
|
||||
static ModelTypeTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class FacialRecognitionConfig {
|
||||
/// Returns a new [FacialRecognitionConfig] instance.
|
||||
FacialRecognitionConfig({
|
||||
class RecognitionConfig {
|
||||
/// Returns a new [RecognitionConfig] instance.
|
||||
RecognitionConfig({
|
||||
required this.enabled,
|
||||
required this.maxDistance,
|
||||
required this.minFaces,
|
||||
required this.minScore,
|
||||
required this.modelName,
|
||||
this.modelType,
|
||||
});
|
||||
|
||||
bool enabled;
|
||||
@@ -35,13 +36,22 @@ class FacialRecognitionConfig {
|
||||
|
||||
String modelName;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
ModelType? modelType;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is FacialRecognitionConfig &&
|
||||
bool operator ==(Object other) => identical(this, other) || other is RecognitionConfig &&
|
||||
other.enabled == enabled &&
|
||||
other.maxDistance == maxDistance &&
|
||||
other.minFaces == minFaces &&
|
||||
other.minScore == minScore &&
|
||||
other.modelName == modelName;
|
||||
other.modelName == modelName &&
|
||||
other.modelType == modelType;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -50,10 +60,11 @@ class FacialRecognitionConfig {
|
||||
(maxDistance.hashCode) +
|
||||
(minFaces.hashCode) +
|
||||
(minScore.hashCode) +
|
||||
(modelName.hashCode);
|
||||
(modelName.hashCode) +
|
||||
(modelType == null ? 0 : modelType!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'FacialRecognitionConfig[enabled=$enabled, maxDistance=$maxDistance, minFaces=$minFaces, minScore=$minScore, modelName=$modelName]';
|
||||
String toString() => 'RecognitionConfig[enabled=$enabled, maxDistance=$maxDistance, minFaces=$minFaces, minScore=$minScore, modelName=$modelName, modelType=$modelType]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -62,32 +73,38 @@ class FacialRecognitionConfig {
|
||||
json[r'minFaces'] = this.minFaces;
|
||||
json[r'minScore'] = this.minScore;
|
||||
json[r'modelName'] = this.modelName;
|
||||
if (this.modelType != null) {
|
||||
json[r'modelType'] = this.modelType;
|
||||
} else {
|
||||
// json[r'modelType'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [FacialRecognitionConfig] instance and imports its values from
|
||||
/// Returns a new [RecognitionConfig] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static FacialRecognitionConfig? fromJson(dynamic value) {
|
||||
static RecognitionConfig? fromJson(dynamic value) {
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return FacialRecognitionConfig(
|
||||
return RecognitionConfig(
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
maxDistance: mapValueOfType<double>(json, r'maxDistance')!,
|
||||
minFaces: mapValueOfType<int>(json, r'minFaces')!,
|
||||
minScore: mapValueOfType<double>(json, r'minScore')!,
|
||||
modelName: mapValueOfType<String>(json, r'modelName')!,
|
||||
modelType: ModelType.fromJson(json[r'modelType']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<FacialRecognitionConfig> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <FacialRecognitionConfig>[];
|
||||
static List<RecognitionConfig> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <RecognitionConfig>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = FacialRecognitionConfig.fromJson(row);
|
||||
final value = RecognitionConfig.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
@@ -96,12 +113,12 @@ class FacialRecognitionConfig {
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, FacialRecognitionConfig> mapFromJson(dynamic json) {
|
||||
final map = <String, FacialRecognitionConfig>{};
|
||||
static Map<String, RecognitionConfig> mapFromJson(dynamic json) {
|
||||
final map = <String, RecognitionConfig>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = FacialRecognitionConfig.fromJson(entry.value);
|
||||
final value = RecognitionConfig.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
@@ -110,14 +127,14 @@ class FacialRecognitionConfig {
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of FacialRecognitionConfig-objects as value to a dart map
|
||||
static Map<String, List<FacialRecognitionConfig>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<FacialRecognitionConfig>>{};
|
||||
// maps a json object with a list of RecognitionConfig-objects as value to a dart map
|
||||
static Map<String, List<RecognitionConfig>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<RecognitionConfig>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = FacialRecognitionConfig.listFromJson(entry.value, growable: growable,);
|
||||
map[entry.key] = RecognitionConfig.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
@@ -26,7 +26,7 @@ class SystemConfigMachineLearningDto {
|
||||
|
||||
bool enabled;
|
||||
|
||||
FacialRecognitionConfig facialRecognition;
|
||||
RecognitionConfig facialRecognition;
|
||||
|
||||
String url;
|
||||
|
||||
@@ -71,7 +71,7 @@ class SystemConfigMachineLearningDto {
|
||||
clip: CLIPConfig.fromJson(json[r'clip'])!,
|
||||
duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!,
|
||||
enabled: mapValueOfType<bool>(json, r'enabled')!,
|
||||
facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!,
|
||||
facialRecognition: RecognitionConfig.fromJson(json[r'facialRecognition'])!,
|
||||
url: mapValueOfType<String>(json, r'url')!,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2299,6 +2299,27 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/validate-license": {
|
||||
"get": {
|
||||
"operationId": "validateLicense",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Authentication"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/validateToken": {
|
||||
"post": {
|
||||
"operationId": "validateAccessToken",
|
||||
@@ -3466,41 +3487,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/notifications/test-email": {
|
||||
"post": {
|
||||
"operationId": "sendTestEmail",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SystemConfigSmtpDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Notifications"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/oauth/authorize": {
|
||||
"post": {
|
||||
"operationId": "startOAuth",
|
||||
@@ -7760,12 +7746,6 @@
|
||||
"type": {
|
||||
"$ref": "#/components/schemas/AssetTypeEnum"
|
||||
},
|
||||
"unassignedFaces": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"updatedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
@@ -7913,8 +7893,14 @@
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"$ref": "#/components/schemas/CLIPMode"
|
||||
},
|
||||
"modelName": {
|
||||
"type": "string"
|
||||
},
|
||||
"modelType": {
|
||||
"$ref": "#/components/schemas/ModelType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -7923,6 +7909,13 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CLIPMode": {
|
||||
"enum": [
|
||||
"vision",
|
||||
"text"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CQMode": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -8345,40 +8338,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FacialRecognitionConfig": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"maxDistance": {
|
||||
"format": "float",
|
||||
"maximum": 2,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"minFaces": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"minScore": {
|
||||
"format": "float",
|
||||
"maximum": 1,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"modelName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"maxDistance",
|
||||
"minFaces",
|
||||
"minScore",
|
||||
"modelName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileChecksumDto": {
|
||||
"properties": {
|
||||
"filenames": {
|
||||
@@ -9095,6 +9054,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ModelType": {
|
||||
"enum": [
|
||||
"facial-recognition",
|
||||
"clip"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"OAuthAuthorizeResponseDto": {
|
||||
"properties": {
|
||||
"url": {
|
||||
@@ -9428,6 +9394,43 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RecognitionConfig": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"maxDistance": {
|
||||
"format": "float",
|
||||
"maximum": 2,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"minFaces": {
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"minScore": {
|
||||
"format": "float",
|
||||
"maximum": 1,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
},
|
||||
"modelName": {
|
||||
"type": "string"
|
||||
},
|
||||
"modelType": {
|
||||
"$ref": "#/components/schemas/ModelType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled",
|
||||
"maxDistance",
|
||||
"minFaces",
|
||||
"minScore",
|
||||
"modelName"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ReverseGeocodingStateResponseDto": {
|
||||
"properties": {
|
||||
"lastImportFileName": {
|
||||
@@ -10533,7 +10536,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"facialRecognition": {
|
||||
"$ref": "#/components/schemas/FacialRecognitionConfig"
|
||||
"$ref": "#/components/schemas/RecognitionConfig"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
|
||||
@@ -194,7 +194,6 @@ export type AssetResponseDto = {
|
||||
tags?: TagResponseDto[];
|
||||
thumbhash: string | null;
|
||||
"type": AssetTypeEnum;
|
||||
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
||||
updatedAt: string;
|
||||
};
|
||||
export type AlbumResponseDto = {
|
||||
@@ -554,19 +553,6 @@ export type MemoryUpdateDto = {
|
||||
memoryAt?: string;
|
||||
seenAt?: string;
|
||||
};
|
||||
export type SystemConfigSmtpTransportDto = {
|
||||
host: string;
|
||||
ignoreCert: boolean;
|
||||
password: string;
|
||||
port: number;
|
||||
username: string;
|
||||
};
|
||||
export type SystemConfigSmtpDto = {
|
||||
enabled: boolean;
|
||||
"from": string;
|
||||
replyTo: string;
|
||||
transport: SystemConfigSmtpTransportDto;
|
||||
};
|
||||
export type OAuthConfigDto = {
|
||||
redirectUri: string;
|
||||
};
|
||||
@@ -975,24 +961,27 @@ export type SystemConfigLoggingDto = {
|
||||
};
|
||||
export type ClipConfig = {
|
||||
enabled: boolean;
|
||||
mode?: CLIPMode;
|
||||
modelName: string;
|
||||
modelType?: ModelType;
|
||||
};
|
||||
export type DuplicateDetectionConfig = {
|
||||
enabled: boolean;
|
||||
maxDistance: number;
|
||||
};
|
||||
export type FacialRecognitionConfig = {
|
||||
export type RecognitionConfig = {
|
||||
enabled: boolean;
|
||||
maxDistance: number;
|
||||
minFaces: number;
|
||||
minScore: number;
|
||||
modelName: string;
|
||||
modelType?: ModelType;
|
||||
};
|
||||
export type SystemConfigMachineLearningDto = {
|
||||
clip: ClipConfig;
|
||||
duplicateDetection: DuplicateDetectionConfig;
|
||||
enabled: boolean;
|
||||
facialRecognition: FacialRecognitionConfig;
|
||||
facialRecognition: RecognitionConfig;
|
||||
url: string;
|
||||
};
|
||||
export type SystemConfigMapDto = {
|
||||
@@ -1003,6 +992,19 @@ export type SystemConfigMapDto = {
|
||||
export type SystemConfigNewVersionCheckDto = {
|
||||
enabled: boolean;
|
||||
};
|
||||
export type SystemConfigSmtpTransportDto = {
|
||||
host: string;
|
||||
ignoreCert: boolean;
|
||||
password: string;
|
||||
port: number;
|
||||
username: string;
|
||||
};
|
||||
export type SystemConfigSmtpDto = {
|
||||
enabled: boolean;
|
||||
"from": string;
|
||||
replyTo: string;
|
||||
transport: SystemConfigSmtpTransportDto;
|
||||
};
|
||||
export type SystemConfigNotificationsDto = {
|
||||
smtp: SystemConfigSmtpDto;
|
||||
};
|
||||
@@ -2022,15 +2024,6 @@ export function addMemoryAssets({ id, bulkIdsDto }: {
|
||||
body: bulkIdsDto
|
||||
})));
|
||||
}
|
||||
export function sendTestEmail({ systemConfigSmtpDto }: {
|
||||
systemConfigSmtpDto: SystemConfigSmtpDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/notifications/test-email", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: systemConfigSmtpDto
|
||||
})));
|
||||
}
|
||||
export function startOAuth({ oAuthConfigDto }: {
|
||||
oAuthConfigDto: OAuthConfigDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
@@ -3080,6 +3073,14 @@ export enum LogLevel {
|
||||
Error = "error",
|
||||
Fatal = "fatal"
|
||||
}
|
||||
export enum CLIPMode {
|
||||
Vision = "vision",
|
||||
Text = "text"
|
||||
}
|
||||
export enum ModelType {
|
||||
FacialRecognition = "facial-recognition",
|
||||
Clip = "clip"
|
||||
}
|
||||
export enum TimeBucketSize {
|
||||
Day = "DAY",
|
||||
Month = "MONTH"
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
</p>
|
||||
|
||||
## تنصل
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="../README.md">English</a>
|
||||
<a href="README_es_ES.md">Español</a>
|
||||
<a href="README_ca_ES.md">Español</a>
|
||||
<a href="README_fr_FR.md">Français</a>
|
||||
<a href="README_it_IT.md">Italiano</a>
|
||||
<a href="README_ja_JP.md">日本語</a>
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<a href="README_de_DE.md">Deutsch</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
</p>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<a href="README_nl_NL.md">Nederlands</a>
|
||||
<a href="README_tr_TR.md">Türkçe</a>
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
<a href="README_ru_RU.md">Русский</a>
|
||||
<a href="README_pt_BR.md">Português Brasileiro</a>
|
||||
<a href="README_sv_SE.md">Svenska</a>
|
||||
<a href="README_ar_JO.md">العربية</a>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<a href="../README.md">English</a>
|
||||
<a href="README_ca_ES.md">Català</a>
|
||||
<a href="README_es_ES.md">Español</a>
|
||||
<a href="README_de_DE.md">Deutsch</a>
|
||||
<a href="README_fr_FR.md">Français</a>
|
||||
<a href="README_it_IT.md">Italiano</a>
|
||||
<a href="README_ja_JP.md">日本語</a>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user