mirror of
https://github.com/immich-app/immich.git
synced 2025-12-08 01:10:00 +03:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
919fd7d41f | ||
|
|
c2fdb6aab8 | ||
|
|
b6c4da37fd | ||
|
|
17c3e8e8bf | ||
|
|
21d3f248da | ||
|
|
a29660aae3 | ||
|
|
6c81fa0f0a | ||
|
|
7156da502f | ||
|
|
13741410a7 | ||
|
|
3408e6b3cb | ||
|
|
434bcec5cc | ||
|
|
ebc71e428d | ||
|
|
a70cd368af | ||
|
|
3225e33fc1 | ||
|
|
85ab916ecf | ||
|
|
7445dad0dd | ||
|
|
0237f9baa3 | ||
|
|
2e059bfbfd | ||
|
|
7bb7f63d57 | ||
|
|
66a5a5718f | ||
|
|
ddc4d2f927 | ||
|
|
0beeb61f5c | ||
|
|
a321db9f48 | ||
|
|
827136fc8b | ||
|
|
088eea88e0 | ||
|
|
15503784c8 | ||
|
|
bc8e236598 | ||
|
|
909bd43e65 | ||
|
|
3330885bcc | ||
|
|
e1ac73718c | ||
|
|
a78eeb9b9c | ||
|
|
86b3e3ee13 | ||
|
|
4b2bc8e4ce | ||
|
|
f92aee204e | ||
|
|
7fd2b7965c | ||
|
|
32ba6e3e3f | ||
|
|
0a6e5e0ec1 | ||
|
|
65a4f86154 | ||
|
|
147c6e3600 | ||
|
|
ee6f1a010c | ||
|
|
a444ea7361 | ||
|
|
59b809012f | ||
|
|
c037a8b8fa | ||
|
|
ce15cf6065 | ||
|
|
04340b3a62 | ||
|
|
ef7a6bb246 | ||
|
|
bc20710c6d | ||
|
|
a63490a23b | ||
|
|
a3799b3053 | ||
|
|
ea5d6780f2 | ||
|
|
62ac9bb7cd | ||
|
|
86a658b891 | ||
|
|
536628ad95 | ||
|
|
2c7db0122d | ||
|
|
ade2901259 | ||
|
|
d180373ec1 | ||
|
|
c2a65d8fac | ||
|
|
8e6bc13540 | ||
|
|
152421e288 | ||
|
|
72a8bbb874 | ||
|
|
b8d2d38bd1 | ||
|
|
9f6ef92f0b | ||
|
|
9e60c107ca | ||
|
|
2179f83d63 | ||
|
|
b259095899 | ||
|
|
145ace0fa1 | ||
|
|
7d3db11a5c | ||
|
|
8725656fd2 | ||
|
|
6394b4a9a3 | ||
|
|
d0b3dd888b | ||
|
|
849bc6e3aa | ||
|
|
3d7a9d79da | ||
|
|
f7cc9517ba | ||
|
|
73305feb5b | ||
|
|
950cd5d996 | ||
|
|
b53bd8c525 | ||
|
|
8b773a2b2e | ||
|
|
1e8806854d | ||
|
|
9d2d556200 | ||
|
|
7ecdcb3bc0 | ||
|
|
54488b1016 | ||
|
|
7c3326b662 | ||
|
|
745b16e4b4 | ||
|
|
a469fe44a1 | ||
|
|
b9fc59ca9f | ||
|
|
e005a123ba | ||
|
|
cd63212118 | ||
|
|
a9dd013daf | ||
|
|
01ba859567 | ||
|
|
173c9070c8 | ||
|
|
d37e8ede3b | ||
|
|
c77702279c | ||
|
|
ef0e1a81b9 | ||
|
|
88f62087fd | ||
|
|
4f89195702 | ||
|
|
ee22bbc85c | ||
|
|
66fae76af2 | ||
|
|
f0d1dbccf4 | ||
|
|
a78365faab | ||
|
|
e3fd766e9b | ||
|
|
c9c56ac600 | ||
|
|
f6da01cb96 | ||
|
|
fb8d9d8c40 | ||
|
|
87e8c16a90 | ||
|
|
99fe7b809a | ||
|
|
04e6e879a2 | ||
|
|
dda9c0057b | ||
|
|
cc1235d4aa | ||
|
|
8193416230 | ||
|
|
8863bd4e7d | ||
|
|
d23aa5e8e2 | ||
|
|
18b466ee52 | ||
|
|
e852971a13 | ||
|
|
fbe29bf4cd | ||
|
|
5748f50c1f | ||
|
|
1b3a7feb67 | ||
|
|
d68bd876c1 | ||
|
|
c50ac55892 | ||
|
|
b2dd4e1c2b | ||
|
|
ff2ba240c9 | ||
|
|
96084355f0 | ||
|
|
25a380d023 | ||
|
|
3cb42de931 | ||
|
|
8dd1d95913 | ||
|
|
0ee2390c7f | ||
|
|
52db9558b3 | ||
|
|
0fbfbc86d2 | ||
|
|
c7432834d0 | ||
|
|
a971fae81f | ||
|
|
a58a2eec53 | ||
|
|
f43721ec92 | ||
|
|
59aa347912 | ||
|
|
1dd1d36120 | ||
|
|
545b206076 | ||
|
|
cf77487c00 | ||
|
|
9d8b755c07 | ||
|
|
bd88b079ea | ||
|
|
27b13b82f5 | ||
|
|
79c8412660 | ||
|
|
a078dde241 | ||
|
|
7e4e96c440 | ||
|
|
94f129d632 | ||
|
|
678111ed3b | ||
|
|
c1036d6f88 | ||
|
|
e8af0e859e | ||
|
|
a0f6d7444a | ||
|
|
eb89208abb | ||
|
|
af94f0f979 | ||
|
|
025a54c462 | ||
|
|
334a709cc6 | ||
|
|
5f25e2ce82 | ||
|
|
04d0f575b7 | ||
|
|
e9683b326a | ||
|
|
cb40db9555 | ||
|
|
39221c8d1f | ||
|
|
a5467d60ea | ||
|
|
d582ec02b1 | ||
|
|
59cdbdc492 | ||
|
|
01706ccf5c | ||
|
|
6c49a4ba34 | ||
|
|
e1f25b44d2 | ||
|
|
f6cafa3290 | ||
|
|
53d4a5268b | ||
|
|
cf88f4b6f8 | ||
|
|
ac8d8d91f7 | ||
|
|
842291124c | ||
|
|
6f5b3c47b0 | ||
|
|
b25642b889 | ||
|
|
7bde19d842 | ||
|
|
eb1ba11d60 | ||
|
|
23b3073687 | ||
|
|
3cd187dced | ||
|
|
6791af8c2c | ||
|
|
e566fbb009 | ||
|
|
e5c92912fc | ||
|
|
f33d5b0a38 | ||
|
|
df10618a7e | ||
|
|
6030349a6f | ||
|
|
6629bf50ae | ||
|
|
e32ce82179 | ||
|
|
10ea894186 | ||
|
|
81d12c0586 | ||
|
|
0b88bef157 | ||
|
|
2b8942026c | ||
|
|
f5937a5a9b | ||
|
|
04f0ac1aad | ||
|
|
4a481acca6 | ||
|
|
de62bd3ba5 | ||
|
|
ab2ea28ed9 | ||
|
|
96f29cefeb | ||
|
|
6f950ea45d | ||
|
|
99c45bd4d2 | ||
|
|
312030f275 | ||
|
|
bed9ccadbc | ||
|
|
d55499eba0 | ||
|
|
910b75c6cc | ||
|
|
6a11464d60 |
@@ -29,3 +29,4 @@ web/node_modules/
|
|||||||
web/coverage/
|
web/coverage/
|
||||||
web/.svelte-kit
|
web/.svelte-kit
|
||||||
web/build/
|
web/build/
|
||||||
|
web/.env
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
title: "[Feature] <feature-name-goes-here>"
|
title: "[Feature] feature-name-goes-here"
|
||||||
labels: ["feature"]
|
labels: ["feature"]
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please use this form to request new feature for Immich
|
Please use this form to request new feature for Immich.
|
||||||
|
Stick to only a single feature per request. If you list multiple different features at once,
|
||||||
|
your request will be closed.
|
||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
6
.github/workflows/cli.yml
vendored
6
.github/workflows/cli.yml
vendored
@@ -56,10 +56,10 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.3.0
|
uses: docker/setup-buildx-action@v3.5.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.2.0
|
uses: docker/build-push-action@v6.5.0
|
||||||
with:
|
with:
|
||||||
file: cli/Dockerfile
|
file: cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -63,10 +63,10 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.3.0
|
uses: docker/setup-buildx-action@v3.5.0
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
# Only push to Docker Hub when making a release
|
# Only push to Docker Hub when making a release
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6.2.0
|
uses: docker/build-push-action@v6.5.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.context }}
|
context: ${{ matrix.context }}
|
||||||
file: ${{ matrix.file }}
|
file: ${{ matrix.file }}
|
||||||
|
|||||||
@@ -131,4 +131,4 @@ conduct enforcement ladder](https://github.com/mozilla/diversity).
|
|||||||
|
|
||||||
For answers to common questions about this code of conduct, see the
|
For answers to common questions about this code of conduct, see the
|
||||||
FAQ at https://www.contributor-covenant.org/faq. Translations are
|
FAQ at https://www.contributor-covenant.org/faq. Translations are
|
||||||
available at https://www.contributor-covenant.org/translations.
|
available at https://www.contributor-covenant.org/translations.
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
- [Roadmap](https://immich.app/roadmap)
|
- [Roadmap](https://immich.app/roadmap)
|
||||||
- [Demo](#demo)
|
- [Demo](#demo)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Translations](https://immich.app/docs/developer/tranlations)
|
- [Translations](https://immich.app/docs/developer/translations)
|
||||||
- [Contributing](https://immich.app/docs/overview/support-the-project)
|
- [Contributing](https://immich.app/docs/overview/support-the-project)
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15
|
20.16.0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:20.15.0-alpine3.20@sha256:df01469346db2bf1cfc1f7261aeab86b2960efa840fe2bd46d83ff339f463665 as core
|
FROM node:20.16.0-alpine3.20@sha256:eb8101caae9ac02229bd64c024919fe3d4504ff7f329da79ca60a04db08cef52 as core
|
||||||
|
|
||||||
WORKDIR /usr/src/open-api/typescript-sdk
|
WORKDIR /usr/src/open-api/typescript-sdk
|
||||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||||
|
|||||||
235
cli/package-lock.json
generated
235
cli/package-lock.json
generated
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.6",
|
"version": "2.2.13",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.6",
|
"version": "2.2.13",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
|
"fastq": "^1.17.1",
|
||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -21,11 +22,11 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^1.2.2",
|
||||||
"byte-size": "^8.1.1",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -34,11 +35,12 @@
|
|||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^54.0.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.2.2",
|
"vitest": "^1.2.2",
|
||||||
|
"vitest-fetch-mock": "^0.2.2",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -47,14 +49,14 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.107.1",
|
"version": "1.111.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.12",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1122,10 +1124,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/cli-progress": {
|
"node_modules/@types/cli-progress": {
|
||||||
"version": "3.11.5",
|
"version": "3.11.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz",
|
||||||
"integrity": "sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==",
|
"integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -1161,9 +1164,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.9",
|
"version": "20.14.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
|
||||||
"integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==",
|
"integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1177,17 +1180,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz",
|
||||||
"integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==",
|
"integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "7.14.1",
|
"@typescript-eslint/scope-manager": "7.16.0",
|
||||||
"@typescript-eslint/type-utils": "7.14.1",
|
"@typescript-eslint/type-utils": "7.16.0",
|
||||||
"@typescript-eslint/utils": "7.14.1",
|
"@typescript-eslint/utils": "7.16.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.14.1",
|
"@typescript-eslint/visitor-keys": "7.16.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@@ -1211,16 +1214,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz",
|
||||||
"integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==",
|
"integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "7.14.1",
|
"@typescript-eslint/scope-manager": "7.16.0",
|
||||||
"@typescript-eslint/types": "7.14.1",
|
"@typescript-eslint/types": "7.16.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.14.1",
|
"@typescript-eslint/typescript-estree": "7.16.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.14.1",
|
"@typescript-eslint/visitor-keys": "7.16.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1240,14 +1243,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz",
|
||||||
"integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==",
|
"integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.14.1",
|
"@typescript-eslint/types": "7.16.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.14.1"
|
"@typescript-eslint/visitor-keys": "7.16.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
@@ -1258,14 +1261,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz",
|
||||||
"integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==",
|
"integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "7.14.1",
|
"@typescript-eslint/typescript-estree": "7.16.0",
|
||||||
"@typescript-eslint/utils": "7.14.1",
|
"@typescript-eslint/utils": "7.16.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@@ -1286,9 +1289,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
|
||||||
"integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==",
|
"integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1300,14 +1303,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
|
||||||
"integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==",
|
"integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.14.1",
|
"@typescript-eslint/types": "7.16.0",
|
||||||
"@typescript-eslint/visitor-keys": "7.14.1",
|
"@typescript-eslint/visitor-keys": "7.16.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"globby": "^11.1.0",
|
"globby": "^11.1.0",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@@ -1329,16 +1332,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz",
|
||||||
"integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==",
|
"integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "7.14.1",
|
"@typescript-eslint/scope-manager": "7.16.0",
|
||||||
"@typescript-eslint/types": "7.14.1",
|
"@typescript-eslint/types": "7.16.0",
|
||||||
"@typescript-eslint/typescript-estree": "7.14.1"
|
"@typescript-eslint/typescript-estree": "7.16.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || >=20.0.0"
|
"node": "^18.18.0 || >=20.0.0"
|
||||||
@@ -1352,13 +1355,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "7.14.1",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
|
||||||
"integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==",
|
"integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "7.14.1",
|
"@typescript-eslint/types": "7.16.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1666,10 +1669,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/byte-size": {
|
"node_modules/byte-size": {
|
||||||
"version": "8.1.1",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/byte-size/-/byte-size-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.0.tgz",
|
||||||
"integrity": "sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==",
|
"integrity": "sha512-xrJ8Hki7eQ6xew55mM6TG9zHI852OoAHcPfduWWtR6yxk2upTuIZy13VioRBDyHReHDdbeDPifUboeNkK/sXXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.17"
|
"node": ">=12.17"
|
||||||
}
|
}
|
||||||
@@ -1853,6 +1857,15 @@
|
|||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
@@ -3146,6 +3159,26 @@
|
|||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||||
@@ -3369,10 +3402,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@@ -3406,9 +3440,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.38",
|
"version": "8.4.39",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3424,9 +3458,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3471,21 +3506,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "3.2.4",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
||||||
"integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==",
|
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@volar/vue-language-plugin-pug": "^1.0.4",
|
"@vue/language-plugin-pug": "^2.0.24",
|
||||||
"@volar/vue-typescript": "^1.0.4",
|
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
"typescript": ">=2.9"
|
"typescript": ">=2.9",
|
||||||
|
"vue-tsc": "^2.0.24"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@volar/vue-language-plugin-pug": {
|
"@vue/language-plugin-pug": {
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"@volar/vue-typescript": {
|
"vue-tsc": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4168,6 +4204,12 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||||
@@ -4240,9 +4282,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.2",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||||
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
|
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4315,14 +4357,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz",
|
||||||
"integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
|
"integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.39",
|
||||||
"rollup": "^4.13.0"
|
"rollup": "^4.13.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4476,6 +4518,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest-fetch-mock": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest-fetch-mock/-/vitest-fetch-mock-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "^3.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.14.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vitest": ">=0.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.6",
|
"version": "2.2.13",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^7.0.0",
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
"@vitest/coverage-v8": "^1.2.2",
|
"@vitest/coverage-v8": "^1.2.2",
|
||||||
"byte-size": "^8.1.1",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
@@ -31,11 +31,12 @@
|
|||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^54.0.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.2.2",
|
"vitest": "^1.2.2",
|
||||||
|
"vitest-fetch-mock": "^0.2.2",
|
||||||
"yaml": "^2.3.1"
|
"yaml": "^2.3.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -59,9 +60,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
|
"fastq": "^1.17.1",
|
||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.0"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import { platform } from 'node:os';
|
import * as fs from 'node:fs';
|
||||||
import { UploadOptionsDto, getAlbumName } from 'src/commands/asset';
|
import * as os from 'node:os';
|
||||||
import { describe, expect, it } from 'vitest';
|
import * as path from 'node:path';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
describe('Unit function tests', () => {
|
import { Action, checkBulkUpload, defaults, Reason } from '@immich/sdk';
|
||||||
|
import createFetchMock from 'vitest-fetch-mock';
|
||||||
|
|
||||||
|
import { checkForDuplicates, getAlbumName, uploadFiles, UploadOptionsDto } from './asset';
|
||||||
|
|
||||||
|
vi.mock('@immich/sdk');
|
||||||
|
|
||||||
|
describe('getAlbumName', () => {
|
||||||
it('should return a non-undefined value', () => {
|
it('should return a non-undefined value', () => {
|
||||||
if (platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
// This is meaningless for Unix systems.
|
// This is meaningless for Unix systems.
|
||||||
expect(getAlbumName(String.raw`D:\test\Filename.txt`, {} as UploadOptionsDto)).toBe('test');
|
expect(getAlbumName(String.raw`D:\test\Filename.txt`, {} as UploadOptionsDto)).toBe('test');
|
||||||
}
|
}
|
||||||
@@ -17,3 +25,177 @@ describe('Unit function tests', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('uploadFiles', () => {
|
||||||
|
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-'));
|
||||||
|
const testFilePath = path.join(testDir, 'test.png');
|
||||||
|
const testFileData = 'test';
|
||||||
|
const baseUrl = 'http://example.com';
|
||||||
|
const apiKey = 'key';
|
||||||
|
const retry = 3;
|
||||||
|
|
||||||
|
const fetchMocker = createFetchMock(vi);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Create a test file
|
||||||
|
fs.writeFileSync(testFilePath, testFileData);
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
vi.mocked(defaults).baseUrl = baseUrl;
|
||||||
|
vi.mocked(defaults).headers = { 'x-api-key': apiKey };
|
||||||
|
|
||||||
|
fetchMocker.enableMocks();
|
||||||
|
fetchMocker.resetMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns new assets when upload file is successful', async () => {
|
||||||
|
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([
|
||||||
|
{
|
||||||
|
filepath: testFilePath,
|
||||||
|
id: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns new assets when upload file retry is successful', async () => {
|
||||||
|
let counter = 0;
|
||||||
|
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
|
||||||
|
counter++;
|
||||||
|
if (counter < retry) {
|
||||||
|
throw new Error('Network error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([
|
||||||
|
{
|
||||||
|
filepath: testFilePath,
|
||||||
|
id: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns new assets when upload file retry is failed', async () => {
|
||||||
|
fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), () => {
|
||||||
|
throw new Error('Network error');
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('checkForDuplicates', () => {
|
||||||
|
const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-'));
|
||||||
|
const testFilePath = path.join(testDir, 'test.png');
|
||||||
|
const testFileData = 'test';
|
||||||
|
const testFileChecksum = 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'; // SHA1
|
||||||
|
const retry = 3;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Create a test file
|
||||||
|
fs.writeFileSync(testFilePath, testFileData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks duplicates', async () => {
|
||||||
|
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
action: Action.Accept,
|
||||||
|
id: testFilePath,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await checkForDuplicates([testFilePath], { concurrency: 1 });
|
||||||
|
|
||||||
|
expect(checkBulkUpload).toHaveBeenCalledWith({
|
||||||
|
assetBulkUploadCheckDto: {
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
checksum: testFileChecksum,
|
||||||
|
id: testFilePath,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns duplicates when check duplicates is rejected', async () => {
|
||||||
|
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
action: Action.Reject,
|
||||||
|
id: testFilePath,
|
||||||
|
assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
|
||||||
|
reason: Reason.Duplicate,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({
|
||||||
|
duplicates: [
|
||||||
|
{
|
||||||
|
filepath: testFilePath,
|
||||||
|
id: 'fc5621b1-86f6-44a1-9905-403e607df9f5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
newFiles: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns new assets when check duplicates is accepted', async () => {
|
||||||
|
vi.mocked(checkBulkUpload).mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
action: Action.Accept,
|
||||||
|
id: testFilePath,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({
|
||||||
|
duplicates: [],
|
||||||
|
newFiles: [testFilePath],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns results when check duplicates retry is successful', async () => {
|
||||||
|
let mocked = vi.mocked(checkBulkUpload);
|
||||||
|
for (let i = 1; i < retry; i++) {
|
||||||
|
mocked = mocked.mockRejectedValueOnce(new Error('Network error'));
|
||||||
|
}
|
||||||
|
mocked.mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
action: Action.Accept,
|
||||||
|
id: testFilePath,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({
|
||||||
|
duplicates: [],
|
||||||
|
newFiles: [testFilePath],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns results when check duplicates retry is failed', async () => {
|
||||||
|
vi.mocked(checkBulkUpload).mockRejectedValue(new Error('Network error'));
|
||||||
|
|
||||||
|
await expect(checkForDuplicates([testFilePath], { concurrency: 1 })).resolves.toEqual({
|
||||||
|
duplicates: [],
|
||||||
|
newFiles: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { chunk } from 'lodash-es';
|
|||||||
import { Stats, createReadStream } from 'node:fs';
|
import { Stats, createReadStream } from 'node:fs';
|
||||||
import { stat, unlink } from 'node:fs/promises';
|
import { stat, unlink } from 'node:fs/promises';
|
||||||
import path, { basename } from 'node:path';
|
import path, { basename } from 'node:path';
|
||||||
|
import { Queue } from 'src/queue';
|
||||||
import { BaseOptions, authenticate, crawl, sha1 } from 'src/utils';
|
import { BaseOptions, authenticate, crawl, sha1 } from 'src/utils';
|
||||||
|
|
||||||
const s = (count: number) => (count === 1 ? '' : 's');
|
const s = (count: number) => (count === 1 ? '' : 's');
|
||||||
@@ -83,7 +84,7 @@ const scan = async (pathsToCrawl: string[], options: UploadOptionsDto) => {
|
|||||||
return files;
|
return files;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForDuplicates = async (files: string[], { concurrency, skipHash }: UploadOptionsDto) => {
|
export const checkForDuplicates = async (files: string[], { concurrency, skipHash }: UploadOptionsDto) => {
|
||||||
if (skipHash) {
|
if (skipHash) {
|
||||||
console.log('Skipping hash check, assuming all files are new');
|
console.log('Skipping hash check, assuming all files are new');
|
||||||
return { newFiles: files, duplicates: [] };
|
return { newFiles: files, duplicates: [] };
|
||||||
@@ -99,32 +100,50 @@ const checkForDuplicates = async (files: string[], { concurrency, skipHash }: Up
|
|||||||
const newFiles: string[] = [];
|
const newFiles: string[] = [];
|
||||||
const duplicates: Asset[] = [];
|
const duplicates: Asset[] = [];
|
||||||
|
|
||||||
try {
|
const queue = new Queue<string[], AssetBulkUploadCheckResults>(
|
||||||
// TODO refactor into a queue
|
async (filepaths: string[]) => {
|
||||||
for (const items of chunk(files, concurrency)) {
|
const dto = await Promise.all(
|
||||||
const dto = await Promise.all(items.map(async (filepath) => ({ id: filepath, checksum: await sha1(filepath) })));
|
filepaths.map(async (filepath) => ({ id: filepath, checksum: await sha1(filepath) })),
|
||||||
const { results } = await checkBulkUpload({ assetBulkUploadCheckDto: { assets: dto } });
|
);
|
||||||
|
const response = await checkBulkUpload({ assetBulkUploadCheckDto: { assets: dto } });
|
||||||
for (const { id: filepath, assetId, action } of results as AssetBulkUploadCheckResults) {
|
const results = response.results as AssetBulkUploadCheckResults;
|
||||||
|
for (const { id: filepath, assetId, action } of results) {
|
||||||
if (action === Action.Accept) {
|
if (action === Action.Accept) {
|
||||||
newFiles.push(filepath);
|
newFiles.push(filepath);
|
||||||
} else {
|
} else {
|
||||||
// rejects are always duplicates
|
// rejects are always duplicates
|
||||||
duplicates.push({ id: assetId as string, filepath });
|
duplicates.push({ id: assetId as string, filepath });
|
||||||
}
|
}
|
||||||
progressBar.increment();
|
|
||||||
}
|
}
|
||||||
}
|
progressBar.increment(filepaths.length);
|
||||||
} finally {
|
return results;
|
||||||
progressBar.stop();
|
},
|
||||||
|
{ concurrency, retry: 3 },
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const items of chunk(files, concurrency)) {
|
||||||
|
await queue.push(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await queue.drained();
|
||||||
|
|
||||||
|
progressBar.stop();
|
||||||
|
|
||||||
console.log(`Found ${newFiles.length} new files and ${duplicates.length} duplicate${s(duplicates.length)}`);
|
console.log(`Found ${newFiles.length} new files and ${duplicates.length} duplicate${s(duplicates.length)}`);
|
||||||
|
|
||||||
|
// Report failures
|
||||||
|
const failedTasks = queue.tasks.filter((task) => task.status === 'failed');
|
||||||
|
if (failedTasks.length > 0) {
|
||||||
|
console.log(`Failed to verify ${failedTasks.length} file${s(failedTasks.length)}:`);
|
||||||
|
for (const task of failedTasks) {
|
||||||
|
console.log(`- ${task.data} - ${task.error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { newFiles, duplicates };
|
return { newFiles, duplicates };
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFiles = async (files: string[], { dryRun, concurrency }: UploadOptionsDto): Promise<Asset[]> => {
|
export const uploadFiles = async (files: string[], { dryRun, concurrency }: UploadOptionsDto): Promise<Asset[]> => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
console.log('All assets were already uploaded, nothing to do.');
|
console.log('All assets were already uploaded, nothing to do.');
|
||||||
return [];
|
return [];
|
||||||
@@ -158,37 +177,52 @@ const uploadFiles = async (files: string[], { dryRun, concurrency }: UploadOptio
|
|||||||
|
|
||||||
const newAssets: Asset[] = [];
|
const newAssets: Asset[] = [];
|
||||||
|
|
||||||
try {
|
const queue = new Queue<string, AssetMediaResponseDto>(
|
||||||
for (const items of chunk(files, concurrency)) {
|
async (filepath: string) => {
|
||||||
await Promise.all(
|
const stats = statsMap.get(filepath);
|
||||||
items.map(async (filepath) => {
|
if (!stats) {
|
||||||
const stats = statsMap.get(filepath) as Stats;
|
throw new Error(`Stats not found for ${filepath}`);
|
||||||
const response = await uploadFile(filepath, stats);
|
}
|
||||||
|
|
||||||
newAssets.push({ id: response.id, filepath });
|
const response = await uploadFile(filepath, stats);
|
||||||
|
newAssets.push({ id: response.id, filepath });
|
||||||
|
if (response.status === AssetMediaStatus.Duplicate) {
|
||||||
|
duplicateCount++;
|
||||||
|
duplicateSize += stats.size ?? 0;
|
||||||
|
} else {
|
||||||
|
successCount++;
|
||||||
|
successSize += stats.size ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.status === AssetMediaStatus.Duplicate) {
|
uploadProgress.update(successSize, { value_formatted: byteSize(successSize + duplicateSize) });
|
||||||
duplicateCount++;
|
|
||||||
duplicateSize += stats.size ?? 0;
|
|
||||||
} else {
|
|
||||||
successCount++;
|
|
||||||
successSize += stats.size ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProgress.update(successSize, { value_formatted: byteSize(successSize + duplicateSize) });
|
return response;
|
||||||
|
},
|
||||||
|
{ concurrency, retry: 3 },
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
for (const filepath of files) {
|
||||||
}),
|
await queue.push(filepath);
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
uploadProgress.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await queue.drained();
|
||||||
|
|
||||||
|
uploadProgress.stop();
|
||||||
|
|
||||||
console.log(`Successfully uploaded ${successCount} new asset${s(successCount)} (${byteSize(successSize)})`);
|
console.log(`Successfully uploaded ${successCount} new asset${s(successCount)} (${byteSize(successSize)})`);
|
||||||
if (duplicateCount > 0) {
|
if (duplicateCount > 0) {
|
||||||
console.log(`Skipped ${duplicateCount} duplicate asset${s(duplicateCount)} (${byteSize(duplicateSize)})`);
|
console.log(`Skipped ${duplicateCount} duplicate asset${s(duplicateCount)} (${byteSize(duplicateSize)})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Report failures
|
||||||
|
const failedTasks = queue.tasks.filter((task) => task.status === 'failed');
|
||||||
|
if (failedTasks.length > 0) {
|
||||||
|
console.log(`Failed to upload ${failedTasks.length} asset${s(failedTasks.length)}:`);
|
||||||
|
for (const task of failedTasks) {
|
||||||
|
console.log(`- ${task.data} - ${task.error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newAssets;
|
return newAssets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
131
cli/src/queue.ts
Normal file
131
cli/src/queue.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import * as fastq from 'fastq';
|
||||||
|
import { uniqueId } from 'lodash-es';
|
||||||
|
|
||||||
|
export type Task<T, R> = {
|
||||||
|
readonly id: string;
|
||||||
|
status: 'idle' | 'processing' | 'succeeded' | 'failed';
|
||||||
|
data: T;
|
||||||
|
error: unknown | undefined;
|
||||||
|
count: number;
|
||||||
|
// TODO: Could be useful to adding progress property.
|
||||||
|
// TODO: Could be useful to adding start_at/end_at/duration properties.
|
||||||
|
result: undefined | R;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QueueOptions = {
|
||||||
|
verbose?: boolean;
|
||||||
|
concurrency?: number;
|
||||||
|
retry?: number;
|
||||||
|
// TODO: Could be useful to adding timeout property for retry.
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComputedQueueOptions = Required<QueueOptions>;
|
||||||
|
|
||||||
|
export const defaultQueueOptions = {
|
||||||
|
concurrency: 1,
|
||||||
|
retry: 0,
|
||||||
|
verbose: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An in-memory queue that processes tasks in parallel with a given concurrency.
|
||||||
|
* @see {@link https://www.npmjs.com/package/fastq}
|
||||||
|
* @template T - The type of the worker task data.
|
||||||
|
* @template R - The type of the worker output data.
|
||||||
|
*/
|
||||||
|
export class Queue<T, R> {
|
||||||
|
private readonly queue: fastq.queueAsPromised<string, Task<T, R>>;
|
||||||
|
private readonly store = new Map<string, Task<T, R>>();
|
||||||
|
readonly options: ComputedQueueOptions;
|
||||||
|
readonly worker: (data: T) => Promise<R>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new queue.
|
||||||
|
* @param worker - The worker function that processes the task.
|
||||||
|
* @param options - The queue options.
|
||||||
|
*/
|
||||||
|
constructor(worker: (data: T) => Promise<R>, options?: QueueOptions) {
|
||||||
|
this.options = { ...defaultQueueOptions, ...options };
|
||||||
|
this.worker = worker;
|
||||||
|
this.store = new Map<string, Task<T, R>>();
|
||||||
|
this.queue = this.buildQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
get tasks(): Task<T, R>[] {
|
||||||
|
const tasks: Task<T, R>[] = [];
|
||||||
|
for (const task of this.store.values()) {
|
||||||
|
tasks.push(task);
|
||||||
|
}
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTask(id: string): Task<T, R> {
|
||||||
|
const task = this.store.get(id);
|
||||||
|
if (!task) {
|
||||||
|
throw new Error(`Task with id ${id} not found`);
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the queue to be empty.
|
||||||
|
* @returns Promise<void> - The returned Promise will be resolved when all tasks in the queue have been processed by a worker.
|
||||||
|
* This promise could be ignored as it will not lead to a `unhandledRejection`.
|
||||||
|
*/
|
||||||
|
async drained(): Promise<void> {
|
||||||
|
await this.queue.drain();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a task at the end of the queue.
|
||||||
|
* @see {@link https://www.npmjs.com/package/fastq}
|
||||||
|
* @param data
|
||||||
|
* @returns Promise<void> - A Promise that will be fulfilled (rejected) when the task is completed successfully (unsuccessfully).
|
||||||
|
* This promise could be ignored as it will not lead to a `unhandledRejection`.
|
||||||
|
*/
|
||||||
|
async push(data: T): Promise<Task<T, R>> {
|
||||||
|
const id = uniqueId();
|
||||||
|
const task: Task<T, R> = { id, status: 'idle', error: undefined, count: 0, data, result: undefined };
|
||||||
|
this.store.set(id, task);
|
||||||
|
return this.queue.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support more function delegation to fastq.
|
||||||
|
|
||||||
|
private buildQueue(): fastq.queueAsPromised<string, Task<T, R>> {
|
||||||
|
return fastq.promise((id: string) => {
|
||||||
|
const task = this.getTask(id);
|
||||||
|
return this.work(task);
|
||||||
|
}, this.options.concurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async work(task: Task<T, R>): Promise<Task<T, R>> {
|
||||||
|
task.count += 1;
|
||||||
|
task.error = undefined;
|
||||||
|
task.status = 'processing';
|
||||||
|
if (this.options.verbose) {
|
||||||
|
console.log('[task] processing:', task);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
task.result = await this.worker(task.data);
|
||||||
|
task.status = 'succeeded';
|
||||||
|
if (this.options.verbose) {
|
||||||
|
console.log('[task] succeeded:', task);
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
} catch (error) {
|
||||||
|
task.error = error;
|
||||||
|
task.status = 'failed';
|
||||||
|
if (this.options.verbose) {
|
||||||
|
console.log('[task] failed:', task);
|
||||||
|
}
|
||||||
|
if (this.options.retry > 0 && task.count < this.options.retry) {
|
||||||
|
if (this.options.verbose) {
|
||||||
|
console.log('[task] retry:', task);
|
||||||
|
}
|
||||||
|
return this.work(task);
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.36.0"
|
version = "4.38.0"
|
||||||
constraints = "4.36.0"
|
constraints = "4.38.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:00/Y+l17VV4RquGSfwDnYsGYzyf2ZmdQwUgeIzXC7eg=",
|
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
||||||
"h1:489GpKItA/VRIUA5S4+F8MsnurGVciRvUFyIV81MJTU=",
|
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
||||||
"h1:7cnczyKGj3+gvaJ0r5JIVWLXPbQfkHYejac76MJx+I8=",
|
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
||||||
"h1:8rmr1PjJc14Xmor2eEvo5/WBojylt1eYdx6VbSU3Ulo=",
|
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
||||||
"h1:HjgphNjtgny5tkcUAQoGgBdcuQ+0IyhL8yLsiBqWAP0=",
|
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
||||||
"h1:LH3umxdBnJcAyeVoBLVn+PC0F0CzN6v9UN6lb6CqQPE=",
|
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
||||||
"h1:Xx6WUD/zB8fM9SjkFx06Fgx2K7aGJIVvsJS2pwqALEM=",
|
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
||||||
"h1:YizL5YN9zQ8YkSR6V/G201YrCVdnkF9EUIK4lpROWiA=",
|
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
||||||
"h1:aPcXVGjYcCJdqvWSzc/dEjwj05LnbWZje8IanygVjcI=",
|
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
||||||
"h1:eKCvfashdCqfDcFGXE2gq+XxAURD5SzuaQ9Brs3zLos=",
|
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
||||||
"h1:gpKcBYkBcfn/uF1A8W7MD/OysMZW7EU4QVYvPEEnxGc=",
|
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
||||||
"h1:kCkcxZZnkKAnMz9scUQHb19d9/l9FPOHovAyrvtA618=",
|
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
||||||
"h1:t8mXXnICTeKqoD29uvyLFHVWMfMzTUrJuHje8lpI0zU=",
|
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
||||||
"h1:zjzavjIdLDGRYsWd3v0HJz6ul12Cewj9RW/cqAQ4DxI=",
|
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
||||||
"zh:02665712b3893307596b3caab99cf1f2502d5caca18e22d4b37bb535e628e102",
|
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
||||||
"zh:1514b0d3ef62934484ac471113ee68cddec0c21e56b4f710922741fe9b6e6fdf",
|
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
||||||
"zh:1fab4dfcecbcea13267b42e5ff05ba0692aa2dcb247b8e633fea0daf49feb156",
|
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
||||||
"zh:24d8367295fe1f1b2be37802aecb96edf32f743364663ffe781d1bb92438395d",
|
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
||||||
"zh:34e84e7940c99dcf65663cfd25afac22bf5c8a5ff2cd21900c67180d3a072be9",
|
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
||||||
"zh:3d71d63204a329acf1d1de8638f2c725243cb94cf444d2d7acde54b3d1ac1696",
|
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
||||||
"zh:57831ba88e779a762bcfa224ba9eac8bc22ef9cd70cd541d848b351e0ba6a75c",
|
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
||||||
"zh:6407560f2e548afcb4852c91efc664627a9ee565c31a9c81fc9ea1806fca0567",
|
|
||||||
"zh:738ddbc664d75f4859aa09444a27809bc398795a8ea8f5be8531040690287712",
|
|
||||||
"zh:841ca2b2d78b6f8d33ec3435bc090c5e04a3a7d85c80df11227a7ea00d36f6b1",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8b3d3d63354032ab9b2403c50728e9aa4e83c7367eaad2d18794221addeafc0f",
|
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
||||||
"zh:9e293443fe3127e488f540229983c1b9688268185f87567bb3d18e794697acd2",
|
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
||||||
"zh:b3a22439156e46461213db183e2e89569cd2e8d7cbcfc4b9f90469090e105807",
|
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
||||||
"zh:f430feb5d51891e84028459e57039045dea4f1f5fcf671161d8ac2d8f28763f3",
|
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
||||||
|
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
||||||
|
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
||||||
|
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.36.0"
|
version = "4.38.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,37 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.36.0"
|
version = "4.38.0"
|
||||||
constraints = "4.36.0"
|
constraints = "4.38.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:00/Y+l17VV4RquGSfwDnYsGYzyf2ZmdQwUgeIzXC7eg=",
|
"h1:+27KAHKHBDvv3dqyJv5vhtdKQZJzoZXoMqIyronlHNw=",
|
||||||
"h1:489GpKItA/VRIUA5S4+F8MsnurGVciRvUFyIV81MJTU=",
|
"h1:/uV9RgOUhkxElkHhWs8fs5ZbX9vj6RCBfP0oJO0JF30=",
|
||||||
"h1:7cnczyKGj3+gvaJ0r5JIVWLXPbQfkHYejac76MJx+I8=",
|
"h1:1DNAdMugJJOAWD/XYiZenYYZLy7fw2ctjT4YZmkRCVQ=",
|
||||||
"h1:8rmr1PjJc14Xmor2eEvo5/WBojylt1eYdx6VbSU3Ulo=",
|
"h1:1wn4PmCLdT7mvd74JkCGmJDJxTQDkcxc+1jNbmwnMHA=",
|
||||||
"h1:HjgphNjtgny5tkcUAQoGgBdcuQ+0IyhL8yLsiBqWAP0=",
|
"h1:BIHB4fBxHg2bA9KbL92njhyctxKC8b6hNDp60y5QBss=",
|
||||||
"h1:LH3umxdBnJcAyeVoBLVn+PC0F0CzN6v9UN6lb6CqQPE=",
|
"h1:HCQpvKPsMsR4HO5eDqt+Kao7T7CYeEH7KZIO7xMcC6M=",
|
||||||
"h1:Xx6WUD/zB8fM9SjkFx06Fgx2K7aGJIVvsJS2pwqALEM=",
|
"h1:HTomuzocukpNLwtWzeSF3yteCVsyVKbwKmN66u9iPac=",
|
||||||
"h1:YizL5YN9zQ8YkSR6V/G201YrCVdnkF9EUIK4lpROWiA=",
|
"h1:YDxsUBhBAwHSXLzVwrSlSBOwv1NvLyry7s5SfCV7VqQ=",
|
||||||
"h1:aPcXVGjYcCJdqvWSzc/dEjwj05LnbWZje8IanygVjcI=",
|
"h1:dchVhxo+Acd1l2RuZ88tW9lWj4422QMfgtxKvKCjYrw=",
|
||||||
"h1:eKCvfashdCqfDcFGXE2gq+XxAURD5SzuaQ9Brs3zLos=",
|
"h1:eypa+P4ZpsEGMPFuCE+6VkRefu0TZRFmVBOpK+PDOPY=",
|
||||||
"h1:gpKcBYkBcfn/uF1A8W7MD/OysMZW7EU4QVYvPEEnxGc=",
|
"h1:f3yjse2OsRZj7ZhR7BLintJMlI4fpyt8HyDP/zcEavw=",
|
||||||
"h1:kCkcxZZnkKAnMz9scUQHb19d9/l9FPOHovAyrvtA618=",
|
"h1:mSJ7xj8K+xcnEmGg7lH0jjzyQb157wH94ULTAlIV+HQ=",
|
||||||
"h1:t8mXXnICTeKqoD29uvyLFHVWMfMzTUrJuHje8lpI0zU=",
|
"h1:tt+2J2Ze8VIdDq2Hr6uHlTJzAMBRpErBwTYx0uD5ilE=",
|
||||||
"h1:zjzavjIdLDGRYsWd3v0HJz6ul12Cewj9RW/cqAQ4DxI=",
|
"h1:uQW8SKxmulqrAisO+365mIf2FueINAp5PY28bqCPCug=",
|
||||||
"zh:02665712b3893307596b3caab99cf1f2502d5caca18e22d4b37bb535e628e102",
|
"zh:171ab67cccceead4514fafb2d39e4e708a90cce79000aaf3c29aab7ed4457071",
|
||||||
"zh:1514b0d3ef62934484ac471113ee68cddec0c21e56b4f710922741fe9b6e6fdf",
|
"zh:18aa7228447baaaefc49a43e8eff970817a7491a63d8937e796357a3829dd979",
|
||||||
"zh:1fab4dfcecbcea13267b42e5ff05ba0692aa2dcb247b8e633fea0daf49feb156",
|
"zh:2cbaab6092e81ba6f41fa60a50f14e980c8ec327ee11d0b21f16a478be4b7567",
|
||||||
"zh:24d8367295fe1f1b2be37802aecb96edf32f743364663ffe781d1bb92438395d",
|
"zh:53b8e49c06f5b31a8c681f8c0669cf43e78abe71657b8182a221d096bb514965",
|
||||||
"zh:34e84e7940c99dcf65663cfd25afac22bf5c8a5ff2cd21900c67180d3a072be9",
|
"zh:6037cfc60b4b647aabae155fcb46d649ed7c650e0287f05db52b2068f1e27c8a",
|
||||||
"zh:3d71d63204a329acf1d1de8638f2c725243cb94cf444d2d7acde54b3d1ac1696",
|
"zh:62460982ce1a869eebfca675603fbbd50416cf6b69459fb855bfbe5ae2b97607",
|
||||||
"zh:57831ba88e779a762bcfa224ba9eac8bc22ef9cd70cd541d848b351e0ba6a75c",
|
"zh:65f6f3a8470917b6398baa5eb4f74b3932b213eac7c0202798bfad6fd1ee17df",
|
||||||
"zh:6407560f2e548afcb4852c91efc664627a9ee565c31a9c81fc9ea1806fca0567",
|
|
||||||
"zh:738ddbc664d75f4859aa09444a27809bc398795a8ea8f5be8531040690287712",
|
|
||||||
"zh:841ca2b2d78b6f8d33ec3435bc090c5e04a3a7d85c80df11227a7ea00d36f6b1",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:8b3d3d63354032ab9b2403c50728e9aa4e83c7367eaad2d18794221addeafc0f",
|
"zh:8b5cebe64bf04105a49178a165b6a8800a9a33bae6767143a47fe4977755f805",
|
||||||
"zh:9e293443fe3127e488f540229983c1b9688268185f87567bb3d18e794697acd2",
|
"zh:a5596635db0993ee3c3060fbc2227d91b239466e96d2d82642625a5aa2486988",
|
||||||
"zh:b3a22439156e46461213db183e2e89569cd2e8d7cbcfc4b9f90469090e105807",
|
"zh:b3a9c63038441f13c311fd4b2c7e69e571445e5a7365a20c7cc9046b7e6c8aba",
|
||||||
"zh:f430feb5d51891e84028459e57039045dea4f1f5fcf671161d8ac2d8f28763f3",
|
"zh:b585e7e4d7648a540b14b9182819214896ca9337729eeb1f2034833b17db754d",
|
||||||
|
"zh:d2c3c545318ac8542369e9fc8228e29ee585febdf203a450fad3e0eded71ce02",
|
||||||
|
"zh:e95dd2d6c3525073af47d47b763cb81b6a51b20cabf76f789c69328922da9ecf",
|
||||||
|
"zh:eee6e590b36d6c6168a7daae8afa74a8721fd7aa9f62a710f04a311975100722",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.36.0"
|
version = "4.38.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
|
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
- DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
|
- DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
|
||||||
|
ports:
|
||||||
|
- 3003:3003
|
||||||
volumes:
|
volumes:
|
||||||
- model-cache:/cache
|
- model-cache:/cache
|
||||||
env_file:
|
env_file:
|
||||||
@@ -41,7 +43,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
|
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
@@ -73,7 +75,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:075b1ba2c4ebb04bc3a6ab86c06ec8d8099f8fda1c96ef6d104d9bb1def1d8bc
|
image: prom/prometheus@sha256:f20d3127bf2876f4a1df76246fca576b41ddf1125ed1c546fbd8b16ea55117e6
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ services:
|
|||||||
# file: hwaccel.transcoding.yml
|
# file: hwaccel.transcoding.yml
|
||||||
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
|
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
|
||||||
volumes:
|
volumes:
|
||||||
|
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
env_file:
|
env_file:
|
||||||
@@ -43,7 +44,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
|
image: docker.io/redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
@@ -57,6 +58,7 @@ services:
|
|||||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||||
volumes:
|
volumes:
|
||||||
|
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
|
||||||
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
|
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15
|
20.16.0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: The Immich core team goes full-time
|
title: The Immich core team goes full-time
|
||||||
authors: [alextran]
|
authors: [alextran]
|
||||||
tags: [update, announcement, futo]
|
tags: [update, announcement, FUTO]
|
||||||
date: 2024-05-01T00:00
|
date: 2024-05-01T00:00
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
91
docs/blog/2024/immich-licensing.mdx
Normal file
91
docs/blog/2024/immich-licensing.mdx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
title: Licensing announcement - Purchase a license to support Immich
|
||||||
|
authors: [alextran]
|
||||||
|
tags: [update, announcement, FUTO]
|
||||||
|
date: 2024-07-18T00:00
|
||||||
|
---
|
||||||
|
|
||||||
|
Hello everybody,
|
||||||
|
|
||||||
|
Firstly, on behalf of the Immich team, I'd like to thank everybody for your continuous support of Immich since the very first day! Your contributions, encouragement, and community engagement have helped bring Immich to its current state. The team and I are forever grateful for that.
|
||||||
|
|
||||||
|
Since our [last announcement of the core team joining FUTO to work on Immich full-time](https://immich.app/blog/2024/immich-core-team-goes-fulltime), one of the goals of our new position is to foster a healthy relationship between the developers and the users. We believe that this enables us to create great software, establish transparent policies and build trust.
|
||||||
|
|
||||||
|
We want to build a great software application that brings value to you and your loved ones' lives. We are not using you as a product, i.e., selling or tracking your data. We are not putting annoying ads into our software. We respect your privacy. We want to be compensated for the hard work we put in to build Immich for you.
|
||||||
|
|
||||||
|
With those notes, we have enabled a way for you to financially support the continued development of Immich, ensuring the software can move forward and will be maintained, by offering a lifetime license of the software. We think if you like and use software, you should pay for it, but _we're never going to force anyone to pay or try to limit Immich for those who don't._
|
||||||
|
|
||||||
|
There are two types of license that you can choose to purchase: **Server License** and **Individual License**.
|
||||||
|
|
||||||
|
### Server License
|
||||||
|
|
||||||
|
This is a lifetime license costing **$99.99**. The license is applied to the whole server. You and all users that use your server are licensed.
|
||||||
|
|
||||||
|
### Individual License
|
||||||
|
|
||||||
|
This is a lifetime license costing **$24.99**. The license is applied to a single user, and can be used on any server they choose to connect to.
|
||||||
|
|
||||||
|
<img
|
||||||
|
width="837"
|
||||||
|
alt="license-social-gh"
|
||||||
|
src="https://github.com/user-attachments/assets/241932ed-ef3b-44ec-a9e2-ee80754e0cca"
|
||||||
|
/>
|
||||||
|
|
||||||
|
You can purchase the license on [our page - https://buy.immich.app](https://buy.immich.app).
|
||||||
|
|
||||||
|
Starting with release `v1.109.0` you can purchase and enter your purchased license key directly in the app.
|
||||||
|
|
||||||
|
<img
|
||||||
|
width="1414"
|
||||||
|
alt="license-page-gh"
|
||||||
|
src="https://github.com/user-attachments/assets/364fc32a-f6ef-4594-9fea-28d5a26ad77c"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Thank you
|
||||||
|
|
||||||
|
Thank you again for your support, this will help create a strong foundation and stability for the Immich team to continue developing and maintaining the project that you love to use.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img
|
||||||
|
src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjY2eWc5Y2F0ZW56MmR4aWE0dDhzZXlidXRmYWZyajl1bWZidXZpcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/87CKDqErVfMqY/giphy.gif"
|
||||||
|
width="550"
|
||||||
|
title="SUPPORT THE PROJECT!"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
Cheers! 🎉
|
||||||
|
|
||||||
|
Immich team
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
### 1. Where can I purchase a license?
|
||||||
|
|
||||||
|
There are several places where you can purchase the license from
|
||||||
|
|
||||||
|
- [https://buy.immich.app](https://buy.immich.app)
|
||||||
|
- [https://pay.futo.org](https://pay.futo.org/)
|
||||||
|
- or directly from the app.
|
||||||
|
|
||||||
|
### 2. Do I need both _Individual License_ and _Server License_?
|
||||||
|
|
||||||
|
No,
|
||||||
|
|
||||||
|
If you are the admin and the sole user, or your instance has less than a total of 4 users, you can buy the **Individual License** for each user.
|
||||||
|
|
||||||
|
If your instance has more than 4 users, it is more cost-effective to buy the **Server License**, which will license all the users on your instance.
|
||||||
|
|
||||||
|
### 3. What do I do if I don't pay?
|
||||||
|
|
||||||
|
You can continue using Immich without any restriction.
|
||||||
|
|
||||||
|
### 4. Will there be any paywalled features?
|
||||||
|
|
||||||
|
No, there will never be any paywalled features.
|
||||||
|
|
||||||
|
### 5. Where can I get support regarding payment issues?
|
||||||
|
|
||||||
|
You can email us with your `orderId` and your email address `billing@futo.org` or on our Discord server.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Immich Update - July 2024
|
title: Immich Update - July 2024
|
||||||
authors: [alextran]
|
authors: [alextran]
|
||||||
|
date: 2024-07-01T00:00
|
||||||
tags: [update, v1.106.0]
|
tags: [update, v1.106.0]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ Immich uses CLIP models. For more information about CLIP and its capabilities, r
|
|||||||
|
|
||||||
### How does facial recognition work?
|
### How does facial recognition work?
|
||||||
|
|
||||||
For face detection and recognition, Immich uses [InsightFace models](https://github.com/deepinsight/insightface/tree/master/model_zoo).
|
See [How Facial Recognition Works](/docs/features/facial-recognition#How-Facial-Recognition-Works) for details.
|
||||||
|
|
||||||
### How can I disable machine learning?
|
### How can I disable machine learning?
|
||||||
|
|
||||||
@@ -181,19 +181,15 @@ However, disabling all jobs will not disable the machine learning service itself
|
|||||||
|
|
||||||
### I'm getting errors about models being corrupt or failing to download. What do I do?
|
### I'm getting errors about models being corrupt or failing to download. What do I do?
|
||||||
|
|
||||||
You can delete the model cache volume, where models are downloaded. This will give the service a clean environment to download the model again. If models are failing to download entirely, you can manually download them from [Huggingface][huggingface] and place them in the cache folder.
|
You can delete the model cache volume, where models are downloaded. This will give the service a clean environment to download the model again. If models are failing to download entirely, you can manually download them from [Hugging Face][huggingface] and place them in the cache folder.
|
||||||
|
|
||||||
### Can I use a custom CLIP model?
|
### Can I use a custom CLIP model?
|
||||||
|
|
||||||
No, this is not supported. Only models listed in the [Huggingface][huggingface] page are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added.
|
No, this is not supported. Only models listed in the [Hugging Face][huggingface] page are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added.
|
||||||
|
|
||||||
### I want to be able to search in other languages besides English. How can I do that?
|
### I want to be able to search in other languages besides English. How can I do that?
|
||||||
|
|
||||||
You can change to a multilingual model listed [here](https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7) by going to Administration > Machine Learning Settings > Smart Search and replacing the name of the model. Be sure to re-run Smart Search on all assets after this change. You can then search in over 100 languages.
|
You can change to a multilingual CLIP model. See [here](/docs/features/smart-search#CLIP-model) for instructions.
|
||||||
|
|
||||||
:::note
|
|
||||||
Feel free to make a feature request if there's a model you want to use that isn't in [Immich Huggingface list][huggingface].
|
|
||||||
:::
|
|
||||||
|
|
||||||
### Does Immich support Facial Recognition for videos?
|
### Does Immich support Facial Recognition for videos?
|
||||||
|
|
||||||
@@ -234,7 +230,7 @@ ls clip/ facial-recognition/
|
|||||||
|
|
||||||
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
### Why is Immich slow on low-memory systems like the Raspberry Pi?
|
||||||
|
|
||||||
Immich optionally uses machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#can-i-lower-cpu-and-ram-usage) this or host Immich's machine-learning container on a [more powerful system](/docs/guides/remote-machine-learning), or [disable](/docs/FAQ#how-can-i-disable-machine-learning) machine learning entirely.
|
Immich optionally uses transcoding and machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#can-i-lower-cpu-and-ram-usage) this or host Immich's machine-learning container on a [more powerful system](/docs/guides/remote-machine-learning), or [disable](/docs/FAQ#how-can-i-disable-machine-learning) machine learning entirely.
|
||||||
|
|
||||||
### Can I lower CPU and RAM usage?
|
### Can I lower CPU and RAM usage?
|
||||||
|
|
||||||
@@ -243,10 +239,12 @@ The initial backup is the most intensive due to the number of jobs running. The
|
|||||||
- Lower the job concurrency for these jobs to 1.
|
- Lower the job concurrency for these jobs to 1.
|
||||||
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
|
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
|
||||||
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
|
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
|
||||||
- For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this.
|
- For facial recognition on new images to work properly, You must re-run the Face Detection job for all images after this.
|
||||||
|
- At the container level, you can [set resource constraints](/docs/FAQ#can-i-limit-cpu-and-ram-usage) to lower usage further.
|
||||||
|
- It's recommended to only apply these constraints _after_ taking some of the measures here for best performance.
|
||||||
- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning.
|
- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for instructions on how to disable machine learning.
|
||||||
|
|
||||||
### Can I limit the amount of CPU and RAM usage?
|
### Can I limit CPU and RAM usage?
|
||||||
|
|
||||||
By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. To limit this, you can add the following to the `docker-compose.yml` block of any containers that you want to have limited resources.
|
By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. To limit this, you can add the following to the `docker-compose.yml` block of any containers that you want to have limited resources.
|
||||||
|
|
||||||
@@ -266,6 +264,8 @@ deploy:
|
|||||||
</details>
|
</details>
|
||||||
For more details, you can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit).
|
For more details, you can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit).
|
||||||
|
|
||||||
|
Note that memory constraints work by terminating the container, so this can introduce instability if set too low.
|
||||||
|
|
||||||
### How can I boost machine learning speed?
|
### How can I boost machine learning speed?
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
@@ -275,21 +275,16 @@ This advice improves throughput, not latency. This is to say that it will make S
|
|||||||
You can increase throughput by increasing the job concurrency for machine learning jobs (Smart Search, Face Detection). With higher concurrency, the host will work on more assets in parallel. You can do this by navigating to Administration > Settings > Job Settings and increasing concurrency as needed.
|
You can increase throughput by increasing the job concurrency for machine learning jobs (Smart Search, Face Detection). With higher concurrency, the host will work on more assets in parallel. You can do this by navigating to Administration > Settings > Job Settings and increasing concurrency as needed.
|
||||||
|
|
||||||
:::danger
|
:::danger
|
||||||
On a normal machine, 2 or 3 concurrent jobs can probably max the CPU. Beyond this, note that storage speed and latency may quickly become the limiting factor; particularly when using HDDs.
|
On a normal machine, 2 or 3 concurrent jobs can probably max the CPU. Storage speed and latency can quickly become the limiting factor beyond this, particularly when using HDDs.
|
||||||
|
|
||||||
Do not exaggerate with the amount of jobs because you're probably thoroughly overloading the server.
|
The concurrency can be increased more comfortably with a GPU, but should still not be above 16 in most cases.
|
||||||
|
|
||||||
More details can be found [here](https://discord.com/channels/979116623879368755/994044917355663450/1174711719994605708)
|
Do not exaggerate with the job concurrency because you're probably thoroughly overloading the server.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Why is Immich using so much of my CPU?
|
### My server shows Server Status Offline | Version Unknown. What can I do?
|
||||||
|
|
||||||
When a large number of assets are uploaded to Immich, it makes sense that the CPU and RAM will be heavily used for machine learning work and creating image thumbnails.
|
You need to enable WebSockets on your reverse proxy.
|
||||||
Once this process is completed, the percentage of CPU usage will drop to around 3-5% usage
|
|
||||||
|
|
||||||
### My server shows Server Status Offline | Version Unknown what can I do?
|
|
||||||
|
|
||||||
You need to enable Websocket on your reverse proxy.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -149,9 +149,21 @@ for more info read the [release notes](https://github.com/immich-app/immich/rele
|
|||||||
- Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces.
|
- Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces.
|
||||||
- Stored in `UPLOAD_LOCATION/thumbs/<userID>`.
|
- Stored in `UPLOAD_LOCATION/thumbs/<userID>`.
|
||||||
- **Encoded Assets:**
|
- **Encoded Assets:**
|
||||||
|
|
||||||
- Videos that have been re-encoded from the original for wider compatibility. The original is not removed.
|
- Videos that have been re-encoded from the original for wider compatibility. The original is not removed.
|
||||||
- Stored in `UPLOAD_LOCATION/encoded-video/<userID>`.
|
- Stored in `UPLOAD_LOCATION/encoded-video/<userID>`.
|
||||||
|
|
||||||
|
- **Postgres**
|
||||||
|
|
||||||
|
- The Immich database containing all the information to allow the system to function properly.
|
||||||
|
**Note:** This folder will only appear to users who have made the changes mentioned in [v1.102.0](https://github.com/immich-app/immich/discussions/8930) (an optional, non-mandatory change) or who started with this version.
|
||||||
|
- Stored in `UPLOAD_LOCATION/postgres`.
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
A backup of this folder does not constitute a backup of your database!
|
||||||
|
Follow the instructions listed [here](/docs/administration/backup-and-restore#database) to learn how to perform a proper backup.
|
||||||
|
:::
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="Storage Template On" label="Storage Template On">
|
<TabItem value="Storage Template On" label="Storage Template On">
|
||||||
|
|
||||||
@@ -187,8 +199,19 @@ When you turn off the storage template engine, it will leave the assets in `UPLO
|
|||||||
- Files uploaded through mobile apps.
|
- Files uploaded through mobile apps.
|
||||||
- Temporarily located in `UPLOAD_LOCATION/upload/<userID>`.
|
- Temporarily located in `UPLOAD_LOCATION/upload/<userID>`.
|
||||||
- Transferred to `UPLOAD_LOCATION/library/<userID>` upon successful upload.
|
- Transferred to `UPLOAD_LOCATION/library/<userID>` upon successful upload.
|
||||||
|
- **Postgres**
|
||||||
|
|
||||||
|
- The Immich database containing all the information to allow the system to function properly.
|
||||||
|
**Note:** This folder will only appear to users who have made the changes mentioned in [v1.102.0](https://github.com/immich-app/immich/discussions/8930) (an optional, non-mandatory change) or who started with this version.
|
||||||
|
- Stored in `UPLOAD_LOCATION/postgres`.
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
A backup of this folder does not constitute a backup of your database!
|
||||||
|
Follow the instructions listed [here](/docs/administration/backup-and-restore#database) to learn how to perform a proper backup.
|
||||||
|
:::
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
:::danger
|
:::danger
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ This environment includes the services below. Additional details are available i
|
|||||||
- Web app - [`/web`](https://github.com/immich-app/immich/tree/main/web)
|
- Web app - [`/web`](https://github.com/immich-app/immich/tree/main/web)
|
||||||
- Machine learning - [`/machine-learning`](https://github.com/immich-app/immich/tree/main/machine-learning)
|
- Machine learning - [`/machine-learning`](https://github.com/immich-app/immich/tree/main/machine-learning)
|
||||||
- Redis
|
- Redis
|
||||||
- PostgreSQL development database with exposed port `5432` so you can use any database client to acess it
|
- PostgreSQL development database with exposed port `5432` so you can use any database client to access it
|
||||||
|
|
||||||
All the services are packaged to run as with single Docker Compose command.
|
All the services are packaged to run as with single Docker Compose command.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Immich recognizes faces in your photos and videos and groups them together. You can then assign names to the faces and search for them.
|
Immich recognizes faces in your photos and videos and groups them together into people. You can then assign names to these people and search for them.
|
||||||
|
|
||||||
The list of people is shown in the Explore page.
|
The list of people is shown in the Explore page.
|
||||||
|
|
||||||
@@ -18,13 +18,75 @@ The asset detail view will also show the faces that are recognized in the asset.
|
|||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
Additional actions you can do with a detected person are:
|
Additional actions you can do include:
|
||||||
|
|
||||||
- Change the feature face photo of the person
|
- Changing the feature photo of the person
|
||||||
- Set date of birth
|
- Setting a person's date of birth
|
||||||
- Merge two or more detected faces into one person
|
- Merging two or more detected faces into one person
|
||||||
- Hide face
|
- Hiding the faces of a person from the Explore page and detail view
|
||||||
|
- Assigning an unrecognized face to a person
|
||||||
|
|
||||||
It can be found from the app bar when you access the detail view of a person.
|
It can be found from the app bar when you access the detail view of a person.
|
||||||
|
|
||||||
<img src={require('./img/facial-recognition-4.png').default} title='Facial Recognition 4' width="70%"/>
|
<img src={require('./img/facial-recognition-4.png').default} title='Facial Recognition 4' width="70%"/>
|
||||||
|
|
||||||
|
## How Face Detection Works
|
||||||
|
|
||||||
|
Face detection sends the generated preview image to the machine learning service for processing. The service checks if it has the relevant model downloaded and downloads it if not. The image is decoded, pre-processed and passed to the face detection model (with hardware acceleration if configured). The bounding boxes and scores outputted from this model are used to crop and preprocess the image once again to be passed to a facial recognition model (also accelerated if configured). The embeddings from the recognition model, together with the bounding boxes and scores from the face detection model, are then sent back to the server to be added to the database. The embeddings in particular are indexed so they can be searched quickly during facial recognition clustering.
|
||||||
|
|
||||||
|
## How Facial Recognition Works
|
||||||
|
|
||||||
|
The facial recognition algorithm we use is derived from [DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok), a popular clustering algorithm. It essentially treats each detected face as a point in a graph and aims to group points that are close to each other.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
An important concept is whether something is a _core point_. A core point has a minimum number of points around it within a certain distance. A non-core point can only be assigned to a cluster if it can reach a core point; a non-core point can't be used to extend a cluster even if it's part of one. In Immich, the _Minimum Recognized Faces_ setting controls the threshold to be considered a core point.
|
||||||
|
:::
|
||||||
|
|
||||||
|
For each face, it looks around it to find other faces within a certain distance. Faces within this distance are considered similar, so it then checks if any of these faces are associated with a person.
|
||||||
|
|
||||||
|
If there is an existing person, it assigns the person of the most similar face to the face being processed.
|
||||||
|
|
||||||
|
If there is none, then it has to determine something from the DBSCAN algorithm: whether the face is a _core point_. If there are a certain number of similar faces (by default 3, including the face being considered), then this face is a core point. A new person is created for this face and the face is assigned to it. When other faces are processed, if they're similar to this face, they'll see that it has an associated person and can be assigned to that person.
|
||||||
|
|
||||||
|
However, if there aren't enough similar faces, no new person will be created. Instead, the face will wait for all the other faces to be processed to see if any matches that previously didn't have an associated person now do. If they do, then the face will be assigned to that person. If not, this face will be considered an outlier, such as a stranger in the background of an image.
|
||||||
|
|
||||||
|
The algorithm has some subtle differences compared to DBSCAN:
|
||||||
|
|
||||||
|
- DBSCAN doesn't have a concept of incremental clustering: it clusters all points at once. In contrast, facial recognition has to evolve as more assets are added without re-clustering everything each time.
|
||||||
|
- The algorithm described above works within a set of queued assets. Once these faces are processed and a new round of faces are detected, the behavior will not be the same as traditional DBSCAN since it preserves the clusters (people) generated from the previous round.
|
||||||
|
- Facial recognition tries to wait for face detection and thumbnail generation to complete before starting for this reason: the larger the set of faces in the queue, the better the results will be.
|
||||||
|
- Re-running facial recognition on all assets afterwards does behave like DBSCAN, however.
|
||||||
|
- DBSCAN is designed for range-based searches (i.e. points within a distance), but high-dimensional vector indices are generally optimized for getting the closest K results. The recognition algorithm doesn't try to get _all_ similar faces within a distance for performance reasons. Instead, it searches for a small number of matches for each face. The end result should be very similar if not identical, but with possibly different performance characteristics.
|
||||||
|
- Because of this, part of the recognition process is handled during a nightly job to ensure that unassigned faces with potential matches can be recognized.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
If you didn't import your assets at once or if the server was able to process jobs faster than you could upload them, it's possible that the clustering was suboptimal. If you haven't put effort into the current results, it may be worth re-running facial recognition on all assets for the best starting point. If it's too late for that, you can also manually assign a selection of unassigned faces and queue _Missing_ for Facial Recognition to help it learn and assign more faces automatically.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Navigating to Administration > Settings > Machine Learning Settings > Facial Recognition will show the options available.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
It's better to only tweak the parameters here than to set them to something very different unless you're ready to test a variety of options. If you do need to set a parameter to a strict setting, relaxing other settings can be a good option to compensate, and vice versa.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Facial recognition model
|
||||||
|
|
||||||
|
There are a few different models available; the default is typically considered the best. On more constrained systems where the default is too intensive, you can choose a smaller model instead.
|
||||||
|
|
||||||
|
### Minimum detection score
|
||||||
|
|
||||||
|
This setting affects whether a result from the face detecton model is filtered out as a false positive. It may seem tempting to set this low to detect more faces, but it can lead to false positives that are difficult to deal with and can harm facial recognition. It is strongly recommended not to go below 0.5 for this setting. Setting it to a very high number like 0.9 is also not recommended: the default is already biased toward precision, so a threshold that high leads to many undetected faces.
|
||||||
|
|
||||||
|
After changing this setting, it will only apply to new face detection jobs. To apply the new setting to all assets, you need to re-run face detection for all assets.
|
||||||
|
|
||||||
|
### Maximum recognition distance
|
||||||
|
|
||||||
|
The distance threshold described in How Facial Recognition Works. The default works well for most people, but it may be worth lowering it if the library has twins or otherwise very similar looking people. A threshold that's too low just means needing to merge duplicate people after facial recognition, whereas a threshold too high can produce unsalvageable results. It is strongly recommended not to go below 0.3 or above 0.7.
|
||||||
|
|
||||||
|
### Minimum recognized faces
|
||||||
|
|
||||||
|
The core point threshold described in How Facial Recognition Works. This setting has a few implications. First, it takes effect immediately in that people with fewer faces than this are hidden from view. Secondly, it makes clustering more robust as it prevents loosely-related faces from being linked to each other by requiring a certain level of density.
|
||||||
|
|
||||||
|
Increasing this setting is a good idea if you increase the recognition distance or reduce the minimum detection score. Setting it to 1 effectively disables the concept of core points, but can be an option if you prefer a more hands-on approach.
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ Once this is done, you can continue to step 3 of "Basic Setup".
|
|||||||
|
|
||||||
- You may want to choose a slower preset than for software transcoding to maintain quality and efficiency
|
- You may want to choose a slower preset than for software transcoding to maintain quality and efficiency
|
||||||
- While you can use VAAPI with NVIDIA and Intel devices, prefer the more specific APIs since they're more optimized for their respective devices
|
- While you can use VAAPI with NVIDIA and Intel devices, prefer the more specific APIs since they're more optimized for their respective devices
|
||||||
|
- You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for NVIDIA, `intel_gpu_top` for Intel, etc.) when transcoding. A lack of error logs when transcoding also indicates that it's being used.
|
||||||
|
|
||||||
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
|
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
|
||||||
[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||||
|
|||||||
@@ -104,18 +104,19 @@ The `immich-server` container will need access to the gallery. Modify your docke
|
|||||||
immich-server:
|
immich-server:
|
||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
|
+ - /mnt/nas/christmas-trip:/mnt/nas/christmas-trip:ro
|
||||||
+ - /home/user/old-pics:/mnt/media/old-pics:ro
|
+ - /home/user/old-pics:/home/user/old-pics:ro
|
||||||
+ - /mnt/media/videos:/mnt/media/videos:ro
|
+ - /mnt/media/videos:/mnt/media/videos:ro
|
||||||
|
+ - /mnt/media/videos2:/mnt/media/videos2 # the files in this folder can be deleted, as it does not end with :ro
|
||||||
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
|
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
The `ro` flag at the end only gives read-only access to the volumes. While Immich does not modify files, it's a good practice to mount read-only.
|
The `ro` flag at the end only gives read-only access to the volumes. This will disallow the images from being deleted in the web UI.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
_Remember to bring the container `docker compose down/up` to register the changes. Make sure you can see the mounted path in the container._
|
_Remember to run `docker compose up -d` to register the changes. Make sure you can see the mounted path in the container._
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Create External Libraries
|
### Create External Libraries
|
||||||
|
|||||||
@@ -32,12 +32,13 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
- Where and how you can get this file depends on device and vendor, but typically, the device vendor also supplies these
|
- Where and how you can get this file depends on device and vendor, but typically, the device vendor also supplies these
|
||||||
- The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere
|
- The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere
|
||||||
- The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file
|
- The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file
|
||||||
|
- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for ARM NN specific settings
|
||||||
|
|
||||||
#### CUDA
|
#### CUDA
|
||||||
|
|
||||||
- The GPU must have compute capability 5.2 or greater.
|
- The GPU must have compute capability 5.2 or greater.
|
||||||
- The server must have the official NVIDIA driver installed.
|
- The server must have the official NVIDIA driver installed.
|
||||||
- The installed driver must be >= 535 (it must support CUDA 12.2).
|
- The installed driver must be >= 545 (it must support CUDA 12.3.2).
|
||||||
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
|
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
|
||||||
|
|
||||||
#### OpenVINO
|
#### OpenVINO
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ The provided file is just a starting point. There are a ton of ways to configure
|
|||||||
After bringing down the containers with `docker compose down` and back up with `docker compose up -d`, a Prometheus instance will now collect metrics from the immich server and microservices containers. Note that we didn't need to expose any new ports for these containers - the communication is handled in the internal Docker network.
|
After bringing down the containers with `docker compose down` and back up with `docker compose up -d`, a Prometheus instance will now collect metrics from the immich server and microservices containers. Note that we didn't need to expose any new ports for these containers - the communication is handled in the internal Docker network.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8081` to the microservices container's ports. Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects.
|
To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8082` to the microservices container's ports. Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|||||||
@@ -7,29 +7,30 @@ Immich uses Postgres as its search database for both metadata and smart search.
|
|||||||
|
|
||||||
Smart search is powered by the [pgvecto.rs](https://github.com/tensorchord/pgvecto.rs) extension, utilizing machine learning models like [CLIP](https://openai.com/research/clip) to provide relevant search results. This allows for freeform searches without requiring specific keywords in the image or video metadata.
|
Smart search is powered by the [pgvecto.rs](https://github.com/tensorchord/pgvecto.rs) extension, utilizing machine learning models like [CLIP](https://openai.com/research/clip) to provide relevant search results. This allows for freeform searches without requiring specific keywords in the image or video metadata.
|
||||||
|
|
||||||
Archived photos are not included in search results by default. To include them, mark the checkbox in [advanced search filters](/docs/features/smart-search#advanced-search-filters).
|
|
||||||
|
|
||||||
:::tip Alternative CLIP Models
|
|
||||||
More powerful models can be used for more accurate search results. For more information, see the related [FAQ](/docs/FAQ#can-i-use-a-custom-clip-model).
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::info
|
|
||||||
Smart Search is currently limited to 5,000 results for a single search on the web.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Advanced Search Filters
|
## Advanced Search Filters
|
||||||
|
|
||||||
In addition, Immich offers advanced search functionality, allowing you to find specific content using customizable search filters. These filters include location, one or more faces, specific albums, and more. You can try out the search filters on the [Demo site](https://demo.immich.app).
|
In addition, Immich offers advanced search functionality, allowing you to find specific content using customizable search filters. These filters include location, one or more faces, specific albums, and more. You can try out the search filters on the [Demo site](https://demo.immich.app).
|
||||||
|
|
||||||
Smart search features include:
|
The filters smart search allows you to search by include:
|
||||||
|
|
||||||
- Search for one or more faces (with or without context search).
|
- People
|
||||||
- Search by Country or State or City or by all three.
|
- Location
|
||||||
- Search by camera make and model.
|
- Country
|
||||||
- Search by date range.
|
- State
|
||||||
- Search by file name.
|
- City
|
||||||
- Search by media types: image, video or all (**Note:** Image includes live images).
|
- Camera
|
||||||
- Search by condition: not in any album or archive or Favorite or all conditions.
|
- Make
|
||||||
|
- Model
|
||||||
|
- Date range
|
||||||
|
- File name or extension
|
||||||
|
- Media type
|
||||||
|
- Image (including live/motion photos)
|
||||||
|
- Video
|
||||||
|
- All
|
||||||
|
- Condition
|
||||||
|
- Not in any album
|
||||||
|
- Archived
|
||||||
|
- Favorited
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem value="Computer" label="Computer" default>
|
<TabItem value="Computer" label="Computer" default>
|
||||||
@@ -47,3 +48,27 @@ Some search examples:
|
|||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available.
|
||||||
|
|
||||||
|
### CLIP model
|
||||||
|
|
||||||
|
More powerful models can be used for more accurate search results, but are slower and can require more server resources. Check out the models [here][huggingface-clip] for more options!
|
||||||
|
|
||||||
|
[Multilingual models][huggingface-multilingual-clip] are also available so users can search in their native language. These models support over 100 languages; the `nllb` models in particular support 200.
|
||||||
|
:::note
|
||||||
|
Multilingual models are much slower and larger and perform slightly worse for English than English-only models. For this reason, only use them if you actually intend to search in a language besides English.
|
||||||
|
|
||||||
|
As a special case, the `ViT-H-14-quickgelu__dfn5b` and `ViT-H-14-378-quickgelu__dfn5b` models are excellent at many European languages despite not specifically being multilingual. They're very intensive regardless, however - especially the latter.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Once you've chosen a model, change this setting to the name of the model you chose. Be sure to re-run Smart Search on all assets after this change.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Feel free to make a feature request if there's a model you want to use that we don't currently support.
|
||||||
|
:::
|
||||||
|
|
||||||
|
[huggingface-clip]: https://huggingface.co/collections/immich-app/clip-654eaefb077425890874cd07
|
||||||
|
[huggingface-multilingual-clip]: https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ In our `.env` file, we will define variables that will help us in the future whe
|
|||||||
|
|
||||||
# Custom location where your uploaded, thumbnails, and transcoded video files are stored
|
# Custom location where your uploaded, thumbnails, and transcoded video files are stored
|
||||||
- UPLOAD_LOCATION=./library
|
- UPLOAD_LOCATION=./library
|
||||||
+ UPLOAD_LOCATION=/custom/location/on/your/system/immich/immich_files
|
+ UPLOAD_LOCATION=/custom/path/immich/immich_files
|
||||||
+ THUMB_LOCATION=/custom/location/on/your/system/immich/thumbs
|
+ THUMB_LOCATION=/custom/path/immich/thumbs
|
||||||
+ ENCODED_VIDEO_LOCATION=/custom/location/on/your/system/immich/encoded-video
|
+ ENCODED_VIDEO_LOCATION=/custom/path/immich/encoded-video
|
||||||
+ PROFILE_LOCATION=/custom/location/on/your/system/immich/profile
|
+ PROFILE_LOCATION=/custom/path/immich/profile
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` and `immich-microservices` containers.
|
After defining the locations for these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container.
|
||||||
|
|
||||||
```diff title="docker-compose.yml"
|
```diff title="docker-compose.yml"
|
||||||
services:
|
services:
|
||||||
@@ -29,16 +29,6 @@ services:
|
|||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
||||||
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
||||||
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
immich-microservices:
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
|
|
||||||
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
|
|
||||||
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
```
|
```
|
||||||
@@ -46,7 +36,6 @@ services:
|
|||||||
Restart Immich to register the changes.
|
Restart Immich to register the changes.
|
||||||
|
|
||||||
```
|
```
|
||||||
docker compose down
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Keep in mind that mucking around in the database might set the moon on fire. Avo
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Run `docker exec -it immich_postgres psql immich <DB_USERNAME>` to connect to the database via the container directly.
|
Run `docker exec -it immich_postgres psql --dbname=immich --username=<DB_USERNAME>` to connect to the database via the container directly.
|
||||||
|
|
||||||
(Replace `<DB_USERNAME>` with the value from your [`.env` file](/docs/install/environment-variables#database)).
|
(Replace `<DB_USERNAME>` with the value from your [`.env` file](/docs/install/environment-variables#database)).
|
||||||
:::
|
:::
|
||||||
@@ -106,3 +106,9 @@ SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
|
|||||||
```sql title="Delete person and unset it for the faces it was associated with"
|
```sql title="Delete person and unset it for the faces it was associated with"
|
||||||
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
|
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Postgres internal
|
||||||
|
|
||||||
|
```sql title="Change DB_PASSWORD"
|
||||||
|
ALTER USER <DB_USERNAME> WITH ENCRYPTED PASSWORD 'newpasswordhere';
|
||||||
|
```
|
||||||
|
|||||||
@@ -6,36 +6,18 @@ in a directory on the same machine.
|
|||||||
|
|
||||||
# Mount the directory into the containers.
|
# Mount the directory into the containers.
|
||||||
|
|
||||||
Edit `docker-compose.yml` to add two new mount points in the section `immich-server:` under `volumes:`
|
Edit `docker-compose.yml` to add one or more new mount points in the section `immich-server:` under `volumes:`.
|
||||||
|
If you want Immich to be able to delete the images in the external library, remove `:ro` from the end of the mount point.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
immich-server:
|
immich-server:
|
||||||
volumes:
|
volumes:
|
||||||
+ - ${EXTERNAL_PATH}:/usr/src/app/external
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
+ - /home/user/photos1:/home/user/photos1:ro
|
||||||
|
+ - /mnt/photos2:/mnt/photos2:ro # you can delete this line if you only have one mount point, or you can add more lines if you have more than two
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env` to define `EXTERNAL_PATH`, substituting in the correct path for your computer:
|
Restart Immich by running `docker compose up -d`.
|
||||||
|
|
||||||
```
|
|
||||||
EXTERNAL_PATH=<your-path-here>
|
|
||||||
```
|
|
||||||
|
|
||||||
On my computer, for example, I use this path:
|
|
||||||
|
|
||||||
```
|
|
||||||
EXTERNAL_PATH=/home/tenino/photos
|
|
||||||
```
|
|
||||||
|
|
||||||
:::info EXTERNAL_PATH design
|
|
||||||
The design choice to put the EXTERNAL_PATH into .env rather than put two copies of the absolute path in the yml file in order to make everything easier, so if you have two copies of the same path that have to be kept in sync, then someday later when you move the data, update only one of the paths, without everything will break mysteriously.
|
|
||||||
:::
|
|
||||||
|
|
||||||
Restart Immich.
|
|
||||||
|
|
||||||
```
|
|
||||||
docker compose down
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
# Create the library
|
# Create the library
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This page gives a few pointers on how to access your Immich instance from outsid
|
|||||||
You can read the [full discussion in Discord](https://discord.com/channels/979116623879368755/1122615710846308484)
|
You can read the [full discussion in Discord](https://discord.com/channels/979116623879368755/1122615710846308484)
|
||||||
|
|
||||||
:::danger
|
:::danger
|
||||||
Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you succeptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.
|
Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you susceptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Option 1: VPN to home network
|
## Option 1: VPN to home network
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ To alleviate [performance issues on low-memory systems](/docs/FAQ.mdx#why-is-imm
|
|||||||
|
|
||||||
- Set the URL in Machine Learning Settings on the Admin Settings page to point to the designated ML system, e.g. `http://workstation:3003`.
|
- Set the URL in Machine Learning Settings on the Admin Settings page to point to the designated ML system, e.g. `http://workstation:3003`.
|
||||||
- Copy the following `docker-compose.yml` to your ML system.
|
- Copy the following `docker-compose.yml` to your ML system.
|
||||||
|
- If using [hardware acceleration](/docs/features/ml-hardware-acceleration), the [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be added
|
||||||
- Start the container by running `docker compose up -d`.
|
- Start the container by running `docker compose up -d`.
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
Starting with version v1.93.0 face detection work and face recognize were split. From now on face detection is done in the immich_machine_learning container, but facial recognition is done in the `microservices` worker.
|
Smart Search and Face Detection will use this feature, but Facial Recognition is handled in the server.
|
||||||
:::
|
|
||||||
|
|
||||||
:::note
|
|
||||||
The [hwaccel.ml.yml](https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml) file also needs to be in the same folder if trying to use [hardware acceleration](/docs/features/ml-hardware-acceleration).
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -37,3 +34,7 @@ volumes:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Please note that version mismatches between both hosts may cause instabilities and bugs, so make sure to always perform updates together.
|
Please note that version mismatches between both hosts may cause instabilities and bugs, so make sure to always perform updates together.
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
As an internal service, the machine learning container has no security measures whatsoever. Please be mindful of where it's deployed and who can access it.
|
||||||
|
:::
|
||||||
|
|||||||
@@ -38,19 +38,19 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
|||||||
|
|
||||||
## General
|
## General
|
||||||
|
|
||||||
| Variable | Description | Default | Containers | Workers |
|
| Variable | Description | Default | Containers | Workers |
|
||||||
| :---------------------------------- | :---------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
| :---------------------------------- | :-------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
|
||||||
| `TZ` | Timezone | | server | microservices |
|
| `TZ` | Timezone | | server | microservices |
|
||||||
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, 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_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_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||||
| `IMMICH_CONFIG_FILE` | Path to config file | | 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 |
|
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
||||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
|
||||||
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
|
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
|
||||||
|
|
||||||
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
|
\*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.
|
It only need to be set if the Immich deployment method is changing.
|
||||||
@@ -157,18 +157,21 @@ Redis (Sentinel) URL example JSON before encoding:
|
|||||||
|
|
||||||
## Machine Learning
|
## Machine Learning
|
||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| Variable | Description | Default | Containers |
|
||||||
| :----------------------------------------------- | :------------------------------------------------------------------- | :-----------------: | :--------------- |
|
| :----------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------------: | :--------------- |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO image) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in cache | | machine learning |
|
||||||
|
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
|
|
||||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ sidebar_position: 20
|
|||||||
This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/docs/install/docker-compose.mdx).
|
This method is experimental and not currently recommended for production use. For production, please refer to installing with [Docker Compose](/docs/install/docker-compose.mdx).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::note
|
||||||
|
The install script only supports Linux operating systems and requires Docker to be already installed on the system.
|
||||||
|
:::
|
||||||
|
|
||||||
In the shell, from a directory of your choice, run the following command:
|
In the shell, from a directory of your choice, run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ const config = {
|
|||||||
alt: 'Immich Logo',
|
alt: 'Immich Logo',
|
||||||
src: 'img/immich-logo-inline-light.png',
|
src: 'img/immich-logo-inline-light.png',
|
||||||
srcDark: 'img/immich-logo-inline-dark.png',
|
srcDark: 'img/immich-logo-inline-dark.png',
|
||||||
|
className: 'rounded-none',
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
|||||||
22
docs/package-lock.json
generated
22
docs/package-lock.json
generated
@@ -12640,9 +12640,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@@ -12753,9 +12754,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.38",
|
"version": "8.4.39",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -12770,9 +12771,10 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -16374,9 +16376,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.5.2",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
||||||
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
|
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
|
|||||||
@@ -56,6 +56,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.0"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function Timeline({ items }: Props): JSX.Element {
|
|||||||
<div className="flex flex-col flex-grow justify-between gap-2">
|
<div className="flex flex-col flex-grow justify-between gap-2">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
{cardIcon === 'immich' ? (
|
{cardIcon === 'immich' ? (
|
||||||
<img src="img/immich-logo.svg" height="30" />
|
<img src="img/immich-logo.svg" height="30" className="rounded-none" />
|
||||||
) : (
|
) : (
|
||||||
<Icon path={cardIcon} size={1} color={item.iconColor} />
|
<Icon path={cardIcon} size={1} color={item.iconColor} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Overpass:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap');
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Snowburst+One&display=swap');
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
button {
|
button {
|
||||||
@@ -48,7 +47,3 @@ img {
|
|||||||
div[class^='announcementBar_'] {
|
div[class^='announcementBar_'] {
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar__brand .navbar__title {
|
|
||||||
@apply font-immich-title text-2xl font-normal text-immich-primary dark:text-immich-dark-primary;
|
|
||||||
}
|
|
||||||
|
|||||||
77
docs/src/pages/cursed-knowledge.tsx
Normal file
77
docs/src/pages/cursed-knowledge.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { mdiCalendarToday, mdiLeadPencil, mdiLockOutline, mdiSpeedometerSlow, mdiWeb } from '@mdi/js';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import React from 'react';
|
||||||
|
import { Item as TimelineItem, Timeline } from '../components/timeline';
|
||||||
|
|
||||||
|
const withLanguage = (date: Date) => (language: string) => date.toLocaleDateString(language);
|
||||||
|
|
||||||
|
type Item = Omit<TimelineItem, 'done' | 'getDateLabel'> & { date: Date };
|
||||||
|
|
||||||
|
const items: Item[] = [
|
||||||
|
{
|
||||||
|
icon: mdiLeadPencil,
|
||||||
|
iconColor: 'gold',
|
||||||
|
title: 'PostgreSQL NOTIFY is cursed',
|
||||||
|
description:
|
||||||
|
'PostgreSQL does everything in a transaction, including NOTIFY. This means using the socket.io postgres-adapter writes to WAL every 5 seconds.',
|
||||||
|
link: { url: 'https://github.com/immich-app/immich/pull/10801', text: '#10801' },
|
||||||
|
date: new Date(2024, 6, 3),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiWeb,
|
||||||
|
iconColor: 'lightskyblue',
|
||||||
|
title: 'npm scripts are cursed',
|
||||||
|
description:
|
||||||
|
'npm scripts make a http call to the npm registry each time they run, which means they are a terrible way to execute a health check.',
|
||||||
|
link: { url: 'https://github.com/immich-app/immich/issues/10796', text: '#10796' },
|
||||||
|
date: new Date(2024, 6, 3),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiSpeedometerSlow,
|
||||||
|
iconColor: 'brown',
|
||||||
|
title: '50 extra packages are cursed',
|
||||||
|
description:
|
||||||
|
'There is a user in the JavaScript community who goes around adding "backwards compatibility" to projects. They do this by adding 50 extra package dependencies to your project, which are maintained by them.',
|
||||||
|
link: { url: 'https://github.com/immich-app/immich/pull/10690', text: '#10690' },
|
||||||
|
date: new Date(2024, 5, 28),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiLockOutline,
|
||||||
|
iconColor: 'gold',
|
||||||
|
title: 'Long passwords are cursed',
|
||||||
|
description:
|
||||||
|
'The bcrypt implementation only uses the first 72 bytes of a string. Any characters after that are ignored.',
|
||||||
|
// link: GHSA-4p64-9f7h-3432
|
||||||
|
date: new Date(2024, 5, 25),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiCalendarToday,
|
||||||
|
iconColor: 'greenyellow',
|
||||||
|
title: 'JavaScript Date objects are cursed',
|
||||||
|
description: 'JavaScript date objects are 1 indexed for years and days, but 0 indexed for months.',
|
||||||
|
link: { url: 'https://github.com/immich-app/immich/pull/6787', text: '#6787' },
|
||||||
|
date: new Date(2024, 0, 31),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CursedKnowledgePage(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Layout title="Cursed Knowledge" description="Things we wish we didn't know">
|
||||||
|
<section className="my-8">
|
||||||
|
<h1 className="md:text-6xl text-center mb-10 text-immich-primary dark:text-immich-dark-primary px-2">
|
||||||
|
Cursed Knowledge
|
||||||
|
</h1>
|
||||||
|
<p className="text-center text-xl px-2">
|
||||||
|
Cursed knowledge we have learned as a result of building Immich that we wish we never knew.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-around mt-8 w-full max-w-full">
|
||||||
|
<Timeline
|
||||||
|
items={items
|
||||||
|
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||||
|
.map((item) => ({ ...item, getDateLabel: withLanguage(item.date) }))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ function HomepageHeader() {
|
|||||||
<section className="text-center m-6 p-12 border border-red-400 rounded-[50px] bg-slate-200 dark:bg-immich-dark-gray">
|
<section className="text-center m-6 p-12 border border-red-400 rounded-[50px] bg-slate-200 dark:bg-immich-dark-gray">
|
||||||
<img
|
<img
|
||||||
src={isDarkTheme ? 'img/immich-logo-stacked-dark.svg' : 'img/immich-logo-stacked-light.svg'}
|
src={isDarkTheme ? 'img/immich-logo-stacked-dark.svg' : 'img/immich-logo-stacked-light.svg'}
|
||||||
className="md:h-60 h-44 mb-2 antialiased"
|
className="md:h-60 h-44 mb-2 antialiased rounded-none"
|
||||||
alt="Immich logo"
|
alt="Immich logo"
|
||||||
/>
|
/>
|
||||||
<div className="sm:text-2xl text-lg md:text-4xl mb-12 sm:leading-tight">
|
<div className="sm:text-2xl text-lg md:text-4xl mb-12 sm:leading-tight">
|
||||||
@@ -41,7 +41,7 @@ function HomepageHeader() {
|
|||||||
Discord
|
Discord
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<img src="/img/immich-screenshots.png" alt="screenshots" width={'70%'} />
|
<img src="/img/immich-screenshots.webp" alt="screenshots" width={'70%'} />
|
||||||
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-4 gap-1">
|
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-4 gap-1">
|
||||||
<div className="h-24">
|
<div className="h-24">
|
||||||
<a href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
<a href="https://play.google.com/store/apps/details?id=app.alextran.immich">
|
||||||
|
|||||||
@@ -66,12 +66,16 @@ import {
|
|||||||
mdiVectorCombine,
|
mdiVectorCombine,
|
||||||
mdiVideo,
|
mdiVideo,
|
||||||
mdiWeb,
|
mdiWeb,
|
||||||
|
mdiLicense,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Item, Timeline } from '../components/timeline';
|
import { Item, Timeline } from '../components/timeline';
|
||||||
|
|
||||||
const releases = {
|
const releases = {
|
||||||
|
// TODO
|
||||||
|
'v1.110.0': new Date(2024, 5, 11),
|
||||||
|
'v1.109.0': new Date(2024, 6, 18),
|
||||||
'v1.106.1': new Date(2024, 5, 11),
|
'v1.106.1': new Date(2024, 5, 11),
|
||||||
'v1.104.0': new Date(2024, 4, 13),
|
'v1.104.0': new Date(2024, 4, 13),
|
||||||
'v1.103.0': new Date(2024, 3, 29),
|
'v1.103.0': new Date(2024, 3, 29),
|
||||||
@@ -220,6 +224,20 @@ const roadmap: Item[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const milestones: Item[] = [
|
const milestones: Item[] = [
|
||||||
|
{
|
||||||
|
icon: mdiStar,
|
||||||
|
iconColor: 'gold',
|
||||||
|
title: '40,000 Stars',
|
||||||
|
description: 'Reached 40K Stars on GitHub!',
|
||||||
|
getDateLabel: withLanguage(new Date(2024, 6, 21)),
|
||||||
|
},
|
||||||
|
withRelease({
|
||||||
|
icon: mdiLicense,
|
||||||
|
iconColor: 'gold',
|
||||||
|
title: 'Supporter Badge',
|
||||||
|
description: 'The option to buy Immich to support its development!',
|
||||||
|
release: 'v1.109.0',
|
||||||
|
}),
|
||||||
withRelease({
|
withRelease({
|
||||||
icon: mdiHistory,
|
icon: mdiHistory,
|
||||||
title: 'Versioned documentation',
|
title: 'Versioned documentation',
|
||||||
@@ -236,7 +254,7 @@ const milestones: Item[] = [
|
|||||||
withRelease({
|
withRelease({
|
||||||
icon: mdiContentDuplicate,
|
icon: mdiContentDuplicate,
|
||||||
title: 'Similar image detection',
|
title: 'Similar image detection',
|
||||||
description: 'Detect duplicate assets that aren’t exactly identical',
|
description: "Detect duplicate assets that aren't exactly identical",
|
||||||
release: 'v1.106.1',
|
release: 'v1.106.1',
|
||||||
}),
|
}),
|
||||||
withRelease({
|
withRelease({
|
||||||
|
|||||||
28
docs/static/archived-versions.json
vendored
28
docs/static/archived-versions.json
vendored
@@ -1,4 +1,32 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.111.0",
|
||||||
|
"url": "https://v1.111.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.110.0",
|
||||||
|
"url": "https://v1.110.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.109.2",
|
||||||
|
"url": "https://v1.109.2.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.109.1",
|
||||||
|
"url": "https://v1.109.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.109.0",
|
||||||
|
"url": "https://v1.109.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.108.0",
|
||||||
|
"url": "https://v1.108.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.107.2",
|
||||||
|
"url": "https://v1.107.2.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.107.1",
|
"label": "v1.107.1",
|
||||||
"url": "https://v1.107.1.archive.immich.app"
|
"url": "https://v1.107.1.archive.immich.app"
|
||||||
|
|||||||
BIN
docs/static/img/immich-screenshots.webp
vendored
Normal file
BIN
docs/static/img/immich-screenshots.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
@@ -21,9 +21,6 @@ module.exports = {
|
|||||||
'immich-dark-fg': '#e5e7eb',
|
'immich-dark-fg': '#e5e7eb',
|
||||||
'immich-dark-gray': '#212121',
|
'immich-dark-gray': '#212121',
|
||||||
},
|
},
|
||||||
fontFamily: {
|
|
||||||
'immich-title': ['Snowburst One', 'cursive'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
20.15
|
20.16.0
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- upload:/usr/src/app/upload
|
- upload:/usr/src/app/upload
|
||||||
- ./test-assets:/test-assets
|
- ./test-assets:/test-assets
|
||||||
|
extra_hosts:
|
||||||
|
- 'auth-server:host-gateway'
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
@@ -33,7 +35,7 @@ services:
|
|||||||
- 2283:3001
|
- 2283:3001
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
|
image: redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
|
||||||
|
|||||||
1206
e2e/package-lock.json
generated
1206
e2e/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.107.1",
|
"version": "1.111.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.12",
|
||||||
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
@@ -34,19 +35,21 @@
|
|||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^54.0.0",
|
"eslint-plugin-unicorn": "^54.0.0",
|
||||||
"exiftool-vendored": "^27.0.0",
|
"exiftool-vendored": "^28.0.0",
|
||||||
|
"jose": "^5.6.3",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
|
"oidc-provider": "^8.5.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vitest": "^1.3.0"
|
"vitest": "^1.6.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "20.15.0"
|
"node": "20.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -472,6 +472,44 @@ describe('/asset', () => {
|
|||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update date time original when sidecar file contains DateTimeOriginal', async () => {
|
||||||
|
const sidecarData = `<?xpacket begin='?' id='W5M0MpCehiHzreSzNTczkc9d'?>
|
||||||
|
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 12.40'>
|
||||||
|
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:exif='http://ns.adobe.com/exif/1.0/'>
|
||||||
|
<exif:ExifVersion>0220</exif:ExifVersion> <exif:DateTimeOriginal>2024-07-11T10:32:52Z</exif:DateTimeOriginal>
|
||||||
|
<exif:GPSVersionID>2.3.0.0</exif:GPSVersionID>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:RDF>
|
||||||
|
</x:xmpmeta>
|
||||||
|
<?xpacket end='w'?>`;
|
||||||
|
|
||||||
|
const { id } = await utils.createAsset(user1.accessToken, {
|
||||||
|
sidecarData: {
|
||||||
|
bytes: Buffer.from(sidecarData),
|
||||||
|
filename: 'example.xmp',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||||
|
|
||||||
|
const assetInfo = await utils.getAssetInfo(user1.accessToken, id);
|
||||||
|
expect(assetInfo.exifInfo?.dateTimeOriginal).toBe('2024-07-11T10:32:52.000Z');
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.put(`/assets/${id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id,
|
||||||
|
exifInfo: expect.objectContaining({
|
||||||
|
dateTimeOriginal: '2023-11-20T01:11:00.000Z',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
it('should reject invalid gps coordinates', async () => {
|
it('should reject invalid gps coordinates', async () => {
|
||||||
for (const test of [
|
for (const test of [
|
||||||
{ latitude: 12 },
|
{ latitude: 12 },
|
||||||
@@ -507,6 +545,22 @@ describe('/asset', () => {
|
|||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip('should geocode country from gps data in the middle of nowhere', async () => {
|
||||||
|
const { status } = await request(app)
|
||||||
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ latitude: 42, longitude: 69 });
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
|
||||||
|
|
||||||
|
const asset = await getAssetInfo({ id: user1Assets[0].id }, { headers: asBearerAuth(user1.accessToken) });
|
||||||
|
expect(asset).toMatchObject({
|
||||||
|
id: user1Assets[0].id,
|
||||||
|
exifInfo: expect.objectContaining({ city: null, country: 'Kazakhstan' }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should set the description', async () => {
|
it('should set the description', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/assets/${user1Assets[0].id}`)
|
.put(`/assets/${user1Assets[0].id}`)
|
||||||
@@ -1083,7 +1137,7 @@ describe('/asset', () => {
|
|||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
originalFileName: '14bit-uncompressed-(3_2).arw',
|
originalFileName: '14bit-uncompressed-(3_2).arw',
|
||||||
resized: true,
|
resized: true,
|
||||||
fileCreatedAt: '2016-01-08T15:08:01.000Z',
|
fileCreatedAt: '2016-01-08T14:08:01.000Z',
|
||||||
exifInfo: {
|
exifInfo: {
|
||||||
make: 'SONY',
|
make: 'SONY',
|
||||||
model: 'ILCE-7M2',
|
model: 'ILCE-7M2',
|
||||||
@@ -1095,7 +1149,7 @@ describe('/asset', () => {
|
|||||||
iso: 100,
|
iso: 100,
|
||||||
lensModel: 'E 25mm F2',
|
lensModel: 'E 25mm F2',
|
||||||
fileSizeInByte: 49_512_448,
|
fileSizeInByte: 49_512_448,
|
||||||
dateTimeOriginal: '2016-01-08T15:08:01.000Z',
|
dateTimeOriginal: '2016-01-08T14:08:01.000Z',
|
||||||
latitude: null,
|
latitude: null,
|
||||||
longitude: null,
|
longitude: null,
|
||||||
orientation: '1',
|
orientation: '1',
|
||||||
@@ -1170,17 +1224,25 @@ describe('/asset', () => {
|
|||||||
// into the test here.
|
// into the test here.
|
||||||
it.each([
|
it.each([
|
||||||
{
|
{
|
||||||
filepath: 'formats/motionphoto/Samsung One UI 5.jpg',
|
filepath: 'formats/motionphoto/samsung-one-ui-5.jpg',
|
||||||
checksum: 'fr14niqCq6N20HB8rJYEvpsUVtI=',
|
checksum: 'fr14niqCq6N20HB8rJYEvpsUVtI=',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filepath: 'formats/motionphoto/Samsung One UI 6.jpg',
|
filepath: 'formats/motionphoto/samsung-one-ui-6.jpg',
|
||||||
checksum: 'lT9Uviw/FFJYCjfIxAGPTjzAmmw=',
|
checksum: 'lT9Uviw/FFJYCjfIxAGPTjzAmmw=',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filepath: 'formats/motionphoto/Samsung One UI 6.heic',
|
filepath: 'formats/motionphoto/samsung-one-ui-6.heic',
|
||||||
checksum: '/ejgzywvgvzvVhUYVfvkLzFBAF0=',
|
checksum: '/ejgzywvgvzvVhUYVfvkLzFBAF0=',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
filepath: 'formats/motionphoto/pixel-6-pro.jpg',
|
||||||
|
checksum: 'bFhLGbdK058PSk4FTfrSnoKWykc=',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filepath: 'formats/motionphoto/pixel-8a.jpg',
|
||||||
|
checksum: '7YdY+WF0h+CXHbiXpi0HiCMTTjs=',
|
||||||
|
},
|
||||||
])(`should extract motionphoto video from $filepath`, async ({ filepath, checksum }) => {
|
])(`should extract motionphoto video from $filepath`, async ({ filepath, checksum }) => {
|
||||||
const response = await utils.createAsset(admin.accessToken, {
|
const response = await utils.createAsset(admin.accessToken, {
|
||||||
assetData: {
|
assetData: {
|
||||||
|
|||||||
@@ -100,6 +100,12 @@ describe('/auth/*', () => {
|
|||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.badRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reject an invalid email', async () => {
|
||||||
|
const { status, body } = await request(app).post('/auth/login').send({ email: [], password });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.invalidEmail);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should accept a correct password', async () => {
|
it('should accept a correct password', async () => {
|
||||||
|
|||||||
@@ -159,4 +159,75 @@ describe('/map', () => {
|
|||||||
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
|
expect(body).toEqual(expect.objectContaining({ id: 'immich-map-dark' }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GET /map/reverse-geocode', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).get('/map/reverse-geocode');
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if a lat is not provided', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/reverse-geocode?lon=123')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if a lat is not a number', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if a lat is out of range', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if a lon is not provided', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/map/reverse-geocode?lat=75')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['lon must be a number between -180 and 180']));
|
||||||
|
});
|
||||||
|
|
||||||
|
const reverseGeocodeTestCases = [
|
||||||
|
{
|
||||||
|
name: 'Vaucluse',
|
||||||
|
lat: -33.858_977_058_663_13,
|
||||||
|
lon: 151.278_490_730_270_48,
|
||||||
|
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ravenhall',
|
||||||
|
lat: -37.765_732_399_174_75,
|
||||||
|
lon: 144.752_453_164_883_3,
|
||||||
|
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Scarborough',
|
||||||
|
lat: -31.894_346_156_789_997,
|
||||||
|
lon: 115.757_617_103_904_64,
|
||||||
|
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(reverseGeocodeTestCases)(`should resolve to $name`, async ({ lat, lon, results }) => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/map/reverse-geocode?lat=${lat}&lon=${lon}`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(Array.isArray(body)).toBe(true);
|
||||||
|
expect(body.length).toBe(results.length);
|
||||||
|
expect(body).toEqual(results);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,85 @@
|
|||||||
|
import {
|
||||||
|
LoginResponseDto,
|
||||||
|
SystemConfigOAuthDto,
|
||||||
|
getConfigDefaults,
|
||||||
|
getMyUser,
|
||||||
|
startOAuth,
|
||||||
|
updateConfig,
|
||||||
|
} from '@immich/sdk';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, utils } from 'src/utils';
|
import { OAuthClient, OAuthUser } from 'src/setup/auth-server';
|
||||||
|
import { app, asBearerAuth, baseUrl, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { beforeAll, describe, expect, it } from 'vitest';
|
import { beforeAll, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const authServer = {
|
||||||
|
internal: 'http://auth-server:3000',
|
||||||
|
external: 'http://127.0.0.1:3000',
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirect = async (url: string, cookies?: string[]) => {
|
||||||
|
const { headers } = await request(url)
|
||||||
|
.get('/')
|
||||||
|
.set('Cookie', cookies || []);
|
||||||
|
return { cookies: (headers['set-cookie'] as unknown as string[]) || [], location: headers.location };
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginWithOAuth = async (sub: OAuthUser | string) => {
|
||||||
|
const { url } = await startOAuth({ oAuthConfigDto: { redirectUri: `${baseUrl}/auth/login` } });
|
||||||
|
|
||||||
|
// login
|
||||||
|
const response1 = await redirect(url.replace(authServer.internal, authServer.external));
|
||||||
|
const response2 = await request(authServer.external + response1.location)
|
||||||
|
.post('/')
|
||||||
|
.set('Cookie', response1.cookies)
|
||||||
|
.type('form')
|
||||||
|
.send({ prompt: 'login', login: sub, password: 'password' });
|
||||||
|
|
||||||
|
// approve
|
||||||
|
const response3 = await redirect(response2.header.location, response1.cookies);
|
||||||
|
const response4 = await request(authServer.external + response3.location)
|
||||||
|
.post('/')
|
||||||
|
.type('form')
|
||||||
|
.set('Cookie', response3.cookies)
|
||||||
|
.send({ prompt: 'consent' });
|
||||||
|
|
||||||
|
const response5 = await redirect(response4.header.location, response3.cookies.slice(1));
|
||||||
|
const redirectUrl = response5.location;
|
||||||
|
|
||||||
|
expect(redirectUrl).toBeDefined();
|
||||||
|
const params = new URL(redirectUrl).searchParams;
|
||||||
|
expect(params.get('code')).toBeDefined();
|
||||||
|
expect(params.get('state')).toBeDefined();
|
||||||
|
|
||||||
|
return redirectUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupOAuth = async (token: string, dto: Partial<SystemConfigOAuthDto>) => {
|
||||||
|
const options = { headers: asBearerAuth(token) };
|
||||||
|
const defaults = await getConfigDefaults(options);
|
||||||
|
const merged = {
|
||||||
|
...defaults.oauth,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
issuerUrl: `${authServer.internal}/.well-known/openid-configuration`,
|
||||||
|
...dto,
|
||||||
|
};
|
||||||
|
await updateConfig({ systemConfigDto: { ...defaults, oauth: merged } }, options);
|
||||||
|
};
|
||||||
|
|
||||||
describe(`/oauth`, () => {
|
describe(`/oauth`, () => {
|
||||||
|
let admin: LoginResponseDto;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
await utils.adminSetup();
|
admin = await utils.adminSetup();
|
||||||
|
|
||||||
|
await setupOAuth(admin.accessToken, {
|
||||||
|
enabled: true,
|
||||||
|
clientId: OAuthClient.DEFAULT,
|
||||||
|
clientSecret: OAuthClient.DEFAULT,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
storageLabelClaim: 'immich_username',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /oauth/authorize', () => {
|
describe('POST /oauth/authorize', () => {
|
||||||
@@ -15,5 +88,171 @@ describe(`/oauth`, () => {
|
|||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['redirectUri must be a string', 'redirectUri should not be empty']));
|
expect(body).toEqual(errorDto.badRequest(['redirectUri must be a string', 'redirectUri should not be empty']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return a redirect uri', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post('/oauth/authorize')
|
||||||
|
.send({ redirectUri: 'http://127.0.0.1:2283/auth/login' });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toEqual({ url: expect.stringContaining(`${authServer.internal}/auth?`) });
|
||||||
|
|
||||||
|
const params = new URL(body.url).searchParams;
|
||||||
|
expect(params.get('client_id')).toBe('client-default');
|
||||||
|
expect(params.get('response_type')).toBe('code');
|
||||||
|
expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2283/auth/login');
|
||||||
|
expect(params.get('state')).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /oauth/callback', () => {
|
||||||
|
it(`should throw an error if a url is not provided`, async () => {
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({});
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['url must be a string', 'url should not be empty']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should throw an error if the url is empty`, async () => {
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest(['url should not be empty']));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should auto register the user by default', async () => {
|
||||||
|
const url = await loginWithOAuth('oauth-auto-register');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
accessToken: expect.any(String),
|
||||||
|
isAdmin: false,
|
||||||
|
name: 'OAuth User',
|
||||||
|
userEmail: 'oauth-auto-register@immich.app',
|
||||||
|
userId: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle a user without an email', async () => {
|
||||||
|
const url = await loginWithOAuth(OAuthUser.NO_EMAIL);
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('OAuth profile does not have an email address'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the quota from a claim', async () => {
|
||||||
|
const url = await loginWithOAuth(OAuthUser.WITH_QUOTA);
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
accessToken: expect.any(String),
|
||||||
|
userId: expect.any(String),
|
||||||
|
userEmail: 'oauth-with-quota@immich.app',
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await getMyUser({ headers: asBearerAuth(body.accessToken) });
|
||||||
|
expect(user.quotaSizeInBytes).toBe(25 * 2 ** 30); // 25 GiB;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the storage label from a claim', async () => {
|
||||||
|
const url = await loginWithOAuth(OAuthUser.WITH_USERNAME);
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
accessToken: expect.any(String),
|
||||||
|
userId: expect.any(String),
|
||||||
|
userEmail: 'oauth-with-username@immich.app',
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await getMyUser({ headers: asBearerAuth(body.accessToken) });
|
||||||
|
expect(user.storageLabel).toBe('user-username');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with RS256 signed tokens', async () => {
|
||||||
|
await setupOAuth(admin.accessToken, {
|
||||||
|
enabled: true,
|
||||||
|
clientId: OAuthClient.RS256_TOKENS,
|
||||||
|
clientSecret: OAuthClient.RS256_TOKENS,
|
||||||
|
autoRegister: true,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
signingAlgorithm: 'RS256',
|
||||||
|
});
|
||||||
|
const url = await loginWithOAuth('oauth-RS256-token');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
accessToken: expect.any(String),
|
||||||
|
isAdmin: false,
|
||||||
|
name: 'OAuth User',
|
||||||
|
userEmail: 'oauth-RS256-token@immich.app',
|
||||||
|
userId: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with RS256 signed user profiles', async () => {
|
||||||
|
await setupOAuth(admin.accessToken, {
|
||||||
|
enabled: true,
|
||||||
|
clientId: OAuthClient.RS256_PROFILE,
|
||||||
|
clientSecret: OAuthClient.RS256_PROFILE,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
profileSigningAlgorithm: 'RS256',
|
||||||
|
});
|
||||||
|
const url = await loginWithOAuth('oauth-signed-profile');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
userId: expect.any(String),
|
||||||
|
userEmail: 'oauth-signed-profile@immich.app',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error for an invalid token algorithm', async () => {
|
||||||
|
await setupOAuth(admin.accessToken, {
|
||||||
|
enabled: true,
|
||||||
|
clientId: OAuthClient.DEFAULT,
|
||||||
|
clientSecret: OAuthClient.DEFAULT,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
signingAlgorithm: 'something-that-does-not-work',
|
||||||
|
});
|
||||||
|
const url = await loginWithOAuth('oauth-signed-bad');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(500);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
error: 'Internal Server Error',
|
||||||
|
message: 'Failed to finish oauth',
|
||||||
|
statusCode: 500,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('autoRegister: false', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await setupOAuth(admin.accessToken, {
|
||||||
|
enabled: true,
|
||||||
|
clientId: OAuthClient.DEFAULT,
|
||||||
|
clientSecret: OAuthClient.DEFAULT,
|
||||||
|
autoRegister: false,
|
||||||
|
buttonText: 'Login with Immich',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not auto register the user', async () => {
|
||||||
|
const url = await loginWithOAuth('oauth-no-auto-register');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should link to an existing user by email', async () => {
|
||||||
|
const { userId } = await utils.userSetup(admin.accessToken, {
|
||||||
|
name: 'OAuth User 3',
|
||||||
|
email: 'oauth-user3@immich.app',
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
|
const url = await loginWithOAuth('oauth-user3');
|
||||||
|
const { status, body } = await request(app).post('/oauth/callback').send({ url });
|
||||||
|
expect(status).toBe(201);
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
userId,
|
||||||
|
userEmail: 'oauth-user3@immich.app',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,10 +6,19 @@ import request from 'supertest';
|
|||||||
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
const invalidBirthday = [
|
const invalidBirthday = [
|
||||||
{ birthDate: 'false', response: 'birthDate must be a date string' },
|
{
|
||||||
{ birthDate: '123567', response: 'birthDate must be a date string' },
|
birthDate: 'false',
|
||||||
{ birthDate: 123_567, response: 'birthDate must be a date string' },
|
response: ['birthDate must be a string in the format yyyy-MM-dd', 'Birth date cannot be in the future'],
|
||||||
{ birthDate: new Date(9999, 0, 0).toISOString(), response: ['Birth date cannot be in the future'] },
|
},
|
||||||
|
{
|
||||||
|
birthDate: '123567',
|
||||||
|
response: ['birthDate must be a string in the format yyyy-MM-dd', 'Birth date cannot be in the future'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
birthDate: 123_567,
|
||||||
|
response: ['birthDate must be a string in the format yyyy-MM-dd', 'Birth date cannot be in the future'],
|
||||||
|
},
|
||||||
|
{ birthDate: '9999-01-01', response: ['Birth date cannot be in the future'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('/people', () => {
|
describe('/people', () => {
|
||||||
@@ -65,6 +74,7 @@ describe('/people', () => {
|
|||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
|
hasNextPage: false,
|
||||||
total: 3,
|
total: 3,
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
people: [
|
people: [
|
||||||
@@ -80,6 +90,7 @@ describe('/people', () => {
|
|||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
|
hasNextPage: false,
|
||||||
total: 3,
|
total: 3,
|
||||||
hidden: 1,
|
hidden: 1,
|
||||||
people: [
|
people: [
|
||||||
@@ -88,6 +99,21 @@ describe('/people', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support pagination', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get('/people')
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.query({ withHidden: true, page: 2, size: 1 });
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual({
|
||||||
|
hasNextPage: true,
|
||||||
|
total: 3,
|
||||||
|
hidden: 1,
|
||||||
|
people: [expect.objectContaining({ name: 'visible_person' })],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /people/:id', () => {
|
describe('GET /people/:id', () => {
|
||||||
@@ -168,13 +194,13 @@ describe('/people', () => {
|
|||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({
|
.send({
|
||||||
name: 'New Person',
|
name: 'New Person',
|
||||||
birthDate: '1990-01-01T05:00:00.000Z',
|
birthDate: '1990-01-01',
|
||||||
});
|
});
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
name: 'New Person',
|
name: 'New Person',
|
||||||
birthDate: '1990-01-01T05:00:00.000Z',
|
birthDate: '1990-01-01',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -216,7 +242,7 @@ describe('/people', () => {
|
|||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/people/${visiblePerson.id}`)
|
.put(`/people/${visiblePerson.id}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ birthDate: '1990-01-01T05:00:00.000Z' });
|
.send({ birthDate: '1990-01-01' });
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toMatchObject({ birthDate: '1990-01-01' });
|
expect(body).toMatchObject({ birthDate: '1990-01-01' });
|
||||||
});
|
});
|
||||||
@@ -230,4 +256,21 @@ describe('/people', () => {
|
|||||||
expect(body).toMatchObject({ birthDate: null });
|
expect(body).toMatchObject({ birthDate: null });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('POST /people/:id/merge', () => {
|
||||||
|
it('should require authentication', async () => {
|
||||||
|
const { status, body } = await request(app).post(`/people/${uuidDto.notFound}/merge`);
|
||||||
|
expect(status).toBe(401);
|
||||||
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not supporting merging a person into themselves', async () => {
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.post(`/people/${visiblePerson.id}/merge`)
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
|
.send({ ids: [visiblePerson.id] });
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorDto.badRequest('Cannot merge a person into themselves'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ describe('/search', () => {
|
|||||||
{ filename: '/albums/nature/silver_fir.jpg' },
|
{ filename: '/albums/nature/silver_fir.jpg' },
|
||||||
{ filename: '/formats/heic/IMG_2682.heic' },
|
{ filename: '/formats/heic/IMG_2682.heic' },
|
||||||
{ filename: '/formats/jpg/el_torcal_rocks.jpg' },
|
{ filename: '/formats/jpg/el_torcal_rocks.jpg' },
|
||||||
{ filename: '/formats/motionphoto/Samsung One UI 6.jpg' },
|
{ filename: '/formats/motionphoto/samsung-one-ui-6.jpg' },
|
||||||
{ filename: '/formats/motionphoto/Samsung One UI 6.heic' },
|
{ filename: '/formats/motionphoto/samsung-one-ui-6.heic' },
|
||||||
{ filename: '/formats/motionphoto/Samsung One UI 5.jpg' },
|
{ filename: '/formats/motionphoto/samsung-one-ui-5.jpg' },
|
||||||
|
|
||||||
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } },
|
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } },
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ describe('/search', () => {
|
|||||||
{
|
{
|
||||||
should: 'should search by originalFilename with spaces',
|
should: 'should search by originalFilename with spaces',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
dto: { originalFileName: 'Samsung One', type: 'IMAGE' },
|
dto: { originalFileName: 'samsung-one', type: 'IMAGE' },
|
||||||
assets: [assetOneJpg5, assetOneJpg6, assetOneHeic6],
|
assets: [assetOneJpg5, assetOneJpg6, assetOneHeic6],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ describe('/server', () => {
|
|||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send(serverLicense);
|
.send(serverLicense);
|
||||||
const { status } = await request(app).get('/server/license').set('Authorization', `Bearer ${admin.accessToken}`);
|
const { status } = await request(app).get('/server/license').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ describe('/shared-links', () => {
|
|||||||
expect(resp.header['content-type']).toContain('text/html');
|
expect(resp.header['content-type']).toContain('text/html');
|
||||||
expect(resp.text).toContain(`<meta name="description" content="1 shared photos & videos" />`);
|
expect(resp.text).toContain(`<meta name="description" content="1 shared photos & videos" />`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have fqdn og:image meta tag for shared asset', async () => {
|
||||||
|
const resp = await request(shareUrl).get(`/${linkWithAssets.key}`);
|
||||||
|
expect(resp.status).toBe(200);
|
||||||
|
expect(resp.header['content-type']).toContain('text/html');
|
||||||
|
expect(resp.text).toContain(`<meta property="og:image" content="http://`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /shared-links', () => {
|
describe('GET /shared-links', () => {
|
||||||
|
|||||||
@@ -9,11 +9,30 @@ describe(`immich-admin`, () => {
|
|||||||
|
|
||||||
describe('list-users', () => {
|
describe('list-users', () => {
|
||||||
it('should list the admin user', async () => {
|
it('should list the admin user', async () => {
|
||||||
const { stdout, stderr, exitCode } = await immichAdmin(['list-users']);
|
const { stdout, stderr, exitCode } = await immichAdmin(['list-users']).promise;
|
||||||
expect(exitCode).toBe(0);
|
expect(exitCode).toBe(0);
|
||||||
expect(stderr).toBe('');
|
expect(stderr).toBe('');
|
||||||
expect(stdout).toContain("email: 'admin@immich.cloud'");
|
expect(stdout).toContain("email: 'admin@immich.cloud'");
|
||||||
expect(stdout).toContain("name: 'Immich Admin'");
|
expect(stdout).toContain("name: 'Immich Admin'");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('reset-admin-password', () => {
|
||||||
|
it('should reset admin password', async () => {
|
||||||
|
const { child, promise } = immichAdmin(['reset-admin-password']);
|
||||||
|
|
||||||
|
let data = '';
|
||||||
|
child.stdout.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
if (data.includes('Please choose a new password (optional)')) {
|
||||||
|
child.stdin.end('\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stderr, stdout, exitCode } = await promise;
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
expect(stderr).toBe('');
|
||||||
|
expect(stdout).toContain('The admin password has been updated to:');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ export const errorDto = {
|
|||||||
message: 'The server already has an admin',
|
message: 'The server already has an admin',
|
||||||
correlationId: expect.any(String),
|
correlationId: expect.any(String),
|
||||||
},
|
},
|
||||||
|
invalidEmail: {
|
||||||
|
error: 'Bad Request',
|
||||||
|
statusCode: 400,
|
||||||
|
message: ['email must be an email'],
|
||||||
|
correlationId: expect.any(String),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signupResponseDto = {
|
export const signupResponseDto = {
|
||||||
|
|||||||
117
e2e/src/setup/auth-server.ts
Normal file
117
e2e/src/setup/auth-server.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { exportJWK, generateKeyPair } from 'jose';
|
||||||
|
import Provider from 'oidc-provider';
|
||||||
|
|
||||||
|
export enum OAuthClient {
|
||||||
|
DEFAULT = 'client-default',
|
||||||
|
RS256_TOKENS = 'client-RS256-tokens',
|
||||||
|
RS256_PROFILE = 'client-RS256-profile',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OAuthUser {
|
||||||
|
NO_EMAIL = 'no-email',
|
||||||
|
NO_NAME = 'no-name',
|
||||||
|
WITH_QUOTA = 'with-quota',
|
||||||
|
WITH_USERNAME = 'with-username',
|
||||||
|
}
|
||||||
|
|
||||||
|
const claims = [
|
||||||
|
{ sub: OAuthUser.NO_EMAIL },
|
||||||
|
{
|
||||||
|
sub: OAuthUser.NO_NAME,
|
||||||
|
email: 'oauth-no-name@immich.app',
|
||||||
|
email_verified: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sub: OAuthUser.WITH_USERNAME,
|
||||||
|
email: 'oauth-with-username@immich.app',
|
||||||
|
email_verified: true,
|
||||||
|
immich_username: 'user-username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sub: OAuthUser.WITH_QUOTA,
|
||||||
|
email: 'oauth-with-quota@immich.app',
|
||||||
|
email_verified: true,
|
||||||
|
preferred_username: 'user-quota',
|
||||||
|
immich_quota: 25,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const withDefaultClaims = (sub: string) => ({
|
||||||
|
sub,
|
||||||
|
email: `${sub}@immich.app`,
|
||||||
|
name: 'OAuth User',
|
||||||
|
given_name: `OAuth`,
|
||||||
|
family_name: 'User',
|
||||||
|
email_verified: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getClaims = (sub: string) => claims.find((user) => user.sub === sub) || withDefaultClaims(sub);
|
||||||
|
|
||||||
|
const setup = async () => {
|
||||||
|
const { privateKey, publicKey } = await generateKeyPair('RS256');
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
const host = '0.0.0.0';
|
||||||
|
const oidc = new Provider(`http://${host}:${port}`, {
|
||||||
|
renderError: async (ctx, out, error) => {
|
||||||
|
console.error(out);
|
||||||
|
console.error(error);
|
||||||
|
ctx.body = 'Internal Server Error';
|
||||||
|
},
|
||||||
|
findAccount: (ctx, sub) => ({ accountId: sub, claims: () => getClaims(sub) }),
|
||||||
|
scopes: ['openid', 'email', 'profile'],
|
||||||
|
claims: {
|
||||||
|
openid: ['sub'],
|
||||||
|
email: ['email', 'email_verified'],
|
||||||
|
profile: ['name', 'given_name', 'family_name', 'preferred_username', 'immich_quota', 'immich_username'],
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
jwtUserinfo: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cookies: {
|
||||||
|
names: {
|
||||||
|
session: 'oidc.session',
|
||||||
|
interaction: 'oidc.interaction',
|
||||||
|
resume: 'oidc.resume',
|
||||||
|
state: 'oidc.state',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pkce: {
|
||||||
|
required: () => false,
|
||||||
|
},
|
||||||
|
jwks: { keys: [await exportJWK(privateKey)] },
|
||||||
|
clients: [
|
||||||
|
{
|
||||||
|
client_id: OAuthClient.DEFAULT,
|
||||||
|
client_secret: OAuthClient.DEFAULT,
|
||||||
|
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
||||||
|
grant_types: ['authorization_code'],
|
||||||
|
response_types: ['code'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
client_id: OAuthClient.RS256_TOKENS,
|
||||||
|
client_secret: OAuthClient.RS256_TOKENS,
|
||||||
|
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
||||||
|
grant_types: ['authorization_code'],
|
||||||
|
id_token_signed_response_alg: 'RS256',
|
||||||
|
jwks: { keys: [await exportJWK(publicKey)] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
client_id: OAuthClient.RS256_PROFILE,
|
||||||
|
client_secret: OAuthClient.RS256_PROFILE,
|
||||||
|
redirect_uris: ['http://127.0.0.1:2283/auth/login'],
|
||||||
|
grant_types: ['authorization_code'],
|
||||||
|
userinfo_signed_response_alg: 'RS256',
|
||||||
|
jwks: { keys: [await exportJWK(publicKey)] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onStart = () => console.log(`[auth-server] http://${host}:${port}/.well-known/openid-configuration`);
|
||||||
|
const app = oidc.listen(port, host, onStart);
|
||||||
|
return () => app.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default setup;
|
||||||
@@ -50,11 +50,10 @@ type CommandResponse = { stdout: string; stderr: string; exitCode: number | null
|
|||||||
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden';
|
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden';
|
||||||
type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: number };
|
type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: number };
|
||||||
type AdminSetupOptions = { onboarding?: boolean };
|
type AdminSetupOptions = { onboarding?: boolean };
|
||||||
type AssetData = { bytes?: Buffer; filename: string };
|
type FileData = { bytes?: Buffer; filename: string };
|
||||||
|
|
||||||
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5433/immich';
|
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5433/immich';
|
||||||
const baseUrl = 'http://127.0.0.1:2283';
|
export const baseUrl = 'http://127.0.0.1:2283';
|
||||||
|
|
||||||
export const shareUrl = `${baseUrl}/share`;
|
export const shareUrl = `${baseUrl}/share`;
|
||||||
export const app = `${baseUrl}/api`;
|
export const app = `${baseUrl}/api`;
|
||||||
// TODO move test assets into e2e/assets
|
// TODO move test assets into e2e/assets
|
||||||
@@ -64,13 +63,13 @@ export const tempDir = tmpdir();
|
|||||||
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
|
||||||
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
|
||||||
export const immichCli = (args: string[]) =>
|
export const immichCli = (args: string[]) =>
|
||||||
executeCommand('node', ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args]);
|
executeCommand('node', ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args]).promise;
|
||||||
export const immichAdmin = (args: string[]) =>
|
export const immichAdmin = (args: string[]) =>
|
||||||
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]);
|
executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]);
|
||||||
|
|
||||||
const executeCommand = (command: string, args: string[]) => {
|
const executeCommand = (command: string, args: string[]) => {
|
||||||
let _resolve: (value: CommandResponse) => void;
|
let _resolve: (value: CommandResponse) => void;
|
||||||
const deferred = new Promise<CommandResponse>((resolve) => (_resolve = resolve));
|
const promise = new Promise<CommandResponse>((resolve) => (_resolve = resolve));
|
||||||
const child = spawn(command, args, { stdio: 'pipe' });
|
const child = spawn(command, args, { stdio: 'pipe' });
|
||||||
|
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
@@ -86,7 +85,7 @@ const executeCommand = (command: string, args: string[]) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred;
|
return { promise, child };
|
||||||
};
|
};
|
||||||
|
|
||||||
let client: pg.Client | null = null;
|
let client: pg.Client | null = null;
|
||||||
@@ -152,10 +151,6 @@ export const utils = {
|
|||||||
|
|
||||||
const sql: string[] = [];
|
const sql: string[] = [];
|
||||||
|
|
||||||
if (tables.includes('asset_stack')) {
|
|
||||||
sql.push('UPDATE "assets" SET "stackId" = NULL;');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (table === 'system_metadata') {
|
if (table === 'system_metadata') {
|
||||||
// prevent reverse geocoder from being re-initialized
|
// prevent reverse geocoder from being re-initialized
|
||||||
@@ -296,7 +291,10 @@ export const utils = {
|
|||||||
|
|
||||||
createAsset: async (
|
createAsset: async (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: AssetData },
|
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData' | 'sidecarData'>> & {
|
||||||
|
assetData?: FileData;
|
||||||
|
sidecarData?: FileData;
|
||||||
|
},
|
||||||
) => {
|
) => {
|
||||||
const _dto = {
|
const _dto = {
|
||||||
deviceAssetId: 'test-1',
|
deviceAssetId: 'test-1',
|
||||||
@@ -318,6 +316,10 @@ export const utils = {
|
|||||||
.attach('assetData', assetData, filename)
|
.attach('assetData', assetData, filename)
|
||||||
.set('Authorization', `Bearer ${accessToken}`);
|
.set('Authorization', `Bearer ${accessToken}`);
|
||||||
|
|
||||||
|
if (dto?.sidecarData?.bytes) {
|
||||||
|
void builder.attach('sidecarData', dto.sidecarData.bytes, dto.sidecarData.filename);
|
||||||
|
}
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(_dto)) {
|
for (const [key, value] of Object.entries(_dto)) {
|
||||||
void builder.field(key, String(value));
|
void builder.field(key, String(value));
|
||||||
}
|
}
|
||||||
@@ -330,7 +332,7 @@ export const utils = {
|
|||||||
replaceAsset: async (
|
replaceAsset: async (
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
assetId: string,
|
assetId: string,
|
||||||
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: AssetData },
|
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: FileData },
|
||||||
) => {
|
) => {
|
||||||
const _dto = {
|
const _dto = {
|
||||||
deviceAssetId: 'test-1',
|
deviceAssetId: 'test-1',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ test.describe('Registration', () => {
|
|||||||
test('admin registration', async ({ page }) => {
|
test('admin registration', async ({ page }) => {
|
||||||
// welcome
|
// welcome
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await page.getByRole('button', { name: 'Getting Started' }).click();
|
await page.getByRole('link', { name: 'Getting Started' }).click();
|
||||||
|
|
||||||
// register
|
// register
|
||||||
await expect(page).toHaveTitle(/Admin Registration/);
|
await expect(page).toHaveTitle(/Admin Registration/);
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ test.describe('Shared Links', () => {
|
|||||||
await page.getByText('DOWNLOADING', { exact: true }).waitFor();
|
await page.getByText('DOWNLOADING', { exact: true }).waitFor();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('download all from shared link', async ({ page }) => {
|
||||||
|
await page.goto(`/share/${sharedLink.key}`);
|
||||||
|
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
||||||
|
await page.getByRole('button', { name: 'Download' }).click();
|
||||||
|
await page.getByText('DOWNLOADING', { exact: true }).waitFor();
|
||||||
|
});
|
||||||
|
|
||||||
test('enter password for a shared link', async ({ page }) => {
|
test('enter password for a shared link', async ({ page }) => {
|
||||||
await page.goto(`/share/${sharedLinkPassword.key}`);
|
await page.goto(`/share/${sharedLinkPassword.key}`);
|
||||||
await page.getByPlaceholder('Password').fill('test-password');
|
await page.getByPlaceholder('Password').fill('test-password');
|
||||||
|
|||||||
Submodule e2e/test-assets updated: 625ec3a5e9...898069e47f
@@ -1,11 +1,11 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
// skip `docker compose up` if `make e2e` was already run
|
// skip `docker compose up` if `make e2e` was already run
|
||||||
const globalSetup: string[] = [];
|
const globalSetup: string[] = ['src/setup/auth-server.ts'];
|
||||||
try {
|
try {
|
||||||
await fetch('http://127.0.0.1:2283/api/server-info/ping');
|
await fetch('http://127.0.0.1:2283/api/server-info/ping');
|
||||||
} catch {
|
} catch {
|
||||||
globalSetup.push('src/setup.ts');
|
globalSetup.push('src/setup/docker-compose.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
ARG DEVICE=cpu
|
ARG DEVICE=cpu
|
||||||
|
|
||||||
FROM python:3.11-bookworm@sha256:7bec1574675e7fd9e3a540a03cd7d6811c59ca261bd300cd665369d8f435298a as builder-cpu
|
FROM python:3.11-bookworm@sha256:ef4b550f029a76b94f8e6cc6e4a8ed0e870fc6c5af1c4e9d77faaea50f41f6cd as builder-cpu
|
||||||
|
|
||||||
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
|
FROM builder-cpu as builder-openvino
|
||||||
USER root
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends python3-dev
|
|
||||||
|
|
||||||
FROM builder-cpu as builder-cuda
|
FROM builder-cpu as builder-cuda
|
||||||
|
|
||||||
@@ -13,7 +11,7 @@ FROM builder-cpu as builder-armnn
|
|||||||
ENV ARMNN_PATH=/opt/armnn
|
ENV ARMNN_PATH=/opt/armnn
|
||||||
COPY ann /opt/ann
|
COPY ann /opt/ann
|
||||||
RUN mkdir /opt/armnn && \
|
RUN mkdir /opt/armnn && \
|
||||||
curl -SL "https://github.com/ARM-software/armnn/releases/download/v23.11/ArmNN-linux-aarch64.tar.gz" | tar -zx -C /opt/armnn && \
|
curl -SL "https://github.com/ARM-software/armnn/releases/download/v24.05/ArmNN-linux-aarch64.tar.gz" | tar -zx -C /opt/armnn && \
|
||||||
cd /opt/ann && \
|
cd /opt/ann && \
|
||||||
sh build.sh
|
sh build.sh
|
||||||
|
|
||||||
@@ -36,15 +34,17 @@ RUN python3 -m venv /opt/venv
|
|||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:17ec9dc2367aa748559d0212f34665ec4df801129de32db705ea34654b5bc77a as prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:ee317183d292ee6ed30e90bc325043ca3f7d2e8c79ac5019575490b5256ae244 as prod-cpu
|
||||||
|
|
||||||
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as prod-openvino
|
FROM prod-cpu as prod-openvino
|
||||||
USER root
|
|
||||||
# TODO: remove this once the image has the fix for https://github.com/intel/compute-runtime/issues/710
|
|
||||||
ENV NEOReadDebugKeys=1 \
|
|
||||||
OverrideGpuAddressSpace=48
|
|
||||||
|
|
||||||
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04@sha256:2d913b09e6be8387e1a10976933642c73c840c0b735f0bf3c28d97fc9bc422e0 as prod-cuda
|
COPY scripts/configure-apt.sh ./
|
||||||
|
RUN ./configure-apt.sh && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -t unstable --no-install-recommends -yqq intel-opencl-icd && \
|
||||||
|
rm configure-apt.sh
|
||||||
|
|
||||||
|
FROM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04@sha256:fa44193567d1908f7ca1f3abf8623ce9c63bc8cba7bcfdb32702eb04d326f7a8 as prod-cuda
|
||||||
|
|
||||||
COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
||||||
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||||
@@ -54,7 +54,7 @@ FROM prod-cpu as prod-armnn
|
|||||||
|
|
||||||
ENV LD_LIBRARY_PATH=/opt/armnn
|
ENV LD_LIBRARY_PATH=/opt/armnn
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd && \
|
RUN apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd libgomp1 && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
mkdir --parents /etc/OpenCL/vendors && \
|
mkdir --parents /etc/OpenCL/vendors && \
|
||||||
echo "/usr/lib/libmali.so" > /etc/OpenCL/vendors/mali.icd && \
|
echo "/usr/lib/libmali.so" > /etc/OpenCL/vendors/mali.icd && \
|
||||||
@@ -71,9 +71,12 @@ COPY --from=builder-armnn \
|
|||||||
/opt/armnn/
|
/opt/armnn/
|
||||||
|
|
||||||
FROM prod-${DEVICE} as prod
|
FROM prod-${DEVICE} as prod
|
||||||
|
ARG DEVICE
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends tini libmimalloc2.0 && \
|
apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ]; then echo "libmimalloc2.0"; fi) && \
|
||||||
|
apt-get autoremove -yqq && \
|
||||||
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|||||||
@@ -48,21 +48,22 @@ public:
|
|||||||
bool saveCachedNetwork,
|
bool saveCachedNetwork,
|
||||||
const char *cachedNetworkPath)
|
const char *cachedNetworkPath)
|
||||||
{
|
{
|
||||||
INetworkPtr network = loadModel(modelPath);
|
NetworkId netId = -2;
|
||||||
IOptimizedNetworkPtr optNet = OptimizeNetwork(network.get(), fastMath, fp16, saveCachedNetwork, cachedNetworkPath);
|
while (netId == -2)
|
||||||
const IOInfos infos = getIOInfos(optNet.get());
|
|
||||||
NetworkId netId;
|
|
||||||
mutex.lock();
|
|
||||||
Status status = runtime->LoadNetwork(netId, std::move(optNet));
|
|
||||||
mutex.unlock();
|
|
||||||
if (status != Status::Success)
|
|
||||||
{
|
{
|
||||||
return -1;
|
try
|
||||||
|
{
|
||||||
|
netId = loadInternal(modelPath, fastMath, fp16, saveCachedNetwork, cachedNetworkPath);
|
||||||
|
}
|
||||||
|
catch (InvalidArgumentException e)
|
||||||
|
{
|
||||||
|
// fp16 models do not support the forced fp16-turbo (runtime fp32->fp16 conversion)
|
||||||
|
if (fp16)
|
||||||
|
fp16 = false;
|
||||||
|
else
|
||||||
|
netId = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spinLock.lock();
|
|
||||||
ioInfos[netId] = infos;
|
|
||||||
mutexes.emplace(netId, std::make_unique<std::mutex>());
|
|
||||||
spinLock.unlock();
|
|
||||||
return netId;
|
return netId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +118,8 @@ public:
|
|||||||
Ann(int tuningLevel, const char *tuningFile)
|
Ann(int tuningLevel, const char *tuningFile)
|
||||||
{
|
{
|
||||||
IRuntime::CreationOptions runtimeOptions;
|
IRuntime::CreationOptions runtimeOptions;
|
||||||
|
runtimeOptions.m_ProfilingOptions.m_EnableProfiling = false;
|
||||||
|
runtimeOptions.m_ProfilingOptions.m_TimelineEnabled = false;
|
||||||
BackendOptions backendOptions{"GpuAcc",
|
BackendOptions backendOptions{"GpuAcc",
|
||||||
{
|
{
|
||||||
{"TuningLevel", tuningLevel},
|
{"TuningLevel", tuningLevel},
|
||||||
@@ -133,6 +136,30 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int loadInternal(const char *modelPath,
|
||||||
|
bool fastMath,
|
||||||
|
bool fp16,
|
||||||
|
bool saveCachedNetwork,
|
||||||
|
const char *cachedNetworkPath)
|
||||||
|
{
|
||||||
|
NetworkId netId = -1;
|
||||||
|
INetworkPtr network = loadModel(modelPath);
|
||||||
|
IOptimizedNetworkPtr optNet = OptimizeNetwork(network.get(), fastMath, fp16, saveCachedNetwork, cachedNetworkPath);
|
||||||
|
const IOInfos infos = getIOInfos(optNet.get());
|
||||||
|
mutex.lock();
|
||||||
|
Status status = runtime->LoadNetwork(netId, std::move(optNet));
|
||||||
|
mutex.unlock();
|
||||||
|
if (status != Status::Success)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
spinLock.lock();
|
||||||
|
ioInfos[netId] = infos;
|
||||||
|
mutexes.emplace(netId, std::make_unique<std::mutex>());
|
||||||
|
spinLock.unlock();
|
||||||
|
return netId;
|
||||||
|
}
|
||||||
|
|
||||||
INetworkPtr loadModel(const char *modelPath)
|
INetworkPtr loadModel(const char *modelPath)
|
||||||
{
|
{
|
||||||
const auto path = std::string(modelPath);
|
const auto path = std::string(modelPath);
|
||||||
@@ -172,6 +199,8 @@ private:
|
|||||||
options.SetReduceFp32ToFp16(fp16);
|
options.SetReduceFp32ToFp16(fp16);
|
||||||
options.SetShapeInferenceMethod(shapeInferenceMethod);
|
options.SetShapeInferenceMethod(shapeInferenceMethod);
|
||||||
options.SetAllowExpandedDims(allowExpandedDims);
|
options.SetAllowExpandedDims(allowExpandedDims);
|
||||||
|
options.SetDebugToFileEnabled(false);
|
||||||
|
options.SetProfilingEnabled(false);
|
||||||
|
|
||||||
BackendOptions gpuAcc("GpuAcc", {{"FastMathEnabled", fastMath}});
|
BackendOptions gpuAcc("GpuAcc", {{"FastMathEnabled", fastMath}});
|
||||||
if (cachedNetworkPath)
|
if (cachedNetworkPath)
|
||||||
@@ -232,8 +261,8 @@ private:
|
|||||||
IRuntime *runtime;
|
IRuntime *runtime;
|
||||||
std::map<NetworkId, IOInfos> ioInfos;
|
std::map<NetworkId, IOInfos> ioInfos;
|
||||||
std::map<NetworkId, std::unique_ptr<std::mutex>> mutexes; // mutex per network to not execute the same the same network concurrently
|
std::map<NetworkId, std::unique_ptr<std::mutex>> mutexes; // mutex per network to not execute the same the same network concurrently
|
||||||
std::mutex mutex; // global mutex for load/unload calls to the runtime
|
std::mutex mutex; // global mutex for load/unload calls to the runtime
|
||||||
SpinLock spinLock; // fast spin lock to guard access to the ioInfos and mutexes maps
|
SpinLock spinLock; // fast spin lock to guard access to the ioInfos and mutexes maps
|
||||||
};
|
};
|
||||||
|
|
||||||
extern "C" void *init(int logLevel, int tuningLevel, const char *tuningFile)
|
extern "C" void *init(int logLevel, int tuningLevel, const char *tuningFile)
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ class Ann(metaclass=_Singleton):
|
|||||||
save_cached_network,
|
save_cached_network,
|
||||||
cached_network_path.encode() if cached_network_path is not None else None,
|
cached_network_path.encode() if cached_network_path is not None else None,
|
||||||
)
|
)
|
||||||
|
if net_id < 0:
|
||||||
|
raise ValueError("Cannot load model!")
|
||||||
|
|
||||||
self.input_shapes[net_id] = tuple(
|
self.input_shapes[net_id] = tuple(
|
||||||
self.shape(net_id, input=True, index=i) for i in range(self.tensors(net_id, input=True))
|
self.shape(net_id, input=True, index=i) for i in range(self.tensors(net_id, input=True))
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class Settings(BaseSettings):
|
|||||||
model_inter_op_threads: int = 0
|
model_inter_op_threads: int = 0
|
||||||
model_intra_op_threads: int = 0
|
model_intra_op_threads: int = 0
|
||||||
ann: bool = True
|
ann: bool = True
|
||||||
|
ann_fp16_turbo: bool = False
|
||||||
|
ann_tuning_level: int = 2
|
||||||
preload: PreloadModelData | None = None
|
preload: PreloadModelData | None = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|||||||
@@ -168,6 +168,12 @@ def warning() -> Iterator[mock.Mock]:
|
|||||||
yield mocked
|
yield mocked
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def exception() -> Iterator[mock.Mock]:
|
||||||
|
with mock.patch.object(log, "exception") as mocked:
|
||||||
|
yield mocked
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def snapshot_download() -> Iterator[mock.Mock]:
|
def snapshot_download() -> Iterator[mock.Mock]:
|
||||||
with mock.patch("app.models.base.snapshot_download") as mocked:
|
with mock.patch("app.models.base.snapshot_download") as mocked:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from .schemas import (
|
|||||||
InferenceEntry,
|
InferenceEntry,
|
||||||
InferenceResponse,
|
InferenceResponse,
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
|
ModelFormat,
|
||||||
ModelIdentity,
|
ModelIdentity,
|
||||||
ModelTask,
|
ModelTask,
|
||||||
ModelType,
|
ModelType,
|
||||||
@@ -195,7 +196,17 @@ async def load(model: InferenceModel) -> InferenceModel:
|
|||||||
if model.load_attempts > 1:
|
if model.load_attempts > 1:
|
||||||
raise HTTPException(500, f"Failed to load model '{model.model_name}'")
|
raise HTTPException(500, f"Failed to load model '{model.model_name}'")
|
||||||
with lock:
|
with lock:
|
||||||
model.load()
|
try:
|
||||||
|
model.load()
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
if model.model_format == ModelFormat.ONNX:
|
||||||
|
raise e
|
||||||
|
log.exception(e)
|
||||||
|
log.warning(
|
||||||
|
f"{model.model_format.upper()} is available, but model '{model.model_name}' does not support it."
|
||||||
|
)
|
||||||
|
model.model_format = ModelFormat.ONNX
|
||||||
|
model.load()
|
||||||
return model
|
return model
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class InferenceModel(ABC):
|
|||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
cache_dir: Path | str | None = None,
|
cache_dir: Path | str | None = None,
|
||||||
preferred_format: ModelFormat | None = None,
|
model_format: ModelFormat | None = None,
|
||||||
session: ModelSession | None = None,
|
session: ModelSession | None = None,
|
||||||
**model_kwargs: Any,
|
**model_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -31,7 +31,7 @@ class InferenceModel(ABC):
|
|||||||
self.load_attempts = 0
|
self.load_attempts = 0
|
||||||
self.model_name = clean_name(model_name)
|
self.model_name = clean_name(model_name)
|
||||||
self.cache_dir = Path(cache_dir) if cache_dir is not None else self._cache_dir_default
|
self.cache_dir = Path(cache_dir) if cache_dir is not None else self._cache_dir_default
|
||||||
self.model_format = preferred_format if preferred_format is not None else self._model_format_default
|
self.model_format = model_format if model_format is not None else self._model_format_default
|
||||||
if session is not None:
|
if session is not None:
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class InferenceModel(ABC):
|
|||||||
self.load_attempts += 1
|
self.load_attempts += 1
|
||||||
|
|
||||||
self.download()
|
self.download()
|
||||||
attempt = f"Attempt #{self.load_attempts + 1} to load" if self.load_attempts else "Loading"
|
attempt = f"Attempt #{self.load_attempts} to load" if self.load_attempts > 1 else "Loading"
|
||||||
log.info(f"{attempt} {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
|
log.info(f"{attempt} {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
|
||||||
self.session = self._load()
|
self.session = self._load()
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
@@ -101,6 +101,9 @@ class InferenceModel(ABC):
|
|||||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def _make_session(self, model_path: Path) -> ModelSession:
|
def _make_session(self, model_path: Path) -> ModelSession:
|
||||||
|
if not model_path.is_file():
|
||||||
|
raise FileNotFoundError(f"Model file not found: {model_path}")
|
||||||
|
|
||||||
match model_path.suffix:
|
match model_path.suffix:
|
||||||
case ".armnn":
|
case ".armnn":
|
||||||
session: ModelSession = AnnSession(model_path)
|
session: ModelSession = AnnSession(model_path)
|
||||||
@@ -144,17 +147,13 @@ class InferenceModel(ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def model_format(self) -> ModelFormat:
|
def model_format(self) -> ModelFormat:
|
||||||
return self._preferred_format
|
return self._model_format
|
||||||
|
|
||||||
@model_format.setter
|
@model_format.setter
|
||||||
def model_format(self, preferred_format: ModelFormat) -> None:
|
def model_format(self, model_format: ModelFormat) -> None:
|
||||||
log.debug(f"Setting preferred format to {preferred_format}")
|
log.debug(f"Setting model format to {model_format}")
|
||||||
self._preferred_format = preferred_format
|
self._model_format = model_format
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _model_format_default(self) -> ModelFormat:
|
def _model_format_default(self) -> ModelFormat:
|
||||||
prefer_ann = ann.ann.is_available and settings.ann
|
return ModelFormat.ARMNN if ann.ann.is_available and settings.ann else ModelFormat.ONNX
|
||||||
ann_exists = (self.model_dir / "model.armnn").is_file()
|
|
||||||
if prefer_ann and not ann_exists:
|
|
||||||
log.warning(f"ARM NN is available, but '{self.model_name}' does not support ARM NN. Falling back to ONNX.")
|
|
||||||
return ModelFormat.ARMNN if prefer_ann and ann_exists else ModelFormat.ONNX
|
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ class BaseCLIPTextualEncoder(InferenceModel):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _load(self) -> ModelSession:
|
def _load(self) -> ModelSession:
|
||||||
|
session = super()._load()
|
||||||
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
|
||||||
self.tokenizer = self._load_tokenizer()
|
self.tokenizer = self._load_tokenizer()
|
||||||
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
|
||||||
|
|
||||||
return super()._load()
|
return session
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _load_tokenizer(self) -> Tokenizer:
|
def _load_tokenizer(self) -> Tokenizer:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -14,15 +13,9 @@ class FaceDetector(InferenceModel):
|
|||||||
depends = []
|
depends = []
|
||||||
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, model_name: str, min_score: float = 0.7, **model_kwargs: Any) -> None:
|
||||||
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)
|
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||||
super().__init__(model_name, cache_dir, **model_kwargs)
|
super().__init__(model_name, **model_kwargs)
|
||||||
|
|
||||||
def _load(self) -> ModelSession:
|
def _load(self) -> ModelSession:
|
||||||
session = self._make_session(self.model_path)
|
session = self._make_session(self.model_path)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from numpy.typing import NDArray
|
|||||||
from onnx.tools.update_model_dims import update_inputs_outputs_dims
|
from onnx.tools.update_model_dims import update_inputs_outputs_dims
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from app.config import clean_name, log
|
from app.config import log
|
||||||
from app.models.base import InferenceModel
|
from app.models.base import InferenceModel
|
||||||
from app.models.transforms import decode_cv2
|
from app.models.transforms import decode_cv2
|
||||||
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType
|
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType
|
||||||
@@ -20,20 +20,14 @@ class FaceRecognizer(InferenceModel):
|
|||||||
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
|
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
|
||||||
identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, model_name: str, min_score: float = 0.7, **model_kwargs: Any) -> None:
|
||||||
self,
|
super().__init__(model_name, **model_kwargs)
|
||||||
model_name: str,
|
|
||||||
min_score: float = 0.7,
|
|
||||||
cache_dir: Path | str | None = None,
|
|
||||||
**model_kwargs: Any,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
|
|
||||||
self.min_score = model_kwargs.pop("minScore", min_score)
|
self.min_score = model_kwargs.pop("minScore", min_score)
|
||||||
self.batch = self.model_format == ModelFormat.ONNX
|
self.batch = self.model_format == ModelFormat.ONNX
|
||||||
|
|
||||||
def _load(self) -> ModelSession:
|
def _load(self) -> ModelSession:
|
||||||
session = self._make_session(self.model_path)
|
session = self._make_session(self.model_path)
|
||||||
if self.model_format == ModelFormat.ONNX and not has_batch_axis(session):
|
if self.batch and not has_batch_axis(session):
|
||||||
self._add_batch_axis(self.model_path)
|
self._add_batch_axis(self.model_path)
|
||||||
session = self._make_session(self.model_path)
|
session = self._make_session(self.model_path)
|
||||||
self.model = ArcFaceONNX(
|
self.model = ArcFaceONNX(
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]:
|
|||||||
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
|
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
|
||||||
if isinstance(image_bytes, Image.Image):
|
if isinstance(image_bytes, Image.Image):
|
||||||
return image_bytes
|
return image_bytes
|
||||||
image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
|
image: Image.Image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
|
||||||
image.load() # type: ignore
|
image.load()
|
||||||
if not image.mode == "RGB":
|
if not image.mode == "RGB":
|
||||||
image = image.convert("RGB")
|
image = image.convert("RGB")
|
||||||
return image
|
return image
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ class AnnSession:
|
|||||||
def __init__(self, model_path: Path, cache_dir: Path = settings.cache_folder) -> None:
|
def __init__(self, model_path: Path, cache_dir: Path = settings.cache_folder) -> None:
|
||||||
self.model_path = model_path
|
self.model_path = model_path
|
||||||
self.cache_dir = cache_dir
|
self.cache_dir = cache_dir
|
||||||
self.ann = Ann(tuning_level=3, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix())
|
self.ann = Ann(tuning_level=settings.ann_tuning_level, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix())
|
||||||
|
|
||||||
log.info("Loading ANN model %s ...", model_path)
|
log.info("Loading ANN model %s ...", model_path)
|
||||||
self.model = self.ann.load(
|
self.model = self.ann.load(
|
||||||
model_path.as_posix(),
|
model_path.as_posix(),
|
||||||
cached_network_path=model_path.with_suffix(".anncache").as_posix(),
|
cached_network_path=model_path.with_suffix(".anncache").as_posix(),
|
||||||
|
fp16=settings.ann_fp16_turbo,
|
||||||
)
|
)
|
||||||
log.info("Loaded ANN model with ID %d", self.model)
|
log.info("Loaded ANN model with ID %d", self.model)
|
||||||
|
|
||||||
|
|||||||
@@ -83,17 +83,21 @@ class OrtSession:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _provider_options_default(self) -> list[dict[str, Any]]:
|
def _provider_options_default(self) -> list[dict[str, Any]]:
|
||||||
options = []
|
provider_options = []
|
||||||
for provider in self.providers:
|
for provider in self.providers:
|
||||||
match provider:
|
match provider:
|
||||||
case "CPUExecutionProvider" | "CUDAExecutionProvider":
|
case "CPUExecutionProvider" | "CUDAExecutionProvider":
|
||||||
option = {"arena_extend_strategy": "kSameAsRequested"}
|
options = {"arena_extend_strategy": "kSameAsRequested"}
|
||||||
case "OpenVINOExecutionProvider":
|
case "OpenVINOExecutionProvider":
|
||||||
option = {"device_type": "GPU_FP32", "cache_dir": (self.model_path.parent / "openvino").as_posix()}
|
options = {
|
||||||
|
"device_type": "GPU",
|
||||||
|
"precision": "FP32",
|
||||||
|
"cache_dir": (self.model_path.parent / "openvino").as_posix(),
|
||||||
|
}
|
||||||
case _:
|
case _:
|
||||||
option = {}
|
options = {}
|
||||||
options.append(option)
|
provider_options.append(options)
|
||||||
return options
|
return provider_options
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sess_options(self) -> ort.SessionOptions:
|
def sess_options(self) -> ort.SessionOptions:
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class TestBase:
|
|||||||
|
|
||||||
assert encoder.cache_dir == cache_dir
|
assert encoder.cache_dir == cache_dir
|
||||||
|
|
||||||
def test_sets_default_preferred_format(self, mocker: MockerFixture) -> None:
|
def test_sets_default_model_format(self, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(settings, "ann", True)
|
mocker.patch.object(settings, "ann", True)
|
||||||
mocker.patch("ann.ann.is_available", False)
|
mocker.patch("ann.ann.is_available", False)
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class TestBase:
|
|||||||
|
|
||||||
assert encoder.model_format == ModelFormat.ONNX
|
assert encoder.model_format == ModelFormat.ONNX
|
||||||
|
|
||||||
def test_sets_default_preferred_format_to_armnn_if_available(self, path: mock.Mock, mocker: MockerFixture) -> None:
|
def test_sets_default_model_format_to_armnn_if_available(self, path: mock.Mock, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(settings, "ann", True)
|
mocker.patch.object(settings, "ann", True)
|
||||||
mocker.patch("ann.ann.is_available", True)
|
mocker.patch("ann.ann.is_available", True)
|
||||||
path.suffix = ".armnn"
|
path.suffix = ".armnn"
|
||||||
@@ -60,11 +60,11 @@ class TestBase:
|
|||||||
|
|
||||||
assert encoder.model_format == ModelFormat.ARMNN
|
assert encoder.model_format == ModelFormat.ARMNN
|
||||||
|
|
||||||
def test_sets_preferred_format_kwarg(self, mocker: MockerFixture) -> None:
|
def test_sets_model_format_kwarg(self, mocker: MockerFixture) -> None:
|
||||||
mocker.patch.object(settings, "ann", False)
|
mocker.patch.object(settings, "ann", False)
|
||||||
mocker.patch("ann.ann.is_available", False)
|
mocker.patch("ann.ann.is_available", False)
|
||||||
|
|
||||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN)
|
||||||
|
|
||||||
assert encoder.model_format == ModelFormat.ARMNN
|
assert encoder.model_format == ModelFormat.ARMNN
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ class TestBase:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None:
|
def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None:
|
||||||
encoder = OpenClipTextualEncoder("ViT-B-32__openai", preferred_format=ModelFormat.ARMNN)
|
encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.ARMNN)
|
||||||
encoder.download()
|
encoder.download()
|
||||||
|
|
||||||
snapshot_download.assert_called_once_with(
|
snapshot_download.assert_called_once_with(
|
||||||
@@ -140,6 +140,19 @@ class TestBase:
|
|||||||
ignore_patterns=[],
|
ignore_patterns=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_throws_exception_if_model_path_does_not_exist(
|
||||||
|
self, snapshot_download: mock.Mock, ort_session: mock.Mock, path: mock.Mock
|
||||||
|
) -> None:
|
||||||
|
path.return_value.__truediv__.return_value.__truediv__.return_value.is_file.return_value = False
|
||||||
|
|
||||||
|
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=path)
|
||||||
|
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
encoder.load()
|
||||||
|
|
||||||
|
snapshot_download.assert_called_once()
|
||||||
|
ort_session.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("ort_session")
|
@pytest.mark.usefixtures("ort_session")
|
||||||
class TestOrtSession:
|
class TestOrtSession:
|
||||||
@@ -199,7 +212,7 @@ class TestOrtSession:
|
|||||||
session = OrtSession(model_path, providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
session = OrtSession(model_path, providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"])
|
||||||
|
|
||||||
assert session.provider_options == [
|
assert session.provider_options == [
|
||||||
{"device_type": "GPU_FP32", "cache_dir": "/cache/ViT-B-32__openai/openvino"},
|
{"device_type": "GPU", "precision": "FP32", "cache_dir": "/cache/ViT-B-32__openai/openvino"},
|
||||||
{"arena_extend_strategy": "kSameAsRequested"},
|
{"arena_extend_strategy": "kSameAsRequested"},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -255,9 +268,9 @@ class TestAnnSession:
|
|||||||
|
|
||||||
AnnSession(model_path, cache_dir)
|
AnnSession(model_path, cache_dir)
|
||||||
|
|
||||||
ann_session.assert_called_once_with(tuning_level=3, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix())
|
ann_session.assert_called_once_with(tuning_level=2, tuning_file=(cache_dir / "gpu-tuning.ann").as_posix())
|
||||||
ann_session.return_value.load.assert_called_once_with(
|
ann_session.return_value.load.assert_called_once_with(
|
||||||
model_path.as_posix(), cached_network_path=model_path.with_suffix(".anncache").as_posix()
|
model_path.as_posix(), cached_network_path=model_path.with_suffix(".anncache").as_posix(), fp16=False
|
||||||
)
|
)
|
||||||
info.assert_has_calls(
|
info.assert_has_calls(
|
||||||
[
|
[
|
||||||
@@ -467,16 +480,18 @@ class TestFaceRecognition:
|
|||||||
assert isinstance(call_args[0][0], np.ndarray)
|
assert isinstance(call_args[0][0], np.ndarray)
|
||||||
assert call_args[0][0].shape == (112, 112, 3)
|
assert call_args[0][0].shape == (112, 112, 3)
|
||||||
|
|
||||||
def test_recognition_adds_batch_axis_for_ort(self, ort_session: mock.Mock, mocker: MockerFixture) -> None:
|
def test_recognition_adds_batch_axis_for_ort(
|
||||||
|
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||||
|
) -> None:
|
||||||
onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True)
|
onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True)
|
||||||
update_dims = mocker.patch(
|
update_dims = mocker.patch(
|
||||||
"app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
"app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||||
)
|
)
|
||||||
mocker.patch("app.models.base.InferenceModel.download")
|
mocker.patch("app.models.base.InferenceModel.download")
|
||||||
mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX")
|
mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX")
|
||||||
|
|
||||||
ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))]
|
||||||
ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))]
|
||||||
|
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||||
|
|
||||||
proto = mock.Mock()
|
proto = mock.Mock()
|
||||||
|
|
||||||
@@ -492,27 +507,30 @@ class TestFaceRecognition:
|
|||||||
|
|
||||||
onnx.load.return_value = proto
|
onnx.load.return_value = proto
|
||||||
|
|
||||||
face_recognizer = FaceRecognizer("buffalo_s")
|
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||||
face_recognizer.load()
|
face_recognizer.load()
|
||||||
|
|
||||||
assert face_recognizer.batch is True
|
assert face_recognizer.batch is True
|
||||||
update_dims.assert_called_once_with(proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]})
|
update_dims.assert_called_once_with(proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]})
|
||||||
onnx.save.assert_called_once_with(update_dims.return_value, face_recognizer.model_path)
|
onnx.save.assert_called_once_with(update_dims.return_value, face_recognizer.model_path)
|
||||||
|
|
||||||
def test_recognition_does_not_add_batch_axis_if_exists(self, ort_session: mock.Mock, mocker: MockerFixture) -> None:
|
def test_recognition_does_not_add_batch_axis_if_exists(
|
||||||
|
self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||||
|
) -> None:
|
||||||
onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True)
|
onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True)
|
||||||
update_dims = mocker.patch(
|
update_dims = mocker.patch(
|
||||||
"app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
"app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||||
)
|
)
|
||||||
mocker.patch("app.models.base.InferenceModel.download")
|
mocker.patch("app.models.base.InferenceModel.download")
|
||||||
mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX")
|
mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX")
|
||||||
|
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx"
|
||||||
|
|
||||||
inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))]
|
inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))]
|
||||||
outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))]
|
outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))]
|
||||||
ort_session.return_value.get_inputs.return_value = inputs
|
ort_session.return_value.get_inputs.return_value = inputs
|
||||||
ort_session.return_value.get_outputs.return_value = outputs
|
ort_session.return_value.get_outputs.return_value = outputs
|
||||||
|
|
||||||
face_recognizer = FaceRecognizer("buffalo_s")
|
face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path)
|
||||||
face_recognizer.load()
|
face_recognizer.load()
|
||||||
|
|
||||||
assert face_recognizer.batch is True
|
assert face_recognizer.batch is True
|
||||||
@@ -520,6 +538,30 @@ class TestFaceRecognition:
|
|||||||
onnx.load.assert_not_called()
|
onnx.load.assert_not_called()
|
||||||
onnx.save.assert_not_called()
|
onnx.save.assert_not_called()
|
||||||
|
|
||||||
|
def test_recognition_does_not_add_batch_axis_for_armnn(
|
||||||
|
self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture
|
||||||
|
) -> None:
|
||||||
|
onnx = mocker.patch("app.models.facial_recognition.recognition.onnx", autospec=True)
|
||||||
|
update_dims = mocker.patch(
|
||||||
|
"app.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True
|
||||||
|
)
|
||||||
|
mocker.patch("app.models.base.InferenceModel.download")
|
||||||
|
mocker.patch("app.models.facial_recognition.recognition.ArcFaceONNX")
|
||||||
|
path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn"
|
||||||
|
|
||||||
|
inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))]
|
||||||
|
outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))]
|
||||||
|
ann_session.return_value.get_inputs.return_value = inputs
|
||||||
|
ann_session.return_value.get_outputs.return_value = outputs
|
||||||
|
|
||||||
|
face_recognizer = FaceRecognizer("buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path)
|
||||||
|
face_recognizer.load()
|
||||||
|
|
||||||
|
assert face_recognizer.batch is False
|
||||||
|
update_dims.assert_not_called()
|
||||||
|
onnx.load.assert_not_called()
|
||||||
|
onnx.save.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
class TestCache:
|
class TestCache:
|
||||||
@@ -693,7 +735,7 @@ class TestLoad:
|
|||||||
mock_model.clear_cache.assert_called_once()
|
mock_model.clear_cache.assert_called_once()
|
||||||
assert mock_model.load.call_count == 2
|
assert mock_model.load.call_count == 2
|
||||||
|
|
||||||
async def test_load_clears_cache_and_raises_if_os_error_and_already_retried(self) -> None:
|
async def test_load_raises_if_os_error_and_already_retried(self) -> None:
|
||||||
mock_model = mock.Mock(spec=InferenceModel)
|
mock_model = mock.Mock(spec=InferenceModel)
|
||||||
mock_model.model_name = "test_model_name"
|
mock_model.model_name = "test_model_name"
|
||||||
mock_model.model_type = ModelType.VISUAL
|
mock_model.model_type = ModelType.VISUAL
|
||||||
@@ -707,6 +749,27 @@ class TestLoad:
|
|||||||
mock_model.clear_cache.assert_not_called()
|
mock_model.clear_cache.assert_not_called()
|
||||||
mock_model.load.assert_not_called()
|
mock_model.load.assert_not_called()
|
||||||
|
|
||||||
|
async def test_falls_back_to_onnx_if_other_format_does_not_exist(
|
||||||
|
self, exception: mock.Mock, warning: mock.Mock
|
||||||
|
) -> 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_format = ModelFormat.ARMNN
|
||||||
|
mock_model.loaded = False
|
||||||
|
mock_model.load_attempts = 0
|
||||||
|
error = FileNotFoundError()
|
||||||
|
mock_model.load.side_effect = [error, None]
|
||||||
|
|
||||||
|
await load(mock_model)
|
||||||
|
|
||||||
|
mock_model.clear_cache.assert_not_called()
|
||||||
|
assert mock_model.load.call_count == 2
|
||||||
|
exception.assert_called_once_with(error)
|
||||||
|
warning.assert_called_once_with("ARMNN is available, but model 'test_model_name' does not support it.")
|
||||||
|
mock_model.model_format = ModelFormat.ONNX
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
not settings.test_full,
|
not settings.test_full,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mambaorg/micromamba:bookworm-slim@sha256:333f7598ff2c2400fb10bfe057709c68b7daab5d847143af85abcf224a07271a as builder
|
FROM mambaorg/micromamba:bookworm-slim@sha256:94d6837f023c0fc0bb68782dd2a984ff7fe0e21ea7e533056c9b8ca060e31de2 as builder
|
||||||
|
|
||||||
ENV TRANSFORMERS_CACHE=/cache \
|
ENV TRANSFORMERS_CACHE=/cache \
|
||||||
PYTHONDONTWRITEBYTECODE=1 \
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
|||||||
550
machine-learning/poetry.lock
generated
550
machine-learning/poetry.lock
generated
@@ -680,13 +680,13 @@ test = ["pytest (>=6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi-slim"
|
name = "fastapi-slim"
|
||||||
version = "0.111.0"
|
version = "0.111.1"
|
||||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "fastapi_slim-0.111.0-py3-none-any.whl", hash = "sha256:6e4b04a555496e5a2590031fcae3ef8e364ad4901b340033e2e1d8136471aca2"},
|
{file = "fastapi_slim-0.111.1-py3-none-any.whl", hash = "sha256:ac29948dcbf84cc78d68ed2c4df4e695ac265cf53c339e5794008476e9befbbb"},
|
||||||
{file = "fastapi_slim-0.111.0.tar.gz", hash = "sha256:100720e4362ec4de97dee83a579b970e79fb5bf48073b37c9ce9b0e63dda4bec"},
|
{file = "fastapi_slim-0.111.1.tar.gz", hash = "sha256:f799a60658f56c49fe3842eb534730fabe1168731c0b407b98a042c8d57be39d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -696,7 +696,7 @@ typing-extensions = ">=4.8.0"
|
|||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
all = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
standard = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.7)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
standard = ["email_validator (>=2.0.0)", "fastapi-cli (>=0.0.2)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
@@ -1236,13 +1236,13 @@ socks = ["socksio (==1.*)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.23.4"
|
version = "0.24.0"
|
||||||
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "huggingface_hub-0.23.4-py3-none-any.whl", hash = "sha256:3a0b957aa87150addf0cc7bd71b4d954b78e749850e1e7fb29ebbd2db64ca037"},
|
{file = "huggingface_hub-0.24.0-py3-none-any.whl", hash = "sha256:7ad92edefb93d8145c061f6df8d99df2ff85f8379ba5fac8a95aca0642afa5d7"},
|
||||||
{file = "huggingface_hub-0.23.4.tar.gz", hash = "sha256:35d99016433900e44ae7efe1c209164a5a81dbbcd53a52f99c281dcd7ce22431"},
|
{file = "huggingface_hub-0.24.0.tar.gz", hash = "sha256:6c7092736b577d89d57b3cdfea026f1b0dc2234ae783fa0d59caf1bf7d52dfa7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1255,17 +1255,17 @@ tqdm = ">=4.42.1"
|
|||||||
typing-extensions = ">=3.7.4.3"
|
typing-extensions = ">=3.7.4.3"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.3.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
|
all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
|
||||||
cli = ["InquirerPy (==0.3.4)"]
|
cli = ["InquirerPy (==0.3.4)"]
|
||||||
dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.3.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
|
dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.5.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"]
|
||||||
fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"]
|
fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"]
|
||||||
hf-transfer = ["hf-transfer (>=0.1.4)"]
|
hf-transfer = ["hf-transfer (>=0.1.4)"]
|
||||||
inference = ["aiohttp", "minijinja (>=1.0)"]
|
inference = ["aiohttp", "minijinja (>=1.0)"]
|
||||||
quality = ["mypy (==1.5.1)", "ruff (>=0.3.0)"]
|
quality = ["mypy (==1.5.1)", "ruff (>=0.5.0)"]
|
||||||
tensorflow = ["graphviz", "pydot", "tensorflow"]
|
tensorflow = ["graphviz", "pydot", "tensorflow"]
|
||||||
tensorflow-testing = ["keras (<3.0)", "tensorflow"]
|
tensorflow-testing = ["keras (<3.0)", "tensorflow"]
|
||||||
testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"]
|
testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio", "jedi", "minijinja (>=1.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"]
|
||||||
torch = ["safetensors", "torch"]
|
torch = ["safetensors[torch]", "torch"]
|
||||||
typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"]
|
typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1530,13 +1530,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "locust"
|
name = "locust"
|
||||||
version = "2.29.0"
|
version = "2.29.1"
|
||||||
description = "Developer-friendly load testing framework"
|
description = "Developer-friendly load testing framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "locust-2.29.0-py3-none-any.whl", hash = "sha256:aa9d94d3604ed9f2aab3248460d91e55d3de980a821dffdf8658b439b049d03f"},
|
{file = "locust-2.29.1-py3-none-any.whl", hash = "sha256:8b15daab44cdf50eef1860a32bb30969423e3795247115e5a37446da3240c6d6"},
|
||||||
{file = "locust-2.29.0.tar.gz", hash = "sha256:649c99ce49d00720a3084c0109547035ad9021222835386599a8b545d31ebe51"},
|
{file = "locust-2.29.1.tar.gz", hash = "sha256:2e0628a59e2689a50cb4735a9a43709e30f2da7ed276c15d877c5325507f44b1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1555,6 +1555,7 @@ requests = [
|
|||||||
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
|
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
|
||||||
]
|
]
|
||||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
|
typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.11\""}
|
||||||
Werkzeug = ">=2.0.0"
|
Werkzeug = ">=2.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1793,44 +1794,44 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
|
{file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"},
|
||||||
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
|
{file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"},
|
||||||
{file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
|
{file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"},
|
||||||
{file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
|
{file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"},
|
||||||
{file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
|
{file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"},
|
||||||
{file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
|
{file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"},
|
||||||
{file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
|
{file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"},
|
||||||
{file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
|
{file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"},
|
||||||
{file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
|
{file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"},
|
||||||
{file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
|
{file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"},
|
||||||
{file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
|
{file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"},
|
||||||
{file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
|
{file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"},
|
||||||
{file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
|
{file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"},
|
||||||
{file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
|
{file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"},
|
||||||
{file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
|
{file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"},
|
||||||
{file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
|
{file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"},
|
||||||
{file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
|
{file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"},
|
||||||
{file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
|
{file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"},
|
||||||
{file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
|
{file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"},
|
||||||
{file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
|
{file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"},
|
||||||
{file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
|
{file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"},
|
||||||
{file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
|
{file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"},
|
||||||
{file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
|
{file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"},
|
||||||
{file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
|
{file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"},
|
||||||
{file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
|
{file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"},
|
||||||
{file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
|
{file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"},
|
||||||
{file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
|
{file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
mypy-extensions = ">=1.0.0"
|
mypy-extensions = ">=1.0.0"
|
||||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
typing-extensions = ">=4.1.0"
|
typing-extensions = ">=4.6.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
@@ -1869,47 +1870,47 @@ test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "1.26.3"
|
version = "1.26.4"
|
||||||
description = "Fundamental package for array computing in Python"
|
description = "Fundamental package for array computing in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "numpy-1.26.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:806dd64230dbbfaca8a27faa64e2f414bf1c6622ab78cc4264f7f5f028fee3bf"},
|
{file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02f98011ba4ab17f46f80f7f8f1c291ee7d855fcef0a5a98db80767a468c85cd"},
|
{file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d45b3ec2faed4baca41c76617fcdcfa4f684ff7a151ce6fc78ad3b6e85af0a6"},
|
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdd2b45bf079d9ad90377048e2747a0c82351989a2165821f0c96831b4a2a54b"},
|
{file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:211ddd1e94817ed2d175b60b6374120244a4dd2287f4ece45d49228b4d529178"},
|
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1240f767f69d7c4c8a29adde2310b871153df9b26b5cb2b54a561ac85146485"},
|
{file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-win32.whl", hash = "sha256:21a9484e75ad018974a2fdaa216524d64ed4212e418e0a551a2d83403b0531d3"},
|
{file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
|
||||||
{file = "numpy-1.26.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e1591f6ae98bcfac2a4bbf9221c0b92ab49762228f38287f6eeb5f3f55905ce"},
|
{file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b831295e5472954104ecb46cd98c08b98b49c69fdb7040483aff799a755a7374"},
|
{file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e87562b91f68dd8b1c39149d0323b42e0082db7ddb8e934ab4c292094d575d6"},
|
{file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c66d6fec467e8c0f975818c1796d25c53521124b7cfb760114be0abad53a0a2"},
|
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f25e2811a9c932e43943a2615e65fc487a0b6b49218899e62e426e7f0a57eeda"},
|
{file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af36e0aa45e25c9f57bf684b1175e59ea05d9a7d3e8e87b7ae1a1da246f2767e"},
|
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:51c7f1b344f302067b02e0f5b5d2daa9ed4a721cf49f070280ac202738ea7f00"},
|
{file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-win32.whl", hash = "sha256:7ca4f24341df071877849eb2034948459ce3a07915c2734f1abb4018d9c49d7b"},
|
{file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
|
||||||
{file = "numpy-1.26.3-cp311-cp311-win_amd64.whl", hash = "sha256:39763aee6dfdd4878032361b30b2b12593fb445ddb66bbac802e2113eb8a6ac4"},
|
{file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7081fd19a6d573e1a05e600c82a1c421011db7935ed0d5c483e9dd96b99cf13"},
|
{file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12c70ac274b32bc00c7f61b515126c9205323703abb99cd41836e8125ea0043e"},
|
{file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f784e13e598e9594750b2ef6729bcd5a47f6cfe4a12cca13def35e06d8163e3"},
|
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f24750ef94d56ce6e33e4019a8a4d68cfdb1ef661a52cdaee628a56d2437419"},
|
{file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:77810ef29e0fb1d289d225cabb9ee6cf4d11978a00bb99f7f8ec2132a84e0166"},
|
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8ed07a90f5450d99dad60d3799f9c03c6566709bd53b497eb9ccad9a55867f36"},
|
{file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-win32.whl", hash = "sha256:f73497e8c38295aaa4741bdfa4fda1a5aedda5473074369eca10626835445511"},
|
{file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
|
||||||
{file = "numpy-1.26.3-cp312-cp312-win_amd64.whl", hash = "sha256:da4b0c6c699a0ad73c810736303f7fbae483bcb012e38d7eb06a5e3b432c981b"},
|
{file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1666f634cb3c80ccbd77ec97bc17337718f56d6658acf5d3b906ca03e90ce87f"},
|
{file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18c3319a7d39b2c6a9e3bb75aab2304ab79a811ac0168a671a62e6346c29b03f"},
|
{file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b7e807d6888da0db6e7e75838444d62495e2b588b99e90dd80c3459594e857b"},
|
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4d362e17bcb0011738c2d83e0a65ea8ce627057b2fdda37678f4374a382a137"},
|
{file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b8c275f0ae90069496068c714387b4a0eba5d531aace269559ff2b43655edd58"},
|
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc0743f0302b94f397a4a65a660d4cd24267439eb16493fb3caad2e4389bccbb"},
|
{file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-win32.whl", hash = "sha256:9bc6d1a7f8cedd519c4b7b1156d98e051b726bf160715b769106661d567b3f03"},
|
{file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
|
||||||
{file = "numpy-1.26.3-cp39-cp39-win_amd64.whl", hash = "sha256:867e3644e208c8922a3be26fc6bbf112a035f50f0a86497f98f228c50c607bb2"},
|
{file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
|
||||||
{file = "numpy-1.26.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c67423b3703f8fbd90f5adaa37f85b5794d3366948efe9a5190a5f3a83fc34e"},
|
{file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
|
||||||
{file = "numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46f47ee566d98849323f01b349d58f2557f02167ee301e5e28809a8c0e27a2d0"},
|
{file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
|
||||||
{file = "numpy-1.26.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a8474703bffc65ca15853d5fd4d06b18138ae90c17c8d12169968e998e448bb5"},
|
{file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
|
||||||
{file = "numpy-1.26.3.tar.gz", hash = "sha256:697df43e2b6310ecc9d95f05d5ef20eacc09c7c4ecc9da3f235d39e71b7da1e4"},
|
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1961,69 +1962,69 @@ reference = ["Pillow", "google-re2"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime"
|
name = "onnxruntime"
|
||||||
version = "1.18.0"
|
version = "1.18.1"
|
||||||
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "onnxruntime-1.18.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:5a3b7993a5ecf4a90f35542a4757e29b2d653da3efe06cdd3164b91167bbe10d"},
|
{file = "onnxruntime-1.18.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ef7683312393d4ba04252f1b287d964bd67d5e6048b94d2da3643986c74d80"},
|
||||||
{file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15b944623b2cdfe7f7945690bfb71c10a4531b51997c8320b84e7b0bb59af902"},
|
{file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc706eb1df06ddf55776e15a30519fb15dda7697f987a2bbda4962845e3cec05"},
|
||||||
{file = "onnxruntime-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e61ce5005118064b1a0ed73ebe936bc773a102f067db34108ea6c64dd62a179"},
|
{file = "onnxruntime-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7de69f5ced2a263531923fa68bbec52a56e793b802fcd81a03487b5e292bc3a"},
|
||||||
{file = "onnxruntime-1.18.0-cp310-cp310-win32.whl", hash = "sha256:a4fc8a2a526eb442317d280610936a9f73deece06c7d5a91e51570860802b93f"},
|
{file = "onnxruntime-1.18.1-cp310-cp310-win32.whl", hash = "sha256:221e5b16173926e6c7de2cd437764492aa12b6811f45abd37024e7cf2ae5d7e3"},
|
||||||
{file = "onnxruntime-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:71ed219b768cab004e5cd83e702590734f968679bf93aa488c1a7ffbe6e220c3"},
|
{file = "onnxruntime-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:75211b619275199c861ee94d317243b8a0fcde6032e5a80e1aa9ded8ab4c6060"},
|
||||||
{file = "onnxruntime-1.18.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:3d24bd623872a72a7fe2f51c103e20fcca2acfa35d48f2accd6be1ec8633d960"},
|
{file = "onnxruntime-1.18.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:f26582882f2dc581b809cfa41a125ba71ad9e715738ec6402418df356969774a"},
|
||||||
{file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f15e41ca9b307a12550bfd2ec93f88905d9fba12bab7e578f05138ad0ae10d7b"},
|
{file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef36f3a8b768506d02be349ac303fd95d92813ba3ba70304d40c3cd5c25d6a4c"},
|
||||||
{file = "onnxruntime-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f45ca2887f62a7b847d526965686b2923efa72538c89b7703c7b3fe970afd59"},
|
{file = "onnxruntime-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:170e711393e0618efa8ed27b59b9de0ee2383bd2a1f93622a97006a5ad48e434"},
|
||||||
{file = "onnxruntime-1.18.0-cp311-cp311-win32.whl", hash = "sha256:9e24d9ecc8781323d9e2eeda019b4b24babc4d624e7d53f61b1fe1a929b0511a"},
|
{file = "onnxruntime-1.18.1-cp311-cp311-win32.whl", hash = "sha256:9b6a33419b6949ea34e0dc009bc4470e550155b6da644571ecace4b198b0d88f"},
|
||||||
{file = "onnxruntime-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:f8608398976ed18aef450d83777ff6f77d0b64eced1ed07a985e1a7db8ea3771"},
|
{file = "onnxruntime-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:5c1380a9f1b7788da742c759b6a02ba771fe1ce620519b2b07309decbd1a2fe1"},
|
||||||
{file = "onnxruntime-1.18.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f1d79941f15fc40b1ee67738b2ca26b23e0181bf0070b5fb2984f0988734698f"},
|
{file = "onnxruntime-1.18.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:31bd57a55e3f983b598675dfc7e5d6f0877b70ec9864b3cc3c3e1923d0a01919"},
|
||||||
{file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e8caf3a8565c853a22d323a3eebc2a81e3de7591981f085a4f74f7a60aab2d"},
|
{file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9e03c4ba9f734500691a4d7d5b381cd71ee2f3ce80a1154ac8f7aed99d1ecaa"},
|
||||||
{file = "onnxruntime-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:498d2b8380635f5e6ebc50ec1b45f181588927280f32390fb910301d234f97b8"},
|
{file = "onnxruntime-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:781aa9873640f5df24524f96f6070b8c550c66cb6af35710fd9f92a20b4bfbf6"},
|
||||||
{file = "onnxruntime-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ba7cc0ce2798a386c082aaa6289ff7e9bedc3dee622eef10e74830cff200a72e"},
|
{file = "onnxruntime-1.18.1-cp312-cp312-win32.whl", hash = "sha256:3a2d9ab6254ca62adbb448222e630dc6883210f718065063518c8f93a32432be"},
|
||||||
{file = "onnxruntime-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:1fa175bd43f610465d5787ae06050c81f7ce09da2bf3e914eb282cb8eab363ef"},
|
{file = "onnxruntime-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:ad93c560b1c38c27c0275ffd15cd7f45b3ad3fc96653c09ce2931179982ff204"},
|
||||||
{file = "onnxruntime-1.18.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:0284c579c20ec8b1b472dd190290a040cc68b6caec790edb960f065d15cf164a"},
|
{file = "onnxruntime-1.18.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:3b55dc9d3c67626388958a3eb7ad87eb7c70f75cb0f7ff4908d27b8b42f2475c"},
|
||||||
{file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d47353d036d8c380558a5643ea5f7964d9d259d31c86865bad9162c3e916d1f6"},
|
{file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f80dbcfb6763cc0177a31168b29b4bd7662545b99a19e211de8c734b657e0669"},
|
||||||
{file = "onnxruntime-1.18.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:885509d2b9ba4b01f08f7fa28d31ee54b6477953451c7ccf124a84625f07c803"},
|
{file = "onnxruntime-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1ff2c61a16d6c8631796c54139bafea41ee7736077a0fc64ee8ae59432f5c58"},
|
||||||
{file = "onnxruntime-1.18.0-cp38-cp38-win32.whl", hash = "sha256:8614733de3695656411d71fc2f39333170df5da6c7efd6072a59962c0bc7055c"},
|
{file = "onnxruntime-1.18.1-cp38-cp38-win32.whl", hash = "sha256:219855bd272fe0c667b850bf1a1a5a02499269a70d59c48e6f27f9c8bcb25d02"},
|
||||||
{file = "onnxruntime-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:47af3f803752fce23ea790fd8d130a47b2b940629f03193f780818622e856e7a"},
|
{file = "onnxruntime-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:afdf16aa607eb9a2c60d5ca2d5abf9f448e90c345b6b94c3ed14f4fb7e6a2d07"},
|
||||||
{file = "onnxruntime-1.18.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:9153eb2b4d5bbab764d0aea17adadffcfc18d89b957ad191b1c3650b9930c59f"},
|
{file = "onnxruntime-1.18.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:128df253ade673e60cea0955ec9d0e89617443a6d9ce47c2d79eb3f72a3be3de"},
|
||||||
{file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c7fd86eca727c989bb8d9c5104f3c45f7ee45f445cc75579ebe55d6b99dfd7c"},
|
{file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9839491e77e5c5a175cab3621e184d5a88925ee297ff4c311b68897197f4cde9"},
|
||||||
{file = "onnxruntime-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac67a4de9c1326c4d87bcbfb652c923039b8a2446bb28516219236bec3b494f5"},
|
{file = "onnxruntime-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad3187c1faff3ac15f7f0e7373ef4788c582cafa655a80fdbb33eaec88976c66"},
|
||||||
{file = "onnxruntime-1.18.0-cp39-cp39-win32.whl", hash = "sha256:6ffb445816d06497df7a6dd424b20e0b2c39639e01e7fe210e247b82d15a23b9"},
|
{file = "onnxruntime-1.18.1-cp39-cp39-win32.whl", hash = "sha256:34657c78aa4e0b5145f9188b550ded3af626651b15017bf43d280d7e23dbf195"},
|
||||||
{file = "onnxruntime-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:46de6031cb6745f33f7eca9e51ab73e8c66037fb7a3b6b4560887c5b55ab5d5d"},
|
{file = "onnxruntime-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:9c14fd97c3ddfa97da5feef595e2c73f14c2d0ec1d4ecbea99c8d96603c89589"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
coloredlogs = "*"
|
coloredlogs = "*"
|
||||||
flatbuffers = "*"
|
flatbuffers = "*"
|
||||||
numpy = ">=1.21.6"
|
numpy = ">=1.21.6,<2.0"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
protobuf = "*"
|
protobuf = "*"
|
||||||
sympy = "*"
|
sympy = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime-gpu"
|
name = "onnxruntime-gpu"
|
||||||
version = "1.18.0"
|
version = "1.18.1"
|
||||||
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:039be9a6b7f71c6739e97eec79f4bf240793a7c0c4108a09e0e1a27b4c33dbca"},
|
{file = "onnxruntime_gpu-1.18.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e9a52f5d43a84fe29e135da6bf10daa18836c81bed9060a5924efd6afc0d259"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:afd4bc090b9412ab695cb34c05f4f92f88dbb6bd52d9b38658ad0115c50ff653"},
|
{file = "onnxruntime_gpu-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:e7c1c665e8a11a5cf15369948b04288dc0a6812ad2e6beaff93a3d157c864d9a"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2280f94c6be2717f010a73c30a94c2721af853c6b7110e83afa52d03de6614a8"},
|
{file = "onnxruntime_gpu-1.18.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1334f802cb1e4e2eb6ceebc4ef71ba44f3ef444d34216baafb940368a7a5d2f5"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:9e3b4e9a0171e53a71001805b9b0e1a98cbad5a413d795c0e132b0f058b386d6"},
|
{file = "onnxruntime_gpu-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ffcc711e89b80c935d5172544f8a605b11525fc1e6f0e78ee79e2c28956e2d9"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:546fbf2dcb7a2830ca69bde0c38665a88a9454e923ebb76bedf85eaed33a6f4a"},
|
{file = "onnxruntime_gpu-1.18.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbb1a6c986b2392eebaebc43e198a1614e3f7d2c191725002dbfa0dceb24454b"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:24146aa670c45734d9b8583cd78bd790363bc8695a3808d129ec913186064e4c"},
|
{file = "onnxruntime_gpu-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:bee352929e6eec2ff4e11e323a025ed8bd5eac24795005bc502ac740971fa7bd"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:75884c51c2aa47c349de3b5485df7f9573e1b89c607dd55984d3fe40615ef002"},
|
{file = "onnxruntime_gpu-1.18.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:76d307a849a863d0457869febe4b2fd2fc07c7f26385c7339d17066312fa6be0"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:f3b00a7443252dbfbd18ff72bcc2f44066fad9128eaa29bff8b315a834241701"},
|
{file = "onnxruntime_gpu-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:b7498d6c64a03558308ce6d7d14dab306ea90d1204b563890c4d2d26c1b520f0"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:14f53bd74ad21c61fee55eca988758e5eec4c39450040c8986ec3a960cb127a8"},
|
{file = "onnxruntime_gpu-1.18.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1d8113cb4b8a51b195fae91cfeb6849728462a4b46aaf51b6764c44e54f81f"},
|
||||||
{file = "onnxruntime_gpu-1.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:aea5c02b3a0ee6682214a61a2a0467773401b075afdcb41dc2ef595f41c2d185"},
|
{file = "onnxruntime_gpu-1.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:fc1d2544a39f5db64c5b8a0c24d0b934d7d64682e6d70763eb2cc726b1fd6c3f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
coloredlogs = "*"
|
coloredlogs = "*"
|
||||||
flatbuffers = "*"
|
flatbuffers = "*"
|
||||||
numpy = ">=1.21.6"
|
numpy = ">=1.21.6,<2.0"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
protobuf = "*"
|
protobuf = "*"
|
||||||
sympy = "*"
|
sympy = "*"
|
||||||
@@ -2035,22 +2036,22 @@ reference = "cuda12"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime-openvino"
|
name = "onnxruntime-openvino"
|
||||||
version = "1.17.1"
|
version = "1.18.0"
|
||||||
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
description = "ONNX Runtime is a runtime accelerator for Machine Learning models"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
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.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:565b874d21bcd48126da7d62f57db019f5ec0e1f82ae9b0740afa2ad91f8d331"},
|
||||||
{file = "onnxruntime_openvino-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:5152b5e56e83e022ced2986700d68dd8ba7b1466761725ce774f679c5710ab87"},
|
{file = "onnxruntime_openvino-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f1931060f710a6c8e32121bb73044c4772ef5925802fc8776d3fe1e87ab3f75"},
|
||||||
{file = "onnxruntime_openvino-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce3b1aa06d6b8b732d314d217028ec4735de5806215c44d3bdbcad03b9260d5"},
|
{file = "onnxruntime_openvino-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb1723d386f70a8e26398d983ebe35d2c25ba56e9cdb382670ebbf1f5139f8ba"},
|
||||||
{file = "onnxruntime_openvino-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:21133a701bb07ea19e01f48b8c23beee575f2e879f49173843f275d7c91a625a"},
|
{file = "onnxruntime_openvino-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:874a1e263dd86674593e5a879257650b06a8609c4d5768c3d8ed8dc4ae874b9c"},
|
||||||
{file = "onnxruntime_openvino-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76824dac3c392ad4b812f29c18be2055ab3bba2e3c111e44baae847b33d5b081"},
|
{file = "onnxruntime_openvino-1.18.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:597eb18f3de7ead69b08a242d74c4573b28bbfba40ca2a1a40f75bf7a834808e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
coloredlogs = "*"
|
coloredlogs = "*"
|
||||||
flatbuffers = "*"
|
flatbuffers = "*"
|
||||||
numpy = ">=1.25.2"
|
numpy = ">=1.26.4"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
protobuf = "*"
|
protobuf = "*"
|
||||||
sympy = "*"
|
sympy = "*"
|
||||||
@@ -2081,57 +2082,62 @@ numpy = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orjson"
|
name = "orjson"
|
||||||
version = "3.10.5"
|
version = "3.10.6"
|
||||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "orjson-3.10.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:545d493c1f560d5ccfc134803ceb8955a14c3fcb47bbb4b2fee0232646d0b932"},
|
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4324929c2dd917598212bfd554757feca3e5e0fa60da08be11b4aa8b90013c1"},
|
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c13ca5e2ddded0ce6a927ea5a9f27cae77eee4c75547b4297252cb20c4d30e6"},
|
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6c8e30adfa52c025f042a87f450a6b9ea29649d828e0fec4858ed5e6caecf63"},
|
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:338fd4f071b242f26e9ca802f443edc588fa4ab60bfa81f38beaedf42eda226c"},
|
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6970ed7a3126cfed873c5d21ece1cd5d6f83ca6c9afb71bbae21a0b034588d96"},
|
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"},
|
||||||
{file = "orjson-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:235dadefb793ad12f7fa11e98a480db1f7c6469ff9e3da5e73c7809c700d746b"},
|
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"},
|
||||||
{file = "orjson-3.10.5-cp310-none-win32.whl", hash = "sha256:be79e2393679eda6a590638abda16d167754393f5d0850dcbca2d0c3735cebe2"},
|
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"},
|
||||||
{file = "orjson-3.10.5-cp310-none-win_amd64.whl", hash = "sha256:c4a65310ccb5c9910c47b078ba78e2787cb3878cdded1702ac3d0da71ddc5228"},
|
{file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cdf7365063e80899ae3a697def1277c17a7df7ccfc979990a403dfe77bb54d40"},
|
{file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b68742c469745d0e6ca5724506858f75e2f1e5b59a4315861f9e2b1df77775a"},
|
{file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7d10cc1b594951522e35a3463da19e899abe6ca95f3c84c69e9e901e0bd93d38"},
|
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcbe82b35d1ac43b0d84072408330fd3295c2896973112d495e7234f7e3da2e1"},
|
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c0eb7e0c75e1e486c7563fe231b40fdd658a035ae125c6ba651ca3b07936f5"},
|
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53ed1c879b10de56f35daf06dbc4a0d9a5db98f6ee853c2dbd3ee9d13e6f302f"},
|
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"},
|
||||||
{file = "orjson-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:099e81a5975237fda3100f918839af95f42f981447ba8f47adb7b6a3cdb078fa"},
|
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"},
|
||||||
{file = "orjson-3.10.5-cp311-none-win32.whl", hash = "sha256:1146bf85ea37ac421594107195db8bc77104f74bc83e8ee21a2e58596bfb2f04"},
|
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"},
|
||||||
{file = "orjson-3.10.5-cp311-none-win_amd64.whl", hash = "sha256:36a10f43c5f3a55c2f680efe07aa93ef4a342d2960dd2b1b7ea2dd764fe4a37c"},
|
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:68f85ecae7af14a585a563ac741b0547a3f291de81cd1e20903e79f25170458f"},
|
{file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28afa96f496474ce60d3340fe8d9a263aa93ea01201cd2bad844c45cd21f5268"},
|
{file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cd684927af3e11b6e754df80b9ffafd9fb6adcaa9d3e8fdd5891be5a5cad51e"},
|
{file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d21b9983da032505f7050795e98b5d9eee0df903258951566ecc358f6696969"},
|
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ad1de7fef79736dde8c3554e75361ec351158a906d747bd901a52a5c9c8d24b"},
|
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d97531cdfe9bdd76d492e69800afd97e5930cb0da6a825646667b2c6c6c0211"},
|
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"},
|
||||||
{file = "orjson-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d69858c32f09c3e1ce44b617b3ebba1aba030e777000ebdf72b0d8e365d0b2b3"},
|
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"},
|
||||||
{file = "orjson-3.10.5-cp312-none-win32.whl", hash = "sha256:64c9cc089f127e5875901ac05e5c25aa13cfa5dbbbd9602bda51e5c611d6e3e2"},
|
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"},
|
||||||
{file = "orjson-3.10.5-cp312-none-win_amd64.whl", hash = "sha256:b2efbd67feff8c1f7728937c0d7f6ca8c25ec81373dc8db4ef394c1d93d13dc5"},
|
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:03b565c3b93f5d6e001db48b747d31ea3819b89abf041ee10ac6988886d18e01"},
|
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:584c902ec19ab7928fd5add1783c909094cc53f31ac7acfada817b0847975f26"},
|
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a35455cc0b0b3a1eaf67224035f5388591ec72b9b6136d66b49a553ce9eb1e6"},
|
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1670fe88b116c2745a3a30b0f099b699a02bb3482c2591514baf5433819e4f4d"},
|
{file = "orjson-3.10.6-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:66680eae4c4e7fc193d91cfc1353ad6d01b4801ae9b5314f17e11ba55e934183"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185c394ef45b18b9a7d8e8f333606e2e8194a50c6e3c664215aae8cf42c5385e"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caff75b425db5ef8e8f23af93c80f072f97b4fb3afd4af44482905c9f588da28"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ca0b3a94ac8d3886c9581b9f9de3ce858263865fdaa383fbc31c310b9eac07c9"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3722fddb821b6036fd2a3c814f6bd9b57a89dc6337b9924ecd614ebce3271394"},
|
||||||
{file = "orjson-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dfc91d4720d48e2a709e9c368d5125b4b5899dced34b5400c3837dadc7d6271b"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2c116072a8533f2fec435fde4d134610f806bdac20188c7bd2081f3e9e0133f"},
|
||||||
{file = "orjson-3.10.5-cp38-none-win32.whl", hash = "sha256:c05f16701ab2a4ca146d0bca950af254cb7c02f3c01fca8efbbad82d23b3d9d4"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6eeb13218c8cf34c61912e9df2de2853f1d009de0e46ea09ccdf3d757896af0a"},
|
||||||
{file = "orjson-3.10.5-cp38-none-win_amd64.whl", hash = "sha256:8a11d459338f96a9aa7f232ba95679fc0c7cedbd1b990d736467894210205c09"},
|
{file = "orjson-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:965a916373382674e323c957d560b953d81d7a8603fbeee26f7b8248638bd48b"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:85c89131d7b3218db1b24c4abecea92fd6c7f9fab87441cfc342d3acc725d807"},
|
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03c95484d53ed8e479cade8628c9cea00fd9d67f5554764a1110e0d5aa2de96e"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66215277a230c456f9038d5e2d84778141643207f85336ef8d2a9da26bd7ca"},
|
{file = "orjson-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e060748a04cccf1e0a6f2358dffea9c080b849a4a68c28b1b907f272b5127e9b"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51bbcdea96cdefa4a9b4461e690c75ad4e33796530d182bdd5c38980202c134a"},
|
{file = "orjson-3.10.6-cp38-none-win32.whl", hash = "sha256:738dbe3ef909c4b019d69afc19caf6b5ed0e2f1c786b5d6215fbb7539246e4c6"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbead71dbe65f959b7bd8cf91e0e11d5338033eba34c114f69078d59827ee139"},
|
{file = "orjson-3.10.6-cp38-none-win_amd64.whl", hash = "sha256:d40f839dddf6a7d77114fe6b8a70218556408c71d4d6e29413bb5f150a692ff7"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df58d206e78c40da118a8c14fc189207fffdcb1f21b3b4c9c0c18e839b5a214"},
|
{file = "orjson-3.10.6-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:697a35a083c4f834807a6232b3e62c8b280f7a44ad0b759fd4dce748951e70db"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4057c3b511bb8aef605616bd3f1f002a697c7e4da6adf095ca5b84c0fd43595"},
|
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd502f96bf5ea9a61cbc0b2b5900d0dd68aa0da197179042bdd2be67e51a1e4b"},
|
||||||
{file = "orjson-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b39e006b00c57125ab974362e740c14a0c6a66ff695bff44615dcf4a70ce2b86"},
|
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f215789fb1667cdc874c1b8af6a84dc939fd802bf293a8334fce185c79cd359b"},
|
||||||
{file = "orjson-3.10.5-cp39-none-win32.whl", hash = "sha256:eded5138cc565a9d618e111c6d5c2547bbdd951114eb822f7f6309e04db0fb47"},
|
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2debd8ddce948a8c0938c8c93ade191d2f4ba4649a54302a7da905a81f00b56"},
|
||||||
{file = "orjson-3.10.5-cp39-none-win_amd64.whl", hash = "sha256:cc28e90a7cae7fcba2493953cff61da5a52950e78dc2dacfe931a317ee3d8de7"},
|
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5410111d7b6681d4b0d65e0f58a13be588d01b473822483f77f513c7f93bd3b2"},
|
||||||
{file = "orjson-3.10.5.tar.gz", hash = "sha256:7a5baef8a4284405d96c90c7c62b755e9ef1ada84c2406c24a9ebec86b89f46d"},
|
{file = "orjson-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb1f28a137337fdc18384079fa5726810681055b32b92253fa15ae5656e1dddb"},
|
||||||
|
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf2fbbce5fe7cd1aa177ea3eab2b8e6a6bc6e8592e4279ed3db2d62e57c0e1b2"},
|
||||||
|
{file = "orjson-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:79b9b9e33bd4c517445a62b90ca0cc279b0f1f3970655c3df9e608bc3f91741a"},
|
||||||
|
{file = "orjson-3.10.6-cp39-none-win32.whl", hash = "sha256:30b0a09a2014e621b1adf66a4f705f0809358350a757508ee80209b2d8dae219"},
|
||||||
|
{file = "orjson-3.10.6-cp39-none-win_amd64.whl", hash = "sha256:49e3bc615652617d463069f91b867a4458114c5b104e13b7ae6872e5f79d0844"},
|
||||||
|
{file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2158,84 +2164,95 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "10.3.0"
|
version = "10.4.0"
|
||||||
description = "Python Imaging Library (Fork)"
|
description = "Python Imaging Library (Fork)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
|
{file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
|
{file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
|
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
|
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
|
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
|
{file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
|
{file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
|
||||||
{file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
|
{file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
|
{file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
|
{file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
|
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
|
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
|
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
|
{file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
|
{file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
|
||||||
{file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
|
{file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
|
{file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
|
{file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
|
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
|
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
|
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
|
{file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
|
{file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
|
||||||
{file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
|
{file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
|
{file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
|
{file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
|
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
|
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
|
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
|
{file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"},
|
||||||
{file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
|
{file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
|
{file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
|
{file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
|
{file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
|
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
|
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
|
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
|
{file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"},
|
||||||
{file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
|
{file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
|
{file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
|
{file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
|
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
|
||||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
|
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
|
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
|
{file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
|
{file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
|
{file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
|
||||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
|
||||||
{file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
|
||||||
|
{file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
|
||||||
|
{file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
|
||||||
|
{file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
|
docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||||
fpx = ["olefile"]
|
fpx = ["olefile"]
|
||||||
mic = ["olefile"]
|
mic = ["olefile"]
|
||||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
@@ -2471,13 +2488,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest-asyncio"
|
name = "pytest-asyncio"
|
||||||
version = "0.23.7"
|
version = "0.23.8"
|
||||||
description = "Pytest support for asyncio"
|
description = "Pytest support for asyncio"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"},
|
{file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
|
||||||
{file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"},
|
{file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2810,28 +2827,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.4.10"
|
version = "0.5.4"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"},
|
{file = "ruff-0.5.4-py3-none-linux_armv6l.whl", hash = "sha256:82acef724fc639699b4d3177ed5cc14c2a5aacd92edd578a9e846d5b5ec18ddf"},
|
||||||
{file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"},
|
{file = "ruff-0.5.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:da62e87637c8838b325e65beee485f71eb36202ce8e3cdbc24b9fcb8b99a37be"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"},
|
{file = "ruff-0.5.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e98ad088edfe2f3b85a925ee96da652028f093d6b9b56b76fc242d8abb8e2059"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c55efbecc3152d614cfe6c2247a3054cfe358cefbf794f8c79c8575456efe19"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9b85eaa1f653abd0a70603b8b7008d9e00c9fa1bbd0bf40dad3f0c0bdd06793"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cf497a47751be8c883059c4613ba2f50dd06ec672692de2811f039432875278"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:09c14ed6a72af9ccc8d2e313d7acf7037f0faff43cde4b507e66f14e812e37f7"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:628f6b8f97b8bad2490240aa84f3e68f390e13fabc9af5c0d3b96b485921cd60"},
|
||||||
{file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3520a00c0563d7a7a7c324ad7e2cde2355733dafa9592c671fb2e9e3cd8194c1"},
|
||||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"},
|
{file = "ruff-0.5.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93789f14ca2244fb91ed481456f6d0bb8af1f75a330e133b67d08f06ad85b516"},
|
||||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"},
|
{file = "ruff-0.5.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:029454e2824eafa25b9df46882f7f7844d36fd8ce51c1b7f6d97e2615a57bbcc"},
|
||||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"},
|
{file = "ruff-0.5.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9492320eed573a13a0bc09a2957f17aa733fff9ce5bf00e66e6d4a88ec33813f"},
|
||||||
{file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"},
|
{file = "ruff-0.5.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6e1f62a92c645e2919b65c02e79d1f61e78a58eddaebca6c23659e7c7cb4ac7"},
|
||||||
{file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"},
|
{file = "ruff-0.5.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:768fa9208df2bec4b2ce61dbc7c2ddd6b1be9fb48f1f8d3b78b3332c7d71c1ff"},
|
||||||
{file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"},
|
{file = "ruff-0.5.4-py3-none-win32.whl", hash = "sha256:e1e7393e9c56128e870b233c82ceb42164966f25b30f68acbb24ed69ce9c3a4e"},
|
||||||
{file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"},
|
{file = "ruff-0.5.4-py3-none-win_amd64.whl", hash = "sha256:58b54459221fd3f661a7329f177f091eb35cf7a603f01d9eb3eb11cc348d38c4"},
|
||||||
{file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"},
|
{file = "ruff-0.5.4-py3-none-win_arm64.whl", hash = "sha256:bd53da65f1085fb5b307c38fd3c0829e76acf7b2a912d8d79cadcdb4875c1eb7"},
|
||||||
|
{file = "ruff-0.5.4.tar.gz", hash = "sha256:2795726d5f71c4f4e70653273d1c23a8182f07dd8e48c12de5d867bfb7557eed"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.107.1"
|
version = "1.111.0"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
13
machine-learning/scripts/configure-apt.sh
Executable file
13
machine-learning/scripts/configure-apt.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
sed -i -e's/ main/ main contrib non-free non-free-firmware/g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
sed -i -e's/ bookworm-updates/ bookworm-updates sid/g' /etc/apt/sources.list.d/debian.sources
|
||||||
|
|
||||||
|
# default priority is 500, so we set unstable to 450 to prefer stable packages
|
||||||
|
cat > /etc/apt/preferences.d/preferences << EOL
|
||||||
|
Package: *
|
||||||
|
Pin: release a=unstable
|
||||||
|
Pin-Priority: 450
|
||||||
|
EOL
|
||||||
@@ -5,12 +5,14 @@ lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
|
|||||||
if ! [ "$DEVICE" = "openvino" ]; then
|
if ! [ "$DEVICE" = "openvino" ]; then
|
||||||
export LD_PRELOAD="$lib_path"
|
export LD_PRELOAD="$lib_path"
|
||||||
export LD_BIND_NOW=1
|
export LD_BIND_NOW=1
|
||||||
|
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
|
||||||
|
else
|
||||||
|
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=300}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
: "${IMMICH_HOST:=[::]}"
|
: "${IMMICH_HOST:=[::]}"
|
||||||
: "${IMMICH_PORT:=3003}"
|
: "${IMMICH_PORT:=3003}"
|
||||||
: "${MACHINE_LEARNING_WORKERS:=1}"
|
: "${MACHINE_LEARNING_WORKERS:=1}"
|
||||||
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
|
|
||||||
|
|
||||||
gunicorn app.main:app \
|
gunicorn app.main:app \
|
||||||
-k app.config.CustomUvicornWorker \
|
-k app.config.CustomUvicornWorker \
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutter": "3.22.2"
|
"flutter": "3.22.3"
|
||||||
}
|
}
|
||||||
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.22.1",
|
"dart.flutterSdkPath": ".fvm/versions/3.22.3",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
<application android:label="Immich" android:name=".ImmichApp" android:usesCleartextTraffic="true"
|
||||||
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true" android:largeHeap="true">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 146,
|
"android.injected.version.code" => 152,
|
||||||
"android.injected.version.name" => "1.107.1",
|
"android.injected.version.name" => "1.111.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "يلغي",
|
"action_common_cancel": "يلغي",
|
||||||
"action_common_clear": "مسح",
|
"action_common_clear": "مسح",
|
||||||
"action_common_confirm": "تأكيد",
|
"action_common_confirm": "تأكيد",
|
||||||
|
"action_common_save": "Save",
|
||||||
|
"action_common_select": "Select",
|
||||||
"action_common_update": "تحديث",
|
"action_common_update": "تحديث",
|
||||||
"add_to_album_bottom_sheet_added": "تمت الاضافة{album}",
|
"add_to_album_bottom_sheet_added": "تمت الاضافة{album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "موجودة مسبقا {album}",
|
"add_to_album_bottom_sheet_already_exists": "موجودة مسبقا {album}",
|
||||||
@@ -141,11 +143,21 @@
|
|||||||
"change_password_form_new_password": "كلمة المرور الجديدة",
|
"change_password_form_new_password": "كلمة المرور الجديدة",
|
||||||
"change_password_form_password_mismatch": "كلمة المرور غير مطابقة",
|
"change_password_form_password_mismatch": "كلمة المرور غير مطابقة",
|
||||||
"change_password_form_reenter_new_password": "أعد إدخال كلمة مرور جديدة",
|
"change_password_form_reenter_new_password": "أعد إدخال كلمة مرور جديدة",
|
||||||
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
|
"client_cert_enter_password": "Enter Password",
|
||||||
|
"client_cert_import": "Import",
|
||||||
|
"client_cert_import_success_msg": "Client certificate is imported",
|
||||||
|
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
||||||
|
"client_cert_remove": "Remove",
|
||||||
|
"client_cert_remove_msg": "Client certificate is removed",
|
||||||
|
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
|
||||||
|
"client_cert_title": "SSL Client Certificate",
|
||||||
"common_add_to_album": "أضف إلى الألبوم",
|
"common_add_to_album": "أضف إلى الألبوم",
|
||||||
"common_change_password": "تغيير كلمة المرور",
|
"common_change_password": "تغيير كلمة المرور",
|
||||||
"common_create_new_album": "إنشاء ألبوم جديد",
|
"common_create_new_album": "إنشاء ألبوم جديد",
|
||||||
"common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.",
|
"common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.",
|
||||||
"common_shared": "مشترك",
|
"common_shared": "مشترك",
|
||||||
|
"contextual_search": "Sunrise on the beach",
|
||||||
"control_bottom_app_bar_add_to_album": "أضف إلى الألبوم",
|
"control_bottom_app_bar_add_to_album": "أضف إلى الألبوم",
|
||||||
"control_bottom_app_bar_album_info": "{} items",
|
"control_bottom_app_bar_album_info": "{} items",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
"control_bottom_app_bar_delete": "يمسح",
|
"control_bottom_app_bar_delete": "يمسح",
|
||||||
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
"control_bottom_app_bar_delete_from_immich": " حذف منال تطبيق",
|
||||||
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
"control_bottom_app_bar_delete_from_local": "حذف من الجهاز",
|
||||||
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
"control_bottom_app_bar_edit_location": "تحديد الوجهة",
|
||||||
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
|
||||||
"control_bottom_app_bar_favorite": "مفضل",
|
"control_bottom_app_bar_favorite": "مفضل",
|
||||||
@@ -203,6 +216,7 @@
|
|||||||
"experimental_settings_title": "تجريبي",
|
"experimental_settings_title": "تجريبي",
|
||||||
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
||||||
"favorites_page_title": "المفضلة",
|
"favorites_page_title": "المفضلة",
|
||||||
|
"filename_search": "File name or extension",
|
||||||
"haptic_feedback_switch": "تمكين ردود الفعل اللمسية",
|
"haptic_feedback_switch": "تمكين ردود الفعل اللمسية",
|
||||||
"haptic_feedback_title": "ردود فعل لمسية",
|
"haptic_feedback_title": "ردود فعل لمسية",
|
||||||
"header_settings_add_header_tip": "Add Header",
|
"header_settings_add_header_tip": "Add Header",
|
||||||
@@ -230,6 +244,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
"image_viewer_page_state_provider_download_started": "بدأ التنزيل",
|
||||||
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
"image_viewer_page_state_provider_download_success": "تم التنزيل بنجاح",
|
||||||
"image_viewer_page_state_provider_share_error": "خطأ في المشاركة",
|
"image_viewer_page_state_provider_share_error": "خطأ في المشاركة",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
|
"invalid_date_format": "Invalid date format",
|
||||||
"library_page_albums": "ألبومات",
|
"library_page_albums": "ألبومات",
|
||||||
"library_page_archive": "أرشيف",
|
"library_page_archive": "أرشيف",
|
||||||
"library_page_device_albums": "ألبومات على الجهاز",
|
"library_page_device_albums": "ألبومات على الجهاز",
|
||||||
@@ -311,6 +327,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "لا يمكن تعديل تاريخ الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
"multiselect_grid_edit_date_time_err_read_only": "لا يمكن تعديل تاريخ الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
"multiselect_grid_edit_gps_err_read_only": "لا يمكن تعديل موقع الأصول (المواد) للقراءة فقط، سوف يتخطى",
|
||||||
"no_assets_to_show": "لا توجد أصول لعرضها",
|
"no_assets_to_show": "لا توجد أصول لعرضها",
|
||||||
|
"no_name": "No name",
|
||||||
"notification_permission_dialog_cancel": "يلغي",
|
"notification_permission_dialog_cancel": "يلغي",
|
||||||
"notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.",
|
"notification_permission_dialog_content": "لتمكين الإخطارات ، انتقل إلى الإعدادات و اختار السماح.",
|
||||||
"notification_permission_dialog_settings": "إعدادات",
|
"notification_permission_dialog_settings": "إعدادات",
|
||||||
@@ -354,17 +371,30 @@
|
|||||||
"scaffold_body_error_occurred": "حدث خطأ",
|
"scaffold_body_error_occurred": "حدث خطأ",
|
||||||
"search_bar_hint": "ابحث عن صورك",
|
"search_bar_hint": "ابحث عن صورك",
|
||||||
"search_filter_apply": "اختار الفلتر ",
|
"search_filter_apply": "اختار الفلتر ",
|
||||||
|
"search_filter_camera": "Camera",
|
||||||
"search_filter_camera_make": "صنع",
|
"search_filter_camera_make": "صنع",
|
||||||
"search_filter_camera_model": "نموذج",
|
"search_filter_camera_model": "نموذج",
|
||||||
|
"search_filter_camera_title": "Select camera type",
|
||||||
|
"search_filter_date": "Date",
|
||||||
|
"search_filter_date_interval": "{start} to {end}",
|
||||||
|
"search_filter_date_title": "Select a date range",
|
||||||
"search_filter_display_option_archive": "أرشيف",
|
"search_filter_display_option_archive": "أرشيف",
|
||||||
"search_filter_display_option_favorite": "مفضل",
|
"search_filter_display_option_favorite": "مفضل",
|
||||||
"search_filter_display_option_not_in_album": "ليس في الألبوم",
|
"search_filter_display_option_not_in_album": "ليس في الألبوم",
|
||||||
|
"search_filter_display_options": "Display Options",
|
||||||
|
"search_filter_display_options_title": "Display options",
|
||||||
|
"search_filter_location": "Location",
|
||||||
"search_filter_location_city": "مدينة",
|
"search_filter_location_city": "مدينة",
|
||||||
"search_filter_location_country": "دولة",
|
"search_filter_location_country": "دولة",
|
||||||
"search_filter_location_state": "ولاية",
|
"search_filter_location_state": "ولاية",
|
||||||
|
"search_filter_location_title": "Select location",
|
||||||
|
"search_filter_media_type": "Media Type",
|
||||||
"search_filter_media_type_all": "الجميع",
|
"search_filter_media_type_all": "الجميع",
|
||||||
"search_filter_media_type_image": "صورة",
|
"search_filter_media_type_image": "صورة",
|
||||||
|
"search_filter_media_type_title": "Select media type",
|
||||||
"search_filter_media_type_video": "شريط فيديو",
|
"search_filter_media_type_video": "شريط فيديو",
|
||||||
|
"search_filter_people": "People",
|
||||||
|
"search_filter_people_title": "Select people",
|
||||||
"search_page_categories": "فئات",
|
"search_page_categories": "فئات",
|
||||||
"search_page_favorites": "المفضلة",
|
"search_page_favorites": "المفضلة",
|
||||||
"search_page_motion_photos": "الصور المتحركه",
|
"search_page_motion_photos": "الصور المتحركه",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "Zrušit",
|
"action_common_cancel": "Zrušit",
|
||||||
"action_common_clear": "Vyčistit",
|
"action_common_clear": "Vyčistit",
|
||||||
"action_common_confirm": "Potvrdit",
|
"action_common_confirm": "Potvrdit",
|
||||||
|
"action_common_save": "Uložit",
|
||||||
|
"action_common_select": "Vybrat",
|
||||||
"action_common_update": "Aktualizovat",
|
"action_common_update": "Aktualizovat",
|
||||||
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Je již v {album}",
|
"add_to_album_bottom_sheet_already_exists": "Je již v {album}",
|
||||||
@@ -141,11 +143,21 @@
|
|||||||
"change_password_form_new_password": "Nové heslo",
|
"change_password_form_new_password": "Nové heslo",
|
||||||
"change_password_form_password_mismatch": "Hesla se neshodují",
|
"change_password_form_password_mismatch": "Hesla se neshodují",
|
||||||
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
|
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
|
||||||
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
|
"client_cert_enter_password": "Zadejte heslo",
|
||||||
|
"client_cert_import": "Importovat",
|
||||||
|
"client_cert_import_success_msg": "Klientský certifikát je importován",
|
||||||
|
"client_cert_invalid_msg": "Neplatný soubor certifikátu nebo špatné heslo",
|
||||||
|
"client_cert_remove": "Odstranit",
|
||||||
|
"client_cert_remove_msg": "Klientský certifikát je odstraněn",
|
||||||
|
"client_cert_subtitle": "Podpora pouze formátu PKCS12 (.p12, .pfx). Import/odstranění certifikátu je možné pouze před přihlášením",
|
||||||
|
"client_cert_title": "Klientský SSL certifikát",
|
||||||
"common_add_to_album": "Přidat do alba",
|
"common_add_to_album": "Přidat do alba",
|
||||||
"common_change_password": "Změnit heslo",
|
"common_change_password": "Změnit heslo",
|
||||||
"common_create_new_album": "Vytvořit nové album",
|
"common_create_new_album": "Vytvořit nové album",
|
||||||
"common_server_error": "Zkontrolujte připojení k internetu. Ujistěte se, že server je dostupný a aplikace/server jsou v kompatibilní verzi.",
|
"common_server_error": "Zkontrolujte připojení k internetu. Ujistěte se, že server je dostupný a aplikace/server jsou v kompatibilní verzi.",
|
||||||
"common_shared": "Sdílené",
|
"common_shared": "Sdílené",
|
||||||
|
"contextual_search": "Východ slunce na pláži",
|
||||||
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
"control_bottom_app_bar_add_to_album": "Přidat do alba",
|
||||||
"control_bottom_app_bar_album_info": "{} položek",
|
"control_bottom_app_bar_album_info": "{} položek",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} položky – sdílené",
|
"control_bottom_app_bar_album_info_shared": "{} položky – sdílené",
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Smazat",
|
"control_bottom_app_bar_delete": "Smazat",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich",
|
"control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Smazat ze zařízení",
|
"control_bottom_app_bar_delete_from_local": "Smazat ze zařízení",
|
||||||
|
"control_bottom_app_bar_edit": "Upravit",
|
||||||
"control_bottom_app_bar_edit_location": "Upravit polohu",
|
"control_bottom_app_bar_edit_location": "Upravit polohu",
|
||||||
"control_bottom_app_bar_edit_time": "Upravit datum a čas",
|
"control_bottom_app_bar_edit_time": "Upravit datum a čas",
|
||||||
"control_bottom_app_bar_favorite": "Oblíbené",
|
"control_bottom_app_bar_favorite": "Oblíbené",
|
||||||
@@ -203,6 +216,7 @@
|
|||||||
"experimental_settings_title": "Experimentální",
|
"experimental_settings_title": "Experimentální",
|
||||||
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
|
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
|
||||||
"favorites_page_title": "Oblíbené",
|
"favorites_page_title": "Oblíbené",
|
||||||
|
"filename_search": "Název nebo přípona souboru",
|
||||||
"haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu",
|
"haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu",
|
||||||
"haptic_feedback_title": "Dotyková zpětná vazba",
|
"haptic_feedback_title": "Dotyková zpětná vazba",
|
||||||
"header_settings_add_header_tip": "Přidat hlavičku",
|
"header_settings_add_header_tip": "Přidat hlavičku",
|
||||||
@@ -230,6 +244,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "Stahování zahájeno",
|
"image_viewer_page_state_provider_download_started": "Stahování zahájeno",
|
||||||
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
|
||||||
"image_viewer_page_state_provider_share_error": "Chyba sdílení",
|
"image_viewer_page_state_provider_share_error": "Chyba sdílení",
|
||||||
|
"invalid_date": "Chybné datum",
|
||||||
|
"invalid_date_format": "Chybný formát data",
|
||||||
"library_page_albums": "Alba",
|
"library_page_albums": "Alba",
|
||||||
"library_page_archive": "Archív",
|
"library_page_archive": "Archív",
|
||||||
"library_page_device_albums": "Alba v zařízení",
|
"library_page_device_albums": "Alba v zařízení",
|
||||||
@@ -311,6 +327,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Nelze upravit datum položek pouze pro čtení, přeskakuji",
|
"multiselect_grid_edit_date_time_err_read_only": "Nelze upravit datum položek pouze pro čtení, přeskakuji",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Nelze upravit polohu položek pouze pro čtení, přeskakuji",
|
"multiselect_grid_edit_gps_err_read_only": "Nelze upravit polohu položek pouze pro čtení, přeskakuji",
|
||||||
"no_assets_to_show": "Žádné položky k zobrazení",
|
"no_assets_to_show": "Žádné položky k zobrazení",
|
||||||
|
"no_name": "Bez jména",
|
||||||
"notification_permission_dialog_cancel": "Zrušit",
|
"notification_permission_dialog_cancel": "Zrušit",
|
||||||
"notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do nastavení a vyberte možnost povolit.",
|
"notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do nastavení a vyberte možnost povolit.",
|
||||||
"notification_permission_dialog_settings": "Nastavení",
|
"notification_permission_dialog_settings": "Nastavení",
|
||||||
@@ -354,17 +371,30 @@
|
|||||||
"scaffold_body_error_occurred": "Došlo k chybě",
|
"scaffold_body_error_occurred": "Došlo k chybě",
|
||||||
"search_bar_hint": "Prohledejte své fotky",
|
"search_bar_hint": "Prohledejte své fotky",
|
||||||
"search_filter_apply": "Použít filtr",
|
"search_filter_apply": "Použít filtr",
|
||||||
|
"search_filter_camera": "Fotoaparát",
|
||||||
"search_filter_camera_make": "Výrobce",
|
"search_filter_camera_make": "Výrobce",
|
||||||
"search_filter_camera_model": "Model",
|
"search_filter_camera_model": "Model",
|
||||||
|
"search_filter_camera_title": "Výběr typu fotoaparátu",
|
||||||
|
"search_filter_date": "Datum",
|
||||||
|
"search_filter_date_interval": "{start} až {end}",
|
||||||
|
"search_filter_date_title": "Výběr rozmezí dat",
|
||||||
"search_filter_display_option_archive": "Archiv",
|
"search_filter_display_option_archive": "Archiv",
|
||||||
"search_filter_display_option_favorite": "Oblíbené",
|
"search_filter_display_option_favorite": "Oblíbené",
|
||||||
"search_filter_display_option_not_in_album": "Není v albu",
|
"search_filter_display_option_not_in_album": "Není v albu",
|
||||||
|
"search_filter_display_options": "Možnost zobrazení",
|
||||||
|
"search_filter_display_options_title": "Možnosti zobrazení",
|
||||||
|
"search_filter_location": "Poloha",
|
||||||
"search_filter_location_city": "Město",
|
"search_filter_location_city": "Město",
|
||||||
"search_filter_location_country": "Země",
|
"search_filter_location_country": "Země",
|
||||||
"search_filter_location_state": "Stát",
|
"search_filter_location_state": "Stát",
|
||||||
|
"search_filter_location_title": "Výběr polohy",
|
||||||
|
"search_filter_media_type": "Typ média",
|
||||||
"search_filter_media_type_all": "Všechny",
|
"search_filter_media_type_all": "Všechny",
|
||||||
"search_filter_media_type_image": "Obrázek",
|
"search_filter_media_type_image": "Obrázek",
|
||||||
|
"search_filter_media_type_title": "Výběr typu média",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
|
"search_filter_people": "Lidé",
|
||||||
|
"search_filter_people_title": "Výběr lidí",
|
||||||
"search_page_categories": "Kategorie",
|
"search_page_categories": "Kategorie",
|
||||||
"search_page_favorites": "Oblíbené",
|
"search_page_favorites": "Oblíbené",
|
||||||
"search_page_motion_photos": "Pohyblivé fotky",
|
"search_page_motion_photos": "Pohyblivé fotky",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "Annuller",
|
"action_common_cancel": "Annuller",
|
||||||
"action_common_clear": "Ryd",
|
"action_common_clear": "Ryd",
|
||||||
"action_common_confirm": "Bekræft",
|
"action_common_confirm": "Bekræft",
|
||||||
|
"action_common_save": "Save",
|
||||||
|
"action_common_select": "Select",
|
||||||
"action_common_update": "Opdater",
|
"action_common_update": "Opdater",
|
||||||
"add_to_album_bottom_sheet_added": "Tilføjet til {album}",
|
"add_to_album_bottom_sheet_added": "Tilføjet til {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
|
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
|
||||||
@@ -141,11 +143,21 @@
|
|||||||
"change_password_form_new_password": "Nyt kodeord",
|
"change_password_form_new_password": "Nyt kodeord",
|
||||||
"change_password_form_password_mismatch": "Kodeord er ikke ens",
|
"change_password_form_password_mismatch": "Kodeord er ikke ens",
|
||||||
"change_password_form_reenter_new_password": "Gentag nyt kodeord",
|
"change_password_form_reenter_new_password": "Gentag nyt kodeord",
|
||||||
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
|
"client_cert_enter_password": "Enter Password",
|
||||||
|
"client_cert_import": "Import",
|
||||||
|
"client_cert_import_success_msg": "Client certificate is imported",
|
||||||
|
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
||||||
|
"client_cert_remove": "Remove",
|
||||||
|
"client_cert_remove_msg": "Client certificate is removed",
|
||||||
|
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
|
||||||
|
"client_cert_title": "SSL Client Certificate",
|
||||||
"common_add_to_album": "Tilføj til album",
|
"common_add_to_album": "Tilføj til album",
|
||||||
"common_change_password": "Skift kodeord",
|
"common_change_password": "Skift kodeord",
|
||||||
"common_create_new_album": "Opret et nyt album",
|
"common_create_new_album": "Opret et nyt album",
|
||||||
"common_server_error": "Tjek din internetforbindelse, sørg for at serveren er tilgængelig og at app- og serversioner er kompatible.",
|
"common_server_error": "Tjek din internetforbindelse, sørg for at serveren er tilgængelig og at app- og serversioner er kompatible.",
|
||||||
"common_shared": "Delt",
|
"common_shared": "Delt",
|
||||||
|
"contextual_search": "Sunrise on the beach",
|
||||||
"control_bottom_app_bar_add_to_album": "Tilføj til album",
|
"control_bottom_app_bar_add_to_album": "Tilføj til album",
|
||||||
"control_bottom_app_bar_album_info": "{} genstande",
|
"control_bottom_app_bar_album_info": "{} genstande",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} genstande • Delt",
|
"control_bottom_app_bar_album_info_shared": "{} genstande • Delt",
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Slet",
|
"control_bottom_app_bar_delete": "Slet",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Slet fra Immich",
|
"control_bottom_app_bar_delete_from_immich": "Slet fra Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Slet fra enhed",
|
"control_bottom_app_bar_delete_from_local": "Slet fra enhed",
|
||||||
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "Rediger placering",
|
"control_bottom_app_bar_edit_location": "Rediger placering",
|
||||||
"control_bottom_app_bar_edit_time": "Rediger tid og dato",
|
"control_bottom_app_bar_edit_time": "Rediger tid og dato",
|
||||||
"control_bottom_app_bar_favorite": "Favorit",
|
"control_bottom_app_bar_favorite": "Favorit",
|
||||||
@@ -203,6 +216,7 @@
|
|||||||
"experimental_settings_title": "Eksperimentelle",
|
"experimental_settings_title": "Eksperimentelle",
|
||||||
"favorites_page_no_favorites": "Ingen favoritter blev fundet",
|
"favorites_page_no_favorites": "Ingen favoritter blev fundet",
|
||||||
"favorites_page_title": "Favoritter",
|
"favorites_page_title": "Favoritter",
|
||||||
|
"filename_search": "File name or extension",
|
||||||
"haptic_feedback_switch": "Slå haptisk feedback til",
|
"haptic_feedback_switch": "Slå haptisk feedback til",
|
||||||
"haptic_feedback_title": "Haptisk feedback",
|
"haptic_feedback_title": "Haptisk feedback",
|
||||||
"header_settings_add_header_tip": "Add Header",
|
"header_settings_add_header_tip": "Add Header",
|
||||||
@@ -230,6 +244,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "Download startet",
|
"image_viewer_page_state_provider_download_started": "Download startet",
|
||||||
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
"image_viewer_page_state_provider_download_success": "Download succesfuld",
|
||||||
"image_viewer_page_state_provider_share_error": "Delingsfejl",
|
"image_viewer_page_state_provider_share_error": "Delingsfejl",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
|
"invalid_date_format": "Invalid date format",
|
||||||
"library_page_albums": "Albummer",
|
"library_page_albums": "Albummer",
|
||||||
"library_page_archive": "Arkiv",
|
"library_page_archive": "Arkiv",
|
||||||
"library_page_device_albums": "Albummer på enhed",
|
"library_page_device_albums": "Albummer på enhed",
|
||||||
@@ -311,6 +327,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over",
|
"multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen på kun læselige elementer. Springer over",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over",
|
"multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af kun læselige elementer. Springer over",
|
||||||
"no_assets_to_show": "Ingen elementer at vise",
|
"no_assets_to_show": "Ingen elementer at vise",
|
||||||
|
"no_name": "No name",
|
||||||
"notification_permission_dialog_cancel": "Annuller",
|
"notification_permission_dialog_cancel": "Annuller",
|
||||||
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
|
"notification_permission_dialog_content": "Gå til indstillinger for at slå notifikationer til.",
|
||||||
"notification_permission_dialog_settings": "Indstillinger",
|
"notification_permission_dialog_settings": "Indstillinger",
|
||||||
@@ -354,17 +371,30 @@
|
|||||||
"scaffold_body_error_occurred": "Der opstod en fejl",
|
"scaffold_body_error_occurred": "Der opstod en fejl",
|
||||||
"search_bar_hint": "Søg i dine billeder",
|
"search_bar_hint": "Søg i dine billeder",
|
||||||
"search_filter_apply": "Tilføj filter",
|
"search_filter_apply": "Tilføj filter",
|
||||||
|
"search_filter_camera": "Camera",
|
||||||
"search_filter_camera_make": "Producent",
|
"search_filter_camera_make": "Producent",
|
||||||
"search_filter_camera_model": "Model",
|
"search_filter_camera_model": "Model",
|
||||||
|
"search_filter_camera_title": "Select camera type",
|
||||||
|
"search_filter_date": "Date",
|
||||||
|
"search_filter_date_interval": "{start} to {end}",
|
||||||
|
"search_filter_date_title": "Select a date range",
|
||||||
"search_filter_display_option_archive": "Arkiv",
|
"search_filter_display_option_archive": "Arkiv",
|
||||||
"search_filter_display_option_favorite": "Favorit",
|
"search_filter_display_option_favorite": "Favorit",
|
||||||
"search_filter_display_option_not_in_album": "Ikke i album",
|
"search_filter_display_option_not_in_album": "Ikke i album",
|
||||||
|
"search_filter_display_options": "Display Options",
|
||||||
|
"search_filter_display_options_title": "Display options",
|
||||||
|
"search_filter_location": "Location",
|
||||||
"search_filter_location_city": "By",
|
"search_filter_location_city": "By",
|
||||||
"search_filter_location_country": "Land",
|
"search_filter_location_country": "Land",
|
||||||
"search_filter_location_state": "Stat",
|
"search_filter_location_state": "Stat",
|
||||||
|
"search_filter_location_title": "Select location",
|
||||||
|
"search_filter_media_type": "Media Type",
|
||||||
"search_filter_media_type_all": "Alle",
|
"search_filter_media_type_all": "Alle",
|
||||||
"search_filter_media_type_image": "Billede",
|
"search_filter_media_type_image": "Billede",
|
||||||
|
"search_filter_media_type_title": "Select media type",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
|
"search_filter_people": "People",
|
||||||
|
"search_filter_people_title": "Select people",
|
||||||
"search_page_categories": "Kategorier",
|
"search_page_categories": "Kategorier",
|
||||||
"search_page_favorites": "Favoritter",
|
"search_page_favorites": "Favoritter",
|
||||||
"search_page_motion_photos": "Bevægelsesbilleder",
|
"search_page_motion_photos": "Bevægelsesbilleder",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "Abbrechen",
|
"action_common_cancel": "Abbrechen",
|
||||||
"action_common_clear": "Leeren",
|
"action_common_clear": "Leeren",
|
||||||
"action_common_confirm": "Bestätigen",
|
"action_common_confirm": "Bestätigen",
|
||||||
|
"action_common_save": "Save",
|
||||||
|
"action_common_select": "Select",
|
||||||
"action_common_update": "Aktualisieren",
|
"action_common_update": "Aktualisieren",
|
||||||
"add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt",
|
"add_to_album_bottom_sheet_added": "Zu {album} hinzugefügt",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Bereits in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Bereits in {album}",
|
||||||
@@ -141,11 +143,21 @@
|
|||||||
"change_password_form_new_password": "Neues Passwort",
|
"change_password_form_new_password": "Neues Passwort",
|
||||||
"change_password_form_password_mismatch": "Passwörter stimmen nicht überein",
|
"change_password_form_password_mismatch": "Passwörter stimmen nicht überein",
|
||||||
"change_password_form_reenter_new_password": "Passwort erneut eingeben",
|
"change_password_form_reenter_new_password": "Passwort erneut eingeben",
|
||||||
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
|
"client_cert_enter_password": "Enter Password",
|
||||||
|
"client_cert_import": "Import",
|
||||||
|
"client_cert_import_success_msg": "Client certificate is imported",
|
||||||
|
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
||||||
|
"client_cert_remove": "Remove",
|
||||||
|
"client_cert_remove_msg": "Client certificate is removed",
|
||||||
|
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
|
||||||
|
"client_cert_title": "SSL Client Certificate",
|
||||||
"common_add_to_album": "Zu Album hinzufügen",
|
"common_add_to_album": "Zu Album hinzufügen",
|
||||||
"common_change_password": "Passwort ändern",
|
"common_change_password": "Passwort ändern",
|
||||||
"common_create_new_album": "Neues Album erstellen",
|
"common_create_new_album": "Neues Album erstellen",
|
||||||
"common_server_error": "Bitte überprüfe Deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.",
|
"common_server_error": "Bitte überprüfe Deine Netzwerkverbindung und stelle sicher, dass die App und Server Versionen kompatibel sind.",
|
||||||
"common_shared": "Geteilt",
|
"common_shared": "Geteilt",
|
||||||
|
"contextual_search": "Sunrise on the beach",
|
||||||
"control_bottom_app_bar_add_to_album": "Zu Album hinzufügen",
|
"control_bottom_app_bar_add_to_album": "Zu Album hinzufügen",
|
||||||
"control_bottom_app_bar_album_info": "{} Elemente",
|
"control_bottom_app_bar_album_info": "{} Elemente",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} Elemente · Geteilt",
|
"control_bottom_app_bar_album_info_shared": "{} Elemente · Geteilt",
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Löschen",
|
"control_bottom_app_bar_delete": "Löschen",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Aus Immich löschen",
|
"control_bottom_app_bar_delete_from_immich": "Aus Immich löschen",
|
||||||
"control_bottom_app_bar_delete_from_local": "Vom Gerät löschen",
|
"control_bottom_app_bar_delete_from_local": "Vom Gerät löschen",
|
||||||
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "Ort bearbeiten",
|
"control_bottom_app_bar_edit_location": "Ort bearbeiten",
|
||||||
"control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten",
|
"control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten",
|
||||||
"control_bottom_app_bar_favorite": "Favorit",
|
"control_bottom_app_bar_favorite": "Favorit",
|
||||||
@@ -203,6 +216,7 @@
|
|||||||
"experimental_settings_title": "Experimentell",
|
"experimental_settings_title": "Experimentell",
|
||||||
"favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden",
|
"favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden",
|
||||||
"favorites_page_title": "Favoriten",
|
"favorites_page_title": "Favoriten",
|
||||||
|
"filename_search": "File name or extension",
|
||||||
"haptic_feedback_switch": "Haptisches Feedback aktivieren",
|
"haptic_feedback_switch": "Haptisches Feedback aktivieren",
|
||||||
"haptic_feedback_title": "Haptisches Feedback",
|
"haptic_feedback_title": "Haptisches Feedback",
|
||||||
"header_settings_add_header_tip": "Header hinzufügen",
|
"header_settings_add_header_tip": "Header hinzufügen",
|
||||||
@@ -230,6 +244,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "Download gestartet",
|
"image_viewer_page_state_provider_download_started": "Download gestartet",
|
||||||
"image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen",
|
"image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen",
|
||||||
"image_viewer_page_state_provider_share_error": "Fehler beim Teilen",
|
"image_viewer_page_state_provider_share_error": "Fehler beim Teilen",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
|
"invalid_date_format": "Invalid date format",
|
||||||
"library_page_albums": "Alben",
|
"library_page_albums": "Alben",
|
||||||
"library_page_archive": "Archiv",
|
"library_page_archive": "Archiv",
|
||||||
"library_page_device_albums": "Alben auf dem Gerät",
|
"library_page_device_albums": "Alben auf dem Gerät",
|
||||||
@@ -311,6 +327,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Das Datum und die Uhrzeit von schreibgeschützten Inhalten kann nicht verändert werden, überspringen...",
|
"multiselect_grid_edit_date_time_err_read_only": "Das Datum und die Uhrzeit von schreibgeschützten Inhalten kann nicht verändert werden, überspringen...",
|
||||||
"multiselect_grid_edit_gps_err_read_only": "Der Aufnahmeort von schreibgeschützten Inhalten kann nicht verändert werden, überspringen...",
|
"multiselect_grid_edit_gps_err_read_only": "Der Aufnahmeort von schreibgeschützten Inhalten kann nicht verändert werden, überspringen...",
|
||||||
"no_assets_to_show": "Keine Vorschau vorhanden",
|
"no_assets_to_show": "Keine Vorschau vorhanden",
|
||||||
|
"no_name": "No name",
|
||||||
"notification_permission_dialog_cancel": "Abbrechen",
|
"notification_permission_dialog_cancel": "Abbrechen",
|
||||||
"notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\"",
|
"notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\"",
|
||||||
"notification_permission_dialog_settings": "Einstellungen",
|
"notification_permission_dialog_settings": "Einstellungen",
|
||||||
@@ -354,17 +371,30 @@
|
|||||||
"scaffold_body_error_occurred": "Ein Fehler ist aufgetreten",
|
"scaffold_body_error_occurred": "Ein Fehler ist aufgetreten",
|
||||||
"search_bar_hint": "Durchsuche deine Fotos",
|
"search_bar_hint": "Durchsuche deine Fotos",
|
||||||
"search_filter_apply": "Filter anwenden",
|
"search_filter_apply": "Filter anwenden",
|
||||||
|
"search_filter_camera": "Camera",
|
||||||
"search_filter_camera_make": "Marke",
|
"search_filter_camera_make": "Marke",
|
||||||
"search_filter_camera_model": "Modell",
|
"search_filter_camera_model": "Modell",
|
||||||
|
"search_filter_camera_title": "Select camera type",
|
||||||
|
"search_filter_date": "Date",
|
||||||
|
"search_filter_date_interval": "{start} to {end}",
|
||||||
|
"search_filter_date_title": "Select a date range",
|
||||||
"search_filter_display_option_archive": "Archiv",
|
"search_filter_display_option_archive": "Archiv",
|
||||||
"search_filter_display_option_favorite": "Favorit",
|
"search_filter_display_option_favorite": "Favorit",
|
||||||
"search_filter_display_option_not_in_album": "Nicht im Album",
|
"search_filter_display_option_not_in_album": "Nicht im Album",
|
||||||
|
"search_filter_display_options": "Display Options",
|
||||||
|
"search_filter_display_options_title": "Display options",
|
||||||
|
"search_filter_location": "Location",
|
||||||
"search_filter_location_city": "Stadt",
|
"search_filter_location_city": "Stadt",
|
||||||
"search_filter_location_country": "Land",
|
"search_filter_location_country": "Land",
|
||||||
"search_filter_location_state": "Bundesland",
|
"search_filter_location_state": "Bundesland",
|
||||||
|
"search_filter_location_title": "Select location",
|
||||||
|
"search_filter_media_type": "Media Type",
|
||||||
"search_filter_media_type_all": "Alle",
|
"search_filter_media_type_all": "Alle",
|
||||||
"search_filter_media_type_image": "Bild",
|
"search_filter_media_type_image": "Bild",
|
||||||
|
"search_filter_media_type_title": "Select media type",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
|
"search_filter_people": "People",
|
||||||
|
"search_filter_people_title": "Select people",
|
||||||
"search_page_categories": "Kategorien",
|
"search_page_categories": "Kategorien",
|
||||||
"search_page_favorites": "Favoriten",
|
"search_page_favorites": "Favoriten",
|
||||||
"search_page_motion_photos": "Live-Fotos",
|
"search_page_motion_photos": "Live-Fotos",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
"action_common_cancel": "Ακύρωση",
|
"action_common_cancel": "Ακύρωση",
|
||||||
"action_common_clear": "Clear",
|
"action_common_clear": "Clear",
|
||||||
"action_common_confirm": "Confirm",
|
"action_common_confirm": "Confirm",
|
||||||
|
"action_common_save": "Save",
|
||||||
|
"action_common_select": "Select",
|
||||||
"action_common_update": "Ενημέρωση",
|
"action_common_update": "Ενημέρωση",
|
||||||
"add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}",
|
"add_to_album_bottom_sheet_added": "Προστέθηκε στο {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Ήδη στο {album}",
|
"add_to_album_bottom_sheet_already_exists": "Ήδη στο {album}",
|
||||||
@@ -141,11 +143,21 @@
|
|||||||
"change_password_form_new_password": "Νέος Κωδικός",
|
"change_password_form_new_password": "Νέος Κωδικός",
|
||||||
"change_password_form_password_mismatch": "Οι κωδικοί δεν ταιριάζουν",
|
"change_password_form_password_mismatch": "Οι κωδικοί δεν ταιριάζουν",
|
||||||
"change_password_form_reenter_new_password": "Επανεισαγωγή Νέου Κωδικού",
|
"change_password_form_reenter_new_password": "Επανεισαγωγή Νέου Κωδικού",
|
||||||
|
"client_cert_dialog_msg_confirm": "OK",
|
||||||
|
"client_cert_enter_password": "Enter Password",
|
||||||
|
"client_cert_import": "Import",
|
||||||
|
"client_cert_import_success_msg": "Client certificate is imported",
|
||||||
|
"client_cert_invalid_msg": "Invalid certificate file or wrong password",
|
||||||
|
"client_cert_remove": "Remove",
|
||||||
|
"client_cert_remove_msg": "Client certificate is removed",
|
||||||
|
"client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate Import/Remove is available only before login",
|
||||||
|
"client_cert_title": "SSL Client Certificate",
|
||||||
"common_add_to_album": "Προσθήκη στο άλμπουμ",
|
"common_add_to_album": "Προσθήκη στο άλμπουμ",
|
||||||
"common_change_password": "Αλλαγή Κωδικού",
|
"common_change_password": "Αλλαγή Κωδικού",
|
||||||
"common_create_new_album": "Δημιουργία νέου άλμπουμ",
|
"common_create_new_album": "Δημιουργία νέου άλμπουμ",
|
||||||
"common_server_error": "Ελέγξτε τη σύνδεσή σας, βεβαιωθείτε ότι ο διακομιστής είναι προσβάσιμος και ότι οι εκδόσεις της εφαρμογής/διακομιστή είναι συμβατές.",
|
"common_server_error": "Ελέγξτε τη σύνδεσή σας, βεβαιωθείτε ότι ο διακομιστής είναι προσβάσιμος και ότι οι εκδόσεις της εφαρμογής/διακομιστή είναι συμβατές.",
|
||||||
"common_shared": "Κοινόχρηστο",
|
"common_shared": "Κοινόχρηστο",
|
||||||
|
"contextual_search": "Sunrise on the beach",
|
||||||
"control_bottom_app_bar_add_to_album": "Προσθήκη στο άλμπουμ",
|
"control_bottom_app_bar_add_to_album": "Προσθήκη στο άλμπουμ",
|
||||||
"control_bottom_app_bar_album_info": "{} αντικείμενα",
|
"control_bottom_app_bar_album_info": "{} αντικείμενα",
|
||||||
"control_bottom_app_bar_album_info_shared": "{} αντικείμενα · Κοινόχρηστα",
|
"control_bottom_app_bar_album_info_shared": "{} αντικείμενα · Κοινόχρηστα",
|
||||||
@@ -154,6 +166,7 @@
|
|||||||
"control_bottom_app_bar_delete": "Διαγραφή",
|
"control_bottom_app_bar_delete": "Διαγραφή",
|
||||||
"control_bottom_app_bar_delete_from_immich": "Διαγραφή από το Immich",
|
"control_bottom_app_bar_delete_from_immich": "Διαγραφή από το Immich",
|
||||||
"control_bottom_app_bar_delete_from_local": "Διαγραφή από τη συσκευή",
|
"control_bottom_app_bar_delete_from_local": "Διαγραφή από τη συσκευή",
|
||||||
|
"control_bottom_app_bar_edit": "Edit",
|
||||||
"control_bottom_app_bar_edit_location": "Επεξεργασία Τοποθεσίας",
|
"control_bottom_app_bar_edit_location": "Επεξεργασία Τοποθεσίας",
|
||||||
"control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας",
|
"control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας",
|
||||||
"control_bottom_app_bar_favorite": "Προσθήκη στα αγαπημένα",
|
"control_bottom_app_bar_favorite": "Προσθήκη στα αγαπημένα",
|
||||||
@@ -203,6 +216,7 @@
|
|||||||
"experimental_settings_title": "Πειραματικό",
|
"experimental_settings_title": "Πειραματικό",
|
||||||
"favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία",
|
"favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία",
|
||||||
"favorites_page_title": "Αγαπημένα",
|
"favorites_page_title": "Αγαπημένα",
|
||||||
|
"filename_search": "File name or extension",
|
||||||
"haptic_feedback_switch": "Enable haptic feedback",
|
"haptic_feedback_switch": "Enable haptic feedback",
|
||||||
"haptic_feedback_title": "Haptic Feedback",
|
"haptic_feedback_title": "Haptic Feedback",
|
||||||
"header_settings_add_header_tip": "Add Header",
|
"header_settings_add_header_tip": "Add Header",
|
||||||
@@ -230,6 +244,8 @@
|
|||||||
"image_viewer_page_state_provider_download_started": "Download Started",
|
"image_viewer_page_state_provider_download_started": "Download Started",
|
||||||
"image_viewer_page_state_provider_download_success": "Download Success",
|
"image_viewer_page_state_provider_download_success": "Download Success",
|
||||||
"image_viewer_page_state_provider_share_error": "Share Error",
|
"image_viewer_page_state_provider_share_error": "Share Error",
|
||||||
|
"invalid_date": "Invalid date",
|
||||||
|
"invalid_date_format": "Invalid date format",
|
||||||
"library_page_albums": "Albums",
|
"library_page_albums": "Albums",
|
||||||
"library_page_archive": "Archive",
|
"library_page_archive": "Archive",
|
||||||
"library_page_device_albums": "Albums on Device",
|
"library_page_device_albums": "Albums on Device",
|
||||||
@@ -311,6 +327,7 @@
|
|||||||
"multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping",
|
"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",
|
"multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping",
|
||||||
"no_assets_to_show": "No assets to show",
|
"no_assets_to_show": "No assets to show",
|
||||||
|
"no_name": "No name",
|
||||||
"notification_permission_dialog_cancel": "Cancel",
|
"notification_permission_dialog_cancel": "Cancel",
|
||||||
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
|
||||||
"notification_permission_dialog_settings": "Settings",
|
"notification_permission_dialog_settings": "Settings",
|
||||||
@@ -354,17 +371,30 @@
|
|||||||
"scaffold_body_error_occurred": "Error occurred",
|
"scaffold_body_error_occurred": "Error occurred",
|
||||||
"search_bar_hint": "Search your photos",
|
"search_bar_hint": "Search your photos",
|
||||||
"search_filter_apply": "Apply filter",
|
"search_filter_apply": "Apply filter",
|
||||||
|
"search_filter_camera": "Camera",
|
||||||
"search_filter_camera_make": "Make",
|
"search_filter_camera_make": "Make",
|
||||||
"search_filter_camera_model": "Model",
|
"search_filter_camera_model": "Model",
|
||||||
|
"search_filter_camera_title": "Select camera type",
|
||||||
|
"search_filter_date": "Date",
|
||||||
|
"search_filter_date_interval": "{start} to {end}",
|
||||||
|
"search_filter_date_title": "Select a date range",
|
||||||
"search_filter_display_option_archive": "Archive",
|
"search_filter_display_option_archive": "Archive",
|
||||||
"search_filter_display_option_favorite": "Favorite",
|
"search_filter_display_option_favorite": "Favorite",
|
||||||
"search_filter_display_option_not_in_album": "Not in album",
|
"search_filter_display_option_not_in_album": "Not in album",
|
||||||
|
"search_filter_display_options": "Display Options",
|
||||||
|
"search_filter_display_options_title": "Display options",
|
||||||
|
"search_filter_location": "Location",
|
||||||
"search_filter_location_city": "City",
|
"search_filter_location_city": "City",
|
||||||
"search_filter_location_country": "Country",
|
"search_filter_location_country": "Country",
|
||||||
"search_filter_location_state": "State",
|
"search_filter_location_state": "State",
|
||||||
|
"search_filter_location_title": "Select location",
|
||||||
|
"search_filter_media_type": "Media Type",
|
||||||
"search_filter_media_type_all": "All",
|
"search_filter_media_type_all": "All",
|
||||||
"search_filter_media_type_image": "Image",
|
"search_filter_media_type_image": "Image",
|
||||||
|
"search_filter_media_type_title": "Select media type",
|
||||||
"search_filter_media_type_video": "Video",
|
"search_filter_media_type_video": "Video",
|
||||||
|
"search_filter_people": "People",
|
||||||
|
"search_filter_people_title": "Select people",
|
||||||
"search_page_categories": "Categories",
|
"search_page_categories": "Categories",
|
||||||
"search_page_favorites": "Favorites",
|
"search_page_favorites": "Favorites",
|
||||||
"search_page_motion_photos": "Motion Photos",
|
"search_page_motion_photos": "Motion Photos",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user