Compare commits

...

372 Commits

Author SHA1 Message Date
Alex Tran
34aa6c1329 Revert "feat(web): make asset grid row height responsive (#16970)"
This reverts commit 7075c5b393.
2025-03-26 09:08:23 -05:00
renovate[bot]
6ba94ac2f2 fix(deps): update machine-learning (#17078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 02:04:41 +00:00
Alex
dfb0626c91 fix(web): default search to context (#17118)
* fix(web): default search to context

* one liner

* Refactor
2025-03-25 17:57:12 -05:00
Alex
392ce7deb2 fix(web): albums display order again (#17117) 2025-03-25 22:14:00 +00:00
Mert
75df8fc10e refactor(server): bulk update exif (#17109)
* bulk update exif

* update sql

* update tests

* check job queeuing in test
2025-03-25 21:24:24 +00:00
github-actions
4cf7c55680 chore: version v1.130.1 2025-03-25 20:25:01 +00:00
Alex
b8ff93a3c9 chore: post release tasks (#17097) 2025-03-25 21:22:30 +01:00
Alex
37eb70c1eb fix(web): albums display order (#17106)
* fix(web): albums display order

* ergonomic

* perf ergonomic

* miss 1
2025-03-25 20:21:38 +00:00
renovate[bot]
aa4d6405f4 chore(deps): update base-image to v202503251114 (major) (#17085)
* chore(deps): update base-image to v202503251114

* fix: geocoding changes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-03-25 20:15:02 +00:00
Alex
ae447542a4 fix(web): asset navigation (#17104) 2025-03-25 15:00:30 -05:00
renovate[bot]
90f21d9047 chore(deps): pin dependencies (#17077)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 18:07:59 +00:00
renovate[bot]
567a92fe77 chore(deps): update dependency vite to v6.2.3 [security] (#17092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 18:07:25 +00:00
Weblate (bot)
8d6f5a2da9 chore(web): update translations (#16807)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/af/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/el/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Cyrl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/te/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ur/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: Agostino Pit <scheccia@gmail.com>
Co-authored-by: Andreas Johansen <andreas@josern.com>
Co-authored-by: Andreas Resch <weblate@resch.io>
Co-authored-by: Basilis Pantelis <bpantelis10@gmail.com>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Bonov <bonov@mail.ru>
Co-authored-by: C D <chinnidiwakar5@gmail.com>
Co-authored-by: Dawider10 <dawider110@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Focron <eliaelmas55@gmail.com>
Co-authored-by: Hurricane-32 <rodrigorimo@hotmail.com>
Co-authored-by: Indrek Haav <IndrekHaav@users.noreply.hosted.weblate.org>
Co-authored-by: Jean-Philippe Jodoin <jpjodoin@gmail.com>
Co-authored-by: Johan Ohly <johanohly@gmail.com>
Co-authored-by: Jørgen Næss Berge <jorgen.n.berge@gmail.com>
Co-authored-by: KecskeTech <teonyitas@gmail.com>
Co-authored-by: Knud Bachmann Røn <knudbachmannron@proton.me>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: MSDNicrosoft <wang3311835119@hotmail.com>
Co-authored-by: Matjaž T <matjaz@moj-svet.si>
Co-authored-by: Miki Mrvos <medolino2009@gmail.com>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: Nicolás McCarthy <nicomcc24@gmail.com>
Co-authored-by: Runskrift <anders@rimfrost.nu>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: Tomas Svec <svec.tomas@gmail.com>
Co-authored-by: Umesh Verma <umesh.verma236@gmail.com>
Co-authored-by: User 123456789 <w0g-1es-5qq@cld3.com>
Co-authored-by: Xo <xocodokie@users.noreply.hosted.weblate.org>
Co-authored-by: beckett <beckett.blakey@proton.me>
Co-authored-by: johnwoo_nl <pb@lunenburg-productions.nl>
Co-authored-by: millallo <millallo@tiscali.it>
Co-authored-by: pyccl <changcongliang@163.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: xuars <yago.rana.gayoso@gmail.com>
Co-authored-by: yousaf465 <yousaf465@gmail.com>
Co-authored-by: 灯笼 <gh_denglong@163.com>
2025-03-25 18:05:23 +00:00
bo0tzz
69662e1ab4 chore: shared renovate configuration (#16903)
* chore: shared renovate configuration

dep: https://github.com/immich-app/.github/pull/2

* chore: move typescript-projects and schedule to shared config

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-25 14:56:54 +00:00
github-actions
42b1efb679 chore: version v1.130.0 2025-03-25 13:48:45 +00:00
Snowknight26
b8bc11b0d9 fix(web): fix escape key not closing video player after seeking (#16860)
Co-authored-by: Yaros <thedj.launchpadder.dmx512@gmail.com>
2025-03-25 13:42:23 +00:00
Ben McCann
91065db3ff chore: migrate previously missed file to Svelte 5 (#17074) 2025-03-24 19:44:05 -04:00
Alex
c14668bdd4 chore(mobile): translation (#17073)
chore(mobile): translation update
2025-03-24 22:16:10 +00:00
Yaros
9757f70064 fix(web): not autoplay after moving playhead on paused video (#17038)
fix(web): prevent autoplay after moving playhead
2025-03-24 16:55:46 -05:00
Min Idzelis
4a0045db44 feat(web): support long-press selection on mobile web (#16906)
* feat(web): max grid row height responsive

* also gallery-viewer

* lint

* feat(web): support long-press selection on mobile web

* use svelte-gestures

* fix test

* Bug fix

* globalThis

* format

* revert generator

* Testing

* bad merge

* Fix typo/tap on thumbnail

* feat: shrink header on small screens (#16909)

* feat(web): shrink header on small screens

* fix test

* test

* Fix test

* Revert user-page-layout chagne

* Restore icons sizes, make consistent, improve logo responsiveness

* remove 4 more pix, lint

* lint

* chore

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>

* Revert "Testing"

This reverts commit 442f11c9e1.

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-24 16:36:36 -05:00
Yaros
a651a4bf0e chore(mobile): search field in separate widget (#16977)
* chore(mobile): search field in separate widget

* fix: removed unnecessary use of context

* chore: minor styling tweak

* fix: controller not bound

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-24 14:40:33 -05:00
Alex
8bc80076bb fix(mobile): show new local assets in offline mode (#16817)
fix: show new local assets in offline mode
2025-03-24 16:56:18 +00:00
Luigi311
89656472ef fix(mobile): fallback authentication client model/type to unknown (#17059)
mobile: fallback authentication client model/type to unknown

Add fallback for client model/type if device is not ios or android

Signed-off-by: Luis Garcia <git@luigi311.com>
2025-03-24 11:26:05 -05:00
Yaros
d9c6ec06e5 chore(mobile): suffix to app name on debug builds (#17044) 2025-03-24 11:23:07 -05:00
Mert
4bfef2460a docs: model benchmarks (#17036)
* model benchmarks

* minor fixes

* formatting

* docs build

* maybe fix reference

* clarify optimal

* use emojis

* wording

* wording

* clarify optimal wording

* bolding

* more detailed instructions

* clarify edge case fix

* early exit in dim loop
2025-03-24 12:02:33 -04:00
shenlong
ad151130f9 chore: rename user api interface (#17062)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-24 11:00:08 -05:00
Damiano Ferrari
a77608e36b fix(mobile): selectedIcon not set when the device is landscape (#17027) 2025-03-24 10:50:49 -05:00
Nicholas Flamy
9e015c7f97 feat: lint workflow files and others files in .github (#16914)
* add npm prettier dep and format script to .github folder

* initial work on prettier formatting test

* attempt index notation

* change name of .github job to be valid

* another use of index notation

this is getting overcomplicated

* Change job ID to `github-files-formatting` and chane the name to `.github Files Checks`

* Change job name to `.github Files Formatting`

* Update Makefile with .github module and `filter-out`s

* run prettier formatting as added in this PR
2025-03-24 10:49:18 -05:00
Damiano Ferrari
df8ba21b7d fix(mobile): Make icons consistent (all outlined) (#17028)
* fix(mobile): Make icons consistent (all outlined)

* fix(mobile): make `date_range` icon outlined
2025-03-24 10:10:15 -05:00
Yaros
a285b1898e fix(mobile): platform-dependent share icons & label (#17034)
fix: platform-dependant icons
2025-03-24 08:36:15 -05:00
Mert
6a8e38042d fix(ml): add librknnrt.so in rknn image (#17022)
add librknnrt.so
2025-03-21 16:57:14 -04:00
Min Idzelis
55b52ecbec feat: mobile-web improvements - scrubber (#16856)
* feat: mobile-web improvements - scrubber

* lint

* cruft

* lint

* fix: thumb style

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-21 18:00:24 +00:00
Alex
b5d5c40c69 fix(web): update stack state in timeline (#17021)
* fix(web): update stack state in timeline

* js docs

* fix: handle state update from unstack action from gallery viewer

* use navigate in View Stack notification

---------

Co-authored-by: Snowknight26 <Snowknight26@users.noreply.github.com>
2025-03-21 12:42:36 -05:00
Snowknight26
b00da18e84 fix(web): timeline renders nothing with an invalid asset scroll target (#16994)
* fix(web): fix asset grid showing nothing with an invalid asset target

* Deduplicate

* Scroll to position where appropriate

* a bit cleaner

* fix: lint

---------

Co-authored-by: Min Idzelis <min123@gmail.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-21 08:40:57 -05:00
Ben
3c87341902 fix(web): clicking away closes context menu (#16989)
* fix(web): clicking away closes context menu

* fix: use pointerdown event instead

* Revert "fix: use pointerdown event instead"

This reverts commit 0d2cf47194.
2025-03-21 08:39:41 -05:00
Alex
bcd9248b43 fix(web): timeline regression 2 (#16982)
* fix(web): timeline renders nothing after archiving in asset viewer

* fix(web): timeline renders nothing after archiving in asset viewer

* fix: ensure geometry updated when performing bulk action on all

* fix: album assets selection
2025-03-20 22:30:27 -05:00
Alex
dbc279f843 fix: gallery viewer sliding window offload assets (#17016)
* fix: gallery viewer sliding window offload assets

* fix: update bottom sliding window

* do not use negative

* Calculate offset before gallery

---------

Co-authored-by: Min Idzelis <min123@gmail.com>
2025-03-20 22:30:01 -05:00
Alex
21954939cf chore: remove limit in memory generation (#16920)
* chore: remove limit in memory generation

* generate sql

* chore: assets limit
2025-03-20 13:31:51 -05:00
renovate[bot]
d537f2c2d1 chore(deps): update github-actions (#16965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 13:17:36 -05:00
Snowknight26
1820c0aa0d fix(web): fix View in Timeline not working for stacked assets (#16993)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2025-03-20 13:17:14 -05:00
Yaros
0d805a1f5b fix(web): removed merge person with itself (#16987)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-20 15:25:42 +00:00
Alex
f5e6042eb1 fix: extend e2e test cookie expiration date (#17007)
fix: extend e2e test cookie
2025-03-20 16:17:55 +01:00
renovate[bot]
8de71ddaf3 chore(deps): update dependency flutter to v3.29.2 (#16963)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 10:58:57 -05:00
Min Idzelis
7075c5b393 feat(web): make asset grid row height responsive (#16970)
* feat(web): max grid row height responsive

* also gallery-viewer

* lint
2025-03-19 10:57:44 -05:00
Min Idzelis
9398b0d4b3 fix: regression in select-all (#16969)
* bug: select-all

* set->[] in interaction store, clear select-all on cancel

* feedback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-19 15:55:50 +00:00
renovate[bot]
1a0a9ef36c chore(deps): update base-image to v202503182202 (major) (#16968)
chore(deps): update base-image to v202503182202

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 00:28:32 -04:00
Dmitry Vakhnenko
ce456709b5 fix(web): reset selection state when adding assets to a album (#16880)
* fix(web): cancel multiselect before adding assets to album

* chore: format with prettier

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-19 00:11:58 +00:00
renovate[bot]
bc90678276 fix(deps): update machine-learning (#16966) 2025-03-18 23:07:21 +00:00
renovate[bot]
217a90bf61 chore(deps): update actions/download-artifact digest to b14cf4c (#16934)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 17:10:19 -05:00
Yaros
62ba8c3e71 fix(web): date alignment on timeline (#16961) 2025-03-18 21:55:36 +00:00
renovate[bot]
564724b398 fix(deps): update machine-learning (#16960)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 20:55:43 +00:00
renovate[bot]
cedeba8723 chore(deps): update prom/prometheus docker digest to 502ad90 (#16956)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 16:48:52 -04:00
bo0tzz
1d994333a6 fix: duplicated steps in docker workflow (#16952)
Not sure how that happened, maybe a bad merge conflict resolution?
2025-03-18 16:39:30 -04:00
renovate[bot]
db8155f738 fix(deps): update typescript-projects (#16945)
* fix(deps): update typescript-projects

* fix: very weird variables

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-03-18 20:29:21 +00:00
renovate[bot]
4d723f4b56 chore(deps): update dependency types-setuptools to v76 (#16949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 15:02:25 -04:00
renovate[bot]
898b3e75c2 fix(deps): update machine-learning (#16935)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 17:09:46 +00:00
bo0tzz
8c2d02c362 chore: run docs and cli builds on all PRs (#16954)
All the other workflows already do this.
2025-03-18 11:55:18 -05:00
Dmitry Vakhnenko
d7a6e78bf0 fix(server): /api/stacks does not handles primaryAssetId query param (#16868)
fix(server): add missing validation decorator
2025-03-18 11:54:50 -05:00
Viharm
8723f585e0 chore(docs): clarify missing ':ro' tag in volume mount as a warning (#16877)
📝 Clarify missing ':ro' tag in volume mount as a warning

Changed description in comment of example docker compose file to clarify it as a warning that Immich may delete it, instead of sounding as if it is ok to delete.

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-18 16:50:59 +00:00
Mert
9f46ba8eb4 fix(server): set pixel format when scaling and not tonemapping (#16932)
set pixel format when scaling and not tonemapping
2025-03-18 11:42:09 -05:00
Andreas
fe19f9ba84 fix(web): asset selection on memories page is broken (#16759)
* 16712: Proper intialisation of the memory store to avoid loading up duplicate object refs of the same asset.

* 16712: Add auth to memory mapping so isFavorite is actually return correctly from the server.

* 16712: Move logic that belongs in the store into the store.

* 16712: Cleanup.

* 16712: Fix init behaviour.

* 16712: Add comment.

* 16712: Make method private.

* 16712: Fix import.

* 16712: Fix format.

* 16712: Cleaner if/else and fix typo.

* fix: icon size mismatch

* 16712: Fixed up state machine managing memory playback:
* Updated to `Tween` (`tweened` was deprecated)
* Removed `resetPromise`. Setting progressController to 0 had the same effect, so not really sure why it was there?
* Removed the many duplicate places the `handleAction` method was called. Now we just called it on `afterNavigate` as well as when `galleryInView` or `$isViewing` state changes.

* 16712: Add aria tag.

* 16712: Fix memory player duplicate invocation bugs. Now we should only call 'reset' and 'play' once, after navigate/page load. This should hopefully fix all the various bugs around playback.

* 16712: Cleanup

* 16712: Cleanup

* 16712: Cleanup

* 16712: Cleanup

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-18 11:34:09 -05:00
renovate[bot]
b609f35841 chore(deps): update base-image to v20250318 (major) (#16950)
* chore(deps): update base-image to v20250318

* chore

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-18 11:07:11 -05:00
shenlong
9cf3b88f80 refactor(mobile): remove int user id (#16814)
* refactor: user entity

* chore: rebase fixes

* refactor: remove int user Id

* refactor: migrate store userId from int to string

* refactor: rename uid to id

* fix: migration

* pr feedback

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-18 11:05:37 -05:00
Min Idzelis
e96ffd43e7 feat: timeline performance (#16446)
* Squash - feature complete

* remove need to init assetstore

* More optimizations. No need to init. Fix tests

* lint

* add missing selector for e2e

* e2e selectors again

* Update: fully reactive store, some transitions, bugfixes

* merge fallout

* Test fallout

* safari quirk

* security

* lint

* lint

* Bug fixes

* lint/format

* accidental commit

* lock

* null check, more throttle

* revert long duration

* Fix intersection bounds

* Fix bugs in intersection calculation

* lint, tweak scrubber ui a tiny bit

* bugfix - deselecting asset doesnt work

* fix not loading bucket, scroll off-by-1 error, jsdoc, naming
2025-03-18 09:14:46 -05:00
shenlong
dd263b010c refactor(mobile): use user service methods (#16783)
* refactor: user entity

* chore: rebase fixes

* refactor(mobile): refactor to use user service methods

* fix: late init error

* fix: lint

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-18 08:32:33 -05:00
renovate[bot]
6c2985df26 chore(deps): update dependency @types/node to ^22.13.10 (#16944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 12:15:07 +01:00
Mert
2b37caba03 feat(ml): rocm (#16613)
* feat(ml): introduce support of onnxruntime-rocm for AMD GPU

* try mutex for algo cache

use OrtMutex

* bump versions, run on mich

use 3.12

use 1.19.2

* acquire lock before any changes can be made

guard algo benchmark results

mark mutex as mutable

re-add /bin/sh (?)

use 3.10

use 6.1.2

* use composite cache key

1.19.2

fix variable name

fix variable reference

aaaaaaaaaaaaaaaaaaaa

* bump deps

* disable algo caching

* fix gha

* try ubuntu runner

* actually fix the gha

* update patch

* skip mimalloc preload for rocm

* increase build threads

* increase timeout for rocm

* Revert "increase timeout for rocm"

This reverts commit 2c4452f5d132198ed381a7b262b4a5cab5114b5f.

* attempt migraphx

* set migraphx_home

* Revert "set migraphx_home"

This reverts commit c121d3e48754b3bce100636f8d666deec58a44b7.

* Revert "attempt migraphx"

This reverts commit 521f9fb72dbe506dc6cb8faeb6494817d87265c6.

* migraphx, take two

* bump rocm

* allow cpu

* try only targeting migraphx

* skip tests

* migraph 

* known issues

* target gfx900 and gfx1102

* mention `HSA_USE_SVM`

* update lock

* set device id for rocm

---------

Co-authored-by: Mehdi GHESH <mehdi.ghesh@hotmail.fr>
2025-03-17 21:08:19 +00:00
Jason Rasmussen
6a40aa83b7 refactor: better types for getList and getDeletedAfter (#16926) 2025-03-17 15:32:12 -04:00
Yaros
93907a89d8 fix(mobile): age calculation & formatting (#16833) 2025-03-17 13:51:17 -05:00
renovate[bot]
3ce8608662 chore(deps): update mcr.microsoft.com/devcontainers/typescript-node:22 docker digest to 2ef2373 (#16925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 14:07:09 -04:00
Abhinav Valecha
d0e283f687 feat(server): version command for immich-admin #9611 (#16924)
* feat(server): Add version command for immich-admin #9611

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-03-17 17:57:59 +00:00
Yaros
f8b40188e2 fix(mobile): change share icons for consistency (#16904) 2025-03-17 12:34:58 -05:00
renovate[bot]
9105e696bf chore(deps): pin github action dependencies (#16923)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 17:25:14 +00:00
bo0tzz
0a8135dde4 fix: docker workflow for rknn (#16922)
* fix: specify gha runner for rknn

* fix: remove s from platforms

* fix: merge job for rknn
2025-03-17 18:13:43 +01:00
Jason Rasmussen
0bb95544e5 chore: pin github action digests (#16875) 2025-03-17 11:30:13 -05:00
Yoni Yang
14c3b99c0f feat(ml): ML on Rockchip NPUs (#15241) 2025-03-17 12:04:08 -04:00
shenlong
1e184a70f1 refactor: cleanup background service (#16855)
refactor: background service

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-17 08:48:22 -05:00
Jason Rasmussen
9a4495eb5b refactor: use more immich ui buttons (#16840) 2025-03-14 09:38:06 -04:00
Jason Rasmussen
8ad95b368b feat: use immich ui components for dialog component (#16839) 2025-03-14 09:37:56 -04:00
shenlong
b778a86c99 refactor(mobile): move user service to domain (#16782)
* refactor: user entity

* chore: rebase fixes

* refactor(mobile): move user service to domain

* fix: timeline not visible on album selection page

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-13 22:20:26 -05:00
Jason Rasmussen
a65ce2ac55 refactor: immich logo assets (#16850) 2025-03-13 18:05:08 -04:00
Jason Rasmussen
f69d7e7bad chore: web cleanup (#16849) 2025-03-13 18:04:21 -04:00
ExceptionsOccur
858d1e9d9b fix(mobile): back gesture in asset selection page from an album (#16449)
* fix(mobile): the page for adding photos to the album cannot be navigated back using gestures #16409

* First-time return gesture adds the feature to cancel all current selections

---------

Co-authored-by: ExceptionsOccur <yuyu.tao@foxmail.com>
2025-03-13 11:37:05 +05:30
renovate[bot]
a1a61f19eb chore(deps): update typescript-projects (#16795)
* chore(deps): update typescript-projects

* fix: aria

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-03-12 23:20:26 +01:00
Jason Rasmussen
996ffed5eb fix: immich ui toggles and switches (#16834)
* fix: immich ui toggles and switches

* Update web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte

Co-authored-by: Alex <alex.tran1502@gmail.com>

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-12 16:25:27 -05:00
Jason Rasmussen
2d7a94ce23 feat: better library rename UX (#16837) 2025-03-12 16:00:16 -05:00
Jason Rasmussen
72a7be26c0 refactor: use immich/ui button component in user settings (#16836) 2025-03-12 15:56:55 -05:00
shenlong
77fad86b82 chore(mobile): bump dependency versions (#16823)
* chore(mobile): bump dep version

* reorganize files

* sort

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-12 14:33:11 +00:00
Yaros
52d90a8280 fix(web): fixed formatting of video length (#16829)
* fix(web): fixed formatting of video time

* shortened the condition
2025-03-12 09:18:43 -05:00
shenlong
d1c8fe5303 refactor: user entity (#16655)
* refactor: user entity

* fix: add users to album & user profile url

* chore: rebase fixes

* generate files

* fix(mobile): timeline not reset on login

* fix: test stub

* refactor: rename user model (#16813)

* refactor: rename user model

* simplify import

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>

* chore: generate files

* fix: use getAllAccessible instead of getAll

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-12 08:56:56 -05:00
Snowknight26
a75718ce99 fix(web): update search results when searching from info panel (#16729)
* fix(web): update search results when searching from info panel

* Prevent double search when using search bar

* Format/lint

* Fix infinite loading on intersect

* Remove redundant function
2025-03-11 17:23:25 -05:00
Nicholas Flamy
d72d715f6b fix(docs): logo not loading dark theme variant in production (#16820)
fix logo not loading dark theme variant in production
2025-03-11 17:13:25 -05:00
Jason Rasmussen
16fd19994b refactor: use factory and kysely types for partner repository (#16812) 2025-03-11 16:29:56 -04:00
Mert
83ed03920e fix(ml): dev environment dependencies (#16815)
use /opt/venv
2025-03-11 13:39:33 -05:00
bo0tzz
9c825e15de fix: run preview label remove job on PR close (#16811)
🤦
2025-03-11 15:26:09 +00:00
Andreas
b8acae2f21 feat(web): Add keyboard shortcut selection on grid (#16713)
* 15712: Added keyboard shortcuts for opening add to album modal and highlighting/selecting an album to add to.

* 15712: Re-factored logic from template code into script. Extracted new album button into separate cmponent.

* 15712: Document new keyboard shortucts now that they work everywhere.

* 15712: Extract some constants/helper functions.

* 15712: Missing comma.

* 15712: Pulled logic out into separate unit testable class.

* 15712: Added a unit test.

* 15712: Move the modal back up to keep the github PR happy.

* 15712: PR feedback - renamed typescript files and switch to class bind directive.

* 15712:Move selection modal into correct package.

* 15712: Better naming of module and files.

* 15712: Add asset highlight using arrow keys.

* 15172: Add escape behaviour everywhere.

* 15712: Don't allow highlighting past start or end.

* 15712: Clear the highlight on changes to the component state.

* 15712: Use focus to track highlighted element.

* 15712: Rename highlight -> focussed.

* 15712: Better naming.

* 15712: Cleanup.

* 15712: Cleanup & simplify.

* 15712: bugfix for clicking on button.

* 15712: Cleanup.

* 15712: Rollback unnecessary changes.

* 15712: Add unit test.

* 15712: Add thumbnail unit test.

* 15712: Prettier.

* 15712: Fix merge issue.

* 15712: Add shortcut info.

* 15712: Fix linter.
2025-03-11 10:18:14 -05:00
Alex
c80afea468 feat(web): better person naming interface (#16631)
* feat(web): better person naming interface

* feat(web): better person naming interface

* feat(web): better person naming interface

* feat(web): better person naming interface

* feat(web): better person naming interface

* feat(web): better person naming interface

* feat(web): better person naming interface
2025-03-11 10:08:52 -05:00
shenlong
6caa11d079 chore(mobile): use path provider foundation (#16804)
* chore(mobile): use path provider foundation

* chore: update podfile

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-11 09:26:43 -05:00
shenlong
653fa3f0b1 chore(mobile): add orientation tests for exif (#16806)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-11 09:25:46 -05:00
Alex
2be8b6c16d chore: ignore correct build folder (#16808) 2025-03-11 14:22:05 +00:00
Jonathan Jogenfors
6bb0aa217c fix(server): set unit test timezone to UTC (#16805) 2025-03-11 10:19:33 -04:00
bo0tzz
04fd83d9da chore: shared suffix for docker tags (#16727) 2025-03-11 12:25:10 +00:00
renovate[bot]
ba9e3715f0 chore(deps): update base-image to v20250311 (major) (#16803)
chore(deps): update base-image to v20250311

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-11 12:24:31 +00:00
shenlong
ac1b2d2fab chore(mobile): generated files and ci check (#16798)
* chore(mobile): more generated files

* ci: verify generated files in mobile are up-to-date

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-11 12:22:31 +00:00
Lorenzo Montanari
d7e0f0e70e feat(web): exposed a job to manually trigger database backup procedures (#16622)
* feat(web): exposed a new job to create a manual database backup

* chore(server): added a new test case

* chore(server): moved job to backup db into the create job popup

* remove irrelevant change

* openapi

* chore: formatting

* docs: trigger backup documentation

---------

Co-authored-by: Lorenzo Montanari <13736036+l0ll098@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2025-03-11 11:30:43 +00:00
Snowknight26
decc878267 feat(web): show full date when hovering over photos date groups (#16561)
* fix(web): Update asset grid date group titles to show full date

* Fix formatting
2025-03-11 11:18:29 +00:00
Zack Pollard
e0a09f2ea0 fix: weblate pre-job not running (#16802)
* chore: add debug step to weblate pre-job

* fix: weblate enforce lock missing needs for pre-job
2025-03-11 11:10:00 +00:00
sarunas-zilinskas
b9ecdf9286 chore: change k which stood for 1000 to more understandable notation of kbit/s (#16734) 2025-03-11 10:54:42 +00:00
Weblate (bot)
4c719cc3bb chore(web): update translations (#16252)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/af/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/el/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Cyrl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ur/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: Ahmad Amin <ahmadamindev@gmail.com>
Co-authored-by: Andreas Johansen <andreas@josern.com>
Co-authored-by: Aniruddha <aniruddha@aniruddhas.com>
Co-authored-by: Bader Alqahtani <baq100@gmail.com>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Björn Boström <weblate@boztrom.com>
Co-authored-by: Bonov <bonov@mail.ru>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: Carlo_Mava <carlomavaracchio@gmail.com>
Co-authored-by: Cem TURKER <forumcemturker@gmail.com>
Co-authored-by: Cohinem <twitch9ofe@gmail.com>
Co-authored-by: ConfusedAlex <alex@confusedalex.dev>
Co-authored-by: Damian Krysta <damian@krysta.dev>
Co-authored-by: Daniel A <aquino.daniel1994@ikmail.com>
Co-authored-by: Eric Lehmann Llevat <lemmi93@googlemail.com>
Co-authored-by: Eskuero <3skuero@gmail.com>
Co-authored-by: Etienne-Bdt <etienne.bardet@gmail.com>
Co-authored-by: FarSniper <ozmatlik@gmail.com>
Co-authored-by: Felipe Simões <felipebouabci@gmail.com>
Co-authored-by: Filip <fjokovic0@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Florian Ostertag <florian.kuepper@gmail.com>
Co-authored-by: Georgi Iliev <georgi.iliev533@outlook.com>
Co-authored-by: Hoi <Hoihoi@users.noreply.hosted.weblate.org>
Co-authored-by: Hurricane-32 <rodrigorimo@hotmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: Indrek Haav <IndrekHaav@users.noreply.hosted.weblate.org>
Co-authored-by: JohannesBoanerges <jb@johannes-boanerges.de>
Co-authored-by: Jonathan Jogenfors <jonathan@jogenfors.se>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Juan Palacios <mastergeek.juan@gmail.com>
Co-authored-by: Julius969 <juliusdjorup@proton.me>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Leonardo Patti <leonardo.patti90@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Macgyver <macgyver@users.noreply.hosted.weblate.org>
Co-authored-by: MaliciousSpark <fijalkowskikonras@gmail.com>
Co-authored-by: Marius Kavoliunas <kavoliunas.m@gmail.com>
Co-authored-by: Mateusz <account.srrr3@slmail.me>
Co-authored-by: Matjaž T <matjaz@moj-svet.si>
Co-authored-by: Medallyon <mbups98@gmail.com>
Co-authored-by: Miki Mrvos <medolino2009@gmail.com>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: Nir Cohen <nir10146@gmail.com>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Pavol Valko <xpaulos2@gmail.com>
Co-authored-by: Philipp Burndorfer <phi.bur@gmx.at>
Co-authored-by: Pixiii <imapixel00@gmail.com>
Co-authored-by: Runskrift <anders@rimfrost.nu>
Co-authored-by: Sandro <account@donner-nsu.de>
Co-authored-by: Santiago <santiwever@hotmail.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sheridan Jegels <sheridanjegels@gmail.com>
Co-authored-by: Stijn <gielisstijn@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: Toine Rademacher <hi@toine.zip>
Co-authored-by: Torin Wu <xuan329269@gmail.com>
Co-authored-by: Vesa Jylhä <vesa.jylha@gmail.com>
Co-authored-by: Vladimir <vladimir.stoev1015@gmail.com>
Co-authored-by: Xo <xocodokie@users.noreply.hosted.weblate.org>
Co-authored-by: Yaros <thedj.launchpadder.dmx512@gmail.com>
Co-authored-by: anton garcias <isaga.percompartir@gmail.com>
Co-authored-by: atsza661 <ats.altmets@gmail.com>
Co-authored-by: chamdim <chamdim@protonmail.com>
Co-authored-by: chapvic <victor@chapaev.org>
Co-authored-by: francesco stigliano <fra.stigliano@gmail.com>
Co-authored-by: icerocker <icerocker@users.noreply.hosted.weblate.org>
Co-authored-by: mattix7771 <mattione7@gmail.com>
Co-authored-by: pierrebengtsson <pierre.bengtsson@gmail.com>
Co-authored-by: pyccl <changcongliang@163.com>
Co-authored-by: qtm <qtm@users.noreply.hosted.weblate.org>
Co-authored-by: szelek <janek.szelewicz@gmail.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: xuars <yago.rana.gayoso@gmail.com>
Co-authored-by: Øyvind Hovden <oyvhov@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2025-03-11 10:48:34 +00:00
shenlong
81df812f56 fix(mobile): calculate isFlipped for exif from db (#16797)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-11 10:41:47 +00:00
Nicholas Flamy
f0f0056fe3 feat(docs): highlight active version in version switcher (#16790)
* docs: highlight active version in version switcher

* Add comment explaining workaround
2025-03-11 10:41:12 +00:00
renovate[bot]
48dddb78d4 chore(deps): update docker/setup-qemu-action action to v3.6.0 (#16794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-11 10:37:36 +00:00
Nicholas Flamy
5d86e6d2d3 fix(web): update old discord logo to new one (#16789)
* fix: update discord svg path and add viewbox

* fix formatting
2025-03-10 22:46:32 -05:00
Alex
75fa305e98 chore: flutter 3.29.1 (#16730)
* update dependencies

* update flutter version reference

* update flutter version reference

* update AndroidManifest with flutter_web_auth_2

* chore: lock file flutter version

* fix: ios build
2025-03-10 21:46:36 -05:00
renovate[bot]
8cd5aec4c5 chore(deps): update dependency @types/node to ^22.13.9 (#16792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 22:43:16 -04:00
renovate[bot]
cb489a1aa9 chore(deps): pin ghcr.io/astral-sh/uv docker tag to 562193a (#16791) 2025-03-10 22:23:50 -04:00
Jason Rasmussen
1382b27349 refactor: repository mocks (#16785) 2025-03-10 16:52:44 -04:00
Jason Rasmussen
1b35400043 chore: remove unused package (#16777) 2025-03-10 14:50:32 -04:00
Jason Rasmussen
a96bba4b26 feat: sync assets, partner assets, exif, and partner exif (#16658)
* feat: sync assets, partner assets, exif, and partner exif

Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>

* refactor: remove duplicate where clause and orderBy statements in sync queries

* fix: asset deletes not filtering by ownerId

---------

Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-10 12:05:39 -04:00
Jason Rasmussen
e97df503f2 refactor: api key spec to use factories (#16776) 2025-03-10 12:04:35 -04:00
renovate[bot]
fe959b2f05 fix(deps): update machine-learning (#16594) 2025-03-10 14:48:53 +00:00
Yaros
f794c3e0df feat(web): show birthdate on person page (#16772)
* feat(web): show birthdate on person page

* shorten null check

Co-authored-by: Jason Rasmussen <jason@rasm.me>

* directly use birthDate

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-10 14:47:44 +00:00
Alex
57272904d6 chore(mobile): upgrade maplibre (#16739)
* chore(mobile): upgrade maplibre

* chore(mobile): upgrade maplibre

* color
2025-03-10 09:41:43 -05:00
Jensen H
2496bd7514 docs: update unraid installation steps (#16766)
Update unraid.md

Current steps omit this key step, which results in the postgresql docker complaining about the data folder not being empty. (It tries to use the `/mnt/user/appdata` folder as its application data folder.
2025-03-10 08:56:42 +00:00
Nicholas Flamy
c6ede48e59 fix(server): set the dev server restart policy of the dev server container to match the other containers (#16753)
set the restart policy of the dev server container to match the other containers
2025-03-09 22:25:03 -05:00
Adam O'neill
70a08707d2 feat(web): remember search context (#16614)
* Retain search context in LocalStorage.

* Remove debug logging

* Prettier

* Added QueryType and VALID_QUERY_TYPES to $lib/constants

* Prettier

* Renamed VALID_QUERY_TYPES to fit the codestyle.

Ran prettier

* show current search type on search bar

* fix: linting

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-09 22:20:25 -05:00
Snowknight26
2f8e89c7ec feat(server): read Android and Sony video camera make/model (#16678)
* feat(server): read Android and Sony video camera exif data

* Remove a logger line
2025-03-09 22:20:11 -05:00
David Bourgault
9870ad9687 fix(server): adjust type of person.birthDate (#16628)
The API currently does not respect the documentation when returning a
person's birthDate. The doc/swagger says it will be of "YYYY-MM-DD"
format but the string is a full ISO8601-with-tz string. This causes
issue #16216 because the <input> tag is strict about supported value
formats.

I believe this was introduced by #15242 which switched some queries from
TypeORM to Kysely for the person repository. TypeORM does not parse
date, but our Kysely configuration does (explicitely).

This commits updates the types to represent both possibilities and ensure
the API always returns the correct format.
2025-03-09 21:32:05 -05:00
Lukas
097749d872 fix(web): add labels to memory lane buttons (#16664)
* fix(web): add labels to memory lane buttons

* use generic button labels
2025-03-09 21:31:55 -05:00
Yaros
bdabea4030 feat(mobile): locate in timeline (#16722)
* feat(mobile): view in timeline

* fix: throwing error on scroll

* only show option if not in photos tab
2025-03-09 21:31:34 -05:00
Mert
6da77600e5 chore(ml): uv (#16725)
* poetry to uv

* update ci

* remove caching

* add typeshed to dev

* no need for `--non-interactive`

* move backends to extras

* oopsie

* update ci
2025-03-09 21:30:16 -05:00
Daniel Dietzler
573d9a7733 fix: 🍪 packages confusion (#16735)
fix: cookie packages confusion
2025-03-09 21:03:10 -05:00
Corentin Hatte
2aac679185 fix(web): Update people-card favorite position (#16746)
Update people-card favorite position

Move heart icon a bit more inward to mak it more visible
2025-03-10 01:32:32 +00:00
Alex
82624b0979 chore(mobile): upgrade riverpod (#16742) 2025-03-09 20:30:58 -05:00
Alex
17c5094719 chore(mobile): upgrade flutter_web_auth_2 (#16741)
* chore(mobile): upgrade flutter_web_auth_2

* pod file
2025-03-09 20:26:37 -05:00
Matthew Momjian
051431b757 fix(docs): edge case when restoring dump that is unreadable as current user (#16758)
* new gunzip setup

* windows
2025-03-09 20:26:00 -05:00
Yaros
6c5f99c47a feat(mobile): person age on photo properties (#16728)
* feat(mobile): person age on photo properties

* switch to using placeholder
2025-03-08 23:02:40 +01:00
Jason Rasmussen
1e127ae3a1 refactor: migrate library spec to factories (#16711) 2025-03-08 13:44:36 -05:00
Jason Rasmussen
fd46d43726 chore: remove unused file (#16707) 2025-03-07 22:47:27 -06:00
Yaros
5252c013ec fix(mobile): fix notification icon not displaying properly (#16710) 2025-03-07 19:08:53 -06:00
Jason Rasmussen
3f06a494a9 refactor: queue asset deletes via stream (#16706) 2025-03-07 22:22:57 +00:00
renovate[bot]
086d8a448a fix(deps): update typescript-projects (#16597)
* fix(deps): update typescript-projects

* chore: update server lock file

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-03-07 21:20:45 +00:00
bo0tzz
8ace44fb95 feat: log before running migrations (#16703)
* feat: log before running migrations

* fix: it's called log not info

It should be called info...

* chore: fix formatting

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-07 21:15:06 +00:00
Jason Rasmussen
ce74f765b1 refactor: memory stub (#16704) 2025-03-07 16:03:34 -05:00
Yaros
b0bf4e4fff feat(mobile): search on people page (#16696) 2025-03-07 14:43:32 -06:00
Jason Rasmussen
2d106755f6 refactor: convert activity stub to a factory (#16702) 2025-03-07 15:20:04 -05:00
Jason Rasmussen
f82786a297 feat: use stream for template migrations (#16700) 2025-03-07 14:30:01 -05:00
Yaros
c12986d38c fix(mobile): remain on albums tab after album deletion (#16698)
fix(mobile): remain on albums after album deletion
2025-03-07 13:25:07 -06:00
Matthew Momjian
19c40e3be9 fix(docs): remove /api from README (#16692)
* no api

* change internationalized
2025-03-07 08:58:18 -05:00
Jonathan Jogenfors
9959755dda refactor(server): use constant for external library batch size (#16685) 2025-03-07 11:29:06 +00:00
Lukas
fdf2331c82 fix(web): hide scroll right button when scrolled to the right in memory lane (#16656)
fix(web): hide scroll right button when scrolled to the right
2025-03-06 20:50:56 -06:00
Lukas
e03d7f888e fix(web): remove margin on last memory item (#16665) 2025-03-07 02:50:16 +00:00
Matthew Momjian
2eeed6524f fix(github): consistent folder format for PR template (#16669)
consistent formatting for folders
2025-03-06 20:32:10 -05:00
Jason Rasmussen
d45fa491ce refactor: stream asset ids for library queue jobs (#16666) 2025-03-06 20:22:17 -05:00
Matthew Momjian
5c82c485d7 feat(server): normalize extensions in storage template (#16667)
* normalize and lowercase extensions

* un const

* do not change ext before stripping off old one

* braces
2025-03-06 18:02:28 -05:00
Sergey Katsubo
feb65bf5a7 docs: reading existing face tag metadata is supported currently (#16662)
Fix FAQ: reading existing face tag metadata is supported currently
2025-03-06 20:42:14 +00:00
Jason Rasmussen
2cdbb0a37c refactor: database repository (#16593)
* refactor: database repository

* fix error reindex check

* chore: remove WIP code

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-03-06 13:33:24 -05:00
shenlong
fe931faf17 refactor: exif entity (#16621)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-06 11:58:24 -06:00
Arno
4ebc25c754 feat(mobile): Folder View for mobile (#15047)
* very rough prototype for folder navigation without assets

* fix: refactored data model and tried to implement asset loading

* fix: openapi generator shadowing query param in /view/folder

* add simple alphanumeric sorting for folders

* basic asset viewing in folders

* rudimentary switch sorting order

* fixed reactivity when toggling sort order

* Fixed trailing comma

* Fixed bad merge conflict resolution

* Regenerated open-api

* Added rudimentary breadcrumbs

* Fixed linting problems

* feat: cleanup

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-06 17:27:43 +00:00
Mert
deb399ea15 refactor(server): use exiftool for file date metadata (#16453)
* use exiftool for file date metadata

* handle tag not existing in exifinfo (?)

* update medium tests

* fix typo

* set file size too

* set file size only if undefined
2025-03-06 16:47:12 +00:00
Yaros
d01b7a0d67 feat(web): aspect ratio of memory cards (#16652)
Fix aspect ratio of memory cards
2025-03-06 15:24:01 +00:00
Jonathan Jogenfors
3af26ee94a feat(server): library refresh go brrr (#14456)
* feat: brr

---------
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2025-03-06 16:00:18 +01:00
Mert
bc61497461 refactor(server): group async calls in metadata extraction (#16450)
* group async calls

use debugFn

no need to change mock

* check call count in tests
2025-03-06 08:56:35 -06:00
Alex
1ed1a0a1fc feat(mobile): new sync (#16556)
* feat(mobile): new sync

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* update analysis option

* remove database operation

* pr feedback
2025-03-06 08:44:28 -06:00
Lukas
2875303b4c feat(web): allow horizontal scrolling in memory lane (#16647) 2025-03-06 08:37:11 -06:00
rrrockey
d84009648e refactor(server): replace switch statement in sendFile with Record lookup (#16630)
* refactor cache control handling in server/utils/file.ts

* add ability to null CacheControl.NONE

* Cache control handling comment

* Added comment to file.ts

This comment provides a better understanding of what the cacheControlHeader is doing.

* Update file.ts

Added comments

* Update server/src/utils/file.ts

* fix comments in file.ts

* run prettier with --write to fix formatting

---------

Co-authored-by: pnleguizamo <pnleguizamo@gmail.com>
Co-authored-by: drew-kearns <dkearns@iastate.edu>
Co-authored-by: Sierra (Izumi) Brown <119357873+SierraIBrown@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-06 12:46:37 +01:00
Nick Huang
fc2df05190 docs: fix png extension typo in supported formats table (#16636)
Update supported-formats.md
2025-03-06 11:44:06 +00:00
Savely Krasovsky
69b5365965 feat: enable PMTiles protocol support (#16629)
This patch enables PMTiles protocol for MapLibre-GL. Protocol allows to fetch tiles from a single file.  This drastically simplifies the process to self-host own tiles.
2025-03-06 10:50:14 +00:00
Alex
c110c9b00e chore(mobile): post release task (#16623) 2025-03-05 14:54:56 -06:00
Yaros
b241a80339 feat(mobile): Navigate back on memories (#16545)
* Navigate back on memories

* Fixes crash on navigating back
2025-03-05 14:42:43 -06:00
github-actions
31dd15ce8a chore: version v1.129.0 2025-03-05 19:47:50 +00:00
Alex
6108587c8b fix(web): show tags timeline (#16617)
* fix(web): show tags timeline

* fix(web): show tags timeline
2025-03-05 13:36:56 -06:00
Alex
3e50f668d9 feat(mobile): add catalan i18n (#16616)
* feat(mobile): Add Catalan

* refactor

* fix: load correct file

* chore: remove unused language files
2025-03-05 11:47:31 -06:00
Daniel Dietzler
9b82617e22 docs: 60k stars! (#16618)
60k stars! 
2025-03-05 11:40:45 -06:00
Alex
76cb32d8d0 chore(mobile): translations update (#16615)
chore(mobile): translation update
2025-03-05 16:33:41 +00:00
Yaros
e8f3348833 fix(mobile): Fixed zh-Hans not persisting (#16608)
Fixed zh-Hans not persisting
2025-03-05 09:56:00 -06:00
Zack Pollard
9922c8de59 fix: storage template failure after re-upload and previous fail (#16611)
fix: storage template breaks when files are re-uploaded after a move failure
2025-03-05 15:00:37 +00:00
shenlong
3f4bbab4eb fix: isar crash on resume from app detach (#16599)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-05 08:28:40 -06:00
Jason Rasmussen
2da9e3152b refactor: download service (#16600) 2025-03-05 08:38:23 -05:00
Min Idzelis
56b85f7479 fix(web): fix lost scrollpos on deep link to timeline asset, scrub stop (#16305)
* Work in progress - super quick asset store->state

* bugfix: deep linking to timeline, on scrub stop

* format, remove stale

* disable test, todo: fix test

* remove unused import

* Fix merge

* lint

* lint

* lint

* Default to non-wasm layout

* lint

* intobs fix

* fix rejected promise

* Review comments, static import wasm

* Back to dynamic

* try top-level-await

* back to the first solution, with more finesse

* comment out wasm for now

* back out the wasm/thumbhash/thumbnail changes

* lint

* Fully remove wasm

* lockfile

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-04 20:34:53 -06:00
waclaw66
8b43066632 fix(mobile): .well-known usage (#16577)
fix: .well-known

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-03-04 20:25:57 -06:00
bo0tzz
20acdcd884 chore: run docker workflow on non-main PRs (#16582) 2025-03-05 02:15:17 +00:00
Jonathan Jogenfors
22d348beca feat(server): e2e for missing jobs (#15910)
* feat: test face detection

* Add duplicate and smart search fixes and tests

* do e2e instead

* Remove ML e2e jobs
2025-03-04 20:44:31 -05:00
shenlong
3b0af1c8a9 fix(mobile): do not pause audio on app start (#16596)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-04 16:00:01 -06:00
Mert
61c8237a4d fix(ml): set face detection threshold correctly in locust (#13419)
* set minScore correctly

* cleanup

* remove outdated tag score
2025-03-04 20:52:07 +00:00
Jason Rasmussen
d740f0283a chore: no more immortal PRs (#16595) 2025-03-04 15:06:41 -05:00
Jonathan Jogenfors
4ada28ac99 fix(server): check updateLibraryIndex for zero (#16585)
* fix(server): check updateLibraryIndex for zero

* Update web/src/routes/admin/library-management/+page.svelte

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-03-04 20:00:10 +00:00
Jason Rasmussen
63c01b78e2 refactor: test utils (#16588) 2025-03-04 16:15:41 +00:00
renovate[bot]
1423cfd53c chore(deps): update ghcr.io/immich-app/base-server-dev docker tag to v20250304 (#16580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 15:39:34 +00:00
Snowknight26
867eec86f5 fix(web): Update menu titles to be more consistent (#16558) 2025-03-04 12:55:54 +00:00
Alex
86e8effd8e fix(mobile): incorrect memories with timezone (#16562) 2025-03-04 12:54:54 +00:00
Jonathan Jogenfors
49d393216a fix(server): fix import path truthiness check (#16570) 2025-03-04 12:54:12 +00:00
renovate[bot]
75c9f63757 chore(deps): update typescript-projects (#16573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 12:53:28 +00:00
Kofi
63984890df docs: clean up environment variables formatting & grammar (#16555)
docs: clean up environment variables formatting & grammar - Just going through the docs and noticed some inconsistent capitalization and minor grammar issues. Fixed them up while having my Monday coffee :) Nothing major, but makes the docs a bit more polished.
2025-03-04 05:00:27 +00:00
Jason Rasmussen
1356468c38 fix: reset/regenerate memories (#16548)
fix: reset memories
2025-03-03 23:48:05 -05:00
renovate[bot]
c23c53bf6f fix(deps): update machine-learning (#16560) 2025-03-04 01:42:35 +00:00
renovate[bot]
0dcfc43461 chore(deps): update node (#16538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 14:31:34 -05:00
Jason Rasmussen
d1fd0076cc refactor: migration tag repository to kysely (#16398) 2025-03-03 18:41:19 +00:00
Zack Pollard
ff19502035 feat: qr code for new shared link (#16543) 2025-03-03 13:40:41 -05:00
renovate[bot]
6ef069b537 chore(deps): update github-actions (#16539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 18:39:15 +00:00
Matthew Momjian
a03e999bde fix(docs): info on preloading ML models (#16452)
info on preload
2025-03-03 18:39:02 +00:00
aviv926
ad1ba4be5f docs: better facial recognition cluster guide (#14911)
* Better Facial Recognition Clusters

* Add information about the guide

* Update docs/docs/features/facial-recognition.md

Co-authored-by: Felix Bühler <Stunkymonkey@users.noreply.github.com>

* PR Feedback

---------

Co-authored-by: Felix Bühler <Stunkymonkey@users.noreply.github.com>
2025-03-03 18:33:32 +00:00
Alessandro Baroni
f89e74181b fix(web): delete action closes asset viewer in asset view (#15469)
fixes #14647
2025-03-03 18:24:37 +00:00
Eli Gao
e2c34f17ba feat(cli): watch paths for auto uploading daemon (#14923)
* feat(cli): watch paths for auto uploading daemon

* chore: update package-lock

* test(cli): Batcher util calss

* feat(cli): expose batcher params from startWatch()

* test(cli): startWatch() for `--watch`

* refactor(cli): more reliable watcher

* feat(cli): disable progress bar on --no-progress or --watch

* fix(cli): extensions match when upload with watch

* feat(cli): basic logs without progress on upload

* feat(cli): hide progress in uploadFiles()

* refactor(cli): use promise-based setTimeout() instead of hand crafted sleep()

* refactor(cli): unexport UPLOAD_WATCH consts

* refactor(cli): rename fsWatchListener() to onFile()

* test(cli): prefix dot to mocked getSupportedMediaTypes()

* test(cli): add tests for ignored patterns/ unsupported exts

* refactor(cli): minor changes for code reviews

* feat(cli): disable onFile logs when progress bar is enabled
2025-03-03 13:05:32 -05:00
Zack Pollard
23b1256592 ci: weblate checks should always run, should skip on en.json (#16544) 2025-03-03 17:12:26 +00:00
Yaros
7bbc1d9f68 feat(web): Video memories on web (#16500)
* Video memories on web

* switched mixed up strings
2025-03-03 09:54:26 -06:00
renovate[bot]
8b24c31d20 fix(deps): update typescript-projects (#16540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 09:38:24 -06:00
shenlong
7f61ac6983 chore(mobile): fix store.put type def (#16517)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-03 09:11:13 -06:00
shenlong
4db8f0c666 refactor(mobile): move timeline methods to timeline repo (#16526)
* refactor: move timeline calls to timeline repo

* refactor: review changes

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-03 09:10:09 -06:00
renovate[bot]
3d6a6f77a8 chore(deps): update dependency eslint-plugin-svelte to v3 (#16532)
* chore(deps): update dependency eslint-plugin-svelte to v3

* chore: linting

* chore: rebase

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-03 14:24:26 +00:00
Mert
5698f446f7 refactor(server): link live photos as part of metadata extraction instead of queueing job (#16390)
* link live photos helper instead of job

* update test

* queue storage template migration

* queue in onDone

* remove link live photos job
2025-03-03 09:19:36 -05:00
renovate[bot]
eb74fafb00 chore(deps): update dependency globals to v16 (#16534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 14:11:44 +00:00
Zack Pollard
24da25dbbf ci: don't check weblate lock on chore/translations and add success job (#16533) 2025-03-03 13:22:33 +01:00
renovate[bot]
9b842d4cca chore(deps): update tensorchord/pgvecto-rs:pg14-v0.2.0 docker digest to 739cdd6 (#16530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 12:13:38 +00:00
renovate[bot]
a99bd94717 fix(deps): update dependency ua-parser-js to v2 (#14301)
* fix(deps): update dependency ua-parser-js to v2

* fix: breaking changes from ua-parsed-js major update

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-03 12:01:40 +00:00
renovate[bot]
4b568dcbb3 chore(deps): update dependency black to v25 (#16033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 11:57:46 +00:00
renovate[bot]
12ab56c885 chore(deps): update prom/prometheus docker digest to 6927e09 (#16529)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 11:52:22 +00:00
renovate[bot]
eed6465b41 chore(deps): update grafana/grafana docker tag to v11.5.2 (#16301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 11:51:44 +00:00
renovate[bot]
5f6c16080b chore(deps): update docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0 docker digest to 739cdd6 (#16528)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 11:51:13 +00:00
Alex
a2aab1f373 fix: don't use public keyword in migration query (#16514)
Co-authored-by: Zack Pollard <zack@futo.org>
2025-03-03 11:40:14 +00:00
bo0tzz
8e076ecfe4 feat: weblate checks workflow (#16251) 2025-03-03 11:39:53 +00:00
Zack Pollard
fe702ba6d7 feat: partner sync (#16424)
feat: partner CUD sync
2025-03-03 11:05:30 +00:00
Jonathan Jogenfors
869839f642 feat(server): library cleanup from ui (#16226)
* feat(server,web): scan all libraries from frontend

* feat(server,web): scan all libraries from frontend

* Add button text
2025-03-02 21:29:02 -06:00
Justin Cichra
8885e3105e chore: reword backup_manual_in_progress (#16513)
fix(i18n): reword backup_manual_in_progress

Split "sometime" into "some time".
2025-03-03 03:27:20 +00:00
bo0tzz
6e51c4ec71 chore: add extra note to no-dupes checkbox (#16499) 2025-03-02 21:02:36 -06:00
knechtandreas
6bf2e8dbcb feat: add album keyboard shortcuts (#16442)
* 15712: Added keyboard shortcuts for opening add to album modal and highlighting/selecting an album to add to.

* 15712: Re-factored logic from template code into script. Extracted new album button into separate cmponent.

* 15712: Document new keyboard shortucts now that they work everywhere.

* 15712: Extract some constants/helper functions.

* 15712: Missing comma.

* 15712: Pulled logic out into separate unit testable class.

* 15712: Added a unit test.

* 15712: Move the modal back up to keep the github PR happy.

* 15712: PR feedback - renamed typescript files and switch to class bind directive.

* 15712:Move selection modal into correct package.

* 15712: Better naming of module and files.
2025-03-02 13:15:00 +00:00
Yaros
366f23774a fix(web): Default to context search on web (#16485)
Default to context search on web
2025-03-02 13:06:15 +00:00
Yaros
fd5e931617 fix(mobile): Updated formatting of server address in networking (#16483)
* Updated formatting of server address in networking

* fallback for undefined endpoint
2025-03-02 06:58:05 -06:00
shenlong
d8d87bb565 chore(mobile): rename log enum to lowercase (#16476)
* chore(mobile): rename log enum to lowercase

* chore(mobile): do not abbreviate

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-03-02 06:30:48 -06:00
Lukas Jost
6cc1978b2d fix(web): Open huggingface.co link on settings page in new tab (#16470)
fix(web): Open huggingface on settings page in new tab
2025-03-01 23:02:56 +00:00
luzpaz
506d2d0f81 fix(web): fix typos (#16466)
Found via codespell
2025-03-01 16:51:50 -06:00
Yaros
f13d13b2ea fix(web): Fixed people list overflowing in advanced search (#16457)
* Fixed people list overflowing in search

* styling: better fix

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-03-01 20:34:57 +00:00
Thomas Laroche
2510684bf7 fix(web): unable to download live photo as anonymous user (#16455) 2025-03-01 14:07:19 -06:00
luzpaz
c8eef5ad4d fix(mobile): fix typos (#16456)
Found via codespell
2025-03-01 20:06:47 +00:00
bo0tzz
0cb3dc6211 chore: add 'not duplicate' checkbox to issue template (#16462) 2025-03-01 14:05:36 -06:00
Alex
f11080cc2d chore(mobile): post release task (#16437) 2025-02-28 21:09:09 -06:00
Matthew Momjian
efcf773ea0 feat(server): Shortened asset ID in storage template (#16433)
* Update storage-template.service.ts

* Update supported-variables-panel.svelte

* docs example

* Update storage-template-settings.svelte
2025-02-28 16:04:34 -05:00
github-actions
dc143046e3 chore: version v1.128.0 2025-02-28 18:54:08 +00:00
Jason Rasmussen
e684062569 fix: memories off by one (#16434) 2025-02-28 12:51:28 -06:00
Desmond Cox
5c0538e52c fix(server): stringify error log parameter to ensure correct overload (#16422)
* fix(server): stringify error log parameter to ensure correct overload

The intended error(message, stack, context) overload is only selected if context is a string.

* formatter
2025-02-28 11:50:00 -06:00
Jason Rasmussen
84cf0d1670 fix: duplicate memories (#16432) 2025-02-28 17:49:29 +00:00
Jonathan Jogenfors
bfcde05b1c chore(server): trash e2e cleanup (#16423) 2025-02-28 12:45:30 -05:00
Mert
b3b15e9b61 fix(server): include deleted assets if searching offline assets (#16417)
include deleted assets if searching for offline assets
2025-02-28 09:23:18 -06:00
Zack Pollard
819e56d9ca fix: user delete sync query sort by id (#16420) 2025-02-28 09:22:36 -06:00
shenlong
9a98712db7 fix(mobile): background backup failing due to store (#16418)
fix: background backup failing due to store

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-28 07:38:51 -06:00
Alex
a185e06399 fix(server): follow logs level setting (#16415) 2025-02-28 00:35:48 -05:00
Calum Dingwall
f2be9f7ad1 fix(web): person favorite icon bad placement (#16412)
move favorite person icon to top left

fixes #16003

Co-authored-by: Calum Dingwall <caburum@users.noreply.github.com>
2025-02-27 22:15:37 -06:00
Alex
5c879acd5b fix(server): don't show assets that no longer associate with a face (#16404) 2025-02-27 17:02:00 -06:00
shenlong
28c664c769 refactor(mobile): log service (#16383)
refactor: log service

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-27 15:18:49 -05:00
Jason Rasmussen
fbd85a89e0 refactor: logger (#16393) 2025-02-27 14:59:50 -05:00
Alex
1c86293035 chore(mobile): update analysis option (#16396)
chore-update-analysis-option
2025-02-27 18:35:28 +00:00
shenlong
4a9d80298b fix(mobile): bootstrap store inside isolates (#16392)
fix: bootstrap store inside isolates

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-27 18:01:36 +00:00
Alex
362feb1e62 feat(web): face tagging dialog enhancement (#16395) 2025-02-27 11:49:07 -06:00
Etienne
5503bf7a60 fix: improve contrast on disabled input field in light mode (#16368) (#16382) 2025-02-27 17:20:03 +00:00
Jonathan Jogenfors
d20e2e268a fix(server): don't reimport files more than once (#16375)
* fix(server) don't reimport files more than once

* fix: test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-02-27 16:45:16 +00:00
Mert
a708649504 fix(server): skip stacked assets in duplicate detection (#16380)
* skip stacked assets in duplicate detection

* update sql

* handle stacking after duplicate detection runs
2025-02-27 10:16:13 -06:00
Tom Graham
a808b8610e fix(server): Fix delay with multiple ml servers (#16284)
* Prospective fix for ensuring that known active ML servers are used to reduce search delay.

* Added some logging and renamed backoff const.

* Fix lint issues.

* Update to use env vars for timeouts and updated documentation and strings.

* Fix docs.

* Make counter logic clearer.

* Minor readability improvements.

* Extract  skipUrl logic per feedback, and change log to verbose.

* Make code harder to read.
2025-02-27 10:14:09 -06:00
Alex
c70c9067b0 refactor(mobile): backup provider (#16360)
* refactor(mobile): backup provider

* refactor(mobile): backup provider
2025-02-27 09:56:23 -06:00
Alex
082471dfd9 chore(mobile): post release task (#16349) 2025-02-27 09:46:34 -06:00
Alex
9a098b4658 fix(web): storage template incorrect example (#16367) 2025-02-27 09:46:20 -06:00
immich-tofu[bot]
9d705097e8 chore: modify .github/FUNDING.yml 2025-02-27 14:28:08 +00:00
Mert
6050485ad8 feat(server): set exiftool process count (#16388)
exiftool concurrency control
2025-02-27 09:24:40 -05:00
Zack Pollard
fb907d707d refactor: use new updateId column for user CUD sync (#16384) 2025-02-27 09:22:02 -05:00
Mert
7d6cfd09e6 fix(server): don't expose source types in face creation api (#16381)
* don't expose source types in face creation api

* update open-api

* remove source type reference from web
2025-02-27 17:17:07 +03:00
Zack Pollard
967c69317b feat: updateId uuidv7 column for all entities with updatedAt (#16353) 2025-02-27 12:55:22 +00:00
Curtis Lowder
128d653fc6 fix(web): update search modal to not jump around (#16308)
* fix(web): update search modal to not jump around

Search People selection will change size while loading. This causes the
search modal to jump around as the people load in.

* loading spinner size

* remove unsued code

---------

Co-authored-by: cwlowder <me@curtislowder.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-02-27 03:06:41 +00:00
David Bourgault
8b69114924 feat(web): remember last chosen map location when editing (#16366)
Uses a global store to remember the last location chosen by a user when
editing asset locations. This fixes an annoyance when adding location
data to multiple assets in a row and having to zoom in the same area
everytime.
2025-02-26 21:01:29 -06:00
David Bourgault
4b55888d16 fix: ensure manually tagged faces have proper source type (#16364)
immich-app/immich#16062 added manual face tagging and deletion, but did
not add a new 'SourceType'. The create faces would default to
'machine-learning' which is incorrect, and has the annoying downside
that they will be wiped when the 'Refresh Faces' job is run.

Handling of non-machine-learning faces was previously added in
immich-app/immich#6455. This PR simply extends it to the new manually
tagged faces.
2025-02-26 20:53:21 -06:00
Alex
8fbd650483 refactor(mobile): refactor user provider (#16358) 2025-02-26 17:04:43 -06:00
Alex
c778516ce2 fix(web): tag people in video (#16351) 2025-02-26 12:55:32 -06:00
Adam O'neill
2969e25ff7 fix: websockets calling on_new_release across all sessions upon new websocket connection. (#16339)
* Implemented possible fix for the new_release window re-appearing across all active sessions when a new websocket connection is established.

* Reverted websocket.ts

Changes not needed to websocket.ts - was bouncing between ideas, current implementation doesn't need this to change.

* Prettier test format.

* Spelling (Aknowledged --> Acknowledged)
2025-02-26 17:48:18 +00:00
luzpaz
c055e1aefe docs: fix typos (#16352)
Found via `codespell -q 3 -S "./i18n,./docs/package-lock.json,./readme_i18n,./mobile/assets/i18n" -L afterall,nd,renderd`
2025-02-26 17:21:27 +00:00
github-actions
5f7f88ff17 chore: version v1.127.0 2025-02-26 15:18:50 +00:00
Zack Pollard
5053130e35 fix: sync set ack validation (#16320) 2025-02-26 09:35:51 -05:00
Alex
4ef7eb56a3 fix(server): memory assets order (#16325) 2025-02-25 19:10:52 -06:00
Alex
8ecc67a364 feat(mobile): use memories api (#16329) 2025-02-25 19:10:31 -06:00
Alex
90f7c3d9ae chore(mobile): translations update (#16328)
chore(mobile): translation update
2025-02-25 15:06:40 -06:00
Alex
d0381fddec refactor(mobile): render list (#16303)
* refactor(mobile): render list 2

* wip

* wip: asset selection page

* remove render_list provider

* remove dead code

* yaml format

* remove unused file

* woop woop more clean up

* woop woop more clean up 2

* fix: album selection doesn't load instantly
2025-02-25 11:33:48 -06:00
Jason Rasmussen
7c851893b4 feat: medium tests for user and sync service (#16304)
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-02-25 16:31:07 +00:00
RoseyWasTaken
ae61ea7984 Update community-guides.tsx (#16316)
* Update community-guides.tsx

Added an additional card linking to a remote access guide

* Update docs/src/components/community-guides.tsx

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-25 15:52:07 +00:00
Alex
bbcaee82f0 chore(web): revert wasm new justify layout (#16277)
* Revert "fix(web): justify layout import (#16267) "

This reverts commit ec58e1065f.

* Revert "fix(web): dynamically import wasm module (#16261)"

This reverts commit 4376fd72b7.

* Revert "feat(web): use wasm for justified layout calculation (#15524)"

This reverts commit 3925445de8.

* Revert "fix(web): viewport reactivity, off-screen thumbhashes being rendered (#15435)"

This reverts commit 52f21fb331.
2025-02-25 09:39:56 -06:00
Nicholas Flamy
16266c9f5a docs: #15988 follow-up: Use URL constructor to fix Version Switcher URL double slash issue (#16014)
* concat location properties and use URL constructor to fix issues

* remove slashes from old version urls

* remove versions 1.125.0 and 1.125.4 that don't have docs archives
2025-02-25 09:34:46 -06:00
Alex
6c64a6dab8 chore(web): Revert slight fade in animation when open/close asset-viewer (#16262) (#16306)
Revert "feat(web): slight fade in animation when open/close asset-viewer (#16262)"

This reverts commit 57829cee26.
2025-02-25 09:27:34 -06:00
ExceptionsOccur
c0fe98fe27 feat(mobile): photos group by date in album page view (#16272)
* feat(mobile): photos group by date in album page view

* fix: format

---------

Co-authored-by: ExceptionsOccur <yuyu.tao@foxmail.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-25 15:10:08 +00:00
Alex
579321251f refactor(mobile): partners provider (#16299)
* refactor(mobile): partners provider

* update analysis option

* update analysis option
2025-02-25 08:52:33 -06:00
Nicholas Flamy
392f9f205c fix(web): thumbnail playback stops when hovering over icon then video (#16302)
fix thumbnail playback when hovering over icon then video
2025-02-24 21:35:46 -06:00
Alex
57829cee26 feat(web): slight fade in animation when open/close asset-viewer (#16262) 2025-02-24 16:38:07 -06:00
Yamagishi Kazutoshi
4be2351d21 fix(web): use anonymous function in setTimeout in ponyfill of requestIdelCallback (#16264) 2025-02-24 16:37:58 -06:00
Nicholas Flamy
edbcf17e3a fix(docs): tsconfig issues in IDE (VSCode) - migrate tsconfig extends file to current docusaurus implementation (#16282)
fix tsx IDE (VSCode) issues in docs by migrating tsconfig extends from the Docusaurus tsconfig 2.X package to the 3.X package
2025-02-24 13:24:28 -06:00
Mert
eef74ee0ba chore: bump justified layout library (#16298)
bump
2025-02-24 10:28:34 -06:00
Alex
ec58e1065f fix(web): justify layout import (#16267)
* fix(web): justify layout import

* remove dead code
2025-02-23 02:38:08 +03:00
Mert
4376fd72b7 fix(web): dynamically import wasm module (#16261)
* dynamically import wasm module

* remove unused import
2025-02-22 12:16:06 -06:00
Jason Rasmussen
e4b6efc1f5 fix: cross site scripting issue on /share pages (#16255) 2025-02-22 11:32:53 +00:00
waclaw66
caea3a0812 fix: vite > 6.0.8 allowedHosts (#16257)
fix(web): vite > 6.0.8 allowedHosts

Enables any host for development environment same as for vite <= 6.0.8
2025-02-21 23:29:58 -05:00
Jonathan Jogenfors
9c2c85cbe1 feat(web): remove library type column (#16254) 2025-02-21 18:00:16 -05:00
Jason Rasmussen
d350022dec feat: persistent memories (#15953)
feat: memories

refactor

chore: use heart as favorite icon

fix: linting
2025-02-21 12:31:37 -06:00
Weblate (bot)
502f6e020d chore(web): update translations (#15559)
Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 6Leoo6 <leo.takacs@yahoo.com>
Co-authored-by: Aldis Bārbelis <ceriemardon@gmail.com>
Co-authored-by: Alessandro Iaselli <alessandroias@gmail.com>
Co-authored-by: Andrea <andreadetomasi12@gmail.com>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: CRY WHY <a.pandagok1@gmail.com>
Co-authored-by: Casper Ong <casper10528@gmail.com>
Co-authored-by: Changhwan Kim <kimch061279@gmail.com>
Co-authored-by: Chris <6st6s7rgw@mozmail.com>
Co-authored-by: Christoph Auer <Christoph.Auer@pilsheim.de>
Co-authored-by: CodingDK <CodingDK@users.noreply.github.com>
Co-authored-by: Daniel <daniel@nikul.in>
Co-authored-by: Daniel A <aquino.daniel1994@ikmail.com>
Co-authored-by: Daniel Correa Lobato <daniel@lobato.org>
Co-authored-by: David Lam <dlam06@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Eitan Nargassi <eitan1112@gmail.com>
Co-authored-by: Fabian Tubbing <fabian@tubbing.nl>
Co-authored-by: Farid <farid.for@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Florian Ostertag <florian.kuepper@gmail.com>
Co-authored-by: Francesco Borio <borio.francesco@gmail.com>
Co-authored-by: HanYuan <lion70332@gmail.com>
Co-authored-by: Hurricane-32 <rodrigorimo@hotmail.com>
Co-authored-by: Indrek Haav <IndrekHaav@users.noreply.hosted.weblate.org>
Co-authored-by: Jan Schwebel <jan@schwebel.de>
Co-authored-by: Jirapan <jirapan_yankhan@hotmail.com>
Co-authored-by: Jiri Grönroos <jiri.gronroos@iki.fi>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Josep M. Ferrer <txemaq@gmail.com>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Karol Klimczak <karol.klimczak.1.kk@gmail.com>
Co-authored-by: Laurentiu <laurfb@gmail.com>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Leonardo Patti <leonardo.patti90@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Lukas Hamm <ideallygrey@tuta.io>
Co-authored-by: Manar Aldroubi <droubi@gmail.com>
Co-authored-by: Mark Rieder <markrieder111@gmail.com>
Co-authored-by: Martin Popovski <martinkozle@yahoo.com>
Co-authored-by: Matjaž T <matjaz@moj-svet.si>
Co-authored-by: Max Lengerer <lengerer.max@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miki Mrvos <medolino2009@gmail.com>
Co-authored-by: Mohammed Al Otaibi <mopes.03.belle@icloud.com>
Co-authored-by: Nicolò <nicveronese@gmail.com>
Co-authored-by: Oleh Horbachov <gorbyo@gmail.com>
Co-authored-by: Pablo Portas López <pabloportas@protonmail.com>
Co-authored-by: Peder Vaagland <halsa.p.vaagland@gmail.com>
Co-authored-by: Petri Hämäläinen <petri.hamalainen@mailbox.org>
Co-authored-by: Rafa <rafa0292@gmail.com>
Co-authored-by: Ram Sujith Reddibathini (Ram) <sujithram.it@gmail.com>
Co-authored-by: Riccardo <lark-unit-rush@duck.com>
Co-authored-by: Rodrigo Bourbon Navarro <rodrigobourbon44@gmail.com>
Co-authored-by: Roi Gabay <roigby@gmail.com>
Co-authored-by: Rookie Nguyễn <nguyenquocthang2004@gmail.com>
Co-authored-by: Runskrift <anders@rimfrost.nu>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: Theofilos Nikolaou <th.nikolaou@gmail.com>
Co-authored-by: Torin Wu <xuan329269@gmail.com>
Co-authored-by: Vegard Fladby <vegard@fladby.org>
Co-authored-by: Vladislav Tkalin <mrtold11@gmail.com>
Co-authored-by: Xo <xocodokie@users.noreply.hosted.weblate.org>
Co-authored-by: YapWC <yapchengcheng3568@gmail.com>
Co-authored-by: Zulhilmi Ramli <ramli.zulhilmi@gmail.com>
Co-authored-by: anton garcias <isaga.percompartir@gmail.com>
Co-authored-by: chamdim <chamdim@protonmail.com>
Co-authored-by: chapvic <victor@chapaev.org>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: iancbogue <iancbogue@gmail.com>
Co-authored-by: intothevolt <francesco.ferriero97@gmail.com>
Co-authored-by: kiwinho <kiwicaja@gmail.com>
Co-authored-by: krzemyk <krzemyk.official@proton.me>
Co-authored-by: pierrebengtsson <pierre.bengtsson@gmail.com>
Co-authored-by: shiuh67 <shiuh.cheng@gmail.com>
Co-authored-by: szelek <janek.szelewicz@gmail.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Co-authored-by: timmy61109 <qazzxcasdqwewsxedc@gmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: wickdj <wickdj@gmail.com>
Co-authored-by: wojtasiq <wojtek.wroclaw@hotmail.com>
Co-authored-by: xmh10000 <xmh10000@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: Мĕтри Сантăр ывалĕ Упа-Миччи <mefisteron@gmail.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-02-21 17:30:19 +00:00
bo0tzz
ca9e02379d feat: remove preview label on pr close (#16249) 2025-02-21 17:54:11 +01:00
bo0tzz
36ec407c66 fix: use correct head sha on PR commit tag (#16248) 2025-02-21 17:02:24 +01:00
Alex
007eaaceb9 feat(web): manual face tagging and deletion (#16062) 2025-02-21 09:58:25 -06:00
shenlong
94c0e8253a test(mobile): store (#16243)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-21 09:10:42 -06:00
Alex
5acf6868b7 refactor(mobile): render list (#16239)
* refactor(mobile): trash provider

* refactor(mobile): trash provider

* pr feedback

* archive timeline

* favorite

* album

* trash timeline

* all videos timeline

* refactor

* refactor: home timeline and partner timeline

* update analysis option
2025-02-21 09:01:46 -06:00
Mert
616905211d fix(server): assets in multiple albums duplicated in map view (#16245) 2025-02-21 15:32:08 +03:00
Mert
3925445de8 feat(web): use wasm for justified layout calculation (#15524)
* working

* use wrapper class

* update import

* simplify

* it works without changing `optimizeDeps`

* inline layout options

* update gallery view

* use es2022

* fix import

* fix vitest

* empty geometry

* bump version

* Update web/src/lib/stores/assets.store.ts

Co-authored-by: Jason Rasmussen <jason@rasm.me>

* fix: typo

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-02-21 09:20:25 +00:00
Mert
52f21fb331 fix(web): viewport reactivity, off-screen thumbhashes being rendered (#15435)
* viewport optimizations

* fade in

* async bitmap

* fast path for smaller date groups

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-20 22:38:12 -06:00
Zack Pollard
ac36effb45 feat: sync implementation for the user entity (#16234)
* ci: print out typeorm generation changes

* feat: sync implementation for the user entity

wip

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2025-02-20 23:37:57 -05:00
bo0tzz
02cd8da871 docs: clarify custom locations guide (#16122) 2025-02-20 22:31:29 -06:00
Alex
17a2043e76 refactor(mobile): trash provider (#16219)
* refactor(mobile): trash provider

* refactor(mobile): trash provider

* pr feedback
2025-02-20 22:14:41 -06:00
Jason Antwi-Appah
34b88bb47a feat(web): support searching by EXIF rating (#16208)
* Add rating to search DTO

* Add search by EXIF rating in search query builder

* Generate OpenAPI spec

* Add rating filter on web

* Add rating filter to search docs

* Format / lint

* Hide rating filter if ratings are disabled

* chore: component order in form

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-02-20 16:17:06 +00:00
Jonathan Jogenfors
f6ba071569 feat(server): add path to metadata logging (#16212)
feat(server): Prefer original path instead of id when logging
2025-02-20 09:46:18 -06:00
Jonathan Jogenfors
6b7a7b0cbc feat(web): library import path onboarding (#16229) 2025-02-20 09:45:34 -06:00
Jonathan Jogenfors
b0102f8025 fix(server): set modifydate (#16225) 2025-02-20 09:28:30 -06:00
Lukas
9c95adc7fb feat(web): show memories in portrait on small screens (#16213) 2025-02-19 23:15:45 +00:00
renovate[bot]
376282e538 chore(deps): update dependency @types/node to ^22.13.4 (#16206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 14:54:12 -06:00
shenlong
76d95cd348 refactor(mobile): move store settings and store into domain folder (#16201)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-19 19:27:32 +00:00
Jonathan Jogenfors
31dc83f3f2 fix(server): don't warn about missing timezone (#16211)
fix(server): don't warn about timezone
2025-02-19 13:21:13 -06:00
shenlong
aeb3e0a84f refactor(mobile): split store into repo and service (#16199)
* refactor(mobile): migrate store

* refactor(mobile): expand abbreviations

* chore(mobile): fix lint

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-19 13:05:24 -06:00
Krassimir Valev
8634c59850 feat(server): search by partial asset path (#16173)
Similarly to how one can search by partial filename, change the
path search to work with partial matches instead of looking for a
full match.

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-19 08:32:52 -06:00
Lukas
b13a98646f fix(web): improve memories layout on small screens (#16162)
* fix(web): improve memories layout on small screens

* decrease viewer height
2025-02-18 17:40:52 -06:00
renovate[bot]
7bf142dc43 chore(deps): update prom/prometheus docker digest to 5888c18 (#16171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 16:02:56 -05:00
renovate[bot]
d8cda6ee40 chore(deps): update base-image to v20250218 (major) (#16204)
chore(deps): update base-image to v20250218

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 16:02:33 -05:00
renovate[bot]
a31bc94460 fix(deps): update typescript-projects (#16203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 21:35:50 +01:00
renovate[bot]
516709ffe1 chore(deps): update dependency @types/node to ^22.13.2 (#16200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 15:10:44 -05:00
renovate[bot]
425cf62482 fix(deps): update typescript-projects (#16178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-02-18 20:40:09 +01:00
Jason Anderson
58242b3b4a chore(docs): Synology set-up guide (#16179)
* Addition of Synology set-up guide

* fix: format

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-18 13:39:42 -06:00
Alex
9d4aee36e2 refactor(mobile): asset provider (#16159)
* refactor(mobile): asset provider

* wip

* wip: delete local assets

* wip: delete remote assets

* wip: deletion logic

* refactor

* pr feedback
2025-02-18 13:10:55 -06:00
shenlong
70d08a2b2a chore(mobile): lint (#16182)
* lint - convert path to lowercase for finding index

* update dcm lint rules

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-02-18 09:34:19 -06:00
Zack Pollard
f1b98d5f45 ci: docker cleanup, cleanup (#16194) 2025-02-18 14:56:58 +00:00
bo0tzz
749eff03d5 fix: pgvectors docs link (#16187)
Fixes #16184
2025-02-18 08:38:07 -05:00
bo0tzz
5f257b9a84 fix: don't write cache on fork PRs (#16189) 2025-02-18 12:47:20 +01:00
Jonathan Jogenfors
0cae20033c fix(server): more e2e library flakiness cleanup (#16176) 2025-02-17 19:04:38 -05:00
Jonathan Jogenfors
115ee0d6cc fix(server): remove unused readme (#16175)
fix(server): remove readme
2025-02-17 19:03:43 -05:00
Jonathan Jogenfors
bfdd6eac01 fix(server): flaky library e2e tests (#16174) 2025-02-17 18:26:44 -05:00
bo0tzz
9eab770e79 fix: don't push on forks (#16165) 2025-02-17 20:13:56 +00:00
João Paulo Ros
efd8d8b884 fix(mobile): Server endpoint on the login screen. (#16149)
Fixing the server endpoint on the login screen. It added the "/api" suffix instead of using the default method getServerUrl, which takes care of sanitizing the URL.

Co-authored-by: Joao Paulo Ros <ros@voxit.ai>
2025-02-17 19:12:48 +00:00
Alessandro Craciun
25e1c8cc7f chore(web): update italian translations (#15695) 2025-02-17 13:09:55 -06:00
Jason Rasmussen
7c26663013 chore: removed unused endpoint (#16167) 2025-02-17 13:07:50 -06:00
bo0tzz
2c88ce8559 chore: run full jobs on workflow file change (#16166) 2025-02-17 12:09:38 -06:00
Nick Overacker
50b072803d fix: limit width of logo in emails to 100% (#16164)
Limit width of logo in emails to 100%

The current live version breaks Yahoo Mail (at least in Firefox). It appears far too large and makes the email unreadable by pushing the text outside of the reading pane.
2025-02-17 17:46:14 +00:00
Mangat Singh Toor | ਮੰਗਤ ਸਿੰਘ ਤੂਰ
1689cecaf7 fix: include live images in person view count (#16116)
* fix: include live images in person view count

Fixed an issue where the total image count in the person view excluded live images.
The query now correctly accounts for all relevant assets by removing the condition
that filtered out assets with a livePhotoVideoId.

Issue:
- Image count under a person’s name was inaccurate, showing only static images.

Fix:
- Removed `.on('assets.livePhotoVideoId', 'is', null)` from the LEFT JOIN condition.

Tested on:
- Web

Ran PR checklist

* chore: run make sql.

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-17 15:49:30 +00:00
Pablo P Varela
5cd1018db3 fix(mobile): failed to load gl-ES locale (#16123) 2025-02-17 08:48:55 -06:00
renovate[bot]
31e6270a28 chore(deps): update docker.io/redis:6.2-alpine docker digest to 148bb54 (#16113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 14:23:28 +00:00
renovate[bot]
b3fbd0809b chore(deps): update redis:6.2-alpine docker digest to 148bb54 (#16140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 14:23:03 +00:00
Zack Pollard
129a4a82e0 ci: docker build cache (#16156) 2025-02-17 13:55:22 +00:00
Zack Pollard
924d11a913 ci: copy image layers from ghcr to dockerhub on release (#16155) 2025-02-17 13:41:45 +00:00
Zack Pollard
425c87bce4 ci: machine learning separate native docker image builds (#16102) 2025-02-17 11:56:28 +00:00
bo0tzz
25fcda6eeb chore: add warning to all compose files (#16146) 2025-02-16 21:28:59 -06:00
Jason Rasmussen
f386b4d377 feat(web): use thumbhash as a cache key (#16106)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-16 03:34:13 +00:00
renovate[bot]
c524fcf084 chore(deps): update node.js to v22.14.0 (#16132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 21:29:33 -06:00
renovate[bot]
194c567a45 chore(deps): update redis:6.2-alpine docker digest to 785233c (#16114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 12:10:44 +00:00
Zack Pollard
411f96ef49 fix: place suggestions not clickable in asset set location modal (#16104)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-02-15 09:44:11 +00:00
Alex
4f912de018 refactor(mobile): album provider (#16099) 2025-02-14 19:27:39 -06:00
Alex
47203d2760 refactor(mobile): asset stack provider (#16100)
* refactor(mobile): asset stack provider

* remove file from ignore list
2025-02-14 13:23:14 -06:00
Zack Pollard
8ab87a8803 ci: retag commit hash unset outside of PRs (#16103) 2025-02-14 19:18:49 +01:00
Zack Pollard
5b4f894211 ci: docker images sha commit tag (#16098) 2025-02-14 16:08:41 +00:00
Mangat Singh Toor | ਮੰਗਤ ਸਿੰਘ ਤੂਰ
b1f05fc18b fix(web): properly project profile picture (#16095)
* fix(profile-image-cropper): ensure correct image area is saved after transparency check

Fixed an issue where users could not set a profile picture due to incorrect transparency detection.
After addressing transparency detection by passing explicit dimensions, another issue arose where the
generated blob did not represent the correct cropped image area. To fix this, a new cropped blob was generated using the canvas that was used to check for transparent pixels.

- Pass image width and height explicitly to `hasTransparentPixels` for accurate processing.
- Return both transparency status and the correctly cropped image blob.
- Ensure the final uploaded image is taken from `croppedImageBlob` to reflect user adjustments.

* chore: run pr web checklist. No issues in the changed file.

* fix(profile-image-cropper): ensure correct image area is saved after transparency check

Fixed an issue where users could not set a profile picture due to incorrect transparency detection.
To fix this, a new cropped blob was generated using the height and width of the imgElement.

Note: this is a simpler fix than the one in the previous commit.

* lint

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-02-14 15:49:22 +00:00
Zack Pollard
dbbefde98d feat: native arm and amd64 server builds (#15408) 2025-02-14 15:55:18 +01:00
Jonathan Jogenfors
5407a28533 feat(server): Nullable asset dates (#15669)
* nullable dates

* wip

* don't search for null dates

* Add placeholder type

* cleanup
2025-02-13 15:30:12 -06:00
bo0tzz
f5edc87e4d feat: comment URL on previewed PRs (#16085) 2025-02-13 21:10:00 +00:00
HelloMihai
bf16b61d43 fix: broken html id (#16084)
ids cannot have spaces

relative should not be in the ID of the element
2025-02-13 14:46:12 -05:00
Joren Guillaume
8c882b54cd docs: put Windows restore command on one line (#16074)
Lots of 'unexpected newline' comments when restoring from other users, this should fix that.
2025-02-13 05:44:33 -05:00
Jason Rasmussen
2d7c333c8c refactor(server): narrow auth types (#16066) 2025-02-12 15:23:08 -05:00
Yaros
7c821dd205 feat(mobile): Made Map Bottom Sheet extendable higher (#16056)
Made Map Bottom Sheet extendable higher
2025-02-12 14:56:50 +00:00
renovate[bot]
703361da1a chore(deps): update dependency svelte to v5.19.9 (#16043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 17:24:39 -06:00
Jason Rasmussen
fa5aeaf539 refactor: last repository (#16042) 2025-02-11 22:15:56 +00:00
Jason Rasmussen
5f3a42a132 refactor: repositories (#16038) 2025-02-11 15:12:31 -05:00
Jason Rasmussen
9d85272c2b refactor: repositories (#16036) 2025-02-11 14:08:13 -05:00
renovate[bot]
d2575d8f00 fix(deps): update typescript-projects (#16023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2025-02-11 18:50:18 +00:00
renovate[bot]
f0a4c945bd chore(deps): update github-actions (#16032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 17:24:47 +00:00
renovate[bot]
a3766b879e fix(deps): update machine-learning (#16012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 11:23:54 -06:00
Alex
1a190c33a0 chore(mobile): post release task (#16004) 2025-02-11 11:23:02 -06:00
renovate[bot]
17a63e37b2 chore(deps): update base-image to v20250211 (major) (#16025)
chore(deps): update base-image to v20250211

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 11:21:25 -06:00
1141 changed files with 46781 additions and 30864 deletions

View File

@@ -1,10 +1,10 @@
ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:9791f4aa527774bc370c6bd2f6705ce5a686f1e6f204badd8dfaacce28c631ae
ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:2ef23730ec68d8511ec8e6e0b82550ca728b256805d81f60ed890f3bfb21cfb9
FROM ${BASEIMAGE}
# Flutter SDK
# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
ENV FLUTTER_CHANNEL="stable"
ENV FLUTTER_VERSION="3.24.5"
ENV FLUTTER_VERSION="3.29.1"
ENV FLUTTER_HOME=/flutter
ENV PATH=${PATH}:${FLUTTER_HOME}/bin

1
.github/.nvmrc vendored Normal file
View File

@@ -0,0 +1 @@
22.14.0

View File

@@ -1,5 +1,5 @@
title: "[Feature] feature-name-goes-here"
labels: ["feature"]
title: '[Feature] feature-name-goes-here'
labels: ['feature']
body:
- type: markdown
@@ -11,9 +11,9 @@ body:
- type: checkboxes
attributes:
label: I have searched the existing feature requests to make sure this is not a duplicate request.
label: I have searched the existing feature requests, both open and closed, to make sure this is not a duplicate request.
options:
- label: "Yes"
- label: 'Yes'
required: true
- type: textarea

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
custom: ['https://buy.immich.app']
custom: ['https://buy.immich.app', 'https://immich.store']

View File

@@ -1,6 +1,13 @@
name: Report an issue with Immich
description: Report an issue with Immich
body:
- type: checkboxes
attributes:
label: I have searched the existing issues, both open and closed, to make sure this is not a duplicate report.
options:
- label: 'Yes'
required: true
- type: markdown
attributes:
value: |
@@ -77,7 +84,7 @@ body:
id: repro
attributes:
label: Reproduction steps
description: "How do you trigger this bug? Please walk us through it step by step."
description: 'How do you trigger this bug? Please walk us through it step by step.'
value: |
1.
2.
@@ -90,12 +97,13 @@ body:
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant logs below. (code formatting is
description:
Please copy and paste any relevant logs below. (code formatting is
enabled, no need for backticks)
render: shell
validations:
required: false
- type: textarea
attributes:
label: Additional information

28
.github/package-lock.json generated vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"name": ".github",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"prettier": "^3.5.3"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
}
}
}

9
.github/package.json vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"scripts": {
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"devDependencies": {
"prettier": "^3.5.3"
}
}

View File

@@ -32,5 +32,5 @@ The `/api/something` endpoint is now `/api/something-else`
- [ ] I have confirmed that any new dependencies are strictly necessary.
- [ ] I have written tests for new code (if applicable)
- [ ] I have followed naming conventions/patterns in the surrounding code
- [ ] All code in `src/services` uses repositories implementations for database calls, filesystem operations, etc.
- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services`)
- [ ] All code in `src/services/` uses repositories implementations for database calls, filesystem operations, etc.
- [ ] All code in `src/repositories/` is pretty basic/simple and does not have any immich specific logic (that belongs in `src/services/`)

66
.github/release.yml vendored
View File

@@ -1,33 +1,33 @@
changelog:
categories:
- title: 🚨 Breaking Changes
labels:
- changelog:breaking-change
- title: 🫥 Deprecated Changes
labels:
- changelog:deprecated
- title: 🔒 Security
labels:
- changelog:security
- title: 🚀 Features
labels:
- changelog:feature
- title: 🌟 Enhancements
labels:
- changelog:enhancement
- title: 🐛 Bug fixes
labels:
- changelog:bugfix
- title: 📚 Documentation
labels:
- changelog:documentation
- title: 🌐 Translations
labels:
- changelog:translation
changelog:
categories:
- title: 🚨 Breaking Changes
labels:
- changelog:breaking-change
- title: 🫥 Deprecated Changes
labels:
- changelog:deprecated
- title: 🔒 Security
labels:
- changelog:security
- title: 🚀 Features
labels:
- changelog:feature
- title: 🌟 Enhancements
labels:
- changelog:enhancement
- title: 🐛 Bug fixes
labels:
- changelog:bugfix
- title: 📚 Documentation
labels:
- changelog:documentation
- title: 🌐 Translations
labels:
- changelog:translation

View File

@@ -22,16 +22,18 @@ jobs:
should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
mobile:
- 'mobile/**'
workflow:
- '.github/workflows/build-mobile.yml'
- name: Check if we should force jobs to run
id: should_force
run: echo "should_force=${{ github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' }}" >> "$GITHUB_OUTPUT"
run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' }}" >> "$GITHUB_OUTPUT"
build-sign-android:
name: Build and sign Android
@@ -49,18 +51,18 @@ jobs:
ref="${input_ref:-$github_ref}"
echo "ref=$ref" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
ref: ${{ steps.get-ref.outputs.ref }}
- uses: actions/setup-java@v4
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4
with:
distribution: 'zulu'
java-version: '17'
cache: 'gradle'
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2
with:
channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml
@@ -87,7 +89,7 @@ jobs:
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
- name: Publish Android Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Cleanup
run: |

View File

@@ -6,7 +6,6 @@ on:
- 'cli/**'
- '.github/workflows/cli.yml'
pull_request:
branches: [main]
paths:
- 'cli/**'
- '.github/workflows/cli.yml'
@@ -29,9 +28,9 @@ jobs:
working-directory: ./cli
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org'
@@ -53,16 +52,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
@@ -77,7 +76,7 @@ jobs:
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
with:
flavor: |
latest=false
@@ -88,7 +87,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
file: cli/Dockerfile
platforms: linux/amd64,linux/arm64

View File

@@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
name: 'CodeQL'
on:
push:
branches: [ "main" ]
branches: ['main']
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
branches: ['main']
schedule:
- cron: '20 13 * * 1'
@@ -36,43 +36,42 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
language: ['javascript', 'python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3
# 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)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# 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
# 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
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3
with:
category: '/language:${{matrix.language}}'

View File

@@ -1,73 +0,0 @@
# This workflow runs on certain conditions to check for and potentially
# delete container images from the GHCR which no longer have an associated
# code branch.
# Requires a PAT with the correct scope set in the secrets.
#
# This workflow will not trigger runs on forked repos.
name: Docker Cleanup
on:
pull_request:
types:
- "closed"
push:
paths:
- ".github/workflows/docker-cleanup.yml"
concurrency:
group: registry-tags-cleanup
cancel-in-progress: false
jobs:
cleanup-images:
name: Cleanup Stale Images Tags for ${{ matrix.primary-name }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- primary-name: "immich-server"
- primary-name: "immich-machine-learning"
env:
# Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
steps:
- name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.9.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"
is_org: "true"
do_delete: "true"
package_name: "${{ matrix.primary-name }}"
scheme: "pull_request"
repo_name: "immich"
match_regex: '^pr-(\d+)$|^(\d+)$'
cleanup-untagged-images:
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
runs-on: ubuntu-24.04
needs:
- cleanup-images
strategy:
fail-fast: false
matrix:
include:
- primary-name: "immich-server"
- primary-name: "immich-machine-learning"
- primary-name: "immich-build-cache"
env:
# Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.PACKAGE_DELETE_TOKEN }}
steps:
- name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.9.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"
do_delete: "true"
is_org: "true"
package_name: "${{ matrix.primary-name }}"

View File

@@ -5,7 +5,6 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]
@@ -24,9 +23,9 @@ jobs:
should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
server:
@@ -36,10 +35,12 @@ jobs:
- 'i18n/**'
machine-learning:
- 'machine-learning/**'
workflow:
- '.github/workflows/docker.yml'
- name: Check if we should force jobs to run
id: should_force
run: echo "should_force=${{ github.event_name == 'workflow_dispatch' || github.event_name == 'release' }}" >> "$GITHUB_OUTPUT"
run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'release' }}" >> "$GITHUB_OUTPUT"
retag_ml:
name: Re-Tag ML
@@ -48,21 +49,23 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
suffix: ["", "-cuda", "-openvino", "-armnn"]
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Re-tag image
run: |
REGISTRY_NAME="ghcr.io"
REPOSITORY=${{ github.repository_owner }}/immich-machine-learning
TAG_OLD=main${{ matrix.suffix }}
TAG_NEW=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_NEW $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Re-tag image
run: |
REGISTRY_NAME="ghcr.io"
REPOSITORY=${{ github.repository_owner }}/immich-machine-learning
TAG_OLD=main${{ matrix.suffix }}
TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
retag_server:
name: Re-Tag Server
@@ -71,10 +74,10 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
suffix: [""]
suffix: ['']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -84,107 +87,110 @@ jobs:
REGISTRY_NAME="ghcr.io"
REPOSITORY=${{ github.repository_owner }}/immich-server
TAG_OLD=main${{ matrix.suffix }}
TAG_NEW=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_NEW $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
build_and_push_ml:
name: Build and Push ML
needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_ml == 'true' }}
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
env:
image: immich-machine-learning
context: machine-learning
file: machine-learning/Dockerfile
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning
strategy:
# Prevent a failure in one image from stopping the other builds
fail-fast: false
matrix:
include:
- platforms: linux/amd64,linux/arm64
- platform: linux/amd64
runner: ubuntu-latest
device: cpu
- platforms: linux/amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
device: cpu
- platform: linux/amd64
runner: ubuntu-latest
device: cuda
suffix: -cuda
- platforms: linux/amd64
- platform: linux/amd64
runner: mich
device: rocm
suffix: -rocm
- platform: linux/amd64
runner: ubuntu-latest
device: openvino
suffix: -openvino
- platforms: linux/arm64
- platform: linux/arm64
runner: ubuntu-24.04-arm
device: armnn
suffix: -armnn
steps:
- name: Checkout
uses: actions/checkout@v4
- platform: linux/arm64
runner: ubuntu-24.04-arm
device: rknn
suffix: -rknn
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
- name: Login to Docker Hub
# Only push to Docker Hub when making a release
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
# Skip when PR from a fork
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
with:
flavor: |
# Disable latest tag
latest=false
images: |
name=ghcr.io/${{ github.repository_owner }}/${{env.image}}
name=altran1502/${{env.image}},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch,suffix=${{ matrix.suffix }}
# Tag with pr-number
type=ref,event=pr,suffix=${{ matrix.suffix }}
# Tag with git tag on release
type=ref,event=tag,suffix=${{ matrix.suffix }}
type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }}
- name: Determine build cache output
id: cache-target
- name: Generate cache key suffix
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Essentially just ignore the cache output (PR can't write to registry cache)
echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV
else
echo "CACHE_KEY_SUFFIX=$(echo ${{ github.ref_name }} | sed 's/[^a-zA-Z0-9]/-/g')" >> $GITHUB_ENV
fi
- name: Generate cache target
id: cache-target
run: |
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
# Essentially just ignore the cache output (forks can't write to registry cache)
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
else
echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ env.image }}" >> $GITHUB_OUTPUT
echo "cache-to=type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }},mode=max,compression=zstd" >> $GITHUB_OUTPUT
fi
- name: Build and push image
uses: docker/build-push-action@v6.13.0
id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{env.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
cache-from: |
type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }}
type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-main
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
build-args: |
DEVICE=${{ matrix.device }}
BUILD_ID=${{ github.run_id }}
@@ -192,100 +198,255 @@ jobs:
BUILD_SOURCE_REF=${{ github.ref_name }}
BUILD_SOURCE_COMMIT=${{ github.sha }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge_ml:
name: Merge & Push ML
runs-on: ubuntu-latest
if: ${{ needs.pre-job.outputs.should_run_ml == 'true' && !github.event.pull_request.head.repo.fork }}
env:
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning
DOCKER_REPO: altran1502/immich-machine-learning
strategy:
matrix:
include:
- device: cpu
- device: cuda
suffix: -cuda
- device: rocm
suffix: -rocm
- device: openvino
suffix: -openvino
- device: armnn
suffix: -armnn
- device: rknn
suffix: -rknn
needs:
- build_and_push_ml
steps:
- name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
with:
path: ${{ runner.temp }}/digests
pattern: ml-digests-${{ matrix.device }}-*
merge-multiple: true
- name: Login to Docker Hub
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
- name: Generate docker image tags
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
env:
DOCKER_METADATA_PR_HEAD_SHA: 'true'
with:
flavor: |
# Disable latest tag
latest=false
suffix=${{ matrix.suffix }}
images: |
name=${{ env.GHCR_REPO }}
name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch
# Tag with pr-number
type=ref,event=pr
# Tag with long commit sha hash
type=sha,format=long,prefix=commit-
# Tag with git tag on release
type=ref,event=tag
type=raw,value=release,enable=${{ github.event_name == 'release' }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
build_and_push_server:
name: Build and Push Server
runs-on: ubuntu-latest
runs-on: ${{ matrix.runner }}
needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
env:
image: immich-server
context: .
file: server/Dockerfile
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
strategy:
fail-fast: false
matrix:
include:
- platforms: linux/amd64,linux/arm64
device: cpu
- platform: linux/amd64
runner: ubuntu-latest
- platform: linux/arm64
runner: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
- name: Login to Docker Hub
# Only push to Docker Hub when making a release
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
# Skip when PR from a fork
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v5
with:
flavor: |
# Disable latest tag
latest=false
images: |
name=ghcr.io/${{ github.repository_owner }}/${{env.image}}
name=altran1502/${{env.image}},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch,suffix=${{ matrix.suffix }}
# Tag with pr-number
type=ref,event=pr,suffix=${{ matrix.suffix }}
# Tag with git tag on release
type=ref,event=tag,suffix=${{ matrix.suffix }}
type=raw,value=release,enable=${{ github.event_name == 'release' }},suffix=${{ matrix.suffix }}
- name: Determine build cache output
id: cache-target
- name: Generate cache key suffix
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Essentially just ignore the cache output (PR can't write to registry cache)
echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV
else
echo "CACHE_KEY_SUFFIX=$(echo ${{ github.ref_name }} | sed 's/[^a-zA-Z0-9]/-/g')" >> $GITHUB_ENV
fi
- name: Generate cache target
id: cache-target
run: |
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
# Essentially just ignore the cache output (forks can't write to registry cache)
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
else
echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ env.image }}" >> $GITHUB_OUTPUT
echo "cache-to=type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }},mode=max,compression=zstd" >> $GITHUB_OUTPUT
fi
- name: Build and push image
uses: docker/build-push-action@v6.13.0
id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{env.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.metadata.outputs.labels }}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
cache-from: |
type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ env.CACHE_KEY_SUFFIX }}
type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-main
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
build-args: |
DEVICE=${{ matrix.device }}
DEVICE=cpu
BUILD_ID=${{ github.run_id }}
BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
BUILD_SOURCE_REF=${{ github.ref_name }}
BUILD_SOURCE_COMMIT=${{ github.sha }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: server-digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
merge_server:
name: Merge & Push Server
runs-on: ubuntu-latest
if: ${{ needs.pre-job.outputs.should_run_server == 'true' && !github.event.pull_request.head.repo.fork }}
env:
GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
DOCKER_REPO: altran1502/immich-server
needs:
- build_and_push_server
steps:
- name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
with:
path: ${{ runner.temp }}/digests
pattern: server-digests-*
merge-multiple: true
- name: Login to Docker Hub
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
- name: Generate docker image tags
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
env:
DOCKER_METADATA_PR_HEAD_SHA: 'true'
with:
flavor: |
# Disable latest tag
latest=false
suffix=${{ matrix.suffix }}
images: |
name=${{ env.GHCR_REPO }}
name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch
# Tag with pr-number
type=ref,event=pr
# Tag with long commit sha hash
type=sha,format=long,prefix=commit-
# Tag with git tag on release
type=ref,event=tag
type=raw,value=release,enable=${{ github.event_name == 'release' }}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.GHCR_REPO }}@sha256:%s ' *)
success-check-server:
name: Docker Build & Push Server Success
needs: [build_and_push_server, retag_server]
needs: [merge_server, retag_server]
runs-on: ubuntu-latest
if: always()
steps:
@@ -298,7 +459,7 @@ jobs:
success-check-ml:
name: Docker Build & Push ML Success
needs: [build_and_push_ml, retag_ml]
needs: [merge_ml, retag_ml]
runs-on: ubuntu-latest
if: always()
steps:

View File

@@ -3,7 +3,6 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
release:
types: [published]
@@ -15,19 +14,21 @@ jobs:
pre-job:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.found_paths.outputs.docs == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run: ${{ steps.found_paths.outputs.docs == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
docs:
- 'docs/**'
workflow:
- '.github/workflows/docs-build.yml'
- name: Check if we should force jobs to run
id: should_force
run: echo "should_force=${{ github.event_name == 'release' || github.ref_name == 'main' }}" >> "$GITHUB_OUTPUT"
run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'release' || github.ref_name == 'main' }}" >> "$GITHUB_OUTPUT"
build:
name: Docs Build
@@ -40,10 +41,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './docs/.nvmrc'
@@ -57,7 +58,7 @@ jobs:
run: npm run build
- name: Upload build output
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: docs-build-output
path: docs/build/

View File

@@ -1,7 +1,7 @@
name: Docs deploy
on:
workflow_run:
workflows: ["Docs build"]
workflows: ['Docs build']
types:
- completed
@@ -17,7 +17,7 @@ jobs:
run: echo 'The triggering workflow did not succeed' && exit 1
- name: Get artifact
id: get-artifact
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -35,7 +35,7 @@ jobs:
return { found: true, id: matchArtifact.id };
- name: Determine deploy parameters
id: parameters
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const eventType = context.payload.workflow_run.event;
@@ -98,11 +98,11 @@ jobs:
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Load parameters
id: parameters
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
const json = `${{ needs.checks.outputs.parameters }}`;
@@ -115,7 +115,7 @@ jobs:
echo "Starting docs deployment for ${{ steps.parameters.outputs.event }} ${{ steps.parameters.outputs.name }}"
- name: Download artifact
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
let artifact = ${{ needs.checks.outputs.artifact }};
@@ -138,12 +138,12 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2
with:
tg_version: "0.58.12"
tofu_version: "1.7.1"
tg_dir: "deployment/modules/cloudflare/docs"
tg_command: "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
id: docs-output
@@ -153,12 +153,12 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2
with:
tg_version: "0.58.12"
tofu_version: "1.7.1"
tg_dir: "deployment/modules/cloudflare/docs"
tg_command: "output -json"
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
@@ -167,13 +167,13 @@ jobs:
echo "output=$TG_OUT" >> $GITHUB_OUTPUT
- name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@v1
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }}
workingDirectory: "docs"
directory: "build"
workingDirectory: 'docs'
directory: 'build'
branch: ${{ steps.parameters.outputs.name }}
wranglerVersion: '3'
@@ -184,7 +184,7 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2
with:
tg_version: '0.58.12'
tofu_version: '1.7.1'
@@ -192,7 +192,7 @@ jobs:
tg_command: 'apply'
- name: Comment
uses: actions-cool/maintain-one-comment@v3
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3
if: ${{ steps.parameters.outputs.event == 'pr' }}
with:
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}

View File

@@ -9,24 +9,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Destroy Docs Subdomain
env:
TF_VAR_prefix_name: "pr-${{ github.event.number }}"
TF_VAR_prefix_event_type: "pr"
TF_VAR_prefix_name: 'pr-${{ github.event.number }}'
TF_VAR_prefix_event_type: 'pr'
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@v2
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2
with:
tg_version: "0.58.12"
tofu_version: "1.7.1"
tg_dir: "deployment/modules/cloudflare/docs"
tg_command: "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
uses: actions-cool/maintain-one-comment@v3
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3
with:
number: ${{ github.event.number }}
delete: true

View File

@@ -13,19 +13,19 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: 'Checkout'
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
ref: ${{ github.event.pull_request.head.ref }}
token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './server/.nvmrc'
@@ -33,13 +33,13 @@ jobs:
run: make install-all && make format-all
- name: Commit and push
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9
with:
default_author: github_actions
message: 'chore: fix formatting'
- name: Remove label
uses: actions/github-script@v7
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
if: always()
with:
script: |
@@ -49,4 +49,3 @@ jobs:
repo: context.repo.repo,
name: 'fix:formatting'
})

View File

@@ -12,11 +12,11 @@ jobs:
pull-requests: write
steps:
- name: Require PR to have a changelog label
uses: mheap/github-action-required-labels@v5
uses: mheap/github-action-required-labels@388fd6af37b34cdfe5a23b37060e763217e58b03 # v5
with:
mode: exactly
count: 1
use_regex: true
labels: "changelog:.*"
labels: 'changelog:.*'
add_comment: true
message: "Label error. Requires {{errorString}} {{count}} of: {{ provided }}. Found: {{ applied }}. A maintainer will add the required label."
message: 'Label error. Requires {{errorString}} {{count}} of: {{ provided }}. Found: {{ applied }}. A maintainer will add the required label.'

View File

@@ -1,6 +1,6 @@
name: "Pull Request Labeler"
name: 'Pull Request Labeler'
on:
- pull_request_target
- pull_request_target
jobs:
labeler:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: PR Conventional Commit Validation
uses: ytanikin/PRConventionalCommits@1.3.0
uses: ytanikin/PRConventionalCommits@b628c5a234cc32513014b7bfdd1e47b532124d98 # 1.3.0
with:
task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
add_label: 'false'

View File

@@ -31,25 +31,25 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Install Poetry
run: pipx install poetry
- name: Install uv
uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5
- name: Bump version
run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}"
- name: Commit and tag
id: push-tag
uses: EndBug/add-and-commit@v9
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9
with:
default_author: github_actions
message: 'chore: version ${{ env.IMMICH_VERSION }}'
@@ -70,23 +70,23 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1
with:
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
token: ${{ steps.generate-token.outputs.token }}
- name: Download APK
uses: actions/download-artifact@v4
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
with:
name: release-apk-signed
- name: Create draft release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2
with:
draft: true
tag_name: ${{ env.IMMICH_VERSION }}

33
.github/workflows/preview-label.yaml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Preview label
on:
pull_request:
types: [labeled, closed]
jobs:
comment-status:
runs-on: ubuntu-latest
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'preview' }}
permissions:
pull-requests: write
steps:
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2
with:
message-id: 'preview-status'
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/'
remove-label:
runs-on: ubuntu-latest
if: ${{ github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'preview') }}
permissions:
pull-requests: write
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
script: |
github.rest.issues.removeLabel({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
name: 'preview'
})

View File

@@ -15,9 +15,9 @@ jobs:
run:
working-directory: ./open-api/typescript-sdk
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './open-api/typescript-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -16,16 +16,18 @@ jobs:
should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
mobile:
- 'mobile/**'
workflow:
- '.github/workflows/static_analysis.yml'
- name: Check if we should force jobs to run
id: should_force
run: echo "should_force=${{ github.event_name == 'release' }}" >> "$GITHUB_OUTPUT"
run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'release' }}" >> "$GITHUB_OUTPUT"
mobile-dart-analyze:
name: Run Dart Code Analysis
@@ -36,10 +38,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2
with:
channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml
@@ -48,6 +50,26 @@ jobs:
run: dart pub get
working-directory: ./mobile
- name: Run Build Runner
run: make build
working-directory: ./mobile
- name: Find file changes
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20
id: verify-changed-files
with:
files: |
mobile/**/*.g.dart
mobile/**/*.gr.dart
mobile/**/*.drift.dart
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |
echo "ERROR: Generated files not up to date! Run make_build inside the mobile directory"
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
exit 1
- name: Run dart analyze
run: dart analyze --fatal-infos
working-directory: ./mobile
@@ -59,8 +81,3 @@ jobs:
- name: Run dart custom_lint
run: dart run custom_lint
working-directory: ./mobile
# Enable after riverpod generator migration is completed
# - name: Run dart custom lint
# run: dart run custom_lint
# working-directory: ./mobile

View File

@@ -21,11 +21,12 @@ jobs:
should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_e2e_web: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.web == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_e2e_server_cli: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.server == 'true' || steps.found_paths.outputs.cli == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_.github: ${{ steps.found_paths.outputs['.github'] == 'true' || steps.should_force.outputs.should_force == 'true' }} # redundant to have should_force but if someone changes the trigger then this won't have to be changed
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@v3
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
web:
@@ -43,10 +44,14 @@ jobs:
- 'mobile/**'
machine-learning:
- 'machine-learning/**'
workflow:
- '.github/workflows/test.yml'
.github:
- '.github/**'
- name: Check if we should force jobs to run
id: should_force
run: echo "should_force=${{ github.event_name == 'workflow_dispatch' }}" >> "$GITHUB_OUTPUT"
run: echo "should_force=${{ steps.found_paths.outputs.workflow == 'true' || github.event_name == 'workflow_dispatch' }}" >> "$GITHUB_OUTPUT"
server-unit-tests:
name: Test & Lint Server
@@ -59,10 +64,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './server/.nvmrc'
@@ -96,10 +101,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './cli/.nvmrc'
@@ -137,10 +142,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './cli/.nvmrc'
@@ -171,10 +176,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './web/.nvmrc'
@@ -216,10 +221,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './e2e/.nvmrc'
@@ -244,25 +249,30 @@ jobs:
run: npm run check
if: ${{ !cancelled() }}
medium-tests-server:
server-medium-tests:
name: Medium Tests (Server)
needs: pre-job
if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
runs-on: mich
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./server
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: 'recursive'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Production build
if: ${{ !cancelled() }}
run: docker compose -f e2e/docker-compose.yml build
- name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './server/.nvmrc'
- name: Run npm install
run: npm ci
- name: Run medium tests
run: npm run test:medium
if: ${{ !cancelled() }}
run: make test-medium
e2e-tests-server-cli:
name: End-to-End Tests (Server & CLI)
@@ -275,12 +285,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
submodules: 'recursive'
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './e2e/.nvmrc'
@@ -317,12 +327,12 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
submodules: 'recursive'
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './e2e/.nvmrc'
@@ -353,9 +363,9 @@ jobs:
if: ${{ needs.pre-job.outputs.should_run_mobile == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2
with:
channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml
@@ -372,34 +382,60 @@ jobs:
run:
working-directory: ./machine-learning
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: 3.11
cache: 'poetry'
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install uv
uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with:
# python-version: 3.11
# cache: 'uv'
- name: Install dependencies
run: |
poetry install --with dev --with cpu
uv sync --extra cpu
- name: Lint with ruff
run: |
poetry run ruff check --output-format=github app export
uv run ruff check --output-format=github app export
- name: Check black formatting
run: |
poetry run black --check app export
uv run black --check app export
- name: Run mypy type checking
run: |
poetry run mypy --install-types --non-interactive --strict app/
uv run mypy --strict app/
- name: Run tests and coverage
run: |
poetry run pytest app --cov=app --cov-report term-missing
uv run pytest app --cov=app --cov-report term-missing
github-files-formatting:
name: .github Files Formatting
needs: pre-job
if: ${{ needs.pre-job.outputs['should_run_.github'] == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./.github
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './.github/.nvmrc'
- name: Run npm install
run: npm ci
- name: Run formatter
run: npm run format
if: ${{ !cancelled() }}
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
@@ -413,10 +449,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './server/.nvmrc'
@@ -430,7 +466,7 @@ jobs:
run: make open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@v20
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20
id: verify-changed-files
with:
files: |
@@ -450,7 +486,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
@@ -468,10 +504,10 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version-file: './server/.nvmrc'
@@ -492,7 +528,7 @@ jobs:
run: npm run typeorm:migrations:generate ./src/migrations/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@v20
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20
id: verify-changed-files
with:
files: |
@@ -502,6 +538,7 @@ jobs:
run: |
echo "ERROR: Generated migration files not up to date!"
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
cat ./src/migrations/*-TestMigration.ts
exit 1
- name: Run SQL generation
@@ -510,7 +547,7 @@ jobs:
DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes
uses: tj-actions/verify-changed-files@v20
uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20
id: verify-changed-sql-files
with:
files: |

57
.github/workflows/weblate-lock.yml vendored Normal file
View File

@@ -0,0 +1,57 @@
name: Weblate checks
on:
pull_request:
branches: [main]
jobs:
pre-job:
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
i18n:
- 'i18n/!(en)**\.json'
- name: Debug
run: |
echo "Should run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}"
echo "Found i18n paths: ${{ steps.found_paths.outputs.i18n }}"
echo "Head ref: ${{ github.head_ref }}"
enforce-lock:
name: Check Weblate Lock
needs: [pre-job]
runs-on: ubuntu-latest
if: ${{ needs.pre-job.outputs.should_run == 'true' }}
steps:
- name: Check weblate lock
run: |
if [[ "false" = $(curl https://hosted.weblate.org/api/components/immich/immich/lock/ | jq .locked) ]]; then
exit 1
fi
- name: Find Pull Request
uses: juliangruber/find-pull-request-action@48b6133aa6c826f267ebd33aa2d29470f9d9e7d0 # v1
id: find-pr
with:
branch: chore/translations
- name: Fail if existing weblate PR
if: ${{ steps.find-pr.outputs.number }}
run: exit 1
success-check-lock:
name: Weblate Lock Check Success
needs: [enforce-lock]
runs-on: ubuntu-latest
if: always()
steps:
- name: Any jobs failed?
if: ${{ contains(needs.*.result, 'failure') }}
run: exit 1
- name: All jobs passed or skipped
if: ${{ !(contains(needs.*.result, 'failure')) }}
run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}"

View File

@@ -39,7 +39,7 @@ attach-server:
renovate:
LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
MODULES = e2e server web cli sdk docs
MODULES = e2e server web cli sdk docs .github
audit-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix
@@ -77,14 +77,14 @@ test-medium:
test-medium-dev:
docker exec -it immich_server /bin/sh -c "npm run test:medium"
build-all: $(foreach M,$(filter-out e2e,$(MODULES)),build-$M) ;
build-all: $(foreach M,$(filter-out e2e .github,$(MODULES)),build-$M) ;
install-all: $(foreach M,$(MODULES),install-$M) ;
check-all: $(foreach M,$(filter-out sdk cli docs,$(MODULES)),check-$M) ;
lint-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),lint-$M) ;
check-all: $(foreach M,$(filter-out sdk cli docs .github,$(MODULES)),check-$M) ;
lint-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),lint-$M) ;
format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ;
audit-all: $(foreach M,$(MODULES),audit-$M) ;
hygiene-all: lint-all format-all check-all sql audit-all;
test-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),test-$M) ;
test-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),test-$M) ;
clean:
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +

View File

@@ -1,11 +1,11 @@
<p align="center">
<br/>
<br/>
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: AGPLv3"></a>
<a href="https://discord.immich.app">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
<br/>
<br/>
<br/>
</p>
<p align="center">
@@ -63,7 +63,7 @@
Access the demo [here](https://demo.immich.app). The demo is running on a Free-tier Oracle VM in Amsterdam with a 2.4Ghz quad-core ARM64 CPU and 24GB RAM.
For the mobile app, you can use `https://demo.immich.app/api` for the `Server Endpoint URL`
For the mobile app, you can use `https://demo.immich.app` for the `Server Endpoint URL`
### Login credentials

View File

@@ -1 +1 @@
22.13.1
22.14.0

View File

@@ -1,4 +1,4 @@
FROM node:22.13.1-alpine3.20@sha256:c52e20859a92b3eccbd3a36c5e1a90adc20617d8d421d65e8a622e87b5dac963 AS core
FROM node:22.14.0-alpine3.20@sha256:40be979442621049f40b1d51a26b55e281246b5de4e5f51a18da7beb6e17e3f9 AS core
WORKDIR /usr/src/open-api/typescript-sdk
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./

676
cli/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.50",
"version": "2.2.55",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",
@@ -19,8 +19,9 @@
"@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/micromatch": "^4.0.9",
"@types/mock-fs": "^4.13.1",
"@types/node": "^22.13.1",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0",
@@ -31,7 +32,7 @@
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^56.0.1",
"globals": "^15.9.0",
"globals": "^16.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^4.0.0",
@@ -62,11 +63,13 @@
"node": ">=20.0.0"
},
"dependencies": {
"chokidar": "^4.0.3",
"fast-glob": "^3.3.2",
"fastq": "^1.17.1",
"lodash-es": "^4.17.21"
"lodash-es": "^4.17.21",
"micromatch": "^4.0.8"
},
"volta": {
"node": "22.13.1"
"node": "22.14.0"
}
}

View File

@@ -1,12 +1,13 @@
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { describe, expect, it, vi } from 'vitest';
import { setTimeout as sleep } from 'node:timers/promises';
import { describe, expect, it, MockedFunction, vi } from 'vitest';
import { Action, checkBulkUpload, defaults, Reason } from '@immich/sdk';
import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk';
import createFetchMock from 'vitest-fetch-mock';
import { checkForDuplicates, getAlbumName, uploadFiles, UploadOptionsDto } from './asset';
import { checkForDuplicates, getAlbumName, startWatch, uploadFiles, UploadOptionsDto } from 'src/commands/asset';
vi.mock('@immich/sdk');
@@ -199,3 +200,112 @@ describe('checkForDuplicates', () => {
});
});
});
describe('startWatch', () => {
let testFolder: string;
let checkBulkUploadMocked: MockedFunction<typeof checkBulkUpload>;
beforeEach(async () => {
vi.restoreAllMocks();
vi.mocked(getSupportedMediaTypes).mockResolvedValue({
image: ['.jpg'],
sidecar: ['.xmp'],
video: ['.mp4'],
});
testFolder = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'test-startWatch-'));
checkBulkUploadMocked = vi.mocked(checkBulkUpload);
checkBulkUploadMocked.mockResolvedValue({
results: [],
});
});
it('should start watching a directory and upload new files', async () => {
const testFilePath = path.join(testFolder, 'test.jpg');
await startWatch([testFolder], { concurrency: 1 }, { batchSize: 1, debounceTimeMs: 10 });
await sleep(100); // to debounce the watcher from considering the test file as a existing file
await fs.promises.writeFile(testFilePath, 'testjpg');
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: [
expect.objectContaining({
id: testFilePath,
}),
],
},
});
});
it('should filter out unsupported files', async () => {
const testFilePath = path.join(testFolder, 'test.jpg');
const unsupportedFilePath = path.join(testFolder, 'test.txt');
await startWatch([testFolder], { concurrency: 1 }, { batchSize: 1, debounceTimeMs: 10 });
await sleep(100); // to debounce the watcher from considering the test file as a existing file
await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(unsupportedFilePath, 'testtxt');
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
});
expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: unsupportedFilePath,
}),
]),
},
});
});
it('should filger out ignored patterns', async () => {
const testFilePath = path.join(testFolder, 'test.jpg');
const ignoredPattern = 'ignored';
const ignoredFolder = path.join(testFolder, ignoredPattern);
await fs.promises.mkdir(ignoredFolder, { recursive: true });
const ignoredFilePath = path.join(ignoredFolder, 'ignored.jpg');
await startWatch([testFolder], { concurrency: 1, ignore: ignoredPattern }, { batchSize: 1, debounceTimeMs: 10 });
await sleep(100); // to debounce the watcher from considering the test file as a existing file
await fs.promises.writeFile(testFilePath, 'testjpg');
await fs.promises.writeFile(ignoredFilePath, 'ignoredjpg');
await vi.waitUntil(() => checkBulkUploadMocked.mock.calls.length > 0, 3000);
expect(checkBulkUpload).toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: testFilePath,
}),
]),
},
});
expect(checkBulkUpload).not.toHaveBeenCalledWith({
assetBulkUploadCheckDto: {
assets: expect.arrayContaining([
expect.objectContaining({
id: ignoredFilePath,
}),
]),
},
});
});
afterEach(async () => {
await fs.promises.rm(testFolder, { recursive: true, force: true });
});
});

View File

@@ -12,13 +12,18 @@ import {
getSupportedMediaTypes,
} from '@immich/sdk';
import byteSize from 'byte-size';
import { Matcher, watch as watchFs } from 'chokidar';
import { MultiBar, Presets, SingleBar } from 'cli-progress';
import { chunk } from 'lodash-es';
import micromatch from 'micromatch';
import { Stats, createReadStream } from 'node:fs';
import { stat, unlink } from 'node:fs/promises';
import path, { basename } from 'node:path';
import { Queue } from 'src/queue';
import { BaseOptions, authenticate, crawl, sha1 } from 'src/utils';
import { BaseOptions, Batcher, authenticate, crawl, sha1 } from 'src/utils';
const UPLOAD_WATCH_BATCH_SIZE = 100;
const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 10_000;
const s = (count: number) => (count === 1 ? '' : 's');
@@ -36,6 +41,8 @@ export interface UploadOptionsDto {
albumName?: string;
includeHidden?: boolean;
concurrency: number;
progress?: boolean;
watch?: boolean;
}
class UploadFile extends File {
@@ -55,19 +62,94 @@ class UploadFile extends File {
}
}
const uploadBatch = async (files: string[], options: UploadOptionsDto) => {
const { newFiles, duplicates } = await checkForDuplicates(files, options);
const newAssets = await uploadFiles(newFiles, options);
await updateAlbums([...newAssets, ...duplicates], options);
await deleteFiles(newFiles, options);
};
export const startWatch = async (
paths: string[],
options: UploadOptionsDto,
{
batchSize = UPLOAD_WATCH_BATCH_SIZE,
debounceTimeMs = UPLOAD_WATCH_DEBOUNCE_TIME_MS,
}: { batchSize?: number; debounceTimeMs?: number } = {},
) => {
const watcherIgnored: Matcher[] = [];
const { image, video } = await getSupportedMediaTypes();
const extensions = new Set([...image, ...video]);
if (options.ignore) {
watcherIgnored.push((path) => micromatch.contains(path, `**/${options.ignore}`));
}
const pathsBatcher = new Batcher<string>({
batchSize,
debounceTimeMs,
onBatch: async (paths: string[]) => {
const uniquePaths = [...new Set(paths)];
await uploadBatch(uniquePaths, options);
},
});
const onFile = async (path: string, stats?: Stats) => {
if (stats?.isDirectory()) {
return;
}
const ext = '.' + path.split('.').pop()?.toLowerCase();
if (!ext || !extensions.has(ext)) {
return;
}
if (!options.progress) {
// logging when progress is disabled as it can cause issues with the progress bar rendering
console.log(`Change detected: ${path}`);
}
pathsBatcher.add(path);
};
const fsWatcher = watchFs(paths, {
ignoreInitial: true,
ignored: watcherIgnored,
alwaysStat: true,
awaitWriteFinish: true,
depth: options.recursive ? undefined : 1,
persistent: true,
})
.on('add', onFile)
.on('change', onFile)
.on('error', (error) => console.error(`Watcher error: ${error}`));
process.on('SIGINT', async () => {
console.log('Exiting...');
await fsWatcher.close();
process.exit();
});
};
export const upload = async (paths: string[], baseOptions: BaseOptions, options: UploadOptionsDto) => {
await authenticate(baseOptions);
const scanFiles = await scan(paths, options);
if (scanFiles.length === 0) {
console.log('No files found, exiting');
return;
if (options.watch) {
console.log('No files found initially.');
} else {
console.log('No files found, exiting');
return;
}
}
const { newFiles, duplicates } = await checkForDuplicates(scanFiles, options);
const newAssets = await uploadFiles(newFiles, options);
await updateAlbums([...newAssets, ...duplicates], options);
await deleteFiles(newFiles, options);
if (options.watch) {
console.log('Watching for changes...');
await startWatch(paths, options);
// watcher does not handle the initial scan
// as the scan() is a more efficient quick start with batched results
}
await uploadBatch(scanFiles, options);
};
const scan = async (pathsToCrawl: string[], options: UploadOptionsDto) => {
@@ -85,19 +167,25 @@ const scan = async (pathsToCrawl: string[], options: UploadOptionsDto) => {
return files;
};
export const checkForDuplicates = async (files: string[], { concurrency, skipHash }: UploadOptionsDto) => {
export const checkForDuplicates = async (files: string[], { concurrency, skipHash, progress }: UploadOptionsDto) => {
if (skipHash) {
console.log('Skipping hash check, assuming all files are new');
return { newFiles: files, duplicates: [] };
}
const multiBar = new MultiBar(
{ format: '{message} | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' },
Presets.shades_classic,
);
let multiBar: MultiBar | undefined;
const hashProgressBar = multiBar.create(files.length, 0, { message: 'Hashing files ' });
const checkProgressBar = multiBar.create(files.length, 0, { message: 'Checking for duplicates' });
if (progress) {
multiBar = new MultiBar(
{ format: '{message} | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets' },
Presets.shades_classic,
);
} else {
console.log(`Received ${files.length} files, hashing...`);
}
const hashProgressBar = multiBar?.create(files.length, 0, { message: 'Hashing files ' });
const checkProgressBar = multiBar?.create(files.length, 0, { message: 'Checking for duplicates' });
const newFiles: string[] = [];
const duplicates: Asset[] = [];
@@ -117,7 +205,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
}
}
checkProgressBar.increment(assets.length);
checkProgressBar?.increment(assets.length);
},
{ concurrency, retry: 3 },
);
@@ -137,7 +225,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
void checkBulkUploadQueue.push(batch);
}
hashProgressBar.increment();
hashProgressBar?.increment();
return results;
},
{ concurrency, retry: 3 },
@@ -155,7 +243,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
await checkBulkUploadQueue.drained();
multiBar.stop();
multiBar?.stop();
console.log(`Found ${newFiles.length} new files and ${duplicates.length} duplicate${s(duplicates.length)}`);
@@ -171,7 +259,10 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas
return { newFiles, duplicates };
};
export const uploadFiles = async (files: string[], { dryRun, concurrency }: UploadOptionsDto): Promise<Asset[]> => {
export const uploadFiles = async (
files: string[],
{ dryRun, concurrency, progress }: UploadOptionsDto,
): Promise<Asset[]> => {
if (files.length === 0) {
console.log('All assets were already uploaded, nothing to do.');
return [];
@@ -191,12 +282,20 @@ export const uploadFiles = async (files: string[], { dryRun, concurrency }: Uplo
return files.map((filepath) => ({ id: '', filepath }));
}
const uploadProgress = new SingleBar(
{ format: 'Uploading assets | {bar} | {percentage}% | ETA: {eta_formatted} | {value_formatted}/{total_formatted}' },
Presets.shades_classic,
);
uploadProgress.start(totalSize, 0);
uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
let uploadProgress: SingleBar | undefined;
if (progress) {
uploadProgress = new SingleBar(
{
format: 'Uploading assets | {bar} | {percentage}% | ETA: {eta_formatted} | {value_formatted}/{total_formatted}',
},
Presets.shades_classic,
);
} else {
console.log(`Uploading ${files.length} asset${s(files.length)} (${byteSize(totalSize)})`);
}
uploadProgress?.start(totalSize, 0);
uploadProgress?.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
let duplicateCount = 0;
let duplicateSize = 0;
@@ -222,7 +321,7 @@ export const uploadFiles = async (files: string[], { dryRun, concurrency }: Uplo
successSize += stats.size ?? 0;
}
uploadProgress.update(successSize, { value_formatted: byteSize(successSize + duplicateSize) });
uploadProgress?.update(successSize, { value_formatted: byteSize(successSize + duplicateSize) });
return response;
},
@@ -235,7 +334,7 @@ export const uploadFiles = async (files: string[], { dryRun, concurrency }: Uplo
await queue.drained();
uploadProgress.stop();
uploadProgress?.stop();
console.log(`Successfully uploaded ${successCount} new asset${s(successCount)} (${byteSize(successSize)})`);
if (duplicateCount > 0) {

View File

@@ -69,6 +69,13 @@ program
.default(4),
)
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
.addOption(new Option('--no-progress', 'Hide progress bars').env('IMMICH_PROGRESS_BAR').default(true))
.addOption(
new Option('--watch', 'Watch for changes and upload automatically')
.env('IMMICH_WATCH_CHANGES')
.default(false)
.implies({ progress: false }),
)
.argument('[paths...]', 'One or more paths to assets to be uploaded')
.action((paths, options) => upload(paths, program.opts(), options));

View File

@@ -1,6 +1,7 @@
import mockfs from 'mock-fs';
import { readFileSync } from 'node:fs';
import { CrawlOptions, crawl } from 'src/utils';
import { Batcher, CrawlOptions, crawl } from 'src/utils';
import { Mock } from 'vitest';
interface Test {
test: string;
@@ -303,3 +304,38 @@ describe('crawl', () => {
}
});
});
describe('Batcher', () => {
let batcher: Batcher;
let onBatch: Mock;
beforeEach(() => {
onBatch = vi.fn();
batcher = new Batcher({ batchSize: 2, onBatch });
});
it('should trigger onBatch() when a batch limit is reached', async () => {
batcher.add('a');
batcher.add('b');
batcher.add('c');
expect(onBatch).toHaveBeenCalledOnce();
expect(onBatch).toHaveBeenCalledWith(['a', 'b']);
});
it('should trigger onBatch() when flush() is called', async () => {
batcher.add('a');
batcher.flush();
expect(onBatch).toHaveBeenCalledOnce();
expect(onBatch).toHaveBeenCalledWith(['a']);
});
it('should trigger onBatch() when debounce time reached', async () => {
vi.useFakeTimers();
batcher = new Batcher({ batchSize: 2, debounceTimeMs: 100, onBatch });
batcher.add('a');
expect(onBatch).not.toHaveBeenCalled();
vi.advanceTimersByTime(200);
expect(onBatch).toHaveBeenCalledOnce();
expect(onBatch).toHaveBeenCalledWith(['a']);
vi.useRealTimers();
});
});

View File

@@ -172,3 +172,64 @@ export const sha1 = (filepath: string) => {
rs.on('end', () => resolve(hash.digest('hex')));
});
};
/**
* Batches items and calls onBatch to process them
* when the batch size is reached or the debounce time has passed.
*/
export class Batcher<T = unknown> {
private items: T[] = [];
private readonly batchSize: number;
private readonly debounceTimeMs?: number;
private readonly onBatch: (items: T[]) => void;
private debounceTimer?: NodeJS.Timeout;
constructor({
batchSize,
debounceTimeMs,
onBatch,
}: {
batchSize: number;
debounceTimeMs?: number;
onBatch: (items: T[]) => Promise<void>;
}) {
this.batchSize = batchSize;
this.debounceTimeMs = debounceTimeMs;
this.onBatch = onBatch;
}
private setDebounceTimer() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
if (this.debounceTimeMs) {
this.debounceTimer = setTimeout(() => this.flush(), this.debounceTimeMs);
}
}
private clearDebounceTimer() {
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = undefined;
}
}
add(item: T) {
this.items.push(item);
this.setDebounceTimer();
if (this.items.length >= this.batchSize) {
this.flush();
}
}
flush() {
this.clearDebounceTimer();
if (this.items.length === 0) {
return;
}
this.onBatch(this.items);
this.items = [];
}
}

View File

@@ -1,4 +1,13 @@
# See:
#
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
# For development see:
# - https://immich.app/docs/developer/setup
# - https://immich.app/docs/developer/troubleshooting
@@ -16,7 +25,7 @@ services:
context: ../
dockerfile: server/Dockerfile
target: dev
restart: always
restart: unless-stopped
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
@@ -86,12 +95,12 @@ services:
image: immich-machine-learning-dev:latest
# extends:
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference
build:
context: ../machine-learning
dockerfile: Dockerfile
args:
- DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference
ports:
- 3003:3003
volumes:
@@ -107,13 +116,13 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
image: redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8
healthcheck:
test: redis-cli ping || exit 1
database:
container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
env_file:
- .env
environment:

View File

@@ -1,3 +1,12 @@
#
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
name: immich-prod
services:
@@ -29,12 +38,12 @@ services:
image: immich-machine-learning:latest
# extends:
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference
build:
context: ../machine-learning
dockerfile: Dockerfile
args:
- DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference
ports:
- 3003:3003
volumes:
@@ -47,14 +56,14 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
image: redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
env_file:
- .env
environment:
@@ -68,22 +77,12 @@ services:
- 5432:5432
healthcheck:
test: >-
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
--command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = '0' ] || exit 1
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: >-
postgres
-c shared_preload_libraries=vectors.so
-c 'search_path="$$user", public, vectors'
-c logging_collector=on
-c max_wal_size=2GB
-c shared_buffers=512MB
-c wal_compression=on
postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on
restart: always
# set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics
@@ -91,7 +90,7 @@ services:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218
image: prom/prometheus@sha256:502ad90314c7485892ce696cb14a99fceab9fc27af29f4b427f41bd39701a199
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
@@ -100,10 +99,10 @@ services:
# add data source for http://immich-prometheus:9090 to get started
immich-grafana:
container_name: immich_grafana
command: ['./run.sh', '-disable-reporting']
command: [ './run.sh', '-disable-reporting' ]
ports:
- 3000:3000
image: grafana/grafana:11.5.1-ubuntu@sha256:9a4ab78cec1a2ec7d1ca5dfd5aacec6412706a1bc9e971fc7184e2f6696a63f5
image: grafana/grafana:11.5.2-ubuntu@sha256:8b5858c447e06fd7a89006b562ba7bba7c4d5813600c7982374c41852adefaeb
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -1,10 +1,11 @@
#
# WARNING: Make sure to use the docker-compose.yml of the current release:
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
#
name: immich
@@ -32,12 +33,12 @@ services:
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, openvino] 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
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
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] 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:
- model-cache:/cache
env_file:
@@ -48,14 +49,14 @@ services:
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
image: docker.io/redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
@@ -66,22 +67,12 @@ services:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: >-
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
--command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = '0' ] || exit 1
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: >-
postgres
-c shared_preload_libraries=vectors.so
-c 'search_path="$$user", public, vectors'
-c logging_collector=on
-c max_wal_size=2GB
-c shared_buffers=512MB
-c wal_compression=on
postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on
restart: always
volumes:

View File

@@ -13,6 +13,13 @@ services:
volumes:
- /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver)
- /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required)
rknn:
security_opt:
- systempaths=unconfined
- apparmor=unconfined
devices:
- /dev/dri:/dev/dri
cpu: {}
@@ -26,6 +33,13 @@ services:
capabilities:
- gpu
rocm:
group_add:
- video
devices:
- /dev/dri:/dev/dri
- /dev/kfd:/dev/kfd
openvino:
device_cgroup_rules:
- 'c 189:* rmw'

View File

@@ -1 +1 @@
22.13.1
22.14.0

View File

@@ -97,7 +97,7 @@ Make sure to [set your reverse proxy](/docs/administration/reverse-proxy/) to al
Also, check the disk space of your reverse proxy.
In some cases, proxies cache requests to disk before passing them on, and if disk space runs out, the request fails.
If you are using Cloudflare Tunnel, please know that they set a maxiumum filesize of 100 MB that cannot be changed.
If you are using Cloudflare Tunnel, please know that they set a maximum filesize of 100 MB that cannot be changed.
At times, files larger than this may work, potentially up to 1 GB. However, the official limit is 100 MB.
If you are having issues, we recommend switching to a different network deployment.
@@ -117,7 +117,7 @@ See [Backup and Restore](/docs/administration/backup-and-restore.md).
### Does Immich support reading existing face tag metadata?
No, it currently does not. There is an [open feature request on GitHub](https://github.com/immich-app/immich/discussions/4348).
Yes, it creates new faces and persons from the imported asset metadata. For details see the [feature request #4348](https://github.com/immich-app/immich/discussions/4348) and [PR #6455](https://github.com/immich-app/immich/pull/6455).
### Does Immich support the filtering of NSFW images?
@@ -170,7 +170,7 @@ If you aren't able to or prefer not to mount Samba on the host (such as Windows
Below is an example in the `docker-compose.yml`.
Change your username, password, local IP, and share name, and see below where the line `- originals:/usr/src/app/originals`,
corrolates to the section where the volume `originals` was created. You can call this whatever you like, and map it to the docker container as you like.
correlates to the section where the volume `originals` was created. You can call this whatever you like, and map it to the docker container as you like.
For example you could change `originals:` to `Photos:`, and change `- originals:/usr/src/app/originals` to `Photos:/usr/src/app/photos`.
```diff
@@ -262,7 +262,7 @@ No, this is not supported. Only models listed in the [Hugging Face][huggingface]
### I want to be able to search in other languages besides English. How can I do that?
You can change to a multilingual CLIP model. See [here](/docs/features/searching#clip-model) for instructions.
You can change to a multilingual CLIP model. See [here](/docs/features/searching#clip-models) for instructions.
### Does Immich support Facial Recognition for videos?

View File

@@ -30,6 +30,13 @@ As mentioned above, you should make your own backup of these together with the a
You can adjust the schedule and amount of kept backups in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup).
By default, Immich will keep the last 14 backups and create a new backup every day at 2:00 AM.
#### Trigger Backup
You are able to trigger a backup in the [admin job status page](http://my.immich.app/admin/jobs-status).
Visit the page, open the "Create job" modal from the top right, select "Backup Database" and click "Confirm".
A job will run and trigger a backup, you can verify this worked correctly by checking the logs or the backup folder.
This backup will count towards the last X backups that will be kept based on your settings.
#### Restoring
We hope to make restoring simpler in future versions, for now you can find the backups in the `UPLOAD_LOCATION/backups` folder on your host.
@@ -53,7 +60,7 @@ docker compose create # Create Docker containers for Immich apps witho
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
# Check the database user if you deviated from the default
gunzip < "/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" \
| docker exec -i immich_postgres psql --dbname=postgres --username=<DB_USERNAME> # Restore Backup
docker compose up -d # Start remainder of Immich apps
@@ -76,10 +83,8 @@ docker compose create # Create Docker containers for
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
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`
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> # Restore Backup
# Check the database user if you deviated from the default. If your backup ends in `.gz`, replace `cat` with `gunzip --stdout`
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
docker compose up -d # Start remainder of Immich apps
```

View File

@@ -70,4 +70,4 @@ When installing a new version of pgvecto.rs, you will need to manually update th
If you get the error `driverError: error: permission denied for view pg_vector_index_stat`, you can fix this by connecting to the Immich database and running `GRANT SELECT ON TABLE pg_vector_index_stat TO <immichdbusername>;`.
[vectors-install]: https://docs.pgvecto.rs/getting-started/installation.html
[vectors-install]: https://docs.vectorchord.ai/getting-started/installation.html

View File

@@ -11,6 +11,7 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
| `enable-oauth-login` | Enable OAuth login |
| `disable-oauth-login` | Disable OAuth login |
| `list-users` | List Immich users |
| `version` | Print Immich version |
## How to run a command
@@ -80,3 +81,10 @@ immich-admin list-users
}
]
```
Print Immich Version
```
immich-admin version
v1.129.0
```

View File

@@ -98,6 +98,14 @@ The default Immich log level is `Log` (commonly known as `Info`). The Immich adm
Through this setting, you can manage all the settings related to machine learning in Immich, from the setting of remote machine learning to the model and its parameters
You can choose to disable a certain type of machine learning, for example smart search or facial recognition.
### URL
The built in (`http://immich-machine-learning:3003`) machine learning server will be configured by default, but you can change this or add additional servers.
Hosting the `immich-machine-learning` container on a machine with a more powerful GPU can be helpful to for processing a large number of photos (such as during batch import) or for faster search.
If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.
### Smart Search
The [smart search](/docs/features/searching) settings allow you to change the [CLIP model](https://openai.com/research/clip). Larger models will typically provide [more accurate search results](https://github.com/immich-app/immich/discussions/11862) but consume more processing power and RAM. When [changing the CLIP model](/docs/FAQ#can-i-use-a-custom-clip-model) it is mandatory to re-run the Smart Search job on all images to fully apply the change.

View File

@@ -69,6 +69,8 @@ Navigating to Administration > Settings > Machine Learning Settings > Facial Rec
:::tip
It's better to only tweak the parameters here than to set them to something very different unless you're ready to test a variety of options. If you do need to set a parameter to a strict setting, relaxing other settings can be a good option to compensate, and vice versa.
You can learn how the tune the result in this [Guide](/docs/guides/better-facial-clusters)
:::
### Facial recognition model

View File

@@ -37,7 +37,7 @@ To validate that Immich can reach your external library, start a shell inside th
### Exclusion Patterns
By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library. Under the hood, Immich uses the [glob](https://www.npmjs.com/package/glob) package to match patterns, so please refer to [their documentation](https://github.com/isaacs/node-glob#glob-primer) to see what patterns are supported.
By default, all files in the import paths will be added to the library. If there are files that should not be added, exclusion patterns can be used to exclude them. Exclusion patterns are glob patterns are matched against the full file path. If a file matches an exclusion pattern, it will not be added to the library. Exclusion patterns can be added in the Scan Settings page for each library.
Some basic examples:
@@ -48,7 +48,11 @@ Some basic examples:
Special characters such as @ should be escaped, for instance:
- `**/\@eadir/**` will exclude all files in any directory named `@eadir`
- `**/\@eaDir/**` will exclude all files in any directory named `@eaDir`
:::info
Internally, Immich uses the [glob](https://www.npmjs.com/package/glob) package to process exclusion patterns, and sometimes those patterns are translated into [Postgres LIKE patterns](https://www.postgresql.org/docs/current/functions-matching.html). The intention is to support basic folder exclusions but we recommend against advanced usage since those can't reliably be translated to the Postgres syntax. Please refer to the [glob documentation](https://github.com/isaacs/node-glob#glob-primer) for a basic overview on glob patterns.
:::
### Automatic watching (EXPERIMENTAL)
@@ -68,7 +72,7 @@ In rare cases, the library watcher can hang, preventing Immich from starting up.
### Nightly job
There is an automatic scan job that is scheduled to run once a day. This job also cleans up any libraries stuck in deletion.
There is an automatic scan job that is scheduled to run once a day. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library managment page.
## Usage
@@ -91,7 +95,7 @@ The `immich-server` container will need access to the gallery. Modify your docke
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
+ - /mnt/media/videos2:/mnt/media/videos2 # the files in this folder can be deleted, as it does not end with :ro
+ - /mnt/media/videos2:/mnt/media/videos2 # WARNING: Immich will be able to delete the files in this folder, as it does not end with :ro
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
```
@@ -111,11 +115,10 @@ These actions must be performed by the Immich administrator.
- Click on Administration -> Libraries
- Click on Create External Library
- Select which user owns the library, this can not be changed later
- Enter `/mnt/media/christmas-trip` then click Add
- Click on Save
- Click the drop-down menu on the newly created library
- Click on Rename Library and rename it to "Christmas Trip"
- Click Edit Import Paths
- Click on Add Path
- Enter `/mnt/media/christmas-trip` then click Add
NOTE: We have to use the `/mnt/media/christmas-trip` path and not the `/mnt/nas/christmas-trip` path since all paths have to be what the Docker containers see.

View File

@@ -11,7 +11,9 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- ARM NN (Mali)
- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher)
- ROCm (AMD GPUs)
- OpenVINO (Intel GPUs such as Iris Xe and Arc)
- RKNN (Rockchip)
## Limitations
@@ -19,6 +21,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- Only Linux and Windows (through WSL2) servers are supported.
- ARM NN is only supported on devices with Mali GPUs. Other Arm devices are not supported.
- Some models may not be compatible with certain backends. CUDA is the most reliable.
- Search latency isn't improved by ARM NN due to model compatibility issues preventing its use. However, smart search jobs do make use of ARM NN.
## Prerequisites
@@ -33,6 +36,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere
- The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file
- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for ARM NN specific settings
- In particular, the `MACHINE_LEARNING_ANN_FP16_TURBO` can significantly improve performance at the cost of very slightly lower accuracy
#### CUDA
@@ -41,22 +45,38 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The installed driver must be >= 535 (it must support CUDA 12.2).
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### ROCm
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/docs/install/environment-variables) setting).
#### OpenVINO
- 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.
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
#### RKNN
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
- Make sure you have the appropriate linux kernel driver installed
- This is usually pre-installed on the device vendor's Linux images
- RKNPU driver V0.9.8 or later must be available in the host server
- You may confirm this by running `cat /sys/kernel/debug/rknpu/version` to check the version
- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for RKNN specific settings
- In particular, setting `MACHINE_LEARNING_RKNN_THREADS` to 2 or 3 can _dramatically_ improve performance for RK3576 and RK3588 compared to the default of 1, at the expense of multiplying the amount of RAM each model uses by that amount.
## Setup
1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, openvino] to the `image` section's tag at the end of the line.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino] to the `image` section's tag at the end of the line.
4. Redeploy the `immich-machine-learning` container with these updated settings.
### Confirming Device Usage
You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel and `intel_gpu_top` for Intel.
You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel, `intel_gpu_top` for Intel, and `radeontop` for AMD.
You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN.
@@ -127,3 +147,12 @@ Note that you should increase job concurrencies to increase overall utilization
- If you encounter an error when a model is running, try a different model to see if the issue is model-specific.
- You may want to increase concurrency past the default for higher utilization. However, keep in mind that this will also increase VRAM consumption.
- Larger models benefit more from hardware acceleration, if you have the VRAM for them.
- Compared to ARM NN, RKNPU has:
- Wider model support (including for search, which ARM NN does not accelerate)
- Less heat generation
- Very slightly lower accuracy (RKNPU always uses FP16, while ARM NN by default uses higher precision FP32 unless `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled)
- Varying speed (tested on RK3588):
- If `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, RKNPU will have substantially lower throughput for ML jobs than ARM NN in most cases, but similar latency (such as when searching)
- If `MACHINE_LEARNING_RKNN_THREADS` is set to 3, it will be somewhat faster than ARM NN at FP32, but somewhat slower than ARM NN if `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled
- When other tasks also use the GPU (like transcoding), RKNPU has a significant advantage over ARM NN as it uses the otherwise idle NPU instead of competing for GPU usage
- Lower RAM usage if `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, but significantly higher if greater than 1 (which is necessary for it to fully utilize the NPU and hence be comparable in speed to ARM NN)

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
| `JPEG 2000` | `.jp2` | :white_check_mark: | |
| `JPEG` | `.webp` `.jpg` `.jpe` `.insp` | :white_check_mark: | |
| `JPEG XL` | `.jxl` | :white_check_mark: | |
| `PNG` | `.webp` | :white_check_mark: | |
| `PNG` | `.png` | :white_check_mark: | |
| `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop |
| `RAW` | `.raw` | :white_check_mark: | |
| `RW2` | `.rw2` | :white_check_mark: | |

View File

@@ -0,0 +1,72 @@
# Better Facial Recognition Clusters
## Purpose
This guide explains how to optimize facial recognition in systems with large image libraries. By following these steps, you'll achieve better clustering of faces, reducing the need for manual merging.
---
## Important Notes
- **Best Suited For:** Large image libraries after importing a significant number of images.
- **Warning:** This method deletes all previously assigned names.
- **Tip:** **Always take a [backup](/docs/administration/backup-and-restore#database) before proceeding!**
---
## Step-by-Step Instructions
### Objective
To enhance face clustering and ensure the model effectively identifies faces using qualitative initial data.
---
### Steps
#### 1. Adjust Machine Learning Settings
Navigate to:
**Admin → Administration → Settings → Machine Learning Settings**
Make the following changes:
- **Maximum recognition distance (Optional):**
Lower this value, e.g., to **0.4**, if the library contains people with similar facial features.
- **Minimum recognized faces:**
Set this to a **high value** (e.g., 20 For libraries with a large amount of assets (~100K+), and 10 for libraries with medium amount of assets (~40K+)).
> A high value ensures clusters only include faces that appear at least 20/`value` times in the library, improving the initial clustering process.
---
#### 2. Run Reset Jobs
Go to:
**Admin → Administration → Settings → Jobs**
Perform the following:
1. **FACIAL RECOGNITION → Reset**
> These reset jobs rebuild the recognition model based on the new settings.
---
#### 3. Refine Recognition with Lower Thresholds
Once the reset jobs are complete, refine the recognition as follows:
- **Step 1:**
Return to **Minimum recognized faces** in Machine Learning Settings and lower the value to **10** (In medium libraries we will lower the value from 10 to 5).
> Run the job: **FACIAL RECOGNITION → MISSING Mode**
- **Step 2:**
Lower the value again to **3**.
> Run the job: **FACIAL RECOGNITION → MISSING Mode**
:::tip try different values
For certain libraries with a larger or smaller amount of assets, other settings will be better or worse. It is recommended to try different values **before assigning names** and see which settings work best for your library.
:::
---

View File

@@ -6,7 +6,7 @@ This guide explains how to store generated and raw files with docker's volume mo
It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools, especially `profile/`.
:::
In our `.env` file, we will define variables that will help us in the future when we want to move to a more advanced server
In our `.env` file, we will define the paths we want to use. Note that you don't have to define all of these: UPLOAD_LOCATION will be the base folder that files are stored in by default, with the other paths acting as overrides.
```diff title=".env"
# You can find documentation for all the supported environment variables [here](/docs/install/environment-variables)
@@ -21,7 +21,7 @@ In our `.env` file, we will define variables that will help us in the future whe
...
```
After defining the locations of these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container.
After defining the locations of these files, we will edit the `docker-compose.yml` file accordingly and add the new variables to the `immich-server` container. These paths are where the mount attaches inside of the container, so don't change those.
```diff title="docker-compose.yml"
services:
@@ -35,7 +35,8 @@ services:
- /etc/localtime:/etc/localtime:ro
```
Restart Immich to register the changes.
After making this change, you have to move the files over to the new folders to make sure Immich can find everything it needs. If you haven't uploaded anything important yet, you can also reset Immich entirely by deleting the database folder.
Then restart Immich to register the changes:
```
docker compose up -d

View File

@@ -31,6 +31,10 @@ SELECT * FROM "assets" WHERE "originalPath" LIKE 'upload/library/admin/2023/%';
SELECT * FROM "assets" WHERE "id" = '9f94e60f-65b6-47b7-ae44-a4df7b57f0e9';
```
```sql title="Find by partial ID"
SELECT * FROM "assets" WHERE "id"::text LIKE '%ab431d3a%';
```
:::note
You can calculate the checksum for a particular file by using the command `sha1sum <filename>`.
:::

View File

@@ -23,12 +23,12 @@ name: immich_remote_ml
services:
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.ml.yml
# service: # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
# service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes:
- model-cache:/cache
restart: always

View File

@@ -37,7 +37,7 @@ You can alternatively download these two files from your browser and move them t
</CodeBlock>
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets. It should be a new directory on the server with enough free space.
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for local authentication.
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publicly exposed, so this password is only used for local authentication.
To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`. `pwgen` is a handy utility for this.
- Set your timezone by uncommenting the `TZ=` line.
- Populate custom database information if necessary.

View File

@@ -11,7 +11,7 @@ Just restarting the containers does not replace the environment within the conta
In order to recreate the container using docker compose, run `docker compose up -d`.
In most cases docker will recognize that the `.env` file has changed and recreate the affected containers.
If this should not work, try running `docker compose up -d --force-recreate`.
If this does not work, try running `docker compose up -d --force-recreate`.
:::
@@ -20,8 +20,8 @@ If this should not work, try running `docker compose up -d --force-recreate`.
| Variable | Description | Default | Containers |
| :----------------- | :------------------------------ | :-------: | :----------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, machine learning |
| `UPLOAD_LOCATION` | Host Path for uploads | | server |
| `DB_DATA_LOCATION` | Host Path for Postgres database | | database |
| `UPLOAD_LOCATION` | Host path for uploads | | server |
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
:::tip
These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly.
@@ -33,15 +33,15 @@ These environment variables are used by the `docker-compose.yml` file and do **N
| :---------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | <sup>\*1</sup> | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `./upload`<sup>\*3</sup> | server | api, microservices |
| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media location inside the container ⚠️**You probably shouldn't set this**<sup>\*2</sup>⚠️ | `./upload`<sup>\*3</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices |
| `IMMICH_TRUSTED_PROXIES` | List of comma separated IPs set as trusted proxies | | server | api |
| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api |
| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/docs/administration/system-integrity) | | server | api, microservices |
\*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
@@ -50,7 +50,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
\*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead.
\*3: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only need to be set if the Immich deployment method is changing.
It only needs to be set if the Immich deployment method is changing.
## Workers
@@ -75,12 +75,12 @@ Information on the current workers can be found [here](/docs/administration/jobs
| Variable | Description | Default | Containers |
| :---------------------------------- | :----------------------------------------------------------------------- | :----------: | :----------------------------- |
| `DB_URL` | Database URL | | server |
| `DB_HOSTNAME` | Database Host | `database` | server |
| `DB_PORT` | Database Port | `5432` | server |
| `DB_USERNAME` | Database User | `postgres` | server, database<sup>\*1</sup> |
| `DB_PASSWORD` | Database Password | `postgres` | server, database<sup>\*1</sup> |
| `DB_DATABASE_NAME` | Database Name | `immich` | server, database<sup>\*1</sup> |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database Vector Extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | server |
| `DB_HOSTNAME` | Database host | `database` | server |
| `DB_PORT` | Database port | `5432` | server |
| `DB_USERNAME` | Database user | `postgres` | server, database<sup>\*1</sup> |
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | server |
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.
@@ -103,18 +103,18 @@ When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSW
| Variable | Description | Default | Containers |
| :--------------- | :------------- | :-----: | :--------- |
| `REDIS_URL` | Redis URL | | server |
| `REDIS_SOCKET` | Redis Socket | | server |
| `REDIS_HOSTNAME` | Redis Host | `redis` | server |
| `REDIS_PORT` | Redis Port | `6379` | server |
| `REDIS_USERNAME` | Redis Username | | server |
| `REDIS_PASSWORD` | Redis Password | | server |
| `REDIS_DBINDEX` | Redis DB Index | `0` | server |
| `REDIS_SOCKET` | Redis socket | | server |
| `REDIS_HOSTNAME` | Redis host | `redis` | server |
| `REDIS_PORT` | Redis port | `6379` | server |
| `REDIS_USERNAME` | Redis username | | server |
| `REDIS_PASSWORD` | Redis password | | server |
| `REDIS_DBINDEX` | Redis DB index | `0` | server |
:::info
All `REDIS_` variables must be provided to all Immich workers, including `api` and `microservices`.
`REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration.
More info can be found in the upstream [ioredis] documentation.
More information can be found in the upstream [ioredis] documentation.
When `REDIS_URL` or `REDIS_SOCKET` are defined, the `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`, and `REDIS_DBINDEX` variables are ignored.
:::
@@ -168,6 +168,10 @@ Redis (Sentinel) URL example JSON before encoding:
| `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_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_PING_TIMEOUT` | How long (ms) to wait for a PING response when checking if an ML server is available | `2000` | server |
| `MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME` | How long to ignore ML servers that are offline before trying again | `30000` | server |
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | 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.
@@ -179,7 +183,11 @@ Redis (Sentinel) URL example JSON before encoding:
:::info
Other machine learning parameters can be tuned from the admin UI.
While the `textual` model is the only one required for smart search, some users may experience slow first searches
due to backups triggering loading of the other models into memory, which blocks other requests until completed.
To avoid this, you can preload the other models (`visual`, `recognition`, and `detection`) if you have enough RAM to do so.
Additional machine learning parameters can be tuned from the admin UI.
:::
@@ -210,7 +218,7 @@ the `_FILE` variable should be set to the path of a file containing the variable
details on how to use Docker Secrets in the Postgres image.
\*2: See [this comment][docker-secrets-example] for an example of how
to use use a Docker secret for the password in the Redis container.
to use a Docker secret for the password in the Redis container.
[tz-list]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234

View File

@@ -0,0 +1,76 @@
---
sidebar_position: 85
---
# Synology [Community]
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
**Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).**
:::
Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager.
## 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`.
Now create a `./postgres` and `./library` directory as sub-directories of the `./docker/immich-app`.
When you're all done, you should have the following:
- `./docker/immich-app/postgres`
- `./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.
## Step 2 - Populate the .env file with custom values
Follow [Step 2 in Docker Compose](./docker-compose#step-2---populate-the-env-file-with-custom-values) for instructions on customizing the `.env` file, and then return back to this guide to continue.
## 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**".
![Create Project](../../static/img/synology-container-manager-create-project.png)
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.
![Set Path](../../static/img/synology-container-manager-set-path.png)
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**".
![start interval](../../static/img/synology-container-manager-customize-docker-compose.png)
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**".
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**.
![Container Details](../../static/img/synology-container-manager-container-details.png)
## Step 4 - Configure Firewall Settings
Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS.
Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**"
![Firewall rules](../../static/img/synology-firewall-rules.png)
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 "**Ports**" rule for the port specified in the `docker-compose.yml`, which should be `2283`
## Next Steps
Read the [Post Installation](/docs/install/post-install.mdx) steps or setup optional features below.
### Setting up optional features
- [External Libraries](/docs/features/libraries.md): Adding your existing photo library to Immich
- [Hardware Transcoding](/docs/features/hardware-transcoding.md): Speeding up video transcoding
- [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md): Speeding up various machine learning tasks in Immich

View File

@@ -198,7 +198,7 @@ The **CPU** value was specified in a different format with a default of `4000m`
The **Memory** value was specified in a different format with a default of `8Gi` which is 8 GiB of RAM. The value was specified in bytes or a number with a measurement suffix. Examples: `129M`, `123Mi`, `1000000000`
:::
Enable **GPU Configuration** options if you have a GPU that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). More info: [GPU Passtrough Docs for TrueNAS Apps](https://www.truenas.com/docs/truenasapps/#gpu-passthrough)
Enable **GPU Configuration** options if you have a GPU that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). More info: [GPU Passthrough Docs for TrueNAS Apps](https://www.truenas.com/docs/truenasapps/#gpu-passthrough)
### Install

View File

@@ -77,7 +77,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
- `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata`). If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting.
- `DB_DATA_LOCATION`: Change this to use an Unraid share (preferably a cache pool, e.g. `/mnt/user/appdata/postgresql/data`). This uses the `appdata` share. Do also create the `postgresql` folder, by running `mkdir /mnt/user/{share_location}/postgresql/data`. If left at default it will try to use Unraid's `/boot/config/plugins/compose.manager/projects/[stack_name]/postgres` folder which it doesn't have permissions to, resulting in this container continuously restarting.
<img
src={require('./img/unraid05.webp').default}

45
docs/package-lock.json generated
View File

@@ -28,6 +28,8 @@
},
"devDependencies": {
"@docusaurus/module-type-aliases": "~3.7.0",
"@docusaurus/tsconfig": "^3.7.0",
"@docusaurus/types": "^3.7.0",
"prettier": "^3.2.4",
"typescript": "^5.1.6"
},
@@ -3698,6 +3700,13 @@
"node": ">=18.0"
}
},
"node_modules/@docusaurus/tsconfig": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.7.0.tgz",
"integrity": "sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@docusaurus/types": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz",
@@ -5299,9 +5308,9 @@
}
},
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
"funding": [
{
"type": "opencollective",
@@ -5318,11 +5327,11 @@
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001646",
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.1",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
@@ -5772,9 +5781,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001695",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz",
"integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==",
"version": "1.0.30001706",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz",
"integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==",
"funding": [
{
"type": "opencollective",
@@ -14061,9 +14070,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"funding": [
{
"type": "opencollective",
@@ -15725,9 +15734,9 @@
}
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -18368,9 +18377,9 @@
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",

View File

@@ -36,6 +36,8 @@
},
"devDependencies": {
"@docusaurus/module-type-aliases": "~3.7.0",
"@docusaurus/tsconfig": "^3.7.0",
"@docusaurus/types": "^3.7.0",
"prettier": "^3.2.4",
"typescript": "^5.1.6"
},
@@ -55,6 +57,6 @@
"node": ">=20"
},
"volta": {
"node": "22.13.1"
"node": "22.14.0"
}
}

View File

@@ -53,6 +53,11 @@ const guides: CommunityGuidesProps[] = [
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',
},
];
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {

View File

@@ -1,2 +1,3 @@
export const discordPath =
'M 9.1367188 3.8691406 C 9.1217187 3.8691406 9.1067969 3.8700938 9.0917969 3.8710938 C 8.9647969 3.8810937 5.9534375 4.1403594 4.0234375 5.6933594 C 3.0154375 6.6253594 1 12.073203 1 16.783203 C 1 16.866203 1.0215 16.946531 1.0625 17.019531 C 2.4535 19.462531 6.2473281 20.102859 7.1113281 20.130859 L 7.1269531 20.130859 C 7.2799531 20.130859 7.4236719 20.057594 7.5136719 19.933594 L 8.3886719 18.732422 C 6.0296719 18.122422 4.8248594 17.086391 4.7558594 17.025391 C 4.5578594 16.850391 4.5378906 16.549563 4.7128906 16.351562 C 4.8068906 16.244563 4.9383125 16.189453 5.0703125 16.189453 C 5.1823125 16.189453 5.2957188 16.228594 5.3867188 16.308594 C 5.4157187 16.334594 7.6340469 18.216797 11.998047 18.216797 C 16.370047 18.216797 18.589328 16.325641 18.611328 16.306641 C 18.702328 16.227641 18.815734 16.189453 18.927734 16.189453 C 19.059734 16.189453 19.190156 16.243562 19.285156 16.351562 C 19.459156 16.549563 19.441141 16.851391 19.244141 17.025391 C 19.174141 17.087391 17.968375 18.120469 15.609375 18.730469 L 16.484375 19.933594 C 16.574375 20.057594 16.718094 20.130859 16.871094 20.130859 L 16.886719 20.130859 C 17.751719 20.103859 21.5465 19.463531 22.9375 17.019531 C 22.9785 16.947531 23 16.866203 23 16.783203 C 23 12.073203 20.984172 6.624875 19.951172 5.671875 C 18.047172 4.140875 15.036203 3.8820937 14.908203 3.8710938 C 14.895203 3.8700938 14.880188 3.8691406 14.867188 3.8691406 C 14.681188 3.8691406 14.510594 3.9793906 14.433594 4.1503906 C 14.427594 4.1623906 14.362062 4.3138281 14.289062 4.5488281 C 15.548063 4.7608281 17.094141 5.1895937 18.494141 6.0585938 C 18.718141 6.1975938 18.787437 6.4917969 18.648438 6.7167969 C 18.558438 6.8627969 18.402188 6.9433594 18.242188 6.9433594 C 18.156188 6.9433594 18.069234 6.9200937 17.990234 6.8710938 C 15.584234 5.3800938 12.578 5.3046875 12 5.3046875 C 11.422 5.3046875 8.4157187 5.3810469 6.0117188 6.8730469 C 5.9327188 6.9210469 5.8457656 6.9433594 5.7597656 6.9433594 C 5.5997656 6.9433594 5.4425625 6.86475 5.3515625 6.71875 C 5.2115625 6.49375 5.2818594 6.1985938 5.5058594 6.0585938 C 6.9058594 5.1905937 8.4528906 4.7627812 9.7128906 4.5507812 C 9.6388906 4.3147813 9.5714062 4.1643437 9.5664062 4.1523438 C 9.4894063 3.9813438 9.3217188 3.8691406 9.1367188 3.8691406 z M 12 7.3046875 C 12.296 7.3046875 14.950594 7.3403125 16.933594 8.5703125 C 17.326594 8.8143125 17.777234 8.9453125 18.240234 8.9453125 C 18.633234 8.9453125 19.010656 8.8555 19.347656 8.6875 C 19.964656 10.2405 20.690828 12.686219 20.923828 15.199219 C 20.883828 15.143219 20.840922 15.089109 20.794922 15.037109 C 20.324922 14.498109 19.644687 14.191406 18.929688 14.191406 C 18.332687 14.191406 17.754078 14.405437 17.330078 14.773438 C 17.257078 14.832437 15.505 16.21875 12 16.21875 C 8.496 16.21875 6.7450313 14.834687 6.7070312 14.804688 C 6.2540312 14.407687 5.6742656 14.189453 5.0722656 14.189453 C 4.3612656 14.189453 3.6838438 14.494391 3.2148438 15.025391 C 3.1658438 15.080391 3.1201719 15.138266 3.0761719 15.197266 C 3.3091719 12.686266 4.0344375 10.235594 4.6484375 8.6835938 C 4.9864375 8.8525938 5.3657656 8.9433594 5.7597656 8.9433594 C 6.2217656 8.9433594 6.6724531 8.8143125 7.0644531 8.5703125 C 9.0494531 7.3393125 11.704 7.3046875 12 7.3046875 z M 8.890625 10.044922 C 7.966625 10.044922 7.2167969 10.901031 7.2167969 11.957031 C 7.2167969 13.013031 7.965625 13.869141 8.890625 13.869141 C 9.815625 13.869141 10.564453 13.013031 10.564453 11.957031 C 10.564453 10.900031 9.815625 10.044922 8.890625 10.044922 z M 15.109375 10.044922 C 14.185375 10.044922 13.435547 10.901031 13.435547 11.957031 C 13.435547 13.013031 14.184375 13.869141 15.109375 13.869141 C 16.034375 13.869141 16.783203 13.013031 16.783203 11.957031 C 16.783203 10.900031 16.033375 10.044922 15.109375 10.044922 z';
'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';

View File

@@ -4,7 +4,7 @@ import React, { useEffect, useState } from 'react';
export default function VersionSwitcher(): JSX.Element {
const [versions, setVersions] = useState([]);
const [label, setLabel] = useState('Versions');
const [activeLabel, setLabel] = useState('Versions');
const windowSize = useWindowSize();
@@ -24,10 +24,13 @@ export default function VersionSwitcher(): JSX.Element {
{ label: 'Next', url: 'https://main.preview.immich.app' },
{ label: 'Latest', url: 'https://immich.app' },
...archiveVersions,
];
].map(({ label, url }) => ({
label,
url: new URL(url),
}));
setVersions(allVersions);
const activeVersion = allVersions.find((version) => new URL(version.url).origin === window.location.origin);
const activeVersion = allVersions.find((version) => version.url.origin === window.location.origin);
if (activeVersion) {
setLabel(activeVersion.label);
}
@@ -45,12 +48,13 @@ export default function VersionSwitcher(): JSX.Element {
versions.length > 0 && (
<DropdownNavbarItem
className="version-switcher-34ab39"
label={label}
label={activeLabel}
mobile={windowSize === 'mobile'}
items={versions.map(({ label, url }) => ({
label,
to: url + location.pathname + location.hash,
to: new URL(location.pathname + location.search + location.hash, url).href,
target: '_self',
className: label === activeLabel ? 'dropdown__link--active menu__link--active' : '', // workaround because React Router `<NavLink>` only supports using URL path for checking if active: https://v5.reactrouter.com/web/api/NavLink/isactive-func
}))}
/>
)

View File

@@ -1,12 +1,10 @@
import React from 'react';
import Link from '@docusaurus/Link';
import Layout from '@theme/Layout';
import { useColorMode } from '@docusaurus/theme-common';
import { discordPath } from '@site/src/components/svg-paths';
import { discordPath, discordViewBox } from '@site/src/components/svg-paths';
import ThemedImage from '@theme/ThemedImage';
import Icon from '@mdi/react';
function HomepageHeader() {
const { isDarkTheme } = useColorMode();
return (
<header>
<div className="top-[calc(12%)] md:top-[calc(30%)] h-screen w-full absolute -z-10">
@@ -14,8 +12,8 @@ function HomepageHeader() {
<div className="w-full h-[120vh] absolute left-0 top-0 backdrop-blur-3xl bg-immich-bg/40 dark:bg-transparent"></div>
</div>
<section className="text-center pt-12 sm:pt-24 bg-immich-bg/50 dark:bg-immich-dark-bg/80">
<img
src={isDarkTheme ? 'img/logomark-dark.svg' : 'img/logomark-light.svg'}
<ThemedImage
sources={{ dark: 'img/logomark-dark.svg', light: 'img/logomark-light.svg' }}
className="h-[115px] w-[115px] mb-2 antialiased rounded-none"
alt="Immich logo"
/>
@@ -35,7 +33,6 @@ function HomepageHeader() {
sacrificing your privacy.
</p>
</div>
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-9 gap-4 ">
<Link
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-primary dark:bg-immich-dark-primary rounded-xl no-underline hover:no-underline text-white hover:text-gray-50 dark:text-immich-dark-bg font-bold uppercase"
@@ -58,27 +55,27 @@ function HomepageHeader() {
Buy Merch
</Link>
</div>
<div className="my-12 flex gap-1 font-medium place-items-center place-content-center text-immich-primary dark:text-immich-dark-primary">
<Icon path={discordPath} size={1} />
<Icon
path={discordPath}
viewBox={discordViewBox} /* viewBox may show an error in your IDE but it is normal. */
size={1}
/>
<Link to="https://discord.immich.app/">Join our Discord</Link>
</div>
<img
src={isDarkTheme ? '/img/screenshot-dark.webp' : '/img/screenshot-light.webp'}
<ThemedImage
sources={{ dark: '/img/screenshot-dark.webp', light: '/img/screenshot-light.webp' }}
alt="screenshots"
className="w-[95%] lg:w-[85%] xl:w-[70%] 2xl:w-[60%] "
/>
<div className="mx-[25%] m-auto my-14 md:my-28">
<hr className="border bg-gray-500 dark:bg-gray-400" />
</div>
<img
src={isDarkTheme ? 'img/logomark-dark.svg' : 'img/logomark-light.svg'}
<ThemedImage
sources={{ dark: 'img/logomark-dark.svg', light: 'img/logomark-light.svg' }}
className="h-[115px] w-[115px] mb-2 antialiased rounded-none"
alt="Immich logo"
/>
<div>
<p className="font-bold text-2xl md:text-5xl ">Download the mobile app</p>
<p className="text-lg">
@@ -97,9 +94,8 @@ function HomepageHeader() {
</a>
</div>
</div>
<img
src={isDarkTheme ? '/img/app-qr-code-dark.svg' : '/img/app-qr-code-light.svg'}
<ThemedImage
sources={{ dark: '/img/app-qr-code-dark.svg', light: '/img/app-qr-code-light.svg' }}
alt="app qr code"
width={'150px'}
className="shadow-lg p-3 my-8 dark:bg-immich-dark-bg "

View File

@@ -1,10 +1,7 @@
import React from 'react';
import Link from '@docusaurus/Link';
import Layout from '@theme/Layout';
import { useColorMode } from '@docusaurus/theme-common';
function HomepageHeader() {
const { isDarkTheme } = useColorMode();
return (
<header>
<section className="max-w-[900px] m-4 p-4 md:p-6 md:m-auto md:my-12 border border-red-400 rounded-2xl bg-slate-200 dark:bg-immich-dark-gray">

View File

@@ -242,6 +242,13 @@ const roadmap: Item[] = [
];
const milestones: Item[] = [
{
icon: mdiStar,
iconColor: 'gold',
title: '60,000 Stars',
description: 'Reached 60K Stars on GitHub!',
getDateLabel: withLanguage(new Date(2025, 2, 4)),
},
withRelease({
icon: mdiLinkEdit,
iconColor: 'crimson',

View File

@@ -1,4 +1,24 @@
[
{
"label": "v1.130.1",
"url": "https://v1.130.1.archive.immich.app"
},
{
"label": "v1.130.0",
"url": "https://v1.130.0.archive.immich.app"
},
{
"label": "v1.129.0",
"url": "https://v1.129.0.archive.immich.app"
},
{
"label": "v1.128.0",
"url": "https://v1.128.0.archive.immich.app"
},
{
"label": "v1.127.0",
"url": "https://v1.127.0.archive.immich.app"
},
{
"label": "v1.126.1",
"url": "https://v1.126.1.archive.immich.app"
@@ -19,10 +39,6 @@
"label": "v1.125.5",
"url": "https://v1.125.5.archive.immich.app"
},
{
"label": "v1.125.4",
"url": "https://v1.125.4.archive.immich.app"
},
{
"label": "v1.125.3",
"url": "https://v1.125.3.archive.immich.app"
@@ -35,10 +51,6 @@
"label": "v1.125.1",
"url": "https://v1.125.1.archive.immich.app"
},
{
"label": "v1.125.0",
"url": "https://v1.125.0.archive.immich.app"
},
{
"label": "v1.124.2",
"url": "https://v1.124.2.archive.immich.app"
@@ -201,46 +213,46 @@
},
{
"label": "v1.105.1",
"url": "https://v1.105.1.archive.immich.app/"
"url": "https://v1.105.1.archive.immich.app"
},
{
"label": "v1.105.0",
"url": "https://v1.105.0.archive.immich.app/"
"url": "https://v1.105.0.archive.immich.app"
},
{
"label": "v1.104.0",
"url": "https://v1.104.0.archive.immich.app/"
"url": "https://v1.104.0.archive.immich.app"
},
{
"label": "v1.103.1",
"url": "https://v1.103.1.archive.immich.app/"
"url": "https://v1.103.1.archive.immich.app"
},
{
"label": "v1.103.0",
"url": "https://v1.103.0.archive.immich.app/"
"url": "https://v1.103.0.archive.immich.app"
},
{
"label": "v1.102.3",
"url": "https://v1.102.3.archive.immich.app/"
"url": "https://v1.102.3.archive.immich.app"
},
{
"label": "v1.102.2",
"url": "https://v1.102.2.archive.immich.app/"
"url": "https://v1.102.2.archive.immich.app"
},
{
"label": "v1.102.1",
"url": "https://v1.102.1.archive.immich.app/"
"url": "https://v1.102.1.archive.immich.app"
},
{
"label": "v1.102.0",
"url": "https://v1.102.0.archive.immich.app/"
"url": "https://v1.102.0.archive.immich.app"
},
{
"label": "v1.101.0",
"url": "https://v1.101.0.archive.immich.app/"
"url": "https://v1.101.0.archive.immich.app"
},
{
"label": "v1.100.0",
"url": "https://v1.100.0.archive.immich.app/"
"url": "https://v1.100.0.archive.immich.app"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -5,7 +5,7 @@ module.exports = {
preflight: false, // disable Tailwind's reset
},
content: ['./src/**/*.{js,jsx,ts,tsx}', './{docs,blog}/**/*.{md,mdx}'], // my markdown stuff is in ../docs, not /src
darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settigns
darkMode: ['class', '[data-theme="dark"]'], // hooks into docusaurus' dark mode settings
theme: {
extend: {
colors: {

View File

@@ -1,9 +1,8 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": ".",
"module": "Node16"
"baseUrl": "."
}
}

View File

@@ -1 +1 @@
22.13.1
22.14.0

View File

@@ -34,10 +34,10 @@ services:
- 2285:2285
redis:
image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
image: redis:6.2-alpine@sha256:148bb5411c184abd288d9aaed139c98123eeb8824c5d3fce03cf721db58066d8
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
command: -c fsync=off -c shared_preload_libraries=vectors.so
environment:
POSTGRES_PASSWORD: postgres

1005
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.126.1",
"version": "1.130.1",
"description": "",
"main": "index.js",
"type": "module",
@@ -25,7 +25,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
"@types/node": "^22.13.1",
"@types/node": "^22.13.10",
"@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4",
@@ -38,7 +38,7 @@
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^56.0.1",
"exiftool-vendored": "^28.3.1",
"globals": "^15.9.0",
"globals": "^16.0.0",
"jose": "^5.6.3",
"luxon": "^3.4.4",
"oidc-provider": "^8.5.1",
@@ -53,6 +53,6 @@
"vitest": "^3.0.0"
},
"volta": {
"node": "22.13.1"
"node": "22.14.0"
}
}

View File

@@ -4,7 +4,6 @@ import {
AssetResponseDto,
AssetTypeEnum,
getAssetInfo,
getConfig,
getMyUser,
LoginResponseDto,
SharedLinkType,
@@ -45,8 +44,6 @@ const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-sp
const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`;
const facesAssetFilepath = `${testAssetDir}/metadata/faces/portrait.jpg`;
const getSystemConfig = (accessToken: string) => getConfig({ headers: asBearerAuth(accessToken) });
const readTags = async (bytes: Buffer, filename: string) => {
const filepath = join(tempDir, filename);
await writeFile(filepath, bytes);
@@ -228,7 +225,7 @@ describe('/asset', () => {
});
it('should get the asset faces', async () => {
const config = await getSystemConfig(admin.accessToken);
const config = await utils.getSystemConfig(admin.accessToken);
config.metadata.faces.import = true;
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) });

View File

@@ -1,8 +1,9 @@
import { JobCommand, JobName, LoginResponseDto } from '@immich/sdk';
import { JobCommand, JobName, LoginResponseDto, updateConfig } from '@immich/sdk';
import { cpSync, rmSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { errorDto } from 'src/responses';
import { app, testAssetDir, utils } from 'src/utils';
import { app, asBearerAuth, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
@@ -20,6 +21,33 @@ describe('/jobs', () => {
command: JobCommand.Resume,
force: false,
});
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Resume,
force: false,
});
await utils.jobCommand(admin.accessToken, JobName.FaceDetection, {
command: JobCommand.Resume,
force: false,
});
await utils.jobCommand(admin.accessToken, JobName.SmartSearch, {
command: JobCommand.Resume,
force: false,
});
await utils.jobCommand(admin.accessToken, JobName.DuplicateDetection, {
command: JobCommand.Resume,
force: false,
});
const config = await utils.getSystemConfig(admin.accessToken);
config.machineLearning.duplicateDetection.enabled = false;
config.machineLearning.enabled = false;
config.metadata.faces.import = false;
config.machineLearning.clip.enabled = false;
await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) });
});
it('should require authentication', async () => {
@@ -29,14 +57,7 @@ describe('/jobs', () => {
});
it('should queue metadata extraction for missing assets', async () => {
const path1 = `${testAssetDir}/formats/raw/Nikon/D700/philadelphia.nef`;
const path2 = `${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`;
await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(path1), filename: basename(path1) },
});
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const path = `${testAssetDir}/formats/raw/Nikon/D700/philadelphia.nef`;
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
command: JobCommand.Pause,
@@ -44,7 +65,7 @@ describe('/jobs', () => {
});
const { id } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(path2), filename: basename(path2) },
assetData: { bytes: await readFile(path), filename: basename(path) },
});
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
@@ -82,5 +103,123 @@ describe('/jobs', () => {
expect(asset.exifInfo?.make).toBe('NIKON CORPORATION');
}
});
it('should not re-extract metadata for existing assets', async () => {
const path = `${testAssetDir}/temp/metadata/asset.jpg`;
cpSync(`${testAssetDir}/formats/raw/Nikon/D700/philadelphia.nef`, path);
const { id } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(path), filename: basename(path) },
});
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
{
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo?.model).toBe('NIKON D700');
}
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, path);
await utils.jobCommand(admin.accessToken, JobName.MetadataExtraction, {
command: JobCommand.Start,
force: false,
});
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
{
const asset = await utils.getAssetInfo(admin.accessToken, id);
expect(asset.exifInfo).toBeDefined();
expect(asset.exifInfo?.model).toBe('NIKON D700');
}
rmSync(path);
});
it('should queue thumbnail extraction for assets missing thumbs', async () => {
const path = `${testAssetDir}/albums/nature/tanners_ridge.jpg`;
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Pause,
force: false,
});
const { id } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(path), filename: basename(path) },
});
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
const assetBefore = await utils.getAssetInfo(admin.accessToken, id);
expect(assetBefore.thumbhash).toBeNull();
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Empty,
force: false,
});
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Resume,
force: false,
});
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Start,
force: false,
});
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
const assetAfter = await utils.getAssetInfo(admin.accessToken, id);
expect(assetAfter.thumbhash).not.toBeNull();
});
it('should not reload existing thumbnail when running thumb job for missing assets', async () => {
const path = `${testAssetDir}/temp/thumbs/asset1.jpg`;
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, path);
const { id } = await utils.createAsset(admin.accessToken, {
assetData: { bytes: await readFile(path), filename: basename(path) },
});
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
const assetBefore = await utils.getAssetInfo(admin.accessToken, id);
cpSync(`${testAssetDir}/albums/nature/notocactus_minimus.jpg`, path);
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Resume,
force: false,
});
// This runs the missing thumbnail job
await utils.jobCommand(admin.accessToken, JobName.ThumbnailGeneration, {
command: JobCommand.Start,
force: false,
});
await utils.waitForQueueFinish(admin.accessToken, JobName.MetadataExtraction);
await utils.waitForQueueFinish(admin.accessToken, JobName.ThumbnailGeneration);
const assetAfter = await utils.getAssetInfo(admin.accessToken, id);
// Asset 1 thumbnail should be untouched since its thumb should not have been reloaded, even though the file was changed
expect(assetAfter.thumbhash).toEqual(assetBefore.thumbhash);
rmSync(path);
});
});
});

View File

@@ -1,4 +1,4 @@
import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { LibraryResponseDto, LoginResponseDto, getAllLibraries } from '@immich/sdk';
import { cpSync, existsSync, rmSync, unlinkSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
@@ -8,8 +8,6 @@ import request from 'supertest';
import { utimes } from 'utimes';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
const scan = async (accessToken: string, id: string) => scanLibrary({ id }, { headers: asBearerAuth(accessToken) });
describe('/libraries', () => {
let admin: LoginResponseDto;
let user: LoginResponseDto;
@@ -298,6 +296,8 @@ describe('/libraries', () => {
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
const { assets } = await utils.searchAssets(admin.accessToken, {
originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`,
@@ -312,15 +312,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/directoryA`],
});
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, {
originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`,
@@ -340,13 +332,7 @@ describe('/libraries', () => {
exclusionPatterns: ['**/directoryA'],
});
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -360,13 +346,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/directoryA`, `${testAssetDirInternal}/temp/directoryB`],
});
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -385,13 +365,7 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/folder, a/assetA.png`);
utils.createImageFile(`${testAssetDir}/temp/folder, b/assetB.png`);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -413,13 +387,7 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/folder{ a/assetA.png`);
utils.createImageFile(`${testAssetDir}/temp/folder} b/assetB.png`);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -471,13 +439,7 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/folder${char}1/asset1.png`);
utils.createImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -501,23 +463,12 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_001);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
@@ -539,7 +490,7 @@ describe('/libraries', () => {
utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
});
it('should not reimport unmodified files', async () => {
it('should not reimport a file with unchanged timestamp', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/reimport`],
@@ -548,21 +499,12 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
@@ -584,6 +526,47 @@ describe('/libraries', () => {
utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
});
it('should not reimport a modified file more than once', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/reimport`],
});
utils.createImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000);
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_001);
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/albums/nature/el_torcal_rocks.jpg`, `${testAssetDir}/temp/reimport/asset.jpg`);
await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_001);
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
});
expect(assets.count).toEqual(1);
const asset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(asset).toEqual(
expect.objectContaining({
originalFileName: 'asset.jpg',
exifInfo: expect.objectContaining({
model: 'NIKON D750',
}),
}),
);
utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`);
});
it('should set an asset offline if its file is missing', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
@@ -592,21 +575,14 @@ describe('/libraries', () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
@@ -624,8 +600,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
@@ -636,13 +611,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/another-path/`],
});
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
@@ -662,8 +631,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, {
libraryId: library.id,
@@ -673,8 +641,7 @@ describe('/libraries', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/directoryB/**'] });
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.isTrashed).toBe(true);
@@ -696,19 +663,12 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets: assetsBefore } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assetsBefore.count).toBeGreaterThan(1);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -725,11 +685,7 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`);
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -752,10 +708,7 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`);
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -779,10 +732,7 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`);
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -806,19 +756,13 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`);
unlinkSync(`${testAssetDir}/temp/xmp/glarus.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -841,18 +785,12 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -875,18 +813,12 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -910,19 +842,13 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`);
unlinkSync(`${testAssetDir}/temp/xmp/glarus.nef.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -946,18 +872,12 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
unlinkSync(`${testAssetDir}/temp/xmp/glarus.nef.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -981,18 +901,12 @@ describe('/libraries', () => {
cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
unlinkSync(`${testAssetDir}/temp/xmp/glarus.xmp`);
await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForQueueFinish(admin.accessToken, 'sidecar');
await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction');
await utils.scan(admin.accessToken, library.id);
const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -1015,22 +929,15 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`);
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(offlineAsset.isTrashed).toBe(true);
@@ -1044,15 +951,7 @@ describe('/libraries', () => {
utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`);
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const backOnlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
@@ -1066,6 +965,58 @@ describe('/libraries', () => {
}
});
it('should set a trashed offline asset to online but keep it in trash', async () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
await utils.deleteAssets(admin.accessToken, [assets.items[0].id]);
{
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(trashedAsset.isTrashed).toBe(true);
}
utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`);
await utils.scan(admin.accessToken, library.id);
const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(offlineAsset.isTrashed).toBe(true);
expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
expect(offlineAsset.isOffline).toBe(true);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true });
expect(assets.count).toBe(1);
}
utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`);
await utils.scan(admin.accessToken, library.id);
const backOnlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
expect(backOnlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
expect(backOnlineAsset.isOffline).toBe(false);
expect(backOnlineAsset.isTrashed).toBe(true);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true });
expect(assets.count).toBe(1);
}
});
it('should not set an offline asset to online if its file exists, is not covered by an exclusion pattern, but is outside of all import paths', async () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
@@ -1074,22 +1025,13 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`);
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true });
@@ -1110,15 +1052,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/another-path`],
});
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const stillOfflineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
@@ -1142,27 +1076,19 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp/offline`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
{
const { assets: assetsBefore } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assetsBefore.count).toBe(1);
}
utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`);
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
{
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true });
expect(assets.count).toBe(1);
}
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true });
expect(assets.count).toBe(1);
const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
@@ -1174,15 +1100,7 @@ describe('/libraries', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
{
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
}
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const stillOfflineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
@@ -1302,8 +1220,7 @@ describe('/libraries', () => {
importPaths: [`${testAssetDirInternal}/temp`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { status, body } = await request(app)
.delete(`/libraries/${library.id}`)

View File

@@ -201,7 +201,7 @@ describe('/people', () => {
expect(body).toMatchObject({
id: expect.any(String),
name: 'New Person',
birthDate: '1990-01-01T00:00:00.000Z',
birthDate: '1990-01-01',
});
});
@@ -262,7 +262,7 @@ describe('/people', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: '1990-01-01' });
expect(status).toBe(200);
expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' });
expect(body).toMatchObject({ birthDate: '1990-01-01' });
});
it('should clear a date of birth', async () => {

View File

@@ -633,7 +633,6 @@ describe('/search', () => {
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual([
'Andalusia',
'Berlin',
'Glarus',
'Greater Accra',
'Havana',
@@ -642,6 +641,7 @@ describe('/search', () => {
'Mississippi',
'New York',
'Shanghai',
'State of Berlin',
'St.-Petersburg',
'Tbilisi',
'Tokyo',
@@ -657,7 +657,6 @@ describe('/search', () => {
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual([
'Andalusia',
'Berlin',
'Glarus',
'Greater Accra',
'Havana',
@@ -666,6 +665,7 @@ describe('/search', () => {
'Mississippi',
'New York',
'Shanghai',
'State of Berlin',
'St.-Petersburg',
'Tbilisi',
'Tokyo',

View File

@@ -95,7 +95,7 @@ describe('/shared-links', () => {
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(
`<meta name="description" content="${metadataAlbum.assets.length} shared photos & videos" />`,
`<meta name="description" content="${metadataAlbum.assets.length} shared photos &amp; videos" />`,
);
});
@@ -103,14 +103,14 @@ describe('/shared-links', () => {
const resp = await request(shareUrl).get(`/${linkWithAlbum.key}`);
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta name="description" content="0 shared photos & videos" />`);
expect(resp.text).toContain(`<meta name="description" content="0 shared photos &amp; videos" />`);
});
it('should have correct asset count in meta tag for shared asset', async () => {
const resp = await request(shareUrl).get(`/${linkWithAssets.key}`);
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta name="description" content="1 shared photos & videos" />`);
expect(resp.text).toContain(`<meta name="description" content="1 shared photos &amp; videos" />`);
});
it('should have fqdn og:image meta tag for shared asset', async () => {

View File

@@ -1,4 +1,4 @@
import { LoginResponseDto, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk';
import { LoginResponseDto, getAssetInfo, getAssetStatistics } from '@immich/sdk';
import { existsSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { errorDto } from 'src/responses';
@@ -6,8 +6,6 @@ import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'sr
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const scan = async (accessToken: string, id: string) => scanLibrary({ id }, { headers: asBearerAuth(accessToken) });
describe('/trash', () => {
let admin: LoginResponseDto;
let ws: Socket;
@@ -81,8 +79,7 @@ describe('/trash', () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items.length).toBe(1);
@@ -90,8 +87,7 @@ describe('/trash', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
@@ -116,8 +112,7 @@ describe('/trash', () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.items.length).toBe(1);
@@ -125,8 +120,7 @@ describe('/trash', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const assetBefore = await utils.getAssetInfo(admin.accessToken, asset.id);
expect(assetBefore).toMatchObject({ isTrashed: true, isOffline: true });
@@ -180,8 +174,7 @@ describe('/trash', () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
@@ -189,9 +182,7 @@ describe('/trash', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.scan(admin.accessToken, library.id);
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
@@ -201,6 +192,8 @@ describe('/trash', () => {
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
});
});
@@ -238,7 +231,7 @@ describe('/trash', () => {
utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`);
await scan(admin.accessToken, library.id);
await utils.scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
@@ -247,7 +240,7 @@ describe('/trash', () => {
await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] });
await scan(admin.accessToken, library.id);
await utils.scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
const before = await utils.getAssetInfo(admin.accessToken, assetId);
@@ -261,6 +254,8 @@ describe('/trash', () => {
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(true);
utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`);
});
});
});

View File

@@ -28,8 +28,10 @@ import {
deleteAssets,
getAllJobsStatus,
getAssetInfo,
getConfig,
getConfigDefaults,
login,
scanLibrary,
searchAssets,
sendJobCommand,
setBaseUrl,
@@ -120,6 +122,7 @@ const execPromise = promisify(exec);
const onEvent = ({ event, id }: { event: EventType; id: string }) => {
// console.log(`Received event: ${event} [id=${id}]`);
const set = events[event];
set.add(id);
const idCallback = idCallbacks[id];
@@ -414,6 +417,8 @@ export const utils = {
rmSync(path, { recursive: true });
},
getSystemConfig: (accessToken: string) => getConfig({ headers: asBearerAuth(accessToken) }),
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
checkExistingAssets: (accessToken: string, checkExistingAssetsDto: CheckExistingAssetsDto) =>
@@ -488,7 +493,7 @@ export const utils = {
value: accessToken,
domain,
path: '/',
expires: 1_742_402_728,
expires: 2_058_028_213,
httpOnly: true,
secure: false,
sameSite: 'Lax',
@@ -498,7 +503,7 @@ export const utils = {
value: 'password',
domain,
path: '/',
expires: 1_742_402_728,
expires: 2_058_028_213,
httpOnly: true,
secure: false,
sameSite: 'Lax',
@@ -508,7 +513,7 @@ export const utils = {
value: 'true',
domain,
path: '/',
expires: 1_742_402_728,
expires: 2_058_028_213,
httpOnly: false,
secure: false,
sameSite: 'Lax',
@@ -553,6 +558,14 @@ export const utils = {
await immichCli(['login', app, `${key.secret}`]);
return key.secret;
},
scan: async (accessToken: string, id: string) => {
await scanLibrary({ id }, { headers: asBearerAuth(accessToken) });
await utils.waitForQueueFinish(accessToken, 'library');
await utils.waitForQueueFinish(accessToken, 'sidecar');
await utils.waitForQueueFinish(accessToken, 'metadataExtraction');
},
};
utils.initSdk();

View File

@@ -45,7 +45,7 @@ test.describe('Shared Links', () => {
await page.goto(`/share/${sharedLink.key}`);
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
await page.locator(`[data-asset-id="${asset.id}"]`).hover();
await page.waitForSelector('#asset-group-by-date svg');
await page.waitForSelector('[data-group] svg');
await page.getByRole('checkbox').click();
await page.getByRole('button', { name: 'Download' }).click();
await page.getByText('DOWNLOADING', { exact: true }).waitFor();

View File

@@ -1,5 +1,5 @@
{
"about": "Verfris",
"about": "Oor",
"account": "Rekening",
"account_settings": "Rekeninginstellings",
"acknowledge": "Erken",
@@ -20,7 +20,7 @@
"add_partner": "Voeg vennoot by",
"add_path": "Voeg pad by",
"add_photos": "Voeg foto's by",
"add_to": "Voeg na...",
"add_to": "Voeg by…",
"add_to_album": "Voeg na album",
"add_to_shared_album": "Voeg na gedeelde album",
"add_url": "Voeg URL by",
@@ -56,7 +56,32 @@
"duplicate_detection_job_description": "Begin masjienleer op bates om soortgelyke beelde op te spoor. Maak staat op Smart Search",
"exclusion_pattern_description": "Met uitsluitingspatrone kan jy lêers en vouers ignoreer wanneer jy jou biblioteek skandeer. Dit is nuttig as jy vouers het wat lêers bevat wat jy nie wil invoer nie, soos RAW-lêers.",
"external_library_created_at": "Eksterne biblioteek (geskep op {date})",
"external_library_management": "Eksterne Biblioteek-opsies",
"face_detection": "Gesigsopsporing"
}
"external_library_management": "Eksterne Biblioteekbestuur",
"face_detection": "Gesig deteksie",
"failed_job_command": "Opdrag {command} het misluk vir werk: {job}",
"force_delete_user_warning": "WAARSKUWING: Dit sal onmiddellik die gebruiker en alle bates verwyder. Dit kan nie ontdoen word nie en die lêers kan nie herstel word nie.",
"forcing_refresh_library_files": "Forseer herlaai van alle biblioteeklêers",
"image_format": "Formaat",
"image_format_description": "WebP produseer kleiner lêers as JPEG, maar is stadiger om te enkodeer.",
"image_prefer_embedded_preview": "Verkies ingebedde voorskou",
"image_prefer_wide_gamut": "Verkies wide gamut",
"image_prefer_wide_gamut_setting_description": "Gebruik Display P3 vir kleinkiekies. Dit behou die lewendheid van beelde met wye kleurruimtes beter, maar beelde kan anders verskyn op ou apparate met 'n ou blaaierweergawe. sRGB-beelde gebruik steeds sRGB om kleurverskuiwings te voorkom.",
"image_preview_description": "Mediumgrootte prent met gestroopte metadata, wat gebruik word wanneer 'n enkele bate bekyk word en vir masjienleer",
"image_preview_quality_description": "Voorskou kwaliteit van 1-100. Hoër is beter, maar produseer groter lêers en kan app-reaksie verminder. Die stel van 'n lae waarde kan masjienleerkwaliteit beïnvloed.",
"image_preview_title": "Voorskou Instellings",
"image_quality": "Kwaliteit",
"image_resolution": "Resolusie",
"image_resolution_description": "Hoër resolusies kan meer detail bewaar, maar neem langer om te enkodeer, het groter lêergroottes en kan app-reaksie verminder.",
"image_settings": "Prent Instellings",
"image_settings_description": "Bestuur die kwaliteit en resolusie van gegenereerde beelde",
"image_thumbnail_description": "Klein kleinkiekies sonder metadata, gebruik om groepe foto's soos die tydlyn te bekyk",
"image_thumbnail_quality_description": "Kleinkiekiekwaliteit van 1-100. Hoër is beter, maar produseer groter lêers en kan die toepassing vertraag.",
"image_thumbnail_title": "Kleinkiekie-instellings",
"job_concurrency": "{job} gelyktydigheid",
"job_created": "Taak gemaak",
"job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.",
"job_settings": "Agtergrondtaakinstellings"
},
"search_by_description": "Soek by beskrywing",
"search_by_description_example": "Stapdag in Sapa"
}

View File

@@ -41,6 +41,7 @@
"backup_settings": "إعدادات النسخ الاحتياطي",
"backup_settings_description": "إدارة إعدادات النسخ الاحتياطي لقاعدة البيانات",
"check_all": "اختر الكل",
"cleanup": "تنظيف",
"cleared_jobs": "تم إخلاء مهام: {job}",
"config_set_by_file": "الإعدادات حاليًا معينة عن طريق ملف الاعدادات",
"confirm_delete_library": "هل أنت متأكد أنك تريد حذف مكتبة {library}؟",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "تفعيل فحص المكتبة الدوري",
"library_settings": "المكتبة الخارجية",
"library_settings_description": "إدارة إعدادات المكتبة الخارجية",
"library_tasks_description": "قم بتنفيذ مهام المكتبة",
"library_tasks_description": "مسح المكتبات الخارجية للعثور على الأصول الجديدة و/أو المتغيرة",
"library_watching_enable_description": "راقب المكتبات الخارجية لتتبع تغييرات الملفات",
"library_watching_settings": "مراقبة المكتبات (تجريبي)",
"library_watching_settings_description": "راقب تلقائيًا التغييرات في الملفات",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "البحث عن الصور بشكل دلالي باستخدام تضمينات CLIP",
"machine_learning_smart_search_enabled": "تفعيل البحث الذكي",
"machine_learning_smart_search_enabled_description": "إذا تم تعطيله، فلن يتم ترميز الصور للبحث الذكي.",
"machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL، فسيتم محاولة الوصول إلى كل خادم على حدة حتى يستجيب أحد الخوادم بنجاح، بالترتيب من الأول إلى الأخير.",
"machine_learning_url_description": "عنوان URL لخادم التعلم الآلي. إذا تم توفير أكثر من عنوان URL واحد، سيتم محاولة الاتصال بكل خادم على حدة حتى يستجيب أحدهم بنجاح، بدءًا من الأول إلى الأخير. سيتم تجاهل الخوادم التي لا تستجيب مؤقتًا حتى تعود للعمل.",
"manage_concurrency": "إدارة التزامن",
"manage_log_settings": "إدارة إعدادات السجلات",
"map_dark_style": "النمط الداكن",
@@ -147,6 +148,8 @@
"map_settings": "الخريطة",
"map_settings_description": "إدارة إعدادات الخريطة",
"map_style_description": "عنوان URL لسمة الخريطة style.json",
"memory_cleanup_job": "تنظيف الذاكرة",
"memory_generate_job": "توليد الذاكرة",
"metadata_extraction_job": "استخراج البيانات الوصفية",
"metadata_extraction_job_description": "استخراج معلومات البيانات الوصفية من كل أصل، مثل إحداثيات الموقع, الوجوه والدقة",
"metadata_faces_import_setting": "تمكين استيراد الوجه",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "إعادة ضبط الإعدادات إلى الوضع الافتراضي",
"reset_settings_to_recent_saved": "إعادة ضبط الإعدادات إلى الإعدادات المحفوظة مؤخرًا",
"scanning_library": "مسح المكتبة",
"search_jobs": "البحث عن وظائف...",
"search_jobs": "البحث عن وظائف",
"send_welcome_email": "إرسال بريد ترحيبي",
"server_external_domain_settings": "إسم النطاق الخارجي",
"server_external_domain_settings_description": "إسم النطاق لروابط المشاركة العامة، بما في ذلك http(s)://",
@@ -250,8 +253,16 @@
"storage_template_user_label": "<code>{label}</code> هو تسمية التخزين الخاصة بالمستخدم",
"system_settings": "إعدادات النظام",
"tag_cleanup_job": "تنظيف العلامة",
"template_email_available_tags": "يمكنك استخدام المتغيرات التالية في القالب الخاص بك: {tags}",
"template_email_if_empty": "إذا كان القالب فارغا، فسيتم استخدام البريد الإلكتروني الافتراضي.",
"template_email_invite_album": "قالب دعوة الألبوم",
"template_email_preview": "عرض مسبق",
"template_email_settings": "نماذج البريد الالكتروني",
"template_email_settings_description": "إدارة قوالب إشعارات البريد الإلكتروني المخصصة",
"template_email_update_album": "تحديث قالب الألبوم",
"template_email_welcome": "قالب البريد الإلكتروني الترحيبي",
"template_settings": "قوالب الإشعارات",
"template_settings_description": "إدارة القوالب المخصصة للإشعارات.",
"theme_custom_css_settings": "CSS مخصص",
"theme_custom_css_settings_description": "أوراق الأنماط المتتالية تسمح بتخصيص تصميم Immich.",
"theme_settings": "إعدادات السمة",
@@ -281,6 +292,8 @@
"transcoding_constant_rate_factor": "عامل معدل الجودة الثابت (-crf)",
"transcoding_constant_rate_factor_description": "مستوى جودة الفيديو. القيم النموذجية هي 23 لـ H.264، 28 لـ HEVC، 31 لـ VP9، و 35 لـ AV1. كلما كانت القيمة أقل كان ذلك أفضل، ولكن يؤدي إلى ملفات أكبر.",
"transcoding_disabled_description": "لا تقم بتحويل أي مقاطع فيديو، قد تؤدي إلى عدم تشغيلها على بعض العملاء",
"transcoding_encoding_options": "خيارات الترميز",
"transcoding_encoding_options_description": "اضبط برامج الترميز والدقة والجودة والخيارات الأخرى لمقاطع الفيديو المشفرة",
"transcoding_hardware_acceleration": "التسريع العتادي",
"transcoding_hardware_acceleration_description": "تجريبي؛ أسرع بكثير، ولكن ستكون جودتها أقل عند نفس معدل البت",
"transcoding_hardware_decoding": "فك تشفير الأجهزة",
@@ -293,6 +306,8 @@
"transcoding_max_keyframe_interval": "الحد الأقصى للفاصل الزمني للإطار الرئيسي",
"transcoding_max_keyframe_interval_description": "يضبط الحد الأقصى لمسافة الإطار بين الإطارات الرئيسية. تؤدي القيم المنخفضة إلى زيادة سوء كفاءة الضغط، ولكنها تعمل على تحسين أوقات البحث وقد تعمل على تحسين الجودة في المشاهد ذات الحركة السريعة. 0 يضبط هذه القيمة تلقائيًا.",
"transcoding_optimal_description": "مقاطع الفيديو ذات الدقة الأعلى من الدقة المستهدفة أو بتنسيق غير مقبول",
"transcoding_policy": "سياسة تحويل الترميز",
"transcoding_policy_description": "اضبط متى سيتم تحويل ترميز الفيديو",
"transcoding_preferred_hardware_device": "الجهاز المفضل",
"transcoding_preferred_hardware_device_description": "ينطبق فقط على VAAPI وQSV. يضبط عقدة dri المستخدمة لتحويل ترميز الأجهزة.",
"transcoding_preset_preset": "الضبط المُسبق (-preset)",
@@ -301,7 +316,7 @@
"transcoding_reference_frames_description": "عدد الإطارات التي يجب الرجوع إليها عند ضغط إطار معين. تعمل القيم الأعلى على تحسين كفاءة الضغط، ولكنها تبطئ عملية التشفير. 0 يضبط هذه القيمة تلقائيًا.",
"transcoding_required_description": "فقط مقاطع الفيديو ذات التنسيق غير المقبول",
"transcoding_settings": "إعدادات تحويل ترميز الفيديو",
"transcoding_settings_description": "إدارة معلومات الدقة والترميز لملفات الفيديو",
"transcoding_settings_description": "إدارة مقاطع الفيديو التي يجب تحويل ترميزها وكيفية معالجتها",
"transcoding_target_resolution": "القرار المستهدف",
"transcoding_target_resolution_description": "يمكن أن تحافظ الدقة الأعلى على المزيد من التفاصيل ولكنها تستغرق وقتًا أطول للتشفير، ولها أحجام ملفات أكبر، ويمكن أن تقلل من استجابة التطبيق.",
"transcoding_temporal_aq": "التكميم التكيفي الزمني",
@@ -314,7 +329,7 @@
"transcoding_transcode_policy_description": "سياسة تحديد متى يجب ترميز الفيديو. سيتم دائمًا ترميز مقاطع الفيديو HDR (ما لم يتم تعطيل الترميز).",
"transcoding_two_pass_encoding": "الترميز بمرورين",
"transcoding_two_pass_encoding_setting_description": "ترميز بمرورين لإنتاج مقاطع فيديو بترميز أفضل. عند تمكين الحد الأقصى لمعدل البت (مطلوب لكي يعمل مع H.264 و HEVC)، يستخدم هذا الوضع نطاق معدل البت استنادًا إلى الحد الأقصى لمعدل البت ويتجاهل CRF. بالنسبة لـ VP9، يمكن استخدام CRF إذا تم تعطيل الحد الأقصى لمعدل البت.",
"transcoding_video_codec": "كود الفيديو",
"transcoding_video_codec": "ترميز الفيديو",
"transcoding_video_codec_description": "يتمتع VP9 بكفاءة عالية وتوافق مع الويب، ولكنه يستغرق وقتًا أطول في تحويل التعليمات البرمجية. يعمل HEVC بشكل مشابه، لكن توافقه مع الويب أقل. H.264 متوافق على نطاق واسع وسريع في تحويل التعليمات البرمجية، ولكنه ينتج ملفات أكبر بكثير. AV1 هو برنامج الترميز الأكثر كفاءة ولكنه يفتقر إلى الدعم على الأجهزة القديمة.",
"trash_enabled_description": "تفعيل ميزات سلة المهملات",
"trash_number_of_days": "عدد الأيام",
@@ -379,6 +394,7 @@
"allow_edits": "إسمح بالتعديل",
"allow_public_user_to_download": "السماح لأي مستخدم عام بالتنزيل",
"allow_public_user_to_upload": "السماح للمستخدم العام بالرفع",
"alt_text_qr_code": "صورة رمز الاستجابة السريعة (QR)",
"anti_clockwise": "عكس اتجاه عقارب الساعة",
"api_key": "مفتاح واجهة برمجة التطبيقات",
"api_key_description": "سيتم عرض هذه القيمة مرة واحدة فقط. يرجى التأكد من نسخها قبل إغلاق النافذة.",
@@ -394,17 +410,17 @@
"are_these_the_same_person": "هل هؤلاء هم نفس الشخص؟",
"are_you_sure_to_do_this": "هل انت متأكد من أنك تريد أن تفعل هذا؟",
"asset_added_to_album": "تمت إضافته إلى الألبوم",
"asset_adding_to_album": "جارٍ الإضافة إلى الألبوم...",
"asset_adding_to_album": "جارٍ الإضافة إلى الألبوم",
"asset_description_updated": "تم تحديث وصف المحتوى",
"asset_filename_is_offline": "الأصل {filename} غير متصل",
"asset_has_unassigned_faces": "يحتوي الأصل على وجوه غير مخصصة",
"asset_hashing": "التجزئة...",
"asset_hashing": "التجزئة",
"asset_offline": "المحتوى غير اتصال",
"asset_offline_description": "لم يعد هذا الأصل الخارجي موجودًا على القرص. يرجى الاتصال بمسؤول Immich للحصول على المساعدة.",
"asset_skipped": "تم تخطيه",
"asset_skipped_in_trash": "في سلة المهملات",
"asset_uploaded": "تم الرفع",
"asset_uploading": "جارٍ الرفع...",
"asset_uploading": "جارٍ الرفع",
"assets": "المحتويات",
"assets_added_count": "تمت إضافة {count, plural, one {# محتوى} other {# محتويات}}",
"assets_added_to_album_count": "تمت إضافة {count, plural, one {# الأصل} other {# الأصول}} إلى الألبوم",
@@ -469,6 +485,7 @@
"comments_are_disabled": "التعليقات معطلة",
"confirm": "تأكيد",
"confirm_admin_password": "تأكيد كلمة مرور المسؤول",
"confirm_delete_face": "هل أنت متأكد من حذف وجه {name} من الأصول؟",
"confirm_delete_shared_link": "هل أنت متأكد أنك تريد حذف هذا الرابط المشترك؟",
"confirm_keep_this_delete_others": "سيتم حذف جميع الأصول الأخرى في المجموعة باستثناء هذا الأصل. هل أنت متأكد من أنك تريد المتابعة؟",
"confirm_password": "تأكيد كلمة المرور",
@@ -511,12 +528,17 @@
"date_range": "نطاق الموعد",
"day": "يوم",
"deduplicate_all": "إلغاء تكرار الكل",
"deduplication_criteria_1": "حجم الصورة بوحدات البايت",
"deduplication_criteria_2": "عدد بيانات EXIF",
"deduplication_info": "معلومات إلغاء البيانات المكررة",
"deduplication_info_description": "لتحديد الأصول مسبقا تلقائيا وإزالة التكرارات بكميات كبيرة، ننظر إلى:",
"default_locale": "اللغة الافتراضية",
"default_locale_description": "تنسيق التواريخ والأرقام بناءً على لغة المتصفح الخاص بك",
"delete": "حذف",
"delete_album": "حذف الألبوم",
"delete_api_key_prompt": "هل أنت متأكد أنك تريد حذف مفتاح API هذا؟",
"delete_duplicates_confirmation": "هل أنت متأكد أنك تريد حذف هذه التكرارات نهائيًا؟",
"delete_face": "حذف الوجه",
"delete_key": "حذف المفتاح",
"delete_library": "حذف المكتبة",
"delete_link": "حذف الرابط",
@@ -584,6 +606,7 @@
"enabled": "مفعل",
"end_date": "تاريخ الإنتهاء",
"error": "خطأ",
"error_delete_face": "حدث خطأ في حذف الوجه من الأصول",
"error_loading_image": "حدث خطأ أثناء تحميل الصورة",
"error_title": "خطأ - حدث خللٌ ما",
"errors": {
@@ -726,6 +749,7 @@
"external": "خارجي",
"external_libraries": "المكتبات الخارجية",
"face_unassigned": "غير معين",
"failed_to_load_assets": "فشل تحميل الأصول",
"favorite": "مفضل",
"favorite_or_unfavorite_photo": "تفضيل أو إلغاء تفضيل الصورة",
"favorites": "المفضلة",
@@ -746,10 +770,13 @@
"get_help": "الحصول على المساعدة",
"getting_started": "البدء",
"go_back": "الرجوع للخلف",
"go_to_folder": "اذهب إلى المجلد",
"go_to_search": "اذهب إلى البحث",
"group_albums_by": "تجميع الألبومات حسب...",
"group_country": "مجموعة البلد",
"group_no": "بدون تجميع",
"group_owner": "تجميع حسب المالك",
"group_places_by": "تجميع الأماكن حسب...",
"group_year": "تجميع حسب السنة",
"has_quota": "محدد بحصة",
"hi_user": "مرحبا {name} ({email})",
@@ -782,6 +809,7 @@
"include_shared_albums": "تضمين الألبومات المشتركة",
"include_shared_partner_assets": "تضمين محتويات الشريك المشتركة",
"individual_share": "حصة فردية",
"individual_shares": "المشاركات الفردية",
"info": "معلومات",
"interval": {
"day_at_onepm": "كل يوم الساعة الواحدة ظهرا",
@@ -804,6 +832,7 @@
"latest_version": "احدث اصدار",
"latitude": "خط العرض",
"leave": "مغادرة",
"lens_model": "نموذج العدسات",
"let_others_respond": "دع الآخرين يستجيبون",
"level": "المستوى",
"library": "مكتبة",
@@ -862,6 +891,7 @@
"month": "شهر",
"more": "المزيد",
"moved_to_trash": "تم النقل إلى سلة المهملات",
"mute_memories": "كتم الذكريات",
"my_albums": "ألبوماتي",
"name": "الاسم",
"name_or_nickname": "الاسم أو اللقب",
@@ -966,6 +996,7 @@
"pick_a_location": "اختر موقعًا",
"place": "مكان",
"places": "الأماكن",
"places_count": "{count, plural, one {{count, number} مكان} other {{count, number} أماكن}}",
"play": "تشغيل",
"play_memories": "تشغيل الذكريات",
"play_motion_photo": "تشغيل الصور المتحركة",
@@ -1025,6 +1056,7 @@
"reassigned_assets_to_new_person": "تمت إعادة تعيين {count, plural, one {# المحتوى} other {# المحتويات}} إلى شخص جديد",
"reassing_hint": "تعيين المحتويات المحددة لشخص موجود",
"recent": "حديث",
"recent-albums": "ألبومات الحديثة",
"recent_searches": "عمليات البحث الأخيرة",
"refresh": "تحديث",
"refresh_encoded_videos": "تحديث مقاطع الفيديو المشفرة",
@@ -1046,11 +1078,14 @@
"remove_from_album": "إزالة من الألبوم",
"remove_from_favorites": "إزالة من المفضلة",
"remove_from_shared_link": "إزالة من الرابط المشترك",
"remove_url": "إزالة عنوان URL",
"remove_user": "إزالة المستخدم",
"removed_api_key": "تم إزالة مفتاح API: {name}",
"removed_from_archive": "تمت إزالتها من الأرشيف",
"removed_from_favorites": "تمت الإزالة من المفضلة",
"removed_from_favorites_count": "{count, plural, other {أُزيلت #}} من التفضيلات",
"removed_memory": "الذاكرة المحذوفة",
"removed_photo_from_memory": "تم إزالة الصورة من الذاكرة",
"removed_tagged_assets": "تمت إزالة العلامة من {count, plural, one {# الأصل} other {# الأصول}}",
"rename": "إعادة تسمية",
"repair": "إصلاح",
@@ -1059,6 +1094,7 @@
"repository": "المستودع",
"require_password": "يتطلب كلمة المرور",
"require_user_to_change_password_on_first_login": "مطالبة المستخدم بتغيير كلمة المرور عند تسجيل الدخول لأول مرة",
"rescan": "إعادة المسح",
"reset": "إعادة ضبط",
"reset_password": "إعادة تعيين كلمة المرور",
"reset_people_visibility": "إعادة ضبط ظهور الأشخاص",
@@ -1084,56 +1120,61 @@
"scan_library": "مسح",
"scan_settings": "إعدادات الفحص",
"scanning_for_album": "جارٍ الفحص عن ألبوم...",
"search": "بحث",
"search_albums": "بحث في الألبومات",
"search": "البحث",
"search_albums": "البحث في الألبومات",
"search_by_context": "البحث حسب السياق",
"search_by_filename": "إبحث بإسم الملف أو نوعه",
"search_by_description": "البحث حسب الوصف",
"search_by_description_example": "يوم المشي لمسافات طويلة في سابا",
"search_by_filename": "البحث بإسم الملف أو نوعه",
"search_by_filename_example": "كـ IMG_1234.JPG أو PNG",
"search_camera_make": "البحث حسب الشركة المصنعة للكاميرا...",
"search_camera_model": "البحث حسب موديل الكاميرا...",
"search_city": "البحث حسب المدينة...",
"search_country": "البحث حسب الدولة...",
"search_for": "البحث عن",
"search_for_existing_person": "البحث عن شخص موجود",
"search_no_people": "لا يوجد أشخاص",
"search_no_people_named": "لا يوجد أشخاص بالاسم \"{name}\"",
"search_options": "خيارات البحث",
"search_people": "البحث عن الأشخاص",
"search_places": "البحث عن الأماكن",
"search_rating": "البحث حسب التقييم...",
"search_settings": "إعدادات البحث",
"search_state": "البحث حسب الولاية...",
"search_tags": "البحث عن العلامات...",
"search_timezone": "البحث حسب المنطقة الزمنية...",
"search_type": "نوع البحث",
"search_your_photos": "ابحث عن صورك",
"search_your_photos": "البحث عن صورك",
"searching_locales": "جارٍ البحث في اللغات...",
"second": "ثانية",
"see_all_people": "عرض جميع الأشخاص",
"select_album_cover": "حدد غلاف الألبوم",
"select_album_cover": "تحديد غلاف الألبوم",
"select_all": "تحديد الكل",
"select_all_duplicates": "تحديد جميع النسخ المكررة",
"select_avatar_color": "حدد لون الصورة الشخصية",
"select_face": "اختيار وجه",
"select_featured_photo": "حدد الصورة المميزة",
"select_from_computer": "اختر من الجهاز",
"select_keep_all": "حدد الاحتفاظ بالكل",
"select_library_owner": "اختر مالِك المكتبة",
"select_new_face": "اختيار وجه جديد",
"select_photos": "حدد الصور",
"select_trash_all": "حدّد حذف الكلِ",
"selected": "المُحدّد",
"select_avatar_color": "تحديد لون الصورة الشخصية",
"select_face": "تحديد وجه",
"select_featured_photo": "تحديد الصورة المميزة",
"select_from_computer": "تحديد من الحاسب الآلي",
"select_keep_all": "تحديد الأحتفاظ بالكل",
"select_library_owner": "تحديد مالِك المكتبة",
"select_new_face": "تحديد وجه جديد",
"select_photos": "تحديد الصور",
"select_trash_all": "تحديد حذف الكلِ",
"selected": "التحديد",
"selected_count": "{count, plural, other {# محددة }}",
"send_message": "أرسل رسالة",
"send_welcome_email": "أرسل بريدًا إلكترونيًا ترحيبيًا",
"send_message": "‏إرسال رسالة",
"send_welcome_email": "إرسال بريدًا إلكترونيًا ترحيبيًا",
"server_offline": "الخادم غير متصل",
"server_online": "الخادم متصل",
"server_stats": "إحصائيات الخادم",
"server_version": "إصدار الخادم",
"set": "تعيين",
"set_as_album_cover": عيين كغلاف للألبوم",
"set_as_profile_picture": عيين كصورة الملف الشخصي",
"set": "‏تحديد",
"set_as_album_cover": حديد كغلاف للألبوم",
"set_as_featured_photo": حديد كصورة مميزة",
"set_as_profile_picture": "تحديد كصورة الملف الشخصي",
"set_date_of_birth": "تحديد تاريخ الميلاد",
"set_profile_picture": عيين صورة الملف الشخصي",
"set_slideshow_to_fullscreen": "اضبط عرض الشرائح على وضع ملء الشاشة",
"set_profile_picture": حديد صورة الملف الشخصي",
"set_slideshow_to_fullscreen": "تحديد عرض الشرائح على وضع ملء الشاشة",
"settings": "الإعدادات",
"settings_saved": "تم حفظ الإعدادات",
"share": "مشاركة",
@@ -1144,6 +1185,7 @@
"shared_from_partner": "صور من {partner}",
"shared_link_options": "خيارات الرابط المشترك",
"shared_links": "روابط مشتركة",
"shared_links_description": "وصف الروابط المشتركة",
"shared_photos_and_videos_count": "{assetCount, plural, other {# الصور ومقاطع الفيديو المُشارَكة.}}",
"shared_with_partner": "تمت المشاركة مع {partner}",
"sharing": "مشاركة",
@@ -1155,17 +1197,18 @@
"show_all_people": "إظهار جميع الأشخاص",
"show_and_hide_people": "إظهار وإخفاء الأشخاص",
"show_file_location": "إظهار موقع الملف",
"show_gallery": "عرض المعرض",
"show_gallery": "إظهار المعرض",
"show_hidden_people": "إظهار الأشخاص المخفيين",
"show_in_timeline": "عرض في المخطط الزمني",
"show_in_timeline_setting_description": "عرض الصور ومقاطع الفيديو من هذا المستخدم في المخطط الزمني الخاص بك",
"show_in_timeline": "إظهار في المخطط الزمني",
"show_in_timeline_setting_description": "إظهار الصور ومقاطع الفيديو من هذا المستخدم في المخطط الزمني الخاص بك",
"show_keyboard_shortcuts": "إظهار اختصارات لوحة المفاتيح",
"show_metadata": "عرض البيانات الوصفية",
"show_metadata": "إظهار البيانات الوصفية",
"show_or_hide_info": "إظهار أو إخفاء المعلومات",
"show_password": "عرض كلمة المرور",
"show_password": "إظهار كلمة المرور",
"show_person_options": "إظهار خيارات الشخص",
"show_progress_bar": "إظهار شريط التقدم",
"show_search_options": "إظهار خيارات البحث",
"show_shared_links": "عرض الروابط المشتركة",
"show_slideshow_transition": "إظهار انتقال عرض الشرائح",
"show_supporter_badge": "شارة المؤيد",
"show_supporter_badge_description": "إظهار شارة المؤيد",
@@ -1173,7 +1216,7 @@
"sidebar": "الشريط الجانبي",
"sidebar_display_description": "عرض رابط للعرض في الشريط الجانبي",
"sign_out": "خروج",
"sign_up": "تسجيل",
"sign_up": "التسجيل",
"size": "الحجم",
"skip_to_content": "تخطي إلى المحتوى",
"skip_to_folders": "تخطي إلى المجلدات",
@@ -1185,6 +1228,7 @@
"sort_items": "عدد العناصر",
"sort_modified": "تم تعديل التاريخ",
"sort_oldest": "أقدم صورة",
"sort_people_by_similarity": "رتب الأشخاص حسب التشابه",
"sort_recent": "أحدث صورة",
"sort_title": "العنوان",
"source": "المصدر",
@@ -1218,6 +1262,7 @@
"tag_created": "تم إنشاء العلامة: {tag}",
"tag_feature_description": "تصفح الصور ومقاطع الفيديو المجمعة حسب مواضيع العلامات المنطقية",
"tag_not_found_question": "لا يمكن العثور على علامة؟ <link>قم بإنشاء علامة جديدة.</link>",
"tag_people": "علِّم الأشخاص",
"tag_updated": "تم تحديث العلامة: {tag}",
"tagged_assets": "تم وضع علامة {count, plural, one {# asset} other {# assets}}",
"tags": "العلامات",
@@ -1252,11 +1297,13 @@
"unfavorite": "أزل التفضيل",
"unhide_person": "أظهر الشخص",
"unknown": "غير معروف",
"unknown_country": "بلد غير معروف",
"unknown_year": "سنة غير معروفة",
"unlimited": "غير محدود",
"unlink_motion_video": "إلغاء ربط فيديو الحركة",
"unlink_oauth": "إلغاء ربط OAuth",
"unlinked_oauth_account": "تم إلغاء ربط حساب OAuth",
"unmute_memories": "تشغيل الصوت للذكريات",
"unnamed_album": "ألبوم بلا إسم",
"unnamed_album_delete_confirmation": "هل أنت متأكد أنك تريد حذف هذا الألبوم؟",
"unnamed_share": "مشاركة بلا إسم",
@@ -1310,6 +1357,7 @@
"view_all": "عرض الكل",
"view_all_users": "عرض كافة المستخدمين",
"view_in_timeline": "عرض في الجدول الزمني",
"view_link": "عرض الرابط",
"view_links": "عرض الروابط",
"view_name": "عرض",
"view_next_asset": "عرض المحتوى التالي",

View File

@@ -20,7 +20,7 @@
"add_partner": "Добавете партньор",
"add_path": "Добави път",
"add_photos": "Добавете снимки",
"add_to": "Добави към...",
"add_to": "Добави към",
"add_to_album": "Добави към албум",
"add_to_shared_album": "Добави към споделен албум",
"add_url": "Добави URL",
@@ -41,6 +41,7 @@
"backup_settings": "Настройка на резервни копия",
"backup_settings_description": "Управление на настройките за резервно копие на базата данни",
"check_all": "Провери всичко",
"cleanup": "Почистване",
"cleared_jobs": "Изчистени задачи от тип: {job}",
"config_set_by_file": "Конфигурацията е зададена от файл",
"confirm_delete_library": "Сигурни ли сте че искате да изтриете библиотеката - {library} ?",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "Включване на периодичното сканиране на библиотеката",
"library_settings": "Външна библиотека",
"library_settings_description": "Управление на настройките за външна библиотека",
"library_tasks_description": "Извършване на задачи за библиотеката",
"library_tasks_description": "Сканирайте външни библиотеки за нови и/или променени активи",
"library_watching_enable_description": "Наблюдаване за промяна на файловете във външната библиотека",
"library_watching_settings": "Наблюдаване на библиотеката (ЕКСПЕРИМЕНТАЛНО)",
"library_watching_settings_description": "Автоматично наблюдавай за променени файлове",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "Семантично търсене на изображения с помощта на CLIP вграждания",
"machine_learning_smart_search_enabled": "Включване на Интелигентно Търсене",
"machine_learning_smart_search_enabled_description": "Ако е деактивирано, изображенията няма да бъдат кодирани за Интелигентно Търсене.",
"machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един не отговори успешно, в реда от първия до последния.",
"machine_learning_url_description": "URL на сървъра за машинно обучение. Ако са предоставени повече от един URL, всеки сървър ще бъде опитан един по един, докато един отговори успешно, в реда от първия до последния. Сървъри, които не отговорят, ще бъдат временно игнорирани, докато не се върнат онлайн.",
"manage_concurrency": "Управление на паралелност",
"manage_log_settings": "Управление на настройките на записване",
"map_dark_style": "Тъмен стил",
@@ -147,6 +148,8 @@
"map_settings": "Карта",
"map_settings_description": "Управление на настройките на картата",
"map_style_description": "URL адрес към файл \"style.json\" за задаване на стил на картата",
"memory_cleanup_job": "Почистване на паметта",
"memory_generate_job": "Генериране на паметта",
"metadata_extraction_job": "Извличане на метаданни",
"metadata_extraction_job_description": "Извличане на метаданни от всеки от елемент, като GPS локация, лица и резолюция на файловете",
"metadata_faces_import_setting": "Включи импорт на лице",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "Възстановяване на настройките по подразбиране",
"reset_settings_to_recent_saved": "Възстановяване на настройките до последните запазени настройки",
"scanning_library": "Сканиране на библиотеката",
"search_jobs": "Търсене на задачи...",
"search_jobs": "Търсене на задачи",
"send_welcome_email": "Изпращане на имейл за добре дошли",
"server_external_domain_settings": "Външен домейн",
"server_external_domain_settings_description": "Домейн за публични споделени връзки, включително http(s)://",
@@ -299,7 +302,7 @@
"transcoding_max_b_frames": "Максимални B-фрейма",
"transcoding_max_b_frames_description": "По-високите стойности подобряват ефективността на компресията, но забавят разкодирането. Може да не е съвместим с хардуерното ускорение на по-стари устройства. 0 деактивира B-фрейма, докато -1 задава тази стойност автоматично.",
"transcoding_max_bitrate": "Максимален битрейт",
"transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600k за VP9 или HEVC или 4500k за H.264. Деактивирано, ако е зададено на 0.",
"transcoding_max_bitrate_description": "Задаването на максимален битрейт може да направи размерите на файловете по-предвидими при незначителни разлики за качеството. При 720p типичните стойности са 2600 kbit/s за VP9 или HEVC или 4500 kbit/s за H.264. Деактивирано, ако е зададено на 0.",
"transcoding_max_keyframe_interval": "Максимален интервал между ключовите кадри",
"transcoding_max_keyframe_interval_description": "Задава максималното разстояние между ключовите кадри. По-ниските стойности влошават ефективността на компресията, но подобряват времето за търсене и могат да подобрят качеството в сцени с бързо движение. 0 задава тази стойност автоматично.",
"transcoding_optimal_description": "Видеоклипове с по-висока от целевата разделителна способност или не в приетия формат",
@@ -391,6 +394,7 @@
"allow_edits": "Позволяване на редакции",
"allow_public_user_to_download": "Позволете на публичен потребител да може да изтегля",
"allow_public_user_to_upload": "Позволете на публичния потребител да може да качва",
"alt_text_qr_code": "Изображение на QR код",
"anti_clockwise": "Обратно на часовниковата стрелка",
"api_key": "API ключ",
"api_key_description": "Тази стойност ще бъде показана само веднъж. Моля, не забравяйте да го копирате, преди да затворите прозореца.",
@@ -406,17 +410,17 @@
"are_these_the_same_person": "Това едно и също лице ли е?",
"are_you_sure_to_do_this": "Сигурни ли сте, че искате да направите това?",
"asset_added_to_album": "Добавено в албум",
"asset_adding_to_album": "Добавяне в албум...",
"asset_adding_to_album": "Добавяне в албум",
"asset_description_updated": "Описанието на елемента е обновено",
"asset_filename_is_offline": "Активът {filename} е офлайн",
"asset_has_unassigned_faces": "Елементът има незададени лица",
"asset_hashing": "Хеширане...",
"asset_hashing": "Хеширане",
"asset_offline": "Елементът е офлайн",
"asset_offline_description": "Този външен актив вече не се намира на диска. Моля, свържете се с администратора на Immich за помощ.",
"asset_skipped": "Пропуснато",
"asset_skipped_in_trash": "В кошчето",
"asset_uploaded": "Качено",
"asset_uploading": "Качване...",
"asset_uploading": "Качване",
"assets": "Елементи",
"assets_added_count": "Добавено {count, plural, one {# asset} other {# assets}}",
"assets_added_to_album_count": "Добавен(и) са {count, plural, one {# актив} other {# актива}} в албума",
@@ -481,6 +485,7 @@
"comments_are_disabled": "Коментарите са деактивирани",
"confirm": "Потвърди",
"confirm_admin_password": "Потвърждаване на паролата на администратора",
"confirm_delete_face": "Сигурни ли сте, че искате да изтриете лицето на {name} от актива?",
"confirm_delete_shared_link": "Сигурни ли сте, че искате да изтриете тази споделена връзка?",
"confirm_keep_this_delete_others": "Всички останали файлове в стека ще бъдат изтрити, с изключение на този файл. Сигурни ли сте, че искате да продължите?",
"confirm_password": "Потвърдете паролата",
@@ -533,6 +538,7 @@
"delete_album": "Изтрий албум",
"delete_api_key_prompt": "Сигурни ли сте, че искате да изтриете този API ключ?",
"delete_duplicates_confirmation": "Сигурни ли сте, че искате да изтриете окончателно тези дубликати?",
"delete_face": "Изтрий лице",
"delete_key": "Изтрий ключ",
"delete_library": "Изтрий библиотека",
"delete_link": "Изтрий линк",
@@ -600,6 +606,7 @@
"enabled": "Включено",
"end_date": "Крайна дата",
"error": "Грешка",
"error_delete_face": "Грешка при изтриване на лице от актива",
"error_loading_image": "Грешка при зареждане на изображението",
"error_title": "Грешка - нещо се обърка",
"errors": {
@@ -766,8 +773,10 @@
"go_to_folder": "Отиди в папката",
"go_to_search": "Преминаване към търсене",
"group_albums_by": "Групирай албум по...",
"group_country": "Групирай по държава",
"group_no": "Няма група",
"group_owner": "Групиране по собственик",
"group_places_by": "Групирай места по…",
"group_year": "Групиране по година",
"has_quota": "Лимит",
"hi_user": "Здравей, {name} {email}",
@@ -800,6 +809,7 @@
"include_shared_albums": "Включване на споделени албуми",
"include_shared_partner_assets": "Включване на споделените с партньор елементи",
"individual_share": "Индивидуално споделяне",
"individual_shares": "Индивидуални споделяния",
"info": "Информация",
"interval": {
"day_at_onepm": "Всеки ден в 13:00",
@@ -822,6 +832,7 @@
"latest_version": "Последна версия",
"latitude": "Ширина",
"leave": "Излез",
"lens_model": "Модел леща",
"let_others_respond": "Позволете на другите да отговорят",
"level": "Ниво",
"library": "Библиотека",
@@ -880,6 +891,7 @@
"month": "Месец",
"more": "Още",
"moved_to_trash": "Преместено в кошчето",
"mute_memories": "Изключване на звука на спомените",
"my_albums": "Мои албуми",
"name": "Име",
"name_or_nickname": "Име или прякор",
@@ -984,6 +996,7 @@
"pick_a_location": "Избери локация",
"place": "Местоположение",
"places": "Местоположения",
"places_count": "{count, plural, one {{count, number} Място} other {{count, number} Места}}",
"play": "Възпроизвеждане",
"play_memories": "Възпроизвеждане на спомени",
"play_motion_photo": "Възпроизведи Motion Photo",
@@ -1067,10 +1080,12 @@
"remove_from_shared_link": "Премахни от споделения линк",
"remove_url": "Премахни URL",
"remove_user": "Премахни потребителя",
"removed_api_key": "Премахни API Key: {name}",
"removed_from_archive": "Премахни от Архива",
"removed_from_favorites": "Премахнато от Любими",
"removed_api_key": "Премахат API ключ: {name}",
"removed_from_archive": "Премахни от архива",
"removed_from_favorites": "Премахнато от любими",
"removed_from_favorites_count": "{count, plural, other {Премахнати #}} от Любими",
"removed_memory": "Премахнат спомен",
"removed_photo_from_memory": "Премахната снимка от спомен",
"removed_tagged_assets": "Премахнат е етикетът от {count, plural, one {# елемент} other {# елемента}}",
"rename": "Преименувай",
"repair": "Поправи",
@@ -1079,6 +1094,7 @@
"repository": "Хранилище",
"require_password": "Изискай парола",
"require_user_to_change_password_on_first_login": "Изисквай потребителят да промени паролата си при първото влизане",
"rescan": "Пресканиране",
"reset": "Нулиране",
"reset_password": "Нулиране на паролата",
"reset_people_visibility": "Нулиране на видимостта на хората",
@@ -1107,18 +1123,22 @@
"search": "Търсене",
"search_albums": "Търси албуми",
"search_by_context": "Търси по контекст",
"search_by_description": "Търси по описание",
"search_by_description_example": "Разходка в Сапа",
"search_by_filename": "Търси по име на файла или разширение",
"search_by_filename_example": "например IMG_1234.JPG или PNG",
"search_camera_make": "Търси производител на камерата...",
"search_camera_model": "Търси модел на камерата...",
"search_city": "Търси град...",
"search_country": "Търси държава...",
"search_for": "Търси за",
"search_for_existing_person": "Търси съществуващ човек",
"search_no_people": "Няма хора",
"search_no_people_named": "Няма хора на име \"{name}\"",
"search_options": "Опции за търсене",
"search_people": "Търсете на хора",
"search_places": "Търсене на места",
"search_rating": "Търси по рейтинг…",
"search_settings": "Търсене на настройки",
"search_state": "Търсене на щат...",
"search_tags": "Търсене на етикети...",
@@ -1165,6 +1185,7 @@
"shared_from_partner": "Снимки от {partner}",
"shared_link_options": "Опции за споделена връзка",
"shared_links": "Споделени връзки",
"shared_links_description": "Сподели снимки и видеа с линк",
"shared_photos_and_videos_count": "{assetCount, plural, other {# споделени снимки и видеа.}}",
"shared_with_partner": "Споделено с {partner}",
"sharing": "Споделени",
@@ -1187,6 +1208,7 @@
"show_person_options": "Показване на опции за лица",
"show_progress_bar": "Показване на прогрес бара",
"show_search_options": "Показване на опциите за търсене",
"show_shared_links": "Покажи споделени линкове",
"show_slideshow_transition": "Покажи прехода на слайдшоуто",
"show_supporter_badge": "Значка поддръжник",
"show_supporter_badge_description": "Покажи значка поддръжник",
@@ -1240,6 +1262,7 @@
"tag_created": "Създаден етикет: {tag}",
"tag_feature_description": "Разглеждане на снимки и видеоклипове, групирани по теми с логически тагове",
"tag_not_found_question": "Не можете да намерите етикет? Създайте такъв <link>тук</link>",
"tag_people": "Отбележи Хора",
"tag_updated": "Актуализиран етикет: {tag}",
"tagged_assets": "Тагнати {count, plural, one {# елемент} other {# елементи}}",
"tags": "Етикет",
@@ -1274,11 +1297,13 @@
"unfavorite": "Премахване от любимите",
"unhide_person": "Покажи отново човека",
"unknown": "Неизвестно",
"unknown_country": "Непозната Държава",
"unknown_year": "Неизвестна година",
"unlimited": "Неограничено",
"unlink_motion_video": "Премахни връзката с видео",
"unlink_oauth": "Премахни OAuth",
"unlinked_oauth_account": "Премахни OAuth акаунт",
"unmute_memories": "Включване на звука на спомените",
"unnamed_album": "Албум без име",
"unnamed_album_delete_confirmation": "Сигурни ли сте, че искате да изтриете този албум?",
"unnamed_share": "Споделяне без име",
@@ -1332,6 +1357,7 @@
"view_all": "Преглед на всички",
"view_all_users": "Преглед на всички потребители",
"view_in_timeline": "Покажи във времева линия",
"view_link": "Преглед на връзката",
"view_links": "Преглед на връзките",
"view_name": "Прегледай",
"view_next_asset": "Преглед на следващия файл",
@@ -1348,4 +1374,4 @@
"yes": "Да",
"you_dont_have_any_shared_links": "Нямате споделени връзки",
"zoom_image": "Увеличаване на изображението"
}
}

View File

@@ -20,7 +20,7 @@
"add_partner": "Afegir company/a",
"add_path": "Afegir una ruta",
"add_photos": "Afegir fotografies",
"add_to": "Afegir a...",
"add_to": "Afegir a",
"add_to_album": "Afegir a un l'àlbum",
"add_to_shared_album": "Afegir a un àlbum compartit",
"add_url": "Afegir URL",
@@ -41,6 +41,7 @@
"backup_settings": "Ajustes de les còpies de seguretat",
"backup_settings_description": "Gestionar la configuració de la còpia de seguretat de la base de dades",
"check_all": "Marca-ho tot",
"cleanup": "Neteja",
"cleared_jobs": "Tasques esborrades per a: {job}",
"config_set_by_file": "La configuració està definida per un fitxer de configuració",
"confirm_delete_library": "Esteu segurs que voleu eliminar la llibreria {library}?",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "Habilita l'escaneig periòdic de biblioteques",
"library_settings": "Llibreria externes",
"library_settings_description": "Gestiona la configuració de les llibreries externes",
"library_tasks_description": "Realitza tasques de la bilbioteca",
"library_tasks_description": "Escaneja biblioteques externes per arxius nous o canviats",
"library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers",
"library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)",
"library_watching_settings_description": "Monitorització automàtica de fitxers modificats",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "Cerca imatges semànticament emprant incrustacions CLIP",
"machine_learning_smart_search_enabled": "Activa la cerca intel·ligent",
"machine_learning_smart_search_enabled_description": "Si està deshabilitat les imatges no es codificaran per la cerca intel·ligent.",
"machine_learning_url_description": "La URL del servidor d'aprenentatge automàtic. Si es proporciona més d'una URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.",
"machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona més d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.",
"manage_concurrency": "Gestiona la concurrència",
"manage_log_settings": "Gestiona la configuració del registre",
"map_dark_style": "Tema fosc",
@@ -147,6 +148,8 @@
"map_settings": "Mapa",
"map_settings_description": "Gestiona la configuració del mapa",
"map_style_description": "URL a un tema del mapa style.json",
"memory_cleanup_job": "Neteja de memòries",
"memory_generate_job": "Generació de memòries",
"metadata_extraction_job": "Extreure metadades",
"metadata_extraction_job_description": "Extreu la informació de metadades de cada element, com per exemple el GPS i la resolució",
"metadata_faces_import_setting": "Activar la importació de cares",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "Restablir configuracions per defecte",
"reset_settings_to_recent_saved": "Restablir la configuració guardada més recent",
"scanning_library": "Escanejant biblioteca",
"search_jobs": "Tasques de cerca...",
"search_jobs": "Cercar treballs…",
"send_welcome_email": "Enviar correu electrònic de benvinguda",
"server_external_domain_settings": "Domini extern",
"server_external_domain_settings_description": "Domini per enllaços públics compartits, incloent http(s)://",
@@ -240,7 +243,7 @@
"storage_template_hash_verification_enabled_description": "Activa la verificació de hash. No la desactiveu a menys que estigueu segurs de les implicacions",
"storage_template_migration": "Migració de plantilles d'emmagatzematge",
"storage_template_migration_description": "Aplica la <link>{template}</link> actual als elements pujats prèviament",
"storage_template_migration_info": "Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la <link>{job}</link>.",
"storage_template_migration_info": "Les extensions es convertiran a minúscules. Els canvis de plantilla només s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la <link>{job}</link>.",
"storage_template_migration_job": "Tasca de migració de la plantilla d'emmagatzematge",
"storage_template_more_details": "Per obtenir més detalls sobre aquesta funció, consulteu la <template-link>Storage Template</template-link> i les seves <implications-link>implications</implications-link>",
"storage_template_onboarding_description": "Quan està activada, aquesta funció organitzarà automàticament els fitxers en funció d'una plantilla definida per l'usuari. A causa de problemes d'estabilitat, la funció s'ha desactivat de manera predeterminada. Per obtenir més informació, consulteu la <link>documentation</link>.",
@@ -299,7 +302,7 @@
"transcoding_max_b_frames": "Nombre màxim de B-frames",
"transcoding_max_b_frames_description": "Els valors més alts milloren l'eficiència de la compressió, però alenteixen la codificació. És possible que no sigui compatible amb l'acceleració de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.",
"transcoding_max_bitrate": "Taxa de bits màxima",
"transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600k per a VP9 o HEVC, o 4500k per a H.264. Desactivat si s'estableix a 0.",
"transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin més previsibles amb un cost menor per a la qualitat. A 720p, els valors típics són 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0.",
"transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau",
"transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors més baixos empitjoren l'eficiència de la compressió, però milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.",
"transcoding_optimal_description": "Vídeos superiors a la resolució objectiu o que no tenen un format acceptat",
@@ -391,6 +394,7 @@
"allow_edits": "Permet editar",
"allow_public_user_to_download": "Permet que l'usuari públic pugui descarregar",
"allow_public_user_to_upload": "Permet que l'usuari públic pugui carregar",
"alt_text_qr_code": "Codi QR",
"anti_clockwise": "En sentit antihorari",
"api_key": "Clau API",
"api_key_description": "Aquest valor només es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.",
@@ -406,17 +410,17 @@
"are_these_the_same_person": "Són la mateixa persona?",
"are_you_sure_to_do_this": "Esteu segurs que voleu fer-ho?",
"asset_added_to_album": "Afegit a l'àlbum",
"asset_adding_to_album": "Afegint a l'àlbum...",
"asset_adding_to_album": "Afegint a l'àlbum",
"asset_description_updated": "La descripció del recurs s'ha actualitzat",
"asset_filename_is_offline": "L'element {filename} està fora de línia",
"asset_has_unassigned_faces": "L'element té cares no assignades",
"asset_hashing": "Hashing...",
"asset_hashing": "Hasheant…",
"asset_offline": "Element fora de línia",
"asset_offline_description": "Aquest recurs extern ja no es troba al disc. Poseu-vos en contacte amb el vostre administrador d'Immich per obtenir ajuda.",
"asset_skipped": "Saltat",
"asset_skipped_in_trash": "A la paperera",
"asset_uploaded": "Carregat",
"asset_uploading": "S'està carregant...",
"asset_uploading": "S'està carregant",
"assets": "Elements",
"assets_added_count": "{count, plural, one {Afegit un element} other {Afegits # elements}}",
"assets_added_to_album_count": "{count, plural, one {Afegit un element} other {Afegits # elements}} a l'àlbum",
@@ -464,7 +468,7 @@
"check_logs": "Comprovar els registres",
"choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les",
"city": "Ciutat",
"clear": "Neteja",
"clear": "Buida",
"clear_all": "Neteja-ho tot",
"clear_all_recent_searches": "Esborra totes les cerques recents",
"clear_message": "Neteja el missatge",
@@ -481,6 +485,7 @@
"comments_are_disabled": "Els comentaris estan desactivats",
"confirm": "Confirmar",
"confirm_admin_password": "Confirmeu la contrasenya d'administrador",
"confirm_delete_face": "Estàs segur que vols eliminar la cara de {name} de les cares reconegudes?",
"confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?",
"confirm_keep_this_delete_others": "Excepte aquest element, tots els altres de la pila se suprimiran. Esteu segur que voleu continuar?",
"confirm_password": "Confirmació de contrasenya",
@@ -533,6 +538,7 @@
"delete_album": "Esborra l'àlbum",
"delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?",
"delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?",
"delete_face": "Esborrar cara",
"delete_key": "Suprimeix la clau",
"delete_library": "Suprimeix la Llibreria",
"delete_link": "Esborra l'enllaç",
@@ -600,6 +606,7 @@
"enabled": "Activat",
"end_date": "Data final",
"error": "Error",
"error_delete_face": "Error esborrant cara de les cares reconegudes",
"error_loading_image": "Error carregant la imatge",
"error_title": "Error - Quelcom ha anat malament",
"errors": {
@@ -766,8 +773,10 @@
"go_to_folder": "Anar al directori",
"go_to_search": "Vés a cercar",
"group_albums_by": "Agrupa àlbums per...",
"group_country": "Agrupar per país",
"group_no": "Cap agrupació",
"group_owner": "Agrupar per propietari",
"group_places_by": "Agrupar llocs per...",
"group_year": "Agrupar per any",
"has_quota": "Quota",
"hi_user": "Hola {name} ({email})",
@@ -800,6 +809,7 @@
"include_shared_albums": "Inclou àlbums compartits",
"include_shared_partner_assets": "Incloure elements dels companys",
"individual_share": "Compartit individualment",
"individual_shares": "Espais individuals",
"info": "Informació",
"interval": {
"day_at_onepm": "Cada dia a les 13h",
@@ -822,6 +832,7 @@
"latest_version": "Última versió",
"latitude": "Latitud",
"leave": "Marxar",
"lens_model": "Model de lents",
"let_others_respond": "Deixa que els altres responguin",
"level": "Nivell",
"library": "Bibilioteca",
@@ -880,6 +891,7 @@
"month": "Mes",
"more": "Més",
"moved_to_trash": "S'ha mogut a la paperera",
"mute_memories": "Silenciar records",
"my_albums": "Els meus àlbums",
"name": "Nom",
"name_or_nickname": "Nom o sobrenom",
@@ -984,6 +996,7 @@
"pick_a_location": "Triar una ubicació",
"place": "Lloc",
"places": "Llocs",
"places_count": "{count, plural, one {{count, number} Lloc} other {{count, number} Llocs}}",
"play": "Reprodueix",
"play_memories": "Reproduir records",
"play_motion_photo": "Reproduir Fotos en Moviment",
@@ -1071,6 +1084,8 @@
"removed_from_archive": "Eliminat de l'arxiu",
"removed_from_favorites": "Eliminat dels preferits",
"removed_from_favorites_count": "{count, plural, other {# eliminats}} dels preferits",
"removed_memory": "Eliminat memòria",
"removed_photo_from_memory": "Eliminat foto de memòria",
"removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# actiu} other {# actius}}",
"rename": "Canviar nom",
"repair": "Reparació",
@@ -1079,6 +1094,7 @@
"repository": "Repositori",
"require_password": "Requereix contrasenya",
"require_user_to_change_password_on_first_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessió",
"rescan": "Tornar a escanejar",
"reset": "Restablir",
"reset_password": "Restablir contrasenya",
"reset_people_visibility": "Restablir la visibilitat de les persones",
@@ -1094,7 +1110,7 @@
"review_duplicates": "Revisar duplicats",
"role": "Rol",
"role_editor": "Editor",
"role_viewer": "Visor",
"role_viewer": "Visualitzador",
"save": "Desa",
"saved_api_key": "Clau d'API guardada",
"saved_profile": "Perfil guardat",
@@ -1107,18 +1123,22 @@
"search": "Cerca",
"search_albums": "Buscar àlbums",
"search_by_context": "Buscar per context",
"search_by_description": "Cercar per descripció",
"search_by_description_example": "Jornada de senderisme a Sapa",
"search_by_filename": "Cerca per nom de fitxer o extensió",
"search_by_filename_example": "per exemple IMG_1234.JPG o PNG",
"search_camera_make": "Buscar per fabricant de càmara...",
"search_camera_model": "Buscar per model de càmera...",
"search_city": "Buscar per ciutat...",
"search_country": "Buscar per país...",
"search_for": "Cercar",
"search_for_existing_person": "Busca una persona existent",
"search_no_people": "Cap persona",
"search_no_people_named": "Cap persona anomenada \"{name}\"",
"search_options": "Opcions de cerca",
"search_people": "Buscar persones",
"search_places": "Buscar llocs",
"search_rating": "Buscar per qualificació...",
"search_settings": "Configuració de cerca",
"search_state": "Buscar per regió...",
"search_tags": "Cercant etiquetes...",
@@ -1165,6 +1185,7 @@
"shared_from_partner": "Fotos de {partner}",
"shared_link_options": "Opcions d'enllaços compartits",
"shared_links": "Enllaços compartits",
"shared_links_description": "Comparteix fotos i vídeos amb un enllaç",
"shared_photos_and_videos_count": "{assetCount, plural, other {# fotos i vídeos compartits.}}",
"shared_with_partner": "Compartit amb {partner}",
"sharing": "Compartit",
@@ -1187,6 +1208,7 @@
"show_person_options": "Mostra opcions de la persona",
"show_progress_bar": "Mostra barra de progrés",
"show_search_options": "Mostra opcions de cerca",
"show_shared_links": "Mostra els enllaços compartits",
"show_slideshow_transition": "Mostra la transició de la presentació de diapositives",
"show_supporter_badge": "Insígnia de contribuent",
"show_supporter_badge_description": "Mostra una insígnia de contributor",
@@ -1240,6 +1262,7 @@
"tag_created": "Etiqueta creada: {tag}",
"tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lògiques",
"tag_not_found_question": "No trobeu una etiqueta? <link>Crear una nova etiqueta</link>",
"tag_people": "Etiquetar personas",
"tag_updated": "Etiqueta actualizada: {tag}",
"tagged_assets": "{count, plural, one {#Etiquetat} other {#Etiquetats}} {count, plural, one {# actiu} other {# actius}}",
"tags": "Etiquetes",
@@ -1274,11 +1297,13 @@
"unfavorite": "Reverteix preferit",
"unhide_person": "Mostra persona",
"unknown": "Desconegut",
"unknown_country": "País Desconegut",
"unknown_year": "Any desconegut",
"unlimited": "Il·limitat",
"unlink_motion_video": "Desvincular vídeo en moviment",
"unlink_oauth": "Desvincula OAuth",
"unlinked_oauth_account": "Compte Oauth desvinculat",
"unmute_memories": "Activar el so dels records",
"unnamed_album": "Àlbum sense nom",
"unnamed_album_delete_confirmation": "Segur que voleu esborrar aquest àlbum?",
"unnamed_share": "Compartit sense nom",
@@ -1332,6 +1357,7 @@
"view_all": "Veure tot",
"view_all_users": "Mostra tot els usuaris",
"view_in_timeline": "Mostrar a la línia de temps",
"view_link": "Veure enllaç",
"view_links": "Mostra enllaços",
"view_name": "Veure",
"view_next_asset": "Mostra el següent element",
@@ -1348,4 +1374,4 @@
"yes": "Sí",
"you_dont_have_any_shared_links": "No tens cap enllaç compartit",
"zoom_image": "Ampliar Imatge"
}
}

View File

@@ -20,7 +20,7 @@
"add_partner": "Přidat partnera",
"add_path": "Přidat cestu",
"add_photos": "Přidat fotky",
"add_to": "Přidat do...",
"add_to": "Přidat do",
"add_to_album": "Přidat do alba",
"add_to_shared_album": "Přidat do sdíleného alba",
"add_url": "Přidat URL",
@@ -41,6 +41,7 @@
"backup_settings": "Nastavení zálohování",
"backup_settings_description": "Správa nastavení zálohování databáze",
"check_all": "Vše zkontrolovat",
"cleanup": "Vyčištění",
"cleared_jobs": "Hotové úlohy pro: {job}",
"config_set_by_file": "Konfigurace je aktuálně prováděna konfiguračním souborem",
"confirm_delete_library": "Opravdu chcete odstranit knihovnu {library}?",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "Povolit pravidelné prohledávání knihovny",
"library_settings": "Externí knihovna",
"library_settings_description": "Správa nastavení externí knihovny",
"library_tasks_description": "Provádění úkolů v knihovně",
"library_tasks_description": "Vyhledávání nových nebo změněných položek v externích knihovnách",
"library_watching_enable_description": "Sledovat změny souborů v externích knihovnách",
"library_watching_settings": "Sledování knihovny (EXPERIMENTÁLNÍ)",
"library_watching_settings_description": "Automatické sledování změněných souborů",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "Sémantické vyhledávání obrázků pomocí CLIP embeddings",
"machine_learning_smart_search_enabled": "Povolit chytré vyhledávání",
"machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrázky nebudou kódovány pro inteligentní vyhledávání.",
"machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu.",
"machine_learning_url_description": "URL serveru strojového učení. Pokud je zadáno více URL adres, budou jednotlivé servery zkoušeny postupně, dokud jeden z nich neodpoví úspěšně, a to v pořadí od prvního k poslednímu. Servery, které neodpoví, budou dočasně ignorovány, dokud nebudou opět online.",
"manage_concurrency": "Správa souběžnosti",
"manage_log_settings": "Správa nastavení protokolu",
"map_dark_style": "Tmavý motiv",
@@ -147,6 +148,8 @@
"map_settings": "Mapa",
"map_settings_description": "Správa nastavení mapy",
"map_style_description": "URL na style.json motivu",
"memory_cleanup_job": "Promazání vzpomínek",
"memory_generate_job": "Vytvoření vzpomínek",
"metadata_extraction_job": "Extrakce metadat",
"metadata_extraction_job_description": "Získání informací o metadatech z každého snímku, jako je GPS, obličeje a rozlišení",
"metadata_faces_import_setting": "Povolit import obličeje",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "Obnovení výchozího nastavení",
"reset_settings_to_recent_saved": "Obnovit poslední uložené nastavení",
"scanning_library": "Prohledat knihovnu",
"search_jobs": "Hledat úlohy...",
"search_jobs": "Hledat úlohy",
"send_welcome_email": "Odeslat uvítací e-mail",
"server_external_domain_settings": "Externí doména",
"server_external_domain_settings_description": "Doména pro veřejně sdílené odkazy, včetně http(s)://",
@@ -240,7 +243,7 @@
"storage_template_hash_verification_enabled_description": "Povolí ověřování hashe, nevypínejte ji, pokud si nejste jisti důsledky",
"storage_template_migration": "Migrace šablony úložiště",
"storage_template_migration_description": "Použít aktuální <link>{template}</link> na dříve nahrané položky",
"storage_template_migration_info": "Změny šablon se uplatní pouze u nových položek. Chcete-li šablonu zpětně použít na dříve nahrané položky, spusťte <link>{job}</link>.",
"storage_template_migration_info": "Šablona úložiště převede všechny přípony na malá písmena. Změny šablon se uplatní pouze u nových položek. Chcete-li šablonu zpětně použít na dříve nahrané položky, spusťte <link>{job}</link>.",
"storage_template_migration_job": "Úloha migrace šablony úložiště",
"storage_template_more_details": "Další podrobnosti o této funkci naleznete v sekci <template-link>Šablona úložiště</template-link> včetně jejích <implications-link>důsledků</implications-link>",
"storage_template_onboarding_description": "Je-li tato funkce povolena, automaticky uspořádá soubory na základě uživatelem definované šablony. Z důvodu problémů se stabilitou byla tato funkce ve výchozím nastavení vypnuta. Další informace naleznete v <link>dokumentaci</link>.",
@@ -288,7 +291,7 @@
"transcoding_constant_quality_mode_description": "ICQ je lepší než CQP, ale některá zařízení pro hardwarovou akceleraci tento režim nepodporují. Nastavením této volby se při použití kódování založeného na kvalitě upřednostní zadaný režim. Ignorováno NVENC, protože nepodporuje ICQ.",
"transcoding_constant_rate_factor": "Faktor konstantní rychlosti (-crf)",
"transcoding_constant_rate_factor_description": "Úroveň kvality videa. Typické hodnoty jsou 23 pro H.264, 28 pro HEVC, 31 pro VP9 a 35 pro AV1. Nižší je lepší, ale vytváří větší soubory.",
"transcoding_disabled_description": "Nepřekódovávejte žádná videa, u některých klientů může dojít k znemožnění přehrávání",
"transcoding_disabled_description": "Nepřekódovávat žádná videa, u některých klientů může dojít k znemožnění přehrávání",
"transcoding_encoding_options": "Možnosti kódování",
"transcoding_encoding_options_description": "Nastavte kodeky, rozlišení, kvalitu a další možnosti pro kódovaná videa",
"transcoding_hardware_acceleration": "Hardwarová akcelerace",
@@ -299,7 +302,7 @@
"transcoding_max_b_frames": "Maximální počet B-snímků",
"transcoding_max_b_frames_description": "Vyšší hodnoty zvyšují účinnost komprese, ale zpomalují kódování. Nemusí být kompatibilní s hardwarovou akcelerací na starších zařízeních. Hodnota 0 zakáže B-snímky, zatímco -1 tuto hodnotu nastaví automaticky.",
"transcoding_max_bitrate": "Maximální datový tok",
"transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600k pro VP9 nebo HEVC nebo 4500k pro H.264. Je zakázáno, pokud je nastavena hodnota 0.",
"transcoding_max_bitrate_description": "Nastavení maximálního datového toku může zvýšit předvídatelnost velikosti souborů za cenu menší újmy na kvalitě. Při rozlišení 720p jsou typické hodnoty 2600 kbit/s pro VP9 nebo HEVC nebo 4500 kbit/s pro H.264. Je zakázáno, pokud je nastavena hodnota 0.",
"transcoding_max_keyframe_interval": "Maximální interval klíčových snímků",
"transcoding_max_keyframe_interval_description": "Nastavuje maximální vzdálenost mezi klíčovými snímky. Nižší hodnoty zhoršují účinnost komprese, ale zlepšují rychlost při přeskakování a mohou zlepšit kvalitu ve scénách s rychlým pohybem. Hodnota 0 nastavuje tuto hodnotu automaticky.",
"transcoding_optimal_description": "Videa s vyšším než cílovým rozlišením nebo videa, která nejsou v akceptovaném formátu",
@@ -391,6 +394,7 @@
"allow_edits": "Povolit úpravy",
"allow_public_user_to_download": "Povolit veřejnosti stahovat",
"allow_public_user_to_upload": "Povolit veřejnosti nahrávat",
"alt_text_qr_code": "Obrázek QR kódu",
"anti_clockwise": "Proti směru hodinových ručiček",
"api_key": "API klíč",
"api_key_description": "Tato hodnota se zobrazí pouze jednou. Před zavřením okna ji nezapomeňte zkopírovat.",
@@ -406,17 +410,17 @@
"are_these_the_same_person": "Jedná se o stejnou osobu?",
"are_you_sure_to_do_this": "Opravdu to chcete udělat?",
"asset_added_to_album": "Přidáno do alba",
"asset_adding_to_album": "Přidávání do alba...",
"asset_adding_to_album": "Přidávání do alba",
"asset_description_updated": "Popis položky byl aktualizován",
"asset_filename_is_offline": "Položka {filename} je offline",
"asset_has_unassigned_faces": "Položka má nepřiřazené obličeje",
"asset_hashing": "Hashování...",
"asset_hashing": "Hashování",
"asset_offline": "Offline položka",
"asset_offline_description": "Toto externí položka se již na disku nenachází. Obraťte se na Immich správce a požádejte o pomoc.",
"asset_skipped": "Přeskočeno",
"asset_skipped_in_trash": "V koši",
"asset_uploaded": "Nahráno",
"asset_uploading": "Nahrávání...",
"asset_uploading": "Nahrávání",
"assets": "Položky",
"assets_added_count": "{count, plural, one {Přidána # položka} few {Přidány # položky} other {Přidáno # položek}}",
"assets_added_to_album_count": "Do alba {count, plural, one {byla přidána # položka} few {byly přidány # položky} other {bylo přidáno # položek}}",
@@ -464,11 +468,11 @@
"check_logs": "Zkontrolujte protokoly",
"choose_matching_people_to_merge": "Zvolte odpovídající osoby ke sloučení",
"city": "Město",
"clear": "Vyčistit",
"clear": "Vymazat",
"clear_all": "Vymazat vše",
"clear_all_recent_searches": "Vymazat všechna nedávná vyhledávání",
"clear_message": "Vyčistit zprávu",
"clear_value": "Vyčistit hodnotu",
"clear_message": "Vymazat zprávu",
"clear_value": "Vymazat hodnotu",
"clockwise": "Po směru hodinových ručiček",
"close": "Zavřít",
"collapse": "Sbalit",
@@ -481,6 +485,7 @@
"comments_are_disabled": "Komentáře jsou vypnuty",
"confirm": "Potvrdit",
"confirm_admin_password": "Potvrzení hesla správce",
"confirm_delete_face": "Opravdu chcete z položky odstranit obličej osoby {name}?",
"confirm_delete_shared_link": "Opravdu chcete odstranit tento sdílený odkaz?",
"confirm_keep_this_delete_others": "Všechny ostatní položky v tomto uskupení mimo této budou odstraněny. Opravdu chcete pokračovat?",
"confirm_password": "Potvrzení hesla",
@@ -533,6 +538,7 @@
"delete_album": "Smazat album",
"delete_api_key_prompt": "Opravdu chcete tento API klíč odstranit?",
"delete_duplicates_confirmation": "Opravdu chcete tyto duplicity trvale odstranit?",
"delete_face": "Odstranit obličej",
"delete_key": "Smazat klíč",
"delete_library": "Smazat knihovnu",
"delete_link": "Smazat odkaz",
@@ -600,6 +606,7 @@
"enabled": "Povoleno",
"end_date": "Konečné datum",
"error": "Chyba",
"error_delete_face": "Chyba při odstraňování obličeje z položky",
"error_loading_image": "Chyba při načítání obrázku",
"error_title": "Chyba - Něco se pokazilo",
"errors": {
@@ -751,8 +758,8 @@
"features_setting_description": "Správa funkcí aplikace",
"file_name": "Název souboru",
"file_name_or_extension": "Název nebo přípona souboru",
"filename": "Filename",
"filetype": "Filetype",
"filename": "Název souboru",
"filetype": "Typ souboru",
"filter_people": "Filtrovat lidi",
"find_them_fast": "Najděte je rychle vyhledáním jejich jména",
"fix_incorrect_match": "Opravit nesprávnou shodu",
@@ -766,8 +773,10 @@
"go_to_folder": "Přejít do složky",
"go_to_search": "Přejít na vyhledávání",
"group_albums_by": "Seskupit alba podle...",
"group_country": "Seskupit podle země",
"group_no": "Neseskupovat",
"group_owner": "Seskupit podle uživatele",
"group_places_by": "Seskupit místa podle...",
"group_year": "Seskupit podle roku",
"has_quota": "Má kvótu",
"hi_user": "Ahoj {name} ({email})",
@@ -800,6 +809,7 @@
"include_shared_albums": "Včetně sdílených alb",
"include_shared_partner_assets": "Včetně sdílených položek partnera",
"individual_share": "Sdílení jednotlivých položek",
"individual_shares": "Sdílení jednotlivých položek",
"info": "Informace",
"interval": {
"day_at_onepm": "Každý den ve 13:00",
@@ -822,6 +832,7 @@
"latest_version": "Nejnovější verze",
"latitude": "Zeměpisná šířka",
"leave": "Opustit",
"lens_model": "Model objektivu",
"let_others_respond": "Nechte ostatní reagovat",
"level": "Úroveň",
"library": "Knihovna",
@@ -880,6 +891,7 @@
"month": "Měsíc",
"more": "Více",
"moved_to_trash": "Přesunuto do koše",
"mute_memories": "Ztlumit vzpomínky",
"my_albums": "Moje alba",
"name": "Jméno",
"name_or_nickname": "Jméno nebo přezdívka",
@@ -975,6 +987,7 @@
"permanently_deleted_asset": "Položka trvale odstraněna",
"permanently_deleted_assets_count": "{count, plural, one {Položka trvale vymazána} other {Položky trvale vymazány}}",
"person": "Osoba",
"person_birthdate": "Narozen/a {date}",
"person_hidden": "{name}{hidden, select, true { (skryto)} other {}}",
"photo_shared_all_users": "Vypadá to, že jste fotky sdíleli se všemi uživateli, nebo nemáte žádného uživatele, se kterým byste je mohli sdílet.",
"photos": "Fotky",
@@ -984,6 +997,7 @@
"pick_a_location": "Vyberte polohu",
"place": "Místo",
"places": "Místa",
"places_count": "{count, plural, one {{count, number} místo} few {{count, number} místa} other {{count, number} míst}}",
"play": "Přehrávat",
"play_memories": "Přehrát vzpomníky",
"play_motion_photo": "Přehrát pohybovou fotografii",
@@ -1065,12 +1079,16 @@
"remove_from_album": "Odstranit z alba",
"remove_from_favorites": "Odstranit z oblíbených",
"remove_from_shared_link": "Odstranit ze sdíleného odkazu",
"remove_memory": "Odstranit vzpomínku",
"remove_photo_from_memory": "Odstranit fotografii z této vzpomínky",
"remove_url": "Odstranit URL",
"remove_user": "Odebrat uživatele",
"removed_api_key": "Odstraněn API klíč: {name}",
"removed_from_archive": "Odstraněno z archivu",
"removed_from_favorites": "Odstraněno z oblíbených",
"removed_from_favorites_count": "{count, plural, one {Odstraněn #} few {Odstraněny #} other {Odstraněno #}} z oblíbených",
"removed_memory": "Vzpomínka odstraněna",
"removed_photo_from_memory": "Fotografie odstraněna ze vzpomínky",
"removed_tagged_assets": "Odstraněná značka z {count, plural, one {# položky} other {# položek}}",
"rename": "Přejmenovat",
"repair": "Opravy",
@@ -1079,6 +1097,7 @@
"repository": "Repozitář",
"require_password": "Požadovat heslo",
"require_user_to_change_password_on_first_login": "Požadovat, aby si uživatel při prvním přihlášení změnil heslo",
"rescan": "Znovu prohledat",
"reset": "Výchozí",
"reset_password": "Obnovit heslo",
"reset_people_visibility": "Obnovit viditelnost lidí",
@@ -1107,18 +1126,22 @@
"search": "Hledat",
"search_albums": "Vyhledávejte alba",
"search_by_context": "Vyhledávání podle obsahu",
"search_by_description": "Vyhledávat podle popisu",
"search_by_description_example": "Pěší turistika v Sapě",
"search_by_filename": "Vyhledávání podle názvu nebo přípony souboru",
"search_by_filename_example": "např. IMG_1234.JPG nebo PNG",
"search_camera_make": "Vyhledat výrobce fotoaparátu...",
"search_camera_model": "Vyhledat model fotoaparátu...",
"search_city": "Vyhledat město...",
"search_country": "Vyhledat zemi...",
"search_for": "Vyhledat",
"search_for_existing_person": "Vyhledat existující osobu",
"search_no_people": "Žádní lidé",
"search_no_people_named": "Žádní lidé se jménem \"{name}\"",
"search_options": "Možnosti vyhledávání",
"search_people": "Vyhledat lidi",
"search_places": "Vyhledat místa",
"search_rating": "Vyhledávání podle hodnocení...",
"search_settings": "Hledat nastavení",
"search_state": "Vyhledat stát...",
"search_tags": "Vyhledávat značky...",
@@ -1128,6 +1151,7 @@
"searching_locales": "Vyhledávání jazyků...",
"second": "Sekunda",
"see_all_people": "Zobrazit všechny lidi",
"select": "Vybrat",
"select_album_cover": "Vybrat obal alba",
"select_all": "Vybrat vše",
"select_all_duplicates": "Vybrat všechny duplicity",
@@ -1165,6 +1189,7 @@
"shared_from_partner": "Fotky od {partner}",
"shared_link_options": "Možnosti sdíleného odkazu",
"shared_links": "Sdílené odkazy",
"shared_links_description": "Sdílet fotky a videa pomocí odkazu",
"shared_photos_and_videos_count": "{assetCount, plural, one {# sdílená fotografie a video.} few {# sdílené fotografie a videa.} other {# sdílených fotografií a videí.}}",
"shared_with_partner": "Sdíleno s {partner}",
"sharing": "Sdílení",
@@ -1187,6 +1212,7 @@
"show_person_options": "Zobrazit možnosti osoby",
"show_progress_bar": "Zobrazit ukazatel průběhu",
"show_search_options": "Zobrazit možnosti vyhledávání",
"show_shared_links": "Zobrazit sdílené odkazy",
"show_slideshow_transition": "Zobrazit přechod prezentace",
"show_supporter_badge": "Odznak podporovatele",
"show_supporter_badge_description": "Zobrazit odznak podporovatele",
@@ -1240,6 +1266,7 @@
"tag_created": "Vytvořena značka: {tag}",
"tag_feature_description": "Procházení fotografií a videí seskupených podle témat logických značek",
"tag_not_found_question": "Nemůžete najít značku? <link>Vytvořte novou.</link>",
"tag_people": "Označit lidi",
"tag_updated": "Aktualizována značka: {tag}",
"tagged_assets": "Přiřazena značka {count, plural, one {# položce} other {# položkám}}",
"tags": "Značky",
@@ -1274,11 +1301,13 @@
"unfavorite": "Zrušit oblíbení",
"unhide_person": "Zrušit skrytí osoby",
"unknown": "Neznámý",
"unknown_country": "Neznámá země",
"unknown_year": "Neznámý rok",
"unlimited": "Neomezeně",
"unlink_motion_video": "Odpojit pohyblivé video",
"unlink_oauth": "Zrušit OAuth propojení",
"unlinked_oauth_account": "OAuth účet odpojen",
"unmute_memories": "Zrušit ztlumení vzpomínek",
"unnamed_album": "Nepojmenované album",
"unnamed_album_delete_confirmation": "Opravdu chcete toto album smazat?",
"unnamed_share": "Nepojmenované sdílení",
@@ -1332,6 +1361,7 @@
"view_all": "Zobrazit vše",
"view_all_users": "Zobrazit všechny uživatele",
"view_in_timeline": "Zobrazit na časové ose",
"view_link": "Zobrazit odkaz",
"view_links": "Zobrazit odkazy",
"view_name": "Zobrazit",
"view_next_asset": "Zobrazit další položku",
@@ -1344,7 +1374,7 @@
"welcome": "Vítejte",
"welcome_to_immich": "Vítejte v Immichi",
"year": "Rok",
"years_ago": "Před {years, plural, one {# rokem} other {# lety}}",
"years_ago": "Před {years, plural, one {rokem} other {# lety}}",
"yes": "Ano",
"you_dont_have_any_shared_links": "Nemáte žádné sdílené odkazy",
"zoom_image": "Zvětšit obrázek"

View File

@@ -20,7 +20,7 @@
"add_partner": "Мӑшӑр хуш",
"add_path": "Ҫулне хуш",
"add_photos": "Сӑнӳкерчӗксем хуш",
"add_to": "Мӗн те пулин хуш...",
"add_to": "Мӗн те пулин хуш",
"add_to_album": "Альбома хуш",
"add_to_shared_album": "Пӗрлехи альбома хуш",
"add_url": "URL хушӑр",

View File

@@ -7,7 +7,7 @@
"actions": "Handlinger",
"active": "Aktive",
"activity": "Aktivitet",
"activity_changed": "Aktivitet er {aktiveret, vælg, sandt {aktiveret} andet {deaktiveret}}",
"activity_changed": "Aktivitet er {enabled, select, true {aktiveret} other {deaktiveret}}",
"add": "Tilføj",
"add_a_description": "Tilføj en beskrivelse",
"add_a_location": "Tilføj en placering",
@@ -20,7 +20,7 @@
"add_partner": "Tilføj partner",
"add_path": "Tilføj sti",
"add_photos": "Tilføj billeder",
"add_to": "Tilføj til...",
"add_to": "Tilføj til",
"add_to_album": "Tilføj til album",
"add_to_shared_album": "Tilføj til delt album",
"add_url": "Tilføj URL",
@@ -41,6 +41,7 @@
"backup_settings": "Backup-indstillinger",
"backup_settings_description": "Administrer backupindstillinger for database",
"check_all": "Tjek Alle",
"cleanup": "Ryd op",
"cleared_jobs": "Ryddet jobs til: {job}",
"config_set_by_file": "konfigurationen er i øjeblikket indstillet af en konfigurations fil",
"confirm_delete_library": "Er du sikker på, at du vil slette {library} bibliotek?",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "Aktiver periodisk biblioteksscanning",
"library_settings": "Eksternt bibliotek",
"library_settings_description": "Administrer eksterne biblioteksindstillinger",
"library_tasks_description": "Udfør biblioteksopgaver",
"library_tasks_description": "Scan eksterne biblioteker for nye og/eller ændrede mediefiler",
"library_watching_enable_description": "Overvåg eksterne biblioteker for filændringer",
"library_watching_settings": "Biblioteks overvågning (EKSPERIMENTEL)",
"library_watching_settings_description": "Tjek automatisk for ændrede filer",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "Søg semantisk efter billeder ved hjælp af CLIP-indlejringer",
"machine_learning_smart_search_enabled": "Aktiver smart søgning",
"machine_learning_smart_search_enabled_description": "Hvis deaktiveret, vil billeder ikke blive kodet til smart søgning.",
"machine_learning_url_description": "URL maskinlæringsserveren. Hvis der er angivet mere end én URL, hver server vil blive forsøgt en ad gangen, indtil en reagerer med succes, i rækkefølge fra første til sidste.",
"machine_learning_url_description": "URLen for maskinlæringsserveren. Hvis mere end én URL angives, vil hver server blive forsøgt én ad gangen, indtil en svarer succesfuldt, i rækkefølge fra første til sidste. Servere, der ikke svarer, vil midlertidigt blive ignoreret, indtil de kommer online igen.",
"manage_concurrency": "Administrer antallet af samtidige opgaver",
"manage_log_settings": "Administrer logindstillinger",
"map_dark_style": "Mørk tema",
@@ -147,6 +148,8 @@
"map_settings": "Kort",
"map_settings_description": "Administrer kortindstillinger",
"map_style_description": "URL til en style.json for et korttema",
"memory_cleanup_job": "Mindeoprydning",
"memory_generate_job": "Mindegeneration",
"metadata_extraction_job": "Udtræk metadata",
"metadata_extraction_job_description": "Udtræk metadataoplysninger fra hvert Billede/Video, såsom GPS og opløsning",
"metadata_faces_import_setting": "Aktivér for at importere ansigter",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "Nulstil indstillingerne til standard",
"reset_settings_to_recent_saved": "Nulstil indstillinger til de senest gemte indstillinger",
"scanning_library": "Scanner bibliotek",
"search_jobs": "søg opgaver ..",
"search_jobs": "Søg opgaver",
"send_welcome_email": "Send velkomst-email",
"server_external_domain_settings": "Eksternt domæne",
"server_external_domain_settings_description": "Domæne til offentligt delte links, inklusiv http(s)://",
@@ -240,7 +243,7 @@
"storage_template_hash_verification_enabled_description": "Slår hash-verifikation til, slå ikke dette fra med mindre du er sikker på dets konsekvenser",
"storage_template_migration": "Lagringsskabelonmigration",
"storage_template_migration_description": "Anvend den nuværende <link>{template}</link> på tidligere uploadede mediefiler",
"storage_template_migration_info": "Skabelonændringer vil kun gælde for nye mediefiler. For at anvende skabelonen retroaktivt på tidligere uploadede mediefiler skal du køre <link>{job}</link>.",
"storage_template_migration_info": "Lager-skabelonen vil konvertere alle filendelser til små bogstaver. Skabelonændringer vil kun gælde for nye mediefiler. For at anvende skabelonen retroaktivt på tidligere uploadede mediefiler skal du køre <link>{job}</link>.",
"storage_template_migration_job": "Lager Skabelon Migreringsjob",
"storage_template_more_details": "For flere detaljer om denne funktion, referer til <template-link>Lager Skabelonen</template-link> og dens <implications-link>implikationer</implications-link>",
"storage_template_onboarding_description": "Når denne funktion er aktiveret, vil den automatisk organisere filer baseret på en brugerdefineret skabelon. På grund af stabilitetsproblemer er funktionen som standard slået fra. For mere information, se <link>dokumentation</link>.",
@@ -299,7 +302,7 @@
"transcoding_max_b_frames": "Maksimum B-frames",
"transcoding_max_b_frames_description": "Højere værdier forbedrer kompressionseffektivitet, men kan gøre indkodning langsommere. Er måske ikke kompatibelt med hardware-acceleration på ældre enheder. 0 slår B-frames fra, mens -1 sætter denne værdi automatisk.",
"transcoding_max_bitrate": "Maksimal bitrate",
"transcoding_max_bitrate_description": "At sætte en maksmimal bitrate kan gøre filstørrelserne mere forudsigelige med et lille tab i kvalitet. Ved 720p er almindelige værdier 2600k for VP9 eller HEVC, eller 4500K for H.264. Slået fra hvis sat til 0.",
"transcoding_max_bitrate_description": "At sætte en maksmimal bitrate kan gøre filstørrelserne mere forudsigelige med et lille tab i kvalitet. Ved 720p er almindelige værdier 2600 kbit/s for VP9 eller HEVC, eller 4500 kbit/s for H.264. Slået fra hvis sat til 0.",
"transcoding_max_keyframe_interval": "Maksimal keyframe-interval",
"transcoding_max_keyframe_interval_description": "Sætter den maksimale frameafstand mellem keyframes. Lavere værdier forringer kompressionseffektiviteten, men forbedrer søgetider og kan forbedre kvaliteten i scener med hurtig bevægelse. 0 sætter denne værdi automatisk.",
"transcoding_optimal_description": "Videoer højere end målopløsningen eller ikke i et godkendt format",
@@ -360,9 +363,9 @@
"admin_password": "Administratoradgangskode",
"administration": "Administration",
"advanced": "Avanceret",
"age_months": "Alder {months, plural, one {# month} other {# months}}",
"age_year_months": "Alder 1 år, {måneder, flertal, en {# måned} flere {# months}}",
"age_years": "{år, år, andre {Alder #}}",
"age_months": "Alder {months, plural, one {# måned} other {# måneder}}",
"age_year_months": "Alder 1 år, {months, plural, one {# måned} other {# måneder}}",
"age_years": "{years, plural, other {Alder #}}",
"album_added": "Album tilføjet",
"album_added_notification_setting_description": "Modtag en emailnotifikation når du bliver tilføjet til en delt album",
"album_cover_updated": "Albumcover opdateret",
@@ -391,6 +394,7 @@
"allow_edits": "Tillad redigeringer",
"allow_public_user_to_download": "Tillad offentlige brugere til at hente",
"allow_public_user_to_upload": "Tillad offentlige brugere til at uploade",
"alt_text_qr_code": "QR-kode billede",
"anti_clockwise": "Mod uret",
"api_key": "API-nøgle",
"api_key_description": "Denne værdi vises kun én gang. Venligst kopiér den før du lukker vinduet.",
@@ -402,33 +406,33 @@
"archive_or_unarchive_photo": "Arkivér eller dearkivér billede",
"archive_size": "Arkiv størelse",
"archive_size_description": "Konfigurer arkivstørrelsen for downloads (i GiB)",
"archived_count": "{antal, flertal, andet {Arkiveret #}}",
"archived_count": "{count, plural, other {Arkiveret #}}",
"are_these_the_same_person": "Er disse den samme person?",
"are_you_sure_to_do_this": "Er du sikker på, at du vil gøre det her?",
"asset_added_to_album": "Tilføjet til album",
"asset_adding_to_album": "Tilføjer til album...",
"asset_adding_to_album": "Tilføjer til album",
"asset_description_updated": "Mediefilsbeskrivelse er blevet opdateret",
"asset_filename_is_offline": "Mediefil {filename} er offline",
"asset_has_unassigned_faces": "Aktivet har ikke-tildelte ansigter",
"asset_hashing": "Hashing...",
"asset_hashing": "Hashing",
"asset_offline": "Mediefil offline",
"asset_offline_description": "Denne eksterne mediefil kan ikke længere findes på drevet. Kontakt venligst din Immich-administrator for hjælp.",
"asset_skipped": "Sprunget over",
"asset_skipped_in_trash": "I skraldespand",
"asset_uploaded": "Uploaded",
"asset_uploading": "Uploader...",
"asset_uploaded": "Uploadet",
"asset_uploading": "Uploader",
"assets": "elementer",
"assets_added_count": "Tilføjet {count, plural, one {# mediefil} other {# mediefiler}}",
"assets_added_to_album_count": "Tilføjet {count, plural, one {# mediefil} other {# mediefiler}} til albummet",
"assets_added_to_album_count": "{count, plural, one {# mediefil} other {# mediefiler}} tilføjet til albummet",
"assets_added_to_name_count": "Tilføjet {count, plural, one {# mediefil} other {# mediefiler}} til {hasName, select, true {<b>{name}</b>} other {nyt album}}",
"assets_count": "{count, plural, one {# mediefil} other {# mediefiler}}",
"assets_moved_to_trash_count": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til papirkurven",
"assets_permanently_deleted_count": "Slettet permanent {count, plural, one {# mediefil} other {# mediefiler}}",
"assets_permanently_deleted_count": "{count, plural, one {# mediefil} other {# mediefiler}} slettet permanent",
"assets_removed_count": "Fjernede {count, plural, one {# mediefil} other {# mediefiler}}",
"assets_restore_confirmation": "Er du sikker på, at du vil gendanne alle dine aktiver i papirkurven? Du kan ikke fortryde denne handling! Bemærk, at offline mediefiler ikke kan gendannes på denne måde.",
"assets_restored_count": "Gendannet {count, plural, one {# mediefil} other {# mediefiler}}",
"assets_trashed_count": "Smidt {count, plural, one {# mediefil} other {# mediefiler}} i papirkurven",
"assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} er allerede en del af albummet",
"assets_restore_confirmation": "Er du sikker på, at du vil gendanne alle dine mediafiler i papirkurven? Du kan ikke fortryde denne handling! Bemærk, at offline mediefiler ikke kan gendannes på denne måde.",
"assets_restored_count": "{count, plural, one {# mediefil} other {# mediefiler}} gendannet",
"assets_trashed_count": "{count, plural, one {# mediefil} other {# mediefiler}} smidt i papirkurven",
"assets_were_part_of_album_count": "mediefil{count, plural, one {mediefil} other {mediefiler}} er allerede en del af albummet",
"authorized_devices": "Tilladte enheder",
"back": "Tilbage",
"back_close_deselect": "Tilbage, luk eller fravælg",
@@ -441,7 +445,7 @@
"build_image": "Byggefil",
"bulk_delete_duplicates_confirmation": "Er du sikker på, at du vil slette alle {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil beholde den største fil i hver gruppe og slette alle dubletter. Denne handling kan ikke fortrydes!",
"bulk_keep_duplicates_confirmation": "Er du sikker på, at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil løse alle dubletgrupper uden at slette noget.",
"bulk_trash_duplicates_confirmation": "Er du sikker på, at du vil masseslette {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil beholde det største aktiv i hver gruppe og smide alle andre dubletter.",
"bulk_trash_duplicates_confirmation": "Er du sikker på, at du vil masseslette {count, plural, one {# duplikeret objekt} other {# duplikerede objekter}}? Dette vil beholde det største objekt i hver gruppe og slette alle andre dubletter.",
"buy": "Køb Immich",
"camera": "Kamera",
"camera_brand": "Kameramærke",
@@ -481,6 +485,7 @@
"comments_are_disabled": "Kommentarer er slået fra",
"confirm": "Bekræft",
"confirm_admin_password": "Bekræft administratoradgangskode",
"confirm_delete_face": "Er du sikker på, du vil slette {name}s ansigt fra denne mediefil?",
"confirm_delete_shared_link": "Er du sikker på, at du vil slette dette delte link?",
"confirm_keep_this_delete_others": "Alle andre aktiver i stakken vil blive slettet undtagen dette aktiv. Er du sikker på, at du vil fortsætte?",
"confirm_password": "Bekræft adgangskode",
@@ -504,7 +509,7 @@
"create_library": "Opret bibliotek",
"create_link": "Opret link",
"create_link_to_share": "Opret link for at dele",
"create_link_to_share_description": "Lad alle med linket se de(t) valgte billede(r)",
"create_link_to_share_description": "Tillad alle med linket at se de(t) valgte billede(r)",
"create_new_person": "Opret ny person",
"create_new_person_hint": "Tildel valgte aktiver til en ny person",
"create_new_user": "Opret ny bruger",
@@ -519,20 +524,21 @@
"date_after": "Dato efter",
"date_and_time": "Dato og klokkeslæt",
"date_before": "Dato før",
"date_of_birth_saved": "Fødselsdatoen blev gemt",
"date_of_birth_saved": "Fødselsdatoen blev gemt korrekt",
"date_range": "Datointerval",
"day": "Dag",
"deduplicate_all": "Dedupliker alle",
"deduplicate_all": "Kopier alle",
"deduplication_criteria_1": "Billedstørrelse i bytes",
"deduplication_criteria_2": "Antal EXIF-data",
"deduplication_info": "Deduplikerings info",
"deduplication_info_description": "For automatisk at forudvælge emner og fjerne dubletter i bulk ser vi på:",
"default_locale": "Standardlokalitet",
"default_locale_description": "Formatér datoer og tal",
"default_locale_description": "Formatér datoer og tal baseret på din browsers regions indstillinger",
"delete": "Slet",
"delete_album": "Slet album",
"delete_api_key_prompt": "Er du sikker på, at du vil slette denne API-nøgle?",
"delete_duplicates_confirmation": "Er du sikker på, at du vil slette disse dubletter permanent?",
"delete_face": "Slet ansigt",
"delete_key": "Slet nøgle",
"delete_library": "Slet bibliotek",
"delete_link": "Slet link",
@@ -565,7 +571,7 @@
"download_settings": "Download",
"download_settings_description": "Administrer indstillinger relateret til mediefil-downloads",
"downloading": "Downloader",
"downloading_asset_filename": "Downloader aktiv {filename}",
"downloading_asset_filename": "Downloader mediefil {filename}",
"drop_files_to_upload": "Slip filer hvor som helst for at uploade dem",
"duplicates": "Duplikater",
"duplicates_description": "Løs hver gruppe ved at angive, hvilke, hvis nogen, er dubletter",
@@ -595,11 +601,12 @@
"editor_crop_tool_h2_rotation": "Rotation",
"email": "E-mail",
"empty_trash": "Tøm papirkurv",
"empty_trash_confirmation": "Er du sikker på, at du vil tømme papirkurven? Dette vil fjerne alle aktiver i papirkurven permanent fra Immich.\nDu kan ikke fortryde denne handling!",
"empty_trash_confirmation": "Er du sikker på, at du vil tømme papirkurven? Dette vil fjerne alle objekter i papirkurven permanent fra Immich.\nDu kan ikke fortryde denne handling!",
"enable": "Aktivér",
"enabled": "Aktiveret",
"end_date": "Slutdato",
"error": "Fejl",
"error_delete_face": "Fejl ved sletning af ansigt fra mediefil",
"error_loading_image": "Fejl ved indlæsning af billede",
"error_title": "Fejl - Noget gik galt",
"errors": {
@@ -607,11 +614,11 @@
"cannot_navigate_previous_asset": "Kan ikke navigere til forrige mediefil",
"cant_apply_changes": "Ændringerne kan ikke anvendes",
"cant_change_activity": "Kan ikke {enabled, select, true {disable} other {enable}} aktivitet",
"cant_change_asset_favorite": "Kan ikke ændre favorit til aktiv",
"cant_change_metadata_assets_count": "Kan ikke ændre metadata for {count, plural, one {# asset} other {# assets}}",
"cant_change_asset_favorite": "Kan ikke ændre favorit til mediefil",
"cant_change_metadata_assets_count": "Kan ikke ændre metadata for {count, plural, one {# objekt} other {# objekter}}",
"cant_get_faces": "Kan ikke hente ansigter",
"cant_get_number_of_comments": "Kan ikke få antallet af kommentarer",
"cant_search_people": "Kan ikke søge efter folk",
"cant_search_people": "Kan ikke søge efter personer",
"cant_search_places": "Kan ikke søge efter steder",
"cleared_jobs": "Ryddede opgaver for: {job}",
"error_adding_assets_to_album": "Fejl i tilføjelse af mediefiler til album",
@@ -620,20 +627,20 @@
"error_downloading": "Fejl i download af {filename}",
"error_hiding_buy_button": "Fejl i skjulning af køb-knap",
"error_removing_assets_from_album": "Fejl i fjernelse af mediefiler fra album. Tjek konsol for flere detaljer",
"error_selecting_all_assets": "Fejl ved valg af alle aktiver",
"error_selecting_all_assets": "Fejl ved valg af alle mediefiler",
"exclusion_pattern_already_exists": "Denne udelukkelsesmønster findes allerede.",
"failed_job_command": "Kommando {command} slog fejl for opgave: {job}",
"failed_to_create_album": "Oprettelse af album mislykkedes",
"failed_to_create_shared_link": "Oprettelse af delt link mislykkedes",
"failed_to_edit_shared_link": "Redigering af delt link mislykkedes",
"failed_to_get_people": "Det lykkedes ikke at hente folk",
"failed_to_keep_this_delete_others": "Kunne ikke beholde dette aktiv og slette de andre aktiver",
"failed_to_get_people": "Det lykkedes ikke at hente personer",
"failed_to_keep_this_delete_others": "Kunne ikke beholde denne mediefil og slette de andre mediefiler",
"failed_to_load_asset": "Indlæsning af mediefil mislykkedes",
"failed_to_load_assets": "Indlæsning af mediefiler mislykkedes",
"failed_to_load_people": "Indlæsning af personer mislykkedes",
"failed_to_remove_product_key": "Fjernelse af produktnøgle mislykkedes",
"failed_to_stack_assets": "Det lykkedes ikke at stable aktiver",
"failed_to_unstack_assets": "Det lykkedes ikke at fjerne stablen af aktiver",
"failed_to_stack_assets": "Det lykkedes ikke at stable mediefiler",
"failed_to_unstack_assets": "Det lykkedes ikke at fjerne gruperingen af mediefiler",
"import_path_already_exists": "Denne importsti findes allerede.",
"incorrect_email_or_password": "Forkert email eller kodeord",
"paths_validation_failed": "{paths, plural, one {# sti} other {# stier}} slog fejl ved validering",
@@ -641,17 +648,17 @@
"quota_higher_than_disk_size": "Du har sat en kvote der er større end disken",
"repair_unable_to_check_items": "Kunne ikke tjekke {count, select, one {element} other {elementer}}",
"unable_to_add_album_users": "Ikke i stand til at tilføje brugere til album",
"unable_to_add_assets_to_shared_link": "Kan ikke tilføje aktiver til delt link",
"unable_to_add_assets_to_shared_link": "Kan ikke tilføje mediefiler til det delte link",
"unable_to_add_comment": "Ikke i stand til at tilføje kommentar",
"unable_to_add_exclusion_pattern": "Kunne ikke tilføje udelukkelsesmønster",
"unable_to_add_import_path": "Kunne ikke tilføje importsti",
"unable_to_add_partners": "Ikke i stand til at tilføje partnere",
"unable_to_add_remove_archive": "Kan Ikke {archived, select, true {fjerne aktiv fra} other {tilføje aktiv til}} Arkiv",
"unable_to_add_remove_favorites": "Kan ikke {favorite, select, true {tilføje aktiv til} other {fjerne aktiv fra}} favoritter",
"unable_to_archive_unarchive": "Ude af stand til at {arkiveret, vælg, sand {arkiv} andet {arkiv}}",
"unable_to_archive_unarchive": "Ude af stand til at {archived, select, true {arkivere} other {fjerne fra arkiv}}",
"unable_to_change_album_user_role": "Ikke i stand til at ændre albumbrugerens rolle",
"unable_to_change_date": "Ikke i stand til at ændre dato",
"unable_to_change_favorite": "Kan ikke ændre favorit for aktiv",
"unable_to_change_favorite": "Kan ikke ændre favorit for mediefil",
"unable_to_change_location": "Ikke i stand til at ændre sted",
"unable_to_change_password": "Kunne ikke ændre adgangskode",
"unable_to_change_visibility": "Kan ikke ændre synligheden for {count, plural, one {# person} other {# personer}}",
@@ -689,8 +696,8 @@
"unable_to_log_out_device": "Enheden kunne ikke logges af",
"unable_to_login_with_oauth": "Kan ikke logge på med OAuth",
"unable_to_play_video": "Ikke i stand til at afspille video",
"unable_to_reassign_assets_existing_person": "Kan ikke gentildele aktiver til {navn, vælg, null {en eksisterende person} anden {{name}}}",
"unable_to_reassign_assets_new_person": "Kan ikke omfordele aktiver til en ny person",
"unable_to_reassign_assets_existing_person": "Kunne ikke tildele mediafiler til {name, select, null {en eksisterende person} other {{name}}}",
"unable_to_reassign_assets_new_person": "Kan ikke omfordele objekter til en ny person",
"unable_to_refresh_user": "Ikke i stand til at genopfriske bruger",
"unable_to_remove_album_users": "Ikke i stand til at fjerne brugere fra album",
"unable_to_remove_api_key": "Kunne ikke fjerne API-nøgle",
@@ -698,16 +705,16 @@
"unable_to_remove_deleted_assets": "Kunne ikke fjerne offlinefiler",
"unable_to_remove_library": "Ikke i stand til at fjerne bibliotek",
"unable_to_remove_partner": "Ikke i stand til at fjerne partner",
"unable_to_remove_reaction": "Ikke i stand til at reaktion",
"unable_to_remove_reaction": "Ikke i stand til at fjerne reaktion",
"unable_to_repair_items": "Ikke i stand til at reparere ting",
"unable_to_reset_password": "Ikke i stand til at nulstille adgangskode",
"unable_to_resolve_duplicate": "Kunne ikke opklare duplikat",
"unable_to_restore_assets": "Kunne ikke genoprette medier",
"unable_to_restore_trash": "Ikke i stand til at genoprette skrald",
"unable_to_restore_user": "Ikke i stand til at genoprette bruger",
"unable_to_restore_assets": "Kunne ikke gendanne medierfil",
"unable_to_restore_trash": "Ikke i stand til at gendanne fra skraldespanden",
"unable_to_restore_user": "Ikke i stand til at gendanne bruger",
"unable_to_save_album": "Ikke i stand til at gemme album",
"unable_to_save_api_key": "Kunne ikke gemme API-nøgle",
"unable_to_save_date_of_birth": "Kan ikke gemme fødselsdatoen",
"unable_to_save_date_of_birth": "Kunne ikke gemme fødselsdatoen",
"unable_to_save_name": "Ikke i stand til at gemme navn",
"unable_to_save_profile": "Ikke i stand til at gemme profil",
"unable_to_save_settings": "Ikke i stand til at gemme indstillinger",
@@ -720,7 +727,7 @@
"unable_to_unlink_account": "Ikke i stand til at frakoble konto",
"unable_to_unlink_motion_video": "Kunne ikke fjerne linket til bevægelsesvideo",
"unable_to_update_album_cover": "Albumomslaget kunne ikke opdateres",
"unable_to_update_album_info": "Albumoplysningerne kunne ikke opdateres",
"unable_to_update_album_info": "Albumsoplysningerne kunne ikke opdateres",
"unable_to_update_library": "Ikke i stand til at opdatere bibliotek",
"unable_to_update_location": "Ikke i stand til at opdatere sted",
"unable_to_update_settings": "Ikke i stand til at opdatere indstillinger",
@@ -729,7 +736,7 @@
"unable_to_upload_file": "Filen kunne ikke uploades"
},
"exif": "Exif",
"exit_slideshow": "Forlad slideshow",
"exit_slideshow": "Afslut slideshow",
"expand_all": "Udvid alle",
"expire_after": "Udløb efter",
"expired": "Udløbet",
@@ -742,7 +749,7 @@
"external": "Ekstern",
"external_libraries": "Eksterne biblioteker",
"face_unassigned": "Ikke tildelt",
"failed_to_load_assets": "Kunne ikke indlæse aktiver",
"failed_to_load_assets": "Kunne ikke indlæse mediefiler",
"favorite": "Favorit",
"favorite_or_unfavorite_photo": "Tilføj eller fjern fra yndlingsbilleder",
"favorites": "Favoritter",
@@ -766,16 +773,18 @@
"go_to_folder": "Gå til mappe",
"go_to_search": "Gå til søgning",
"group_albums_by": "Gruppér albummer efter...",
"group_country": "Gruppér efter land",
"group_no": "Ingen gruppering",
"group_owner": "Grupper efter ejer",
"group_places_by": "Gruppér steder efter...",
"group_year": "Grupper efter år",
"has_quota": "Har kvote",
"hi_user": "Hej {name} ({email})",
"hide_all_people": "Skjul alle personer",
"hide_gallery": "Gem galleri",
"hide_gallery": "Skjul galleri",
"hide_named_person": "Skjul person {name}",
"hide_password": "Gem adgangskode",
"hide_person": "Gem person",
"hide_password": "Skjul adgangskode",
"hide_person": "Skjul person",
"hide_unnamed_people": "Skjul unavngivne personer",
"host": "Host",
"hour": "Time",
@@ -800,6 +809,7 @@
"include_shared_albums": "Inkludér delte albummer",
"include_shared_partner_assets": "Inkludér delte partnermedier",
"individual_share": "Individuel andel",
"individual_shares": "Individuelle delinger",
"info": "Info",
"interval": {
"day_at_onepm": "Hver dag kl. 13",
@@ -809,12 +819,12 @@
},
"invite_people": "Inviter personer",
"invite_to_album": "Inviter til album",
"items_count": "{count, plural, one {# genstand} other {# genstande}}",
"items_count": "{count, plural, one {# element} other {# elementer}}",
"jobs": "Opgaver",
"keep": "Behold",
"keep_all": "Behold alle",
"keep_this_delete_others": "Behold dette, slet andre",
"kept_this_deleted_others": "Beholdt dette aktiv og slettede {count, plural, one {# aktiv} other {# aktiver}}",
"kept_this_deleted_others": "Beholdt denne mediefil og slettede {count, plural, one {# aktiv} other {# aktiver}}",
"keyboard_shortcuts": "Tastaturgenveje",
"language": "Sprog",
"language_setting_description": "Vælg dit foretrukne sprog",
@@ -822,19 +832,20 @@
"latest_version": "Seneste version",
"latitude": "Breddegrad",
"leave": "Forlad",
"lens_model": "Objektivmodel",
"let_others_respond": "Lad andre svare",
"level": "Niveau",
"library": "Bibliotek",
"library_options": "Biblioteksindstillinger",
"light": "Lys",
"like_deleted": "Ligesom slettet",
"link_motion_video": "Link bevægelses video",
"link_motion_video": "Link bevægelsesvideo",
"link_options": "Link-indstillinger",
"link_to_oauth": "Link til OAuth",
"linked_oauth_account": "Tilsluttet OAuth-konto",
"list": "Liste",
"loading": "Loader",
"loading_search_results_failed": "At loade søgeresultater slog fejl",
"loading": "Indlæser",
"loading_search_results_failed": "Indlæsning af søgeresultater fejlede",
"log_out": "Log ud",
"log_out_all_devices": "Log ud af alle enheder",
"logged_out_all_devices": "Logget ud af alle enheder",
@@ -864,15 +875,15 @@
"media_type": "Medietype",
"memories": "Minder",
"memories_setting_description": "Administrér hvad du ser i dine minder",
"memory": "Hukommelse",
"memory": "Minde",
"memory_lane_title": "Minder {title}",
"menu": "Menu",
"merge": "Sammenflet",
"merge_people": "Sammenflet personer",
"merge_people_limit": "Du kan kun flette op til 5 ansigter ad gangen",
"merge_people_prompt": "Vil du slå disse mennesker sammen? Denne handling er uigenkaldelig.",
"merge_people_prompt": "Vil du flette disse mennesker sammen? Denne handling er uigenkaldelig.",
"merge_people_successfully": "Personer sammenflettet med succes",
"merged_people_count": "Slået sammen {count, plural, one {# person} other {# people}}",
"merged_people_count": "{count, plural, one {# person} other {# personer}} lagt sammen",
"minimize": "Minimér",
"minute": "Minut",
"missing": "Mangler",
@@ -880,6 +891,7 @@
"month": "Måned",
"more": "Mere",
"moved_to_trash": "Flyttet til skraldespand",
"mute_memories": "Dæmp minder",
"my_albums": "Mine albummer",
"name": "Navn",
"name_or_nickname": "Navn eller kælenavn",
@@ -897,7 +909,7 @@
"no_albums_message": "Opret et album for at organisere dine billeder og videoer",
"no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.",
"no_albums_yet": "Det ser ud til, at du ikke har nogen album endnu.",
"no_archived_assets_message": "Arkivér billeder og fotos for at gemme dem væk fra dit Billed-view",
"no_archived_assets_message": "Arkivér billeder og videoer for at gemme dem væk fra din Billede oversigt",
"no_assets_message": "KLIK FOR AT UPLOADE DIT FØRSTE BILLEDE",
"no_duplicates_found": "Ingen duplikater fundet.",
"no_exif_info_available": "Ingen tilgængelig exif information",
@@ -923,9 +935,9 @@
"offline_paths_description": "Disse resultater kan være på grund af manuel sletning af filer, som ikke er en del af et eksternt bibliotek.",
"ok": "Ok",
"oldest_first": "Ældste først",
"onboarding": "Onboarding",
"onboarding": "Introduktion",
"onboarding_privacy_description": "Følgende (valgfrie) funktioner er afhængige af eksterne tjenester, og kan til enhver tid deaktiveres i administrationsindstillingerne.",
"onboarding_theme_description": "Vælg et farvetema til din forekomst. Du kan ændre dette senere i dine indstillinger.",
"onboarding_theme_description": "Vælg et farvetema til din instans. Du kan ændre dette senere i dine indstillinger.",
"onboarding_welcome_description": "Lad os få din instans sat op med nogle almindelige indstillinger.",
"onboarding_welcome_user": "Velkommen, {user}",
"online": "Online",
@@ -940,11 +952,11 @@
"other": "Andet",
"other_devices": "Andre enheder",
"other_variables": "Andre variable",
"owned": "Ejet",
"owned": "Egne",
"owner": "Ejer",
"partner": "Partner",
"partner_can_access": "{partner} kan tilgå",
"partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkiveret og Slettet",
"partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkivet og Slettet",
"partner_can_access_location": "Stedet, hvor dine billeder blev taget",
"partner_sharing": "Partnerdeling",
"partners": "Partnere",
@@ -973,8 +985,9 @@
"permanently_delete_assets_count": "Slet permanent {count, plural, one {asset} other {assets}}",
"permanently_delete_assets_prompt": "Er du sikker på, at du permanent vil slette {count, plural, one {dette aktiv?} other {disse <b>#</b> aktiver?}} Dette vil også fjerne {count, plural, one {det fra dets} other {dem fra deres}} album(er).",
"permanently_deleted_asset": "Permanent slettet medie",
"permanently_deleted_assets_count": "Slettet permanent {count, plural, one {# aktiv} other {# aktiver}}",
"permanently_deleted_assets_count": "{count, plural, one {# aktiv} other {# aktiver}} permanent slettet",
"person": "Person",
"person_birthdate": "Født den {date}",
"person_hidden": "{name}{hidden, select, true { (skjult)} other {}}",
"photo_shared_all_users": "Det ser ud til, at du har delt dine billeder med alle brugere, eller også har du ikke nogen bruger at dele med.",
"photos": "Billeder",
@@ -984,10 +997,11 @@
"pick_a_location": "Vælg et sted",
"place": "Sted",
"places": "Steder",
"places_count": "{count, plural, one {{count, number} Sted} other {{count, number} Steder}}",
"play": "Afspil",
"play_memories": "Afspil minder",
"play_motion_photo": "Afspil bevægelsesbillede",
"play_or_pause_video": "Afspil eller paus video",
"play_or_pause_video": "Afspil eller pause video",
"port": "Port",
"preset": "Forudindstilling",
"preview": "Forhåndsvisning",
@@ -1018,11 +1032,11 @@
"purchase_input_suggestion": "Har du en produktnøgle? Indtast nøglen nedenfor",
"purchase_license_subtitle": "Køb Immich for at understøtte den fortsatte udvikling af tjenesten",
"purchase_lifetime_description": "Livsvarigt køb",
"purchase_option_title": "KØBEMULIGHEDER",
"purchase_option_title": "KØBSMULIGHEDER",
"purchase_panel_info_1": "At bygge Immich tager meget tid og kræfter, og vi har fuldtidsingeniører, der arbejder på det for at gøre det så godt, som vi overhovedet kan. Vores mission er, at open source-software og etisk forretningspraksis bliver en bæredygtig indtægtskilde for udviklere og at skabe et privatlivsrespekterende økosystem med reelle alternativer til udnyttende cloud-tjenester.",
"purchase_panel_info_2": "Da vi er forpligtet til ikke at tilføje betalingsvægge, vil dette køb ikke give dig yderligere funktioner i Immich. Vi er afhængige af, at brugere som dig støtter Immichs løbende udvikling.",
"purchase_panel_title": "Støt projektet",
"purchase_per_server": "Per server",
"purchase_per_server": "Pr. server",
"purchase_per_user": "Per bruger",
"purchase_remove_product_key": "Fjern produktnøgle",
"purchase_remove_product_key_prompt": "Er du sikker på, at du vil fjerne produktnøglen?",
@@ -1039,9 +1053,9 @@
"reaction_options": "Reaktionsindstillinger",
"read_changelog": "Læs ændringslog",
"reassign": "Gentildel",
"reassigned_assets_to_existing_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til {name, select, null {en eksisterende person} other {{navne}}}",
"reassigned_assets_to_existing_person": "{count, plural, one {# mediefil} other {# mediefiler}} er blevet gentildelt til {name, select, null {en eksisterende person} other {{name}}}",
"reassigned_assets_to_new_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til en ny person",
"reassing_hint": "Tildel valgte aktiver til en eksisterende person",
"reassing_hint": "Tildel valgte mediefiler til en eksisterende person",
"recent": "For nylig",
"recent-albums": "Seneste albums",
"recent_searches": "Seneste søgninger",
@@ -1059,68 +1073,77 @@
"remove": "Fjern",
"remove_assets_album_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra albummet?",
"remove_assets_shared_link_confirmation": "Er du sikker på, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra dette delte link?",
"remove_assets_title": "Fjern aktiver?",
"remove_assets_title": "Fjern mediefiler?",
"remove_custom_date_range": "Fjern tilpasset datointerval",
"remove_deleted_assets": "Fjern fra offlinefiler",
"remove_deleted_assets": "Fjern slettede mediefiler",
"remove_from_album": "Fjern fra album",
"remove_from_favorites": "Fjern fra favoritter",
"remove_from_shared_link": "Fjern fra delt link",
"remove_memory": "Fjern minde",
"remove_photo_from_memory": "Fjern foto fra dette minde",
"remove_url": "Fjern URL",
"remove_user": "Fjern bruger",
"removed_api_key": "Fjernede API-nøgle: {name}",
"removed_from_archive": "Fjernet fra arkiv",
"removed_from_favorites": "Fjernet fra favoritter",
"removed_from_favorites_count": "{count, plural, other {Fjernede #}} fra favoritter",
"removed_memory": "Fjernede minde",
"removed_photo_from_memory": "Fjernede foto fra minde",
"removed_tagged_assets": "Fjernede tag fra {count, plural, one {# aktiv} other {# aktiver}}",
"rename": "Omdøb",
"repair": "Reparér",
"repair_no_results_message": "Utrackede og manglende filer vil blive vist her",
"repair_no_results_message": "Usporede og manglende filer vil blive vist her",
"replace_with_upload": "Erstat med upload",
"repository": "Depot",
"require_password": "Kræv adgangskode",
"require_user_to_change_password_on_first_login": "Kræv at bruger skifter adgangskode ved første login",
"rescan": "Genopfrisk",
"reset": "Nulstil",
"reset_password": "Nulstil adgangskode",
"reset_people_visibility": "Nulstil personsynlighed",
"reset_to_default": "Nulstil til standard",
"resolve_duplicates": "Løs dubletter",
"resolved_all_duplicates": "Løste alle dubletter",
"resolved_all_duplicates": "Alle dubletter løst",
"restore": "Gendan",
"restore_all": "Gendan alle",
"restore_user": "Gendan bruger",
"restored_asset": "Gendannet aktiv",
"restored_asset": "Gendannet mediefilen",
"resume": "Genoptag",
"retry_upload": "Forsøg upload igen",
"review_duplicates": "Gennemgå dubletter",
"role": "Rolle",
"role_editor": "Editor",
"role_editor": "Redaktør",
"role_viewer": "Seer",
"save": "Gem",
"saved_api_key": "Gemt API-nøgle",
"saved_profile": "Gemte profil",
"saved_settings": "Gemte indstillinger",
"say_something": "Skriv noget",
"scan_all_libraries": "Skan gennem alle biblioteker",
"scan_all_libraries": "Skan alle biblioteker",
"scan_library": "Skan",
"scan_settings": "Skanningsindstillinger",
"scanning_for_album": "Skanner efter albummer...",
"search": "Søg",
"search_albums": "Søg i albummer",
"search_by_context": "Søg efter kontekst",
"search_by_description": "Søg efter beskrivelse",
"search_by_description_example": "Vandredag i Paris",
"search_by_filename": "Søg efter filnavn eller filtypenavn",
"search_by_filename_example": "dvs. IMG_1234.JPG eller PNG",
"search_camera_make": "Søg efter kameraproducent...",
"search_camera_model": "Søg efter kameramodel...",
"search_city": "Søg efter by...",
"search_country": "Søg efter land...",
"search_for": "Søg efter",
"search_for_existing_person": "Søg efter eksisterende person",
"search_no_people": "Ingen personer",
"search_no_people_named": "Ingen personer med navnet \"{name}\"",
"search_options": "Søgemuligheder",
"search_people": "Søg i personer",
"search_places": "Søg i steder",
"search_rating": "Søg efter vurdering...",
"search_settings": "søgeindstillinger",
"search_state": "Søg efter stat...",
"search_state": "Søg efter lansdel...",
"search_tags": "Søg tags...",
"search_timezone": "Søg i tidszone...",
"search_type": "Søg efter type",
@@ -1128,6 +1151,7 @@
"searching_locales": "Søger lokaler...",
"second": "Sekund",
"see_all_people": "Se alle personer",
"select": "Vælg",
"select_album_cover": "Vælg albumcover",
"select_all": "Vælg alle",
"select_all_duplicates": "Vælg alle dubletter",
@@ -1141,7 +1165,7 @@
"select_photos": "Vælg billeder",
"select_trash_all": "Vælg smid alle ud",
"selected": "Valgt",
"selected_count": "{count, plural, other {# valgt}}",
"selected_count": "{count, plural, one {# valgt} other {# valgte}}",
"send_message": "Send besked",
"send_welcome_email": "Send velkomstemail",
"server_offline": "Server Offline",
@@ -1165,6 +1189,7 @@
"shared_from_partner": "Billeder fra {partner}",
"shared_link_options": "Muligheder for delt link",
"shared_links": "Delte links",
"shared_links_description": "Del billeder og videoer med et link",
"shared_photos_and_videos_count": "{assetCount, plural, other {# delte billeder & videoer.}}",
"shared_with_partner": "Delt med {partner}",
"sharing": "Delte",
@@ -1174,19 +1199,20 @@
"show_album_options": "Vis albumindstillinger",
"show_albums": "Vis albummer",
"show_all_people": "Vis alle personer",
"show_and_hide_people": "Vis & gem personer",
"show_and_hide_people": "Vis & skjul personer",
"show_file_location": "Vis filplacering",
"show_gallery": "Vis galleri",
"show_hidden_people": "Vis gemte personer",
"show_hidden_people": "Vis skjulte personer",
"show_in_timeline": "Vis på tidslinje",
"show_in_timeline_setting_description": "Vis billeder og videoer fra denne bruger på din tidslinje",
"show_keyboard_shortcuts": "Vis tastaturgenveje",
"show_metadata": "Vis metadata",
"show_or_hide_info": "Vis eller gem info",
"show_or_hide_info": "Vis eller skjul info",
"show_password": "Vis adgangskode",
"show_person_options": "Vis personindstillinger",
"show_progress_bar": "Vis statuslinje",
"show_search_options": "Vis søgeindstillinger",
"show_shared_links": "Vis delte links",
"show_slideshow_transition": "Vis overgang til diasshow",
"show_supporter_badge": "Supportermærke",
"show_supporter_badge_description": "Vis et supportermærke",
@@ -1206,7 +1232,7 @@
"sort_items": "Antal genstande",
"sort_modified": "Ændret dato",
"sort_oldest": "Ældste foto",
"sort_people_by_similarity": "Sorter folk efter lighed",
"sort_people_by_similarity": "Sorter efter personer der ligner hinanden",
"sort_recent": "Seneste foto",
"sort_title": "Titel",
"source": "Kilde",
@@ -1232,14 +1258,15 @@
"sunrise_on_the_beach": "Solopgang på stranden",
"support": "Support",
"support_and_feedback": "Support & Feedback",
"support_third_party_description": "Din Immich-installation blev pakket af en tredjepart. Problemer, du oplever, kan være forårsaget af denne pakke, så rejs venligst problemer med dem i første omgang ved at bruge nedenstående links.",
"support_third_party_description": "Din Immich-installation blev sammensat af en tredjepart. Problemer, du oplever, kan være forårsaget af denne udvikler, så rejs venligst problemer med dem i første omgang ved at bruge nedenstående links.",
"swap_merge_direction": "Byt retning for sammenfletning",
"sync": "Synkronisér",
"tag": "Tag",
"tag_assets": "Tag aktiver",
"tag_assets": "Tag mediefiler",
"tag_created": "Oprettet tag: {tag}",
"tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner",
"tag_not_found_question": "Kan du ikke finde et tag? <link>Opret et nyt tag.</link>",
"tag_people": "Tag personer",
"tag_updated": "Opdateret tag: {tag}",
"tagged_assets": "Tagget {count, plural, one {# aktiv} other {# aktiver}}",
"tags": "Tags",
@@ -1264,21 +1291,23 @@
"total_usage": "Samlet forbrug",
"trash": "Papirkurv",
"trash_all": "Smid alle ud",
"trash_count": "Skrald {count, number}",
"trash_delete_asset": "Papirkurv/slet aktiv",
"trash_no_results_message": "Udsmidte billeder og videoer vil kunne findes her.",
"trash_count": "Slet {count, number}",
"trash_delete_asset": "Flyt mediefil til Papirkurv",
"trash_no_results_message": "Billeder og videoer markeret til sletning vil blive vist her.",
"trashed_items_will_be_permanently_deleted_after": "Mediefiler i skraldespanden vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.",
"type": "Type",
"unarchive": "Afakivér",
"unarchived_count": "{count, plural, other {Uarkiveret #}}",
"unfavorite": "Fjern favorit",
"unhide_person": "Hold op med at gemme person væk",
"unhide_person": "Stop med at skjule person",
"unknown": "Ukendt",
"unknown_country": "Ukendt land",
"unknown_year": "Ukendt år",
"unlimited": "Ubegrænset",
"unlink_motion_video": "Fjern link til bevægelsesvideo",
"unlink_oauth": "Frakobl OAuth",
"unlinked_oauth_account": "Frakoblede OAuth-konto",
"unmute_memories": "Ophæv dæmpning af minder",
"unnamed_album": "Unavngivet album",
"unnamed_album_delete_confirmation": "Er du sikker på, at du vil slette dette album?",
"unnamed_share": "Unavngivet deling",
@@ -1287,18 +1316,18 @@
"unselect_all_duplicates": "Fjern markeringen af alle dubletter",
"unstack": "Fjern fra stak",
"unstacked_assets_count": "Ikke-stablet {count, plural, one {# aktiv} other {# aktiver}}",
"untracked_files": "Usporede filer",
"untracked_files": "Ikke overvågede filer",
"untracked_files_decription": "Disse filer bliver ikke sporet af applikationen. De kan være resultatet af mislykkede flytninger, afbrudte uploads eller efterladt på grund af en fejl",
"up_next": "Næste",
"updated_password": "Opdaterede adgangskode",
"upload": "Upload",
"upload_concurrency": "Uploadsamtidighed",
"upload_concurrency": "Upload samtidighed",
"upload_errors": "Upload afsluttet med {count, plural, one {# fejl} other {# fejl}}. Opdater siden for at se nye uploadaktiver.",
"upload_progress": "Resterende {remaining, number} - Behandlet {processed, number}/{total, number}",
"upload_skipped_duplicates": "Sprang over {count, plural, one {# duplet aktiv} other {# duplikerede aktiver}}",
"upload_status_duplicates": "Dubletter",
"upload_status_errors": "Fejl",
"upload_status_uploaded": "Uploaded",
"upload_status_uploaded": "Uploadet",
"upload_success": "Upload gennemført. Opdater siden for at se nye uploadaktiver.",
"url": "URL",
"usage": "Forbrug",
@@ -1310,7 +1339,7 @@
"user_purchase_settings_description": "Administrer dit køb",
"user_role_set": "Indstil {user} som {role}",
"user_usage_detail": "Detaljer om brugers forbrug",
"user_usage_stats": "Konto anvendelsesstatistik",
"user_usage_stats": "Kontoens anvendelsesstatistik",
"user_usage_stats_description": "Vis konto anvendelsesstatistik",
"username": "Brugernavn",
"users": "Brugere",
@@ -1332,6 +1361,7 @@
"view_all": "Se alle",
"view_all_users": "Se alle brugere",
"view_in_timeline": "Se på tidslinjen",
"view_link": "Vis Link",
"view_links": "Vis links",
"view_name": "Se",
"view_next_asset": "Se næste medie",

View File

@@ -20,7 +20,7 @@
"add_partner": "Partner hinzufügen",
"add_path": "Pfad hinzufügen",
"add_photos": "Fotos hinzufügen",
"add_to": "Hinzufügen zu ...",
"add_to": "Hinzufügen zu ",
"add_to_album": "Zu Album hinzufügen",
"add_to_shared_album": "Zu geteiltem Album hinzufügen",
"add_url": "URL hinzufügen",
@@ -31,7 +31,7 @@
"add_exclusion_pattern_description": "Ausschlussmuster hinzufügen. Platzhalter, wie *, **, und ? werden unterstützt. Um alle Dateien in einem Verzeichnis namens „Raw\" zu ignorieren, „**/Raw/**“ verwenden. Um alle Dateien zu ignorieren, die auf „.tif“ enden, „**/*.tif“ verwenden. Um einen absoluten Pfad zu ignorieren, „/pfad/zum/ignorieren/**“ verwenden.",
"asset_offline_description": "Diese Datei einer externen Bibliothek befindet sich nicht mehr auf der Festplatte und wurde in den Papierkorb verschoben. Falls die Datei innerhalb der Bibliothek verschoben wurde, überprüfe deine Zeitleiste auf die neue entsprechende Datei. Um diese Datei wiederherzustellen, stelle bitte sicher, dass Immich auf den unten stehenden Dateipfad zugreifen kann und scanne die Bibliothek.",
"authentication_settings": "Authentifizierungseinstellungen",
"authentication_settings_description": "Passwort-, OAuth- und sonstigen Authentifizierungseinstellungen verwalten",
"authentication_settings_description": "Passwort-, OAuth- und sonstige Authentifizierungseinstellungen verwalten",
"authentication_settings_disable_all": "Bist du sicher, dass du alle Anmeldemethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.",
"authentication_settings_reenable": "Nutze einen <link>Server-Befehl</link> zur Reaktivierung.",
"background_task_job": "Hintergrundaufgaben",
@@ -41,6 +41,7 @@
"backup_settings": "Datensicherungs-Einstellungen",
"backup_settings_description": "Datensicherungs-Einstellungen verwalten",
"check_all": "Alle überprüfen",
"cleanup": "Aufräumen",
"cleared_jobs": "Folgende Aufgaben zurückgesetzt: {job}",
"config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt",
"confirm_delete_library": "Bist du sicher, dass du die Bibliothek {library} löschen willst?",
@@ -96,7 +97,7 @@
"library_scanning_enable_description": "Regelmäßiges Scannen der Bibliothek aktivieren",
"library_settings": "Externe Bibliothek",
"library_settings_description": "Einstellungen externer Bibliotheken verwalten",
"library_tasks_description": "Diese Aufgabe aktualisiert und überprüft die Bibliotheken",
"library_tasks_description": "Überprüfe externe Bibliotheken auf neue oder veränderte Medien",
"library_watching_enable_description": "Überwache externe Bibliotheken auf Dateiänderungen",
"library_watching_settings": "Bibliotheksüberwachung (EXPERIMENTELL)",
"library_watching_settings_description": "Automatisch auf geänderte Dateien prüfen",
@@ -131,7 +132,7 @@
"machine_learning_smart_search_description": "Semantische Bildsuche mittels CLIP-Einbettungen",
"machine_learning_smart_search_enabled": "Intelligente Suche aktivieren",
"machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht für die intelligente Suche verwendet.",
"machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten.",
"machine_learning_url_description": "Die URL des Servers für maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfügbar sind.",
"manage_concurrency": "Gleichzeitige Ausführungen verwalten",
"manage_log_settings": "Log-Einstellungen verwalten",
"map_dark_style": "Dunkler Stil",
@@ -147,6 +148,8 @@
"map_settings": "Karte",
"map_settings_description": "Karten- und GPS-Einstellungen verwalten",
"map_style_description": "URL zu einem style.json Karten-Theme",
"memory_cleanup_job": "Erinnerungen aufräumen",
"memory_generate_job": "Erinnerungen Generierung",
"metadata_extraction_job": "Metadaten extrahieren",
"metadata_extraction_job_description": "Extrahieren von Metadaten, wie zum Beispiel GPS, Gesichtern und Auflösung aus jeder Datei",
"metadata_faces_import_setting": "Import von Gesichtern aktivieren",
@@ -187,7 +190,7 @@
"oauth_issuer_url": "Aussteller-URL",
"oauth_mobile_redirect_uri": "Mobile Umleitungs-URI",
"oauth_mobile_redirect_uri_override": "Mobile Umleitungs-URI überschreiben",
"oauth_mobile_redirect_uri_override_description": "Einschalten, wenn der OAuth-Provider keine mobile URI wie '{callback}' erlaubt",
"oauth_mobile_redirect_uri_override_description": "Einschalten, wenn der OAuth-Anbieter keine mobile URI wie '{callback}' erlaubt",
"oauth_profile_signing_algorithm": "Algorithmus zur Profilsignierung",
"oauth_profile_signing_algorithm_description": "Dieser Algorithmus wird für die Signatur des Benutzerprofils verwendet.",
"oauth_scope": "Umfang",
@@ -219,7 +222,7 @@
"reset_settings_to_default": "Einstellungen auf Standard zurücksetzen",
"reset_settings_to_recent_saved": "Einstellungen auf die zuletzt gespeicherten Einstellungen zurücksetzen",
"scanning_library": "Bibliothek scannen",
"search_jobs": "Aufgaben suchen...",
"search_jobs": "Suchaufgaben",
"send_welcome_email": "Begrüssungsmail senden",
"server_external_domain_settings": "Externe Domain",
"server_external_domain_settings_description": "Domäne für öffentlich freigegebene Links, einschließlich http(s)://",
@@ -240,7 +243,7 @@
"storage_template_hash_verification_enabled_description": "Aktiviert die Hash-Verifizierung. Deaktiviere diese Option nur, wenn du dir über die damit verbundenen Auswirkungen im Klaren bist",
"storage_template_migration": "Migration von Speichervorlagen",
"storage_template_migration_description": "Diese Aufgabe wendet die aktuelle <link>{template}</link> auf zuvor hochgeladene Dateien an",
"storage_template_migration_info": "Vorlagenänderungen gelten nur für neue Dateien. Um die Vorlage rückwirkend auf bereits hochgeladene Assets anzuwenden, führe den <link>{job}</link> aus.",
"storage_template_migration_info": "Die Vorlage wird alle Dateierweiterungen in Kleinbuchstaben umwandeln. Vorlagenänderungen gelten nur für neue Dateien. Um die Vorlage rückwirkend auf bereits hochgeladene Assets anzuwenden, führe den <link>{job}</link> aus.",
"storage_template_migration_job": "Speichervorlagenmigrations-Aufgabe",
"storage_template_more_details": "Weitere Details zu dieser Funktion findest du unter <template-link>Speichervorlage</template-link> und dessen <implications-link>Implikationen</implications-link>",
"storage_template_onboarding_description": "Wenn aktiviert, sortiert diese Funktion Dateien automatisch basierend auf einer benutzerdefinierten Vorlage. Aufgrund von Stabilitätsproblemen ist die Funktion standardmäßig deaktiviert. Weitere Informationen findest du in der <link>Dokumentation</link>.",
@@ -290,7 +293,7 @@
"transcoding_constant_rate_factor_description": "Videoqualitätsstufe. Typische Werte sind 23 für H.264, 28 für HEVC, 31 für VP9 und 35 für AV1. Ein niedrigerer Wert ist besser, erzeugt aber größere Dateien.",
"transcoding_disabled_description": "Videos nicht transkodieren, dies kann die Wiedergabe auf manchen Geräten beeinträchtigen",
"transcoding_encoding_options": "Kodierungsoptionen",
"transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für kodierte Videos",
"transcoding_encoding_options_description": "Setze Codec, Auflösung, Qualität und andere Optionen für die kodierten Videos",
"transcoding_hardware_acceleration": "Hardware-Beschleunigung",
"transcoding_hardware_acceleration_description": "Experimentell; viel schneller, aber bei gleicher Bitrate mit geringerer Qualität",
"transcoding_hardware_decoding": "Hardware-Dekodierung",
@@ -299,7 +302,7 @@
"transcoding_max_b_frames": "Maximale B-Frames",
"transcoding_max_b_frames_description": "Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. Ist möglicherweise nicht mit der Hardware-Beschleunigung älterer Geräte kompatibel. 0 deaktiviert die B-Frames, während -1 diesen Wert automatisch setzt.",
"transcoding_max_bitrate": "Maximale Bitrate",
"transcoding_max_bitrate_description": "Die Festlegung einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600k für VP9 oder HEVC oder 4500k für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist.",
"transcoding_max_bitrate_description": "Die Festlegung einer maximalen Bitrate kann die Dateigrößen vorhersagbarer machen, ohne dass die Qualität darunter leidet. Bei 720p sind typische Werte 2600 kbit/s für VP9 oder HEVC oder 4500 kbit/s für H.264. Deaktiviert, wenn der Wert auf 0 gesetzt ist.",
"transcoding_max_keyframe_interval": "Maximales Keyframe-Intervall",
"transcoding_max_keyframe_interval_description": "Legt den maximalen Frame-Abstand zwischen Keyframes fest. Niedrigere Werte verschlechtern die Komprimierungseffizienz, verbessern aber die Suchzeiten und können die Qualität in Szenen mit schnellen Bewegungen verbessern. Bei 0 wird dieser Wert automatisch eingestellt.",
"transcoding_optimal_description": "Videos mit einer höheren Auflösung als der Zielauflösung oder in einem nicht akzeptierten Format",
@@ -313,7 +316,7 @@
"transcoding_reference_frames_description": "Die Anzahl der Bilder, auf die bei der Komprimierung eines bestimmten Bildes Bezug genommen wird. Höhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. 0 setzt diesen Wert automatisch.",
"transcoding_required_description": "Nur Videos in einem nicht akzeptierten Format",
"transcoding_settings": "Einstellungen für die Videotranskodierung",
"transcoding_settings_description": "Verwalten welche Videos transkodiert werden und wie diese verarbeitet werden",
"transcoding_settings_description": "Verwalten welche Videos transkodiert und wie diese verarbeitet werden",
"transcoding_target_resolution": "Ziel-Auflösung",
"transcoding_target_resolution_description": "Höhere Auflösungen können mehr Details erhalten, benötigen aber mehr Zeit für die Codierung, haben größere Dateigrößen und können die Reaktionszeit der Anwendung beeinträchtigen.",
"transcoding_temporal_aq": "Temporäre AQ",
@@ -391,6 +394,7 @@
"allow_edits": "Bearbeiten erlauben",
"allow_public_user_to_download": "Erlaube öffentlichen Benutzern, herunterzuladen",
"allow_public_user_to_upload": "Erlaube öffentlichen Benutzern, hochzuladen",
"alt_text_qr_code": "QR-Code Bild",
"anti_clockwise": "Gegen den Uhrzeigersinn",
"api_key": "API-Schlüssel",
"api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.",
@@ -406,17 +410,17 @@
"are_these_the_same_person": "Ist das dieselbe Person?",
"are_you_sure_to_do_this": "Bist du sicher, dass du das tun willst?",
"asset_added_to_album": "Zum Album hinzugefügt",
"asset_adding_to_album": "Hinzufügen zum Album...",
"asset_adding_to_album": "Hinzufügen zum Album",
"asset_description_updated": "Die Beschreibung der Datei wurde aktualisiert",
"asset_filename_is_offline": "Datei {filename} ist offline",
"asset_has_unassigned_faces": "Datei hat nicht zugewiesene Gesichter",
"asset_hashing": "Berechnung des Hashwerts...",
"asset_hashing": "Berechne Prüfsumme…",
"asset_offline": "Datei offline",
"asset_offline_description": "Diese externe Datei ist nicht mehr auf dem Datenträger vorhanden. Bitte wende dich an deinen Immich-Administrator, um Hilfe zu erhalten.",
"asset_skipped": "Übersprungen",
"asset_skipped_in_trash": "Im Papierkorb",
"asset_uploaded": "Hochgeladen",
"asset_uploading": "Hochladen...",
"asset_uploading": "Hochladen",
"assets": "Dateien",
"assets_added_count": "{count, plural, one {# Datei} other {# Dateien}} hinzugefügt",
"assets_added_to_album_count": "{count, plural, one {# Datei} other {# Dateien}} zum Album hinzugefügt",
@@ -481,6 +485,7 @@
"comments_are_disabled": "Kommentare sind deaktiviert",
"confirm": "Bestätigen",
"confirm_admin_password": "Administrator Passwort bestätigen",
"confirm_delete_face": "Bist du sicher dass du das Gesicht von {name} aus der Datei entfernen willst?",
"confirm_delete_shared_link": "Bist du sicher, dass du diesen geteilten Link löschen willst?",
"confirm_keep_this_delete_others": "Alle anderen Dateien im Stapel bis auf diese werden gelöscht. Bist du sicher, dass du fortfahren möchten?",
"confirm_password": "Passwort bestätigen",
@@ -533,6 +538,7 @@
"delete_album": "Album löschen",
"delete_api_key_prompt": "Bist du sicher, dass du diesen API-Schlüssel löschen willst?",
"delete_duplicates_confirmation": "Bist du sicher, dass du diese Duplikate endgültig löschen willst?",
"delete_face": "Gesicht löschen",
"delete_key": "Schlüssel löschen",
"delete_library": "Bibliothek löschen",
"delete_link": "Link löschen",
@@ -600,6 +606,7 @@
"enabled": "Aktiviert",
"end_date": "Enddatum",
"error": "Fehler",
"error_delete_face": "Fehler beim Löschen des Gesichts",
"error_loading_image": "Fehler beim Laden des Bildes",
"error_title": "Fehler - Etwas ist schief gelaufen",
"errors": {
@@ -687,7 +694,7 @@
"unable_to_load_liked_status": "Gewünschter Status kann nicht geladen werden",
"unable_to_log_out_all_devices": "Konnte nicht von allen Geräten abmelden",
"unable_to_log_out_device": "Konnte nicht vom Gerät abmelden",
"unable_to_login_with_oauth": "Konnte nicht mit OAuth anmelden",
"unable_to_login_with_oauth": "Anmeldung mit OAuth nicht möglich",
"unable_to_play_video": "Das Video kann nicht wiedergegeben werden",
"unable_to_reassign_assets_existing_person": "Kann Dateien nicht {name, select, null {einer vorhandenen Person} other {{name}}} zuweisen",
"unable_to_reassign_assets_new_person": "Dateien konnten nicht einer neuen Person zugeordnet werden",
@@ -766,8 +773,10 @@
"go_to_folder": "Gehe zu Ordner",
"go_to_search": "Zur Suche gehen",
"group_albums_by": "Alben gruppieren nach...",
"group_country": "Nach Land gruppieren",
"group_no": "Keine Gruppierung",
"group_owner": "Gruppierung nach Besitzer",
"group_places_by": "Orte gruppieren nach...",
"group_year": "Gruppierung nach Jahr",
"has_quota": "Kontingent",
"hi_user": "Hallo {name} ({email})",
@@ -800,6 +809,7 @@
"include_shared_albums": "Freigegebene Alben einbeziehen",
"include_shared_partner_assets": "Geteilte Partner-Dateien mit einbeziehen",
"individual_share": "Individuelle Freigabe",
"individual_shares": "Individuelles Teilen",
"info": "Info",
"interval": {
"day_at_onepm": "Täglich um 13:00 Uhr",
@@ -822,6 +832,7 @@
"latest_version": "Aktuellste Version",
"latitude": "Breitengrad",
"leave": "Verlassen",
"lens_model": "Objektivmodell",
"let_others_respond": "Antworten zulassen",
"level": "Level",
"library": "Bibliothek",
@@ -830,7 +841,7 @@
"like_deleted": "Like gelöscht",
"link_motion_video": "Bewegungsvideo verknüpfen",
"link_options": "Link-Optionen",
"link_to_oauth": "Link zu OAuth",
"link_to_oauth": "Mit OAuth verknüpfen",
"linked_oauth_account": "Verknüpftes OAuth-Konto",
"list": "Liste",
"loading": "Laden",
@@ -855,7 +866,7 @@
"manage_your_account": "Dein Konto verwalten",
"manage_your_api_keys": "Deine API-Schlüssel verwalten",
"manage_your_devices": "Deine eingeloggten Geräte verwalten",
"manage_your_oauth_connection": "Deine OAuth-Verbindung verwalten",
"manage_your_oauth_connection": "Deine OAuth-Verknüpfung verwalten",
"map": "Karte",
"map_marker_for_images": "Kartenmarkierung für Bilder, die in {city}, {country} aufgenommen wurden",
"map_marker_with_image": "Kartenmarkierung mit Bild",
@@ -880,6 +891,7 @@
"month": "Monat",
"more": "Mehr",
"moved_to_trash": "In den Papierkorb verschoben",
"mute_memories": "Erinnerungen stumm schalten",
"my_albums": "Meine Alben",
"name": "Name",
"name_or_nickname": "Name oder Nickname",
@@ -975,6 +987,7 @@
"permanently_deleted_asset": "Endgültig gelöschtes Objekt",
"permanently_deleted_assets_count": "{count, plural, one {# Datei} other {# Dateien}} endgültig gelöscht",
"person": "Person",
"person_birthdate": "Geboren am {date}",
"person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}",
"photo_shared_all_users": "Es sieht so aus, als hättest du deine Fotos mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.",
"photos": "Fotos",
@@ -984,6 +997,7 @@
"pick_a_location": "Wähle einen Ort",
"place": "Ort",
"places": "Orte",
"places_count": "{count, plural, one {{count, number} Ort} other {{count, number} Orte}}",
"play": "Abspielen",
"play_memories": "Erinnerungen abspielen",
"play_motion_photo": "Bewegte Bilder abspielen",
@@ -1065,12 +1079,16 @@
"remove_from_album": "Aus Album entfernen",
"remove_from_favorites": "Aus Favoriten entfernen",
"remove_from_shared_link": "Aus geteiltem Link entfernen",
"remove_memory": "Erinnerung entfernen",
"remove_photo_from_memory": "Foto aus dieser Erinnerung entfernen",
"remove_url": "URL entfernen",
"remove_user": "Nutzer entfernen",
"removed_api_key": "API-Schlüssel {name} wurde entfernt",
"removed_from_archive": "Aus dem Archiv entfernt",
"removed_from_favorites": "Aus den Favoriten entfernt",
"removed_from_favorites_count": "{count, plural, other {#}} aus den Favoriten entfernt",
"removed_memory": "Erinnerung entfernt",
"removed_photo_from_memory": "Foto aus Erinnerung entfernt",
"removed_tagged_assets": "Tag von {count, plural, one {# Datei} other {# Dateien}} entfernt",
"rename": "Umbenennen",
"repair": "Reparatur",
@@ -1079,6 +1097,7 @@
"repository": "Repository",
"require_password": "Passwort erforderlich",
"require_user_to_change_password_on_first_login": "Benutzer muss das Passwort beim ersten Login ändern",
"rescan": "Erneut scannen",
"reset": "Zurücksetzen",
"reset_password": "Passwort zurücksetzen",
"reset_people_visibility": "Sichtbarkeit von Personen zurücksetzen",
@@ -1107,18 +1126,22 @@
"search": "Suche",
"search_albums": "Album suchen",
"search_by_context": "Suche nach Kontext",
"search_by_description": "Nach Beschreibung suchen",
"search_by_description_example": "Wandern in Sapa",
"search_by_filename": "Suche nach Dateiname oder -erweiterung",
"search_by_filename_example": "z.B. IMG_1234.JPG oder PNG",
"search_camera_make": "Suche nach Kameramarke...",
"search_camera_model": "Suche nach Kameramodell...",
"search_city": "Suche nach Stadt...",
"search_country": "Suche nach Land...",
"search_for": "Suche nach",
"search_for_existing_person": "Suche nach vorhandener Person",
"search_no_people": "Keine Personen",
"search_no_people_named": "Keine Person mit dem Namen \"{name}\"",
"search_options": "Suchoptionen",
"search_people": "Suche nach Personen",
"search_places": "Suche nach Orten",
"search_rating": "Suche nach Bewertung...",
"search_settings": "Suche nach Einstellungen",
"search_state": "Suche nach Bundesland / Provinz...",
"search_tags": "Sache nach Tags...",
@@ -1128,6 +1151,7 @@
"searching_locales": "Suche nach Orten...",
"second": "Sekunde",
"see_all_people": "Alle Personen anzeigen",
"select": "Auswählen",
"select_album_cover": "Album-Cover auswählen",
"select_all": "Alles auswählen",
"select_all_duplicates": "Alle Duplikate auswählen",
@@ -1165,6 +1189,7 @@
"shared_from_partner": "Fotos von {partner}",
"shared_link_options": "Optionen für geteilten Link",
"shared_links": "Geteilte Links",
"shared_links_description": "Teile Fotos und Videos mit einem Link",
"shared_photos_and_videos_count": "{assetCount, plural, one {# geteiltes Foto oder Video.} other {# geteilte Fotos & Videos.}}",
"shared_with_partner": "Geteilt mit {partner}",
"sharing": "Geteiltes",
@@ -1187,6 +1212,7 @@
"show_person_options": "Personen-Optionen anzeigen",
"show_progress_bar": "Fortschrittsbalken anzeigen",
"show_search_options": "Suchoptionen anzeigen",
"show_shared_links": "Zeige geteilte Links",
"show_slideshow_transition": "Slideshow-Übergang anzeigen",
"show_supporter_badge": "Unterstützerabzeichen",
"show_supporter_badge_description": "Zeige Unterstützerabzeichen",
@@ -1240,6 +1266,7 @@
"tag_created": "Tag erstellt: {tag}",
"tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen",
"tag_not_found_question": "Kein Tag zu finden? <link>Erstelle einen neuen Tag.</link>",
"tag_people": "Personen taggen",
"tag_updated": "Tag aktualisiert: {tag}",
"tagged_assets": "{count, plural, one {# Datei} other {# Dateien}} getagged",
"tags": "Tags",
@@ -1274,11 +1301,13 @@
"unfavorite": "Entfavorisieren",
"unhide_person": "Person einblenden",
"unknown": "Unbekannt",
"unknown_country": "Unbekanntes Land",
"unknown_year": "Unbekanntes Jahr",
"unlimited": "Unlimitiert",
"unlink_motion_video": "Verknüpfung zum Bewegungsvideo aufheben",
"unlink_oauth": "OAuth entfernen",
"unlinked_oauth_account": "Nicht verknüpftes OAuth-Konto",
"unlinked_oauth_account": "OAuth-Konto entfernt",
"unmute_memories": "Stummschaltung für Erinnerungen aufheben",
"unnamed_album": "Unbenanntes Album",
"unnamed_album_delete_confirmation": "Bist du sicher, dass du dieses Album löschen willst?",
"unnamed_share": "Unbenannte Freigabe",
@@ -1332,6 +1361,7 @@
"view_all": "Alles anzeigen",
"view_all_users": "Alle Nutzer anzeigen",
"view_in_timeline": "In Zeitleiste anzeigen",
"view_link": "Link anzeigen",
"view_links": "Links anzeigen",
"view_name": "Ansicht",
"view_next_asset": "Nächste Datei anzeigen",

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