Compare commits
302 Commits
feat/timel
...
feat/conte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd0a12df37 | ||
|
|
6913697ad1 | ||
|
|
a4ae86ce29 | ||
|
|
2c50f2e244 | ||
|
|
365abd8906 | ||
|
|
25fb43bbe3 | ||
|
|
125e8cee01 | ||
|
|
c15e9bfa72 | ||
|
|
35e188e6e7 | ||
|
|
3cc9dd126c | ||
|
|
aa69d89b9f | ||
|
|
29c14a3f58 | ||
|
|
0df70365d7 | ||
|
|
c34be73d81 | ||
|
|
f396e9e374 | ||
|
|
821a9d4691 | ||
|
|
cad654586f | ||
|
|
28eb1bc13c | ||
|
|
1e4779cf48 | ||
|
|
0647c22956 | ||
|
|
b8087b4fa2 | ||
|
|
d94cb9641b | ||
|
|
517c3e1d4c | ||
|
|
619de2a5e4 | ||
|
|
79d0e3e1ed | ||
|
|
f5ff36a1f8 | ||
|
|
b5efc9c16e | ||
|
|
1036076b0d | ||
|
|
c76324c611 | ||
|
|
0ddb92e1ec | ||
|
|
d08a520aa2 | ||
|
|
7bdf0f6c50 | ||
|
|
2b33a58448 | ||
|
|
b35f00f768 | ||
|
|
86cc7c3c73 | ||
|
|
5854cbbe97 | ||
|
|
ceb36a304d | ||
|
|
f5d7e5acca | ||
|
|
be15a84f9b | ||
|
|
32791e98c2 | ||
|
|
7ea443b3a9 | ||
|
|
c69786b039 | ||
|
|
5c7d5539ea | ||
|
|
3531856d1c | ||
|
|
4abaad548a | ||
|
|
61a2c3ace3 | ||
|
|
e9038193db | ||
|
|
3f5cd48a59 | ||
|
|
4cb094e7ae | ||
|
|
57c8378ca7 | ||
|
|
b073f9b802 | ||
|
|
1a2e7d06cb | ||
|
|
217d719b0b | ||
|
|
cf75ad2f26 | ||
|
|
2286444158 | ||
|
|
b489bdf8d3 | ||
|
|
5e6087ea28 | ||
|
|
4ae7cadeae | ||
|
|
fdfb04d83c | ||
|
|
8273c822d7 | ||
|
|
12bb39a111 | ||
|
|
9098717c55 | ||
|
|
8d25f81bec | ||
|
|
52596255c8 | ||
|
|
106effca2e | ||
|
|
9676da27c9 | ||
|
|
3edcb180eb | ||
|
|
9f0b5790af | ||
|
|
e0c2cdddd4 | ||
|
|
74f2c10a5a | ||
|
|
fb97d9f4d9 | ||
|
|
f72bcc8a8f | ||
|
|
46a4dce16b | ||
|
|
62ed5fe27f | ||
|
|
8e3f6cdbbf | ||
|
|
d51b8c1cdf | ||
|
|
698531d6e0 | ||
|
|
44149d187f | ||
|
|
9e3b4ef3db | ||
|
|
ac0d646401 | ||
|
|
664a8fa499 | ||
|
|
3194538817 | ||
|
|
b0d427f8f9 | ||
|
|
02b29046b3 | ||
|
|
c666dc6c67 | ||
|
|
382481735a | ||
|
|
6bb1a9e083 | ||
|
|
3f03a88767 | ||
|
|
328380cfda | ||
|
|
65f29afb0f | ||
|
|
f721a62776 | ||
|
|
c73e3dacea | ||
|
|
78fb815cdb | ||
|
|
d9cddeb0f1 | ||
|
|
c4ff2ea6d5 | ||
|
|
b91b855473 | ||
|
|
7773d6d44f | ||
|
|
2129f889f5 | ||
|
|
221e0ef02f | ||
|
|
0a6b2ad26e | ||
|
|
719bf763e4 | ||
|
|
34bad1ce71 | ||
|
|
6164b027e2 | ||
|
|
d9a13dc8ac | ||
|
|
722dbfa11f | ||
|
|
f8afef0f9d | ||
|
|
3c8df55986 | ||
|
|
47436ad0ce | ||
|
|
9b58d5663a | ||
|
|
b6cebb3ece | ||
|
|
cb7e68a287 | ||
|
|
e196cac6f4 | ||
|
|
351c0d2a4d | ||
|
|
f4969694cd | ||
|
|
b334288529 | ||
|
|
834e52fda6 | ||
|
|
8c27ba3e52 | ||
|
|
cd8d66f5dd | ||
|
|
446f738c7d | ||
|
|
f19ad9726f | ||
|
|
65cac118ca | ||
|
|
efac8c6667 | ||
|
|
a70843e2b4 | ||
|
|
0b941d78c4 | ||
|
|
fc5fc58759 | ||
|
|
9bb2fc238a | ||
|
|
76f5036026 | ||
|
|
032de9ff2f | ||
|
|
c3a533ab40 | ||
|
|
dbd6dcb786 | ||
|
|
9dffbaea98 | ||
|
|
70bda45551 | ||
|
|
d9452e485c | ||
|
|
85e9ced68d | ||
|
|
04e2e42c88 | ||
|
|
bcfdb2f9df | ||
|
|
23a34bee6f | ||
|
|
6f31f27218 | ||
|
|
b102f94e97 | ||
|
|
becb56e1b1 | ||
|
|
05f174a180 | ||
|
|
476bb1cacd | ||
|
|
24fe62ff9d | ||
|
|
a390e44402 | ||
|
|
08f81eb3c6 | ||
|
|
13d33f834f | ||
|
|
58f9659cf6 | ||
|
|
e14d5fb277 | ||
|
|
06151ad173 | ||
|
|
0700758621 | ||
|
|
f26db8053b | ||
|
|
4836047e50 | ||
|
|
0979528a05 | ||
|
|
24a6757630 | ||
|
|
67f093f75b | ||
|
|
3174a27902 | ||
|
|
e7d6a066f8 | ||
|
|
73da80394e | ||
|
|
471cc74ff2 | ||
|
|
ca745d00ee | ||
|
|
3ea8d140a2 | ||
|
|
8b8012f89d | ||
|
|
4b7f851428 | ||
|
|
cc1cd299f3 | ||
|
|
3163afd24a | ||
|
|
95889a69c9 | ||
|
|
81554e5ad1 | ||
|
|
505e16c37c | ||
|
|
24bfdf3263 | ||
|
|
a23dfff6cf | ||
|
|
2919ee4c65 | ||
|
|
d0eae97037 | ||
|
|
9d639607c7 | ||
|
|
74a9be4a0e | ||
|
|
26e877cba7 | ||
|
|
7b7d91a5e1 | ||
|
|
b3055d2e94 | ||
|
|
f1e03d0022 | ||
|
|
9b5855f848 | ||
|
|
7d0228a159 | ||
|
|
c18df7ae25 | ||
|
|
72f5ca4420 | ||
|
|
02beb85642 | ||
|
|
1b62c2ef55 | ||
|
|
43eccca86a | ||
|
|
e6b9cc09c2 | ||
|
|
b484a52252 | ||
|
|
d778286777 | ||
|
|
4d41fa08ad | ||
|
|
6d00930082 | ||
|
|
e4d2c4926c | ||
|
|
dbee133764 | ||
|
|
8473dab684 | ||
|
|
146973b072 | ||
|
|
e8ca7f235c | ||
|
|
d411594c84 | ||
|
|
cf52b879b1 | ||
|
|
46869f664d | ||
|
|
f2b553182a | ||
|
|
8fe54a4de1 | ||
|
|
ce4e8fa6ba | ||
|
|
efa21af6f9 | ||
|
|
ea610760ee | ||
|
|
a5e0d83d9f | ||
|
|
9793828dc7 | ||
|
|
aed7bb53aa | ||
|
|
1fdbe2c6b8 | ||
|
|
84302dc14c | ||
|
|
f7250f24fe | ||
|
|
53680d9643 | ||
|
|
b2d00405f1 | ||
|
|
cf60f4cdcd | ||
|
|
d764a59011 | ||
|
|
7ee1b977c1 | ||
|
|
9838634067 | ||
|
|
eee793bfe4 | ||
|
|
b3342323de | ||
|
|
6f3cb4f1bb | ||
|
|
54ed78d0bf | ||
|
|
265ed0b38f | ||
|
|
63c2f4415b | ||
|
|
a7cfd7f183 | ||
|
|
ee4c45d5d3 | ||
|
|
24334aa3df | ||
|
|
882baecf21 | ||
|
|
f16327d0ab | ||
|
|
8353db6a50 | ||
|
|
5270107926 | ||
|
|
740ca14a68 | ||
|
|
966ab22065 | ||
|
|
78fbe0fd49 | ||
|
|
5862c454b7 | ||
|
|
8ee495b08f | ||
|
|
83db851b00 | ||
|
|
70037018c8 | ||
|
|
796444d211 | ||
|
|
0d66a15d9b | ||
|
|
3cf8ed5f2d | ||
|
|
ff01af2450 | ||
|
|
2de1b832e5 | ||
|
|
25142bb6c6 | ||
|
|
01660b20fd | ||
|
|
9affee1ea0 | ||
|
|
d02a82b618 | ||
|
|
ad87dff18d | ||
|
|
b7e06e7b6f | ||
|
|
21f49572b1 | ||
|
|
2b7d28528d | ||
|
|
cf4cf56ac0 | ||
|
|
50ac27238e | ||
|
|
b06b8ceef6 | ||
|
|
119f92bb20 | ||
|
|
6973683ea7 | ||
|
|
42f46b11f4 | ||
|
|
0fd16a3c46 | ||
|
|
43b06a036d | ||
|
|
55ad83d80d | ||
|
|
a80b9be07c | ||
|
|
24234bedf1 | ||
|
|
51150a3ed1 | ||
|
|
075436a5d1 | ||
|
|
9da138e01e | ||
|
|
1a2a46014e | ||
|
|
29acf89979 | ||
|
|
bb72d723e2 | ||
|
|
295e406a17 | ||
|
|
3f6b0f3127 | ||
|
|
27665801e9 | ||
|
|
2e16a88f38 | ||
|
|
d59f8e68be | ||
|
|
212649edf9 | ||
|
|
3c5a125762 | ||
|
|
60b1faac0f | ||
|
|
3af0f0c8ad | ||
|
|
2594cd47ab | ||
|
|
28958ba48a | ||
|
|
7dc9cb121f | ||
|
|
ca8a6e5f95 | ||
|
|
1086623457 | ||
|
|
a2b25b7a74 | ||
|
|
950f268cb0 | ||
|
|
5ece0e5e56 | ||
|
|
00ce6354f0 | ||
|
|
9b938d8954 | ||
|
|
e4234af3b3 | ||
|
|
1618902ebd | ||
|
|
83e783bbc2 | ||
|
|
7dbdc7a635 | ||
|
|
7a4bfc21c9 | ||
|
|
b3f38301bf | ||
|
|
1f7201fbd3 | ||
|
|
6c67bbd528 | ||
|
|
8f1bc7e821 | ||
|
|
f1f8c8e7a3 | ||
|
|
d0a872622a | ||
|
|
a498166cb6 | ||
|
|
de6d91946d | ||
|
|
58344f520b | ||
|
|
44284b4351 | ||
|
|
75b9bd163e | ||
|
|
ee3c07d049 | ||
|
|
fea5e6783c |
@@ -6,28 +6,35 @@ services:
|
|||||||
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
||||||
volumes: !override # bind mount host to /workspaces/immich
|
volumes: !override # bind mount host to /workspaces/immich
|
||||||
- ..:/workspaces/immich
|
- ..:/workspaces/immich
|
||||||
- cli_node_modules:/workspaces/immich/cli/node_modules
|
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||||
- e2e_node_modules:/workspaces/immich/e2e/node_modules
|
- pnpm-store:/usr/src/app/.pnpm-store
|
||||||
- open_api_node_modules:/workspaces/immich/open-api/typescript-sdk/node_modules
|
- server-node_modules:/usr/src/app/server/node_modules
|
||||||
- server_node_modules:/workspaces/immich/server/node_modules
|
- web-node_modules:/usr/src/app/web/node_modules
|
||||||
- web_node_modules:/workspaces/immich/web/node_modules
|
- github-node_modules:/usr/src/app/.github/node_modules
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
- cli-node_modules:/usr/src/app/cli/node_modules
|
||||||
|
- docs-node_modules:/usr/src/app/docs/node_modules
|
||||||
|
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
||||||
|
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
||||||
|
- app-node_modules:/usr/src/app/node_modules
|
||||||
|
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||||
|
- coverage:/usr/src/app/web/coverage
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
immich-web:
|
||||||
|
env_file: !reset []
|
||||||
|
immich-machine-learning:
|
||||||
|
env_file: !reset []
|
||||||
database:
|
database:
|
||||||
|
env_file: !reset []
|
||||||
|
environment: !override
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD-postgres}
|
||||||
|
POSTGRES_USER: ${DB_USERNAME-postgres}
|
||||||
|
POSTGRES_DB: ${DB_DATABASE_NAME-immich}
|
||||||
|
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||||
|
POSTGRES_HOST_AUTH_METHOD: md5
|
||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
|
- ${UPLOAD_LOCATION:-postgres-devcontainer-volume}${UPLOAD_LOCATION:+/postgres}:/var/lib/postgresql/data
|
||||||
|
redis:
|
||||||
|
env_file: !reset []
|
||||||
volumes:
|
volumes:
|
||||||
# Node modules for each service to avoid conflicts and ensure consistent dependencies
|
upload-devcontainer-volume:
|
||||||
cli_node_modules:
|
postgres-devcontainer-volume:
|
||||||
e2e_node_modules:
|
|
||||||
open_api_node_modules:
|
|
||||||
server_node_modules:
|
|
||||||
web_node_modules:
|
|
||||||
|
|
||||||
# UPLOAD_LOCATION must be set to a absolute path or vol-upload
|
|
||||||
vol-upload:
|
|
||||||
|
|
||||||
# DB_DATA_LOCATION must be set to a absolute path or vol-database
|
|
||||||
vol-database:
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"userEnvProbe": "loginInteractiveShell",
|
"userEnvProbe": "loginInteractiveShell",
|
||||||
"remoteEnv": {
|
"remoteEnv": {
|
||||||
// The location where your uploaded files are stored
|
// The location where your uploaded files are stored
|
||||||
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./Library}",
|
"UPLOAD_LOCATION": "${localEnv:UPLOAD_LOCATION:./library}",
|
||||||
// Connection secret for postgres. You should change it to a random password
|
// Connection secret for postgres. You should change it to a random password
|
||||||
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
// Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
"DB_PASSWORD": "${localEnv:DB_PASSWORD:postgres}",
|
||||||
|
|||||||
2
.github/.nvmrc
vendored
@@ -1 +1 @@
|
|||||||
22.19.0
|
24.11.0
|
||||||
|
|||||||
2
.github/labeler.yml
vendored
@@ -31,7 +31,7 @@ documentation:
|
|||||||
🧠machine-learning:
|
🧠machine-learning:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- machine-learning/app/**
|
- machine-learning/**
|
||||||
|
|
||||||
changelog:translation:
|
changelog:translation:
|
||||||
- head-branch: ['^chore/translations$']
|
- head-branch: ['^chore/translations$']
|
||||||
|
|||||||
193
.github/workflows/build-mobile.yml
vendored
@@ -1,12 +1,16 @@
|
|||||||
name: Build Mobile
|
name: Build Mobile
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
ref:
|
ref:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
environment:
|
||||||
|
description: 'Target environment'
|
||||||
|
required: true
|
||||||
|
default: 'development'
|
||||||
|
type: string
|
||||||
secrets:
|
secrets:
|
||||||
KEY_JKS:
|
KEY_JKS:
|
||||||
required: true
|
required: true
|
||||||
@@ -16,6 +20,30 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
ANDROID_STORE_PASSWORD:
|
ANDROID_STORE_PASSWORD:
|
||||||
required: true
|
required: true
|
||||||
|
APP_STORE_CONNECT_API_KEY_ID:
|
||||||
|
required: true
|
||||||
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID:
|
||||||
|
required: true
|
||||||
|
APP_STORE_CONNECT_API_KEY:
|
||||||
|
required: true
|
||||||
|
IOS_CERTIFICATE_P12:
|
||||||
|
required: true
|
||||||
|
IOS_CERTIFICATE_PASSWORD:
|
||||||
|
required: true
|
||||||
|
IOS_PROVISIONING_PROFILE:
|
||||||
|
required: true
|
||||||
|
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION:
|
||||||
|
required: true
|
||||||
|
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION:
|
||||||
|
required: true
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE:
|
||||||
|
required: true
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION:
|
||||||
|
required: true
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION:
|
||||||
|
required: true
|
||||||
|
FASTLANE_TEAM_ID:
|
||||||
|
required: true
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
@@ -34,10 +62,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
@@ -55,10 +90,17 @@ jobs:
|
|||||||
runs-on: mich
|
runs-on: mich
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
env:
|
env:
|
||||||
@@ -66,14 +108,14 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
||||||
|
|
||||||
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -130,7 +172,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -140,3 +182,142 @@ jobs:
|
|||||||
mobile/android/.gradle
|
mobile/android/.gradle
|
||||||
mobile/.dart_tool
|
mobile/.dart_tool
|
||||||
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }}
|
||||||
|
|
||||||
|
build-sign-ios:
|
||||||
|
name: Build and sign iOS
|
||||||
|
needs: pre-job
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
# Run on main branch or workflow_dispatch
|
||||||
|
if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true && github.ref == 'refs/heads/main' }}
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Flutter SDK
|
||||||
|
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
flutter-version-file: ./mobile/pubspec.yaml
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install Flutter dependencies
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Generate translation files
|
||||||
|
run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart
|
||||||
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
- name: Generate platform APIs
|
||||||
|
run: make pigeon
|
||||||
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '3.3'
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
|
||||||
|
- name: Install CocoaPods dependencies
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
pod install
|
||||||
|
|
||||||
|
- name: Install Fastlane
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
gem install bundler
|
||||||
|
bundle config set --local path 'vendor/bundle'
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
- name: Create API Key
|
||||||
|
env:
|
||||||
|
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
|
API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
|
API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.appstoreconnect/private_keys
|
||||||
|
echo "$API_KEY_CONTENT" | base64 --decode > ~/.appstoreconnect/private_keys/AuthKey_${API_KEY_ID}.p8
|
||||||
|
|
||||||
|
- name: Import Certificate and Provisioning Profiles
|
||||||
|
env:
|
||||||
|
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
|
||||||
|
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
|
||||||
|
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }}
|
||||||
|
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||||
|
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
# Decode certificate
|
||||||
|
echo "$IOS_CERTIFICATE_P12" | base64 --decode > certificate.p12
|
||||||
|
|
||||||
|
# Decode provisioning profiles based on environment
|
||||||
|
if [[ "$ENVIRONMENT" == "development" ]]; then
|
||||||
|
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE" | base64 --decode > profile_dev.mobileprovision
|
||||||
|
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_dev_share.mobileprovision
|
||||||
|
echo "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_dev_widget.mobileprovision
|
||||||
|
ls -lh profile_dev*.mobileprovision
|
||||||
|
else
|
||||||
|
echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > profile.mobileprovision
|
||||||
|
echo "$IOS_PROVISIONING_PROFILE_SHARE_EXTENSION" | base64 --decode > profile_share.mobileprovision
|
||||||
|
echo "$IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION" | base64 --decode > profile_widget.mobileprovision
|
||||||
|
ls -lh profile*.mobileprovision
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create keychain and import certificate
|
||||||
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
# Create keychain
|
||||||
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
|
security default-keychain -s build.keychain
|
||||||
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
|
security set-keychain-settings -t 3600 -u build.keychain
|
||||||
|
|
||||||
|
# Import certificate
|
||||||
|
security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||||
|
|
||||||
|
# Verify certificate was imported
|
||||||
|
security find-identity -v -p codesigning build.keychain
|
||||||
|
|
||||||
|
- name: Build and deploy to TestFlight
|
||||||
|
env:
|
||||||
|
FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }}
|
||||||
|
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
KEYCHAIN_NAME: build.keychain
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
|
ENVIRONMENT: ${{ inputs.environment || 'development' }}
|
||||||
|
working-directory: ./mobile/ios
|
||||||
|
run: |
|
||||||
|
if [[ "$ENVIRONMENT" == "development" ]]; then
|
||||||
|
bundle exec fastlane gha_testflight_dev
|
||||||
|
else
|
||||||
|
bundle exec fastlane gha_release_prod
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Clean up keychain
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
security delete-keychain build.keychain || true
|
||||||
|
|
||||||
|
- name: Upload IPA artifact
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||||
|
with:
|
||||||
|
name: ios-release-ipa
|
||||||
|
path: mobile/ios/Runner.ipa
|
||||||
|
|||||||
11
.github/workflows/cache-cleanup.yml
vendored
@@ -18,14 +18,21 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
REF: ${{ github.ref }}
|
REF: ${{ github.ref }}
|
||||||
run: |
|
run: |
|
||||||
gh extension install actions/gh-actions-cache
|
gh extension install actions/gh-actions-cache
|
||||||
|
|||||||
26
.github/workflows/cli.yml
vendored
@@ -29,15 +29,22 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
@@ -50,7 +57,7 @@ jobs:
|
|||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm build
|
- run: pnpm build
|
||||||
- run: pnpm publish
|
- run: pnpm publish --no-git-checks
|
||||||
if: ${{ github.event_name == 'release' }}
|
if: ${{ github.event_name == 'release' }}
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
@@ -64,10 +71,17 @@ jobs:
|
|||||||
needs: publish
|
needs: publish
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||||
@@ -76,7 +90,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
|
|||||||
2
.github/workflows/close-duplicates.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:d8ae47cf2e6cf4e2559bd57a60b73674fe44f897cba2c2bddff2987a05be10a4
|
image: ghcr.io/immich-app/mdq:main@sha256:6b8450bfc06770af1af66bce9bf2ced7d1d9b90df1a59fc4c83a17777a9f6723
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
15
.github/workflows/codeql-analysis.yml
vendored
@@ -43,14 +43,21 @@ jobs:
|
|||||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -63,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -76,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
|
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
32
.github/workflows/docker.yml
vendored
@@ -22,10 +22,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
server:
|
server:
|
||||||
- 'server/**'
|
- 'server/**'
|
||||||
@@ -53,11 +60,12 @@ jobs:
|
|||||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Re-tag image
|
- name: Re-tag image
|
||||||
env:
|
env:
|
||||||
REGISTRY_NAME: 'ghcr.io'
|
REGISTRY_NAME: 'ghcr.io'
|
||||||
@@ -82,11 +90,12 @@ jobs:
|
|||||||
suffix: ['']
|
suffix: ['']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Re-tag image
|
- name: Re-tag image
|
||||||
env:
|
env:
|
||||||
REGISTRY_NAME: 'ghcr.io'
|
REGISTRY_NAME: 'ghcr.io'
|
||||||
@@ -107,24 +116,23 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- device: cpu
|
- device: cpu
|
||||||
tag-suffix: ''
|
|
||||||
- device: cuda
|
- device: cuda
|
||||||
tag-suffix: '-cuda'
|
suffixes: '-cuda'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
- device: openvino
|
- device: openvino
|
||||||
tag-suffix: '-openvino'
|
suffixes: '-openvino'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
- device: armnn
|
- device: armnn
|
||||||
tag-suffix: '-armnn'
|
suffixes: '-armnn'
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
- device: rknn
|
- device: rknn
|
||||||
tag-suffix: '-rknn'
|
suffixes: '-rknn'
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
- device: rocm
|
- device: rocm
|
||||||
tag-suffix: '-rocm'
|
suffixes: '-rocm'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
runner-mapping: '{"linux/amd64": "mich"}'
|
runner-mapping: '{"linux/amd64": "mich"}'
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -138,7 +146,7 @@ jobs:
|
|||||||
dockerfile: machine-learning/Dockerfile
|
dockerfile: machine-learning/Dockerfile
|
||||||
platforms: ${{ matrix.platforms }}
|
platforms: ${{ matrix.platforms }}
|
||||||
runner-mapping: ${{ matrix.runner-mapping }}
|
runner-mapping: ${{ matrix.runner-mapping }}
|
||||||
tag-suffix: ${{ matrix.tag-suffix }}
|
suffixes: ${{ matrix.suffixes }}
|
||||||
dockerhub-push: ${{ github.event_name == 'release' }}
|
dockerhub-push: ${{ github.event_name == 'release' }}
|
||||||
build-args: |
|
build-args: |
|
||||||
DEVICE=${{ matrix.device }}
|
DEVICE=${{ matrix.device }}
|
||||||
@@ -147,7 +155,7 @@ jobs:
|
|||||||
name: Build and Push Server
|
name: Build and Push Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@129aeda75a450666ce96e8bc8126652e717917a7 # multi-runner-build-workflow-0.1.1
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@47a2ee86898ccff51592d6572391fb1abcd7f782 # multi-runner-build-workflow-v2.0.1
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
22
.github/workflows/docs-build.yml
vendored
@@ -20,10 +20,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
docs:
|
docs:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
@@ -46,16 +53,23 @@ jobs:
|
|||||||
working-directory: ./docs
|
working-directory: ./docs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './docs/.nvmrc'
|
node-version-file: './docs/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
72
.github/workflows/docs-deploy.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
|
||||||
|
env:
|
||||||
|
TG_NON_INTERACTIVE: 'true'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
checks:
|
checks:
|
||||||
name: Docs Deploy Checks
|
name: Docs Deploy Checks
|
||||||
@@ -16,12 +19,19 @@ jobs:
|
|||||||
parameters: ${{ steps.parameters.outputs.result }}
|
parameters: ${{ steps.parameters.outputs.result }}
|
||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
||||||
run: echo 'The triggering workflow did not succeed' && exit 1
|
run: echo 'The triggering workflow did not succeed' && exit 1
|
||||||
- name: Get artifact
|
- name: Get artifact
|
||||||
id: get-artifact
|
id: get-artifact
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
@@ -38,10 +48,11 @@ jobs:
|
|||||||
return { found: true, id: matchArtifact.id };
|
return { found: true, id: matchArtifact.id };
|
||||||
- name: Determine deploy parameters
|
- name: Determine deploy parameters
|
||||||
id: parameters
|
id: parameters
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
env:
|
env:
|
||||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const eventType = context.payload.workflow_run.event;
|
const eventType = context.payload.workflow_run.event;
|
||||||
const isFork = context.payload.workflow_run.repository.fork;
|
const isFork = context.payload.workflow_run.repository.fork;
|
||||||
@@ -107,17 +118,28 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||||
|
|
||||||
- name: Load parameters
|
- name: Load parameters
|
||||||
id: parameters
|
id: parameters
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
env:
|
env:
|
||||||
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
PARAM_JSON: ${{ needs.checks.outputs.parameters }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const parameters = JSON.parse(process.env.PARAM_JSON);
|
const parameters = JSON.parse(process.env.PARAM_JSON);
|
||||||
core.setOutput("event", parameters.event);
|
core.setOutput("event", parameters.event);
|
||||||
@@ -125,10 +147,11 @@ jobs:
|
|||||||
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
core.setOutput("shouldDeploy", parameters.shouldDeploy);
|
||||||
|
|
||||||
- name: Download artifact
|
- name: Download artifact
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
env:
|
env:
|
||||||
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
let artifact = JSON.parse(process.env.ARTIFACT_JSON);
|
let artifact = JSON.parse(process.env.ARTIFACT_JSON);
|
||||||
let download = await github.rest.actions.downloadArtifact({
|
let download = await github.rest.actions.downloadArtifact({
|
||||||
@@ -150,12 +173,8 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
run: 'mise run tf apply'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'apply'
|
|
||||||
|
|
||||||
- name: Deploy Docs Subdomain Output
|
- name: Deploy Docs Subdomain Output
|
||||||
id: docs-output
|
id: docs-output
|
||||||
@@ -165,20 +184,12 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'output -json'
|
|
||||||
|
|
||||||
- name: Output Cleaning
|
|
||||||
id: clean
|
|
||||||
env:
|
|
||||||
TG_OUTPUT: ${{ steps.docs-output.outputs.tg_action_output }}
|
|
||||||
run: |
|
run: |
|
||||||
CLEANED=$(echo "$TG_OUTPUT" | sed 's|%0A|\n|g ; s|%3C|<|g' | jq -c .)
|
mise run tf output -- -json | jq -r '
|
||||||
echo "output=$CLEANED" >> $GITHUB_OUTPUT
|
"projectName=\(.pages_project_name.value)",
|
||||||
|
"subdomain=\(.immich_app_branch_subdomain.value)"
|
||||||
|
' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
- name: Publish to Cloudflare Pages
|
||||||
# TODO: Action is deprecated
|
# TODO: Action is deprecated
|
||||||
@@ -186,7 +197,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }}
|
projectName: ${{ steps.docs-output.outputs.projectName }}
|
||||||
workingDirectory: 'docs'
|
workingDirectory: 'docs'
|
||||||
directory: 'build'
|
directory: 'build'
|
||||||
branch: ${{ steps.parameters.outputs.name }}
|
branch: ${{ steps.parameters.outputs.name }}
|
||||||
@@ -199,19 +210,16 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs-release'
|
||||||
with:
|
run: 'mise run tf apply'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs-release'
|
|
||||||
tg_command: 'apply'
|
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
||||||
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
if: ${{ steps.parameters.outputs.event == 'pr' }}
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
|
||||||
body: |
|
body: |
|
||||||
📖 Documentation deployed to [${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }}](https://${{ fromJson(steps.clean.outputs.output).immich_app_branch_subdomain.value }})
|
📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }})
|
||||||
emojis: 'rocket'
|
emojis: 'rocket'
|
||||||
body-include: '<!-- Docs PR URL -->'
|
body-include: '<!-- Docs PR URL -->'
|
||||||
|
|||||||
24
.github/workflows/docs-destroy.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
|
env:
|
||||||
|
TG_NON_INTERACTIVE: 'true'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Docs Destroy
|
name: Docs Destroy
|
||||||
@@ -13,10 +16,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
||||||
|
|
||||||
- name: Destroy Docs Subdomain
|
- name: Destroy Docs Subdomain
|
||||||
env:
|
env:
|
||||||
@@ -25,16 +38,13 @@ jobs:
|
|||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
|
||||||
uses: gruntwork-io/terragrunt-action@aee21a7df999be8b471c2a8564c6cd853cb674e1 # v2.1.8
|
working-directory: 'deployment/modules/cloudflare/docs'
|
||||||
with:
|
run: 'mise run tf destroy -- -refresh=false'
|
||||||
tg_version: '0.58.12'
|
|
||||||
tofu_version: '1.7.1'
|
|
||||||
tg_dir: 'deployment/modules/cloudflare/docs'
|
|
||||||
tg_command: 'destroy -refresh=false'
|
|
||||||
|
|
||||||
- name: Comment
|
- name: Comment
|
||||||
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
number: ${{ github.event.number }}
|
number: ${{ github.event.number }}
|
||||||
delete: true
|
delete: true
|
||||||
body-include: '<!-- Docs PR URL -->'
|
body-include: '<!-- Docs PR URL -->'
|
||||||
|
|||||||
11
.github/workflows/fix-format.yml
vendored
@@ -22,24 +22,24 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: 'Checkout'
|
- name: 'Checkout'
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
|
||||||
- name: Fix formatting
|
- name: Fix formatting
|
||||||
run: make install-all && make format-all
|
run: pnpm --recursive install && pnpm run --recursive --parallel fix:format
|
||||||
|
|
||||||
- name: Commit and push
|
- name: Commit and push
|
||||||
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
||||||
@@ -48,9 +48,10 @@ jobs:
|
|||||||
message: 'chore: fix formatting'
|
message: 'chore: fix formatting'
|
||||||
|
|
||||||
- name: Remove label
|
- name: Remove label
|
||||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
|
|||||||
18
.github/workflows/merge-translations.yml
vendored
@@ -28,11 +28,19 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate_token
|
||||||
|
if: ${{ inputs.skip != true }}
|
||||||
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Find translation PR
|
- name: Find translation PR
|
||||||
id: find_pr
|
id: find_pr
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -55,14 +63,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Generate a token
|
|
||||||
id: generate_token
|
|
||||||
if: ${{ inputs.skip != true }}
|
|
||||||
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
|
||||||
|
|
||||||
- name: Lock weblate
|
- name: Lock weblate
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
7
.github/workflows/pr-label-validation.yml
vendored
@@ -13,9 +13,16 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Require PR to have a changelog label
|
- name: Require PR to have a changelog label
|
||||||
uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1
|
uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5.1
|
||||||
with:
|
with:
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
mode: exactly
|
mode: exactly
|
||||||
count: 1
|
count: 1
|
||||||
use_regex: true
|
use_regex: true
|
||||||
|
|||||||
10
.github/workflows/pr-labeler.yml
vendored
@@ -11,4 +11,12 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
30
.github/workflows/prepare-release.yml
vendored
@@ -55,20 +55,20 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7.1.1
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -99,8 +99,23 @@ jobs:
|
|||||||
ALIAS: ${{ secrets.ALIAS }}
|
ALIAS: ${{ secrets.ALIAS }}
|
||||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||||
|
# iOS secrets
|
||||||
|
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
|
APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }}
|
||||||
|
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
|
||||||
|
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
|
||||||
|
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||||
|
IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }}
|
||||||
|
IOS_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_EXTENSION }}
|
||||||
|
IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_SHARE_EXTENSION }}
|
||||||
|
IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_WIDGET_EXTENSION }}
|
||||||
|
FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }}
|
||||||
|
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.bump_version.outputs.ref }}
|
ref: ${{ needs.bump_version.outputs.ref }}
|
||||||
|
environment: production
|
||||||
|
|
||||||
prepare_release:
|
prepare_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -117,18 +132,19 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Download APK
|
- name: Download APK
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: release-apk-signed
|
name: release-apk-signed
|
||||||
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
tag_name: ${{ env.IMMICH_VERSION }}
|
tag_name: ${{ env.IMMICH_VERSION }}
|
||||||
|
|||||||
20
.github/workflows/preview-label.yaml
vendored
@@ -13,10 +13,17 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/'
|
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/'
|
||||||
|
|
||||||
remove-label:
|
remove-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -24,8 +31,15 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
with:
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
@@ -37,11 +51,13 @@ jobs:
|
|||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'PRs from forks cannot have preview environments.'
|
message: 'PRs from forks cannot have preview environments.'
|
||||||
|
|
||||||
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2.8.2
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
message-id: 'preview-status'
|
message-id: 'preview-status'
|
||||||
message: 'Preview environment has been removed.'
|
message: 'Preview environment has been removed.'
|
||||||
|
|||||||
15
.github/workflows/sdk.yml
vendored
@@ -16,15 +16,22 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./open-api/typescript-sdk
|
working-directory: ./open-api/typescript-sdk
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
@@ -35,6 +42,6 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: pnpm publish
|
run: pnpm publish --no-git-checks
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
20
.github/workflows/static_analysis.yml
vendored
@@ -19,10 +19,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
@@ -41,10 +48,17 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
||||||
@@ -58,7 +72,7 @@ jobs:
|
|||||||
- name: Install DCM
|
- name: Install DCM
|
||||||
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
version: auto
|
version: auto
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
|
|
||||||
|
|||||||
215
.github/workflows/test.yml
vendored
@@ -16,10 +16,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
@@ -55,14 +62,22 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -92,14 +107,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -132,14 +154,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -167,14 +196,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -185,7 +221,7 @@ jobs:
|
|||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: pnpm rebuild && pnpm install --frozen-lockfile
|
run: pnpm rebuild && pnpm install --frozen-lockfile
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: pnpm lint:p
|
run: pnpm lint
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run formatter
|
- name: Run formatter
|
||||||
run: pnpm format
|
run: pnpm format
|
||||||
@@ -204,14 +240,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -235,14 +278,21 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -276,14 +326,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -315,14 +372,22 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
submodules: 'recursive'
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -346,15 +411,22 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -394,15 +466,22 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -441,9 +520,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup Flutter SDK
|
- name: Setup Flutter SDK
|
||||||
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
uses: subosito/flutter-action@fd55f4c5af5b953cc57a2be44cb082c8f6635e8e # v2.21.0
|
||||||
with:
|
with:
|
||||||
@@ -466,12 +552,19 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7.1.1
|
||||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
|
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
|
||||||
# with:
|
# with:
|
||||||
# python-version: 3.11
|
# python-version: 3.11
|
||||||
@@ -502,14 +595,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './.github/.nvmrc'
|
node-version-file: './.github/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -525,9 +625,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Run ShellCheck
|
- name: Run ShellCheck
|
||||||
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
|
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
|
||||||
with:
|
with:
|
||||||
@@ -539,14 +646,21 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -581,7 +695,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:da52bbead5d818adaa8077c8dcdaad0aaf93038c31ad8348b51f9f0ec1310a4d
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3@sha256:dbf18b3ffea4a81434c65b71e20d27203baf903a0275f4341e4c16dfd901fd67
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
@@ -594,14 +708,21 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
17
.github/workflows/weblate-lock.yml
vendored
@@ -23,10 +23,17 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
||||||
with:
|
with:
|
||||||
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/!(en)**\.json'
|
- 'i18n/!(en)**\.json'
|
||||||
@@ -40,10 +47,16 @@ jobs:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
|
- id: token
|
||||||
|
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Bot review status
|
- name: Bot review status
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number || github.event.pull_request_review.pull_request.number }}
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||||
run: |
|
run: |
|
||||||
# Then check for APPROVED by the bot, if absent fail
|
# Then check for APPROVED by the bot, if absent fail
|
||||||
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == env.BOT_NAME and .state == "APPROVED")) | length > 0' \
|
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json reviews | jq -e '.reviews | map(select(.author.login == env.BOT_NAME and .state == "APPROVED")) | length > 0' \
|
||||||
|
|||||||
14
.vscode/launch.json
vendored
@@ -18,6 +18,20 @@
|
|||||||
"name": "Immich Workers",
|
"name": "Immich Workers",
|
||||||
"remoteRoot": "/usr/src/app/server",
|
"remoteRoot": "/usr/src/app/server",
|
||||||
"localRoot": "${workspaceFolder}/server"
|
"localRoot": "${workspaceFolder}/server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Immich CLI",
|
||||||
|
"program": "${workspaceFolder}/cli/dist/index.js",
|
||||||
|
"args": ["upload", "--help"],
|
||||||
|
"runtimeArgs": ["--enable-source-maps"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"],
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outFiles": ["${workspaceFolder}/cli/dist/**/*.js"],
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"preLaunchTask": "Build Immich CLI"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
8
.vscode/tasks.json
vendored
@@ -5,6 +5,7 @@
|
|||||||
"label": "Fix Permissions, Install Dependencies",
|
"label": "Fix Permissions, Install Dependencies",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
|
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
|
||||||
|
"isBackground": true,
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
|
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
|
||||||
|
"isBackground": true,
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
|
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
|
||||||
|
"isBackground": true,
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
"reveal": "always",
|
"reveal": "always",
|
||||||
@@ -67,6 +70,11 @@
|
|||||||
"runOn": "folderOpen"
|
"runOn": "folderOpen"
|
||||||
},
|
},
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build Immich CLI",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "pnpm --filter cli build:dev"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
2
Makefile
@@ -91,8 +91,6 @@ format-%:
|
|||||||
pnpm --filter $(call map-package,$*) run format:fix
|
pnpm --filter $(call map-package,$*) run format:fix
|
||||||
lint-%:
|
lint-%:
|
||||||
pnpm --filter $(call map-package,$*) run lint:fix
|
pnpm --filter $(call map-package,$*) run lint:fix
|
||||||
lint-web:
|
|
||||||
pnpm --filter $(call map-package,$*) run lint:p
|
|
||||||
check-%:
|
check-%:
|
||||||
pnpm --filter $(call map-package,$*) run check
|
pnpm --filter $(call map-package,$*) run check
|
||||||
check-web:
|
check-web:
|
||||||
|
|||||||
24
README.md
@@ -28,7 +28,8 @@
|
|||||||
<a href="readme_i18n/README_de_DE.md">Deutsch</a>
|
<a href="readme_i18n/README_de_DE.md">Deutsch</a>
|
||||||
<a href="readme_i18n/README_nl_NL.md">Nederlands</a>
|
<a href="readme_i18n/README_nl_NL.md">Nederlands</a>
|
||||||
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
|
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
|
||||||
<a href="readme_i18n/README_zh_CN.md">中文</a>
|
<a href="readme_i18n/README_zh_CN.md">简体中文</a>
|
||||||
|
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
|
||||||
<a href="readme_i18n/README_uk_UA.md">Українська</a>
|
<a href="readme_i18n/README_uk_UA.md">Українська</a>
|
||||||
<a href="readme_i18n/README_ru_RU.md">Русский</a>
|
<a href="readme_i18n/README_ru_RU.md">Русский</a>
|
||||||
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
|
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
|
||||||
@@ -38,26 +39,25 @@
|
|||||||
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
|
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
|
|
||||||
- ⚠️ The project is under **very active** development.
|
> [!WARNING]
|
||||||
- ⚠️ Expect bugs and breaking changes.
|
> ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
|
||||||
- ⚠️ **Do not use the app as the only way to store your photos and videos.**
|
>
|
||||||
- ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> You can find the main documentation, including installation guides, at https://immich.app/.
|
> You can find the main documentation, including installation guides, at https://immich.app/.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [Documentation](https://immich.app/docs)
|
- [Documentation](https://docs.immich.app/)
|
||||||
- [About](https://immich.app/docs/overview/introduction)
|
- [About](https://docs.immich.app/overview/introduction)
|
||||||
- [Installation](https://immich.app/docs/install/requirements)
|
- [Installation](https://docs.immich.app/install/requirements)
|
||||||
- [Roadmap](https://immich.app/roadmap)
|
- [Roadmap](https://immich.app/roadmap)
|
||||||
- [Demo](#demo)
|
- [Demo](#demo)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Translations](https://immich.app/docs/developer/translations)
|
- [Translations](https://docs.immich.app/developer/translations)
|
||||||
- [Contributing](https://immich.app/docs/overview/support-the-project)
|
- [Contributing](https://docs.immich.app/overview/support-the-project)
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ Access the demo [here](https://demo.immich.app). For the mobile app, you can use
|
|||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
Read more about translations [here](https://immich.app/docs/developer/translations).
|
Read more about translations [here](https://docs.immich.app/developer/translations).
|
||||||
|
|
||||||
<a href="https://hosted.weblate.org/engage/immich/">
|
<a href="https://hosted.weblate.org/engage/immich/">
|
||||||
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Translation status" />
|
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Translation status" />
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
22.19.0
|
24.11.0
|
||||||
|
|||||||
@@ -1,30 +1,38 @@
|
|||||||
A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/).
|
A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/).
|
||||||
|
|
||||||
Please see the [Immich CLI documentation](https://immich.app/docs/features/command-line-interface).
|
Please see the [Immich CLI documentation](https://docs.immich.app/features/command-line-interface).
|
||||||
|
|
||||||
# For developers
|
# For developers
|
||||||
|
|
||||||
Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
|
Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
|
||||||
|
|
||||||
$ npm install
|
$ pnpm install
|
||||||
$ npm run build
|
$ pnpm run build
|
||||||
|
|
||||||
Then, to build the open-api client run the following in the open-api folder:
|
Then, to build the open-api client run the following in the open-api folder:
|
||||||
|
|
||||||
$ ./bin/generate-open-api.sh
|
$ ./bin/generate-open-api.sh
|
||||||
|
|
||||||
To run the Immich CLI from source, run the following in the cli folder:
|
## Run from build
|
||||||
|
|
||||||
$ npm install
|
Go to the cli folder and build it:
|
||||||
$ npm run build
|
|
||||||
$ ts-node .
|
|
||||||
|
|
||||||
You'll need ts-node, the easiest way to install it is to use npm:
|
$ pnpm install
|
||||||
|
$ pnpm run build
|
||||||
|
$ node dist/index.js
|
||||||
|
|
||||||
$ npm i -g ts-node
|
## Run and Debug from source (VSCode)
|
||||||
|
|
||||||
|
With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug
|
||||||
|
|
||||||
|
`"args": ["upload", "--help"],`
|
||||||
|
|
||||||
|
replace that for the command of your choice.
|
||||||
|
|
||||||
|
## Install from build
|
||||||
|
|
||||||
You can also build and install the CLI using
|
You can also build and install the CLI using
|
||||||
|
|
||||||
$ npm run build
|
$ pnpm run build
|
||||||
$ npm install -g .
|
$ pnpm install -g .
|
||||||
****
|
****
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.92",
|
"version": "2.2.101",
|
||||||
"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",
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/micromatch": "^4.0.9",
|
"@types/micromatch": "^4.0.9",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^22.18.1",
|
"@types/node": "^22.18.13",
|
||||||
"@vitest/coverage-v8": "^3.0.0",
|
"@vitest/coverage-v8": "^3.0.0",
|
||||||
"byte-size": "^9.0.0",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:dev": "vite build --sourcemap true",
|
||||||
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"prepack": "npm run build",
|
"prepack": "npm run build",
|
||||||
@@ -68,6 +69,6 @@
|
|||||||
"micromatch": "^4.0.8"
|
"micromatch": "^4.0.8"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.19.0"
|
"node": "24.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ describe('startWatch', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filger out ignored patterns', async () => {
|
it('should filter out ignored patterns', async () => {
|
||||||
const testFilePath = path.join(testFolder, 'test.jpg');
|
const testFilePath = path.join(testFolder, 'test.jpg');
|
||||||
const ignoredPattern = 'ignored';
|
const ignoredPattern = 'ignored';
|
||||||
const ignoredFolder = path.join(testFolder, ignoredPattern);
|
const ignoredFolder = path.join(testFolder, ignoredPattern);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface UploadOptionsDto {
|
|||||||
dryRun?: boolean;
|
dryRun?: boolean;
|
||||||
skipHash?: boolean;
|
skipHash?: boolean;
|
||||||
delete?: boolean;
|
delete?: boolean;
|
||||||
|
deleteDuplicates?: boolean;
|
||||||
album?: boolean;
|
album?: boolean;
|
||||||
albumName?: string;
|
albumName?: string;
|
||||||
includeHidden?: boolean;
|
includeHidden?: boolean;
|
||||||
@@ -70,10 +71,8 @@ const uploadBatch = async (files: string[], options: UploadOptionsDto) => {
|
|||||||
console.log(JSON.stringify({ newFiles, duplicates, newAssets }, undefined, 4));
|
console.log(JSON.stringify({ newFiles, duplicates, newAssets }, undefined, 4));
|
||||||
}
|
}
|
||||||
await updateAlbums([...newAssets, ...duplicates], options);
|
await updateAlbums([...newAssets, ...duplicates], options);
|
||||||
await deleteFiles(
|
|
||||||
newAssets.map(({ filepath }) => filepath),
|
await deleteFiles(newAssets, duplicates, options);
|
||||||
options,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startWatch = async (
|
export const startWatch = async (
|
||||||
@@ -406,28 +405,46 @@ const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaRespon
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteFiles = async (files: string[], options: UploadOptionsDto): Promise<void> => {
|
const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise<void> => {
|
||||||
if (!options.delete) {
|
let fileCount = 0;
|
||||||
return;
|
if (options.delete) {
|
||||||
|
fileCount += uploaded.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.deleteDuplicates) {
|
||||||
|
fileCount += duplicates.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.dryRun) {
|
if (options.dryRun) {
|
||||||
console.log(`Would have deleted ${files.length} local asset${s(files.length)}`);
|
console.log(`Would have deleted ${fileCount} local asset${s(fileCount)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileCount === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Deleting assets that have been uploaded...');
|
console.log('Deleting assets that have been uploaded...');
|
||||||
|
|
||||||
const deletionProgress = new SingleBar(
|
const deletionProgress = new SingleBar(
|
||||||
{ format: 'Deleting local assets | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' },
|
{ format: 'Deleting local assets | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' },
|
||||||
Presets.shades_classic,
|
Presets.shades_classic,
|
||||||
);
|
);
|
||||||
deletionProgress.start(files.length, 0);
|
deletionProgress.start(fileCount, 0);
|
||||||
|
|
||||||
|
const chunkDelete = async (files: Asset[]) => {
|
||||||
|
for (const assetBatch of chunk(files, options.concurrency)) {
|
||||||
|
await Promise.all(assetBatch.map((input: Asset) => unlink(input.filepath)));
|
||||||
|
deletionProgress.update(assetBatch.length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const assetBatch of chunk(files, options.concurrency)) {
|
if (options.delete) {
|
||||||
await Promise.all(assetBatch.map((input: string) => unlink(input)));
|
await chunkDelete(uploaded);
|
||||||
deletionProgress.update(assetBatch.length);
|
}
|
||||||
|
|
||||||
|
if (options.deleteDuplicates) {
|
||||||
|
await chunkDelete(duplicates);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
deletionProgress.stop();
|
deletionProgress.stop();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { serverInfo } from 'src/commands/server-info';
|
|||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
|
|
||||||
const defaultConfigDirectory = path.join(os.homedir(), '.config/immich/');
|
const defaultConfigDirectory = path.join(os.homedir(), '.config/immich/');
|
||||||
|
const defaultConcurrency = Math.max(1, os.cpus().length - 1);
|
||||||
|
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
.name('immich')
|
.name('immich')
|
||||||
@@ -66,7 +67,7 @@ program
|
|||||||
.addOption(
|
.addOption(
|
||||||
new Option('-c, --concurrency <number>', 'Number of assets to upload at the same time')
|
new Option('-c, --concurrency <number>', 'Number of assets to upload at the same time')
|
||||||
.env('IMMICH_UPLOAD_CONCURRENCY')
|
.env('IMMICH_UPLOAD_CONCURRENCY')
|
||||||
.default(4),
|
.default(defaultConcurrency),
|
||||||
)
|
)
|
||||||
.addOption(
|
.addOption(
|
||||||
new Option('-j, --json-output', 'Output detailed information in json format')
|
new Option('-j, --json-output', 'Output detailed information in json format')
|
||||||
@@ -74,6 +75,11 @@ program
|
|||||||
.default(false),
|
.default(false),
|
||||||
)
|
)
|
||||||
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
|
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
|
||||||
|
.addOption(
|
||||||
|
new Option('--delete-duplicates', 'Delete local assets that are duplicates (already exist on server)').env(
|
||||||
|
'IMMICH_DELETE_DUPLICATES',
|
||||||
|
),
|
||||||
|
)
|
||||||
.addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true))
|
.addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true))
|
||||||
.addOption(
|
.addOption(
|
||||||
new Option('--watch', 'Watch for changes and upload automatically')
|
new Option('--watch', 'Watch for changes and upload automatically')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
|
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
|
||||||
#
|
#
|
||||||
# Make sure to use the docker-compose.yml of the current release:
|
# Make sure to use the docker-compose.yml of the current release:
|
||||||
#
|
#
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
# The compose file on main may not be compatible with the latest release.
|
# The compose file on main may not be compatible with the latest release.
|
||||||
|
|
||||||
# For development see:
|
# For development see:
|
||||||
# - https://immich.app/docs/developer/setup
|
# - https://docs.immich.app/developer/setup
|
||||||
# - https://immich.app/docs/developer/troubleshooting
|
# - https://docs.immich.app/developer/troubleshooting
|
||||||
|
|
||||||
name: immich-dev
|
name: immich-dev
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ services:
|
|||||||
IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server
|
IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server
|
||||||
IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/
|
IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/
|
||||||
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues
|
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues
|
||||||
IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://immich.app/docs
|
IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app
|
||||||
IMMICH_THIRD_PARTY_SUPPORT_URL: https://immich.app/docs/community-guides
|
IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides
|
||||||
ulimits:
|
ulimits:
|
||||||
nofile:
|
nofile:
|
||||||
soft: 1048576
|
soft: 1048576
|
||||||
@@ -122,7 +122,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 3003:3003
|
- 3003:3003
|
||||||
volumes:
|
volumes:
|
||||||
- ../machine-learning:/usr/src/app
|
- ../machine-learning/immich_ml:/usr/src/immich_ml
|
||||||
- model-cache:/cache
|
- model-cache:/cache
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
@@ -134,13 +134,13 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
|
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
|
||||||
#
|
#
|
||||||
# Make sure to use the docker-compose.yml of the current release:
|
# Make sure to use the docker-compose.yml of the current release:
|
||||||
#
|
#
|
||||||
@@ -56,14 +56,14 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -83,7 +83,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996
|
image: prom/prometheus@sha256:49214755b6153f90a597adcbff0252cc61069f8ab69ce8411285cd4a560e8038
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -95,7 +95,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.1.1-ubuntu@sha256:d1da838234ff2de93e0065ee1bf0e66d38f948dcc5d718c25fa6237e14b4424a
|
image: grafana/grafana:12.2.1-ubuntu@sha256:797530c642f7b41ba7848c44cfda5e361ef1f3391a98bed1e5d448c472b6826a
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
|
# WARNING: To install Immich, follow our guide: https://docs.immich.app/install/docker-compose
|
||||||
#
|
#
|
||||||
# Make sure to use the docker-compose.yml of the current release:
|
# Make sure to use the docker-compose.yml of the current release:
|
||||||
#
|
#
|
||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
|
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
|
||||||
# Example tag: ${IMMICH_VERSION:-release}-cuda
|
# Example tag: ${IMMICH_VERSION:-release}-cuda
|
||||||
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
|
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
|
||||||
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
|
# extends: # uncomment this section for hardware acceleration - see https://docs.immich.app/features/ml-hardware-acceleration
|
||||||
# file: hwaccel.ml.yml
|
# file: hwaccel.ml.yml
|
||||||
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
|
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
|
||||||
volumes:
|
volumes:
|
||||||
@@ -49,14 +49,14 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:8-bookworm@sha256:fea8b3e67b15729d4bb70589eb03367bab9ad1ee89c876f54327fc7c6e618571
|
image: docker.io/valkey/valkey:8@sha256:81db6d39e1bba3b3ff32bd3a1b19a6d69690f94a3954ec131277b9a26b95b3aa
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
|
# You can find documentation for all the supported env variables at https://docs.immich.app/install/environment-variables
|
||||||
|
|
||||||
# The location where your uploaded files are stored
|
# The location where your uploaded files are stored
|
||||||
UPLOAD_LOCATION=./library
|
UPLOAD_LOCATION=./library
|
||||||
@@ -9,8 +9,8 @@ DB_DATA_LOCATION=./postgres
|
|||||||
# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
||||||
# TZ=Etc/UTC
|
# TZ=Etc/UTC
|
||||||
|
|
||||||
# The Immich version to use. You can pin this to a specific version like "v1.71.0"
|
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
|
||||||
IMMICH_VERSION=release
|
IMMICH_VERSION=v2
|
||||||
|
|
||||||
# Connection secret for postgres. You should change it to a random password
|
# Connection secret for postgres. You should change it to a random password
|
||||||
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# you can inline the config for a backend by copying its contents
|
# you can inline the config for a backend by copying its contents
|
||||||
# into the immich-machine-learning service in the docker-compose.yml file.
|
# into the immich-machine-learning service in the docker-compose.yml file.
|
||||||
|
|
||||||
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
|
# See https://docs.immich.app/features/ml-hardware-acceleration for info on usage.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
armnn:
|
armnn:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# you can inline the config for a backend by copying its contents
|
# you can inline the config for a backend by copying its contents
|
||||||
# into the immich-microservices service in the docker-compose.yml file.
|
# into the immich-microservices service in the docker-compose.yml file.
|
||||||
|
|
||||||
# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.
|
# See https://docs.immich.app/features/hardware-transcoding for more info on using hardware transcoding.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
cpu: {}
|
cpu: {}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
22.19.0
|
24.11.0
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ For organizations seeking to resell Immich, we have established the following gu
|
|||||||
|
|
||||||
- Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team.
|
- Do not misrepresent your reseller site or services as being officially affiliated with or endorsed by Immich or our development team.
|
||||||
|
|
||||||
- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directy from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work.
|
- For small resellers who wish to contribute financially to Immich's development, we recommend directing your customers to purchase licenses directly from us rather than attempting to broker revenue-sharing arrangements. We ask that you refrain from misrepresenting reseller activities as directly supporting our development work.
|
||||||
|
|
||||||
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ Then please follow the steps in the following section for restoring the database
|
|||||||
<TabItem value="Linux system" label="Linux system" default>
|
<TabItem value="Linux system" label="Linux system" default>
|
||||||
|
|
||||||
```bash title='Backup'
|
```bash title='Backup'
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
|
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME> | gzip > "/path/to/backup/dump.sql.gz"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,16 +70,18 @@ docker compose create # Create Docker containers for Immich apps witho
|
|||||||
docker start immich_postgres # Start Postgres server
|
docker start immich_postgres # Start Postgres server
|
||||||
sleep 10 # Wait for Postgres server to start up
|
sleep 10 # Wait for Postgres server to start up
|
||||||
# Check the database user if you deviated from the default
|
# Check the database user if you deviated from the default
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
gunzip --stdout "/path/to/backup/dump.sql.gz" \
|
gunzip --stdout "/path/to/backup/dump.sql.gz" \
|
||||||
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
|
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
|
||||||
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
|
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
|
||||||
docker compose up -d # Start remainder of Immich apps
|
docker compose up -d # Start remainder of Immich apps
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
|
||||||
|
|
||||||
```powershell title='Backup'
|
```powershell title='Backup'
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>))
|
[System.IO.File]::WriteAllLines("C:\absolute\path\to\backup\dump.sql", (docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=<DB_USERNAME>))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -92,13 +95,15 @@ docker compose create # Create Docker containers for
|
|||||||
docker start immich_postgres # Start Postgres server
|
docker start immich_postgres # Start Postgres server
|
||||||
sleep 10 # Wait for Postgres server to start up
|
sleep 10 # Wait for Postgres server to start up
|
||||||
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
|
docker exec -it immich_postgres bash # Enter the Docker shell and run the following command
|
||||||
# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
|
# If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
|
||||||
|
# Replace <DB_USERNAME> with the database username - usually postgres unless you have changed it.
|
||||||
|
|
||||||
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
|
cat "/dump.sql" | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | psql --dbname=postgres --username=<DB_USERNAME>
|
||||||
exit # Exit the Docker shell
|
exit # Exit the Docker shell
|
||||||
docker compose up -d # Start remainder of Immich apps
|
docker compose up -d # Start remainder of Immich apps
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database.
|
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database.
|
||||||
|
|||||||
@@ -55,3 +55,19 @@ Additionally, some jobs (such as memories generation) run on a schedule, which i
|
|||||||
:::note
|
:::note
|
||||||
Some jobs ([External Libraries](/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings.
|
Some jobs ([External Libraries](/features/libraries) scanning, Database Dump) are configured in their own sections in System Settings.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Job processing order
|
||||||
|
|
||||||
|
The below diagram shows the job run order for newly uploaded files
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Asset Upload] --> B[Metadata Extraction]
|
||||||
|
B --> C[Storage Template Migration]
|
||||||
|
C --> D["Thumbnail Generation (Large, small, blurred and person)"]
|
||||||
|
D --> E[Smart Search]
|
||||||
|
D --> F[Face Detection]
|
||||||
|
D --> G[Video Transcoding]
|
||||||
|
E --> H[Duplicate Detection]
|
||||||
|
F --> I[Facial Recognition]
|
||||||
|
```
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ Users can deploy a custom reverse proxy that forwards requests to Immich. This w
|
|||||||
Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain.
|
Immich does not support being served on a sub-path such as `location /immich {`. It has to be served on the root path of a (sub)domain.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::info
|
||||||
|
If your reverse proxy uses the [Let's Encrypt](https://letsencrypt.org/) [http-01 challenge](https://letsencrypt.org/docs/challenge-types/#http-01-challenge), you may want to verify that the Immich well-known endpoint (`/.well-known/immich`) gets correctly routed to Immich, otherwise it will likely be routed elsewhere and the mobile app may run into connection issues.
|
||||||
|
:::
|
||||||
|
|
||||||
### Nginx example config
|
### Nginx example config
|
||||||
|
|
||||||
Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server.
|
Below is an example config for nginx. Make sure to set `public_url` to the front-facing URL of your instance, and `backend_url` to the path of the Immich server.
|
||||||
@@ -37,29 +41,14 @@ server {
|
|||||||
location / {
|
location / {
|
||||||
proxy_pass http://<backend_url>:2283;
|
proxy_pass http://<backend_url>:2283;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# useful when using Let's Encrypt http-01 challenge
|
||||||
|
# location = /.well-known/immich {
|
||||||
|
# proxy_pass http://<backend_url>:2283;
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Compatibility with Let's Encrypt
|
|
||||||
|
|
||||||
In the event that your nginx configuration includes a section for Let's Encrypt, it's likely that you have a segment similar to the following:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location ~ /.well-known {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This particular `location` directive can inadvertently prevent mobile clients from reaching the `/.well-known/immich` path, which is crucial for discovery. Usual error message for this case is: "Your app major version is not compatible with the server". To remedy this, you should introduce an additional location block specifically for this path, ensuring that requests are correctly proxied to the Immich server:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
location = /.well-known/immich {
|
|
||||||
proxy_pass http://<backend_url>:2283;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By doing so, you'll maintain the functionality of Let's Encrypt while allowing mobile clients to access the necessary Immich path without obstruction.
|
|
||||||
|
|
||||||
### Caddy example config
|
### Caddy example config
|
||||||
|
|
||||||
As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config.
|
As an alternative to nginx, you can also use [Caddy](https://caddyserver.com/) as a reverse proxy (with automatic HTTPS configuration). Below is an example config.
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# Community Guides
|
|
||||||
|
|
||||||
This page lists community guides that are written around Immich, but not officially supported by the development team.
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk.
|
|
||||||
:::
|
|
||||||
|
|
||||||
import CommunityGuides from '../src/components/community-guides.tsx';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
<CommunityGuides />
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Community Projects
|
|
||||||
|
|
||||||
This page lists community projects that are built around Immich, but not officially supported by the development team.
|
|
||||||
|
|
||||||
:::warning
|
|
||||||
This list comes with no guarantees about security, performance, reliability, or accuracy. Use at your own risk.
|
|
||||||
:::
|
|
||||||
|
|
||||||
import CommunityProjects from '../src/components/community-projects.tsx';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
<CommunityProjects />
|
|
||||||
@@ -4,7 +4,7 @@ Immich supports the Google's Cast protocol so that photos and videos can be cast
|
|||||||
|
|
||||||
## Enable Google Cast Support
|
## Enable Google Cast Support
|
||||||
|
|
||||||
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retreive them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
|
Google Cast support is disabled by default. The web UI uses Google-provided scripts and must retrieve them from Google servers when the page loads. This is a privacy concern for some and is thus opt-in.
|
||||||
|
|
||||||
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`
|
You can enable Google Cast support through `Account Settings > Features > Cast > Google Cast`
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ Options:
|
|||||||
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
|
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
|
||||||
-j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT)
|
-j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT)
|
||||||
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
|
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
|
||||||
|
--delete-duplicates Delete local assets that are duplicates (already exist on server) (env: IMMICH_DELETE_DUPLICATES)
|
||||||
--no-progress Hide progress bars (env: IMMICH_PROGRESS_BAR)
|
--no-progress Hide progress bars (env: IMMICH_PROGRESS_BAR)
|
||||||
--watch Watch for changes and upload automatically (default: false, env: IMMICH_WATCH_CHANGES)
|
--watch Watch for changes and upload automatically (default: false, env: IMMICH_WATCH_CHANGES)
|
||||||
--help display help for command
|
--help display help for command
|
||||||
@@ -182,7 +183,7 @@ For example to get a list of files that would be uploaded for further
|
|||||||
processing:
|
processing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
immich upload --dry-run . | tail -n +4 | jq .newFiles[]
|
immich upload --dry-run . | tail -n +6 | jq .newFiles[]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Obtain the API Key
|
### Obtain the API Key
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# External Libraries
|
# External Libraries
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Currently an external library can only belong to a single user which is selected when the library is initially created.
|
||||||
|
:::
|
||||||
|
|
||||||
External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up.
|
External libraries track assets stored in the filesystem outside of Immich. When the external library is scanned, Immich will load videos and photos from disk and create the corresponding assets. These assets will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc. Later, if a file is modified outside of Immich, you need to scan the library for the changes to show up.
|
||||||
|
|
||||||
If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost.
|
If an external asset is deleted from disk, Immich will move it to trash on rescan. To restore the asset, you need to restore the original file. After 30 days the file will be removed from trash, and any changes to metadata within Immich will be lost.
|
||||||
|
|||||||
@@ -54,9 +54,25 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
#### OpenVINO
|
#### OpenVINO
|
||||||
|
|
||||||
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
|
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
|
||||||
- Ensure the server's kernel version is new enough to use the device for hardware accceleration.
|
- Ensure the server's kernel version is new enough to use the device for hardware acceleration.
|
||||||
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
|
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
|
||||||
|
|
||||||
|
#### OpenVINO-WSL
|
||||||
|
|
||||||
|
- Ensure your container can access the /dev/dri directory, you can verify this by doing `docker exec -t immich_machine_learning ls -la /dev/dri`. If this is not the case execute `getent group render` and `getent group video` on the WSL host, then add those groups to hwaccel.ml.yaml
|
||||||
|
```yaml
|
||||||
|
openvino-wsl:
|
||||||
|
devices:
|
||||||
|
- /dev/dri:/dev/dri
|
||||||
|
- /dev/dxg:/dev/dxg
|
||||||
|
volumes:
|
||||||
|
- /dev/bus/usb:/dev/bus/usb
|
||||||
|
- /usr/lib/wsl:/usr/lib/wsl
|
||||||
|
group_add:
|
||||||
|
- 44 # Replace this number with the number you found with getent group video
|
||||||
|
- 992 # Replace this number with the number you found with getent group render
|
||||||
|
```
|
||||||
|
|
||||||
#### RKNN
|
#### RKNN
|
||||||
|
|
||||||
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
|
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { mdiCloudOffOutline, mdiCloudCheckOutline } from '@mdi/js';
|
|||||||
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
|
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
|
||||||
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
|
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
|
||||||
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
||||||
import { cloudDonePath, cloudOffPath } from '@site/src/components/svg-paths';
|
|
||||||
|
|
||||||
# Mobile App
|
# Mobile App
|
||||||
|
|
||||||
@@ -11,6 +10,16 @@ import { cloudDonePath, cloudOffPath } from '@site/src/components/svg-paths';
|
|||||||
|
|
||||||
<MobileAppDownload />
|
<MobileAppDownload />
|
||||||
|
|
||||||
|
:::info Android verification
|
||||||
|
Below are the SHA-256 fingerprints for the certificates signing the android applications.
|
||||||
|
|
||||||
|
- Playstore / Github releases:
|
||||||
|
`86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20`
|
||||||
|
- F-Droid releases:
|
||||||
|
`FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF`
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
:::info Beta Program
|
:::info Beta Program
|
||||||
The beta release channel allows users to test upcoming changes before they are officially released. To join the channel use the links below.
|
The beta release channel allows users to test upcoming changes before they are officially released. To join the channel use the links below.
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ You can read this guide to learn more about [partner sharing](/features/partner-
|
|||||||
|
|
||||||
## Public sharing
|
## Public sharing
|
||||||
|
|
||||||
You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a varierity of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account.
|
You can create a public link to share a group of photos or videos, or an album, with anyone. The public link can be shared via email, social media, or any other method. There are a variety of options to customize the public link, such as setting an expiration date, password protection, and more. Public shared link is handy when you want to share a group of photos or videos with someone who doesn't have an Immich account and allow the shared user to upload their photos or videos to your account.
|
||||||
|
|
||||||
The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance.
|
The public shared link is generated with a random URL, which acts as as a secret to avoid the link being guessed by unwanted parties, for instance.
|
||||||
|
|
||||||
|
|||||||
@@ -106,14 +106,14 @@ SELECT "user"."email", "asset"."type", COUNT(*) FROM "asset"
|
|||||||
|
|
||||||
```sql title="Count by tag"
|
```sql title="Count by tag"
|
||||||
SELECT "t"."value" AS "tag_name", COUNT(*) AS "number_assets" FROM "tag" "t"
|
SELECT "t"."value" AS "tag_name", COUNT(*) AS "number_assets" FROM "tag" "t"
|
||||||
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id"
|
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id"
|
||||||
WHERE "a"."visibility" != 'hidden'
|
WHERE "a"."visibility" != 'hidden'
|
||||||
GROUP BY "t"."value" ORDER BY "number_assets" DESC;
|
GROUP BY "t"."value" ORDER BY "number_assets" DESC;
|
||||||
```
|
```
|
||||||
|
|
||||||
```sql title="Count by tag (per user)"
|
```sql title="Count by tag (per user)"
|
||||||
SELECT "t"."value" AS "tag_name", "u"."email" as "user_email", COUNT(*) AS "number_assets" FROM "tag" "t"
|
SELECT "t"."value" AS "tag_name", "u"."email" as "user_email", COUNT(*) AS "number_assets" FROM "tag" "t"
|
||||||
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagsId" JOIN "asset" "a" ON "ta"."assetsId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id"
|
JOIN "tag_asset" "ta" ON "t"."id" = "ta"."tagId" JOIN "asset" "a" ON "ta"."assetId" = "a"."id" JOIN "user" "u" ON "a"."ownerId" = "u"."id"
|
||||||
WHERE "a"."visibility" != 'hidden'
|
WHERE "a"."visibility" != 'hidden'
|
||||||
GROUP BY "t"."value", "u"."email" ORDER BY "number_assets" DESC;
|
GROUP BY "t"."value", "u"."email" ORDER BY "number_assets" DESC;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ Restart Immich by running `docker compose up -d`.
|
|||||||
|
|
||||||
# Create the library
|
# Create the library
|
||||||
|
|
||||||
|
:::info
|
||||||
|
External library management requires administrator access and the steps below assume you are using an admin account.
|
||||||
|
:::
|
||||||
|
|
||||||
In the Immich web UI:
|
In the Immich web UI:
|
||||||
|
|
||||||
- click the **Administration** link in the upper right corner.
|
- click the **Administration** link in the upper right corner.
|
||||||
@@ -33,7 +37,7 @@ In the Immich web UI:
|
|||||||
<img src={require('./img/create-external-library.webp').default} width="50%" title="Create Library button" />
|
<img src={require('./img/create-external-library.webp').default} width="50%" title="Create Library button" />
|
||||||
|
|
||||||
- In the dialog, select which user should own the new library
|
- In the dialog, select which user should own the new library
|
||||||
<img src={require('./img/library-owner.webp').default} width="50%" title="Library owner diaglog" />
|
<img src={require('./img/library-owner.webp').default} width="50%" title="Library owner dialog" />
|
||||||
|
|
||||||
- Click the three-dots menu and select **Edit Import Paths**
|
- Click the three-dots menu and select **Edit Import Paths**
|
||||||
<img src={require('./img/edit-import-paths.webp').default} width="50%" title="Edit Import Paths menu option" />
|
<img src={require('./img/edit-import-paths.webp').default} width="50%" title="Edit Import Paths menu option" />
|
||||||
|
|||||||
@@ -16,48 +16,76 @@ The default configuration looks like this:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ffmpeg": {
|
|
||||||
"crf": 23,
|
|
||||||
"threads": 0,
|
|
||||||
"preset": "ultrafast",
|
|
||||||
"targetVideoCodec": "h264",
|
|
||||||
"acceptedVideoCodecs": ["h264"],
|
|
||||||
"targetAudioCodec": "aac",
|
|
||||||
"acceptedAudioCodecs": ["aac", "mp3", "libopus", "pcm_s16le"],
|
|
||||||
"acceptedContainers": ["mov", "ogg", "webm"],
|
|
||||||
"targetResolution": "720",
|
|
||||||
"maxBitrate": "0",
|
|
||||||
"bframes": -1,
|
|
||||||
"refs": 0,
|
|
||||||
"gopSize": 0,
|
|
||||||
"temporalAQ": false,
|
|
||||||
"cqMode": "auto",
|
|
||||||
"twoPass": false,
|
|
||||||
"preferredHwDevice": "auto",
|
|
||||||
"transcode": "required",
|
|
||||||
"tonemap": "hable",
|
|
||||||
"accel": "disabled",
|
|
||||||
"accelDecode": false
|
|
||||||
},
|
|
||||||
"backup": {
|
"backup": {
|
||||||
"database": {
|
"database": {
|
||||||
"enabled": true,
|
|
||||||
"cronExpression": "0 02 * * *",
|
"cronExpression": "0 02 * * *",
|
||||||
|
"enabled": true,
|
||||||
"keepLastAmount": 14
|
"keepLastAmount": 14
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"accel": "disabled",
|
||||||
|
"accelDecode": false,
|
||||||
|
"acceptedAudioCodecs": ["aac", "mp3", "libopus"],
|
||||||
|
"acceptedContainers": ["mov", "ogg", "webm"],
|
||||||
|
"acceptedVideoCodecs": ["h264"],
|
||||||
|
"bframes": -1,
|
||||||
|
"cqMode": "auto",
|
||||||
|
"crf": 23,
|
||||||
|
"gopSize": 0,
|
||||||
|
"maxBitrate": "0",
|
||||||
|
"preferredHwDevice": "auto",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"refs": 0,
|
||||||
|
"targetAudioCodec": "aac",
|
||||||
|
"targetResolution": "720",
|
||||||
|
"targetVideoCodec": "h264",
|
||||||
|
"temporalAQ": false,
|
||||||
|
"threads": 0,
|
||||||
|
"tonemap": "hable",
|
||||||
|
"transcode": "required",
|
||||||
|
"twoPass": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"colorspace": "p3",
|
||||||
|
"extractEmbedded": false,
|
||||||
|
"fullsize": {
|
||||||
|
"enabled": false,
|
||||||
|
"format": "jpeg",
|
||||||
|
"quality": 80
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"format": "jpeg",
|
||||||
|
"quality": 80,
|
||||||
|
"size": 1440
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"format": "webp",
|
||||||
|
"quality": 80,
|
||||||
|
"size": 250
|
||||||
|
}
|
||||||
|
},
|
||||||
"job": {
|
"job": {
|
||||||
"backgroundTask": {
|
"backgroundTask": {
|
||||||
"concurrency": 5
|
"concurrency": 5
|
||||||
},
|
},
|
||||||
"smartSearch": {
|
"faceDetection": {
|
||||||
"concurrency": 2
|
"concurrency": 2
|
||||||
},
|
},
|
||||||
|
"library": {
|
||||||
|
"concurrency": 5
|
||||||
|
},
|
||||||
"metadataExtraction": {
|
"metadataExtraction": {
|
||||||
"concurrency": 5
|
"concurrency": 5
|
||||||
},
|
},
|
||||||
"faceDetection": {
|
"migration": {
|
||||||
"concurrency": 2
|
"concurrency": 5
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"concurrency": 5
|
||||||
|
},
|
||||||
|
"ocr": {
|
||||||
|
"concurrency": 1
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"concurrency": 5
|
"concurrency": 5
|
||||||
@@ -65,20 +93,23 @@ The default configuration looks like this:
|
|||||||
"sidecar": {
|
"sidecar": {
|
||||||
"concurrency": 5
|
"concurrency": 5
|
||||||
},
|
},
|
||||||
"library": {
|
"smartSearch": {
|
||||||
"concurrency": 5
|
"concurrency": 2
|
||||||
},
|
|
||||||
"migration": {
|
|
||||||
"concurrency": 5
|
|
||||||
},
|
},
|
||||||
"thumbnailGeneration": {
|
"thumbnailGeneration": {
|
||||||
"concurrency": 3
|
"concurrency": 3
|
||||||
},
|
},
|
||||||
"videoConversion": {
|
"videoConversion": {
|
||||||
"concurrency": 1
|
"concurrency": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"scan": {
|
||||||
|
"cronExpression": "0 0 * * *",
|
||||||
|
"enabled": true
|
||||||
},
|
},
|
||||||
"notifications": {
|
"watch": {
|
||||||
"concurrency": 5
|
"enabled": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
@@ -86,8 +117,11 @@ The default configuration looks like this:
|
|||||||
"level": "log"
|
"level": "log"
|
||||||
},
|
},
|
||||||
"machineLearning": {
|
"machineLearning": {
|
||||||
"enabled": true,
|
"availabilityChecks": {
|
||||||
"urls": ["http://immich-machine-learning:3003"],
|
"enabled": true,
|
||||||
|
"interval": 30000,
|
||||||
|
"timeout": 2000
|
||||||
|
},
|
||||||
"clip": {
|
"clip": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"modelName": "ViT-B-32__openai"
|
"modelName": "ViT-B-32__openai"
|
||||||
@@ -96,27 +130,59 @@ The default configuration looks like this:
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"maxDistance": 0.01
|
"maxDistance": 0.01
|
||||||
},
|
},
|
||||||
|
"enabled": true,
|
||||||
"facialRecognition": {
|
"facialRecognition": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"modelName": "buffalo_l",
|
|
||||||
"minScore": 0.7,
|
|
||||||
"maxDistance": 0.5,
|
"maxDistance": 0.5,
|
||||||
"minFaces": 3
|
"minFaces": 3,
|
||||||
}
|
"minScore": 0.7,
|
||||||
|
"modelName": "buffalo_l"
|
||||||
|
},
|
||||||
|
"ocr": {
|
||||||
|
"enabled": true,
|
||||||
|
"maxResolution": 736,
|
||||||
|
"minDetectionScore": 0.5,
|
||||||
|
"minRecognitionScore": 0.8,
|
||||||
|
"modelName": "PP-OCRv5_mobile"
|
||||||
|
},
|
||||||
|
"urls": ["http://immich-machine-learning:3003"]
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
|
"darkStyle": "https://tiles.immich.cloud/v1/style/dark.json",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"lightStyle": "https://tiles.immich.cloud/v1/style/light.json",
|
"lightStyle": "https://tiles.immich.cloud/v1/style/light.json"
|
||||||
"darkStyle": "https://tiles.immich.cloud/v1/style/dark.json"
|
|
||||||
},
|
|
||||||
"reverseGeocoding": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"faces": {
|
"faces": {
|
||||||
"import": false
|
"import": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newVersionCheck": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"nightlyTasks": {
|
||||||
|
"clusterNewFaces": true,
|
||||||
|
"databaseCleanup": true,
|
||||||
|
"generateMemories": true,
|
||||||
|
"missingThumbnails": true,
|
||||||
|
"startTime": "00:00",
|
||||||
|
"syncQuotaUsage": true
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"smtp": {
|
||||||
|
"enabled": false,
|
||||||
|
"from": "",
|
||||||
|
"replyTo": "",
|
||||||
|
"transport": {
|
||||||
|
"host": "",
|
||||||
|
"ignoreCert": false,
|
||||||
|
"password": "",
|
||||||
|
"port": 587,
|
||||||
|
"secure": false,
|
||||||
|
"username": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"oauth": {
|
"oauth": {
|
||||||
"autoLaunch": false,
|
"autoLaunch": false,
|
||||||
"autoRegister": true,
|
"autoRegister": true,
|
||||||
@@ -128,70 +194,44 @@ The default configuration looks like this:
|
|||||||
"issuerUrl": "",
|
"issuerUrl": "",
|
||||||
"mobileOverrideEnabled": false,
|
"mobileOverrideEnabled": false,
|
||||||
"mobileRedirectUri": "",
|
"mobileRedirectUri": "",
|
||||||
|
"profileSigningAlgorithm": "none",
|
||||||
|
"roleClaim": "immich_role",
|
||||||
"scope": "openid email profile",
|
"scope": "openid email profile",
|
||||||
"signingAlgorithm": "RS256",
|
"signingAlgorithm": "RS256",
|
||||||
"profileSigningAlgorithm": "none",
|
|
||||||
"storageLabelClaim": "preferred_username",
|
"storageLabelClaim": "preferred_username",
|
||||||
"storageQuotaClaim": "immich_quota"
|
"storageQuotaClaim": "immich_quota",
|
||||||
|
"timeout": 30000,
|
||||||
|
"tokenEndpointAuthMethod": "client_secret_post"
|
||||||
},
|
},
|
||||||
"passwordLogin": {
|
"passwordLogin": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
"reverseGeocoding": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"externalDomain": "",
|
||||||
|
"loginPageMessage": "",
|
||||||
|
"publicUsers": true
|
||||||
|
},
|
||||||
"storageTemplate": {
|
"storageTemplate": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"hashVerificationEnabled": true,
|
"hashVerificationEnabled": true,
|
||||||
"template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}"
|
"template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}"
|
||||||
},
|
},
|
||||||
"image": {
|
"templates": {
|
||||||
"thumbnail": {
|
"email": {
|
||||||
"format": "webp",
|
"albumInviteTemplate": "",
|
||||||
"size": 250,
|
"albumUpdateTemplate": "",
|
||||||
"quality": 80
|
"welcomeTemplate": ""
|
||||||
},
|
}
|
||||||
"preview": {
|
|
||||||
"format": "jpeg",
|
|
||||||
"size": 1440,
|
|
||||||
"quality": 80
|
|
||||||
},
|
|
||||||
"colorspace": "p3",
|
|
||||||
"extractEmbedded": false
|
|
||||||
},
|
|
||||||
"newVersionCheck": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"trash": {
|
|
||||||
"enabled": true,
|
|
||||||
"days": 30
|
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"customCss": ""
|
"customCss": ""
|
||||||
},
|
},
|
||||||
"library": {
|
"trash": {
|
||||||
"scan": {
|
"days": 30,
|
||||||
"enabled": true,
|
"enabled": true
|
||||||
"cronExpression": "0 0 * * *"
|
|
||||||
},
|
|
||||||
"watch": {
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"externalDomain": "",
|
|
||||||
"loginPageMessage": ""
|
|
||||||
},
|
|
||||||
"notifications": {
|
|
||||||
"smtp": {
|
|
||||||
"enabled": false,
|
|
||||||
"from": "",
|
|
||||||
"replyTo": "",
|
|
||||||
"transport": {
|
|
||||||
"ignoreCert": false,
|
|
||||||
"host": "",
|
|
||||||
"port": 587,
|
|
||||||
"username": "",
|
|
||||||
"password": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"deleteDelay": 7
|
"deleteDelay": 7
|
||||||
|
|||||||
@@ -149,28 +149,31 @@ 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_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | HTTP Keep-alive time in seconds | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
|
||||||
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | 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_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 |
|
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
|
||||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning |
|
||||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
|
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning |
|
||||||
|
| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning |
|
||||||
|
| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | 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.
|
||||||
|
|
||||||
|
|||||||
32
docs/docs/install/one-click.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 65
|
||||||
|
---
|
||||||
|
|
||||||
|
# One-Click [Cloud Service]
|
||||||
|
|
||||||
|
:::note
|
||||||
|
This version of Immich is provided via cloud service providers' one-click marketplaces. Hosting costs are set by the cloud service providers.
|
||||||
|
Support for these are provided by the individual cloud service providers.
|
||||||
|
|
||||||
|
**Please report issues to the corresponding [Github Repository][github].**
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Go to the provider's marketplace and choose Immich, then follow the provided instructions.
|
||||||
|
|
||||||
|
## One-Click Immich marketplace providers
|
||||||
|
|
||||||
|
### DigitalOcean
|
||||||
|
|
||||||
|
https://marketplace.digitalocean.com/apps/immich
|
||||||
|
|
||||||
|
### Vultr
|
||||||
|
|
||||||
|
https://www.vultr.com/marketplace/apps/immich
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
For issues, open an issue on the associated [GitHub Repository][github].
|
||||||
|
|
||||||
|
[github]: https://github.com/immich-app/immich/
|
||||||
@@ -16,7 +16,7 @@ Immich can easily be installed on a Synology NAS using Container Manager within
|
|||||||
|
|
||||||
## Step 1 - Download the required files
|
## Step 1 - Download the required files
|
||||||
|
|
||||||
Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's a best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`.
|
Create a directory of your choice (e.g. `./immich-app`) to house Immich. In general, it's best practice to have all Docker-based applications running under the `./docker` directory, so in this case, your directory structure will look like `./docker/immich-app`.
|
||||||
|
|
||||||
Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`.
|
Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`.
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ When you're all done, you should have the following:
|
|||||||
- `./docker/immich-app/postgres`
|
- `./docker/immich-app/postgres`
|
||||||
- `./docker/immich-app/library`
|
- `./docker/immich-app/library`
|
||||||
|
|
||||||
Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`.
|
Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) to your computer. Upload the files to the `./docker/immich-app` directory, and rename `example.env` to `.env`. Note: If you plan to use the Synology Text editor to edit the `.env` file on the NAS within File Station, you will need to rename it to a temporary name (e.g. `example.txt`) in order to see 'Open with Text Editor' in the file context menu. Once saved, rename it back to `.env`.
|
||||||
|
|
||||||
## Step 2 - Populate the .env file with custom values
|
## Step 2 - Populate the .env file with custom values
|
||||||
|
|
||||||
@@ -34,23 +34,23 @@ Follow [Step 2 in Docker Compose](/install/docker-compose#step-2---populate-the-
|
|||||||
## Step 3 - Create a new project in Container Manager
|
## Step 3 - Create a new project in Container Manager
|
||||||
|
|
||||||
Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**".
|
Open Container Manager, and select the "**Project**" action on the left navigation bar and then click "**Create**".
|
||||||

|

|
||||||
|
|
||||||
In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue.
|
In the settings of your new project, set "**Project name**" to a name you'll remember, such as _immich-app_. When setting the "**Path**", select the `./docker/immich-app` directory you created earlier. Doing so will prompt a message to use the existing `docker-compose.yml` already present in the directory for your project. Click "**OK**" to continue.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The following screen will give you the option to further customize your `docker-compose.yml` file, giving you a warning regarding the `start_interval` property. Under the `healthcheck` heading, remove the `start_interval: 30s` completely and click "**Next**".
|
The following screen will give you the option to further customize your `docker-compose.yml` file. Take note of `DB_STORAGE_TYPE: 'HDD'` and uncomment if applicable for your Synology setup.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project.
|
Skip the section asking to set-up a portal for Web Station, and then complete the wizard which will build and start the containers for your project.
|
||||||
|
|
||||||
Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**".
|
Once your containers are successfully running, navigate to the "**Container**" section of Container Manager, right-click on the "**immich-server**" container, and choose the "**Details**".
|
||||||
|
|
||||||
Scroll to the bottom of the "**Details**" section, and find the `IP Address` of the container, located in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**.
|
Scroll to the bottom of the "**Details**" section and find the `IP Address` listed in the `Network` section. Take note of the container's IP address as you will need it for **Step 4**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Step 4 - Configure Firewall Settings
|
## Step 4 - Configure Firewall Settings
|
||||||
|
|
||||||
@@ -63,8 +63,66 @@ Open "**Control Panel**" on your Synology NAS, and select "**Security**". Naviga
|
|||||||
Click "**Edit Rules**" and add the following firewall rules:
|
Click "**Edit Rules**" and add the following firewall rules:
|
||||||
|
|
||||||
- Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above
|
- Add a "**Source IP**" rule for the IP address of your container that you obtained in Step 3 above
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
- Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283`
|
- Add a "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md).
|
Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instructions](/install/upgrading.md).
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Updating Immich using Container Manager</summary>
|
||||||
|
Check the post installation and upgrade instructions at the links above before proceeding with this section.
|
||||||
|
|
||||||
|
## Step 1. Backup
|
||||||
|
|
||||||
|
Ensure your photos and videos are backed up. Your `.env` settings will define where they are stored. There is no need to delete any files or folders within the `docker` folder when doing a release upgrade unless instructed in the release notes.
|
||||||
|
|
||||||
|
## Step 2. Check release notes
|
||||||
|
|
||||||
|
Always check the [release notes](https://github.com/immich-app/immich/releases) before proceeding with an update!
|
||||||
|
|
||||||
|
## Step 3. Stop containers & clean up
|
||||||
|
|
||||||
|
Open **Container Manager**. Select **Project** then your Immich app
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Select **Stop**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Select **Action** then **Clean**. This removes the containers.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Go to **Image** and select **Remove Unused Images**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Step 4. Build
|
||||||
|
|
||||||
|
Go to **Project**, select **Action** then **Build**. This will download, unpack, install and start the containers.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Step 5. Update firewall rule
|
||||||
|
|
||||||
|
The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address.
|
||||||
|
|
||||||
|
Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address.
|
||||||
|

|
||||||
|
|
||||||
|
Go to Synology **Control Panel**. Select **Security** and **Firewall**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In this example, the IP addresses mismatch and the firewall rule needs to be edited to match above.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
@@ -387,27 +387,35 @@ To migrate from the old storage configuration to the new one, you will need to c
|
|||||||
3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples:
|
3. **Copy the data** from the old datasets to the new dataset. We advise using the `rsync` command to copy the data, as it will preserve the permissions and ownership of the files. The following commands are examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/
|
sudo rsync -av /mnt/tank/immich/library/ /mnt/tank/immich/data/library/
|
||||||
rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/
|
sudo rsync -av /mnt/tank/immich/upload/ /mnt/tank/immich/data/upload/
|
||||||
rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/
|
sudo rsync -av /mnt/tank/immich/thumbs/ /mnt/tank/immich/data/thumbs/
|
||||||
rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/
|
sudo rsync -av /mnt/tank/immich/profile/ /mnt/tank/immich/data/profile/
|
||||||
rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/
|
sudo rsync -av /mnt/tank/immich/video/ /mnt/tank/immich/data/encoded-video/
|
||||||
rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/
|
sudo rsync -av /mnt/tank/immich/backups/ /mnt/tank/immich/data/backups/
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset.
|
Make sure to replace `/mnt/tank/immich/` with the correct path to your old datasets and `/mnt/tank/immich/data/` with the correct path to your new dataset.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
If you were using **ixVolume (dataset created automatically by the system)** for Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example:
|
If you were using **ixVolume (dataset created automatically by the system)** for some of Immich data storage, the path to the data should be `/mnt/.ix-apps/app_mounts/immich/`. You have to use this path instead of `/mnt/tank/immich/` in the `rsync` command above, for example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/
|
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/library/ /mnt/tank/immich/data/library/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you also were storing your files in the **ixVolume**, the **_upload_** folder is named `uploads` instead of `upload`, so the command to run should be:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/uploads/ /mnt/tank/immich/data/upload/
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that depending on your old storage configuration, you might have to use a mix of paths in the `rsync` commands above.
|
||||||
|
|
||||||
If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data:
|
If you were also using an ixVolume for Postgres data storage, you also should, first create the pgData dataset, as described in the [Setting up Storage Datasets](#setting-up-storage-datasets) section above, and then you can use the following command to copy the Postgres data:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
sudo rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -416,7 +424,7 @@ rsync -av /mnt/.ix-apps/app_mounts/immich/pgData/ /mnt/tank/immich/pgData/
|
|||||||
Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example:
|
Make sure that for each folder, the `.immich` file is copied as well, as it contains important metadata for Immich. If for some reason the `.immich` file is not copied, you can copy it manually with the `rsync` command, for example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/
|
sudo rsync -av /mnt/tank/immich/library/.immich /mnt/tank/immich/data/library/
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `library` with the name of the folder where you are copying the file.
|
Replace `library` with the name of the folder where you are copying the file.
|
||||||
@@ -437,38 +445,37 @@ This will recreate the Immich container with the new storage configuration and s
|
|||||||
|
|
||||||
If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface.
|
If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data has been copied correctly by checking the Immich web interface and ensuring that all your photos and videos are still available. You may delete the old datasets, if you no longer need them, using the TrueNAS web interface.
|
||||||
|
|
||||||
|
:::tip
|
||||||
If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands:
|
If you were using **ixVolume (dataset created automatically by the system)** or folders for Immich data storage, you can delete the old datasets using the following commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/library
|
sudo rm -r /mnt/.ix-apps/app_mounts/immich/*
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/uploads
|
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/thumbs
|
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/profile
|
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/video
|
|
||||||
rm -r /mnt/.ix-apps/app_mounts/immich/backups
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="migrate-old-dataset" label="Keep the existing datasets">
|
<TabItem value="migrate-old-dataset" label="Keep the existing datasets">
|
||||||
|
|
||||||
To migrate from the old storage configuration to the new one without creating new datasets.
|
To migrate from the old storage configuration to the new one without creating new datasets.
|
||||||
|
|
||||||
1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app.
|
1. **Stop the Immich app** from the TrueNAS web interface to ensure no data is being written while you are updating the app.
|
||||||
2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have ***modify*** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with ***modify*** permissions.
|
2. **Update the datasets permissions**: Ensure that the datasets used for Immich data storage (`library`, `upload`, `thumbs`, `profile`, `video`, `backups`) have the correct permissions set for the user who will run Immich. The user should have **_modify_** permissions on these datasets. The default user for Immich is `apps` (UID 568) and the default group is `apps` (GID 568). If you are using a different user, make sure to set the permissions accordingly. You can do this from the TrueNAS web interface by going to the **Datasets** screen, selecting each dataset, clicking on the **Edit** button next to **Permissions**, and adding the user with **_modify_** permissions.
|
||||||
3. **Update the Immich app** to use the existing datasets:
|
3. **Update the Immich app** to use the existing datasets:
|
||||||
- Go to the **Installed Applications** screen and select Immich from the list of installed applications.
|
- Go to the **Installed Applications** screen and select Immich from the list of installed applications.
|
||||||
- Click **Edit** on the **Application Info** widget.
|
- Click **Edit** on the **Application Info** widget.
|
||||||
- In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox.
|
- In the **Storage Configuration** section, untick the **Use Old Storage Configuration (Deprecated)** checkbox.
|
||||||
- For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`.
|
- For the **Data Storage**, you can keep the **ixVolume (dataset created automatically by the system)** as no data will be directly written to it. We recommend selecting **Host Path (Path that already exists on the system)** and then select a **new** dataset you created for Immich data storage, for example, `data`.
|
||||||
- For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`.
|
- For the **Postgres Data Storage**, keep **Host Path (Path that already exists on the system)** and then select the existing dataset you used for Postgres data storage, for example, `pgData`.
|
||||||
- Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings:
|
- Following the instructions in the [Multiple Datasets for Immich Storage](#additional-storage-advanced-users) section, you can add, **for each old dataset**, a new Additional Storage with the following settings:
|
||||||
- **Type**: `Host Path (Path that already exists on the system)`
|
- **Type**: `Host Path (Path that already exists on the system)`
|
||||||
- **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`)
|
- **Mount Path**: `/data/<folder-name>` (e.g. `/data/library`)
|
||||||
- **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`)
|
- **Host Path**: `/mnt/<your-pool-name>/<dataset-name>` (e.g. `/mnt/tank/immich/library`)
|
||||||
:::danger Ensure using the correct paths names
|
:::danger Ensure using the correct paths names
|
||||||
Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset.
|
Make sure to replace `<folder-name>` with the actual name of the folder used by Immich: `library`, `upload`, `thumbs`, `profile`, `encoded-video`, and `backups`. Also, replace `<your-pool-name>` and `<dataset-name>` with the actual names of your pool and dataset.
|
||||||
:::
|
:::
|
||||||
- **Read Only**: Keep it unticked as Immich needs to write to these datasets.
|
- **Read Only**: Keep it unticked as Immich needs to write to these datasets.
|
||||||
- Click **Update** at the bottom of the page to save changes.
|
- Click **Update** at the bottom of the page to save changes.
|
||||||
4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible.
|
4. **Start the Immich app** from the TrueNAS web interface. This will recreate the Immich container with the new storage configuration and start the app. If everything went well, you should now be able to access Immich with the new storage configuration. You can verify that the data is still available by checking the Immich web interface and ensuring that all your photos and videos are still accessible.
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ sidebar_position: 95
|
|||||||
|
|
||||||
# Upgrading
|
# Upgrading
|
||||||
|
|
||||||
:::danger Read the release notes
|
:::tip Breaking changes
|
||||||
Immich is currently under heavy development, which means you can expect [breaking changes][breaking] and bugs. You should read the release notes prior to updating and take special care when using automated tools like [Watchtower][watchtower].
|
|
||||||
|
|
||||||
You can see versions that had breaking changes [here][breaking].
|
You can see versions that had breaking changes [here][breaking].
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -89,7 +87,7 @@ After making a backup, please modify your `docker-compose.yml` file with the fol
|
|||||||
If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff.
|
If you deviated from the defaults of pg14 or pgvectors0.2.0, you must adjust the pg major version and pgvecto.rs version. If you are still using the default `docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0` image, you can just follow the changes above. For example, if the previous image is `docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0`, the new image should be `ghcr.io/immich-app/postgres:16-vectorchord0.3.0-pgvectors0.3.0` instead of the image specified in the diff.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index`for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time.
|
After making these changes, you can start Immich as normal. Immich will make some changes to the DB during startup, which can take seconds to minutes to finish, depending on hardware and library size. In particular, it’s normal for the server logs to be seemingly stuck at `Reindexing clip_index` and `Reindexing face_index` for some time if you have over 100k assets in Immich and/or Immich is on a relatively weak server. If you see these logs and there are no errors, just give it time.
|
||||||
|
|
||||||
:::danger
|
:::danger
|
||||||
After switching to VectorChord, you should not downgrade Immich below 1.133.0.
|
After switching to VectorChord, you should not downgrade Immich below 1.133.0.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
The mobile app can be downloaded from the following places:
|
The mobile app can be downloaded from the following places:
|
||||||
|
|
||||||
|
- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities).
|
||||||
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
||||||
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
|
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
|
||||||
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
|
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
Now that you have imported some pictures, you should setup server backups to preserve your memories.
|
Now that you have imported some pictures, you should setup server backups to preserve your memories.
|
||||||
You can do so by following our [backup guide](/administration/backup-and-restore.md).
|
You can do so by following our [backup guide](/administration/backup-and-restore.md).
|
||||||
|
|
||||||
:::danger
|
:::info
|
||||||
Immich is still under heavy development _and_ handles very important data.
|
A 3-2-1 backup strategy is still crucial. The team has the responsibility to ensure that the application doesn’t cause loss of your precious memories; however, we cannot guarantee that hard drives will not fail, or an electrical event causes unexpected shutdown of your server/system, leading to data loss. Therefore, we still encourage users to follow best practices when safeguarding their data. Keep multiple copies of your most precious data: at least two local copies and one copy offsite in cold storage.
|
||||||
It is essential that you set up good backups, and test them.
|
|
||||||
:::
|
:::
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const prism = require('prism-react-renderer');
|
|||||||
const config = {
|
const config = {
|
||||||
title: 'Immich',
|
title: 'Immich',
|
||||||
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
||||||
url: 'https://immich.app',
|
url: 'https://docs.immich.app',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
onBrokenMarkdownLinks: 'warn',
|
||||||
@@ -65,11 +65,6 @@ const config = {
|
|||||||
themeConfig:
|
themeConfig:
|
||||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||||
({
|
({
|
||||||
announcementBar: {
|
|
||||||
id: 'site_announcement_immich',
|
|
||||||
content: `⚠️ The project is under <strong>very active</strong> development. Expect bugs and changes. Do not use it as <strong>the only way</strong> to store your photos and videos!`,
|
|
||||||
isCloseable: false,
|
|
||||||
},
|
|
||||||
docs: {
|
docs: {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
autoCollapseCategories: false,
|
autoCollapseCategories: false,
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
"write-heading-ids": "docusaurus write-heading-ids"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "~3.8.0",
|
"@docusaurus/core": "~3.9.0",
|
||||||
"@docusaurus/preset-classic": "~3.8.0",
|
"@docusaurus/preset-classic": "~3.9.0",
|
||||||
"@docusaurus/theme-common": "~3.8.0",
|
"@docusaurus/theme-common": "~3.9.0",
|
||||||
"@mdi/js": "^7.3.67",
|
"@mdi/js": "^7.3.67",
|
||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "~3.8.0",
|
"@docusaurus/module-type-aliases": "~3.9.0",
|
||||||
"@docusaurus/tsconfig": "^3.7.0",
|
"@docusaurus/tsconfig": "^3.7.0",
|
||||||
"@docusaurus/types": "^3.7.0",
|
"@docusaurus/types": "^3.7.0",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
@@ -57,6 +57,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.19.0"
|
"node": "24.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import Link from '@docusaurus/Link';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface CommunityGuidesProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const guides: CommunityGuidesProps[] = [
|
|
||||||
{
|
|
||||||
title: 'Cloudflare Tunnels with SSO/OAuth',
|
|
||||||
description: `Setting up Cloudflare Tunnels and a SaaS App for Immich.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8299',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Database backup in TrueNAS',
|
|
||||||
description: `Create a database backup with pgAdmin in TrueNAS.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8809',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Unraid backup scripts',
|
|
||||||
description: `Back up your assets in Unraid with a pre-prepared script.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/8416',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Sync folders with albums',
|
|
||||||
description: `synchronize folders in imported library with albums having the folders name.`,
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/3382',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Podman Quadlets Handbook',
|
|
||||||
description:
|
|
||||||
'A rewrite of the original Immich Docker Compose file using Podman Quadlets, with a set of extra guides in the repository’s wiki.',
|
|
||||||
url: 'https://github.com/linux-universe/immich-podman-quadlets/blob/main/README.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Podman/Quadlets Install',
|
|
||||||
description: 'Documentation for simple podman setup using quadlets.',
|
|
||||||
url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Google Photos import + albums',
|
|
||||||
description: 'Import your Google Photos files into Immich and add your albums.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/1340',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Access Immich with custom domain',
|
|
||||||
description: 'Access your local Immich installation over the internet using your own domain.',
|
|
||||||
url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Nginx caching map server',
|
|
||||||
description: 'Increase privacy by using nginx as a caching proxy in front of a map tile server.',
|
|
||||||
url: 'https://github.com/pcouy/pcouy.github.io/blob/main/_posts/2024-08-30-proxying-a-map-tile-server-for-increased-privacy.md',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'fail2ban setup instructions',
|
|
||||||
description: 'How to configure an existing fail2ban installation to block incorrect login attempts.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/3243#discussioncomment-6681948',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich remote access with NordVPN Meshnet',
|
|
||||||
description: 'Access Immich with an end-to-end encrypted connection.',
|
|
||||||
url: 'https://meshnet.nordvpn.com/how-to/remote-files-media-access/immich-remote-access',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Trust Self Signed Certificates with Immich - OAuth Setup',
|
|
||||||
description:
|
|
||||||
'Set up Certificate Authority trust with Immich, and your private OAuth2/OpenID service, while using a private CA for HTTPS commication.',
|
|
||||||
url: 'https://github.com/immich-app/immich/discussions/18614',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section className="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl px-4 py-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="m-0 items-start flex gap-2 text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">
|
|
||||||
<span>{title}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{description}</p>
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300 my-4">
|
|
||||||
<a href={url}>{url}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Link
|
|
||||||
className="px-4 py-2 bg-immich-primary/10 dark:bg-gray-300 rounded-xl text-sm hover:no-underline text-immich-primary dark:text-immich-dark-bg font-semibold"
|
|
||||||
to={url}
|
|
||||||
>
|
|
||||||
View Guide
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CommunityGuides(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
||||||
{guides.map((guides) => (
|
|
||||||
<CommunityGuide {...guides} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import Link from '@docusaurus/Link';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface CommunityProjectProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects: CommunityProjectProps[] = [
|
|
||||||
{
|
|
||||||
title: 'immich-go',
|
|
||||||
description: `An alternative to the immich-CLI that doesn't depend on nodejs. It specializes in importing Google Photos Takeout archives.`,
|
|
||||||
url: 'https://github.com/simulot/immich-go',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'ImmichFrame',
|
|
||||||
description: 'Run an Immich slideshow in a photo frame.',
|
|
||||||
url: 'https://github.com/3rob3/ImmichFrame',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'API Album Sync',
|
|
||||||
description: 'A Python script to sync folders as albums.',
|
|
||||||
url: 'https://git.orenit.solutions/open/immichalbumpull',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Remove offline files',
|
|
||||||
description: 'A simple way to remove orphaned offline assets from the Immich database',
|
|
||||||
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich-Tools',
|
|
||||||
description: 'Provides scripts for handling problems on the repair page.',
|
|
||||||
url: 'https://github.com/clumsyCoder00/Immich-Tools',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lightroom Publisher: mi.Immich.Publisher',
|
|
||||||
description: 'Lightroom plugin to publish photos from Lightroom collections to Immich albums.',
|
|
||||||
url: 'https://github.com/midzelis/mi.Immich.Publisher',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Lightroom Immich Plugin: lrc-immich-plugin',
|
|
||||||
description:
|
|
||||||
'Lightroom plugin to publish, export photos from Lightroom to Immich. Import from Immich to Lightroom is also supported.',
|
|
||||||
url: 'https://blog.fokuspunk.de/lrc-immich-plugin/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich-Tiktok-Remover',
|
|
||||||
description: 'Script to search for and remove TikTok videos from your Immich library.',
|
|
||||||
url: 'https://github.com/mxc2/immich-tiktok-remover',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Android TV',
|
|
||||||
description: 'Unofficial Immich Android TV app.',
|
|
||||||
url: 'https://github.com/giejay/Immich-Android-TV',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Create albums from folders',
|
|
||||||
description: 'A Python script to create albums based on the folder structure of an external library.',
|
|
||||||
url: 'https://github.com/Salvoxia/immich-folder-album-creator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Powershell Module PSImmich',
|
|
||||||
description: 'Powershell Module for the Immich API',
|
|
||||||
url: 'https://github.com/hanpq/PSImmich',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Distribution',
|
|
||||||
description: 'Snap package for easy install and zero-care auto updates of Immich. Self-hosted photo management.',
|
|
||||||
url: 'https://immich-distribution.nsg.cc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Kiosk',
|
|
||||||
description: 'Lightweight slideshow to run on kiosk devices and browsers.',
|
|
||||||
url: 'https://github.com/damongolding/immich-kiosk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Power Tools',
|
|
||||||
description: 'Power tools for organizing your immich library.',
|
|
||||||
url: 'https://github.com/varun-raj/immich-power-tools',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Public Proxy',
|
|
||||||
description:
|
|
||||||
'Share your Immich photos and albums in a safe way without exposing your Immich instance to the public.',
|
|
||||||
url: 'https://github.com/alangrainger/immich-public-proxy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Kodi',
|
|
||||||
description: 'Unofficial Kodi plugin for Immich.',
|
|
||||||
url: 'https://github.com/vladd11/immich-kodi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Downloader',
|
|
||||||
description: 'Downloads a configurable number of random photos based on people or album ID.',
|
|
||||||
url: 'https://github.com/jon6fingrs/immich-dl',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Upload Optimizer',
|
|
||||||
description: 'Automatically optimize files uploaded to Immich in order to save storage space',
|
|
||||||
url: 'https://github.com/miguelangel-nubla/immich-upload-optimizer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Machine Learning Load Balancer',
|
|
||||||
description: 'Speed up your machine learning by load balancing your requests to multiple computers',
|
|
||||||
url: 'https://github.com/apetersson/immich_ml_balancer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Drop Uploader',
|
|
||||||
description: 'A tiny, zero-login web app for collecting photos/videos from anyone into your Immich server.',
|
|
||||||
url: 'https://github.com/Nasogaa/immich-drop',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Birthday Sync',
|
|
||||||
description: 'Bulk-upload and -download birthdays, with CardDAV sync support',
|
|
||||||
url: 'https://github.com/sid3windr/immich-birthday',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Immich Stack',
|
|
||||||
description: 'Auto-stack photos with identical filenames and differing extensions (i.e. JPG+RAW)',
|
|
||||||
url: 'https://github.com/sid3windr/immich-stack',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function CommunityProject({ title, description, url }: CommunityProjectProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<section className="flex flex-col gap-4 justify-between dark:bg-immich-dark-gray bg-immich-gray dark:border-0 border-gray-200 border border-solid rounded-2xl px-4 py-6">
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<p className="m-0 items-start flex gap-2 text-2xl font-bold text-immich-primary dark:text-immich-dark-primary">
|
|
||||||
<span>{title}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300">{description}</p>
|
|
||||||
<p className="m-0 text-sm text-gray-600 dark:text-gray-300 my-4">
|
|
||||||
<a href={url}>{url}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<Link
|
|
||||||
className="px-4 py-2 bg-immich-primary/10 dark:bg-gray-300 rounded-xl text-sm hover:no-underline text-immich-primary dark:text-immich-dark-bg font-semibold"
|
|
||||||
to={url}
|
|
||||||
>
|
|
||||||
View Link
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CommunityProjects(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
||||||
{projects.map((project) => (
|
|
||||||
<CommunityProject {...project} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export const discordPath =
|
|
||||||
'M81.15,0c-1.2376,2.1973-2.3489,4.4704-3.3591,6.794-9.5975-1.4396-19.3718-1.4396-28.9945,0-.985-2.3236-2.1216-4.5967-3.3591-6.794-9.0166,1.5407-17.8059,4.2431-26.1405,8.0568C2.779,32.5304-1.6914,56.3725.5312,79.8863c9.6732,7.1476,20.5083,12.603,32.0505,16.0884,2.6014-3.4854,4.8998-7.1981,6.8698-11.0623-3.738-1.3891-7.3497-3.1318-10.8098-5.1523.9092-.6567,1.7932-1.3386,2.6519-1.9953,20.281,9.547,43.7696,9.547,64.0758,0,.8587.7072,1.7427,1.3891,2.6519,1.9953-3.4601,2.0457-7.0718,3.7632-10.835,5.1776,1.97,3.8642,4.2683,7.5769,6.8698,11.0623,11.5419-3.4854,22.3769-8.9156,32.0509-16.0631,2.626-27.2771-4.496-50.9172-18.817-71.8548C98.9811,4.2684,90.1918,1.5659,81.1752.0505l-.0252-.0505ZM42.2802,65.4144c-6.2383,0-11.4159-5.6575-11.4159-12.6535s4.9755-12.6788,11.3907-12.6788,11.5169,5.708,11.4159,12.6788c-.101,6.9708-5.026,12.6535-11.3907,12.6535ZM84.3576,65.4144c-6.2637,0-11.3907-5.6575-11.3907-12.6535s4.9755-12.6788,11.3907-12.6788,11.4917,5.708,11.3906,12.6788c-.101,6.9708-5.026,12.6535-11.3906,12.6535Z';
|
|
||||||
export const discordViewBox = '0 0 126.644 96';
|
|
||||||
6
docs/static/_redirects
vendored
@@ -27,8 +27,10 @@
|
|||||||
/administration/password-login /administration/system-settings 307
|
/administration/password-login /administration/system-settings 307
|
||||||
/features/search /features/searching 307
|
/features/search /features/searching 307
|
||||||
/features/smart-search /features/searching 307
|
/features/smart-search /features/searching 307
|
||||||
/guides/api-album-sync /community-projects 307
|
/guides/api-album-sync https://awesome.immich.app/ 307
|
||||||
/guides/remove-offline-files /community-projects 307
|
/guides/remove-offline-files https://awesome.immich.app/ 307
|
||||||
|
/community-guides https://awesome.immich.app/ 307
|
||||||
|
/community-projects https://awesome.immich.app/ 307
|
||||||
/overview/introduction /overview/quick-start 307
|
/overview/introduction /overview/quick-start 307
|
||||||
/overview/welcome /overview/quick-start 307
|
/overview/welcome /overview/quick-start 307
|
||||||
/docs/* /:splat 307
|
/docs/* /:splat 307
|
||||||
|
|||||||
36
docs/static/archived-versions.json
vendored
@@ -1,4 +1,40 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v2.2.3",
|
||||||
|
"url": "https://docs.v2.2.3.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.2.2",
|
||||||
|
"url": "https://docs.v2.2.2.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.2.1",
|
||||||
|
"url": "https://docs.v2.2.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.2.0",
|
||||||
|
"url": "https://docs.v2.2.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.1.0",
|
||||||
|
"url": "https://docs.v2.1.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.0.1",
|
||||||
|
"url": "https://docs.v2.0.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v2.0.0",
|
||||||
|
"url": "https://docs.v2.0.0.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.144.1",
|
||||||
|
"url": "https://docs.v1.144.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.144.0",
|
||||||
|
"url": "https://docs.v1.144.0.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.143.1",
|
"label": "v1.143.1",
|
||||||
"url": "https://docs.v1.143.1.archive.immich.app"
|
"url": "https://docs.v1.143.1.archive.immich.app"
|
||||||
|
|||||||
BIN
docs/static/img/synology-action-clean.png
vendored
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/static/img/synology-build.png
vendored
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/static/img/synology-container-ip.png
vendored
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 153 KiB |
BIN
docs/static/img/synology-custom-port-firewall-rule.png
vendored
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
docs/static/img/synology-fw-ipedit.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/synology-fw-rules.png
vendored
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/static/img/synology-ipaddress-firewall-rule.png
vendored
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/static/img/synology-project-stop.png
vendored
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/static/img/synology-remove-unused.png
vendored
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/static/img/synology-select-proj.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -1 +1 @@
|
|||||||
22.19.0
|
24.11.0
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ services:
|
|||||||
- 2285:2285
|
- 2285:2285
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:7fe72c486b910f6b1a9769c937dad5d63648ddee82e056f47417542dd40825bb
|
image: redis:6.2-alpine@sha256:37e002448575b32a599109664107e374c8709546905c372a34d64919043b9ceb
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:11ced39d65a92a54d12890ced6a26cc2003f92697d6f0d4d944b98459dba7138
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338
|
||||||
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.143.1",
|
"version": "2.2.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@socket.io/component-emitter": "^3.1.2",
|
"@socket.io/component-emitter": "^3.1.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^22.18.1",
|
"@types/node": "^22.18.13",
|
||||||
"@types/oidc-provider": "^9.0.0",
|
"@types/oidc-provider": "^9.0.0",
|
||||||
"@types/pg": "^8.15.1",
|
"@types/pg": "^8.15.1",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^60.0.0",
|
"eslint-plugin-unicorn": "^60.0.0",
|
||||||
"exiftool-vendored": "^28.3.1",
|
"exiftool-vendored": "^31.1.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"jose": "^5.6.3",
|
"jose": "^5.6.3",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
"sharp": "^0.34.3",
|
"sharp": "^0.34.4",
|
||||||
"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",
|
||||||
@@ -52,6 +52,6 @@
|
|||||||
"vitest": "^3.0.0"
|
"vitest": "^3.0.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.19.0"
|
"node": "24.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ isFavorite: false })],
|
assets: [expect.objectContaining({ isFavorite: false })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -310,6 +311,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -345,6 +347,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
startDate: expect.any(String),
|
startDate: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -362,6 +365,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user1Albums[0],
|
...user1Albums[0],
|
||||||
assets: [],
|
assets: [],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
@@ -382,6 +386,7 @@ describe('/albums', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
...user2Albums[0],
|
...user2Albums[0],
|
||||||
assets: [],
|
assets: [],
|
||||||
|
contributorCounts: [{ userId: user1.userId, assetCount: 1 }],
|
||||||
assetCount: 1,
|
assetCount: 1,
|
||||||
lastModifiedAssetTimestamp: expect.any(String),
|
lastModifiedAssetTimestamp: expect.any(String),
|
||||||
endDate: expect.any(String),
|
endDate: expect.any(String),
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { DateTime } from 'luxon';
|
|||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { readFile, writeFile } from 'node:fs/promises';
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
import { basename, join } from 'node:path';
|
import { basename, join } from 'node:path';
|
||||||
import sharp from 'sharp';
|
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { createUserDto, uuidDto } from 'src/fixtures';
|
import { createUserDto, uuidDto } from 'src/fixtures';
|
||||||
import { makeRandomImage } from 'src/generators';
|
import { makeRandomImage } from 'src/generators';
|
||||||
@@ -41,40 +40,6 @@ const today = DateTime.fromObject({
|
|||||||
}) as DateTime<true>;
|
}) as DateTime<true>;
|
||||||
const yesterday = today.minus({ days: 1 });
|
const yesterday = today.minus({ days: 1 });
|
||||||
|
|
||||||
const createTestImageWithExif = async (filename: string, exifData: Record<string, any>) => {
|
|
||||||
// Generate unique color to ensure different checksums for each image
|
|
||||||
const r = Math.floor(Math.random() * 256);
|
|
||||||
const g = Math.floor(Math.random() * 256);
|
|
||||||
const b = Math.floor(Math.random() * 256);
|
|
||||||
|
|
||||||
// Create a 100x100 solid color JPEG using Sharp
|
|
||||||
const imageBytes = await sharp({
|
|
||||||
create: {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
channels: 3,
|
|
||||||
background: { r, g, b },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.jpeg({ quality: 90 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
// Add random suffix to filename to avoid collisions
|
|
||||||
const uniqueFilename = filename.replace('.jpg', `-${randomBytes(4).toString('hex')}.jpg`);
|
|
||||||
const filepath = join(tempDir, uniqueFilename);
|
|
||||||
await writeFile(filepath, imageBytes);
|
|
||||||
|
|
||||||
// Filter out undefined values before writing EXIF
|
|
||||||
const cleanExifData = Object.fromEntries(Object.entries(exifData).filter(([, value]) => value !== undefined));
|
|
||||||
|
|
||||||
await exiftool.write(filepath, cleanExifData);
|
|
||||||
|
|
||||||
// Re-read the image bytes after EXIF has been written
|
|
||||||
const finalImageBytes = await readFile(filepath);
|
|
||||||
|
|
||||||
return { filepath, imageBytes: finalImageBytes, filename: uniqueFilename };
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('/asset', () => {
|
describe('/asset', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
let websocket: Socket;
|
let websocket: Socket;
|
||||||
@@ -1249,411 +1214,6 @@ describe('/asset', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('EXIF metadata extraction', () => {
|
|
||||||
describe('Additional date tag extraction', () => {
|
|
||||||
describe('Date-time vs time-only tag handling', () => {
|
|
||||||
it('should fall back to file timestamps when only time-only tags are available', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('time-only-fallback.jpg', {
|
|
||||||
TimeCreated: '2023:11:15 14:30:00', // Time-only tag, should not be used for dateTimeOriginal
|
|
||||||
// Exclude all date-time tags to force fallback to file timestamps
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
SubSecMediaCreateDate: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
MediaCreateDate: undefined,
|
|
||||||
CreationDate: undefined,
|
|
||||||
DateTimeCreated: undefined,
|
|
||||||
GPSDateTime: undefined,
|
|
||||||
DateTimeUTC: undefined,
|
|
||||||
SonyDateTime2: undefined,
|
|
||||||
GPSDateStamp: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const oldDate = new Date('2020-01-01T00:00:00.000Z');
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
fileCreatedAt: oldDate.toISOString(),
|
|
||||||
fileModifiedAt: oldDate.toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should fall back to file timestamps, which we set to 2020-01-01
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2020-01-01T00:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should prefer DateTimeOriginal over time-only tags', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('datetime-over-time.jpg', {
|
|
||||||
DateTimeOriginal: '2023:10:10 10:00:00', // Should be preferred
|
|
||||||
TimeCreated: '2023:11:15 14:30:00', // Should be ignored (time-only)
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should use DateTimeOriginal, not TimeCreated
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-10-10T10:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GPSDateTime tag extraction', () => {
|
|
||||||
it('should extract GPSDateTime with GPS coordinates', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('gps-datetime.jpg', {
|
|
||||||
GPSDateTime: '2023:11:15 12:30:00Z',
|
|
||||||
GPSLatitude: 37.7749,
|
|
||||||
GPSLongitude: -122.4194,
|
|
||||||
// Exclude other date tags
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
SubSecMediaCreateDate: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
MediaCreateDate: undefined,
|
|
||||||
CreationDate: undefined,
|
|
||||||
DateTimeCreated: undefined,
|
|
||||||
TimeCreated: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
expect(assetInfo.exifInfo?.latitude).toBeCloseTo(37.7749, 4);
|
|
||||||
expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-122.4194, 4);
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-11-15T12:30:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('CreateDate tag extraction', () => {
|
|
||||||
it('should extract CreateDate when available', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('create-date.jpg', {
|
|
||||||
CreateDate: '2023:11:15 10:30:00',
|
|
||||||
// Exclude other higher priority date tags
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
SubSecMediaCreateDate: undefined,
|
|
||||||
MediaCreateDate: undefined,
|
|
||||||
CreationDate: undefined,
|
|
||||||
DateTimeCreated: undefined,
|
|
||||||
TimeCreated: undefined,
|
|
||||||
GPSDateTime: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-11-15T10:30:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GPSDateStamp tag extraction', () => {
|
|
||||||
it('should fall back to file timestamps when only date-only tags are available', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp.jpg', {
|
|
||||||
GPSDateStamp: '2023:11:15', // Date-only tag, should not be used for dateTimeOriginal
|
|
||||||
// Note: NOT including GPSTimeStamp to avoid automatic GPSDateTime creation
|
|
||||||
GPSLatitude: 51.5074,
|
|
||||||
GPSLongitude: -0.1278,
|
|
||||||
// Explicitly exclude all testable date-time tags to force fallback to file timestamps
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
CreationDate: undefined,
|
|
||||||
GPSDateTime: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const oldDate = new Date('2020-01-01T00:00:00.000Z');
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
fileCreatedAt: oldDate.toISOString(),
|
|
||||||
fileModifiedAt: oldDate.toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
expect(assetInfo.exifInfo?.latitude).toBeCloseTo(51.5074, 4);
|
|
||||||
expect(assetInfo.exifInfo?.longitude).toBeCloseTo(-0.1278, 4);
|
|
||||||
// Should fall back to file timestamps, which we set to 2020-01-01
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2020-01-01T00:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* NOTE: The following EXIF date tags are NOT effectively usable with JPEG test files:
|
|
||||||
*
|
|
||||||
* NOT WRITABLE to JPEG:
|
|
||||||
* - MediaCreateDate: Can be read from video files but not written to JPEG
|
|
||||||
* - DateTimeCreated: Read-only tag in JPEG format
|
|
||||||
* - DateTimeUTC: Cannot be written to JPEG files
|
|
||||||
* - SonyDateTime2: Proprietary Sony tag, not writable to JPEG
|
|
||||||
* - SubSecMediaCreateDate: Tag not defined for JPEG format
|
|
||||||
* - SourceImageCreateTime: Non-standard insta360 tag, not writable to JPEG
|
|
||||||
*
|
|
||||||
* WRITABLE but NOT READABLE from JPEG:
|
|
||||||
* - SubSecDateTimeOriginal: Can be written but not read back from JPEG
|
|
||||||
* - SubSecCreateDate: Can be written but not read back from JPEG
|
|
||||||
*
|
|
||||||
* EFFECTIVELY TESTABLE TAGS (writable and readable):
|
|
||||||
* - DateTimeOriginal ✓
|
|
||||||
* - CreateDate ✓
|
|
||||||
* - CreationDate ✓
|
|
||||||
* - GPSDateTime ✓
|
|
||||||
*
|
|
||||||
* The metadata service correctly handles non-readable tags and will fall back to
|
|
||||||
* file timestamps when only non-readable tags are present.
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('Date tag priority order', () => {
|
|
||||||
it('should respect the complete date tag priority order', async () => {
|
|
||||||
// Test cases using only EFFECTIVELY TESTABLE tags (writable AND readable from JPEG)
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
name: 'DateTimeOriginal has highest priority among testable tags',
|
|
||||||
exifData: {
|
|
||||||
DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags
|
|
||||||
CreateDate: '2023:05:05 05:00:00', // TESTABLE
|
|
||||||
CreationDate: '2023:07:07 07:00:00', // TESTABLE
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
|
||||||
},
|
|
||||||
expectedDate: '2023-04-04T04:00:00.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CreationDate when DateTimeOriginal missing',
|
|
||||||
exifData: {
|
|
||||||
CreationDate: '2023:05:05 05:00:00', // TESTABLE
|
|
||||||
CreateDate: '2023:07:07 07:00:00', // TESTABLE
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
|
||||||
},
|
|
||||||
expectedDate: '2023-05-05T05:00:00.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CreationDate when standard EXIF tags missing',
|
|
||||||
exifData: {
|
|
||||||
CreationDate: '2023:07:07 07:00:00', // TESTABLE
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
|
||||||
},
|
|
||||||
expectedDate: '2023-07-07T07:00:00.000Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'GPSDateTime when no other testable date tags present',
|
|
||||||
exifData: {
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
|
||||||
Make: 'SONY',
|
|
||||||
},
|
|
||||||
expectedDate: '2023-10-10T10:00:00.000Z',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const testCase of testCases) {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif(
|
|
||||||
`${testCase.name.replaceAll(/\s+/g, '-').toLowerCase()}.jpg`,
|
|
||||||
testCase.exifData,
|
|
||||||
);
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal, `Failed for: ${testCase.name}`).toBeDefined();
|
|
||||||
expect(
|
|
||||||
new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime(),
|
|
||||||
`Date mismatch for: ${testCase.name}`,
|
|
||||||
).toBe(new Date(testCase.expectedDate).getTime());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Edge cases for date tag handling', () => {
|
|
||||||
it('should fall back to file timestamps with GPSDateStamp alone', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('gps-datestamp-only.jpg', {
|
|
||||||
GPSDateStamp: '2023:08:08', // Date-only tag, should not be used for dateTimeOriginal
|
|
||||||
// Intentionally no GPSTimeStamp
|
|
||||||
// Exclude all other date tags
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
SubSecMediaCreateDate: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
MediaCreateDate: undefined,
|
|
||||||
CreationDate: undefined,
|
|
||||||
DateTimeCreated: undefined,
|
|
||||||
TimeCreated: undefined,
|
|
||||||
GPSDateTime: undefined,
|
|
||||||
DateTimeUTC: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const oldDate = new Date('2020-01-01T00:00:00.000Z');
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
fileCreatedAt: oldDate.toISOString(),
|
|
||||||
fileModifiedAt: oldDate.toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should fall back to file timestamps, which we set to 2020-01-01
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2020-01-01T00:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle all testable date tags present to verify complete priority order', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('all-testable-date-tags.jpg', {
|
|
||||||
// All TESTABLE date tags to JPEG format (writable AND readable)
|
|
||||||
DateTimeOriginal: '2023:04:04 04:00:00', // TESTABLE - highest priority among readable tags
|
|
||||||
CreateDate: '2023:05:05 05:00:00', // TESTABLE
|
|
||||||
CreationDate: '2023:07:07 07:00:00', // TESTABLE
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // TESTABLE
|
|
||||||
// Note: Excluded non-testable tags:
|
|
||||||
// SubSec tags: writable but not readable from JPEG
|
|
||||||
// Non-writable tags: MediaCreateDate, DateTimeCreated, DateTimeUTC, SonyDateTime2, etc.
|
|
||||||
// Time-only/date-only tags: already excluded from EXIF_DATE_TAGS
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should use DateTimeOriginal as it has the highest priority among testable tags
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-04-04T04:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use CreationDate when SubSec tags are missing', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('creation-date-priority.jpg', {
|
|
||||||
CreationDate: '2023:07:07 07:00:00', // WRITABLE
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // WRITABLE
|
|
||||||
// Note: DateTimeCreated, DateTimeUTC, SonyDateTime2 are NOT writable to JPEG
|
|
||||||
// Note: TimeCreated and GPSDateStamp are excluded from EXIF_DATE_TAGS (time-only/date-only)
|
|
||||||
// Exclude SubSec and standard EXIF tags
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should use CreationDate when available
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-07-07T07:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should skip invalid date formats and use next valid tag', async () => {
|
|
||||||
const { imageBytes, filename } = await createTestImageWithExif('invalid-date-handling.jpg', {
|
|
||||||
// Note: Testing invalid date handling with only WRITABLE tags
|
|
||||||
GPSDateTime: '2023:10:10 10:00:00', // WRITABLE - Valid date
|
|
||||||
CreationDate: '2023:13:13 13:00:00', // WRITABLE - Valid date
|
|
||||||
// Note: TimeCreated excluded (time-only), DateTimeCreated not writable to JPEG
|
|
||||||
// Exclude other date tags
|
|
||||||
SubSecDateTimeOriginal: undefined,
|
|
||||||
DateTimeOriginal: undefined,
|
|
||||||
SubSecCreateDate: undefined,
|
|
||||||
CreateDate: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const asset = await utils.createAsset(admin.accessToken, {
|
|
||||||
assetData: {
|
|
||||||
filename,
|
|
||||||
bytes: imageBytes,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id });
|
|
||||||
|
|
||||||
const assetInfo = await getAssetInfo({ id: asset.id }, { headers: asBearerAuth(admin.accessToken) });
|
|
||||||
|
|
||||||
expect(assetInfo.exifInfo?.dateTimeOriginal).toBeDefined();
|
|
||||||
// Should skip invalid dates and use the first valid one (GPSDateTime)
|
|
||||||
expect(new Date(assetInfo.exifInfo!.dateTimeOriginal!).getTime()).toBe(
|
|
||||||
new Date('2023-10-10T10:00:00.000Z').getTime(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /assets/exist', () => {
|
describe('POST /assets/exist', () => {
|
||||||
it('ignores invalid deviceAssetIds', async () => {
|
it('ignores invalid deviceAssetIds', async () => {
|
||||||
const response = await utils.checkExistingAssets(user1.accessToken, {
|
const response = await utils.checkExistingAssets(user1.accessToken, {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ describe('/server', () => {
|
|||||||
importFaces: false,
|
importFaces: false,
|
||||||
oauth: false,
|
oauth: false,
|
||||||
oauthAutoLaunch: false,
|
oauthAutoLaunch: false,
|
||||||
|
ocr: false,
|
||||||
passwordLogin: true,
|
passwordLogin: true,
|
||||||
search: true,
|
search: true,
|
||||||
sidecar: true,
|
sidecar: true,
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ describe('/tags', () => {
|
|||||||
expect(body).toEqual([expect.objectContaining({ id: userAsset.id, success: true })]);
|
expect(body).toEqual([expect.objectContaining({ id: userAsset.id, success: true })]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove duplicate assets only once', async () => {
|
it.skip('should remove duplicate assets only once', async () => {
|
||||||
const tagA = await create(user.accessToken, { name: 'TagA' });
|
const tagA = await create(user.accessToken, { name: 'TagA' });
|
||||||
await tagAssets(
|
await tagAssets(
|
||||||
{ id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } },
|
{ id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } },
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
JobName,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
createStack,
|
createStack,
|
||||||
deleteUserAdmin,
|
deleteUserAdmin,
|
||||||
@@ -327,6 +328,8 @@ describe('/admin/users', () => {
|
|||||||
{ headers: asBearerAuth(user.accessToken) },
|
{ headers: asBearerAuth(user.accessToken) },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await utils.waitForQueueFinish(admin.accessToken, JobName.BackgroundTask);
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.delete(`/admin/users/${user.userId}`)
|
.delete(`/admin/users/${user.userId}`)
|
||||||
.send({ force: true })
|
.send({ force: true })
|
||||||
|
|||||||
@@ -442,6 +442,176 @@ describe(`immich upload`, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('immich upload --delete-duplicates', () => {
|
||||||
|
it('should delete local duplicate files', async () => {
|
||||||
|
const {
|
||||||
|
stderr: firstStderr,
|
||||||
|
stdout: firstStdout,
|
||||||
|
exitCode: firstExitCode,
|
||||||
|
} = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
|
||||||
|
expect(firstStderr).toContain('{message}');
|
||||||
|
expect(firstStdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]),
|
||||||
|
);
|
||||||
|
expect(firstExitCode).toBe(0);
|
||||||
|
|
||||||
|
await mkdir(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`);
|
||||||
|
|
||||||
|
// Upload with --delete-duplicates flag
|
||||||
|
const { stderr, stdout, exitCode } = await immichCli([
|
||||||
|
'upload',
|
||||||
|
`/tmp/albums/nature/silver_fir.jpg`,
|
||||||
|
'--delete-duplicates',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check that the duplicate file was deleted
|
||||||
|
const files = await readdir(`/tmp/albums/nature`);
|
||||||
|
await rm(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
expect(files.length).toBe(0);
|
||||||
|
|
||||||
|
expect(stdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.stringContaining('Found 0 new files and 1 duplicate'),
|
||||||
|
expect.stringContaining('All assets were already uploaded, nothing to do'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(stderr).toContain('{message}');
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Verify no new assets were uploaded
|
||||||
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
|
expect(assets.total).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have accurate dry run with --delete-duplicates', async () => {
|
||||||
|
const {
|
||||||
|
stderr: firstStderr,
|
||||||
|
stdout: firstStdout,
|
||||||
|
exitCode: firstExitCode,
|
||||||
|
} = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
|
||||||
|
expect(firstStderr).toContain('{message}');
|
||||||
|
expect(firstStdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]),
|
||||||
|
);
|
||||||
|
expect(firstExitCode).toBe(0);
|
||||||
|
|
||||||
|
await mkdir(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`);
|
||||||
|
|
||||||
|
// Upload with --delete-duplicates and --dry-run flags
|
||||||
|
const { stderr, stdout, exitCode } = await immichCli([
|
||||||
|
'upload',
|
||||||
|
`/tmp/albums/nature/silver_fir.jpg`,
|
||||||
|
'--delete-duplicates',
|
||||||
|
'--dry-run',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check that the duplicate file was NOT deleted in dry run mode
|
||||||
|
const files = await readdir(`/tmp/albums/nature`);
|
||||||
|
await rm(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
expect(files.length).toBe(1);
|
||||||
|
|
||||||
|
expect(stdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.stringContaining('Found 0 new files and 1 duplicate'),
|
||||||
|
expect.stringContaining('Would have deleted 1 local asset'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(stderr).toContain('{message}');
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Verify no new assets were uploaded
|
||||||
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
|
expect(assets.total).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with both --delete and --delete-duplicates flags', async () => {
|
||||||
|
// First, upload a file to create a duplicate on the server
|
||||||
|
const {
|
||||||
|
stderr: firstStderr,
|
||||||
|
stdout: firstStdout,
|
||||||
|
exitCode: firstExitCode,
|
||||||
|
} = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
|
||||||
|
expect(firstStderr).toContain('{message}');
|
||||||
|
expect(firstStdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]),
|
||||||
|
);
|
||||||
|
expect(firstExitCode).toBe(0);
|
||||||
|
|
||||||
|
// Both new and duplicate files
|
||||||
|
await mkdir(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); // duplicate
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, `/tmp/albums/nature/el_torcal_rocks.jpg`); // new
|
||||||
|
|
||||||
|
// Upload with both --delete and --delete-duplicates flags
|
||||||
|
const { stderr, stdout, exitCode } = await immichCli([
|
||||||
|
'upload',
|
||||||
|
`/tmp/albums/nature`,
|
||||||
|
'--delete',
|
||||||
|
'--delete-duplicates',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check that both files were deleted (new file due to --delete, duplicate due to --delete-duplicates)
|
||||||
|
const files = await readdir(`/tmp/albums/nature`);
|
||||||
|
await rm(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
expect(files.length).toBe(0);
|
||||||
|
|
||||||
|
expect(stdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.stringContaining('Found 1 new files and 1 duplicate'),
|
||||||
|
expect.stringContaining('Successfully uploaded 1 new asset'),
|
||||||
|
expect.stringContaining('Deleting assets that have been uploaded'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(stderr).toContain('{message}');
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Verify one new asset was uploaded (total should be 2 now)
|
||||||
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
|
expect(assets.total).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only delete duplicates when --delete-duplicates is used without --delete', async () => {
|
||||||
|
const {
|
||||||
|
stderr: firstStderr,
|
||||||
|
stdout: firstStdout,
|
||||||
|
exitCode: firstExitCode,
|
||||||
|
} = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
|
||||||
|
expect(firstStderr).toContain('{message}');
|
||||||
|
expect(firstStdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]),
|
||||||
|
);
|
||||||
|
expect(firstExitCode).toBe(0);
|
||||||
|
|
||||||
|
// Both new and duplicate files
|
||||||
|
await mkdir(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/silver_fir.jpg`, `/tmp/albums/nature/silver_fir.jpg`); // duplicate
|
||||||
|
await symlink(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, `/tmp/albums/nature/el_torcal_rocks.jpg`); // new
|
||||||
|
|
||||||
|
// Upload with only --delete-duplicates flag
|
||||||
|
const { stderr, stdout, exitCode } = await immichCli(['upload', `/tmp/albums/nature`, '--delete-duplicates']);
|
||||||
|
|
||||||
|
// Check that only the duplicate was deleted, new file should remain
|
||||||
|
const files = await readdir(`/tmp/albums/nature`);
|
||||||
|
await rm(`/tmp/albums/nature`, { recursive: true });
|
||||||
|
expect(files).toEqual(['el_torcal_rocks.jpg']);
|
||||||
|
|
||||||
|
expect(stdout.split('\n')).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.stringContaining('Found 1 new files and 1 duplicate'),
|
||||||
|
expect.stringContaining('Successfully uploaded 1 new asset'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(stderr).toContain('{message}');
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
|
// Verify one new asset was uploaded (total should be 2 now)
|
||||||
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
|
expect(assets.total).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('immich upload --skip-hash', () => {
|
describe('immich upload --skip-hash', () => {
|
||||||
it('should skip hashing', async () => {
|
it('should skip hashing', async () => {
|
||||||
const filename = `albums/nature/silver_fir.jpg`;
|
const filename = `albums/nature/silver_fir.jpg`;
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Script to generate test images with additional EXIF date tags
|
|
||||||
* This creates actual JPEG images with embedded metadata for testing
|
|
||||||
* Images are generated into e2e/test-assets/metadata/dates/
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { execSync } from 'node:child_process';
|
|
||||||
import { writeFileSync } from 'node:fs';
|
|
||||||
import { dirname, join } from 'node:path';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import sharp from 'sharp';
|
|
||||||
|
|
||||||
interface TestImage {
|
|
||||||
filename: string;
|
|
||||||
description: string;
|
|
||||||
exifTags: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const testImages: TestImage[] = [
|
|
||||||
{
|
|
||||||
filename: 'time-created.jpg',
|
|
||||||
description: 'Image with TimeCreated tag',
|
|
||||||
exifTags: {
|
|
||||||
TimeCreated: '2023:11:15 14:30:00',
|
|
||||||
Make: 'Canon',
|
|
||||||
Model: 'EOS R5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'gps-datetime.jpg',
|
|
||||||
description: 'Image with GPSDateTime and coordinates',
|
|
||||||
exifTags: {
|
|
||||||
GPSDateTime: '2023:11:15 12:30:00Z',
|
|
||||||
GPSLatitude: '37.7749',
|
|
||||||
GPSLongitude: '-122.4194',
|
|
||||||
GPSLatitudeRef: 'N',
|
|
||||||
GPSLongitudeRef: 'W',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'datetime-utc.jpg',
|
|
||||||
description: 'Image with DateTimeUTC tag',
|
|
||||||
exifTags: {
|
|
||||||
DateTimeUTC: '2023:11:15 10:30:00',
|
|
||||||
Make: 'Nikon',
|
|
||||||
Model: 'D850',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'gps-datestamp.jpg',
|
|
||||||
description: 'Image with GPSDateStamp and GPSTimeStamp',
|
|
||||||
exifTags: {
|
|
||||||
GPSDateStamp: '2023:11:15',
|
|
||||||
GPSTimeStamp: '08:30:00',
|
|
||||||
GPSLatitude: '51.5074',
|
|
||||||
GPSLongitude: '-0.1278',
|
|
||||||
GPSLatitudeRef: 'N',
|
|
||||||
GPSLongitudeRef: 'W',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'sony-datetime2.jpg',
|
|
||||||
description: 'Sony camera image with SonyDateTime2 tag',
|
|
||||||
exifTags: {
|
|
||||||
SonyDateTime2: '2023:11:15 06:30:00',
|
|
||||||
Make: 'SONY',
|
|
||||||
Model: 'ILCE-7RM5',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'date-priority-test.jpg',
|
|
||||||
description: 'Image with multiple date tags to test priority',
|
|
||||||
exifTags: {
|
|
||||||
SubSecDateTimeOriginal: '2023:01:01 01:00:00',
|
|
||||||
DateTimeOriginal: '2023:02:02 02:00:00',
|
|
||||||
SubSecCreateDate: '2023:03:03 03:00:00',
|
|
||||||
CreateDate: '2023:04:04 04:00:00',
|
|
||||||
CreationDate: '2023:05:05 05:00:00',
|
|
||||||
DateTimeCreated: '2023:06:06 06:00:00',
|
|
||||||
TimeCreated: '2023:07:07 07:00:00',
|
|
||||||
GPSDateTime: '2023:08:08 08:00:00',
|
|
||||||
DateTimeUTC: '2023:09:09 09:00:00',
|
|
||||||
GPSDateStamp: '2023:10:10',
|
|
||||||
SonyDateTime2: '2023:11:11 11:00:00',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
filename: 'new-tags-only.jpg',
|
|
||||||
description: 'Image with only additional date tags (no standard tags)',
|
|
||||||
exifTags: {
|
|
||||||
TimeCreated: '2023:12:01 15:45:30',
|
|
||||||
GPSDateTime: '2023:12:01 13:45:30Z',
|
|
||||||
DateTimeUTC: '2023:12:01 13:45:30',
|
|
||||||
GPSDateStamp: '2023:12:01',
|
|
||||||
SonyDateTime2: '2023:12:01 08:45:30',
|
|
||||||
GPSLatitude: '40.7128',
|
|
||||||
GPSLongitude: '-74.0060',
|
|
||||||
GPSLatitudeRef: 'N',
|
|
||||||
GPSLongitudeRef: 'W',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const generateTestImages = async (): Promise<void> => {
|
|
||||||
// Target directory: e2e/test-assets/metadata/dates/
|
|
||||||
// Current file is in: e2e/src/
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
const targetDir = join(__dirname, '..', 'test-assets', 'metadata', 'dates');
|
|
||||||
|
|
||||||
console.log('Generating test images with additional EXIF date tags...');
|
|
||||||
console.log(`Target directory: ${targetDir}`);
|
|
||||||
|
|
||||||
for (const image of testImages) {
|
|
||||||
try {
|
|
||||||
const imagePath = join(targetDir, image.filename);
|
|
||||||
|
|
||||||
// Create unique JPEG file using Sharp
|
|
||||||
const r = Math.floor(Math.random() * 256);
|
|
||||||
const g = Math.floor(Math.random() * 256);
|
|
||||||
const b = Math.floor(Math.random() * 256);
|
|
||||||
|
|
||||||
const jpegData = await sharp({
|
|
||||||
create: {
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
channels: 3,
|
|
||||||
background: { r, g, b },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.jpeg({ quality: 90 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
writeFileSync(imagePath, jpegData);
|
|
||||||
|
|
||||||
// Build exiftool command to add EXIF data
|
|
||||||
const exifArgs = Object.entries(image.exifTags)
|
|
||||||
.map(([tag, value]) => `-${tag}="${value}"`)
|
|
||||||
.join(' ');
|
|
||||||
|
|
||||||
const command = `exiftool ${exifArgs} -overwrite_original "${imagePath}"`;
|
|
||||||
|
|
||||||
console.log(`Creating ${image.filename}: ${image.description}`);
|
|
||||||
execSync(command, { stdio: 'pipe' });
|
|
||||||
|
|
||||||
// Verify the tags were written
|
|
||||||
const verifyCommand = `exiftool -json "${imagePath}"`;
|
|
||||||
const result = execSync(verifyCommand, { encoding: 'utf8' });
|
|
||||||
const metadata = JSON.parse(result)[0];
|
|
||||||
|
|
||||||
console.log(` ✓ Created with ${Object.keys(image.exifTags).length} EXIF tags`);
|
|
||||||
|
|
||||||
// Log first date tag found for verification
|
|
||||||
const firstDateTag = Object.keys(image.exifTags).find(
|
|
||||||
(tag) => tag.includes('Date') || tag.includes('Time') || tag.includes('Created'),
|
|
||||||
);
|
|
||||||
if (firstDateTag && metadata[firstDateTag]) {
|
|
||||||
console.log(` ✓ Verified ${firstDateTag}: ${metadata[firstDateTag]}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to create ${image.filename}:`, (error as Error).message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nTest image generation complete!');
|
|
||||||
console.log('Files created in:', targetDir);
|
|
||||||
console.log('\nTo test these images:');
|
|
||||||
console.log(`cd ${targetDir} && exiftool -time:all -gps:all *.jpg`);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { generateTestImages };
|
|
||||||
|
|
||||||
// Run the generator if this file is executed directly
|
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
||||||
generateTestImages().catch(console.error);
|
|
||||||
}
|
|
||||||
@@ -119,5 +119,6 @@ export const deviceDto = {
|
|||||||
isPendingSyncReset: false,
|
isPendingSyncReset: false,
|
||||||
deviceOS: '',
|
deviceOS: '',
|
||||||
deviceType: '',
|
deviceType: '',
|
||||||
|
appVersion: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ test.describe('Asset Viewer Navbar', () => {
|
|||||||
await page.goto(`/photos/${asset.id}`);
|
await page.goto(`/photos/${asset.id}`);
|
||||||
await page.waitForSelector('#immich-asset-viewer');
|
await page.waitForSelector('#immich-asset-viewer');
|
||||||
await page.keyboard.press('f');
|
await page.keyboard.press('f');
|
||||||
await expect(page.locator('#notification-list').getByTestId('message')).toHaveText('Added to favorites');
|
await expect(page.getByText('Added to favorites')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,6 +51,6 @@ test.describe('Slideshow', () => {
|
|||||||
|
|
||||||
await expect(page.getByRole('button', { name: 'Exit Slideshow' })).toBeVisible();
|
await expect(page.getByRole('button', { name: 'Exit Slideshow' })).toBeVisible();
|
||||||
await page.keyboard.press('f');
|
await page.keyboard.press('f');
|
||||||
await expect(page.locator('#notification-list')).not.toBeVisible();
|
await expect(page.getByText('Added to favorites')).not.toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ test.describe('Registration', () => {
|
|||||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Template' }).click();
|
await page.getByRole('button', { name: 'Storage Template' }).click();
|
||||||
await page.getByRole('button', { name: 'Backups' }).click();
|
await page.getByRole('button', { name: 'Backups' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Mobile App' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
// success
|
// success
|
||||||
@@ -85,6 +86,7 @@ test.describe('Registration', () => {
|
|||||||
await page.getByRole('button', { name: 'Theme' }).click();
|
await page.getByRole('button', { name: 'Theme' }).click();
|
||||||
await page.getByRole('button', { name: 'Language' }).click();
|
await page.getByRole('button', { name: 'Language' }).click();
|
||||||
await page.getByRole('button', { name: 'User Privacy' }).click();
|
await page.getByRole('button', { name: 'User Privacy' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Mobile App' }).click();
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
// success
|
// success
|
||||||
|
|||||||
@@ -170,7 +170,6 @@
|
|||||||
"duplicates": "Duplikate",
|
"duplicates": "Duplikate",
|
||||||
"duration": "Duur",
|
"duration": "Duur",
|
||||||
"edit": "Wysig",
|
"edit": "Wysig",
|
||||||
"edited": "Gewysigd",
|
|
||||||
"search_by_description": "Soek by beskrywing",
|
"search_by_description": "Soek by beskrywing",
|
||||||
"search_by_description_example": "Stapdag in Sapa",
|
"search_by_description_example": "Stapdag in Sapa",
|
||||||
"version": "Weergawe",
|
"version": "Weergawe",
|
||||||
|
|||||||
83
i18n/ar.json
@@ -28,10 +28,12 @@
|
|||||||
"add_to_album": "إضافة إلى ألبوم",
|
"add_to_album": "إضافة إلى ألبوم",
|
||||||
"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}",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "لا يمكن إضافة بعض الأصول المحلية إلى الألبوم",
|
||||||
"add_to_album_toggle": "تبديل التحديد لـ{album}",
|
"add_to_album_toggle": "تبديل التحديد لـ{album}",
|
||||||
"add_to_albums": "إضافة الى البومات",
|
"add_to_albums": "إضافة الى البومات",
|
||||||
"add_to_albums_count": "إضافه إلى البومات ({count})",
|
"add_to_albums_count": "إضافه إلى البومات ({count})",
|
||||||
"add_to_shared_album": "إضافة إلى ألبوم مشارك",
|
"add_to_shared_album": "إضافة إلى ألبوم مشارك",
|
||||||
|
"add_upload_to_stack": "اضف رفع الى حزمة",
|
||||||
"add_url": "إضافة رابط",
|
"add_url": "إضافة رابط",
|
||||||
"added_to_archive": "أُضيفت للأرشيف",
|
"added_to_archive": "أُضيفت للأرشيف",
|
||||||
"added_to_favorites": "أُضيفت للمفضلات",
|
"added_to_favorites": "أُضيفت للمفضلات",
|
||||||
@@ -117,13 +119,19 @@
|
|||||||
"library_settings": "المكتبة الخارجية",
|
"library_settings": "المكتبة الخارجية",
|
||||||
"library_settings_description": "إدارة إعدادات المكتبة الخارجية",
|
"library_settings_description": "إدارة إعدادات المكتبة الخارجية",
|
||||||
"library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة",
|
"library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة",
|
||||||
"library_watching_enable_description": "راقب المكتبات الخارجية لتغييرات الملفات",
|
"library_watching_enable_description": "مراقبة المكتبات الخارجية لاكتشاف تغييرات الملفات",
|
||||||
"library_watching_settings": "مراقبة المكتبات (تجريبي)",
|
"library_watching_settings": "مراقبة المكتبات [تجريبي]",
|
||||||
"library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات",
|
"library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات",
|
||||||
"logging_enable_description": "تفعيل تسجيل الأحداث",
|
"logging_enable_description": "تفعيل تسجيل الأحداث",
|
||||||
"logging_level_description": "عند التفعيل، أي مستوى تسجيل سيستخدم.",
|
"logging_level_description": "عند التفعيل، أي مستوى تسجيل سيستخدم.",
|
||||||
"logging_settings": "تسجيل الاحداث",
|
"logging_settings": "السجلات",
|
||||||
"machine_learning_availability_checks": "تحقق من التوفر",
|
"machine_learning_availability_checks": "تحقق من التوفر",
|
||||||
|
"machine_learning_availability_checks_description": "تحديد خوادم التعلم الآلي المتاحة تلقائيًا وإعطاءها الأولوية",
|
||||||
|
"machine_learning_availability_checks_enabled": "تفعيل عمليات فحص التوفر",
|
||||||
|
"machine_learning_availability_checks_interval": "فترة التحقق",
|
||||||
|
"machine_learning_availability_checks_interval_description": "الفترة الزمنية بالمللي ثانية بين عمليات فحص التوفر",
|
||||||
|
"machine_learning_availability_checks_timeout": "انتهت مدة انتظار الطلب",
|
||||||
|
"machine_learning_availability_checks_timeout_description": "مدة انتظار (بالمللي ثانية) لاختبارات توفر الخدمة",
|
||||||
"machine_learning_clip_model": "نموذج CLIP",
|
"machine_learning_clip_model": "نموذج CLIP",
|
||||||
"machine_learning_clip_model_description": "اسم نموذج CLIP مدرجٌ <link>هنا</link>. يرجى ملاحظة أنه يجب إعادة تشغيل وظيفة \"البحث الذكي\" لجميع الصور بعد تغيير النموذج.",
|
"machine_learning_clip_model_description": "اسم نموذج CLIP مدرجٌ <link>هنا</link>. يرجى ملاحظة أنه يجب إعادة تشغيل وظيفة \"البحث الذكي\" لجميع الصور بعد تغيير النموذج.",
|
||||||
"machine_learning_duplicate_detection": "كشف التكرار",
|
"machine_learning_duplicate_detection": "كشف التكرار",
|
||||||
@@ -198,7 +206,7 @@
|
|||||||
"note_cannot_be_changed_later": "ملاحظة: لا يمكن تغيير هذا لاحقًا!",
|
"note_cannot_be_changed_later": "ملاحظة: لا يمكن تغيير هذا لاحقًا!",
|
||||||
"notification_email_from_address": "عنوان المرسل",
|
"notification_email_from_address": "عنوان المرسل",
|
||||||
"notification_email_from_address_description": "عنوان البريد الإلكتروني للمرسل، على سبيل المثال: \"Immich Photo Server noreply@example.com\". تاكد من استخدام عنوان بريد الكتروني يسمح لك بارسال البريد الالكتروني منه.",
|
"notification_email_from_address_description": "عنوان البريد الإلكتروني للمرسل، على سبيل المثال: \"Immich Photo Server noreply@example.com\". تاكد من استخدام عنوان بريد الكتروني يسمح لك بارسال البريد الالكتروني منه.",
|
||||||
"notification_email_host_description": "مضيف خادم البريد الإلكتروني (مثلًا: smtp.immich.app)",
|
"notification_email_host_description": "عنوان خادم البريد الإلكتروني (مثل smtp.immich.app)",
|
||||||
"notification_email_ignore_certificate_errors": "تجاهل أخطاء الشهادة",
|
"notification_email_ignore_certificate_errors": "تجاهل أخطاء الشهادة",
|
||||||
"notification_email_ignore_certificate_errors_description": "تجاهل أخطاء التحقق من صحة شهادة TLS (غير مستحسن)",
|
"notification_email_ignore_certificate_errors_description": "تجاهل أخطاء التحقق من صحة شهادة TLS (غير مستحسن)",
|
||||||
"notification_email_password_description": "كلمة المرور المستخدمة للمصادقة مع خادم البريد الإلكتروني",
|
"notification_email_password_description": "كلمة المرور المستخدمة للمصادقة مع خادم البريد الإلكتروني",
|
||||||
@@ -325,7 +333,7 @@
|
|||||||
"transcoding_max_b_frames": "أقصى عدد من الإطارات B",
|
"transcoding_max_b_frames": "أقصى عدد من الإطارات B",
|
||||||
"transcoding_max_b_frames_description": "القيم الأعلى تعزز كفاءة الضغط، ولكنها تبطئ عملية الترميز. قد لا تكون متوافقة مع التسريع العتادي على الأجهزة القديمة. قيمة 0 تعطل إطارات B، بينما تضبط القيمة -1 هذا القيمة تلقائيًا.",
|
"transcoding_max_b_frames_description": "القيم الأعلى تعزز كفاءة الضغط، ولكنها تبطئ عملية الترميز. قد لا تكون متوافقة مع التسريع العتادي على الأجهزة القديمة. قيمة 0 تعطل إطارات B، بينما تضبط القيمة -1 هذا القيمة تلقائيًا.",
|
||||||
"transcoding_max_bitrate": "الحد الأقصى لمعدل البت",
|
"transcoding_max_bitrate": "الحد الأقصى لمعدل البت",
|
||||||
"transcoding_max_bitrate_description": "يمكن أن يؤدي تعيين الحد الأقصى لمعدل البت إلى جعل أحجام الملفات أكثر قابلية للتنبؤ بها بتكلفة بسيطة بالنسبة للجودة. عند دقة 720 بكسل، تكون القيم النموذجية 2600 كيلو بت لـ VP9 أو HEVC، أو 4500 كيلو بت لـ H.264. معطل إذا تم ضبطه على 0.",
|
"transcoding_max_bitrate_description": "يتيح تعيين معدل البت الأقصى التحكم في حجم الملف مع تأثير طفيف على الجودة.عند دقة 720p، القيم المقترحة هي 2600 كيلوبت/ثانية لـ VP9 أو HEVC، و4500 كيلوبت/ثانية لـ H.264.يتم تعطيل الإعداد عند القيمة 0. إذا لم تُحدَّد وحدة، يُفترض k (كيلوبت/ثانية)؛ لذا فإن 5000، 5000k، و5M متكافئة.",
|
||||||
"transcoding_max_keyframe_interval": "الحد الأقصى للفاصل الزمني للإطار الرئيسي",
|
"transcoding_max_keyframe_interval": "الحد الأقصى للفاصل الزمني للإطار الرئيسي",
|
||||||
"transcoding_max_keyframe_interval_description": "يضبط الحد الأقصى لمسافة الإطار بين الإطارات الرئيسية. تؤدي القيم المنخفضة إلى زيادة سوء كفاءة الضغط، ولكنها تعمل على تحسين أوقات البحث وقد تعمل على تحسين الجودة في المشاهد ذات الحركة السريعة. 0 يضبط هذه القيمة تلقائيًا.",
|
"transcoding_max_keyframe_interval_description": "يضبط الحد الأقصى لمسافة الإطار بين الإطارات الرئيسية. تؤدي القيم المنخفضة إلى زيادة سوء كفاءة الضغط، ولكنها تعمل على تحسين أوقات البحث وقد تعمل على تحسين الجودة في المشاهد ذات الحركة السريعة. 0 يضبط هذه القيمة تلقائيًا.",
|
||||||
"transcoding_optimal_description": "مقاطع الفيديو ذات الدقة الأعلى من الدقة المستهدفة أو بتنسيق غير مقبول",
|
"transcoding_optimal_description": "مقاطع الفيديو ذات الدقة الأعلى من الدقة المستهدفة أو بتنسيق غير مقبول",
|
||||||
@@ -343,7 +351,7 @@
|
|||||||
"transcoding_target_resolution": "القرار المستهدف",
|
"transcoding_target_resolution": "القرار المستهدف",
|
||||||
"transcoding_target_resolution_description": "يمكن أن تحافظ الدقة الأعلى على المزيد من التفاصيل ولكنها تستغرق وقتًا أطول للتشفير، ولها أحجام ملفات أكبر، ويمكن أن تقلل من استجابة التطبيق.",
|
"transcoding_target_resolution_description": "يمكن أن تحافظ الدقة الأعلى على المزيد من التفاصيل ولكنها تستغرق وقتًا أطول للتشفير، ولها أحجام ملفات أكبر، ويمكن أن تقلل من استجابة التطبيق.",
|
||||||
"transcoding_temporal_aq": "التكميم التكيفي الزمني",
|
"transcoding_temporal_aq": "التكميم التكيفي الزمني",
|
||||||
"transcoding_temporal_aq_description": "ينطبق فقط على NVENC. يزيد من جودة المشاهد عالية التفاصيل ومنخفضة الحركة. قد لا يكون متوافقًا مع الأجهزة القديمة.",
|
"transcoding_temporal_aq_description": "ينطبق فقط على NVENC. تعمل \"الكمّية التكيفية الزمنية\" على تحسين جودة المشاهد ذات التفاصيل الدقيقة والحركة البطيئة. قد لا يكون هذا الخيار متوافقًا مع الأجهزة القديمة.",
|
||||||
"transcoding_threads": "الخيوط",
|
"transcoding_threads": "الخيوط",
|
||||||
"transcoding_threads_description": "تؤدي القيم الأعلى إلى تشفير أسرع، ولكنها تترك مساحة أقل للخادم لمعالجة المهام الأخرى أثناء النشاط. يجب ألا تزيد هذه القيمة عن عدد مراكز وحدة المعالجة المركزية. يزيد من الإستغلال إذا تم ضبطه على 0.",
|
"transcoding_threads_description": "تؤدي القيم الأعلى إلى تشفير أسرع، ولكنها تترك مساحة أقل للخادم لمعالجة المهام الأخرى أثناء النشاط. يجب ألا تزيد هذه القيمة عن عدد مراكز وحدة المعالجة المركزية. يزيد من الإستغلال إذا تم ضبطه على 0.",
|
||||||
"transcoding_tone_mapping": "رسم الخرائط النغمية",
|
"transcoding_tone_mapping": "رسم الخرائط النغمية",
|
||||||
@@ -395,6 +403,7 @@
|
|||||||
"advanced_settings_prefer_remote_title": "تفضل الصور البعيدة",
|
"advanced_settings_prefer_remote_title": "تفضل الصور البعيدة",
|
||||||
"advanced_settings_proxy_headers_subtitle": "عرف عناوين الوكيل التي يستخدمها Immich لارسال كل طلب شبكي",
|
"advanced_settings_proxy_headers_subtitle": "عرف عناوين الوكيل التي يستخدمها Immich لارسال كل طلب شبكي",
|
||||||
"advanced_settings_proxy_headers_title": "عناوين الوكيل",
|
"advanced_settings_proxy_headers_title": "عناوين الوكيل",
|
||||||
|
"advanced_settings_readonly_mode_subtitle": "تتيح هذه الميزة وضع العرض فقط، حيث يمكن للمستخدم معاينة الصور فقط، بينما يتم تعطيل جميع الخيارات الأخرى مثل تحديد عدة صور، أو مشاركتها، أو بثها، أو حذفها. يمكن تفعيل/تعطيل وضع العرض فقط من خلال صورة المستخدم في الشاشة الرئيسية",
|
||||||
"advanced_settings_readonly_mode_title": "وضع القراءة فقط",
|
"advanced_settings_readonly_mode_title": "وضع القراءة فقط",
|
||||||
"advanced_settings_self_signed_ssl_subtitle": "تخطي التحقق من شهادة SSL لخادم النقطة النهائي. مكلوب للشهادات الموقعة ذاتيا.",
|
"advanced_settings_self_signed_ssl_subtitle": "تخطي التحقق من شهادة SSL لخادم النقطة النهائي. مكلوب للشهادات الموقعة ذاتيا.",
|
||||||
"advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا",
|
"advanced_settings_self_signed_ssl_title": "السماح بشهادات SSL الموقعة ذاتيًا",
|
||||||
@@ -423,6 +432,7 @@
|
|||||||
"album_remove_user_confirmation": "هل أنت متأكد أنك تريد إزالة {user}؟",
|
"album_remove_user_confirmation": "هل أنت متأكد أنك تريد إزالة {user}؟",
|
||||||
"album_search_not_found": "لم يتم ايجاد البوم مطابق لبحثك",
|
"album_search_not_found": "لم يتم ايجاد البوم مطابق لبحثك",
|
||||||
"album_share_no_users": "يبدو أنك قمت بمشاركة هذا الألبوم مع جميع المستخدمين أو ليس لديك أي مستخدم للمشاركة معه.",
|
"album_share_no_users": "يبدو أنك قمت بمشاركة هذا الألبوم مع جميع المستخدمين أو ليس لديك أي مستخدم للمشاركة معه.",
|
||||||
|
"album_summary": "ملخص الألبوم",
|
||||||
"album_updated": "تم تحديث الألبوم",
|
"album_updated": "تم تحديث الألبوم",
|
||||||
"album_updated_setting_description": "تلقي إشعارًا عبر البريد الإلكتروني عندما يحتوي الألبوم المشترك على محتويات جديدة",
|
"album_updated_setting_description": "تلقي إشعارًا عبر البريد الإلكتروني عندما يحتوي الألبوم المشترك على محتويات جديدة",
|
||||||
"album_user_left": "تم ترك {album}",
|
"album_user_left": "تم ترك {album}",
|
||||||
@@ -461,6 +471,7 @@
|
|||||||
"app_bar_signout_dialog_title": "خروج",
|
"app_bar_signout_dialog_title": "خروج",
|
||||||
"app_settings": "إعدادات التطبيق",
|
"app_settings": "إعدادات التطبيق",
|
||||||
"appears_in": "يظهر في",
|
"appears_in": "يظهر في",
|
||||||
|
"apply_count": "تطبيق ({count, number})",
|
||||||
"archive": "الأرشيف",
|
"archive": "الأرشيف",
|
||||||
"archive_action_prompt": "{count} اضيف إلى الارشيف",
|
"archive_action_prompt": "{count} اضيف إلى الارشيف",
|
||||||
"archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها",
|
"archive_or_unarchive_photo": "أرشفة الصورة أو إلغاء أرشفتها",
|
||||||
@@ -493,6 +504,8 @@
|
|||||||
"asset_restored_successfully": "تم استعادة الاصل بنجاح",
|
"asset_restored_successfully": "تم استعادة الاصل بنجاح",
|
||||||
"asset_skipped": "تم تخطيه",
|
"asset_skipped": "تم تخطيه",
|
||||||
"asset_skipped_in_trash": "في سلة المهملات",
|
"asset_skipped_in_trash": "في سلة المهملات",
|
||||||
|
"asset_trashed": "اصول محذوفة",
|
||||||
|
"asset_troubleshoot": "استكشاف مشاكل الأصول",
|
||||||
"asset_uploaded": "تم الرفع",
|
"asset_uploaded": "تم الرفع",
|
||||||
"asset_uploading": "جارٍ الرفع…",
|
"asset_uploading": "جارٍ الرفع…",
|
||||||
"asset_viewer_settings_subtitle": "إدارة إعدادات عارض المعرض الخاص بك",
|
"asset_viewer_settings_subtitle": "إدارة إعدادات عارض المعرض الخاص بك",
|
||||||
@@ -500,7 +513,9 @@
|
|||||||
"assets": "المحتويات",
|
"assets": "المحتويات",
|
||||||
"assets_added_count": "تمت إضافة {count, plural, one {# محتوى} other {# محتويات}}",
|
"assets_added_count": "تمت إضافة {count, plural, one {# محتوى} other {# محتويات}}",
|
||||||
"assets_added_to_album_count": "تمت إضافة {count, plural, one {# الأصل} other {# الأصول}} إلى الألبوم",
|
"assets_added_to_album_count": "تمت إضافة {count, plural, one {# الأصل} other {# الأصول}} إلى الألبوم",
|
||||||
|
"assets_added_to_albums_count": "تمت اضافة {assetTotal, plural, one {# اصل} other {# اصول}} to {albumTotal, plural, one {# البوم} other {# البومات}}",
|
||||||
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} لايمكن اضافته الى الالبوم",
|
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} لايمكن اضافته الى الالبوم",
|
||||||
|
"assets_cannot_be_added_to_albums": "{count, plural, one {اصل} other {اصول}} لا يمكن إضافته إلى أي من الألبومات",
|
||||||
"assets_count": "{count, plural, one {# محتوى} other {# محتويات}}",
|
"assets_count": "{count, plural, one {# محتوى} other {# محتويات}}",
|
||||||
"assets_deleted_permanently": "{count} الاص(و)ل المحذوف(ه) بشكل دائم",
|
"assets_deleted_permanently": "{count} الاص(و)ل المحذوف(ه) بشكل دائم",
|
||||||
"assets_deleted_permanently_from_server": "{count} الاص(و)ل المحذوف(ه) بشكل دائمي من خادم Immich",
|
"assets_deleted_permanently_from_server": "{count} الاص(و)ل المحذوف(ه) بشكل دائمي من خادم Immich",
|
||||||
@@ -517,14 +532,17 @@
|
|||||||
"assets_trashed_count": "تم إرسال {count, plural, one {# محتوى} other {# محتويات}} إلى سلة المهملات",
|
"assets_trashed_count": "تم إرسال {count, plural, one {# محتوى} other {# محتويات}} إلى سلة المهملات",
|
||||||
"assets_trashed_from_server": "{count} الاص(و)ل المنقولة الى سلة المهملات من خادم Immich",
|
"assets_trashed_from_server": "{count} الاص(و)ل المنقولة الى سلة المهملات من خادم Immich",
|
||||||
"assets_were_part_of_album_count": "{count, plural, one {هذا المحتوى} other {هذه المحتويات}} في الألبوم بالفعل",
|
"assets_were_part_of_album_count": "{count, plural, one {هذا المحتوى} other {هذه المحتويات}} في الألبوم بالفعل",
|
||||||
|
"assets_were_part_of_albums_count": "{count, plural, one {اصل هو} other {اصول هي}}بالفعل جزء من الألبومات",
|
||||||
"authorized_devices": "الأجهزه المخولة",
|
"authorized_devices": "الأجهزه المخولة",
|
||||||
"automatic_endpoint_switching_subtitle": "اتصل محليا من خلال شبكه Wi-Fi عند توفرها و استخدم اتصالات بديله في الاماكن الاخرى",
|
"automatic_endpoint_switching_subtitle": "اتصل محليا من خلال شبكه Wi-Fi عند توفرها و استخدم اتصالات بديله في الاماكن الاخرى",
|
||||||
"automatic_endpoint_switching_title": "تبديل URL تلقائي",
|
"automatic_endpoint_switching_title": "تبديل URL تلقائي",
|
||||||
"autoplay_slideshow": "تشغيل تلقائي لعرض الشرائح",
|
"autoplay_slideshow": "تشغيل تلقائي لعرض الشرائح",
|
||||||
"back": "خلف",
|
"back": "خلف",
|
||||||
"back_close_deselect": "الرجوع أو الإغلاق أو إلغاء التحديد",
|
"back_close_deselect": "الرجوع أو الإغلاق أو إلغاء التحديد",
|
||||||
|
"background_backup_running_error": "يتم تشغيل النسخ الاحتياطي في الخلفية حاليًا، ولا يمكن بدء النسخ الاحتياطي اليدوي",
|
||||||
"background_location_permission": "اذن الوصول للموقع في الخلفية",
|
"background_location_permission": "اذن الوصول للموقع في الخلفية",
|
||||||
"background_location_permission_content": "للتمكن من تبديل الشبكه بالخلفية، Immich يحتاج*دائما* للحصول على موقع دقيق ليتمكن التطبيق من قرائة اسم شبكة الWi-Fi",
|
"background_location_permission_content": "للتمكن من تبديل الشبكه بالخلفية، Immich يحتاج*دائما* للحصول على موقع دقيق ليتمكن التطبيق من قرائة اسم شبكة الWi-Fi",
|
||||||
|
"background_options": "خيارات الخلفية",
|
||||||
"backup": "نسخ احتياطي",
|
"backup": "نسخ احتياطي",
|
||||||
"backup_album_selection_page_albums_device": "الالبومات على الجهاز ({count})",
|
"backup_album_selection_page_albums_device": "الالبومات على الجهاز ({count})",
|
||||||
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
"backup_album_selection_page_albums_tap": "انقر للتضمين، وانقر نقرًا مزدوجًا للاستثناء",
|
||||||
@@ -532,6 +550,7 @@
|
|||||||
"backup_album_selection_page_select_albums": "حدد الألبومات",
|
"backup_album_selection_page_select_albums": "حدد الألبومات",
|
||||||
"backup_album_selection_page_selection_info": "معلومات الاختيار",
|
"backup_album_selection_page_selection_info": "معلومات الاختيار",
|
||||||
"backup_album_selection_page_total_assets": "إجمالي الأصول الفريدة",
|
"backup_album_selection_page_total_assets": "إجمالي الأصول الفريدة",
|
||||||
|
"backup_albums_sync": "مزامنة ألبومات النسخ الاحتياطي",
|
||||||
"backup_all": "الجميع",
|
"backup_all": "الجميع",
|
||||||
"backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة…",
|
"backup_background_service_backup_failed_message": "فشل في النسخ الاحتياطي للأصول. جارٍ إعادة المحاولة…",
|
||||||
"backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة…",
|
"backup_background_service_connection_failed_message": "فشل في الاتصال بالخادم. جارٍ إعادة المحاولة…",
|
||||||
@@ -581,6 +600,7 @@
|
|||||||
"backup_controller_page_turn_on": "قم بتشغيل النسخ الاحتياطي المقدمة",
|
"backup_controller_page_turn_on": "قم بتشغيل النسخ الاحتياطي المقدمة",
|
||||||
"backup_controller_page_uploading_file_info": "تحميل معلومات الملف",
|
"backup_controller_page_uploading_file_info": "تحميل معلومات الملف",
|
||||||
"backup_err_only_album": "لا يمكن إزالة الألبوم الوحيد",
|
"backup_err_only_album": "لا يمكن إزالة الألبوم الوحيد",
|
||||||
|
"backup_error_sync_failed": "فشل المزامنة. لا يمكن معالجة النسخ الاحتياطي.",
|
||||||
"backup_info_card_assets": "أصول",
|
"backup_info_card_assets": "أصول",
|
||||||
"backup_manual_cancelled": "ملغي",
|
"backup_manual_cancelled": "ملغي",
|
||||||
"backup_manual_in_progress": "قيد التحميل حاول مره اخرى",
|
"backup_manual_in_progress": "قيد التحميل حاول مره اخرى",
|
||||||
@@ -648,6 +668,8 @@
|
|||||||
"change_pin_code": "تغيير رمز PIN",
|
"change_pin_code": "تغيير رمز PIN",
|
||||||
"change_your_password": "غير كلمة المرور الخاصة بك",
|
"change_your_password": "غير كلمة المرور الخاصة بك",
|
||||||
"changed_visibility_successfully": "تم تغيير الرؤية بنجاح",
|
"changed_visibility_successfully": "تم تغيير الرؤية بنجاح",
|
||||||
|
"charging": "الشحن",
|
||||||
|
"charging_requirement_mobile_backup": "يتطلب النسخ الاحتياطي في الخلفية أن يكون الجهاز قيد الشحن",
|
||||||
"check_corrupt_asset_backup": "التحقق من وجود نسخ احتياطية فاسدة للاصول",
|
"check_corrupt_asset_backup": "التحقق من وجود نسخ احتياطية فاسدة للاصول",
|
||||||
"check_corrupt_asset_backup_button": "اجراء فحص",
|
"check_corrupt_asset_backup_button": "اجراء فحص",
|
||||||
"check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.",
|
"check_corrupt_asset_backup_description": "قم بإجراء هذا الفحص فقط عبر شبكة Wi-Fi وبعد نسخ جميع الأصول احتياطيًا. قد يستغرق الإجراء بضع دقائق.",
|
||||||
@@ -679,7 +701,6 @@
|
|||||||
"comments_and_likes": "التعليقات والإعجابات",
|
"comments_and_likes": "التعليقات والإعجابات",
|
||||||
"comments_are_disabled": "التعليقات معطلة",
|
"comments_are_disabled": "التعليقات معطلة",
|
||||||
"common_create_new_album": "إنشاء ألبوم جديد",
|
"common_create_new_album": "إنشاء ألبوم جديد",
|
||||||
"common_server_error": "يرجى التحقق من اتصال الشبكة الخاص بك ، والتأكد من أن الجهاز قابل للوصول وإصدارات التطبيق/الجهاز متوافقة.",
|
|
||||||
"completed": "اكتمل",
|
"completed": "اكتمل",
|
||||||
"confirm": "تأكيد",
|
"confirm": "تأكيد",
|
||||||
"confirm_admin_password": "تأكيد كلمة مرور المسؤول",
|
"confirm_admin_password": "تأكيد كلمة مرور المسؤول",
|
||||||
@@ -734,6 +755,7 @@
|
|||||||
"create_user": "إنشاء مستخدم",
|
"create_user": "إنشاء مستخدم",
|
||||||
"created": "تم الإنشاء",
|
"created": "تم الإنشاء",
|
||||||
"created_at": "مخلوق",
|
"created_at": "مخلوق",
|
||||||
|
"creating_linked_albums": "جاري إنشاء الألبومات المرتبطة...",
|
||||||
"crop": "قص",
|
"crop": "قص",
|
||||||
"curated_object_page_title": "أشياء",
|
"curated_object_page_title": "أشياء",
|
||||||
"current_device": "الجهاز الحالي",
|
"current_device": "الجهاز الحالي",
|
||||||
@@ -860,7 +882,6 @@
|
|||||||
"edit_tag": "تعديل العلامة",
|
"edit_tag": "تعديل العلامة",
|
||||||
"edit_title": "تعديل العنوان",
|
"edit_title": "تعديل العنوان",
|
||||||
"edit_user": "تعديل المستخدم",
|
"edit_user": "تعديل المستخدم",
|
||||||
"edited": "تم التعديل",
|
|
||||||
"editor": "محرر",
|
"editor": "محرر",
|
||||||
"editor_close_without_save_prompt": "لن يتم حفظ التغييرات",
|
"editor_close_without_save_prompt": "لن يتم حفظ التغييرات",
|
||||||
"editor_close_without_save_title": "إغلاق المحرر؟",
|
"editor_close_without_save_title": "إغلاق المحرر؟",
|
||||||
@@ -883,7 +904,9 @@
|
|||||||
"error": "خطأ",
|
"error": "خطأ",
|
||||||
"error_change_sort_album": "فشل في تغيير ترتيب الألبوم",
|
"error_change_sort_album": "فشل في تغيير ترتيب الألبوم",
|
||||||
"error_delete_face": "حدث خطأ في حذف الوجه من الأصول",
|
"error_delete_face": "حدث خطأ في حذف الوجه من الأصول",
|
||||||
|
"error_getting_places": "خطأ أثناء استرجاع بيانات المواقع",
|
||||||
"error_loading_image": "حدث خطأ أثناء تحميل الصورة",
|
"error_loading_image": "حدث خطأ أثناء تحميل الصورة",
|
||||||
|
"error_loading_partners": "خطأ بتحميل بيانات الشركاء: {error}",
|
||||||
"error_saving_image": "خطأ: {error}",
|
"error_saving_image": "خطأ: {error}",
|
||||||
"error_tag_face_bounding_box": "خطأ في وضع علامة على الوجه - لا يمكن الحصول على إحداثيات المربع المحيط",
|
"error_tag_face_bounding_box": "خطأ في وضع علامة على الوجه - لا يمكن الحصول على إحداثيات المربع المحيط",
|
||||||
"error_title": "خطأ - حدث خللٌ ما",
|
"error_title": "خطأ - حدث خللٌ ما",
|
||||||
@@ -1048,6 +1071,7 @@
|
|||||||
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
|
||||||
"feature_photo_updated": "تم تحديث الصورة المميزة",
|
"feature_photo_updated": "تم تحديث الصورة المميزة",
|
||||||
"features": "الميزات",
|
"features": "الميزات",
|
||||||
|
"features_in_development": "الميزات قيد التطوير",
|
||||||
"features_setting_description": "إدارة ميزات التطبيق",
|
"features_setting_description": "إدارة ميزات التطبيق",
|
||||||
"file_name": "إسم الملف",
|
"file_name": "إسم الملف",
|
||||||
"file_name_or_extension": "اسم الملف أو امتداده",
|
"file_name_or_extension": "اسم الملف أو امتداده",
|
||||||
@@ -1068,12 +1092,15 @@
|
|||||||
"gcast_enabled": "كوكل كاست",
|
"gcast_enabled": "كوكل كاست",
|
||||||
"gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.",
|
"gcast_enabled_description": "تقوم هذه الميزة بتحميل الموارد الخارجية من Google حتى تعمل.",
|
||||||
"general": "عام",
|
"general": "عام",
|
||||||
|
"geolocation_instruction_location": "انقر على الاصل الذي يحتوي على إحداثيات نظام تحديد المواقع لاستخدام موقعه، أو اختر الموقع مباشرة من الخريطة",
|
||||||
"get_help": "الحصول على المساعدة",
|
"get_help": "الحصول على المساعدة",
|
||||||
"get_wifiname_error": "تعذر الحصول على اسم شبكة Wi-Fi. تأكد من منح الأذونات اللازمة واتصالك بشبكة Wi-Fi",
|
"get_wifiname_error": "تعذر الحصول على اسم شبكة Wi-Fi. تأكد من منح الأذونات اللازمة واتصالك بشبكة Wi-Fi",
|
||||||
"getting_started": "البدء",
|
"getting_started": "البدء",
|
||||||
"go_back": "الرجوع للخلف",
|
"go_back": "الرجوع للخلف",
|
||||||
"go_to_folder": "اذهب إلى المجلد",
|
"go_to_folder": "اذهب إلى المجلد",
|
||||||
"go_to_search": "اذهب إلى البحث",
|
"go_to_search": "اذهب إلى البحث",
|
||||||
|
"gps": "نظام تحديد المواقع",
|
||||||
|
"gps_missing": "لا يوجد نظام تحديد المواقع",
|
||||||
"grant_permission": "منح الاذن",
|
"grant_permission": "منح الاذن",
|
||||||
"group_albums_by": "تجميع الألبومات حسب...",
|
"group_albums_by": "تجميع الألبومات حسب...",
|
||||||
"group_country": "مجموعة البلد",
|
"group_country": "مجموعة البلد",
|
||||||
@@ -1091,7 +1118,6 @@
|
|||||||
"header_settings_field_validator_msg": "القيمة لا يمكن ان تكون فارغة",
|
"header_settings_field_validator_msg": "القيمة لا يمكن ان تكون فارغة",
|
||||||
"header_settings_header_name_input": "اسم الرأس",
|
"header_settings_header_name_input": "اسم الرأس",
|
||||||
"header_settings_header_value_input": "قيمة الرأس",
|
"header_settings_header_value_input": "قيمة الرأس",
|
||||||
"headers_settings_tile_subtitle": "قم بتعريف رؤوس الوكيل التي يجب أن يرسلها التطبيق مع كل طلب شبكة",
|
|
||||||
"headers_settings_tile_title": "رؤوس وكيل مخصصة",
|
"headers_settings_tile_title": "رؤوس وكيل مخصصة",
|
||||||
"hi_user": "مرحبا {name} ({email})",
|
"hi_user": "مرحبا {name} ({email})",
|
||||||
"hide_all_people": "إخفاء جميع الأشخاص",
|
"hide_all_people": "إخفاء جميع الأشخاص",
|
||||||
@@ -1209,6 +1235,7 @@
|
|||||||
"local": "محلّي",
|
"local": "محلّي",
|
||||||
"local_asset_cast_failed": "غير قادر على بث أصل لم يتم تحميله إلى الخادم",
|
"local_asset_cast_failed": "غير قادر على بث أصل لم يتم تحميله إلى الخادم",
|
||||||
"local_assets": "أُصول (ملفات) محلية",
|
"local_assets": "أُصول (ملفات) محلية",
|
||||||
|
"local_media_summary": "ملخص الملفات المحلية",
|
||||||
"local_network": "شبكة محلية",
|
"local_network": "شبكة محلية",
|
||||||
"local_network_sheet_info": "سيتصل التطبيق بالخادم من خلال عنوان URL هذا عند استخدام شبكة Wi-Fi المحددة",
|
"local_network_sheet_info": "سيتصل التطبيق بالخادم من خلال عنوان URL هذا عند استخدام شبكة Wi-Fi المحددة",
|
||||||
"location_permission": "اذن الموقع",
|
"location_permission": "اذن الموقع",
|
||||||
@@ -1220,6 +1247,7 @@
|
|||||||
"location_picker_longitude_hint": "أدخل خط الطول هنا",
|
"location_picker_longitude_hint": "أدخل خط الطول هنا",
|
||||||
"lock": "قفل",
|
"lock": "قفل",
|
||||||
"locked_folder": "مجلد مقفول",
|
"locked_folder": "مجلد مقفول",
|
||||||
|
"log_detail_title": "تفاصيل السجل",
|
||||||
"log_out": "تسجيل خروج",
|
"log_out": "تسجيل خروج",
|
||||||
"log_out_all_devices": "تسجيل الخروج من كافة الأجهزة",
|
"log_out_all_devices": "تسجيل الخروج من كافة الأجهزة",
|
||||||
"logged_in_as": "تم تسجيل الدخول باسم {user}",
|
"logged_in_as": "تم تسجيل الدخول باسم {user}",
|
||||||
@@ -1250,6 +1278,7 @@
|
|||||||
"login_password_changed_success": "تم تحديث كلمة السر بنجاح",
|
"login_password_changed_success": "تم تحديث كلمة السر بنجاح",
|
||||||
"logout_all_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من جميع الأجهزة؟",
|
"logout_all_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من جميع الأجهزة؟",
|
||||||
"logout_this_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من هذا الجهاز؟",
|
"logout_this_device_confirmation": "هل أنت متأكد أنك تريد تسجيل الخروج من هذا الجهاز؟",
|
||||||
|
"logs": "السجلات",
|
||||||
"longitude": "خط الطول",
|
"longitude": "خط الطول",
|
||||||
"look": "الشكل",
|
"look": "الشكل",
|
||||||
"loop_videos": "تكرار مقاطع الفيديو",
|
"loop_videos": "تكرار مقاطع الفيديو",
|
||||||
@@ -1257,6 +1286,7 @@
|
|||||||
"main_branch_warning": "أنت تستخدم إصداراً قيد التطوير؛ ونحن نوصي بشدة باستخدام إصدار النشر!",
|
"main_branch_warning": "أنت تستخدم إصداراً قيد التطوير؛ ونحن نوصي بشدة باستخدام إصدار النشر!",
|
||||||
"main_menu": "القائمة الرئيسية",
|
"main_menu": "القائمة الرئيسية",
|
||||||
"make": "صنع",
|
"make": "صنع",
|
||||||
|
"manage_geolocation": "إدارة الموقع",
|
||||||
"manage_shared_links": "إدارة الروابط المشتركة",
|
"manage_shared_links": "إدارة الروابط المشتركة",
|
||||||
"manage_sharing_with_partners": "إدارة المشاركة مع الشركاء",
|
"manage_sharing_with_partners": "إدارة المشاركة مع الشركاء",
|
||||||
"manage_the_app_settings": "إدارة إعدادات التطبيق",
|
"manage_the_app_settings": "إدارة إعدادات التطبيق",
|
||||||
@@ -1291,6 +1321,7 @@
|
|||||||
"mark_as_read": "تحديد كمقروء",
|
"mark_as_read": "تحديد كمقروء",
|
||||||
"marked_all_as_read": "تم تحديد الكل كمقروء",
|
"marked_all_as_read": "تم تحديد الكل كمقروء",
|
||||||
"matches": "تطابقات",
|
"matches": "تطابقات",
|
||||||
|
"matching_assets": "الاصول المطابقة",
|
||||||
"media_type": "نوع الوسائط",
|
"media_type": "نوع الوسائط",
|
||||||
"memories": "الذكريات",
|
"memories": "الذكريات",
|
||||||
"memories_all_caught_up": "كل شيء محدث",
|
"memories_all_caught_up": "كل شيء محدث",
|
||||||
@@ -1331,6 +1362,7 @@
|
|||||||
"name_or_nickname": "الاسم أو اللقب",
|
"name_or_nickname": "الاسم أو اللقب",
|
||||||
"network_requirement_photos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية للصور",
|
"network_requirement_photos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية للصور",
|
||||||
"network_requirement_videos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية لمقاطع الفيديو",
|
"network_requirement_videos_upload": "استخدام بيانات الهاتف المحمول لعمل نسخة احتياطية لمقاطع الفيديو",
|
||||||
|
"network_requirements": "متطلبات الشبكة",
|
||||||
"network_requirements_updated": "تم تغيير متطلبات الشبكة، يتم إعادة تعيين قائمة انتظار النسخ الاحتياطي",
|
"network_requirements_updated": "تم تغيير متطلبات الشبكة، يتم إعادة تعيين قائمة انتظار النسخ الاحتياطي",
|
||||||
"networking_settings": "الشبكات",
|
"networking_settings": "الشبكات",
|
||||||
"networking_subtitle": "إدارة إعدادات نقطة الخادم النهائية",
|
"networking_subtitle": "إدارة إعدادات نقطة الخادم النهائية",
|
||||||
@@ -1341,6 +1373,7 @@
|
|||||||
"new_person": "شخص جديد",
|
"new_person": "شخص جديد",
|
||||||
"new_pin_code": "رمز PIN الجديد",
|
"new_pin_code": "رمز PIN الجديد",
|
||||||
"new_pin_code_subtitle": "هذه أول مرة تدخل فيها إلى المجلد المقفل. أنشئ رمزًا PIN للوصول بامان إلى هذه الصفحة",
|
"new_pin_code_subtitle": "هذه أول مرة تدخل فيها إلى المجلد المقفل. أنشئ رمزًا PIN للوصول بامان إلى هذه الصفحة",
|
||||||
|
"new_timeline": "الخط الزمني الجديد",
|
||||||
"new_user_created": "تم إنشاء مستخدم جديد",
|
"new_user_created": "تم إنشاء مستخدم جديد",
|
||||||
"new_version_available": "إصدار جديد متاح",
|
"new_version_available": "إصدار جديد متاح",
|
||||||
"newest_first": "الأحدث أولاً",
|
"newest_first": "الأحدث أولاً",
|
||||||
@@ -1354,20 +1387,25 @@
|
|||||||
"no_assets_message": "انقر لتحميل صورتك الأولى",
|
"no_assets_message": "انقر لتحميل صورتك الأولى",
|
||||||
"no_assets_to_show": "لا توجد أصول لعرضها",
|
"no_assets_to_show": "لا توجد أصول لعرضها",
|
||||||
"no_cast_devices_found": "لم يتم ايجاد جهاز بث",
|
"no_cast_devices_found": "لم يتم ايجاد جهاز بث",
|
||||||
|
"no_checksum_local": "لا توجد بيانات تحقق متاحة - يتعذر تحميل الاصول المحلية",
|
||||||
|
"no_checksum_remote": "لا يوجد رمز تحقق متاح - يتعذر تحميل الاصل من الموقع البعيد",
|
||||||
"no_duplicates_found": "لم يتم العثور على أي تكرارات.",
|
"no_duplicates_found": "لم يتم العثور على أي تكرارات.",
|
||||||
"no_exif_info_available": "لا تتوفر معلومات exif",
|
"no_exif_info_available": "لا تتوفر معلومات exif",
|
||||||
"no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.",
|
"no_explore_results_message": "قم برفع المزيد من الصور لاستكشاف مجموعتك.",
|
||||||
"no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو",
|
"no_favorites_message": "أضف المفضلة للعثور بسرعة على أفضل الصور ومقاطع الفيديو",
|
||||||
"no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك",
|
"no_libraries_message": "إنشاء مكتبة خارجية لعرض الصور ومقاطع الفيديو الخاصة بك",
|
||||||
|
"no_local_assets_found": "لم يتم العثور على أي اصول محلية تتطابق مع قيمة التحقق هذه",
|
||||||
"no_locked_photos_message": "الصور والفديوهات في المجلد المقفل مخفية ولن تصهر في التصفح او البحث في مكتبتك.",
|
"no_locked_photos_message": "الصور والفديوهات في المجلد المقفل مخفية ولن تصهر في التصفح او البحث في مكتبتك.",
|
||||||
"no_name": "لا اسم",
|
"no_name": "لا اسم",
|
||||||
"no_notifications": "لا توجد تنبيهات",
|
"no_notifications": "لا توجد تنبيهات",
|
||||||
"no_people_found": "لم يتم العثور على اشخاص مطابقين",
|
"no_people_found": "لم يتم العثور على اشخاص مطابقين",
|
||||||
"no_places": "لا أماكن",
|
"no_places": "لا أماكن",
|
||||||
|
"no_remote_assets_found": "لم يتم العثور على أي اصول بعيدة تتطابق مع رمز التحقق هذل",
|
||||||
"no_results": "لا يوجد نتائج",
|
"no_results": "لا يوجد نتائج",
|
||||||
"no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية",
|
"no_results_description": "جرب كلمة رئيسية مرادفة أو أكثر عمومية",
|
||||||
"no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك",
|
"no_shared_albums_message": "قم بإنشاء ألبوم لمشاركة الصور ومقاطع الفيديو مع الأشخاص في شبكتك",
|
||||||
"no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع",
|
"no_uploads_in_progress": "لا يوجد اي ملفات قيد الرفع",
|
||||||
|
"not_available": "غير متاح",
|
||||||
"not_in_any_album": "ليست في أي ألبوم",
|
"not_in_any_album": "ليست في أي ألبوم",
|
||||||
"not_selected": "لم يختار",
|
"not_selected": "لم يختار",
|
||||||
"note_apply_storage_label_to_previously_uploaded assets": "ملاحظة: لتطبيق سمة التخزين على المحتويات التي تم رفعها مسبقًا، قم بتشغيل",
|
"note_apply_storage_label_to_previously_uploaded assets": "ملاحظة: لتطبيق سمة التخزين على المحتويات التي تم رفعها مسبقًا، قم بتشغيل",
|
||||||
@@ -1402,6 +1440,8 @@
|
|||||||
"open_the_search_filters": "افتح مرشحات البحث",
|
"open_the_search_filters": "افتح مرشحات البحث",
|
||||||
"options": "خيارات",
|
"options": "خيارات",
|
||||||
"or": "أو",
|
"or": "أو",
|
||||||
|
"organize_into_albums": "ترتيب في ألبومات",
|
||||||
|
"organize_into_albums_description": "أضف الصور الموجودة إلى الألبومات باستخدام إعدادات النسخ المتزامن الحالية",
|
||||||
"organize_your_library": "تنظيم مكتبتك",
|
"organize_your_library": "تنظيم مكتبتك",
|
||||||
"original": "أصلي",
|
"original": "أصلي",
|
||||||
"other": "أخرى",
|
"other": "أخرى",
|
||||||
@@ -1487,6 +1527,7 @@
|
|||||||
"port": "المنفذ",
|
"port": "المنفذ",
|
||||||
"preferences_settings_subtitle": "ادارة تفضيلات التطبيق",
|
"preferences_settings_subtitle": "ادارة تفضيلات التطبيق",
|
||||||
"preferences_settings_title": "التفضيلات",
|
"preferences_settings_title": "التفضيلات",
|
||||||
|
"preparing": "قيد التحضير",
|
||||||
"preset": "الإعداد المسبق",
|
"preset": "الإعداد المسبق",
|
||||||
"preview": "معاينة",
|
"preview": "معاينة",
|
||||||
"previous": "السابق",
|
"previous": "السابق",
|
||||||
@@ -1499,12 +1540,9 @@
|
|||||||
"privacy": "الخصوصية",
|
"privacy": "الخصوصية",
|
||||||
"profile": "حساب تعريفي",
|
"profile": "حساب تعريفي",
|
||||||
"profile_drawer_app_logs": "السجلات",
|
"profile_drawer_app_logs": "السجلات",
|
||||||
"profile_drawer_client_out_of_date_major": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار رئيسي.",
|
|
||||||
"profile_drawer_client_out_of_date_minor": "تطبيق الهاتف المحمول قديم.يرجى التحديث إلى أحدث إصدار صغير.",
|
|
||||||
"profile_drawer_client_server_up_to_date": "العميل والخادم محدثان",
|
"profile_drawer_client_server_up_to_date": "العميل والخادم محدثان",
|
||||||
"profile_drawer_github": "Github",
|
"profile_drawer_github": "Github",
|
||||||
"profile_drawer_server_out_of_date_major": "الخادم قديم.يرجى التحديث إلى أحدث إصدار رئيسي.",
|
"profile_drawer_readonly_mode": "تم تفعيل وضع القراءة فقط. اضغط مطولا على رمز صورة المستخدم للخروج.",
|
||||||
"profile_drawer_server_out_of_date_minor": "الخادم قديم.يرجى التحديث إلى أحدث إصدار صغير.",
|
|
||||||
"profile_image_of_user": "صورة الملف الشخصي لـ {user}",
|
"profile_image_of_user": "صورة الملف الشخصي لـ {user}",
|
||||||
"profile_picture_set": "مجموعة الصور الشخصية.",
|
"profile_picture_set": "مجموعة الصور الشخصية.",
|
||||||
"public_album": "الألبوم العام",
|
"public_album": "الألبوم العام",
|
||||||
@@ -1541,6 +1579,7 @@
|
|||||||
"purchase_server_description_2": "حالة الداعم",
|
"purchase_server_description_2": "حالة الداعم",
|
||||||
"purchase_server_title": "الخادم",
|
"purchase_server_title": "الخادم",
|
||||||
"purchase_settings_server_activated": "يتم إدارة مفتاح منتج الخادم من قبل مدير النظام",
|
"purchase_settings_server_activated": "يتم إدارة مفتاح منتج الخادم من قبل مدير النظام",
|
||||||
|
"query_asset_id": "استعلام عن معرف الأصل",
|
||||||
"queue_status": "يتم الاضافة الى قائمة انتظار النسخ الاحتياطي {count}/{total}",
|
"queue_status": "يتم الاضافة الى قائمة انتظار النسخ الاحتياطي {count}/{total}",
|
||||||
"rating": "تقييم نجمي",
|
"rating": "تقييم نجمي",
|
||||||
"rating_clear": "مسح التقييم",
|
"rating_clear": "مسح التقييم",
|
||||||
@@ -1548,6 +1587,9 @@
|
|||||||
"rating_description": "اعرض تقييم EXIF في لوحة المعلومات",
|
"rating_description": "اعرض تقييم EXIF في لوحة المعلومات",
|
||||||
"reaction_options": "خيارات رد الفعل",
|
"reaction_options": "خيارات رد الفعل",
|
||||||
"read_changelog": "قراءة سجل التغيير",
|
"read_changelog": "قراءة سجل التغيير",
|
||||||
|
"readonly_mode_disabled": "تم تعطيل وضع القراءة فقط",
|
||||||
|
"readonly_mode_enabled": "تم تفعيل وضع القراءة فقط",
|
||||||
|
"ready_for_upload": "جاهز للرفع",
|
||||||
"reassign": "إعادة التعيين",
|
"reassign": "إعادة التعيين",
|
||||||
"reassigned_assets_to_existing_person": "تمت إعادة تعيين {count, plural, one {# الأصل} other {# الاصول}} إلى {name, select, null {شخص موجود } other {{name}}}",
|
"reassigned_assets_to_existing_person": "تمت إعادة تعيين {count, plural, one {# الأصل} other {# الاصول}} إلى {name, select, null {شخص موجود } other {{name}}}",
|
||||||
"reassigned_assets_to_new_person": "تمت إعادة تعيين {count, plural, one {# المحتوى} other {# المحتويات}} إلى شخص جديد",
|
"reassigned_assets_to_new_person": "تمت إعادة تعيين {count, plural, one {# المحتوى} other {# المحتويات}} إلى شخص جديد",
|
||||||
@@ -1572,6 +1614,7 @@
|
|||||||
"regenerating_thumbnails": "جارٍ تجديد الصور المصغرة",
|
"regenerating_thumbnails": "جارٍ تجديد الصور المصغرة",
|
||||||
"remote": "بعيد",
|
"remote": "بعيد",
|
||||||
"remote_assets": "الأُصول البعيدة",
|
"remote_assets": "الأُصول البعيدة",
|
||||||
|
"remote_media_summary": "ملخص الملفات البعيدة",
|
||||||
"remove": "إزالة",
|
"remove": "إزالة",
|
||||||
"remove_assets_album_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من الألبوم ؟",
|
"remove_assets_album_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من الألبوم ؟",
|
||||||
"remove_assets_shared_link_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من رابط المشاركة هذا؟",
|
"remove_assets_shared_link_confirmation": "هل أنت متأكد أنك تريد إزالة {count, plural, one {# المحتوى} other {# المحتويات}} من رابط المشاركة هذا؟",
|
||||||
@@ -1624,6 +1667,7 @@
|
|||||||
"restore_user": "استعادة المستخدم",
|
"restore_user": "استعادة المستخدم",
|
||||||
"restored_asset": "المحتويات المستعادة",
|
"restored_asset": "المحتويات المستعادة",
|
||||||
"resume": "استئناف",
|
"resume": "استئناف",
|
||||||
|
"resume_paused_jobs": "استكمال {count, plural, one {# وظيفة معلقة} other {# وظائف معلقة}}",
|
||||||
"retry_upload": "أعد محاولة الرفع",
|
"retry_upload": "أعد محاولة الرفع",
|
||||||
"review_duplicates": "مراجعة التكرارات",
|
"review_duplicates": "مراجعة التكرارات",
|
||||||
"review_large_files": "مراجعة الملفات الكبيرة",
|
"review_large_files": "مراجعة الملفات الكبيرة",
|
||||||
@@ -1717,6 +1761,7 @@
|
|||||||
"select_user_for_sharing_page_err_album": "فشل في إنشاء ألبوم",
|
"select_user_for_sharing_page_err_album": "فشل في إنشاء ألبوم",
|
||||||
"selected": "التحديد",
|
"selected": "التحديد",
|
||||||
"selected_count": "{count, plural, other {# محددة }}",
|
"selected_count": "{count, plural, other {# محددة }}",
|
||||||
|
"selected_gps_coordinates": "إحداثيات نظام تحديد المواقع المختارة",
|
||||||
"send_message": "إرسال رسالة",
|
"send_message": "إرسال رسالة",
|
||||||
"send_welcome_email": "إرسال بريدًا إلكترونيًا ترحيبيًا",
|
"send_welcome_email": "إرسال بريدًا إلكترونيًا ترحيبيًا",
|
||||||
"server_endpoint": "نقطة نهاية الخادم",
|
"server_endpoint": "نقطة نهاية الخادم",
|
||||||
@@ -1845,6 +1890,7 @@
|
|||||||
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
|
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
|
||||||
"show_supporter_badge": "شارة المؤيد",
|
"show_supporter_badge": "شارة المؤيد",
|
||||||
"show_supporter_badge_description": "إظهار شارة المؤيد",
|
"show_supporter_badge_description": "إظهار شارة المؤيد",
|
||||||
|
"show_text_search_menu": "عرض قائمة خيارات البحث في النص",
|
||||||
"shuffle": "خلط",
|
"shuffle": "خلط",
|
||||||
"sidebar": "الشريط الجانبي",
|
"sidebar": "الشريط الجانبي",
|
||||||
"sidebar_display_description": "عرض رابط للعرض في الشريط الجانبي",
|
"sidebar_display_description": "عرض رابط للعرض في الشريط الجانبي",
|
||||||
@@ -1875,6 +1921,7 @@
|
|||||||
"stacktrace": "تتّبُع التكديس",
|
"stacktrace": "تتّبُع التكديس",
|
||||||
"start": "ابدأ",
|
"start": "ابدأ",
|
||||||
"start_date": "تاريخ البدء",
|
"start_date": "تاريخ البدء",
|
||||||
|
"start_date_before_end_date": "يجب أن يكون تاريخ بدء الفترة قبل تاريخ نهايتها",
|
||||||
"state": "الولاية",
|
"state": "الولاية",
|
||||||
"status": "الحالة",
|
"status": "الحالة",
|
||||||
"stop_casting": "ايقاف البث",
|
"stop_casting": "ايقاف البث",
|
||||||
@@ -1899,6 +1946,8 @@
|
|||||||
"sync_albums_manual_subtitle": "مزامنة جميع الفديوهات والصور المرفوعة الى البومات الخزن الاحتياطي المختارة",
|
"sync_albums_manual_subtitle": "مزامنة جميع الفديوهات والصور المرفوعة الى البومات الخزن الاحتياطي المختارة",
|
||||||
"sync_local": "مزامنة الملفات المحلية",
|
"sync_local": "مزامنة الملفات المحلية",
|
||||||
"sync_remote": "مزامنة الملفات البعيدة",
|
"sync_remote": "مزامنة الملفات البعيدة",
|
||||||
|
"sync_status": "حالة النسخ المتزامن",
|
||||||
|
"sync_status_subtitle": "عرض وإدارة نظام النسخ المتزامن",
|
||||||
"sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich",
|
"sync_upload_album_setting_subtitle": "انشئ و ارفع صورك و فديوهاتك الالبومات المختارة في Immich",
|
||||||
"tag": "العلامة",
|
"tag": "العلامة",
|
||||||
"tag_assets": "أصول العلامة",
|
"tag_assets": "أصول العلامة",
|
||||||
@@ -1936,7 +1985,9 @@
|
|||||||
"to_change_password": "تغيير كلمة المرور",
|
"to_change_password": "تغيير كلمة المرور",
|
||||||
"to_favorite": "تفضيل",
|
"to_favorite": "تفضيل",
|
||||||
"to_login": "تسجيل الدخول",
|
"to_login": "تسجيل الدخول",
|
||||||
|
"to_multi_select": "للتحديد المتعدد",
|
||||||
"to_parent": "انتقل إلى الوالد",
|
"to_parent": "انتقل إلى الوالد",
|
||||||
|
"to_select": "للتحديد",
|
||||||
"to_trash": "حذف",
|
"to_trash": "حذف",
|
||||||
"toggle_settings": "الإعدادات",
|
"toggle_settings": "الإعدادات",
|
||||||
"total": "الإجمالي",
|
"total": "الإجمالي",
|
||||||
@@ -1956,6 +2007,7 @@
|
|||||||
"trash_page_select_assets_btn": "اختر الأصول",
|
"trash_page_select_assets_btn": "اختر الأصول",
|
||||||
"trash_page_title": "سلة المهملات ({count})",
|
"trash_page_title": "سلة المهملات ({count})",
|
||||||
"trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.",
|
"trashed_items_will_be_permanently_deleted_after": "سيتم حذفُ العناصر المحذوفة نِهائيًا بعد {days, plural, one {# يوم} other {# أيام }}.",
|
||||||
|
"troubleshoot": "استكشاف المشاكل",
|
||||||
"type": "النوع",
|
"type": "النوع",
|
||||||
"unable_to_change_pin_code": "تفيير رمز PIN غير ممكن",
|
"unable_to_change_pin_code": "تفيير رمز PIN غير ممكن",
|
||||||
"unable_to_setup_pin_code": "انشاء رمز PIN غير ممكن",
|
"unable_to_setup_pin_code": "انشاء رمز PIN غير ممكن",
|
||||||
@@ -1986,6 +2038,7 @@
|
|||||||
"unstacked_assets_count": "تم إخراج {count, plural, one {# الأصل} other {# الأصول}} من التكديس",
|
"unstacked_assets_count": "تم إخراج {count, plural, one {# الأصل} other {# الأصول}} من التكديس",
|
||||||
"untagged": "غير مُعَلَّم",
|
"untagged": "غير مُعَلَّم",
|
||||||
"up_next": "التالي",
|
"up_next": "التالي",
|
||||||
|
"update_location_action_prompt": "تحديث موقع {count} عناصر محددة على النحو التالي:",
|
||||||
"updated_at": "تم التحديث",
|
"updated_at": "تم التحديث",
|
||||||
"updated_password": "تم تحديث كلمة المرور",
|
"updated_password": "تم تحديث كلمة المرور",
|
||||||
"upload": "رفع",
|
"upload": "رفع",
|
||||||
@@ -2052,6 +2105,7 @@
|
|||||||
"view_next_asset": "عرض المحتوى التالي",
|
"view_next_asset": "عرض المحتوى التالي",
|
||||||
"view_previous_asset": "عرض المحتوى السابق",
|
"view_previous_asset": "عرض المحتوى السابق",
|
||||||
"view_qr_code": "عرض رمز الاستجابة السريعة",
|
"view_qr_code": "عرض رمز الاستجابة السريعة",
|
||||||
|
"view_similar_photos": "عرض صور مشابهة",
|
||||||
"view_stack": "عرض التكديس",
|
"view_stack": "عرض التكديس",
|
||||||
"view_user": "عرض المستخدم",
|
"view_user": "عرض المستخدم",
|
||||||
"viewer_remove_from_stack": "حذف من الكومه أو المجموعة",
|
"viewer_remove_from_stack": "حذف من الكومه أو المجموعة",
|
||||||
@@ -2070,5 +2124,6 @@
|
|||||||
"yes": "نعم",
|
"yes": "نعم",
|
||||||
"you_dont_have_any_shared_links": "ليس لديك أي روابط مشتركة",
|
"you_dont_have_any_shared_links": "ليس لديك أي روابط مشتركة",
|
||||||
"your_wifi_name": "اسم شبكة Wi-Fi الخاص بك",
|
"your_wifi_name": "اسم شبكة Wi-Fi الخاص بك",
|
||||||
"zoom_image": "تكبير الصورة"
|
"zoom_image": "تكبير الصورة",
|
||||||
|
"zoom_to_bounds": "تكبير حتى حدود المنطقة"
|
||||||
}
|
}
|
||||||
|
|||||||
26
i18n/az.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"about": "Haqqında",
|
"about": "Haqqında",
|
||||||
"account": "Hesab",
|
"account": "Hesab",
|
||||||
"account_settings": "Hesab parametrləri",
|
"account_settings": "Hesab Parametrləri",
|
||||||
"acknowledge": "Təsdiq et",
|
"acknowledge": "Təsdiq et",
|
||||||
"action": "Əməliyyat",
|
"action": "Əməliyyat",
|
||||||
"action_common_update": "Yenilə",
|
"action_common_update": "Yenilə",
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
"add_a_title": "Başlıq əlavə et",
|
"add_a_title": "Başlıq əlavə et",
|
||||||
"add_birthday": "Doğum günü əlavə et",
|
"add_birthday": "Doğum günü əlavə et",
|
||||||
"add_endpoint": "Son nöqtə əlavə et",
|
"add_endpoint": "Son nöqtə əlavə et",
|
||||||
"add_exclusion_pattern": "İstisna nümunəsi əlavə et",
|
"add_exclusion_pattern": "Çıxarma nümunəsi əlavə et",
|
||||||
"add_import_path": "Import yolunu əlavə et",
|
"add_import_path": "İdxal yolu əlavə et",
|
||||||
"add_location": "Məkan əlavə et",
|
"add_location": "Məkan əlavə et",
|
||||||
"add_more_users": "Daha çox istifadəçi əlavə et",
|
"add_more_users": "Daha çox istifadəçi əlavə et",
|
||||||
"add_partner": "Partnyor əlavə et",
|
"add_partner": "Partnyor əlavə et",
|
||||||
@@ -25,20 +25,21 @@
|
|||||||
"add_photos": "Şəkillər əlavə et",
|
"add_photos": "Şəkillər əlavə et",
|
||||||
"add_tag": "Etiket əlavə et",
|
"add_tag": "Etiket əlavə et",
|
||||||
"add_to": "Bura əlavə et…",
|
"add_to": "Bura əlavə et…",
|
||||||
"add_to_album": "Albom əlavə et",
|
"add_to_album": "Alboma əlavə et",
|
||||||
"add_to_album_bottom_sheet_added": "{album} albomuna əlavə edildi",
|
"add_to_album_bottom_sheet_added": "{album} albomuna əlavə edildi",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Artıq {album} albomunda var",
|
"add_to_album_bottom_sheet_already_exists": "Artıq {album} albomunda var",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "Bəzi lokal resurslar alboma əlavə edilə bilmədi",
|
||||||
"add_to_album_toggle": "{album} üçün seçimi dəyişin",
|
"add_to_album_toggle": "{album} üçün seçimi dəyişin",
|
||||||
"add_to_albums": "Albomlara əlavə et",
|
"add_to_albums": "Albomlara əlavə et",
|
||||||
"add_to_albums_count": "Albomlara əlavə et ({count})",
|
"add_to_albums_count": "({count}) albomlarına əlavə et",
|
||||||
"add_to_shared_album": "Paylaşılan alboma əlavə et",
|
"add_to_shared_album": "Paylaşılan alboma əlavə et",
|
||||||
"add_url": "URL əlavə et",
|
"add_url": "URL əlavə et",
|
||||||
"added_to_archive": "Arxivə əlavə edildi",
|
"added_to_archive": "Arxivə əlavə edildi",
|
||||||
"added_to_favorites": "Sevimlilələrə əlavə edildi",
|
"added_to_favorites": "Sevimlilələrə əlavə edildi",
|
||||||
"added_to_favorites_count": "{count, number} şəkil sevimlilələrə əlavə edildi",
|
"added_to_favorites_count": "{count, number} şəkil sevimlilələrə əlavə edildi",
|
||||||
"admin": {
|
"admin": {
|
||||||
"add_exclusion_pattern_description": "İstisna şablonlarını əlavə edin. *, ** və ? ilə Globbing dəstəklənir. Məs.: \"Raw\" adlanan hər hansısa bir qovluqda bütün faylları saymamaq üçün \"**/Raw/**\"-dan istifadə edin. \".tif\" ilə bitən bütün faylları saymamaq üçün \"**/*.tif\"-dən istifadə edin. Faylı mütləq yoldan istifadə etməklə saymamaq istəyirsinizsə \"/path/to/ignore/**\"-dan istifadə edin.",
|
"add_exclusion_pattern_description": "Çıxarma nümunələri əlavə et. *, ** və ? istifadə edilərək globbing dəstəklənir. Hər hansı bir \"Raw\" adlı qovluqdakı bütün faylları görməməzlikdən gəlmək üçün **/Raw/** istifadə et. “.tif” ilə bitən bütün faylları görməməzlikdən gəlmək üçün **/*.tif istifadə et. Tam yolu görməməzlikdən gəlmək üçün /path/to/ignore/** istifadə et.",
|
||||||
"admin_user": "Admin İstifadəçi",
|
"admin_user": "İdarəçi İstifadəçi",
|
||||||
"asset_offline_description": "Bu xarici kitabxana varlığı diskdə artıq tapılmadı və zibil qutusuna köçürüldü. Əgər fayl kitabxana içərisində köçürülübsə, zaman şkalanızı yeni uyğun gələn varlıq üçün yoxlayın. Varlığı yenidən qaytarmaq üçün aşağıda verilmiş fayl yolunun Immich tərəfindən əlçatan olduğundan əmin olduqdan sonra kitabxananı skan edin.",
|
"asset_offline_description": "Bu xarici kitabxana varlığı diskdə artıq tapılmadı və zibil qutusuna köçürüldü. Əgər fayl kitabxana içərisində köçürülübsə, zaman şkalanızı yeni uyğun gələn varlıq üçün yoxlayın. Varlığı yenidən qaytarmaq üçün aşağıda verilmiş fayl yolunun Immich tərəfindən əlçatan olduğundan əmin olduqdan sonra kitabxananı skan edin.",
|
||||||
"authentication_settings": "Səlahiyyətləndirmə parametrləri",
|
"authentication_settings": "Səlahiyyətləndirmə parametrləri",
|
||||||
"authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri",
|
"authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri",
|
||||||
@@ -48,8 +49,15 @@
|
|||||||
"backup_database": "Verilənlər bazasının dump-ını yaradın",
|
"backup_database": "Verilənlər bazasının dump-ını yaradın",
|
||||||
"backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et",
|
"backup_database_enable_description": "Verilənlər bazasının artıq nüsxələrini aktiv et",
|
||||||
"backup_keep_last_amount": "Tutulması gərəkən nüsxələrin sayı",
|
"backup_keep_last_amount": "Tutulması gərəkən nüsxələrin sayı",
|
||||||
"backup_settings": "Ehtiyat Nüsxə Parametrləri",
|
"backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.",
|
||||||
|
"backup_onboarding_2_description": "müxtəlif cihazlarda yerli nüsxələr. Bura əsas fayllar və həmin faylların ehtiyat lokal nüsxəsi daxildir.",
|
||||||
|
"backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarınızın ümumi surətləri. Buraya 1 kənar nüsxə və 2 lokal nüsxə daxildir.",
|
||||||
|
"backup_onboarding_footer": "Immich-in ehtiyat nüsxəsini çıxarmaq haqqında ətraflı məlumat üçün <link>sənədlərə</link> müraciət edin.",
|
||||||
|
"backup_onboarding_parts_title": "3-2-1 ehtiyat nüsxəsinə aşağıdakılar daxildir:",
|
||||||
|
"backup_onboarding_title": "Ehtiyat surətlər",
|
||||||
|
"backup_settings": "Bazanın Dump Parametrləri",
|
||||||
"backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et",
|
"backup_settings_description": "Verilənlər bazasının ehtiyat nüsxə parametrlərini idarə et",
|
||||||
|
"cleared_jobs": "{job} üçün tapşırıqlar silindi",
|
||||||
"config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub",
|
"config_set_by_file": "Konfiqurasiya hal-hazırda konfiqurasiya faylı ilə təyin olunub",
|
||||||
"confirm_delete_library": "{library} kitabxanasını silmək istədiyinizdən əminmisiniz?",
|
"confirm_delete_library": "{library} kitabxanasını silmək istədiyinizdən əminmisiniz?",
|
||||||
"confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın",
|
"confirm_email_below": "Təsdiqləmək üçün aşağıya {email} yazın",
|
||||||
@@ -69,7 +77,7 @@
|
|||||||
"image_thumbnail_title": "Önizləmə parametrləri",
|
"image_thumbnail_title": "Önizləmə parametrləri",
|
||||||
"job_concurrency": "{job}paralellik",
|
"job_concurrency": "{job}paralellik",
|
||||||
"job_created": "Tapşırıq yaradıldı",
|
"job_created": "Tapşırıq yaradıldı",
|
||||||
"job_not_concurrency_safe": "Bu tapşırıq parallel fəaliyyət üçün uyğun deyil",
|
"job_not_concurrency_safe": "Bu iş eyni vaxtda icra üçün təhlükəsiz deyil.",
|
||||||
"job_settings": "Tapşırıq parametrləri",
|
"job_settings": "Tapşırıq parametrləri",
|
||||||
"job_settings_description": "Parallel şəkildə fəaliyyət göstərən tapşırıqları idarə et",
|
"job_settings_description": "Parallel şəkildə fəaliyyət göstərən tapşırıqları idarə et",
|
||||||
"job_status": "Tapşırıq statusu",
|
"job_status": "Tapşırıq statusu",
|
||||||
|
|||||||
13
i18n/be.json
@@ -28,6 +28,8 @@
|
|||||||
"add_to_album": "Дадаць у альбом",
|
"add_to_album": "Дадаць у альбом",
|
||||||
"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}",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "Некаторыя лакальныя актывы не могуць быць дададзены ў альбом",
|
||||||
|
"add_to_album_toggle": "Пераключыць выбар для {album}",
|
||||||
"add_to_albums": "Дадаць у альбомы",
|
"add_to_albums": "Дадаць у альбомы",
|
||||||
"add_to_albums_count": "Дадаць у альбомы ({count})",
|
"add_to_albums_count": "Дадаць у альбомы ({count})",
|
||||||
"add_to_shared_album": "Дадаць у агульны альбом",
|
"add_to_shared_album": "Дадаць у агульны альбом",
|
||||||
@@ -49,6 +51,9 @@
|
|||||||
"backup_keep_last_amount": "Колькасць папярэдніх рэзервовых копій для захавання",
|
"backup_keep_last_amount": "Колькасць папярэдніх рэзервовых копій для захавання",
|
||||||
"backup_onboarding_1_description": "зняшняя копія ў воблаку або ў іншым фізічным месцы.",
|
"backup_onboarding_1_description": "зняшняя копія ў воблаку або ў іншым фізічным месцы.",
|
||||||
"backup_onboarding_2_description": "лакальныя копіі на іншых прыладах. Гэта ўключае ў сябе асноўныя файлы і лакальную рэзервовую копію гэтых файлаў.",
|
"backup_onboarding_2_description": "лакальныя копіі на іншых прыладах. Гэта ўключае ў сябе асноўныя файлы і лакальную рэзервовую копію гэтых файлаў.",
|
||||||
|
"backup_onboarding_3_description": "поўная колькасць копій вашых данных, у тым ліку зыходных файлаў. Гэта ўключае 1 пазаштатную копію і 2 лакальныя копіі.",
|
||||||
|
"backup_onboarding_description": "<backblaze-link> стратэгія рэзервавання 3-2-1 </backblaze-link> рэкамендавана для аховы вашых данных. Вы павінны захоўваць копіі вашых загружаных фота / відэа гэтак жа добра, як базу данных Immich для вычарпальна поўнага рэзервовага капіявання.",
|
||||||
|
"backup_onboarding_footer": "Каб атрымаць дадатковую інфармацыю пра рэзервовае капіраванне Immich, звярніцеся да <link>дакументацыі</link>.",
|
||||||
"backup_onboarding_parts_title": "Рэзервовая копія «3-2-1» уключае ў сябе:",
|
"backup_onboarding_parts_title": "Рэзервовая копія «3-2-1» уключае ў сябе:",
|
||||||
"backup_onboarding_title": "Рэзервовыя копіі",
|
"backup_onboarding_title": "Рэзервовыя копіі",
|
||||||
"backup_settings": "Налады рэзервовага капіявання",
|
"backup_settings": "Налады рэзервовага капіявання",
|
||||||
@@ -91,6 +96,8 @@
|
|||||||
"image_resolution": "Раздзяляльнасць",
|
"image_resolution": "Раздзяляльнасць",
|
||||||
"image_settings": "Налады відарыса",
|
"image_settings": "Налады відарыса",
|
||||||
"image_settings_description": "Кіруйце якасцю і раздзяляльнасцю сгенерыраваных відарысаў",
|
"image_settings_description": "Кіруйце якасцю і раздзяляльнасцю сгенерыраваных відарысаў",
|
||||||
|
"image_thumbnail_description": "Маленькая мініяцюра з выдаленымі метададзенымі, якая выкарыстоўваецца пры праглядзе груп фатаграфій, такіх як асноўная хроніка",
|
||||||
|
"image_thumbnail_quality_description": "Якасць мініяцюр ад 1 да 100. Чым вышэй якасць, тым лепш, але пры гэтым ствараюцца файлы большага памеру і можа знізіцца хуткасць водгуку прыкладання.",
|
||||||
"image_thumbnail_title": "Налады мініяцюр",
|
"image_thumbnail_title": "Налады мініяцюр",
|
||||||
"job_concurrency": "{job} канкурэнтнасць",
|
"job_concurrency": "{job} канкурэнтнасць",
|
||||||
"job_created": "Заданне створана",
|
"job_created": "Заданне створана",
|
||||||
@@ -98,6 +105,8 @@
|
|||||||
"job_settings": "Налады заданняў",
|
"job_settings": "Налады заданняў",
|
||||||
"job_settings_description": "Кіраваць наладамі адначасовага (паралельнага) выканання задання",
|
"job_settings_description": "Кіраваць наладамі адначасовага (паралельнага) выканання задання",
|
||||||
"job_status": "Становішча задання",
|
"job_status": "Становішча задання",
|
||||||
|
"jobs_delayed": "{jobCount, plural, other {# адкладзена}}",
|
||||||
|
"jobs_failed": "{jobCount, plural, other {# не выканалася}}",
|
||||||
"library_created": "Створана бібліятэка: {library}",
|
"library_created": "Створана бібліятэка: {library}",
|
||||||
"library_deleted": "Бібліятэка выдалена",
|
"library_deleted": "Бібліятэка выдалена",
|
||||||
"library_scanning": "Сканаванне па раскладзе",
|
"library_scanning": "Сканаванне па раскладзе",
|
||||||
@@ -154,6 +163,9 @@
|
|||||||
"trash_settings": "Налады сметніцы",
|
"trash_settings": "Налады сметніцы",
|
||||||
"trash_settings_description": "Кіраванне наладамі сметніцы",
|
"trash_settings_description": "Кіраванне наладамі сметніцы",
|
||||||
"user_cleanup_job": "Ачыстка карыстальніка",
|
"user_cleanup_job": "Ачыстка карыстальніка",
|
||||||
|
"user_delete_delay": "Уліковы запіс <b>{user}</b> і актывы будуць запланаваны для канчатковага выдалення праз {delay, plural, one {# дзень} few {# дні} many {# дзён} other {# дзён}}.",
|
||||||
|
"user_delete_delay_settings_description": "Колькасць дзён пасля выдалення, па заканчэнні якіх уліковы запіс карыстальніка і яго актывы будуць выдаленыя незваротна. Заданне на выдаленне карыстальніка запускаецца апоўначы для праверкі гатоўнасці карыстальнікаў да выдалення. Змены ў гэтым параметры будуць улічаныя пры наступным выкананні.",
|
||||||
|
"user_delete_immediately": "Уліковы запіс <b>{user}</b> і актывы будуць <b>неадкладна</b> змешчаны ў чаргу на канчатковае выдаленне.",
|
||||||
"user_management": "Кіраванне карыстальнікамі",
|
"user_management": "Кіраванне карыстальнікамі",
|
||||||
"user_password_has_been_reset": "Пароль карыстальніка быў скінуты:",
|
"user_password_has_been_reset": "Пароль карыстальніка быў скінуты:",
|
||||||
"user_password_reset_description": "Задайце карыстальніку часовы пароль і паведаміце яму, што пры наступным уваходзе ў сістэму яму трэба будзе змяніць пароль.",
|
"user_password_reset_description": "Задайце карыстальніку часовы пароль і паведаміце яму, што пры наступным уваходзе ў сістэму яму трэба будзе змяніць пароль.",
|
||||||
@@ -317,7 +329,6 @@
|
|||||||
"edit_tag": "Рэдагаваць тэг",
|
"edit_tag": "Рэдагаваць тэг",
|
||||||
"edit_title": "Рэдагаваць загаловак",
|
"edit_title": "Рэдагаваць загаловак",
|
||||||
"edit_user": "Рэдагаваць карыстальніка",
|
"edit_user": "Рэдагаваць карыстальніка",
|
||||||
"edited": "Адрэдагавана",
|
|
||||||
"editor": "Рэдактар",
|
"editor": "Рэдактар",
|
||||||
"editor_close_without_save_prompt": "Змены не будуць захаваны",
|
"editor_close_without_save_prompt": "Змены не будуць захаваны",
|
||||||
"editor_close_without_save_title": "Закрыць рэдактар?",
|
"editor_close_without_save_title": "Закрыць рэдактар?",
|
||||||
|
|||||||
64
i18n/bg.json
@@ -28,10 +28,12 @@
|
|||||||
"add_to_album": "Добави към албум",
|
"add_to_album": "Добави към албум",
|
||||||
"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}",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "Някои локални файлове не успяха да се добавят към албума",
|
||||||
"add_to_album_toggle": "Сменете избора за {album}",
|
"add_to_album_toggle": "Сменете избора за {album}",
|
||||||
"add_to_albums": "Добавяне в албуми",
|
"add_to_albums": "Добавяне в албуми",
|
||||||
"add_to_albums_count": "Добавяне в албуми ({count})",
|
"add_to_albums_count": "Добавяне в албуми ({count})",
|
||||||
"add_to_shared_album": "Добави към споделен албум",
|
"add_to_shared_album": "Добави към споделен албум",
|
||||||
|
"add_upload_to_stack": "Добави качените в група",
|
||||||
"add_url": "Добави URL",
|
"add_url": "Добави URL",
|
||||||
"added_to_archive": "Добавено към архива",
|
"added_to_archive": "Добавено към архива",
|
||||||
"added_to_favorites": "Добавени към любимите ви",
|
"added_to_favorites": "Добавени към любимите ви",
|
||||||
@@ -89,7 +91,7 @@
|
|||||||
"image_prefer_embedded_preview_setting_description": "Използване на вградените миниатюри в RAW снимките като входни за обработка на изображенията, когато има такива. Това може да доведе до по-точни цветове за някои изображения, но качеството на прегледите зависи от камерата и изображението може да има повече компресионни артефакти.",
|
"image_prefer_embedded_preview_setting_description": "Използване на вградените миниатюри в RAW снимките като входни за обработка на изображенията, когато има такива. Това може да доведе до по-точни цветове за някои изображения, но качеството на прегледите зависи от камерата и изображението може да има повече компресионни артефакти.",
|
||||||
"image_prefer_wide_gamut": "Предпочитане на широка гама",
|
"image_prefer_wide_gamut": "Предпочитане на широка гама",
|
||||||
"image_prefer_wide_gamut_setting_description": "Използване на Display P3 за миниатюри. Това запазва по-добре жизнеността на изображенията с широки цветови пространства, но изображенията може да изглеждат по различен начин на стари устройства със стара версия на браузъра. sRGB изображенията се запазват като sRGB, за да се избегнат цветови промени.",
|
"image_prefer_wide_gamut_setting_description": "Използване на Display P3 за миниатюри. Това запазва по-добре жизнеността на изображенията с широки цветови пространства, но изображенията може да изглеждат по различен начин на стари устройства със стара версия на браузъра. sRGB изображенията се запазват като sRGB, за да се избегнат цветови промени.",
|
||||||
"image_preview_description": "Среден размер на изображението с премахнати метаданни, използвано при преглед на един актив и за машинно обучение",
|
"image_preview_description": "Среден размер на изображението с премахнати метаданни, използвано при преглед на един елемент и за машинно обучение",
|
||||||
"image_preview_quality_description": "Качество на предварителния преглед от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението. Задаването на ниска стойност може да повлияе на качеството на машинното обучение.",
|
"image_preview_quality_description": "Качество на предварителния преглед от 1 до 100. По-високата стойност е по-добра, но води до по-големи файлове и може да намали бързодействието на приложението. Задаването на ниска стойност може да повлияе на качеството на машинното обучение.",
|
||||||
"image_preview_title": "Настройки на прегледа",
|
"image_preview_title": "Настройки на прегледа",
|
||||||
"image_quality": "Качество",
|
"image_quality": "Качество",
|
||||||
@@ -116,13 +118,20 @@
|
|||||||
"library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката",
|
"library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката",
|
||||||
"library_settings": "Външна библиотека",
|
"library_settings": "Външна библиотека",
|
||||||
"library_settings_description": "Управление на настройките за външна библиотека",
|
"library_settings_description": "Управление на настройките за външна библиотека",
|
||||||
"library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени активи",
|
"library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени елементи",
|
||||||
"library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека",
|
"library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека",
|
||||||
"library_watching_settings": "Наблюдаване на библиотеката (ЕКСПЕРИМЕНТАЛНО)",
|
"library_watching_settings": "Наблюдаване на библиотеката [ЕКСПЕРИМЕНТАЛНО]",
|
||||||
"library_watching_settings_description": "Автоматично наблюдавай за променени файлове",
|
"library_watching_settings_description": "Автоматично наблюдавай за променени файлове",
|
||||||
"logging_enable_description": "Включване на запис (логове)",
|
"logging_enable_description": "Включване на запис (логове)",
|
||||||
"logging_level_description": "Когато е включено, какво ниво на записване да се използва.",
|
"logging_level_description": "Когато е включено, какво ниво на записване да се използва.",
|
||||||
"logging_settings": "Записване",
|
"logging_settings": "Записване",
|
||||||
|
"machine_learning_availability_checks": "Проверки за наличност",
|
||||||
|
"machine_learning_availability_checks_description": "Автоматично откриване и предпочитане на налични сървъри за машинно обучение",
|
||||||
|
"machine_learning_availability_checks_enabled": "Активиране на проверки за наличност",
|
||||||
|
"machine_learning_availability_checks_interval": "Интервал на проверяване",
|
||||||
|
"machine_learning_availability_checks_interval_description": "Време в милисекунди между проверките за наличност",
|
||||||
|
"machine_learning_availability_checks_timeout": "Време за изчакване на отговор",
|
||||||
|
"machine_learning_availability_checks_timeout_description": "Време за изчакване на отговор в милисекунди при проверка на наличност",
|
||||||
"machine_learning_clip_model": "CLIP модел",
|
"machine_learning_clip_model": "CLIP модел",
|
||||||
"machine_learning_clip_model_description": "Името на CLIP модела, посочен <link>тук</link>. Имайте предвид, че при промяна на модела трябва да стартирате отново задачата \"Интелигентно Търсене\" за всички изображения.",
|
"machine_learning_clip_model_description": "Името на CLIP модела, посочен <link>тук</link>. Имайте предвид, че при промяна на модела трябва да стартирате отново задачата \"Интелигентно Търсене\" за всички изображения.",
|
||||||
"machine_learning_duplicate_detection": "Откриване на дубликати",
|
"machine_learning_duplicate_detection": "Откриване на дубликати",
|
||||||
@@ -170,7 +179,7 @@
|
|||||||
"memory_cleanup_job": "Почистване на паметта",
|
"memory_cleanup_job": "Почистване на паметта",
|
||||||
"memory_generate_job": "Генериране на паметта",
|
"memory_generate_job": "Генериране на паметта",
|
||||||
"metadata_extraction_job": "Извличане на метаданни",
|
"metadata_extraction_job": "Извличане на метаданни",
|
||||||
"metadata_extraction_job_description": "Извличане на метаданни от всеки от елемент, като GPS локация, лица и резолюция на файловете",
|
"metadata_extraction_job_description": "Извличане на метаданни от всеки елемент, като GPS локация, лица и резолюция на файловете",
|
||||||
"metadata_faces_import_setting": "Включи импорт на лице",
|
"metadata_faces_import_setting": "Включи импорт на лице",
|
||||||
"metadata_faces_import_setting_description": "Импортирай лица от EXIF данни и помощни файлове",
|
"metadata_faces_import_setting_description": "Импортирай лица от EXIF данни и помощни файлове",
|
||||||
"metadata_settings": "Опции за метаданни",
|
"metadata_settings": "Опции за метаданни",
|
||||||
@@ -202,6 +211,8 @@
|
|||||||
"notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)",
|
"notification_email_ignore_certificate_errors_description": "Игнорирай грешки свързани с валидация на TLS сертификат (не се препоръчва)",
|
||||||
"notification_email_password_description": "Парола използвана за удостоверяване пред сървъра за електронна поща",
|
"notification_email_password_description": "Парола използвана за удостоверяване пред сървъра за електронна поща",
|
||||||
"notification_email_port_description": "Порт на сървъра за електронна поща (например 25, 465 или 587)",
|
"notification_email_port_description": "Порт на сървъра за електронна поща (например 25, 465 или 587)",
|
||||||
|
"notification_email_secure": "SMTPS",
|
||||||
|
"notification_email_secure_description": "Използвай SMTPS (SMTP по TLS)",
|
||||||
"notification_email_sent_test_email_button": "Изпрати тестов имейл и запази",
|
"notification_email_sent_test_email_button": "Изпрати тестов имейл и запази",
|
||||||
"notification_email_setting_description": "Настройки за изпращане на имейл известия",
|
"notification_email_setting_description": "Настройки за изпращане на имейл известия",
|
||||||
"notification_email_test_email": "Изпрати тестов имейл",
|
"notification_email_test_email": "Изпрати тестов имейл",
|
||||||
@@ -324,7 +335,7 @@
|
|||||||
"transcoding_max_b_frames": "Максимални B-фрейма",
|
"transcoding_max_b_frames": "Максимални B-фрейма",
|
||||||
"transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.",
|
"transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.",
|
||||||
"transcoding_max_bitrate": "Максимален битрейт",
|
"transcoding_max_bitrate": "Максимален битрейт",
|
||||||
"transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0.",
|
"transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0. Когато не е зададена мерна единица, подразбира се k (kbit/s); така 5000, 5000k и 5M (Mbit/s) са еквивалентни.",
|
||||||
"transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри",
|
"transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри",
|
||||||
"transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.",
|
"transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.",
|
||||||
"transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат",
|
"transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат",
|
||||||
@@ -342,7 +353,7 @@
|
|||||||
"transcoding_target_resolution": "Целева резолюция",
|
"transcoding_target_resolution": "Целева резолюция",
|
||||||
"transcoding_target_resolution_description": "По-високите разделителни способности могат да представят повече детайли, но отнемат повече време за разкодиране, имат по-големи размери на файловете и могат да намалят отзивчивостта на приложението.",
|
"transcoding_target_resolution_description": "По-високите разделителни способности могат да представят повече детайли, но отнемат повече време за разкодиране, имат по-големи размери на файловете и могат да намалят отзивчивостта на приложението.",
|
||||||
"transcoding_temporal_aq": "Темпорален AQ",
|
"transcoding_temporal_aq": "Темпорален AQ",
|
||||||
"transcoding_temporal_aq_description": "Само за NVENC. Повишава качеството на сцени с висока детайлност и ниско ниво на движение. Може да не е съвместимо с по-стари устройства.",
|
"transcoding_temporal_aq_description": "Само за NVENC. Повишава качеството на сцени с висока детайлност и малко движение. Може да не е съвместимо с по-стари устройства.",
|
||||||
"transcoding_threads": "Нишки",
|
"transcoding_threads": "Нишки",
|
||||||
"transcoding_threads_description": "По-високите стойности водят до по-бързо разкодиране, но оставят по-малко място за сървъра да обработва други задачи, докато е активен. Тази стойност не трябва да надвишава броя на процесорните ядра. Увеличава максимално използването, ако е зададено на 0.",
|
"transcoding_threads_description": "По-високите стойности водят до по-бързо разкодиране, но оставят по-малко място за сървъра да обработва други задачи, докато е активен. Тази стойност не трябва да надвишава броя на процесорните ядра. Увеличава максимално използването, ако е зададено на 0.",
|
||||||
"transcoding_tone_mapping": "Тонално картографиране",
|
"transcoding_tone_mapping": "Тонално картографиране",
|
||||||
@@ -393,11 +404,11 @@
|
|||||||
"advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.",
|
"advanced_settings_prefer_remote_subtitle": "Някои устройства са твърде бавни за да генерират миниатюри. Активирай тази опция за да се зареждат винаги от сървъра.",
|
||||||
"advanced_settings_prefer_remote_title": "Предпочитай изображенията на сървъра",
|
"advanced_settings_prefer_remote_title": "Предпочитай изображенията на сървъра",
|
||||||
"advanced_settings_proxy_headers_subtitle": "Дефиниране на прокси хедъри, които Immich трябва да изпраща с всяка мрежова заявка",
|
"advanced_settings_proxy_headers_subtitle": "Дефиниране на прокси хедъри, които Immich трябва да изпраща с всяка мрежова заявка",
|
||||||
"advanced_settings_proxy_headers_title": "Прокси хедъри",
|
"advanced_settings_proxy_headers_title": "Прокси хедъри [ЕКСПЕРИМЕНТАЛНО]",
|
||||||
"advanced_settings_readonly_mode_subtitle": "Активира режима \"само за четене\", при който снимките могат да бъдат разглеждани, но неща като избор на няколко изображения, споделяне, изтриване са забранени. Активиране/деактивиране на режима само за четене става от картинката-аватар на потребителя от основния екран",
|
"advanced_settings_readonly_mode_subtitle": "Активира режима \"само за четене\", при който снимките могат да бъдат разглеждани, но неща като избор на няколко изображения, споделяне, изтриване са забранени. Активиране/деактивиране на режима само за четене става от картинката-аватар на потребителя от основния екран",
|
||||||
"advanced_settings_readonly_mode_title": "Режим само за четене",
|
"advanced_settings_readonly_mode_title": "Режим само за четене",
|
||||||
"advanced_settings_self_signed_ssl_subtitle": "Пропуска проверката на SSL-сертификата на сървъра. Изисква се при самоподписани сертификати.",
|
"advanced_settings_self_signed_ssl_subtitle": "Пропуска проверката на SSL-сертификата на сървъра. Изисква се при самоподписани сертификати.",
|
||||||
"advanced_settings_self_signed_ssl_title": "Разреши самоподписани SSL сертификати",
|
"advanced_settings_self_signed_ssl_title": "Разреши самоподписани SSL сертификати [ЕКСПЕРИМЕНТАЛНО]",
|
||||||
"advanced_settings_sync_remote_deletions_subtitle": "Автоматично изтрии или възстанови обект на това устройство, когато действието е извършено през уеб-интерфейса",
|
"advanced_settings_sync_remote_deletions_subtitle": "Автоматично изтрии или възстанови обект на това устройство, когато действието е извършено през уеб-интерфейса",
|
||||||
"advanced_settings_sync_remote_deletions_title": "Синхронизация на дистанционни изтривания [ЕКСПЕРИМЕНТАЛНО]",
|
"advanced_settings_sync_remote_deletions_title": "Синхронизация на дистанционни изтривания [ЕКСПЕРИМЕНТАЛНО]",
|
||||||
"advanced_settings_tile_subtitle": "Разширени потребителски настройки",
|
"advanced_settings_tile_subtitle": "Разширени потребителски настройки",
|
||||||
@@ -457,10 +468,14 @@
|
|||||||
"api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.",
|
"api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.",
|
||||||
"api_key_empty": "Името на вашия API ключ не трябва да е празно",
|
"api_key_empty": "Името на вашия API ключ не трябва да е празно",
|
||||||
"api_keys": "API ключове",
|
"api_keys": "API ключове",
|
||||||
|
"app_architecture_variant": "Вариант (Ахитектура)",
|
||||||
"app_bar_signout_dialog_content": "Наистина ли искате да излезете?",
|
"app_bar_signout_dialog_content": "Наистина ли искате да излезете?",
|
||||||
"app_bar_signout_dialog_ok": "Да",
|
"app_bar_signout_dialog_ok": "Да",
|
||||||
"app_bar_signout_dialog_title": "Излез от профила",
|
"app_bar_signout_dialog_title": "Излез от профила",
|
||||||
|
"app_download_links": "Линкове за сваляне на приложението",
|
||||||
"app_settings": "Настройки ма приложението",
|
"app_settings": "Настройки ма приложението",
|
||||||
|
"app_stores": "Магазини за приложения",
|
||||||
|
"app_update_available": "Налична е нова версия",
|
||||||
"appears_in": "Излиза в",
|
"appears_in": "Излиза в",
|
||||||
"apply_count": "Приложи ({count, number})",
|
"apply_count": "Приложи ({count, number})",
|
||||||
"archive": "Архив",
|
"archive": "Архив",
|
||||||
@@ -544,6 +559,7 @@
|
|||||||
"backup_albums_sync": "Синхронизиране на архивите",
|
"backup_albums_sync": "Синхронизиране на архивите",
|
||||||
"backup_all": "Всичко",
|
"backup_all": "Всичко",
|
||||||
"backup_background_service_backup_failed_message": "Неуспешно архивиране. Нов опит…",
|
"backup_background_service_backup_failed_message": "Неуспешно архивиране. Нов опит…",
|
||||||
|
"backup_background_service_complete_notification": "Завърши архивирането на обектите",
|
||||||
"backup_background_service_connection_failed_message": "Неуспешно свързване към сървъра. Нов опит…",
|
"backup_background_service_connection_failed_message": "Неуспешно свързване към сървъра. Нов опит…",
|
||||||
"backup_background_service_current_upload_notification": "Зареждам {filename}",
|
"backup_background_service_current_upload_notification": "Зареждам {filename}",
|
||||||
"backup_background_service_default_notification": "Търсене на нови обекти…",
|
"backup_background_service_default_notification": "Търсене на нови обекти…",
|
||||||
@@ -591,6 +607,7 @@
|
|||||||
"backup_controller_page_turn_on": "Включи архивиране в активен режим",
|
"backup_controller_page_turn_on": "Включи архивиране в активен режим",
|
||||||
"backup_controller_page_uploading_file_info": "Инфо за архивирания файл",
|
"backup_controller_page_uploading_file_info": "Инфо за архивирания файл",
|
||||||
"backup_err_only_album": "Не може да се премахне единствения албум",
|
"backup_err_only_album": "Не може да се премахне единствения албум",
|
||||||
|
"backup_error_sync_failed": "Синхронизацията е неуспешна. Резервното копие не може да се обработи.",
|
||||||
"backup_info_card_assets": "обекта",
|
"backup_info_card_assets": "обекта",
|
||||||
"backup_manual_cancelled": "Отменено",
|
"backup_manual_cancelled": "Отменено",
|
||||||
"backup_manual_in_progress": "Върви архивиране. Опитай след малко",
|
"backup_manual_in_progress": "Върви архивиране. Опитай след малко",
|
||||||
@@ -678,8 +695,8 @@
|
|||||||
"client_cert_import_success_msg": "Клиентския сертификат е импортиран",
|
"client_cert_import_success_msg": "Клиентския сертификат е импортиран",
|
||||||
"client_cert_invalid_msg": "Невалиден сертификат или грешна парола",
|
"client_cert_invalid_msg": "Невалиден сертификат или грешна парола",
|
||||||
"client_cert_remove_msg": "Клиентския сертификат е премахнат",
|
"client_cert_remove_msg": "Клиентския сертификат е премахнат",
|
||||||
"client_cert_subtitle": "Поддържа се само формат PKCS12 (.p12, .pfx). Импорт и премахване на сертификат може само преди вписване в системата",
|
"client_cert_subtitle": "Поддържа се само формат PKCS12 (.p12, .pfx). Импорт/премахване на сертификат може само преди вписване в системата",
|
||||||
"client_cert_title": "Клиентски SSL сертификат",
|
"client_cert_title": "Клиентски SSL сертификат [ЕКСПЕРИМЕНТАЛНО]",
|
||||||
"clockwise": "По часовниковата стрелка",
|
"clockwise": "По часовниковата стрелка",
|
||||||
"close": "Затвори",
|
"close": "Затвори",
|
||||||
"collapse": "Свиване",
|
"collapse": "Свиване",
|
||||||
@@ -691,7 +708,6 @@
|
|||||||
"comments_and_likes": "Коментари и харесвания",
|
"comments_and_likes": "Коментари и харесвания",
|
||||||
"comments_are_disabled": "Коментарите са деактивирани",
|
"comments_are_disabled": "Коментарите са деактивирани",
|
||||||
"common_create_new_album": "Създай нов албум",
|
"common_create_new_album": "Създай нов албум",
|
||||||
"common_server_error": "Моля, проверете мрежовата връзка, убедете се, че сървъра е достъпен и версиите на сървъра и приложението са съвместими.",
|
|
||||||
"completed": "Завършено",
|
"completed": "Завършено",
|
||||||
"confirm": "Потвърди",
|
"confirm": "Потвърди",
|
||||||
"confirm_admin_password": "Потвърждаване на паролата на администратора",
|
"confirm_admin_password": "Потвърждаване на паролата на администратора",
|
||||||
@@ -730,6 +746,7 @@
|
|||||||
"create": "Създай",
|
"create": "Създай",
|
||||||
"create_album": "Създай албум",
|
"create_album": "Създай албум",
|
||||||
"create_album_page_untitled": "Без заглавие",
|
"create_album_page_untitled": "Без заглавие",
|
||||||
|
"create_api_key": "Създайте API ключ",
|
||||||
"create_library": "Създай библиотека",
|
"create_library": "Създай библиотека",
|
||||||
"create_link": "Създай линк",
|
"create_link": "Създай линк",
|
||||||
"create_link_to_share": "Създаване на линк за споделяне",
|
"create_link_to_share": "Създаване на линк за споделяне",
|
||||||
@@ -873,7 +890,6 @@
|
|||||||
"edit_tag": "Редактирай таг",
|
"edit_tag": "Редактирай таг",
|
||||||
"edit_title": "Редактиране на заглавието",
|
"edit_title": "Редактиране на заглавието",
|
||||||
"edit_user": "Редактиране на потребител",
|
"edit_user": "Редактиране на потребител",
|
||||||
"edited": "Редактирано",
|
|
||||||
"editor": "Редактор",
|
"editor": "Редактор",
|
||||||
"editor_close_without_save_prompt": "Промените няма да бъдат запазени",
|
"editor_close_without_save_prompt": "Промените няма да бъдат запазени",
|
||||||
"editor_close_without_save_title": "Затваряне на редактора?",
|
"editor_close_without_save_title": "Затваряне на редактора?",
|
||||||
@@ -1029,6 +1045,7 @@
|
|||||||
"exif_bottom_sheet_description_error": "Неуспешно обновяване на описание",
|
"exif_bottom_sheet_description_error": "Неуспешно обновяване на описание",
|
||||||
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
"exif_bottom_sheet_details": "ПОДРОБНОСТИ",
|
||||||
"exif_bottom_sheet_location": "МЯСТО",
|
"exif_bottom_sheet_location": "МЯСТО",
|
||||||
|
"exif_bottom_sheet_no_description": "Няма описание",
|
||||||
"exif_bottom_sheet_people": "ХОРА",
|
"exif_bottom_sheet_people": "ХОРА",
|
||||||
"exif_bottom_sheet_person_add_person": "Добави име",
|
"exif_bottom_sheet_person_add_person": "Добави име",
|
||||||
"exit_slideshow": "Изход от слайдшоуто",
|
"exit_slideshow": "Изход от слайдшоуто",
|
||||||
@@ -1110,7 +1127,6 @@
|
|||||||
"header_settings_field_validator_msg": "Недопустимо е да няма стойност",
|
"header_settings_field_validator_msg": "Недопустимо е да няма стойност",
|
||||||
"header_settings_header_name_input": "Име на заглавието",
|
"header_settings_header_name_input": "Име на заглавието",
|
||||||
"header_settings_header_value_input": "Стойност на заглавието",
|
"header_settings_header_value_input": "Стойност на заглавието",
|
||||||
"headers_settings_tile_subtitle": "Дефиниране на прокси заглавия, които приложението трябва да изпраща с всяка мрежова заявка",
|
|
||||||
"headers_settings_tile_title": "Потребителски прокси заглавия",
|
"headers_settings_tile_title": "Потребителски прокси заглавия",
|
||||||
"hi_user": "Здравей, {name} {email}",
|
"hi_user": "Здравей, {name} {email}",
|
||||||
"hide_all_people": "Скрий всички хора",
|
"hide_all_people": "Скрий всички хора",
|
||||||
@@ -1228,7 +1244,7 @@
|
|||||||
"local": "Локално",
|
"local": "Локално",
|
||||||
"local_asset_cast_failed": "Не може да се предава обект, който още не е качен на сървъра",
|
"local_asset_cast_failed": "Не може да се предава обект, който още не е качен на сървъра",
|
||||||
"local_assets": "Локални обекти",
|
"local_assets": "Локални обекти",
|
||||||
"local_media_summary": "Обобщение на локалните медийни файлове",
|
"local_media_summary": "Обобщение на локалните файлове",
|
||||||
"local_network": "Локална мрежа",
|
"local_network": "Локална мрежа",
|
||||||
"local_network_sheet_info": "Приложението ще се свърже със сървъра на този URL, когато устройството е свързано към зададената Wi-Fi мрежа",
|
"local_network_sheet_info": "Приложението ще се свърже със сървъра на този URL, когато устройството е свързано към зададената Wi-Fi мрежа",
|
||||||
"location_permission": "Разрешение за местоположение",
|
"location_permission": "Разрешение за местоположение",
|
||||||
@@ -1335,9 +1351,11 @@
|
|||||||
"minute": "Минута",
|
"minute": "Минута",
|
||||||
"minutes": "Минути",
|
"minutes": "Минути",
|
||||||
"missing": "Липсващи",
|
"missing": "Липсващи",
|
||||||
|
"mobile_app": "Мобилно приложение",
|
||||||
|
"mobile_app_download_onboarding_note": "Свалете мобилното приложение Immich с някоя от следните опции",
|
||||||
"model": "Модел",
|
"model": "Модел",
|
||||||
"month": "Месец",
|
"month": "Месец",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM г",
|
||||||
"more": "Още",
|
"more": "Още",
|
||||||
"move": "Премести",
|
"move": "Премести",
|
||||||
"move_off_locked_folder": "Извади от заключената папка",
|
"move_off_locked_folder": "Извади от заключената папка",
|
||||||
@@ -1353,6 +1371,8 @@
|
|||||||
"my_albums": "Мои албуми",
|
"my_albums": "Мои албуми",
|
||||||
"name": "Име",
|
"name": "Име",
|
||||||
"name_or_nickname": "Име или прякор",
|
"name_or_nickname": "Име или прякор",
|
||||||
|
"navigate": "Придвижване",
|
||||||
|
"navigate_to_time": "Придвижване до момент във времето",
|
||||||
"network_requirement_photos_upload": "Използвай мобилни данни за архивиране на снимки",
|
"network_requirement_photos_upload": "Използвай мобилни данни за архивиране на снимки",
|
||||||
"network_requirement_videos_upload": "Използвай мобилни данни за архивиране на видео",
|
"network_requirement_videos_upload": "Използвай мобилни данни за архивиране на видео",
|
||||||
"network_requirements": "Изисквания към мрежата",
|
"network_requirements": "Изисквания към мрежата",
|
||||||
@@ -1362,6 +1382,7 @@
|
|||||||
"never": "Никога",
|
"never": "Никога",
|
||||||
"new_album": "Нов Албум",
|
"new_album": "Нов Албум",
|
||||||
"new_api_key": "Нов API ключ",
|
"new_api_key": "Нов API ключ",
|
||||||
|
"new_date_range": "Нов период от време",
|
||||||
"new_password": "Нова парола",
|
"new_password": "Нова парола",
|
||||||
"new_person": "Нов човек",
|
"new_person": "Нов човек",
|
||||||
"new_pin_code": "Нов PIN код",
|
"new_pin_code": "Нов PIN код",
|
||||||
@@ -1412,6 +1433,8 @@
|
|||||||
"notifications": "Известия",
|
"notifications": "Известия",
|
||||||
"notifications_setting_description": "Управление на известията",
|
"notifications_setting_description": "Управление на известията",
|
||||||
"oauth": "OAuth",
|
"oauth": "OAuth",
|
||||||
|
"obtainium_configurator": "Конфигуратор за получаване",
|
||||||
|
"obtainium_configurator_instructions": "Използвайте Obtainium за инсталация и обновяване на приложението за Android директно от GitHub на Immich. Създайте API ключ и изберете вариант за да създадете Obtainium конфигурационен линк",
|
||||||
"official_immich_resources": "Официална информация за Immich",
|
"official_immich_resources": "Официална информация за Immich",
|
||||||
"offline": "Офлайн",
|
"offline": "Офлайн",
|
||||||
"offset": "Отместване",
|
"offset": "Отместване",
|
||||||
@@ -1520,6 +1543,7 @@
|
|||||||
"port": "Порт",
|
"port": "Порт",
|
||||||
"preferences_settings_subtitle": "Управление на предпочитанията на приложението",
|
"preferences_settings_subtitle": "Управление на предпочитанията на приложението",
|
||||||
"preferences_settings_title": "Предпочитания",
|
"preferences_settings_title": "Предпочитания",
|
||||||
|
"preparing": "Подготовка",
|
||||||
"preset": "Шаблон",
|
"preset": "Шаблон",
|
||||||
"preview": "Прегледи",
|
"preview": "Прегледи",
|
||||||
"previous": "Предишно",
|
"previous": "Предишно",
|
||||||
@@ -1532,13 +1556,9 @@
|
|||||||
"privacy": "Поверителност",
|
"privacy": "Поверителност",
|
||||||
"profile": "Профил",
|
"profile": "Профил",
|
||||||
"profile_drawer_app_logs": "Дневник",
|
"profile_drawer_app_logs": "Дневник",
|
||||||
"profile_drawer_client_out_of_date_major": "Мобилното приложение е остаряло. Моля, актуализирай до най-новата основна версия.",
|
|
||||||
"profile_drawer_client_out_of_date_minor": "Мобилното приложение е остаряло. Моля, актуализирай до най-новата версия.",
|
|
||||||
"profile_drawer_client_server_up_to_date": "Клиента и сървъра са обновени",
|
"profile_drawer_client_server_up_to_date": "Клиента и сървъра са обновени",
|
||||||
"profile_drawer_github": "GitHub",
|
"profile_drawer_github": "GitHub",
|
||||||
"profile_drawer_readonly_mode": "Режима само за четене е активиран. С дълго натискане върху картиката-аватар на потребителя ще деактивирате само за четене.",
|
"profile_drawer_readonly_mode": "Режима само за четене е активиран. С дълго натискане върху картиката-аватар на потребителя ще деактивирате само за четене.",
|
||||||
"profile_drawer_server_out_of_date_major": "Версията на сървъра е остаряла. Моля, актуализирай поне до последната главна версия.",
|
|
||||||
"profile_drawer_server_out_of_date_minor": "Версията на сървъра е остаряла. Моля, актуализирай до последната версия.",
|
|
||||||
"profile_image_of_user": "Профилна снимка на {user}",
|
"profile_image_of_user": "Профилна снимка на {user}",
|
||||||
"profile_picture_set": "Профилната снимка е сложена.",
|
"profile_picture_set": "Профилната снимка е сложена.",
|
||||||
"public_album": "Публичен албум",
|
"public_album": "Публичен албум",
|
||||||
@@ -1585,6 +1605,7 @@
|
|||||||
"read_changelog": "Прочети промените",
|
"read_changelog": "Прочети промените",
|
||||||
"readonly_mode_disabled": "Режима само за четене е деактивиран",
|
"readonly_mode_disabled": "Режима само за четене е деактивиран",
|
||||||
"readonly_mode_enabled": "Режима само за четене е активиран",
|
"readonly_mode_enabled": "Режима само за четене е активиран",
|
||||||
|
"ready_for_upload": "Готово за качване",
|
||||||
"reassign": "Преназначаване",
|
"reassign": "Преназначаване",
|
||||||
"reassigned_assets_to_existing_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на {name, select, null {съществуващ човек} other {{name}}}",
|
"reassigned_assets_to_existing_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на {name, select, null {съществуващ човек} other {{name}}}",
|
||||||
"reassigned_assets_to_new_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на нов човек",
|
"reassigned_assets_to_new_person": "Преназначени {count, plural, one {# елемент} other {# елемента}} на нов човек",
|
||||||
@@ -1766,6 +1787,7 @@
|
|||||||
"server_online": "Сървър онлайн",
|
"server_online": "Сървър онлайн",
|
||||||
"server_privacy": "Поверителност на сървъра",
|
"server_privacy": "Поверителност на сървъра",
|
||||||
"server_stats": "Статус на сървъра",
|
"server_stats": "Статус на сървъра",
|
||||||
|
"server_update_available": "Налична е нова версия за сървъра",
|
||||||
"server_version": "Версия на сървъра",
|
"server_version": "Версия на сървъра",
|
||||||
"set": "Задай",
|
"set": "Задай",
|
||||||
"set_as_album_cover": "Задаване като обложка на албум",
|
"set_as_album_cover": "Задаване като обложка на албум",
|
||||||
@@ -1794,6 +1816,8 @@
|
|||||||
"setting_notifications_subtitle": "Настройка на известията",
|
"setting_notifications_subtitle": "Настройка на известията",
|
||||||
"setting_notifications_total_progress_subtitle": "Общ напредък на зареждане (готово/всички обекти)",
|
"setting_notifications_total_progress_subtitle": "Общ напредък на зареждане (готово/всички обекти)",
|
||||||
"setting_notifications_total_progress_title": "Показване на общия напредък на архивиране във фонов режим",
|
"setting_notifications_total_progress_title": "Показване на общия напредък на архивиране във фонов режим",
|
||||||
|
"setting_video_viewer_auto_play_subtitle": "Автоматично започни възпроизвеждане на видео при отваряне",
|
||||||
|
"setting_video_viewer_auto_play_title": "Автоматично възпроизвеждане на видео",
|
||||||
"setting_video_viewer_looping_title": "Циклично",
|
"setting_video_viewer_looping_title": "Циклично",
|
||||||
"setting_video_viewer_original_video_subtitle": "При показване на видео от сървъра показвай оригиналния файл, дори и да има транскодирана версия. Може да използва буфериране. Локално наличните видеа се показват винаги в оригинал, независимо от тази настройка.",
|
"setting_video_viewer_original_video_subtitle": "При показване на видео от сървъра показвай оригиналния файл, дори и да има транскодирана версия. Може да използва буфериране. Локално наличните видеа се показват винаги в оригинал, независимо от тази настройка.",
|
||||||
"setting_video_viewer_original_video_title": "Само оригинално видео",
|
"setting_video_viewer_original_video_title": "Само оригинално видео",
|
||||||
@@ -1916,6 +1940,7 @@
|
|||||||
"stacktrace": "Следа на събраните",
|
"stacktrace": "Следа на събраните",
|
||||||
"start": "Старт",
|
"start": "Старт",
|
||||||
"start_date": "Начална дата",
|
"start_date": "Начална дата",
|
||||||
|
"start_date_before_end_date": "Началната дата трябва да бъде преди крайната дата",
|
||||||
"state": "Щат",
|
"state": "Щат",
|
||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"stop_casting": "Спри предаването",
|
"stop_casting": "Спри предаването",
|
||||||
@@ -2004,6 +2029,7 @@
|
|||||||
"troubleshoot": "Отстраняване на проблеми",
|
"troubleshoot": "Отстраняване на проблеми",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
"unable_to_change_pin_code": "Невъзможна промяна на PIN кода",
|
"unable_to_change_pin_code": "Невъзможна промяна на PIN кода",
|
||||||
|
"unable_to_check_version": "Невъзможна проверка на версията на приложението или сървъра",
|
||||||
"unable_to_setup_pin_code": "Неуспешно задаване на PIN кода",
|
"unable_to_setup_pin_code": "Неуспешно задаване на PIN кода",
|
||||||
"unarchive": "Разархивирай",
|
"unarchive": "Разархивирай",
|
||||||
"unarchive_action_prompt": "{count} са премахнати от Архива",
|
"unarchive_action_prompt": "{count} са премахнати от Архива",
|
||||||
|
|||||||